diff options
Diffstat (limited to 'test/ruby')
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 |
