summaryrefslogtreecommitdiff
path: root/test/ruby
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby')
-rw-r--r--test/ruby/enc/test_case_comprehensive.rb2
-rw-r--r--test/ruby/enc/test_cesu8.rb4
-rw-r--r--test/ruby/enc/test_emoji_breaks.rb208
-rw-r--r--test/ruby/enc/test_grapheme_breaks.rb117
-rw-r--r--test/ruby/enc/test_regex_casefold.rb2
-rw-r--r--test/ruby/test_alias.rb39
-rw-r--r--test/ruby/test_argf.rb15
-rw-r--r--test/ruby/test_arity.rb44
-rw-r--r--test/ruby/test_array.rb132
-rw-r--r--test/ruby/test_assignment.rb108
-rw-r--r--test/ruby/test_ast.rb621
-rw-r--r--test/ruby/test_autoload.rb104
-rw-r--r--test/ruby/test_backtrace.rb29
-rw-r--r--test/ruby/test_bignum.rb13
-rw-r--r--test/ruby/test_call.rb7
-rw-r--r--test/ruby/test_class.rb88
-rw-r--r--test/ruby/test_clone.rb53
-rw-r--r--test/ruby/test_complex.rb119
-rw-r--r--test/ruby/test_complex2.rb2
-rw-r--r--test/ruby/test_complexrational.rb4
-rw-r--r--test/ruby/test_data.rb249
-rw-r--r--test/ruby/test_default_gems.rb2
-rw-r--r--test/ruby/test_dir.rb81
-rw-r--r--test/ruby/test_dup.rb110
-rw-r--r--test/ruby/test_encoding.rb21
-rw-r--r--test/ruby/test_enum.rb25
-rw-r--r--test/ruby/test_enumerator.rb102
-rw-r--r--test/ruby/test_env.rb882
-rw-r--r--test/ruby/test_eval.rb70
-rw-r--r--test/ruby/test_exception.rb165
-rw-r--r--test/ruby/test_fiber.rb17
-rw-r--r--test/ruby/test_file.rb54
-rw-r--r--test/ruby/test_file_exhaustive.rb84
-rw-r--r--test/ruby/test_float.rb21
-rw-r--r--test/ruby/test_frozen.rb30
-rw-r--r--test/ruby/test_gc.rb151
-rw-r--r--test/ruby/test_gc_compact.rb325
-rw-r--r--test/ruby/test_hash.rb115
-rw-r--r--test/ruby/test_inlinecache.rb2
-rw-r--r--test/ruby/test_integer.rb67
-rw-r--r--test/ruby/test_io.rb185
-rw-r--r--test/ruby/test_io_buffer.rb501
-rw-r--r--test/ruby/test_io_m17n.rb10
-rw-r--r--test/ruby/test_io_timeout.rb58
-rw-r--r--test/ruby/test_iseq.rb162
-rw-r--r--test/ruby/test_jit_debug.rb17
-rw-r--r--test/ruby/test_keyword.rb263
-rw-r--r--test/ruby/test_lazy_enumerator.rb20
-rw-r--r--test/ruby/test_literal.rb2
-rw-r--r--test/ruby/test_m17n.rb57
-rw-r--r--test/ruby/test_marshal.rb32
-rw-r--r--test/ruby/test_method.rb203
-rw-r--r--test/ruby/test_mjit.rb (renamed from test/ruby/test_jit.rb)410
-rw-r--r--test/ruby/test_mjit_debug.rb17
-rw-r--r--test/ruby/test_module.rb207
-rw-r--r--test/ruby/test_numeric.rb3
-rw-r--r--test/ruby/test_object.rb31
-rw-r--r--test/ruby/test_objectspace.rb7
-rw-r--r--test/ruby/test_optimization.rb46
-rw-r--r--test/ruby/test_pack.rb94
-rw-r--r--test/ruby/test_parse.rb74
-rw-r--r--test/ruby/test_pattern_matching.rb50
-rw-r--r--test/ruby/test_proc.rb154
-rw-r--r--test/ruby/test_process.rb251
-rw-r--r--test/ruby/test_rand.rb14
-rw-r--r--test/ruby/test_random_formatter.rb123
-rw-r--r--test/ruby/test_range.rb68
-rw-r--r--test/ruby/test_rational.rb2
-rw-r--r--test/ruby/test_refinement.rb243
-rw-r--r--test/ruby/test_regexp.rb429
-rw-r--r--test/ruby/test_require.rb113
-rw-r--r--test/ruby/test_require_lib.rb2
-rw-r--r--test/ruby/test_rubyoptions.rb86
-rw-r--r--test/ruby/test_rubyvm.rb10
-rw-r--r--test/ruby/test_rubyvm_jit.rb91
-rw-r--r--test/ruby/test_rubyvm_mjit.rb105
-rw-r--r--test/ruby/test_settracefunc.rb495
-rw-r--r--test/ruby/test_shapes.rb441
-rw-r--r--test/ruby/test_signal.rb5
-rw-r--r--test/ruby/test_sprintf.rb32
-rw-r--r--test/ruby/test_string.rb818
-rw-r--r--test/ruby/test_struct.rb61
-rw-r--r--test/ruby/test_super.rb37
-rw-r--r--test/ruby/test_symbol.rb13
-rw-r--r--test/ruby/test_syntax.rb206
-rw-r--r--test/ruby/test_thread.rb111
-rw-r--r--test/ruby/test_thread_queue.rb59
-rw-r--r--test/ruby/test_time.rb174
-rw-r--r--test/ruby/test_time_tz.rb38
-rw-r--r--test/ruby/test_transcode.rb14
-rw-r--r--test/ruby/test_undef.rb16
-rw-r--r--test/ruby/test_variable.rb25
-rw-r--r--test/ruby/test_vm_dump.rb2
-rw-r--r--test/ruby/test_weakmap.rb57
-rw-r--r--test/ruby/test_yjit.rb853
-rw-r--r--test/ruby/test_yjit_exit_locations.rb110
96 files changed, 10306 insertions, 1685 deletions
diff --git a/test/ruby/enc/test_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb
index bde47017a2..bc57d57ee4 100644
--- a/test/ruby/enc/test_case_comprehensive.rb
+++ b/test/ruby/enc/test_case_comprehensive.rb
@@ -24,7 +24,7 @@ class TestComprehensiveCaseMapping < Test::Unit::TestCase
def test_data_files_available
unless TestComprehensiveCaseMapping.data_files_available?
- skip "Unicode data files not available in #{UNICODE_DATA_PATH}."
+ omit "Unicode data files not available in #{UNICODE_DATA_PATH}."
end
end
end
diff --git a/test/ruby/enc/test_cesu8.rb b/test/ruby/enc/test_cesu8.rb
index d9debe76cd..68a08389ea 100644
--- a/test/ruby/enc/test_cesu8.rb
+++ b/test/ruby/enc/test_cesu8.rb
@@ -106,4 +106,8 @@ EOT
assert_equal chr, ord.chr("cesu-8")
end
end
+
+ def test_cesu8_left_adjust_char_head
+ assert_equal("", "\u{10000}".encode("cesu-8").chop)
+ end
end
diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb
index cdde4da9bf..3e2999d61e 100644
--- a/test/ruby/enc/test_emoji_breaks.rb
+++ b/test/ruby/enc/test_emoji_breaks.rb
@@ -4,50 +4,47 @@
require "test/unit"
class TestEmojiBreaks < Test::Unit::TestCase
-end
-
-class TestEmojiBreaks::BreakTest
- attr_reader :string, :comment, :filename, :line_number, :type, :shortname
-
- def initialize(filename, line_number, data, comment='')
- @filename = filename
- @line_number = line_number
- @comment = comment.gsub(/\s+/, ' ').strip
- if filename=='emoji-test' or filename=='emoji-variation-sequences'
- codes, @type = data.split(/\s*;\s*/)
- @shortname = ''
- else
- codes, @type, @shortname = data.split(/\s*;\s*/)
+ class BreakTest
+ attr_reader :string, :comment, :filename, :line_number, :type, :shortname
+
+ def initialize(filename, line_number, data, comment='')
+ @filename = filename
+ @line_number = line_number
+ @comment = comment.gsub(/\s+/, ' ').strip
+ if filename=='emoji-test' or filename=='emoji-variation-sequences'
+ codes, @type = data.split(/\s*;\s*/)
+ @shortname = ''
+ else
+ codes, @type, @shortname = data.split(/\s*;\s*/)
+ end
+ @type = @type.gsub(/\s+/, ' ').strip
+ @shortname = @shortname.gsub(/\s+/, ' ').strip
+ @string = codes.split(/\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
- @type = @type.gsub(/\s+/, ' ').strip
- @shortname = @shortname.gsub(/\s+/, ' ').strip
- @string = codes.split(/\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
-end
-class TestEmojiBreaks::BreakFile
- attr_reader :basename, :fullname, :version
- FILES = []
+ class BreakFile
+ attr_reader :basename, :fullname, :version
+ FILES = []
- def initialize(basename, path, version)
- @basename = basename
- @fullname = "#{path}/#{basename}.txt" # File.expand_path(path + version, __dir__)
- @version = version
- FILES << self
- end
+ def initialize(basename, path, version)
+ @basename = basename
+ @fullname = "#{path}/#{basename}.txt" # File.expand_path(path + version, __dir__)
+ @version = version
+ FILES << self
+ end
- def self.files
- FILES
+ def self.files
+ FILES
+ end
end
-end
-class TestEmojiBreaks < Test::Unit::TestCase
UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION']
UNICODE_DATA_PATH = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}/ucd/emoji", __dir__)
EMOJI_VERSION = RbConfig::CONFIG['UNICODE_EMOJI_VERSION']
@@ -56,7 +53,7 @@ class TestEmojiBreaks < Test::Unit::TestCase
EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-zwj-sequences].map do |basename|
BreakFile.new(basename, EMOJI_DATA_PATH, EMOJI_VERSION)
end
- UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, UNICODE_VERSION[0..-3]) # [0..-3] deals with a versioning mismatch problem in Unicode
+ UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, UNICODE_VERSION)
EMOJI_DATA_FILES << UNICODE_DATA_FILE
def self.data_files_available?
@@ -68,83 +65,90 @@ class TestEmojiBreaks < Test::Unit::TestCase
def test_data_files_available
assert_equal 4, EMOJI_DATA_FILES.size # debugging test
unless TestEmojiBreaks.data_files_available?
- skip "Emoji data files not available in #{EMOJI_DATA_PATH}."
+ omit "Emoji data files not available in #{EMOJI_DATA_PATH}."
end
end
-end
-TestEmojiBreaks.data_files_available? and class TestEmojiBreaks
- def read_data
- tests = []
- EMOJI_DATA_FILES.each do |file|
- version_mismatch = true
- file_tests = []
- IO.foreach(file.fullname, encoding: Encoding::UTF_8) do |line|
- line.chomp!
- raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt" if $.==1 and not line=="# #{file.basename}.txt"
- version_mismatch = false if line =~ /^# Version: #{file.version}/
- next if line.match?(/\A(#|\z)/)
- if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028)
- range_start = $1.to_i(16)
- range_end = $2.to_i(16)
- rest = $3
- (range_start..range_end).each do |code_point|
- file_tests << BreakTest.new(file.basename, $., *(code_point.to_s(16)+rest).split('#', 2))
+ if data_files_available?
+ def read_data
+ tests = []
+ EMOJI_DATA_FILES.each do |file|
+ version_mismatch = true
+ file_tests = []
+ IO.foreach(file.fullname, encoding: Encoding::UTF_8) do |line|
+ line.chomp!
+ if $.==1
+ if line=="# #{file.basename}-#{file.version}.txt"
+ version_mismatch = false
+ elsif line!="# #{file.basename}.txt"
+ raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt"
+ end
+ end
+ version_mismatch = false if line =~ /^# Version: #{file.version}/ # 13.0 and older
+ version_mismatch = false if line =~ /^# Used with Emoji Version #{EMOJI_VERSION}/ # 14.0 and newer
+ next if line.match?(/\A(#|\z)/)
+ if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028)
+ range_start = $1.to_i(16)
+ range_end = $2.to_i(16)
+ rest = $3
+ (range_start..range_end).each do |code_point|
+ file_tests << BreakTest.new(file.basename, $., *(code_point.to_s(16)+rest).split('#', 2))
+ end
+ else
+ file_tests << BreakTest.new(file.basename, $., *line.split('#', 2))
end
- else
- file_tests << BreakTest.new(file.basename, $., *line.split('#', 2))
end
+ raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch
+ tests += file_tests
end
- raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch
- tests += file_tests
+ tests
end
- tests
- end
-
- def all_tests
- @@tests ||= read_data
- rescue Errno::ENOENT
- @@tests ||= []
- end
- def test_single_emoji
- all_tests.each do |test|
- expected = [test.string]
- actual = test.string.each_grapheme_cluster.to_a
- assert_equal expected, actual,
- "file: #{test.filename}, line #{test.line_number}, " +
- "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ def all_tests
+ @@tests ||= read_data
+ rescue Errno::ENOENT
+ @@tests ||= []
end
- end
- def test_embedded_emoji
- all_tests.each do |test|
- expected = ["\t", test.string, "\t"]
- actual = "\t#{test.string}\t".each_grapheme_cluster.to_a
- assert_equal expected, actual,
- "file: #{test.filename}, line #{test.line_number}, " +
- "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ def test_single_emoji
+ all_tests.each do |test|
+ expected = [test.string]
+ actual = test.string.each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "file: #{test.filename}, line #{test.line_number}, " +
+ "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ end
end
- end
- # test some pseodorandom combinations of emoji
- def test_mixed_emoji
- srand 0
- length = all_tests.length
- step = 503 # use a prime number
- all_tests.each do |test1|
- start = rand step
- start.step(by: step, to: length-1) do |t2|
- test2 = all_tests[t2]
- # exclude skin tones, because they glue to previous grapheme clusters
- next if (0x1F3FB..0x1F3FF).include? test2.string.ord
- expected = [test1.string, test2.string]
- actual = (test1.string+test2.string).each_grapheme_cluster.to_a
+ def test_embedded_emoji
+ all_tests.each do |test|
+ expected = ["\t", test.string, "\t"]
+ actual = "\t#{test.string}\t".each_grapheme_cluster.to_a
assert_equal expected, actual,
- "file1: #{test1.filename}, line1 #{test1.line_number}, " +
- "file2: #{test2.filename}, line2 #{test2.line_number},\n" +
- "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" +
- "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}"
+ "file: #{test.filename}, line #{test.line_number}, " +
+ "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}"
+ end
+ end
+
+ # test some pseodorandom combinations of emoji
+ def test_mixed_emoji
+ srand 0
+ length = all_tests.length
+ step = 503 # use a prime number
+ all_tests.each do |test1|
+ start = rand step
+ start.step(by: step, to: length-1) do |t2|
+ test2 = all_tests[t2]
+ # exclude skin tones, because they glue to previous grapheme clusters
+ next if (0x1F3FB..0x1F3FF).include? test2.string.ord
+ expected = [test1.string, test2.string]
+ actual = (test1.string+test2.string).each_grapheme_cluster.to_a
+ assert_equal expected, actual,
+ "file1: #{test1.filename}, line1 #{test1.line_number}, " +
+ "file2: #{test2.filename}, line2 #{test2.line_number},\n" +
+ "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" +
+ "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}"
+ end
end
end
end
diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb
index 2d210946a9..f4e3c93b47 100644
--- a/test/ruby/enc/test_grapheme_breaks.rb
+++ b/test/ruby/enc/test_grapheme_breaks.rb
@@ -4,31 +4,28 @@
require "test/unit"
class TestGraphemeBreaksFromFile < Test::Unit::TestCase
-end
-
-class TestGraphemeBreaksFromFile::BreakTest
- attr_reader :clusters, :string, :comment, :line_number
+ 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
+ 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
-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
@@ -40,56 +37,56 @@ class TestGraphemeBreaksFromFile < Test::Unit::TestCase
def test_data_files_available
unless TestGraphemeBreaksFromFile.file_available?
- skip "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}."
+ omit "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"
+ if file_available?
+ 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
- next if /\A#/.match? line
- tests << BreakTest.new($., *line.chomp.split('#')) rescue 'whatever'
+ tests
end
- tests
- end
- def all_tests
- @@tests ||= read_data
- rescue Errno::ENOENT
- @@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}"
+ 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
- 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
+ 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}, removals: #{removals}, expected '#{expected}', " +
+ "line #{test.line_number}, after last removal, 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/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb
index ec5dc7f220..eaabbc58a2 100644
--- a/test/ruby/enc/test_regex_casefold.rb
+++ b/test/ruby/enc/test_regex_casefold.rb
@@ -39,7 +39,7 @@ class TestCaseFold < Test::Unit::TestCase
@@tests ||= read_tests
rescue Errno::ENOENT => e
@@tests ||= []
- skip e.message
+ omit e.message
end
def self.generate_test_casefold(encoding)
diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb
index 271d552bf5..0d33cb993c 100644
--- a/test/ruby/test_alias.rb
+++ b/test/ruby/test_alias.rb
@@ -253,4 +253,43 @@ class TestAlias < Test::Unit::TestCase
assert_equal(:foo, k.instance_method(:bar).original_name)
assert_equal(:foo, name)
end
+
+ def test_alias_suppressing_redefinition
+ assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class A
+ def foo; end
+ alias foo foo
+ def foo; end
+ end
+ end;
+ end
+
+ class C2
+ public :system
+ alias_method :bar, :system
+ alias_method :system, :bar
+ end
+
+ def test_zsuper_alias_visibility
+ assert(C2.new.respond_to?(:system))
+ end
+
+ def test_alias_memory_leak
+ assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true)
+ begin;
+ class A
+ 500.times do
+ 1000.times do |i|
+ define_method(:"foo_#{i}") {}
+
+ alias :"foo_#{i}" :"foo_#{i}"
+
+ remove_method :"foo_#{i}"
+ end
+ GC.start
+ end
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb
index e3bd1cd075..12f7d6485a 100644
--- a/test/ruby/test_argf.rb
+++ b/test/ruby/test_argf.rb
@@ -143,6 +143,17 @@ class TestArgf < Test::Unit::TestCase
};
end
+ def test_lineno_after_shebang
+ expected = %w"1 1 1 2 2 2 3 3 1 4 4 2"
+ assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected)
+ #!/usr/bin/env ruby
+ {#
+ ARGF.each do |line|
+ puts [$., ARGF.lineno, ARGF.file.lineno]
+ end
+ };
+ end
+
def test_new_lineno_each
f = ARGF.class.new(@t1.path, @t2.path, @t3.path)
result = []
@@ -257,13 +268,13 @@ class TestArgf < Test::Unit::TestCase
def test_inplace_nonascii
ext = Encoding.default_external or
- skip "no default external encoding"
+ omit "no default external encoding"
t = nil
["\u{3042}", "\u{e9}"].any? do |n|
t = make_tempfile(n.encode(ext))
rescue Encoding::UndefinedConversionError
end
- t or skip "no name to test"
+ t or omit "no name to test"
assert_in_out_err(["-i.bak", "-", t.path],
"#{<<~"{#"}\n#{<<~'};'}")
{#
diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb
index b98248f603..d26338e0aa 100644
--- a/test/ruby/test_arity.rb
+++ b/test/ruby/test_arity.rb
@@ -2,7 +2,7 @@
require 'test/unit'
class TestArity < Test::Unit::TestCase
- def err_mess(method_proc = nil, argc = 0)
+ def assert_arity(expected, method_proc = nil, argc = 0)
args = (1..argc).to_a
assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b(\d+)\b.* (\d\S*?)\)/) do
case method_proc
@@ -14,7 +14,7 @@ class TestArity < Test::Unit::TestCase
method_proc.call(*args)
end
end
- [$1, $2]
+ assert_equal expected, [$1, $2]
end
def a
@@ -36,22 +36,22 @@ class TestArity < Test::Unit::TestCase
end
def test_method_err_mess
- assert_equal %w[1 0], err_mess(:a, 1)
- assert_equal %w[10 7..9], err_mess(:b, 10)
- assert_equal %w[2 3+], err_mess(:c, 2)
- assert_equal %w[2 1], err_mess(:d, 2)
- assert_equal %w[0 1], err_mess(:d, 0)
- assert_equal %w[2 1], err_mess(:e, 2)
- assert_equal %w[0 1], err_mess(:e, 0)
- assert_equal %w[1 2+], err_mess(:f, 1)
+ assert_arity(%w[1 0], :a, 1)
+ assert_arity(%w[10 7..9], :b, 10)
+ assert_arity(%w[2 3+], :c, 2)
+ assert_arity(%w[2 1], :d, 2)
+ assert_arity(%w[0 1], :d, 0)
+ assert_arity(%w[2 1], :e, 2)
+ assert_arity(%w[0 1], :e, 0)
+ assert_arity(%w[1 2+], :f, 1)
end
def test_proc_err_mess
- assert_equal %w[0 1..2], err_mess(->(b, c=42){}, 0)
- assert_equal %w[1 2+], err_mess(->(a, b, c=42, *d){}, 1)
- assert_equal %w[3 4+], err_mess(->(a, b, *c, d, e){}, 3)
- assert_equal %w[3 1..2], err_mess(->(b, c=42){}, 3)
- assert_equal %w[1 0], err_mess(->(&block){}, 1)
+ assert_arity(%w[0 1..2], ->(b, c=42){}, 0)
+ assert_arity(%w[1 2+], ->(a, b, c=42, *d){}, 1)
+ assert_arity(%w[3 4+], ->(a, b, *c, d, e){}, 3)
+ assert_arity(%w[3 1..2], ->(b, c=42){}, 3)
+ assert_arity(%w[1 0], ->(&block){}, 1)
# Double checking:
p = Proc.new{|b, c=42| :ok}
assert_equal :ok, p.call(1, 2, 3)
@@ -59,12 +59,12 @@ class TestArity < Test::Unit::TestCase
end
def test_message_change_issue_6085
- assert_equal %w[3 1..2], err_mess{ SignalException.new(1, "", nil) }
- assert_equal %w[1 0], err_mess{ Hash.new(1){} }
- assert_equal %w[3 1..2], err_mess{ Module.send :define_method, 1, 2, 3 }
- assert_equal %w[1 2], err_mess{ "".sub!(//) }
- assert_equal %w[0 1..2], err_mess{ "".sub!{} }
- assert_equal %w[0 1+], err_mess{ exec }
- assert_equal %w[0 1+], err_mess{ Struct.new }
+ assert_arity(%w[3 1..2]) { SignalException.new(1, "", nil) }
+ assert_arity(%w[1 0]) { Hash.new(1){} }
+ assert_arity(%w[3 1..2]) { Module.send :define_method, 1, 2, 3 }
+ assert_arity(%w[1 2]) { "".sub!(//) }
+ assert_arity(%w[0 1..2]) { "".sub!{} }
+ assert_arity(%w[0 1+]) { exec }
+ assert_arity(%w[0 1+]) { Struct.new }
end
end
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index a97a9c2558..6c0db0832b 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -1294,6 +1294,12 @@ class TestArray < Test::Unit::TestCase
=end
end
+ def test_pack_with_buffer
+ n = [ 65, 66, 67 ]
+ str = "a" * 100
+ assert_equal("aaaABC", n.pack("@3ccc", buffer: str.dup), "[Bug #19116]")
+ end
+
def test_pop
a = @cls[ 'cat', 'dog' ]
assert_equal('dog', a.pop)
@@ -1439,6 +1445,14 @@ class TestArray < Test::Unit::TestCase
assert_raise(FrozenError) { fa.replace(42) }
end
+ def test_replace_wb_variable_width_alloc
+ small_embed = []
+ 4.times { GC.start } # age small_embed
+ large_embed = [1, 2, 3, 4, 5, Array.new] # new young object
+ small_embed.replace(large_embed) # adds old to young reference
+ GC.verify_internal_consistency
+ end
+
def test_reverse
a = @cls[*%w( dog cat bee ant )]
assert_equal(@cls[*%w(ant bee cat dog)], a.reverse)
@@ -1572,6 +1586,96 @@ class TestArray < Test::Unit::TestCase
assert_equal_instance(a.values_at(*idx), a.slice((3..90)%2))
idx = 90.step(3, -2).to_a
assert_equal_instance(a.values_at(*idx), a.slice((90 .. 3)% -2))
+
+ a = [0, 1, 2, 3, 4, 5]
+ assert_equal([2, 1, 0], a.slice((2..).step(-1)))
+ assert_equal([2, 0], a.slice((2..).step(-2)))
+ assert_equal([2], a.slice((2..).step(-3)))
+ assert_equal([2], a.slice((2..).step(-4)))
+
+ assert_equal([3, 2, 1, 0], a.slice((-3..).step(-1)))
+ assert_equal([3, 1], a.slice((-3..).step(-2)))
+ assert_equal([3, 0], a.slice((-3..).step(-3)))
+ assert_equal([3], a.slice((-3..).step(-4)))
+ assert_equal([3], a.slice((-3..).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1, 0], a.slice((..0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..0).step(-2)))
+ assert_equal([5, 2], a.slice((..0).step(-3)))
+ assert_equal([5, 1], a.slice((..0).step(-4)))
+ assert_equal([5, 0], a.slice((..0).step(-5)))
+ assert_equal([5], a.slice((..0).step(-6)))
+ assert_equal([5], a.slice((..0).step(-7)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((...0).step(-1)))
+ assert_equal([5, 3, 1], a.slice((...0).step(-2)))
+ assert_equal([5, 2], a.slice((...0).step(-3)))
+ assert_equal([5, 1], a.slice((...0).step(-4)))
+ assert_equal([5], a.slice((...0).step(-5)))
+ assert_equal([5], a.slice((...0).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...1).step(-1)))
+ assert_equal([5, 3], a.slice((...1).step(-2)))
+ assert_equal([5, 2], a.slice((...1).step(-3)))
+ assert_equal([5], a.slice((...1).step(-4)))
+ assert_equal([5], a.slice((...1).step(-5)))
+
+ assert_equal([5, 4, 3, 2, 1], a.slice((..-5).step(-1)))
+ assert_equal([5, 3, 1], a.slice((..-5).step(-2)))
+ assert_equal([5, 2], a.slice((..-5).step(-3)))
+ assert_equal([5, 1], a.slice((..-5).step(-4)))
+ assert_equal([5], a.slice((..-5).step(-5)))
+ assert_equal([5], a.slice((..-5).step(-6)))
+
+ assert_equal([5, 4, 3, 2], a.slice((...-5).step(-1)))
+ assert_equal([5, 3], a.slice((...-5).step(-2)))
+ assert_equal([5, 2], a.slice((...-5).step(-3)))
+ assert_equal([5], a.slice((...-5).step(-4)))
+ assert_equal([5], a.slice((...-5).step(-5)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..1).step(-1)))
+ assert_equal([4, 2], a.slice((4..1).step(-2)))
+ assert_equal([4, 1], a.slice((4..1).step(-3)))
+ assert_equal([4], a.slice((4..1).step(-4)))
+ assert_equal([4], a.slice((4..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...1).step(-1)))
+ assert_equal([4, 2], a.slice((4...1).step(-2)))
+ assert_equal([4], a.slice((4...1).step(-3)))
+ assert_equal([4], a.slice((4...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..1).step(-1)))
+ assert_equal([4, 2], a.slice((-2..1).step(-2)))
+ assert_equal([4, 1], a.slice((-2..1).step(-3)))
+ assert_equal([4], a.slice((-2..1).step(-4)))
+ assert_equal([4], a.slice((-2..1).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...1).step(-1)))
+ assert_equal([4, 2], a.slice((-2...1).step(-2)))
+ assert_equal([4], a.slice((-2...1).step(-3)))
+ assert_equal([4], a.slice((-2...1).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((4..-5).step(-1)))
+ assert_equal([4, 2], a.slice((4..-5).step(-2)))
+ assert_equal([4, 1], a.slice((4..-5).step(-3)))
+ assert_equal([4], a.slice((4..-5).step(-4)))
+ assert_equal([4], a.slice((4..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((4...-5).step(-1)))
+ assert_equal([4, 2], a.slice((4...-5).step(-2)))
+ assert_equal([4], a.slice((4...-5).step(-3)))
+ assert_equal([4], a.slice((4...-5).step(-4)))
+
+ assert_equal([4, 3, 2, 1], a.slice((-2..-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2..-5).step(-2)))
+ assert_equal([4, 1], a.slice((-2..-5).step(-3)))
+ assert_equal([4], a.slice((-2..-5).step(-4)))
+ assert_equal([4], a.slice((-2..-5).step(-5)))
+
+ assert_equal([4, 3, 2], a.slice((-2...-5).step(-1)))
+ assert_equal([4, 2], a.slice((-2...-5).step(-2)))
+ assert_equal([4], a.slice((-2...-5).step(-3)))
+ assert_equal([4], a.slice((-2...-5).step(-4)))
end
def test_slice_out_of_range
@@ -2882,7 +2986,9 @@ class TestArray < Test::Unit::TestCase
assert_raise(RangeError) {
[*0..2].shuffle(random: gen)
}
+ end
+ def test_shuffle_random_clobbering
ary = (0...10000).to_a
gen = proc do
ary.replace([])
@@ -2892,7 +2998,9 @@ class TestArray < Test::Unit::TestCase
alias rand call
end
assert_raise(RuntimeError) {ary.shuffle!(random: gen)}
+ end
+ def test_shuffle_random_zero
zero = Object.new
def zero.to_int
0
@@ -2905,7 +3013,10 @@ class TestArray < Test::Unit::TestCase
end
ary = (0...10000).to_a
assert_equal(ary.rotate, ary.shuffle(random: gen_to_int))
+ end
+ def test_shuffle_random_invalid_generator
+ ary = (0...10).to_a
assert_raise(NoMethodError) {
ary.shuffle(random: Object.new)
}
@@ -2922,7 +3033,9 @@ class TestArray < Test::Unit::TestCase
assert_include([0, 1, 2], sample)
}
end
+ end
+ def test_sample_statistics
srand(0)
a = (1..18).to_a
(0..20).each do |n|
@@ -2939,9 +3052,13 @@ class TestArray < Test::Unit::TestCase
end
assert_operator(h.values.min * 2, :>=, h.values.max) if n != 0
end
+ end
+ def test_sample_invalid_argument
assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)}
+ end
+ def test_sample_random_srand0
gen = Random.new(0)
srand(0)
a = (1..18).to_a
@@ -2950,13 +3067,15 @@ class TestArray < Test::Unit::TestCase
assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}")
end
end
+ end
+ def test_sample_unknown_keyword
assert_raise_with_message(ArgumentError, /unknown keyword/) do
[0, 1, 2].sample(xawqij: "a")
end
end
- def test_sample_random
+ def test_sample_random_generator
ary = (0...10000).to_a
assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)}
gen0 = proc do |max|
@@ -2999,7 +3118,9 @@ class TestArray < Test::Unit::TestCase
assert_equal([5000, 0, 5001, 2, 5002, 4, 5003, 6, 5004, 8, 5005], ary.sample(11, random: gen0))
ary.sample(11, random: gen1) # implementation detail, may change in the future
assert_equal([], ary)
+ end
+ def test_sample_random_generator_half
half = Object.new
def half.to_int
5000
@@ -3012,7 +3133,10 @@ class TestArray < Test::Unit::TestCase
end
ary = (0...10000).to_a
assert_equal(5000, ary.sample(random: gen_to_int))
+ end
+ def test_sample_random_invalid_generator
+ ary = (0..10).to_a
assert_raise(NoMethodError) {
ary.sample(random: Object.new)
}
@@ -3212,6 +3336,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch {|x| (-1) * (2**100) })
+ assert_equal(4, a.bsearch {|x| (4 - x).to_r })
+
assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -3247,6 +3373,8 @@ class TestArray < Test::Unit::TestCase
assert_equal(nil, a.bsearch_index {|x| 1 * (2**100) })
assert_equal(nil, a.bsearch_index {|x| (-1) * (2**100) })
+ assert_equal(1, a.bsearch_index {|x| (4 - x).to_r })
+
assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first })
end
@@ -3270,7 +3398,7 @@ class TestArray < Test::Unit::TestCase
end
EOS
rescue Timeout::Error => e
- skip e.message
+ omit e.message
end
end
diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb
index 41e8bffe82..3a8dafb7f0 100644
--- a/test/ruby/test_assignment.rb
+++ b/test/ruby/test_assignment.rb
@@ -139,6 +139,104 @@ class TestAssignment < Test::Unit::TestCase
order.clear
end
+ def test_massign_const_order
+ order = []
+
+ test_mod_class = Class.new(Module) do
+ define_method(:x1){order << :x1; self}
+ define_method(:y1){order << :y1; self}
+ define_method(:x2){order << :x2; self}
+ define_method(:x3){order << :x3; self}
+ define_method(:x4){order << :x4; self}
+ define_method(:[]){|*args| order << [:[], *args]; self}
+ define_method(:r1){order << :r1; :r1}
+ define_method(:r2){order << :r2; :r2}
+
+ define_method(:constant_values) do
+ h = {}
+ constants.each do |sym|
+ h[sym] = const_get(sym)
+ end
+ h
+ end
+
+ define_singleton_method(:run) do |code|
+ m = new
+ m.instance_eval(code)
+ ret = [order.dup, m.constant_values]
+ order.clear
+ ret
+ end
+ end
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, x2[1, 2, 3]::B, self[4]::C = r1, 6, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>6, :C=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, self[4]::C = r1, 6, 7, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C = r1, 6, 7, r2"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants)
+
+
+ ord, constants = test_mod_class.run(
+ "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C, x4::D = r1, 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2, :D=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x2::B), _a = [r1, r2], 7"
+ )
+ assert_equal([:x1, :y1, :x2, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>:r2}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C = [r1, 5], 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7, :r2, 8]}, constants)
+
+ ord, constants = test_mod_class.run(
+ "*x2[1, 2, 3]::A, (x3[4]::B, x4::C) = 6, 7, [r2, 8]"
+ )
+ assert_equal([:x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r2], ord)
+ assert_equal({:A=>[6, 7], :B=>:r2, :C=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, x3[4]::D, x4::E = [r1, 5], 6, 7, r2, 8"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, (x3[4]::D, x4::E) = [r1, 5], 6, 7, [r2, 8]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants)
+
+ ord, constants = test_mod_class.run(
+ "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((*x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]"
+ )
+ assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord)
+ assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>[:r2], :E=>8}, constants)
+ end
+
def test_massign_splat
a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c])
a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c])
@@ -619,6 +717,16 @@ class TestAssignment < Test::Unit::TestCase
result = eval("if (a, b = MyObj.new); [a, b]; end", nil, __FILE__, __LINE__)
assert_equal [[1,2],[3,4]], result
end
+
+ def test_const_assign_order
+ assert_raise(RuntimeError) do
+ eval('raise("recv")::C = raise(ArgumentError, "bar")')
+ end
+
+ assert_raise(RuntimeError) do
+ eval('m = 1; m::C = raise("bar")')
+ end
+ end
end
require_relative 'sentence'
diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb
index 953c8435c3..e43dac28b3 100644
--- a/test/ruby/test_ast.rb
+++ b/test/ruby/test_ast.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: false
require 'test/unit'
require 'tempfile'
+require 'pp'
class RubyVM
module AbstractSyntaxTree
@@ -131,6 +132,34 @@ class TestAst < Test::Unit::TestCase
end
end
+ Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
+ define_method("test_all_tokens:#{path}") do
+ node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
+ tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
+ tokens_bytes = tokens.map { _1[2]}.join.bytes
+ source_bytes = File.read("#{SRCDIR}/#{path}").bytes
+
+ assert_equal(source_bytes, tokens_bytes)
+
+ (tokens.count - 1).times do |i|
+ token_0 = tokens[i]
+ token_1 = tokens[i + 1]
+ end_pos = token_0.last[2..3]
+ beg_pos = token_1.last[0..1]
+
+ if end_pos[0] == beg_pos[0]
+ # When both tokens are same line, column should be consecutives
+ assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}")
+ else
+ # Line should be next
+ assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}")
+ # It should be on the beginning of the line
+ assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}")
+ end
+ end
+ end
+ end
+
private def parse(src)
EnvUtil.suppress_warning {
RubyVM::AbstractSyntaxTree.parse(src)
@@ -185,7 +214,26 @@ class TestAst < Test::Unit::TestCase
end
end
- def test_of
+ def test_node_id_for_location
+ exception = begin
+ raise
+ rescue => e
+ e
+ end
+ loc = exception.backtrace_locations.first
+ node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
+ node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
+
+ assert_equal node.node_id, node_id
+ end
+
+ def test_node_id_for_backtrace_location_raises_argument_error
+ bug19262 = '[ruby-core:111435]'
+
+ assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) }
+ end
+
+ def test_of_proc_and_method
proc = Proc.new { 1 + 2 }
method = self.method(__method__)
@@ -194,7 +242,6 @@ class TestAst < Test::Unit::TestCase
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
- assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
Tempfile.create(%w"test_of .rb") do |tmp|
tmp.print "#{<<-"begin;"}\n#{<<-'end;'}"
@@ -211,7 +258,25 @@ class TestAst < Test::Unit::TestCase
end
end
- def test_of_eval
+ def sample_backtrace_location
+ [caller_locations(0).first, __LINE__]
+ end
+
+ def test_of_backtrace_location
+ backtrace_location, lineno = sample_backtrace_location
+ node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
+ assert_equal(lineno, node.first_lineno)
+ end
+
+ def test_of_error
+ assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
+ end
+
+ def test_of_proc_and_method_under_eval
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = false
+
method = self.method(eval("def example_method_#{$$}; end"))
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
@@ -229,6 +294,83 @@ class TestAst < Test::Unit::TestCase
method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_proc_and_method_under_eval_with_keep_script_lines
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = true
+
+ method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("proc{}")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}"))
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_backtrace_location_under_eval
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = false
+
+ m = Module.new do
+ eval(<<-END, nil, __FILE__, __LINE__)
+ def self.sample_backtrace_location
+ caller_locations(0).first
+ end
+ END
+ end
+ backtrace_location = m.sample_backtrace_location
+ assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) }
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
+ end
+
+ def test_of_backtrace_location_under_eval_with_keep_script_lines
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
+ keep_script_lines_back = RubyVM.keep_script_lines
+ RubyVM.keep_script_lines = true
+
+ m = Module.new do
+ eval(<<-END, nil, __FILE__, __LINE__)
+ def self.sample_backtrace_location
+ caller_locations(0).first
+ end
+ END
+ end
+ backtrace_location = m.sample_backtrace_location
+ node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
+ assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
+ assert_equal(2, node.first_lineno)
+
+ ensure
+ RubyVM.keep_script_lines = keep_script_lines_back
end
def test_of_c_method
@@ -353,6 +495,30 @@ class TestAst < Test::Unit::TestCase
assert_not_equal(type1, type2)
end
+ def test_rest_arg
+ rest_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-4]
+ end
+
+ assert_equal(nil, rest_arg.call(''))
+ assert_equal(:r, rest_arg.call('*r'))
+ assert_equal(:r, rest_arg.call('a, *r'))
+ assert_equal(:*, rest_arg.call('*'))
+ assert_equal(:*, rest_arg.call('a, *'))
+ end
+
+ def test_block_arg
+ block_arg = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1].children[-1]
+ end
+
+ assert_equal(nil, block_arg.call(''))
+ assert_equal(:block, block_arg.call('&block'))
+ assert_equal(:&, block_arg.call('&'))
+ end
+
def test_keyword_rest
kwrest = lambda do |arg_str|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
@@ -361,11 +527,21 @@ class TestAst < Test::Unit::TestCase
end
assert_equal(nil, kwrest.call(''))
- assert_equal([nil], kwrest.call('**'))
+ assert_equal([:**], kwrest.call('**'))
assert_equal(false, kwrest.call('**nil'))
assert_equal([:a], kwrest.call('**a'))
end
+ def test_argument_forwarding
+ forwarding = lambda do |arg_str|
+ node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
+ node = node.children.last.children.last.children[1]
+ node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : []
+ end
+
+ assert_equal([:*, nil, :&], forwarding.call('...'))
+ end
+
def test_ranges_numbered_parameter
helper = Helper.new(__FILE__, src: "1.times {_1}")
helper.validate_range
@@ -447,4 +623,441 @@ dummy
assert_equal("{ 1 + 2 }", node_proc.source)
assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
end
+
+ def test_encoding_with_keep_script_lines
+ # Stop a warning "possibly useless use of a literal in void context"
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+
+ enc = Encoding::EUC_JP
+ code = "__ENCODING__".encode(enc)
+
+ assert_equal(enc, eval(code))
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false)
+ assert_equal(enc, node.children[2].children[0])
+
+ node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true)
+ assert_equal(enc, node.children[2].children[0])
+
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ def test_e_option
+ assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
+ "", [":SCOPE"], [])
+ end
+
+ def test_error_tolerant
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
+ class A
+ def m
+ if;
+ a = 10
+ end
+ end
+ STR
+ assert_nil($!)
+
+ assert_equal(:SCOPE, node.type)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+
+ def test_error_tolerant_end_is_short_for_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-2:4
+ mid: :m
+ body:
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_singleton_method_define
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def obj.m
+ m2
+ STR
+ (SCOPE@1:0-2:4
+ tbl: []
+ args: nil
+ body:
+ (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m
+ (SCOPE@1:0-2:4
+ tbl: []
+ args:
+ (ARGS@1:9-1:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:4 :m2))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_begin
+ assert_error_tolerant(<<~STR, <<~EXP)
+ begin
+ a = 1
+ STR
+ (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_if
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)) nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ if cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_unless
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ STR
+ (SCOPE@1:0-2:7
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1))
+ nil))
+ EXP
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ unless cond
+ a = 1
+ else
+ STR
+ (SCOPE@1:0-3:4
+ tbl: [:a]
+ args: nil
+ body:
+ (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1))
+ (BEGIN@3:4-3:4 nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_while
+ assert_error_tolerant(<<~STR, <<~EXP)
+ while true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_until
+ assert_error_tolerant(<<~STR, <<~EXP)
+ until true
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_case
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ when 1
+ STR
+ (SCOPE@1:0-2:6
+ tbl: []
+ args: nil
+ body:
+ (CASE@1:0-2:6 (VCALL@1:5-1:6 :a)
+ (WHEN@2:0-2:6 (LIST@2:5-2:6 (LIT@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil)
+ nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case
+ when a == 1
+ STR
+ (SCOPE@1:0-2:11
+ tbl: []
+ args: nil
+ body:
+ (CASE2@1:0-2:11 nil
+ (WHEN@2:0-2:11
+ (LIST@2:5-2:11
+ (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :==
+ (LIST@2:10-2:11 (LIT@2:10-2:11 1) nil)) nil)
+ (BEGIN@2:11-2:11 nil) nil)))
+ EXP
+
+
+ assert_error_tolerant(<<~STR, <<~EXP)
+ case a
+ in {a: String}
+ STR
+ (SCOPE@1:0-2:14
+ tbl: []
+ args: nil
+ body:
+ (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a)
+ (IN@2:0-2:14
+ (HSHPTN@2:4-2:13
+ const: nil
+ kw:
+ (HASH@2:4-2:13
+ (LIST@2:4-2:13 (LIT@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil))
+ kwrest: nil) (BEGIN@2:14-2:14 nil) nil)))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_for
+ assert_error_tolerant(<<~STR, <<~EXP)
+ for i in ary
+ m
+ STR
+ (SCOPE@1:0-2:3
+ tbl: [:i]
+ args: nil
+ body:
+ (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary)
+ (SCOPE@1:0-2:3
+ tbl: [nil]
+ args:
+ (ARGS@1:4-1:5
+ pre_num: 1
+ pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil))
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :m))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_class
+ assert_error_tolerant(<<~STR, <<~EXP)
+ class C
+ STR
+ (SCOPE@1:0-1:7
+ tbl: []
+ args: nil
+ body:
+ (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil
+ (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_module
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module M
+ STR
+ (SCOPE@1:0-1:8
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M)
+ (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil)
+ (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_block
+ assert_error_tolerant(<<~STR, <<~EXP)
+ m 1 do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (LIT@1:2-1:3 1) nil))
+ (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_end_is_short_for_do_LAMBDA
+ assert_error_tolerant(<<~STR, <<~EXP)
+ -> do
+ a
+ STR
+ (SCOPE@1:0-2:3
+ tbl: []
+ args: nil
+ body:
+ (LAMBDA@1:0-2:3
+ (SCOPE@1:2-2:3
+ tbl: []
+ args:
+ (ARGS@1:2-1:2
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (VCALL@2:2-2:3 :a))))
+ EXP
+ end
+
+ def test_error_tolerant_treat_end_as_keyword_based_on_indent
+ assert_error_tolerant(<<~STR, <<~EXP)
+ module Z
+ class Foo
+ foo.
+ end
+
+ def bar
+ end
+ end
+ STR
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z)
+ (SCOPE@1:0-8:3
+ tbl: []
+ args: nil
+ body:
+ (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil)
+ (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil
+ (SCOPE@2:2-4:5
+ tbl: []
+ args: nil
+ body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5))))
+ (DEFN@6:2-7:5
+ mid: :bar
+ body:
+ (SCOPE@6:2-7:5
+ tbl: []
+ args:
+ (ARGS@6:9-6:9
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: nil))))))
+ EXP
+ end
+
+ def test_error_tolerant_expr_value_can_be_error
+ assert_error_tolerant(<<~STR, <<~EXP)
+ def m
+ if
+ end
+ STR
+ (SCOPE@1:0-3:3
+ tbl: []
+ args: nil
+ body:
+ (DEFN@1:0-3:3
+ mid: :m
+ body:
+ (SCOPE@1:0-3:3
+ tbl: []
+ args:
+ (ARGS@1:5-1:5
+ pre_num: 0
+ pre_init: nil
+ opt: nil
+ first_post: nil
+ post_num: 0
+ post_init: nil
+ rest: nil
+ kw: nil
+ kwrest: nil
+ block: nil)
+ body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil))))
+ EXP
+ end
+
+ def assert_error_tolerant(src, expected)
+ begin
+ verbose_bak, $VERBOSE = $VERBOSE, false
+ node = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true)
+ ensure
+ $VERBOSE = verbose_bak
+ end
+ assert_nil($!)
+ str = ""
+ PP.pp(node, str, 80)
+ assert_equal(expected, str)
+ end
end
diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb
index 7709760d19..e475520321 100644
--- a/test/ruby/test_autoload.rb
+++ b/test/ruby/test_autoload.rb
@@ -65,6 +65,24 @@ p Foo::Bar
}
end
+ def test_autoload_p_with_static_extensions
+ require 'rbconfig'
+ omit unless RbConfig::CONFIG['EXTSTATIC'] == 'static'
+ begin
+ require 'fcntl.so'
+ rescue LoadError
+ omit('fcntl not included in the build')
+ end
+
+ assert_separately(['--disable-all'], <<~RUBY)
+ autoload :Fcntl, 'fcntl.so'
+
+ assert_equal('fcntl.so', autoload?(:Fcntl))
+ assert(Object.const_defined?(:Fcntl))
+ assert_equal('constant', defined?(Fcntl), '[Bug #19115]')
+ RUBY
+ end
+
def test_autoload_with_unqualified_file_name # [ruby-core:69206]
Object.send(:remove_const, :A) if Object.const_defined?(:A)
@@ -150,6 +168,7 @@ p Foo::Bar
end
def test_nameerror_when_autoload_did_not_define_the_constant
+ verbose_bak, $VERBOSE = $VERBOSE, nil
Tempfile.create(['autoload', '.rb']) {|file|
file.puts ''
file.close
@@ -162,6 +181,8 @@ p Foo::Bar
remove_autoload_constant
end
}
+ ensure
+ $VERBOSE = verbose_bak
end
def test_override_autoload
@@ -443,6 +464,23 @@ p Foo::Bar
end
end
+ def test_source_location_after_require
+ bug = "Bug18624"
+ Dir.mktmpdir('autoload') do |tmpdir|
+ path = "#{tmpdir}/test-#{bug}.rb"
+ File.write(path, "C::#{bug} = __FILE__\n")
+ assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ class C; end
+ C.autoload(:Bug18624, #{path.dump})
+ require #{path.dump}
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ assert_equal #{path.dump}, C.const_get(#{bug.dump})
+ assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump})
+ end;
+ end
+ end
+
def test_no_memory_leak
assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60)
begin;
@@ -462,6 +500,7 @@ p Foo::Bar
File.write(autoload_path, '')
assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ $VERBOSE = nil
path = #{File.realpath(autoload_path).inspect}
autoload :X, path
assert_equal(path, Object.autoload?(:X))
@@ -491,4 +530,69 @@ p Foo::Bar
::Object.class_eval {remove_const(:AutoloadTest)} if defined? Object::AutoloadTest
TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest
end
+
+ def test_autoload_module_gc
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "autoload_module_gc.rb")
+ File.write(autoload_path, "X = 1; Y = 2;")
+
+ x = Module.new
+ x.autoload :X, "./feature.rb"
+
+ 1000.times do
+ y = Module.new
+ y.autoload :Y, "./feature.rb"
+ end
+
+ x = y = nil
+
+ # Ensure the internal data structures are cleaned up correctly / don't crash:
+ GC.start
+ end
+ end
+
+ def test_autoload_parallel_race
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "autoload_parallel_race.rb")
+ File.write(autoload_path, 'module Foo; end; module Bar; end')
+
+ assert_separately([], <<-RUBY, timeout: 100)
+ autoload_path = #{File.realpath(autoload_path).inspect}
+
+ # This should work with no errors or failures.
+ 1000.times do
+ autoload :Foo, autoload_path
+ autoload :Bar, autoload_path
+
+ t1 = Thread.new {Foo}
+ t2 = Thread.new {Bar}
+
+ t1.join
+ GC.start # force GC.
+ t2.join
+
+ Object.send(:remove_const, :Foo)
+ Object.send(:remove_const, :Bar)
+
+ $LOADED_FEATURES.delete(autoload_path)
+ end
+ RUBY
+ end
+ end
+
+ def test_autoload_parent_namespace
+ Dir.mktmpdir('autoload') do |tmpdir|
+ autoload_path = File.join(tmpdir, "some_const.rb")
+ File.write(autoload_path, 'class SomeConst; end')
+
+ assert_separately(%W[-I #{tmpdir}], <<-RUBY)
+ module SomeNamespace
+ autoload :SomeConst, #{File.realpath(autoload_path).inspect}
+ assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do
+ assert_not_nil SomeConst
+ end
+ end
+ RUBY
+ end
+ end
end
diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb
index aa79db24cb..bb0562c0bb 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -154,6 +154,35 @@ class TestBacktrace < Test::Unit::TestCase
assert_equal caller(0), caller(0, nil)
end
+ def test_each_backtrace_location
+ i = 0
+ cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1}
+ assert_equal(cl.to_s, ecl.to_s)
+ assert_kind_of(Thread::Backtrace::Location, ecl)
+
+ i = 0
+ ary = []
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ i = 0
+ ary = []
+ ->{->{
+ cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2}
+ }.()}.()
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+ assert_kind_of(Thread::Backtrace::Location, last)
+
+ cllr = caller_locations(1, 2); ary = Thread.to_enum(:each_caller_location).to_a[2..3]
+ assert_equal(cllr.map(&:to_s), ary.map(&:to_s))
+
+ ecl = Thread.to_enum(:each_caller_location)
+ assert_raise(StopIteration) {
+ ecl.next
+ }
+ end
+
def test_caller_locations_first_label
def self.label
caller_locations.first.label
diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb
index 3ffe7114b5..065a944853 100644
--- a/test/ruby/test_bignum.rb
+++ b/test/ruby/test_bignum.rb
@@ -203,6 +203,15 @@ class TestBignum < Test::Unit::TestCase
assert_equal(00_02, '00_02'.to_i)
end
+ def test_very_big_str_to_inum
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ digits = [["3", 700], ["0", 2700], ["1", 1], ["0", 26599]]
+ num = digits.inject("") {|s,(c,n)|s << c*n}.to_i
+ assert_equal digits.sum {|c,n|n}, num.to_s.size
+ end;
+ end
+
def test_to_s2
assert_raise(ArgumentError) { T31P.to_s(37) }
assert_equal("9" * 32768, (10**32768-1).to_s)
@@ -644,7 +653,7 @@ class TestBignum < Test::Unit::TestCase
thread.raise
thread.join
time = Time.now - time
- skip "too fast cpu" if end_flag
+ omit "too fast cpu" if end_flag
assert_operator(time, :<, 10)
end
@@ -675,7 +684,7 @@ class TestBignum < Test::Unit::TestCase
return
end
end
- skip "cannot create suitable test case"
+ omit "cannot create suitable test case"
ensure
Signal.trap(:INT, oldtrap) if oldtrap
end
diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb
index 67b3a936d4..88e2ddcb7b 100644
--- a/test/ruby/test_call.rb
+++ b/test/ruby/test_call.rb
@@ -47,12 +47,19 @@ class TestCall < Test::Unit::TestCase
assert_equal(5, o.y)
o&.z ||= 6
assert_equal(6, o.z)
+ o&.z &&= 7
+ assert_equal(7, o.z)
o = nil
assert_nil(o&.x)
assert_nothing_raised(NoMethodError) {o&.x = raise}
+ assert_nothing_raised(NoMethodError) {o&.x = raise; nil}
assert_nothing_raised(NoMethodError) {o&.x *= raise}
assert_nothing_raised(NoMethodError) {o&.x *= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise}
+ assert_nothing_raised(NoMethodError) {o&.x ||= raise; nil}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise}
+ assert_nothing_raised(NoMethodError) {o&.x &&= raise; nil}
end
def test_safe_call_evaluate_arguments_only_method_call_is_made
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index 368c046261..300ab950ac 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -96,6 +96,13 @@ class TestClass < Test::Unit::TestCase
def test_superclass_of_basicobject
assert_equal(nil, BasicObject.superclass)
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ module Mod end
+ BasicObject.include(Mod)
+ assert_equal(nil, BasicObject.superclass)
+ end;
end
def test_module_function
@@ -737,4 +744,85 @@ class TestClass < Test::Unit::TestCase
c = Class.new.freeze
assert_same(c, Module.new.const_set(:Foo, c))
end
+
+ def test_subclasses
+ c = Class.new
+ sc = Class.new(c)
+ ssc = Class.new(sc)
+ [c, sc, ssc].each do |k|
+ k.include Module.new
+ k.new.define_singleton_method(:force_singleton_class){}
+ end
+ assert_equal([sc], c.subclasses)
+ assert_equal([ssc], sc.subclasses)
+ assert_equal([], ssc.subclasses)
+
+ object_subclasses = Object.subclasses
+ assert_include(object_subclasses, c)
+ assert_not_include(object_subclasses, sc)
+ assert_not_include(object_subclasses, ssc)
+ object_subclasses.each do |subclass|
+ assert_equal Object, subclass.superclass, "Expected #{subclass}.superclass to be Object"
+ end
+ end
+
+ def test_attached_object
+ c = Class.new
+ sc = c.singleton_class
+ obj = c.new
+
+ assert_equal(obj, obj.singleton_class.attached_object)
+ assert_equal(c, sc.attached_object)
+
+ assert_raise_with_message(TypeError, /is not a singleton class/) do
+ c.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /`NilClass' is not a singleton class/) do
+ nil.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /`FalseClass' is not a singleton class/) do
+ false.singleton_class.attached_object
+ end
+
+ assert_raise_with_message(TypeError, /`TrueClass' is not a singleton class/) do
+ true.singleton_class.attached_object
+ end
+ end
+
+ def test_subclass_gc
+ c = Class.new
+ 10_000.times do
+ cc = Class.new(c)
+ 100.times { Class.new(cc) }
+ end
+ assert(c.subclasses.size <= 10_000)
+ end
+
+ def test_subclass_gc_stress
+ 10000.times do
+ c = Class.new
+ 100.times { Class.new(c) }
+ assert(c.subclasses.size <= 100)
+ end
+ end
+
+ def test_classext_memory_leak
+ assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
+code = proc { Class.new }
+1_000.times(&code)
+PREP
+3_000_000.times(&code)
+CODE
+ end
+
+ def test_instance_freeze_dont_freeze_the_class_bug_19164
+ klass = Class.new
+ klass.prepend(Module.new)
+
+ klass.new.freeze
+ klass.define_method(:bar) {}
+ assert_equal klass, klass.remove_method(:bar), '[Bug #19164]'
+ end
end
diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb
index 321feb07c7..775c9ed848 100644
--- a/test/ruby/test_clone.rb
+++ b/test/ruby/test_clone.rb
@@ -27,6 +27,59 @@ class TestClone < Test::Unit::TestCase
assert_equal([M003, M002, M001], M003.ancestors)
end
+ def test_frozen_properties_retained_on_clone
+ obj = Object.new.freeze
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_predicate(cloned_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_clone
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ cloned_obj = obj.clone
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_clone
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ cloned_obj = obj.clone
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(cloned_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_and_ivars_retained_on_clone_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ cloned_obj = obj.clone
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ assert_predicate(cloned_obj, :frozen?)
+ assert_equal(cloned_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_proc_obj_id_flag_reset
+ # [Bug #20250]
+ proc = Proc.new { }
+ proc.object_id
+ proc.clone.object_id # Would crash with RUBY_DEBUG=1
+ end
+
def test_user_flags
assert_separately([], <<-EOS)
#
diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb
index a3a7546575..b262b2c1c1 100644
--- a/test/ruby/test_complex.rb
+++ b/test/ruby/test_complex.rb
@@ -221,10 +221,16 @@ class Complex_Test < Test::Unit::TestCase
assert_equal([1,2], Complex.polar(1,2).polar)
assert_equal(Complex.polar(1.0, Math::PI * 2 / 3), Complex.polar(1, Math::PI * 2 / 3))
- assert_in_out_err([], <<-'end;', ['OK'], [])
- Complex.polar(1, Complex(1, 0))
- puts :OK
- end;
+ one = 1+0i
+ c = Complex.polar(0, one)
+ assert_equal(0, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one, 0)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
+ c = Complex.polar(one)
+ assert_equal(1, c)
+ assert_predicate(c.real, :real?)
end
def test_uplus
@@ -567,20 +573,24 @@ class Complex_Test < Test::Unit::TestCase
assert_raise_with_message(TypeError, /C\u{1f5ff}/) { Complex(1).coerce(obj) }
end
- class ObjectX
- def +(x) Rational(1) end
+ class ObjectX < Numeric
+ def initialize(real = true, n = 1) @n = n; @real = real; end
+ def +(x) Rational(@n) end
alias - +
alias * +
alias / +
alias quo +
alias ** +
- def coerce(x) [x, Complex(1)] end
+ def coerce(x) [x, Complex(@n)] end
+ def real?; @real; end
end
def test_coerce2
x = ObjectX.new
- %w(+ - * / quo **).each do |op|
- assert_kind_of(Numeric, Complex(1).__send__(op, x))
+ y = ObjectX.new(false)
+ %w(+ - * / quo ** <=>).each do |op|
+ assert_kind_of(Numeric, Complex(1).__send__(op, x), op)
+ assert_kind_of(Numeric, Complex(1).__send__(op, y), op)
end
end
@@ -843,20 +853,42 @@ class Complex_Test < Test::Unit::TestCase
assert_equal(Complex(0), '_5'.to_c)
assert_equal(Complex(5), '5_'.to_c)
assert_equal(Complex(5), '5x'.to_c)
+ assert_equal(Complex(51), '5_1'.to_c)
+ assert_equal(Complex(5), '5__1'.to_c)
assert_equal(Complex(5), '5+_3i'.to_c)
assert_equal(Complex(5), '5+3_i'.to_c)
assert_equal(Complex(5,3), '5+3i_'.to_c)
assert_equal(Complex(5,3), '5+3ix'.to_c)
+ assert_equal(Complex(5,31), '5+3_1i'.to_c)
+ assert_equal(Complex(5), '5+3__1i'.to_c)
+ assert_equal(Complex(51), Complex('5_1'))
+ assert_equal(Complex(5,31), Complex('5+3_1i'))
+ assert_equal(Complex(5,31), Complex('5+3_1I'))
+ assert_equal(Complex(5,31), Complex('5+3_1j'))
+ assert_equal(Complex(5,31), Complex('5+3_1J'))
+ assert_equal(Complex(0,31), Complex('3_1i'))
+ assert_equal(Complex(0,31), Complex('3_1I'))
+ assert_equal(Complex(0,31), Complex('3_1j'))
+ assert_equal(Complex(0,31), Complex('3_1J'))
assert_raise(ArgumentError){ Complex('')}
assert_raise(ArgumentError){ Complex('_')}
assert_raise(ArgumentError){ Complex("\f\n\r\t\v5\0")}
assert_raise(ArgumentError){ Complex('_5')}
assert_raise(ArgumentError){ Complex('5_')}
+ assert_raise(ArgumentError){ Complex('5__1')}
assert_raise(ArgumentError){ Complex('5x')}
assert_raise(ArgumentError){ Complex('5+_3i')}
assert_raise(ArgumentError){ Complex('5+3_i')}
assert_raise(ArgumentError){ Complex('5+3i_')}
assert_raise(ArgumentError){ Complex('5+3ix')}
+ assert_raise(ArgumentError){ Complex('5+3__1i')}
+ assert_raise(ArgumentError){ Complex('5+3__1I')}
+ assert_raise(ArgumentError){ Complex('5+3__1j')}
+ assert_raise(ArgumentError){ Complex('5+3__1J')}
+ assert_raise(ArgumentError){ Complex('3__1i')}
+ assert_raise(ArgumentError){ Complex('3__1I')}
+ assert_raise(ArgumentError){ Complex('3__1j')}
+ assert_raise(ArgumentError){ Complex('3__1J')}
assert_equal(Complex(Rational(1,5)), '1/5'.to_c)
assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c)
@@ -883,31 +915,27 @@ class Complex_Test < Test::Unit::TestCase
}
end
- def test_Complex_without_exception
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex('5x', exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(nil, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(Object.new, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, nil, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, Object.new, exception: false))
- }
+ def assert_complex_with_exception(error, *args, message: "")
+ assert_raise(error, message) do
+ Complex(*args, exception: true)
+ end
+ assert_nothing_raised(error, message) do
+ assert_nil(Complex(*args, exception: false))
+ assert_nil($!)
+ end
+ end
+
+ def test_Complex_with_exception
+ assert_complex_with_exception(ArgumentError, '5x')
+ assert_complex_with_exception(TypeError, nil)
+ assert_complex_with_exception(TypeError, Object.new)
+ assert_complex_with_exception(TypeError, 1, nil)
+ assert_complex_with_exception(TypeError, 1, Object.new)
o = Object.new
def o.to_c; raise; end
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(o, exception: false))
- }
- assert_nothing_raised(ArgumentError){
- assert_equal(nil, Complex(1, o, exception: false))
- }
+ assert_complex_with_exception(RuntimeError, o)
+ assert_complex_with_exception(TypeError, 1, o)
end
def test_respond
@@ -1135,15 +1163,34 @@ class Complex_Test < Test::Unit::TestCase
end
def test_canonicalize_polar
- obj = Class.new(Numeric) do
- def initialize
- @x = 2
+ error = "not a real"
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(1i, 0)
+ end
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(0, 1i)
+ end
+ n = Class.new(Numeric) do
+ def initialize(x = 1)
+ @x = x
end
def real?
(@x -= 1) > 0
end
- end.new
- assert_raise(TypeError) do
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
+ Complex.polar(obj, 0)
+ end
+ obj = n.new
+ assert_raise_with_message(TypeError, error) do
Complex.polar(1, obj)
end
end
diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb
index 594fc3f45a..b89e83efb2 100644
--- a/test/ruby/test_complex2.rb
+++ b/test/ruby/test_complex2.rb
@@ -4,7 +4,7 @@ require 'test/unit'
class Complex_Test2 < Test::Unit::TestCase
def test_kumi
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
assert_equal(Complex(1, 0), +Complex(1, 0))
assert_equal(Complex(-1, 0), -Complex(1, 0))
diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb
index bf4e2b1809..31d11fe317 100644
--- a/test/ruby/test_complexrational.rb
+++ b/test/ruby/test_complexrational.rb
@@ -4,7 +4,7 @@ require 'test/unit'
class ComplexRational_Test < Test::Unit::TestCase
def test_rat_srat
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
c = SimpleRat(1,3)
cc = Rational(3,2)
@@ -89,7 +89,7 @@ class ComplexRational_Test < Test::Unit::TestCase
end
def test_comp_srat
- skip unless defined?(Rational)
+ omit unless defined?(Rational)
c = Complex(SimpleRat(2,3),SimpleRat(1,2))
cc = Complex(Rational(3,2),Rational(2,1))
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
new file mode 100644
index 0000000000..3cafb365ed
--- /dev/null
+++ b/test/ruby/test_data.rb
@@ -0,0 +1,249 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+require 'test/unit'
+require 'timeout'
+
+class TestData < Test::Unit::TestCase
+ def test_define
+ klass = Data.define(:foo, :bar)
+ assert_kind_of(Class, klass)
+ assert_equal(%i[foo bar], klass.members)
+
+ assert_raise(NoMethodError) { Data.new(:foo) }
+ assert_raise(TypeError) { Data.define(0) }
+
+ # Because some code is shared with Struct, check we don't share unnecessary functionality
+ assert_raise(TypeError) { Data.define(:foo, keyword_init: true) }
+
+ assert_not_respond_to(Data.define, :define, "Cannot define from defined Data class")
+ end
+
+ def test_define_edge_cases
+ # non-ascii
+ klass = Data.define(:"r\u{e9}sum\u{e9}")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"r\u{e9}sum\u{e9}"))
+
+ # junk string
+ klass = Data.define(:"a\000")
+ o = klass.new(1)
+ assert_equal(1, o.send(:"a\000"))
+
+ # special characters in attribute names
+ klass = Data.define(:a, :b?)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b?)
+
+ klass = Data.define(:a, :b!)
+ x = Object.new
+ o = klass.new("test", x)
+ assert_same(x, o.b!)
+
+ assert_raise(ArgumentError) { Data.define(:x=) }
+ assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) }
+ end
+
+ def test_define_with_block
+ klass = Data.define(:a, :b) do
+ def c
+ a + b
+ end
+ end
+
+ assert_equal(3, klass.new(1, 2).c)
+ end
+
+ def test_initialize
+ klass = Data.define(:foo, :bar)
+
+ # Regular
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(1, 2))
+ assert_predicate(test, :frozen?)
+
+ # Keywords
+ test_kw = klass.new(foo: 1, bar: 2)
+ assert_equal(1, test_kw.foo)
+ assert_equal(2, test_kw.bar)
+ assert_equal(test_kw, klass.new(foo: 1, bar: 2))
+ assert_equal(test_kw, test)
+
+ # Wrong protocol
+ assert_raise(ArgumentError) { klass.new(1) }
+ assert_raise(ArgumentError) { klass.new(1, 2, 3) }
+ assert_raise(ArgumentError) { klass.new(foo: 1) }
+ assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) }
+ # Could be converted to foo: 1, bar: 2, but too smart is confusing
+ assert_raise(ArgumentError) { klass.new(1, bar: 2) }
+ end
+
+ def test_initialize_redefine
+ klass = Data.define(:foo, :bar) do
+ attr_reader :passed
+
+ def initialize(*args, **kwargs)
+ @passed = [args, kwargs]
+ super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args
+ end
+ end
+
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed)
+
+ # Positional arguments are converted to keyword ones
+ assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed)
+
+ # Missing arguments can be fixed in initialize
+ assert_equal([[], {foo: 1}], klass.new(foo: 1).passed)
+
+ # Extra keyword arguments can be dropped in initialize
+ assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed)
+ end
+
+ def test_instance_behavior
+ klass = Data.define(:foo, :bar)
+
+ test = klass.new(1, 2)
+ assert_equal(1, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(%i[foo bar], test.members)
+ assert_equal(1, test.public_send(:foo))
+ assert_equal(0, test.method(:foo).arity)
+ assert_equal([], test.method(:foo).parameters)
+
+ assert_equal({foo: 1, bar: 2}, test.to_h)
+ assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] })
+
+ assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo]))
+ assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz]))
+ assert_raise(TypeError) { test.deconstruct_keys(0) }
+
+ assert_kind_of(Integer, test.hash)
+ end
+
+ def test_inspect
+ klass = Data.define(:a)
+ o = klass.new(1)
+ assert_equal("#<data a=1>", o.inspect)
+
+ Object.const_set(:Foo, klass)
+ assert_equal("#<data Foo a=1>", o.inspect)
+ Object.instance_eval { remove_const(:Foo) }
+
+ klass = Data.define(:@a)
+ o = klass.new(1)
+ assert_equal("#<data :@a=1>", o.inspect)
+ end
+
+ def test_equal
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_equal(o1, o2)
+ assert_not_equal(o1, o3)
+ end
+
+ def test_eql
+ klass1 = Data.define(:a)
+ klass2 = Data.define(:a)
+ o1 = klass1.new(1)
+ o2 = klass1.new(1)
+ o3 = klass2.new(1)
+ assert_operator(o1, :eql?, o2)
+ assert_not_operator(o1, :eql?, o3)
+ end
+
+ def test_with
+ klass = Data.define(:foo, :bar)
+ source = klass.new(foo: 1, bar: 2)
+
+ # Simple
+ test = source.with
+ assert_equal(source.object_id, test.object_id)
+
+ # Changes
+ test = source.with(foo: 10)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(2, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 2))
+
+ test = source.with(foo: 10, bar: 20)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Keyword splat
+ changes = { foo: 10, bar: 20 }
+ test = source.with(**changes)
+
+ assert_equal(1, source.foo)
+ assert_equal(2, source.bar)
+ assert_equal(source, klass.new(foo: 1, bar: 2))
+
+ assert_equal(10, test.foo)
+ assert_equal(20, test.bar)
+ assert_equal(test, klass.new(foo: 10, bar: 20))
+
+ # Wrong protocol
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(10)
+ end
+ assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do
+ source.with(foo: 1, bar: 2, baz: 3, quux: 4)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with(1, bar: 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do
+ source.with(1, 2)
+ end
+ assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do
+ source.with({ bar: 2 })
+ end
+ end
+
+ def test_memberless
+ klass = Data.define
+
+ test = klass.new
+
+ assert_equal(klass.new, test)
+ assert_not_equal(Data.define.new, test)
+
+ assert_equal('#<data >', test.inspect)
+ assert_equal([], test.members)
+ assert_equal({}, test.to_h)
+ end
+
+ def test_dup
+ klass = Data.define(:foo, :bar)
+ test = klass.new(foo: 1, bar: 2)
+ assert_equal(klass.new(foo: 1, bar: 2), test.dup)
+ assert_predicate(test.dup, :frozen?)
+ end
+
+ Klass = Data.define(:foo, :bar)
+
+ def test_marshal
+ test = Klass.new(foo: 1, bar: 2)
+ loaded = Marshal.load(Marshal.dump(test))
+ assert_equal(test, loaded)
+ assert_not_same(test, loaded)
+ assert_predicate(loaded, :frozen?)
+ end
+end
diff --git a/test/ruby/test_default_gems.rb b/test/ruby/test_default_gems.rb
index 3c4aea1561..467e5ecf83 100644
--- a/test/ruby/test_default_gems.rb
+++ b/test/ruby/test_default_gems.rb
@@ -4,7 +4,7 @@ require 'rubygems'
class TestDefaultGems < Test::Unit::TestCase
def test_validate_gemspec
- skip "git not found" unless system("git", "rev-parse", %i[out err]=>IO::NULL)
+ omit "git not found" unless system("git", "rev-parse", %i[out err]=>IO::NULL)
srcdir = File.expand_path('../../..', __FILE__)
Dir.glob("#{srcdir}/{lib,ext}/**/*.gemspec").map do |src|
assert_nothing_raised do
diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb
index 71bff5f5c3..63ef99f159 100644
--- a/test/ruby/test_dir.rb
+++ b/test/ruby/test_dir.rb
@@ -19,11 +19,21 @@ class TestDir < Test::Unit::TestCase
@dirs << File.join(i, "")
end
end
+ @envs = nil
end
def teardown
$VERBOSE = @verbose
FileUtils.remove_entry_secure @root if File.directory?(@root)
+ ENV.update(@envs) if @envs
+ end
+
+ def setup_envs(envs = %w"HOME LOGDIR")
+ @envs ||= {}
+ envs.each do |e, v|
+ @envs[e] = ENV.delete(e)
+ ENV[e] = v if v
+ end
end
def test_seek
@@ -88,10 +98,7 @@ class TestDir < Test::Unit::TestCase
def test_chdir
pwd = Dir.pwd
- env_home = ENV["HOME"]
- env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ setup_envs
assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) }
assert_raise(ArgumentError) { Dir.chdir }
@@ -125,8 +132,6 @@ class TestDir < Test::Unit::TestCase
rescue
abort("cannot return the original directory: #{ pwd }")
end
- ENV["HOME"] = env_home
- ENV["LOGDIR"] = env_logdir
end
def test_chdir_conflict
@@ -152,7 +157,7 @@ class TestDir < Test::Unit::TestCase
end
def test_chroot_nodir
- skip if RUBY_PLATFORM =~ /android/
+ omit if RUBY_PLATFORM =~ /android/
assert_raise(NotImplementedError, Errno::ENOENT, Errno::EPERM
) { Dir.chroot(File.join(@nodir, "")) }
end
@@ -171,9 +176,17 @@ class TestDir < Test::Unit::TestCase
Dir.glob([@root, File.join(@root, "*")]))
assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
Dir.glob([@root, File.join(@root, "*")], sort: false).sort)
+ assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
+ Dir.glob([@root, File.join(@root, "*")], sort: true))
assert_raise_with_message(ArgumentError, /nul-separated/) do
Dir.glob(@root + "\0\0\0" + File.join(@root, "*"))
end
+ assert_raise_with_message(ArgumentError, /expected true or false/) do
+ Dir.glob(@root, sort: 1)
+ end
+ assert_raise_with_message(ArgumentError, /expected true or false/) do
+ Dir.glob(@root, sort: nil)
+ end
assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") },
Dir.glob(File.join(@root, "*/")))
@@ -251,6 +264,20 @@ class TestDir < Test::Unit::TestCase
end
end
+ def test_glob_recursive_with_brace
+ Dir.chdir(@root) do
+ bug19042 = '[ruby-core:110220] [Bug #19042]'
+ %w"c/dir_a c/dir_b c/dir_b/dir".each do |d|
+ Dir.mkdir(d)
+ end
+ expected = %w"c/dir_a/file c/dir_b/dir/file"
+ expected.each do |f|
+ File.write(f, "")
+ end
+ assert_equal(expected, Dir.glob("**/{dir_a,dir_b/dir}/file"), bug19042)
+ end
+ end
+
def test_glob_order
Dir.chdir(@root) do
assert_equal(["#{@root}/a", "#{@root}/b"], Dir.glob("#{@root}/[ba]"))
@@ -482,9 +509,9 @@ class TestDir < Test::Unit::TestCase
def test_glob_legacy_short_name
bug10819 = '[ruby-core:67954] [Bug #10819]'
bug11206 = '[ruby-core:69435] [Bug #11206]'
- skip unless /\A\w:/ =~ ENV["ProgramFiles"]
+ omit unless /\A\w:/ =~ ENV["ProgramFiles"]
short = "#$&/PROGRA~1"
- skip unless File.directory?(short)
+ omit unless File.directory?(short)
entries = Dir.glob("#{short}/Common*")
assert_not_empty(entries, bug10819)
long = File.expand_path(short)
@@ -494,13 +521,28 @@ class TestDir < Test::Unit::TestCase
assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819)
assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819)
end
+
+ def test_home_windows
+ setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH])
+
+ ENV['HOME'] = "C:\\ruby\\home"
+ assert_equal("C:/ruby/home", Dir.home)
+
+ ENV['USERPROFILE'] = "C:\\ruby\\userprofile"
+ assert_equal("C:/ruby/home", Dir.home)
+ ENV.delete('HOME')
+ assert_equal("C:/ruby/userprofile", Dir.home)
+
+ ENV['HOMEDRIVE'] = "C:"
+ ENV['HOMEPATH'] = "\\ruby\\homepath"
+ assert_equal("C:/ruby/userprofile", Dir.home)
+ ENV.delete('USERPROFILE')
+ assert_equal("C:/ruby/homepath", Dir.home)
+ end
end
def test_home
- env_home = ENV["HOME"]
- env_logdir = ENV["LOGDIR"]
- ENV.delete("HOME")
- ENV.delete("LOGDIR")
+ setup_envs
ENV["HOME"] = @nodir
assert_nothing_raised(ArgumentError) do
@@ -518,9 +560,16 @@ class TestDir < Test::Unit::TestCase
%W[no:such:user \u{7559 5b88}:\u{756a}].each do |user|
assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)}
end
- ensure
- ENV["HOME"] = env_home
- ENV["LOGDIR"] = env_logdir
+ end
+
+ if Encoding.find("filesystem") == Encoding::UTF_8
+ # On Windows and macOS, file system encoding is always UTF-8.
+ def test_home_utf8
+ setup_envs
+
+ ENV["HOME"] = "/\u{e4}~\u{1f3e0}"
+ assert_equal("/\u{e4}~\u{1f3e0}", Dir.home)
+ end
end
def test_symlinks_not_resolved
diff --git a/test/ruby/test_dup.rb b/test/ruby/test_dup.rb
new file mode 100644
index 0000000000..75c4fc0339
--- /dev/null
+++ b/test/ruby/test_dup.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestDup < Test::Unit::TestCase
+ module M001; end
+ module M002; end
+ module M003; include M002; end
+ module M002; include M001; end
+ module M003; include M002; end
+
+ def test_dup
+ foo = Object.new
+ def foo.test
+ "test"
+ end
+ bar = foo.dup
+ def bar.test2
+ "test2"
+ end
+
+ assert_equal("test2", bar.test2)
+ assert_raise(NoMethodError) { bar.test }
+ assert_equal("test", foo.test)
+
+ assert_raise(NoMethodError) {foo.test2}
+
+ assert_equal([M003, M002, M001], M003.ancestors)
+ end
+
+ def test_frozen_properties_not_retained_on_dup
+ obj = Object.new.freeze
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ refute_predicate(duped_obj, :frozen?)
+ end
+
+ def test_ivar_retained_on_dup
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ duped_obj = obj.dup
+
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_ivars_retained_on_extended_obj_dup
+ ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 }
+ obj = Object.new
+ ivars.each do |ivar_name, val|
+ obj.instance_variable_set(ivar_name, val)
+ end
+
+ duped_obj = obj.dup
+
+ ivars.each do |ivar_name, val|
+ assert_equal(obj.instance_variable_get(ivar_name), val)
+ assert_equal(duped_obj.instance_variable_get(ivar_name), val)
+ end
+ end
+
+ def test_frozen_properties_not_retained_on_dup_with_ivar
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+
+ duped_obj = obj.dup
+
+ assert_predicate(obj, :frozen?)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+
+ refute_predicate(duped_obj, :frozen?)
+ assert_equal(duped_obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_user_flags
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1, 2, 3].dup
+ assert_equal [], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Array
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ x = [1,2,3,4,5,6,7][1..-2].dup
+ x.push(1,1,1,1,1)
+ assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]'
+ EOS
+
+ assert_separately([], <<-EOS)
+ #
+ class Hash
+ undef initialize_copy
+ def initialize_copy(*); end
+ end
+ h = {}
+ h.default_proc = proc { raise }
+ h = h.dup
+ assert_equal nil, h[:not_exist], '[Bug #14847]'
+ EOS
+ end
+end
diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb
index 4a6dd932ed..cde2951413 100644
--- a/test/ruby/test_encoding.rb
+++ b/test/ruby/test_encoding.rb
@@ -57,6 +57,7 @@ class TestEncoding < Test::Unit::TestCase
def test_replicate
assert_separately([], "#{<<~'END;'}")
+ Warning[:deprecated] = false
assert_instance_of(Encoding, Encoding::UTF_8.replicate("UTF-8-ANOTHER#{Time.now.to_f}"))
assert_instance_of(Encoding, Encoding::ISO_2022_JP.replicate("ISO-2022-JP-ANOTHER#{Time.now.to_f}"))
bug3127 = '[ruby-dev:40954]'
@@ -68,8 +69,8 @@ class TestEncoding < Test::Unit::TestCase
def test_extra_encoding
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
- 200.times {|i|
- Encoding::UTF_8.replicate("dummy#{i}")
+ 100.times {|i|
+ EnvUtil.suppress_warning { Encoding::UTF_8.replicate("dummy#{i}") }
}
e = Encoding.list.last
format = "%d".force_encoding(e)
@@ -82,7 +83,7 @@ class TestEncoding < Test::Unit::TestCase
name = "A" * 64
Encoding.list.each do |enc|
- assert_raise(ArgumentError) {enc.replicate(name)}
+ assert_raise(ArgumentError) { EnvUtil.suppress_warning { enc.replicate(name) } }
name.succ!
end
end;
@@ -159,4 +160,18 @@ class TestEncoding < Test::Unit::TestCase
end
end;
end
+
+ def test_exceed_encoding_table_size
+ assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ begin
+ enc = Encoding::UTF_8
+ 1_000.times{|i| EnvUtil.suppress_warning{ enc.replicate("R_#{i}") } } # now 256 entries
+ rescue EncodingError => e
+ assert_match(/too many encoding/, e.message)
+ else
+ assert false
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb
index 4674f984ff..cc217b05cc 100644
--- a/test/ruby/test_enum.rb
+++ b/test/ruby/test_enum.rb
@@ -134,6 +134,12 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal([1, 2, 3, 1, 2], @obj.to_a)
end
+ def test_to_a_keywords
+ @obj.singleton_class.remove_method(:each)
+ def @obj.each(foo:) yield foo end
+ assert_equal([1], @obj.to_a(foo: 1))
+ end
+
def test_to_a_size_symbol
sym = Object.new
class << sym
@@ -228,6 +234,8 @@ class TestEnumerable < Test::Unit::TestCase
assert_equal(24, @obj.inject(2) {|z, x| z * x })
assert_equal(24, assert_warning(/given block not used/) {@obj.inject(2, :*) {|z, x| z * x }})
assert_equal(nil, @empty.inject() {9})
+
+ assert_raise(ArgumentError) {@obj.inject}
end
FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
@@ -455,6 +463,17 @@ class TestEnumerable < Test::Unit::TestCase
empty.first
empty.block.call
end;
+
+ bug18475 = '[ruby-dev:107059]'
+ assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475)
+ e = Enumerator.new do |g|
+ Thread.new do
+ g << 1
+ end.join
+ end
+
+ e.first
+ end;
end
def test_sort
@@ -731,6 +750,9 @@ class TestEnumerable < Test::Unit::TestCase
ary.clear
(1..10).each_slice(11) {|a| ary << a}
assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary)
+
+ assert_equal(1..10, (1..10).each_slice(3) { })
+ assert_equal([], [].each_slice(3) { })
end
def test_each_cons
@@ -750,6 +772,9 @@ class TestEnumerable < Test::Unit::TestCase
ary.clear
(1..5).each_cons(6) {|a| ary << a}
assert_empty(ary)
+
+ assert_equal(1..5, (1..5).each_cons(3) { })
+ assert_equal([], [].each_cons(3) { })
end
def test_zip
diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb
index c823b79c6d..bbaa91b703 100644
--- a/test/ruby/test_enumerator.rb
+++ b/test/ruby/test_enumerator.rb
@@ -244,6 +244,26 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal(res, exc.result)
end
+ def test_stopiteration_rescue
+ e = [1].each
+ res = e.each {}
+ e.next
+ exc0 = assert_raise(StopIteration) { e.peek }
+ assert_include(exc0.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_nil(exc0.cause)
+ assert_equal(res, exc0.result)
+
+ exc1 = assert_raise(StopIteration) { e.next }
+ assert_include(exc1.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc1.cause)
+ assert_equal(res, exc1.result)
+
+ exc2 = assert_raise(StopIteration) { e.next }
+ assert_include(exc2.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:")
+ assert_same(exc0, exc2.cause)
+ assert_equal(res, exc2.result)
+ end
+
def test_next_values
o = Object.new
def o.each
@@ -906,4 +926,86 @@ class TestEnumerator < Test::Unit::TestCase
e.chain.each(&->{})
assert_equal(true, e.is_lambda)
end
+
+ def test_product
+ ##
+ ## Enumerator::Product
+ ##
+
+ # 0-dimensional
+ e = Enumerator::Product.new
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(1, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [nil], heads
+
+ # 1-dimensional
+ e = Enumerator::Product.new(1..3)
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, e.to_a
+
+ # 2-dimensional
+ e = Enumerator::Product.new(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+ assert_kind_of(Enumerator, e)
+ assert_equal(3 * 2, e.size)
+ elts = []
+ e.each { |x| elts << x }
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts
+ assert_equal elts, e.to_a
+ heads = []
+ e.each { |x,| heads << x }
+ assert_equal [1, 1, 2, 2, 3, 3], heads
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator::Product.new(1..3, foo: 1, bar: 2)
+ }
+
+ ##
+ ## Enumerator.product
+ ##
+
+ # without a block
+ e = Enumerator.product(1..3, %w[a b])
+ assert_instance_of(Enumerator::Product, e)
+
+ # with a block
+ elts = []
+ ret = Enumerator.product(1..3) { |x| elts << x }
+ assert_equal(nil, ret)
+ assert_equal [[1], [2], [3]], elts
+ assert_equal elts, Enumerator.product(1..3).to_a
+
+ # an infinite enumerator and a finite enumerable
+ e = Enumerator.product(1.., 'a'..'c')
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(Float::INFINITY, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ # an infinite enumerator and an unknown enumerator
+ e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' })
+ assert_equal(nil, e.size)
+ assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4)
+
+ # Reject keyword arguments
+ assert_raise(ArgumentError) {
+ Enumerator.product(1..3, foo: 1, bar: 2)
+ }
+ end
end
diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb
index ea0f367334..cdadeac148 100644
--- a/test/ruby/test_env.rb
+++ b/test/ruby/test_env.rb
@@ -69,25 +69,20 @@ class TestEnv < Test::Unit::TestCase
end
def test_clone
- warning = /ENV\.clone is deprecated; use ENV\.to_h instead/
- clone = assert_deprecated_warning(warning) {
+ message = /Cannot clone ENV/
+ assert_raise_with_message(TypeError, message) {
ENV.clone
}
- assert_same(ENV, clone)
-
- clone = assert_deprecated_warning(warning) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: false)
}
- assert_same(ENV, clone)
-
- clone = assert_deprecated_warning(warning) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: nil)
}
- assert_same(ENV, clone)
-
- assert_raise(TypeError) {
+ assert_raise_with_message(TypeError, message) {
ENV.clone(freeze: true)
}
+
assert_raise(ArgumentError) {
ENV.clone(freeze: 1)
}
@@ -494,16 +489,24 @@ class TestEnv < Test::Unit::TestCase
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"})
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"})
+ check(ENV.to_hash.to_a, [%w(foo zot), %w(baz quux), %w(a b), %w(c d)])
ENV.clear
ENV["foo"] = "bar"
ENV["baz"] = "qux"
ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ ENV.update({"foo"=>"zot"}, {"c"=>"d"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ check(ENV.to_hash.to_a, [%w(foo foo_bar_zot), %w(baz baz_qux_quux), %w(a b), %w(c d)])
end
def test_huge_value
- if /mswin/ =~ RUBY_PLATFORM || /ucrt/ =~ RbConfig::CONFIG['sitearch']
+ if /mswin|ucrt/ =~ RUBY_PLATFORM
# On Windows >= Vista each environment variable can be max 32768 characters
huge_value = "bar" * 10900
else
@@ -578,6 +581,861 @@ class TestEnv < Test::Unit::TestCase
assert_nil(e1, bug12475)
end
+ def ignore_case_str
+ IGNORE_CASE ? "true" : "false"
+ end
+
+ def str_for_yielding_exception_class(code_str, exception_var: "raised_exception")
+ <<-"end;"
+ #{exception_var} = nil
+ begin
+ #{code_str}
+ rescue Exception => e
+ #{exception_var} = e
+ end
+ Ractor.yield #{exception_var}.class
+ end;
+ end
+
+ def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var)
+ <<-"end;"
+ error_class = #{ractor_var}.take
+ assert_raise(#{expected_error_class}) do
+ if error_class < Exception
+ raise error_class
+ end
+ end
+ end;
+ end
+
+ def str_to_yield_invalid_envvar_errors(var_name, code_str)
+ <<-"end;"
+ envvars_to_check = [
+ "foo\0bar",
+ "#{'\xa1\xa1'}".force_encoding(Encoding::UTF_16LE),
+ "foo".force_encoding(Encoding::ISO_2022_JP),
+ ]
+ envvars_to_check.each do |#{var_name}|
+ #{str_for_yielding_exception_class(code_str)}
+ end
+ end;
+ end
+
+ def str_to_receive_invalid_envvar_errors(ractor_var)
+ <<-"end;"
+ 3.times do
+ #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, ractor_var)}
+ end
+ end;
+ end
+
+ STR_DEFINITION_FOR_CHECK = %Q{
+ def check(as, bs)
+ if #{IGNORE_CASE ? "true" : "false"}
+ as = as.map {|k, v| [k.upcase, v] }
+ bs = bs.map {|k, v| [k.upcase, v] }
+ end
+ assert_equal(as.sort, bs.sort)
+ end
+ }
+
+ def test_bracket_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ Ractor.yield ENV['test']
+ Ractor.yield ENV['TEST']
+ ENV['test'] = 'foo'
+ Ractor.yield ENV['test']
+ Ractor.yield ENV['TEST']
+ ENV['TEST'] = 'bar'
+ Ractor.yield ENV['TEST']
+ Ractor.yield ENV['test']
+ #{str_for_yielding_exception_class("ENV[1]")}
+ #{str_for_yielding_exception_class("ENV[1] = 'foo'")}
+ #{str_for_yielding_exception_class("ENV['test'] = 0")}
+ end
+ assert_nil(r.take)
+ assert_nil(r.take)
+ assert_equal('foo', r.take)
+ if #{ignore_case_str}
+ assert_equal('foo', r.take)
+ else
+ assert_nil(r.take)
+ end
+ assert_equal('bar', r.take)
+ if #{ignore_case_str}
+ assert_equal('bar', r.take)
+ else
+ assert_equal('foo', r.take)
+ end
+ 3.times do
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ end
+ end;
+ end
+
+ def test_dup_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ #{str_for_yielding_exception_class("ENV.dup")}
+ end
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ end;
+ end
+
+ def test_has_value_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ val = 'a'
+ val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
+ ENV['test'] = val[0...-1]
+ Ractor.yield(ENV.has_value?(val))
+ Ractor.yield(ENV.has_value?(val.upcase))
+ ENV['test'] = val
+ Ractor.yield(ENV.has_value?(val))
+ Ractor.yield(ENV.has_value?(val.upcase))
+ ENV['test'] = val.upcase
+ Ractor.yield ENV.has_value?(val)
+ Ractor.yield ENV.has_value?(val.upcase)
+ end
+ assert_equal(false, r.take)
+ assert_equal(false, r.take)
+ assert_equal(true, r.take)
+ assert_equal(false, r.take)
+ assert_equal(false, r.take)
+ assert_equal(true, r.take)
+ end;
+ end
+
+ def test_key_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ val = 'a'
+ val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
+ ENV['test'] = val[0...-1]
+ Ractor.yield ENV.key(val)
+ Ractor.yield ENV.key(val.upcase)
+ ENV['test'] = val
+ Ractor.yield ENV.key(val)
+ Ractor.yield ENV.key(val.upcase)
+ ENV['test'] = val.upcase
+ Ractor.yield ENV.key(val)
+ Ractor.yield ENV.key(val.upcase)
+ end
+ assert_nil(r.take)
+ assert_nil(r.take)
+ if #{ignore_case_str}
+ assert_equal('TEST', r.take.upcase)
+ else
+ assert_equal('test', r.take)
+ end
+ assert_nil(r.take)
+ assert_nil(r.take)
+ if #{ignore_case_str}
+ assert_equal('TEST', r.take.upcase)
+ else
+ assert_equal('test', r.take)
+ end
+ end;
+
+ end
+
+ def test_delete_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")}
+ Ractor.yield ENV.delete("TEST")
+ #{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")}
+ Ractor.yield(ENV.delete("TEST"){|name| "NO "+name})
+ end
+ #{str_to_receive_invalid_envvar_errors("r")}
+ assert_nil(r.take)
+ exception_class = r.take
+ assert_equal(NilClass, exception_class)
+ assert_equal("NO TEST", r.take)
+ end;
+ end
+
+ def test_getenv_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ #{str_to_yield_invalid_envvar_errors("v", "ENV[v]")}
+ ENV["#{PATH_ENV}"] = ""
+ Ractor.yield ENV["#{PATH_ENV}"]
+ Ractor.yield ENV[""]
+ end
+ #{str_to_receive_invalid_envvar_errors("r")}
+ assert_equal("", r.take)
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_fetch_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV["test"] = "foo"
+ Ractor.yield ENV.fetch("test")
+ ENV.delete("test")
+ #{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")}
+ Ractor.yield ex.receiver.object_id
+ Ractor.yield ex.key
+ Ractor.yield ENV.fetch("test", "foo")
+ Ractor.yield(ENV.fetch("test"){"bar"})
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")}
+ #{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")}
+ ENV['#{PATH_ENV}'] = ""
+ Ractor.yield ENV.fetch('#{PATH_ENV}')
+ end
+ assert_equal("foo", r.take)
+ #{str_for_assert_raise_on_yielded_exception_class(KeyError, "r")}
+ assert_equal(ENV.object_id, r.take)
+ assert_equal("test", r.take)
+ assert_equal("foo", r.take)
+ assert_equal("bar", r.take)
+ #{str_to_receive_invalid_envvar_errors("r")}
+ exception_class = r.take
+ assert_equal(NilClass, exception_class)
+ assert_equal("", r.take)
+ end;
+ end
+
+ def test_aset_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ #{str_for_yielding_exception_class("ENV['test'] = nil")}
+ ENV["test"] = nil
+ Ractor.yield ENV["test"]
+ #{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")}
+ #{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")}
+ end
+ exception_class = r.take
+ assert_equal(NilClass, exception_class)
+ assert_nil(r.take)
+ #{str_to_receive_invalid_envvar_errors("r")}
+ #{str_to_receive_invalid_envvar_errors("r")}
+ end;
+ end
+
+ def test_keys_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ a = ENV.keys
+ Ractor.yield a
+ end
+ a = r.take
+ assert_kind_of(Array, a)
+ a.each {|k| assert_kind_of(String, k) }
+ end;
+
+ end
+
+ def test_each_key_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.each_key {|k| Ractor.yield(k)}
+ Ractor.yield "finished"
+ end
+ while((x=r.take) != "finished")
+ assert_kind_of(String, x)
+ end
+ end;
+ end
+
+ def test_values_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ a = ENV.values
+ Ractor.yield a
+ end
+ a = r.take
+ assert_kind_of(Array, a)
+ a.each {|k| assert_kind_of(String, k) }
+ end;
+ end
+
+ def test_each_value_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.each_value {|k| Ractor.yield(k)}
+ Ractor.yield "finished"
+ end
+ while((x=r.take) != "finished")
+ assert_kind_of(String, x)
+ end
+ end;
+ end
+
+ def test_each_pair_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.each_pair {|k, v| Ractor.yield([k,v])}
+ Ractor.yield "finished"
+ end
+ while((k,v=r.take) != "finished")
+ assert_kind_of(String, k)
+ assert_kind_of(String, v)
+ end
+ end;
+ end
+
+ def test_reject_bang_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ Ractor.yield [h1, h2]
+ Ractor.yield(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" })
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_delete_if_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ Ractor.yield [h1, h2]
+ Ractor.yield (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }).object_id
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ assert_equal(ENV.object_id, r.take)
+ end;
+ end
+
+ def test_select_bang_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ Ractor.yield [h1, h2]
+ Ractor.yield(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_filter_bang_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ Ractor.yield [h1, h2]
+ Ractor.yield(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" })
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_keep_if_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }
+ h2 = {}
+ ENV.each_pair {|k, v| h2[k] = v }
+ Ractor.yield [h1, h2]
+ Ractor.yield (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }).object_id
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ assert_equal(ENV.object_id, r.take)
+ end;
+ end
+
+ def test_values_at_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV["test"] = "foo"
+ Ractor.yield ENV.values_at("test", "test")
+ end
+ assert_equal(["foo", "foo"], r.take)
+ end;
+ end
+
+ def test_select_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV["test"] = "foo"
+ h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ Ractor.yield h.size
+ k = h.keys.first
+ v = h.values.first
+ Ractor.yield [k, v]
+ end
+ assert_equal(1, r.take)
+ k, v = r.take
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_filter_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV["test"] = "foo"
+ h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ Ractor.yield(h.size)
+ k = h.keys.first
+ v = h.values.first
+ Ractor.yield [k, v]
+ end
+ assert_equal(1, r.take)
+ k, v = r.take
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_slice_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ Ractor.yield(ENV.slice())
+ Ractor.yield(ENV.slice(""))
+ Ractor.yield(ENV.slice("unknown"))
+ Ractor.yield(ENV.slice("foo", "baz"))
+ end
+ assert_equal({}, r.take)
+ assert_equal({}, r.take)
+ assert_equal({}, r.take)
+ assert_equal({"foo"=>"bar", "baz"=>"qux"}, r.take)
+ end;
+ end
+
+ def test_except_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV["bar"] = "rab"
+ Ractor.yield ENV.except()
+ Ractor.yield ENV.except("")
+ Ractor.yield ENV.except("unknown")
+ Ractor.yield ENV.except("foo", "baz")
+ end
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
+ assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take)
+ assert_equal({"bar"=>"rab"}, r.take)
+ end;
+ end
+
+ def test_clear_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ Ractor.yield ENV.size
+ end
+ assert_equal(0, r.take)
+ end;
+ end
+
+ def test_to_s_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.to_s
+ end
+ assert_equal("ENV", r.take)
+ end;
+ end
+
+ def test_inspect_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ s = ENV.inspect
+ Ractor.yield s
+ end
+ s = r.take
+ if #{ignore_case_str}
+ s = s.upcase
+ assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}')
+ else
+ assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}')
+ end
+ end;
+ end
+
+ def test_to_a_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ a = ENV.to_a
+ Ractor.yield a
+ end
+ a = r.take
+ assert_equal(2, a.size)
+ if #{ignore_case_str}
+ a = a.map {|x| x.map {|y| y.upcase } }
+ assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)])
+ else
+ assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)])
+ end
+ end;
+ end
+
+ def test_rehash_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.rehash
+ end
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_size_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ s = ENV.size
+ ENV["test"] = "foo"
+ Ractor.yield [s, ENV.size]
+ end
+ s, s2 = r.take
+ assert_equal(s + 1, s2)
+ end;
+ end
+
+ def test_empty_p_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ Ractor.yield ENV.empty?
+ ENV["test"] = "foo"
+ Ractor.yield ENV.empty?
+ end
+ assert r.take
+ assert !r.take
+ end;
+ end
+
+ def test_has_key_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ Ractor.yield ENV.has_key?("test")
+ ENV["test"] = "foo"
+ Ractor.yield ENV.has_key?("test")
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")}
+ end
+ assert !r.take
+ assert r.take
+ #{str_to_receive_invalid_envvar_errors("r")}
+ end;
+ end
+
+ def test_assoc_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ Ractor.yield ENV.assoc("test")
+ ENV["test"] = "foo"
+ Ractor.yield ENV.assoc("test")
+ #{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")}
+ end
+ assert_nil(r.take)
+ k, v = r.take
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ #{str_to_receive_invalid_envvar_errors("r")}
+ encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale")
+ assert_equal(encoding, v.encoding)
+ end;
+ end
+
+ def test_has_value2_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ Ractor.yield ENV.has_value?("foo")
+ ENV["test"] = "foo"
+ Ractor.yield ENV.has_value?("foo")
+ end
+ assert !r.take
+ assert r.take
+ end;
+ end
+
+ def test_rassoc_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV.clear
+ Ractor.yield ENV.rassoc("foo")
+ ENV["foo"] = "bar"
+ ENV["test"] = "foo"
+ ENV["baz"] = "qux"
+ Ractor.yield ENV.rassoc("foo")
+ end
+ assert_nil(r.take)
+ k, v = r.take
+ if #{ignore_case_str}
+ assert_equal("TEST", k.upcase)
+ assert_equal("FOO", v.upcase)
+ else
+ assert_equal("test", k)
+ assert_equal("foo", v)
+ end
+ end;
+ end
+
+ def test_to_hash_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h = {}
+ ENV.each {|k, v| h[k] = v }
+ Ractor.yield [h, ENV.to_hash]
+ end
+ h, h2 = r.take
+ assert_equal(h, h2)
+ end;
+ end
+
+ def test_to_h_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ Ractor.yield [ENV.to_hash, ENV.to_h]
+ Ractor.yield [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}]
+ end
+ a, b = r.take
+ assert_equal(a,b)
+ c, d = r.take
+ assert_equal(c,d)
+ end;
+ end
+
+ def test_reject_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ h1 = {}
+ ENV.each_pair {|k, v| h1[k] = v }
+ ENV["test"] = "foo"
+ h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }
+ Ractor.yield [h1, h2]
+ end
+ h1, h2 = r.take
+ assert_equal(h1, h2)
+ end;
+ end
+
+ def test_shift_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ a = ENV.shift
+ b = ENV.shift
+ Ractor.yield [a,b]
+ Ractor.yield ENV.shift
+ end
+ a,b = r.take
+ check([a, b], [%w(foo bar), %w(baz qux)])
+ assert_nil(r.take)
+ end;
+ end
+
+ def test_invert_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ Ractor.yield(ENV.invert)
+ end
+ check(r.take.to_a, [%w(bar foo), %w(qux baz)])
+ end;
+ end
+
+ def test_replace_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ r = Ractor.new do
+ ENV["foo"] = "xxx"
+ ENV.replace({"foo"=>"bar", "baz"=>"qux"})
+ Ractor.yield ENV.to_hash
+ ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
+ Ractor.yield ENV.to_hash
+ end
+ check(r.take.to_a, [%w(foo bar), %w(baz qux)])
+ check(r.take.to_a, [%w(Foo Bar), %w(Baz Qux)])
+ end;
+ end
+
+ def test_update_in_ractor
+ assert_ractor(<<-"end;")
+ #{STR_DEFINITION_FOR_CHECK}
+ r = Ractor.new do
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV.update({"baz"=>"quux","a"=>"b"})
+ Ractor.yield ENV.to_hash
+ ENV.clear
+ ENV["foo"] = "bar"
+ ENV["baz"] = "qux"
+ ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 }
+ Ractor.yield ENV.to_hash
+ end
+ check(r.take.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
+ check(r.take.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
+ end;
+ end
+
+ def test_huge_value_in_ractor
+ assert_ractor(<<-"end;")
+ huge_value = "bar" * 40960
+ r = Ractor.new huge_value do |v|
+ ENV["foo"] = "bar"
+ #{str_for_yielding_exception_class("ENV['foo'] = v ")}
+ Ractor.yield ENV["foo"]
+ end
+
+ if /mswin|ucrt/ =~ RUBY_PLATFORM
+ #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "r")}
+ result = r.take
+ assert_equal("bar", result)
+ else
+ exception_class = r.take
+ assert_equal(NilClass, exception_class)
+ result = r.take
+ assert_equal(huge_value, result)
+ end
+ end;
+ end
+
+ def test_frozen_env_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ #{str_for_yielding_exception_class("ENV.freeze")}
+ end
+ #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")}
+ end;
+ end
+
+ def test_frozen_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ ENV["#{PATH_ENV}"] = "/"
+ ENV.each do |k, v|
+ Ractor.yield [k.frozen?]
+ Ractor.yield [v.frozen?]
+ end
+ ENV.each_key do |k|
+ Ractor.yield [k.frozen?]
+ end
+ ENV.each_value do |v|
+ Ractor.yield [v.frozen?]
+ end
+ ENV.each_key do |k|
+ Ractor.yield [ENV[k].frozen?, "[\#{k.dump}]"]
+ Ractor.yield [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"]
+ end
+ Ractor.yield "finished"
+ end
+ while((params=r.take) != "finished")
+ assert(*params)
+ end
+ end;
+ end
+
+ def test_shared_substring_in_ractor
+ assert_ractor(<<-"end;")
+ r = Ractor.new do
+ bug12475 = '[ruby-dev:49655] [Bug #12475]'
+ n = [*"0".."9"].join("")*3
+ e0 = ENV[n0 = "E\#{n}"]
+ e1 = ENV[n1 = "E\#{n}."]
+ ENV[n0] = nil
+ ENV[n1] = nil
+ ENV[n1.chop] = "T\#{n}.".chop
+ ENV[n0], e0 = e0, ENV[n0]
+ ENV[n1], e1 = e1, ENV[n1]
+ Ractor.yield [n, e0, e1, bug12475]
+ end
+ n, e0, e1, bug12475 = r.take
+ assert_equal("T\#{n}", e0, bug12475)
+ assert_nil(e1, bug12475)
+ end;
+ end
+
+ def test_ivar_in_env_should_not_be_access_from_non_main_ractors
+ assert_ractor <<~RUBY
+ ENV.instance_eval{ @a = "hello" }
+ assert_equal "hello", ENV.instance_variable_get(:@a)
+
+ r_get = Ractor.new do
+ ENV.instance_variable_get(:@a)
+ rescue Ractor::IsolationError => e
+ e
+ end
+ assert_equal Ractor::IsolationError, r_get.take.class
+
+ r_get = Ractor.new do
+ ENV.instance_eval{ @a }
+ rescue Ractor::IsolationError => e
+ e
+ end
+
+ assert_equal Ractor::IsolationError, r_get.take.class
+
+ r_set = Ractor.new do
+ ENV.instance_eval{ @b = "hello" }
+ rescue Ractor::IsolationError => e
+ e
+ end
+
+ assert_equal Ractor::IsolationError, r_set.take.class
+ RUBY
+ end
+
if RUBY_PLATFORM =~ /bccwin|mswin|mingw/
def test_memory_leak_aset
bug9977 = '[ruby-dev:48323] [Bug #9977]'
diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb
index bf551c6845..d55977c986 100644
--- a/test/ruby/test_eval.rb
+++ b/test/ruby/test_eval.rb
@@ -219,6 +219,12 @@ class TestEval < Test::Unit::TestCase
end
end
+ def test_instance_exec_cvar
+ [Object.new, [], 7, :sym, true, false, nil].each do |obj|
+ assert_equal(13, obj.instance_exec{@@cvar})
+ end
+ end
+
def test_instance_eval_method
bug2788 = '[ruby-core:28324]'
[Object.new, [], nil, true, false].each do |o|
@@ -253,6 +259,70 @@ class TestEval < Test::Unit::TestCase
assert_equal(2, bar)
end
+ def test_instance_exec_block_basic
+ forall_TYPE do |o|
+ assert_equal nil, o.instance_exec { nil }
+ assert_equal true, o.instance_exec { true }
+ assert_equal false, o.instance_exec { false }
+ assert_equal o, o.instance_exec { self }
+ assert_equal 1, o.instance_exec { 1 }
+ assert_equal :sym, o.instance_exec { :sym }
+
+ assert_equal 11, o.instance_exec { 11 }
+ assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
+ assert_equal 13, o.instance_exec { @@cvar }
+ assert_equal 14, o.instance_exec { $gvar__eval }
+ assert_equal 15, o.instance_exec { Const }
+ assert_equal 16, o.instance_exec { 7 + 9 }
+ assert_equal 17, o.instance_exec { 17.to_i }
+ assert_equal "18", o.instance_exec { "18" }
+ assert_equal "19", o.instance_exec { "1#{9}" }
+
+ 1.times {
+ assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
+ assert_equal 13, o.instance_exec { @@cvar }
+ assert_equal 14, o.instance_exec { $gvar__eval }
+ assert_equal 15, o.instance_exec { Const }
+ }
+ end
+ end
+
+ def test_instance_exec_method_definition
+ klass = Class.new
+ o = klass.new
+
+ o.instance_exec do
+ def foo
+ :foo_result
+ end
+ end
+
+ assert_respond_to o, :foo
+ refute_respond_to klass, :foo
+ refute_respond_to klass.new, :foo
+
+ assert_equal :foo_result, o.foo
+ end
+
+ def test_instance_exec_eval_method_definition
+ klass = Class.new
+ o = klass.new
+
+ o.instance_exec do
+ eval %{
+ def foo
+ :foo_result
+ end
+ }
+ end
+
+ assert_respond_to o, :foo
+ refute_respond_to klass, :foo
+ refute_respond_to klass.new, :foo
+
+ assert_equal :foo_result, o.foo
+ end
+
#
# From ruby/test/ruby/test_eval.rb
#
diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb
index 67f38c2e91..c2eada0bfb 100644
--- a/test/ruby/test_exception.rb
+++ b/test/ruby/test_exception.rb
@@ -252,6 +252,27 @@ class TestException < Test::Unit::TestCase
}
end
+ def test_catch_throw_in_require_cant_be_rescued
+ bug18562 = '[ruby-core:107403]'
+ Tempfile.create(["dep", ".rb"]) {|t|
+ t.puts("throw :extdep, 42")
+ t.close
+
+ rescue_all = Class.new(Exception)
+ def rescue_all.===(_)
+ raise "should not reach here"
+ end
+
+ v = assert_throw(:extdep, bug18562) do
+ require t.path
+ rescue rescue_all
+ assert(false, "should not reach here")
+ end
+
+ assert_equal(42, v, bug18562)
+ }
+ end
+
def test_throw_false
bug12743 = '[ruby-core:77229] [Bug #12743]'
Thread.start {
@@ -423,7 +444,7 @@ class TestException < Test::Unit::TestCase
end
def test_thread_signal_location
- skip
+ # pend('TODO: a known bug [Bug #14474]')
_, stderr, _ = EnvUtil.invoke_ruby(%w"--disable-gems -d", <<-RUBY, false, true)
Thread.start do
Thread.current.report_on_exception = false
@@ -457,6 +478,12 @@ end.join
def to_s; ""; end
end
assert_equal(e.inspect, e.new.inspect)
+
+ # https://bugs.ruby-lang.org/issues/18170#note-13
+ assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect)
+ assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect)
+ assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect)
+ assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').inspect)
end
def test_to_s
@@ -561,7 +588,7 @@ end.join
end
def test_ensure_after_nomemoryerror
- skip "Forcing NoMemoryError causes problems in some environments"
+ omit "Forcing NoMemoryError causes problems in some environments"
assert_separately([], "$_ = 'a' * 1_000_000_000_000_000_000")
rescue NoMemoryError
assert_raise(NoMemoryError) do
@@ -786,6 +813,7 @@ end.join
cause = ArgumentError.new("foobar")
e = assert_raise(RuntimeError) {raise msg, cause: cause}
assert_same(cause, e.cause)
+ assert_raise(TypeError) {raise msg, {cause: cause}}
end
def test_cause_with_no_arguments
@@ -970,11 +998,12 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
assert_not_nil(e)
assert_include(e.message, "\0")
- assert_in_out_err([], src, [], [], *args, **opts) do |_, err,|
- err.each do |e|
- assert_not_include(e, "\0")
- end
- end
+ # Disabled by [Feature #18367]
+ #assert_in_out_err([], src, [], [], *args, **opts) do |_, err,|
+ # err.each do |e|
+ # assert_not_include(e, "\0")
+ # end
+ #end
e
end
@@ -1030,7 +1059,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
warning << [str, category]
end
else
- define_method(:warn) do |str|
+ define_method(:warn) do |str, category: nil|
warning << str
end
end
@@ -1064,25 +1093,17 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
def test_warn_deprecated_backwards_compatibility_category
- warning = capture_warning_warn { Dir.exists?("non-existent") }
+ omit "no method to test"
+
+ warning = capture_warning_warn { }
assert_match(/deprecated/, warning[0])
end
def test_warn_deprecated_category
- warning = capture_warning_warn(category: true) { Dir.exists?("non-existent") }
-
- assert_equal :deprecated, warning[0][1]
- end
-
- def test_warn_deprecated_to_remove_backwards_compatibility_category
- warning = capture_warning_warn { Object.new.tainted? }
-
- assert_match(/deprecated/, warning[0])
- end
+ omit "no method to test"
- def test_warn_deprecated_to_remove_category
- warning = capture_warning_warn(category: true) { Object.new.tainted? }
+ warning = capture_warning_warn(category: true) { }
assert_equal :deprecated, warning[0][1]
end
@@ -1200,6 +1221,14 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
assert_empty warning
end
+ def test_undef_Warning_warn
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ Warning.undef_method(:warn)
+ assert_raise(NoMethodError) { warn "" }
+ end;
+ end
+
def test_undefined_backtrace
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
@@ -1383,6 +1412,18 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end;
end
+ def test_marshal_circular_cause
+ begin
+ raise RuntimeError, "err", [], cause: Exception.new
+ rescue => e
+ end
+ dump = Marshal.dump(e).sub(/o:\x0EException\x08;.0;.0;.0/, "@\x05")
+ assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do
+ e = Marshal.load(dump)
+ assert_same(e, e.cause)
+ end
+ end
+
def test_super_in_method_missing
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
@@ -1399,4 +1440,86 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status|
end
end;
end
+
+ def test_detailed_message
+ e = RuntimeError.new("message")
+ assert_equal("message (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mmessage (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("foo\nbar\nbaz")
+ assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message)
+ assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new("")
+ assert_equal("unhandled exception", e.detailed_message)
+ assert_equal("\e[1;4munhandled exception\e[m", e.detailed_message(highlight: true))
+
+ e = RuntimeError.new
+ assert_equal("RuntimeError (RuntimeError)", e.detailed_message)
+ assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true))
+ end
+
+ def test_full_message_with_custom_detailed_message
+ e = RuntimeError.new("message")
+ opt_ = nil
+ e.define_singleton_method(:detailed_message) do |**opt|
+ opt_ = opt
+ "BOO!"
+ end
+ assert_match("BOO!", e.full_message.lines.first)
+ assert_equal({ highlight: Exception.to_tty? }, opt_)
+ end
+
+ def test_syntax_error_detailed_message
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ Thread.new {}.join
+ "<#{super}>\n""<#{File.basename(__FILE__)}>"
+ rescue ThreadError => e
+ e.message
+ end
+ end
+ end;
+ pattern = /^<detail\.rb>/
+ assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern)
+
+ File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ 1 +
+ end;
+ assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,|
+ assert_empty(stdout)
+ assert_not_empty(stderr.grep(pattern))
+ error, = stderr.grep(/unexpected end-of-input/)
+ assert_not_nil(error)
+ assert_match(/<.*unexpected end-of-input.*>/, error)
+ end
+ end
+ end
+
+ def test_syntax_error_path
+ e = assert_raise(SyntaxError) {
+ eval("1+", nil, "test_syntax_error_path.rb")
+ }
+ assert_equal("test_syntax_error_path.rb", e.path)
+
+ Dir.mktmpdir do |dir|
+ lib = File.join(dir, "syntax_error-path.rb")
+ File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ class SyntaxError
+ def detailed_message(**)
+ STDERR.puts "\n""path=#{path}\n"
+ super
+ end
+ end
+ end;
+ main = File.join(dir, "syntax_error.rb")
+ File.write(main, "1+\n")
+ assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*])
+ end
+ end
end
diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb
index 67fef33b85..5825aaf6cc 100644
--- a/test/ruby/test_fiber.rb
+++ b/test/ruby/test_fiber.rb
@@ -34,7 +34,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers
- skip 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
+ omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
max = 1000
assert_equal(max, max.times{
Fiber.new{}
@@ -50,7 +50,7 @@ class TestFiber < Test::Unit::TestCase
end
def test_many_fibers_with_threads
- assert_normal_exit <<-SRC, timeout: (/solaris/i =~ RUBY_PLATFORM ? 300 : 60)
+ assert_normal_exit <<-SRC, timeout: (/solaris/i =~ RUBY_PLATFORM ? 1000 : 60)
max = 1000
@cnt = 0
(1..100).map{|ti|
@@ -381,7 +381,7 @@ class TestFiber < Test::Unit::TestCase
def test_fork_from_fiber
- skip 'fork not supported' unless Process.respond_to?(:fork)
+ omit 'fork not supported' unless Process.respond_to?(:fork)
pid = nil
bug5700 = '[ruby-core:41456]'
assert_nothing_raised(bug5700) do
@@ -396,7 +396,7 @@ class TestFiber < Test::Unit::TestCase
Fiber.new {}.transfer
Fiber.new { Fiber.yield }
end
- exit!(0)
+ exit!(true)
end
}.transfer
_, status = Process.waitpid2(xpid)
@@ -405,8 +405,13 @@ class TestFiber < Test::Unit::TestCase
end.resume
end
pid, status = Process.waitpid2(pid)
- assert_equal(0, status.exitstatus, bug5700)
- assert_equal(false, status.signaled?, bug5700)
+ assert_not_predicate(status, :signaled?, bug5700)
+ assert_predicate(status, :success?, bug5700)
+
+ pid = Fiber.new {fork}.resume
+ pid, status = Process.waitpid2(pid)
+ assert_not_predicate(status, :signaled?)
+ assert_predicate(status, :success?)
end
def test_exit_in_fiber
diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb
index d570b6f6fb..409d21fc4e 100644
--- a/test/ruby/test_file.rb
+++ b/test/ruby/test_file.rb
@@ -273,7 +273,7 @@ class TestFile < Test::Unit::TestCase
begin
File.symlink(tst, a)
rescue Errno::EACCES, Errno::EPERM
- skip "need privilege"
+ omit "need privilege"
end
assert_equal(File.join(realdir, tst), File.realpath(a))
File.unlink(a)
@@ -317,7 +317,7 @@ class TestFile < Test::Unit::TestCase
Dir.mktmpdir('rubytest-realpath') {|tmpdir|
Dir.chdir(tmpdir) do
Dir.mkdir('foo')
- skip "cannot run mklink" unless system('mklink /j bar foo > nul')
+ omit "cannot run mklink" unless system('mklink /j bar foo > nul')
assert_equal(File.realpath('foo'), File.realpath('bar'))
end
}
@@ -460,6 +460,48 @@ class TestFile < Test::Unit::TestCase
end
end
+ def test_file_open_newline_option
+ Dir.mktmpdir(__method__.to_s) do |tmpdir|
+ path = File.join(tmpdir, "foo")
+ test = lambda do |newline|
+ File.open(path, "wt", newline: newline) do |f|
+ f.write "a\n"
+ f.puts "b"
+ end
+ File.binread(path)
+ end
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\r\nb\r\n", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+
+ test = lambda do |newline|
+ File.open(path, "rt", newline: newline) do |f|
+ f.read
+ end
+ end
+
+ File.binwrite(path, "a\nb\n")
+ assert_equal("a\nb\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\nb\n", test.(:crlf))
+ assert_equal("a\nb\n", test.(:cr))
+
+ File.binwrite(path, "a\r\nb\r\n")
+ assert_equal("a\r\nb\r\n", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ # Work on both Windows and non-Windows
+ assert_include(["a\r\nb\r\n", "a\nb\n"], test.(:crlf))
+ assert_equal("a\r\nb\r\n", test.(:cr))
+
+ File.binwrite(path, "a\rb\r")
+ assert_equal("a\rb\r", test.(:lf))
+ assert_equal("a\nb\n", test.(:universal))
+ assert_equal("a\rb\r", test.(:crlf))
+ assert_equal("a\rb\r", test.(:cr))
+ end
+ end
+
def test_open_nul
Dir.mktmpdir(__method__.to_s) do |tmpdir|
path = File.join(tmpdir, "foo")
@@ -475,17 +517,17 @@ class TestFile < Test::Unit::TestCase
begin
io = File.open(tmpdir, File::RDWR | File::TMPFILE)
rescue Errno::EINVAL
- skip 'O_TMPFILE not supported (EINVAL)'
+ omit 'O_TMPFILE not supported (EINVAL)'
rescue Errno::EISDIR
- skip 'O_TMPFILE not supported (EISDIR)'
+ omit 'O_TMPFILE not supported (EISDIR)'
rescue Errno::EOPNOTSUPP
- skip 'O_TMPFILE not supported (EOPNOTSUPP)'
+ omit 'O_TMPFILE not supported (EOPNOTSUPP)'
end
io.write "foo"
io.flush
assert_equal 3, io.size
- assert_raise(IOError) { io.path }
+ assert_nil io.path
ensure
io&.close
end
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 9dd05f6c93..d0472a0081 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -173,9 +173,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def chardev
- return @chardev if defined? @chardev
- @chardev = File::NULL == "/dev/null" ? "/dev/null" : nil
- @chardev
+ File::NULL
end
def blockdev
@@ -332,7 +330,7 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_file.not_chardev?(regular_file)
assert_file.not_chardev?(utf8_file)
assert_file.not_chardev?(nofile)
- assert_file.chardev?(chardev) if chardev
+ assert_file.chardev?(chardev)
end
def test_exist_p
@@ -640,7 +638,7 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_birthtime
- skip if RUBY_PLATFORM =~ /android/
+ omit if RUBY_PLATFORM =~ /android/
[regular_file, utf8_file].each do |file|
t1 = File.birthtime(file)
t2 = File.open(file) {|f| f.birthtime}
@@ -651,7 +649,7 @@ class TestFileExhaustive < Test::Unit::TestCase
# ignore unsupporting filesystems
rescue Errno::EPERM
# Docker prohibits statx syscall by the default.
- skip("statx(2) is prohibited by seccomp")
+ omit("statx(2) is prohibited by seccomp")
end
assert_raise(Errno::ENOENT) { File.birthtime(nofile) }
end if File.respond_to?(:birthtime)
@@ -773,7 +771,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_readlink_junction
base = File.basename(nofile)
err = IO.popen(%W"cmd.exe /c mklink /j #{base} .", chdir: @dir, err: %i[child out], &:read)
- skip err unless $?.success?
+ omit err unless $?.success?
assert_equal(@dir, File.readlink(nofile))
end
@@ -872,9 +870,10 @@ class TestFileExhaustive < Test::Unit::TestCase
bug9934 = '[ruby-core:63114] [Bug #9934]'
require "objspace"
path = File.expand_path("/foo")
- assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934)
+ assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
path = File.expand_path("/a"*25)
- assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934)
+ assert_operator(ObjectSpace.memsize_of(path), :<=,
+ (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934)
end
def test_expand_path_encoding
@@ -1119,7 +1118,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_expand_path_for_existent_username
user = ENV['USER']
- skip "ENV['USER'] is not set" unless user
+ omit "ENV['USER'] is not set" unless user
assert_equal(ENV['HOME'], File.expand_path("~#{user}"))
end unless DRIVE
@@ -1408,21 +1407,26 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_exclusive
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ timeout = EnvUtil.apply_timeout_scale(0.1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_EX)
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
assert(!f.flock(File::LOCK_SH|File::LOCK_NB))
end
end
end;
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
assert_raise(Timeout::Error) do
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
f.flock(File::LOCK_SH)
end
end
@@ -1434,21 +1438,26 @@ class TestFileExhaustive < Test::Unit::TestCase
end
def test_flock_shared
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
+ timeout = EnvUtil.apply_timeout_scale(0.1).to_s
File.open(regular_file, "r+") do |f|
f.flock(File::LOCK_SH)
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r") do |f|
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
assert(f.flock(File::LOCK_SH))
end
end
end;
- assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
- begin
+ assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ timeout = ARGV[1].to_f
open(ARGV[0], "r+") do |f|
assert_raise(Timeout::Error) do
- Timeout.timeout(0.1) do
+ Timeout.timeout(timeout) do
f.flock(File::LOCK_EX)
end
end
@@ -1506,6 +1515,31 @@ class TestFileExhaustive < Test::Unit::TestCase
assert_equal(File.executable?(f), test(?x, f), f)
assert_equal(File.executable_real?(f), test(?X, f), f)
assert_equal(File.zero?(f), test(?z, f), f)
+
+ stat = File.stat(f)
+ assert_equal(stat.atime, File.atime(f), f)
+ assert_equal(stat.ctime, File.ctime(f), f)
+ assert_equal(stat.mtime, File.mtime(f), f)
+ assert_bool_equal(stat.blockdev?, File.blockdev?(f), f)
+ assert_bool_equal(stat.chardev?, File.chardev?(f), f)
+ assert_bool_equal(stat.directory?, File.directory?(f), f)
+ assert_bool_equal(stat.file?, File.file?(f), f)
+ assert_bool_equal(stat.setgid?, File.setgid?(f), f)
+ assert_bool_equal(stat.grpowned?, File.grpowned?(f), f)
+ assert_bool_equal(stat.sticky?, File.sticky?(f), f)
+ assert_bool_equal(File.lstat(f).symlink?, File.symlink?(f), f)
+ assert_bool_equal(stat.owned?, File.owned?(f), f)
+ assert_bool_equal(stat.pipe?, File.pipe?(f), f)
+ assert_bool_equal(stat.readable?, File.readable?(f), f)
+ assert_bool_equal(stat.readable_real?, File.readable_real?(f), f)
+ assert_equal(stat.size?, File.size?(f), f)
+ assert_bool_equal(stat.socket?, File.socket?(f), f)
+ assert_bool_equal(stat.setuid?, File.setuid?(f), f)
+ assert_bool_equal(stat.writable?, File.writable?(f), f)
+ assert_bool_equal(stat.writable_real?, File.writable_real?(f), f)
+ assert_bool_equal(stat.executable?, File.executable?(f), f)
+ assert_bool_equal(stat.executable_real?, File.executable_real?(f), f)
+ assert_bool_equal(stat.zero?, File.zero?(f), f)
end
assert_equal(false, test(?-, @dir, fn1))
assert_equal(true, test(?-, fn1, fn1))
@@ -1615,7 +1649,7 @@ class TestFileExhaustive < Test::Unit::TestCase
def test_stat_chardev_p
assert_not_predicate(File::Stat.new(@dir), :chardev?)
assert_not_predicate(File::Stat.new(regular_file), :chardev?)
- assert_predicate(File::Stat.new(chardev), :chardev?) if chardev
+ assert_predicate(File::Stat.new(chardev), :chardev?)
end
def test_stat_readable_p
@@ -1758,4 +1792,8 @@ class TestFileExhaustive < Test::Unit::TestCase
dir = File.expand_path("/bar")
assert_equal(File.join(dir, "~foo"), File.absolute_path("~foo", dir))
end
+
+ def assert_bool_equal(expected, result, *messages)
+ assert_equal(expected, true & result, *messages)
+ end
end
diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb
index 4be2cfeeda..fdc5d28ed7 100644
--- a/test/ruby/test_float.rb
+++ b/test/ruby/test_float.rb
@@ -141,6 +141,9 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError){Float("1__1")}
assert_raise(ArgumentError){Float("1.")}
assert_raise(ArgumentError){Float("1.e+00")}
+ assert_raise(ArgumentError){Float("0x.1")}
+ assert_raise(ArgumentError){Float("0x1.")}
+ assert_raise(ArgumentError){Float("0x1.0")}
assert_raise(ArgumentError){Float("0x1.p+0")}
# add expected behaviour here.
assert_equal(10, Float("1_0"))
@@ -171,6 +174,24 @@ class TestFloat < Test::Unit::TestCase
assert_raise(ArgumentError, n += z + "A") {Float(n)}
assert_raise(ArgumentError, n += z + ".0") {Float(n)}
end
+
+ x = nil
+ 2000.times do
+ x = Float("0x"+"0"*30)
+ break unless x == 0.0
+ end
+ assert_equal(0.0, x, ->{"%a" % x})
+ x = nil
+ 2000.times do
+ begin
+ x = Float("0x1."+"0"*270)
+ rescue ArgumentError => e
+ raise unless /"0x1\.0{270}"/ =~ e.message
+ else
+ break
+ end
+ end
+ assert_nil(x, ->{"%a" % x})
end
def test_divmod
diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb
new file mode 100644
index 0000000000..2918a2afd8
--- /dev/null
+++ b/test/ruby/test_frozen.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+class TestFrozen < Test::Unit::TestCase
+ def test_setting_ivar_on_frozen_obj
+ obj = Object.new
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_obj_with_ivars
+ obj = Object.new
+ obj.instance_variable_set(:@a, 1)
+ obj.freeze
+ assert_raise(FrozenError) { obj.instance_variable_set(:@b, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string
+ str = "str"
+ str.freeze
+ assert_raise(FrozenError) { str.instance_variable_set(:@a, 1) }
+ end
+
+ def test_setting_ivar_on_frozen_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) }
+ end
+end
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index baf9971c48..bb136656ce 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -54,7 +54,7 @@ class TestGc < Test::Unit::TestCase
def test_start_full_mark
return unless use_rgengc?
- skip 'stress' if GC.stress
+ omit 'stress' if GC.stress
3.times { GC.start } # full mark and next time it should be minor mark
GC.start(full_mark: false)
@@ -65,7 +65,7 @@ class TestGc < Test::Unit::TestCase
end
def test_start_immediate_sweep
- skip 'stress' if GC.stress
+ omit 'stress' if GC.stress
GC.start(immediate_sweep: false)
assert_equal false, GC.latest_gc_info(:immediate_sweep)
@@ -117,7 +117,7 @@ class TestGc < Test::Unit::TestCase
end
def test_stat_single
- skip 'stress' if GC.stress
+ omit 'stress' if GC.stress
stat = GC.stat
assert_equal stat[:count], GC.stat(:count)
@@ -125,7 +125,7 @@ class TestGc < Test::Unit::TestCase
end
def test_stat_constraints
- skip 'stress' if GC.stress
+ omit 'stress' if GC.stress
stat = GC.stat
assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
@@ -139,8 +139,79 @@ class TestGc < Test::Unit::TestCase
end
end
+ def test_stat_heap
+ omit 'stress' if GC.stress
+
+ stat_heap = {}
+ stat = {}
+ # Initialize to prevent GC in future calls
+ GC.stat_heap(0, stat_heap)
+ GC.stat(stat)
+
+ GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
+ GC.stat_heap(i, stat_heap)
+ GC.stat(stat)
+
+ assert_equal GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] * (2**i), stat_heap[:slot_size]
+ assert_operator stat_heap[:heap_allocatable_pages], :<=, stat[:heap_allocatable_pages]
+ assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages]
+ assert_operator stat_heap[:heap_eden_slots], :>=, 0
+ assert_operator stat_heap[:heap_tomb_pages], :<=, stat[:heap_tomb_pages]
+ assert_operator stat_heap[:heap_tomb_slots], :>=, 0
+ assert_operator stat_heap[:total_allocated_pages], :>=, 0
+ assert_operator stat_heap[:total_freed_pages], :>=, 0
+ assert_operator stat_heap[:force_major_gc_count], :>=, 0
+ end
+
+ GC.stat_heap(0, stat_heap)
+ assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size)
+ assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size]
+
+ assert_raise(ArgumentError) { GC.stat_heap(-1) }
+ assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT]) }
+ end
+
+ def test_stat_heap_all
+ stat_heap_all = {}
+ stat_heap = {}
+
+ 2.times do
+ GC.stat_heap(0, stat_heap)
+ GC.stat_heap(nil, stat_heap_all)
+ end
+
+ GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
+ GC.stat_heap(i, stat_heap)
+
+ assert_equal stat_heap, stat_heap_all[i]
+ end
+
+ assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) }
+ end
+
+ def test_stat_heap_constraints
+ omit 'stress' if GC.stress
+
+ stat = GC.stat
+ stat_heap = GC.stat_heap
+ GC.stat(stat)
+ GC.stat_heap(nil, stat_heap)
+
+ stat_heap_sum = Hash.new(0)
+ stat_heap.values.each do |hash|
+ hash.each { |k, v| stat_heap_sum[k] += v }
+ end
+
+ assert_equal stat[:heap_allocatable_pages], stat_heap_sum[:heap_allocatable_pages]
+ assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages]
+ assert_equal stat[:heap_tomb_pages], stat_heap_sum[:heap_tomb_pages]
+ assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + stat_heap_sum[:heap_tomb_slots]
+ assert_equal stat[:total_allocated_pages], stat_heap_sum[:total_allocated_pages]
+ assert_equal stat[:total_freed_pages], stat_heap_sum[:total_freed_pages]
+ end
+
def test_latest_gc_info
- skip 'stress' if GC.stress
+ omit 'stress' if GC.stress
assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
GC.start
@@ -158,6 +229,7 @@ class TestGc < Test::Unit::TestCase
assert_equal :force, h[:major_by] if use_rgengc?
assert_equal :method, h[:gc_by]
assert_equal true, h[:immediate_sweep]
+ assert_equal true, h.key?(:need_major_by)
GC.stress = true
assert_equal :force, GC.latest_gc_info[:major_by]
@@ -175,6 +247,25 @@ class TestGc < Test::Unit::TestCase
assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")}
end
+ def test_latest_gc_info_need_major_by
+ return unless use_rgengc?
+ omit 'stress' if GC.stress
+
+ 3.times { GC.start }
+ assert_nil GC.latest_gc_info(:need_major_by)
+
+ # allocate objects until need_major_by is set or major GC happens
+ major_count = GC.stat(:major_gc_count)
+ objects = []
+ while GC.stat(:major_gc_count) == major_count && GC.latest_gc_info(:need_major_by).nil?
+ objects.append(100.times.map { '*' })
+ end
+
+ assert_not_nil GC.latest_gc_info(:need_major_by)
+ GC.start(full_mark: false) # should be upgraded to major
+ assert_not_nil GC.latest_gc_info(:major_by)
+ end
+
def test_stress_compile_send
assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "")
GC.stress = true
@@ -214,6 +305,11 @@ class TestGc < Test::Unit::TestCase
def test_gc_parameter
env = {
+ "RUBY_GC_HEAP_INIT_SLOTS" => "100"
+ }
+ assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[Bug #19284]")
+
+ env = {
"RUBY_GC_MALLOC_LIMIT" => "60000000",
"RUBY_GC_HEAP_INIT_SLOTS" => "100000"
}
@@ -280,7 +376,7 @@ class TestGc < Test::Unit::TestCase
end
def test_profiler_clear
- skip "for now"
+ omit "for now"
assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30
GC::Profiler.enable
@@ -331,6 +427,40 @@ class TestGc < Test::Unit::TestCase
eom
end
+ def test_thrashing_for_young_objects
+ # This test prevents bugs like [Bug #18929]
+
+ assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'RUBY'
+ # Grow the heap
+ @ary = 100_000.times.map { Object.new }
+
+ # Warmup to make sure heap stabilizes
+ 1_000_000.times { Object.new }
+
+ before_stats = GC.stat
+ before_stat_heap = GC.stat_heap
+
+ 1_000_000.times { Object.new }
+
+ # Previous loop may have caused GC to be in an intermediate state,
+ # running a minor GC here will guarantee that GC will be complete
+ GC.start(full_mark: false)
+
+ after_stats = GC.stat
+ after_stat_heap = GC.stat_heap
+
+ # Debugging output to for failures in trunk-repeat50@phosphorus-docker
+ debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}"
+
+ # Should not be thrashing in page creation
+ assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg
+ assert_equal 0, after_stats[:heap_tomb_pages], debug_msg
+ assert_equal 0, after_stats[:total_freed_pages], debug_msg
+ # Only young objects, so should not trigger major GC
+ assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg
+ RUBY
+ end
+
def test_gc_internals
assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
@@ -479,6 +609,15 @@ class TestGc < Test::Unit::TestCase
obj = nil
end
end;
+
+ assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #20042]'
+ begin;
+ def (f = Object.new).call = nil # missing ID
+ o = Object.new
+ ObjectSpace.define_finalizer(o, f)
+ o = nil
+ GC.start
+ end;
end
def test_object_ids_never_repeat
diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb
index 46683d0ed5..d53e61d252 100644
--- a/test/ruby/test_gc_compact.rb
+++ b/test/ruby/test_gc_compact.rb
@@ -3,32 +3,31 @@ require 'test/unit'
require 'fiddle'
require 'etc'
+if RUBY_PLATFORM =~ /s390x/
+ warn "Currently, it is known that the compaction does not work well on s390x; contribution is welcome https://github.com/ruby/ruby/pull/5077"
+ return
+end
+
class TestGCCompact < Test::Unit::TestCase
- module SupportsCompact
- def setup
- skip "autocompact not supported on this platform" unless supports_auto_compact?
- super
+ module CompactionSupportInspector
+ def supports_auto_compact?
+ GC::OPTS.include?("GC_COMPACTION_SUPPORTED")
end
+ end
- private
-
- def supports_auto_compact?
- return true unless defined?(Etc::SC_PAGE_SIZE)
+ module OmitUnlessCompactSupported
+ include CompactionSupportInspector
- begin
- return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0
- rescue NotImplementedError
- rescue ArgumentError
- end
-
- true
+ def setup
+ omit "autocompact not supported on this platform" unless supports_auto_compact?
+ super
end
end
- include SupportsCompact
+ include OmitUnlessCompactSupported
class AutoCompact < Test::Unit::TestCase
- include SupportsCompact
+ include OmitUnlessCompactSupported
def test_enable_autocompact
before = GC.auto_compact
@@ -73,7 +72,7 @@ class TestGCCompact < Test::Unit::TestCase
n.times do
break if count < GC.stat(:compact_count)
list2 << Object.new
- end and skip "implicit compaction didn't happen within #{n} objects"
+ end and omit "implicit compaction didn't happen within #{n} objects"
compact_stats = GC.latest_compact_info
refute_predicate compact_stats[:considered], :empty?
refute_predicate compact_stats[:moved], :empty?
@@ -82,13 +81,39 @@ class TestGCCompact < Test::Unit::TestCase
end
end
- def os_page_size
- return true unless defined?(Etc::SC_PAGE_SIZE)
+ class CompactMethodsNotImplemented < Test::Unit::TestCase
+ include CompactionSupportInspector
+
+ def assert_not_implemented(method, *args)
+ omit "autocompact is supported on this platform" if supports_auto_compact?
+
+ assert_raise(NotImplementedError) { GC.send(method, *args) }
+ refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement")
+ end
+
+ def test_gc_compact_not_implemented
+ assert_not_implemented(:compact)
+ end
+
+ def test_gc_auto_compact_get_not_implemented
+ assert_not_implemented(:auto_compact)
+ end
+
+ def test_gc_auto_compact_set_not_implemented
+ assert_not_implemented(:auto_compact=, true)
+ end
+
+ def test_gc_latest_compact_info_not_implemented
+ assert_not_implemented(:latest_compact_info)
+ end
+
+ def test_gc_verify_compaction_references_not_implemented
+ assert_not_implemented(:verify_compaction_references)
+ end
end
- def setup
- skip "autocompact not supported on this platform" unless supports_auto_compact?
- super
+ def os_page_size
+ return true unless defined?(Etc::SC_PAGE_SIZE)
end
def test_gc_compact_stats
@@ -141,7 +166,7 @@ class TestGCCompact < Test::Unit::TestCase
hash = list_of_objects.hash
GC.verify_compaction_references(toward: :empty)
assert_equal hash, list_of_objects.hash
- GC.verify_compaction_references(double_heap: false)
+ GC.verify_compaction_references(expand_heap: false)
assert_equal hash, list_of_objects.hash
end
@@ -166,4 +191,256 @@ class TestGCCompact < Test::Unit::TestCase
GC.compact
assert_equal count + 1, GC.stat(:compact_count)
end
+
+ def test_compacting_from_trace_point
+ obj = Object.new
+ def obj.tracee
+ :ret # expected to emit both line and call event from one instruction
+ end
+
+ results = []
+ TracePoint.new(:call, :line) do |tp|
+ results << tp.event
+ GC.verify_compaction_references
+ end.enable(target: obj.method(:tracee)) do
+ obj.tracee
+ end
+
+ assert_equal([:call, :line], results)
+ end
+
+ def test_updating_references_for_heap_allocated_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+
+ # Pointer in slice should point to buffer of ary
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_heap_allocated_frozen_shared_arrays
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = []
+ 50.times { |i| ary << i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_updating_references_for_embed_frozen_shared_arrays
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ary = Array.new(50)
+ 50.times { |i| ary[i] = i }
+ # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG
+ ary.freeze
+
+ # Ensure ary is embedded
+ assert_include(ObjectSpace.dump(ary), '"embedded":true')
+
+ slice = ary[10..40]
+
+ # Check that slice is pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+
+ # Run compaction to re-embed ary
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ # Assert that slice is pointer to updated buffer in ary
+ assert_equal(10, slice[0])
+ # Check that slice is still pointing to buffer of ary
+ assert_include(ObjectSpace.dump(slice), '"shared":true')
+ end;
+ end
+
+ def test_moving_arrays_down_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ arys = ARY_COUNT.times.map do
+ ary = "abbbbbbbbbb".chars
+ ary.uniq!
+ end
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_down, :T_ARRAY), :>=, ARY_COUNT)
+ assert(arys) # warning: assigned but unused variable - arys
+ end;
+ end
+
+ def test_moving_arrays_up_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ ARY_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ ary = "hello".chars
+ arys = ARY_COUNT.times.map do
+ x = []
+ ary.each { |e| x << e }
+ x
+ end
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+ assert_operator(stats.dig(:moved_up, :T_ARRAY), :>=, ARY_COUNT)
+ assert(arys) # warning: assigned but unused variable - arys
+ end;
+ end
+
+ def test_moving_objects_between_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ class Foo
+ def add_ivars
+ 10.times do |i|
+ instance_variable_set("@foo" + i.to_s, 0)
+ end
+ end
+ end
+
+ OBJ_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ ary = OBJ_COUNT.times.map { Foo.new }
+ ary.each(&:add_ivars)
+
+ GC.start
+ Foo.new.add_ivars
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_up][:T_OBJECT], :>=, OBJ_COUNT)
+ end;
+ end
+
+ def test_moving_strings_up_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ STR_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ ary = STR_COUNT.times.map { "" << str }
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT)
+ assert_include(ObjectSpace.dump(ary[0]), '"embedded":true')
+ end;
+ end
+
+ def test_moving_strings_down_size_pools
+ omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1
+
+ assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
+ begin;
+ STR_COUNT = 500
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).squeeze! }
+
+ stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT)
+ assert_include(ObjectSpace.dump(ary[0]), '"embedded":true')
+ end;
+ end
+
+ def test_moving_objects_between_size_pools_keeps_shape_frozen_status
+ # [Bug #19536]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
+ begin;
+ class A
+ def add_ivars
+ @a = @b = @c = @d = 1
+ end
+
+ def set_a
+ @a = 10
+ end
+ end
+
+ a = A.new
+ a.add_ivars
+ a.freeze
+
+ b = A.new
+ b.add_ivars
+ b.set_a # Set the inline cache in set_a
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_raise(FrozenError) { a.set_a }
+ end;
+ end
end
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index f79879c20a..cef9f88a3c 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -304,6 +304,20 @@ class TestHash < Test::Unit::TestCase
assert_equal before, ObjectSpace.count_objects[:T_STRING]
end
+ def test_AREF_fstring_key_default_proc
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ h = Hash.new do |h, k|
+ k.frozen?
+ end
+
+ str = "foo"
+ refute str.frozen? # assumes this file is frozen_string_literal: false
+ refute h[str]
+ refute h["foo"]
+ end;
+ end
+
def test_ASET_fstring_key
a, b = {}, {}
assert_equal 1, a["abc"] = 1
@@ -1048,14 +1062,14 @@ class TestHash < Test::Unit::TestCase
h = @cls.new {|hh, k| :foo }
h[1] = 2
assert_equal([1, 2], h.shift)
- assert_equal(:foo, h.shift)
- assert_equal(:foo, h.shift)
+ assert_nil(h.shift)
+ assert_nil(h.shift)
h = @cls.new(:foo)
h[1] = 2
assert_equal([1, 2], h.shift)
- assert_equal(:foo, h.shift)
- assert_equal(:foo, h.shift)
+ assert_nil(h.shift)
+ assert_nil(h.shift)
h =@cls[1=>2]
h.each { assert_equal([1, 2], h.shift) }
@@ -1066,7 +1080,20 @@ class TestHash < Test::Unit::TestCase
def h.default(k = nil)
super.upcase
end
- assert_equal("FOO", h.shift)
+ assert_nil(h.shift)
+ end
+
+ def test_shift_for_empty_hash
+ # [ruby-dev:51159]
+ h = @cls[]
+ 100.times{|n|
+ while h.size < n
+ k = Random.rand 0..1<<30
+ h[k] = 1
+ end
+ 0 while h.shift
+ assert_equal({}, h)
+ }
end
def test_reject_bang2
@@ -1370,6 +1397,15 @@ class TestHash < Test::Unit::TestCase
assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h)
end
+ def test_update_on_identhash
+ key = +'a'
+ i = @cls[].compare_by_identity
+ i[key] = 0
+ h = @cls[].update(i)
+ key.upcase!
+ assert_equal(0, h.fetch('a'))
+ end
+
def test_merge
h1 = @cls[1=>2, 3=>4]
h2 = {1=>3, 5=>7}
@@ -1392,10 +1428,10 @@ class TestHash < Test::Unit::TestCase
expected[7] = 8
h2 = h.merge(7=>8)
assert_equal(expected, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h2 = h.merge({})
assert_equal(h, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h = @cls[]
h.compare_by_identity
@@ -1403,10 +1439,10 @@ class TestHash < Test::Unit::TestCase
h1.compare_by_identity
h2 = h.merge(7=>8)
assert_equal(h1, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
h2 = h.merge({})
assert_equal(h, h2)
- assert_equal(true, h2.compare_by_identity?)
+ assert_predicate(h2, :compare_by_identity?)
end
def test_merge!
@@ -1529,6 +1565,17 @@ class TestHash < Test::Unit::TestCase
end
end
+ def hash_iter_recursion(h, level)
+ return if level == 0
+ h.each_key {}
+ h.each_value { hash_iter_recursion(h, level - 1) }
+ end
+
+ def test_iterlevel_in_ivar_bug19589
+ h = { a: nil }
+ hash_iter_recursion(h, 200)
+ end
+
def test_threaded_iter_level
bug9105 = '[ruby-dev:47807] [Bug #9105]'
h = @cls[1=>2]
@@ -1720,6 +1767,15 @@ class TestHash < Test::Unit::TestCase
assert_no_memory_leak([], prepare, code, bug9187)
end
+ def test_memory_size_after_delete
+ require 'objspace'
+ h = {}
+ 1000.times {|i| h[i] = true}
+ big = ObjectSpace.memsize_of(h)
+ 1000.times {|i| h.delete(i)}
+ assert_operator ObjectSpace.memsize_of(h), :<, big/10
+ end
+
def test_wrapper
bug9381 = '[ruby-core:59638] [Bug #9381]'
@@ -2153,6 +2209,27 @@ class TestHash < Test::Unit::TestCase
end
end
+ # Previously this test would fail because rb_hash inside opt_aref would look
+ # at the current method name
+ def test_hash_recursion_independent_of_mid
+ o = Class.new do
+ def hash(h, k)
+ h[k]
+ end
+
+ def any_other_name(h, k)
+ h[k]
+ end
+ end.new
+
+ rec = []; rec << rec
+
+ h = @cls[]
+ h[rec] = 1
+ assert o.hash(h, rec)
+ assert o.any_other_name(h, rec)
+ end
+
def test_any_hash_fixable
20.times do
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
@@ -2178,4 +2255,24 @@ class TestHash < Test::Unit::TestCase
end;
end
end
+
+ def test_ar_hash_to_st_hash
+ assert_normal_exit("#{<<~"begin;"}\n#{<<~'end;'}", 'https://bugs.ruby-lang.org/issues/20050#note-5')
+ begin;
+ srand(0)
+ class Foo
+ def to_a
+ []
+ end
+ def hash
+ $h.delete($h.keys.sample) if rand < 0.1
+ to_a.hash
+ end
+ end
+ 1000.times do
+ $h = {}
+ (0..10).each {|i| $h[Foo.new] ||= {} }
+ end
+ end;
+ end
end
diff --git a/test/ruby/test_inlinecache.rb b/test/ruby/test_inlinecache.rb
index 6c2d86aefd..d48d95d74e 100644
--- a/test/ruby/test_inlinecache.rb
+++ b/test/ruby/test_inlinecache.rb
@@ -3,7 +3,7 @@
require 'test/unit'
-class TestMethod < Test::Unit::TestCase
+class TestMethodInlineCache < Test::Unit::TestCase
def test_alias
m0 = Module.new do
def foo; :M0 end
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index 9354514df0..dd177090ab 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -2,16 +2,9 @@
require 'test/unit'
class TestInteger < Test::Unit::TestCase
- BDSIZE = 0x4000000000000000.coerce(0)[0].size
- def self.bdsize(x)
- ((x + 1) / 8 + BDSIZE) / BDSIZE * BDSIZE
- end
- def bdsize(x)
- self.class.bdsize(x)
- end
-
FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN']
FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX']
+ LONG_MAX = RbConfig::LIMITS['LONG_MAX']
def test_aref
@@ -96,11 +89,16 @@ class TestInteger < Test::Unit::TestCase
assert_equal(0, 1 << -0x40000001)
assert_equal(0, 1 << -0x80000000)
assert_equal(0, 1 << -0x80000001)
- # assert_equal(bdsize(0x80000000), (1 << 0x80000000).size)
+
+ char_bit = RbConfig::LIMITS["UCHAR_MAX"].bit_length
+ size_max = RbConfig::LIMITS["SIZE_MAX"]
+ size_bit_max = size_max * char_bit
+ assert_raise_with_message(RangeError, /shift width/) {
+ 1 << size_bit_max
+ }
end
def test_rshift
- # assert_equal(bdsize(0x40000001), (1 >> -0x40000001).size)
assert_predicate((1 >> 0x80000000), :zero?)
assert_predicate((1 >> 0xffffffff), :zero?)
assert_predicate((1 >> 0x100000000), :zero?)
@@ -247,6 +245,13 @@ class TestInteger < Test::Unit::TestCase
end;
end
+ def test_Integer_when_to_str
+ def (obj = Object.new).to_str
+ "0x10"
+ end
+ assert_equal(16, Integer(obj))
+ end
+
def test_int_p
assert_not_predicate(1.0, :integer?)
assert_predicate(1, :integer?)
@@ -299,6 +304,31 @@ class TestInteger < Test::Unit::TestCase
end
end
+ def test_times_bignum_redefine_plus_lt
+ assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ called = false
+ Integer.class_eval do
+ alias old_plus +
+ undef +
+ define_method(:+){|x| called = true; 1}
+ alias old_lt <
+ undef <
+ define_method(:<){|x| called = true}
+ end
+ big = 2**65
+ big.times{break 0}
+ Integer.class_eval do
+ undef +
+ alias + old_plus
+ undef <
+ alias < old_lt
+ end
+ bug18377 = "[ruby-core:106361]"
+ assert_equal(false, called, bug18377)
+ end;
+ end
+
def assert_int_equal(expected, result, mesg = nil)
assert_kind_of(Integer, result, mesg)
assert_equal(expected, result, mesg)
@@ -679,4 +709,21 @@ class TestInteger < Test::Unit::TestCase
def o.to_int; Object.new; end
assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)}
end
+
+ def test_ceildiv
+ assert_equal(0, 0.ceildiv(3))
+ assert_equal(1, 1.ceildiv(3))
+ assert_equal(1, 3.ceildiv(3))
+ assert_equal(2, 4.ceildiv(3))
+
+ assert_equal(-1, 4.ceildiv(-3))
+ assert_equal(-1, -4.ceildiv(3))
+ assert_equal(2, -4.ceildiv(-3))
+
+ assert_equal(3, 3.ceildiv(1.2))
+ assert_equal(3, 3.ceildiv(6/5r))
+
+ assert_equal(10, (10**100-11).ceildiv(10**99-1))
+ assert_equal(11, (10**100-9).ceildiv(10**99-1))
+ end
end
diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb
index a90140fd0b..5ea9a8e574 100644
--- a/test/ruby/test_io.rb
+++ b/test/ruby/test_io.rb
@@ -312,7 +312,7 @@ class TestIO < Test::Unit::TestCase
w.print "a\n\nb\n\n"
w.close
end, proc do |r|
- assert_equal "a\n\nb\n", r.gets(nil, chomp: true)
+ assert_equal("a\n\nb\n\n", r.gets(nil, chomp: true), "[Bug #18770]")
assert_nil r.gets("")
r.close
end)
@@ -476,6 +476,18 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_copy_stream_append_to_nonempty
+ with_srccontent("foobar") {|src, content|
+ preface = 'preface'
+ File.write('dst', preface)
+ File.open('dst', 'ab') do |dst|
+ ret = IO.copy_stream(src, dst)
+ assert_equal(content.bytesize, ret)
+ assert_equal(preface + content, File.read("dst"))
+ end
+ }
+ end
+
def test_copy_stream_smaller
with_srccontent {|src, content|
@@ -643,8 +655,8 @@ class TestIO < Test::Unit::TestCase
if have_nonblock?
def test_copy_stream_no_busy_wait
- skip "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
- skip "multiple threads already active" if Thread.list.size > 1
+ omit "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ omit "multiple threads already active" if Thread.list.size > 1
msg = 'r58534 [ruby-core:80969] [Backport #13533]'
IO.pipe do |r,w|
@@ -663,7 +675,7 @@ class TestIO < Test::Unit::TestCase
begin
w2.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
s = w2.syswrite("a" * 100000)
t = Thread.new { sleep 0.1; r2.read }
@@ -767,7 +779,7 @@ class TestIO < Test::Unit::TestCase
r1.nonblock = true
w2.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
t1 = Thread.new { w1 << megacontent; w1.close }
t2 = Thread.new { r2.read }
@@ -831,7 +843,7 @@ class TestIO < Test::Unit::TestCase
assert_equal("bcd", r.read)
end)
rescue NotImplementedError
- skip "pread(2) is not implemtented."
+ omit "pread(2) is not implemtented."
end
}
end
@@ -888,6 +900,10 @@ class TestIO < Test::Unit::TestCase
end if defined? UNIXSocket
def test_copy_stream_socket4
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(0, f.pos)
@@ -904,9 +920,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket5
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f|
assert_equal(bigcontent[0,100], f.read(100))
@@ -924,9 +944,13 @@ class TestIO < Test::Unit::TestCase
}
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket6
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
mkcdtmpdir {
megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent }
@@ -935,7 +959,7 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
t1 = Thread.new { s2.read }
t2 = Thread.new {
@@ -947,9 +971,13 @@ class TestIO < Test::Unit::TestCase
assert_equal(megacontent, result)
}
}
- end if defined? UNIXSocket
+ end
def test_copy_stream_socket7
+ if RUBY_PLATFORM =~ /mingw|mswin/
+ omit "pread(2) is not implemented."
+ end
+
GC.start
mkcdtmpdir {
megacontent = "abc" * 1234567
@@ -959,7 +987,7 @@ class TestIO < Test::Unit::TestCase
begin
s1.nonblock = true
rescue Errno::EBADF
- skip "nonblocking IO for pipe is not implemented"
+ omit "nonblocking IO for pipe is not implemented"
end
trapping_usr2 do |rd|
nr = 30
@@ -984,7 +1012,7 @@ class TestIO < Test::Unit::TestCase
end
}
}
- end if defined? UNIXSocket and IO.method_defined?("nonblock=")
+ end
def test_copy_stream_strio
src = StringIO.new("abcd")
@@ -1429,6 +1457,16 @@ class TestIO < Test::Unit::TestCase
End
end
+ def test_dup_timeout
+ with_pipe do |r, w|
+ r.timeout = 0.1
+ r2 = r.dup
+ assert_equal(0.1, r2.timeout)
+ ensure
+ r2&.close
+ end
+ end
+
def test_inspect
with_pipe do |r, w|
assert_match(/^#<IO:fd \d+>$/, r.inspect)
@@ -1482,6 +1520,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_readpartial_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.readpartial(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_readpartial_buffer_error
with_pipe do |r, w|
s = ""
@@ -1527,6 +1572,13 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_read_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
def test_read_buffer_error
with_pipe do |r, w|
s = ""
@@ -1564,6 +1616,35 @@ class TestIO < Test::Unit::TestCase
}
end
+ def test_read_nonblock_zero_size
+ File.open(IO::NULL) do |r|
+ assert_empty(r.read_nonblock(0, s = "01234567"))
+ assert_empty(s)
+ end
+ end
+
+ def test_read_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'r') do |file|
+ file.read_nonblock(4)
+ end
+ end
+ end
+
+ def test_write_nonblock_file
+ make_tempfile do |path|
+ File.open(path, 'w') do |file|
+ file.write_nonblock("Ruby")
+ end
+ end
+ end
+
+ def test_explicit_path
+ io = IO.for_fd(0, path: "Fake Path", autoclose: false)
+ assert_match %r"Fake Path", io.inspect
+ assert_equal "Fake Path", io.path
+ end
+
def test_write_nonblock_simple_no_exceptions
pipe(proc do |w|
w.write_nonblock('1', exception: false)
@@ -1598,7 +1679,7 @@ class TestIO < Test::Unit::TestCase
end if have_nonblock?
def test_read_nonblock_no_exceptions
- skip '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # TODO: consider acquiring GVL from MJIT worker.
+ omit '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # TODO: consider acquiring GVL from MJIT worker.
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, exception: false)
w.puts "HI!"
@@ -1861,6 +1942,20 @@ class TestIO < Test::Unit::TestCase
assert_equal("baz\n", e.next)
assert_raise(StopIteration) { e.next }
end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]")
+ end)
+
+ pipe(proc do |w|
+ w.write "foo\n"
+ w.close
+ end, proc do |r|
+ assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]")
+ end)
end
def test_each_byte2
@@ -2166,6 +2261,14 @@ class TestIO < Test::Unit::TestCase
end)
end
+ def test_sysread_with_negative_length
+ make_tempfile {|t|
+ open(t.path) do |f|
+ assert_raise(ArgumentError) { f.sysread(-1) }
+ end
+ }
+ end
+
def test_flag
make_tempfile {|t|
assert_raise(ArgumentError) do
@@ -2246,7 +2349,7 @@ class TestIO < Test::Unit::TestCase
def test_autoclose_true_closed_by_finalizer
# http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1465760
# http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1469765
- skip 'this randomly fails with MJIT' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
+ omit 'this randomly fails with MJIT' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
feature2250 = '[ruby-core:26222]'
pre = 'ft2250'
@@ -2258,7 +2361,7 @@ class TestIO < Test::Unit::TestCase
t.close
rescue Errno::EBADF
end
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_raise(Errno::EBADF, feature2250) {t.close}
end
@@ -2274,7 +2377,7 @@ class TestIO < Test::Unit::TestCase
begin
w.close
t.close
- skip "expect IO object was GC'ed but not recycled yet"
+ omit "expect IO object was GC'ed but not recycled yet"
rescue WeakRef::RefError
assert_nothing_raised(Errno::EBADF, feature2250) {t.close}
end
@@ -2555,6 +2658,8 @@ class TestIO < Test::Unit::TestCase
bug = '[ruby-dev:31525]'
assert_raise(ArgumentError, bug) {IO.foreach}
+ assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}}
+
a = nil
assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a}
assert_equal(["foo\n", "bar\n", "baz\n"], a, bug)
@@ -2563,6 +2668,8 @@ class TestIO < Test::Unit::TestCase
assert_raise_with_message(IOError, /not opened for reading/, bug6054) do
IO.foreach(t.path, mode:"w").next
end
+
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}}
}
end
@@ -2572,6 +2679,7 @@ class TestIO < Test::Unit::TestCase
assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b"))
assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2))
assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2))
+ assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}}
}
end
@@ -2637,7 +2745,7 @@ class TestIO < Test::Unit::TestCase
end
def test_puts_parallel
- skip "not portable"
+ omit "not portable"
pipe(proc do |w|
threads = []
100.times do
@@ -3064,6 +3172,8 @@ __END__
end
def test_cross_thread_close_stdio
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], <<-'end;')
IO.pipe do |r,w|
$stdin.reopen(r)
@@ -3271,7 +3381,7 @@ __END__
begin
f = File.open('/dev/tty')
rescue Errno::ENOENT, Errno::ENXIO => e
- skip e.message
+ omit e.message
else
tiocgwinsz=0x5413
winsize=""
@@ -3397,10 +3507,10 @@ __END__
with_pipe do |r,w|
# Linux 2.6.15 and earlier returned EINVAL instead of ESPIPE
assert_raise(Errno::ESPIPE, Errno::EINVAL) {
- r.advise(:willneed) or skip "fadvise(2) is not implemented"
+ r.advise(:willneed) or omit "fadvise(2) is not implemented"
}
assert_raise(Errno::ESPIPE, Errno::EINVAL) {
- w.advise(:willneed) or skip "fadvise(2) is not implemented"
+ w.advise(:willneed) or omit "fadvise(2) is not implemented"
}
end
end if /linux/ =~ RUBY_PLATFORM
@@ -3540,14 +3650,14 @@ __END__
f.write('1')
pos = f.tell
rescue Errno::ENOSPC
- skip "non-sparse file system"
+ omit "non-sparse file system"
rescue SystemCallError
else
assert_equal(0x1_0000_0000, pos, msg)
end
end;
rescue Timeout::Error
- skip "Timeout because of slow file writing"
+ omit "Timeout because of slow file writing"
end
}
end if /mswin|mingw/ =~ RUBY_PLATFORM
@@ -3732,6 +3842,8 @@ __END__
end
def test_race_closed_stream
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
begin;
bug13158 = '[ruby-core:79262] [Bug #13158]'
@@ -3826,6 +3938,8 @@ __END__
end
def test_closed_stream_in_rescue
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
10.times do
@@ -3847,30 +3961,6 @@ __END__
end
end;
end
-
- def test_write_no_garbage
- skip "multiple threads already active" if Thread.list.size > 1
- res = {}
- ObjectSpace.count_objects(res) # creates strings on first call
- [ 'foo'.b, '*' * 24 ].each do |buf|
- with_pipe do |r, w|
- GC.disable
- begin
- before = ObjectSpace.count_objects(res)[:T_STRING]
- n = w.write(buf)
- s = w.syswrite(buf)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- ensure
- GC.enable
- end
- assert_equal before, after,
- "no strings left over after write [ruby-core:78898] [Bug #13085]: #{ before } strings before write -> #{ after } strings after write"
- assert_not_predicate buf, :frozen?, 'no inadvertent freeze'
- assert_equal buf.bytesize, n, 'IO#write wrote expected size'
- assert_equal s, n, 'IO#syswrite wrote expected size'
- end
- end
- end
end
def test_pread
@@ -3918,6 +4008,9 @@ __END__
noex = Thread.new do # everything right and never see exceptions :)
until sig_rd.wait_readable(0)
IO.pipe do |r, w|
+ assert_nil r.timeout
+ assert_nil w.timeout
+
th = Thread.new { r.read(1) }
w.write(dot)
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
new file mode 100644
index 0000000000..0c62216642
--- /dev/null
+++ b/test/ruby/test_io_buffer.rb
@@ -0,0 +1,501 @@
+# frozen_string_literal: false
+
+require 'tempfile'
+
+class TestIOBuffer < Test::Unit::TestCase
+ experimental = Warning[:experimental]
+ begin
+ Warning[:experimental] = false
+ IO::Buffer.new(0)
+ ensure
+ Warning[:experimental] = experimental
+ end
+
+ def assert_negative(value)
+ assert(value < 0, "Expected #{value} to be negative!")
+ end
+
+ def assert_positive(value)
+ assert(value > 0, "Expected #{value} to be positive!")
+ end
+
+ def test_flags
+ assert_equal 1, IO::Buffer::EXTERNAL
+ assert_equal 2, IO::Buffer::INTERNAL
+ assert_equal 4, IO::Buffer::MAPPED
+
+ assert_equal 32, IO::Buffer::LOCKED
+ assert_equal 64, IO::Buffer::PRIVATE
+
+ assert_equal 128, IO::Buffer::READONLY
+ end
+
+ def test_endian
+ assert_equal 4, IO::Buffer::LITTLE_ENDIAN
+ assert_equal 8, IO::Buffer::BIG_ENDIAN
+ assert_equal 8, IO::Buffer::NETWORK_ENDIAN
+
+ assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN
+ end
+
+ def test_default_size
+ assert_equal IO::Buffer::DEFAULT_SIZE, IO::Buffer.new.size
+ end
+
+ def test_new_internal
+ buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL)
+ assert_equal 1024, buffer.size
+ refute buffer.external?
+ assert buffer.internal?
+ refute buffer.mapped?
+ end
+
+ def test_new_mapped
+ buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED)
+ assert_equal 1024, buffer.size
+ refute buffer.external?
+ refute buffer.internal?
+ assert buffer.mapped?
+ end
+
+ def test_new_readonly
+ buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY)
+ assert buffer.readonly?
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.set_string("")
+ end
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.set_string("!", 1)
+ end
+ end
+
+ def test_file_mapped
+ buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)}
+ contents = buffer.get_string
+
+ assert_include contents, "Hello World"
+ assert_equal Encoding::BINARY, contents.encoding
+ end
+
+ def test_file_mapped_invalid
+ assert_raise NoMethodError do
+ IO::Buffer.map("foobar")
+ end
+ end
+
+ def test_string_mapped
+ string = "Hello World"
+ buffer = IO::Buffer.for(string)
+ assert buffer.readonly?
+ end
+
+ def test_string_mapped_frozen
+ string = "Hello World".freeze
+ buffer = IO::Buffer.for(string)
+ assert buffer.readonly?
+ end
+
+ def test_string_mapped_mutable
+ string = "Hello World"
+ IO::Buffer.for(string) do |buffer|
+ refute buffer.readonly?
+
+ # Cannot modify string as it's locked by the buffer:
+ assert_raise RuntimeError do
+ string[0] = "h"
+ end
+
+ buffer.set_value(:U8, 0, "h".ord)
+
+ # Buffer releases it's ownership of the string:
+ buffer.free
+
+ assert_equal "hello World", string
+ end
+ end
+
+ def test_non_string
+ not_string = Object.new
+
+ assert_raise TypeError do
+ IO::Buffer.for(not_string)
+ end
+ end
+
+ def test_resize_mapped
+ buffer = IO::Buffer.new
+
+ buffer.resize(2048)
+ assert_equal 2048, buffer.size
+
+ buffer.resize(4096)
+ assert_equal 4096, buffer.size
+ end
+
+ def test_resize_preserve
+ message = "Hello World"
+ buffer = IO::Buffer.new(1024)
+ buffer.set_string(message)
+ buffer.resize(2048)
+ assert_equal message, buffer.get_string(0, message.bytesize)
+ end
+
+ def test_resize_zero_internal
+ buffer = IO::Buffer.new(1)
+
+ buffer.resize(0)
+ assert_equal 0, buffer.size
+
+ buffer.resize(1)
+ assert_equal 1, buffer.size
+ end
+
+ def test_resize_zero_external
+ buffer = IO::Buffer.for('1')
+
+ assert_raise IO::Buffer::AccessError do
+ buffer.resize(0)
+ end
+ end
+
+ def test_compare_same_size
+ buffer1 = IO::Buffer.new(1)
+ assert_equal buffer1, buffer1
+
+ buffer2 = IO::Buffer.new(1)
+ buffer1.set_value(:U8, 0, 0x10)
+ buffer2.set_value(:U8, 0, 0x20)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
+ def test_compare_different_size
+ buffer1 = IO::Buffer.new(3)
+ buffer2 = IO::Buffer.new(5)
+
+ assert_negative buffer1 <=> buffer2
+ assert_positive buffer2 <=> buffer1
+ end
+
+ def test_slice
+ buffer = IO::Buffer.new(128)
+ slice = buffer.slice(8, 32)
+ slice.set_string("Hello World")
+ assert_equal("Hello World", buffer.get_string(8, 11))
+ end
+
+ def test_slice_arguments
+ buffer = IO::Buffer.for("Hello World")
+
+ slice = buffer.slice
+ assert_equal "Hello World", slice.get_string
+
+ slice = buffer.slice(2)
+ assert_equal("llo World", slice.get_string)
+ end
+
+ def test_slice_bounds_error
+ buffer = IO::Buffer.new(128)
+
+ assert_raise ArgumentError do
+ buffer.slice(128, 10)
+ end
+
+ assert_raise ArgumentError do
+ buffer.slice(-10, 10)
+ end
+ end
+
+ def test_locked
+ buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::LOCKED)
+
+ assert_raise IO::Buffer::LockedError do
+ buffer.resize(256)
+ end
+
+ assert_equal 128, buffer.size
+
+ assert_raise IO::Buffer::LockedError do
+ buffer.free
+ end
+
+ assert_equal 128, buffer.size
+ end
+
+ def test_get_string
+ message = "Hello World 🤓"
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string(message)
+
+ chunk = buffer.get_string(0, message.bytesize, Encoding::UTF_8)
+ assert_equal message, chunk
+ assert_equal Encoding::UTF_8, chunk.encoding
+
+ chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY)
+ assert_equal Encoding::BINARY, chunk.encoding
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(0, 129)
+ end
+
+ assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do
+ buffer.get_string(129)
+ end
+
+ assert_raise_with_message(ArgumentError, /Offset can't be negative/) do
+ buffer.get_string(-1)
+ end
+ end
+
+ # We check that values are correctly round tripped.
+ RANGES = {
+ :U8 => [0, 2**8-1],
+ :S8 => [-2**7, 0, 2**7-1],
+
+ :U16 => [0, 2**16-1],
+ :S16 => [-2**15, 0, 2**15-1],
+ :u16 => [0, 2**16-1],
+ :s16 => [-2**15, 0, 2**15-1],
+
+ :U32 => [0, 2**32-1],
+ :S32 => [-2**31, 0, 2**31-1],
+ :u32 => [0, 2**32-1],
+ :s32 => [-2**31, 0, 2**31-1],
+
+ :U64 => [0, 2**64-1],
+ :S64 => [-2**63, 0, 2**63-1],
+ :u64 => [0, 2**64-1],
+ :s64 => [-2**63, 0, 2**63-1],
+
+ :F32 => [-1.0, 0.0, 0.5, 1.0, 128.0],
+ :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0],
+ }
+
+ def test_get_set_value
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ values.each do |value|
+ buffer.set_value(data_type, 0, value)
+ assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}."
+ end
+ end
+ end
+
+ def test_get_set_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}."
+ end
+ end
+
+ def test_values
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+
+ buffer.set_values(format, 0, values)
+ assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}."
+ end
+ end
+
+ def test_each
+ buffer = IO::Buffer.new(128)
+
+ RANGES.each do |data_type, values|
+ format = [data_type] * values.size
+ data_type_size = IO::Buffer.size_of(data_type)
+ values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]}
+
+ buffer.set_values(format, 0, values)
+ assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}."
+ end
+ end
+
+ def test_each_byte
+ string = "The quick brown fox jumped over the lazy dog."
+ buffer = IO::Buffer.for(string)
+
+ assert_equal string.bytes, buffer.each_byte.to_a
+ end
+
+ def test_clear
+ buffer = IO::Buffer.new(16)
+ buffer.set_string("Hello World!")
+ end
+
+ def test_invalidation
+ input, output = IO.pipe
+
+ # (1) rb_write_internal creates IO::Buffer object,
+ buffer = IO::Buffer.new(128)
+
+ # (2) it is passed to (malicious) scheduler
+ # (3) scheduler starts a thread which call system call with the buffer object
+ thread = Thread.new{buffer.locked{input.read}}
+
+ Thread.pass until thread.stop?
+
+ # (4) scheduler returns
+ # (5) rb_write_internal invalidate the buffer object
+ assert_raise IO::Buffer::LockedError do
+ buffer.free
+ end
+
+ # (6) the system call access the memory area after invalidation
+ output.write("Hello World")
+ output.close
+ thread.join
+
+ input.close
+ end
+
+ def hello_world_tempfile
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ yield io
+ ensure
+ io&.close!
+ end
+
+ def test_read
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_length
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, 5)
+ assert_equal "Hello", buffer.get_string(0, 5)
+ end
+ end
+
+ def test_read_with_with_offset
+ hello_world_tempfile do |io|
+ buffer = IO::Buffer.new(128)
+ buffer.read(io, nil, 6)
+ assert_equal "Hello", buffer.get_string(6, 5)
+ end
+ end
+
+ def test_write
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("Hello")
+ buffer.write(io)
+
+ io.seek(0)
+ assert_equal "Hello", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_pread
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ buffer = IO::Buffer.new(128)
+ buffer.pread(io, 6, 5)
+
+ assert_equal "World", buffer.get_string(0, 5)
+ assert_equal 0, io.tell
+ ensure
+ io.close!
+ end
+
+ def test_pread_offset
+ io = Tempfile.new
+ io.write("Hello World")
+ io.seek(0)
+
+ buffer = IO::Buffer.new(128)
+ buffer.pread(io, 6, 5, 6)
+
+ assert_equal "World", buffer.get_string(6, 5)
+ assert_equal 0, io.tell
+ ensure
+ io.close!
+ end
+
+ def test_pwrite
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("World")
+ buffer.pwrite(io, 6, 5)
+
+ assert_equal 0, io.tell
+
+ io.seek(6)
+ assert_equal "World", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_pwrite_offset
+ io = Tempfile.new
+
+ buffer = IO::Buffer.new(128)
+ buffer.set_string("Hello World")
+ buffer.pwrite(io, 6, 5, 6)
+
+ assert_equal 0, io.tell
+
+ io.seek(6)
+ assert_equal "World", io.read(5)
+ ensure
+ io.close!
+ end
+
+ def test_operators
+ source = IO::Buffer.for("1234123412")
+ mask = IO::Buffer.for("133\x00")
+
+ assert_equal IO::Buffer.for("123\x00123\x0012"), (source & mask)
+ assert_equal IO::Buffer.for("1334133413"), (source | mask)
+ assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), (source ^ mask)
+ assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), ~source
+ end
+
+ def test_inplace_operators
+ source = IO::Buffer.for("1234123412")
+ mask = IO::Buffer.for("133\x00")
+
+ assert_equal IO::Buffer.for("123\x00123\x0012"), source.dup.and!(mask)
+ assert_equal IO::Buffer.for("1334133413"), source.dup.or!(mask)
+ assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask)
+ assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not!
+ end
+
+ def test_shared
+ message = "Hello World"
+ buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED)
+
+ pid = fork do
+ buffer.set_string(message)
+ end
+
+ Process.wait(pid)
+ string = buffer.get_string(0, message.bytesize)
+ assert_equal message, string
+ rescue NotImplementedError
+ omit "Fork/shared memory is not supported."
+ end
+end
diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb
index 27b16a2a36..9c14087eba 100644
--- a/test/ruby/test_io_m17n.rb
+++ b/test/ruby/test_io_m17n.rb
@@ -1142,8 +1142,18 @@ EOT
IO.pipe do |r, w|
assert_nothing_raised(bug5567) do
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx")}
+ w.puts("foo")
+ assert_equal("foo\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")}
+ w.puts("bar")
+ assert_equal("bar\n", r.gets)
assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")}
+ w.puts("zot")
+ begin
+ assert_equal("zot\n", r.gets)
+ rescue Encoding::ConverterNotFoundError => e
+ assert_match(/\((\S+) to \1\)/, e.message)
+ end
end
end
end
diff --git a/test/ruby/test_io_timeout.rb b/test/ruby/test_io_timeout.rb
new file mode 100644
index 0000000000..e017395980
--- /dev/null
+++ b/test/ruby/test_io_timeout.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: false
+
+require 'io/nonblock'
+
+class TestIOTimeout < Test::Unit::TestCase
+ def with_pipe
+ omit "UNIXSocket is not defined!" unless defined?(UNIXSocket)
+
+ begin
+ i, o = UNIXSocket.pair
+
+ yield i, o
+ ensure
+ i.close
+ o.close
+ end
+ end
+
+ def test_timeout_attribute
+ with_pipe do |i, o|
+ assert_nil i.timeout
+
+ i.timeout = 10
+ assert_equal 10, i.timeout
+ assert_nil o.timeout
+
+ o.timeout = 20
+ assert_equal 20, o.timeout
+ assert_equal 10, i.timeout
+ end
+ end
+
+ def test_timeout_read_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.read}
+ end
+ end
+
+ def test_timeout_gets_exception
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+
+ assert_raise(IO::TimeoutError) {i.gets}
+ end
+ end
+
+ def test_timeout_puts
+ with_pipe do |i, o|
+ i.timeout = 0.0001
+ o.puts("Hello World")
+ o.close
+
+ assert_equal "Hello World", i.gets.chomp
+ end
+ end
+end
diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb
index edd131823e..0b94bf993b 100644
--- a/test/ruby/test_iseq.rb
+++ b/test/ruby/test_iseq.rb
@@ -10,13 +10,16 @@ class TestISeq < Test::Unit::TestCase
end
def compile(src, line = nil, opt = nil)
+ unless line
+ line = caller_locations(1).first.lineno
+ end
EnvUtil.suppress_warning do
ISeq.new(src, __FILE__, __FILE__, line, opt)
end
end
- def lines src
- body = compile(src).to_a[13]
+ def lines src, lines = nil
+ body = compile(src, lines).to_a[13]
body.find_all{|e| e.kind_of? Integer}
end
@@ -25,24 +28,22 @@ class TestISeq < Test::Unit::TestCase
end
def test_to_a_lines
- src = <<-EOS
+ assert_equal [__LINE__+1, __LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
p __LINE__ # 1
p __LINE__ # 2
# 3
p __LINE__ # 4
EOS
- assert_equal [1, 2, 4], lines(src)
- src = <<-EOS
+ assert_equal [__LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1)
# 1
p __LINE__ # 2
# 3
p __LINE__ # 4
# 5
EOS
- assert_equal [2, 4], lines(src)
- src = <<-EOS
+ assert_equal [__LINE__+3, __LINE__+4, __LINE__+7, __LINE__+9], lines(<<~EOS, __LINE__+1)
1 # should be optimized out
2 # should be optimized out
p __LINE__ # 3
@@ -53,10 +54,9 @@ class TestISeq < Test::Unit::TestCase
8 # should be optimized out
9
EOS
- assert_equal [3, 4, 7, 9], lines(src)
end
- def test_unsupport_type
+ def test_unsupported_type
ary = compile("p").to_a
ary[9] = :foobar
assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)}
@@ -86,7 +86,7 @@ class TestISeq < Test::Unit::TestCase
# CDHASH was not built properly when loading from binary and
# was causing opt_case_dispatch to clobber its stack canary
# for its "leaf" instruction attribute.
- iseq = compile(<<~EOF)
+ iseq = compile(<<~EOF, __LINE__+1)
case Class.new(String).new("foo")
when "foo"
42
@@ -95,16 +95,86 @@ class TestISeq < Test::Unit::TestCase
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
end
+ def test_super_with_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(*) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ end
+
+ def test_super_with_block_hash_0
+ iseq = compile(<<~EOF, __LINE__+1)
+ # [Bug #18250] `req` specifically cause `Assertion failed: (key != 0), function hash_table_raw_insert`
+ def (Object.new).touch(req, *)
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ end
+
+ def test_super_with_block_and_kwrest
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(**) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ end
+
def test_lambda_with_ractor_roundtrip
- iseq = compile(<<~EOF)
+ iseq = compile(<<~EOF, __LINE__+1)
x = 42
- y = lambda { x }
+ y = nil.instance_eval{ lambda { x } }
Ractor.make_shareable(y)
y.call
EOF
assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
end
+ def test_super_with_anonymous_block
+ iseq = compile(<<~EOF, __LINE__+1)
+ def (Object.new).touch(&) # :nodoc:
+ foo { super }
+ end
+ 42
+ EOF
+ assert_equal(42, ISeq.load_from_binary(iseq.to_binary).eval)
+ end
+
+ def test_ractor_unshareable_outer_variable
+ name = "\u{2603 26a1}"
+ y = nil.instance_eval do
+ eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
+ end
+ assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
+ Ractor.make_shareable(y)
+ end
+ y = nil.instance_eval do
+ eval("proc {#{name} = []; proc {|x| #{name}}}").call
+ end
+ assert_raise_with_message(Ractor::IsolationError, /`#{name}'/) do
+ Ractor.make_shareable(y)
+ end
+ obj = Object.new
+ def obj.foo(*) nil.instance_eval{ ->{super} } end
+ assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable `\*'/) do
+ Ractor.make_shareable(obj.foo)
+ end
+ end
+
+ def test_ractor_shareable_value_frozen_core
+ iseq = RubyVM::InstructionSequence.compile(<<~'RUBY')
+ # shareable_constant_value: literal
+ REGEX = /#{}/ # [Bug #20569]
+ RUBY
+ assert_includes iseq.to_binary, "REGEX".b
+ end
+
def test_disasm_encoding
src = "\u{3042} = 1; \u{3042}; \u{3043}"
asm = compile(src).disasm
@@ -147,16 +217,16 @@ class TestISeq < Test::Unit::TestCase
end
def test_line_trace
- iseq = compile \
- %q{ a = 1
+ iseq = compile(<<~EOF, __LINE__+1)
+ a = 1
b = 2
c = 3
# d = 4
e = 5
# f = 6
g = 7
+ EOF
- }
assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all)
iseq.line_trace_specify(1, true) # line 2
iseq.line_trace_specify(3, true) # line 5
@@ -293,6 +363,13 @@ class TestISeq < Test::Unit::TestCase
end
end
+ # [Bug #19173]
+ def test_compile_error
+ assert_raise SyntaxError do
+ RubyVM::InstructionSequence.compile 'using Module.new; yield'
+ end
+ end
+
def test_compile_file_error
Tempfile.create(%w"test_iseq .rb") do |f|
f.puts "end"
@@ -328,6 +405,30 @@ class TestISeq < Test::Unit::TestCase
end
end
+ def anon_star(*); end
+
+ def test_anon_rest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_star))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:*], param_names
+ end
+
+ def anon_keyrest(**); end
+
+ def test_anon_keyrest_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:**], param_names
+ end
+
+ def anon_block(&); end
+
+ def test_anon_block_param_in_disasm
+ iseq = RubyVM::InstructionSequence.of(method(:anon_block))
+ param_names = iseq.to_a[iseq.to_a.index(:method) + 1]
+ assert_equal [:&], param_names
+ end
+
def strip_lineno(source)
source.gsub(/^.*?: /, "")
end
@@ -453,7 +554,7 @@ class TestISeq < Test::Unit::TestCase
bin = assert_nothing_raised(mesg) do
iseq.to_binary
rescue RuntimeError => e
- skip e.message if /compile with coverage/ =~ e.message
+ omit e.message if /compile with coverage/ =~ e.message
raise
end
10.times do
@@ -464,6 +565,11 @@ class TestISeq < Test::Unit::TestCase
a1 = iseq.to_a
a2 = iseq2.to_a
assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble})
+ if iseq2.script_lines
+ assert_kind_of(Array, iseq2.script_lines)
+ else
+ assert_nil(iseq2.script_lines)
+ end
iseq2
end
@@ -635,4 +741,28 @@ class TestISeq < Test::Unit::TestCase
RubyVM::InstructionSequence.compile("", debug_level: 5)
end;
end
+
+ def test_mandatory_only
+ assert_separately [], <<~RUBY
+ at0 = Time.at(0)
+ assert_equal at0, Time.public_send(:at, 0, 0)
+ RUBY
+ end
+
+ def test_mandatory_only_redef
+ assert_separately ['-W0'], <<~RUBY
+ r = Ractor.new{
+ Float(10)
+ module Kernel
+ undef Float
+ def Float(n)
+ :new
+ end
+ end
+ GC.start
+ Float(30)
+ }
+ assert_equal :new, r.take
+ RUBY
+ end
end
diff --git a/test/ruby/test_jit_debug.rb b/test/ruby/test_jit_debug.rb
deleted file mode 100644
index 50e52b4c2e..0000000000
--- a/test/ruby/test_jit_debug.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require_relative 'test_jit'
-
-return unless defined?(TestJIT)
-return if ENV.key?('APPVEYOR')
-return if ENV.key?('RUBYCI_NICKNAME')
-return if ENV['RUBY_DEBUG']&.include?('ci') # ci.rvm.jp
-return if /mswin/ =~ RUBY_PLATFORM
-
-class TestJITDebug < TestJIT
- @@test_suites.delete TestJIT if self.respond_to? :on_parallel_worker?
-
- def setup
- super
- # let `#eval_with_jit` use --jit-debug
- @jit_debug = true
- end
-end
diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb
index 9094259bc2..94b5323ab0 100644
--- a/test/ruby/test_keyword.rb
+++ b/test/ruby/test_keyword.rb
@@ -190,6 +190,53 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(["bar", 111111], f[str: "bar", num: 111111])
end
+ def test_unset_hash_flag
+ bug18625 = "[ruby-core: 107847]"
+ singleton_class.class_eval do
+ ruby2_keywords def foo(*args)
+ args
+ end
+
+ def single(arg)
+ arg
+ end
+
+ def splat(*args)
+ args.last
+ end
+
+ def kwargs(**kw)
+ kw
+ end
+ end
+
+ h = { a: 1 }
+ args = foo(**h)
+ marked = args.last
+ assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+
+ after_usage = single(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+ after_usage = splat(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage, bug18625)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage), bug18625)
+
+ after_usage = kwargs(*args)
+ assert_equal(h, after_usage)
+ assert_same(marked, args.last)
+ assert_not_same(marked, after_usage, bug18625)
+ assert_not_same(marked, after_usage)
+ assert_equal(false, Hash.ruby2_keywords_hash?(after_usage))
+
+ assert_equal(true, Hash.ruby2_keywords_hash?(marked))
+ end
+
def test_keyword_splat_new
kw = {}
h = {a: 1}
@@ -200,17 +247,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_not_same(kw, res)
end
- def self.y(**kw) kw end
- m = method(:y)
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ def self.yo(**kw) kw end
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -219,25 +266,25 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.send(:call, **h))
m = method(:send)
- assert_equal(false, m.(:y, **{}).frozen?)
- assert_equal_not_same(kw, m.(:y, **kw))
- assert_equal_not_same(h, m.(:y, **h))
- assert_equal(false, m.send(:call, :y, **{}).frozen?)
- assert_equal_not_same(kw, m.send(:call, :y, **kw))
- assert_equal_not_same(h, m.send(:call, :y, **h))
-
- singleton_class.send(:remove_method, :y)
- define_singleton_method(:y) { |**kw| kw }
- m = method(:y)
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ assert_equal(false, m.(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.(:yo, **kw))
+ assert_equal_not_same(h, m.(:yo, **h))
+ assert_equal(false, m.send(:call, :yo, **{}).frozen?)
+ assert_equal_not_same(kw, m.send(:call, :yo, **kw))
+ assert_equal_not_same(h, m.send(:call, :yo, **h))
+
+ singleton_class.send(:remove_method, :yo)
+ define_singleton_method(:yo) { |**kw| kw }
+ m = method(:yo)
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -245,17 +292,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- y = lambda { |**kw| kw }
- m = y.method(:call)
- assert_equal(false, y.(**{}).frozen?)
- assert_equal_not_same(kw, y.(**kw))
- assert_equal_not_same(h, y.(**h))
- assert_equal(false, y.send(:call, **{}).frozen?)
- assert_equal_not_same(kw, y.send(:call, **kw))
- assert_equal_not_same(h, y.send(:call, **h))
- assert_equal(false, y.public_send(:call, **{}).frozen?)
- assert_equal_not_same(kw, y.public_send(:call, **kw))
- assert_equal_not_same(h, y.public_send(:call, **h))
+ yo = lambda { |**kw| kw }
+ m = yo.method(:call)
+ assert_equal(false, yo.(**{}).frozen?)
+ assert_equal_not_same(kw, yo.(**kw))
+ assert_equal_not_same(h, yo.(**h))
+ assert_equal(false, yo.send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, **kw))
+ assert_equal_not_same(h, yo.send(:call, **h))
+ assert_equal(false, yo.public_send(:call, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -263,17 +310,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- y = :y.to_proc
- m = y.method(:call)
- assert_equal(false, y.(self, **{}).frozen?)
- assert_equal_not_same(kw, y.(self, **kw))
- assert_equal_not_same(h, y.(self, **h))
- assert_equal(false, y.send(:call, self, **{}).frozen?)
- assert_equal_not_same(kw, y.send(:call, self, **kw))
- assert_equal_not_same(h, y.send(:call, self, **h))
- assert_equal(false, y.public_send(:call, self, **{}).frozen?)
- assert_equal_not_same(kw, y.public_send(:call, self, **kw))
- assert_equal_not_same(h, y.public_send(:call, self, **h))
+ yo = :yo.to_proc
+ m = yo.method(:call)
+ assert_equal(false, yo.(self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.(self, **kw))
+ assert_equal_not_same(h, yo.(self, **h))
+ assert_equal(false, yo.send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.send(:call, self, **kw))
+ assert_equal_not_same(h, yo.send(:call, self, **h))
+ assert_equal(false, yo.public_send(:call, self, **{}).frozen?)
+ assert_equal_not_same(kw, yo.public_send(:call, self, **kw))
+ assert_equal_not_same(h, yo.public_send(:call, self, **h))
assert_equal(false, m.(self, **{}).frozen?)
assert_equal_not_same(kw, m.(self, **kw))
assert_equal_not_same(h, m.(self, **h))
@@ -282,20 +329,20 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.send(:call, self, **h))
c = Class.new do
- def y(**kw) kw end
+ def yo(**kw) kw end
end
o = c.new
- def o.y(**kw) super end
- m = o.method(:y)
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -303,17 +350,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- o.singleton_class.send(:remove_method, :y)
- def o.y(**kw) super(**kw) end
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -325,17 +372,17 @@ class TestKeywordArguments < Test::Unit::TestCase
def method_missing(_, **kw) kw end
end
o = c.new
- def o.y(**kw) super end
- m = o.method(:y)
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ def o.yo(**kw) super end
+ m = o.method(:yo)
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -343,17 +390,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(kw, m.send(:call, **kw))
assert_equal_not_same(h, m.send(:call, **h))
- o.singleton_class.send(:remove_method, :y)
- def o.y(**kw) super(**kw) end
- assert_equal(false, o.y(**{}).frozen?)
- assert_equal_not_same(kw, o.y(**kw))
- assert_equal_not_same(h, o.y(**h))
- assert_equal(false, o.send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.send(:y, **kw))
- assert_equal_not_same(h, o.send(:y, **h))
- assert_equal(false, o.public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, o.public_send(:y, **kw))
- assert_equal_not_same(h, o.public_send(:y, **h))
+ o.singleton_class.send(:remove_method, :yo)
+ def o.yo(**kw) super(**kw) end
+ assert_equal(false, o.yo(**{}).frozen?)
+ assert_equal_not_same(kw, o.yo(**kw))
+ assert_equal_not_same(h, o.yo(**h))
+ assert_equal(false, o.send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.send(:yo, **kw))
+ assert_equal_not_same(h, o.send(:yo, **h))
+ assert_equal(false, o.public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, o.public_send(:yo, **kw))
+ assert_equal_not_same(h, o.public_send(:yo, **h))
assert_equal(false, m.(**{}).frozen?)
assert_equal_not_same(kw, m.(**kw))
assert_equal_not_same(h, m.(**h))
@@ -389,17 +436,17 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal_not_same(h, m.(**h))
assert_equal_not_same(h, m.send(:call, **h))
- singleton_class.send(:remove_method, :y)
+ singleton_class.send(:remove_method, :yo)
def self.method_missing(_, **kw) kw end
- assert_equal(false, y(**{}).frozen?)
- assert_equal_not_same(kw, y(**kw))
- assert_equal_not_same(h, y(**h))
- assert_equal(false, send(:y, **{}).frozen?)
- assert_equal_not_same(kw, send(:y, **kw))
- assert_equal_not_same(h, send(:y, **h))
- assert_equal(false, public_send(:y, **{}).frozen?)
- assert_equal_not_same(kw, public_send(:y, **kw))
- assert_equal_not_same(h, public_send(:y, **h))
+ assert_equal(false, yo(**{}).frozen?)
+ assert_equal_not_same(kw, yo(**kw))
+ assert_equal_not_same(h, yo(**h))
+ assert_equal(false, send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, send(:yo, **kw))
+ assert_equal_not_same(h, send(:yo, **h))
+ assert_equal(false, public_send(:yo, **{}).frozen?)
+ assert_equal_not_same(kw, public_send(:yo, **kw))
+ assert_equal_not_same(h, public_send(:yo, **h))
end
def test_regular_kwsplat
@@ -3538,7 +3585,7 @@ class TestKeywordArguments < Test::Unit::TestCase
assert_equal(splat_expect, pr.call(a), bug8463)
pr = proc {|a, **opt| next a, opt}
- assert_equal(splat_expect.values_at(0, -1), pr.call(splat_expect), bug8463)
+ assert_equal([splat_expect, {}], pr.call(splat_expect), bug8463)
end
def req_plus_keyword(x, **h)
diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb
index 2116d0ee31..32bf097318 100644
--- a/test/ruby/test_lazy_enumerator.rb
+++ b/test/ruby/test_lazy_enumerator.rb
@@ -295,6 +295,26 @@ class TestLazyEnumerator < Test::Unit::TestCase
assert_equal(nil, a.current)
end
+ def test_take_0_bug_18971
+ def (bomb = Object.new.extend(Enumerable)).each
+ raise
+ end
+ [2..10, bomb].each do |e|
+ assert_equal([], e.lazy.take(0).map(&:itself).to_a)
+ assert_equal([], e.lazy.take(0).select(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).select(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:even?).to_a)
+ assert_equal([], e.lazy.take(0).reject(&:odd?).to_a)
+ assert_equal([], e.lazy.take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).take(0).take(1).to_a)
+ assert_equal([], e.lazy.take(0).drop(0).to_a)
+ assert_equal([], e.lazy.take(0).find_all {|_| true}.to_a)
+ assert_equal([], e.lazy.take(0).zip((12..20)).to_a)
+ assert_equal([], e.lazy.take(0).uniq.to_a)
+ assert_equal([], e.lazy.take(0).sort.to_a)
+ end
+ end
+
def test_take_bad_arg
a = Step.new(1..10)
assert_raise(ArgumentError) { a.lazy.take(-1) }
diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb
index 8c3256c034..99dd3a0c56 100644
--- a/test/ruby/test_literal.rb
+++ b/test/ruby/test_literal.rb
@@ -26,7 +26,7 @@ class TestRubyLiteral < Test::Unit::TestCase
assert_equal '5', 0b101.inspect
assert_instance_of Integer, 0b101
assert_raise(SyntaxError) { eval("0b") }
- assert_equal '123456789012345678901234567890', 123456789012345678901234567890.inspect
+ assert_equal '123456789012345678901234567890', 123456789012345678901234567890.to_s
assert_instance_of Integer, 123456789012345678901234567890
assert_instance_of Float, 1.3
assert_equal '2', eval("0x00+2").inspect
diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb
index 6b0bc4de5e..28293ffffc 100644
--- a/test/ruby/test_m17n.rb
+++ b/test/ruby/test_m17n.rb
@@ -226,38 +226,16 @@ class TestM17N < Test::Unit::TestCase
end
end
- STR_WITHOUT_BOM = "\u3042".freeze
- STR_WITH_BOM = "\uFEFF\u3042".freeze
- bug8940 = '[ruby-core:59757] [Bug #8940]'
- bug9415 = '[ruby-dev:47895] [Bug #9415]'
- %w/UTF-16 UTF-32/.each do |enc|
- %w/BE LE/.each do |endian|
- bom = "\uFEFF".encode("#{enc}#{endian}").force_encoding(enc)
-
- define_method("test_utf_16_32_inspect(#{enc}#{endian})") do
- s = STR_WITHOUT_BOM.encode(enc + endian)
- # When a UTF-16/32 string doesn't have a BOM,
- # inspect as a dummy encoding string.
- assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect,
- s.dup.force_encoding(enc).inspect)
- assert_normal_exit("#{bom.b.dump}.force_encoding('#{enc}').inspect", bug8940)
- end
-
- define_method("test_utf_16_32_codepoints(#{enc}#{endian})") do
- assert_equal([0xFEFF], bom.codepoints, bug9415)
- end
-
- define_method("test_utf_16_32_ord(#{enc}#{endian})") do
- assert_equal(0xFEFF, bom.ord, bug9415)
- end
-
- define_method("test_utf_16_32_inspect(#{enc}#{endian}-BOM)") do
- s = STR_WITH_BOM.encode(enc + endian)
- # When a UTF-16/32 string has a BOM,
- # inspect as a particular encoding string.
- assert_equal(s.inspect,
- s.dup.force_encoding(enc).inspect)
- end
+ def test_utf_dummy_are_like_regular_dummy_encodings
+ [Encoding::UTF_16, Encoding::UTF_32].each do |enc|
+ s = "\u3042".encode("UTF-32BE")
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
+ s = "\x00\x00\xFE\xFF"
+ assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect)
+
+ assert_equal [0, 0, 254, 255], "\x00\x00\xFE\xFF".force_encoding(enc).codepoints
+ assert_equal 0, "\x00\x00\xFE\xFF".force_encoding(enc).ord
+ assert_equal 255, "\xFF\xFE\x00\x00".force_encoding(enc).ord
end
end
@@ -299,6 +277,9 @@ class TestM17N < Test::Unit::TestCase
orig_v, $VERBOSE = $VERBOSE, false
orig_int, Encoding.default_internal = Encoding.default_internal, nil
orig_ext = Encoding.default_external
+
+ omit "https://bugs.ruby-lang.org/issues/18338"
+
o = Object.new
Encoding.default_external = Encoding::UTF_16BE
@@ -889,10 +870,22 @@ class TestM17N < Test::Unit::TestCase
assert_raise(Encoding::CompatibilityError) {
"%s%s" % [s("\xc2\xa1"), e("\xc2\xa1")]
}
+
+ assert_equal("\u3042".encode('Windows-31J'), "%c" % "\u3042\u3044".encode('Windows-31J'))
end
def test_sprintf_p
Encoding.list.each do |e|
+ unless e.ascii_compatible?
+ format = e.dummy? ? "%p".force_encoding(e) : "%p".encode(e)
+ assert_raise(Encoding::CompatibilityError) do
+ sprintf(format, nil)
+ end
+ assert_raise(Encoding::CompatibilityError) do
+ format % nil
+ end
+ next
+ end
format = "%p".force_encoding(e)
['', 'a', "\xC2\xA1", "\x00"].each do |s|
s.force_encoding(e)
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index 19f41de27e..6cd0b9acc3 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -33,7 +33,7 @@ class TestMarshal < Test::Unit::TestCase
end
def test_marshal
- a = [1, 2, 3, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
+ a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
assert_equal a, Marshal.load(Marshal.dump(a))
[[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
@@ -47,6 +47,26 @@ class TestMarshal < Test::Unit::TestCase
}
end
+ def test_marshal_integers
+ a = []
+ [-2, -1, 0, 1, 2].each do |i|
+ 0.upto(65).map do |exp|
+ a << 2**exp + i
+ end
+ end
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, 2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+ end
+
+ def test_marshal_small_bignum_backref
+ assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06")
+ end
+
StrClone = String.clone
def test_marshal_cloned_class
assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc"))))
@@ -72,6 +92,14 @@ class TestMarshal < Test::Unit::TestCase
TestMarshal.instance_eval { remove_const :StructInvalidMembers }
end
+ def test_load_range_as_struct
+ assert_raise(TypeError, 'GH-6832') do
+ # Can be obtained with:
+ # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))'
+ Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0")
+ end
+ end
+
class C
def initialize(str)
@str = str
@@ -922,7 +950,7 @@ class TestMarshal < Test::Unit::TestCase
end
def test_modules_and_classes_are_not_frozen
- objects = Marshal.load(encode([Object, Kernel]), freeze: true)
+ _objects = Marshal.load(encode([Object, Kernel]), freeze: true)
refute_predicate Object, :frozen?
refute_predicate Kernel, :frozen?
end
diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb
index daf0ec73ca..a04666890e 100644
--- a/test/ruby/test_method.rb
+++ b/test/ruby/test_method.rb
@@ -318,6 +318,17 @@ class TestMethod < Test::Unit::TestCase
assert_equal(:foo, o.foo)
end
+ PUBLIC_SINGLETON_TEST = Object.new
+ class << PUBLIC_SINGLETON_TEST
+ private
+ PUBLIC_SINGLETON_TEST.define_singleton_method(:dsm){}
+ def PUBLIC_SINGLETON_TEST.def; end
+ end
+ def test_define_singleton_method_public
+ assert_nil(PUBLIC_SINGLETON_TEST.dsm)
+ assert_nil(PUBLIC_SINGLETON_TEST.def)
+ end
+
def test_define_singleton_method_no_proc
o = Object.new
assert_raise(ArgumentError) {
@@ -566,9 +577,9 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters)
- assert_equal([[:req, :a], [:opt, :b], [:rest], [:req, :d], [:block, :e]], method(:mo8).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], method(:ma1).parameters)
- assert_equal([[:keyrest]], method(:mk1).parameters)
+ assert_equal([[:keyrest, :**]], method(:mk1).parameters)
assert_equal([[:keyrest, :o]], method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters)
@@ -592,9 +603,9 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters)
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters)
- assert_equal([[:req, :a], [:opt, :b], [:rest], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters)
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters)
- assert_equal([[:keyrest]], self.class.instance_method(:mk1).parameters)
+ assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters)
@@ -619,7 +630,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], method(:pma1).parameters)
- assert_equal([[:keyrest]], method(:pmk1).parameters)
+ assert_equal([[:keyrest, :**]], method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters)
@@ -643,7 +654,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters)
- assert_equal([[:keyrest]], self.class.instance_method(:pmk1).parameters)
+ assert_equal([[:keyrest, :**]], self.class.instance_method(:pmk1).parameters)
assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters)
assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters)
@@ -1045,7 +1056,7 @@ class TestMethod < Test::Unit::TestCase
assert_equal(sm, im.clone.bind(o).super_method)
end
- def test_super_method_removed
+ def test_super_method_removed_public
c1 = Class.new {private def foo; end}
c2 = Class.new(c1) {public :foo}
c3 = Class.new(c2) {def foo; end}
@@ -1055,20 +1066,35 @@ class TestMethod < Test::Unit::TestCase
assert_nil(m, Feature9781)
end
+ def test_super_method_removed_regular
+ c1 = Class.new { def foo; end }
+ c2 = Class.new(c1) { def foo; end }
+ assert_equal c1.instance_method(:foo), c2.instance_method(:foo).super_method
+ c1.remove_method :foo
+ assert_equal nil, c2.instance_method(:foo).super_method
+ end
+
def test_prepended_public_zsuper
- mod = EnvUtil.labeled_module("Mod") {private def foo; :ok end}
- mods = [mod]
+ mod = EnvUtil.labeled_module("Mod") {private def foo; [:ok] end}
obj = Object.new.extend(mod)
+
class << obj
public :foo
end
- 2.times do |i|
- mods.unshift(mod = EnvUtil.labeled_module("Mod#{i}") {def foo; end})
- obj.singleton_class.prepend(mod)
- end
+
+ mod1 = EnvUtil.labeled_module("Mod1") {def foo; [:mod1] + super end}
+ obj.singleton_class.prepend(mod1)
+
+ mod2 = EnvUtil.labeled_module("Mod2") {def foo; [:mod2] + super end}
+ obj.singleton_class.prepend(mod2)
+
m = obj.method(:foo)
- assert_equal(mods, mods.map {m.owner.tap {m = m.super_method}})
- assert_nil(m)
+ assert_equal mod2, m.owner
+ assert_equal mod1, m.super_method.owner
+ assert_equal obj.singleton_class, m.super_method.super_method.owner
+ assert_equal nil, m.super_method.super_method.super_method
+
+ assert_equal [:mod2, :mod1, :ok], obj.foo
end
def test_super_method_with_prepended_module
@@ -1181,6 +1207,147 @@ class TestMethod < Test::Unit::TestCase
assert_nil(super_method)
end
+ # Bug 18435
+ def test_instance_methods_owner_consistency
+ a = Module.new { def method1; end }
+
+ b = Class.new do
+ include a
+ protected :method1
+ end
+
+ assert_equal [:method1], b.instance_methods(false)
+ assert_equal b, b.instance_method(:method1).owner
+ end
+
+ def test_zsuper_method_removed
+ a = EnvUtil.labeled_class('A') do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal unbound, b.public_instance_method(:foo)
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088'
+ assert_raise(NameError, link) { b.instance_method(:foo) }
+ # For #test_method_list below, otherwise we get the same error as just above
+ b.remove_method(:foo)
+ end
+
+ def test_zsuper_method_removed_higher_method
+ a0 = EnvUtil.labeled_class('A0') do
+ def foo(arg1 = nil, arg2 = nil)
+ 0
+ end
+ end
+ line0 = __LINE__ - 4
+ a0_foo = a0.instance_method(:foo)
+
+ a = EnvUtil.labeled_class('A', a0) do
+ private
+ def foo(arg = nil)
+ 1
+ end
+ end
+ line = __LINE__ - 4
+
+ b = EnvUtil.labeled_class('B', a) do
+ public :foo
+ end
+
+ unbound = b.instance_method(:foo)
+
+ assert_equal a0_foo, unbound.super_method
+
+ a.remove_method(:foo)
+
+ assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect
+ assert_equal [[:opt, :arg]], unbound.parameters
+ assert_equal a0_foo, unbound.super_method
+
+ obj = b.new
+ assert_equal 1, unbound.bind_call(obj)
+
+ assert_include b.instance_methods(false), :foo
+ assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect
+ end
+
+ def test_zsuper_method_redefined_bind_call
+ c0 = EnvUtil.labeled_class('C0') do
+ def foo
+ [:foo]
+ end
+ end
+
+ c1 = EnvUtil.labeled_class('C1', c0) do
+ def foo
+ super + [:bar]
+ end
+ end
+ m1 = c1.instance_method(:foo)
+
+ c2 = EnvUtil.labeled_class('C2', c1) do
+ private :foo
+ end
+
+ assert_equal [:foo], c2.private_instance_methods(false)
+ m2 = c2.instance_method(:foo)
+
+ c1.class_exec do
+ remove_method :foo
+ def foo
+ [:bar2]
+ end
+ end
+
+ m3 = c2.instance_method(:foo)
+ c = c2.new
+ assert_equal [:foo, :bar], m1.bind_call(c)
+ assert_equal c1, m1.owner
+ assert_equal [:foo, :bar], m2.bind_call(c)
+ assert_equal c2, m2.owner
+ assert_equal [:bar2], m3.bind_call(c)
+ assert_equal c2, m3.owner
+ end
+
+ # Bug #18751
+ def method_equality_visbility_alias
+ c = Class.new do
+ class << self
+ alias_method :n, :new
+ private :new
+ end
+ end
+
+ assert_equal c.method(:n), c.method(:new)
+
+ assert_not_equal c.method(:n), Class.method(:new)
+ assert_equal c.method(:n) == Class.instance_method(:new).bind(c)
+
+ assert_not_equal c.method(:new), Class.method(:new)
+ assert_equal c.method(:new), Class.instance_method(:new).bind(c)
+ end
+
def rest_parameter(*rest)
rest
end
@@ -1188,7 +1355,7 @@ class TestMethod < Test::Unit::TestCase
def test_splat_long_array
if File.exist?('/etc/os-release') && File.read('/etc/os-release').include?('openSUSE Leap')
# For RubyCI's openSUSE machine http://rubyci.s3.amazonaws.com/opensuseleap/ruby-trunk/recent.html, which tends to die with NoMemoryError here.
- skip 'do not exhaust memory on RubyCI openSUSE Leap machine'
+ omit 'do not exhaust memory on RubyCI openSUSE Leap machine'
end
n = 10_000_000
assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]'
@@ -1299,7 +1466,7 @@ class TestMethod < Test::Unit::TestCase
::Object.prepend(M2)
m = Object.instance_method(:x)
- assert_equal M, m.owner
+ assert_equal M2, m.owner
end;
end
@@ -1390,7 +1557,7 @@ class TestMethod < Test::Unit::TestCase
# use_symbol = Object.instance_methods[0].is_a?(Symbol)
nummodule = nummethod = 0
mods = []
- ObjectSpace.each_object(Module) {|m| mods << m if m.name }
+ ObjectSpace.each_object(Module) {|m| mods << m if String === m.name }
mods = mods.sort_by {|m| m.name }
mods.each {|mod|
nummodule += 1
diff --git a/test/ruby/test_jit.rb b/test/ruby/test_mjit.rb
index 60e128b3d9..a97747eb88 100644
--- a/test/ruby/test_jit.rb
+++ b/test/ruby/test_mjit.rb
@@ -3,14 +3,15 @@ require 'test/unit'
require 'tmpdir'
require_relative '../lib/jit_support'
-# Test for --jit option
-class TestJIT < Test::Unit::TestCase
+# Test for --mjit option
+class TestMJIT < Test::Unit::TestCase
include JITSupport
IGNORABLE_PATTERNS = [
/\AJIT recompile: .+\n\z/,
/\AJIT inline: .+\n\z/,
/\AJIT cancel: .+\n\z/,
+ /\AJIT batch \([^)]+ms\): .+\n\z/,
/\ASuccessful MJIT finish\n\z/,
]
MAX_CACHE_PATTERNS = [
@@ -24,14 +25,11 @@ class TestJIT < Test::Unit::TestCase
# not supported yet
:defineclass,
- # to be tested
- :invokebuiltin,
-
# never used
:opt_invokebuiltin_delegate,
].each do |insn|
if !RubyVM::INSTRUCTION_NAMES.include?(insn.to_s)
- warn "instruction #{insn.inspect} is not defined but included in TestJIT::TEST_PENDING_INSNS"
+ warn "instruction #{insn.inspect} is not defined but included in TestMJIT::TEST_PENDING_INSNS"
end
end
@@ -46,16 +44,17 @@ class TestJIT < Test::Unit::TestCase
# ci.rvm.jp caches its build environment. Clean up temporary files left by SEGV.
if ENV['RUBY_DEBUG']&.include?('ci')
Dir.glob("#{ENV.fetch('TMPDIR', '/tmp')}/_ruby_mjit_p*u*.*").each do |file|
- puts "test/ruby/test_jit.rb: removing #{file}"
+ puts "test/ruby/test_mjit.rb: removing #{file}"
File.unlink(file)
end
end
- # ruby -w -Itest/lib test/ruby/test_jit.rb
+ # ruby -w -Itest/lib test/ruby/test_mjit.rb
if $VERBOSE
+ pid = $$
at_exit do
- unless TestJIT.untested_insns.empty?
- warn "you may want to add tests for following insns, when you have a chance: #{TestJIT.untested_insns.join(' ')}"
+ if pid == $$ && !TestMJIT.untested_insns.empty?
+ warn "you may want to add tests for following insns, when you have a chance: #{TestMJIT.untested_insns.join(' ')}"
end
end
end
@@ -63,17 +62,17 @@ class TestJIT < Test::Unit::TestCase
def setup
unless JITSupport.supported?
- skip 'JIT seems not supported on this platform'
+ omit 'JIT seems not supported on this platform'
end
self.class.setup
end
def test_compile_insn_nop
- assert_compile_once('nil rescue true', result_inspect: 'nil', insns: %i[nop])
+ assert_compile_twice('nil rescue true', result_inspect: 'nil', insns: %i[nop])
end
def test_compile_insn_local
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0])
begin;
foo = 1
foo
@@ -98,7 +97,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_blockparam
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2, insns: %i[getblockparam setblockparam])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, insns: %i[getblockparam setblockparam])
begin;
def foo(&b)
a = b
@@ -107,6 +106,7 @@ class TestJIT < Test::Unit::TestCase
end
print foo { 1 }
+ print foo { 1 }
end;
end
@@ -126,25 +126,25 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_getspecial
- assert_compile_once('$1', result_inspect: 'nil', insns: %i[getspecial])
+ assert_compile_twice('$1', result_inspect: 'nil', insns: %i[getspecial])
end
def test_compile_insn_setspecial
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial])
begin;
true if nil.nil?..nil.nil?
end;
end
def test_compile_insn_instancevariable
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable])
begin;
@foo = 1
@foo
end;
# optimized getinstancevariable call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, call_threshold: 2)
begin;
class A
def initialize
@@ -164,7 +164,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_classvariable
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 1, insns: %i[getclassvariable setclassvariable])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, insns: %i[getclassvariable setclassvariable])
begin;
class Foo
def self.foo
@@ -174,19 +174,22 @@ class TestJIT < Test::Unit::TestCase
end
print Foo.foo
+ print Foo.foo
end;
end
def test_compile_insn_constant
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getconstant setconstant])
- begin;
- FOO = 1
- FOO
- end;
+ EnvUtil.suppress_warning do
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_getconstant_path setconstant])
+ begin;
+ FOO = 1
+ FOO
+ end;
+ end
end
def test_compile_insn_global
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal])
begin;
$foo = 1
$foo
@@ -194,26 +197,27 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_putnil
- assert_compile_once('nil', result_inspect: 'nil', insns: %i[putnil])
+ assert_compile_twice('nil', result_inspect: 'nil', insns: %i[putnil])
end
def test_compile_insn_putself
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1, insns: %i[putself])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1, insns: %i[putself])
begin;
- proc { print "hello" }.call
+ 2.times { print "hello" }
end;
end
def test_compile_insn_putobject
- assert_compile_once('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_])
- assert_compile_once('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_])
- assert_compile_once('2', result_inspect: '2', insns: %i[putobject])
+ assert_compile_twice('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_])
+ assert_compile_twice('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_])
+ assert_compile_twice('2', result_inspect: '2', insns: %i[putobject])
end
def test_compile_insn_definemethod_definesmethod
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworld', success_count: 3, insns: %i[definemethod definesmethod])
+ verbose_bak, $VERBOSE = $VERBOSE, nil
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworldhelloworld', success_count: 3, insns: %i[definemethod definesmethod])
begin;
- print 1.times.map {
+ print 2.times.map {
def method_definition
'hello'
end
@@ -225,12 +229,14 @@ class TestJIT < Test::Unit::TestCase
method_definition + smethod_definition
}.join
end;
+ ensure
+ $VERBOSE = verbose_bak
end
def test_compile_insn_putspecialobject
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'a', success_count: 2, insns: %i[putspecialobject])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'aa', success_count: 2, insns: %i[putspecialobject])
begin;
- print 1.times.map {
+ print 2.times.map {
def a
'a'
end
@@ -242,16 +248,16 @@ class TestJIT < Test::Unit::TestCase
end;
end
- def test_compile_insn_putstring_concatstrings_tostring
- assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings tostring])
+ def test_compile_insn_putstring_concatstrings_objtostring
+ assert_compile_twice('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring])
end
def test_compile_insn_toregexp
- assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp])
+ assert_compile_twice('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp])
end
def test_compile_insn_newarray
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray])
begin;
a, b, c = 1, 2, 3
[a, b, c]
@@ -259,39 +265,39 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_newarraykwsplat
- assert_compile_once('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat])
+ assert_compile_twice('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat])
end
def test_compile_insn_intern_duparray
- assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray])
+ assert_compile_twice('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray])
end
def test_compile_insn_expandarray
- assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray])
+ assert_compile_twice('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray])
end
def test_compile_insn_concatarray
- assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray])
+ assert_compile_twice('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray])
end
def test_compile_insn_splatarray
- assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray])
+ assert_compile_twice('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray])
end
def test_compile_insn_newhash
- assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash])
+ assert_compile_twice('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash])
end
def test_compile_insn_duphash
- assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash])
+ assert_compile_twice('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash])
end
def test_compile_insn_newrange
- assert_compile_once('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange])
+ assert_compile_twice('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange])
end
def test_compile_insn_pop
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop])
begin;
a = false
b = 1
@@ -300,7 +306,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_dup
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup])
begin;
a = 1
a&.+(2)
@@ -308,7 +314,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_dupn
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn])
begin;
klass = Class.new
klass::X ||= true
@@ -316,19 +322,19 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_swap_topn
- assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn])
+ assert_compile_twice('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn])
end
def test_compile_insn_reput
- skip "write test"
+ omit "write test"
end
def test_compile_insn_setn
- assert_compile_once('[nil][0] = 1', result_inspect: '1', insns: %i[setn])
+ assert_compile_twice('[nil][0] = 1', result_inspect: '1', insns: %i[setn])
end
def test_compile_insn_adjuststack
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack])
begin;
x = [true]
x[0] ||= nil
@@ -337,57 +343,59 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_defined
- assert_compile_once('defined?(a)', result_inspect: 'nil', insns: %i[defined])
+ assert_compile_twice('defined?(a)', result_inspect: 'nil', insns: %i[defined])
end
def test_compile_insn_checkkeyword
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1, insns: %i[checkkeyword])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'truetrue', success_count: 1, insns: %i[checkkeyword])
begin;
def test(x: rand)
x
end
print test(x: true)
+ print test(x: true)
end;
end
def test_compile_insn_tracecoverage
- skip "write test"
+ omit "write test"
end
def test_compile_insn_defineclass
- skip "support this in mjit_compile (low priority)"
+ omit "support this in mjit_compile (low priority)"
end
def test_compile_insn_send
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 3, insns: %i[send])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4, insns: %i[send])
begin;
print proc { yield_self { 1 } }.call
+ print proc { yield_self { 1 } }.call
end;
end
def test_compile_insn_opt_str_freeze
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze])
begin;
'foo'.freeze
end;
end
def test_compile_insn_opt_nil_p
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p])
begin;
nil.nil?.nil?
end;
end
def test_compile_insn_opt_str_uminus
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus])
begin;
-'bar'
end;
end
def test_compile_insn_opt_newarray_max
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max])
begin;
a = 1
b = 2
@@ -396,7 +404,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_opt_newarray_min
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min])
begin;
a = 1
b = 2
@@ -405,11 +413,11 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_opt_send_without_block
- assert_compile_once('print', result_inspect: 'nil', insns: %i[opt_send_without_block])
+ assert_compile_twice('print', result_inspect: 'nil', insns: %i[opt_send_without_block])
end
def test_compile_insn_invokesuper
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4, insns: %i[invokesuper])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 4, insns: %i[invokesuper])
begin;
mod = Module.new {
def test
@@ -423,21 +431,23 @@ class TestJIT < Test::Unit::TestCase
end
}
print klass.new.test
+ print klass.new.test
end;
end
def test_compile_insn_invokeblock_leave
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2, insns: %i[invokeblock leave])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '22', success_count: 2, insns: %i[invokeblock leave])
begin;
def foo
yield
end
print foo { 2 }
+ print foo { 2 }
end;
end
def test_compile_insn_throw
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2, insns: %i[throw])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '44', success_count: 2, insns: %i[throw])
begin;
def test
proc do
@@ -450,11 +460,12 @@ class TestJIT < Test::Unit::TestCase
end.call
end
print test
+ print test
end;
end
def test_compile_insn_jump_branchif
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif])
begin;
a = false
1 + 1 while a
@@ -462,7 +473,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_branchunless
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless])
begin;
a = true
if a
@@ -474,31 +485,31 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_branchnil
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil])
begin;
a = 2
a&.+(1)
end;
end
- def test_compile_insn_checktype
- assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[checktype])
+ def test_compile_insn_objtostring
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring])
begin;
a = '2'
"4#{a}"
end;
end
- def test_compile_insn_inlinecache
- assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getinlinecache opt_setinlinecache])
+ def test_compile_insn_getconstant_path
+ assert_compile_twice('Struct', result_inspect: 'Struct', insns: %i[opt_getconstant_path])
end
def test_compile_insn_once
- assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once])
+ assert_compile_twice('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once])
end
def test_compile_insn_checkmatch_opt_case_dispatch
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
begin;
case 'hello'
when 'hello'
@@ -507,35 +518,45 @@ class TestJIT < Test::Unit::TestCase
end;
end
+ def test_compile_multiple_values_case
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch])
+ begin;
+ case 'hello'
+ when 'hello', 'world'
+ 'world'
+ end
+ end;
+ end
+
def test_compile_insn_opt_calc
- assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
- assert_compile_once('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
- assert_compile_once('4 + 2', result_inspect: '6')
+ assert_compile_twice('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
+ assert_compile_twice('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod])
+ assert_compile_twice('4 + 2', result_inspect: '6')
end
def test_compile_insn_opt_cmp
- assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq])
+ assert_compile_twice('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq])
end
def test_compile_insn_opt_rel
- assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge])
+ assert_compile_twice('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge])
end
def test_compile_insn_opt_ltlt
- assert_compile_once('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt])
+ assert_compile_twice('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt])
end
def test_compile_insn_opt_and
- assert_compile_once('1 & 3', result_inspect: '1', insns: %i[opt_and])
+ assert_compile_twice('1 & 3', result_inspect: '1', insns: %i[opt_and])
end
def test_compile_insn_opt_or
- assert_compile_once('1 | 3', result_inspect: '3', insns: %i[opt_or])
+ assert_compile_twice('1 | 3', result_inspect: '3', insns: %i[opt_or])
end
def test_compile_insn_opt_aref
# optimized call (optimized JIT) -> send call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, min_calls: 1, insns: %i[opt_aref])
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '221', success_count: 1, call_threshold: 2, insns: %i[opt_aref])
begin;
obj = Object.new
def obj.[](h)
@@ -544,11 +565,12 @@ class TestJIT < Test::Unit::TestCase
block = proc { |h| h[1] }
print block.call({ 1 => 2 })
+ print block.call({ 1 => 2 })
print block.call(obj)
end;
# send call -> optimized call (send JIT) -> optimized call
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 2, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 3, call_threshold: 2)
begin;
obj = Object.new
def obj.[](h)
@@ -563,11 +585,11 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_opt_aref_with
- assert_compile_once("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with])
+ assert_compile_twice("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with])
end
def test_compile_insn_opt_aset
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with])
begin;
hash = { '1' => 2 }
(hash['2'] = 2) + (hash[1.to_s] = 3)
@@ -575,7 +597,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_opt_length_size
- assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size])
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size])
begin;
array = [1, 2]
array.length + array.size
@@ -583,20 +605,29 @@ class TestJIT < Test::Unit::TestCase
end
def test_compile_insn_opt_empty_p
- assert_compile_once('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p])
+ assert_compile_twice('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p])
end
def test_compile_insn_opt_succ
- assert_compile_once('1.succ', result_inspect: '2', insns: %i[opt_succ])
+ assert_compile_twice('1.succ', result_inspect: '2', insns: %i[opt_succ])
end
def test_compile_insn_opt_not
- assert_compile_once('!!true', result_inspect: 'true', insns: %i[opt_not])
+ assert_compile_twice('!!true', result_inspect: 'true', insns: %i[opt_not])
end
def test_compile_insn_opt_regexpmatch2
- assert_compile_once("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2])
- assert_compile_once("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2])
+ assert_compile_twice("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2])
+ assert_compile_twice("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2])
+ end
+
+ def test_compile_insn_invokebuiltin
+ iseq = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first)
+ p RubyVM::InstructionSequence.of([].method(:sample)).to_a
+ EOS
+ insns = collect_insns(iseq)
+ mark_tested_insn(:invokebuiltin, used_insns: insns)
+ assert_eval_with_jit('print [].sample(1); print [].sample(1)', stdout: '[][]', success_count: 1)
end
def test_compile_insn_opt_invokebuiltin_delegate_leave
@@ -605,17 +636,40 @@ class TestJIT < Test::Unit::TestCase
EOS
insns = collect_insns(iseq)
mark_tested_insn(:opt_invokebuiltin_delegate_leave, used_insns: insns)
- assert_eval_with_jit('print "\x00".unpack("c")', stdout: '[0]', success_count: 1)
+ assert_eval_with_jit('print "\x00".unpack("c");print "\x00".unpack("c")', stdout: '[0][0]', success_count: 1)
+ end
+
+ def test_compile_insn_checkmatch
+ assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch])
+ begin;
+ ary = %w(hello good-bye)
+ case 'hello'
+ when *ary
+ 'world'
+ end
+ end;
end
- def test_jit_output
- out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, min_calls: 5)
- assert_equal("MJIT\n" * 5, out)
- assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err)
+ def test_compile_opt_pc
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1)
+ begin;
+ def test(arg = 'hello')
+ print arg
+ end
+ test
+ test
+ end;
+ end
+
+ def test_mjit_output
+ out, err = eval_with_jit('4.times { puts "MJIT" }', verbose: 1, call_threshold: 2)
+ assert_equal("MJIT\n" * 4, out)
+ assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1$/, err)
assert_match(/^Successful MJIT finish$/, err)
end
def test_nothing_to_unload_with_jit_wait
+ omit 'unload_units is removed for now'
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
begin;
def a1() a2() end
@@ -634,6 +688,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_unload_units_on_fiber
+ omit 'unload_units is removed for now'
assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS)
begin;
def a1() a2(false); a2(true) end
@@ -656,9 +711,10 @@ class TestJIT < Test::Unit::TestCase
end
def test_unload_units_and_compaction
+ omit 'unload_units is removed for now'
Dir.mktmpdir("jit_test_unload_units_") do |dir|
# MIN_CACHE_SIZE is 10
- out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, min_calls: 1, max_cache: 10)
+ out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, call_threshold: 2, max_cache: 10)
begin;
i = 0
while i < 11
@@ -697,11 +753,13 @@ class TestJIT < Test::Unit::TestCase
if RUBY_PLATFORM.match?(/mswin/)
# "Permission Denied" error is preventing to remove so file on AppVeyor/RubyCI.
- skip 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
- else
- # verify .c files are deleted on unload_units
- assert_send([Dir, :empty?, dir], debug_info)
+ omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
+ end
+ if RUBY_PLATFORM.match?(/darwin/)
+ omit '.bundle.dSYM directory is left but removing it is not supported for now'
end
+ # verify .c files are deleted on unload_units
+ assert_send([Dir, :empty?, dir], debug_info)
end
end
@@ -711,12 +769,13 @@ class TestJIT < Test::Unit::TestCase
def arr
[nil, [:type => :development]]
end
+ arr
p arr
end;
end
def test_local_stack_on_exception
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2)
begin;
def b
raise
@@ -725,17 +784,18 @@ class TestJIT < Test::Unit::TestCase
end
def a
- # Calling #b should be vm_exec, not direct mjit_exec.
+ # Calling #b should be vm_exec, not direct jit_exec.
# Otherwise `1` on local variable would be purged.
1 + b
end
print a
+ print a
end;
end
def test_local_stack_with_sp_motion_by_blockargs
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 2)
begin;
def b(base)
1
@@ -752,15 +812,16 @@ class TestJIT < Test::Unit::TestCase
end
print a
+ print a
end;
end
def test_catching_deep_exception
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 4)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4)
begin;
- def catch_true(paths, prefixes) # catch_except_p: TRUE
- prefixes.each do |prefix| # catch_except_p: TRUE
- paths.each do |path| # catch_except_p: FALSE
+ def catch_true(paths, prefixes) # catch_except_p: true
+ prefixes.each do |prefix| # catch_except_p: true
+ paths.each do |path| # catch_except_p: false
return path
end
end
@@ -771,11 +832,12 @@ class TestJIT < Test::Unit::TestCase
end
print wrapper(['1'], ['2'])
+ print wrapper(['1'], ['2'])
end;
end
def test_inlined_builtin_methods
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 1, call_threshold: 2)
begin;
def test
float = 0.0
@@ -789,7 +851,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_inlined_c_method
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 2, recompile_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 1, recompile_count: 1, call_threshold: 2)
begin;
def test(obj, recursive: nil)
if recursive
@@ -807,7 +869,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_inlined_exivar
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 3, recompile_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 4, recompile_count: 2, call_threshold: 2)
begin;
class Foo < Hash
def initialize
@@ -826,7 +888,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_inlined_undefined_ivar
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 3, min_calls: 3)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 4, call_threshold: 2)
begin;
class Foo
def initialize
@@ -847,7 +909,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_inlined_setivar_frozen
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 2, min_calls: 3)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 1, call_threshold: 2)
begin;
class A
def a
@@ -869,7 +931,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_inlined_getconstant
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, call_threshold: 2)
begin;
FOO = 1
def const
@@ -881,7 +943,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_attr_reader
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 3, call_threshold: 2)
begin;
class A
attr_reader :a, :b
@@ -912,7 +974,7 @@ class TestJIT < Test::Unit::TestCase
print(2 * a.test)
end;
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 3, call_threshold: 2)
begin;
class Hoge
attr_reader :foo
@@ -945,23 +1007,24 @@ class TestJIT < Test::Unit::TestCase
end
def test_heap_promotion_of_ivar_in_the_middle_of_jit
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\n", success_count: 2, min_calls: 2)
+ omit if GC.using_rvargc?
+
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\n", success_count: 2, call_threshold: 2)
begin;
class A
def initialize
@iv0 = nil
@iv1 = []
- @iv2 = nil
end
def test(add)
@iv0.nil?
- @iv2.nil?
add_ivar if add
@iv1.empty?
end
def add_ivar
+ @iv2 = nil
@iv3 = nil
end
end
@@ -973,23 +1036,27 @@ class TestJIT < Test::Unit::TestCase
end
def test_jump_to_precompiled_branch
- assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0", success_count: 1, min_calls: 1)
+ assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0.0", success_count: 1, call_threshold: 2)
begin;
def test(foo)
".#{foo unless foo == 1}" if true
end
print test(0)
+ print test(0)
end;
end
def test_clean_so
if RUBY_PLATFORM.match?(/mswin/)
- skip 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
+ omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.'
+ end
+ if RUBY_PLATFORM.match?(/darwin/)
+ omit '.bundle.dSYM directory is left but removing it is not supported for now'
end
Dir.mktmpdir("jit_test_clean_so_") do |dir|
code = "x = 0; 10.times {|i|x+=i}"
eval_with_jit({"TMPDIR"=>dir}, code)
- assert_send([Dir, :empty?, dir])
+ assert_send([Dir, :empty?, dir], "Directory #{dir} was not empty:\n#{Dir.glob("#{dir}/*").join("\n")}\n")
eval_with_jit({"TMPDIR"=>dir}, code, save_temps: true)
assert_not_send([Dir, :empty?, dir])
end
@@ -998,10 +1065,13 @@ class TestJIT < Test::Unit::TestCase
def test_clean_objects_on_exec
if /mswin|mingw/ =~ RUBY_PLATFORM
# TODO: check call stack and close handle of code which is not on stack, and remove objects on best-effort basis
- skip 'Removing so file being used does not work on Windows'
+ omit 'Removing so file being used does not work on Windows'
+ end
+ if RUBY_PLATFORM.match?(/darwin/)
+ omit '.bundle.dSYM directory is left but removing it is not supported for now'
end
Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir|
- eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1)
+ eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2)
begin;
def a; end; a
exec "true"
@@ -1033,7 +1103,7 @@ class TestJIT < Test::Unit::TestCase
end
def test_frame_omitted_inlining
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\n", success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\ntrue\n", success_count: 2, call_threshold: 2)
begin;
class Integer
remove_method :zero?
@@ -1042,27 +1112,27 @@ class TestJIT < Test::Unit::TestCase
end
end
- 3.times do
+ 4.times do
p 0.zero?
end
end;
end
def test_block_handler_with_possible_frame_omitted_inlining
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n", success_count: 2, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n70.0\n", success_count: 2, call_threshold: 2)
begin;
def multiply(a, b)
a *= b
end
- 3.times do
+ 4.times do
p multiply(7.0, 10.0)
end
end;
end
def test_builtin_frame_omitted_inlining
- assert_eval_with_jit('0.zero?; 0.zero?; 3.times { p 0.zero? }', stdout: "true\ntrue\ntrue\n", success_count: 1, min_calls: 2)
+ assert_eval_with_jit('0.zero?; 0.zero?; 3.times { p 0.zero? }', stdout: "true\ntrue\ntrue\n", success_count: 1, call_threshold: 2)
end
def test_program_counter_with_regexpmatch
@@ -1094,15 +1164,15 @@ class TestJIT < Test::Unit::TestCase
end
def test_mjit_pause_wait
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, min_calls: 1)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, call_threshold: 2)
begin;
- RubyVM::JIT.pause
+ RubyVM::MJIT.pause
proc {}.call
end;
end
def test_not_cancel_by_tracepoint_class
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 1, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, call_threshold: 2)
begin;
TracePoint.new(:class) {}.enable
2.times {}
@@ -1110,15 +1180,27 @@ class TestJIT < Test::Unit::TestCase
end
def test_cancel_by_tracepoint
- assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, min_calls: 2)
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, call_threshold: 2)
begin;
TracePoint.new(:line) {}.enable
2.times {}
end;
end
+ def test_cancel_by_bop_redefinition
+ assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, call_threshold: 2)
+ begin;
+ class Integer
+ def <(x)
+ true
+ end
+ end
+ 2.times {}
+ end;
+ end
+
def test_caller_locations_without_catch_table
- out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1)
+ out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2)
begin;
def b # 2
caller_locations.first # 3
@@ -1136,56 +1218,40 @@ class TestJIT < Test::Unit::TestCase
assert_equal("-e:8:in `a'\n", lines[1])
end
- def test_fork_with_mjit_worker_thread
- Dir.mktmpdir("jit_test_fork_with_mjit_worker_thread_") do |dir|
- # min_calls: 2 to skip fork block
- out, err = eval_with_jit({ "TMPDIR" => dir }, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 2, verbose: 1)
+ def test_jit_failure
+ _, err = Dir.mktmpdir("jit_test_jit_failure") do |dir|
+ eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2, verbose: 1)
begin;
- def before_fork; end
- def after_fork; end
-
- before_fork; before_fork # the child should not delete this .o file
- pid = Process.fork do # this child should not delete shared .pch file
- sleep 2.0 # to prevent mixing outputs on Solaris
- after_fork; after_fork # this child does not share JIT-ed after_fork with parent
+ 2.times do
+ class A
+ end
end
- after_fork; after_fork # this parent does not share JIT-ed after_fork with child
-
- Process.waitpid(pid)
end;
- success_count = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
- debug_info = "stdout:\n```\n#{out}\n```\n\nstderr:\n```\n#{err}```\n"
- assert_equal(3, success_count, debug_info)
-
- # assert no remove error
- assert_equal("Successful MJIT finish\n" * 2, err.gsub(/^#{JIT_SUCCESS_PREFIX}:[^\n]+\n/, ''), debug_info)
-
- # ensure objects are deleted
- assert_send([Dir, :empty?, dir], debug_info)
end
- end if defined?(fork)
+ assert_match(/^MJIT warning: .+ unsupported instruction: defineclass/, err)
+ end
private
# The shortest way to test one proc
- def assert_compile_once(script, result_inspect:, insns: [], uplevel: 1)
+ def assert_compile_twice(script, result_inspect:, insns: [], uplevel: 1)
if script.match?(/\A\n.+\n\z/m)
script = script.gsub(/^/, ' ')
else
script = " #{script} "
end
- assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1)
+ assert_eval_with_jit("test = proc {#{script}}; p test.call; p test.call", stdout: "#{result_inspect}\n#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1)
end
# Shorthand for normal test cases
- def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, min_calls: 1, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: [])
- out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls, max_cache: max_cache)
+ def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, call_threshold: 2, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: [])
+ out, err = eval_with_jit(script, verbose: 1, call_threshold: call_threshold, max_cache: max_cache)
success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size
recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size
- # Add --jit-verbose=2 logs for cl.exe because compiler's error message is suppressed
- # for cl.exe with --jit-verbose=1. See `start_process` in mjit_worker.c.
+ # Add --mjit-verbose=2 logs for cl.exe because compiler's error message is suppressed
+ # for cl.exe with --mjit-verbose=1. See `start_process` in mjit.c.
if RUBY_PLATFORM.match?(/mswin/) && success_count != success_actual
- out2, err2 = eval_with_jit(script, verbose: 2, min_calls: min_calls, max_cache: max_cache)
+ out2, err2 = eval_with_jit(script, verbose: 2, call_threshold: call_threshold, max_cache: max_cache)
end
# Make sure that the script has insns expected to be tested
@@ -1219,11 +1285,13 @@ class TestJIT < Test::Unit::TestCase
end
def mark_tested_insn(insn, used_insns:, uplevel: 1)
- unless used_insns.include?(insn)
+ # Currently, this check emits a false-positive warning against opt_regexpmatch2,
+ # so the insn is excluded explicitly. See https://bugs.ruby-lang.org/issues/18269
+ if !used_insns.include?(insn) && insn != :opt_regexpmatch2
$stderr.puts
warn "'#{insn}' insn is not included in the script. Actual insns are: #{used_insns.join(' ')}\n", uplevel: uplevel
end
- TestJIT.untested_insns.delete(insn)
+ TestMJIT.untested_insns.delete(insn)
end
# Collect block's insns or defined method's insns, which are expected to be JIT-ed.
diff --git a/test/ruby/test_mjit_debug.rb b/test/ruby/test_mjit_debug.rb
new file mode 100644
index 0000000000..0b50acc68d
--- /dev/null
+++ b/test/ruby/test_mjit_debug.rb
@@ -0,0 +1,17 @@
+require_relative 'test_mjit'
+
+return unless defined?(TestMJIT)
+return if ENV.key?('APPVEYOR')
+return if ENV.key?('RUBYCI_NICKNAME')
+return if ENV['RUBY_DEBUG']&.include?('ci') # ci.rvm.jp
+return if /mswin/ =~ RUBY_PLATFORM
+
+class TestMJITDebug < TestMJIT
+ @@test_suites.delete TestMJIT if self.respond_to? :on_parallel_worker?
+
+ def setup
+ super
+ # let `#eval_with_jit` use --mjit-debug
+ @mjit_debug = true
+ end
+end
diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb
index be23b84c46..6129f0cbbe 100644
--- a/test/ruby/test_module.rb
+++ b/test/ruby/test_module.rb
@@ -253,6 +253,14 @@ class TestModule < Test::Unit::TestCase
assert_operator(Math, :const_defined?, "PI")
assert_not_operator(Math, :const_defined?, :IP)
assert_not_operator(Math, :const_defined?, "IP")
+
+ # Test invalid symbol name
+ # [Bug #20245]
+ EnvUtil.under_gc_stress do
+ assert_raise(EncodingError) do
+ Math.const_defined?("\xC3")
+ end
+ end
end
def each_bad_constants(m, &b)
@@ -475,6 +483,15 @@ class TestModule < Test::Unit::TestCase
assert_not_include(mod.ancestor_list, BasicObject)
end
+ def test_module_collected_extended_object
+ m1 = labeled_module("m1")
+ m2 = labeled_module("m2")
+ Object.new.extend(m1)
+ GC.start
+ m1.include(m2)
+ assert_equal([m1, m2], m1.ancestors)
+ end
+
def test_dup
OtherSetup.call
@@ -519,6 +536,16 @@ class TestModule < Test::Unit::TestCase
assert_raise(ArgumentError) { Module.new { include } }
end
+ def test_include_before_initialize
+ m = Class.new(Module) do
+ def initialize(...)
+ include Enumerable
+ super
+ end
+ end.new
+ assert_operator(m, :<, Enumerable)
+ end
+
def test_prepend_self
m = Module.new
assert_equal([m], m.ancestors)
@@ -553,6 +580,26 @@ class TestModule < Test::Unit::TestCase
assert_equal(2, a2.b)
end
+ def test_ancestry_of_duped_classes
+ m = Module.new
+ sc = Class.new
+ a = Class.new(sc) do
+ def b; 2 end
+ prepend m
+ end
+
+ a2 = a.dup.new
+
+ assert_kind_of Object, a2
+ assert_kind_of sc, a2
+ refute_kind_of a, a2
+ assert_kind_of m, a2
+
+ assert_kind_of Class, a2.class
+ assert_kind_of sc.singleton_class, a2.class
+ assert_same sc, a2.class.superclass
+ end
+
def test_gc_prepend_chain
assert_separately([], <<-EOS)
10000.times { |i|
@@ -737,6 +784,25 @@ class TestModule < Test::Unit::TestCase
assert_equal([:m1, :m0, :m, :sc, :m1, :m0, :c], sc.new.m)
end
+ def test_protected_include_into_included_module
+ m1 = Module.new do
+ def other_foo(other)
+ other.foo
+ end
+
+ protected
+ def foo
+ :ok
+ end
+ end
+ m2 = Module.new
+ c1 = Class.new { include m2 }
+ c2 = Class.new { include m2 }
+ m2.include(m1)
+
+ assert_equal :ok, c1.new.other_foo(c2.new)
+ end
+
def test_instance_methods
assert_equal([:user, :user2], User.instance_methods(false).sort)
assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort)
@@ -936,6 +1002,15 @@ class TestModule < Test::Unit::TestCase
assert_equal([:bClass1], BClass.public_instance_methods(false))
end
+ def test_undefined_instance_methods
+ assert_equal([], AClass.undefined_instance_methods)
+ assert_equal([], BClass.undefined_instance_methods)
+ c = Class.new(AClass) {undef aClass}
+ assert_equal([:aClass], c.undefined_instance_methods)
+ c = Class.new(c)
+ assert_equal([], c.undefined_instance_methods)
+ end
+
def test_s_public
o = (c = Class.new(AClass)).new
assert_raise(NoMethodError, /private method/) {o.aClass1}
@@ -1014,6 +1089,28 @@ class TestModule < Test::Unit::TestCase
assert_raise(NoMethodError, /protected method/) {o.aClass2}
end
+ def test_visibility_method_return_value
+ no_arg_results = nil
+ c = Module.new do
+ singleton_class.send(:public, :public, :private, :protected, :module_function)
+ def foo; end
+ def bar; end
+ no_arg_results = [public, private, protected, module_function]
+ end
+
+ assert_equal([nil]*4, no_arg_results)
+
+ assert_equal(:foo, c.private(:foo))
+ assert_equal(:foo, c.public(:foo))
+ assert_equal(:foo, c.protected(:foo))
+ assert_equal(:foo, c.module_function(:foo))
+
+ assert_equal([:foo, :bar], c.private(:foo, :bar))
+ assert_equal([:foo, :bar], c.public(:foo, :bar))
+ assert_equal([:foo, :bar], c.protected(:foo, :bar))
+ assert_equal([:foo, :bar], c.module_function(:foo, :bar))
+ end
+
def test_s_constants
c1 = Module.constants
Object.module_eval "WALTER = 99"
@@ -1239,8 +1336,6 @@ class TestModule < Test::Unit::TestCase
end
end
include LangModuleSpecInObject
- module LangModuleTop
- end
puts "ok" if LangModuleSpecInObject::LangModuleTop == LangModuleTop
INPUT
@@ -1643,6 +1738,45 @@ class TestModule < Test::Unit::TestCase
assert_match(/::X\u{df}:/, c.new.to_s)
end
+
+ def test_const_added
+ eval(<<~RUBY)
+ module TestConstAdded
+ @memo = []
+ class << self
+ attr_accessor :memo
+
+ def const_added(sym)
+ memo << sym
+ end
+ end
+ CONST = 1
+ module SubModule
+ end
+
+ class SubClass
+ end
+ end
+ TestConstAdded::OUTSIDE_CONST = 2
+ module TestConstAdded::OutsideSubModule; end
+ class TestConstAdded::OutsideSubClass; end
+ RUBY
+ TestConstAdded.const_set(:CONST_SET, 3)
+ assert_equal [
+ :CONST,
+ :SubModule,
+ :SubClass,
+ :OUTSIDE_CONST,
+ :OutsideSubModule,
+ :OutsideSubClass,
+ :CONST_SET,
+ ], TestConstAdded.memo
+ ensure
+ if self.class.const_defined? :TestConstAdded
+ self.class.send(:remove_const, :TestConstAdded)
+ end
+ end
+
def test_method_added
memo = []
mod = Module.new do
@@ -2224,6 +2358,18 @@ class TestModule < Test::Unit::TestCase
assert_equal(:foo, removed)
end
+ def test_frozen_prepend_remove_method
+ [Module, Class].each do |klass|
+ mod = klass.new do
+ prepend(Module.new)
+ def foo; end
+ end
+ mod.freeze
+ assert_raise(FrozenError, '[Bug #19166]') { mod.send(:remove_method, :foo) }
+ assert_equal([:foo], mod.instance_methods(false))
+ end
+ end
+
def test_prepend_class_ancestors
bug6658 = '[ruby-core:45919]'
m = labeled_module("m")
@@ -3034,6 +3180,7 @@ class TestModule < Test::Unit::TestCase
end
def test_redefinition_mismatch
+ omit "Investigating trunk-mjit failure on ci.rvm.jp" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
m = Module.new
m.module_eval "A = 1", __FILE__, line = __LINE__
e = assert_raise_with_message(TypeError, /is not a module/) {
@@ -3135,6 +3282,62 @@ class TestModule < Test::Unit::TestCase
assert_match(/::Foo$/, mod.name, '[Bug #14895]')
end
+ def test_iclass_memory_leak
+ # [Bug #19550]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ mod = Module.new
+ Class.new do
+ include mod
+ end
+ end
+ 1_000.times(&code)
+ PREP
+ 3_000_000.times(&code)
+ CODE
+ end
+
+ def test_complemented_method_entry_memory_leak
+ # [Bug #19894] [Bug #19896]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ $c = Class.new do
+ def foo; end
+ end
+
+ $m = Module.new do
+ refine $c do
+ def foo; end
+ end
+ end
+
+ Class.new do
+ using $m
+
+ def initialize
+ o = $c.new
+ o.method(:foo).unbind
+ end
+ end.new
+ end
+ 1_000.times(&code)
+ PREP
+ 300_000.times(&code)
+ CODE
+ end
+
+ def test_module_clone_memory_leak
+ # [Bug #19901]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Module.new.clone
+ end
+ 1_000.times(&code)
+ PREP
+ 1_000_000.times(&code)
+ CODE
+ end
+
private
def assert_top_method_is_private(method)
diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb
index 0593cb535d..ab492743f6 100644
--- a/test/ruby/test_numeric.rb
+++ b/test/ruby/test_numeric.rb
@@ -324,6 +324,9 @@ class TestNumeric < Test::Unit::TestCase
e = 1.step(10, {by: "1"})
assert_raise(TypeError) {e.next}
assert_raise(TypeError) {e.size}
+ e = 1.step(to: "10")
+ assert_raise(ArgumentError) {e.next}
+ assert_raise(ArgumentError) {e.size}
assert_equal(bignum*2+1, (-bignum).step(bignum, 1).size)
assert_equal(bignum*2, (-bignum).step(bignum-1, 1).size)
diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb
index 83208bbcdb..26256b80db 100644
--- a/test/ruby/test_object.rb
+++ b/test/ruby/test_object.rb
@@ -853,6 +853,15 @@ class TestObject < Test::Unit::TestCase
x.instance_variable_set(:@bar, 42)
assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect)
+ # Bug: [ruby-core:19167]
+ x = Object.new
+ x.instance_variable_set(:@foo, NilClass)
+ assert_match(/\A#<Object:0x\h+ @foo=NilClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, TrueClass)
+ assert_match(/\A#<Object:0x\h+ @foo=TrueClass>\z/, x.inspect)
+ x.instance_variable_set(:@foo, FalseClass)
+ assert_match(/\A#<Object:0x\h+ @foo=FalseClass>\z/, x.inspect)
+
# #inspect does not call #to_s anymore
feature6130 = '[ruby-core:43238]'
x = Object.new
@@ -925,6 +934,19 @@ class TestObject < Test::Unit::TestCase
end
end
+ def test_singleton_class_freeze
+ x = Object.new
+ xs = x.singleton_class
+ x.freeze
+ assert_predicate(xs, :frozen?)
+
+ y = Object.new
+ ys = y.singleton_class
+ ys.prepend(Module.new)
+ y.freeze
+ assert_predicate(ys, :frozen?, '[Bug #19169]')
+ end
+
def test_redef_method_missing
bug5473 = '[ruby-core:40287]'
['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code|
@@ -993,4 +1015,13 @@ class TestObject < Test::Unit::TestCase
end
EOS
end
+
+ def test_frozen_inspect
+ obj = Object.new
+ obj.instance_variable_set(:@a, "a")
+ ins = obj.inspect
+ obj.freeze
+
+ assert_equal(ins, obj.inspect)
+ end
end
diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb
index 24a190b2df..a7cfb064a8 100644
--- a/test/ruby/test_objectspace.rb
+++ b/test/ruby/test_objectspace.rb
@@ -65,6 +65,11 @@ End
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)}
end
+ def test_id2ref_invalid_symbol_id
+ msg = /is not symbol id value/
+ assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]) }
+ end
+
def test_count_objects
h = {}
ObjectSpace.count_objects(h)
@@ -219,7 +224,7 @@ End
assert_same(new_obj, found[0])
end
- def test_each_object_no_gabage
+ def test_each_object_no_garbage
assert_separately([], <<-End)
GC.disable
eval('begin; 1.times{}; rescue; ensure; end')
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index cbae6d5e8c..88de3fa8b9 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -437,6 +437,20 @@ class TestRubyOptimization < Test::Unit::TestCase
message(bug12565) {disasm(:add_one_and_two)})
end
+ def test_c_func_with_sp_offset_under_tailcall
+ tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
+ begin;
+ def calc_one_plus_two
+ 1 + 2.abs
+ end
+
+ def one_plus_two
+ calc_one_plus_two
+ end
+ end;
+ assert_equal(3, one_plus_two)
+ end
+
def test_tailcall_interrupted_by_sigint
bug12576 = 'ruby-core:76327'
script = "#{<<-"begin;"}\n#{<<~'end;'}"
@@ -510,7 +524,7 @@ class TestRubyOptimization < Test::Unit::TestCase
end
def test_tailcall_not_to_grow_stack
- skip 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
+ omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
bug16161 = '[ruby-core:94881]'
tailcall("#{<<-"begin;"}\n#{<<~"end;"}")
@@ -903,4 +917,34 @@ class TestRubyOptimization < Test::Unit::TestCase
raise "END"
end;
end
+
+ class Objtostring
+ end
+
+ def test_objtostring
+ assert_raise(NoMethodError){"#{BasicObject.new}"}
+ assert_redefine_method('Symbol', 'to_s', <<-'end')
+ assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}"
+ end
+ assert_redefine_method('NilClass', 'to_s', <<-'end')
+ assert_match %r{\A#<NilClass:0x[0-9a-f]+>\z}, "#{nil}"
+ end
+ assert_redefine_method('TrueClass', 'to_s', <<-'end')
+ assert_match %r{\A#<TrueClass:0x[0-9a-f]+>\z}, "#{true}"
+ end
+ assert_redefine_method('FalseClass', 'to_s', <<-'end')
+ assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}"
+ end
+ assert_redefine_method('Integer', 'to_s', <<-'end')
+ (-1..10).each { |i|
+ assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}"
+ }
+ end
+ assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}"
+ assert_match %r{\A#<Class:0x[0-9a-f]+>\z}, "#{Class.new}"
+ assert_match %r{\A#<Module:0x[0-9a-f]+>\z}, "#{Module.new}"
+ o = Object.new
+ def o.to_s; 1; end
+ assert_match %r{\A#<Object:0x[0-9a-f]+>\z}, "#{o}"
+ end
end
diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb
index c9ca9c3dfd..0ee36857ba 100644
--- a/test/ruby/test_pack.rb
+++ b/test/ruby/test_pack.rb
@@ -4,22 +4,36 @@ require 'test/unit'
class TestPack < Test::Unit::TestCase
def test_pack
- $format = "c2x5CCxsdils_l_a6";
+ format = "c2x5CCxsdils_l_a6";
# Need the expression in here to force ary[5] to be numeric. This avoids
# test2 failing because ary2 goes str->numeric->str and ary does not.
ary = [1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,-32767,-123456,"abcdef"]
- $x = ary.pack($format)
- ary2 = $x.unpack($format)
+ x = ary.pack(format)
+ ary2 = x.unpack(format)
assert_equal(ary.length, ary2.length)
assert_equal(ary.join(':'), ary2.join(':'))
- assert_match(/def/, $x)
+ assert_match(/def/, x)
- $x = [-1073741825]
- assert_equal($x, $x.pack("q").unpack("q"))
+ x = [-1073741825]
+ assert_equal(x, x.pack("q").unpack("q"))
- $x = [-1]
- assert_equal($x, $x.pack("l").unpack("l"))
+ x = [-1]
+ assert_equal(x, x.pack("l").unpack("l"))
+ end
+
+ def test_ascii_incompatible
+ assert_raise(Encoding::CompatibilityError) do
+ ["foo"].pack("u".encode("UTF-32BE"))
+ end
+
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack("C".encode("UTF-32BE"))
+ end
+
+ assert_raise(Encoding::CompatibilityError) do
+ "foo".unpack1("C".encode("UTF-32BE"))
+ end
end
def test_pack_n
@@ -638,6 +652,14 @@ EXPECTED
end;
end
+ def test_bug_18343
+ bug18343 = '[ruby-core:106096] [Bug #18343]'
+ assert_separately(%W[- #{bug18343}], <<-'end;')
+ bug = ARGV.shift
+ assert_raise(ArgumentError, bug){[0].pack('c', {})}
+ end;
+ end
+
def test_pack_unpack_m0
assert_equal("", [""].pack("m0"))
assert_equal("AA==", ["\0"].pack("m0"))
@@ -755,41 +777,15 @@ EXPECTED
end
def test_pack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
- assert_equal "\000", [0].pack("*U")
- end
-
- $VERBOSE = true
-
- _, err = capture_output do
+ assert_warn(%r%unknown pack directive '\*' in '\*U'$%) do
assert_equal "\000", [0].pack("*U")
end
-
- assert_match %r%unknown pack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_unpack_garbage
- verbose = $VERBOSE
- $VERBOSE = false
-
- assert_silent do
- assert_equal [0], "\000".unpack("*U")
- end
-
- $VERBOSE = true
-
- _, err = capture_output do
+ assert_warn(%r%unknown unpack directive '\*' in '\*U'$%) do
assert_equal [0], "\000".unpack("*U")
end
-
- assert_match %r%unknown unpack directive '\*' in '\*U'$%, err
- ensure
- $VERBOSE = verbose
end
def test_invalid_warning
@@ -869,4 +865,30 @@ EXPECTED
assert_equal "hogefuga", "aG9nZWZ1Z2E=".unpack1("m")
assert_equal "01000001", "A".unpack1("B*")
end
+
+ def test_unpack1_offset
+ assert_equal 65, "ZA".unpack1("C", offset: 1)
+ assert_equal "01000001", "YZA".unpack1("B*", offset: 2)
+ assert_nil "abc".unpack1("C", offset: 3)
+ assert_raise_with_message(ArgumentError, /offset can't be negative/) {
+ "a".unpack1("C", offset: -1)
+ }
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack1("C", offset: 2)
+ }
+ assert_nil "a".unpack1("C", offset: 1)
+ end
+
+ def test_unpack_offset
+ assert_equal [65], "ZA".unpack("C", offset: 1)
+ assert_equal ["01000001"], "YZA".unpack("B*", offset: 2)
+ assert_equal [nil, nil, nil], "abc".unpack("CCC", offset: 3)
+ assert_raise_with_message(ArgumentError, /offset can't be negative/) {
+ "a".unpack("C", offset: -1)
+ }
+ assert_raise_with_message(ArgumentError, /offset outside of string/) {
+ "a".unpack("C", offset: 2)
+ }
+ assert_equal [nil], "a".unpack("C", offset: 1)
+ end
end
diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb
index 3120016e60..ca9c305639 100644
--- a/test/ruby/test_parse.rb
+++ b/test/ruby/test_parse.rb
@@ -577,6 +577,10 @@ class TestParse < Test::Unit::TestCase
assert_equal(' ^~~~~'"\n", e.message.lines.last)
e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax')
assert_equal(' ^~~~~'"\n", e.message.lines.last)
+
+ e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax')
+ assert_match(/^\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )$/x, e.message.lines.last)
+ assert_not_include(e.message, "invalid multibyte char")
end
def test_question
@@ -607,6 +611,8 @@ class TestParse < Test::Unit::TestCase
assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax')
assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax')
+
+ assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF"))
end
def test_percent
@@ -930,6 +936,10 @@ x = __ENCODING__
assert_no_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")}
end
+ def test_shadowing_private_local_variable
+ assert_equal 1, eval("_ = 1; [[2]].each{ |(_)| }; _")
+ end
+
def test_unused_variable
o = Object.new
assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; a=1; nil; end")}
@@ -1037,6 +1047,22 @@ x = __ENCODING__
assert_syntax_error(" 0b\n", /\^/)
end
+ def test_unclosed_unicode_escape_at_eol_bug_19750
+ assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}")
+ begin;
+ assert_syntax_error("/\\u", /too short escape sequence/)
+ assert_syntax_error("/\\u{", /unterminated regexp meets end of file/)
+ assert_syntax_error("/\\u{\\n", /invalid Unicode list/)
+ assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/)
+ re = eval("/a#\\u{\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ re = eval("/a#\\u\n$/x")
+ assert_match(re, 'a')
+ assert_not_match(re, 'a#')
+ end;
+ end
+
def test_error_def_in_argument
assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}")
begin;
@@ -1044,7 +1070,7 @@ x = __ENCODING__
end;
assert_syntax_error("def\nf(000)end", /^ \^~~/)
- assert_syntax_error("def\nf(&)end", /^ \^/)
+ assert_syntax_error("def\nf(&0)end", /^ \^/)
end
def test_method_location_in_rescue
@@ -1355,6 +1381,52 @@ x = __ENCODING__
end;
end
+ def test_if_after_class
+ assert_valid_syntax('module if true; Object end::Kernel; end')
+ assert_valid_syntax('module while true; break Object end::Kernel; end')
+ assert_valid_syntax('class if true; Object end::Kernel; end')
+ assert_valid_syntax('class while true; break Object end::Kernel; end')
+ end
+
+ def test_escaped_space
+ assert_syntax_error('x = \ 42', /escaped space/)
+ end
+
+ def test_label
+ expected = {:foo => 1}
+
+ code = '{"foo": 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ code = '{foo: 1}'
+ assert_valid_syntax(code)
+ assert_equal(expected, eval(code))
+
+ class << (obj = Object.new)
+ attr_reader :arg
+ def set(arg)
+ @arg = arg
+ end
+ end
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set foo:
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+
+ assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}")
+ do;
+ obj.set "foo":
+ 1
+ end;
+ assert_equal(expected, eval(code))
+ assert_equal(expected, obj.arg)
+ end
+
=begin
def test_past_scope_variable
assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}}
diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb
index 35c41eb8b6..0337e5d945 100644
--- a/test/ruby/test_pattern_matching.rb
+++ b/test/ruby/test_pattern_matching.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
require 'test/unit'
-experimental, Warning[:experimental] = Warning[:experimental], false # suppress "warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!"
-eval "\n#{<<~'END_of_GUARD'}", binding, __FILE__, __LINE__
class TestPatternMatching < Test::Unit::TestCase
class NullFormatter
def message_for(corrections)
@@ -466,6 +464,8 @@ END
true
end
end
+
+ assert_valid_syntax("1 in ^(1\n)")
end
def test_array_pattern
@@ -1157,6 +1157,28 @@ END
end
end
+ bug18890 = assert_warning(/(?:.*:[47]: warning: unused literal ignored\n){2}/) do
+ eval("#{<<~';;;'}")
+ proc do |i|
+ case i
+ in a:
+ 0 # line 4
+ a
+ in "b":
+ 0 # line 7
+ b
+ else
+ false
+ end
+ end
+ ;;;
+ end
+ [{a: 42}, {b: 42}].each do |i|
+ assert_block('newline should be significant after pattern label') do
+ bug18890.call(i)
+ end
+ end
+
assert_syntax_error(%q{
case _
in a:, a:
@@ -1550,20 +1572,16 @@ END
assert_equal false, (1 in 2)
end
- def assert_experimental_warning(code)
- w = Warning[:experimental]
-
- Warning[:experimental] = false
- assert_warn('') {eval(code)}
-
- Warning[:experimental] = true
- assert_warn(/is experimental/) {eval(code)}
- ensure
- Warning[:experimental] = w
- end
+ def test_bug18990
+ {a: 0} => a:
+ assert_equal 0, a
+ {a: 0} => a:
+ assert_equal 0, a
- def test_experimental_warning
- assert_experimental_warning("case [0]; in [*, 0, *]; end")
+ {a: 0} in a:
+ assert_equal 0, a
+ {a: 0} in a:
+ assert_equal 0, a
end
################################################################
@@ -1693,5 +1711,3 @@ END
end
end
end
-END_of_GUARD
-Warning[:experimental] = experimental
diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb
index 16efd13d7c..2b3590d4d0 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -412,6 +412,11 @@ class TestProc < Test::Unit::TestCase
assert_equal(:foo, bc.foo)
end
+ def test_dup_subclass
+ c1 = Class.new(Proc)
+ assert_equal c1, c1.new{}.dup.class, '[Bug #17545]'
+ end
+
def test_binding
b = proc {|x, y, z| proc {}.binding }.call(1, 2, 3)
class << b; attr_accessor :foo; end
@@ -440,6 +445,11 @@ class TestProc < Test::Unit::TestCase
assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427')
end
+ def test_binding_error_unless_ruby_frame
+ define_singleton_method :binding_from_c!, method(:binding).to_proc >> ->(bndg) {bndg}
+ assert_raise(RuntimeError) { binding_from_c! }
+ end
+
def test_proc_lambda
assert_raise(ArgumentError) { proc }
assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} }
@@ -847,6 +857,88 @@ class TestProc < Test::Unit::TestCase
assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x})
end
+ def test_proc_args_single_kw_no_autosplat
+ pr = proc {|c, a: 1| [c, a] }
+ assert_equal [nil, 1], pr.call()
+ assert_equal [1, 1], pr.call(1)
+ assert_equal [[1], 1], pr.call([1])
+ assert_equal [1, 1], pr.call(1,2)
+ assert_equal [[1, 2], 1], pr.call([1,2])
+
+ assert_equal [nil, 3], pr.call(a: 3)
+ assert_equal [1, 3], pr.call(1, a: 3)
+ assert_equal [[1], 3], pr.call([1], a: 3)
+ assert_equal [1, 3], pr.call(1,2, a: 3)
+ assert_equal [[1, 2], 3], pr.call([1,2], a: 3)
+ end
+
+ def test_proc_args_single_kwsplat_no_autosplat
+ pr = proc {|c, **kw| [c, kw] }
+ assert_equal [nil, {}], pr.call()
+ assert_equal [1, {}], pr.call(1)
+ assert_equal [[1], {}], pr.call([1])
+ assert_equal [1, {}], pr.call(1,2)
+ assert_equal [[1, 2], {}], pr.call([1,2])
+
+ assert_equal [nil, {a: 3}], pr.call(a: 3)
+ assert_equal [1, {a: 3}], pr.call(1, a: 3)
+ assert_equal [[1], {a: 3}], pr.call([1], a: 3)
+ assert_equal [1, {a: 3}], pr.call(1,2, a: 3)
+ assert_equal [[1, 2], {a: 3}], pr.call([1,2], a: 3)
+ end
+
+ def test_proc_args_multiple_kw_autosplat
+ pr = proc {|c, b, a: 1| [c, b, a] }
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c=nil, b=nil, a: 1| [c, b, a] }
+ assert_equal [nil, nil, 1], pr.call([])
+ assert_equal [1, nil, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c, b=nil, a: 1| [c, b, a] }
+ assert_equal [1, nil, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c=nil, b, a: 1| [c, b, a] }
+ assert_equal [nil, 1, 1], pr.call([1])
+ assert_equal [1, 2, 1], pr.call([1,2])
+
+ pr = proc {|c, *b, a: 1| [c, b, a] }
+ assert_equal [1, [], 1], pr.call([1])
+ assert_equal [1, [2], 1], pr.call([1,2])
+
+ pr = proc {|*c, b, a: 1| [c, b, a] }
+ assert_equal [[], 1, 1], pr.call([1])
+ assert_equal [[1], 2, 1], pr.call([1,2])
+ end
+
+ def test_proc_args_multiple_kwsplat_autosplat
+ pr = proc {|c, b, **kw| [c, b, kw] }
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c=nil, b=nil, **kw| [c, b, kw] }
+ assert_equal [nil, nil, {}], pr.call([])
+ assert_equal [1, nil, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c, b=nil, **kw| [c, b, kw] }
+ assert_equal [1, nil, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c=nil, b, **kw| [c, b, kw] }
+ assert_equal [nil, 1, {}], pr.call([1])
+ assert_equal [1, 2, {}], pr.call([1,2])
+
+ pr = proc {|c, *b, **kw| [c, b, kw] }
+ assert_equal [1, [], {}], pr.call([1])
+ assert_equal [1, [2], {}], pr.call([1,2])
+
+ pr = proc {|*c, b, **kw| [c, b, kw] }
+ assert_equal [[], 1, {}], pr.call([1])
+ assert_equal [[1], 2, {}], pr.call([1,2])
+ end
+
def test_proc_args_only_rest
pr = proc {|*c| c }
assert_equal [], pr.call()
@@ -1229,6 +1321,54 @@ class TestProc < Test::Unit::TestCase
assert_empty(pr.parameters.map{|_,n|n}.compact)
end
+ def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759
+ def self.yielder_ab(splat)
+ yield([:a, :b], *splat)
+ end
+
+ res = yielder_ab([[:aa, :bb], Hash.ruby2_keywords_hash({k: :k})]) do |a, b, k:|
+ [a, b, k]
+ end
+ assert_equal([[:a, :b], [:aa, :bb], :k], res)
+
+ def self.yielder(splat)
+ yield(*splat)
+ end
+ res = yielder([ [:a, :b] ]){|a, b, **| [a, b]}
+ assert_equal([:a, :b], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({c: 1}) ]){|a, b, **| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+
+ res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **nil| [a, b]}
+ assert_equal([[:a, :b], nil], res)
+ end
+
+ def test_parameters_lambda
+ assert_equal([], proc {}.parameters(lambda: true))
+ assert_equal([], proc {||}.parameters(lambda: true))
+ assert_equal([[:req, :a]], proc {|a|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters(lambda: true))
+ assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:opt, :b]], proc {|a, b=:b|}.parameters(lambda: true))
+ assert_equal([[:rest, :a]], proc {|*a|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, c|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters(lambda: true))
+ assert_equal([[:req], [:block, :b]], proc {|(a), &b|a}.parameters(lambda: true))
+ assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: true))
+
+ pr = eval("proc{|"+"(_),"*30+"|}")
+ assert_empty(pr.parameters(lambda: true).map{|_,n|n}.compact)
+
+ assert_equal([[:opt, :a]], lambda {|a|}.parameters(lambda: false))
+ assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: false))
+ end
+
def pm0() end
def pm1(a) end
def pm2(a, b) end
@@ -1261,7 +1401,7 @@ class TestProc < Test::Unit::TestCase
assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters)
assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters)
assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters)
- assert_equal([[:keyrest]], method(:pmk1).to_proc.parameters)
+ assert_equal([[:keyrest, :**]], method(:pmk1).to_proc.parameters)
assert_equal([[:keyrest, :o]], method(:pmk2).to_proc.parameters)
assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).to_proc.parameters)
assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).to_proc.parameters)
@@ -1597,18 +1737,24 @@ class TestProc < Test::Unit::TestCase
def test_isolate
assert_raise_with_message ArgumentError, /\(a\)/ do
a = :a
- Proc.new{p a}.isolate.call
+ Proc.new{p a}.isolate
end
assert_raise_with_message ArgumentError, /\(a\)/ do
a = :a
1.times{
- Proc.new{p a}.isolate.call
+ Proc.new{p a}.isolate
}
end
assert_raise_with_message ArgumentError, /yield/ do
- Proc.new{yield}.isolate.call
+ Proc.new{yield}.isolate
+ end
+
+
+ name = "\u{2603 26a1}"
+ assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
+ eval("#{name} = :#{name}; Proc.new {p #{name}}").isolate
end
# binding
diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 98c934945c..1228f2c0b1 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -3,7 +3,6 @@
require 'test/unit'
require 'tempfile'
require 'timeout'
-require 'io/wait'
require 'rbconfig'
class TestProcess < Test::Unit::TestCase
@@ -169,7 +168,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_pgroup
- skip "system(:pgroup) is not supported" if windows?
+ omit "system(:pgroup) is not supported" if windows?
assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) }
io = IO.popen([RUBY, "-e", "print Process.getpgrp"])
@@ -208,39 +207,39 @@ class TestProcess < Test::Unit::TestCase
n = max
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
- assert_equal("[#{n}, #{n}]\n", io.read)
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
n = 0
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
- assert_equal("[#{n}, #{n}]\n", io.read)
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
n = max
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
- assert_equal("[#{n}, #{n}]", io.read.chomp)
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io|
+ assert_equal("#{n}\n#{n}\n", io.read)
}
m, n = 0, max
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
- assert_equal("[#{m}, #{n}]", io.read.chomp)
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
+ assert_equal("#{m}\n#{n}\n", io.read)
}
m, n = 0, 0
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
- assert_equal("[#{m}, #{n}]", io.read.chomp)
+ "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io|
+ assert_equal("#{m}\n#{n}\n", io.read)
}
n = max
IO.popen([RUBY, "-e",
- "p Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
+ "puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)",
:rlimit_core=>n, :rlimit_cpu=>3600]) {|io|
- assert_equal("[#{n}, #{n}]\n[3600, 3600]", io.read.chomp)
+ assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read)
}
assert_raise(ArgumentError) do
@@ -511,7 +510,7 @@ class TestProcess < Test::Unit::TestCase
UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask']
def test_execopts_umask
- skip "umask is not supported" if windows?
+ omit "umask is not supported" if windows?
IO.popen([*UMASK, :umask => 0]) {|io|
assert_equal("0000", io.read.chomp)
}
@@ -837,7 +836,7 @@ class TestProcess < Test::Unit::TestCase
STDERR=>"out", STDOUT=>[:child, STDERR])
assert_equal("errout", File.read("out"))
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'",
STDOUT=>"out",
STDERR=>[:child, 3],
@@ -889,7 +888,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_popen_extra_fd
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io|
@@ -918,7 +917,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_fd_inheritance
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_pipe {|r, w|
system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w)
w.close
@@ -964,7 +963,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_close_others
- skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
+ omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows?
with_tmpchdir {|d|
with_pipe {|r, w|
system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true)
@@ -1058,7 +1057,7 @@ class TestProcess < Test::Unit::TestCase
}
}
rescue NotImplementedError
- skip "IO#close_on_exec= is not supported"
+ omit "IO#close_on_exec= is not supported"
end
end unless windows? # passing non-stdio fds is not supported on Windows
@@ -1610,7 +1609,7 @@ class TestProcess < Test::Unit::TestCase
else
assert_kind_of(Integer, max)
assert_predicate(max, :positive?)
- skip "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
+ omit "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM
gs = Process.groups
assert_operator(gs.size, :<=, max)
gs[0] ||= 0
@@ -1637,7 +1636,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_setegid
- skip "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
+ omit "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/
assert_nothing_raised(TypeError) {Process.egid += 0}
rescue NotImplementedError
end
@@ -1698,7 +1697,7 @@ class TestProcess < Test::Unit::TestCase
if /freebsd|openbsd/ =~ RUBY_PLATFORM
# this relates #4173
# When ruby can use 2 cores, signal and wait4 may miss the signal.
- skip "this fails on FreeBSD and OpenBSD on multithreaded environment"
+ omit "this fails on FreeBSD and OpenBSD on multithreaded environment"
end
signal_received = []
IO.pipe do |sig_r, sig_w|
@@ -1718,7 +1717,7 @@ class TestProcess < Test::Unit::TestCase
Process.wait pid
assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable'
end
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # checking -DMJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
+ if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE. It may trigger extra SIGCHLD.
assert_equal [true], signal_received.uniq, "[ruby-core:19744]"
else
assert_equal [true], signal_received, "[ruby-core:19744]"
@@ -1733,7 +1732,7 @@ class TestProcess < Test::Unit::TestCase
def test_no_curdir
if /solaris/i =~ RUBY_PLATFORM
- skip "Temporary skip to avoid CI failures after commit to use realpath on required files"
+ omit "Temporary omit to avoid CI failures after commit to use realpath on required files"
end
with_tmpchdir {|d|
Dir.mkdir("vd")
@@ -1775,7 +1774,7 @@ class TestProcess < Test::Unit::TestCase
def test_aspawn_too_long_path
if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC)
- skip "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
+ omit "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10"
end
bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613'
assert_fail_too_long_path(%w"echo |", bug4315)
@@ -1817,7 +1816,7 @@ class TestProcess < Test::Unit::TestCase
with_tmpchdir do
assert_nothing_raised('[ruby-dev:12261]') do
- EnvUtil.timeout(3) do
+ EnvUtil.timeout(10) do
pid = spawn('yes | ls')
Process.waitpid pid
end
@@ -1876,6 +1875,28 @@ class TestProcess < Test::Unit::TestCase
assert_not_equal(cpid, dpid)
end
+ def test_daemon_detached
+ IO.popen("-", "r+") do |f|
+ if f
+ assert_equal(f.pid, Process.wait(f.pid))
+
+ dpid, ppid, dsid = 3.times.map {Integer(f.gets)}
+
+ message = "daemon #{dpid} should be detached"
+ assert_not_equal($$, ppid, message) # would be 1 almost always
+ assert_raise(Errno::ECHILD, message) {Process.wait(dpid)}
+ assert_kind_of(Integer, Process.kill(0, dpid), message)
+ assert_equal(dpid, dsid)
+
+ break # close f, and let the daemon resume and exit
+ end
+ Process.setsid rescue nil
+ Process.daemon(false, true)
+ puts $$, Process.ppid, Process.getsid
+ $stdin.gets # wait for the above assertions using signals
+ end
+ end
+
if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM
def test_daemon_no_threads
pid, data = IO.popen("-", "r+") do |f|
@@ -1958,7 +1979,7 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_uid
- skip "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/
+ omit "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
[30000, [Process.uid, ENV["USER"]]].each do |uid, user|
@@ -1989,8 +2010,8 @@ class TestProcess < Test::Unit::TestCase
end
def test_execopts_gid
- skip "Process.groups not implemented on Windows platform" if windows?
- skip "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/
+ omit "Process.groups not implemented on Windows platform" if windows?
+ omit "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/
feature6975 = '[ruby-core:47414]'
groups = Process.groups.map do |g|
@@ -2511,7 +2532,7 @@ EOS
end
def test_forked_child_handles_signal
- skip "fork not supported" unless Process.respond_to?(:fork)
+ omit "fork not supported" unless Process.respond_to?(:fork)
assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]')
require 'timeout'
pid = fork { sleep }
@@ -2545,4 +2566,172 @@ EOS
end
assert_empty(Process.waitall)
end
+
+ def test__fork
+ r, w = IO.pipe
+ pid = Process._fork
+ if pid == 0
+ begin
+ r.close
+ w << "ok: #$$"
+ w.close
+ ensure
+ exit!
+ end
+ else
+ w.close
+ assert_equal("ok: #{pid}", r.read)
+ r.close
+ Process.waitpid(pid)
+ end
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_hook
+ %w(fork Process.fork).each do |method|
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", [], [], feature17795, timeout: 60) do |r, e|
+ module ForkHook
+ def _fork
+ p :before
+ ret = super
+ p :after
+ ret
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ pid = #{ method }
+ p pid
+ Process.waitpid(pid) if pid
+ end;
+ assert_equal([], e)
+ assert_equal(":before", r.shift)
+ assert_equal(":after", r.shift)
+ s = r.map {|s| s.chomp }.sort #=> [pid, ":after", "nil"]
+ assert_match(/^\d+$/, s[0]) # pid
+ assert_equal(":after", s[1])
+ assert_equal("nil", s[2])
+ end
+ end
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_hook_popen
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", %w(:before :after :after foo bar), [], feature17795, timeout: 60)
+ module ForkHook
+ def _fork
+ p :before
+ ret = super
+ p :after
+ ret
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ IO.popen("-") {|io|
+ if !io
+ puts "foo"
+ else
+ puts io.read + "bar"
+ end
+ }
+ end;
+ end if Process.respond_to?(:_fork)
+
+ def test__fork_wrong_type_hook
+ feature17795 = '[ruby-core:103400] [Feature #17795]'
+ assert_in_out_err([], <<-"end;", ["OK"], [], feature17795, timeout: 60)
+ module ForkHook
+ def _fork
+ "BOO"
+ end
+ end
+
+ Process.singleton_class.prepend(ForkHook)
+
+ begin
+ fork
+ rescue TypeError
+ puts "OK"
+ end
+ end;
+ end if Process.respond_to?(:_fork)
+
+ def test_concurrent_group_and_pid_wait
+ # Use a pair of pipes that will make long_pid exit when this test exits, to avoid
+ # leaking temp processes.
+ long_rpipe, long_wpipe = IO.pipe
+ short_rpipe, short_wpipe = IO.pipe
+ # This process should run forever
+ long_pid = fork do
+ [short_rpipe, short_wpipe, long_wpipe].each(&:close)
+ long_rpipe.read
+ end
+ # This process will exit
+ short_pid = fork do
+ [long_rpipe, long_wpipe, short_wpipe].each(&:close)
+ short_rpipe.read
+ end
+ t1, t2, t3 = nil
+ EnvUtil.timeout(5) do
+ t1 = Thread.new do
+ Process.waitpid long_pid
+ end
+ # Wait for us to be blocking in a call to waitpid2
+ Thread.pass until t1.stop?
+ short_wpipe.close # Make short_pid exit
+
+ # The short pid has exited, so -1 should pick that up.
+ assert_equal short_pid, Process.waitpid(-1)
+
+ # Terminate t1 for the next phase of the test.
+ t1.kill
+ t1.join
+
+ t2 = Thread.new do
+ Process.waitpid -1
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t2.stop?
+ t3 = Thread.new do
+ Process.waitpid long_pid
+ rescue Errno::ECHILD
+ nil
+ end
+ Thread.pass until t3.stop?
+
+ # it's actually nondeterministic which of t2 or t3 will receive the wait (this
+ # nondeterminism comes from the behaviour of the underlying system calls)
+ long_wpipe.close
+ assert_equal [long_pid], [t2, t3].map(&:value).compact
+ end
+ ensure
+ [t1, t2, t3].each { _1&.kill rescue nil }
+ [t1, t2, t3].each { _1&.join rescue nil }
+ [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil }
+ end if defined?(fork)
+
+ def test_handle_interrupt_with_fork
+ Thread.handle_interrupt(RuntimeError => :never) do
+ Thread.current.raise(RuntimeError, "Queued error")
+
+ assert_predicate Thread, :pending_interrupt?
+
+ pid = Process.fork do
+ if Thread.pending_interrupt?
+ exit 1
+ end
+ end
+
+ _, status = Process.waitpid2(pid)
+ assert_predicate status, :success?
+
+ assert_predicate Thread, :pending_interrupt?
+ end
+ rescue RuntimeError
+ # Ignore.
+ end if defined?(fork)
end
diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb
index 13b7329269..a4beffd689 100644
--- a/test/ruby/test_rand.rb
+++ b/test/ruby/test_rand.rb
@@ -317,7 +317,7 @@ class TestRand < Test::Unit::TestCase
assert_equal(r1, r2, bug5661)
assert_fork_status(1, '[ruby-core:82100] [Bug #13753]') do
- Random::DEFAULT.rand(4)
+ Random.rand(4)
end
rescue NotImplementedError
end
@@ -336,6 +336,14 @@ class TestRand < Test::Unit::TestCase
}
end
+ def test_seed_leading_zero_guard
+ guard = 1<<32
+ range = 0...(1<<32)
+ all_assertions_foreach(nil, 0, 1, 2) do |i|
+ assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range))
+ end
+ end
+
def test_marshal
bug3656 = '[ruby-core:31622]'
assert_raise(TypeError, bug3656) {
@@ -395,8 +403,8 @@ class TestRand < Test::Unit::TestCase
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
verbose, $VERBOSE = $VERBOSE, nil
- seed = Random::DEFAULT::seed
- rand1 = Random::DEFAULT::rand
+ seed = Random.seed
+ rand1 = Random.rand
$VERBOSE = verbose
rand2 = Random.new(seed).rand
assert_equal(rand1, rand2)
diff --git a/test/ruby/test_random_formatter.rb b/test/ruby/test_random_formatter.rb
new file mode 100644
index 0000000000..a5072099e1
--- /dev/null
+++ b/test/ruby/test_random_formatter.rb
@@ -0,0 +1,123 @@
+require 'test/unit'
+require 'random/formatter'
+
+module Random::Formatter
+ module FormatterTest
+ def test_random_bytes
+ assert_equal(16, @it.random_bytes.size)
+ assert_equal(Encoding::ASCII_8BIT, @it.random_bytes.encoding)
+ 65.times do |idx|
+ assert_equal(idx, @it.random_bytes(idx).size)
+ end
+ end
+
+ def test_hex
+ s = @it.hex
+ assert_equal(16 * 2, s.size)
+ assert_match(/\A\h+\z/, s)
+ 33.times do |idx|
+ s = @it.hex(idx)
+ assert_equal(idx * 2, s.size)
+ assert_match(/\A\h*\z/, s)
+ end
+ end
+
+ def test_hex_encoding
+ assert_equal(Encoding::US_ASCII, @it.hex.encoding)
+ end
+
+ def test_base64
+ assert_equal(16, @it.base64.unpack1('m*').size)
+ 17.times do |idx|
+ assert_equal(idx, @it.base64(idx).unpack1('m*').size)
+ end
+ end
+
+ def test_urlsafe_base64
+ safe = /[\n+\/]/
+ 65.times do |idx|
+ assert_not_match(safe, @it.urlsafe_base64(idx))
+ end
+ # base64 can include unsafe byte
+ assert((0..10000).any? {|idx| safe =~ @it.base64(idx)}, "None of base64(0..10000) is url-safe")
+ end
+
+ def test_random_number_float
+ 101.times do
+ v = @it.random_number
+ assert_in_range(0.0...1.0, v)
+ end
+ end
+
+ def test_random_number_float_by_zero
+ 101.times do
+ v = @it.random_number(0)
+ assert_in_range(0.0...1.0, v)
+ end
+ end
+
+ def test_random_number_int
+ 101.times do |idx|
+ next if idx.zero?
+ v = @it.random_number(idx)
+ assert_in_range(0...idx, v)
+ end
+ end
+
+ def test_uuid
+ uuid = @it.uuid
+ assert_equal(36, uuid.size)
+
+ # Check time_hi_and_version and clock_seq_hi_res bits (RFC 4122 4.4)
+ assert_equal('4', uuid[14])
+ assert_include(%w'8 9 a b', uuid[19])
+
+ assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid)
+ end
+
+ def test_alphanumeric
+ 65.times do |n|
+ an = @it.alphanumeric(n)
+ assert_match(/\A[0-9a-zA-Z]*\z/, an)
+ assert_equal(n, an.length)
+ end
+ end
+
+ def assert_in_range(range, result, mesg = nil)
+ assert(range.cover?(result), build_message(mesg, "Expected #{result} to be in #{range}"))
+ end
+ end
+
+ module NotDefaultTest
+ def test_random_number_not_default
+ msg = "random_number should not be affected by srand"
+ seed = srand(0)
+ x = @it.random_number(1000)
+ 10.times do|i|
+ srand(0)
+ return unless @it.random_number(1000) == x
+ end
+ srand(0)
+ assert_not_equal(x, @it.random_number(1000), msg)
+ ensure
+ srand(seed) if seed
+ end
+ end
+
+ class TestClassMethods < Test::Unit::TestCase
+ include FormatterTest
+
+ def setup
+ @it = Random
+ end
+ end
+
+ class TestInstanceMethods < Test::Unit::TestCase
+ include FormatterTest
+ include NotDefaultTest
+
+ def setup
+ @it = Random.new
+ end
+ end
+end
diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb
index 8ac1930be6..d84184c3bf 100644
--- a/test/ruby/test_range.rb
+++ b/test/ruby/test_range.rb
@@ -3,6 +3,7 @@ require 'test/unit'
require 'delegate'
require 'timeout'
require 'bigdecimal'
+require 'date'
require 'rbconfig/sizeof'
class TestRange < Test::Unit::TestCase
@@ -539,6 +540,8 @@ class TestRange < Test::Unit::TestCase
assert_not_operator(0..10, :===, 11)
assert_operator(5..nil, :===, 11)
assert_not_operator(5..nil, :===, 0)
+ assert_operator(nil..10, :===, 0)
+ assert_operator(nil..nil, :===, 0)
end
def test_eqq_string
@@ -546,7 +549,7 @@ class TestRange < Test::Unit::TestCase
assert_not_operator('A'..'Z', :===, 'ana')
assert_operator('A'.., :===, 'ANA')
assert_operator(..'Z', :===, 'ANA')
- assert_operator(nil..nil, :===, 'ANA')
+ assert_raise(TypeError) {(nil..nil) === 'ANA'}
end
def test_eqq_time
@@ -583,6 +586,28 @@ class TestRange < Test::Unit::TestCase
assert_operator(c.new(0)..c.new(10), :===, c.new(5), bug12003)
end
+ def test_eqq_unbounded_ruby_bug_19864
+ t1 = Date.today
+ t2 = t1 + 1
+ assert_equal(true, (..t1) === t1)
+ assert_equal(false, (..t1) === t2)
+ assert_equal(true, (..t2) === t1)
+ assert_equal(true, (..t2) === t2)
+ assert_equal(false, (...t1) === t1)
+ assert_equal(false, (...t1) === t2)
+ assert_equal(true, (...t2) === t1)
+ assert_equal(false, (...t2) === t2)
+
+ assert_equal(true, (t1..) === t1)
+ assert_equal(true, (t1..) === t2)
+ assert_equal(false, (t2..) === t1)
+ assert_equal(true, (t2..) === t2)
+ assert_equal(true, (t1...) === t1)
+ assert_equal(true, (t1...) === t2)
+ assert_equal(false, (t2...) === t1)
+ assert_equal(true, (t2...) === t2)
+ end
+
def test_eqq_non_iteratable
k = Class.new do
include Comparable
@@ -599,11 +624,16 @@ class TestRange < Test::Unit::TestCase
assert_include("a"..."z", "y")
assert_not_include("a"..."z", "z")
assert_not_include("a".."z", "cc")
- assert_include("a".., "c")
- assert_not_include("a".., "5")
+ assert_raise(TypeError) {("a"..).include?("c")}
+ assert_raise(TypeError) {("a"..).include?("5")}
+
assert_include(0...10, 5)
assert_include(5..., 10)
assert_not_include(5..., 0)
+ assert_raise(TypeError) {(.."z").include?("z")}
+ assert_raise(TypeError) {(..."z").include?("z")}
+ assert_include(..10, 10)
+ assert_not_include(...10, 10)
end
def test_cover
@@ -666,6 +696,35 @@ class TestRange < Test::Unit::TestCase
assert_not_operator(1..10, :cover?, 3...3)
assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz')
assert_not_operator(1..10, :cover?, 1...10.1)
+
+ assert_operator(..2, :cover?, 1)
+ assert_operator(..2, :cover?, 2)
+ assert_not_operator(..2, :cover?, 3)
+ assert_not_operator(...2, :cover?, 2)
+ assert_not_operator(..2, :cover?, "2")
+ assert_operator(..2, :cover?, ..2)
+ assert_operator(..2, :cover?, ...2)
+ assert_not_operator(..2, :cover?, .."2")
+ assert_not_operator(...2, :cover?, ..2)
+
+ assert_not_operator(2.., :cover?, 1)
+ assert_operator(2.., :cover?, 2)
+ assert_operator(2..., :cover?, 3)
+ assert_operator(2.., :cover?, 2)
+ assert_not_operator(2.., :cover?, "2")
+ assert_operator(2.., :cover?, 2..)
+ assert_operator(2.., :cover?, 2...)
+ assert_not_operator(2.., :cover?, "2"..)
+ assert_not_operator(2..., :cover?, 2..)
+ assert_operator(2..., :cover?, 3...)
+ assert_not_operator(2..., :cover?, 3..)
+ assert_not_operator(3.., :cover?, 2..)
+
+ assert_operator(nil..., :cover?, Object.new)
+ assert_operator(nil..., :cover?, nil...)
+ assert_operator(nil.., :cover?, nil...)
+ assert_not_operator(nil..., :cover?, nil..)
+ assert_not_operator(nil..., :cover?, 1..)
end
def test_beg_len
@@ -744,6 +803,9 @@ class TestRange < Test::Unit::TestCase
assert_equal 5, (1.1...6).size
assert_equal 42, (1..42).each.size
assert_nil ("a"..."z").size
+ assert_nil ("a"...).size
+ assert_nil (..."z").size # [Bug #18983]
+ assert_nil (nil...nil).size # [Bug #18983]
assert_equal Float::INFINITY, (1...).size
assert_equal Float::INFINITY, (1.0...).size
diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb
index fe9de64c4c..a51ce3dc99 100644
--- a/test/ruby/test_rational.rb
+++ b/test/ruby/test_rational.rb
@@ -823,6 +823,8 @@ class Rational_Test < Test::Unit::TestCase
ng[5, 1, '5/_3']
ng[5, 3, '5/3_']
ng[5, 3, '5/3x']
+
+ ng[5, 1, '5/-3']
end
def test_parse_zero_denominator
diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb
index fa9f5532d5..56f33ae00a 100644
--- a/test/ruby/test_refinement.rb
+++ b/test/ruby/test_refinement.rb
@@ -225,7 +225,7 @@ class TestRefinement < Test::Unit::TestCase
end
end
def test_method_should_use_refinements
- skip if Test::Unit::Runner.current_repeat_count > 0
+ omit if Test::Unit::Runner.current_repeat_count > 0
foo = Foo.new
assert_raise(NameError) { foo.method(:z) }
@@ -248,7 +248,7 @@ class TestRefinement < Test::Unit::TestCase
end
end
def test_instance_method_should_use_refinements
- skip if Test::Unit::Runner.current_repeat_count > 0
+ omit if Test::Unit::Runner.current_repeat_count > 0
foo = Foo.new
assert_raise(NameError) { Foo.instance_method(:z) }
@@ -747,130 +747,37 @@ class TestRefinement < Test::Unit::TestCase
end
end
- module IncludeIntoRefinement
- class C
- def bar
- return "C#bar"
- end
-
- def baz
- return "C#baz"
- end
- end
-
- module Mixin
- def foo
- return "Mixin#foo"
- end
-
- def bar
- return super << " Mixin#bar"
- end
-
- def baz
- return super << " Mixin#baz"
- end
- end
-
- module M
- refine C do
- include Mixin
-
- def baz
- return super << " M#baz"
- end
- end
- end
+ def self.suppress_verbose
+ verbose, $VERBOSE = $VERBOSE, nil
+ yield
+ ensure
+ $VERBOSE = verbose
end
- eval <<-EOF, Sandbox::BINDING
- using TestRefinement::IncludeIntoRefinement::M
-
- module TestRefinement::IncludeIntoRefinement::User
- def self.invoke_foo_on(x)
- x.foo
- end
-
- def self.invoke_bar_on(x)
- x.bar
- end
-
- def self.invoke_baz_on(x)
- x.baz
- end
- end
- EOF
-
def test_include_into_refinement
- x = IncludeIntoRefinement::C.new
- assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x))
- assert_equal("C#bar Mixin#bar",
- IncludeIntoRefinement::User.invoke_bar_on(x))
- assert_equal("C#baz Mixin#baz M#baz",
- IncludeIntoRefinement::User.invoke_baz_on(x))
- end
-
- module PrependIntoRefinement
- class C
- def bar
- return "C#bar"
- end
-
- def baz
- return "C#baz"
- end
- end
-
- module Mixin
- def foo
- return "Mixin#foo"
- end
-
- def bar
- return super << " Mixin#bar"
- end
-
- def baz
- return super << " Mixin#baz"
- end
- end
-
- module M
- refine C do
- prepend Mixin
+ assert_raise(TypeError) do
+ c = Class.new
+ mixin = Module.new
- def baz
- return super << " M#baz"
+ Module.new do
+ refine c do
+ include mixin
end
end
end
end
- eval <<-EOF, Sandbox::BINDING
- using TestRefinement::PrependIntoRefinement::M
-
- module TestRefinement::PrependIntoRefinement::User
- def self.invoke_foo_on(x)
- x.foo
- end
-
- def self.invoke_bar_on(x)
- x.bar
- end
+ def test_prepend_into_refinement
+ assert_raise(TypeError) do
+ c = Class.new
+ mixin = Module.new
- def self.invoke_baz_on(x)
- x.baz
+ Module.new do
+ refine c do
+ prepend mixin
+ end
end
end
- EOF
-
- def test_prepend_into_refinement
- x = PrependIntoRefinement::C.new
- assert_equal("Mixin#foo", PrependIntoRefinement::User.invoke_foo_on(x))
- assert_equal("C#bar Mixin#bar",
- PrependIntoRefinement::User.invoke_bar_on(x))
- assert_equal("C#baz M#baz Mixin#baz",
- PrependIntoRefinement::User.invoke_baz_on(x))
end
PrependAfterRefine_CODE = <<-EOC
@@ -912,7 +819,7 @@ class TestRefinement < Test::Unit::TestCase
def test_prepend_after_refine_wb_miss
if /\A(arm|mips)/ =~ RUBY_PLATFORM
- skip "too slow cpu"
+ omit "too slow cpu"
end
assert_normal_exit %Q{
GC.stress = true
@@ -1299,6 +1206,41 @@ class TestRefinement < Test::Unit::TestCase
INPUT
end
+ def test_refined_protected_methods
+ assert_separately([], <<-"end;")
+ bug18806 = '[ruby-core:108705] [Bug #18806]'
+ class C; end
+
+ module R
+ refine C do
+ def refined_call_foo = foo
+ def refined_call_foo_on(other) = other.foo
+
+ protected
+
+ def foo = :foo
+ end
+ end
+
+ class C
+ using R
+
+ def call_foo = foo
+ def call_foo_on(other) = other.foo
+ end
+
+ c = C.new
+ assert_equal :foo, c.call_foo, bug18806
+ assert_equal :foo, c.call_foo_on(c), bug18806
+ assert_equal :foo, c.call_foo_on(C.new), bug18806
+
+ using R
+ assert_equal :foo, c.refined_call_foo, bug18806
+ assert_equal :foo, c.refined_call_foo_on(c), bug18806
+ assert_equal :foo, c.refined_call_foo_on(C.new), bug18806
+ end;
+ end
+
def test_refine_basic_object
assert_separately([], <<-"end;")
bug10106 = '[ruby-core:64166] [Bug #10106]'
@@ -1780,6 +1722,8 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_a
end
+
+ RefA.const_set(:REF, self)
end
end
@@ -1787,6 +1731,8 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_b
end
+
+ RefB.const_set(:REF, self)
end
end
@@ -1796,23 +1742,28 @@ class TestRefinement < Test::Unit::TestCase
refine Object do
def in_ref_c
end
+
+ RefC.const_set(:REF, self)
end
end
module Foo
using RefB
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
module Bar
using RefC
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
module Combined
using RefA
using RefB
USED_MODS = Module.used_modules
+ USED_REFS = Module.used_refinements
end
end
@@ -1824,6 +1775,41 @@ class TestRefinement < Test::Unit::TestCase
assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS
end
+ def test_used_refinements
+ ref = VisibleRefinements
+ assert_equal [], Module.used_refinements
+ assert_equal [ref::RefB::REF], ref::Foo::USED_REFS
+ assert_equal [ref::RefC::REF], ref::Bar::USED_REFS
+ assert_equal [ref::RefB::REF, ref::RefA::REF], ref::Combined::USED_REFS
+ end
+
+ def test_refinements
+ int_refinement = nil
+ str_refinement = nil
+ m = Module.new {
+ refine Integer do
+ int_refinement = self
+ end
+
+ refine String do
+ str_refinement = self
+ end
+ }
+ assert_equal([int_refinement, str_refinement], m.refinements)
+ end
+
+ def test_refined_class
+ refinements = Module.new {
+ refine Integer do
+ end
+
+ refine String do
+ end
+ }.refinements
+ assert_equal(Integer, refinements[0].refined_class)
+ assert_equal(String, refinements[1].refined_class)
+ end
+
def test_warn_setconst_in_refinmenet
bug10103 = '[ruby-core:64143] [Bug #10103]'
warnings = [
@@ -1968,10 +1954,10 @@ class TestRefinement < Test::Unit::TestCase
m = Module.new do
r = refine(String) {def test;:ok end}
end
- assert_raise_with_message(ArgumentError, /refinement/, bug) do
+ assert_raise_with_message(TypeError, /refinement/, bug) do
m.module_eval {include r}
end
- assert_raise_with_message(ArgumentError, /refinement/, bug) do
+ assert_raise_with_message(TypeError, /refinement/, bug) do
m.module_eval {prepend r}
end
end
@@ -2615,16 +2601,6 @@ class TestRefinement < Test::Unit::TestCase
end
end
- module D
- refine A do
- include B
-
- def foo
- "refined"
- end
- end
- end
-
module UsingC
using C
@@ -2632,19 +2608,10 @@ class TestRefinement < Test::Unit::TestCase
A.new.bar
end
end
-
- module UsingD
- using D
-
- def self.call_bar
- A.new.bar
- end
- end
end
def test_import_methods
assert_equal("refined:bar", TestImport::UsingC.call_bar)
- assert_equal("original:bar", TestImport::UsingD.call_bar)
assert_raise(ArgumentError) do
Module.new do
@@ -2655,6 +2622,10 @@ class TestRefinement < Test::Unit::TestCase
end
end
+ def test_inherit_singleton_methods_of_module
+ assert_equal([], Refinement.used_modules)
+ end
+
private
def eval_using(mod, s)
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb
index 2bf4649f14..b385760b42 100644
--- a/test/ruby/test_regexp.rb
+++ b/test/ruby/test_regexp.rb
@@ -42,14 +42,14 @@ class TestRegexp < Test::Unit::TestCase
def test_yoshidam_net_20041111_1
s = "[\xC2\xA0-\xC3\xBE]"
- r = assert_deprecated_warning(/ignored/) {Regexp.new(s, nil, "u")}
+ r = assert_deprecated_warning(/3\.3/) {Regexp.new(s, nil, "u")}
assert_match(r, "\xC3\xBE")
end
def test_yoshidam_net_20041111_2
assert_raise(RegexpError) do
s = "[\xFF-\xFF]".force_encoding("utf-8")
- assert_warning(/ignored/) {Regexp.new(s, nil, "u")}
+ assert_warning(/3\.3/) {Regexp.new(s, nil, "u")}
end
end
@@ -91,6 +91,122 @@ class TestRegexp < Test::Unit::TestCase
assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s}
end
+ def test_extended_comment_invalid_escape_bug_18294
+ assert_separately([], <<-RUBY)
+ re = / C:\\\\[a-z]{5} # e.g. C:\\users /x
+ assert_match(re, 'C:\\users')
+ assert_not_match(re, 'C:\\user')
+
+ re = /
+ foo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[#o]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f[[:alnum:]#]o # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /
+ f(?# \\M-ca)oo # \\M-ca
+ bar
+ /x
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# \\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /[-(?# fca)]oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+
+ re = /f(?# ca\0\\M-ca)oobar/
+ assert_match(re, 'foobar')
+ assert_not_match(re, 'foobaz')
+ RUBY
+
+ assert_raise(SyntaxError) {eval "/\\users/x"}
+ assert_raise(SyntaxError) {eval "/[\\users]/x"}
+ assert_raise(SyntaxError) {eval "/(?<\\users)/x"}
+ assert_raise(SyntaxError) {eval "/# \\users/"}
+ end
+
+ def test_nonextended_section_of_extended_regexp_bug_19379
+ assert_separately([], <<-'RUBY')
+ re = /(?-x:#)/x
+ assert_match(re, '#')
+ assert_not_match(re, '-')
+
+ re = /(?xi:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?mix:#
+ y)/
+ assert_match(re, 'Y')
+ assert_not_match(re, '-')
+
+ re = /(?x-im:#
+ y)/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ y))/x
+ assert_match(re, 'y')
+ assert_not_match(re, '-')
+
+ re = /(?x)#
+ y/
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?mx-i)#
+ y/i
+ assert_match(re, 'y')
+ assert_not_match(re, 'Y')
+
+ re = /(?-imx:(?xim:#
+ (?-x)y#))/x
+ assert_match(re, 'Y#')
+ assert_not_match(re, '-#')
+
+ re = /(?imx:#
+ (?-xim:#(?im)#(?x)#
+ )#
+ (?x)#
+ y)/
+ assert_match(re, '###Y')
+ assert_not_match(re, '###-')
+
+ re = %r{#c-\w+/comment/[\w-]+}
+ re = %r{https?://[^/]+#{re}}x
+ assert_match(re, 'http://foo#c-x/comment/bar')
+ assert_not_match(re, 'http://foo#cx/comment/bar')
+ RUBY
+ end
+
+ def test_utf8_comment_in_usascii_extended_regexp_bug_19455
+ assert_separately([], <<-RUBY)
+ assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding)
+ assert_equal(Encoding::UTF_8, /#\u1000/x.encoding)
+ RUBY
+ end
+
def test_union
assert_equal :ok, begin
Regexp.union(
@@ -424,6 +540,27 @@ class TestRegexp < Test::Unit::TestCase
assert_equal([2, 3], m.offset(3))
end
+ def test_match_byteoffset_begin_end
+ m = /(?<x>b..)/.match("foobarbaz")
+ assert_equal([3, 6], m.byteoffset("x"))
+ assert_equal(3, m.begin("x"))
+ assert_equal(6, m.end("x"))
+ assert_raise(IndexError) { m.byteoffset("y") }
+ assert_raise(IndexError) { m.byteoffset(2) }
+ assert_raise(IndexError) { m.begin(2) }
+ assert_raise(IndexError) { m.end(2) }
+
+ m = /(?<x>q..)?/.match("foobarbaz")
+ assert_equal([nil, nil], m.byteoffset("x"))
+ assert_equal(nil, m.begin("x"))
+ assert_equal(nil, m.end("x"))
+
+ m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044")
+ assert_equal([3, 6], m.byteoffset(1))
+ assert_equal([nil, nil], m.byteoffset(2))
+ assert_equal([6, 9], m.byteoffset(3))
+ end
+
def test_match_to_s
m = /(?<x>b..)/.match("foobarbaz")
assert_equal("bar", m.to_s)
@@ -467,6 +604,7 @@ class TestRegexp < Test::Unit::TestCase
assert_nil(m[5])
assert_raise(IndexError) { m[:foo] }
assert_raise(TypeError) { m[nil] }
+ assert_equal(["baz", nil], m[-2, 3])
end
def test_match_values_at
@@ -533,17 +671,80 @@ class TestRegexp < Test::Unit::TestCase
assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect)
end
+ def test_match_data_deconstruct
+ m = /foo.+/.match("foobarbaz")
+ assert_equal([], m.deconstruct)
+
+ m = /(foo).+(baz)/.match("foobarbaz")
+ assert_equal(["foo", "baz"], m.deconstruct)
+
+ m = /(...)(...)(...)(...)?/.match("foobarbaz")
+ assert_equal(["foo", "bar", "baz", nil], m.deconstruct)
+ end
+
+ def test_match_data_deconstruct_keys
+ m = /foo.+/.match("foobarbaz")
+ assert_equal({}, m.deconstruct_keys([:a]))
+
+ m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz")
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil))
+ assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b]))
+ assert_equal({b: "baz"}, m.deconstruct_keys([:b]))
+ assert_equal({}, m.deconstruct_keys([:c, :a]))
+ assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c]))
+ assert_equal({}, m.deconstruct_keys([:a, :b, :c]))
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(0)
+ }
+
+ assert_raise(TypeError) {
+ m.deconstruct_keys(["a", "b"])
+ }
+ end
+
def test_initialize
assert_raise(ArgumentError) { Regexp.new }
assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)})
+ assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)})
+ assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)})
+
+ arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value
+
+ assert_deprecated_warning('') do
+ assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding)
+ assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)])
+ assert_equal(//, Regexp.new(""))
+ assert_equal(//, Regexp.new("", timeout: 1))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING))
+ assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1))
- assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding)
- assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")])
- assert_equal(//n, Regexp.new("", nil, "n"))
+ assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options)
- arg_encoding_none = 32 # ARG_ENCODING_NONE is implementation defined value
- assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options)
- assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options)
+ assert_nil(Regexp.new("").timeout)
+ assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout)
+ assert_nil(Regexp.compile("").timeout)
+ assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout)
+ end
+
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding)
+ end
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n", timeout: 1).encoding)
+ end
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")])
+ end
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal(//n, Regexp.new("", nil, "n"))
+ end
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options)
+ end
+ assert_deprecated_warning(/3\.3/) do
+ assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options)
+ end
assert_raise(RegexpError) { Regexp.new(")(") }
assert_raise(RegexpError) { Regexp.new('[\\40000000000') }
@@ -551,6 +752,29 @@ class TestRegexp < Test::Unit::TestCase
assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") }
end
+ def test_initialize_bool_warning
+ assert_warning(/expected true or false as ignorecase/) do
+ Regexp.new("foo", :i)
+ end
+ end
+
+ def test_initialize_option
+ assert_equal(//i, Regexp.new("", "i"))
+ assert_equal(//m, Regexp.new("", "m"))
+ assert_equal(//x, Regexp.new("", "x"))
+ assert_equal(//imx, Regexp.new("", "imx"))
+ assert_equal(//, Regexp.new("", ""))
+ assert_equal(//imx, Regexp.new("", "mimix"))
+
+ assert_raise(ArgumentError) { Regexp.new("", "e") }
+ assert_raise(ArgumentError) { Regexp.new("", "n") }
+ assert_raise(ArgumentError) { Regexp.new("", "s") }
+ assert_raise(ArgumentError) { Regexp.new("", "u") }
+ assert_raise(ArgumentError) { Regexp.new("", "o") }
+ assert_raise(ArgumentError) { Regexp.new("", "j") }
+ assert_raise(ArgumentError) { Regexp.new("", "xmen") }
+ end
+
def test_match_control_meta_escape
assert_equal(0, /\c\xFF/ =~ "\c\xFF")
assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF")
@@ -1184,6 +1408,11 @@ class TestRegexp < Test::Unit::TestCase
assert_no_match(/^\p{age=12.0}$/u, "\u32FF")
assert_match(/^\p{age=12.1}$/u, "\u32FF")
+ assert_no_match(/^\p{age=13.0}$/u, "\u{10570}")
+ assert_match(/^\p{age=14.0}$/u, "\u{10570}")
+ assert_match(/^\p{age=14.0}$/u, "\u9FFF")
+ assert_match(/^\p{age=14.0}$/u, "\u{2A6DF}")
+ assert_match(/^\p{age=14.0}$/u, "\u{2B738}")
end
MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end")
@@ -1388,6 +1617,9 @@ class TestRegexp < Test::Unit::TestCase
assert_equal(0, /(?~(a)c)/ =~ "abb")
assert_nil($1)
+
+ assert_equal(0, /(?~(a))/ =~ "")
+ assert_nil($1)
end
def test_backref_overrun
@@ -1396,6 +1628,21 @@ class TestRegexp < Test::Unit::TestCase
end
end
+ def test_bug18631
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaaa")
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaa")
+ assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaab")
+ end
+
+ def test_invalid_group
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_raise_with_message(RegexpError, /invalid conditional pattern/) do
+ Regexp.new("((?(1)x|x|)x)+")
+ end
+ end;
+ end
+
# This assertion is for porting x2() tests in testpy.py of Onigmo.
def assert_match_at(re, str, positions, msg = nil)
re = Regexp.new(re) unless re.is_a?(Regexp)
@@ -1425,4 +1672,170 @@ class TestRegexp < Test::Unit::TestCase
}
assert_empty(errs, msg)
end
+
+ def test_s_timeout
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect }
+ begin;
+ Regexp.timeout = timeout
+ assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON)
+
+ t = Time.now
+ assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
+ # A typical ReDoS case
+ /^(a*)*\1$/ =~ "a" * 1000000 + "x"
+ end
+ t = Time.now - t
+
+ assert_in_delta(timeout, t, timeout / 2)
+ end;
+ end
+
+ def test_s_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_nil(Regexp.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ Regexp.timeout = Float::INFINITY
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ Regexp.timeout = 1e300
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
+
+ assert_raise(ArgumentError) { Regexp.timeout = 0 }
+ assert_raise(ArgumentError) { Regexp.timeout = -1 }
+
+ Regexp.timeout = nil
+ assert_nil(Regexp.timeout)
+ end;
+ end
+
+ def test_bug_20453
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ Regexp.timeout = 0.001
+
+ assert_raise(Regexp::TimeoutError) do
+ /^(a*)x$/ =~ "a" * 1000000 + "x"
+ end
+ end;
+ end
+
+ def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect }
+ per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect }
+ expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect }
+ begin;
+ Regexp.timeout = global_timeout
+
+ re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout)
+ if per_instance_timeout
+ assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON)
+ else
+ assert_nil(re.timeout)
+ end
+
+ t = Time.now
+ assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
+ re =~ "a" * 1000000 + "x"
+ end
+ t = Time.now - t
+
+ assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4)
+ end;
+ end
+
+ def test_timeout_shorter_than_global
+ per_instance_redos_test(10, 0.2, 0.2)
+ end
+
+ def test_timeout_longer_than_global
+ per_instance_redos_test(0.01, 0.5, 0.5)
+ end
+
+ def test_timeout_nil
+ per_instance_redos_test(0.5, nil, 0.5)
+ end
+
+ def test_timeout_corner_cases
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ assert_nil(//.timeout)
+
+ # This is just an implementation detail that users should not depend on:
+ # If Regexp.timeout is set to a value greater than the value that can be
+ # represented in the internal representation of timeout, it uses the
+ # maximum value that can be represented.
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout)
+
+ assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout)
+
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) }
+ assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
+ end;
+ end
+
+ def test_cache_optimization_exponential
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_cache_optimization_square
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x")
+ end;
+ end
+
+ def test_cache_index_initialize
+ str = 'test1-test2-test3-test4-test_5'
+ re = '^([0-9a-zA-Z\-/]*){1,256}$'
+ 100.times do
+ assert !Regexp.new(re).match?(str)
+ end
+ end
+
+ def test_bug_19273 # [Bug #19273]
+ pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/
+ assert_equal("10:0:0".match(pattern)[0], "10:0:0")
+ end
+
+ def test_bug_19467
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ timeout = #{ EnvUtil.apply_timeout_scale(10).inspect }
+ begin;
+ Regexp.timeout = timeout
+
+ assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y")
+ end;
+ end
+
+ def test_bug_19476 # [Bug #19476]
+ assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789")
+ assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789")
+ end
+
+ def test_linear_time_p
+ assert_send [Regexp, :linear_time?, /a/]
+ assert_send [Regexp, :linear_time?, 'a']
+ assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE]
+ assert_not_send [Regexp, :linear_time?, /(a)\1/]
+ assert_not_send [Regexp, :linear_time?, "(a)\\1"]
+
+ assert_raise(TypeError) {Regexp.linear_time?(nil)}
+ assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)}
+ end
end
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 7a9faf18f9..1b93a42e42 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -6,11 +6,27 @@ require 'tmpdir'
class TestRequire < Test::Unit::TestCase
def test_load_error_path
- filename = "should_not_exist"
- error = assert_raise(LoadError) do
- require filename
- end
- assert_equal filename, error.path
+ Tempfile.create(["should_not_exist", ".rb"]) {|t|
+ filename = t.path
+ t.close
+ File.unlink(filename)
+
+ error = assert_raise(LoadError) do
+ require filename
+ end
+ assert_equal filename, error.path
+
+ # with --disable=gems
+ assert_separately(["-", filename], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ filename = ARGV[0]
+ path = Struct.new(:to_path).new(filename)
+ error = assert_raise(LoadError) do
+ require path
+ end
+ assert_equal filename, error.path
+ end;
+ }
end
def test_require_invalid_shared_object
@@ -52,7 +68,8 @@ class TestRequire < Test::Unit::TestCase
def test_require_nonascii
bug3758 = '[ruby-core:31915]'
["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path|
- assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path}
+ e = assert_raise(LoadError, bug3758) {require path}
+ assert_operator(e.message, :end_with?, path, bug3758)
end
end
@@ -93,7 +110,7 @@ class TestRequire < Test::Unit::TestCase
begin
require_path = File.join(tmp, dir, 'foo.rb').encode(encoding)
rescue
- skip "cannot convert path encoding to #{encoding}"
+ omit "cannot convert path encoding to #{encoding}"
end
Dir.mkdir(File.dirname(require_path))
open(require_path, "wb") {|f| f.puts '$:.push __FILE__'}
@@ -175,7 +192,7 @@ class TestRequire < Test::Unit::TestCase
t.close
path = File.expand_path(t.path).sub(/\A(\w):/, '//127.0.0.1/\1$')
- skip "local drive #$1: is not shared" unless File.exist?(path)
+ omit "local drive #$1: is not shared" unless File.exist?(path)
args = ['--disable-gems', "-I#{File.dirname(path)}"]
assert_in_out_err(args, "#{<<~"END;"}", [path], [])
begin
@@ -352,6 +369,26 @@ class TestRequire < Test::Unit::TestCase
end
end
+ def test_public_in_wrapped_load
+ Tempfile.create(["test_public_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "public :foo"
+ t.close
+ assert_warning(/main\.public/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
+ def test_private_in_wrapped_load
+ Tempfile.create(["test_private_in_wrapped_load", ".rb"]) do |t|
+ t.puts "def foo; end", "private :foo"
+ t.close
+ assert_warning(/main\.private/) do
+ assert load(t.path, true)
+ end
+ end
+ end
+
def test_load_scope
bug1982 = '[ruby-core:25039] [Bug #1982]'
Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
@@ -367,6 +404,38 @@ class TestRequire < Test::Unit::TestCase
}
end
+ def test_load_into_module
+ Tempfile.create(["test_ruby_test_require", ".rb"]) {|t|
+ t.puts "def b; 1 end"
+ t.puts "class Foo"
+ t.puts " def c; 2 end"
+ t.puts "end"
+ t.close
+
+ m = Module.new
+ load(t.path, m)
+ assert_equal([:b], m.private_instance_methods(false))
+ c = Class.new do
+ include m
+ public :b
+ end
+ assert_equal(1, c.new.b)
+ assert_equal(2, m::Foo.new.c)
+ }
+ end
+
+ def test_load_wrap_nil
+ Dir.mktmpdir do |tmp|
+ File.write("#{tmp}/1.rb", "class LoadWrapNil; end\n")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ path = ""#{tmp.dump}"/1.rb"
+ begin;
+ load path, nil
+ assert_instance_of(Class, LoadWrapNil)
+ end;
+ end
+ end
+
def test_load_ospath
bug = '[ruby-list:49994] path in ospath'
base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J)
@@ -428,7 +497,7 @@ class TestRequire < Test::Unit::TestCase
result = IO.popen([EnvUtil.rubybin, "b/tst.rb"], &:read)
assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]")
rescue NotImplementedError, Errno::EACCES
- skip "File.symlink is not implemented"
+ omit "File.symlink is not implemented"
end
}
}
@@ -454,7 +523,7 @@ class TestRequire < Test::Unit::TestCase
result = IO.popen([EnvUtil.rubybin, "c.rb"], &:read)
assert_equal("1", result, "bug17885 [ruby-core:104010]")
rescue NotImplementedError, Errno::EACCES
- skip "File.symlink is not implemented"
+ omit "File.symlink is not implemented"
end
}
}
@@ -530,9 +599,6 @@ class TestRequire < Test::Unit::TestCase
assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}")
assert_equal([:pre, :post], scratch, bug5754)
-
- assert_match(/circular require/, output)
- assert_match(/in #{__method__}'$/o, output)
}
ensure
$VERBOSE = verbose
@@ -792,6 +858,8 @@ class TestRequire < Test::Unit::TestCase
end if File.respond_to?(:mkfifo)
def test_loading_fifo_threading_success
+ omit "[Bug #18613]" if /freebsd/=~ RUBY_PLATFORM
+
Tempfile.create(%w'fifo .rb') {|f|
f.close
File.unlink(f.path)
@@ -818,7 +886,7 @@ class TestRequire < Test::Unit::TestCase
end if File.respond_to?(:mkfifo)
def test_loading_fifo_fd_leak
- skip if RUBY_PLATFORM =~ /android/ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/android29-x86_64/ruby-master/log/20200419T124100Z.fail.html.gz
+ omit if RUBY_PLATFORM =~ /android/ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/android29-x86_64/ruby-master/log/20200419T124100Z.fail.html.gz
Tempfile.create(%w'fifo .rb') {|f|
f.close
@@ -879,7 +947,7 @@ class TestRequire < Test::Unit::TestCase
begin
File.symlink "real", File.join(tmp, "symlink")
rescue NotImplementedError, Errno::EACCES
- skip "File.symlink is not implemented"
+ omit "File.symlink is not implemented"
end
File.write(File.join(tmp, "real/test_symlink_load_path.rb"), "print __FILE__")
result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'test_symlink_load_path.rb'"], &:read)
@@ -927,4 +995,19 @@ class TestRequire < Test::Unit::TestCase
assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso"))
end
end
+
+ def test_require_with_public_method_missing
+ # [Bug #19793]
+ assert_separately(["-W0", "--disable-gems", "-rtempfile"], __FILE__, __LINE__, <<~RUBY)
+ GC.stress = true
+
+ class Object
+ public :method_missing
+ end
+
+ Tempfile.create(["empty", ".rb"]) do |file|
+ require file.path
+ end
+ RUBY
+ end
end
diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb
index 6b2846c8fd..95fa3f29e1 100644
--- a/test/ruby/test_require_lib.rb
+++ b/test/ruby/test_require_lib.rb
@@ -18,7 +18,7 @@ class TestRequireLib < Test::Unit::TestCase
begin
require #{lib.dump}
rescue Exception
- skip $!
+ omit $!
end
assert_equal n, Thread.list.size
end;
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index 579d0cb362..15c9f293ed 100644
--- a/test/ruby/test_rubyoptions.rb
+++ b/test/ruby/test_rubyoptions.rb
@@ -7,11 +7,14 @@ require 'tempfile'
require_relative '../lib/jit_support'
class TestRubyOptions < Test::Unit::TestCase
+ def self.mjit_enabled? = defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
+ def self.yjit_enabled? = defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled?
+
NO_JIT_DESCRIPTION =
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # checking -DMJIT_FORCE_ENABLE
- RUBY_DESCRIPTION.sub(/\+JIT /, '')
- elsif defined?(YJIT.enabled?) && YJIT.enabled? # checking -DYJIT_FORCE_ENABLE
- RUBY_DESCRIPTION.sub(/\+YJIT /, '')
+ if mjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+MJIT /, '')
+ elsif yjit_enabled?
+ RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '')
else
RUBY_DESCRIPTION
end
@@ -119,6 +122,8 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(["--disable-gems", "--debug", "-e", "p $DEBUG"],
"", %w(true), [])
+
+ assert_in_out_err(["--disable-gems", "--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/)
end
q = Regexp.method(:quote)
@@ -135,7 +140,7 @@ class TestRubyOptions < Test::Unit::TestCase
VERSION_PATTERN_WITH_JIT =
case RUBY_ENGINE
when 'ruby'
- /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+JIT \[#{q[RUBY_PLATFORM]}\]$/
+ /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+MJIT \[#{q[RUBY_PLATFORM]}\]$/
else
VERSION_PATTERN
end
@@ -144,9 +149,9 @@ class TestRubyOptions < Test::Unit::TestCase
def test_verbose
assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e|
assert_match(VERSION_PATTERN, r[0])
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? && !mjit_force_enabled? # checking -DMJIT_FORCE_ENABLE
+ if self.class.mjit_enabled? && !JITSupport.mjit_force_enabled?
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif defined?(YJIT.enabled?) && YJIT.enabled? && !yjit_force_enabled? # checking -DYJIT_FORCE_ENABLE
+ elsif self.class.yjit_enabled? && !yjit_force_enabled? # checking -DYJIT_FORCE_ENABLE
assert_equal(NO_JIT_DESCRIPTION, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -212,7 +217,7 @@ class TestRubyOptions < Test::Unit::TestCase
assert_match(VERSION_PATTERN, r[0])
if ENV['RUBY_YJIT_ENABLE'] == '1'
assert_equal(NO_JIT_DESCRIPTION, r[0])
- elsif defined?(RubyVM::JIT) && RubyVM::JIT.enabled? || defined?(YJIT.enabled?) && YJIT.enabled? # checking -D(M|Y)JIT_FORCE_ENABLE
+ elsif self.class.mjit_enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE
assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
else
assert_equal(RUBY_DESCRIPTION, r[0])
@@ -224,9 +229,14 @@ class TestRubyOptions < Test::Unit::TestCase
return if yjit_force_enabled?
[
- %w(--version --jit --disable=jit),
- %w(--version --enable=jit --disable=jit),
- %w(--version --enable-jit --disable-jit),
+ %w(--version --mjit --disable=mjit),
+ %w(--version --enable=mjit --disable=mjit),
+ %w(--version --enable-mjit --disable-mjit),
+ *([
+ %w(--version --jit --disable=jit),
+ %w(--version --enable=jit --disable=jit),
+ %w(--version --enable-jit --disable-jit),
+ ] unless JITSupport.yjit_supported?),
].each do |args|
assert_in_out_err([env] + args) do |r, e|
assert_match(VERSION_PATTERN, r[0])
@@ -237,16 +247,21 @@ class TestRubyOptions < Test::Unit::TestCase
if JITSupport.supported?
[
- %w(--version --jit),
- %w(--version --enable=jit),
- %w(--version --enable-jit),
+ %w(--version --mjit),
+ %w(--version --enable=mjit),
+ %w(--version --enable-mjit),
+ *([
+ %w(--version --jit),
+ %w(--version --enable=jit),
+ %w(--version --enable-jit),
+ ] unless JITSupport.yjit_supported?),
].each do |args|
assert_in_out_err([env] + args) do |r, e|
assert_match(VERSION_PATTERN_WITH_JIT, r[0])
- if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # checking -DMJIT_FORCE_ENABLE
+ if JITSupport.mjit_force_enabled?
assert_equal(RUBY_DESCRIPTION, r[0])
else
- assert_equal(EnvUtil.invoke_ruby([env, '--jit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
+ assert_equal(EnvUtil.invoke_ruby([env, '--mjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0])
end
assert_equal([], e)
end
@@ -343,9 +358,9 @@ class TestRubyOptions < Test::Unit::TestCase
assert_in_out_err(%W(-\r -e) + [""], "", [], [])
- assert_in_out_err(%W(-\rx), "", [], /invalid option -\\r \(-h will show valid options\) \(RuntimeError\)/)
+ assert_in_out_err(%W(-\rx), "", [], /invalid option -[\r\n] \(-h will show valid options\) \(RuntimeError\)/)
- assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/)
+ assert_in_out_err(%W(-\x01), "", [], /invalid option -\x01 \(-h will show valid options\) \(RuntimeError\)/)
assert_in_out_err(%w(-Z), "", [], /invalid option -Z \(-h will show valid options\) \(RuntimeError\)/)
end
@@ -662,7 +677,7 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_set_program_name
- skip "platform dependent feature" unless defined?(PSCMD) and PSCMD
+ omit "platform dependent feature" unless defined?(PSCMD) and PSCMD
with_tmpchdir do
write_file("test-script", "$0 = 'hello world'; /test-script/ =~ Process.argv0 or $0 = 'Process.argv0 changed!'; sleep 60")
@@ -685,7 +700,7 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_setproctitle
- skip "platform dependent feature" unless defined?(PSCMD) and PSCMD
+ omit "platform dependent feature" unless defined?(PSCMD) and PSCMD
assert_separately([], "#{<<-"{#"}\n#{<<-'};'}")
{#
@@ -726,7 +741,7 @@ class TestRubyOptions < Test::Unit::TestCase
-e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n
)x,
%r(
- #{ Regexp.quote(NO_JIT_DESCRIPTION) }\n\n
+ #{ Regexp.quote((TestRubyOptions.mjit_enabled? && !JITSupport.mjit_force_enabled?) ? NO_JIT_DESCRIPTION : RUBY_DESCRIPTION) }\n\n
)x,
%r(
(?:--\s(?:.+\n)*\n)?
@@ -748,6 +763,7 @@ class TestRubyOptions < Test::Unit::TestCase
%r(
(?:
--\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n
+ (?:Un(?:expected|supported|known)\s.*\n)*
(?:(?:.*\s)?\[0x\h+\].*\n|.*:\d+\n)*\n
)?
)x,
@@ -760,7 +776,11 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_segv(args, message=nil)
- skip if ENV['RUBY_ON_BUG']
+ omit if ENV['RUBY_ON_BUG']
+
+ # We want YJIT to be enabled in the subprocess if it's enabled for us
+ # so that the Ruby description matches.
+ args.unshift("--yjit") if self.class.yjit_enabled?
test_stdin = ""
opt = SEGVTest::ExecOptions.dup
@@ -845,7 +865,7 @@ class TestRubyOptions < Test::Unit::TestCase
Process.wait pid
}
rescue RuntimeError
- skip $!
+ omit $!
end
}
assert_equal("", result, '[ruby-dev:37798]')
@@ -895,7 +915,7 @@ class TestRubyOptions < Test::Unit::TestCase
name = c.chr(Encoding::UTF_8)
expected = name.encode("locale") rescue nil
}
- skip "can't make locale name"
+ omit "can't make locale name"
end
name << ".rb"
expected << ".rb"
@@ -981,8 +1001,7 @@ class TestRubyOptions < Test::Unit::TestCase
stderr = []
Tempfile.create(%w"bug10435- .rb") do |script|
dir, base = File.split(script.path)
- script.puts "abort ':run'"
- script.close
+ File.write(script, "abort ':run'\n")
opts = ['-C', dir, '-r', "./#{base}", *opt]
_, e = assert_in_out_err([*opts, '-ep'], "", //)
stderr.concat(e) if e
@@ -1006,6 +1025,8 @@ class TestRubyOptions < Test::Unit::TestCase
def test_dump_parsetree_with_rflag
assert_norun_with_rflag('--dump=parsetree')
assert_norun_with_rflag('--dump=parsetree', '-e', '#frozen-string-literal: true')
+ assert_norun_with_rflag('--dump=parsetree+error_tolerant')
+ assert_norun_with_rflag('--dump=parse+error_tolerant')
end
def test_dump_insns_with_rflag
@@ -1103,24 +1124,19 @@ class TestRubyOptions < Test::Unit::TestCase
end
def test_null_script
- skip "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL)
+ omit "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL)
assert_in_out_err([IO::NULL], success: true)
end
- def test_jit_debug
- # mswin uses prebuilt precompiled header. Thus it does not show a pch compilation log to check "-O0 -O1".
- if JITSupport.supported? && !RUBY_PLATFORM.match?(/mswin/)
+ def test_mjit_debug
+ if JITSupport.supported?
env = { 'MJIT_SEARCH_BUILD_DIR' => 'true' }
- assert_in_out_err([env, "--disable-yjit", "--jit-debug=-O0 -O1", "--jit-verbose=2", "" ], "", [], /-O0 -O1/)
+ assert_in_out_err([env, "--disable-yjit", "--mjit-debug=-O0 -O1", "--mjit-verbose=2", "" ], "", [], /-O0 -O1/)
end
end
private
- def mjit_force_enabled?
- "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?MJIT_FORCE_ENABLE\b/)
- end
-
def yjit_force_enabled?
"#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?YJIT_FORCE_ENABLE\b/)
end
diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb
index a6d0b1a727..d729aa5af8 100644
--- a/test/ruby/test_rubyvm.rb
+++ b/test/ruby/test_rubyvm.rb
@@ -4,11 +4,9 @@ require 'test/unit'
class TestRubyVM < Test::Unit::TestCase
def test_stat
assert_kind_of Hash, RubyVM.stat
- assert_kind_of Integer, RubyVM.stat[:global_constant_state]
RubyVM.stat(stat = {})
assert_not_empty stat
- assert_equal stat[:global_constant_state], RubyVM.stat(:global_constant_state)
end
def test_stat_unknown
@@ -18,12 +16,12 @@ class TestRubyVM < Test::Unit::TestCase
def parse_and_compile
script = <<~RUBY
- a = 1
+ _a = 1
def foo
- b = 2
+ _b = 2
end
1.times{
- c = 3
+ _c = 3
}
RUBY
@@ -34,6 +32,8 @@ class TestRubyVM < Test::Unit::TestCase
end
def test_keep_script_lines
+ pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
+
prev_conf = RubyVM.keep_script_lines
# keep
diff --git a/test/ruby/test_rubyvm_jit.rb b/test/ruby/test_rubyvm_jit.rb
deleted file mode 100644
index 2104ce3d18..0000000000
--- a/test/ruby/test_rubyvm_jit.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-require 'test/unit'
-require_relative '../lib/jit_support'
-
-return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no'
-
-class TestRubyVMJIT < Test::Unit::TestCase
- include JITSupport
-
- def setup
- unless JITSupport.supported?
- skip 'JIT seems not supported on this platform'
- end
- end
-
- def test_pause
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- i = 0
- while i < 5
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::JIT.pause
- print RubyVM::JIT.pause
- while i < 10
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::JIT.pause # no JIT here
- EOS
- assert_equal('truefalsefalse', out)
- assert_equal(
- 5, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- )
- end
-
- def test_pause_waits_until_compaction
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- def a() end; a
- def b() end; b
- RubyVM::JIT.pause
- EOS
- assert_equal(
- 2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- )
- assert_equal(
- 1, err.scan(/#{JITSupport::JIT_COMPACTION_PREFIX}/).size,
- "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
- ) unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet
- end
-
- def test_pause_does_not_hang_on_full_units
- out, _ = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, max_cache: 10, wait: false)
- i = 0
- while i < 11
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::JIT.pause
- EOS
- assert_equal('true', out)
- end
-
- def test_pause_wait_false
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- i = 0
- while i < 10
- eval("def mjit#{i}; end; mjit#{i}")
- i += 1
- end
- print RubyVM::JIT.pause(wait: false)
- print RubyVM::JIT.pause(wait: false)
- EOS
- assert_equal('truefalse', out)
- assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10)
- end
-
- def test_resume
- out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false)
- print RubyVM::JIT.resume
- print RubyVM::JIT.pause
- print RubyVM::JIT.resume
- print RubyVM::JIT.resume
- print RubyVM::JIT.pause
- EOS
- assert_equal('falsetruetruefalsetrue', out)
- assert_equal(0, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size)
- end
-end
diff --git a/test/ruby/test_rubyvm_mjit.rb b/test/ruby/test_rubyvm_mjit.rb
new file mode 100644
index 0000000000..73f43af7f9
--- /dev/null
+++ b/test/ruby/test_rubyvm_mjit.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+require 'test/unit'
+require_relative '../lib/jit_support'
+
+return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no'
+
+class TestRubyVMMJIT < Test::Unit::TestCase
+ include JITSupport
+
+ def setup
+ unless JITSupport.supported?
+ omit 'JIT seems not supported on this platform'
+ end
+ end
+
+ def test_pause
+ out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
+ i = 0
+ while i < 5
+ eval("def mjit#{i}; end; mjit#{i}; mjit#{i}")
+ i += 1
+ end
+ print RubyVM::MJIT.pause
+ print RubyVM::MJIT.pause
+ while i < 10
+ eval("def mjit#{i}; end; mjit#{i}")
+ i += 1
+ end
+ print RubyVM::MJIT.pause # no JIT here
+ EOS
+ assert_equal('truefalsefalse', out)
+ assert_equal(
+ 5, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
+ "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
+ )
+ end
+
+ def test_pause_waits_until_compaction
+ out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
+ def a() end; a; a
+ def b() end; b; b
+ RubyVM::MJIT.pause
+ EOS
+ assert_equal(
+ 2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
+ "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
+ )
+ end
+
+ def test_pause_after_waitall
+ out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
+ def test() = nil
+ test
+ test
+ Process.waitall
+ print RubyVM::MJIT.pause
+ EOS
+ assert_equal(
+ 1, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size,
+ "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```",
+ )
+ end
+
+ def test_pause_does_not_hang_on_full_units
+ out, _ = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, max_cache: 10, wait: false)
+ i = 0
+ while i < 11
+ eval("def mjit#{i}; end; mjit#{i}")
+ i += 1
+ end
+ print RubyVM::MJIT.pause
+ EOS
+ assert_equal('true', out)
+ end
+
+ def test_pause_wait_false
+ out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
+ i = 0
+ while i < 10
+ eval("def mjit#{i}; end; mjit#{i}")
+ i += 1
+ end
+ print RubyVM::MJIT.pause(wait: false)
+ print RubyVM::MJIT.pause(wait: false)
+ EOS
+ assert_equal('truefalse', out)
+ if RUBY_PLATFORM.match?(/mswin|mingw/) # MJIT synchronously compiles methods on Windows
+ assert_equal(10, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size)
+ else
+ assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10)
+ end
+ end
+
+ def test_resume
+ out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false)
+ print RubyVM::MJIT.resume
+ print RubyVM::MJIT.pause
+ print RubyVM::MJIT.resume
+ print RubyVM::MJIT.resume
+ print RubyVM::MJIT.pause
+ EOS
+ assert_equal('falsetruetruefalsetrue', out)
+ assert_equal(0, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size)
+ end
+end
diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb
index 88488aef5c..fa1601b0e3 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -50,6 +50,49 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal([], events)
end
+ def test_c_return_no_binding
+ binding = :none
+ TracePoint.new(:c_return){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_no_binding
+ binding = :none
+ TracePoint.new(:c_call){|tp|
+ binding = tp.binding
+ }.enable{
+ 1.object_id
+ }
+ assert_nil(binding)
+ end
+
+ def test_c_call_removed_method
+ # [Bug #19305]
+ klass = Class.new do
+ attr_writer :bar
+ alias_method :set_bar, :bar=
+ remove_method :bar=
+ end
+
+ obj = klass.new
+ method_id = nil
+ parameters = nil
+
+ TracePoint.new(:c_call) { |tp|
+ method_id = tp.method_id
+ parameters = tp.parameters
+ }.enable {
+ obj.set_bar(1)
+ }
+
+ assert_equal(:bar=, method_id)
+ assert_equal([[:req]], parameters)
+ end
+
def test_call
events = []
name = "#{self.class}\##{__method__}"
@@ -108,6 +151,10 @@ class TestSetTraceFunc < Test::Unit::TestCase
events.shift)
assert_equal(["line", 4, __method__, self.class],
events.shift)
+ assert_equal(["c-call", 4, :const_added, Module],
+ events.shift)
+ assert_equal(["c-return", 4, :const_added, Module],
+ events.shift)
assert_equal(["c-call", 4, :inherited, Class],
events.shift)
assert_equal(["c-return", 4, :inherited, Class],
@@ -345,6 +392,8 @@ class TestSetTraceFunc < Test::Unit::TestCase
[["c-return", 2, :add_trace_func, Thread],
["line", 3, __method__, self.class],
+ ["c-call", 3, :const_added, Module],
+ ["c-return", 3, :const_added, Module],
["c-call", 3, :inherited, Class],
["c-return", 3, :inherited, Class],
["class", 3, nil, nil],
@@ -393,7 +442,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
[["c-return", 3, :set_trace_func, Kernel],
["line", 6, __method__, self.class],
["call", 1, :foobar, FooBar],
- ["return", 6, :foobar, FooBar],
+ ["return", 1, :foobar, FooBar],
["line", 7, __method__, self.class],
["c-call", 7, :set_trace_func, Kernel]].each{|e|
assert_equal(e, events.shift)
@@ -453,7 +502,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
begin
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
- 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
+ 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
3: }
4: 1.times{|;_local_var| _local_var = :inner
5: tap{}
@@ -482,27 +531,29 @@ class TestSetTraceFunc < Test::Unit::TestCase
answer_events = [
#
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
+ [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, :nothing],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
- [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
+ [:c_return, 4, "xyzzy", Integer, :times, 1, nil, 1],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
- [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
+ [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing],
+ [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil],
+ [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing],
+ [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing],
+ [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
- [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
- [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
- [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
+ [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing],
+ [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing],
+ [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy],
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
@@ -513,17 +564,17 @@ class TestSetTraceFunc < Test::Unit::TestCase
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
- [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
- [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
- [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
+ [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing],
+ [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc],
+ [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc],
+ [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing],
+ [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
- [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
- [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
+ [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing],
+ [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true],
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
]
@@ -564,6 +615,29 @@ class TestSetTraceFunc < Test::Unit::TestCase
}
end
+ # Bug #18264
+ def test_tracepoint_memory_leak
+ assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
+code = proc { TracePoint.new(:line) { } }
+1_000.times(&code)
+PREP
+1_000_000.times(&code)
+CODE
+ end
+
+ def test_tracepoint_bmethod_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true)
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+ tp = TracePoint.new(:return) {}
+ begin;
+ 1_000_000.times do
+ tp.enable(target: bmethod) {}
+ end
+ end;
+ end
+
def trace_by_set_trace_func
events = []
trace = nil
@@ -576,7 +650,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
1: set_trace_func(lambda{|event, file, line, id, binding, klass|
- 2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
+ 2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy'
3: })
4: 1.times{|;_local_var| _local_var = :inner
5: tap{}
@@ -601,29 +675,31 @@ class TestSetTraceFunc < Test::Unit::TestCase
answer_events = [
#
- [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace],
+ [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, nil, nil],
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
+ [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, nil],
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
- [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
+ [:c_return, 4, "xyzzy", Integer, :times, 1, nil, nil],
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
- [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
- [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
+ [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, nil],
+ [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil],
+ [:c_call, 7, "xyzzy", Class, :const_added, Object, nil, nil],
+ [:c_return, 7, "xyzzy", Class, :const_added, Object, nil, nil],
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
- [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
+ [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
+ [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil],
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
- [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
- [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
- [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
+ [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, nil],
+ [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil],
+ [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, nil],
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
@@ -634,23 +710,29 @@ class TestSetTraceFunc < Test::Unit::TestCase
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
- [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
- [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
- [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
- [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
- [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
+ [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, nil],
+ [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil],
+ [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
+ [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil],
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
- [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
- [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
+ [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, nil],
+ [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, nil],
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
- [:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing],
+ [:c_call, 21, "xyzzy", TracePoint, :disable, trace, nil, nil],
]
return events, answer_events
end
+ def test_set_trace_func_curry_argument_error
+ b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) }.curry[1, 2]
+ set_trace_func(proc {})
+ assert_raise(ArgumentError) {b[3, 4]}
+ end
+
def test_set_trace_func
actual_events, expected_events = trace_by_set_trace_func
expected_events.zip(actual_events){|e, a|
@@ -707,25 +789,30 @@ class TestSetTraceFunc < Test::Unit::TestCase
def test_tracepoint_enable
ary = []
args = nil
- trace = TracePoint.new(:call){|tp|
- next if !target_thread?
- ary << tp.method_id
- }
- foo
- trace.enable{|*a|
- args = a
+ begin
+ trace = TracePoint.new(:call){|tp|
+ next if !target_thread?
+ ary << tp.method_id
+ }
foo
- }
- foo
- assert_equal([:foo], ary)
- assert_equal([], args)
+ trace.enable(target_thread: nil){|*a|
+ args = a
+ foo
+ }
+ foo
+ assert_equal([:foo], ary)
+ assert_equal([], args)
+ ensure
+ trace&.disable
+ end
trace = TracePoint.new{}
begin
assert_equal(false, trace.enable)
assert_equal(true, trace.enable)
- trace.enable{}
- assert_equal(true, trace.enable)
+ trace.enable(target_thread: nil){}
+ trace.disable
+ assert_equal(false, trace.enable)
ensure
trace.disable
end
@@ -957,7 +1044,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
tp.defined_class, #=> nil,
tp.self.class # tp.self return creating/ending thread
]
- }.enable{
+ }.enable(target_thread: nil){
created_thread = Thread.new{thread_self = self}
created_thread.join
}
@@ -1941,7 +2028,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
define_method(:f_break_defined) do
- return :f_break_defined
+ break :f_break_defined
end
define_method(:f_raise_defined) do
@@ -1962,27 +2049,44 @@ class TestSetTraceFunc < Test::Unit::TestCase
tp_return_value(:f_last_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_return_defined, nil], # current limitation
+ assert_equal [[:b_return, :f_return_defined, :f_return_defined],
[:return, :f_return_defined, :f_return_defined]],
tp_return_value(:f_return_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_break_defined, nil],
+ assert_equal [[:b_return, :f_break_defined, :f_break_defined],
[:return, :f_break_defined, :f_break_defined]],
tp_return_value(:f_break_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_raise_defined, nil],
+ assert_equal [[:b_return, :f_raise_defined, f_raise_defined],
[:return, :f_raise_defined, f_raise_defined]],
tp_return_value(:f_raise_defined),
'[Bug #13369]'
- assert_equal [[:b_return, :f_break_in_rescue_defined, nil],
+ assert_equal [[:b_return, :f_break_in_rescue_defined, f_break_in_rescue_defined],
[:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]],
tp_return_value(:f_break_in_rescue_defined),
'[Bug #13369]'
end
+ define_method(:just_yield) do |&block|
+ block.call
+ end
+
+ define_method(:unwind_multiple_bmethods) do
+ just_yield { return :unwind_multiple_bmethods }
+ end
+
+ def test_non_local_return_across_multiple_define_methods
+ assert_equal [[:b_return, :unwind_multiple_bmethods, nil],
+ [:b_return, :just_yield, nil],
+ [:return, :just_yield, nil],
+ [:b_return, :unwind_multiple_bmethods, :unwind_multiple_bmethods],
+ [:return, :unwind_multiple_bmethods, :unwind_multiple_bmethods]],
+ tp_return_value(:unwind_multiple_bmethods)
+ end
+
def f_iter
yield
end
@@ -2079,7 +2183,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
Thread.pass until t.status == 'sleep'
# When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop.
# This sleep forces it to reach m2t_q.pop for --jit-wait.
- sleep 1 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
+ sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
t.add_trace_func proc{|ev, file, line, *args|
if file == __FILE__
@@ -2092,17 +2196,16 @@ class TestSetTraceFunc < Test::Unit::TestCase
m2t_q.push 1
t.join
- assert_equal ["c-return", base_line + 31], events[0]
- assert_equal ["line", base_line + 32], events[1]
- assert_equal ["line", base_line + 33], events[2]
- assert_equal ["call", base_line + -6], events[3]
- assert_equal ["return", base_line + -4], events[4]
- assert_equal ["line", base_line + 34], events[5]
- assert_equal ["line", base_line + 35], events[6]
- assert_equal ["c-call", base_line + 35], events[7] # Thread.current
- assert_equal ["c-return", base_line + 35], events[8] # Thread.current
- assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func
- assert_equal nil, events[10]
+ assert_equal ["line", base_line + 32], events[0]
+ assert_equal ["line", base_line + 33], events[1]
+ assert_equal ["call", base_line + -6], events[2]
+ assert_equal ["return", base_line + -4], events[3]
+ assert_equal ["line", base_line + 34], events[4]
+ assert_equal ["line", base_line + 35], events[5]
+ assert_equal ["c-call", base_line + 35], events[6] # Thread.current
+ assert_equal ["c-return", base_line + 35], events[7] # Thread.current
+ assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func
+ assert_equal nil, events[9]
end
def test_lineno_in_optimized_insn
@@ -2202,7 +2305,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
# global TP and targeted TP
ex = assert_raise(ArgumentError) do
tp = TracePoint.new(:line){}
- tp.enable{
+ tp.enable(target_thread: nil){
tp.enable(target: code2){}
}
end
@@ -2316,6 +2419,28 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal [:tp1, 1, 2, :tp2, 3], events
end
+ def test_multiple_tracepoints_same_bmethod
+ events = []
+ tp1 = TracePoint.new(:return) do |tp|
+ events << :tp1
+ end
+ tp2 = TracePoint.new(:return) do |tp|
+ events << :tp2
+ end
+
+ obj = Object.new
+ obj.define_singleton_method(:foo) {}
+ bmethod = obj.method(:foo)
+
+ tp1.enable(target: bmethod) do
+ tp2.enable(target: bmethod) do
+ obj.foo
+ end
+ end
+
+ assert_equal([:tp2, :tp1], events, '[Bug #18031]')
+ end
+
def test_script_compiled
events = []
tp = TracePoint.new(:script_compiled){|tp|
@@ -2344,7 +2469,7 @@ class TestSetTraceFunc < Test::Unit::TestCase
}
assert_equal [], events, 'script_compiled event should not be invoked on compile error'
- skip "TODO: test for requires"
+ omit "TODO: test for requires"
events.clear
tp.enable{
@@ -2393,6 +2518,99 @@ class TestSetTraceFunc < Test::Unit::TestCase
assert_equal Array.new(2){th}, events
end
+ def test_return_bmethod_location
+ bug13392 = "[ruby-core:80515] incorrect bmethod return location"
+ actual = nil
+ obj = Object.new
+ expected = __LINE__ + 1
+ obj.define_singleton_method(:t){}
+ tp = TracePoint.new(:return) do
+ next unless target_thread?
+ actual = tp.lineno
+ end
+ tp.enable {obj.t}
+ assert_equal(expected, actual, bug13392)
+ end
+
+ def test_b_tracepoints_going_away
+ # test that call and return TracePoints continue to work
+ # when b_call and b_return TracePoints stop
+ events = []
+ record_events = ->(tp) do
+ next unless target_thread?
+ events << [tp.event, tp.method_id]
+ end
+
+ call_ret_tp = TracePoint.new(:call, :return, &record_events)
+ block_call_ret_tp = TracePoint.new(:b_call, :b_return, &record_events)
+
+ obj = Object.new
+ obj.define_singleton_method(:foo) {} # a bmethod
+
+ foo = obj.method(:foo)
+ call_ret_tp.enable(target: foo) do
+ block_call_ret_tp.enable(target: foo) do
+ obj.foo
+ end
+ obj.foo
+ end
+
+ assert_equal(
+ [
+ [:call, :foo],
+ [:b_call, :foo],
+ [:b_return, :foo],
+ [:return, :foo],
+ [:call, :foo],
+ [:return, :foo],
+ ],
+ events,
+ )
+ end
+
+ def test_target_different_bmethod_same_iseq
+ # make two bmethods that share the same block iseq
+ block = Proc.new {}
+ obj = Object.new
+ obj.define_singleton_method(:one, &block)
+ obj.define_singleton_method(:two, &block)
+
+ events = []
+ record_events = ->(tp) do
+ next unless target_thread?
+ events << [tp.event, tp.method_id]
+ end
+ tp_one = TracePoint.new(:call, :return, &record_events)
+ tp_two = TracePoint.new(:call, :return, &record_events)
+
+ tp_one.enable(target: obj.method(:one)) do
+ obj.one
+ obj.two # not targeted
+ end
+ assert_equal([[:call, :one], [:return, :one]], events)
+ events.clear
+
+ tp_one.enable(target: obj.method(:one)) do
+ obj.one
+ tp_two.enable(target: obj.method(:two)) do
+ obj.two
+ end
+ obj.two
+ obj.one
+ end
+ assert_equal(
+ [
+ [:call, :one],
+ [:return, :one],
+ [:call, :two],
+ [:return, :two],
+ [:call, :one],
+ [:return, :one],
+ ],
+ events
+ )
+ end
+
def test_return_event_with_rescue
obj = Object.new
def obj.example
@@ -2420,6 +2638,20 @@ class TestSetTraceFunc < Test::Unit::TestCase
end
bar
EOS
+
+ assert_normal_exit(<<-EOS, 'Bug #18730')
+ def bar
+ 42
+ end
+ tp_line = TracePoint.new(:line) do |tp0|
+ tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp|
+ tp0.disable
+ end
+ tp_multi1.enable
+ end
+ tp_line.enable(target: method(:bar))
+ bar
+ EOS
end
def test_stat_exists
@@ -2455,4 +2687,103 @@ class TestSetTraceFunc < Test::Unit::TestCase
}
assert_equal [__LINE__ - 5, __LINE__ - 4, __LINE__ - 3], lines, 'Bug #17868'
end
+
+ def test_allow_reentry
+ event_lines = []
+ _l1 = _l2 = _l3 = _l4 = nil
+ TracePoint.new(:line) do |tp|
+ next unless target_thread?
+
+ event_lines << tp.lineno
+ next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno)
+ TracePoint.allow_reentry do
+ _a = 1; _l3 = __LINE__
+ _b = 2; _l4 = __LINE__
+ end
+ end.enable do
+ _c = 3; _l1 = __LINE__
+ _d = 4; _l2 = __LINE__
+ end
+
+ assert_equal [_l1, _l3, _l4, _l2, _l3, _l4], event_lines
+
+ assert_raise RuntimeError do
+ TracePoint.allow_reentry{}
+ end
+ end
+
+ def test_raising_from_b_return_tp_tracing_bmethod
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.trace(:b_return) do |tp|
+ raise
+ end
+
+ Foo.foo
+ RUBY
+
+ # Same thing but with a target
+ assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3)
+ class Foo
+ define_singleton_method(:foo) { return } # a bmethod
+ end
+
+ TracePoint.new(:b_return) do |tp|
+ raise
+ end.enable(target: Foo.method(:foo))
+
+ Foo.foo
+ RUBY
+ end
+
+ def test_tracepoint_thread_begin
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_begin) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ Thread.new{}.join
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
+
+ def test_tracepoint_thread_end_with_exception
+ target_thread = nil
+
+ trace = TracePoint.new(:thread_end) do |tp|
+ target_thread = tp.self
+ end
+
+ trace.enable(target_thread: nil) do
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+ raise
+ end
+
+ # Ignore the exception raised by the thread:
+ thread.join rescue nil
+ end
+
+ assert_kind_of(Thread, target_thread)
+ end
end
diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb
new file mode 100644
index 0000000000..7db4d0998d
--- /dev/null
+++ b/test/ruby/test_shapes.rb
@@ -0,0 +1,441 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+# These test the functionality of object shapes
+class TestShapes < Test::Unit::TestCase
+ class IVOrder
+ def expected_ivs
+ %w{ @a @b @c @d @e @f @g @h @i @j @k }
+ end
+
+ def set_ivs
+ expected_ivs.each { instance_variable_set(_1, 1) }
+ self
+ end
+ end
+
+ class ShapeOrder
+ def initialize
+ @b = :b # 5 => 6
+ end
+
+ def set_b
+ @b = :b # 5 => 6
+ end
+
+ def set_c
+ @c = :c # 5 => 7
+ end
+ end
+
+ class Example
+ def initialize
+ @a = 1
+ end
+ end
+
+ class RemoveAndAdd
+ def add_foo
+ @foo = 1
+ end
+
+ def remove_foo
+ remove_instance_variable(:@foo)
+ end
+
+ def add_bar
+ @bar = 1
+ end
+ end
+
+ class TooComplex
+ attr_reader :hopefully_unique_name, :b
+
+ def initialize
+ @hopefully_unique_name = "a"
+ @b = "b"
+ end
+
+ # Make enough lazily defined accessors to allow us to force
+ # polymorphism
+ class_eval (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ "def a#{_1}_m; @a#{_1} ||= #{_1}; end"
+ }.join(" ; ")
+
+ class_eval "attr_accessor " + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map {
+ ":a#{_1}"
+ }.join(", ")
+
+ def iv_not_defined; @not_defined; end
+
+ def write_iv_method
+ self.a3 = 12345
+ end
+
+ def write_iv
+ @a3 = 12345
+ end
+ end
+
+ # RubyVM::Shape.of returns new instances of shape objects for
+ # each call. This helper method allows us to define equality for
+ # shapes
+ def assert_shape_equal(shape1, shape2)
+ assert_equal(shape1.id, shape2.id)
+ assert_equal(shape1.parent_id, shape2.parent_id)
+ assert_equal(shape1.depth, shape2.depth)
+ assert_equal(shape1.type, shape2.type)
+ end
+
+ def refute_shape_equal(shape1, shape2)
+ refute_equal(shape1.id, shape2.id)
+ end
+
+ def test_iv_order_correct_on_complex_objects
+ (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times {
+ IVOrder.new.instance_variable_set("@a#{_1}", 1)
+ }
+
+ obj = IVOrder.new
+ iv_list = obj.set_ivs.instance_variables
+ assert_equal obj.expected_ivs, iv_list.map(&:to_s)
+ end
+
+ def test_too_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ end
+
+ def test_too_complex_ractor
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take
+ assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort
+ end;
+ end
+
+ def test_too_complex_ractor_shareable
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ $VERBOSE = nil
+ class TooComplex
+ attr_reader :very_unique
+ end
+
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new)
+ end
+
+ tc = TooComplex.new
+ tc.instance_variable_set(:"@very_unique", 3)
+
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.very_unique
+ assert_equal 3, Ractor.make_shareable(tc).very_unique
+ end;
+ end
+
+ def test_read_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ end
+
+ def test_read_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ assert_equal 3, tc.a3
+ end
+
+ def test_write_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv_method
+ tc.write_iv_method
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_write_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.write_iv
+ tc.write_iv
+ assert_equal 12345, tc.a3_m
+ assert_equal 12345, tc.a3
+ end
+
+ def test_iv_read_via_method_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal 3, tc.a3_m
+ assert_equal 3, tc.instance_variable_get(:@a3)
+ end
+
+ def test_delete_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ assert_equal 3, tc.a3_m # make sure IV is initialized
+ assert tc.instance_variable_defined?(:@a3)
+ tc.remove_instance_variable(:@a3)
+ assert_nil tc.a3
+ end
+
+ def test_delete_undefined_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+
+ refute tc.instance_variable_defined?(:@a3)
+ assert_raise(NameError) do
+ tc.remove_instance_variable(:@a3)
+ end
+ assert_nil tc.a3
+ end
+
+ def test_freeze_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ tc.freeze
+ assert_raise(FrozenError) { tc.a3_m }
+ end
+
+ def test_read_undefined_iv_after_complex
+ ensure_complex
+
+ tc = TooComplex.new
+ tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m")
+ assert_predicate RubyVM::Shape.of(tc), :too_complex?
+ assert_equal nil, tc.iv_not_defined
+ end
+
+ def test_shape_order
+ bar = ShapeOrder.new # 0 => 1
+ bar.set_c # 1 => 2
+ bar.set_b # 2 => 2
+
+ foo = ShapeOrder.new # 0 => 1
+ shape_id = RubyVM::Shape.of(foo).id
+ foo.set_b # should not transition
+ assert_equal shape_id, RubyVM::Shape.of(foo).id
+ end
+
+ def test_iv_index
+ example = RemoveAndAdd.new
+ initial_shape = RubyVM::Shape.of(example)
+ assert_equal 0, initial_shape.next_iv_index
+
+ example.add_foo # makes a transition
+ add_foo_shape = RubyVM::Shape.of(example)
+ assert_equal([:@foo], example.instance_variables)
+ assert_equal(initial_shape.id, add_foo_shape.parent.id)
+ assert_equal(1, add_foo_shape.next_iv_index)
+
+ example.remove_foo # makes a transition
+ remove_foo_shape = RubyVM::Shape.of(example)
+ assert_equal([], example.instance_variables)
+ assert_shape_equal(initial_shape, remove_foo_shape)
+
+ example.add_bar # makes a transition
+ bar_shape = RubyVM::Shape.of(example)
+ assert_equal([:@bar], example.instance_variables)
+ assert_equal(initial_shape.id, bar_shape.parent_id)
+ assert_equal(1, bar_shape.next_iv_index)
+ end
+
+ def test_remove_then_add_again
+ example = RemoveAndAdd.new
+ _initial_shape = RubyVM::Shape.of(example)
+
+ example.add_foo # makes a transition
+ add_foo_shape = RubyVM::Shape.of(example)
+ example.remove_foo # makes a transition
+ example.add_foo # makes a transition
+ assert_shape_equal(add_foo_shape, RubyVM::Shape.of(example))
+ end
+
+ class TestObject; end
+
+ def test_new_obj_has_t_object_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(TestObject.new).parent)
+ end
+
+ def test_str_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(""))
+ end
+
+ def test_array_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([]))
+ end
+
+ def test_hash_has_root_shape
+ assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of({}))
+ end
+
+ def test_true_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id)
+ end
+
+ def test_nil_has_special_const_shape_id
+ assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id)
+ end
+
+ def test_basic_shape_transition
+ obj = Example.new
+ shape = RubyVM::Shape.of(obj)
+ refute_equal(RubyVM::Shape.root_shape, shape)
+ assert_equal :@a, shape.edge_name
+ assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type
+
+ shape = shape.parent
+ assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type
+
+ shape = shape.parent
+ assert_equal(RubyVM::Shape.root_shape.id, shape.id)
+ assert_equal(obj.instance_variable_get(:@a), 1)
+ end
+
+ def test_different_objects_make_same_transition
+ obj = []
+ obj2 = ""
+ obj.instance_variable_set(:@a, 1)
+ obj2.instance_variable_set(:@a, 1)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_objects
+ obj = Example.new
+ obj2 = obj.dup
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_duplicating_too_complex_objects_memory_leak
+ assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true)
+ RubyVM::Shape.exhaust_shapes
+
+ o = Object.new
+ o.instance_variable_set(:@a, 0)
+ begin;
+ 1_000_000.times do
+ o.dup
+ end
+ end;
+ end
+
+ def test_freezing_and_duplicating_object
+ obj = Object.new.freeze
+ obj2 = obj.dup
+ refute_predicate(obj2, :frozen?)
+ # dup'd objects shouldn't be frozen, and the shape should be the
+ # parent shape of the copied object
+ assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id)
+ end
+
+ def test_freezing_and_duplicating_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.dup
+ refute_predicate(obj2, :frozen?)
+ refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_duplicating_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ str2 = str.dup
+ refute_predicate(str2, :frozen?)
+ refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id)
+ assert_equal(str2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_cloning_objects
+ obj = Object.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ end
+
+ def test_freezing_and_cloning_object_with_ivars
+ obj = Example.new.freeze
+ obj2 = obj.clone(freeze: true)
+ assert_predicate(obj2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2))
+ assert_equal(obj2.instance_variable_get(:@a), 1)
+ end
+
+ def test_freezing_and_cloning_string
+ str = "str".freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ end
+
+ def test_freezing_and_cloning_string_with_ivars
+ str = "str"
+ str.instance_variable_set(:@a, 1)
+ str.freeze
+ str2 = str.clone(freeze: true)
+ assert_predicate(str2, :frozen?)
+ assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2))
+ assert_equal(str2.instance_variable_get(:@a), 1)
+ end
+
+ def test_out_of_bounds_shape
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(RubyVM.stat[:next_shape_id])
+ end
+ assert_raise ArgumentError do
+ RubyVM::Shape.find_by_id(-1)
+ end
+ end
+
+ def ensure_complex
+ RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
+ tc = TooComplex.new
+ tc.send("a#{_1}_m")
+ end
+ end
+end if defined?(RubyVM::Shape)
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb
index a62537d59d..c5043eea59 100644
--- a/test/ruby/test_signal.rb
+++ b/test/ruby/test_signal.rb
@@ -291,7 +291,8 @@ class TestSignal < Test::Unit::TestCase
if trap = Signal.list['TRAP']
bug9820 = '[ruby-dev:48592] [Bug #9820]'
- status = assert_in_out_err(['-e', 'Process.kill(:TRAP, $$)'])
+ no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE)
+ status = assert_in_out_err(['-e', "#{no_core}Process.kill(:TRAP, $$)"])
assert_predicate(status, :signaled?, bug9820)
assert_equal(trap, status.termsig, bug9820)
end
@@ -323,7 +324,7 @@ class TestSignal < Test::Unit::TestCase
end
def test_sigchld_ignore
- skip 'no SIGCHLD' unless Signal.list['CHLD']
+ omit 'no SIGCHLD' unless Signal.list['CHLD']
old = trap(:CHLD, 'IGNORE')
cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ]
assert(system(*cmd, 'exit!(0)'), 'no ECHILD')
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index 7986e9d141..c453ecd350 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -362,11 +362,16 @@ class TestSprintf < Test::Unit::TestCase
def test_char
assert_equal("a", sprintf("%c", 97))
assert_equal("a", sprintf("%c", ?a))
- assert_raise(ArgumentError) { sprintf("%c", sprintf("%c%c", ?a, ?a)) }
+ assert_equal("a", sprintf("%c", "a"))
+ assert_equal("a", sprintf("%c", sprintf("%c%c", ?a, ?a)))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%c", ?a))
assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%-1c", ?a))
assert_equal(" " * BSIZ + "a", sprintf("%#{ BSIZ + 1 }c", ?a))
assert_equal("a" + " " * BSIZ, sprintf("%-#{ BSIZ + 1 }c", ?a))
+ assert_raise(ArgumentError) { sprintf("%c", -1) }
+ s = sprintf("%c".encode(Encoding::US_ASCII), 0x80)
+ assert_equal("\x80".b, s)
+ assert_predicate(s, :valid_encoding?)
end
def test_string
@@ -507,6 +512,16 @@ class TestSprintf < Test::Unit::TestCase
end
end
+ def test_coderange
+ format_str = "wrong constant name %s"
+ interpolated_str = "\u3042"
+ assert_predicate format_str, :ascii_only?
+ refute_predicate interpolated_str, :ascii_only?
+
+ str = format_str % interpolated_str
+ refute_predicate str, :ascii_only?
+ end
+
def test_named_default
h = Hash.new('world')
assert_equal("hello world", "hello %{location}" % h)
@@ -528,19 +543,4 @@ class TestSprintf < Test::Unit::TestCase
sprintf("%*s", RbConfig::LIMITS["INT_MIN"], "")
end
end
-
- def test_no_hidden_garbage
- skip unless Thread.list.size == 1
-
- fmt = [4, 2, 2].map { |x| "%0#{x}d" }.join('-') # defeats optimization
- ObjectSpace.count_objects(res = {}) # creates strings on first call
- GC.disable
- before = ObjectSpace.count_objects(res)[:T_STRING]
- val = sprintf(fmt, 1970, 1, 1)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- assert_equal before + 1, after, 'only new string is the created one'
- assert_equal '1970-01-01', val
- ensure
- GC.enable
- end
end
diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb
index 3f7c06e075..24527a527f 100644
--- a/test/ruby/test_string.rb
+++ b/test/ruby/test_string.rb
@@ -83,7 +83,7 @@ class TestString < Test::Unit::TestCase
end
def test_initialize_shared
- String.new(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize)
+ S(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize)
assert_equal("mystring", str[0, 8])
end
@@ -97,6 +97,8 @@ class TestString < Test::Unit::TestCase
end
def test_initialize_memory_leak
+ return unless @cls == String
+
assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
code = proc {('x'*100000).__send__(:initialize, '')}
1_000.times(&code)
@@ -107,6 +109,8 @@ CODE
# Bug #18154
def test_initialize_nofree_memory_leak
+ return unless @cls == String
+
assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true)
code = proc {0.to_s.__send__(:initialize, capacity: 10000)}
1_000.times(&code)
@@ -240,23 +244,23 @@ CODE
assert_equal(-1, S("ABCDEF") <=> S("abcdef"))
- assert_nil("foo" <=> Object.new)
+ assert_nil(S("foo") <=> Object.new)
o = Object.new
def o.to_str; "bar"; end
- assert_equal(1, "foo" <=> o)
+ assert_equal(1, S("foo") <=> o)
class << o;remove_method :to_str;end
def o.<=>(x); nil; end
- assert_nil("foo" <=> o)
+ assert_nil(S("foo") <=> o)
class << o;remove_method :<=>;end
def o.<=>(x); 1; end
- assert_equal(-1, "foo" <=> o)
+ assert_equal(-1, S("foo") <=> o)
class << o;remove_method :<=>;end
def o.<=>(x); 2**100; end
- assert_equal(-1, "foo" <=> o)
+ assert_equal(-1, S("foo") <=> o)
end
def test_EQUAL # '=='
@@ -270,10 +274,10 @@ CODE
o = Object.new
def o.to_str; end
def o.==(x); false; end
- assert_equal(false, "foo" == o)
+ assert_equal(false, S("foo") == o)
class << o;remove_method :==;end
def o.==(x); true; end
- assert_equal(true, "foo" == o)
+ assert_equal(true, S("foo") == o)
end
def test_LSHIFT # '<<'
@@ -297,6 +301,9 @@ CODE
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -1}
assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << 0x81308130}
assert_nothing_raised {S("a".force_encoding(Encoding::GB18030)) << 0x81308130}
+
+ s = "\x95".force_encoding(Encoding::SJIS).tap(&:valid_encoding?)
+ assert_predicate(s << 0x5c, :valid_encoding?)
end
def test_MATCH # '=~'
@@ -648,12 +655,12 @@ CODE
result << 0x0300
expected = S("\u0300".encode(Encoding::UTF_16LE))
assert_equal(expected, result, bug7090)
- assert_raise(TypeError) { 'foo' << :foo }
- assert_raise(FrozenError) { 'foo'.freeze.concat('bar') }
+ assert_raise(TypeError) { S('foo') << :foo }
+ assert_raise(FrozenError) { S('foo').freeze.concat('bar') }
end
def test_concat_literals
- s="." * 50
+ s=S("." * 50)
assert_equal(Encoding::UTF_8, "#{s}x".encoding)
end
@@ -664,18 +671,18 @@ CODE
assert_equal(4, a.count(S("hello"), S("^l")))
assert_equal(4, a.count(S("ej-m")))
assert_equal(0, S("y").count(S("a\\-z")))
- assert_equal(5, "abc\u{3042 3044 3046}".count("^a"))
- assert_equal(1, "abc\u{3042 3044 3046}".count("\u3042"))
- assert_equal(5, "abc\u{3042 3044 3046}".count("^\u3042"))
- assert_equal(2, "abc\u{3042 3044 3046}".count("a-z", "^a"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("a", "\u3042"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "a"))
- assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "\u3044"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^a", "^\u3044"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3044", "^a"))
- assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3042", "^\u3044"))
+ assert_equal(5, S("abc\u{3042 3044 3046}").count("^a"))
+ assert_equal(1, S("abc\u{3042 3044 3046}").count("\u3042"))
+ assert_equal(5, S("abc\u{3042 3044 3046}").count("^\u3042"))
+ assert_equal(2, S("abc\u{3042 3044 3046}").count("a-z", "^a"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("a", "\u3042"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "a"))
+ assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "\u3044"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^a", "^\u3044"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3044", "^a"))
+ assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3042", "^\u3044"))
- assert_raise(ArgumentError) { "foo".count }
+ assert_raise(ArgumentError) { S("foo").count }
end
def crypt_supports_des_crypt?
@@ -717,17 +724,17 @@ CODE
assert_equal(S("hell"), S("hello").delete(S("aeiou"), S("^e")))
assert_equal(S("ho"), S("hello").delete(S("ej-m")))
- assert_equal("a".hash, "a\u0101".delete("\u0101").hash, '[ruby-talk:329267]')
- assert_equal(true, "a\u0101".delete("\u0101").ascii_only?)
- assert_equal(true, "a\u3041".delete("\u3041").ascii_only?)
- assert_equal(false, "a\u3041\u3042".delete("\u3041").ascii_only?)
+ assert_equal(S("a").hash, S("a\u0101").delete("\u0101").hash, '[ruby-talk:329267]')
+ assert_equal(true, S("a\u0101").delete("\u0101").ascii_only?)
+ assert_equal(true, S("a\u3041").delete("\u3041").ascii_only?)
+ assert_equal(false, S("a\u3041\u3042").delete("\u3041").ascii_only?)
- assert_equal("a", "abc\u{3042 3044 3046}".delete("^a"))
- assert_equal("bc\u{3042 3044 3046}", "abc\u{3042 3044 3046}".delete("a"))
- assert_equal("\u3042", "abc\u{3042 3044 3046}".delete("^\u3042"))
+ assert_equal("a", S("abc\u{3042 3044 3046}").delete("^a"))
+ assert_equal("bc\u{3042 3044 3046}", S("abc\u{3042 3044 3046}").delete("a"))
+ assert_equal("\u3042", S("abc\u{3042 3044 3046}").delete("^\u3042"))
bug6160 = '[ruby-dev:45374]'
- assert_equal("", '\\'.delete('\\'), bug6160)
+ assert_equal("", S('\\').delete('\\'), bug6160)
end
def test_delete!
@@ -832,10 +839,10 @@ CODE
assert_equal(Encoding::UTF_8, S('"\\u3042"').encode(Encoding::EUC_JP).undump.encoding)
assert_equal("abc".encode(Encoding::UTF_16LE),
- '"a\x00b\x00c\x00".force_encoding("UTF-16LE")'.undump)
+ S('"a\x00b\x00c\x00".force_encoding("UTF-16LE")').undump)
- assert_equal('\#', '"\\\\#"'.undump)
- assert_equal('\#{', '"\\\\\#{"'.undump)
+ assert_equal('\#', S('"\\\\#"').undump)
+ assert_equal('\#{', S('"\\\\\#{"').undump)
assert_raise(RuntimeError) { S('\u3042').undump }
assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump }
@@ -867,7 +874,7 @@ CODE
assert_raise(RuntimeError) { S('"\\"').undump }
assert_raise(RuntimeError) { S(%("\0")).undump }
assert_raise_with_message(RuntimeError, /invalid/) {
- '"\\u{007F}".xxxxxx'.undump
+ S('"\\u{007F}".xxxxxx').undump
}
end
@@ -883,6 +890,18 @@ CODE
end
end
+ class StringWithIVSet < String
+ def set_iv
+ @foo = 1
+ end
+ end
+
+ def test_ivar_set_after_frozen_dup
+ str = StringWithIVSet.new.freeze
+ str.dup.set_iv
+ assert_raise(FrozenError) { str.set_iv }
+ end
+
def test_each
verbose, $VERBOSE = $VERBOSE, nil
@@ -1047,9 +1066,9 @@ CODE
g = g.encode(enc)
assert_equal g.chars, g.grapheme_clusters
end
- assert_equal ["a", "b", "c"], "abc".b.grapheme_clusters
+ assert_equal ["a", "b", "c"], S("abc").b.grapheme_clusters
- s = "ABC".b
+ s = S("ABC").b
res = []
assert_same s, s.grapheme_clusters {|x| res << x }
assert_equal(3, res.size)
@@ -1058,6 +1077,17 @@ CODE
assert_equal("C", res[2])
end
+ def test_grapheme_clusters_memory_leak
+ assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #todo]", rss: true)
+ begin;
+ str = "hello world".encode(Encoding::UTF_32LE)
+
+ 10_000.times do
+ str.grapheme_clusters
+ end
+ end;
+ end
+
def test_each_line
verbose, $VERBOSE = $VERBOSE, nil
@@ -1095,7 +1125,7 @@ CODE
$/ = save
s = nil
- "foo\nbar".each_line(nil) {|s2| s = s2 }
+ S("foo\nbar").each_line(nil) {|s2| s = s2 }
assert_equal("foo\nbar", s)
assert_equal "hello\n", S("hello\nworld").each_line.next
@@ -1103,7 +1133,7 @@ CODE
bug7646 = "[ruby-dev:46827]"
assert_nothing_raised(bug7646) do
- "\n\u0100".each_line("\n") {}
+ S("\n\u0100").each_line("\n") {}
end
ensure
$/ = save
@@ -1117,14 +1147,19 @@ CODE
assert_equal(S("world"), res[1])
res = []
- S("hello\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\n\n\nworld\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\n"), res[1])
res = []
- S("hello\r\n\r\nworld").each_line(S(''), chomp: true) {|x| res << x}
- assert_equal(S("hello\r\n"), res[0])
- assert_equal(S("world"), res[1])
+ S("hello\r\n\r\nworld\r\n").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world\r\n"), res[1])
+
+ res = []
+ S("hello\r\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x}
+ assert_equal(S("hello"), res[0])
+ assert_equal(S("world"), res[1])
res = []
S("hello!world").each_line(S('!'), chomp: true) {|x| res << x}
@@ -1137,7 +1172,7 @@ CODE
assert_equal(S("a"), res[0])
s = nil
- "foo\nbar".each_line(nil, chomp: true) {|s2| s = s2 }
+ S("foo\nbar").each_line(nil, chomp: true) {|s2| s = s2 }
assert_equal("foo\nbar", s)
assert_equal "hello", S("hello\nworld").each_line(chomp: true).next
@@ -1202,9 +1237,9 @@ CODE
S("hello").gsub(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 })
assert_equal(S("<>h<>e<>l<>l<>o<>"), S("hello").gsub(S(''), S('<\0>')))
- assert_equal("z", "abc".gsub(/./, "a" => "z"), "moved from btest/knownbug")
+ assert_equal("z", S("abc").gsub(/./, "a" => "z"), "moved from btest/knownbug")
- assert_raise(ArgumentError) { "foo".gsub }
+ assert_raise(ArgumentError) { S("foo").gsub }
end
def test_gsub_encoding
@@ -1251,23 +1286,23 @@ CODE
end
def test_sub_hash
- assert_equal('azc', 'abc'.sub(/b/, "b" => "z"))
- assert_equal('ac', 'abc'.sub(/b/, {}))
- assert_equal('a1c', 'abc'.sub(/b/, "b" => 1))
- assert_equal('aBc', 'abc'.sub(/b/, Hash.new {|h, k| k.upcase }))
- assert_equal('a[\&]c', 'abc'.sub(/b/, "b" => '[\&]'))
- assert_equal('aBcabc', 'abcabc'.sub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
- assert_equal('aBcdef', 'abcdef'.sub(/de|b/, "b" => "B", "de" => "DE"))
+ assert_equal('azc', S('abc').sub(/b/, "b" => "z"))
+ assert_equal('ac', S('abc').sub(/b/, {}))
+ assert_equal('a1c', S('abc').sub(/b/, "b" => 1))
+ assert_equal('aBc', S('abc').sub(/b/, Hash.new {|h, k| k.upcase }))
+ assert_equal('a[\&]c', S('abc').sub(/b/, "b" => '[\&]'))
+ assert_equal('aBcabc', S('abcabc').sub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
+ assert_equal('aBcdef', S('abcdef').sub(/de|b/, "b" => "B", "de" => "DE"))
end
def test_gsub_hash
- assert_equal('azc', 'abc'.gsub(/b/, "b" => "z"))
- assert_equal('ac', 'abc'.gsub(/b/, {}))
- assert_equal('a1c', 'abc'.gsub(/b/, "b" => 1))
- assert_equal('aBc', 'abc'.gsub(/b/, Hash.new {|h, k| k.upcase }))
- assert_equal('a[\&]c', 'abc'.gsub(/b/, "b" => '[\&]'))
- assert_equal('aBcaBc', 'abcabc'.gsub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
- assert_equal('aBcDEf', 'abcdef'.gsub(/de|b/, "b" => "B", "de" => "DE"))
+ assert_equal('azc', S('abc').gsub(/b/, "b" => "z"))
+ assert_equal('ac', S('abc').gsub(/b/, {}))
+ assert_equal('a1c', S('abc').gsub(/b/, "b" => 1))
+ assert_equal('aBc', S('abc').gsub(/b/, Hash.new {|h, k| k.upcase }))
+ assert_equal('a[\&]c', S('abc').gsub(/b/, "b" => '[\&]'))
+ assert_equal('aBcaBc', S('abcabc').gsub(/b/, Hash.new {|h, k| h[k] = k.upcase }))
+ assert_equal('aBcDEf', S('abcdef').gsub(/de|b/, "b" => "B", "de" => "DE"))
end
def test_hash
@@ -1329,13 +1364,22 @@ CODE
o = Object.new
def o.to_str; "bar"; end
- assert_equal(3, "foobarbarbaz".index(o))
- assert_raise(TypeError) { "foo".index(Object.new) }
+ assert_equal(3, S("foobarbarbaz").index(o))
+ assert_raise(TypeError) { S("foo").index(Object.new) }
- assert_nil("foo".index(//, -100))
+ assert_nil(S("foo").index(//, -100))
assert_nil($~)
assert_equal(2, S("abcdbce").index(/b\Kc/))
+
+ assert_equal(0, S("こんにちは").index(?こ))
+ assert_equal(1, S("こんにちは").index(S("んにち")))
+ assert_equal(2, S("こんにちは").index(/にち./))
+
+ assert_equal(0, S("にんにちは").index(?に, 0))
+ assert_equal(2, S("にんにちは").index(?に, 1))
+ assert_equal(2, S("にんにちは").index(?に, 2))
+ assert_nil(S("にんにちは").index(?に, 3))
end
def test_insert
@@ -1442,16 +1486,16 @@ CODE
b = a.replace(S("xyz"))
assert_equal(S("xyz"), b)
- s = "foo" * 100
+ s = S("foo") * 100
s2 = ("bar" * 100).dup
s.replace(s2)
assert_equal(s2, s)
- s2 = ["foo"].pack("p")
+ s2 = [S("foo")].pack("p")
s.replace(s2)
assert_equal(s2, s)
- fs = "".freeze
+ fs = S("").freeze
assert_raise(FrozenError) { fs.replace("a") }
assert_raise(FrozenError) { fs.replace(fs) }
assert_raise(ArgumentError) { fs.replace() }
@@ -1498,18 +1542,41 @@ CODE
assert_nil(S("hello").rindex(S("z")))
assert_nil(S("hello").rindex(/z./))
+ assert_equal(5, S("hello").rindex(S("")))
+ assert_equal(5, S("hello").rindex(S(""), 5))
+ assert_equal(4, S("hello").rindex(S(""), 4))
+ assert_equal(0, S("hello").rindex(S(""), 0))
+
o = Object.new
def o.to_str; "bar"; end
- assert_equal(6, "foobarbarbaz".rindex(o))
- assert_raise(TypeError) { "foo".rindex(Object.new) }
+ assert_equal(6, S("foobarbarbaz").rindex(o))
+ assert_raise(TypeError) { S("foo").rindex(Object.new) }
- assert_nil("foo".rindex(//, -100))
+ assert_nil(S("foo").rindex(//, -100))
assert_nil($~)
- assert_equal(3, "foo".rindex(//))
+ assert_equal(3, S("foo").rindex(//))
assert_equal([3, 3], $~.offset(0))
assert_equal(5, S("abcdbce").rindex(/b\Kc/))
+
+ assert_equal(2, S("こんにちは").rindex(?に))
+ assert_equal(6, S("にちは、こんにちは").rindex(S("にちは")))
+ assert_equal(6, S("にちは、こんにちは").rindex(/にち./))
+
+ assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 7))
+ assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -2))
+ assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 6))
+ assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -3))
+ assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 5))
+ assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), -4))
+ assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 1))
+ assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 0))
+
+ assert_equal(0, S("こんにちは").rindex(S("こんにちは")))
+ assert_nil(S("こんにち").rindex(S("こんにちは")))
+ assert_nil(S("こ").rindex(S("こんにちは")))
+ assert_nil(S("").rindex(S("こんにちは")))
end
def test_rjust
@@ -1548,6 +1615,15 @@ CODE
assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./))
end
+ def test_scan_segv
+ bug19159 = '[Bug #19159]'
+ assert_nothing_raised(Exception, bug19159) do
+ ObjectSpace.each_object(MatchData).to_a
+ "".scan(//)
+ ObjectSpace.each_object(MatchData).to_a.inspect
+ end
+ end
+
def test_size
assert_equal(0, S("").size)
assert_equal(4, S("1234").size)
@@ -1689,7 +1765,8 @@ CODE
assert_equal(S("Bar"), a.slice!(S("Bar")))
assert_equal(S("Foo"), a)
- assert_raise(ArgumentError) { "foo".slice! }
+ a = S("foo")
+ assert_raise(ArgumentError) { a.slice! }
end
def test_split
@@ -1714,7 +1791,7 @@ CODE
assert_equal([S("a"), S(""), S("b"), S("c")], S("a||b|c|").split(S('|')))
assert_equal([S("a"), S(""), S("b"), S("c"), S("")], S("a||b|c|").split(S('|'), -1))
- assert_equal([], "".split(//, 1))
+ assert_equal([], S("").split(//, 1))
ensure
EnvUtil.suppress_warning {$; = fs}
end
@@ -1753,16 +1830,18 @@ CODE
result = []; S("a||b|c|").split(S('|'), -1) {|s| result << s}
assert_equal([S("a"), S(""), S("b"), S("c"), S("")], result)
- result = []; "".split(//, 1) {|s| result << s}
+ result = []; S("").split(//, 1) {|s| result << s}
assert_equal([], result)
- result = []; "aaa,bbb,ccc,ddd".split(/,/) {|s| result << s.gsub(/./, "A")}
+ result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")}
assert_equal(["AAA"]*4, result)
ensure
EnvUtil.suppress_warning {$; = fs}
end
def test_fs
+ return unless @cls == String
+
assert_raise_with_message(TypeError, /\$;/) {
$; = []
}
@@ -1856,9 +1935,9 @@ CODE
bug5536 = '[ruby-core:40623]'
assert_raise(TypeError, bug5536) {S("str").start_with? :not_convertible_to_string}
- assert_equal(true, "hello".start_with?(/hel/))
+ assert_equal(true, S("hello").start_with?(/hel/))
assert_equal("hel", $&)
- assert_equal(false, "hello".start_with?(/el/))
+ assert_equal(false, S("hello").start_with?(/el/))
assert_nil($&)
end
@@ -1868,9 +1947,9 @@ CODE
assert_equal(S("x"), S("\x00x\x00").strip)
assert_equal("0b0 ".force_encoding("UTF-16BE"),
- "\x00 0b0 ".force_encoding("UTF-16BE").strip)
+ S("\x00 0b0 ").force_encoding("UTF-16BE").strip)
assert_equal("0\x000b0 ".force_encoding("UTF-16BE"),
- "0\x000b0 ".force_encoding("UTF-16BE").strip)
+ S("0\x000b0 ").force_encoding("UTF-16BE").strip)
end
def test_strip!
@@ -1935,17 +2014,17 @@ CODE
o = Object.new
def o.to_str; "bar"; end
- assert_equal("fooBARbaz", "foobarbaz".sub(o, "BAR"))
+ assert_equal("fooBARbaz", S("foobarbaz").sub(o, "BAR"))
- assert_raise(TypeError) { "foo".sub(Object.new, "") }
+ assert_raise(TypeError) { S("foo").sub(Object.new, "") }
- assert_raise(ArgumentError) { "foo".sub }
+ assert_raise(ArgumentError) { S("foo").sub }
assert_raise(IndexError) { "foo"[/(?:(o$)|(x))/, 2] = 'bar' }
o = Object.new
def o.to_s; self; end
- assert_match(/^foo#<Object:0x.*>baz$/, "foobarbaz".sub("bar") { o })
+ assert_match(/^foo#<Object:0x.*>baz$/, S("foobarbaz").sub("bar") { o })
assert_equal(S("Abc"), S("abc").sub("a", "A"))
m = nil
@@ -1954,7 +2033,7 @@ CODE
assert_equal(/a/, m.regexp)
bug = '[ruby-core:78686] [Bug #13042] other than regexp has no name references'
assert_raise_with_message(IndexError, /oops/, bug) {
- 'hello'.gsub('hello', '\k<oops>')
+ S('hello').gsub('hello', '\k<oops>')
}
end
@@ -2001,18 +2080,18 @@ CODE
assert_equal(S("AAAAA000"), S("ZZZZ999").succ)
assert_equal(S("*+"), S("**").succ)
- assert_equal("abce", "abcd".succ)
- assert_equal("THX1139", "THX1138".succ)
- assert_equal("<\<koalb>>", "<\<koala>>".succ)
- assert_equal("2000aaa", "1999zzz".succ)
- assert_equal("AAAA0000", "ZZZ9999".succ)
- assert_equal("**+", "***".succ)
+ assert_equal("abce", S("abcd").succ)
+ assert_equal("THX1139", S("THX1138").succ)
+ assert_equal("<\<koalb>>", S("<\<koala>>").succ)
+ assert_equal("2000aaa", S("1999zzz").succ)
+ assert_equal("AAAA0000", S("ZZZ9999").succ)
+ assert_equal("**+", S("***").succ)
- assert_equal("!", " ".succ)
- assert_equal("", "".succ)
+ assert_equal("!", S(" ").succ)
+ assert_equal("", S("").succ)
bug = '[ruby-core:83062] [Bug #13952]'
- s = "\xff".b
+ s = S("\xff").b
assert_not_predicate(s, :ascii_only?)
assert_predicate(s.succ, :ascii_only?, bug)
end
@@ -2064,8 +2143,8 @@ CODE
assert_equal(S(""), a.succ!)
assert_equal(S(""), a)
- assert_equal("aaaaaaaaaaaa", "zzzzzzzzzzz".succ!)
- assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", "zzzzzzzzzzzzzzzzzzzzzzz".succ!)
+ assert_equal("aaaaaaaaaaaa", S("zzzzzzzzzzz").succ!)
+ assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", S("zzzzzzzzzzzzzzzzzzzzzzz").succ!)
end
def test_sum
@@ -2087,8 +2166,8 @@ CODE
end
def test_sum_2
- assert_equal(0, "".sum)
- assert_equal(294, "abc".sum)
+ assert_equal(0, S("").sum)
+ assert_equal(294, S("abc").sum)
check_sum("abc")
check_sum("\x80")
-3.upto(70) {|bits|
@@ -2135,39 +2214,39 @@ CODE
def test_to_i
assert_equal(1480, S("1480ft/sec").to_i)
assert_equal(0, S("speed of sound in water @20C = 1480ft/sec)").to_i)
- assert_equal(0, " 0".to_i)
- assert_equal(0, "+0".to_i)
- assert_equal(0, "-0".to_i)
- assert_equal(0, "--0".to_i)
- assert_equal(16, "0x10".to_i(0))
- assert_equal(16, "0X10".to_i(0))
- assert_equal(2, "0b10".to_i(0))
- assert_equal(2, "0B10".to_i(0))
- assert_equal(8, "0o10".to_i(0))
- assert_equal(8, "0O10".to_i(0))
- assert_equal(10, "0d10".to_i(0))
- assert_equal(10, "0D10".to_i(0))
- assert_equal(8, "010".to_i(0))
- assert_raise(ArgumentError) { "010".to_i(-10) }
+ assert_equal(0, S(" 0").to_i)
+ assert_equal(0, S("+0").to_i)
+ assert_equal(0, S("-0").to_i)
+ assert_equal(0, S("--0").to_i)
+ assert_equal(16, S("0x10").to_i(0))
+ assert_equal(16, S("0X10").to_i(0))
+ assert_equal(2, S("0b10").to_i(0))
+ assert_equal(2, S("0B10").to_i(0))
+ assert_equal(8, S("0o10").to_i(0))
+ assert_equal(8, S("0O10").to_i(0))
+ assert_equal(10, S("0d10").to_i(0))
+ assert_equal(10, S("0D10").to_i(0))
+ assert_equal(8, S("010").to_i(0))
+ assert_raise(ArgumentError) { S("010").to_i(-10) }
2.upto(36) {|radix|
- assert_equal(radix, "10".to_i(radix))
- assert_equal(radix**2, "100".to_i(radix))
+ assert_equal(radix, S("10").to_i(radix))
+ assert_equal(radix**2, S("100").to_i(radix))
}
- assert_raise(ArgumentError) { "0".to_i(1) }
- assert_raise(ArgumentError) { "0".to_i(37) }
- assert_equal(0, "z".to_i(10))
- assert_equal(12, "1_2".to_i(10))
- assert_equal(0x40000000, "1073741824".to_i(10))
- assert_equal(0x4000000000000000, "4611686018427387904".to_i(10))
- assert_equal(1, "1__2".to_i(10))
- assert_equal(1, "1_z".to_i(10))
+ assert_raise(ArgumentError) { S("0").to_i(1) }
+ assert_raise(ArgumentError) { S("0").to_i(37) }
+ assert_equal(0, S("z").to_i(10))
+ assert_equal(12, S("1_2").to_i(10))
+ assert_equal(0x40000000, S("1073741824").to_i(10))
+ assert_equal(0x4000000000000000, S("4611686018427387904").to_i(10))
+ assert_equal(1, S("1__2").to_i(10))
+ assert_equal(1, S("1_z").to_i(10))
bug6192 = '[ruby-core:43566]'
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16be").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16le").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32be").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32le").to_i}
- assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("iso-2022-jp").to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16be")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16le")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32be")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32le")).to_i}
+ assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("iso-2022-jp")).to_i}
end
def test_to_s
@@ -2199,13 +2278,13 @@ CODE
assert_equal(S("*e**o"), S("hello").tr(S("^aeiou"), S("*")))
assert_equal(S("hal"), S("ibm").tr(S("b-z"), S("a-z")))
- a = "abc".force_encoding(Encoding::US_ASCII)
+ a = S("abc".force_encoding(Encoding::US_ASCII))
assert_equal(Encoding::US_ASCII, a.tr(S("z"), S("\u0101")).encoding, '[ruby-core:22326]')
- assert_equal("a".hash, "a".tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]')
- assert_equal(true, "\u0101".tr("\u0101", "a").ascii_only?)
- assert_equal(true, "\u3041".tr("\u3041", "a").ascii_only?)
- assert_equal(false, "\u3041\u3042".tr("\u3041", "a").ascii_only?)
+ assert_equal("a".hash, S("a").tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]')
+ assert_equal(true, S("\u0101").tr("\u0101", "a").ascii_only?)
+ assert_equal(true, S("\u3041").tr("\u3041", "a").ascii_only?)
+ assert_equal(false, S("\u3041\u3042").tr("\u3041", "a").ascii_only?)
bug6156 = '[ruby-core:43335]'
bug13950 = '[ruby-core:83056] [Bug #13950]'
@@ -2236,7 +2315,7 @@ CODE
assert_nil(a.tr!(S("B-Z"), S("A-Z")))
assert_equal(S("ibm"), a)
- a = "abc".force_encoding(Encoding::US_ASCII)
+ a = S("abc".force_encoding(Encoding::US_ASCII))
assert_nil(a.tr!(S("z"), S("\u0101")), '[ruby-core:22326]')
assert_equal(Encoding::US_ASCII, a.encoding, '[ruby-core:22326]')
end
@@ -2244,8 +2323,8 @@ CODE
def test_tr_s
assert_equal(S("hypo"), S("hello").tr_s(S("el"), S("yp")))
assert_equal(S("h*o"), S("hello").tr_s(S("el"), S("*")))
- assert_equal("a".hash, "\u0101\u0101".tr_s("\u0101", "a").hash)
- assert_equal(true, "\u3041\u3041".tr("\u3041", "a").ascii_only?)
+ assert_equal("a".hash, S("\u0101\u0101").tr_s("\u0101", "a").hash)
+ assert_equal(true, S("\u3041\u3041").tr("\u3041", "a").ascii_only?)
end
def test_tr_s!
@@ -2345,6 +2424,7 @@ CODE
assert_equal(S("HELLO"), S("HELLO").upcase)
assert_equal(S("ABC HELLO 123"), S("abc HELLO 123").upcase)
assert_equal(S("H\0""ELLO"), S("H\0""ello").upcase)
+ assert_equal(S("\u{10574}"), S("\u{1059B}").upcase)
end
def test_upcase!
@@ -2418,6 +2498,8 @@ CODE
class S2 < String
end
def test_str_new4
+ return unless @cls == String
+
s = (0..54).to_a.join # length = 100
s2 = S2.new(s[10,90])
s3 = s2[10,80]
@@ -2426,7 +2508,7 @@ CODE
end
def test_rb_str_new4
- s = "a" * 100
+ s = S("a" * 100)
s2 = s[10,90]
assert_equal("a" * 90, s2)
s3 = s2[10,80]
@@ -2444,11 +2526,11 @@ CODE
end
def test_rb_str_to_str
- assert_equal("ab", "a" + StringLike.new("b"))
+ assert_equal("ab", S("a") + StringLike.new("b"))
end
def test_rb_str_shared_replace
- s = "a" * 100
+ s = S("a" * 100)
s.succ!
assert_equal("a" * 99 + "b", s)
s = ""
@@ -2472,12 +2554,12 @@ CODE
def test_times2
s1 = ''
100.times {|n|
- s2 = "a" * n
+ s2 = S("a") * n
assert_equal(s1, s2)
s1 << 'a'
}
- assert_raise(ArgumentError) { "foo" * (-1) }
+ assert_raise(ArgumentError) { S("foo") * (-1) }
end
def test_respond_to
@@ -2485,41 +2567,41 @@ CODE
def o.respond_to?(arg) [:to_str].include?(arg) ? nil : super end
def o.to_str() "" end
def o.==(other) "" == other end
- assert_equal(false, "" == o)
+ assert_equal(false, S("") == o)
end
def test_match_method
- assert_equal("bar", "foobarbaz".match(/bar/).to_s)
+ assert_equal("bar", S("foobarbaz").match(/bar/).to_s)
o = Regexp.new('foo')
def o.match(x, y, z); x + y + z; end
- assert_equal("foobarbaz", "foo".match(o, "bar", "baz"))
+ assert_equal("foobarbaz", S("foo").match(o, "bar", "baz"))
x = nil
- "foo".match(o, "bar", "baz") {|y| x = y }
+ S("foo").match(o, "bar", "baz") {|y| x = y }
assert_equal("foobarbaz", x)
- assert_raise(ArgumentError) { "foo".match }
+ assert_raise(ArgumentError) { S("foo").match }
end
def test_match_p_regexp
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
# to check if $~ is affected or not.
- assert_equal(true, "".match?(//))
+ assert_equal(true, S("").match?(//))
assert_equal(true, :abc.match?(/.../))
- assert_equal(true, 'abc'.match?(/b/))
- assert_equal(true, 'abc'.match?(/b/, 1))
- assert_equal(true, 'abc'.match?(/../, 1))
- assert_equal(true, 'abc'.match?(/../, -2))
- assert_equal(false, 'abc'.match?(/../, -4))
- assert_equal(false, 'abc'.match?(/../, 4))
- assert_equal(true, "\u3042xx".match?(/../, 1))
- assert_equal(false, "\u3042x".match?(/../, 1))
- assert_equal(true, ''.match?(/\z/))
- assert_equal(true, 'abc'.match?(/\z/))
- assert_equal(true, 'Ruby'.match?(/R.../))
- assert_equal(false, 'Ruby'.match?(/R.../, 1))
- assert_equal(false, 'Ruby'.match?(/P.../))
+ assert_equal(true, S('abc').match?(/b/))
+ assert_equal(true, S('abc').match?(/b/, 1))
+ assert_equal(true, S('abc').match?(/../, 1))
+ assert_equal(true, S('abc').match?(/../, -2))
+ assert_equal(false, S('abc').match?(/../, -4))
+ assert_equal(false, S('abc').match?(/../, 4))
+ assert_equal(true, S("\u3042xx").match?(/../, 1))
+ assert_equal(false, S("\u3042x").match?(/../, 1))
+ assert_equal(true, S('').match?(/\z/))
+ assert_equal(true, S('abc').match?(/\z/))
+ assert_equal(true, S('Ruby').match?(/R.../))
+ assert_equal(false, S('Ruby').match?(/R.../, 1))
+ assert_equal(false, S('Ruby').match?(/P.../))
assert_equal('backref', $&)
end
@@ -2527,21 +2609,21 @@ CODE
/backref/ =~ 'backref'
# must match here, but not in a separate method, e.g., assert_send,
# to check if $~ is affected or not.
- assert_equal(true, "".match?(''))
+ assert_equal(true, S("").match?(''))
assert_equal(true, :abc.match?('...'))
- assert_equal(true, 'abc'.match?('b'))
- assert_equal(true, 'abc'.match?('b', 1))
- assert_equal(true, 'abc'.match?('..', 1))
- assert_equal(true, 'abc'.match?('..', -2))
- assert_equal(false, 'abc'.match?('..', -4))
- assert_equal(false, 'abc'.match?('..', 4))
- assert_equal(true, "\u3042xx".match?('..', 1))
- assert_equal(false, "\u3042x".match?('..', 1))
- assert_equal(true, ''.match?('\z'))
- assert_equal(true, 'abc'.match?('\z'))
- assert_equal(true, 'Ruby'.match?('R...'))
- assert_equal(false, 'Ruby'.match?('R...', 1))
- assert_equal(false, 'Ruby'.match?('P...'))
+ assert_equal(true, S('abc').match?('b'))
+ assert_equal(true, S('abc').match?('b', 1))
+ assert_equal(true, S('abc').match?('..', 1))
+ assert_equal(true, S('abc').match?('..', -2))
+ assert_equal(false, S('abc').match?('..', -4))
+ assert_equal(false, S('abc').match?('..', 4))
+ assert_equal(true, S("\u3042xx").match?('..', 1))
+ assert_equal(false, S("\u3042x").match?('..', 1))
+ assert_equal(true, S('').match?('\z'))
+ assert_equal(true, S('abc').match?('\z'))
+ assert_equal(true, S('Ruby').match?('R...'))
+ assert_equal(false, S('Ruby').match?('R...', 1))
+ assert_equal(false, S('Ruby').match?('P...'))
assert_equal('backref', $&)
end
@@ -2561,18 +2643,23 @@ CODE
def test_inspect_nul
bug8290 = '[ruby-core:54458]'
- s = "\0" + "12"
+ s = S("\0") + "12"
assert_equal '"\u000012"', s.inspect, bug8290
- s = "\0".b + "12"
+ s = S("\0".b) + "12"
assert_equal '"\x0012"', s.inspect, bug8290
end
+ def test_inspect_next_line
+ bug16842 = '[ruby-core:98231]'
+ assert_equal '"\\u0085"', 0x85.chr(Encoding::UTF_8).inspect, bug16842
+ end
+
def test_partition
- assert_equal(%w(he l lo), "hello".partition(/l/))
- assert_equal(%w(he l lo), "hello".partition("l"))
- assert_raise(TypeError) { "hello".partition(1) }
+ assert_equal(%w(he l lo), S("hello").partition(/l/))
+ assert_equal(%w(he l lo), S("hello").partition("l"))
+ assert_raise(TypeError) { S("hello").partition(1) }
def (hyphen = Object.new).to_str; "-"; end
- assert_equal(%w(foo - bar), "foo-bar".partition(hyphen), '[ruby-core:23540]')
+ assert_equal(%w(foo - bar), S("foo-bar").partition(hyphen), '[ruby-core:23540]')
bug6206 = '[ruby-dev:45441]'
Encoding.list.each do |enc|
@@ -2582,24 +2669,24 @@ CODE
end
assert_equal(["\u30E6\u30FC\u30B6", "@", "\u30C9\u30E1.\u30A4\u30F3"],
- "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".partition(/[@.]/))
+ S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").partition(/[@.]/))
bug = '[ruby-core:82911]'
- hello = "hello"
+ hello = S("hello")
hello.partition("hi").map(&:upcase!)
assert_equal("hello", hello, bug)
- assert_equal(["", "", "foo"], "foo".partition(/^=*/))
+ assert_equal(["", "", "foo"], S("foo").partition(/^=*/))
assert_equal([S("ab"), S("c"), S("dbce")], S("abcdbce").partition(/b\Kc/))
end
def test_rpartition
- assert_equal(%w(hel l o), "hello".rpartition(/l/))
- assert_equal(%w(hel l o), "hello".rpartition("l"))
- assert_raise(TypeError) { "hello".rpartition(1) }
+ assert_equal(%w(hel l o), S("hello").rpartition(/l/))
+ assert_equal(%w(hel l o), S("hello").rpartition("l"))
+ assert_raise(TypeError) { S("hello").rpartition(1) }
def (hyphen = Object.new).to_str; "-"; end
- assert_equal(%w(foo - bar), "foo-bar".rpartition(hyphen), '[ruby-core:23540]')
+ assert_equal(%w(foo - bar), S("foo-bar").rpartition(hyphen), '[ruby-core:23540]')
bug6206 = '[ruby-dev:45441]'
Encoding.list.each do |enc|
@@ -2610,7 +2697,7 @@ CODE
bug8138 = '[ruby-dev:47183]'
assert_equal(["\u30E6\u30FC\u30B6@\u30C9\u30E1", ".", "\u30A4\u30F3"],
- "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".rpartition(/[@.]/), bug8138)
+ S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").rpartition(/[@.]/), bug8138)
bug = '[ruby-core:82911]'
hello = "hello"
@@ -2620,10 +2707,13 @@ CODE
assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/))
end
- def test_setter
+ def test_fs_setter
+ return unless @cls == String
+
assert_raise(TypeError) { $/ = 1 }
name = "\u{5206 884c}"
- assert_separately([], <<-"end;") # do
+ assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}")
+ do;
alias $#{name} $/
assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 }
end;
@@ -2656,16 +2746,19 @@ CODE
end
def test_gsub_enumerator
- assert_normal_exit %q{"abc".gsub(/./).next}, "[ruby-dev:34828]"
+ e = S("abc").gsub(/./)
+ assert_equal("a", e.next, "[ruby-dev:34828]")
+ assert_equal("b", e.next)
+ assert_equal("c", e.next)
end
def test_clear_nonasciicompat
- assert_equal("", "\u3042".encode("ISO-2022-JP").clear)
+ assert_equal("", S("\u3042".encode("ISO-2022-JP")).clear)
end
def test_try_convert
- assert_equal(nil, String.try_convert(1))
- assert_equal("foo", String.try_convert("foo"))
+ assert_equal(nil, @cls.try_convert(1))
+ assert_equal("foo", @cls.try_convert("foo"))
end
def test_substr_negative_begin
@@ -2674,55 +2767,55 @@ CODE
=begin
def test_compare_different_encoding_string
- s1 = "\xff".force_encoding("UTF-8")
- s2 = "\xff".force_encoding("ISO-2022-JP")
+ s1 = S("\xff".force_encoding("UTF-8"))
+ s2 = S("\xff".force_encoding("ISO-2022-JP"))
assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort)
end
=end
def test_casecmp
- assert_equal(0, "FoO".casecmp("fOO"))
- assert_equal(1, "FoO".casecmp("BaR"))
- assert_equal(-1, "baR".casecmp("FoO"))
- assert_equal(1, "\u3042B".casecmp("\u3042a"))
- assert_equal(-1, "foo".casecmp("foo\0"))
+ assert_equal(0, S("FoO").casecmp("fOO"))
+ assert_equal(1, S("FoO").casecmp("BaR"))
+ assert_equal(-1, S("baR").casecmp("FoO"))
+ assert_equal(1, S("\u3042B").casecmp("\u3042a"))
+ assert_equal(-1, S("foo").casecmp("foo\0"))
- assert_nil("foo".casecmp(:foo))
- assert_nil("foo".casecmp(Object.new))
+ assert_nil(S("foo").casecmp(:foo))
+ assert_nil(S("foo").casecmp(Object.new))
o = Object.new
def o.to_str; "fOO"; end
- assert_equal(0, "FoO".casecmp(o))
+ assert_equal(0, S("FoO").casecmp(o))
end
def test_casecmp?
- assert_equal(true, 'FoO'.casecmp?('fOO'))
- assert_equal(false, 'FoO'.casecmp?('BaR'))
- assert_equal(false, 'baR'.casecmp?('FoO'))
- assert_equal(true, 'äöü'.casecmp?('ÄÖÜ'))
- assert_equal(false, "foo".casecmp?("foo\0"))
+ assert_equal(true, S('FoO').casecmp?('fOO'))
+ assert_equal(false, S('FoO').casecmp?('BaR'))
+ assert_equal(false, S('baR').casecmp?('FoO'))
+ assert_equal(true, S('äöü').casecmp?('ÄÖÜ'))
+ assert_equal(false, S("foo").casecmp?("foo\0"))
- assert_nil("foo".casecmp?(:foo))
- assert_nil("foo".casecmp?(Object.new))
+ assert_nil(S("foo").casecmp?(:foo))
+ assert_nil(S("foo").casecmp?(Object.new))
o = Object.new
def o.to_str; "fOO"; end
- assert_equal(true, "FoO".casecmp?(o))
+ assert_equal(true, S("FoO").casecmp?(o))
end
def test_upcase2
- assert_equal("\u3042AB", "\u3042aB".upcase)
+ assert_equal("\u3042AB", S("\u3042aB").upcase)
end
def test_downcase2
- assert_equal("\u3042ab", "\u3042aB".downcase)
+ assert_equal("\u3042ab", S("\u3042aB").downcase)
end
def test_rstrip
- assert_equal(" hello", " hello ".rstrip)
- assert_equal("\u3042", "\u3042 ".rstrip)
- assert_equal("\u3042", "\u3042\u0000".rstrip)
- assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip }
+ assert_equal(" hello", S(" hello ").rstrip)
+ assert_equal("\u3042", S("\u3042 ").rstrip)
+ assert_equal("\u3042", S("\u3042\u0000").rstrip)
+ assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip }
end
def test_rstrip_bang
@@ -2746,13 +2839,18 @@ CODE
assert_equal("\u3042", s5.rstrip!)
assert_equal("\u3042", s5)
- assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc\x80 ".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("abc \x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S("\x80".force_encoding('UTF-8')).rstrip! }
+ assert_raise(Encoding::CompatibilityError) { S(" \x80 ".force_encoding('UTF-8')).rstrip! }
end
def test_lstrip
- assert_equal("hello ", " hello ".lstrip)
- assert_equal("\u3042", " \u3042".lstrip)
- assert_equal("hello ", "\x00hello ".lstrip)
+ assert_equal("hello ", S(" hello ").lstrip)
+ assert_equal("\u3042", S(" \u3042").lstrip)
+ assert_equal("hello ", S("\x00hello ").lstrip)
end
def test_lstrip_bang
@@ -2779,9 +2877,9 @@ CODE
end
def test_delete_prefix
- assert_raise(TypeError) { 'hello'.delete_prefix(nil) }
- assert_raise(TypeError) { 'hello'.delete_prefix(1) }
- assert_raise(TypeError) { 'hello'.delete_prefix(/hel/) }
+ assert_raise(TypeError) { S('hello').delete_prefix(nil) }
+ assert_raise(TypeError) { S('hello').delete_prefix(1) }
+ assert_raise(TypeError) { S('hello').delete_prefix(/hel/) }
s = S("hello")
assert_equal("lo", s.delete_prefix('hel'))
@@ -2825,9 +2923,9 @@ CODE
end
def test_delete_prefix_bang
- assert_raise(TypeError) { 'hello'.delete_prefix!(nil) }
- assert_raise(TypeError) { 'hello'.delete_prefix!(1) }
- assert_raise(TypeError) { 'hello'.delete_prefix!(/hel/) }
+ assert_raise(TypeError) { S('hello').delete_prefix!(nil) }
+ assert_raise(TypeError) { S('hello').delete_prefix!(1) }
+ assert_raise(TypeError) { S('hello').delete_prefix!(/hel/) }
s = S("hello")
assert_equal("lo", s.delete_prefix!('hel'))
@@ -2878,9 +2976,9 @@ CODE
end
def test_delete_suffix
- assert_raise(TypeError) { 'hello'.delete_suffix(nil) }
- assert_raise(TypeError) { 'hello'.delete_suffix(1) }
- assert_raise(TypeError) { 'hello'.delete_suffix(/hel/) }
+ assert_raise(TypeError) { S('hello').delete_suffix(nil) }
+ assert_raise(TypeError) { S('hello').delete_suffix(1) }
+ assert_raise(TypeError) { S('hello').delete_suffix(/hel/) }
s = S("hello")
assert_equal("hel", s.delete_suffix('lo'))
@@ -2929,9 +3027,9 @@ CODE
end
def test_delete_suffix_bang
- assert_raise(TypeError) { 'hello'.delete_suffix!(nil) }
- assert_raise(TypeError) { 'hello'.delete_suffix!(1) }
- assert_raise(TypeError) { 'hello'.delete_suffix!(/hel/) }
+ assert_raise(TypeError) { S('hello').delete_suffix!(nil) }
+ assert_raise(TypeError) { S('hello').delete_suffix!(1) }
+ assert_raise(TypeError) { S('hello').delete_suffix!(/hel/) }
s = S("hello").freeze
assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!('lo')}
@@ -3020,7 +3118,7 @@ CODE
end
def test_shared_force_encoding
- s = "\u{3066}\u{3059}\u{3068}".gsub(//, '')
+ s = S("\u{3066}\u{3059}\u{3068}").gsub(//, '')
h = {}
h[s] = nil
k = h.keys[0]
@@ -3035,16 +3133,16 @@ CODE
def test_ascii_incomat_inspect
bug4081 = '[ruby-core:33283]'
WIDE_ENCODINGS.each do |e|
- assert_equal('"abc"', "abc".encode(e).inspect)
- assert_equal('"\\u3042\\u3044\\u3046"', "\u3042\u3044\u3046".encode(e).inspect)
- assert_equal('"ab\\"c"', "ab\"c".encode(e).inspect, bug4081)
+ assert_equal('"abc"', S("abc".encode(e)).inspect)
+ assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect)
+ assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081)
end
begin
verbose, $VERBOSE = $VERBOSE, nil
ext = Encoding.default_external
Encoding.default_external = "us-ascii"
$VERBOSE = verbose
- i = "abc\"\\".force_encoding("utf-8").inspect
+ i = S("abc\"\\".force_encoding("utf-8")).inspect
ensure
$VERBOSE = nil
Encoding.default_external = ext
@@ -3055,11 +3153,11 @@ CODE
def test_dummy_inspect
assert_equal('"\e\x24\x42\x22\x4C\x22\x68\e\x28\x42"',
- "\u{ffe2}\u{2235}".encode("cp50220").inspect)
+ S("\u{ffe2}\u{2235}".encode("cp50220")).inspect)
end
def test_prepend
- assert_equal(S("hello world!"), "!".prepend("hello ", "world"))
+ assert_equal(S("hello world!"), S("!").prepend("hello ", "world"))
b = S("ue")
assert_equal(S("ueueue"), b.prepend(b, b))
@@ -3067,7 +3165,7 @@ CODE
def foo.to_str
"b"
end
- assert_equal(S("ba"), "a".prepend(foo))
+ assert_equal(S("ba"), S("a").prepend(foo))
a = S("world")
b = S("hello ")
@@ -3081,34 +3179,34 @@ CODE
end
def test_byteslice
- assert_equal("h", "hello".byteslice(0))
- assert_equal(nil, "hello".byteslice(5))
- assert_equal("o", "hello".byteslice(-1))
- assert_equal(nil, "hello".byteslice(-6))
-
- assert_equal("", "hello".byteslice(0, 0))
- assert_equal("hello", "hello".byteslice(0, 6))
- assert_equal("hello", "hello".byteslice(0, 6))
- assert_equal("", "hello".byteslice(5, 1))
- assert_equal("o", "hello".byteslice(-1, 6))
- assert_equal(nil, "hello".byteslice(-6, 1))
- assert_equal(nil, "hello".byteslice(0, -1))
-
- assert_equal("h", "hello".byteslice(0..0))
- assert_equal("", "hello".byteslice(5..0))
- assert_equal("o", "hello".byteslice(4..5))
- assert_equal(nil, "hello".byteslice(6..0))
- assert_equal("", "hello".byteslice(-1..0))
- assert_equal("llo", "hello".byteslice(-3..5))
-
- assert_equal(u("\x81"), "\u3042".byteslice(1))
- assert_equal(u("\x81\x82"), "\u3042".byteslice(1, 2))
- assert_equal(u("\x81\x82"), "\u3042".byteslice(1..2))
-
- assert_equal(u("\x82")+("\u3042"*9), ("\u3042"*10).byteslice(2, 28))
+ assert_equal("h", S("hello").byteslice(0))
+ assert_equal(nil, S("hello").byteslice(5))
+ assert_equal("o", S("hello").byteslice(-1))
+ assert_equal(nil, S("hello").byteslice(-6))
+
+ assert_equal("", S("hello").byteslice(0, 0))
+ assert_equal("hello", S("hello").byteslice(0, 6))
+ assert_equal("hello", S("hello").byteslice(0, 6))
+ assert_equal("", S("hello").byteslice(5, 1))
+ assert_equal("o", S("hello").byteslice(-1, 6))
+ assert_equal(nil, S("hello").byteslice(-6, 1))
+ assert_equal(nil, S("hello").byteslice(0, -1))
+
+ assert_equal("h", S("hello").byteslice(0..0))
+ assert_equal("", S("hello").byteslice(5..0))
+ assert_equal("o", S("hello").byteslice(4..5))
+ assert_equal(nil, S("hello").byteslice(6..0))
+ assert_equal("", S("hello").byteslice(-1..0))
+ assert_equal("llo", S("hello").byteslice(-3..5))
+
+ assert_equal(u("\x81"), S("\u3042").byteslice(1))
+ assert_equal(u("\x81\x82"), S("\u3042").byteslice(1, 2))
+ assert_equal(u("\x81\x82"), S("\u3042").byteslice(1..2))
+
+ assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28))
bug7954 = '[ruby-dev:47108]'
- assert_equal(false, "\u3042".byteslice(0, 2).valid_encoding?, bug7954)
+ assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954)
assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954)
end
@@ -3123,6 +3221,8 @@ CODE
end
def test_eq_tilde_can_be_overridden
+ return unless @cls == String
+
assert_separately([], <<-RUBY)
class String
undef =~
@@ -3140,7 +3240,7 @@ CODE
end
def test_regexp_match_subclass
- s = Bug9581.new("abc")
+ s = Bug9581.new(S("abc"))
r = /abc/
assert_equal(:foo, s =~ r)
assert_equal(:foo, s.send(:=~, r))
@@ -3150,6 +3250,7 @@ CODE
def test_LSHIFT_neary_long_max
return unless @cls == String
+
assert_ruby_status([], <<-'end;', '[ruby-core:61886] [Bug #9709]', timeout: 20)
begin
a = "a" * 0x4000_0000
@@ -3161,6 +3262,8 @@ CODE
# enable only when string size range is smaller than memory space
def test_uplus_minus
+ return unless @cls == String
+
str = "foo"
assert_not_predicate(str, :frozen?)
assert_not_predicate(+str, :frozen?)
@@ -3182,6 +3285,8 @@ CODE
end
def test_uminus_frozen
+ return unless @cls == String
+
# embedded
str1 = ("foobar" * 3).freeze
str2 = ("foobar" * 3).freeze
@@ -3198,40 +3303,195 @@ CODE
end
def test_uminus_no_freeze_not_bare
- str = @cls.new("foo")
+ str = S("foo")
assert_instance_of(@cls, -str)
assert_equal(false, str.frozen?)
- str = @cls.new("foo")
+ str = S("foo")
str.instance_variable_set(:@iv, 1)
assert_instance_of(@cls, -str)
assert_equal(false, str.frozen?)
assert_equal(1, str.instance_variable_get(:@iv))
- str = @cls.new("foo")
+ str = S("foo")
assert_instance_of(@cls, -str)
assert_equal(false, str.frozen?)
end
def test_ord
- assert_equal(97, "a".ord)
- assert_equal(97, "abc".ord)
- assert_equal(0x3042, "\u3042\u3043".ord)
- assert_raise(ArgumentError) { "".ord }
+ assert_equal(97, S("a").ord)
+ assert_equal(97, S("abc").ord)
+ assert_equal(0x3042, S("\u3042\u3043").ord)
+ assert_raise(ArgumentError) { S("").ord }
end
def test_chr
- assert_equal("a", "abcde".chr)
- assert_equal("a", "a".chr)
- assert_equal("\u3042", "\u3042\u3043".chr)
- assert_equal('', ''.chr)
+ assert_equal("a", S("abcde").chr)
+ assert_equal("a", S("a").chr)
+ assert_equal("\u3042", S("\u3042\u3043").chr)
+ assert_equal('', S('').chr)
end
def test_substr_code_range
- data = "\xff" + "a"*200
+ data = S("\xff" + "a"*200)
assert_not_predicate(data, :valid_encoding?)
assert_predicate(data[100..-1], :valid_encoding?)
end
+
+ def test_byteindex
+ assert_equal(0, S("hello").byteindex(?h))
+ assert_equal(1, S("hello").byteindex(S("ell")))
+ assert_equal(2, S("hello").byteindex(/ll./))
+
+ assert_equal(3, S("hello").byteindex(?l, 3))
+ assert_equal(3, S("hello").byteindex(S("l"), 3))
+ assert_equal(3, S("hello").byteindex(/l./, 3))
+
+ assert_nil(S("hello").byteindex(?z, 3))
+ assert_nil(S("hello").byteindex(S("z"), 3))
+ assert_nil(S("hello").byteindex(/z./, 3))
+
+ assert_nil(S("hello").byteindex(?z))
+ assert_nil(S("hello").byteindex(S("z")))
+ assert_nil(S("hello").byteindex(/z./))
+
+ assert_equal(0, S("").byteindex(S("")))
+ assert_equal(0, S("").byteindex(//))
+ assert_nil(S("").byteindex(S("hello")))
+ assert_nil(S("").byteindex(/hello/))
+ assert_equal(0, S("hello").byteindex(S("")))
+ assert_equal(0, S("hello").byteindex(//))
+
+ s = S("long") * 1000 << "x"
+ assert_nil(s.byteindex(S("y")))
+ assert_equal(4 * 1000, s.byteindex(S("x")))
+ s << "yx"
+ assert_equal(4 * 1000, s.byteindex(S("x")))
+ assert_equal(4 * 1000, s.byteindex(S("xyx")))
+
+ o = Object.new
+ def o.to_str; "bar"; end
+ assert_equal(3, S("foobarbarbaz").byteindex(o))
+ assert_raise(TypeError) { S("foo").byteindex(Object.new) }
+
+ assert_nil(S("foo").byteindex(//, -100))
+ assert_nil($~)
+
+ assert_equal(2, S("abcdbce").byteindex(/b\Kc/))
+
+ assert_equal(0, S("こんにちは").byteindex(?こ))
+ assert_equal(3, S("こんにちは").byteindex(S("んにち")))
+ assert_equal(6, S("こんにちは").byteindex(/にち./))
+
+ assert_equal(0, S("にんにちは").byteindex(?に, 0))
+ assert_raise(IndexError) { S("にんにちは").byteindex(?に, 1) }
+ assert_raise(IndexError) { S("にんにちは").byteindex(?に, 5) }
+ assert_equal(6, S("にんにちは").byteindex(?に, 6))
+ assert_equal(6, S("にんにちは").byteindex(S("に"), 6))
+ assert_equal(6, S("にんにちは").byteindex(/に./, 6))
+ assert_raise(IndexError) { S("にんにちは").byteindex(?に, 7) }
+
+ s = S("foobarbarbaz")
+ assert !1000.times.any? {s.byteindex("", 100_000_000)}
+ end
+
+ def test_byterindex
+ assert_equal(3, S("hello").byterindex(?l))
+ assert_equal(6, S("ell, hello").byterindex(S("ell")))
+ assert_equal(7, S("ell, hello").byterindex(/ll./))
+
+ assert_equal(3, S("hello,lo").byterindex(?l, 3))
+ assert_equal(3, S("hello,lo").byterindex(S("l"), 3))
+ assert_equal(3, S("hello,lo").byterindex(/l./, 3))
+
+ assert_nil(S("hello").byterindex(?z, 3))
+ assert_nil(S("hello").byterindex(S("z"), 3))
+ assert_nil(S("hello").byterindex(/z./, 3))
+
+ assert_nil(S("hello").byterindex(?z))
+ assert_nil(S("hello").byterindex(S("z")))
+ assert_nil(S("hello").byterindex(/z./))
+
+ assert_equal(5, S("hello").byterindex(S("")))
+ assert_equal(5, S("hello").byterindex(S(""), 5))
+ assert_equal(4, S("hello").byterindex(S(""), 4))
+ assert_equal(0, S("hello").byterindex(S(""), 0))
+
+ o = Object.new
+ def o.to_str; "bar"; end
+ assert_equal(6, S("foobarbarbaz").byterindex(o))
+ assert_raise(TypeError) { S("foo").byterindex(Object.new) }
+
+ assert_nil(S("foo").byterindex(//, -100))
+ assert_nil($~)
+
+ assert_equal(3, S("foo").byterindex(//))
+ assert_equal([3, 3], $~.offset(0))
+
+ assert_equal(5, S("abcdbce").byterindex(/b\Kc/))
+
+ assert_equal(6, S("こんにちは").byterindex(?に))
+ assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは")))
+ assert_equal(18, S("にちは、こんにちは").byterindex(/にち./))
+
+ assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 19) }
+ assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -2) }
+ assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), 18))
+ assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), -3))
+ assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 17) }
+ assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -4) }
+ assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 1) }
+ assert_equal(0, S("にちは、こんにちは").byterindex(S("にちは"), 0))
+
+ assert_equal(0, S("こんにちは").byterindex(S("こんにちは")))
+ assert_nil(S("こんにち").byterindex(S("こんにちは")))
+ assert_nil(S("こ").byterindex(S("こんにちは")))
+ assert_nil(S("").byterindex(S("こんにちは")))
+ end
+
+ def test_bytesplice
+ assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "xxx")
+ assert_bytesplice_result("xxxhello", S("hello"), -5, 0, "xxx")
+ assert_bytesplice_result("xxxhello", S("hello"), 0, 0, "xxx")
+ assert_bytesplice_result("xxxello", S("hello"), 0, 1, "xxx")
+ assert_bytesplice_result("xxx", S("hello"), 0, 5, "xxx")
+ assert_bytesplice_result("xxx", S("hello"), 0, 6, "xxx")
+
+ assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "xxx")
+ assert_bytesplice_result("xxxhello", S("hello"), -5...-5, "xxx")
+ assert_bytesplice_result("xxxhello", S("hello"), 0...0, "xxx")
+ assert_bytesplice_result("xxxello", S("hello"), 0..0, "xxx")
+ assert_bytesplice_result("xxxello", S("hello"), 0...1, "xxx")
+ assert_bytesplice_result("xxxllo", S("hello"), 0..1, "xxx")
+ assert_bytesplice_result("xxx", S("hello"), 0..-1, "xxx")
+ assert_bytesplice_result("xxx", S("hello"), 0...5, "xxx")
+ assert_bytesplice_result("xxx", S("hello"), 0...6, "xxx")
+
+ assert_bytesplice_raise(TypeError, S("hello"), 0, "xxx")
+
+ assert_bytesplice_raise(IndexError, S("こんにちは"), -16, 0, "xxx")
+ assert_bytesplice_result("xxxこんにちは", S("こんにちは"), -15, 0, "xxx")
+ assert_bytesplice_result("xxxこんにちは", S("こんにちは"), 0, 0, "xxx")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 1, 0, "xxx")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 1, "xxx")
+ assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 2, "xxx")
+ assert_bytesplice_result("xxxんにちは", S("こんにちは"), 0, 3, "xxx")
+ assert_bytesplice_result("こんにちはxxx", S("こんにちは"), 15, 0, "xxx")
+
+ assert_bytesplice_result("", S(""), 0, 0, "")
+ assert_bytesplice_result("xxx", S(""), 0, 0, "xxx")
+ end
+
+ private
+
+ def assert_bytesplice_result(expected, s, *args)
+ assert_equal(expected, s.send(:bytesplice, *args))
+ assert_equal(expected, s)
+ end
+
+ def assert_bytesplice_raise(e, s, *args)
+ assert_raise(e) { s.send(:bytesplice, *args) }
+ end
end
class TestString2 < TestString
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index 19577266c7..8a5706b18c 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -100,8 +100,9 @@ module TestStruct
assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members)
end
- def test_struct_new_with_empty_hash
- assert_equal({:a=>1}, Struct.new(:a, {}).new({:a=>1}).a)
+ def test_struct_new_with_hash
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {})}
+ assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {name: "b"})}
end
def test_struct_new_with_keyword_init
@@ -362,9 +363,8 @@ module TestStruct
end
def test_keyword_args_warning
- warning = /warning: Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3\.2\./
- assert_warn(warning) { assert_equal({a: 1}, @Struct.new(:a).new(a: 1).a) }
- assert_warn(warning) { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new(a: 1).a) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a).new(a: 1).a) }
+ assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: nil).new(a: 1).a) }
assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) }
assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) }
assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) }
@@ -489,6 +489,57 @@ module TestStruct
}
end
+ def test_public_send
+ klass = @Struct.new(:a)
+ x = klass.new(1)
+ assert_equal(1, x.public_send("a"))
+ assert_equal(42, x.public_send("a=", 42))
+ assert_equal(42, x.public_send("a"))
+ end
+
+ def test_arity
+ klass = @Struct.new(:a)
+ assert_equal 0, klass.instance_method(:a).arity
+ assert_equal 1, klass.instance_method(:a=).arity
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal 1, klass.instance_method(:b=).arity
+ assert_equal 1, klass.instance_method(:c=).arity
+ end
+
+ def test_parameters
+ klass = @Struct.new(:a)
+ assert_equal [], klass.instance_method(:a).parameters
+ # NOTE: :_ may not be a spec.
+ assert_equal [[:req, :_]], klass.instance_method(:a=).parameters
+
+ klass.module_eval do
+ define_method(:b=, instance_method(:a=))
+ alias c= a=
+ end
+
+ assert_equal [[:req, :_]], klass.instance_method(:b=).parameters
+ assert_equal [[:req, :_]], klass.instance_method(:c=).parameters
+ end
+
+ def test_named_structs_are_not_rooted
+ # [Bug #20311]
+ assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true)
+ code = proc do
+ Struct.new("A")
+ Struct.send(:remove_const, :A)
+ end
+
+ 1_000.times(&code)
+ PREP
+ 50_000.times(&code)
+ CODE
+ end
+
class TopStruct < Test::Unit::TestCase
include TestStruct
diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb
index d94f4679d3..6a575b88c5 100644
--- a/test/ruby/test_super.rb
+++ b/test/ruby/test_super.rb
@@ -521,6 +521,43 @@ class TestSuper < Test::Unit::TestCase
assert_equal(%w[B A], result, bug9721)
end
+ # [Bug #18329]
+ def test_super_missing_prepended_module
+ a = Module.new do
+ def probe(*methods)
+ prepend(probing_module(methods))
+ end
+
+ def probing_module(methods)
+ Module.new do
+ methods.each do |method|
+ define_method(method) do |*args, **kwargs, &block|
+ super(*args, **kwargs, &block)
+ end
+ end
+ end
+ end
+ end
+
+ b = Class.new do
+ extend a
+
+ probe :danger!, :missing
+
+ def danger!; end
+ end
+
+ o = b.new
+ o.danger!
+ begin
+ original_gc_stress = GC.stress
+ GC.stress = true
+ 2.times { o.missing rescue NoMethodError }
+ ensure
+ GC.stress = original_gc_stress
+ end
+ end
+
def test_from_eval
bug10263 = '[ruby-core:65122] [Bug #10263a]'
a = Class.new do
diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb
index f7f17b8d67..1d2a18d734 100644
--- a/test/ruby/test_symbol.rb
+++ b/test/ruby/test_symbol.rb
@@ -36,6 +36,19 @@ class TestSymbol < Test::Unit::TestCase
assert_eval_inspected(:"@@1", false)
assert_eval_inspected(:"@", false)
assert_eval_inspected(:"@@", false)
+ assert_eval_inspected(:"[]=")
+ assert_eval_inspected(:"[][]", false)
+ assert_eval_inspected(:"[][]=", false)
+ assert_eval_inspected(:"@=", false)
+ assert_eval_inspected(:"@@=", false)
+ assert_eval_inspected(:"@x=", false)
+ assert_eval_inspected(:"@@x=", false)
+ assert_eval_inspected(:"$$=", false)
+ assert_eval_inspected(:"$==", false)
+ assert_eval_inspected(:"$x=", false)
+ assert_eval_inspected(:"$$$=", false)
+ assert_eval_inspected(:"foo?=", false)
+ assert_eval_inspected(:"foo!=", false)
end
def assert_inspect_evaled(n)
diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb
index fc40a7f21a..7553cd74a6 100644
--- a/test/ruby/test_syntax.rb
+++ b/test/ruby/test_syntax.rb
@@ -66,6 +66,119 @@ class TestSyntax < Test::Unit::TestCase
f&.close!
end
+ def test_script_lines_encoding
+ require 'tmpdir'
+ Dir.mktmpdir do |dir|
+ File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n")
+ assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name",
+ %w"EUC-JP", /-K is specified/, chdir: dir)
+ end
+ end
+
+ def test_anonymous_block_forwarding
+ assert_syntax_error("def b; c(&); end", /no anonymous block parameter/)
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(&); c(&) end
+ def c(&); yield 1 end
+ a = nil
+ b{|c| a = c}
+ assert_equal(1, a)
+
+ def inner
+ yield
+ end
+
+ def block_only(&)
+ inner(&)
+ end
+ assert_equal(1, block_only{1})
+
+ def pos(arg1, &)
+ inner(&)
+ end
+ assert_equal(2, pos(nil){2})
+
+ def pos_kwrest(arg1, **kw, &)
+ inner(&)
+ end
+ assert_equal(3, pos_kwrest(nil){3})
+
+ def no_kw(arg1, **nil, &)
+ inner(&)
+ end
+ assert_equal(4, no_kw(nil){4})
+
+ def rest_kw(*a, kwarg: 1, &)
+ inner(&)
+ end
+ assert_equal(5, rest_kw{5})
+
+ def kw(kwarg:1, &)
+ inner(&)
+ end
+ assert_equal(6, kw{6})
+
+ def pos_kw_kwrest(arg1, kwarg:1, **kw, &)
+ inner(&)
+ end
+ assert_equal(7, pos_kw_kwrest(nil){7})
+
+ def pos_rkw(arg1, kwarg1:, &)
+ inner(&)
+ end
+ assert_equal(8, pos_rkw(nil, kwarg1: nil){8})
+
+ def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &)
+ inner(&)
+ end
+ assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9})
+
+ def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &)
+ inner(&)
+ end
+ assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10})
+ end;
+ end
+
+ def test_anonymous_rest_forwarding
+ assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/)
+ assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/)
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(*); c(*) end
+ def c(*a); a end
+ def d(*); b(*, *) end
+ assert_equal([1, 2], b(1, 2))
+ assert_equal([1, 2, 1, 2], d(1, 2))
+ end;
+ end
+
+ def test_anonymous_keyword_rest_forwarding
+ assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/)
+ assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
+ begin;
+ def b(**); c(**) end
+ def c(**kw); kw end
+ def d(**); b(k: 1, **) end
+ def e(**); b(**, k: 1) end
+ def f(a: nil, **); b(**) end
+ assert_equal({a: 1, k: 3}, b(a: 1, k: 3))
+ assert_equal({a: 1, k: 3}, d(a: 1, k: 3))
+ assert_equal({a: 1, k: 1}, e(a: 1, k: 3))
+ assert_equal({k: 3}, f(a: 1, k: 3))
+ end;
+ end
+
+ def test_argument_forwarding_with_anon_rest_kwrest_and_block
+ assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./)
+ assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/)
+ assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/)
+ assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/)
+ end
+
def test_newline_in_block_parameters
bug = '[ruby-dev:45292]'
["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params|
@@ -107,6 +220,7 @@ class TestSyntax < Test::Unit::TestCase
def test_array_kwsplat_hash
kw = {}
h = {a: 1}
+ a = []
assert_equal([], [**{}])
assert_equal([], [**kw])
assert_equal([h], [**h])
@@ -121,6 +235,20 @@ class TestSyntax < Test::Unit::TestCase
assert_equal([1, kw], [1, kw])
assert_equal([1, h], [1, h])
+ assert_equal([], [*a, **{}])
+ assert_equal([], [*a, **kw])
+ assert_equal([h], [*a, **h])
+ assert_equal([{}], [*a, {}])
+ assert_equal([kw], [*a, kw])
+ assert_equal([h], [*a, h])
+
+ assert_equal([1], [1, *a, **{}])
+ assert_equal([1], [1, *a, **kw])
+ assert_equal([1, h], [1, *a, **h])
+ assert_equal([1, {}], [1, *a, {}])
+ assert_equal([1, kw], [1, *a, kw])
+ assert_equal([1, h], [1, *a, h])
+
assert_equal([], [**kw, **kw])
assert_equal([], [**kw, **{}, **kw])
assert_equal([1], [1, **kw, **{}, **kw])
@@ -1060,6 +1188,20 @@ eom
assert_syntax_error("a&.x,=0", /multiple assignment destination/)
end
+ def test_safe_call_in_for_variable
+ assert_valid_syntax("for x&.bar in []; end")
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ foo = nil
+ for foo&.bar in [1]; end
+ assert_nil(foo)
+
+ foo = Struct.new(:bar).new
+ for foo&.bar in [1]; end
+ assert_equal(1, foo.bar)
+ end;
+ end
+
def test_no_warning_logop_literal
assert_warning("") do
eval("true||raise;nil")
@@ -1256,6 +1398,8 @@ eom
begin raise; ensure return; end and self
nil&defined?0--begin e=no_method_error(); return; 0;end
return puts('ignored') #=> ignored
+ BEGIN {return}
+ END {return if false}
end;
.split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]}
failed = proc do |n, s|
@@ -1293,6 +1437,10 @@ eom
assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/)
end
+ def test_return_in_END
+ assert_normal_exit('END {return}')
+ end
+
def test_syntax_error_in_rescue
bug12613 = '[ruby-core:76531] [Bug #12613]'
assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613)
@@ -1557,6 +1705,9 @@ eom
assert_raise(NameError) {eval("_1")},
]
}
+
+ assert_valid_syntax("proc {def foo(_);end;_1}")
+ assert_valid_syntax("p { [_1 **2] }")
end
def test_value_expr_in_condition
@@ -1575,6 +1726,15 @@ eom
def test_argument_forwarding
assert_valid_syntax('def foo(...) bar(...) end')
assert_valid_syntax('def foo(...) end')
+ assert_valid_syntax('def foo(a, ...) bar(...) end')
+ assert_valid_syntax("def foo ...\n bar(...)\nend")
+ assert_valid_syntax("def foo a, ...\n bar(...)\nend")
+ assert_valid_syntax("def foo b = 1, ...\n bar(...)\nend")
+ assert_valid_syntax("def foo ...; bar(...); end")
+ assert_valid_syntax("def foo a, ...; bar(...); end")
+ assert_valid_syntax("def foo b = 1, ...; bar(...); end")
+ assert_valid_syntax("(def foo ...\n bar(...)\nend)")
+ assert_valid_syntax("(def foo ...; bar(...); end)")
assert_valid_syntax('def ==(...) end')
assert_valid_syntax('def [](...) end')
assert_valid_syntax('def nil(...) end')
@@ -1595,6 +1755,8 @@ eom
assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/)
assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/)
assert_syntax_error('def foo(...) defined?(...); end', /unexpected/)
+ assert_syntax_error('def foo(*rest, ...) end', '... after rest argument')
+ assert_syntax_error('def foo(*, ...) end', '... after rest argument')
obj1 = Object.new
def obj1.bar(*args, **kws, &block)
@@ -1604,7 +1766,11 @@ eom
[args, kws]
end
end
+ obj4 = obj1.clone
+ obj5 = obj1.clone
obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
+ obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__)
+ obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__)
klass = Class.new {
def foo(*args, **kws, &block)
@@ -1633,7 +1799,7 @@ eom
end
obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__)
- [obj1, obj2, obj3].each do |obj|
+ [obj1, obj2, obj3, obj4, obj5].each do |obj|
assert_warning('') {
assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x})
}
@@ -1766,6 +1932,44 @@ eom
assert_equal [[4, 1, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1)
assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1)
assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval("def foo a, ...\n bar(a, ...)\n"" end", __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4)
+ assert_equal [[4, 2], {}], obj.foo(4, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ obj.singleton_class.send(:remove_method, :foo)
+ obj.instance_eval("def foo a, ...; bar(a, ...); end", __FILE__, __LINE__)
+ assert_equal [[4], {}], obj.foo(4)
+ assert_equal [[4, 2], {}], obj.foo(4, 2)
+ assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3)
+ assert_equal [[4], {a: 1}], obj.foo(4, a: 1)
+ assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1)
+ assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]}
+
+ exp = eval("-> (a: nil) {a...1}")
+ assert_equal 0...1, exp.call(a: 0)
+ end
+
+ def test_class_module_Object_ancestors
+ assert_separately([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ class Bug18832; end
+ RUBY
+ assert_separately([], <<-RUBY)
+ m = Module.new
+ m::Bug18832 = 1
+ include m
+ module Bug18832; end
+ RUBY
end
def test_cdhash
diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 7b37aeb202..85fbde2de8 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -29,13 +29,19 @@ class TestThread < Test::Unit::TestCase
end
def test_inspect
+ m = Thread::Mutex.new
+ m.lock
line = __LINE__+1
- th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{}
+ th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do
+ m.synchronize {}
+ end
+ Thread.pass until th.stop?
s = th.inspect
assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:")
assert_include(s, " #{__FILE__}:#{line} ")
assert_equal(s, th.to_s)
ensure
+ m.unlock
th.join
end
@@ -317,7 +323,7 @@ class TestThread < Test::Unit::TestCase
s += 1
end
Thread.pass until t.stop?
- sleep 1 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
+ sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait
assert_equal(1, s)
t.wakeup
Thread.pass while t.alive?
@@ -500,7 +506,7 @@ class TestThread < Test::Unit::TestCase
def test_ignore_deadlock
if /mswin|mingw/ =~ RUBY_PLATFORM
- skip "can't trap a signal from another process on Windows"
+ omit "can't trap a signal from another process on Windows"
end
assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil)
p Thread.ignore_deadlock
@@ -731,7 +737,7 @@ class TestThread < Test::Unit::TestCase
end
def test_no_valid_cfp
- skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
+ omit 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE)
bug5083 = '[ruby-dev:44208]'
assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083)
assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083)
@@ -966,6 +972,8 @@ _eom
end
def test_thread_timer_and_interrupt
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5757 = '[ruby-dev:44985]'
pid = nil
cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read'
@@ -1068,7 +1076,7 @@ q.pop
puts mth.status
Process.kill(:INT, $$)
}
- sleep 0.1
+ sleep
INPUT
end
@@ -1244,6 +1252,21 @@ q.pop
assert_predicate(status, :success?, bug9751)
end if Process.respond_to?(:fork)
+ def test_fork_value
+ bug18902 = "[Bug #18902]"
+ th = Thread.start { sleep 2 }
+ begin
+ pid = fork do
+ th.value
+ end
+ _, status = Process.wait2(pid)
+ assert_predicate(status, :success?, bug18902)
+ ensure
+ th.kill
+ th.join
+ end
+ end if Process.respond_to?(:fork)
+
def test_fork_while_locked
m = Thread::Mutex.new
thrs = []
@@ -1256,7 +1279,7 @@ q.pop
end if Process.respond_to?(:fork)
def test_fork_while_parent_locked
- skip 'needs fork' unless Process.respond_to?(:fork)
+ omit 'needs fork' unless Process.respond_to?(:fork)
m = Thread::Mutex.new
nr = 1
thrs = []
@@ -1277,7 +1300,7 @@ q.pop
end
def test_fork_while_mutex_locked_by_forker
- skip 'needs fork' unless Process.respond_to?(:fork)
+ omit 'needs fork' unless Process.respond_to?(:fork)
m = Thread::Mutex.new
m.synchronize do
pid = fork do
@@ -1338,6 +1361,61 @@ q.pop
t.join
end
+ def test_yield_across_thread_through_enum
+ bug18649 = '[ruby-core:107980] [Bug #18649]'
+ @log = []
+
+ def self.p(arg)
+ @log << arg
+ end
+
+ def self.synchronize
+ yield
+ end
+
+ def self.execute(task)
+ success = true
+ value = reason = nil
+ end_sync = false
+
+ synchronize do
+ begin
+ p :before
+ value = task.call
+ p :never_reached
+ success = true
+ rescue StandardError => ex
+ ex = ex.class
+ p [:rescue, ex]
+ reason = ex
+ success = false
+ end
+
+ end_sync = true
+ p :end_sync
+ end
+
+ p :should_not_reach_here! unless end_sync
+ [success, value, reason]
+ end
+
+ def self.foo
+ Thread.new do
+ result = execute(-> { yield 42 })
+ p [:result, result]
+ end.join
+ end
+
+ value = to_enum(:foo).first
+ expected = [:before,
+ [:rescue, LocalJumpError],
+ :end_sync,
+ [:result, [false, nil, LocalJumpError]]]
+
+ assert_equal(expected, @log, bug18649)
+ assert_equal(42, value, bug18649)
+ end
+
def test_thread_setname_in_initialize
bug12290 = '[ruby-core:74963] [Bug #12290]'
c = Class.new(Thread) {def initialize() self.name = "foo"; super; end}
@@ -1345,7 +1423,7 @@ q.pop
end
def test_thread_native_thread_id
- skip "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id)
+ omit "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id)
assert_instance_of Integer, Thread.main.native_thread_id
th1 = Thread.start{sleep}
@@ -1369,7 +1447,7 @@ q.pop
opts = { timeout: 5, timeout_error: nil }
# prevent SIGABRT from slow shutdown with MJIT
- opts[:reprieve] = 3 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled?
+ opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
assert_normal_exit(<<-_end, '[Bug #8996]', **opts)
Thread.report_on_exception = false
@@ -1384,9 +1462,14 @@ q.pop
def test_signal_at_join
if /mswin|mingw/ =~ RUBY_PLATFORM
- skip "can't trap a signal from another process on Windows"
+ omit "can't trap a signal from another process on Windows"
# opt = {new_pgroup: true}
end
+
+ if /freebsd/ =~ RUBY_PLATFORM
+ omit "[Bug #18613]"
+ end
+
assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120)
{#
n = 1000
@@ -1433,4 +1516,12 @@ q.pop
end
};
end
+
+ def test_pending_interrupt?
+ t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } }
+ t.raise(StandardError)
+ assert_equal(true, t.pending_interrupt?)
+ assert_equal(true, t.pending_interrupt?(Exception))
+ assert_equal(false, t.pending_interrupt?(ArgumentError))
+ end
end
diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb
index 3fa0eae2c1..723450ad23 100644
--- a/test/ruby/test_thread_queue.rb
+++ b/test/ruby/test_thread_queue.rb
@@ -111,6 +111,23 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal(0, q.num_waiting)
end
+ def test_queue_pop_timeout
+ q = Thread::Queue.new
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_queue_pop_non_block
q = Thread::Queue.new
assert_raise_with_message(ThreadError, /empty/) do
@@ -126,6 +143,24 @@ class TestThreadQueue < Test::Unit::TestCase
assert_equal(0, q.num_waiting)
end
+ def test_sized_queue_pop_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.pop(timeout: 1)
+
+ t1 = Thread.new { q.pop(timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.pop(timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_sized_queue_pop_non_block
q = Thread::SizedQueue.new(1)
assert_raise_with_message(ThreadError, /empty/) do
@@ -133,6 +168,24 @@ class TestThreadQueue < Test::Unit::TestCase
end
end
+ def test_sized_queue_push_timeout
+ q = Thread::SizedQueue.new(1)
+
+ q << 1
+ assert_equal 1, q.size
+
+ t1 = Thread.new { q.push(2, timeout: 1) }
+ assert_equal t1, t1.join(2)
+ assert_nil t1.value
+
+ t2 = Thread.new { q.push(2, timeout: 0.1) }
+ assert_equal t2, t2.join(1)
+ assert_nil t2.value
+ ensure
+ t1&.kill&.join
+ t2&.kill&.join
+ end
+
def test_sized_queue_push_interrupt
q = Thread::SizedQueue.new(1)
q.push(1)
@@ -151,6 +204,8 @@ class TestThreadQueue < Test::Unit::TestCase
end
def test_thr_kill
+ omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM
+
bug5343 = '[ruby-core:39634]'
Dir.mktmpdir {|d|
timeout = EnvUtil.apply_timeout_scale(60)
@@ -580,10 +635,10 @@ class TestThreadQueue < Test::Unit::TestCase
def test_queue_with_trap
if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/)
- skip 'This test fails too often on AppVeyor vs140'
+ omit 'This test fails too often on AppVeyor vs140'
end
if RUBY_PLATFORM.match?(/mingw/)
- skip 'This test fails too often on MinGW'
+ omit 'This test fails too often on MinGW'
end
assert_in_out_err([], <<-INPUT, %w(INT INT exit), [])
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index fc21c74078..0faad878fc 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -37,8 +37,11 @@ class TestTime < Test::Unit::TestCase
end
def test_new
+ assert_equal(Time.new(2000,1,1,0,0,0), Time.new(2000))
+ assert_equal(Time.new(2000,2,1,0,0,0), Time.new("2000", "Feb"))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, 3600*11))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,9, 13,0,0, -3600*11))
+ assert_equal(Time.utc(2000,2,29,23,0,0), Time.new(2000, 3, 1, 0, 0, 0, 3600))
assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, "+11:00"))
assert_equal(Rational(1,2), Time.new(2000,2,10, 11,0,5.5, "+11:00").subsec)
bug4090 = '[ruby-dev:42631]'
@@ -46,7 +49,108 @@ class TestTime < Test::Unit::TestCase
t = Time.new(*tm, "-12:00")
assert_equal([2001,2,28,23,59,30,-43200], [t.year, t.month, t.mday, t.hour, t.min, t.sec, t.gmt_offset], bug4090)
assert_raise(ArgumentError) { Time.new(2000,1,1, 0,0,0, "+01:60") }
+ msg = /invalid value for Integer/
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, 1, "+09:99") }
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, "+09:99") }
+ assert_raise_with_message(ArgumentError, msg) { Time.new(2021, "+09:99") }
+
+ assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], Time.new(2000, 1, 1, 0, 0, 0, "-00:00").to_a)
+ assert_equal([0, 0, 0, 2, 1, 2000, 0, 2, false, "UTC"], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a)
+ end
+
+ def test_new_from_string
assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") }
+
+ t = Time.utc(2020, 12, 24, 15, 56, 17)
+ assert_equal(t, Time.new("2020-12-24T15:56:17Z"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +0900"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +090130"))
+ assert_equal(t, Time.new("2020-12-25T00:56:17+09:00"))
+ assert_raise_with_message(ArgumentError, /missing sec part/) {
+ Time.new("2020-12-25 00:56 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /missing min part/) {
+ Time.new("2020-12-25 00 +09:00")
+ }
+
+ assert_equal(Time.new(2021), Time.new("2021"))
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00"))
+
+ assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec)
+ assert_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec)
+ assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec)
+ assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec)
+ assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") {
+ Time.new("2020-12-25 00:56:17. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /year must be 4 or more/) {
+ Time.new("021-12-25 00:00:00.123456 +09:00")
+ }
+ assert_raise_with_message(ArgumentError, /fraction min is.*56\./) {
+ Time.new("2020-12-25 00:56. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /fraction hour is.*00\./) {
+ Time.new("2020-12-25 00. +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:017\b/) {
+ Time.new("2020-12-25 00:56:017 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits sec.*:9\b/) {
+ Time.new("2020-12-25 00:56:9 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /sec out of range/) {
+ Time.new("2020-12-25 00:56:64 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:056\b/) {
+ Time.new("2020-12-25 00:056:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits min.*:5\b/) {
+ Time.new("2020-12-25 00:5:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /min out of range/) {
+ Time.new("2020-12-25 00:64:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b000\b/) {
+ Time.new("2020-12-25 000:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits hour.*\b0\b/) {
+ Time.new("2020-12-25 0:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /hour out of range/) {
+ Time.new("2020-12-25 33:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b025\b/) {
+ Time.new("2020-12-025 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mday.*\b5\b/) {
+ Time.new("2020-12-5 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mday out of range/) {
+ Time.new("2020-12-33 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b012\b/) {
+ Time.new("2020-012-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /two digits mon.*\b1\b/) {
+ Time.new("2020-1-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /mon out of range/) {
+ Time.new("2020-17-25 00:56:17 +0900")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12")
+ }
+ assert_raise_with_message(ArgumentError, /no time information/) {
+ Time.new("2020-12-02")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new(" 2020-12-02 00:00:00")
+ }
+ assert_raise_with_message(ArgumentError, /can't parse/) {
+ Time.new("2020-12-02 00:00:00 ")
+ }
end
def test_time_add()
@@ -110,7 +214,7 @@ class TestTime < Test::Unit::TestCase
assert_equal(946684800, Time.utc(2000, 1, 1, 0, 0, 0).tv_sec)
# Giveup to try 2nd test because some state is changed.
- skip if Test::Unit::Runner.current_repeat_count > 0
+ omit if Test::Unit::Runner.current_repeat_count > 0
assert_equal(0x7fffffff, Time.utc(2038, 1, 19, 3, 14, 7).tv_sec)
assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec)
@@ -240,6 +344,10 @@ class TestTime < Test::Unit::TestCase
assert_equal(1, Time.at(0, 0.001).nsec)
end
+ def test_at_splat
+ assert_equal(Time.at(1, 2), Time.at(*[1, 2]))
+ end
+
def test_at_with_unit
assert_equal(123456789, Time.at(0, 123456789, :nanosecond).nsec)
assert_equal(123456789, Time.at(0, 123456789, :nsec).nsec)
@@ -379,6 +487,11 @@ class TestTime < Test::Unit::TestCase
end
end
+ def test_marshal_broken_month
+ data = "\x04\x08u:\tTime\r\x20\x7c\x1e\xc0\x00\x00\x00\x00"
+ assert_equal(Time.utc(2022, 4, 1), Marshal.load(data))
+ end
+
def test_marshal_distant_past
assert_marshal_roundtrip(Time.utc(1890, 1, 1))
assert_marshal_roundtrip(Time.utc(-4.5e9, 1, 1))
@@ -1168,7 +1281,7 @@ class TestTime < Test::Unit::TestCase
def test_2038
# Giveup to try 2nd test because some state is changed.
- skip if Test::Unit::Runner.current_repeat_count > 0
+ omit if Test::Unit::Runner.current_repeat_count > 0
if no_leap_seconds?
assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec)
@@ -1281,22 +1394,6 @@ class TestTime < Test::Unit::TestCase
assert_equal("366", t.strftime("%j"))
end
- def test_strftime_no_hidden_garbage
- skip unless Thread.list.size == 1
-
- fmt = %w(Y m d).map { |x| "%#{x}" }.join('-') # defeats optimization
- t = Time.at(0).getutc
- ObjectSpace.count_objects(res = {}) # creates strings on first call
- GC.disable
- before = ObjectSpace.count_objects(res)[:T_STRING]
- val = t.strftime(fmt)
- after = ObjectSpace.count_objects(res)[:T_STRING]
- assert_equal before + 1, after, 'only new string is the created one'
- assert_equal '1970-01-01', val
- ensure
- GC.enable
- end
-
def test_num_exact_error
bad = EnvUtil.labeled_class("BadValue").new
x = EnvUtil.labeled_class("Inexact") do
@@ -1309,21 +1406,38 @@ class TestTime < Test::Unit::TestCase
def test_memsize
# Time objects are common in some code, try to keep them small
- skip "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
- skip "GC is in debug" if GC::INTERNAL_CONSTANTS[:DEBUG]
+ omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM
+ omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:DEBUG]
require 'objspace'
t = Time.at(0)
- size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
- case size
- when 20 then expect = 50
- when 24 then expect = 54
- when 40 then expect = 86
- when 48 then expect = 94
- else
- flunk "Unsupported RVALUE_SIZE=#{size}, update test_memsize"
- end
+ sizeof_timew =
+ if RbConfig::SIZEOF.key?("uint64_t") && RbConfig::SIZEOF["long"] * 2 <= RbConfig::SIZEOF["uint64_t"]
+ RbConfig::SIZEOF["uint64_t"]
+ else
+ RbConfig::SIZEOF["void*"] # Same size as VALUE
+ end
+ sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8
+ expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm
assert_equal expect, ObjectSpace.memsize_of(t)
rescue LoadError => e
- skip "failed to load objspace: #{e.message}"
+ omit "failed to load objspace: #{e.message}"
+ end
+
+ def test_deconstruct_keys
+ t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) }
+ assert_equal(
+ {year: 2022, month: 10, day: 16, wday: 0, yday: 289,
+ hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'},
+ t.deconstruct_keys(nil)
+ )
+
+ assert_equal(
+ {year: 2022, month: 10, sec: 30},
+ t.deconstruct_keys(%i[year month sec nonexistent])
+ )
+ end
+
+ def test_parse_zero_bigint
+ assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]'
end
end
diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb
index fdc9e114b5..f66cd9bec2 100644
--- a/test/ruby/test_time_tz.rb
+++ b/test/ruby/test_time_tz.rb
@@ -7,9 +7,9 @@ class TestTimeTZ < Test::Unit::TestCase
has_lisbon_tz = true
force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes"
case RUBY_PLATFORM
- when /linux/
+ when /darwin|linux/
force_tz_test = true
- when /darwin|freebsd|openbsd/
+ when /freebsd|openbsd/
has_lisbon_tz = false
force_tz_test = true
end
@@ -95,6 +95,9 @@ class TestTimeTZ < Test::Unit::TestCase
CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") {
Time.local(1994, 12, 31, 0, 0, 0).year == 1995
}
+ CORRECT_SINGAPORE_1982 = with_tz("Asia/Singapore") {
+ "2022g" if Time.local(1981, 12, 31, 23, 59, 59).utc_offset == 8*3600
+ }
def time_to_s(t)
t.to_s
@@ -112,7 +115,7 @@ class TestTimeTZ < Test::Unit::TestCase
t = with_tz("America/Los_Angeles") {
Time.local(2000, 1, 1)
}
- skip "force_tz_test is false on this environment" unless t
+ omit "force_tz_test is false on this environment" unless t
z1 = t.zone
z2 = with_tz(tz="Asia/Singapore") {
t.localtime.zone
@@ -140,9 +143,12 @@ class TestTimeTZ < Test::Unit::TestCase
def test_asia_singapore
with_tz(tz="Asia/Singapore") {
- assert_time_constructor(tz, "1981-12-31 23:59:59 +0730", :local, [1981,12,31,23,59,59])
- assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,0,0])
- assert_time_constructor(tz, "1982-01-01 00:59:59 +0800", :local, [1982,1,1,0,29,59])
+ assert_time_constructor(tz, "1981-12-31 23:29:59 +0730", :local, [1981,12,31,23,29,59])
+ if CORRECT_SINGAPORE_1982
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1981,12,31,23,30,00])
+ assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1982,1,1,0,0,0])
+ assert_time_constructor(tz, "1982-01-01 00:29:59 +0800", :local, [1982,1,1,0,29,59])
+ end
assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0])
}
end
@@ -196,7 +202,7 @@ class TestTimeTZ < Test::Unit::TestCase
def test_europe_lisbon
with_tz("Europe/Lisbon") {
- assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone)
+ assert_include(%w"LMT CET", Time.new(-0x1_0000_0000_0000_0000).zone)
}
end if has_lisbon_tz
@@ -450,9 +456,12 @@ America/Managua Fri Jan 1 06:00:00 1993 UTC = Fri Jan 1 01:00:00 1993 EST isd
America/Managua Wed Jan 1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000
America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600
Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0 gmtoff=27000
-Asia/Singapore Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000
+Asia/Singapore Thu Dec 31 15:59:59 1981 UTC = Thu Dec 31 23:29:59 1981 SGT isdst=0 gmtoff=27000
Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0 gmtoff=28800
End
+ gen_zdump_test <<'End' if CORRECT_SINGAPORE_1982
+Asia/Singapore Thu Dec 31 16:00:00 1981 UTC = Fri Jan 1 00:00:00 1982 SGT isdst=0 gmtoff=28800
+End
gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End'
Asia/Tokyo Sat May 5 14:59:59 1951 UTC = Sat May 5 23:59:59 1951 JST isdst=0 gmtoff=32400
Asia/Tokyo Sat May 5 15:00:00 1951 UTC = Sun May 6 01:00:00 1951 JDT isdst=1 gmtoff=36000
@@ -612,6 +621,11 @@ module TestTimeTZ::WithTZ
assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)}
end
+ def subtest_hour24(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2000, 1, 1, 24, 0, 0, tzarg)
+ assert_equal([0, 0, 0, 2, 1, 2000], [t.sec, t.min, t.hour, t.mday, t.mon, t.year])
+ end
+
def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset)
t = time_class.now(in: tzarg)
assert_equal(tz, t.zone)
@@ -681,6 +695,13 @@ module TestTimeTZ::WithTZ
assert_equal(t.dst?, t2.dst?)
end
+ def subtest_fractional_second(time_class, tz, tzarg, tzname, abbr, utc_offset)
+ t = time_class.new(2024, 1, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ t = time_class.new(2024, 7, 1, 23, 59, 59.9r, tzarg)
+ assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset)
+ end
+
def test_invalid_zone
make_timezone("INVALID", "INV", 0)
rescue => e
@@ -705,6 +726,7 @@ module TestTimeTZ::WithTZ
"Asia/Tokyo" => ["JST", +9*3600],
"America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600],
"Africa/Ndjamena" => ["WAT", +1*3600],
+ "Etc/UTC" => ["UTC", 0],
}
def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb
index c8b0034e06..24ee9b9533 100644
--- a/test/ruby/test_transcode.rb
+++ b/test/ruby/test_transcode.rb
@@ -2232,12 +2232,12 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback))
end
- bug8940 = '[ruby-core:57318] [Bug #8940]'
- %w[UTF-32 UTF-16].each do |enc|
- define_method("test_pseudo_encoding_inspect(#{enc})") do
- assert_normal_exit("'aaa'.encode('#{enc}').inspect", bug8940)
- assert_equal(4, 'aaa'.encode(enc).length, "should count in #{enc} with BOM")
- end
+ def test_pseudo_encoding_inspect
+ s = 'aaa'.encode "UTF-16"
+ assert_equal '"\xFE\xFF\x00\x61\x00\x61\x00\x61"', s.inspect
+
+ s = 'aaa'.encode "UTF-32"
+ assert_equal '"\x00\x00\xFE\xFF\x00\x00\x00\x61\x00\x00\x00\x61\x00\x00\x00\x61"', s.inspect
end
def test_encode_with_invalid_chars
@@ -2305,5 +2305,7 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("A\rB\r\rC", s.encode(usascii, newline: :cr))
assert_equal("A\r\nB\r\r\nC", s.encode(usascii, crlf_newline: true))
assert_equal("A\r\nB\r\r\nC", s.encode(usascii, newline: :crlf))
+ assert_equal("A\nB\nC", s.encode(usascii, lf_newline: true))
+ assert_equal("A\nB\nC", s.encode(usascii, newline: :lf))
end
end
diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb
index e0add7c3ab..074b92be55 100644
--- a/test/ruby/test_undef.rb
+++ b/test/ruby/test_undef.rb
@@ -35,4 +35,20 @@ class TestUndef < Test::Unit::TestCase
end
end
end
+
+ def test_singleton_undef
+ klass = Class.new do
+ def foo
+ :ok
+ end
+ end
+
+ klass.new.foo
+
+ klass.new.instance_eval do
+ undef foo
+ end
+
+ klass.new.foo
+ end
end
diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb
index f8a7c68fd3..5e8abb0734 100644
--- a/test/ruby/test_variable.rb
+++ b/test/ruby/test_variable.rb
@@ -33,6 +33,12 @@ class TestVariable < Test::Unit::TestCase
end
end
+ Athena = Gods.clone
+
+ def test_cloned_classes_copy_cvar_cache
+ assert_equal "Cronus", Athena.new.ruler0
+ end
+
def test_setting_class_variable_on_module_through_inheritance
mod = Module.new
mod.class_variable_set(:@@foo, 1)
@@ -43,6 +49,19 @@ class TestVariable < Test::Unit::TestCase
assert_equal(1, c.class_variable_get(:@@foo))
end
+ Zeus = Gods.clone
+
+ def test_cloned_allows_setting_cvar
+ Zeus.class_variable_set(:@@rule, "Athena")
+
+ god = Gods.new.ruler0
+ zeus = Zeus.new.ruler0
+
+ assert_equal "Cronus", god
+ assert_equal "Athena", zeus
+ assert_not_equal god.object_id, zeus.object_id
+ end
+
def test_singleton_class_included_class_variable
c = Class.new
c.extend(Olympians)
@@ -261,6 +280,12 @@ class TestVariable < Test::Unit::TestCase
v.instance_variable_set(:@foo, :bar)
end
+ assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do
+ v.instance_eval do
+ @a = 1
+ end
+ end
+
assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)}
assert_not_send([v, :instance_variable_defined?, :@foo])
diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb
index 679ce94b91..9c06ec14fb 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -3,7 +3,7 @@ require 'test/unit'
class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args)
- skip if RUBY_PLATFORM !~ /darwin/
+ omit if RUBY_PLATFORM !~ /darwin/
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/)
end
diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb
index 3b9eef770a..7fc956dfae 100644
--- a/test/ruby/test_weakmap.rb
+++ b/test/ruby/test_weakmap.rb
@@ -59,7 +59,7 @@ class TestWeakMap < Test::Unit::TestCase
assert_weak_include(m, k)
end
GC.start
- skip('TODO: failure introduced from r60440')
+ pend('TODO: failure introduced from 837fd5e494731d7d44786f29e7d6e8c27029806f')
assert_not_send([@wm, m, k])
end
alias test_member? test_include?
@@ -73,6 +73,15 @@ class TestWeakMap < Test::Unit::TestCase
@wm.inspect)
end
+ def test_inspect_garbage
+ 1000.times do |i|
+ @wm[i] = Object.new
+ @wm.inspect
+ end
+ assert_match(/\A\#<#{@wm.class.name}:[^:]++:(?:\s\d+\s=>\s\#<(?:Object|collected):[^:<>]*+>(?:,|>\z))+/,
+ @wm.inspect)
+ end
+
def test_each
m = __callee__[/test_(.*)/, 1]
x1 = Object.new
@@ -158,4 +167,50 @@ class TestWeakMap < Test::Unit::TestCase
assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
assert_nothing_raised(FrozenError) {@wm['foo'] = o}
end
+
+ def test_no_memory_leak
+ assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #19398]", rss: true, limit: 1.5, timeout: 60)
+ begin;
+ 1_000_000.times do
+ ObjectSpace::WeakMap.new
+ end
+ end;
+ end
+
+ def test_compaction_bug_19529
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ obj = Object.new
+ 100.times do |i|
+ GC.compact
+ @wm[i] = obj
+ end
+
+ assert_separately(%w(--disable-gems), <<-'end;')
+ wm = ObjectSpace::WeakMap.new
+ obj = Object.new
+ 100.times do
+ wm[Object.new] = obj
+ GC.start
+ end
+ GC.compact
+ end;
+ end
+
+ def test_replaced_values_bug_19531
+ a = "A".dup
+ b = "B".dup
+
+ @wm[1] = a
+ @wm[1] = a
+ @wm[1] = a
+
+ @wm[1] = b
+ assert_equal b, @wm[1]
+
+ a = nil
+ GC.start
+
+ assert_equal b, @wm[1]
+ end
end
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 0905b68218..b90235cd7d 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -1,17 +1,25 @@
# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS='test/ruby/test_yjit.rb'
+
require 'test/unit'
require 'envutil'
require 'tmpdir'
+require_relative '../lib/jit_support'
-return unless YJIT.enabled?
+return unless JITSupport.yjit_supported?
# Tests for YJIT with assertions on compilation and side exits
-# insipired by the MJIT tests in test/ruby/test_jit.rb
+# insipired by the MJIT tests in test/ruby/test_mjit.rb
class TestYJIT < Test::Unit::TestCase
+ running_with_yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
+
def test_yjit_in_ruby_description
assert_includes(RUBY_DESCRIPTION, '+YJIT')
- end
+ end if running_with_yjit
+ # Check that YJIT is in the version string
def test_yjit_in_version
[
%w(--version --yjit),
@@ -21,27 +29,46 @@ class TestYJIT < Test::Unit::TestCase
%w(--version --disable=yjit --yjit),
%w(--version --disable=yjit --enable-yjit),
%w(--version --disable=yjit --enable=yjit),
+ %w(--version --jit),
+ %w(--version --disable-jit --jit),
+ %w(--version --disable-jit --enable-jit),
+ %w(--version --disable-jit --enable=jit),
+ %w(--version --disable=jit --yjit),
+ %w(--version --disable=jit --enable-jit),
+ %w(--version --disable=jit --enable=jit),
].each do |version_args|
assert_in_out_err(version_args) do |stdout, stderr|
- assert_equal([RUBY_DESCRIPTION], stdout)
+ assert_equal(RUBY_DESCRIPTION, stdout.first)
assert_equal([], stderr)
end
end
- end
+ end if running_with_yjit
def test_command_line_switches
- assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
- assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/)
- assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/)
- assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
- assert_in_out_err('--yjit-greedy-versioning=1', '', [], /warning: argument to --yjit-greedy-versioning is ignored/)
+ assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/)
+ assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/)
+ #assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/)
+ #assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/)
+ end
+
+ def test_yjit_stats_and_v_no_error
+ _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-stats), '', true, true)
+ refute_includes(stderr, "NoMethodError")
end
def test_enable_from_env_var
yjit_child_env = {'RUBY_YJIT_ENABLE' => '1'}
- assert_in_out_err([yjit_child_env, '--version'], '', [RUBY_DESCRIPTION])
+ assert_in_out_err([yjit_child_env, '--version'], '') do |stdout, stderr|
+ assert_equal(RUBY_DESCRIPTION, stdout.first)
+ assert_equal([], stderr)
+ end
assert_in_out_err([yjit_child_env, '-e puts RUBY_DESCRIPTION'], '', [RUBY_DESCRIPTION])
- assert_in_out_err([yjit_child_env, '-e p YJIT.enabled?'], '', ['true'])
+ assert_in_out_err([yjit_child_env, '-e p RubyVM::YJIT.enabled?'], '', ['true'])
+ end if running_with_yjit
+
+ def test_compile_setclassvariable
+ script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo'
+ assert_compiles(script, insns: %i[setclassvariable], result: 1)
end
def test_compile_getclassvariable
@@ -59,6 +86,10 @@ class TestYJIT < Test::Unit::TestCase
assert_compiles(':foo', insns: %i[putobject], result: :foo)
end
+ def test_compile_opt_succ
+ assert_compiles('1.succ', insns: %i[opt_succ], result: 2)
+ end
+
def test_compile_opt_not
assert_compiles('!false', insns: %i[opt_not], result: true)
assert_compiles('!nil', insns: %i[opt_not], result: true)
@@ -85,6 +116,16 @@ class TestYJIT < Test::Unit::TestCase
assert_compiles('e = 5; (..e)', insns: %i[newrange], result: ..5)
end
+ def test_compile_duphash
+ assert_compiles('{ two: 2 }', insns: %i[duphash], result: { two: 2 })
+ end
+
+ def test_compile_newhash
+ assert_compiles('{}', insns: %i[newhash], result: {})
+ assert_compiles('{ two: 1 + 1 }', insns: %i[newhash], result: { two: 2 })
+ assert_compiles('{ 1 + 1 => :two }', insns: %i[newhash], result: { 2 => :two })
+ end
+
def test_compile_opt_nil_p
assert_compiles('nil.nil?', insns: %i[opt_nil_p], result: true)
assert_compiles('false.nil?', insns: %i[opt_nil_p], result: false)
@@ -224,6 +265,11 @@ class TestYJIT < Test::Unit::TestCase
assert_no_exits('/#{true}/')
end
+ def test_compile_dynamic_symbol
+ assert_compiles(':"#{"foo"}"', insns: %i[intern])
+ assert_compiles('s = "bar"; :"foo#{s}"', insns: %i[intern])
+ end
+
def test_getlocal_with_level
assert_compiles(<<~RUBY, insns: %i[getlocal opt_plus], result: [[7]])
def foo(foo, bar)
@@ -274,6 +320,32 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_string_concat_utf8
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: true)
+ def str_cat_utf8
+ s = String.new
+ 10.times { s << "✅" }
+ s
+ end
+
+ str_cat_utf8 == "✅" * 10
+ RUBY
+ end
+
+ def test_string_concat_ascii
+ # Constant-get for classes (e.g. String, Encoding) can cause a side-exit in getinlinecache. For now, ignore exits.
+ assert_compiles(<<~RUBY, exits: :any)
+ str_arg = "b".encode(Encoding::ASCII)
+ def str_cat_ascii(arg)
+ s = String.new(encoding: Encoding::ASCII)
+ 10.times { s << arg }
+ s
+ end
+
+ str_cat_ascii(str_arg) == str_arg * 10
+ RUBY
+ end
+
def test_opt_length_in_method
assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5)
def foo(str)
@@ -317,8 +389,31 @@ class TestYJIT < Test::Unit::TestCase
assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil)
end
- def test_compile_opt_getinlinecache
- assert_compiles(<<~RUBY, insns: %i[opt_getinlinecache], result: 123, min_calls: 2)
+ def test_compile_getconstant
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: [], call_threshold: 1)
+ def get_argv(klass)
+ klass::ARGV
+ end
+
+ get_argv(Object)
+ RUBY
+ end
+
+ def test_compile_getconstant_with_sp_offset
+ assert_compiles(<<~RUBY, insns: %i[getconstant], result: 2, call_threshold: 1)
+ class Foo
+ Bar = 1
+ end
+
+ 2.times do
+ s = Foo # this opt_getconstant_path needs warmup, so 2.times is needed
+ Class.new(Foo).const_set(:Bar, s::Bar)
+ end
+ RUBY
+ end
+
+ def test_compile_opt_getconstant_path
+ assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2)
def get_foo
FOO
end
@@ -330,8 +425,8 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
- def test_opt_getinlinecache_slowpath
- assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], min_calls: 2)
+ def test_opt_getconstant_path_slowpath
+ assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
class A
FOO = 42
class << self
@@ -359,7 +454,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_string_interpolation
- assert_compiles(<<~'RUBY', insns: %i[checktype concatstrings], result: "foobar", min_calls: 2)
+ assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2)
def make_str(foo, bar)
"#{foo}#{bar}"
end
@@ -370,7 +465,7 @@ class TestYJIT < Test::Unit::TestCase
end
def test_string_interpolation_cast
- assert_compiles(<<~'RUBY', insns: %i[checktype concatstrings tostring], result: "123")
+ assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "123")
def make_str(foo, bar)
"#{foo}#{bar}"
end
@@ -379,7 +474,30 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
- def test_invokebuiltin
+ def test_checkkeyword
+ assert_compiles(<<~'RUBY', insns: %i[checkkeyword], result: [2, 5])
+ def foo(foo: 1+1)
+ foo
+ end
+
+ [foo, foo(foo: 5)]
+ RUBY
+ end
+
+ def test_struct_aref
+ assert_compiles(<<~RUBY)
+ def foo(obj)
+ obj.foo
+ obj.bar
+ end
+
+ Foo = Struct.new(:foo, :bar)
+ foo(Foo.new(123))
+ foo(Foo.new(123))
+ RUBY
+ end
+
+ def test_struct_aset
assert_compiles(<<~RUBY)
def foo(obj)
obj.foo = 123
@@ -392,6 +510,232 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_getblockparam
+ assert_compiles(<<~'RUBY', insns: [:getblockparam])
+ def foo &blk
+ 2.times do
+ blk
+ end
+ end
+
+ foo {}
+ foo {}
+ RUBY
+ end
+
+ def test_getblockparamproxy
+ # Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 })
+ def foo &blk
+ p blk.call
+ p blk.call
+ end
+
+ foo { 1 }
+ foo { 2 }
+ RUBY
+ end
+
+ def test_send_blockarg
+ assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {})
+ def bar
+ end
+
+ def foo &blk
+ bar(&blk)
+ bar(&blk)
+ end
+
+ foo
+ foo
+
+ foo { }
+ foo { }
+ RUBY
+ end
+
+ def test_send_splat
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {})
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, *[1, 2, 3]) + "/P"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_multiarg
+ assert_compiles(<<~'RUBY', result: "3#1,2,3/Q")
+ def internal_method(*args)
+ "#{args.size}##{args.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, 1, 2, 3) + "/Q"
+ end
+
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs
+ # For now, this side-exits when calls include keyword args
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A", exits: {opt_send_without_block: 1})
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, a: 1, b: 2) + "/A"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_in_receiver_only
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ send(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_with_underscores
+ assert_compiles(<<~'RUBY', result: "0/RK", exits: {})
+ def internal_method(**kw)
+ "#{kw.size}"
+ end
+
+ def jit_method
+ __send__(:internal_method) + "/RK"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_kwargs_splat
+ # For now, this side-exits when calling with a splat
+ assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B", exits: {opt_send_without_block: 1})
+ def internal_method(**kw)
+ "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}"
+ end
+
+ def jit_method
+ send(:internal_method, **{ a: 1, b: 2 }) + "/B"
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block
+ # Setlocal_wc_0 sometimes side-exits on write barrier
+ assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n", exits: { :setlocal_WC_0 => 0..1 })
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_calling
+ assert_compiles(<<~'RUBY', result: "1a2", exits: {})
+ def internal_method
+ out = yield
+ "1" + out + "2"
+ end
+
+ def jit_method
+ __send__(:internal_method) { "a" }
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_receiver
+ assert_compiles(<<~'RUBY', result: "b:n", exits: {})
+ def internal_method(&b)
+ "b:#{block_given? ? "y" : "n"}"
+ end
+
+ def jit_method
+ send(:internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_send_block_only_sender
+ assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {})
+ def internal_method
+ "Y"
+ end
+
+ def jit_method
+ b7 = proc { 7 }
+ [
+ send(:internal_method),
+ send(:internal_method, &b7),
+ send(:internal_method) { 7 },
+ send(:internal_method, &nil),
+ ].join("/")
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_multisend
+ assert_compiles(<<~'RUBY', result: "77")
+ def internal_method
+ "7"
+ end
+
+ def jit_method
+ send(:send, :internal_method) + send(:send, :send, :internal_method)
+ end
+ jit_method
+ RUBY
+ end
+
+ def test_getivar_opt_plus
+ assert_no_exits(<<~RUBY)
+ class TheClass
+ def initialize
+ @levar = 1
+ end
+
+ def get_sum
+ sum = 0
+ # The type of levar is unknown,
+ # but this still should not exit
+ sum += @levar
+ sum
+ end
+ end
+
+ obj = TheClass.new
+ obj.get_sum
+ RUBY
+ end
+
def test_super_iseq
assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15)
class A
@@ -428,7 +772,7 @@ class TestYJIT < Test::Unit::TestCase
# Tests calling a variadic cfunc with many args
def test_build_large_struct
- assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], min_calls: 2)
+ assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2)
::Foo = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h)
def build_foo
@@ -451,8 +795,26 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_optarg_and_kwarg
+ assert_no_exits(<<~'RUBY')
+ def opt_and_kwarg(a, b=nil, c: nil)
+ end
+
+ 2.times do
+ opt_and_kwarg(1, 2, c: 3)
+ end
+ RUBY
+ end
+
+ def test_cfunc_kwarg
+ assert_no_exits('{}.store(:value, foo: 123)')
+ assert_no_exits('{}.store(:value, foo: 123, bar: 456, baz: 789)')
+ assert_no_exits('{}.merge(foo: 123)')
+ assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)')
+ end
+
+ # regression test simplified from URI::Generic#hostname=
def test_ctx_different_mappings
- # regression test simplified from URI::Generic#hostname=
assert_compiles(<<~'RUBY', frozen_string_literal: true)
def foo(v)
!(v&.start_with?('[')) && v&.index(':')
@@ -480,7 +842,7 @@ class TestYJIT < Test::Unit::TestCase
objects[1].foo
}
- stats = YJIT.runtime_stats
+ stats = RubyVM::YJIT.runtime_stats
return :ok unless stats[:all_stats]
return :ok if stats[:invalidation_count] < 10
@@ -488,43 +850,400 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
+ def test_int_equal
+ assert_compiles(<<~'RUBY', exits: :any, result: [true, false, true, false, true, false, true, false])
+ def eq(a, b)
+ a == b
+ end
+
+ def eqq(a, b)
+ a === b
+ end
+
+ big1 = 2 ** 65
+ big2 = big1 + 1
+ [eq(1, 1), eq(1, 2), eq(big1, big1), eq(big1, big2), eqq(1, 1), eqq(1, 2), eqq(big1, big1), eqq(big1, big2)]
+ RUBY
+ end
+
+ def test_opt_case_dispatch
+ assert_compiles(<<~'RUBY', exits: :any, result: [:"1", "2", 3])
+ def case_dispatch(val)
+ case val
+ when 1
+ :"#{val}"
+ when 2
+ "#{val}"
+ else
+ val
+ end
+ end
+
+ [case_dispatch(1), case_dispatch(2), case_dispatch(3)]
+ RUBY
+ end
+
+ def test_code_gc
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ return :not_paged unless add_pages(100) # prepare freeable pages
+ RubyVM::YJIT.code_gc # first code GC
+ return :not_compiled1 unless compiles { nil } # should be JITable again
+
+ RubyVM::YJIT.code_gc # second code GC
+ return :not_compiled2 unless compiles { nil } # should be JITable again
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 2
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_call
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+ return :broken_resume2 if fiber.resume != 0 # The code should be still callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_on_stack_code_gc_twice
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while Fiber.yield(nil.to_i); end
+ }
+
+ return :not_paged1 unless add_pages(400) # go to a page without initial ocb code
+ return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber
+ RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page
+
+ return :not_paged2 unless add_pages(300) # add some stuff to be freed
+ # Not calling fiber.resume here to test the case that the YJIT payload loses some
+ # information at the previous code GC. The payload should still be there, and
+ # thus we could know the fiber ISEQ is still on stack on this second code GC.
+ RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page
+
+ return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't)
+ return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine
+
+ return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber
+ RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack
+
+ return :not_paged4 unless add_pages(100) # check everything still works
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count != 3
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_with_many_iseqs
+ assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1)
+ fiber = Fiber.new {
+ # Loop to call the same basic block again after Fiber.yield
+ while true
+ Fiber.yield(nil.to_i)
+ end
+ }
+
+ return :not_paged1 unless add_pages(250) # use some pages
+ return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well
+
+ add_pages(2000) # use a whole lot of pages to run out of 1MiB
+ return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable
+
+ code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count]
+ return :"code_gc_#{code_gc_count}" if code_gc_count == 0
+
+ :ok
+ RUBY
+ end
+
+ def test_code_gc_partial_last_page
+ # call_threshold: 2 to avoid JIT-ing code_gc itself. If code_gc were JITed right before
+ # code_gc is called, the last page would be on stack.
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok, call_threshold: 2)
+ # Leave a bunch of off-stack pages
+ i = 0
+ while i < 1000
+ eval("x = proc { 1.to_s }; x.call; x.call")
+ i += 1
+ end
+
+ # On Linux, memory page size != code page size. So the last code page could be partially
+ # mapped. This call tests that assertions and other things work fine under the situation.
+ RubyVM::YJIT.code_gc
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_script_compiled # not ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @eval_counter = 0
+ def eval_script
+ eval('@eval_counter += 1')
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:script_compiled) do |t|
+ @trace_counter += 1
+ end
+
+ eval_script # JIT without TracePoint
+ trace.enable
+ eval_script # call with TracePoint
+ trace.disable
+
+ return :"eval_#{@eval_counter}" if @eval_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_trace_b_call # ISEQ_TRACE_EVENTS
+ assert_compiles(<<~'RUBY', exits: :any, result: :ok)
+ @call_counter = 0
+ def block_call
+ 1.times { @call_counter += 1 }
+ end
+
+ @trace_counter = 0
+ trace = TracePoint.new(:b_call) do |t|
+ @trace_counter += 1
+ end
+
+ block_call # JIT without TracePoint
+ trace.enable
+ block_call # call with TracePoint
+ trace.disable
+
+ return :"call_#{@call_counter}" if @call_counter != 2
+ return :"trace_#{@trace_counter}" if @trace_counter != 1
+
+ :ok
+ RUBY
+ end
+
+ def test_send_to_call
+ assert_compiles(<<~'RUBY', result: :ok)
+ ->{ :ok }.send(:call)
+ RUBY
+ end
+
+ def test_invokeblock_many_locals
+ # [Bug #19299]
+ assert_compiles(<<~'RUBY', result: :ok)
+ def foo
+ yield
+ end
+
+ foo do
+ a1=a2=a3=a4=a5=a6=a7=a8=a9=a10=a11=a12=a13=a14=a15=a16=a17=a18=a19=a20=a21=a22=a23=a24=a25=a26=a27=a28=a29=a30 = :ok
+ a30
+ end
+ RUBY
+ end
+
+ def test_bug_19316
+ n = 2 ** 64
+ # foo's extra param and the splats are relevant
+ assert_compiles(<<~'RUBY', result: [[n, -n], [n, -n]])
+ def foo(_, a, b, c)
+ [a & b, ~c]
+ end
+
+ n = 2 ** 64
+ args = [0, -n, n, n-1]
+
+ GC.stress = true
+ [foo(*args), foo(*args)]
+ RUBY
+ end
+
+ def test_tracing_str_uplus
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok)
+ def str_uplus
+ _ = 1
+ _ = 2
+ ret = [+"frfr", __LINE__]
+ _ = 3
+ _ = 4
+
+ ret
+ end
+
+ str_uplus
+ require 'objspace'
+ ObjectSpace.trace_object_allocations_start
+
+ str, expected_line = str_uplus
+ alloc_line = ObjectSpace.allocation_sourceline(str)
+
+ if expected_line == alloc_line
+ :ok
+ else
+ [expected_line, alloc_line]
+ end
+ RUBY
+ end
+
+ def test_str_uplus_subclass
+ assert_compiles(<<~RUBY, frozen_string_literal: true, result: :subclass)
+ class S < String
+ def encoding
+ :subclass
+ end
+ end
+
+ def test(str)
+ (+str).encoding
+ end
+
+ test ""
+ test S.new
+ RUBY
+ end
+
+ def test_nested_send
+ #[Bug #19464]
+ assert_compiles(<<~RUBY, result: [:ok, :ok])
+ klass = Class.new do
+ class << self
+ alias_method :my_send, :send
+
+ def bar = :ok
+
+ def foo = bar
+ end
+ end
+
+ with_break = -> { break klass.send(:my_send, :foo) }
+ wo_break = -> { klass.send(:my_send, :foo) }
+
+ [with_break[], wo_break[]]
+ RUBY
+ end
+
+ def test_return_to_invalidated_block
+ # [Bug #19463]
+ assert_compiles(<<~RUBY, result: [1, 1, :ugokanai])
+ klass = Class.new do
+ def self.lookup(hash, key) = hash[key]
+
+ def self.foo(a, b) = []
+
+ def self.test(hash, key)
+ [lookup(hash, key), key, "".freeze]
+ # 05 opt_send_without_block :lookup
+ # 07 getlocal_WC_0 :hash
+ # 09 opt_str_freeze ""
+ # 12 newarray 3
+ # 14 leave
+ #
+ # YJIT will put instructions (07..14) into a block.
+ # When String#freeze is redefined from within lookup(),
+ # the return address to the block is still on-stack. We rely
+ # on invalidation patching the code at the return address
+ # to service this situation correctly.
+ end
+ end
+
+ # get YJIT to compile test()
+ hash = { 1 => [] }
+ 31.times { klass.test(hash, 1) }
+
+ # inject invalidation into lookup()
+ evil_hash = Hash.new do |_, key|
+ class String
+ undef :freeze
+ def freeze = :ugokanai
+ end
+
+ key
+ end
+ klass.test(evil_hash, 1)
+ RUBY
+ end
+
+ def test_str_concat_encoding_mismatch
+ assert_compiles(<<~'RUBY', result: "incompatible character encodings: ASCII-8BIT and EUC-JP")
+ def bar(a, b)
+ a << b
+ rescue => e
+ e.message
+ end
+
+ def foo(a, b, h)
+ h[nil]
+ bar(a, b) # Ruby call, not set cfp->pc
+ end
+
+ h = Hash.new { nil }
+ foo("\x80".b, "\xA1A1".force_encoding("EUC-JP"), h)
+ foo("\x80".b, "\xA1A1".force_encoding("EUC-JP"), h)
+ RUBY
+ end
+
+ private
+
+ def code_gc_helpers
+ <<~'RUBY'
+ def compiles(&block)
+ failures = RubyVM::YJIT.runtime_stats[:compilation_failure]
+ block.call
+ failures == RubyVM::YJIT.runtime_stats[:compilation_failure]
+ end
+
+ def add_pages(num_jits)
+ pages = RubyVM::YJIT.runtime_stats[:compiled_page_count]
+ num_jits.times { return false unless eval('compiles { nil.to_i }') }
+ pages.nil? || pages < RubyVM::YJIT.runtime_stats[:compiled_page_count]
+ end
+ RUBY
+ end
+
def assert_no_exits(script)
assert_compiles(script)
end
ANY = Object.new
- def assert_compiles(test_script, insns: [], min_calls: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil)
+ def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil, mem_size: nil)
reset_stats = <<~RUBY
- YJIT.runtime_stats
- YJIT.reset_stats!
+ RubyVM::YJIT.runtime_stats
+ RubyVM::YJIT.reset_stats!
RUBY
write_results = <<~RUBY
- stats = YJIT.runtime_stats
-
- def collect_blocks(blocks)
- blocks.sort_by(&:address).map { |b| [b.iseq_start_index, b.iseq_end_index] }
- end
+ stats = RubyVM::YJIT.runtime_stats
- def collect_iseqs(iseq)
- iseq_array = iseq.to_a
- insns = iseq_array.last.grep(Array)
- blocks = YJIT.blocks_for(iseq)
- h = {
- name: iseq_array[5],
- insns: insns,
- blocks: collect_blocks(blocks),
- }
- arr = [h]
- iseq.each_child { |c| arr.concat collect_iseqs(c) }
- arr
+ def collect_insns(iseq)
+ insns = RubyVM::YJIT.insns_compiled(iseq)
+ iseq.each_child { |c| insns.concat collect_insns(c) }
+ insns
end
iseq = RubyVM::InstructionSequence.of(_test_proc)
IO.open(3).write Marshal.dump({
result: #{result == ANY ? "nil" : "result"},
stats: stats,
- iseqs: collect_iseqs(iseq),
+ insns: collect_insns(iseq),
disasm: iseq.disasm
})
RUBY
@@ -539,7 +1258,7 @@ class TestYJIT < Test::Unit::TestCase
#{write_results}
RUBY
- status, out, err, stats = eval_with_jit(script, min_calls: min_calls)
+ status, out, err, stats = eval_with_jit(script, call_threshold:, mem_size:)
assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}"
@@ -550,59 +1269,57 @@ class TestYJIT < Test::Unit::TestCase
end
runtime_stats = stats[:stats]
- iseqs = stats[:iseqs]
+ insns_compiled = stats[:insns]
disasm = stats[:disasm]
- # Only available when RUBY_DEBUG enabled
+ # Check that exit counts are as expected
+ # Full stats are only available when --enable-yjit=dev
if runtime_stats[:all_stats]
recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") }
recorded_exits = recorded_exits.reject { |k, v| v == 0 }
recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym }
- if exits != :any && exits != recorded_exits
+ # Exits can be specified as a hash of stat-name symbol to integer for exact exits.
+ # or stat-name symbol to range if the number of side exits might vary (e.g. write
+ # barriers, cache misses.)
+ if exits != :any &&
+ exits != recorded_exits &&
+ !exits.all? { |k, v| v === recorded_exits[k] } # triple-equal checks range membership or integer equality
flunk "Expected #{exits.empty? ? "no" : exits.inspect} exits" \
", but got\n#{recorded_exits.inspect}"
end
end
- # Only available when RUBY_DEBUG enabled
+ # Only available when --enable-yjit=dev
if runtime_stats[:all_stats]
missed_insns = insns.dup
- all_compiled_blocks = {}
- iseqs.each do |iseq|
- compiled_blocks = iseq[:blocks].map { |from, to| (from...to) }
- all_compiled_blocks[iseq[:name]] = compiled_blocks
- compiled_insns = iseq[:insns]
- next_idx = 0
- compiled_insns.map! do |insn|
- # TODO: not sure this is accurate for determining insn size
- idx = next_idx
- next_idx += insn.length
- [idx, *insn]
- end
-
- compiled_insns.each do |idx, op, *arguments|
- next unless missed_insns.include?(op)
- next unless compiled_blocks.any? { |block| block === idx }
+ insns_compiled.each do |op|
+ if missed_insns.include?(op)
# This instruction was compiled
missed_insns.delete(op)
end
end
unless missed_insns.empty?
- flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\nCompiled ranges: #{all_compiled_blocks.inspect}\niseq:\n#{disasm}"
+ flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}"
end
end
end
- def eval_with_jit(script, min_calls: 1, timeout: 1000)
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+
+ def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil)
args = [
"--disable-gems",
- "--yjit-call-threshold=#{min_calls}",
+ "--yjit-call-threshold=#{call_threshold}",
"--yjit-stats"
]
- args << "-e" << script
+ args << "--yjit-exec-mem-size=#{mem_size}" if mem_size
+ args << "-e" << script_shell_encode(script)
stats_r, stats_w = IO.pipe
out, err, status = EnvUtil.invoke_ruby(args,
'', true, true, timeout: timeout, ios: {3 => stats_w}
diff --git a/test/ruby/test_yjit_exit_locations.rb b/test/ruby/test_yjit_exit_locations.rb
new file mode 100644
index 0000000000..5421f9c804
--- /dev/null
+++ b/test/ruby/test_yjit_exit_locations.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+#
+# This set of tests can be run with:
+# make test-all TESTS='test/ruby/test_yjit_exit_locations.rb'
+
+require 'test/unit'
+require 'envutil'
+require 'tmpdir'
+require_relative '../lib/jit_support'
+
+return unless JITSupport.yjit_supported?
+
+# Tests for YJIT with assertions on tracing exits
+# insipired by the MJIT tests in test/ruby/test_yjit.rb
+class TestYJITExitLocations < Test::Unit::TestCase
+ def test_yjit_trace_exits_and_v_no_error
+ _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-trace-exits), '', true, true)
+ refute_includes(stderr, "NoMethodError")
+ end
+
+ def test_trace_exits_setclassvariable
+ script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo'
+ assert_exit_locations(script)
+ end
+
+ def test_trace_exits_putobject
+ assert_exit_locations('true')
+ assert_exit_locations('123')
+ assert_exit_locations(':foo')
+ end
+
+ def test_trace_exits_opt_not
+ assert_exit_locations('!false')
+ assert_exit_locations('!nil')
+ assert_exit_locations('!true')
+ assert_exit_locations('![]')
+ end
+
+ private
+
+ def assert_exit_locations(test_script)
+ write_results = <<~RUBY
+ IO.open(3).write Marshal.dump({
+ enabled: RubyVM::YJIT.trace_exit_locations_enabled?,
+ exit_locations: RubyVM::YJIT.exit_locations
+ })
+ RUBY
+
+ script = <<~RUBY
+ _test_proc = -> {
+ #{test_script}
+ }
+ result = _test_proc.call
+ #{write_results}
+ RUBY
+
+ run_script = eval_with_jit(script)
+ # If stats are disabled when configuring, --yjit-exit-locations
+ # can't be true. We don't want to check if exit_locations hash
+ # is not empty because that could indicate a bug in the exit
+ # locations collection.
+ return unless run_script[:enabled]
+ exit_locations = run_script[:exit_locations]
+
+ assert exit_locations.key?(:raw)
+ assert exit_locations.key?(:frames)
+ assert exit_locations.key?(:lines)
+ assert exit_locations.key?(:samples)
+ assert exit_locations.key?(:missed_samples)
+ assert exit_locations.key?(:gc_samples)
+
+ assert_equal 0, exit_locations[:missed_samples]
+ assert_equal 0, exit_locations[:gc_samples]
+
+ assert_not_empty exit_locations[:raw]
+ assert_not_empty exit_locations[:frames]
+ assert_not_empty exit_locations[:lines]
+
+ exit_locations[:frames].each do |frame_id, frame|
+ assert frame.key?(:name)
+ assert frame.key?(:file)
+ assert frame.key?(:samples)
+ assert frame.key?(:total_samples)
+ assert frame.key?(:edges)
+ end
+ end
+
+ def eval_with_jit(script)
+ args = [
+ "--disable-gems",
+ "--yjit-call-threshold=1",
+ "--yjit-trace-exits"
+ ]
+ args << "-e" << script_shell_encode(script)
+ stats_r, stats_w = IO.pipe
+ _out, _err, _status = EnvUtil.invoke_ruby(args,
+ '', true, true, timeout: 1000, ios: { 3 => stats_w }
+ )
+ stats_w.close
+ stats = stats_r.read
+ stats = Marshal.load(stats) if !stats.empty?
+ stats_r.close
+ stats
+ end
+
+ def script_shell_encode(s)
+ # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants.
+ s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join
+ end
+end