diff options
Diffstat (limited to 'test/ruby')
105 files changed, 13432 insertions, 3560 deletions
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 7048d8d59f..cdde4da9bf 100644 --- a/test/ruby/enc/test_emoji_breaks.rb +++ b/test/ruby/enc/test_emoji_breaks.rb @@ -13,7 +13,7 @@ class TestEmojiBreaks::BreakTest @filename = filename @line_number = line_number @comment = comment.gsub(/\s+/, ' ').strip - if filename=='emoji-test' + if filename=='emoji-test' or filename=='emoji-variation-sequences' codes, @type = data.split(/\s*;\s*/) @shortname = '' else @@ -31,22 +31,42 @@ class TestEmojiBreaks::BreakTest end end +class TestEmojiBreaks::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 self.files + FILES + end +end + class TestEmojiBreaks < Test::Unit::TestCase - EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-variation-sequences emoji-zwj-sequences] - EMOJI_VERSION = RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] - EMOJI_DATA_PATH = File.expand_path("../../../enc/unicode/data/emoji/#{EMOJI_VERSION}", __dir__) + 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'] + EMOJI_DATA_PATH = File.expand_path("../../../enc/unicode/data/emoji/#{EMOJI_VERSION}", __dir__) - def self.expand_filename(basename) - File.expand_path("#{EMOJI_DATA_PATH}/#{basename}.txt", __dir__) + 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 + EMOJI_DATA_FILES << UNICODE_DATA_FILE def self.data_files_available? EMOJI_DATA_FILES.all? do |f| - File.exist?(expand_filename(f)) + File.exist?(f.fullname) end end 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}." end @@ -56,17 +76,26 @@ end TestEmojiBreaks.data_files_available? and class TestEmojiBreaks def read_data tests = [] - EMOJI_DATA_FILES.each do |filename| + EMOJI_DATA_FILES.each do |file| version_mismatch = true file_tests = [] - IO.foreach(TestEmojiBreaks.expand_filename(filename), encoding: Encoding::UTF_8) do |line| + IO.foreach(file.fullname, encoding: Encoding::UTF_8) do |line| line.chomp! - raise "File Name Mismatch" if $.==1 and not line=="# #{filename}.txt" - version_mismatch = false if line=="# Version: #{EMOJI_VERSION}" - next if /\A(#|\z)/.match? line - file_tests << BreakTest.new(filename, $., *line.split('#')) rescue 'whatever' + 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)) + end + else + file_tests << BreakTest.new(file.basename, $., *line.split('#', 2)) + end end - raise "File Version Mismatch" if version_mismatch + raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch tests += file_tests end tests diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb index 2d210946a9..e8f3aa04a7 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 @@ -43,53 +40,53 @@ class TestGraphemeBreaksFromFile < Test::Unit::TestCase skip "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}." end end -end -TestGraphemeBreaksFromFile.file_available? and class TestGraphemeBreaksFromFile - def read_data - tests = [] - IO.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line| - if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt") - raise "File Version Mismatch" + 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 2b252bd441..ec5dc7f220 100644 --- a/test/ruby/enc/test_regex_casefold.rb +++ b/test/ruby/enc/test_regex_casefold.rb @@ -11,7 +11,7 @@ class TestCaseFold < Test::Unit::TestCase def check_downcase_properties(expected, start, *flags) assert_equal expected, start.downcase(*flags) - temp = start + temp = start.dup assert_equal expected, temp.downcase!(*flags) assert_equal expected, expected.downcase(*flags) temp = expected diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb index 5c48a8d853..7f100b7873 100644 --- a/test/ruby/marshaltestlib.rb +++ b/test/ruby/marshaltestlib.rb @@ -112,7 +112,7 @@ module MarshalTestLib marshal_equal(Exception.new('foo')) {|o| o.message} obj = Object.new e = assert_raise(NoMethodError) {obj.no_such_method()} - marshal_equal(e) {|o| o.message} + marshal_equal(e) {|o| o.message.lines.first.chomp} end def test_exception_subclass diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb index 28fb5d1cf8..9bfd7c7599 100644 --- a/test/ruby/sentence.rb +++ b/test/ruby/sentence.rb @@ -353,7 +353,7 @@ class Sentence # * No rule derives to empty sequence # * Underivable rule simplified # * No channel rule - # * Symbols which has zero or one choices are not appered in rhs. + # * Symbols which has zero or one choices are not appeared in rhs. # # Note that the rules which can derive empty and non-empty # sequences are modified to derive only non-empty sequences. diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb index 33fb82e1d7..0d33cb993c 100644 --- a/test/ruby/test_alias.rb +++ b/test/ruby/test_alias.rb @@ -35,6 +35,18 @@ class TestAlias < Test::Unit::TestCase end end + class Alias4 < Alias0 + alias foo1 foo + alias foo2 foo1 + alias foo3 foo2 + end + + class Alias5 < Alias4 + alias foo1 foo + alias foo3 foo2 + alias foo2 foo1 + end + def test_alias x = Alias2.new assert_equal "foo", x.bar @@ -47,6 +59,20 @@ class TestAlias < Test::Unit::TestCase assert_raise(NoMethodError) { x.quux } end + def test_alias_inspect + o = Alias4.new + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1]) + + o = Alias5.new + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1]) + end + def test_nonexistmethod assert_raise(NameError){ Class.new{ @@ -227,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 a76bdccf45..e3bd1cd075 100644 --- a/test/ruby/test_argf.rb +++ b/test/ruby/test_argf.rb @@ -387,6 +387,21 @@ class TestArgf < Test::Unit::TestCase assert_equal("foo", File.read(name+suffix)) end + def test_inplace_bug_17117 + assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}") + {# + #!/usr/bin/ruby -pi.bak + BEGIN { + GC.start + arr = [] + 1000000.times { |x| arr << "fooo#{x}" } + } + puts "hello" + }; + assert_equal("hello\n1\nhello\n2\n", File.read(@t1.path)) + assert_equal("1\n2\n", File.read("#{@t1.path}.bak")) + end + def test_encoding ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| {# @@ -725,6 +740,25 @@ class TestArgf < Test::Unit::TestCase ["\"a\\n\\n\"", "\"b\\n\""], []) end + def test_each_line_chomp + assert_in_out_err(['-e', 'ARGF.each_line(chomp: false) {|para| p para}'], "a\nb\n", + ["\"a\\n\"", "\"b\\n\""], []) + assert_in_out_err(['-e', 'ARGF.each_line(chomp: true) {|para| p para}'], "a\nb\n", + ["\"a\"", "\"b\""], []) + + t = make_tempfile + argf = ARGF.class.new(t.path) + lines = [] + begin + argf.each_line(chomp: true) do |line| + lines << line + end + ensure + argf.close + end + assert_equal(%w[foo bar baz], lines) + end + def test_each_byte ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| {# @@ -971,53 +1005,55 @@ class TestArgf < Test::Unit::TestCase assert_nil(argf.gets, bug4274) end - def test_readlines_twice - bug5952 = '[ruby-dev:45160]' - assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952) + def test_readlines_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal(%w[foo bar baz], argf.readlines(chomp: true)) + ensure + argf.close + end + + assert_in_out_err(['-e', 'p readlines(chomp: true)'], "a\nb\n", + ["[\"a\", \"b\"]"], []) end - def test_lines - ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| - {# - $stderr = $stdout - s = [] - ARGF.lines {|l| s << l } - p s - }; - assert_match(/deprecated/, f.gets) - assert_equal("[\"1\\n\", \"2\\n\", \"3\\n\", \"4\\n\", \"5\\n\", \"6\\n\"]\n", f.read) + def test_readline_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal("foo", argf.readline(chomp: true)) + ensure + argf.close end + + assert_in_out_err(['-e', 'p readline(chomp: true)'], "a\nb\n", + ["\"a\""], []) end - def test_bytes - ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| - {# - $stderr = $stdout - print Marshal.dump(ARGF.bytes.to_a) - }; - assert_match(/deprecated/, f.gets) - assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read)) + def test_gets_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal("foo", argf.gets(chomp: true)) + ensure + argf.close end + + assert_in_out_err(['-e', 'p gets(chomp: true)'], "a\nb\n", + ["\"a\""], []) end - def test_chars - ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| - {# - $stderr = $stdout - print [Marshal.dump(ARGF.chars.to_a)].pack('m') - }; - assert_match(/deprecated/, f.gets) - assert_equal(["1", "\n", "2", "\n", "3", "\n", "4", "\n", "5", "\n", "6", "\n"], Marshal.load(f.read.unpack('m').first)) - end + def test_readlines_twice + bug5952 = '[ruby-dev:45160]' + assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952) end - def test_codepoints + def test_each_codepoint ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| {# - $stderr = $stdout - print Marshal.dump(ARGF.codepoints.to_a) + print Marshal.dump(ARGF.each_codepoint.to_a) }; - assert_match(/deprecated/, f.gets) assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read)) end end @@ -1074,4 +1110,23 @@ class TestArgf < Test::Unit::TestCase assert_raise(TypeError, bug11610) {gets} }; end + + def test_sized_read + s = "a" + [@t1, @t2, @t3].each { |t| + File.binwrite(t.path, s) + s = s.succ + } + + ruby('-e', "print ARGF.read(3)", @t1.path, @t2.path, @t3.path) do |f| + assert_equal("abc", f.read) + end + + argf = ARGF.class.new(@t1.path, @t2.path, @t3.path) + begin + assert_equal("abc", argf.read(3)) + ensure + argf.close + end + end end diff --git a/test/ruby/test_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb index 45a0ab9222..5e2a825265 100644 --- a/test/ruby/test_arithmetic_sequence.rb +++ b/test/ruby/test_arithmetic_sequence.rb @@ -162,11 +162,6 @@ class TestArithmeticSequence < Test::Unit::TestCase assert_equal([], seq.first(1)) assert_equal([], seq.first(3)) - seq = 1.step(10, by: 0) - assert_equal(1, seq.first) - assert_equal([1], seq.first(1)) - assert_equal([1, 1, 1], seq.first(3)) - seq = 10.0.step(-1.0, by: -2.0) assert_equal(10.0, seq.first) assert_equal([10.0], seq.first(1)) @@ -269,6 +264,11 @@ class TestArithmeticSequence < Test::Unit::TestCase assert_instance_of Integer, res[1] end + def test_last_bug17218 + seq = (1.0997r .. 1.1r).step(0.0001r) + assert_equal(1.1r, seq.last, '[ruby-core:100312] [Bug #17218]') + end + def test_to_a assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1.step(10).to_a) assert_equal([1, 3, 5, 7, 9], 1.step(10, 2).to_a) @@ -284,6 +284,11 @@ class TestArithmeticSequence < Test::Unit::TestCase '[ruby-core:90648] [Bug #15444]') end + def test_to_a_bug17218 + seq = (1.0997r .. 1.1r).step(0.0001r) + assert_equal([1.0997r, 1.0998r, 1.0999r, 1.1r], seq.to_a, '[ruby-core:100312] [Bug #17218]') + end + def test_slice seq = 1.step(10, 2) assert_equal([[1, 3, 5], [7, 9]], seq.each_slice(3).to_a) @@ -340,9 +345,6 @@ class TestArithmeticSequence < Test::Unit::TestCase [10, 8, 6, 4, 2].each do |i| assert_equal(i, seq.next) end - - seq = ((1/10r)..(1/2r)).step(0) - assert_equal(1/10r, seq.next) end def test_next_bug15444 diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 476cf795f0..0ed8ada95c 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -7,7 +7,6 @@ require "rbconfig/sizeof" class TestArray < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil @cls = Array end @@ -15,6 +14,11 @@ class TestArray < Test::Unit::TestCase $VERBOSE = @verbose end + def assert_equal_instance(x, y, *msg) + assert_equal(x, y, *msg) + assert_instance_of(x.class, y) + end + def test_percent_i assert_equal([:foo, :bar], %i[foo bar]) assert_equal([:"\"foo"], %i["foo]) @@ -114,6 +118,9 @@ class TestArray < Test::Unit::TestCase assert_equal('1', (x * 1).join(":")) assert_equal('', (x * 0).join(":")) + assert_instance_of(Array, (@cls[] * 5)) + assert_instance_of(Array, (@cls[1] * 5)) + *x = *(1..7).to_a assert_equal(7, x.size) assert_equal([1, 2, 3, 4, 5, 6, 7], x) @@ -645,8 +652,17 @@ class TestArray < Test::Unit::TestCase b.concat(b, b) assert_equal([4, 5, 4, 5, 4, 5], b) - assert_raise(TypeError) { [0].concat(:foo) } - assert_raise(FrozenError) { [0].freeze.concat(:foo) } + assert_raise(TypeError) { @cls[0].concat(:foo) } + assert_raise(FrozenError) { @cls[0].freeze.concat(:foo) } + + a = @cls[nil] + def (x = Object.new).to_ary + ary = Array.new(2) + ary << [] << [] << :ok + end + EnvUtil.under_gc_stress {a.concat(x)} + GC.start + assert_equal(:ok, a.last) end def test_count @@ -654,7 +670,7 @@ class TestArray < Test::Unit::TestCase assert_equal(5, a.count) assert_equal(2, a.count(1)) assert_equal(3, a.count {|x| x % 2 == 1 }) - assert_equal(2, a.count(1) {|x| x % 2 == 1 }) + assert_equal(2, assert_warning(/given block not used/) {a.count(1) {|x| x % 2 == 1 }}) assert_raise(ArgumentError) { a.count(0, 1) } bug8654 = '[ruby-core:56072]' @@ -747,6 +763,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 5, 6, 7, 8, 9, 10 ] assert_equal(9, a.delete_if {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.delete_if do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_dup @@ -842,14 +867,14 @@ class TestArray < Test::Unit::TestCase a2 = @cls[ 5, 6 ] a3 = @cls[ 4, a2 ] a4 = @cls[ a1, a3 ] - assert_equal(@cls[1, 2, 3, 4, 5, 6], a4.flatten) - assert_equal(@cls[ a1, a3], a4) + assert_equal_instance([1, 2, 3, 4, 5, 6], a4.flatten) + assert_equal_instance(@cls[ a1, a3], a4) a5 = @cls[ a1, @cls[], a3 ] - assert_equal(@cls[1, 2, 3, 4, 5, 6], a5.flatten) - assert_equal(@cls[1, 2, 3, 4, [5, 6]], a5.flatten(1)) - assert_equal(@cls[], @cls[].flatten) - assert_equal(@cls[], + assert_equal_instance([1, 2, 3, 4, 5, 6], a5.flatten) + assert_equal_instance([1, 2, 3, 4, [5, 6]], a5.flatten(1)) + assert_equal_instance([], @cls[].flatten) + assert_equal_instance([], @cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten) end @@ -886,6 +911,17 @@ class TestArray < Test::Unit::TestCase assert_raise(NoMethodError, bug12738) { a.flatten.m } end + def test_flatten_recursive + a = [] + a << a + assert_raise(ArgumentError) { a.flatten } + b = [1]; c = [2, b]; b << c + assert_raise(ArgumentError) { b.flatten } + + assert_equal([1, 2, b], b.flatten(1)) + assert_equal([1, 2, 1, 2, 1, c], b.flatten(4)) + end + def test_flatten! a1 = @cls[ 1, 2, 3] a2 = @cls[ 5, 6 ] @@ -1073,6 +1109,19 @@ class TestArray < Test::Unit::TestCase assert_not_include(a, [1,2]) end + def test_intersect? + a = @cls[ 1, 2, 3] + assert_send([a, :intersect?, [3]]) + assert_not_send([a, :intersect?, [4]]) + assert_not_send([a, :intersect?, []]) + end + + def test_intersect_big_array + assert_send([@cls[ 1, 4, 5 ]*64, :intersect?, @cls[ 1, 2, 3 ]*64]) + assert_not_send([@cls[ 1, 2, 3 ]*64, :intersect?, @cls[ 4, 5, 6 ]*64]) + assert_not_send([@cls[], :intersect?, @cls[ 1, 2, 3 ]*64]) + end + def test_index a = @cls[ 'cat', 99, /a/, 99, @cls[ 1, 2, 3] ] assert_equal(0, a.index('cat')) @@ -1081,7 +1130,7 @@ class TestArray < Test::Unit::TestCase assert_nil(a.index('ca')) assert_nil(a.index([1,2])) - assert_equal(1, a.index(99) {|x| x == 'cat' }) + assert_equal(1, assert_warn(/given block not used/) {a.index(99) {|x| x == 'cat' }}) end def test_values_at @@ -1092,42 +1141,42 @@ class TestArray < Test::Unit::TestCase end def test_join - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[] - assert_equal("", a.join) + assert_equal("", assert_deprecated_warn(/non-nil value/) {a.join}) assert_equal("", a.join(',')) - assert_equal(Encoding::US_ASCII, a.join.encoding) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {a.join}.encoding) - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[1, 2] - assert_equal("12", a.join) - assert_equal("12", a.join(nil)) + assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) assert_equal("1,2", a.join(',')) - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[1, 2, 3] - assert_equal("123", a.join) - assert_equal("123", a.join(nil)) + assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) assert_equal("1,2,3", a.join(',')) - $, = ":" + assert_deprecated_warning {$, = ":"} a = @cls[1, 2, 3] - assert_equal("1:2:3", a.join) - assert_equal("1:2:3", a.join(nil)) + assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) assert_equal("1,2,3", a.join(',')) - $, = "" + assert_deprecated_warning {$, = ""} e = ''.force_encoding('EUC-JP') u = ''.force_encoding('UTF-8') - assert_equal(Encoding::US_ASCII, [[]].join.encoding) - assert_equal(Encoding::US_ASCII, [1, [u]].join.encoding) - assert_equal(Encoding::UTF_8, [u, [e]].join.encoding) - assert_equal(Encoding::UTF_8, [u, [1]].join.encoding) - assert_equal(Encoding::UTF_8, [Struct.new(:to_str).new(u)].join.encoding) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[]].join}.encoding) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[1, [u]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [e]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [1]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[Struct.new(:to_str).new(u)].join}.encoding) bug5379 = '[ruby-core:39776]' - assert_equal(Encoding::US_ASCII, [[], u, nil].join.encoding, bug5379) - assert_equal(Encoding::UTF_8, [[], "\u3042", nil].join.encoding, bug5379) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[], u, nil].join}.encoding, bug5379) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[[], "\u3042", nil].join}.encoding, bug5379) ensure $, = nil end @@ -1245,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) @@ -1304,6 +1359,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 5, 6, 7, 8, 9, 10 ] assert_equal(9, a.reject! {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.reject! do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_shared_array_reject! @@ -1421,7 +1485,7 @@ class TestArray < Test::Unit::TestCase assert_nil(a.rindex('ca')) assert_nil(a.rindex([1,2])) - assert_equal(3, a.rindex(99) {|x| x == [1,2,3] }) + assert_equal(3, assert_warning(/given block not used/) {a.rindex(99) {|x| x == [1,2,3] }}) bug15951 = "[Bug #15951]" o2 = Object.new @@ -1461,35 +1525,74 @@ class TestArray < Test::Unit::TestCase assert_equal(1, a.slice(-100)) assert_nil(a.slice(-101)) - assert_equal(@cls[1], a.slice(0,1)) - assert_equal(@cls[100], a.slice(99,1)) - assert_equal(@cls[], a.slice(100,1)) - assert_equal(@cls[100], a.slice(99,100)) - assert_equal(@cls[100], a.slice(-1,1)) - assert_equal(@cls[99], a.slice(-2,1)) + assert_equal_instance([1], a.slice(0,1)) + assert_equal_instance([100], a.slice(99,1)) + assert_equal_instance([], a.slice(100,1)) + assert_equal_instance([100], a.slice(99,100)) + assert_equal_instance([100], a.slice(-1,1)) + assert_equal_instance([99], a.slice(-2,1)) - assert_equal(@cls[10, 11, 12], a.slice(9, 3)) - assert_equal(@cls[10, 11, 12], a.slice(-91, 3)) + assert_equal_instance([10, 11, 12], a.slice(9, 3)) + assert_equal_instance([10, 11, 12], a.slice(-91, 3)) assert_nil(a.slice(-101, 2)) - assert_equal(@cls[1], a.slice(0..0)) - assert_equal(@cls[100], a.slice(99..99)) - assert_equal(@cls[], a.slice(100..100)) - assert_equal(@cls[100], a.slice(99..200)) - assert_equal(@cls[100], a.slice(-1..-1)) - assert_equal(@cls[99], a.slice(-2..-2)) - - assert_equal(@cls[10, 11, 12], a.slice(9..11)) - assert_equal(@cls[98, 99, 100], a.slice(97..)) - assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) - assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) + assert_equal_instance([1], a.slice(0..0)) + assert_equal_instance([100], a.slice(99..99)) + assert_equal_instance([], a.slice(100..100)) + assert_equal_instance([100], a.slice(99..200)) + assert_equal_instance([100], a.slice(-1..-1)) + assert_equal_instance([99], a.slice(-2..-2)) + + assert_equal_instance([10, 11, 12], a.slice(9..11)) + assert_equal_instance([98, 99, 100], a.slice(97..)) + assert_equal_instance([10, 11, 12], a.slice(-91..-89)) + assert_equal_instance([10, 11, 12], a.slice(-91..-89)) + + assert_equal_instance([5, 8, 11], a.slice((4..12)%3)) + assert_equal_instance([95, 97, 99], a.slice((94..)%2)) + + # [0] [1] [2] [3] [4] [5] [6] [7] + # ary = [ 1 2 3 4 5 6 7 8 ... ] + # (0) (1) (2) <- (..7) % 3 + # (2) (1) (0) <- (7..) % -3 + assert_equal_instance([1, 4, 7], a.slice((..7)%3)) + assert_equal_instance([8, 5, 2], a.slice((7..)% -3)) + + # [-98] [-97] [-96] [-95] [-94] [-93] [-92] [-91] [-90] + # ary = [ ... 3 4 5 6 7 8 9 10 11 ... ] + # (0) (1) (2) <- (-98..-90) % 3 + # (2) (1) (0) <- (-90..-98) % -3 + assert_equal_instance([3, 6, 9], a.slice((-98..-90)%3)) + assert_equal_instance([11, 8, 5], a.slice((-90..-98)% -3)) + + # [ 48] [ 49] [ 50] [ 51] [ 52] [ 53] + # [-52] [-51] [-50] [-49] [-48] [-47] + # ary = [ ... 49 50 51 52 53 54 ... ] + # (0) (1) (2) <- (48..-47) % 2 + # (2) (1) (0) <- (-47..48) % -2 + assert_equal_instance([49, 51, 53], a.slice((48..-47)%2)) + assert_equal_instance([54, 52, 50], a.slice((-47..48)% -2)) + + idx = ((3..90) % 2).to_a + 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)) + end + + def test_slice_out_of_range + a = @cls[*(1..100).to_a] assert_nil(a.slice(-101..-1)) assert_nil(a.slice(-101..)) + assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.slice((-101..-1)%2) } + assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.slice((-101..)%2) } + assert_nil(a.slice(10, -3)) assert_equal @cls[], a.slice(10..7) + + assert_equal([100], a.slice(-1, 1_000_000_000)) end def test_slice! @@ -1502,15 +1605,18 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[1, 2, 3, 5], a) a = @cls[1, 2, 3, 4, 5] - assert_equal(@cls[3,4], a.slice!(2,2)) + s = a.slice!(2,2) + assert_equal_instance([3,4], s) assert_equal(@cls[1, 2, 5], a) a = @cls[1, 2, 3, 4, 5] - assert_equal(@cls[4,5], a.slice!(-2,2)) + s = a.slice!(-2,2) + assert_equal_instance([4,5], s) assert_equal(@cls[1, 2, 3], a) a = @cls[1, 2, 3, 4, 5] - assert_equal(@cls[3,4], a.slice!(2..3)) + s = a.slice!(2..3) + assert_equal_instance([3,4], s) assert_equal(@cls[1, 2, 5], a) a = @cls[1, 2, 3, 4, 5] @@ -1535,6 +1641,21 @@ class TestArray < Test::Unit::TestCase assert_raise(ArgumentError) { @cls[1].slice!(0, 0, 0) } end + def test_slice_out_of_range! + a = @cls[*(1..100).to_a] + + assert_nil(a.clone.slice!(-101..-1)) + assert_nil(a.clone.slice!(-101..)) + + # assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.clone.slice!((-101..-1)%2) } + # assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.clone.slice!((-101..)%2) } + + assert_nil(a.clone.slice!(10, -3)) + assert_equal @cls[], a.clone.slice!(10..7) + + assert_equal([100], a.clone.slice!(-1, 1_000_000_000)) + end + def test_sort a = @cls[ 4, 1, 2, 3 ] assert_equal(@cls[1, 2, 3, 4], a.sort) @@ -1574,6 +1695,37 @@ class TestArray < Test::Unit::TestCase assert_equal([1, 2, 3, 4], a) end + def test_freeze_inside_sort! + array = [1, 2, 3, 4, 5] + frozen_array = nil + assert_raise(FrozenError) do + count = 0 + array.sort! do |a, b| + array.freeze if (count += 1) == 6 + frozen_array ||= array.map.to_a if array.frozen? + b <=> a + end + end + assert_equal(frozen_array, array) + + object = Object.new + array = [1, 2, 3, 4, 5] + object.define_singleton_method(:>){|_| array.freeze; true} + assert_raise(FrozenError) do + array.sort! do |a, b| + object + end + end + + object = Object.new + array = [object, object] + object.define_singleton_method(:>){|_| array.freeze; true} + object.define_singleton_method(:<=>){|o| object} + assert_raise(FrozenError) do + array.sort! + end + end + def test_sort_with_callcc need_continuation n = 1000 @@ -1637,6 +1789,13 @@ class TestArray < Test::Unit::TestCase TEST end + def test_sort_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].sort} + assert_raise(ArgumentError) {[1.0, Float::NAN].sort} + assert_raise(ArgumentError) {[Float::NAN, 1].sort} + assert_raise(ArgumentError) {[Float::NAN, 1.0].sort} + end + def test_to_a a = @cls[ 1, 2, 3 ] a_id = a.__id__ @@ -1668,19 +1827,19 @@ class TestArray < Test::Unit::TestCase end def test_to_s - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[] assert_equal("[]", a.to_s) - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[1, 2] assert_equal("[1, 2]", a.to_s) - $, = "" + assert_deprecated_warning {$, = ""} a = @cls[1, 2, 3] assert_equal("[1, 2, 3]", a.to_s) - $, = ":" + assert_deprecated_warning {$, = ""} a = @cls[1, 2, 3] assert_equal("[1, 2, 3]", a.to_s) ensure @@ -1734,10 +1893,12 @@ class TestArray < Test::Unit::TestCase end def test_min + assert_equal(3, [3].min) assert_equal(1, [1, 2, 3, 1, 2].min) assert_equal(3, [1, 2, 3, 1, 2].min {|a,b| b <=> a }) cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } assert_equal([3, 2], [1, 2, 3, 1, 2].each_with_index.min(&cond)) + assert_equal(1.0, [3.0, 1.0, 2.0].min) ary = %w(albatross dog horse) assert_equal("albatross", ary.min) assert_equal("dog", ary.min {|a,b| a.length <=> b.length }) @@ -1755,11 +1916,20 @@ class TestArray < Test::Unit::TestCase assert_same(obj, [obj, 1.0].min) end + def test_min_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].min} + assert_raise(ArgumentError) {[1.0, Float::NAN].min} + assert_raise(ArgumentError) {[Float::NAN, 1].min} + assert_raise(ArgumentError) {[Float::NAN, 1.0].min} + end + def test_max + assert_equal(1, [1].max) assert_equal(3, [1, 2, 3, 1, 2].max) assert_equal(1, [1, 2, 3, 1, 2].max {|a,b| b <=> a }) cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } assert_equal([1, 3], [1, 2, 3, 1, 2].each_with_index.max(&cond)) + assert_equal(3.0, [1.0, 3.0, 2.0].max) ary = %w(albatross dog horse) assert_equal("horse", ary.max) assert_equal("albatross", ary.max {|a,b| a.length <=> b.length }) @@ -1776,7 +1946,15 @@ class TestArray < Test::Unit::TestCase assert_same(obj, [obj, 1.0].max) end + def test_max_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].max} + assert_raise(ArgumentError) {[1.0, Float::NAN].max} + assert_raise(ArgumentError) {[Float::NAN, 1].max} + assert_raise(ArgumentError) {[Float::NAN, 1.0].max} + end + def test_minmax + assert_equal([3, 3], [3].minmax) assert_equal([1, 3], [1, 2, 3, 1, 2].minmax) assert_equal([3, 1], [1, 2, 3, 1, 2].minmax {|a,b| b <=> a }) cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } @@ -1847,26 +2025,22 @@ class TestArray < Test::Unit::TestCase sc = Class.new(@cls) a = sc[] b = a.dup - assert_instance_of(sc, a.uniq) - assert_equal(sc[], a.uniq) + assert_equal_instance([], a.uniq) assert_equal(b, a) a = sc[1] b = a.dup - assert_instance_of(sc, a.uniq) - assert_equal(sc[1], a.uniq) + assert_equal_instance([1], a.uniq) assert_equal(b, a) a = sc[1, 1] b = a.dup - assert_instance_of(sc, a.uniq) - assert_equal(sc[1], a.uniq) + assert_equal_instance([1], a.uniq) assert_equal(b, a) a = sc[1, 1] b = a.dup - assert_instance_of(sc, a.uniq{|x| x}) - assert_equal(sc[1], a.uniq{|x| x}) + assert_equal_instance([1], a.uniq{|x| x}) assert_equal(b, a) end @@ -2281,23 +2455,23 @@ class TestArray < Test::Unit::TestCase end def test_take - assert_equal([1,2,3], [1,2,3,4,5,0].take(3)) + assert_equal_instance([1,2,3], @cls[1,2,3,4,5,0].take(3)) assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].take(-1) } - assert_equal([1,2], [1,2].take(1000000000), '[ruby-dev:34123]') + assert_equal_instance([1,2], @cls[1,2].take(1000000000), '[ruby-dev:34123]') end def test_take_while - assert_equal([1,2], [1,2,3,4,5,0].take_while {|i| i < 3 }) + assert_equal_instance([1,2], @cls[1,2,3,4,5,0].take_while {|i| i < 3 }) end def test_drop - assert_equal([4,5,0], [1,2,3,4,5,0].drop(3)) + assert_equal_instance([4,5,0], @cls[1,2,3,4,5,0].drop(3)) assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].drop(-1) } - assert_equal([], [1,2].drop(1000000000), '[ruby-dev:34123]') + assert_equal_instance([], @cls[1,2].drop(1000000000), '[ruby-dev:34123]') end def test_drop_while - assert_equal([3,4,5,0], [1,2,3,4,5,0].drop_while {|i| i < 3 }) + assert_equal_instance([3,4,5,0], @cls[1,2,3,4,5,0].drop_while {|i| i < 3 }) end LONGP = [127, 63, 31, 15, 7].map {|x| 2**x-1 }.find do |x| @@ -2322,13 +2496,13 @@ class TestArray < Test::Unit::TestCase def test_initialize assert_nothing_raised { [].instance_eval { initialize } } - assert_nothing_raised { Array.new { } } + assert_warning(/given block not used/) { Array.new { } } assert_equal([1, 2, 3], Array.new([1, 2, 3])) assert_raise(ArgumentError) { Array.new(-1, 1) } assert_raise(ArgumentError) { Array.new(LONGP, 1) } assert_equal([1, 1, 1], Array.new(3, 1)) assert_equal([1, 1, 1], Array.new(3) { 1 }) - assert_equal([1, 1, 1], Array.new(3, 1) { 1 }) + assert_equal([1, 1, 1], assert_warning(/block supersedes default value argument/) {Array.new(3, 1) { 1 }}) end def test_aset_error @@ -2344,6 +2518,9 @@ class TestArray < Test::Unit::TestCase assert_raise(ArgumentError) { [0].freeze[0, 0, 0] = 0 } assert_raise(TypeError) { [0][:foo] = 0 } assert_raise(FrozenError) { [0].freeze[:foo] = 0 } + + # [Bug #17271] + assert_raise_with_message(RangeError, "-7.. out of range") { [*0..5][-7..] = 1 } end def test_first2 @@ -2374,11 +2551,11 @@ class TestArray < Test::Unit::TestCase def test_aref assert_raise(ArgumentError) { [][0, 0, 0] } - assert_raise(TypeError) { [][(1..10).step(2)] } + assert_raise(ArgumentError) { @cls[][0, 0, 0] } end def test_fetch - assert_equal(1, [].fetch(0, 0) { 1 }) + assert_equal(1, assert_warning(/block supersedes default value argument/) {[].fetch(0, 0) { 1 }}) assert_equal(1, [0, 1].fetch(-1)) assert_raise(IndexError) { [0, 1].fetch(2) } assert_raise(IndexError) { [0, 1].fetch(-3) } @@ -2447,6 +2624,27 @@ class TestArray < Test::Unit::TestCase assert_equal("12345", [1,[2,[3,4],5]].join) end + def test_join_recheck_elements_type + x = Struct.new(:ary).new + def x.to_str + ary[2] = [0, 1, 2] + "z" + end + (x.ary = ["a", "b", "c", x]) + assert_equal("ab012z", x.ary.join("")) + end + + def test_join_recheck_array_length + x = Struct.new(:ary).new + def x.to_str + ary.clear + ary[0] = "b" + "z" + end + x.ary = Array.new(1023) {"a"*1} << x + assert_equal("b", x.ary.join("")) + end + def test_to_a2 klass = Class.new(Array) a = klass.new.to_a @@ -2495,6 +2693,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] a.select! {|i| a.clear if i == 5; false } assert_equal(0, a.size, bug13053) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.select! do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) end # also select! @@ -2510,6 +2717,15 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(a, a.keep_if { |i| i > 3 }) assert_equal(@cls[4, 5], a) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.keep_if do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) end def test_filter @@ -2575,13 +2791,24 @@ class TestArray < Test::Unit::TestCase def test_zip_bug bug8153 = "ruby-core:53650" - r = 1..1 + r = [1] def r.respond_to?(*) super end assert_equal [[42, 1]], [42].zip(r), bug8153 end + def test_zip_with_enumerator + bug17814 = "ruby-core:103513" + + step = 0.step + e = Enumerator.produce { step.next } + a = %w(a b c) + assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e), bug17814) + assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e), bug17814) + assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e), bug17814) + end + def test_transpose assert_equal([[1, :a], [2, :b], [3, :c]], [[1, 2, 3], [:a, :b, :c]].transpose) @@ -2608,25 +2835,21 @@ class TestArray < Test::Unit::TestCase assert_not_equal([0, 1, 2], [0, 1, 3]) end - A = Array.new(3, &:to_s) - B = A.dup - def test_equal_resize + $test_equal_resize_a = Array.new(3, &:to_s) + $test_equal_resize_b = $test_equal_resize_a.dup o = Object.new def o.==(o) - A.clear - B.clear + $test_equal_resize_a.clear + $test_equal_resize_b.clear true end - A[1] = o - assert_equal(A, B) + $test_equal_resize_a[1] = o + assert_equal($test_equal_resize_a, $test_equal_resize_b) end def test_flatten_error a = [] - a << a - assert_raise(ArgumentError) { a.flatten } - f = [].freeze assert_raise(ArgumentError) { a.flatten!(1, 2) } assert_raise(TypeError) { a.flatten!(:foo) } @@ -2861,15 +3084,6 @@ class TestArray < Test::Unit::TestCase end end - class Array2 < Array - end - - def test_array_subclass - assert_equal(Array2, Array2[1,2,3].uniq.class, "[ruby-dev:34581]") - assert_equal(Array2, Array2[1,2][0,1].class) # embedded - assert_equal(Array2, Array2[*(1..100)][1..99].class) #not embedded - end - def test_initialize2 a = [1] * 1000 a.instance_eval { initialize } @@ -3004,6 +3218,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 @@ -3039,6 +3255,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 @@ -3209,3 +3427,23 @@ class TestArray < Test::Unit::TestCase end end end + +class TestArraySubclass < TestArray + def setup + @verbose = $VERBOSE + @cls = Class.new(Array) + end + + def test_to_a + a = @cls[ 1, 2, 3 ] + a_id = a.__id__ + assert_equal_instance([1, 2, 3], a.to_a) + assert_not_equal(a_id, a.to_a.__id__) + end + + def test_array_subclass + assert_equal(Array, @cls[1,2,3].uniq.class, "[ruby-dev:34581]") + assert_equal(Array, @cls[1,2][0,1].class) # embedded + assert_equal(Array, @cls[*(1..100)][1..99].class) #not embedded + end +end diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index bf6602ab13..41e8bffe82 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -81,6 +81,64 @@ class TestAssignment < Test::Unit::TestCase a,b,*c = [*[1,2]]; assert_equal([1,2,[]], [a,b,c]) end + def test_massign_order + order = [] + define_singleton_method(:x1){order << :x1; self} + define_singleton_method(:y1){order << :y1; self} + define_singleton_method(:z=){|x| order << [:z=, x]} + define_singleton_method(:x2){order << :x2; self} + define_singleton_method(:x3){order << :x3; self} + define_singleton_method(:x4){order << :x4; self} + define_singleton_method(:x5=){|x| order << [:x5=, x]; self} + define_singleton_method(:[]=){|*args| order << [:[]=, *args]} + define_singleton_method(:r1){order << :r1; :r1} + define_singleton_method(:r2){order << :r2; :r2} + + x1.y1.z, x2[1, 2, 3], self[4] = r1, 6, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, 6], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], self[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :x3, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4], x4.x5 = r1, 6, 7, r2, 8 + assert_equal([:x1, :y1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x2.x5), _a = [r1, r2], 7 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:x5=, :r2]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3] = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7, :r2, 8]]], order) + order.clear + + *x2[1, 2, 3], (x3[4], x4.x5) = 6, 7, [r2, 8] + assert_equal([:x2, :x3, :x4, :r2, [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], x3[4], x4.x5 = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], (x3[4], x4.x5) = [r1, 5], 6, 7, [r2, 8] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, x1.x5), _a), *x2[1, 2, 3], ((x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, *x1.x5), _a), *x2[1, 2, 3], ((*x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, [5]], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, [:r2]], [:x5=, 8]], order) + order.clear + 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]) @@ -456,7 +514,7 @@ class TestAssignment < Test::Unit::TestCase assert(defined?(a)) assert_nil(a) - # multiple asignment + # multiple assignment a, b = 1, 2 assert_equal 1, a assert_equal 2, b diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 4c156650b8..cd7299f200 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -42,10 +42,11 @@ class TestAst < Test::Unit::TestCase class Helper attr_reader :errors - def initialize(path) + def initialize(path, src: nil) @path = path @errors = [] @debug = false + @ast = RubyVM::AbstractSyntaxTree.parse(src) if src end def validate_range @@ -184,7 +185,7 @@ class TestAst < Test::Unit::TestCase end end - def test_of + def test_of_proc_and_method proc = Proc.new { 1 + 2 } method = self.method(__method__) @@ -193,7 +194,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;'}" @@ -210,6 +210,122 @@ class TestAst < Test::Unit::TestCase end end + 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) } + + method = self.method(eval("def self.example_singleton_method_#{$$}; end")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("proc{}") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}")) + 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) } + + 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 + 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 + 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 + c = Class.new { attr_reader :foo } + assert_nil(RubyVM::AbstractSyntaxTree.of(c.instance_method(:foo))) + end + def test_scope_local_variables node = RubyVM::AbstractSyntaxTree.parse("_x = 0") lv, _, body = *node.children @@ -252,6 +368,19 @@ class TestAst < Test::Unit::TestCase mid, defn = body.children assert_equal(:a, mid) assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_defn_endless + node = RubyVM::AbstractSyntaxTree.parse("def a = nil") + _, _, body = *node.children + assert_equal(:DEFN, body.type) + mid, defn = body.children + assert_equal(:a, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) end def test_defs @@ -262,6 +391,20 @@ class TestAst < Test::Unit::TestCase assert_equal(:VCALL, recv.type) assert_equal(:b, mid) assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_defs_endless + node = RubyVM::AbstractSyntaxTree.parse("def a.b = nil") + _, _, body = *node.children + assert_equal(:DEFS, body.type) + recv, mid, defn = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:b, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) end def test_dstr @@ -312,4 +455,104 @@ class TestAst < Test::Unit::TestCase assert_equal(false, kwrest.call('**nil')) assert_equal([:a], kwrest.call('**a')) end + + def test_ranges_numbered_parameter + helper = Helper.new(__FILE__, src: "1.times {_1}") + helper.validate_range + assert_equal([], helper.errors) + end + + def test_op_asgn2 + node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo") + _, _, body = *node.children + assert_equal(:OP_ASGN2, body.type) + recv, _, mid, op, value = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:field, mid) + assert_equal(:+, op) + assert_equal(:VCALL, value.type) + end + + def test_args + rest = 6 + node = RubyVM::AbstractSyntaxTree.parse("proc { |a| }") + _, args = *node.children.last.children[1].children + assert_equal(nil, args.children[rest]) + + node = RubyVM::AbstractSyntaxTree.parse("proc { |a,| }") + _, args = *node.children.last.children[1].children + assert_equal(:NODE_SPECIAL_EXCESSIVE_COMMA, args.children[rest]) + + node = RubyVM::AbstractSyntaxTree.parse("proc { |*a| }") + _, args = *node.children.last.children[1].children + assert_equal(:a, args.children[rest]) + end + + def test_keep_script_lines_for_parse + node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_script_lines: true) +1.times do + 2.times do + end +end +__END__ +dummy + END + + expected = [ + "1.times do\n", + " 2.times do\n", + " end\n", + "end\n", + "__END__\n", + ] + assert_equal(expected, node.script_lines) + + expected = + "1.times do\n" + + " 2.times do\n" + + " end\n" + + "end" + assert_equal(expected, node.source) + + expected = + "do\n" + + " 2.times do\n" + + " end\n" + + "end" + assert_equal(expected, node.children.last.children.last.source) + + expected = + "2.times do\n" + + " end" + assert_equal(expected, node.children.last.children.last.children.last.source) + end + + def test_keep_script_lines_for_of + proc = Proc.new { 1 + 2 } + method = self.method(__method__) + + node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true) + node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true) + + 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 + 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]) + end + + def test_e_option + assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"], + "", [":SCOPE"], []) + end end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 2e53c9203d..7010645317 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -65,7 +65,27 @@ 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) + lp = $LOAD_PATH.dup lf = $LOADED_FEATURES.dup @@ -425,8 +445,25 @@ p Foo::Bar end end - def test_no_leak - assert_no_memory_leak([], '', <<~'end;', 'many autoloads', timeout: 60) + def test_source_location + bug = "Bug16764" + 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(:Bug16764, #{path.dump}) + assert_equal [__FILE__, __LINE__-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; 200000.times do |i| m = Module.new m.instance_eval do @@ -437,6 +474,31 @@ p Foo::Bar end; end + def test_autoload_after_failed_and_removed_from_loaded_features + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "test-bug-15790.rb") + File.write(autoload_path, '') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + path = #{File.realpath(autoload_path).inspect} + autoload :X, path + assert_equal(path, Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_nil(Object.autoload?(:X)) + assert_equal(false, Object.const_defined?(:X)) + + $LOADED_FEATURES.delete(path) + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + RUBY + end + end + def add_autoload(path) (@autoload_paths ||= []) << path ::Object.class_eval {autoload(:AutoloadTest, path)} @@ -444,6 +506,7 @@ p Foo::Bar def remove_autoload_constant $".replace($" - @autoload_paths) - ::Object.class_eval {remove_const(:AutoloadTest)} + ::Object.class_eval {remove_const(:AutoloadTest)} if defined? Object::AutoloadTest + TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest end end diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index 00c96b3b9f..aa79db24cb 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -138,10 +138,66 @@ class TestBacktrace < Test::Unit::TestCase rec[m] end + def test_caller_with_limit + x = nil + c = Class.new do + define_method(:bar) do + x = caller(1, 1) + end + end + [c.new].group_by(&:bar) + assert_equal 1, x.length + assert_equal caller(0), caller(0, nil) + end + def test_caller_with_nil_length assert_equal caller(0), caller(0, nil) end + def test_caller_locations_first_label + def self.label + caller_locations.first.label + end + + def self.label_caller + label + end + + assert_equal 'label_caller', label_caller + + [1].group_by do + assert_equal 'label_caller', label_caller + end + end + + def test_caller_limit_cfunc_iseq_no_pc + def self.a; [1].group_by { b } end + def self.b + [ + caller_locations(2, 1).first.base_label, + caller_locations(3, 1).first.base_label + ] + end + assert_equal({["each", "group_by"]=>[1]}, a) + end + + def test_caller_location_inspect_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1).inspect + end + @line = __LINE__ + 1 + 1.times.map { 1.times.map { foo } } + assert_equal("[\"#{__FILE__}:#{@line}:in `times'\"]", @res) + end + + def test_caller_location_path_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1)[0].path + end + 1.times.map { 1.times.map { foo } } + assert_equal(__FILE__, @res) + end + def test_caller_locations cs = caller(0); locs = caller_locations(0).map{|loc| loc.to_s diff --git a/test/ruby/test_basicinstructions.rb b/test/ruby/test_basicinstructions.rb index ab32ee54e2..f6b69cc1e5 100644 --- a/test/ruby/test_basicinstructions.rb +++ b/test/ruby/test_basicinstructions.rb @@ -428,7 +428,9 @@ class TestBasicInstructions < Test::Unit::TestCase end class CVarA - @@cv = 'CVarA@@cv' + def self.setup + @@cv = 'CVarA@@cv' + end def self.cv() @@cv end def self.cv=(v) @@cv = v end class << self @@ -449,6 +451,7 @@ class TestBasicInstructions < Test::Unit::TestCase end def test_class_variable + CVarA.setup assert_equal 'CVarA@@cv', CVarA.cv assert_equal 'CVarA@@cv', CVarA.cv2 assert_equal 'CVarA@@cv', CVarA.new.cv diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb index 434c5befd9..3ffe7114b5 100644 --- a/test/ruby/test_bignum.rb +++ b/test/ruby/test_bignum.rb @@ -19,23 +19,22 @@ class TestBignum < Test::Unit::TestCase end BIGNUM_MIN_BITS = n - T_ZERO = 0.to_bignum - T_ONE = 1.to_bignum - T_MONE = (-1).to_bignum - T31 = (2**31).to_bignum # 2147483648 - T31P = (T31 - 1).to_bignum # 2147483647 - T32 = (2**32).to_bignum # 4294967296 - T32P = (T32 - 1).to_bignum # 4294967295 - T64 = (2**64).to_bignum # 18446744073709551616 - T64P = (T64 - 1).to_bignum # 18446744073709551615 - T128 = (2**128).to_bignum - T128P = (T128 - 1).to_bignum - T1024 = (2**1024).to_bignum - T1024P = (T1024 - 1).to_bignum + T_ZERO = Bug::Integer.to_bignum(0) + T_ONE = Bug::Integer.to_bignum(1) + T_MONE = Bug::Integer.to_bignum(-1) + T31 = Bug::Integer.to_bignum(2**31) # 2147483648 + T31P = Bug::Integer.to_bignum(T31 - 1) # 2147483647 + T32 = Bug::Integer.to_bignum(2**32) # 4294967296 + T32P = Bug::Integer.to_bignum(T32 - 1) # 4294967295 + T64 = Bug::Integer.to_bignum(2**64) # 18446744073709551616 + T64P = Bug::Integer.to_bignum(T64 - 1) # 18446744073709551615 + T128 = Bug::Integer.to_bignum(2**128) + T128P = Bug::Integer.to_bignum(T128 - 1) + T1024 = Bug::Integer.to_bignum(2**1024) + T1024P = Bug::Integer.to_bignum(T1024 - 1) def setup @verbose = $VERBOSE - $VERBOSE = nil @fmax = Float::MAX.to_i @fmax2 = @fmax * 2 @big = (1 << BIGNUM_MIN_BITS) - 1 @@ -214,9 +213,11 @@ class TestBignum < Test::Unit::TestCase def test_to_f assert_nothing_raised { T31P.to_f.to_i } - assert_raise(FloatDomainError) { (1024**1024).to_f.to_i } - assert_equal(1, (2**50000).to_f.infinite?) - assert_equal(-1, (-(2**50000)).to_f.infinite?) + assert_raise(FloatDomainError) { + assert_warning(/out of Float range/) {(1024**1024).to_f}.to_i + } + assert_equal(1, assert_warning(/out of Float range/) {(2**50000).to_f}.infinite?) + assert_equal(-1, assert_warning(/out of Float range/) {(-(2**50000)).to_f}.infinite?) end def test_cmp @@ -415,7 +416,7 @@ class TestBignum < Test::Unit::TestCase def test_divide bug5490 = '[ruby-core:40429]' assert_raise(ZeroDivisionError, bug5490) {T1024./(0)} - assert_equal(Float::INFINITY, T1024./(0.0), bug5490) + assert_equal(Float::INFINITY, assert_warning(/out of Float range/) {T1024./(0.0)}, bug5490) end def test_div @@ -466,8 +467,8 @@ class TestBignum < Test::Unit::TestCase def test_pow assert_equal(1.0, T32 ** 0.0) assert_equal(1.0 / T32, T32 ** -1) - assert_equal(1, (T32 ** T32).infinite?) - assert_equal(1, (T32 ** (2**30-1)).infinite?) + assert_equal(1, assert_warning(/may be too big/) {T32 ** T32}.infinite?) + assert_equal(1, assert_warning(/may be too big/) {T32 ** (2**30-1)}.infinite?) ### rational changes the behavior of Bignum#** #assert_raise(TypeError) { T32**"foo" } @@ -505,39 +506,57 @@ class TestBignum < Test::Unit::TestCase end def test_and_with_float - assert_raise(TypeError) { T1024 & 1.5 } + assert_raise(TypeError) { + assert_warning(/out of Float range/) {T1024 & 1.5} + } end def test_and_with_rational - assert_raise(TypeError, "#1792") { T1024 & Rational(3, 2) } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 & Rational(3, 2)} + } end def test_and_with_nonintegral_numeric - assert_raise(TypeError, "#1792") { T1024 & DummyNumeric.new } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 & DummyNumeric.new} + } end def test_or_with_float - assert_raise(TypeError) { T1024 | 1.5 } + assert_raise(TypeError) { + assert_warn(/out of Float range/) {T1024 | 1.5} + } end def test_or_with_rational - assert_raise(TypeError, "#1792") { T1024 | Rational(3, 2) } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 | Rational(3, 2)} + } end def test_or_with_nonintegral_numeric - assert_raise(TypeError, "#1792") { T1024 | DummyNumeric.new } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 | DummyNumeric.new} + } end def test_xor_with_float - assert_raise(TypeError) { T1024 ^ 1.5 } + assert_raise(TypeError) { + assert_warn(/out of Float range/) {T1024 ^ 1.5} + } end def test_xor_with_rational - assert_raise(TypeError, "#1792") { T1024 ^ Rational(3, 2) } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 ^ Rational(3, 2)} + } end def test_xor_with_nonintegral_numeric - assert_raise(TypeError, "#1792") { T1024 ^ DummyNumeric.new } + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 ^ DummyNumeric.new} + } end def test_shift2 @@ -613,7 +632,7 @@ class TestBignum < Test::Unit::TestCase time = Time.now end_flag = false num = (65536 ** 65536) - q = Queue.new + q = Thread::Queue.new thread = Thread.new do assert_raise(RuntimeError) { q << true @@ -663,7 +682,7 @@ class TestBignum < Test::Unit::TestCase end def test_too_big_to_s - if (big = 2**31-1).fixnum? + if Bug::Integer.fixnum?(big = 2**31-1) return end assert_raise_with_message(RangeError, /too big to convert/) {(1 << big).to_s} @@ -746,7 +765,7 @@ class TestBignum < Test::Unit::TestCase end def test_digits - assert_equal([90, 78, 56, 34, 12], 1234567890.to_bignum.digits(100)) + assert_equal([90, 78, 56, 34, 12], Bug::Integer.to_bignum(1234567890).digits(100)) assert_equal([7215, 2413, 6242], T1024P.digits(10_000).first(3)) assert_equal([11], 11.digits(T1024P)) assert_equal([T1024P - 1, 1], (T1024P + T1024P - 1).digits(T1024P)) @@ -759,13 +778,13 @@ class TestBignum < Test::Unit::TestCase end def test_digits_for_invalid_base_numbers - assert_raise(ArgumentError) { T1024P.to_bignum.digits(0) } - assert_raise(ArgumentError) { T1024P.to_bignum.digits(-1) } - assert_raise(ArgumentError) { T1024P.to_bignum.digits(0.to_bignum) } - assert_raise(ArgumentError) { T1024P.to_bignum.digits(1.to_bignum) } - assert_raise(ArgumentError) { T1024P.to_bignum.digits(-T1024P) } - assert_raise(ArgumentError) { 10.digits(0.to_bignum) } - assert_raise(ArgumentError) { 10.digits(1.to_bignum) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(0) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-1) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(0)) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(1)) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-T1024P) } + assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(0)) } + assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(1)) } end def test_digits_for_non_integral_base_numbers diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 2a1b671cac..67b3a936d4 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -99,4 +99,13 @@ class TestCall < Test::Unit::TestCase ary = [1, 2] assert_equal([0, 1, 2, 1], aaa(0, *ary, ary.shift), bug12860) end + + def test_call_block_order + bug16504 = '[ruby-core:96769] [Bug# 16504]' + b = proc{} + ary = [1, 2, b] + assert_equal([1, 2, b], aaa(*ary, &ary.pop), bug16504) + ary = [1, 2, b] + assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504) + end end diff --git a/test/ruby/test_case.rb b/test/ruby/test_case.rb index 77612a8945..4a0f1bf78d 100644 --- a/test/ruby/test_case.rb +++ b/test/ruby/test_case.rb @@ -102,6 +102,18 @@ class TestCase < Test::Unit::TestCase else assert(false) end + case 0 + when 0r + assert(true) + else + assert(false) + end + case 0 + when 0i + assert(true) + else + assert(false) + end end def test_method_missing diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index ca78473026..07c34ce9d5 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -312,7 +312,7 @@ class TestClass < Test::Unit::TestCase end def test_invalid_yield_from_class_definition - assert_raise(LocalJumpError) { + assert_raise(SyntaxError) { EnvUtil.suppress_warning {eval("class C; yield; end")} } end @@ -483,6 +483,53 @@ class TestClass < Test::Unit::TestCase assert_equal(:foo, d.foo) end + def test_clone_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_singleton_class_of_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_method_exists_on_singleton_class_of_singleton_class + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class.define_method(:s2_method) { :s2 } + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_equal(:s2, o.singleton_class.s2_method) + assert_equal(:s2, clone.singleton_class.s2_method) + assert_equal([:s2_method], o.singleton_class.singleton_class.instance_methods(false)) + assert_equal([:s2_method], clone.singleton_class.singleton_class.instance_methods(false)) + end + def test_singleton_class_p feature7609 = '[ruby-core:51087] [Feature #7609]' assert_predicate(self.singleton_class, :singleton_class?, feature7609) @@ -683,4 +730,58 @@ class TestClass < Test::Unit::TestCase end; end + + def test_assign_frozen_class_to_const + c = Class.new.freeze + assert_same(c, Module.new.module_eval("self::Foo = c")) + 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_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 end diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index a4fe9d4232..a3a7546575 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -220,6 +220,11 @@ class Complex_Test < Test::Unit::TestCase def test_polar 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; end def test_uplus diff --git a/test/ruby/test_const.rb b/test/ruby/test_const.rb index 1c73b66648..f6b9ea83d3 100644 --- a/test/ruby/test_const.rb +++ b/test/ruby/test_const.rb @@ -3,20 +3,30 @@ require 'test/unit' class TestConst < Test::Unit::TestCase - TEST1 = 1 - TEST2 = 2 - module Const - TEST3 = 3 - TEST4 = 4 - end + Constants_Setup = -> do + remove_const :TEST1 if defined? ::TestConst::TEST1 + remove_const :TEST2 if defined? ::TestConst::TEST2 + remove_const :Const if defined? ::TestConst::Const + remove_const :Const2 if defined? ::TestConst::Const2 + + TEST1 = 1 + TEST2 = 2 - module Const2 - TEST3 = 6 - TEST4 = 8 + module Const + TEST3 = 3 + TEST4 = 4 + end + + module Const2 + TEST3 = 6 + TEST4 = 8 + end end def test_const + Constants_Setup.call + assert defined?(TEST1) assert_equal 1, TEST1 assert defined?(TEST2) diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index 9976db3b6f..3324a09afe 100644 --- a/test/ruby/test_defined.rb +++ b/test/ruby/test_defined.rb @@ -23,40 +23,80 @@ class TestDefined < Test::Unit::TestCase return !defined?(yield) end - def test_defined + def test_defined_global_variable $x = nil assert(defined?($x)) # global variable assert_equal('global-variable', defined?($x))# returns description + end + def test_defined_local_variable assert_nil(defined?(foo)) # undefined foo=5 assert(defined?(foo)) # local variable + end + def test_defined_constant assert(defined?(Array)) # constant assert(defined?(::Array)) # toplevel constant assert(defined?(File::Constants)) # nested constant + end + + def test_defined_public_method assert(defined?(Object.new)) # method assert(defined?(Object::new)) # method + end + + def test_defined_private_method assert(!defined?(Object.print)) # private method + end + + def test_defined_operator assert(defined?(1 == 2)) # operator expression + end + def test_defined_protected_method f = Foo.new assert_nil(defined?(f.foo)) # protected method f.bar(f) { |v| assert(v) } + f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") } + end + + def test_defined_undefined_method + f = Foo.new assert_nil(defined?(f.quux)) # undefined method + end + + def test_defined_undefined_argument + f = Foo.new assert_nil(defined?(f.baz(x))) # undefined argument x = 0 assert(defined?(f.baz(x))) assert_nil(defined?(f.quux(x))) assert(defined?(print(x))) assert_nil(defined?(quux(x))) + end + + def test_defined_attrasgn + f = Foo.new assert(defined?(f.attr = 1)) f.attrasgn_test { |v| assert(v) } + end + + def test_defined_undef + x = Object.new + def x.foo; end + assert(defined?(x.foo)) + x.instance_eval {undef :foo} + assert(!defined?(x.foo), "undefed method should not be defined?") + end + def test_defined_yield assert(defined_test) # not iterator assert(!defined_test{}) # called as iterator + end + def test_defined_matchdata /a/ =~ '' assert_equal nil, defined?($&) assert_equal nil, defined?($`) @@ -85,12 +125,65 @@ class TestDefined < Test::Unit::TestCase assert_equal 'global-variable', defined?($+) assert_equal 'global-variable', defined?($1) assert_equal nil, defined?($2) + end + def test_defined_literal assert_equal("nil", defined?(nil)) assert_equal("true", defined?(true)) assert_equal("false", defined?(false)) assert_equal("expression", defined?(1)) + end + + def test_defined_method + self_ = self + assert_equal("method", defined?(test_defined_method)) + assert_equal("method", defined?(self.test_defined_method)) + assert_equal("method", defined?(self_.test_defined_method)) + + assert_equal(nil, defined?(1.test_defined_method)) + assert_equal("method", defined?(1.to_i)) + assert_equal(nil, defined?(1.to_i.test_defined_method)) + assert_equal(nil, defined?(1.test_defined_method.to_i)) + + assert_equal("method", defined?("x".reverse)) + assert_equal("method", defined?("x".reverse(1))) + assert_equal("method", defined?("x".reverse.reverse)) + assert_equal(nil, defined?("x".reverse(1).reverse)) + + assert_equal("method", defined?(1.to_i(10))) + assert_equal("method", defined?(1.to_i("x"))) + assert_equal(nil, defined?(1.to_i("x").undefined)) + assert_equal(nil, defined?(1.to_i(undefined).to_i)) + assert_equal(nil, defined?(1.to_i("x").undefined.to_i)) + assert_equal(nil, defined?(1.to_i(undefined).to_i.to_i)) + end + + def test_defined_method_single_call + times_called = 0 + define_singleton_method(:t) do + times_called += 1 + self + end + assert_equal("method", defined?(t)) + assert_equal(0, times_called) + + assert_equal("method", defined?(t.t)) + assert_equal(1, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t)) + assert_equal(2, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t.t)) + assert_equal(3, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t.t.t)) + assert_equal(4, times_called) + end + def test_defined_empty_paren_expr bug8224 = '[ruby-core:54024] [Bug #8224]' (1..3).each do |level| expr = "("*level+")"*level @@ -214,6 +307,23 @@ class TestDefined < Test::Unit::TestCase assert_separately([], "assert_nil(defined?(super))") end + def test_respond_to + obj = "#{self.class.name}##{__method__}" + class << obj + def respond_to?(mid) + true + end + end + assert_warn(/deprecated method signature.*\n.*respond_to\? is defined here/) do + Warning[:deprecated] = true + defined?(obj.foo) + end + assert_warn('') do + Warning[:deprecated] = false + defined?(obj.foo) + end + end + class ExampleRespondToMissing attr_reader :called @@ -257,4 +367,53 @@ class TestDefined < Test::Unit::TestCase def test_top_level_constant_not_defined assert_nil(defined?(TestDefined::Object)) end + + class RefinedClass + end + + module RefiningModule + refine RefinedClass do + def pub + end + + private + + def priv + end + end + + def self.call_without_using(x = RefinedClass.new) + defined?(x.pub) + end + + def self.vcall_without_using(x = RefinedClass.new) + x.instance_eval {defined?(priv)} + end + + using self + + def self.call_with_using(x = RefinedClass.new) + defined?(x.pub) + end + + def self.vcall_with_using(x = RefinedClass.new) + x.instance_eval {defined?(priv)} + end + end + + def test_defined_refined_call_without_using + assert(!RefiningModule.call_without_using, "refined public method without using") + end + + def test_defined_refined_vcall_without_using + assert(!RefiningModule.vcall_without_using, "refined private method without using") + end + + def test_defined_refined_call_with_using + assert(RefiningModule.call_with_using, "refined public method with using") + end + + def test_defined_refined_vcall_with_using + assert(RefiningModule.vcall_with_using, "refined private method with using") + end end diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb index 1bb228fd45..39a1dae889 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -8,7 +8,6 @@ class TestDir < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil @root = File.realpath(Dir.mktmpdir('__test_dir__')) @nodir = File.join(@root, "dummy") @dirs = [] @@ -88,36 +87,67 @@ class TestDir < Test::Unit::TestCase end def test_chdir - @pwd = Dir.pwd - @env_home = ENV["HOME"] - @env_logdir = ENV["LOGDIR"] + pwd = Dir.pwd + env_home = ENV["HOME"] + env_logdir = ENV["LOGDIR"] ENV.delete("HOME") ENV.delete("LOGDIR") assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) } assert_raise(ArgumentError) { Dir.chdir } - ENV["HOME"] = @pwd + ENV["HOME"] = pwd Dir.chdir do - assert_equal(@pwd, Dir.pwd) - Dir.chdir(@root) + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) } + + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) } + assert_equal(@root, Dir.pwd) + + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) } + + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) }.join } + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) { } }.join } + + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) } + + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) } assert_equal(@root, Dir.pwd) + + assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) } + Dir.chdir(@root) do + assert_equal(@root, Dir.pwd) + end + assert_equal(pwd, Dir.pwd) end ensure begin - Dir.chdir(@pwd) + Dir.chdir(pwd) rescue - abort("cannot return the original directory: #{ @pwd }") + abort("cannot return the original directory: #{ pwd }") end - if @env_home - ENV["HOME"] = @env_home - else - ENV.delete("HOME") + ENV["HOME"] = env_home + ENV["LOGDIR"] = env_logdir + end + + def test_chdir_conflict + pwd = Dir.pwd + q = Thread::Queue.new + t = Thread.new do + q.pop + Dir.chdir(pwd) rescue $! + end + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) end - if @env_logdir - ENV["LOGDIR"] = @env_logdir - else - ENV.delete("LOGDIR") + + t = Thread.new do + q.pop + Dir.chdir(pwd){} rescue $! + end + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) end end @@ -135,16 +165,26 @@ class TestDir < Test::Unit::TestCase end def test_glob - assert_equal((%w(. ..) + ("a".."z").to_a).map{|f| File.join(@root, f) }, - Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH).sort) - assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }.sort, - Dir.glob([@root, File.join(@root, "*")]).sort) + assert_equal((%w(.) + ("a".."z").to_a).map{|f| File.join(@root, f) }, + Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH)) + assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }, + 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), "") }.sort, - Dir.glob(File.join(@root, "*/")).sort) + assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") }, + Dir.glob(File.join(@root, "*/"))) assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a')) FileUtils.touch(File.join(@root, "{}")) @@ -154,7 +194,7 @@ class TestDir < Test::Unit::TestCase assert_equal([], Dir.glob(File.join(@root, '[a-\\'))) assert_equal([File.join(@root, "a")], Dir.glob(File.join(@root, 'a\\'))) - assert_equal(("a".."f").map {|f| File.join(@root, f) }.sort, Dir.glob(File.join(@root, '[abc/def]')).sort) + assert_equal(("a".."f").map {|f| File.join(@root, f) }, Dir.glob(File.join(@root, '[abc/def]'))) open(File.join(@root, "}}{}"), "wb") {} open(File.join(@root, "}}a"), "wb") {} @@ -171,6 +211,9 @@ class TestDir < Test::Unit::TestCase Dir.chdir(@root) do assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006) + Dir.mkdir("a/b") + assert_not_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/b/.") + FileUtils.mkdir_p("a/b/c/d/e/f") assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977) assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977) @@ -184,7 +227,7 @@ class TestDir < Test::Unit::TestCase dirs = ["a/.x", "a/b/.y"] FileUtils.mkdir_p(dirs) dirs.map {|dir| open("#{dir}/z", "w") {}} - assert_equal([], Dir.glob("a/**/z").sort, bug8283) + assert_equal([], Dir.glob("a/**/z"), bug8283) assert_equal(["a/.x/z"], Dir.glob("a/**/.x/z"), bug8283) assert_equal(["a/.x/z"], Dir.glob("a/.x/**/z"), bug8283) assert_equal(["a/b/.y/z"], Dir.glob("a/**/.y/z"), bug8283) @@ -202,6 +245,9 @@ class TestDir < Test::Unit::TestCase bug15540 = '[ruby-core:91110] [Bug #15540]' assert_equal(["c/d/a/", "c/d/a/b/", "c/d/a/b/c/", "c/e/a/", "c/e/a/b/", "c/e/a/b/c/"], Dir.glob('c/{d,e}/a/**/'), bug15540) + + assert_equal(["c/e/a/", "c/e/a/b/", "c/e/a/b/c/", "c/d/a/", "c/d/a/b/", "c/d/a/b/c/"], + Dir.glob('c/{e,d}/a/**/')) end end @@ -213,6 +259,31 @@ 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]")) + assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob(%W"#{@root}/b #{@root}/a")) + assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob("#{@root}/{b,a}")) + end + assert_equal(["a", "b"], Dir.glob("[ba]", base: @root)) + assert_equal(["b", "a"], Dir.glob(%W"b a", base: @root)) + assert_equal(["b", "a"], Dir.glob("{b,a}", base: @root)) + end + if Process.const_defined?(:RLIMIT_NOFILE) def test_glob_too_may_open_files assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", chdir: @root) @@ -237,21 +308,38 @@ class TestDir < Test::Unit::TestCase Dir.mkdir(File.join(@root, "a/dir")) dirs = @dirs + %w[a/dir/] dirs.sort! - assert_equal(files, Dir.glob("*/*.c", base: @root).sort) - assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".").sort}) - assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a").sort}) - assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "").sort}) - assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil).sort}) - assert_equal(@dirs, Dir.glob("*/", base: @root).sort) - assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".").sort}) - assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a").sort}) - assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "").sort}) - assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil).sort}) - assert_equal(dirs, Dir.glob("**/*/", base: @root).sort) - assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".").sort}) - assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a").sort}) - assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "").sort}) - assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil).sort}) + + assert_equal(files, Dir.glob("*/*.c", base: @root)) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".")}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a")}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "")}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil)}) + assert_equal(@dirs, Dir.glob("*/", base: @root)) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".")}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a")}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "")}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil)}) + assert_equal(dirs, Dir.glob("**/*/", base: @root)) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".")}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a")}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "")}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil)}) + + assert_equal(files, Dir.glob("*/*.c", base: @root, sort: false).sort) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".", sort: false).sort}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a", sort: false).sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "", sort: false).sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil, sort: false).sort}) + assert_equal(@dirs, Dir.glob("*/", base: @root)) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".", sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a", sort: false).sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "", sort: false).sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil, sort: false).sort}) + assert_equal(dirs, Dir.glob("**/*/", base: @root)) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".", sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a", sort: false).sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "", sort: false).sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil, sort: false).sort}) end def test_glob_base_dir @@ -260,12 +348,31 @@ class TestDir < Test::Unit::TestCase Dir.mkdir(File.join(@root, "a/dir")) dirs = @dirs + %w[a/dir/] dirs.sort! - assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}.sort) + + assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}) assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d)}}) - assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d).sort}) - assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d).sort}}) - assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d).sort}) - assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d).sort}}) + assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d)}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d)}}) + assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d)}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d)}}) + + assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d, sort: false).sort}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d, sort: false).sort}}) + assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d, sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d, sort: false).sort}}) + assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d, sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d, sort: false).sort}}) + end + + def test_glob_ignore_casefold_invalid_encoding + bug14456 = "[ruby-core:85448]" + filename = "\u00AAa123".encode('ISO-8859-1') + File.write(File.join(@root, filename), "") + matches = Dir.chdir(@root) {|d| Dir.glob("*a123".encode('UTF-8'), File::FNM_CASEFOLD)} + assert_equal(1, matches.size, bug14456) + matches.each{|f| f.force_encoding('ISO-8859-1')} + # Handle MacOS/Windows, which saves under a different filename + assert_include([filename, "\u00C2\u00AAa123".encode('ISO-8859-1')], matches.first, bug14456) end def assert_entries(entries, children_only = false) @@ -277,26 +384,52 @@ class TestDir < Test::Unit::TestCase def test_entries assert_entries(Dir.open(@root) {|dir| dir.entries}) - assert_entries(Dir.entries(@root).to_a) + assert_entries(Dir.entries(@root)) assert_raise(ArgumentError) {Dir.entries(@root+"\0")} + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + assert_equal(enc, Dir.entries(@root, encoding: enc).first.encoding) + end end def test_foreach assert_entries(Dir.open(@root) {|dir| dir.each.to_a}) assert_entries(Dir.foreach(@root).to_a) assert_raise(ArgumentError) {Dir.foreach(@root+"\0").to_a} + newdir = @root+"/new" + e = Dir.foreach(newdir) + assert_raise(Errno::ENOENT) {e.to_a} + Dir.mkdir(newdir) + File.write(newdir+"/a", "") + assert_equal(%w[. .. a], e.to_a.sort) + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + e = Dir.foreach(newdir, encoding: enc) + assert_equal(enc, e.to_a.first.encoding) + end end def test_children assert_entries(Dir.open(@root) {|dir| dir.children}, true) assert_entries(Dir.children(@root), true) assert_raise(ArgumentError) {Dir.children(@root+"\0")} + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + assert_equal(enc, Dir.children(@root, encoding: enc).first.encoding) + end end def test_each_child assert_entries(Dir.open(@root) {|dir| dir.each_child.to_a}, true) assert_entries(Dir.each_child(@root).to_a, true) assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a} + newdir = @root+"/new" + e = Dir.each_child(newdir) + assert_raise(Errno::ENOENT) {e.to_a} + Dir.mkdir(newdir) + File.write(newdir+"/a", "") + assert_equal(%w[a], e.to_a) + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + e = Dir.each_child(newdir, encoding: enc) + assert_equal(enc, e.to_a.first.encoding) + end end def test_dir_enc @@ -337,10 +470,10 @@ class TestDir < Test::Unit::TestCase end assert_equal([*"a".."z", *"symlink-a".."symlink-z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort, - Dir.glob(File.join(@root, "*/")).sort) + Dir.glob(File.join(@root, "*/"))) - assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort], - Dir.glob(File.join(@root, "**/")).sort) + assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }], + Dir.glob(File.join(@root, "**/"))) end def test_glob_metachar @@ -424,8 +557,8 @@ class TestDir < Test::Unit::TestCase Dir.mkdir('some-dir') File.write('some-dir/foo', 'some content') - assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*'].sort - assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*'].sort + assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*'] + assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*'] end end end @@ -471,9 +604,21 @@ class TestDir < Test::Unit::TestCase ensure fs.clear end - list = Dir.glob("*").sort + list = Dir.glob("*") assert_not_empty(list) assert_equal([*"a".."z"], list) end; end if defined?(Process::RLIMIT_NOFILE) + + def test_glob_array_with_destructive_element + args = Array.new(100, "") + pat = Struct.new(:ary).new(args) + args.push(pat, *Array.new(100) {"."*40}) + def pat.to_path + ary.clear + GC.start + "" + end + assert_empty(Dir.glob(args)) + end end diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb index c2c0c4999e..67bad8a514 100644 --- a/test/ruby/test_dir_m17n.rb +++ b/test/ruby/test_dir_m17n.rb @@ -17,27 +17,19 @@ class TestDir_M17N < Test::Unit::TestCase assert_separately(["-E#{encoding}"], <<-EOS, :chdir=>dir) filename = #{code}.chr('UTF-8').force_encoding("#{encoding}") File.open(filename, "w") {} - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", **(opts||{})) + ents = Dir.entries(".") + if /mswin|mingw/ =~ RUBY_PLATFORM + filename = filename.encode("UTF-8") + end assert_include(ents, filename) EOS return if /cygwin/ =~ RUBY_PLATFORM assert_separately(%w[-EASCII-8BIT], <<-EOS, :chdir=>dir) filename = #{code}.chr('UTF-8').force_encoding("ASCII-8BIT") - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", **(opts||{})) - expected_filename = #{code}.chr('UTF-8').encode(Encoding.find("filesystem")) rescue expected_filename = "?" - expected_filename = expected_filename.force_encoding("ASCII-8BIT") + ents = Dir.entries(".") if /mswin|mingw/ =~ RUBY_PLATFORM - case - when ents.include?(filename) - when ents.include?(expected_filename) - filename = expected_filename - else - ents = Dir.entries(".", :encoding => Encoding.find("filesystem")) - filename = expected_filename - end + filename.force_encoding("UTF-8") end assert_include(ents, filename) EOS @@ -199,27 +191,23 @@ class TestDir_M17N < Test::Unit::TestCase assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", **(opts||{})) + ents = Dir.entries(".") if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8").force_encoding("euc-jp") + elsif /mswin|mingw/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8") end assert_include(ents, filename) EOS assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding('ASCII-8BIT') - win_expected_filename = filename.encode(Encoding.find("filesystem"), "euc-jp") rescue "?" - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", **(opts||{})) + ents = Dir.entries(".") unless ents.include?(filename) case RUBY_PLATFORM when /darwin/ filename = filename.encode("utf-8", "euc-jp").b when /mswin|mingw/ - if ents.include?(win_expected_filename.b) - ents = Dir.entries(".", :encoding => Encoding.find("filesystem")) - filename = win_expected_filename - end + filename = filename.encode("utf-8", "euc-jp") end end assert_include(ents, filename) @@ -414,13 +402,8 @@ class TestDir_M17N < Test::Unit::TestCase orig.each {|n| open(n, "w") {}} enc = Encoding.find("filesystem") enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII - if /mswin|mingw/ =~ RUBY_PLATFORM - opts = {:encoding => enc} - orig.map! {|o| o.encode("filesystem") rescue o.tr("^a-z", "?")} - else - orig.each {|o| o.force_encoding(enc) } - end - ents = Dir.entries(".", **(opts||{})).reject {|n| /\A\./ =~ n} + orig.each {|o| o.force_encoding(enc) } + ents = Dir.entries(".").reject {|n| /\A\./ =~ n} ents.sort! PP.assert_equal(orig, ents, bug7267) } @@ -431,13 +414,9 @@ class TestDir_M17N < Test::Unit::TestCase expected = [] results = [] orig.each {|o| - if /mswin|mingw/ =~ RUBY_PLATFORM - n = (o.encode("filesystem") rescue next) - else - enc = Encoding.find("filesystem") - enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII - n = o.dup.force_encoding(enc) - end + enc = Encoding.find("filesystem") + enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII + n = o.dup.force_encoding(enc) expected << n with_tmpdir { Dir.mkdir(o) diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index a469614d84..1aad0de347 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -803,7 +803,7 @@ class TestEncodingConverter < Test::Unit::TestCase assert_equal('', ec.finish) ec = Encoding::Converter.new("", "xml_attr_content_escape") - assert_equal('&<>"', ec.convert("&<>\"")) + assert_equal('&<>"'', ec.convert("&<>\"'")) assert_equal('', ec.finish) end @@ -844,7 +844,7 @@ class TestEncodingConverter < Test::Unit::TestCase def test_xml_hasharg assert_equal("&\e$B$&\e(B♥&\"'".force_encoding("iso-2022-jp"), "&\u3046\u2665&\"'".encode("iso-2022-jp", xml: :text)) - assert_equal("\"&\e$B$&\e(B♡&"'\"".force_encoding("iso-2022-jp"), + assert_equal("\"&\e$B$&\e(B♡&"'\"".force_encoding("iso-2022-jp"), "&\u3046\u2661&\"'".encode("iso-2022-jp", xml: :attr)) assert_equal("&\u3046\u2661&\"'".force_encoding("utf-8"), @@ -912,6 +912,21 @@ class TestEncodingConverter < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /\u{3042}/) { Encoding::Converter.new("", "", newline: "\u{3042}".to_sym) } + newlines = %i[universal_newline crlf_newline cr_newline] + (2..newlines.size).each do |i| + newlines.combination(i) do |opts| + assert_raise(Encoding::ConverterNotFoundError, "#{opts} are mutually exclusive") do + Encoding::Converter.new("", "", **opts.inject({}) {|o,nl|o[nl]=true;o}) + end + end + end + newlines.each do |nl| + opts = {newline: :universal, nl => true} + ec2 = assert_warning(/:newline option precedes/, opts.inspect) do + Encoding::Converter.new("", "", **opts) + end + assert_equal(ec1, ec2) + end end def test_default_external diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 019cb2417f..4a6dd932ed 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -56,11 +56,36 @@ class TestEncoding < Test::Unit::TestCase end def test_replicate - assert_instance_of(Encoding, Encoding::UTF_8.replicate('UTF-8-ANOTHER')) - assert_instance_of(Encoding, Encoding::ISO_2022_JP.replicate('ISO-2022-JP-ANOTHER')) + assert_separately([], "#{<<~'END;'}") + 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]' assert_raise(TypeError, bug3127) {Encoding::UTF_8.replicate(0)} - assert_raise(ArgumentError, bug3127) {Encoding::UTF_8.replicate("\0")} + assert_raise_with_message(ArgumentError, /\bNUL\b/, bug3127) {Encoding::UTF_8.replicate("\0")} + END; + end + + def test_extra_encoding + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 200.times {|i| + Encoding::UTF_8.replicate("dummy#{i}") + } + e = Encoding.list.last + format = "%d".force_encoding(e) + assert_equal("0", format % 0) + assert_equal(e, format.dup.encoding) + assert_equal(e, (format*1).encoding) + + assert_equal(e, (("x"*30).force_encoding(e)*1).encoding) + GC.start + + name = "A" * 64 + Encoding.list.each do |enc| + assert_raise(ArgumentError) {enc.replicate(name)} + name.succ! + end + end; end def test_dummy_p @@ -130,7 +155,7 @@ class TestEncoding < Test::Unit::TestCase assert_equal(Encoding::US_ASCII, __ENCODING__) $:.unshift("/\x80") assert_raise_with_message(LoadError, /\[Bug #16382\]/) do - $:.resolve_feature_path "[Bug #16382]" + require "[Bug #16382]" end end; end diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 7b647231c8..b0c43b9a7f 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -27,7 +27,6 @@ class TestEnumerable < Test::Unit::TestCase end end @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -63,11 +62,32 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([[2, 1], [2, 4]], a) end + def test_grep_optimization + bug17030 = '[ruby-core:99156]' + 'set last match' =~ /set last (.*)/ + assert_equal([:a, 'b', :c], [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/), bug17030) + assert_equal(['z', 42, nil], [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/), bug17030) + assert_equal('match', $1, bug17030) + + regexp = Regexp.new('x') + assert_equal([], @obj.grep(regexp), bug17030) # sanity check + def regexp.===(other) + true + end + assert_equal([1, 2, 3, 1, 2], @obj.grep(regexp), bug17030) + + o = Object.new + def o.to_str + 'hello' + end + assert_same(o, [o].grep(/ll/).first, bug17030) + end + def test_count assert_equal(5, @obj.count) assert_equal(2, @obj.count(1)) assert_equal(3, @obj.count {|x| x % 2 == 1 }) - assert_equal(2, @obj.count(1) {|x| x % 2 == 1 }) + assert_equal(2, assert_warning(/given block not used/) {@obj.count(1) {|x| x % 2 == 1 }}) assert_raise(ArgumentError) { @obj.count(0, 1) } if RUBY_ENGINE == "ruby" @@ -95,7 +115,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(1, @obj.find_index {|x| x % 2 == 0 }) assert_equal(nil, @obj.find_index {|x| false }) assert_raise(ArgumentError) { @obj.find_index(0, 1) } - assert_equal(1, @obj.find_index(2) {|x| x == 1 }) + assert_equal(1, assert_warning(/given block not used/) {@obj.find_index(2) {|x| x == 1 }}) end def test_find_all @@ -114,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 @@ -206,7 +232,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(48, @obj.inject {|z, x| z * 2 + x }) assert_equal(12, @obj.inject(:*)) assert_equal(24, @obj.inject(2) {|z, x| z * x }) - 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}) end @@ -228,17 +254,75 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(15, [3, 5, 7].inject(:+)) assert_float_equal(15.0, [3, 5, 7.0].inject(:+)) assert_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).inject(:+)) + assert_equal(3*FIXNUM_MAX, Array.new(3, FIXNUM_MAX).inject(:+)) assert_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).inject(:+)) assert_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).inject(:+)) assert_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).inject(:+)) assert_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).inject(:+)) assert_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).inject(:+)) + assert_equal(3*FIXNUM_MIN, Array.new(3, FIXNUM_MIN).inject(:+)) assert_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].inject(:+)) assert_float_equal(10.0, [3.0, 5].inject(2.0, :+)) assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].inject(:+)) assert_equal(2.0+3.0i, [2.0, 3.0i].inject(:+)) end + def test_inject_op_redefined + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-dev:49510] [Bug#12178] should respect redefinition' + begin + Integer.class_eval do + alias_method :orig, op + define_method(op) do |x| + 0 + end + end + assert_equal(0, k.new.inject(op), bug) + ensure + Integer.class_eval do + undef_method op + alias_method op, :orig + end + end + end; + end + + def test_inject_op_private + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-core:81349] [Bug #13592] should respect visibility' + assert_raise_with_message(NoMethodError, /private method/, bug) do + begin + Integer.class_eval do + private op + end + k.new.inject(op) + ensure + Integer.class_eval do + public op + end + end + end + end; + end + def test_inject_array_op_redefined assert_separately([], "#{<<~"end;"}\n""end") all_assertions_foreach("", *%i[+ * / - %]) do |op| @@ -279,6 +363,25 @@ class TestEnumerable < Test::Unit::TestCase end; end + def test_refine_Enumerable_then_include + assert_separately([], "#{<<~"end;"}\n") + module RefinementBug + refine Enumerable do + def refined_method + :rm + end + end + end + using RefinementBug + + class A + include Enumerable + end + + assert_equal(:rm, [].refined_method) + end; + end + def test_partition assert_equal([[1, 3, 1], [2, 2]], @obj.partition {|x| x % 2 == 1 }) cond = ->(x, i) { x % 2 == 1 } @@ -297,6 +400,45 @@ class TestEnumerable < Test::Unit::TestCase def test_tally h = {1 => 2, 2 => 2, 3 => 1} assert_equal(h, @obj.tally) + + h = {1 => 5, 2 => 2, 3 => 1, 4 => "x"} + assert_equal(h, @obj.tally({1 => 3, 4 => "x"})) + + assert_raise(TypeError) do + @obj.tally({1 => ""}) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_same(h, @obj.tally(h)) + + h = {1 => 2, 2 => 2, 3 => 1}.freeze + assert_raise(FrozenError) do + @obj.tally(h) + end + assert_equal({1 => 2, 2 => 2, 3 => 1}, h) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"} + end + assert_equal({1 => 5, 2 => 2, 3 => 1, 4 => "x"}, @obj.tally(hash_convertible)) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"}.freeze + end + assert_raise(FrozenError) do + @obj.tally(hash_convertible) + end + assert_equal({1 => 3, 4 => "x"}, hash_convertible.to_hash) + + assert_raise(TypeError) do + @obj.tally(BasicObject.new) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_equal(h, @obj.tally(Hash.new(100))) + assert_equal(h, @obj.tally(Hash.new {100})) end def test_first @@ -319,6 +461,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 @@ -341,7 +494,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, [true, true, false].all?) assert_equal(true, [].all?) assert_equal(true, @empty.all?) - assert_equal(true, @obj.all?(Fixnum)) + assert_equal(true, @obj.all?(Integer)) assert_equal(false, @obj.all?(1..2)) end @@ -595,6 +748,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 @@ -614,6 +770,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 @@ -1134,6 +1293,21 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([1, [1, 2]], Foo.new.to_enum.uniq) end + def test_compact + class << (enum = Object.new) + include Enumerable + def each + yield 3 + yield nil + yield 7 + yield 9 + yield nil + end + end + + assert_equal([3, 7, 9], enum.compact) + end + def test_transient_heap_sort_by klass = Class.new do include Comparable diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 75cf1aeec6..c823b79c6d 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -69,18 +69,17 @@ class TestEnumerator < Test::Unit::TestCase def test_initialize assert_equal([1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).to_a) - _, err = capture_io do - assert_equal([1, 2, 3], Enumerator.new(@obj, :foo, 1, 2, 3).to_a) - end - assert_match 'Enumerator.new without a block is deprecated', err + assert_raise(ArgumentError) { + Enumerator.new(@obj, :foo, 1, 2, 3) + } assert_equal([1, 2, 3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.take(3)) assert_raise(ArgumentError) { Enumerator.new } enum = @obj.to_enum assert_raise(NoMethodError) { enum.each {} } enum.freeze - assert_raise(FrozenError) { - capture_io do + assert_raise(ArgumentError) { + capture_output do # warning: Enumerator.new without a block is deprecated; use Object#to_enum enum.__send__(:initialize, @obj, :foo) end @@ -697,6 +696,11 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([0, 1], u.force) end + def test_compact + u = [0, 1, nil, 2, 3, nil].to_enum.lazy.compact + assert_equal([0, 1, 2, 3], u.force) + end + def test_enum_chain_and_plus r = 1..5 @@ -812,6 +816,28 @@ class TestEnumerator < Test::Unit::TestCase ) end + def test_chain_with_index + assert_equal([[3, 0], [4, 1]], [3].chain([4]).with_index.to_a) + end + + def test_lazy_chain + ea = (10..).lazy.select(&:even?).take(10) + ed = (20..).lazy.select(&:odd?) + chain = (ea + ed).select{|x| x % 3 == 0} + assert_equal(12, chain.next) + assert_equal(18, chain.next) + assert_equal(24, chain.next) + assert_equal(21, chain.next) + assert_equal(27, chain.next) + assert_equal(33, chain.next) + end + + def test_chain_undef_methods + chain = [1].to_enum + [2].to_enum + meths = (chain.methods & [:feed, :next, :next_values, :peek, :peek_values]) + assert_equal(0, meths.size) + end + def test_produce assert_raise(ArgumentError) { Enumerator.produce } diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index f93cd503d8..87ccd5102b 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -22,7 +22,6 @@ class TestEnv < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil @backup = ENV.to_hash ENV.delete('test') ENV.delete('TEST') @@ -63,6 +62,46 @@ class TestEnv < Test::Unit::TestCase } end + def test_dup + assert_raise(TypeError) { + ENV.dup + } + end + + def test_clone + warning = /ENV\.clone is deprecated; use ENV\.to_h instead/ + clone = assert_deprecated_warning(warning) { + ENV.clone + } + assert_same(ENV, clone) + + clone = assert_deprecated_warning(warning) { + ENV.clone(freeze: false) + } + assert_same(ENV, clone) + + clone = assert_deprecated_warning(warning) { + ENV.clone(freeze: nil) + } + assert_same(ENV, clone) + + assert_raise(TypeError) { + ENV.clone(freeze: true) + } + assert_raise(ArgumentError) { + ENV.clone(freeze: 1) + } + assert_raise(ArgumentError) { + ENV.clone(foo: false) + } + assert_raise(ArgumentError) { + ENV.clone(1) + } + assert_raise(ArgumentError) { + ENV.clone(1, foo: false) + } + end + def test_has_value val = 'a' val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) @@ -84,7 +123,6 @@ class TestEnv < Test::Unit::TestCase ENV['test'] = val[0...-1] assert_nil(ENV.key(val)) - assert_nil(ENV.index(val)) assert_nil(ENV.key(val.upcase)) ENV['test'] = val if IGNORE_CASE @@ -106,6 +144,7 @@ class TestEnv < Test::Unit::TestCase assert_invalid_env {|v| ENV.delete(v)} assert_nil(ENV.delete("TEST")) assert_nothing_raised { ENV.delete(PATH_ENV) } + assert_equal("NO TEST", ENV.delete("TEST") {|name| "NO "+name}) end def test_getenv @@ -286,6 +325,17 @@ class TestEnv < Test::Unit::TestCase assert_equal({"foo"=>"bar", "baz"=>"qux"}, ENV.slice("foo", "baz")) end + def test_except + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except()) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except("")) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except("unknown")) + assert_equal({"bar"=>"rab"}, ENV.except("foo", "baz")) + end + def test_clear ENV.clear assert_equal(0, ENV.size) @@ -358,7 +408,8 @@ class TestEnv < Test::Unit::TestCase assert_equal("foo", v) end assert_invalid_env {|var| ENV.assoc(var)} - assert_equal(Encoding.find("locale"), v.encoding) + encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale") + assert_equal(encoding, v.encoding) end def test_has_value2 @@ -406,8 +457,8 @@ class TestEnv < Test::Unit::TestCase def check(as, bs) if IGNORE_CASE - as = as.map {|xs| xs.map {|x| x.upcase } } - bs = bs.map {|xs| xs.map {|x| x.upcase } } + as = as.map {|k, v| [k.upcase, v] } + bs = bs.map {|k, v| [k.upcase, v] } end assert_equal(as.sort, bs.sort) end @@ -433,6 +484,8 @@ class TestEnv < Test::Unit::TestCase ENV["foo"] = "xxx" ENV.replace({"foo"=>"bar", "baz"=>"qux"}) check(ENV.to_hash.to_a, [%w(foo bar), %w(baz qux)]) + ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"}) + check(ENV.to_hash.to_a, [%w(Foo Bar), %w(Baz Qux)]) end def test_update @@ -450,15 +503,15 @@ class TestEnv < Test::Unit::TestCase end def test_huge_value - huge_value = "bar" * 40960 - ENV["foo"] = "bar" - if /mswin/ =~ RUBY_PLATFORM - assert_raise(Errno::EINVAL) { ENV["foo"] = huge_value } - assert_equal("bar", ENV["foo"]) + if /mswin|ucrt/ =~ RUBY_PLATFORM + # On Windows >= Vista each environment variable can be max 32768 characters + huge_value = "bar" * 10900 else - assert_nothing_raised { ENV["foo"] = huge_value } - assert_equal(huge_value, ENV["foo"]) + huge_value = "bar" * 40960 end + ENV["foo"] = "overwritten" + assert_nothing_raised { ENV["foo"] = huge_value } + assert_equal(huge_value, ENV["foo"]) end if /mswin|mingw/ =~ RUBY_PLATFORM @@ -525,6 +578,892 @@ 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_clone_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + original_warning_state = Warning[:deprecated] + Warning[:deprecated] = false + + begin + Ractor.yield ENV.clone.object_id + Ractor.yield ENV.clone(freeze: false).object_id + Ractor.yield ENV.clone(freeze: nil).object_id + + #{str_for_yielding_exception_class("ENV.clone(freeze: true)")} + #{str_for_yielding_exception_class("ENV.clone(freeze: 1)")} + #{str_for_yielding_exception_class("ENV.clone(foo: false)")} + #{str_for_yielding_exception_class("ENV.clone(1)")} + #{str_for_yielding_exception_class("ENV.clone(1, foo: false)")} + + ensure + Warning[:deprecated] = original_warning_state + end + end + assert_equal(ENV.object_id, r.take) + assert_equal(ENV.object_id, r.take) + assert_equal(ENV.object_id, r.take) + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + 4.times do + #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, "r")} + end + 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]' @@ -566,15 +1505,13 @@ class TestEnv < Test::Unit::TestCase end; end - if Encoding.find("locale") == Encoding::UTF_8 - def test_utf8 - text = "testing \u{e5 e1 e2 e4 e3 101 3042}" - test = ENV["test"] - ENV["test"] = text - assert_equal text, ENV["test"] - ensure - ENV["test"] = test - end + def test_utf8 + text = "testing \u{e5 e1 e2 e4 e3 101 3042}" + test = ENV["test"] + ENV["test"] = text + assert_equal text, ENV["test"] + ensure + ENV["test"] = test end end end diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb index 3d6116edbc..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 # @@ -347,6 +417,10 @@ class TestEval < Test::Unit::TestCase assert_equal(55, eval("foo22")) assert_equal(55, foo22) }.call + + self.class.class_eval do + remove_const :EvTest + end end def test_nil_instance_eval_cvar @@ -470,9 +544,12 @@ class TestEval < Test::Unit::TestCase end def test_eval_location_binding - assert_warning(/__FILE__ in eval/) do - assert_equal(__FILE__, eval("__FILE__", binding)) - end + assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", nil)) + assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", binding)) + assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo')) + assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo')) + assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2)) + assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", binding, 'foo', 2)) end def test_fstring_instance_eval diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 56cd19d0a2..ffca877f1e 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -78,6 +78,77 @@ class TestException < Test::Unit::TestCase assert(!bad) end + def test_exception_in_ensure_with_next + string = "[ruby-core:82936] [Bug #13930]" + assert_raise_with_message(RuntimeError, string) do + lambda do + next + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + + assert_raise_with_message(RuntimeError, string) do + flag = true + while flag + flag = false + begin + next + rescue + assert(false) + ensure + raise string + end + end + end + + iseq = RubyVM::InstructionSequence.compile(<<-RUBY) + begin + while true + break + end + rescue + end + RUBY + + assert_equal false, iseq.to_a[13].any?{|(e,_)| e == :throw} + end + + def test_exception_in_ensure_with_redo + string = "[ruby-core:82936] [Bug #13930]" + + assert_raise_with_message(RuntimeError, string) do + i = 0 + lambda do + i += 1 + redo if i < 2 + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + end + + def test_exception_in_ensure_with_return + @string = "[ruby-core:97104] [Bug #16618]" + def self.meow + return if true # This if modifier suppresses "warning: statement not reached" + assert(false) + rescue + assert(false) + ensure + raise @string + end + assert_raise_with_message(RuntimeError, @string) do + meow + end + end + def test_errinfo_in_debug bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do def to_s @@ -181,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 => e + 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 { @@ -438,16 +530,6 @@ end.join assert_not_send([e, :success?], "abort means failure") end - def test_nomethoderror - bug3237 = '[ruby-core:29948]' - str = "\u2600" - id = :"\u2604" - msg = "undefined method `#{id}' for \"#{str}\":String" - assert_raise_with_message(NoMethodError, msg, bug3237) do - str.__send__(id) - end - end - def test_errno assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding) end @@ -499,6 +581,35 @@ end.join end; end + def test_ensure_after_nomemoryerror + skip "Forcing NoMemoryError causes problems in some environments" + assert_separately([], "$_ = 'a' * 1_000_000_000_000_000_000") + rescue NoMemoryError + assert_raise(NoMemoryError) do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug15779 = bug15779 = '[ruby-core:92342]' + begin; + require 'open-uri' + + begin + 'a' * 1_000_000_000_000_000_000 + ensure + URI.open('http://www.ruby-lang.org/') + end + end; + end + rescue Test::Unit::AssertionFailedError + # Possibly compiled with -DRUBY_DEBUG, in which + # case rb_bug is used instead of NoMemoryError, + # and we cannot test ensure after NoMemoryError. + rescue RangeError + # MingW can raise RangeError instead of NoMemoryError, + # so we cannot test this case. + rescue Timeout::Error + # Solaris 11 CI times out instead of raising NoMemoryError, + # so we cannot test this case. + end + def test_equal bug5865 = '[ruby-core:41979]' assert_equal(RuntimeError.new("a"), RuntimeError.new("a"), bug5865) @@ -753,7 +864,7 @@ end.join bug12741 = '[ruby-core:77222] [Bug #12741]' x = Thread.current - q = Queue.new + q = Thread::Queue.new y = Thread.start do q.pop begin @@ -824,261 +935,28 @@ end.join } end - def test_anonymous_message - assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/) - end - - PrettyObject = - Class.new(BasicObject) do - alias object_id __id__ - def pretty_inspect; "`obj'"; end - alias inspect pretty_inspect - end - - def test_frozen_error_receiver - obj = Object.new.freeze - (obj.foo = 1) rescue (e = $!) - assert_same(obj, e.receiver) - obj.singleton_class.const_set(:A, 2) rescue (e = $!) - assert_same(obj.singleton_class, e.receiver) - end - - def test_frozen_error_initialize - obj = Object.new - exc = FrozenError.new("bar", obj) - assert_equal("bar", exc.message) - assert_same(obj, exc.receiver) - - exc = FrozenError.new("bar") - assert_equal("bar", exc.message) - assert_raise_with_message(ArgumentError, "no receiver is available") { - exc.receiver - } - - exc = FrozenError.new - assert_equal("FrozenError", exc.message) - assert_raise_with_message(ArgumentError, "no receiver is available") { - exc.receiver - } - end - - def test_frozen_error_message - obj = Object.new.freeze - e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { - obj.instance_variable_set(:@test, true) - } - assert_include(e.message, obj.inspect) - - klass = Class.new do - def init - @x = true - end - def inspect - init - super - end - end - obj = klass.new.freeze - e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { - obj.init - } - assert_include(e.message, klass.inspect) - end - - def test_name_error_new_default - error = NameError.new - assert_equal("NameError", error.message) - end - - def test_name_error_new_message - error = NameError.new("Message") - assert_equal("Message", error.message) - end - - def test_name_error_new_name - error = NameError.new("Message") - assert_nil(error.name) - - error = NameError.new("Message", :foo) - assert_equal(:foo, error.name) - end - - def test_name_error_new_receiver - receiver = Object.new - - error = NameError.new - assert_raise(ArgumentError) {error.receiver} - assert_equal("NameError", error.message) - - error = NameError.new(receiver: receiver) - assert_equal(["NameError", receiver], - [error.message, error.receiver]) - - error = NameError.new("Message", :foo, receiver: receiver) - assert_equal(["Message", receiver, :foo], - [error.message, error.receiver, error.name]) - end - - def test_nomethod_error_new_default - error = NoMethodError.new - assert_equal("NoMethodError", error.message) - end - - def test_nomethod_error_new_message - error = NoMethodError.new("Message") - assert_equal("Message", error.message) - end - - def test_nomethod_error_new_name - error = NoMethodError.new("Message") - assert_nil(error.name) - - error = NoMethodError.new("Message", :foo) - assert_equal(:foo, error.name) - end - - def test_nomethod_error_new_name_args - error = NoMethodError.new("Message", :foo) - assert_nil(error.args) - - error = NoMethodError.new("Message", :foo, [1, 2]) - assert_equal([:foo, [1, 2]], [error.name, error.args]) - end - - def test_nomethod_error_new_name_args_priv - error = NoMethodError.new("Message", :foo, [1, 2]) - assert_not_predicate(error, :private_call?) - - error = NoMethodError.new("Message", :foo, [1, 2], true) - assert_equal([:foo, [1, 2], true], - [error.name, error.args, error.private_call?]) - end - - def test_nomethod_error_new_receiver - receiver = Object.new - - error = NoMethodError.new - assert_raise(ArgumentError) {error.receiver} - - error = NoMethodError.new(receiver: receiver) - assert_equal(receiver, error.receiver) - - error = NoMethodError.new("Message") - assert_raise(ArgumentError) {error.receiver} - - error = NoMethodError.new("Message", receiver: receiver) - assert_equal(["Message", receiver], - [error.message, error.receiver]) - - error = NoMethodError.new("Message", :foo) - assert_raise(ArgumentError) {error.receiver} - - msg = defined?(DidYouMean.formatter) ? - "Message\nDid you mean? for" : "Message" - - error = NoMethodError.new("Message", :foo, receiver: receiver) - assert_equal([msg, :foo, receiver], - [error.message, error.name, error.receiver]) - - error = NoMethodError.new("Message", :foo, [1, 2]) - assert_raise(ArgumentError) {error.receiver} - - error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver) - assert_equal([msg, :foo, [1, 2], receiver], - [error.message, error.name, error.args, error.receiver]) - - error = NoMethodError.new("Message", :foo, [1, 2], true) - assert_raise(ArgumentError) {error.receiver} - - error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver) - assert_equal([:foo, [1, 2], true, receiver], - [error.name, error.args, error.private_call?, error.receiver]) - end - - def test_name_error_info_const - obj = PrettyObject.new - - e = assert_raise(NameError) { - obj.instance_eval("Object") - } - assert_equal(:Object, e.name) - - e = assert_raise(NameError) { - BasicObject::X - } - assert_same(BasicObject, e.receiver) - assert_equal(:X, e.name) - end - - def test_name_error_info_method - obj = PrettyObject.new - - e = assert_raise(NameError) { - obj.instance_eval {foo} - } - assert_equal(:foo, e.name) - assert_same(obj, e.receiver) - - e = assert_raise(NoMethodError) { - obj.foo(1, 2) - } - assert_equal(:foo, e.name) - assert_equal([1, 2], e.args) - assert_same(obj, e.receiver) - assert_not_predicate(e, :private_call?) - - e = assert_raise(NoMethodError) { - obj.instance_eval {foo(1, 2)} - } - assert_equal(:foo, e.name) - assert_equal([1, 2], e.args) - assert_same(obj, e.receiver) - assert_predicate(e, :private_call?) - end - - def test_name_error_info_local_variables - obj = PrettyObject.new - def obj.test(a, b=nil, *c, &d) - e = a - 1.times {|f| g = foo; g} - e + def test_cause_exception_in_cause_message + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}") do |outs, errs, status| + begin; + exc = Class.new(StandardError) do + def initialize(obj, cnt) + super(obj) + @errcnt = cnt + end + def to_s + return super if @errcnt <= 0 + @errcnt -= 1 + raise "xxx" + end + end.new("ok", 10) + raise "[Bug #17033]", cause: exc + end; + assert_equal(1, errs.count {|m| m.include?("[Bug #17033]")}, proc {errs.pretty_inspect}) end - - e = assert_raise(NameError) { - obj.test(3) - } - assert_equal(:foo, e.name) - assert_same(obj, e.receiver) - assert_equal(%i[a b c d e f g], e.local_variables.sort) end - def test_name_error_info_method_missing - obj = PrettyObject.new - def obj.method_missing(*) - super - end - - e = assert_raise(NoMethodError) { - obj.foo(1, 2) - } - assert_equal(:foo, e.name) - assert_equal([1, 2], e.args) - assert_same(obj, e.receiver) - assert_not_predicate(e, :private_call?) - - e = assert_raise(NoMethodError) { - obj.instance_eval {foo(1, 2)} - } - assert_equal(:foo, e.name) - assert_equal([1, 2], e.args) - assert_same(obj, e.receiver) - assert_predicate(e, :private_call?) - end - - def test_name_error_info_parent_iseq_mark - assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;') - -> {require ARGV[0]}.call - end; + def test_anonymous_message + assert_in_out_err([], "raise Class.new(RuntimeError), 'foo'", [], /foo\n/) end def test_output_string_encoding @@ -1158,25 +1036,37 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end end - def capture_warning_warn + def capture_warning_warn(category: false) verbose = $VERBOSE + deprecated = Warning[:deprecated] + experimental = Warning[:experimental] warning = [] ::Warning.class_eval do alias_method :warn2, :warn remove_method :warn - define_method(:warn) do |str| - warning << str + if category + define_method(:warn) do |str, category: nil| + warning << [str, category] + end + else + define_method(:warn) do |str| + warning << str + end end end $VERBOSE = true + Warning[:deprecated] = true + Warning[:experimental] = true yield return warning ensure $VERBOSE = verbose + Warning[:deprecated] = deprecated + Warning[:experimental] = experimental ::Warning.class_eval do remove_method :warn @@ -1186,14 +1076,38 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end def test_warning_warn - warning = capture_warning_warn {@a} - assert_match(/instance variable @a not initialized/, warning[0]) + warning = capture_warning_warn {$asdfasdsda_test_warning_warn} + assert_match(/global variable `\$asdfasdsda_test_warning_warn' not initialized/, warning[0]) assert_equal(["a\nz\n"], capture_warning_warn {warn "a\n", "z"}) assert_equal([], capture_warning_warn {warn}) assert_equal(["\n"], capture_warning_warn {warn ""}) end + def test_warn_deprecated_backwards_compatibility_category + warning = capture_warning_warn { Dir.exists?("non-existent") } + + 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 + + def test_warn_deprecated_to_remove_category + warning = capture_warning_warn(category: true) { Object.new.tainted? } + + assert_equal :deprecated, warning[0][1] + end + def test_kernel_warn_uplevel warning = capture_warning_warn {warn("test warning", uplevel: 0)} assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) @@ -1205,8 +1119,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| assert_raise(ArgumentError) {warn("test warning", uplevel: -1)} assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/) warning = capture_warning_warn {warn("test warning", {uplevel: 0})} - assert_equal("#{__FILE__}:#{__LINE__-1}: warning: The last argument is used as the keyword parameter\n", warning[0]) - assert_match(/warning: for method defined here|warning: test warning/, warning[1]) + assert_match(/test warning.*{:uplevel=>0}/m, warning[0]) warning = capture_warning_warn {warn("test warning", **{uplevel: 0})} assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) warning = capture_warning_warn {warn("test warning", {uplevel: 0}, **{})} @@ -1235,10 +1148,12 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| t.puts "require '#{basename}'" t.close $LOAD_PATH.push(File.dirname(t)) - warning = capture_warning_warn {require basename} + warning = capture_warning_warn { + assert require(basename) + } ensure $LOAD_PATH.pop - $LOADED_FEATURES.delete(t) + $LOADED_FEATURES.delete(t.path) end assert_equal(1, warning.size) assert_match(/circular require/, warning.first) @@ -1246,7 +1161,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end def test_warning_warn_super - assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /instance variable @a not initialized/) + assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /global variable `\$asdfiasdofa_test_warning_warn_super' not initialized/) {# module Warning def warn(message) @@ -1255,7 +1170,7 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end $VERBOSE = true - @a + $asdfiasdofa_test_warning_warn_super }; end @@ -1263,6 +1178,55 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| assert_raise(TypeError) {Warning[nil]} assert_raise(ArgumentError) {Warning[:XXXX]} assert_include([true, false], Warning[:deprecated]) + assert_include([true, false], Warning[:experimental]) + end + + def test_warning_category_deprecated + warning = EnvUtil.verbose_warning do + deprecated = Warning[:deprecated] + Warning[:deprecated] = true + Warning.warn "deprecated feature", category: :deprecated + ensure + Warning[:deprecated] = deprecated + end + assert_equal "deprecated feature", warning + + warning = EnvUtil.verbose_warning do + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + Warning.warn "deprecated feature", category: :deprecated + ensure + Warning[:deprecated] = deprecated + end + assert_empty warning + end + + def test_warning_category_experimental + warning = EnvUtil.verbose_warning do + experimental = Warning[:experimental] + Warning[:experimental] = true + Warning.warn "experimental feature", category: :experimental + ensure + Warning[:experimental] = experimental + end + assert_equal "experimental feature", warning + + warning = EnvUtil.verbose_warning do + experimental = Warning[:experimental] + Warning[:experimental] = false + Warning.warn "experimental feature", category: :experimental + ensure + Warning[:experimental] = experimental + end + 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 @@ -1414,13 +1378,11 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| if Exception.to_tty? assert_match(/\e/, message) message = message.gsub(/\e\[[\d;]*m/, '') - assert_operator(message, :start_with?, remark) - assert_operator(message, :end_with?, bottom) else assert_not_match(/\e/, message) - assert_operator(message, :start_with?, bottom) - assert_operator(message, :end_with?, top) end + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) end def test_exception_in_message @@ -1450,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; diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index a7ed9ac7e0..e4b7322bd9 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -34,8 +34,8 @@ class TestFiber < Test::Unit::TestCase end def test_many_fibers - skip 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if RubyVM::MJIT.enabled? - max = 10_000 + skip '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: 60 + assert_normal_exit <<-SRC, timeout: (/solaris/i =~ RUBY_PLATFORM ? 1000 : 60) max = 1000 @cnt = 0 (1..100).map{|ti| @@ -169,6 +169,16 @@ class TestFiber < Test::Unit::TestCase assert_equal(:ok, fib.raise) end + def test_raise_transferring_fiber + root = Fiber.current + fib = Fiber.new { root.transfer } + fib.transfer + assert_raise(RuntimeError){ + fib.raise "can raise with transfer: true" + } + assert_not_predicate(fib, :alive?) + end + def test_transfer ary = [] f2 = nil @@ -184,6 +194,33 @@ class TestFiber < Test::Unit::TestCase assert_equal([:baz], ary) end + def test_terminate_transferred_fiber + log = [] + fa1 = fa2 = fb1 = r1 = nil + + fa1 = Fiber.new{ + fa2 = Fiber.new{ + log << :fa2_terminate + } + fa2.resume + log << :fa1_terminate + } + fb1 = Fiber.new{ + fa1.transfer + log << :fb1_terminate + } + + r1 = Fiber.new{ + fb1.transfer + log << :r1_terminate + } + + r1.resume + log << :root_terminate + + assert_equal [:fa2_terminate, :fa1_terminate, :r1_terminate, :root_terminate], log + end + def test_tls # def tvar(var, val) @@ -278,29 +315,71 @@ class TestFiber < Test::Unit::TestCase assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s), bug5083) end - def test_prohibit_resume_transferred_fiber + def test_prohibit_transfer_to_resuming_fiber + root_fiber = Fiber.current + assert_raise(FiberError){ - root_fiber = Fiber.current - f = Fiber.new{ - root_fiber.transfer - } - f.transfer - f.resume + fiber = Fiber.new{ root_fiber.transfer } + fiber.resume + } + + fa1 = Fiber.new{ + _fa2 = Fiber.new{ root_fiber.transfer } + } + fb1 = Fiber.new{ + _fb2 = Fiber.new{ root_fiber.transfer } } + fa1.transfer + fb1.transfer + assert_raise(FiberError){ - g=nil - f=Fiber.new{ - g.resume - g.resume - } - g=Fiber.new{ - f.resume - f.resume + fa1.transfer + } + assert_raise(FiberError){ + fb1.transfer + } + end + + def test_prohibit_transfer_to_yielding_fiber + f1 = f2 = f3 = nil + + f1 = Fiber.new{ + f2 = Fiber.new{ + f3 = Fiber.new{ + p f3: Fiber.yield + } + f3.resume } - f.transfer + f2.resume + } + f1.resume + + assert_raise(FiberError){ f3.transfer 10 } + end + + def test_prohibit_resume_to_transferring_fiber + root_fiber = Fiber.current + + assert_raise(FiberError){ + Fiber.new{ + root_fiber.resume + }.transfer + } + + f1 = f2 = nil + f1 = Fiber.new do + f2.transfer + end + f2 = Fiber.new do + f1.resume # attempt to resume transferring fiber + end + + assert_raise(FiberError){ + f1.transfer } end + def test_fork_from_fiber skip 'fork not supported' unless Process.respond_to?(:fork) pid = nil @@ -312,13 +391,12 @@ class TestFiber < Test::Unit::TestCase Fiber.new { xpid = fork do # enough to trigger GC on old root fiber - count = 10000 - count = 1000 if /openbsd/i =~ RUBY_PLATFORM + count = 1000 count.times do Fiber.new {}.transfer Fiber.new { Fiber.yield } end - exit!(0) + exit!(true) end }.transfer _, status = Process.waitpid2(xpid) @@ -327,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 @@ -341,56 +424,12 @@ class TestFiber < Test::Unit::TestCase def test_fatal_in_fiber assert_in_out_err(["-r-test-/fatal/rb_fatal", "-e", <<-EOS], "", [], /ok/) Fiber.new{ - rb_fatal "ok" + Bug.rb_fatal "ok" }.resume puts :ng # unreachable. EOS end - def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true - env = {} - env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size - env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size - out = Dir.mktmpdir("test_fiber") {|tmpdir| - out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30) - assert(!status.signaled?, FailDesc[status, nil, err]) - out - } - use_length ? out.length : out - end - - def test_stack_size - h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false)) - h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false)) - h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 5, 1024 * 1024 * 10, false)) - - assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size]) - assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size]) - assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size]) - assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size]) - - # check VM machine stack size - script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume' - size_default = invoke_rec script, nil, nil - assert_operator(size_default, :>, 0) - size_0 = invoke_rec script, 0, nil - assert_operator(size_default, :>, size_0) - size_large = invoke_rec script, 1024 * 1024 * 5, nil - assert_operator(size_default, :<, size_large) - - return if /mswin|mingw/ =~ RUBY_PLATFORM - - # check machine stack size - # Note that machine stack size may not change size (depend on OSs) - script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume' - vm_stack_size = 1024 * 1024 - size_default = invoke_rec script, vm_stack_size, nil - size_0 = invoke_rec script, vm_stack_size, 0 - assert_operator(size_default, :>=, size_0) - size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10 - assert_operator(size_default, :<=, size_large) - end - def test_separate_lastmatch bug7678 = '[ruby-core:51331]' /a/ =~ "a" diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb index 96e2ca291f..d570b6f6fb 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -233,11 +233,9 @@ class TestFile < Test::Unit::TestCase end def test_chown - assert_nothing_raised { - File.open(__FILE__) {|f| f.chown(-1, -1) } - } - assert_nothing_raised("[ruby-dev:27140]") { - File.open(__FILE__) {|f| f.chown nil, nil } + Tempfile.create("test-chown") {|f| + assert_nothing_raised {f.chown(-1, -1)} + assert_nothing_raised("[ruby-dev:27140]") {f.chown(nil, nil)} } end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 975bcb6bc2..a960ef0d74 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -78,6 +78,19 @@ class TestFileExhaustive < Test::Unit::TestCase @notownedfile end + def grpownedfile + return nil unless POSIX + return @grpownedfile if defined? @grpownedfile + if group = (Process.groups - [Process.egid]).last + grpownedfile = make_tmp_filename("grpownedfile") + make_file("grpowned", grpownedfile) + File.chown(nil, group, grpownedfile) + return @grpownedfile = grpownedfile + end + rescue + @grpownedfile = nil + end + def suidfile return @suidfile if defined? @suidfile if POSIX @@ -130,7 +143,7 @@ class TestFileExhaustive < Test::Unit::TestCase @hardlinkfile = make_tmp_filename("hardlinkfile") begin File.link(regular_file, @hardlinkfile) - rescue NotImplementedError, Errno::EINVAL # EINVAL for Windows Vista + rescue NotImplementedError, Errno::EINVAL, Errno::EACCES # EINVAL for Windows Vista, EACCES for Android Termux @hardlinkfile = nil end @hardlinkfile @@ -160,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 @@ -319,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 @@ -493,6 +504,9 @@ class TestFileExhaustive < Test::Unit::TestCase def test_grpowned_p ## xxx assert_file.grpowned?(regular_file) assert_file.grpowned?(utf8_file) + if file = grpownedfile + assert_file.grpowned?(file) + end end if POSIX def io_open(file_name) @@ -679,6 +693,13 @@ class TestFileExhaustive < Test::Unit::TestCase File.utime(t + 1, t + 2, zerofile) assert_equal(t + 1, File.atime(zerofile)) assert_equal(t + 2, File.mtime(zerofile)) + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + path = "foo\u{30b3 30d4 30fc}" + File.write(path, "") rescue next + assert_equal(1, File.utime(nil, nil, path)) + end + end end def test_utime_symlinkfile @@ -880,6 +901,8 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path)) assert_incompatible_encoding {|d| File.expand_path(d)} + + assert_equal(Encoding::UTF_8, File.expand_path("foo", "#{drive}/").encoding) end def test_expand_path_encoding_filesystem @@ -1245,6 +1268,12 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(@dir, File.dirname(regular_file)) assert_equal(@dir, File.dirname(utf8_file)) assert_equal(".", File.dirname("")) + assert_equal(regular_file, File.dirname(regular_file, 0)) + assert_equal(@dir, File.dirname(regular_file, 1)) + assert_equal(File.dirname(@dir), File.dirname(regular_file, 2)) + return if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # rootdir and tmpdir are in different drives + assert_equal(rootdir, File.dirname(regular_file, regular_file.count('/'))) + assert_raise(ArgumentError) {File.dirname(regular_file, -1)} end def test_dirname_encoding @@ -1377,21 +1406,24 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_flock_exclusive + 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 @@ -1403,21 +1435,24 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_flock_shared + 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 @@ -1439,6 +1474,7 @@ class TestFileExhaustive < Test::Unit::TestCase fn1, zerofile, notownedfile, + grpownedfile, suidfile, sgidfile, stickyfile, @@ -1449,31 +1485,56 @@ class TestFileExhaustive < Test::Unit::TestCase fifo, socket ].compact.each do |f| - assert_equal(File.atime(f), test(?A, f)) - assert_equal(File.ctime(f), test(?C, f)) - assert_equal(File.mtime(f), test(?M, f)) - assert_equal(File.blockdev?(f), test(?b, f)) - assert_equal(File.chardev?(f), test(?c, f)) - assert_equal(File.directory?(f), test(?d, f)) - assert_equal(File.exist?(f), test(?e, f)) - assert_equal(File.file?(f), test(?f, f)) - assert_equal(File.setgid?(f), test(?g, f)) - assert_equal(File.grpowned?(f), test(?G, f)) - assert_equal(File.sticky?(f), test(?k, f)) - assert_equal(File.symlink?(f), test(?l, f)) - assert_equal(File.owned?(f), test(?o, f)) - assert_nothing_raised { test(?O, f) } - assert_equal(File.pipe?(f), test(?p, f)) - assert_equal(File.readable?(f), test(?r, f)) - assert_equal(File.readable_real?(f), test(?R, f)) - assert_equal(File.size?(f), test(?s, f)) - assert_equal(File.socket?(f), test(?S, f)) - assert_equal(File.setuid?(f), test(?u, f)) - assert_equal(File.writable?(f), test(?w, f)) - assert_equal(File.writable_real?(f), test(?W, f)) - assert_equal(File.executable?(f), test(?x, f)) - assert_equal(File.executable_real?(f), test(?X, f)) - assert_equal(File.zero?(f), test(?z, f)) + assert_equal(File.atime(f), test(?A, f), f) + assert_equal(File.ctime(f), test(?C, f), f) + assert_equal(File.mtime(f), test(?M, f), f) + assert_equal(File.blockdev?(f), test(?b, f), f) + assert_equal(File.chardev?(f), test(?c, f), f) + assert_equal(File.directory?(f), test(?d, f), f) + assert_equal(File.exist?(f), test(?e, f), f) + assert_equal(File.file?(f), test(?f, f), f) + assert_equal(File.setgid?(f), test(?g, f), f) + assert_equal(File.grpowned?(f), test(?G, f), f) + assert_equal(File.sticky?(f), test(?k, f), f) + assert_equal(File.symlink?(f), test(?l, f), f) + assert_equal(File.owned?(f), test(?o, f), f) + assert_nothing_raised(f) { test(?O, f) } + assert_equal(File.pipe?(f), test(?p, f), f) + assert_equal(File.readable?(f), test(?r, f), f) + assert_equal(File.readable_real?(f), test(?R, f), f) + assert_equal(File.size?(f), test(?s, f), f) + assert_equal(File.socket?(f), test(?S, f), f) + assert_equal(File.setuid?(f), test(?u, f), f) + assert_equal(File.writable?(f), test(?w, f), f) + assert_equal(File.writable_real?(f), test(?W, f), f) + 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_equal(stat.blockdev?, File.blockdev?(f), f) + assert_equal(stat.chardev?, File.chardev?(f), f) + assert_equal(stat.directory?, File.directory?(f), f) + assert_equal(stat.file?, File.file?(f), f) + assert_equal(stat.setgid?, File.setgid?(f), f) + assert_equal(stat.grpowned?, File.grpowned?(f), f) + assert_equal(stat.sticky?, File.sticky?(f), f) + assert_equal(File.lstat(f).symlink?, File.symlink?(f), f) + assert_equal(stat.owned?, File.owned?(f), f) + assert_equal(stat.pipe?, File.pipe?(f), f) + assert_equal(stat.readable?, File.readable?(f), f) + assert_equal(stat.readable_real?, File.readable_real?(f), f) + assert_equal(stat.size?, File.size?(f), f) + assert_equal(stat.socket?, File.socket?(f), f) + assert_equal(stat.setuid?, File.setuid?(f), f) + assert_equal(stat.writable?, File.writable?(f), f) + assert_equal(stat.writable_real?, File.writable_real?(f), f) + assert_equal(stat.executable?, File.executable?(f), f) + assert_equal(stat.executable_real?, File.executable_real?(f), f) + assert_equal(stat.zero?, File.zero?(f), f) end assert_equal(false, test(?-, @dir, fn1)) assert_equal(true, test(?-, fn1, fn1)) @@ -1583,7 +1644,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 @@ -1674,6 +1735,9 @@ class TestFileExhaustive < Test::Unit::TestCase def test_stat_grpowned_p ## xxx assert_predicate(File::Stat.new(regular_file), :grpowned?) + if file = grpownedfile + assert_predicate(File::Stat.new(file), :grpowned?) + end end if POSIX def test_stat_suid diff --git a/test/ruby/test_fixnum.rb b/test/ruby/test_fixnum.rb index bd18067dda..2878258920 100644 --- a/test/ruby/test_fixnum.rb +++ b/test/ruby/test_fixnum.rb @@ -4,7 +4,6 @@ require 'test/unit' class TestFixnum < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index 7cbf3b5a8f..57a46fce92 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -171,6 +171,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 @@ -305,6 +323,7 @@ class TestFloat < Test::Unit::TestCase assert_equal(1.0, 1.0 ** (2**32)) assert_equal(1.0, 1.0 ** 1.0) assert_raise(TypeError) { 1.0 ** nil } + assert_equal(9.0, 3.0 ** 2) end def test_eql @@ -764,6 +783,9 @@ class TestFloat < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /xxx/) { 1.0.round(half: "\0xxx") } + assert_raise_with_message(Encoding::CompatibilityError, /ASCII incompatible/) { + 1.0.round(half: "up".force_encoding("utf-16be")) + } end def test_Float @@ -878,6 +900,11 @@ class TestFloat < Test::Unit::TestCase end assert_equal([5.0, 4.0, 3.0, 2.0], 5.0.step(1.5, -1).to_a) + + assert_equal(11, ((0.24901079128550474)..(340.2500808898068)).step(34.00010700985213).to_a.size) + assert_equal(11, ((0.24901079128550474)..(340.25008088980684)).step(34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)..(-340.2500808898068)).step(-34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)..(-340.25008088980684)).step(-34.00010700985213).to_a.size) end def test_step2 @@ -889,7 +916,9 @@ class TestFloat < Test::Unit::TestCase a = rand b = a+rand*1000 s = (b - a) / 10 - assert_equal(10, (a...b).step(s).to_a.length) + b = a + s*9.999999 + seq = (a...b).step(s) + assert_equal(10, seq.to_a.length, seq.inspect) end assert_equal([1.0, 2.9, 4.8, 6.699999999999999], (1.0...6.8).step(1.9).to_a) @@ -898,6 +927,11 @@ class TestFloat < Test::Unit::TestCase (1.0 ... e).step(1E-16) do |n| assert_operator(n, :<=, e) end + + assert_equal(10, ((0.24901079128550474)...(340.2500808898068)).step(34.00010700985213).to_a.size) + assert_equal(11, ((0.24901079128550474)...(340.25008088980684)).step(34.00010700985213).to_a.size) + assert_equal(10, ((-0.24901079128550474)...(-340.2500808898068)).step(-34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)...(-340.25008088980684)).step(-34.00010700985213).to_a.size) end def test_singleton_method diff --git a/test/ruby/test_frozen_error.rb b/test/ruby/test_frozen_error.rb new file mode 100644 index 0000000000..ace1e4c775 --- /dev/null +++ b/test/ruby/test_frozen_error.rb @@ -0,0 +1,57 @@ +require 'test/unit' + +class TestFrozenError < Test::Unit::TestCase + def test_new_default + exc = FrozenError.new + assert_equal("FrozenError", exc.message) + assert_raise_with_message(ArgumentError, "no receiver is available") { + exc.receiver + } + end + + def test_new_message + exc = FrozenError.new("bar") + assert_equal("bar", exc.message) + assert_raise_with_message(ArgumentError, "no receiver is available") { + exc.receiver + } + end + + def test_new_receiver + obj = Object.new + exc = FrozenError.new("bar", receiver: obj) + assert_equal("bar", exc.message) + assert_same(obj, exc.receiver) + end + + def test_message + obj = Object.new.freeze + e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { + obj.instance_variable_set(:@test, true) + } + assert_include(e.message, obj.inspect) + + klass = Class.new do + def init + @x = true + end + def inspect + init + super + end + end + obj = klass.new.freeze + e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { + obj.init + } + assert_include(e.message, klass.inspect) + end + + def test_receiver + obj = Object.new.freeze + e = assert_raise(FrozenError) {def obj.foo; end} + assert_same(obj, e.receiver) + e = assert_raise(FrozenError) {obj.singleton_class.const_set(:A, 2)} + assert_same(obj.singleton_class, e.receiver) + end +end diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index ef99f69f50..fa81bcb1ad 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -56,6 +56,7 @@ class TestGc < Test::Unit::TestCase return unless use_rgengc? skip 'stress' if GC.stress + 3.times { GC.start } # full mark and next time it should be minor mark GC.start(full_mark: false) assert_nil GC.latest_gc_info(:major_by) @@ -91,16 +92,23 @@ class TestGc < Test::Unit::TestCase assert_kind_of(Integer, res[:count]) stat, count = {}, {} - GC.start - GC.stat(stat) - ObjectSpace.count_objects(count) + 2.times{ # to ignore const cache imemo creation + GC.start + GC.stat(stat) + ObjectSpace.count_objects(count) + # repeat same methods invocation for cache object creation. + GC.stat(stat) + ObjectSpace.count_objects(count) + } assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots]) assert_equal(count[:FREE], stat[:heap_free_slots]) # measure again without GC.start - 1000.times{ "a" + "b" } - GC.stat(stat) - ObjectSpace.count_objects(count) + 2.times{ # to ignore const cache imemo creation + 1000.times{ "a" + "b" } + GC.stat(stat) + ObjectSpace.count_objects(count) + } assert_equal(count[:FREE], stat[:heap_free_slots]) end @@ -141,10 +149,15 @@ class TestGc < Test::Unit::TestCase assert_equal :newobj, GC.latest_gc_info[:gc_by] eom + GC.latest_gc_info(h = {}) # allocate hash and rehearsal GC.start - assert_equal :force, GC.latest_gc_info[:major_by] if use_rgengc? - assert_equal :method, GC.latest_gc_info[:gc_by] - assert_equal true, GC.latest_gc_info[:immediate_sweep] + GC.start + GC.start + GC.latest_gc_info(h) + + assert_equal :force, h[:major_by] if use_rgengc? + assert_equal :method, h[:gc_by] + assert_equal true, h[:immediate_sweep] GC.stress = true assert_equal :force, GC.latest_gc_info[:major_by] @@ -162,6 +175,16 @@ 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_stress_compile_send + assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "") + GC.stress = true + begin + eval("A::B.c(1, 1, d: 234)") + rescue + end + EOS + end + def test_singleton_method assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]") GC.stress = true @@ -191,6 +214,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" } @@ -224,12 +252,6 @@ class TestGc < Test::Unit::TestCase # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0 assert_in_out_err([env, "-e", "1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") if use_rgengc? - # check obsolete - assert_in_out_err([{'RUBY_FREE_MIN' => '100'}, '-w', '-eexit'], '', [], - /RUBY_FREE_MIN is obsolete. Use RUBY_GC_HEAP_FREE_SLOTS instead/) - assert_in_out_err([{'RUBY_HEAP_MIN_SLOTS' => '100'}, '-w', '-eexit'], '', [], - /RUBY_HEAP_MIN_SLOTS is obsolete. Use RUBY_GC_HEAP_INIT_SLOTS instead/) - env = { "RUBY_GC_MALLOC_LIMIT" => "60000000", "RUBY_GC_MALLOC_LIMIT_MAX" => "160000000", @@ -425,49 +447,43 @@ class TestGc < Test::Unit::TestCase end def test_exception_in_finalizer_procs - result = [] + assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) c1 = proc do - result << :c1 + puts "c1" raise end c2 = proc do - result << :c2 + puts "c2" raise end - tap { - tap { + begin; + tap do obj = Object.new ObjectSpace.define_finalizer(obj, c1) ObjectSpace.define_finalizer(obj, c2) obj = nil - } - } - GC.start - skip "finalizers did not get run" if result.empty? - assert_equal([:c1, :c2], result) + end + end; end def test_exception_in_finalizer_method - @result = [] + assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) def self.c1(x) - @result << :c1 + puts "c1" raise end def self.c2(x) - @result << :c2 + puts "c2" raise end - tap { - tap { + begin; + tap do obj = Object.new ObjectSpace.define_finalizer(obj, method(:c1)) ObjectSpace.define_finalizer(obj, method(:c2)) obj = nil - } - } - GC.start - skip "finalizers did not get run" if @result.empty? - assert_equal([:c1, :c2], @result) + end + end; end def test_object_ids_never_repeat @@ -477,4 +493,10 @@ class TestGc < Test::Unit::TestCase b = 1000.times.map { Object.new.object_id } assert_empty(a & b) end + + def test_ast_node_buffer + # https://github.com/ruby/ruby/pull/4416 + Module.new.class_eval( (["# shareable_constant_value: literal"] + + (0..100000).map {|i| "M#{ i } = {}" }).join("\n")) + end end diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index e93e775279..42ad028530 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -1,8 +1,115 @@ # frozen_string_literal: true 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 + end + + private + + def supports_auto_compact? + return true unless defined?(Etc::SC_PAGE_SIZE) + + begin + return GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE] % Etc.sysconf(Etc::SC_PAGE_SIZE) == 0 + rescue NotImplementedError + rescue ArgumentError + end + + true + end + end + + include SupportsCompact + + class AutoCompact < Test::Unit::TestCase + include SupportsCompact + + def test_enable_autocompact + before = GC.auto_compact + GC.auto_compact = true + assert GC.auto_compact + ensure + GC.auto_compact = before + end + + def test_disable_autocompact + before = GC.auto_compact + GC.auto_compact = false + refute GC.auto_compact + ensure + GC.auto_compact = before + end + + def test_major_compacts + before = GC.auto_compact + GC.auto_compact = true + compact = GC.stat :compact_count + GC.start + assert_operator GC.stat(:compact_count), :>, compact + ensure + GC.auto_compact = before + end + + def test_implicit_compaction_does_something + before = GC.auto_compact + list = [] + list2 = [] + + # Try to make some fragmentation + 500.times { + list << Object.new + Object.new + Object.new + } + count = GC.stat :compact_count + GC.auto_compact = true + n = 1_000_000 + n.times do + break if count < GC.stat(:compact_count) + list2 << Object.new + end and skip "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? + ensure + GC.auto_compact = before + end + end + + def os_page_size + return true unless defined?(Etc::SC_PAGE_SIZE) + end + + def setup + skip "autocompact not supported on this platform" unless supports_auto_compact? + super + end + + def test_gc_compact_stats + list = [] + + # Try to make some fragmentation + 500.times { + list << Object.new + Object.new + Object.new + } + compact_stats = GC.compact + refute_predicate compact_stats[:considered], :empty? + refute_predicate compact_stats[:moved], :empty? + end + def memory_location(obj) (Fiddle.dlwrap(obj) >> 1) end @@ -39,20 +146,24 @@ class TestGCCompact < Test::Unit::TestCase hash = list_of_objects.hash GC.verify_compaction_references(toward: :empty) assert_equal hash, list_of_objects.hash - end - - def walk_ast ast - children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node) - children.each do |child| - assert child.type - walk_ast child - end + GC.verify_compaction_references(double_heap: false) + assert_equal hash, list_of_objects.hash end def test_ast_compacts - ast = RubyVM::AbstractSyntaxTree.parse_file __FILE__ - assert GC.compact - walk_ast ast + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + def walk_ast ast + children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node) + children.each do |child| + assert child.type + walk_ast child + end + end + ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump} + assert GC.compact + walk_ast ast + end; end def test_compact_count diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index ccc3355930..683ec3855d 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -86,7 +86,6 @@ class TestHash < Test::Unit::TestCase 'nil' => nil ] @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -114,16 +113,36 @@ class TestHash < Test::Unit::TestCase assert_equal(2, h[1]) end - def test_dup_will_rehash - set1 = @cls[] - set2 = @cls[set1 => true] + def test_dup_will_not_rehash + assert_hash_does_not_rehash(&:dup) + end - set1[set1] = true + def assert_hash_does_not_rehash + obj = Object.new + class << obj + attr_accessor :hash_calls + def hash + @hash_calls += 1 + super + end + end + obj.hash_calls = 0 + hash = {obj => 42} + assert_equal(1, obj.hash_calls) + yield hash + assert_equal(1, obj.hash_calls) + end - assert_equal set2, set2.dup + def test_select_reject_will_not_rehash + assert_hash_does_not_rehash do |hash| + hash.select { true } + end + assert_hash_does_not_rehash do |hash| + hash.reject { false } + end end - def test_s_AREF + def test_s_AREF_from_hash h = @cls["a" => 100, "b" => 200] assert_equal(100, h['a']) assert_equal(200, h['b']) @@ -134,11 +153,21 @@ class TestHash < Test::Unit::TestCase assert_equal(200, h['b']) assert_nil(h['c']) + h = @cls[Hash.new(42)] + assert_nil(h['a']) + + h = @cls[Hash.new {42}] + assert_nil(h['a']) + end + + def test_s_AREF_from_list h = @cls["a", 100, "b", 200] assert_equal(100, h['a']) assert_equal(200, h['b']) assert_nil(h['c']) + end + def test_s_AREF_from_pairs h = @cls[[["a", 100], ["b", 200]]] assert_equal(100, h['a']) assert_equal(200, h['b']) @@ -266,10 +295,13 @@ class TestHash < Test::Unit::TestCase end def test_AREF_fstring_key + # warmup ObjectSpace.count_objects + ObjectSpace.count_objects + h = {"abc" => 1} - before = GC.stat(:total_allocated_objects) + before = ObjectSpace.count_objects[:T_STRING] 5.times{ h["abc"] } - assert_equal before, GC.stat(:total_allocated_objects) + assert_equal before, ObjectSpace.count_objects[:T_STRING] end def test_ASET_fstring_key @@ -417,6 +449,15 @@ class TestHash < Test::Unit::TestCase true } assert_equal(base.size, n) + + h = base.dup + assert_raise(FrozenError) do + h.delete_if do + h.freeze + true + end + end + assert_equal(base.dup, h) end def test_keep_if @@ -424,6 +465,14 @@ class TestHash < Test::Unit::TestCase assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 }) h = @cls[1=>2,3=>4,5=>6] assert_equal({1=>2,3=>4,5=>6}, h.keep_if{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.keep_if do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_compact @@ -453,6 +502,7 @@ class TestHash < Test::Unit::TestCase h1 = @cls[h => 1] assert_equal(h1, h1.dup) h[1] = 2 + h1.rehash assert_equal(h1, h1.dup) end @@ -699,6 +749,33 @@ class TestHash < Test::Unit::TestCase assert_not_send([h, :instance_variable_defined?, :@foo]) end + def test_reject_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + h2 = h.reject{|k,| k != 'str'} + assert_equal(expected, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.reject{true} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.reject{true} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.reject{|k,| k != 'str'} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + def test_reject! base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ] h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ] @@ -720,6 +797,15 @@ class TestHash < Test::Unit::TestCase h = base.dup assert_equal(h3, h.reject! {|k,v| v }) assert_equal(h3, h) + + h = base.dup + assert_raise(FrozenError) do + h.reject! do + h.freeze + true + end + end + assert_equal(base.dup, h) end def test_replace @@ -875,7 +961,7 @@ class TestHash < Test::Unit::TestCase def test_to_s h = @cls[ 1 => 2, "cat" => "dog", 1.5 => :fred ] assert_equal(h.inspect, h.to_s) - $, = ":" + assert_deprecated_warning { $, = ":" } assert_equal(h.inspect, h.to_s) h = @cls[] assert_equal(h.inspect, h.to_s) @@ -944,7 +1030,7 @@ class TestHash < Test::Unit::TestCase end def test_fetch2 - assert_equal(:bar, @h.fetch(0, :foo) { :bar }) + assert_equal(:bar, assert_warning(/block supersedes default value argument/) {@h.fetch(0, :foo) { :bar }}) end def test_default_proc @@ -983,6 +1069,19 @@ class TestHash < Test::Unit::TestCase assert_equal("FOO", 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 assert_equal({1=>2}, @cls[1=>2,3=>4].reject! {|k, v| k + v == 7 }) assert_nil(@cls[1=>2,3=>4].reject! {|k, v| k == 5 }) @@ -1017,12 +1116,47 @@ class TestHash < Test::Unit::TestCase assert_not_send([h, :instance_variable_defined?, :@foo]) end + def test_select_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + h2 = h.select{|k,| k == 'str'} + assert_equal(expected, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.select{false} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.select{false} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.select{|k,| k == 'str'} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + def test_select! h = @cls[1=>2,3=>4,5=>6] assert_equal(h, h.select! {|k, v| k + v >= 7 }) assert_equal({3=>4,5=>6}, h) h = @cls[1=>2,3=>4,5=>6] assert_equal(nil, h.select!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.select! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_slice @@ -1033,6 +1167,65 @@ class TestHash < Test::Unit::TestCase assert_equal({}, {}.slice) end + def test_slice_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + sliced = h.slice(str1, str2) + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + assert_equal(expected, sliced) + assert_equal(true, sliced.compare_by_identity?) + sliced = h.slice + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + sliced= h.slice + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + sliced = h.slice(str1, str2) + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + end + + def test_except + h = @cls[1=>2,3=>4,5=>6] + assert_equal({5=>6}, h.except(1, 3)) + assert_equal({1=>2,3=>4,5=>6}, h.except(7)) + assert_equal({1=>2,3=>4,5=>6}, h.except) + assert_equal({}, {}.except) + end + + def test_except_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + excepted = h.except(str1, str2) + assert_equal({1=>2,3=>4,5=>6}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + excepted = h.except + assert_equal(h, excepted) + assert_equal(true, excepted.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + excepted = h.except + assert_equal({}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + excepted = h.except(str1, str2) + assert_equal({}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + end + def test_filter assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].filter {|k, v| k + v >= 7 }) @@ -1067,6 +1260,14 @@ class TestHash < Test::Unit::TestCase assert_equal({3=>4,5=>6}, h) h = @cls[1=>2,3=>4,5=>6] assert_equal(nil, h.filter!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.filter! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) end def test_clear2 @@ -1090,6 +1291,15 @@ class TestHash < Test::Unit::TestCase assert_raise(FrozenError) { h2.replace(42) } end + def test_replace_memory_leak + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}") + h = ("aa".."zz").each_with_index.to_h + 10_000.times {h.dup} + begin; + 500_000.times {h.dup.replace(h)} + end; + end + def test_size2 assert_equal(0, @cls[].size) end @@ -1101,6 +1311,7 @@ class TestHash < Test::Unit::TestCase def o.to_hash; @cls[]; end def o.==(x); true; end assert_equal({}, o) + o.singleton_class.remove_method(:==) def o.==(x); false; end assert_not_equal({}, o) @@ -1117,6 +1328,7 @@ class TestHash < Test::Unit::TestCase def o.to_hash; @cls[]; end def o.eql?(x); true; end assert_send([@cls[], :eql?, o]) + o.singleton_class.remove_method(:eql?) def o.eql?(x); false; end assert_not_send([@cls[], :eql?, o]) end @@ -1157,6 +1369,20 @@ class TestHash < Test::Unit::TestCase assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1) end + def test_update5 + h = @cls[a: 1, b: 2, c: 3] + assert_raise(FrozenError) do + h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3], h) + + h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + 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_merge h1 = @cls[1=>2, 3=>4] h2 = {1=>3, 5=>7} @@ -1168,6 +1394,48 @@ class TestHash < Test::Unit::TestCase assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3) {|k, v1, v2| k + v1 + v2 }) end + def test_merge_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = h.dup + expected[7] = 8 + h2 = h.merge(7=>8) + assert_equal(expected, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h1 = @cls[7=>8] + h1.compare_by_identity + h2 = h.merge(7=>8) + assert_equal(h1, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_equal(true, h2.compare_by_identity?) + end + + def test_merge! + h = @cls[a: 1, b: 2, c: 3] + assert_raise(FrozenError) do + h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3], h) + + h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + 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_assoc assert_equal([3,4], @cls[1=>2, 3=>4, 5=>6].assoc(3)) assert_nil(@cls[1=>2, 3=>4, 5=>6].assoc(4)) @@ -1465,6 +1733,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]' @@ -1624,6 +1901,8 @@ class TestHash < Test::Unit::TestCase } assert_equal([10, 20, 30], [1, 2, 3].map(&h)) + + assert_equal(true, h.to_proc.lambda?) end def test_transform_keys @@ -1638,6 +1917,27 @@ class TestHash < Test::Unit::TestCase y = x.transform_keys.with_index {|k, i| "#{k}.#{i}" } assert_equal(%w(a.0 b.1 c.2), y.keys) + + assert_equal({A: 1, B: 2, c: 3}, x.transform_keys({a: :A, b: :B, d: :D})) + assert_equal({A: 1, B: 2, "c" => 3}, x.transform_keys({a: :A, b: :B, d: :D}, &:to_s)) + end + + def test_transform_keys_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + h2 = h.transform_keys(&:itself) + assert_equal(Hash[h.to_a], h2) + assert_equal(false, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.transform_keys(&:itself) + assert_equal({}, h2) + assert_equal(false, h2.compare_by_identity?) end def test_transform_keys_bang @@ -1657,21 +1957,57 @@ class TestHash < Test::Unit::TestCase x.transform_keys! {|k| -k } assert_equal([-1, :a, 1, :b], x.flatten) + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys! { |k| k == :b && break } + assert_equal({false => 1, b: 2, c: 3}, x) + x = @cls[true => :a, false => :b] x.transform_keys! {|k| !k } assert_equal([false, :a, true, :b], x.flatten) + + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys!({a: :A, b: :B, d: :D}) + assert_equal({A: 1, B: 2, c: 3}, x) + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys!({a: :A, b: :B, d: :D}, &:to_s) + assert_equal({A: 1, B: 2, "c" => 3}, x) end def test_transform_values x = @cls[a: 1, b: 2, c: 3] + x.default = 42 y = x.transform_values {|v| v ** 2 } assert_equal([1, 4, 9], y.values_at(:a, :b, :c)) assert_not_same(x, y) + assert_nil(y.default) + + x.default_proc = proc {|h, k| k} + y = x.transform_values {|v| v ** 2 } + assert_nil(y.default_proc) + assert_nil(y.default) y = x.transform_values.with_index {|v, i| "#{v}.#{i}" } assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c)) end + def test_transform_values_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + h2 = h.transform_values(&:itself) + assert_equal(h, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.transform_values(&:itself) + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + def test_transform_values_bang x = @cls[a: 1, b: 2, c: 3] y = x.transform_values! {|v| v ** 2 } @@ -1679,8 +2015,21 @@ class TestHash < Test::Unit::TestCase assert_same(x, y) x = @cls[a: 1, b: 2, c: 3] + x.transform_values! { |v| v == 2 && break } + assert_equal({a: false, b: 2, c: 3}, x) + + x = @cls[a: 1, b: 2, c: 3] y = x.transform_values!.with_index {|v, i| "#{v}.#{i}" } assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c)) + + x = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + x.transform_values!() do |v| + x.freeze if v == 2 + v.succ + end + end + assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x) end def test_broken_hash_value @@ -1730,9 +2079,6 @@ class TestHash < Test::Unit::TestCase class TestSubHash < TestHash class SubHash < Hash - def reject(*) - super - end end def setup @@ -1740,4 +2086,118 @@ class TestHash < Test::Unit::TestCase super end end + + ruby2_keywords def get_flagged_hash(*args) + args.last + end + + def check_flagged_hash(k: :NG) + k + end + + def test_ruby2_keywords_hash? + flagged_hash = get_flagged_hash(k: 1) + assert_equal(true, Hash.ruby2_keywords_hash?(flagged_hash)) + assert_equal(false, Hash.ruby2_keywords_hash?({})) + assert_raise(TypeError) { Hash.ruby2_keywords_hash?(1) } + end + + def test_ruby2_keywords_hash + hash = {k: 1} + assert_equal(false, Hash.ruby2_keywords_hash?(hash)) + hash = Hash.ruby2_keywords_hash(hash) + assert_equal(true, Hash.ruby2_keywords_hash?(hash)) + assert_equal(1, check_flagged_hash(*[hash])) + assert_raise(TypeError) { Hash.ruby2_keywords_hash(1) } + end + + def test_ar2st + # insert + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + h[obj] = true + assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, test=>true}', h.inspect + + # delete + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + def obj.eql? other + other.class == Object + end + obj2 = Object.new + def obj2.hash + 0 + end + + h[obj2] = true + h.delete obj + assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9}', h.inspect + + # lookup + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + def obj.eql? other + other.class == Object + end + obj2 = Object.new + def obj2.hash + 0 + end + + h[obj2] = true + assert_equal true, h[obj] + end + + def test_bug_12706 + assert_raise(ArgumentError) do + {a: 1}.each(&->(k, v) {}) + end + end + + def test_any_hash_fixable + 20.times do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + require "delegate" + typename = DelegateClass(String) + + hash = { + "Int" => true, + "Float" => true, + "String" => true, + "Boolean" => true, + "WidgetFilter" => true, + "WidgetAggregation" => true, + "WidgetEdge" => true, + "WidgetSortOrder" => true, + "WidgetGrouping" => true, + } + + hash.each_key do |key| + assert_send([hash, :key?, typename.new(key)]) + end + end; + end + end end diff --git a/test/ruby/test_inlinecache.rb b/test/ruby/test_inlinecache.rb new file mode 100644 index 0000000000..d48d95d74e --- /dev/null +++ b/test/ruby/test_inlinecache.rb @@ -0,0 +1,110 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +require 'test/unit' + +class TestMethodInlineCache < Test::Unit::TestCase + def test_alias + m0 = Module.new do + def foo; :M0 end + end + m1 = Module.new do + include m0 + end + c = Class.new do + include m1 + alias bar foo + end + d = Class.new(c) do + end + + test = -> do + d.new.bar + end + + assert_equal :M0, test[] + + c.class_eval do + def bar + :C + end + end + + assert_equal :C, test[] + end + + def test_zsuper + assert_separately [], <<-EOS + class C + private def foo + :C + end + end + + class D < C + public :foo + end + + class E < D; end + class F < E; end + + test = -> do + F.new().foo + end + + assert_equal :C, test[] + + class E + def foo; :E; end + end + + assert_equal :E, test[] + EOS + end + + def test_module_methods_redefiniton + m0 = Module.new do + def foo + super + end + end + + c1 = Class.new do + def foo + :C1 + end + end + + c2 = Class.new do + def foo + :C2 + end + end + + d1 = Class.new(c1) do + include m0 + end + + d2 = Class.new(c2) do + include m0 + end + + assert_equal :C1, d1.new.foo + + m = Module.new do + def foo + super + end + end + + d1.class_eval do + include m + end + + d2.class_eval do + include m + end + + assert_equal :C2, d2.new.foo + end +end diff --git a/test/ruby/test_insns_leaf.rb b/test/ruby/test_insns_leaf.rb new file mode 100644 index 0000000000..9c9a4324cb --- /dev/null +++ b/test/ruby/test_insns_leaf.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestInsnsLeaf < Test::Unit::TestCase + require "set" + + class Id + attr_reader :db_id + def initialize(db_id) + @db_id = db_id + end + + def ==(other) + other.class == self.class && other.db_id == db_id + end + alias_method :eql?, :== + + def hash + 10 + end + + def <=>(other) + db_id <=> other.db_id if other.is_a?(self.class) + end + end + + class Namespace + IDS = Set[ + Id.new(1).freeze, + Id.new(2).freeze, + Id.new(3).freeze, + Id.new(4).freeze, + ].freeze + + class << self + def test?(id) + IDS.include?(id) + end + end + end + + def test_insns_leaf + assert Namespace.test?(Id.new(1)), "IDS should include 1" + assert !Namespace.test?(Id.new(5)), "IDS should not include 5" + end +end diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index a111698771..2f3f00ecda 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?) @@ -260,6 +258,7 @@ class TestInteger < Test::Unit::TestCase assert_equal("a", "a".ord.chr) assert_raise(RangeError) { (-1).chr } assert_raise(RangeError) { 0x100.chr } + assert_raise_with_message(RangeError, "3000000000 out of char range") { 3_000_000_000.chr } end def test_upto @@ -298,6 +297,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) @@ -575,6 +599,8 @@ class TestInteger < Test::Unit::TestCase assert_equal([0, 9, 8, 7, 6, 5, 4, 3, 2, 1], 1234567890.digits) assert_equal([90, 78, 56, 34, 12], 1234567890.digits(100)) assert_equal([10, 5, 6, 8, 0, 10, 8, 6, 1], 1234567890.digits(13)) + assert_equal((2 ** 1024).to_s(7).chars.map(&:to_i).reverse, (2 ** 1024).digits(7)) + assert_equal([0] * 100 + [1], (2 ** (128 * 100)).digits(2 ** 128)) end def test_digits_for_negative_numbers @@ -659,4 +685,21 @@ class TestInteger < Test::Unit::TestCase def o.fdiv(x); 1; end assert_equal(1.0, 1.fdiv(o)) end + + def test_try_convert + assert_equal(1, Integer.try_convert(1)) + assert_equal(1, Integer.try_convert(1.0)) + assert_nil Integer.try_convert("1") + o = Object.new + assert_nil Integer.try_convert(o) + def o.to_i; 1; end + assert_nil Integer.try_convert(o) + o = Object.new + def o.to_int; 1; end + assert_equal(1, Integer.try_convert(o)) + + o = Object.new + def o.to_int; Object.new; end + assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)} + end end diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index f3b08154c8..4f54052d3b 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -394,6 +394,24 @@ class TestIO < Test::Unit::TestCase } end + def test_each_byte_closed + pipe(proc do |w| + w << "abc def" + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_byte {|byte| r.close if byte == 32 } + end + end) + make_tempfile {|t| + File.open(t, 'rt') {|f| + assert_raise(IOError) do + f.each_byte {|c| f.close if c == 10} + end + } + } + end + def test_each_codepoint make_tempfile {|t| bug2959 = '[ruby-core:28650]' @@ -405,16 +423,21 @@ class TestIO < Test::Unit::TestCase } end - def test_codepoints + def test_each_codepoint_closed + pipe(proc do |w| + w.print("abc def") + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_codepoint {|c| r.close if c == 32} + end + end) make_tempfile {|t| - bug2959 = '[ruby-core:28650]' - a = "" File.open(t, 'rt') {|f| - assert_warn(/deprecated/) { - f.codepoints {|c| a << c} - } + assert_raise(IOError) do + f.each_codepoint {|c| f.close if c == 10} + end } - assert_equal("foo\nbar\nbaz\n", a, bug2959) } end @@ -453,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| @@ -620,7 +655,7 @@ 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 RubyVM::MJIT.enabled? + skip "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? skip "multiple threads already active" if Thread.list.size > 1 msg = 'r58534 [ruby-core:80969] [Backport #13533]' @@ -1459,6 +1494,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 = "" @@ -1504,6 +1546,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 = "" @@ -1541,6 +1590,13 @@ 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_write_nonblock_simple_no_exceptions pipe(proc do |w| w.write_nonblock('1', exception: false) @@ -1575,7 +1631,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 RubyVM::MJIT.enabled? # TODO: consider acquiring GVL from MJIT worker. + skip '[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!" @@ -1822,8 +1878,7 @@ class TestIO < Test::Unit::TestCase end) end - def test_lines - verbose, $VERBOSE = $VERBOSE, nil + def test_each_line pipe(proc do |w| w.puts "foo" w.puts "bar" @@ -1831,20 +1886,17 @@ class TestIO < Test::Unit::TestCase w.close end, proc do |r| e = nil - assert_warn(/deprecated/) { - e = r.lines + assert_warn('') { + e = r.each_line } assert_equal("foo\n", e.next) assert_equal("bar\n", e.next) assert_equal("baz\n", e.next) assert_raise(StopIteration) { e.next } end) - ensure - $VERBOSE = verbose end - def test_bytes - verbose, $VERBOSE = $VERBOSE, nil + def test_each_byte2 pipe(proc do |w| w.binmode w.puts "foo" @@ -1853,20 +1905,17 @@ class TestIO < Test::Unit::TestCase w.close end, proc do |r| e = nil - assert_warn(/deprecated/) { - e = r.bytes + assert_warn('') { + e = r.each_byte } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c.ord, e.next) end assert_raise(StopIteration) { e.next } end) - ensure - $VERBOSE = verbose end - def test_chars - verbose, $VERBOSE = $VERBOSE, nil + def test_each_char2 pipe(proc do |w| w.puts "foo" w.puts "bar" @@ -1874,16 +1923,14 @@ class TestIO < Test::Unit::TestCase w.close end, proc do |r| e = nil - assert_warn(/deprecated/) { - e = r.chars + assert_warn('') { + e = r.each_char } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c, e.next) end assert_raise(StopIteration) { e.next } end) - ensure - $VERBOSE = verbose end def test_readbyte @@ -2232,7 +2279,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 RubyVM::MJIT.enabled? + skip 'this randomly fails with MJIT' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? feature2250 = '[ruby-core:26222]' pre = 'ft2250' @@ -2284,26 +2331,14 @@ class TestIO < Test::Unit::TestCase def o.to_open(**kw); kw; end assert_equal({:a=>1}, open(o, a: 1)) - w = /The last argument is used as the keyword parameter.*for `(to_)?open'/m - redefined = nil - w.singleton_class.define_method(:===) do |s| - match = super(s) - redefined = !$1 - match - end - - assert_warn(w) do - assert_equal({:a=>1}, open(o, {a: 1})) - end + assert_raise(ArgumentError) { open(o, {a: 1}) } class << o remove_method(:to_open) end def o.to_open(kw); kw; end assert_equal({:a=>1}, open(o, a: 1)) - unless redefined - assert_equal({:a=>1}, open(o, {a: 1})) - end + assert_equal({:a=>1}, open(o, {a: 1})) end def test_open_pipe @@ -2501,6 +2536,17 @@ class TestIO < Test::Unit::TestCase end end + def test_reopen_ivar + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + f = File.open(IO::NULL) + f.instance_variable_set(:@foo, 42) + f.reopen(STDIN) + f.instance_variable_defined?(:@foo) + f.instance_variable_get(:@foo) + end; + end + def test_foreach a = [] IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :foo; puts :bar; puts :baz'") {|x| a << x } @@ -2580,11 +2626,13 @@ class TestIO < Test::Unit::TestCase end def test_print_separators - EnvUtil.suppress_warning {$, = ':'} - $\ = "\n" + EnvUtil.suppress_warning { + $, = ':' + $\ = "\n" + } pipe(proc do |w| w.print('a') - w.print('a','b','c') + EnvUtil.suppress_warning {w.print('a','b','c')} w.close end, proc do |r| assert_equal("a\n", r.gets) @@ -2642,7 +2690,7 @@ class TestIO < Test::Unit::TestCase end capture.clear - assert_warning(/[.#]write is outdated/) do + assert_deprecated_warning(/[.#]write is outdated/) do stdout, $stdout = $stdout, capture puts "hey" ensure @@ -2825,7 +2873,7 @@ __END__ def test_flush_in_finalizer2 bug3910 = '[ruby-dev:42341]' - Tempfile.open("bug3910") {|t| + Tempfile.create("bug3910") {|t| path = t.path t.close begin @@ -2844,7 +2892,6 @@ __END__ end } end - t.close! } end @@ -3138,11 +3185,8 @@ __END__ assert_equal("\00f", File.read(path)) assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8")) assert_equal("ff", File.read(path)) - assert_raise(TypeError) { - assert_warn(/The last argument is split into positional and keyword parameters/) do - File.write(path, "foo", Object.new => Object.new) - end - } + File.write(path, "foo", Object.new => Object.new) + assert_equal("foo", File.read(path)) end end @@ -3341,11 +3385,17 @@ __END__ data = "a" * 100 with_pipe do |r,w| th = Thread.new {r.sysread(100, buf)} + Thread.pass until th.stop? - buf.replace("") - assert_empty(buf, bug6099) + + assert_equal 100, buf.bytesize + + msg = /can't modify string; temporarily locked/ + assert_raise_with_message(RuntimeError, msg) do + buf.replace("") + end + assert_predicate(th, :alive?) w.write(data) - Thread.pass while th.alive? th.join end assert_equal(data, buf, bug6099) @@ -3466,10 +3516,17 @@ __END__ tempfiles = [] (0..fd_setsize+1).map {|i| - tempfiles << Tempfile.open("test_io_select_with_many_files") + tempfiles << Tempfile.create("test_io_select_with_many_files") } - IO.select(tempfiles) + begin + IO.select(tempfiles) + ensure + tempfiles.each { |t| + t.close + File.unlink(t.path) + } + end }, bug8080, timeout: 100 end if defined?(Process::RLIMIT_NOFILE) @@ -3643,15 +3700,27 @@ __END__ end def test_open_flag_binary + binary_enc = Encoding.find("BINARY") make_tempfile do |t| open(t.path, File::RDONLY, flags: File::BINARY) do |f| assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding end open(t.path, 'r', flags: File::BINARY) do |f| assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding end open(t.path, mode: 'r', flags: File::BINARY) do |f| assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, File::RDONLY|File::BINARY) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, File::RDONLY|File::BINARY, autoclose: true) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding end end end if File::BINARY != 0 @@ -3700,7 +3769,7 @@ __END__ begin; bug13158 = '[ruby-core:79262] [Bug #13158]' closed = nil - q = Queue.new + q = Thread::Queue.new IO.pipe do |r, w| thread = Thread.new do begin @@ -3811,30 +3880,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 @@ -3914,21 +3959,22 @@ __END__ end end - def test_select_leak + def test_select_memory_leak # avoid malloc arena explosion from glibc and jemalloc: env = { 'MALLOC_ARENA_MAX' => '1', 'MALLOC_ARENA_TEST' => '1', 'MALLOC_CONF' => 'narenas:1', } - assert_no_memory_leak([env], <<-"end;", <<-"end;", rss: true, timeout: 60) + assert_no_memory_leak([env], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", rss: true, timeout: 60) + begin; r, w = IO.pipe rset = [r] wset = [w] exc = StandardError.new(-"select used to leak on exception") exc.set_backtrace([]) Thread.new { IO.select(rset, wset, nil, 0) }.join - end; + else; th = Thread.new do Thread.handle_interrupt(StandardError => :on_blocking) do begin @@ -3953,4 +3999,34 @@ __END__ assert_raise(TypeError) {Marshal.dump(w)} } end + + def test_marshal_closed_io + bug18077 = '[ruby-core:104927] [Bug #18077]' + r, w = IO.pipe + r.close; w.close + assert_raise(TypeError, bug18077) {Marshal.dump(r)} + + class << r + undef_method :closed? + end + assert_raise(TypeError, bug18077) {Marshal.dump(r)} + end + + def test_stdout_to_closed_pipe + EnvUtil.invoke_ruby(["-e", "loop {puts :ok}"], "", true, true) do + |in_p, out_p, err_p, pid| + out = out_p.gets + out_p.close + err = err_p.read + ensure + status = Process.wait2(pid)[1] + assert_equal("ok\n", out) + assert_empty(err) + assert_not_predicate(status, :success?) + if Signal.list["PIPE"] + assert_predicate(status, :signaled?) + assert_equal("PIPE", Signal.signame(status.termsig) || status.termsig) + end + end + end end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb new file mode 100644 index 0000000000..c1034efe34 --- /dev/null +++ b/test/ruby/test_io_buffer.rb @@ -0,0 +1,360 @@ +# 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_bounds + buffer = IO::Buffer.new(128) + + assert_raise ArgumentError do + buffer.slice(128, 10) + end + + # assert_raise RuntimeError do + # pp 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, /exceeds buffer size/) do + buffer.get_string(0, 129) + end + + assert_raise_with_message(ArgumentError, /exceeds buffer size/) do + buffer.get_string(129) + 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_primitives + buffer = IO::Buffer.new(128) + + RANGES.each do |type, values| + values.each do |value| + buffer.set_value(type, 0, value) + assert_equal value, buffer.get_value(type, 0), "Converting #{value} as #{type}." + end + end + 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 test_read + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.read(io, 5) + + assert_equal "Hello", buffer.get_string(0, 5) + ensure + io.close! + end + + def test_write + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello") + buffer.write(io, 5) + + 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, 5, 6) + + assert_equal "World", buffer.get_string(0, 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, 5, 6) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end +end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index e5b0ef0585..27b16a2a36 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -776,10 +776,10 @@ EOT assert_equal(eucjp, r.read) end) - assert_raise_with_message(ArgumentError, /invalid name encoding/) do + assert_raise_with_message(ArgumentError, /invalid encoding name/) do with_pipe("UTF-8", "UTF-8".encode("UTF-32BE")) {} end - assert_raise_with_message(ArgumentError, /invalid name encoding/) do + assert_raise_with_message(ArgumentError, /invalid encoding name/) do with_pipe("UTF-8".encode("UTF-32BE")) {} end @@ -2047,19 +2047,19 @@ EOT with_tmpdir { open("raw.txt", "wb", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } content = File.read("raw.txt", :mode=>"rb:ascii-8bit") - assert_equal("\"&<>"'\u4E02\u3042\n\"".force_encoding("ascii-8bit"), content) + assert_equal("\"&<>"'\u4E02\u3042\n\"".force_encoding("ascii-8bit"), content) open("ascii.txt", "wb:us-ascii", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } content = File.read("ascii.txt", :mode=>"rb:ascii-8bit") - assert_equal("\"&<>"'丂あ\n\"".force_encoding("ascii-8bit"), content) + assert_equal("\"&<>"'丂あ\n\"".force_encoding("ascii-8bit"), content) open("iso-2022-jp.txt", "wb:iso-2022-jp", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } content = File.read("iso-2022-jp.txt", :mode=>"rb:ascii-8bit") - assert_equal("\"&<>"'丂\e$B$\"\e(B\n\"".force_encoding("ascii-8bit"), content) + assert_equal("\"&<>"'丂\e$B$\"\e(B\n\"".force_encoding("ascii-8bit"), content) open("utf-16be.txt", "wb:utf-16be", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } content = File.read("utf-16be.txt", :mode=>"rb:ascii-8bit") - assert_equal("\0\"\0&\0a\0m\0p\0;\0&\0l\0t\0;\0&\0g\0t\0;\0&\0q\0u\0o\0t\0;\0'\x4E\x02\x30\x42\0\n\0\"".force_encoding("ascii-8bit"), content) + assert_equal("\0\"\0&\0a\0m\0p\0;\0&\0l\0t\0;\0&\0g\0t\0;\0&\0q\0u\0o\0t\0;\0&\0a\0p\0o\0s\0;\x4E\x02\x30\x42\0\n\0\"".force_encoding("ascii-8bit"), content) open("eucjp.txt", "w:euc-jp:utf-8", xml: :attr) {|f| f.print "\u4E02" # U+4E02 is 0x3021 in JIS X 0212 diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 7c384c19bd..f01d36cc5a 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)} @@ -82,6 +82,91 @@ class TestISeq < Test::Unit::TestCase end; end if defined?(RubyVM::InstructionSequence.load) + def test_cdhash_after_roundtrip + # 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, __LINE__+1) + case Class.new(String).new("foo") + when "foo" + 42 + end + EOF + 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, __LINE__+1) + x = 42 + 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, /hidden variable/) do + Ractor.make_shareable(obj.foo) + end + end + def test_disasm_encoding src = "\u{3042} = 1; \u{3042}; \u{3043}" asm = compile(src).disasm @@ -98,6 +183,22 @@ class TestISeq < Test::Unit::TestCase assert_include(RubyVM::InstructionSequence.of(obj.method(name)).disasm, name) end + def test_compile_file_encoding + Tempfile.create(%w"test_iseq .rb") do |f| + f.puts "{ '\u00de' => 'Th', '\u00df' => 'ss', '\u00e0' => 'a' }" + f.close + + EnvUtil.with_default_external(Encoding::US_ASCII) do + assert_warn('') { + load f.path + } + assert_nothing_raised(SyntaxError) { + RubyVM::InstructionSequence.compile_file(f.path) + } + end + end + end + LINE_BEFORE_METHOD = __LINE__ def method_test_line_trace @@ -108,16 +209,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 @@ -187,8 +288,8 @@ class TestISeq < Test::Unit::TestCase s1, s2, s3, s4 = compile(code, line, {frozen_string_literal: true}).eval assert_predicate(s1, :frozen?) assert_predicate(s2, :frozen?) - assert_predicate(s3, :frozen?) - assert_predicate(s4, :frozen?) + assert_not_predicate(s3, :frozen?) + assert_not_predicate(s4, :frozen?) end # Safe call chain is not optimized when Coverage is running. @@ -289,6 +390,22 @@ class TestISeq < Test::Unit::TestCase end end + def anon_star(*); end + + def test_anon_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_star)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [2], 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 @@ -425,6 +542,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 @@ -465,6 +587,11 @@ class TestISeq < Test::Unit::TestCase attr_reader :i end end; + + # cleanup + ::Object.class_eval do + remove_const :P + end end def collect_from_binary_tracepoint_lines(tracepoint_type, filename) @@ -561,9 +688,58 @@ class TestISeq < Test::Unit::TestCase end def test_iseq_builtin_to_a - insns = RubyVM::InstructionSequence.of([].method(:pack)).to_a.last - invokebuiltin = insns.find { |insn| insn.is_a?(Array) && insn[0] == :opt_invokebuiltin_delegate_leave } + invokebuiltin = eval(EnvUtil.invoke_ruby(['-e', <<~EOS], '', true).first) + insns = RubyVM::InstructionSequence.of([].method(:pack)).to_a.last + p insns.find { |insn| insn.is_a?(Array) && insn[0] == :opt_invokebuiltin_delegate_leave } + EOS assert_not_nil(invokebuiltin) assert_equal([:func_ptr, :argc, :index, :name], invokebuiltin[1].keys) end + + def test_iseq_builtin_load + Tempfile.create(["builtin", ".iseq"]) do |f| + f.binmode + f.write(RubyVM::InstructionSequence.of(1.method(:abs)).to_binary) + f.close + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bin = File.binread(ARGV[0]) + assert_raise(ArgumentError) do + RubyVM::InstructionSequence.load_from_binary(bin) + end + end; + end + end + + def test_iseq_option_debug_level + assert_raise(TypeError) {ISeq.compile("", debug_level: "")} + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 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_iterator.rb b/test/ruby/test_iterator.rb index 54c095338f..820d5591c1 100644 --- a/test/ruby/test_iterator.rb +++ b/test/ruby/test_iterator.rb @@ -339,8 +339,7 @@ class TestIterator < Test::Unit::TestCase marity_test(:marity_test) marity_test(:p) - lambda(&method(:assert)).call(true) - lambda(&get_block{|a,n| assert(a,n)}).call(true, "marity") + get_block{|a,n| assert(a,n)}.call(true, "marity") end def foo diff --git a/test/ruby/test_jit.rb b/test/ruby/test_jit.rb index a8ce18b48c..07ac76210d 100644 --- a/test/ruby/test_jit.rb +++ b/test/ruby/test_jit.rb @@ -10,14 +10,19 @@ class TestJIT < Test::Unit::TestCase IGNORABLE_PATTERNS = [ /\AJIT recompile: .+\n\z/, /\AJIT inline: .+\n\z/, + /\AJIT cancel: .+\n\z/, /\ASuccessful MJIT finish\n\z/, ] + MAX_CACHE_PATTERNS = [ + /\AJIT compaction \([^)]+\): .+\n\z/, + /\AToo many JIT code, but skipped unloading units for JIT compaction\n\z/, + /\ANo units can be unloaded -- .+\n\z/, + ] # trace_* insns are not compiled for now... TEST_PENDING_INSNS = RubyVM::INSTRUCTION_NAMES.select { |n| n.start_with?('trace_') }.map(&:to_sym) + [ # not supported yet :defineclass, - :opt_call_c_function, # to be tested :invokebuiltin, @@ -34,22 +39,36 @@ class TestJIT < Test::Unit::TestCase @untested_insns ||= (RubyVM::INSTRUCTION_NAMES.map(&:to_sym) - TEST_PENDING_INSNS) end - def setup - unless JITSupport.supported? - skip 'JIT seems not supported on this platform' + def self.setup + return if defined?(@setup_hooked) + @setup_hooked = true + + # 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}" + File.unlink(file) + end end # ruby -w -Itest/lib test/ruby/test_jit.rb - if $VERBOSE && !defined?(@@at_exit_hooked) + if $VERBOSE + pid = $$ at_exit do - unless TestJIT.untested_insns.empty? + if pid == $$ && !TestJIT.untested_insns.empty? warn "you may want to add tests for following insns, when you have a chance: #{TestJIT.untested_insns.join(' ')}" end end - @@at_exit_hooked = true end end + def setup + unless JITSupport.supported? + skip '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]) end @@ -224,16 +243,8 @@ 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]) - end - - def test_compile_insn_freezestring - assert_eval_with_jit("#{<<~"begin;"}\n#{<<~'end;'}", stdout: 'true', success_count: 1, insns: %i[freezestring]) - begin; - # frozen_string_literal: true - print proc { "#{true}".frozen? }.call - end; + def test_compile_insn_putstring_concatstrings_objtostring + assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring]) end def test_compile_insn_toregexp @@ -309,10 +320,6 @@ class TestJIT < Test::Unit::TestCase assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn]) end - def test_compile_insn_reverse - assert_compile_once('q, (w, e), r = 1, [2, 3], 4; [q, w, e, r]', result_inspect: '[1, 2, 3, 4]', insns: %i[reverse]) - end - def test_compile_insn_reput skip "write test" end @@ -353,7 +360,7 @@ class TestJIT < Test::Unit::TestCase end def test_compile_insn_send - assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2, insns: %i[send]) + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 3, insns: %i[send]) begin; print proc { yield_self { 1 } }.call end; @@ -475,8 +482,8 @@ class TestJIT < Test::Unit::TestCase 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_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring]) begin; a = '2' "4#{a}" @@ -492,7 +499,7 @@ class TestJIT < Test::Unit::TestCase end def test_compile_insn_checkmatch_opt_case_dispatch - assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch opt_case_dispatch]) + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch]) begin; case 'hello' when 'hello' @@ -593,16 +600,26 @@ class TestJIT < Test::Unit::TestCase assert_compile_once("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2]) end - def test_compile_insn_opt_call_c_function - skip "support this in opt_call_c_function (low priority)" - end - def test_compile_insn_opt_invokebuiltin_delegate_leave - insns = collect_insns(RubyVM::InstructionSequence.of("\x00".method(:unpack)).to_a) + iseq = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first) + p RubyVM::InstructionSequence.of("\x00".method(:unpack)).to_a + 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) end + def test_compile_insn_checkmatch + assert_compile_once("#{<<~"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) @@ -610,6 +627,46 @@ class TestJIT < Test::Unit::TestCase assert_match(/^Successful MJIT finish$/, err) end + def test_nothing_to_unload_with_jit_wait + 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 + def a2() a3() end + def a3() a4() end + def a4() a5() end + def a5() a6() end + def a6() a7() end + def a7() a8() end + def a8() a9() end + def a9() a10() end + def a10() a11() end + def a11() print('hello') end + a1 + end; + end + + def test_unload_units_on_fiber + 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 + def a2(a) a3(a) end + def a3(a) a4(a) end + def a4(a) a5(a) end + def a5(a) a6(a) end + def a6(a) a7(a) end + def a7(a) a8(a) end + def a8(a) a9(a) end + def a9(a) a10(a) end + def a10(a) + if a + Fiber.new { a11 }.resume + end + end + def a11() print('hello') end + a1 + end; + end + def test_unload_units_and_compaction Dir.mktmpdir("jit_test_unload_units_") do |dir| # MIN_CACHE_SIZE is 10 @@ -636,30 +693,40 @@ class TestJIT < Test::Unit::TestCase debug_info = %Q[stdout:\n"""\n#{out}\n"""\n\nstderr:\n"""\n#{err}"""\n] assert_equal('012345678910', out, debug_info) compactions, errs = err.lines.partition do |l| - l.match?(/\AJIT compaction \(\d+\.\dms\): Compacted \d+ methods ->/) + l.match?(/\AJIT compaction \(\d+\.\dms\): Compacted \d+ methods /) end 10.times do |i| assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit#{i}@\(eval\):/, errs[i], debug_info) end - assert_equal("Too many JIT code -- 1 units unloaded\n", errs[10], debug_info) - assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit10@\(eval\):/, errs[11], debug_info) + assert_equal("No units can be unloaded -- incremented max-cache-size to 11 for --jit-wait\n", errs[10], debug_info) + assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit10@\(eval\):/, errs[11], debug_info) # On --jit-wait, when the number of JIT-ed code reaches --jit-max-cache, # it should trigger compaction. unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet - assert_equal(3, compactions.size, debug_info) + assert_equal(1, compactions.size, debug_info) end 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 .o files are deleted on unload_units + # verify .c files are deleted on unload_units assert_send([Dir, :empty?, dir], debug_info) end end end + def test_newarraykwsplat_on_stack + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "[nil, [{:type=>:development}]]\n", success_count: 1, insns: %i[newarraykwsplat]) + begin; + def arr + [nil, [:type => :development]] + end + p arr + end; + end + def test_local_stack_on_exception assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2) begin; @@ -719,6 +786,57 @@ class TestJIT < Test::Unit::TestCase end; end + def test_inlined_builtin_methods + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 1, min_calls: 2) + begin; + def test + float = 0.0 + float.abs + float.-@ + float.zero? + end + test + test + end; + end + + def test_inlined_c_method + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 2, recompile_count: 1, min_calls: 2) + begin; + def test(obj, recursive: nil) + if recursive + test(recursive) + end + obj.to_s + end + + print(test('a')) # set #to_s cc to String#to_s (expecting C method) + print(test('a')) # JIT with #to_s cc: String#to_s + # update #to_s cd->cc to Symbol#to_s, then go through the Symbol#to_s cd->cc + # after checking receiver class using inlined #to_s cc with String#to_s. + print(test('a', recursive: :foo)) + end; + end + + def test_inlined_exivar + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aaa", success_count: 3, recompile_count: 1, min_calls: 2) + begin; + class Foo < Hash + def initialize + @a = :a + end + + def bar + @a + end + end + + print(Foo.new.bar) + print(Foo.new.bar) # compile #initialize, #bar -> recompile #bar + print(Foo.new.bar) # compile #bar with exivar + end; + end + def test_inlined_undefined_ivar assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 3, min_calls: 3) begin; @@ -734,11 +852,9 @@ class TestJIT < Test::Unit::TestCase end end - verbose, $VERBOSE = $VERBOSE, false # suppress "instance variable @b not initialized" print(Foo.new.bar) print(Foo.new.bar) print(Foo.new.bar) - $VERBOSE = verbose end; end @@ -764,6 +880,18 @@ class TestJIT < Test::Unit::TestCase end; end + def test_inlined_getconstant + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, min_calls: 2) + begin; + FOO = 1 + def const + FOO + end + print const + print const + end; + end + def test_attr_reader assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2) begin; @@ -828,6 +956,44 @@ class TestJIT < Test::Unit::TestCase end; 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) + 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 + @iv3 = nil + end + end + + a = A.new + p a.test(false) + p a.test(true) + end; + end + + def test_jump_to_precompiled_branch + assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0", success_count: 1, min_calls: 1) + begin; + def test(foo) + ".#{foo unless foo == 1}" if true + end + 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.' @@ -881,7 +1047,7 @@ class TestJIT < Test::Unit::TestCase def test_frame_omitted_inlining assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\n", success_count: 1, min_calls: 2) begin; - class Numeric + class Integer remove_method :zero? def zero? self == 0 @@ -907,6 +1073,10 @@ class TestJIT < Test::Unit::TestCase 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) + end + def test_program_counter_with_regexpmatch assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aa", success_count: 1) begin; @@ -935,6 +1105,30 @@ class TestJIT < Test::Unit::TestCase end; end + def test_mjit_pause_wait + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, min_calls: 1) + begin; + 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) + begin; + TracePoint.new(:class) {}.enable + 2.times {} + end; + end + + def test_cancel_by_tracepoint + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, min_calls: 2) + begin; + TracePoint.new(:line) {}.enable + 2.times {} + end; + end + def test_caller_locations_without_catch_table out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1) begin; @@ -996,13 +1190,14 @@ class TestJIT < Test::Unit::TestCase end # Shorthand for normal test cases - def assert_eval_with_jit(script, stdout: nil, success_count:, min_calls: 1, insns: [], uplevel: 1) - out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls) - actual = err.scan(/^#{JIT_SUCCESS_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. - if RUBY_PLATFORM.match?(/mswin/) && success_count != actual - out2, err2 = eval_with_jit(script, verbose: 2, min_calls: min_calls) + 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) + success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size + recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size + # 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_worker.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) end # Make sure that the script has insns expected to be tested @@ -1011,18 +1206,24 @@ class TestJIT < Test::Unit::TestCase mark_tested_insn(insn, used_insns: used_insns, uplevel: uplevel + 3) end + suffix = "script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}#{( + "\nstdout(verbose=2 retry):\n#{code_block(out2)}\nstderr(verbose=2 retry):\n#{code_block(err2)}" if out2 || err2 + )}" assert_equal( - success_count, actual, - "Expected #{success_count} times of JIT success, but succeeded #{actual} times.\n\n"\ - "script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}#{( - "\nstdout(verbose=2 retry):\n#{code_block(out2)}\nstderr(verbose=2 retry):\n#{code_block(err2)}" if out2 || err2 - )}", + success_count, success_actual, + "Expected #{success_count} times of JIT success, but succeeded #{success_actual} times.\n\n#{suffix}", ) + if recompile_count + assert_equal( + recompile_count, recompile_actual, + "Expected #{success_count} times of JIT recompile, but recompiled #{success_actual} times.\n\n#{suffix}", + ) + end if stdout assert_equal(stdout, out, "Expected stdout #{out.inspect} to match #{stdout.inspect} with script:\n#{code_block(script)}") end err_lines = err.lines.reject! do |l| - l.chomp.empty? || l.match?(/\A#{JIT_SUCCESS_PREFIX}/) || IGNORABLE_PATTERNS.any? { |pat| pat.match?(l) } + l.chomp.empty? || l.match?(/\A#{JIT_SUCCESS_PREFIX}/) || (IGNORABLE_PATTERNS + ignorable_patterns).any? { |pat| pat.match?(l) } end unless err_lines.empty? warn err_lines.join(''), uplevel: uplevel @@ -1030,7 +1231,9 @@ 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 diff --git a/test/ruby/test_jit_debug.rb b/test/ruby/test_jit_debug.rb new file mode 100644 index 0000000000..b8dc9416ef --- /dev/null +++ b/test/ruby/test_jit_debug.rb @@ -0,0 +1,17 @@ +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 --mjit-debug + @mjit_debug = true + end +end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 874b09bcfa..9094259bc2 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -24,9 +24,7 @@ class TestKeywordArguments < Test::Unit::TestCase def test_f2 assert_equal([:xyz, "foo", 424242], f2(:xyz)) - assert_warn(/The keyword argument is passed as the last hash parameter.* for `f2'/m) do - assert_equal([{"bar"=>42}, "foo", 424242], f2("bar"=>42)) - end + assert_raise(ArgumentError) { f2("bar"=>42) } end @@ -192,6 +190,218 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(["bar", 111111], f[str: "bar", num: 111111]) end + def test_keyword_splat_new + kw = {} + h = {a: 1} + + def self.assert_equal_not_same(kw, res) + assert_instance_of(Hash, res) + assert_equal(kw, res) + 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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + 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.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + 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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + 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)) + assert_equal(false, m.(self, **{}).frozen?) + assert_equal_not_same(kw, m.(self, **kw)) + assert_equal_not_same(h, m.(self, **h)) + assert_equal(false, m.send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, self, **kw)) + assert_equal_not_same(h, m.send(:call, self, **h)) + + c = Class.new do + def y(**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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + 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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + c = Class.new do + 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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + 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)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + c = Class.new do + attr_reader :kw + def initialize(**kw) @kw = kw end + end + m = c.method(:new) + assert_equal(false, c.new(**{}).kw.frozen?) + assert_equal_not_same(kw, c.new(**kw).kw) + assert_equal_not_same(h, c.new(**h).kw) + assert_equal(false, c.send(:new, **{}).kw.frozen?) + assert_equal_not_same(kw, c.send(:new, **kw).kw) + assert_equal_not_same(h, c.send(:new, **h).kw) + assert_equal(false, c.public_send(:new, **{}).kw.frozen?) + assert_equal_not_same(kw, c.public_send(:new, **kw).kw) + assert_equal_not_same(h, c.public_send(:new, **h).kw) + assert_equal(false, m.(**{}).kw.frozen?) + assert_equal_not_same(kw, m.(**kw).kw) + assert_equal_not_same(h, m.(**h).kw) + assert_equal(false, m.send(:call, **{}).kw.frozen?) + assert_equal_not_same(kw, m.send(:call, **kw).kw) + assert_equal_not_same(h, m.send(:call, **h).kw) + + singleton_class.send(:attr_writer, :y) + m = method(:y=) + assert_equal_not_same(h, send(:y=, **h)) + assert_equal_not_same(h, public_send(:y=, **h)) + assert_equal_not_same(h, m.(**h)) + assert_equal_not_same(h, m.send(:call, **h)) + + singleton_class.send(:remove_method, :y) + 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)) + end + def test_regular_kwsplat kw = {} h = {:a=>1} @@ -224,12 +434,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(kw, c.m(kw, **kw)) assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) @@ -248,39 +454,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**{}) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**kw) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } assert_equal([h, kw], c.m(h)) assert_equal([h2, kw], c.m(h2)) assert_equal([h3, kw], c.m(h3)) @@ -296,13 +484,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.m(**h2)) assert_equal([1, h3], c.m(**h3)) assert_equal([1, h3], c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal([1, h], c.m(h)) - end + assert_equal([h, kw], c.m(h)) assert_equal([h2, kw], c.m(h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_equal([h2, h], c.m(h3)) - end + assert_equal([h3, kw], c.m(h3)) end def test_implicit_super_kwsplat @@ -313,19 +497,9 @@ class TestKeywordArguments < Test::Unit::TestCase sc = Class.new c = sc.new - redef = -> do - if defined?(c.m) - class << c - remove_method(:m) - end - end - eval <<-END - def c.m(*args, **kw) - super(*args, **kw) - end - END + def c.m(*args, **kw) + super(*args, **kw) end - redef[] sc.class_eval do def m(*args) args @@ -357,13 +531,8 @@ class TestKeywordArguments < Test::Unit::TestCase args end end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**{})) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -383,13 +552,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } sc.class_eval do remove_method(:m) @@ -397,34 +562,13 @@ class TestKeywordArguments < Test::Unit::TestCase [arg, args] end end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**{}) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**kw) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(**h)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } sc.class_eval do remove_method(:m) @@ -449,19 +593,9 @@ class TestKeywordArguments < Test::Unit::TestCase sc = Class.new c = sc.new - redef = -> do - if defined?(c.m) - class << c - remove_method(:m) - end - end - eval <<-END - def c.m(*args, **kw) - super(*args, **kw) - end - END + def c.m(*args, **kw) + super(*args, **kw) end - redef[] sc.class_eval do def m(*args) args @@ -493,13 +627,8 @@ class TestKeywordArguments < Test::Unit::TestCase args end end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**{})) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -519,13 +648,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } sc.class_eval do remove_method(:m) @@ -533,34 +658,13 @@ class TestKeywordArguments < Test::Unit::TestCase [arg, args] end end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**{}) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.m(**kw) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(**h)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } sc.class_eval do remove_method(:m) @@ -592,12 +696,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { f[**h3] } f = ->(a) { a } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, f[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, f[**kw]) - end + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } assert_equal(h, f[**h]) assert_equal(h, f[a: 1]) assert_equal(h2, f[**h2]) @@ -612,36 +712,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, f[**h2]) assert_equal(h3, f[**h3]) assert_equal(h3, f[a: 1, **h2]) - assert_warn(/The last argument is used as the keyword parameter.*for `\[\]'/m) do - assert_equal(h, f[h]) - end + assert_raise(ArgumentError) { f[h] } assert_raise(ArgumentError) { f[h2] } - assert_warn(/The last argument is split into positional and keyword parameters.*for `\[\]'/m) do - assert_raise(ArgumentError) { f[h3] } - end + assert_raise(ArgumentError) { f[h3] } f = ->(a, **x) { [a,x] } - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([{}, {}], f[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([{}, {}], f[**kw]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([h, {}], f[**h]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([h, {}], f[a: 1]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([h2, {}], f[**h2]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([h3, {}], f[**h3]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `\[\]'/m) do - assert_equal([h3, {}], f[a: 1, **h2]) - end + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + assert_raise(ArgumentError) { f[a: 1, **h2] } f = ->(a=1, **x) { [a, x] } assert_equal([1, kw], f[**{}]) @@ -670,12 +752,8 @@ class TestKeywordArguments < Test::Unit::TestCase f = ->(a) { a } f = f.method(:call) - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, f[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, f[**kw]) - end + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } assert_equal(h, f[**h]) assert_equal(h, f[a: 1]) assert_equal(h2, f[**h2]) @@ -691,37 +769,19 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, f[**h2]) assert_equal(h3, f[**h3]) assert_equal(h3, f[a: 1, **h2]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(h, f[h]) - end + assert_raise(ArgumentError) { f[h] } assert_raise(ArgumentError) { f[h2] } - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_raise(ArgumentError) { f[h3] } - end + assert_raise(ArgumentError) { f[h3] } f = ->(a, **x) { [a,x] } f = f.method(:call) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], f[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], f[**kw]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], f[**h]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], f[a: 1]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, {}], f[**h2]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], f[**h3]) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], f[a: 1, **h2]) - end + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + assert_raise(ArgumentError) { f[a: 1, **h2] } f = ->(a=1, **x) { [a, x] } f = f.method(:call) @@ -751,12 +811,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { t.new(**h3, &f).value } f = ->(a) { a } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, t.new(**{}, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, t.new(**kw, &f).value) - end + assert_raise(ArgumentError) { t.new(**{}, &f).value } + assert_raise(ArgumentError) { t.new(**kw, &f).value } assert_equal(h, t.new(**h, &f).value) assert_equal(h, t.new(a: 1, &f).value) assert_equal(h2, t.new(**h2, &f).value) @@ -771,36 +827,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, t.new(**h2, &f).value) assert_equal(h3, t.new(**h3, &f).value) assert_equal(h3, t.new(a: 1, **h2, &f).value) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(h, t.new(h, &f).value) - end + assert_raise(ArgumentError) { t.new(h, &f).value } assert_raise(ArgumentError) { t.new(h2, &f).value } - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_raise(ArgumentError) { t.new(h3, &f).value } - end + assert_raise(ArgumentError) { t.new(h3, &f).value } f = ->(a, **x) { [a,x] } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], t.new(**{}, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], t.new(**kw, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], t.new(**h, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], t.new(a: 1, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, {}], t.new(**h2, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], t.new(**h3, &f).value) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], t.new(a: 1, **h2, &f).value) - end + assert_raise(ArgumentError) { t.new(**{}, &f).value } + assert_raise(ArgumentError) { t.new(**kw, &f).value } + assert_raise(ArgumentError) { t.new(**h, &f).value } + assert_raise(ArgumentError) { t.new(a: 1, &f).value } + assert_raise(ArgumentError) { t.new(**h2, &f).value } + assert_raise(ArgumentError) { t.new(**h3, &f).value } + assert_raise(ArgumentError) { t.new(a: 1, **h2, &f).value } f = ->(a=1, **x) { [a, x] } assert_equal([1, kw], t.new(**{}, &f).value) @@ -830,12 +868,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { t.new(&f).resume(**h3) } f = ->(a) { a } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, t.new(&f).resume(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, t.new(&f).resume(**kw)) - end + assert_raise(ArgumentError) { t.new(&f).resume(**{}) } + assert_raise(ArgumentError) { t.new(&f).resume(**kw) } assert_equal(h, t.new(&f).resume(**h)) assert_equal(h, t.new(&f).resume(a: 1)) assert_equal(h2, t.new(&f).resume(**h2)) @@ -850,36 +884,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, t.new(&f).resume(**h2)) assert_equal(h3, t.new(&f).resume(**h3)) assert_equal(h3, t.new(&f).resume(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(h, t.new(&f).resume(h)) - end + assert_raise(ArgumentError) { t.new(&f).resume(h) } assert_raise(ArgumentError) { t.new(&f).resume(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_raise(ArgumentError) { t.new(&f).resume(h3) } - end + assert_raise(ArgumentError) { t.new(&f).resume(h3) } f = ->(a, **x) { [a,x] } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], t.new(&f).resume(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], t.new(&f).resume(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], t.new(&f).resume(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], t.new(&f).resume(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, {}], t.new(&f).resume(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], t.new(&f).resume(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], t.new(&f).resume(a: 1, **h2)) - end + assert_raise(ArgumentError) { t.new(&f).resume(**{}) } + assert_raise(ArgumentError) { t.new(&f).resume(**kw) } + assert_raise(ArgumentError) { t.new(&f).resume(**h) } + assert_raise(ArgumentError) { t.new(&f).resume(a: 1) } + assert_raise(ArgumentError) { t.new(&f).resume(**h2) } + assert_raise(ArgumentError) { t.new(&f).resume(**h3) } + assert_raise(ArgumentError) { t.new(&f).resume(a: 1, **h2) } f = ->(a=1, **x) { [a, x] } assert_equal([1, kw], t.new(&f).resume(**{})) @@ -907,12 +923,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { g.new(&f).each(**h3) } f = ->(_, a) { a } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, g.new(&f).each(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, g.new(&f).each(**kw)) - end + assert_raise(ArgumentError) { g.new(&f).each(**{}) } + assert_raise(ArgumentError) { g.new(&f).each(**kw) } assert_equal(h, g.new(&f).each(**h)) assert_equal(h, g.new(&f).each(a: 1)) assert_equal(h2, g.new(&f).each(**h2)) @@ -927,36 +939,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, g.new(&f).each(**h2)) assert_equal(h3, g.new(&f).each(**h3)) assert_equal(h3, g.new(&f).each(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(h, g.new(&f).each(h)) - end + assert_raise(ArgumentError) { g.new(&f).each(h) } assert_raise(ArgumentError) { g.new(&f).each(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_raise(ArgumentError) { g.new(&f).each(h3) } - end + assert_raise(ArgumentError) { g.new(&f).each(h3) } f = ->(_, a, **x) { [a,x] } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], g.new(&f).each(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], g.new(&f).each(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], g.new(&f).each(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], g.new(&f).each(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, {}], g.new(&f).each(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], g.new(&f).each(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], g.new(&f).each(a: 1, **h2)) - end + assert_raise(ArgumentError) { g.new(&f).each(**{}) } + assert_raise(ArgumentError) { g.new(&f).each(**kw) } + assert_raise(ArgumentError) { g.new(&f).each(**h) } + assert_raise(ArgumentError) { g.new(&f).each(a: 1) } + assert_raise(ArgumentError) { g.new(&f).each(**h2) } + assert_raise(ArgumentError) { g.new(&f).each(**h3) } + assert_raise(ArgumentError) { g.new(&f).each(a: 1, **h2) } f = ->(_, a=1, **x) { [a, x] } assert_equal([1, kw], g.new(&f).each(**{})) @@ -984,12 +978,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) } f = ->(a) { a } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, g.new{|y| y.yield(**kw)}.each(&f)) - end + assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) } assert_equal(h, g.new{|y| y.yield(**h)}.each(&f)) assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f)) assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) @@ -1004,36 +994,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(h, g.new{|y| y.yield(h)}.each(&f)) - end + assert_raise(ArgumentError) { g.new{|y| y.yield(h)}.each(&f) } assert_raise(ArgumentError) { g.new{|y| y.yield(h2)}.each(&f) } - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) } - end + assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) } f = ->(a, **x) { [a,x] } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], g.new{|y| y.yield(**{})}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([{}, {}], g.new{|y| y.yield(**kw)}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], g.new{|y| y.yield(**h)}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, {}], g.new{|y| y.yield(a: 1)}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, {}], g.new{|y| y.yield(**h2)}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], g.new{|y| y.yield(**h3)}.each(&f)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, {}], g.new{|y| y.yield(a: 1, **h2)}.each(&f)) - end + assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1, **h2)}.each(&f) } f = ->(a=1, **x) { [a, x] } assert_equal([1, kw], g.new{|y| y.yield(**{})}.each(&f)) @@ -1087,12 +1059,8 @@ class TestKeywordArguments < Test::Unit::TestCase @args = args end end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal(kw, c[**{}].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal(kw, c[**kw].args) - end + assert_raise(ArgumentError) { c[**{}] } + assert_raise(ArgumentError) { c[**kw] } assert_equal(h, c[**h].args) assert_equal(h, c[a: 1].args) assert_equal(h2, c[**h2].args) @@ -1111,40 +1079,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c[**h2].args) assert_equal(h3, c[**h3].args) assert_equal(h3, c[a: 1, **h2].args) - assert_warn(/The last argument is used as the keyword parameter.*for `initialize'/m) do - assert_equal(h, c[h].args) - end + assert_raise(ArgumentError) { c[h].args } assert_raise(ArgumentError) { c[h2].args } - assert_warn(/The last argument is split into positional and keyword parameters.*for `initialize'/m) do - assert_raise(ArgumentError) { c[h3].args } - end + assert_raise(ArgumentError) { c[h3].args } c = Class.new(sc) do def initialize(arg, **args) @args = [arg, args] end end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([kw, kw], c[**{}].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([kw, kw], c[**kw].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h, kw], c[**h].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h, kw], c[a: 1].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h2, kw], c[**h2].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h3, kw], c[**h3].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h3, kw], c[a: 1, **h2].args) - end + assert_raise(ArgumentError) { c[**{}].args } + assert_raise(ArgumentError) { c[**kw].args } + assert_raise(ArgumentError) { c[**h].args } + assert_raise(ArgumentError) { c[a: 1].args } + assert_raise(ArgumentError) { c[**h2].args } + assert_raise(ArgumentError) { c[**h3].args } + assert_raise(ArgumentError) { c[a: 1, **h2].args } c = Class.new(sc) do def initialize(arg=1, **args) @@ -1199,12 +1149,8 @@ class TestKeywordArguments < Test::Unit::TestCase @args = args end end.method(:new) - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal(kw, c[**{}].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal(kw, c[**kw].args) - end + assert_raise(ArgumentError) { c[**{}] } + assert_raise(ArgumentError) { c[**kw] } assert_equal(h, c[**h].args) assert_equal(h, c[a: 1].args) assert_equal(h2, c[**h2].args) @@ -1223,40 +1169,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c[**h2].args) assert_equal(h3, c[**h3].args) assert_equal(h3, c[a: 1, **h2].args) - assert_warn(/The last argument is used as the keyword parameter.*for `initialize'/m) do - assert_equal(h, c[h].args) - end + assert_raise(ArgumentError) { c[h].args } assert_raise(ArgumentError) { c[h2].args } - assert_warn(/The last argument is split into positional and keyword parameters.*for `initialize'/m) do - assert_raise(ArgumentError) { c[h3].args } - end + assert_raise(ArgumentError) { c[h3].args } c = Class.new(sc) do def initialize(arg, **args) @args = [arg, args] end end.method(:new) - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([kw, kw], c[**{}].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([kw, kw], c[**kw].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h, kw], c[**h].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h, kw], c[a: 1].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h2, kw], c[**h2].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h3, kw], c[**h3].args) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `initialize'/m) do - assert_equal([h3, kw], c[a: 1, **h2].args) - end + assert_raise(ArgumentError) { c[**{}].args } + assert_raise(ArgumentError) { c[**kw].args } + assert_raise(ArgumentError) { c[**h].args } + assert_raise(ArgumentError) { c[a: 1].args } + assert_raise(ArgumentError) { c[**h2].args } + assert_raise(ArgumentError) { c[**h3].args } + assert_raise(ArgumentError) { c[a: 1, **h2].args } c = Class.new(sc) do def initialize(arg=1, **args) @@ -1304,12 +1232,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.method(:m)[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.method(:m)[**kw]) - end + assert_raise(ArgumentError) { c.method(:m)[**{}] } + assert_raise(ArgumentError) { c.method(:m)[**kw] } assert_equal(h, c.method(:m)[**h]) assert_equal(h, c.method(:m)[a: 1]) assert_equal(h2, c.method(:m)[**h2]) @@ -1327,39 +1251,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.method(:m)[**h2]) assert_equal(h3, c.method(:m)[**h3]) assert_equal(h3, c.method(:m)[a: 1, **h2]) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.method(:m)[h]) - end + assert_raise(ArgumentError) { c.method(:m)[h] } assert_raise(ArgumentError) { c.method(:m)[h2] } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.method(:m)[h3] } - end + assert_raise(ArgumentError) { c.method(:m)[h3] } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], c.method(:m)[**{}]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], c.method(:m)[**kw]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.method(:m)[**h]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.method(:m)[a: 1]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.method(:m)[**h2]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.method(:m)[**h3]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.method(:m)[a: 1, **h2]) - end + assert_raise(ArgumentError) { c.method(:m)[**{}] } + assert_raise(ArgumentError) { c.method(:m)[**kw] } + assert_raise(ArgumentError) { c.method(:m)[**h] } + assert_raise(ArgumentError) { c.method(:m)[a: 1] } + assert_raise(ArgumentError) { c.method(:m)[**h2] } + assert_raise(ArgumentError) { c.method(:m)[**h3] } + assert_raise(ArgumentError) { c.method(:m)[a: 1, **h2] } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -1407,12 +1313,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, sc.instance_method(:m).bind_call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, sc.instance_method(:m).bind_call(c, **kw)) - end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) } assert_equal(h, sc.instance_method(:m).bind_call(c, **h)) assert_equal(h, sc.instance_method(:m).bind_call(c, a: 1)) assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2)) @@ -1430,39 +1332,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2)) assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3)) assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, sc.instance_method(:m).bind_call(c, h)) - end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h) } assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h3) } - end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h3) } sc.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], sc.instance_method(:m).bind_call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], sc.instance_method(:m).bind_call(c, **kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], sc.instance_method(:m).bind_call(c, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], sc.instance_method(:m).bind_call(c, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], sc.instance_method(:m).bind_call(c, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], sc.instance_method(:m).bind_call(c, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], sc.instance_method(:m).bind_call(c, a: 1, **h2)) - end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h2) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h3) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1, **h2) } sc.remove_method(:m) def c.m(arg=1, **args) @@ -1509,12 +1393,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.send(:m, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.send(:m, **kw)) - end + assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_raise(ArgumentError) { c.send(:m, **kw) } assert_equal(h, c.send(:m, **h)) assert_equal(h, c.send(:m, a: 1)) assert_equal(h2, c.send(:m, **h2)) @@ -1532,39 +1412,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.send(:m, **h2)) assert_equal(h3, c.send(:m, **h3)) assert_equal(h3, c.send(:m, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.send(:m, h)) - end + assert_raise(ArgumentError) { c.send(:m, h) } assert_raise(ArgumentError) { c.send(:m, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.send(:m, h3) } - end + assert_raise(ArgumentError) { c.send(:m, h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.send(:m, **{}) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.send(:m, **kw) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.send(:m, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.send(:m, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.send(:m, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.send(:m, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.send(:m, a: 1, **h2)) - end + assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_raise(ArgumentError) { c.send(:m, **kw) } + assert_raise(ArgumentError) { c.send(:m, **h) } + assert_raise(ArgumentError) { c.send(:m, a: 1) } + assert_raise(ArgumentError) { c.send(:m, **h2) } + assert_raise(ArgumentError) { c.send(:m, **h3) } + assert_raise(ArgumentError) { c.send(:m, a: 1, **h2) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -1611,12 +1473,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.public_send(:m, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.public_send(:m, **kw)) - end + assert_raise(ArgumentError) { c.public_send(:m, **{}) } + assert_raise(ArgumentError) { c.public_send(:m, **kw) } assert_equal(h, c.public_send(:m, **h)) assert_equal(h, c.public_send(:m, a: 1)) assert_equal(h2, c.public_send(:m, **h2)) @@ -1634,39 +1492,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.public_send(:m, **h2)) assert_equal(h3, c.public_send(:m, **h3)) assert_equal(h3, c.public_send(:m, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.public_send(:m, h)) - end + assert_raise(ArgumentError) { c.public_send(:m, h) } assert_raise(ArgumentError) { c.public_send(:m, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.public_send(:m, h3) } - end + assert_raise(ArgumentError) { c.public_send(:m, h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.public_send(:m, **{}) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - c.public_send(:m, **kw) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.public_send(:m, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.public_send(:m, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.public_send(:m, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.public_send(:m, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.public_send(:m, a: 1, **h2)) - end + assert_raise(ArgumentError) { c.public_send(:m, **{}) } + assert_raise(ArgumentError) { c.public_send(:m, **kw) } + assert_raise(ArgumentError) { c.public_send(:m, **h) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1) } + assert_raise(ArgumentError) { c.public_send(:m, **h2) } + assert_raise(ArgumentError) { c.public_send(:m, **h3) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -1716,12 +1556,8 @@ class TestKeywordArguments < Test::Unit::TestCase args end m = c.method(:send) - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, m.call(:m, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, m.call(:m, **kw)) - end + assert_raise(ArgumentError) { m.call(:m, **{}) } + assert_raise(ArgumentError) { m.call(:m, **kw) } assert_equal(h, m.call(:m, **h)) assert_equal(h, m.call(:m, a: 1)) assert_equal(h2, m.call(:m, **h2)) @@ -1740,40 +1576,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, m.call(:m, **h2)) assert_equal(h3, m.call(:m, **h3)) assert_equal(h3, m.call(:m, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, m.call(:m, h)) - end + assert_raise(ArgumentError) { m.call(:m, h) } assert_raise(ArgumentError) { m.call(:m, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { m.call(:m, h3) } - end + assert_raise(ArgumentError) { m.call(:m, h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end m = c.method(:send) - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - m.call(:m, **{}) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - m.call(:m, **kw) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], m.call(:m, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], m.call(:m, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], m.call(:m, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], m.call(:m, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], m.call(:m, a: 1, **h2)) - end + assert_raise(ArgumentError) { m.call(:m, **{}) } + assert_raise(ArgumentError) { m.call(:m, **kw) } + assert_raise(ArgumentError) { m.call(:m, **h) } + assert_raise(ArgumentError) { m.call(:m, a: 1) } + assert_raise(ArgumentError) { m.call(:m, **h2) } + assert_raise(ArgumentError) { m.call(:m, **h3) } + assert_raise(ArgumentError) { m.call(:m, a: 1, **h2) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -1821,12 +1639,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, :m.to_proc.call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, :m.to_proc.call(c, **kw)) - end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) } assert_equal(h, :m.to_proc.call(c, **h)) assert_equal(h, :m.to_proc.call(c, a: 1)) assert_equal(h2, :m.to_proc.call(c, **h2)) @@ -1844,39 +1658,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, :m.to_proc.call(c, **h2)) assert_equal(h3, :m.to_proc.call(c, **h3)) assert_equal(h3, :m.to_proc.call(c, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, :m.to_proc.call(c, h)) - end + assert_raise(ArgumentError) { :m.to_proc.call(c, h) } assert_raise(ArgumentError) { :m.to_proc.call(c, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { :m.to_proc.call(c, h3) } - end + assert_raise(ArgumentError) { :m.to_proc.call(c, h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], :m.to_proc.call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], :m.to_proc.call(c, **kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], :m.to_proc.call(c, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], :m.to_proc.call(c, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], :m.to_proc.call(c, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], :m.to_proc.call(c, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], :m.to_proc.call(c, a: 1, **h2)) - end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1, **h2) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -1924,12 +1720,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, m.call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, m.call(c, **kw)) - end + assert_raise(ArgumentError) { m.call(c, **{}) } + assert_raise(ArgumentError) { m.call(c, **kw) } assert_equal(h, m.call(c, **h)) assert_equal(h, m.call(c, a: 1)) assert_equal(h2, m.call(c, **h2)) @@ -1947,39 +1739,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, m.call(c, **h2)) assert_equal(h3, m.call(c, **h3)) assert_equal(h3, m.call(c, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, m.call(c, h)) - end + assert_raise(ArgumentError) { m.call(c, h) } assert_raise(ArgumentError) { m.call(c, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { m.call(c, h3) } - end + assert_raise(ArgumentError) { m.call(c, h3) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], m.call(c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], m.call(c, **kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], m.call(c, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], m.call(c, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], m.call(c, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], m.call(c, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], m.call(c, a: 1, **h2)) - end + assert_raise(ArgumentError) { m.call(c, **{}) } + assert_raise(ArgumentError) { m.call(c, **kw) } + assert_raise(ArgumentError) { m.call(c, **h) } + assert_raise(ArgumentError) { m.call(c, a: 1) } + assert_raise(ArgumentError) { m.call(c, **h2) } + assert_raise(ArgumentError) { m.call(c, **h3) } + assert_raise(ArgumentError) { m.call(c, a: 1, **h2) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -2026,12 +1800,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.method_missing(_, args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -2049,39 +1819,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg=1, **args) @@ -2128,22 +1880,12 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { c.m(**h3) } assert_raise(ArgumentError) { c.m(a: 1, **h2) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, args) - args - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**{})) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**kw)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -2161,50 +1903,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `m'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `m'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, arg, **args) - [arg, args] - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**{})) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**kw)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(**h)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg=1, **args) @@ -2252,12 +1965,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.method_missing(_, args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -2275,39 +1984,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.m(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.m(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h2, kw], c.m(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg=1, **args) @@ -2344,12 +2035,8 @@ class TestKeywordArguments < Test::Unit::TestCase class << c define_method(:m) {|arg| arg } end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, c.m(**kw)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } assert_equal(h, c.m(**h)) assert_equal(h, c.m(a: 1)) assert_equal(h2, c.m(**h2)) @@ -2379,39 +2066,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.m(**h2)) assert_equal(h3, c.m(**h3)) assert_equal(h3, c.m(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal(h, c.m(h)) - end + assert_raise(ArgumentError) { c.m(h) } assert_raise(ArgumentError) { c.m(h2) } - assert_warn(/The last argument is split into positional and keyword parameters/m) do - assert_raise(ArgumentError) { c.m(h3) } - end + assert_raise(ArgumentError) { c.m(h3) } c = Object.new class << c define_method(:m) {|arg, **opt| [arg, opt] } end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([kw, kw], c.m(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([kw, kw], c.m(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], c.m(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], c.m(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h2, kw], c.m(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], c.m(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], c.m(a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } c = Object.new class << c @@ -2429,23 +2098,15 @@ class TestKeywordArguments < Test::Unit::TestCase class << c define_method(:m) {|*args, **opt| [args, opt] } end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal([[], h], c.m(h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal([[h], h], c.m(h, h)) - end + assert_equal([[h], kw], c.m(h)) + assert_equal([[h, h], kw], c.m(h, h)) c = Object.new class << c define_method(:m) {|arg=nil, a: nil| [arg, a] } end - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_equal([h2, 1], c.m(h3)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_equal([h2, 1], c.m(**h3)) - end + assert_equal([h3, nil], c.m(h3)) + assert_raise(ArgumentError) { c.m(**h3) } end def test_define_method_method_kwsplat @@ -2472,12 +2133,8 @@ class TestKeywordArguments < Test::Unit::TestCase define_method(:m) {|arg| arg } end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, m.call(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, m.call(**kw)) - end + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } assert_equal(h, m.call(**h)) assert_equal(h, m.call(a: 1)) assert_equal(h2, m.call(**h2)) @@ -2509,40 +2166,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, m.call(**h2)) assert_equal(h3, m.call(**h3)) assert_equal(h3, m.call(a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal(h, m.call(h)) - end + assert_raise(ArgumentError) { m.call(h) } assert_raise(ArgumentError) { m.call(h2) } - assert_warn(/The last argument is split into positional and keyword parameters/m) do - assert_raise(ArgumentError) { m.call(h3) } - end + assert_raise(ArgumentError) { m.call(h3) } c = Object.new class << c define_method(:m) {|arg, **opt| [arg, opt] } end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([kw, kw], m.call(**{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([kw, kw], m.call(**kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], m.call(**h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], m.call(a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h2, kw], m.call(**h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], m.call(**h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], m.call(a: 1, **h2)) - end + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } + assert_raise(ArgumentError) { m.call(**h) } + assert_raise(ArgumentError) { m.call(a: 1) } + assert_raise(ArgumentError) { m.call(**h2) } + assert_raise(ArgumentError) { m.call(**h3) } + assert_raise(ArgumentError) { m.call(a: 1, **h2) } c = Object.new class << c @@ -2562,24 +2201,16 @@ class TestKeywordArguments < Test::Unit::TestCase define_method(:m) {|*args, **opt| [args, opt] } end m = c.method(:m) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal([[], h], m.call(h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal([[h], h], m.call(h, h)) - end + assert_equal([[h], kw], m.call(h)) + assert_equal([[h, h], kw], m.call(h, h)) c = Object.new class << c define_method(:m) {|arg=nil, a: nil| [arg, a] } end m = c.method(:m) - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_equal([h2, 1], m.call(h3)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for method/m) do - assert_equal([h2, 1], m.call(**h3)) - end + assert_equal([h3, nil], m.call(h3)) + assert_raise(ArgumentError) { m.call(**h3) } end def test_attr_reader_kwsplat @@ -2631,12 +2262,8 @@ class TestKeywordArguments < Test::Unit::TestCase class << c attr_writer :m end - assert_warn(/The keyword argument for `m=' is passed as the last hash parameter/) do - c.send(:m=, **{}) - end - assert_warn(/The keyword argument for `m=' is passed as the last hash parameter/) do - c.send(:m=, **kw) - end + assert_raise(ArgumentError) { c.send(:m=, **{}) } + assert_raise(ArgumentError) { c.send(:m=, **kw) } assert_equal(h, c.send(:m=, **h)) assert_equal(h, c.send(:m=, a: 1)) assert_equal(h2, c.send(:m=, **h2)) @@ -2663,12 +2290,8 @@ class TestKeywordArguments < Test::Unit::TestCase attr_writer :m end m = c.method(:m=) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - m.call(**{}) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - m.call(**kw) - end + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } assert_equal(h, m.call(**h)) assert_equal(h, m.call(a: 1)) assert_equal(h2, m.call(**h2)) @@ -2691,13 +2314,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1], h1], foo.call(1, :a=>1, &->(*args, **kw){[args, kw]})) assert_equal([1, h1], foo.call(1, :a=>1, &->(*args){args})) - assert_warn(/The last argument is used as the keyword parameter/) do - assert_equal([[1], h1], foo.call(1, {:a=>1}, &->(*args, **kw){[args, kw]})) - end + assert_equal([[1, h1], {}], foo.call(1, {:a=>1}, &->(*args, **kw){[args, kw]})) assert_equal([1, h1], foo.call(1, {:a=>1}, &->(*args){args})) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h1, {}], foo.call(:a=>1, &->(arg, **kw){[arg, kw]})) - end + assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) } assert_equal(h1, foo.call(:a=>1, &->(arg){arg})) [->(){}, ->(arg){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr| @@ -2724,6 +2343,12 @@ class TestKeywordArguments < Test::Unit::TestCase end def test_ruby2_keywords + assert_raise(ArgumentError) do + Class.new do + ruby2_keywords + end + end + c = Class.new do ruby2_keywords def foo(meth, *args) send(meth, *args) @@ -2741,6 +2366,11 @@ class TestKeywordArguments < Test::Unit::TestCase baz(*args) end + define_method(:block_splat) {|*args| } + ruby2_keywords :block_splat, def foo_bar_after_bmethod(*args) + bar(*args) + end + ruby2_keywords def foo_baz2(*args) baz(*args) baz(*args) @@ -2781,6 +2411,13 @@ class TestKeywordArguments < Test::Unit::TestCase args end + def empty_method + end + + def opt(arg = :opt) + arg + end + ruby2_keywords def foo_dbar(*args) dbar(*args) end @@ -2789,6 +2426,16 @@ class TestKeywordArguments < Test::Unit::TestCase dbaz(*args) end + ruby2_keywords def clear_last_empty_method(*args) + args.last.clear + empty_method(*args) + end + + ruby2_keywords def clear_last_opt(*args) + args.last.clear + opt(*args) + end + define_method(:dbar) do |*args, **kw| [args, kw] end @@ -2876,6 +2523,7 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h1], o.foo(:foo_baz, 1, :a=>1)) assert_equal([[1], h1], o.foo_foo_bar(1, :a=>1)) assert_equal([1, h1], o.foo_foo_baz(1, :a=>1)) + assert_equal([[1], h1], o.foo_bar_after_bmethod(1, :a=>1)) assert_equal([[1], h1], o.foo(:bar, 1, **h1)) assert_equal([1, h1], o.foo(:baz, 1, **h1)) @@ -2891,6 +2539,7 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h1], o.foo(:foo_baz, 1, **h1)) assert_equal([[1], h1], o.foo_foo_bar(1, **h1)) assert_equal([1, h1], o.foo_foo_baz(1, **h1)) + assert_equal([[1], h1], o.foo_bar_after_bmethod(1, **h1)) assert_equal([[h1], {}], o.foo(:bar, h1, **{})) assert_equal([h1], o.foo(:baz, h1, **{})) @@ -2906,23 +2555,17 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([h1], o.foo(:foo_baz, h1, **{})) assert_equal([[h1], {}], o.foo_foo_bar(h1, **{})) assert_equal([h1], o.foo_foo_baz(h1, **{})) + assert_equal([[h1], {}], o.foo_bar_after_bmethod(h1, **{})) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.foo(:bar, 1, h1)) - end + assert_equal([[1, h1], {}], o.foo(:bar, 1, h1)) assert_equal([1, h1], o.foo(:baz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.bfoo(:bar, 1, h1)) - end + assert_equal([[1, h1], {}], o.bfoo(:bar, 1, h1)) assert_equal([1, h1], o.bfoo(:baz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.store_foo(:bar, 1, h1)) - end + assert_equal([[1, h1], {}], o.store_foo(:bar, 1, h1)) assert_equal([1, h1], o.store_foo(:baz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.foo_bar(1, h1)) - end + assert_equal([[1, h1], {}], o.foo_bar(1, h1)) assert_equal([1, h1], o.foo_baz(1, h1)) + assert_equal([[1, h1], {}], o.foo_bar_after_bmethod(1, h1)) assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, :a=>1)) assert_equal([1, h1, 1], o.foo_mod(:baz, 1, :a=>1)) @@ -2934,10 +2577,10 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, **h1)) assert_equal([1, h1, 1], o.foo_baz_mod(1, **h1)) - assert_equal([[h1, {}, 1], {}], o.foo_mod(:bar, h1, **{})) - assert_equal([h1, {}, 1], o.foo_mod(:baz, h1, **{})) - assert_equal([[h1, {}, 1], {}], o.foo_bar_mod(h1, **{})) - assert_equal([h1, {}, 1], o.foo_baz_mod(h1, **{})) + assert_equal([[h1, 1], {}], o.foo_mod(:bar, h1, **{})) + assert_equal([h1, 1], o.foo_mod(:baz, h1, **{})) + assert_equal([[h1, 1], {}], o.foo_bar_mod(h1, **{})) + assert_equal([h1, 1], o.foo_baz_mod(h1, **{})) assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1)) assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1)) @@ -2971,43 +2614,29 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([[h1], {}], o.foo_dbar(h1, **{})) assert_equal([h1], o.foo_dbaz(h1, **{})) - assert_warn(/The last argument is used as the keyword parameter.* for method/m) do - assert_equal([[1], h1], o.foo(:dbar, 1, h1)) - end + assert_equal([[1, h1], {}], o.foo(:dbar, 1, h1)) assert_equal([1, h1], o.foo(:dbaz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for method/m) do - assert_equal([[1], h1], o.bfoo(:dbar, 1, h1)) - end + assert_equal([[1, h1], {}], o.bfoo(:dbar, 1, h1)) assert_equal([1, h1], o.bfoo(:dbaz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for method/m) do - assert_equal([[1], h1], o.store_foo(:dbar, 1, h1)) - end + assert_equal([[1, h1], {}], o.store_foo(:dbar, 1, h1)) assert_equal([1, h1], o.store_foo(:dbaz, 1, h1)) - assert_warn(/The last argument is used as the keyword parameter.* for method/m) do - assert_equal([[1], h1], o.foo_dbar(1, h1)) - end + assert_equal([[1, h1], {}], o.foo_dbar(1, h1)) assert_equal([1, h1], o.foo_dbaz(1, h1)) assert_equal([[1], h1], o.block(1, :a=>1)) assert_equal([[1], h1], o.block(1, **h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do - assert_equal([[1], h1], o.block(1, h1)) - end + assert_equal([[1, h1], {}], o.block(1, h1)) assert_equal([[h1], {}], o.block(h1, **{})) assert_equal([[1], h1], o.cfunc(1, :a=>1)) assert_equal([[1], h1], o.cfunc(1, **h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `initialize'/m) do - assert_equal([[1], h1], o.cfunc(1, h1)) - end + assert_equal([[1, h1], {}], o.cfunc(1, h1)) assert_equal([[h1], {}], o.cfunc(h1, **{})) o = mmkw.new assert_equal([[:b, 1], h1], o.b(1, :a=>1)) assert_equal([[:b, 1], h1], o.b(1, **h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `method_missing'/m) do - assert_equal([[:b, 1], h1], o.b(1, h1)) - end + assert_equal([[:b, 1, h1], {}], o.b(1, h1)) assert_equal([[:b, h1], {}], o.b(h1, **{})) o = mmnokw.new @@ -3019,9 +2648,7 @@ class TestKeywordArguments < Test::Unit::TestCase o = implicit_super.new assert_equal([[1], h1], o.bar(1, :a=>1)) assert_equal([[1], h1], o.bar(1, **h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.bar(1, h1)) - end + assert_equal([[1, h1], {}], o.bar(1, h1)) assert_equal([[h1], {}], o.bar(h1, **{})) assert_equal([1, h1], o.baz(1, :a=>1)) @@ -3032,9 +2659,7 @@ class TestKeywordArguments < Test::Unit::TestCase o = explicit_super.new assert_equal([[1], h1], o.bar(1, :a=>1)) assert_equal([[1], h1], o.bar(1, **h1)) - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.bar(1, h1)) - end + assert_equal([[1, h1], {}], o.bar(1, h1)) assert_equal([[h1], {}], o.bar(h1, **{})) assert_equal([1, h1], o.baz(1, :a=>1)) @@ -3042,19 +2667,11 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h1], o.baz(1, h1)) assert_equal([h1], o.baz(h1, **{})) - c.class_eval do - remove_method(:bar) - def bar(*args, **kw) - [args, kw] - end - end - assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do - assert_equal([[1], h1], o.foo(:pass_bar, 1, :a=>1)) - end + assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1)) + assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1)) - assert_warn(/The last argument is used as the keyword parameter.* for `initialize'/m) do - assert_equal([[1], h1], o.foo(:pass_cfunc, 1, :a=>1)) - end + assert_equal(:opt, o.clear_last_opt(a: 1)) + assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) } assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do assert_nil(c.send(:ruby2_keywords, :bar)) @@ -3142,25 +2759,14 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal(c, [c].dig(0, **{})) assert_equal(c, [c].dig(0, **kw)) - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal(h, [c].dig(0, **h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal(h, [c].dig(0, a: 1)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_raise(ArgumentError) { [c].dig(0, **h3) } - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } - end - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal(h, [c].dig(0, h)) - end + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + assert_raise(ArgumentError) { [c].dig(0, h) } assert_raise(ArgumentError) { [c].dig(0, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_raise(ArgumentError) { [c].dig(0, h3) } - end + assert_raise(ArgumentError) { [c].dig(0, h3) } c.singleton_class.remove_method(:dig) def c.dig(arg, **args) @@ -3180,26 +2786,14 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal(c, [c].dig(0, **{})) assert_equal(c, [c].dig(0, **kw)) - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal([1, h], [c].dig(0, **h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal([1, h], [c].dig(0, a: 1)) - end + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) assert_equal([h2, kw], [c].dig(0, **h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_equal([h2, h], [c].dig(0, **h3)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_equal([h2, h], [c].dig(0, a: 1, **h2)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `dig'/m) do - assert_equal([1, h], [c].dig(0, h)) - end + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + assert_equal([h, {}], [c].dig(0, h)) assert_equal([h2, kw], [c].dig(0, h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `dig'/m) do - assert_equal([h2, h], [c].dig(0, h3)) - end + assert_equal([h3, kw], [c].dig(0, h3)) assert_equal([h, kw], [c].dig(0, h, **{})) assert_equal([h2, kw], [c].dig(0, h2, **{})) assert_equal([h3, kw], [c].dig(0, h3, **{})) @@ -3252,25 +2846,14 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal(c, [c].dig(0, **{})) assert_equal(c, [c].dig(0, **kw)) - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal(h, [c].dig(0, **h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal(h, [c].dig(0, a: 1)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_raise(ArgumentError) { [c].dig(0, **h3) } - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } - end - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal(h, [c].dig(0, h)) - end + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + assert_raise(ArgumentError) { [c].dig(0, h) } assert_raise(ArgumentError) { [c].dig(0, h2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_raise(ArgumentError) { [c].dig(0, h3) } - end + assert_raise(ArgumentError) { [c].dig(0, h3) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg, **args) @@ -3290,26 +2873,14 @@ class TestKeywordArguments < Test::Unit::TestCase end assert_equal(c, [c].dig(0, **{})) assert_equal(c, [c].dig(0, **kw)) - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal([1, h], [c].dig(0, **h)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal([1, h], [c].dig(0, a: 1)) - end + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) assert_equal([h2, kw], [c].dig(0, **h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_equal([h2, h], [c].dig(0, **h3)) - end - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_equal([h2, h], [c].dig(0, a: 1, **h2)) - end - assert_warn(/The last argument is used as the keyword parameter.*for `method_missing'/m) do - assert_equal([1, h], [c].dig(0, h)) - end + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + assert_equal([h, kw], [c].dig(0, h)) assert_equal([h2, kw], [c].dig(0, h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `method_missing'/m) do - assert_equal([h2, h], [c].dig(0, h3)) - end + assert_equal([h3, kw], [c].dig(0, h3)) assert_equal([h, kw], [c].dig(0, h, **{})) assert_equal([h2, kw], [c].dig(0, h2, **{})) assert_equal([h3, kw], [c].dig(0, h3, **{})) @@ -3342,12 +2913,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size } m = ->(args){ args } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, c.to_enum(:each, **{}, &m).size) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal(kw, c.to_enum(:each, **kw, &m).size) - end + assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size } assert_equal(kw, c.to_enum(:each, kw, **kw, &m).size) assert_equal(h, c.to_enum(:each, **h, &m).size) assert_equal(h, c.to_enum(:each, a: 1, &m).size) @@ -3363,36 +2930,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.to_enum(:each, **h2, &m).size) assert_equal(h3, c.to_enum(:each, **h3, &m).size) assert_equal(h3, c.to_enum(:each, a: 1, **h2, &m).size) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal(h, c.to_enum(:each, h, &m).size) - end + assert_raise(ArgumentError) { c.to_enum(:each, h, &m).size } assert_raise(ArgumentError) { c.to_enum(:each, h2, &m).size } - assert_warn(/The last argument is split into positional and keyword parameters/m) do - assert_raise(ArgumentError) { c.to_enum(:each, h3, &m).size } - end + assert_raise(ArgumentError) { c.to_enum(:each, h3, &m).size } m = ->(arg, **args){ [arg, args] } - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - c.to_enum(:each, **{}, &m).size - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - c.to_enum(:each, **kw, &m).size - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], c.to_enum(:each, **h, &m).size) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h, kw], c.to_enum(:each, a: 1, &m).size) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h2, kw], c.to_enum(:each, **h2, &m).size) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], c.to_enum(:each, **h3, &m).size) - end - assert_warn(/The keyword argument is passed as the last hash parameter/m) do - assert_equal([h3, kw], c.to_enum(:each, a: 1, **h2, &m).size) - end + assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h2, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h3, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size } assert_equal([h, kw], c.to_enum(:each, h, &m).size) assert_equal([h2, kw], c.to_enum(:each, h2, &m).size) assert_equal([h3, kw], c.to_enum(:each, h3, &m).size) @@ -3405,13 +2954,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.to_enum(:each, **h2, &m).size) assert_equal([1, h3], c.to_enum(:each, **h3, &m).size) assert_equal([1, h3], c.to_enum(:each, a: 1, **h2, &m).size) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal([1, h], c.to_enum(:each, h, &m).size) - end + assert_equal([h, kw], c.to_enum(:each, h, &m).size) assert_equal([h2, kw], c.to_enum(:each, h2, &m).size) - assert_warn(/The last argument is split into positional and keyword parameters/m) do - assert_equal([h2, h], c.to_enum(:each, h3, &m).size) - end + assert_equal([h3, kw], c.to_enum(:each, h3, &m).size) end def test_instance_exec_kwsplat @@ -3440,12 +2985,8 @@ class TestKeywordArguments < Test::Unit::TestCase assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } m = ->(args) { args } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**{}, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**kw, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } assert_equal(kw, c.instance_exec(kw, **kw, &m)) assert_equal(h, c.instance_exec(**h, &m)) assert_equal(h, c.instance_exec(a: 1, &m)) @@ -3461,36 +3002,18 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.instance_exec(**h2, &m)) assert_equal(h3, c.instance_exec(**h3, &m)) assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/) do - assert_equal(h, c.instance_exec(h, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(h, &m) } assert_raise(ArgumentError) { c.instance_exec(h2, &m) } - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_raise(ArgumentError) { c.instance_exec(h3, &m) } - end + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } m = ->(arg, **args) { [arg, args] } - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**{}, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**kw, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(**h, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(a: 1, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, kw], c.instance_exec(**h2, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(**h3, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) assert_equal([h3, kw], c.instance_exec(h3, &m)) @@ -3503,13 +3026,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.instance_exec(**h2, &m)) assert_equal([1, h3], c.instance_exec(**h3, &m)) assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal([1, h], c.instance_exec(h, &m)) - end + assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) - assert_warn(/The last argument is split into positional and keyword parameters/m) do - assert_equal([h2, h], c.instance_exec(h3, &m)) - end + assert_equal([h3, kw], c.instance_exec(h3, &m)) end def test_instance_exec_method_kwsplat @@ -3548,12 +3067,8 @@ class TestKeywordArguments < Test::Unit::TestCase args end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**{}, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**kw, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } assert_equal(kw, c.instance_exec(kw, **kw, &m)) assert_equal(h, c.instance_exec(**h, &m)) assert_equal(h, c.instance_exec(a: 1, &m)) @@ -3573,40 +3088,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.instance_exec(**h2, &m)) assert_equal(h3, c.instance_exec(**h3, &m)) assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/) do - assert_equal(h, c.instance_exec(h, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(h, &m) } assert_raise(ArgumentError) { c.instance_exec(h2, &m) } - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_raise(ArgumentError) { c.instance_exec(h3, &m) } - end + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**{}, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**kw, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(**h, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(a: 1, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, kw], c.instance_exec(**h2, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(**h3, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) assert_equal([h3, kw], c.instance_exec(h3, &m)) @@ -3623,13 +3120,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.instance_exec(**h2, &m)) assert_equal([1, h3], c.instance_exec(**h3, &m)) assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal([1, h], c.instance_exec(h, &m)) - end + assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_equal([h2, h], c.instance_exec(h3, &m)) - end + assert_equal([h3, kw], c.instance_exec(h3, &m)) end def test_instance_exec_define_method_kwsplat @@ -3668,12 +3161,8 @@ class TestKeywordArguments < Test::Unit::TestCase args end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**{}, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(**kw, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } assert_equal(kw, c.instance_exec(kw, **kw, &m)) assert_equal(h, c.instance_exec(**h, &m)) assert_equal(h, c.instance_exec(a: 1, &m)) @@ -3693,40 +3182,22 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.instance_exec(**h2, &m)) assert_equal(h3, c.instance_exec(**h3, &m)) assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/) do - assert_equal(h, c.instance_exec(h, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(h, &m) } assert_raise(ArgumentError) { c.instance_exec(h2, &m) } - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_raise(ArgumentError) { c.instance_exec(h3, &m) } - end + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } c.singleton_class.remove_method(:m) c.define_singleton_method(:m) do |arg, **args| [arg, args] end m = c.method(:m) - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**{}, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(**kw, &m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(**h, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(a: 1, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, kw], c.instance_exec(**h2, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(**h3, &m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(a: 1, **h2, &m)) - end + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) assert_equal([h3, kw], c.instance_exec(h3, &m)) @@ -3743,13 +3214,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.instance_exec(**h2, &m)) assert_equal([1, h3], c.instance_exec(**h3, &m)) assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal([1, h], c.instance_exec(h, &m)) - end + assert_equal([h, kw], c.instance_exec(h, &m)) assert_equal([h2, kw], c.instance_exec(h2, &m)) - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_equal([h2, h], c.instance_exec(h3, &m)) - end + assert_equal([h3, kw], c.instance_exec(h3, &m)) end def test_instance_exec_sym_proc_kwsplat @@ -3785,12 +3252,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.m(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(c, **{}, &:m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal(kw, c.instance_exec(c, **kw, &:m)) - end + assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) } assert_equal(kw, c.instance_exec(c, kw, **kw, &:m)) assert_equal(h, c.instance_exec(c, **h, &:m)) assert_equal(h, c.instance_exec(c, a: 1, &:m)) @@ -3809,39 +3272,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(h2, c.instance_exec(c, **h2, &:m)) assert_equal(h3, c.instance_exec(c, **h3, &:m)) assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m)) - assert_warn(/The last argument is used as the keyword parameter/) do - assert_equal(h, c.instance_exec(c, h, &:m)) - end + assert_raise(ArgumentError) { c.instance_exec(c, h, &:m) } assert_raise(ArgumentError) { c.instance_exec(c, h2, &:m) } - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) } - end + assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) } c.singleton_class.remove_method(:m) def c.m(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(c, **{}, &:m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - c.instance_exec(c, **kw, &:m) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(c, **h, &:m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h, kw], c.instance_exec(c, a: 1, &:m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h2, kw], c.instance_exec(c, **h2, &:m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(c, **h3, &:m)) - end - assert_warn(/The keyword argument is passed as the last hash parameter/) do - assert_equal([h3, kw], c.instance_exec(c, a: 1, **h2, &:m)) - end + assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) } assert_equal([h, kw], c.instance_exec(c, h, &:m)) assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) assert_equal([h3, kw], c.instance_exec(c, h3, &:m)) @@ -3857,13 +3302,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.instance_exec(c, **h2, &:m)) assert_equal([1, h3], c.instance_exec(c, **h3, &:m)) assert_equal([1, h3], c.instance_exec(c, a: 1, **h2, &:m)) - assert_warn(/The last argument is used as the keyword parameter/m) do - assert_equal([1, h], c.instance_exec(c, h, &:m)) - end + assert_equal([h, kw], c.instance_exec(c, h, &:m)) assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) - assert_warn(/The last argument is split into positional and keyword parameters/) do - assert_equal([h2, h], c.instance_exec(c, h3, &:m)) - end + assert_equal([h3, kw], c.instance_exec(c, h3, &:m)) end def test_rb_yield_block_kwsplat @@ -3902,12 +3343,8 @@ class TestKeywordArguments < Test::Unit::TestCase def c.c(args) args end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal(kw, c.m(:c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal(kw, c.m(:c, **kw)) - end + assert_raise(ArgumentError) { c.m(:c, **{}) } + assert_raise(ArgumentError) { c.m(:c, **kw) } assert_equal(kw, c.m(:c, kw, **kw)) assert_equal(h, c.m(:c, **h)) assert_equal(h, c.m(:c, a: 1)) @@ -3927,39 +3364,21 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([h2, h2], c.m(:c, **h2, &m)) assert_equal([h3, h3], c.m(:c, **h3, &m)) assert_equal([h3, h3], c.m(:c, a: 1, **h2, &m)) - assert_warn(/The last argument is used as the keyword parameter.*for `c'/m) do - assert_equal([h, h], c.m(:c, h, &m)) - end + assert_raise(ArgumentError) { c.m(:c, h, &m) } assert_raise(ArgumentError) { c.m(:c, h2, &m) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `c'/m) do - assert_raise(ArgumentError) { c.m(:c, h3, &m) } - end + assert_raise(ArgumentError) { c.m(:c, h3, &m) } c.singleton_class.remove_method(:c) def c.c(arg, **args) [arg, args] end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([kw, kw], c.m(:c, **{})) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([kw, kw], c.m(:c, **kw)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([h, kw], c.m(:c, **h)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([h, kw], c.m(:c, a: 1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([h2, kw], c.m(:c, **h2)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([h3, kw], c.m(:c, **h3)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `c'/m) do - assert_equal([h3, kw], c.m(:c, a: 1, **h2)) - end + assert_raise(ArgumentError) { c.m(:c, **{}, &m) } + assert_raise(ArgumentError) { c.m(:c, **kw, &m) } + assert_raise(ArgumentError) { c.m(:c, **h, &m) } + assert_raise(ArgumentError) { c.m(:c, a: 1, &m) } + assert_raise(ArgumentError) { c.m(:c, **h2, &m) } + assert_raise(ArgumentError) { c.m(:c, **h3, &m) } + assert_raise(ArgumentError) { c.m(:c, a: 1, **h2, &m) } assert_equal([h, kw], c.m(:c, h)) assert_equal([h2, kw], c.m(:c, h2)) assert_equal([h3, kw], c.m(:c, h3)) @@ -3975,13 +3394,9 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, h2], c.m(:c, **h2)) assert_equal([1, h3], c.m(:c, **h3)) assert_equal([1, h3], c.m(:c, a: 1, **h2)) - assert_warn(/The last argument is used as the keyword parameter.*for `c'/m) do - assert_equal([1, h], c.m(:c, h)) - end + assert_equal([h, kw], c.m(:c, h)) assert_equal([h2, kw], c.m(:c, h2)) - assert_warn(/The last argument is split into positional and keyword parameters.*for `c'/m) do - assert_equal([h2, h], c.m(:c, h3)) - end + assert_equal([h3, kw], c.m(:c, h3)) end def p1 @@ -4109,25 +3524,21 @@ class TestKeywordArguments < Test::Unit::TestCase def test_rest_keyrest bug7665 = '[ruby-core:51278]' bug8463 = '[ruby-core:55203] [Bug #8463]' - expect = [*%w[foo bar], {zzz: 42}] - assert_warn(/The last argument is used as the keyword parameter.* for `rest_keyrest'/m) do - assert_equal(expect, rest_keyrest(*expect), bug7665) - end + a = [*%w[foo bar], {zzz: 42}] + splat_expect = a + [{}] + nonsplat_expect = [a, {}] + assert_equal(splat_expect, rest_keyrest(*a), bug7665) + assert_equal(nonsplat_expect, rest_keyrest(a), bug7665) + pr = proc {|*args, **opt| next *args, opt} - assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do - assert_equal(expect, pr.call(*expect), bug7665) - end - assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do - assert_equal(expect, pr.call(expect), bug8463) - end + assert_equal(splat_expect, pr.call(*a), bug7665) + assert_equal(nonsplat_expect, pr.call(a), bug8463) + pr = proc {|a, *b, **opt| next a, *b, opt} - assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do - assert_equal(expect, pr.call(expect), bug8463) - end + assert_equal(splat_expect, pr.call(a), bug8463) + pr = proc {|a, **opt| next a, opt} - assert_warn(/The last argument is used as the keyword parameter.* for `call'/m) do - assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463) - end + assert_equal(splat_expect.values_at(0, -1), pr.call(splat_expect), bug8463) end def req_plus_keyword(x, **h) @@ -4142,16 +3553,10 @@ class TestKeywordArguments < Test::Unit::TestCase [a, h] end - def test_keyword_split - assert_warn(/The keyword argument is passed as the last hash parameter.* for `req_plus_keyword'/m) do - assert_equal([{:a=>1}, {}], req_plus_keyword(:a=>1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `req_plus_keyword'/m) do - assert_equal([{"a"=>1}, {}], req_plus_keyword("a"=>1)) - end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `req_plus_keyword'/m) do - assert_equal([{"a"=>1, :a=>1}, {}], req_plus_keyword("a"=>1, :a=>1)) - end + def test_keyword_no_split + assert_raise(ArgumentError) { req_plus_keyword(:a=>1) } + assert_raise(ArgumentError) { req_plus_keyword("a"=>1) } + assert_raise(ArgumentError) { req_plus_keyword("a"=>1, :a=>1) } assert_equal([{:a=>1}, {}], req_plus_keyword({:a=>1})) assert_equal([{"a"=>1}, {}], req_plus_keyword({"a"=>1})) assert_equal([{"a"=>1, :a=>1}, {}], req_plus_keyword({"a"=>1, :a=>1})) @@ -4159,24 +3564,16 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, {:a=>1}], opt_plus_keyword(:a=>1)) assert_equal([1, {"a"=>1}], opt_plus_keyword("a"=>1)) assert_equal([1, {"a"=>1, :a=>1}], opt_plus_keyword("a"=>1, :a=>1)) - assert_warn(/The last argument is used as the keyword parameter.* for `opt_plus_keyword'/m) do - assert_equal([1, {:a=>1}], opt_plus_keyword({:a=>1})) - end + assert_equal([{:a=>1}, {}], opt_plus_keyword({:a=>1})) assert_equal([{"a"=>1}, {}], opt_plus_keyword({"a"=>1})) - assert_warn(/The last argument is split into positional and keyword parameters.* for `opt_plus_keyword'/m) do - assert_equal([{"a"=>1}, {:a=>1}], opt_plus_keyword({"a"=>1, :a=>1})) - end + assert_equal([{"a"=>1, :a=>1}, {}], opt_plus_keyword({"a"=>1, :a=>1})) assert_equal([[], {:a=>1}], splat_plus_keyword(:a=>1)) assert_equal([[], {"a"=>1}], splat_plus_keyword("a"=>1)) assert_equal([[], {"a"=>1, :a=>1}], splat_plus_keyword("a"=>1, :a=>1)) - assert_warn(/The last argument is used as the keyword parameter.* for `splat_plus_keyword'/m) do - assert_equal([[], {:a=>1}], splat_plus_keyword({:a=>1})) - end + assert_equal([[{:a=>1}], {}], splat_plus_keyword({:a=>1})) assert_equal([[{"a"=>1}], {}], splat_plus_keyword({"a"=>1})) - assert_warn(/The last argument is split into positional and keyword parameters.* for `splat_plus_keyword'/m) do - assert_equal([[{"a"=>1}], {:a=>1}], splat_plus_keyword({"a"=>1, :a=>1})) - end + assert_equal([[{"a"=>1, :a=>1}], {}], splat_plus_keyword({"a"=>1, :a=>1})) end def test_bare_kwrest @@ -4313,6 +3710,25 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([42, {:bar=>"x"}], b.new.foo(42), bug8236) end + def test_super_with_keyword_kwrest + base = Class.new do + def foo(**h) + h + end + end + a = Class.new(base) do + attr_reader :h + def foo(a:, b:, **h) + @h = h + super + end + end + + o = a.new + assert_equal({a: 1, b: 2, c: 3}, o.foo(a: 1, b: 2, c: 3)) + assert_equal({c: 3}, o.h) + end + def test_zsuper_only_named_kwrest bug8416 = '[ruby-core:55033] [Bug #8416]' base = Class.new do @@ -4321,11 +3737,15 @@ class TestKeywordArguments < Test::Unit::TestCase end end a = Class.new(base) do + attr_reader :h def foo(**h) + @h = h super end end - assert_equal({:bar=>"x"}, a.new.foo(bar: "x"), bug8416) + o = a.new + assert_equal({:bar=>"x"}, o.foo(bar: "x"), bug8416) + assert_equal({:bar=>"x"}, o.h) end def test_zsuper_only_anonymous_kwrest @@ -4355,15 +3775,12 @@ class TestKeywordArguments < Test::Unit::TestCase end def test_precedence_of_keyword_arguments_with_post_argument - bug8993 = '[ruby-core:57706] [Bug #8993]' a = Class.new do def foo(a, b, c=1, *d, e, f:2, **g) [a, b, c, d, e, f, g] end end - assert_warn(/The keyword argument is passed as the last hash parameter.* for `foo'/m) do - assert_equal([1, 2, 1, [], {:f=>5}, 2, {}], a.new.foo(1, 2, f:5), bug8993) - end + assert_raise(ArgumentError) { a.new.foo(1, 2, f:5) } end def test_splat_keyword_nondestructive @@ -4390,18 +3807,12 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal({a: 1, b: 2}, m1(**o, **o2) {|x| break x}, bug9898) end - def test_implicit_hash_conversion - bug10016 = '[ruby-core:63593] [Bug #10016]' - + def test_no_implicit_hash_conversion o = Object.new def o.to_hash() { k: 9 } end assert_equal([1, 42, [], o, :key, {}, nil], f9(1, o)) - assert_warn(/The last argument is used as the keyword parameter.* for `m1'/m) do - assert_equal([1, 9], m1(1, o) {|a, k: 0| break [a, k]}, bug10016) - end - assert_warn(/The last argument is used as the keyword parameter.* for `m1'/m) do - assert_equal([1, 9], m1(1, o, &->(a, k: 0) {break [a, k]}), bug10016) - end + assert_equal([1, 0], m1(1, o) {|a, k: 0| break [a, k]}) + assert_raise(ArgumentError) { m1(1, o, &->(a, k: 0) {break [a, k]}) } end def test_splat_hash @@ -4412,7 +3823,7 @@ class TestKeywordArguments < Test::Unit::TestCase def m.f3(**a) a; end def m.f4(*a) a; end o = {a: 1} - assert_raise_with_message(ArgumentError, /unknown keyword: :a/) { + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given 1, expected 0\)/) { m.f(**o) } o = {} @@ -4520,7 +3931,7 @@ class TestKeywordArguments < Test::Unit::TestCase def test_nonsymbol_key result = m(["a" => 10]) { |a = nil, **b| [a, b] } - assert_equal([{"a" => 10}, {}], result) + assert_equal([[{"a" => 10}], {}], result) end def method_for_test_to_hash_call_during_setup_complex_parameters k1:, k2:, **rest_kw @@ -4654,6 +4065,12 @@ class TestKeywordArguments < Test::Unit::TestCase def test_splat_empty_hash_with_block_passing assert_valid_syntax("bug15087(**{}, &nil)") end + + def test_do_not_use_newarraykwsplat + assert_equal([42, "foo", 424242], f2(*[], 42, **{})) + a = [1, 2, 3] + assert_equal([[1,2,3,4,5,6], "foo", 424242, {:k=>:k}], f7(*a, 4,5,6, k: :k)) + end end class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase @@ -4699,22 +4116,12 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } - redef = -> do - c.singleton_class.remove_method(:m) - eval <<-END - def c.m(args) - args - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.call(**{}, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal(kw, c.call(**kw, &:m)) + c.singleton_class.remove_method(:m) + def c.m(args) + args end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } assert_equal(h, c.call(**h, &:m)) assert_equal(h, c.call(a: 1, &:m)) assert_equal(h2, c.call(**h2, &:m)) @@ -4732,50 +4139,21 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_equal(h2, c.call(**h2, &:m)) assert_equal(h3, c.call(**h3, &:m)) assert_equal(h3, c.call(a: 1, **h2, &:m)) - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(h, c.call(h, &:m)) - end + assert_raise(ArgumentError) { c.call(h, &:m) } assert_raise(ArgumentError) { c.call(h2, &:m) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `call'/m) do - assert_raise(ArgumentError) { c.call(h3, &:m) } - end + assert_raise(ArgumentError) { c.call(h3, &:m) } - redef = -> do - c.singleton_class.remove_method(:m) - eval <<-END - def c.m(arg, **args) - [arg, args] - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], c.call(**{}, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([kw, kw], c.call(**kw, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.call(**h, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h, kw], c.call(a: 1, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h2, kw], c.call(**h2, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.call(**h3, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `m'/m) do - assert_equal([h3, kw], c.call(a: 1, **h2, &:m)) + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } + assert_raise(ArgumentError) { c.call(**h, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, &:m) } + assert_raise(ArgumentError) { c.call(**h2, &:m) } + assert_raise(ArgumentError) { c.call(**h3, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } c.singleton_class.remove_method(:m) def c.m(arg=1, **args) @@ -4819,22 +4197,12 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, args) - args - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.call(**{}, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.call(**kw, &:m)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } assert_equal(h, c.call(**h, &:m)) assert_equal(h, c.call(a: 1, &:m)) assert_equal(h2, c.call(**h2, &:m)) @@ -4852,50 +4220,21 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_equal(h2, c.call(**h2, &:m)) assert_equal(h3, c.call(**h3, &:m)) assert_equal(h3, c.call(a: 1, **h2, &:m)) - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(h, c.call(h, &:m2)) - end + assert_raise(ArgumentError) { c.call(h, &:m2) } assert_raise(ArgumentError) { c.call(h2, &:m2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `call'/m) do - assert_raise(ArgumentError) { c.call(h3, &:m2) } - end + assert_raise(ArgumentError) { c.call(h3, &:m2) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, arg, **args) - [arg, args] - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.call(**{}, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.call(**kw, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.call(**h, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.call(a: 1, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h2, kw], c.call(**h2, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.call(**h3, &:m)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.call(a: 1, **h2, &:m)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } + assert_raise(ArgumentError) { c.call(**h, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, &:m2) } + assert_raise(ArgumentError) { c.call(**h2, &:m2) } + assert_raise(ArgumentError) { c.call(**h3, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg=1, **args) @@ -4939,22 +4278,12 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_raise(ArgumentError) { c.call(**h3, &:m2) } assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, args) - args - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.call(**{}, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal(kw, c.call(**kw, &:m2)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } assert_equal(h, c.call(**h, &:m2)) assert_equal(h, c.call(a: 1, &:m2)) assert_equal(h2, c.call(**h2, &:m2)) @@ -4972,50 +4301,21 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase assert_equal(h2, c.call(**h2, &:m2)) assert_equal(h3, c.call(**h3, &:m2)) assert_equal(h3, c.call(a: 1, **h2, &:m2)) - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(h, c.call(h, &:m2)) - end + assert_raise(ArgumentError) { c.call(h, &:m2) } assert_raise(ArgumentError) { c.call(h2, &:m2) } - assert_warn(/The last argument is split into positional and keyword parameters.*for `call'/m) do - assert_raise(ArgumentError) { c.call(h3, &:m2) } - end + assert_raise(ArgumentError) { c.call(h3, &:m2) } - redef = -> do - c.singleton_class.remove_method(:method_missing) - eval <<-END - def c.method_missing(_, arg, **args) - [arg, args] - end - END - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.call(**{}, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([kw, kw], c.call(**kw, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.call(**h, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h, kw], c.call(a: 1, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h2, kw], c.call(**h2, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.call(**h3, &:m2)) - end - redef[] - assert_warn(/The keyword argument is passed as the last hash parameter.* for `method_missing'/m) do - assert_equal([h3, kw], c.call(a: 1, **h2, &:m2)) + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } + assert_raise(ArgumentError) { c.call(**h, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, &:m2) } + assert_raise(ArgumentError) { c.call(**h2, &:m2) } + assert_raise(ArgumentError) { c.call(**h3, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } c.singleton_class.remove_method(:method_missing) def c.method_missing(_, arg=1, **args) @@ -5045,4 +4345,27 @@ class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase mock.new.foo end end + + def test_splat_fixnum + bug16603 = '[ruby-core:97047] [Bug #16603]' + assert_raise(TypeError, bug16603) { p(**42) } + assert_raise(TypeError, bug16603) { p(k:1, **42) } + end + + def test_value_omission + f = ->(**kwargs) { kwargs } + x = 1 + y = 2 + assert_equal({x: 1, y: 2}, f.call(x:, y:)) + assert_equal({x: 1, y: 2, z: 3}, f.call(x:, y:, z: 3)) + assert_equal({one: 1, two: 2}, f.call(one:, two:)) + end + + private def one + 1 + end + + private def two + 2 + end end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index b9412d4540..9949fab8c7 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -74,6 +74,135 @@ class TestLambdaParameters < Test::Unit::TestCase assert_raise(ArgumentError, bug9605) {proc(&plus).call [1,2]} end + def test_proc_inside_lambda_inside_method_return_inside_lambda_inside_method + def self.a + -> do + p = Proc.new{return :a} + p.call + end.call + end + assert_equal(:a, a) + + def self.b + lambda do + p = Proc.new{return :b} + p.call + end.call + end + assert_equal(:b, b) + end + + def test_proc_inside_lambda_inside_method_return_inside_lambda_outside_method + def self.a + -> do + p = Proc.new{return :a} + p.call + end + end + assert_equal(:a, a.call) + + def self.b + lambda do + p = Proc.new{return :b} + p.call + end + end + assert_equal(:b, b.call) + end + + def test_proc_inside_lambda_inside_method_return_outside_lambda_inside_method + def self.a + -> do + Proc.new{return :a} + end.call.call + end + assert_raise(LocalJumpError) {a} + + def self.b + lambda do + Proc.new{return :b} + end.call.call + end + assert_raise(LocalJumpError) {b} + end + + def test_proc_inside_lambda_inside_method_return_outside_lambda_outside_method + def self.a + -> do + Proc.new{return :a} + end + end + assert_raise(LocalJumpError) {a.call.call} + + def self.b + lambda do + Proc.new{return :b} + end + end + assert_raise(LocalJumpError) {b.call.call} + end + + def test_proc_inside_lambda2_inside_method_return_outside_lambda1_inside_method + def self.a + -> do + -> do + Proc.new{return :a} + end.call.call + end.call + end + assert_raise(LocalJumpError) {a} + + def self.b + lambda do + lambda do + Proc.new{return :a} + end.call.call + end.call + end + assert_raise(LocalJumpError) {b} + end + + def test_proc_inside_lambda_toplevel + assert_separately [], <<~RUBY + lambda{ + $g = proc{ return :pr } + }.call + begin + $g.call + rescue LocalJumpError + # OK! + else + raise + end + RUBY + end + + def pass_along(&block) + lambda(&block) + end + + def pass_along2(&block) + pass_along(&block) + end + + def test_create_non_lambda_for_proc_one_level + prev_warning, Warning[:deprecated] = Warning[:deprecated], false + f = pass_along {} + refute_predicate(f, :lambda?, '[Bug #15620]') + assert_nothing_raised(ArgumentError) { f.call(:extra_arg) } + ensure + Warning[:deprecated] = prev_warning + end + + def test_create_non_lambda_for_proc_two_levels + prev_warning, Warning[:deprecated] = Warning[:deprecated], false + f = pass_along2 {} + refute_predicate(f, :lambda?, '[Bug #15620]') + assert_nothing_raised(ArgumentError) { f.call(:extra_arg) } + ensure + Warning[:deprecated] = prev_warning + end + def test_instance_exec bug12568 = '[ruby-core:76300] [Bug #12568]' assert_nothing_raised(ArgumentError, bug12568) do diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 6e5c1714a5..2116d0ee31 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -160,6 +160,10 @@ class TestLazyEnumerator < Test::Unit::TestCase assert_equal([{?a=>97}, {?b=>98}, {?c=>99}], [?a, ?b, ?c].lazy.flat_map {|x| {x=>x.ord}}.force) end + def test_flat_map_take + assert_equal([1,2]*3, [[1,2]].cycle.lazy.take(3).flat_map {|x| x}.to_a) + end + def test_reject a = Step.new(1..6) assert_equal(4, a.reject {|x| x < 4}.first) @@ -678,4 +682,8 @@ EOS ary = (0..Float::INFINITY).lazy.with_index.take(2).to_a assert_equal([[0, 0], [1, 1]], ary) end + + def test_with_index_size + assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) + end end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index 7f4a329c4a..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 @@ -187,14 +187,14 @@ class TestRubyLiteral < Test::Unit::TestCase if defined?(RubyVM::InstructionSequence.compile_option) and RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal) def test_debug_frozen_string - src = 'n = 1; _="foo#{n ? "-#{n}" : ""}"'; f = "test.rb"; n = 1 + src = '_="foo-1"'; f = "test.rb"; n = 1 opt = {frozen_string_literal: true, debug_frozen_string_literal: true} str = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval assert_equal("foo-1", str) assert_predicate(str, :frozen?) assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { str << "x" - } + } unless ENV['RUBY_ISEQ_DUMP_DEBUG'] end def test_debug_frozen_string_in_array_literal @@ -461,17 +461,46 @@ class TestRubyLiteral < Test::Unit::TestCase def test_hash_duplicated_key h = EnvUtil.suppress_warning do - eval <<~end + eval "#{<<-"begin;"}\n#{<<-'end;'}" + begin; # This is a syntax that renders warning at very early stage. # eval used to delay warning, to be suppressible by EnvUtil. {"a" => 100, "b" => 200, "a" => 300, "a" => 400} - end + end; end assert_equal(2, h.size) assert_equal(400, h['a']) assert_equal(200, h['b']) assert_nil(h['c']) assert_equal(nil, h.key('300')) + + a = [] + h = EnvUtil.suppress_warning do + eval <<~end + # This is a syntax that renders warning at very early stage. + # eval used to delay warning, to be suppressible by EnvUtil. + {"a" => a.push(100).last, "b" => a.push(200).last, "a" => a.push(300).last, "a" => a.push(400).last} + end + end + assert_equal({'a' => 400, 'b' => 200}, h) + assert_equal([100, 200, 300, 400], a) + + assert_all_assertions_foreach( + "duplicated literal key", + ':foo', + '"a"', + '1000', + '1.0', + '1_000_000_000_000_000_000_000', + '1.0r', + '1.0i', + '1.72723e-77', + '//', + ) do |key| + assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) do + eval("{#{key} => :bar, #{key} => :foo}") + end + end end def test_hash_frozen_key_id @@ -488,6 +517,30 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal(100, h['a']) end + FOO = "foo" + + def test_hash_value_omission + x = 1 + y = 2 + assert_equal({x: 1, y: 2}, {x:, y:}) + assert_equal({x: 1, y: 2, z: 3}, {x:, y:, z: 3}) + assert_equal({one: 1, two: 2}, {one:, two:}) + b = binding + b.local_variable_set(:if, "if") + b.local_variable_set(:self, "self") + assert_equal({FOO: "foo", if: "if", self: "self"}, + eval('{FOO:, if:, self:}', b)) + assert_syntax_error('{"#{x}":}', /'\}'/) + end + + private def one + 1 + end + + private def two + 2 + end + def test_range assert_instance_of Range, (1..2) assert_equal(1..2, 1..2) diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index 6c7d0e6bae..c793520ac4 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -299,6 +299,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 + + skip "https://bugs.ruby-lang.org/issues/18338" + o = Object.new Encoding.default_external = Encoding::UTF_16BE @@ -310,6 +313,7 @@ class TestM17N < Test::Unit::TestCase def o.inspect "abc".encode(Encoding.default_external) end + assert_equal '[abc]', [o].inspect Encoding.default_external = Encoding::US_ASCII @@ -1324,10 +1328,14 @@ class TestM17N < Test::Unit::TestCase end def test_env - locale_encoding = Encoding.find("locale") + if RUBY_PLATFORM =~ /bccwin|mswin|mingw/ + env_encoding = Encoding::UTF_8 + else + env_encoding = Encoding.find("locale") + end ENV.each {|k, v| - assert_equal(locale_encoding, k.encoding, k) - assert_equal(locale_encoding, v.encoding, v) + assert_equal(env_encoding, k.encoding, proc {"key(#{k.encoding})=#{k.dump}"}) + assert_equal(env_encoding, v.encoding, proc {"key(#{k.encoding})=#{k.dump}\n" "value(#{v.encoding})=#{v.dump}"}) } end diff --git a/test/ruby/test_m17n_comb.rb b/test/ruby/test_m17n_comb.rb index cfb8bff882..e48a1948be 100644 --- a/test/ruby/test_m17n_comb.rb +++ b/test/ruby/test_m17n_comb.rb @@ -593,6 +593,21 @@ class TestM17NComb < Test::Unit::TestCase } end + def test_str_casecmp? + strings = STRINGS.dup + strings.push( + # prevent wrong single byte optimization + "\xC0".force_encoding("ISO-8859-1"), + "\xE0".force_encoding("ISO-8859-1"), + ) + combination(strings, strings) {|s1, s2| + #puts "#{encdump(s1)}.casecmp(#{encdump(s2)})" + next unless s1.valid_encoding? && s2.valid_encoding? && Encoding.compatible?(s1, s2) + r = s1.casecmp?(s2) + assert_equal(s1.downcase(:fold) == s2.downcase(:fold), r) + } + end + def test_str_center combination(STRINGS, [0,1,2,3,10]) {|s1, width| t = s1.center(width) @@ -751,8 +766,14 @@ class TestM17NComb < Test::Unit::TestCase # glibc 2.16 or later denies salt contained other than [0-9A-Za-z./] #7312 # we use this check to test strict and non-strict behavior separately #11045 strict_crypt = if defined? Etc::CS_GNU_LIBC_VERSION - glibcver = Etc.confstr(Etc::CS_GNU_LIBC_VERSION).scan(/\d+/).map(&:to_i) - (glibcver <=> [2, 16]) >= 0 + begin + confstr = Etc.confstr(Etc::CS_GNU_LIBC_VERSION) + rescue Errno::EINVAL + false + else + glibcver = confstr.scan(/\d+/).map(&:to_i) + (glibcver <=> [2, 16]) >= 0 + end end def test_str_crypt diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index f300710d2c..361d18dd4b 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -8,7 +8,6 @@ class TestMarshal < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -59,6 +58,8 @@ class TestMarshal < Test::Unit::TestCase TestMarshal.instance_eval { remove_const :StructOrNot } TestMarshal.const_set :StructOrNot, Class.new assert_raise(TypeError, "[ruby-dev:31709]") { Marshal.load(s) } + ensure + TestMarshal.instance_eval { remove_const :StructOrNot } end def test_struct_invalid_members @@ -67,6 +68,8 @@ class TestMarshal < Test::Unit::TestCase Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo") TestMarshal::StructInvalidMembers.members } + ensure + TestMarshal.instance_eval { remove_const :StructInvalidMembers } end class C @@ -157,20 +160,29 @@ class TestMarshal < Test::Unit::TestCase end def test_change_class_name + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) eval("class C3; def _dump(s); 'foo'; end; end") m = Marshal.dump(C3.new) assert_raise(TypeError) { Marshal.load(m) } + self.class.__send__(:remove_const, :C3) eval("C3 = nil") assert_raise(TypeError) { Marshal.load(m) } + ensure + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) end def test_change_struct + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) eval("C3 = Struct.new(:foo, :bar)") m = Marshal.dump(C3.new("FOO", "BAR")) + self.class.__send__(:remove_const, :C3) eval("C3 = Struct.new(:foo)") assert_raise(TypeError) { Marshal.load(m) } + self.class.__send__(:remove_const, :C3) eval("C3 = Struct.new(:foo, :baz)") assert_raise(TypeError) { Marshal.load(m) } + ensure + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) end class C4 @@ -542,7 +554,7 @@ class TestMarshal < Test::Unit::TestCase end class TestForRespondToFalse - def respond_to?(a) + def respond_to?(a, priv = false) false end end @@ -570,7 +582,7 @@ class TestMarshal < Test::Unit::TestCase end def test_continuation - require "continuation" + EnvUtil.suppress_warning {require "continuation"} c = Bug9523.new assert_raise_with_message(RuntimeError, /Marshal\.dump reentered at marshal_dump/) do Marshal.dump(c) @@ -596,7 +608,8 @@ class TestMarshal < Test::Unit::TestCase end def test_unloadable_data - c = eval("class Unloadable\u{23F0 23F3}<Time;;self;end") + name = "Unloadable\u{23F0 23F3}" + c = eval("class #{name} < Time;;self;end") c.class_eval { alias _dump_data _dump undef _dump @@ -605,10 +618,16 @@ class TestMarshal < Test::Unit::TestCase assert_raise_with_message(TypeError, /Unloadable\u{23F0 23F3}/) { Marshal.load(d) } + + # cleanup + self.class.class_eval do + remove_const name + end end def test_unloadable_userdef - c = eval("class Userdef\u{23F0 23F3}<Time;self;end") + name = "Userdef\u{23F0 23F3}" + c = eval("class #{name} < Time;self;end") class << c undef _load end @@ -616,6 +635,11 @@ class TestMarshal < Test::Unit::TestCase assert_raise_with_message(TypeError, /Userdef\u{23F0 23F3}/) { Marshal.load(d) } + + # cleanup + self.class.class_eval do + remove_const name + end end def test_unloadable_usrmarshal @@ -631,15 +655,16 @@ class TestMarshal < Test::Unit::TestCase def test_no_internal_ids opt = %w[--disable=gems] - args = [opt, 'Marshal.dump("",STDOUT)', true, true, encoding: Encoding::ASCII_8BIT] - out, err, status = EnvUtil.invoke_ruby(*args) + args = [opt, 'Marshal.dump("",STDOUT)', true, true] + kw = {encoding: Encoding::ASCII_8BIT} + out, err, status = EnvUtil.invoke_ruby(*args, **kw) assert_empty(err) assert_predicate(status, :success?) expected = out opt << "--enable=frozen-string-literal" opt << "--debug=frozen-string-literal" - out, err, status = EnvUtil.invoke_ruby(*args) + out, err, status = EnvUtil.invoke_ruby(*args, **kw) assert_empty(err) assert_predicate(status, :success?) assert_equal(expected, out) @@ -651,6 +676,23 @@ class TestMarshal < Test::Unit::TestCase assert_equal(['X', 'X'], Marshal.load(Marshal.dump(obj), ->(v) { v == str ? v.upcase : v })) end + def test_marshal_proc_string_encoding + string = "foo" + payload = Marshal.dump(string) + Marshal.load(payload, ->(v) { + if v.is_a?(String) + assert_equal(string, v) + assert_equal(string.encoding, v.encoding) + end + v + }) + end + + def test_marshal_proc_freeze + object = { foo: [42, "bar"] } + assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc) + end + def test_marshal_load_extended_class_crash assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; @@ -753,4 +795,136 @@ class TestMarshal < Test::Unit::TestCase Marshal.dump(obj) end end + + ruby2_keywords def ruby2_keywords_hash(*a) + a.last + end + + def ruby2_keywords_test(key: 1) + key + end + + def test_marshal_with_ruby2_keywords_hash + flagged_hash = ruby2_keywords_hash(key: 42) + data = Marshal.dump(flagged_hash) + hash = Marshal.load(data) + assert_equal(42, ruby2_keywords_test(*[hash])) + + hash2 = Marshal.load(data.sub(/\x06K(?=T\z)/, "\x08KEY")) + assert_raise(ArgumentError, /\(given 1, expected 0\)/) { + ruby2_keywords_test(*[hash2]) + } + hash2 = Marshal.load(data.sub(/:\x06K(?=T\z)/, "I\\&\x06:\x0dencoding\"\x0aUTF-7")) + assert_raise(ArgumentError, /\(given 1, expected 0\)/) { + ruby2_keywords_test(*[hash2]) + } + end + + def test_invalid_byte_sequence_symbol + data = Marshal.dump(:K) + data = data.sub(/:\x06K/, "I\\&\x06:\x0dencoding\"\x0dUTF-16LE") + assert_raise(ArgumentError, /UTF-16LE: "\\x4B"/) { + Marshal.load(data) + } + end + + def exception_test + raise + end + + def test_marshal_exception + begin + exception_test + rescue => e + e2 = Marshal.load(Marshal.dump(e)) + assert_equal(e.message, e2.message) + assert_equal(e.backtrace, e2.backtrace) + assert_nil(e2.backtrace_locations) # temporal + end + end + + def nameerror_test + unknown_method + end + + def test_marshal_nameerror + begin + nameerror_test + rescue NameError => e + e2 = Marshal.load(Marshal.dump(e)) + assert_equal(e.message.lines.first.chomp, e2.message.lines.first) + assert_equal(e.name, e2.name) + assert_equal(e.backtrace, e2.backtrace) + assert_nil(e2.backtrace_locations) # temporal + end + end + + class TestMarshalFreezeProc < Test::Unit::TestCase + include MarshalTestLib + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s, :freeze.to_proc) + end + end + + def _test_hash_compared_by_identity(h) + h.compare_by_identity + h["a" + "0"] = 1 + h["a" + "0"] = 2 + h = Marshal.load(Marshal.dump(h)) + assert_predicate(h, :compare_by_identity?) + a = h.to_a + assert_equal([["a0", 1], ["a0", 2]], a.sort) + assert_not_same(a[1][0], a[0][0]) + end + + def test_hash_compared_by_identity + _test_hash_compared_by_identity(Hash.new) + end + + def test_hash_default_compared_by_identity + _test_hash_compared_by_identity(Hash.new(true)) + end + + class TestMarshalFreeze < Test::Unit::TestCase + include MarshalTestLib + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s, freeze: true) + end + + def test_return_objects_are_frozen + source = ["foo", {}, /foo/, 1..2] + objects = decode(encode(source)) + assert_equal source, objects + assert_predicate objects, :frozen? + objects.each do |obj| + assert_predicate obj, :frozen? + end + end + + def test_proc_returned_object_are_not_frozen + source = ["foo", {}, /foo/, 1..2] + objects = Marshal.load(encode(source), ->(o) { o.dup }, freeze: true) + assert_equal source, objects + refute_predicate objects, :frozen? + objects.each do |obj| + refute_predicate obj, :frozen? + end + end + + def test_modules_and_classes_are_not_frozen + _objects = Marshal.load(encode([Object, Kernel]), freeze: true) + refute_predicate Object, :frozen? + refute_predicate Kernel, :frozen? + end + end end diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb index 5cc12bcfeb..73f44c6ae3 100644 --- a/test/ruby/test_math.rb +++ b/test/ruby/test_math.rb @@ -73,9 +73,9 @@ class TestMath < Test::Unit::TestCase check(1 * Math::PI / 4, Math.acos( 1.0 / Math.sqrt(2))) check(2 * Math::PI / 4, Math.acos( 0.0)) check(4 * Math::PI / 4, Math.acos(-1.0)) - assert_raise(Math::DomainError) { Math.acos(+1.0 + Float::EPSILON) } - assert_raise(Math::DomainError) { Math.acos(-1.0 - Float::EPSILON) } - assert_raise(Math::DomainError) { Math.acos(2.0) } + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(-1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(2.0) } end def test_asin @@ -83,9 +83,9 @@ class TestMath < Test::Unit::TestCase check( 1 * Math::PI / 4, Math.asin( 1.0 / Math.sqrt(2))) check( 2 * Math::PI / 4, Math.asin( 1.0)) check(-2 * Math::PI / 4, Math.asin(-1.0)) - assert_raise(Math::DomainError) { Math.asin(+1.0 + Float::EPSILON) } - assert_raise(Math::DomainError) { Math.asin(-1.0 - Float::EPSILON) } - assert_raise(Math::DomainError) { Math.asin(2.0) } + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(-1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(2.0) } end def test_atan @@ -119,8 +119,8 @@ class TestMath < Test::Unit::TestCase check(0, Math.acosh(1)) check(1, Math.acosh((Math::E ** 1 + Math::E ** -1) / 2)) check(2, Math.acosh((Math::E ** 2 + Math::E ** -2) / 2)) - assert_raise(Math::DomainError) { Math.acosh(1.0 - Float::EPSILON) } - assert_raise(Math::DomainError) { Math.acosh(0) } + assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(0) } end def test_asinh @@ -135,8 +135,8 @@ class TestMath < Test::Unit::TestCase check(2, Math.atanh(Math.sinh(2) / Math.cosh(2))) assert_nothing_raised { assert_infinity(Math.atanh(1)) } assert_nothing_raised { assert_infinity(-Math.atanh(-1)) } - assert_raise(Math::DomainError) { Math.atanh(+1.0 + Float::EPSILON) } - assert_raise(Math::DomainError) { Math.atanh(-1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(-1.0 - Float::EPSILON) } end def test_exp @@ -157,10 +157,14 @@ class TestMath < Test::Unit::TestCase assert_nothing_raised { assert_infinity(Math.log(1.0/0)) } assert_nothing_raised { assert_infinity(-Math.log(+0.0)) } assert_nothing_raised { assert_infinity(-Math.log(-0.0)) } - assert_raise(Math::DomainError) { Math.log(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(-Float::EPSILON) } assert_raise(TypeError) { Math.log(1,nil) } - assert_raise(Math::DomainError, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(1.0, -Float::EPSILON) } assert_nothing_raised { assert_nan(Math.log(0.0, 0.0)) } + assert_nothing_raised { assert_nan(Math.log(Float::NAN)) } + assert_nothing_raised { assert_nan(Math.log(1.0, Float::NAN)) } end def test_log2 @@ -172,7 +176,9 @@ class TestMath < Test::Unit::TestCase assert_nothing_raised { assert_infinity(Math.log2(1.0/0)) } assert_nothing_raised { assert_infinity(-Math.log2(+0.0)) } assert_nothing_raised { assert_infinity(-Math.log2(-0.0)) } - assert_raise(Math::DomainError) { Math.log2(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.log2(Float::NAN)) } end def test_log10 @@ -184,7 +190,9 @@ class TestMath < Test::Unit::TestCase assert_nothing_raised { assert_infinity(Math.log10(1.0/0)) } assert_nothing_raised { assert_infinity(-Math.log10(+0.0)) } assert_nothing_raised { assert_infinity(-Math.log10(-0.0)) } - assert_raise(Math::DomainError) { Math.log10(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.log10(Float::NAN)) } end def test_sqrt @@ -193,7 +201,9 @@ class TestMath < Test::Unit::TestCase check(2, Math.sqrt(4)) assert_nothing_raised { assert_infinity(Math.sqrt(1.0/0)) } assert_equal("0.0", Math.sqrt(-0.0).to_s) # insure it is +0.0, not -0.0 - assert_raise(Math::DomainError) { Math.sqrt(-1.0) } + assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-1.0) } + assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) } end def test_cbrt @@ -201,8 +211,11 @@ class TestMath < Test::Unit::TestCase check(-2, Math.cbrt(-8)) check(3, Math.cbrt(27)) check(-0.1, Math.cbrt(-0.001)) + check(0.0, Math.cbrt(0.0)) assert_nothing_raised { assert_infinity(Math.cbrt(1.0/0)) } assert_operator(Math.cbrt(1.0 - Float::EPSILON), :<=, 1.0) + assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) } + assert_nothing_raised { assert_nan(Math.cbrt(Float::NAN)) } end def test_frexp @@ -211,6 +224,7 @@ class TestMath < Test::Unit::TestCase assert_float_and_int([0.5, 1], Math.frexp(1.0)) assert_float_and_int([0.5, 2], Math.frexp(2.0)) assert_float_and_int([0.75, 2], Math.frexp(3.0)) + assert_nan(Math.frexp(Float::NAN)[0]) end def test_ldexp @@ -228,11 +242,13 @@ class TestMath < Test::Unit::TestCase def test_erf check(0, Math.erf(0)) check(1, Math.erf(1.0 / 0.0)) + assert_nan(Math.erf(Float::NAN)) end def test_erfc check(1, Math.erfc(0)) check(0, Math.erfc(1.0 / 0.0)) + assert_nan(Math.erfc(Float::NAN)) end def test_gamma @@ -257,11 +273,13 @@ class TestMath < Test::Unit::TestCase assert_infinity(Math.gamma(i-1), "Math.gamma(#{i-1}) should be INF") end - assert_raise(Math::DomainError) { Math.gamma(-Float::INFINITY) } + assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-Float::INFINITY) } + assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-1.0) } x = Math.gamma(-0.0) mesg = "Math.gamma(-0.0) should be -INF" assert_infinity(x, mesg) assert_predicate(x, :negative?, mesg) + assert_nan(Math.gamma(Float::NAN)) end def test_lgamma @@ -277,12 +295,14 @@ class TestMath < Test::Unit::TestCase assert_float_and_int([Math.log(15 * sqrt_pi / 8), 1], Math.lgamma(3.5)) assert_float_and_int([Math.log(6), 1], Math.lgamma(4)) - assert_raise(Math::DomainError) { Math.lgamma(-Float::INFINITY) } + assert_raise_with_message(Math::DomainError, /\blgamma\b/) { Math.lgamma(-Float::INFINITY) } x, sign = Math.lgamma(-0.0) mesg = "Math.lgamma(-0.0) should be [INF, -1]" assert_infinity(x, mesg) assert_predicate(x, :positive?, mesg) assert_equal(-1, sign, mesg) + x, sign = Math.lgamma(Float::NAN) + assert_nan(x) end def test_fixnum_to_f diff --git a/test/ruby/test_memory_view.rb b/test/ruby/test_memory_view.rb new file mode 100644 index 0000000000..5a39084d18 --- /dev/null +++ b/test/ruby/test_memory_view.rb @@ -0,0 +1,341 @@ +require "-test-/memory_view" +require "rbconfig/sizeof" + +class TestMemoryView < Test::Unit::TestCase + NATIVE_ENDIAN = MemoryViewTestUtils::NATIVE_ENDIAN + LITTLE_ENDIAN = :little_endian + BIG_ENDIAN = :big_endian + + %I(SHORT INT INT16 INT32 INT64 INTPTR LONG LONG_LONG FLOAT DOUBLE).each do |type| + name = :"#{type}_ALIGNMENT" + const_set(name, MemoryViewTestUtils.const_get(name)) + end + + def test_rb_memory_view_register_duplicated + assert_warning(/Duplicated registration of memory view to/) do + MemoryViewTestUtils.register(MemoryViewTestUtils::ExportableString) + end + end + + def test_rb_memory_view_register_nonclass + assert_raise(TypeError) do + MemoryViewTestUtils.register(Object.new) + end + end + + def sizeof(type) + RbConfig::SIZEOF[type.to_s] + end + + def test_rb_memory_view_item_size_from_format + [ + [nil, 1], ['c', 1], ['C', 1], + ['n', 2], ['v', 2], + ['l', 4], ['L', 4], ['N', 4], ['V', 4], ['f', 4], ['e', 4], ['g', 4], + ['q', 8], ['Q', 8], ['d', 8], ['E', 8], ['G', 8], + ['s', sizeof(:short)], ['S', sizeof(:short)], ['s!', sizeof(:short)], ['S!', sizeof(:short)], + ['i', sizeof(:int)], ['I', sizeof(:int)], ['i!', sizeof(:int)], ['I!', sizeof(:int)], + ['l!', sizeof(:long)], ['L!', sizeof(:long)], + ['q!', sizeof('long long')], ['Q!', sizeof('long long')], + ['j', sizeof(:intptr_t)], ['J', sizeof(:intptr_t)], + ].each do |format, expected| + actual, err = MemoryViewTestUtils.item_size_from_format(format) + assert_nil(err) + assert_equal(expected, actual, "rb_memory_view_item_size_from_format(#{format || 'NULL'}) == #{expected}") + end + end + + def test_rb_memory_view_item_size_from_format_composed + actual, = MemoryViewTestUtils.item_size_from_format("ccc") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("c3") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fd") + assert_equal(12, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fx2d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_with_spaces + # spaces should be ignored + actual, = MemoryViewTestUtils.item_size_from_format("f x2 d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_error + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccca")) + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccc4a")) + end + + def test_rb_memory_view_parse_item_format + total_size, members, err = MemoryViewTestUtils.parse_item_format("ccc2f3x2d4q!<") + assert_equal(58, total_size) + assert_nil(err) + assert_equal([ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 4, size: 4, repeat: 3}, + {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 18, size: 8, repeat: 4}, + {format: 'q', native_size_p: true, endianness: :little_endian, offset: 50, size: sizeof('long long'), repeat: 1} + ], + members) + end + + def test_rb_memory_view_parse_item_format_with_alignment_signle + [ + ["c", false, NATIVE_ENDIAN, 1, 1, 1], + ["C", false, NATIVE_ENDIAN, 1, 1, 1], + ["s", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["s!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["n", false, :big_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["v", false, :little_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["i", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["i!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["l", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["L", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["l!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["L!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["N", false, :big_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["V", false, :little_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["f", false, NATIVE_ENDIAN, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["e", false, :little_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["g", false, :big_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["Q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["Q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["d", false, NATIVE_ENDIAN, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["E", false, :little_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["G", false, :big_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["j", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ["J", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ].each do |type, native_size_p, endianness, alignment, size, repeat, total_size| + total_size, members, err = MemoryViewTestUtils.parse_item_format("|c#{type}") + assert_nil(err) + + padding_size = alignment - 1 + expected_total_size = 1 + padding_size + size + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: type[0], native_size_p: native_size_p, endianness: endianness, offset: alignment, size: size, repeat: repeat}, + ] + assert_equal(expected_result, members) + end + end + + def alignment_padding(total_size, alignment) + res = total_size % alignment + if res > 0 + alignment - res + else + 0 + end + end + + def test_rb_memory_view_parse_item_format_with_alignment_total_size_with_tail_padding + total_size, _members, err = MemoryViewTestUtils.parse_item_format("|lqc") + assert_nil(err) + + expected_total_size = sizeof(:int32_t) + expected_total_size += alignment_padding(expected_total_size, INT32_ALIGNMENT) + expected_total_size += sizeof(:int64_t) + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + expected_total_size += 1 + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + assert_equal(expected_total_size, total_size) + end + + def test_rb_memory_view_parse_item_format_with_alignment_compound + total_size, members, err = MemoryViewTestUtils.parse_item_format("|ccc2f3x2d4cq!<") + assert_nil(err) + + expected_total_size = 1 + 1 + 1*2 + expected_total_size += alignment_padding(expected_total_size, FLOAT_ALIGNMENT) + expected_total_size += sizeof(:float)*3 + 1*2 + expected_total_size += alignment_padding(expected_total_size, DOUBLE_ALIGNMENT) + expected_total_size += sizeof(:double)*4 + 1 + expected_total_size += alignment_padding(expected_total_size, LONG_LONG_ALIGNMENT) + expected_total_size += sizeof("long long") + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + ] + offset = 4 + + res = offset % FLOAT_ALIGNMENT + offset += FLOAT_ALIGNMENT - res if res > 0 + expected_result << {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 4, repeat: 3} + offset += 12 + + offset += 2 # 2x + + res = offset % DOUBLE_ALIGNMENT + offset += DOUBLE_ALIGNMENT - res if res > 0 + expected_result << {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 8, repeat: 4} + offset += 32 + + expected_result << {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 1, repeat: 1} + offset += 1 + + res = offset % LONG_LONG_ALIGNMENT + offset += LONG_LONG_ALIGNMENT - res if res > 0 + expected_result << {format: 'q', native_size_p: true, endianness: :little_endian, offset: offset, size: 8, repeat: 1} + + assert_equal(expected_result, members) + end + + def test_rb_memory_view_extract_item_members + m = MemoryViewTestUtils + assert_equal(1, m.extract_item_members([1].pack("c"), "c")) + assert_equal([1, 2], m.extract_item_members([1, 2].pack("ii"), "ii")) + assert_equal([1, 2, 3], m.extract_item_members([1, 2, 3].pack("cls"), "cls")) + end + + def test_rb_memory_view_extract_item_members_endianness + m = MemoryViewTestUtils + assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S>2")) + assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "n2")) + assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S<2")) + assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "v2")) + assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L>")) + assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "N")) + assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L<")) + assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "V")) + assert_equal(0x0102030405060708, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q>")) + assert_equal(0x0807060504030201, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q<")) + end + + def test_rb_memory_view_extract_item_members_float + m = MemoryViewTestUtils + packed = [1.23].pack("f") + assert_equal(packed.unpack("f")[0], m.extract_item_members(packed, "f")) + end + + def test_rb_memory_view_extract_item_members_float_endianness + m = MemoryViewTestUtils + hi, lo = [1.23].pack("f").unpack("L")[0].divmod(0x10000) + packed = [lo, hi].pack("S*") + assert_equal(packed.unpack("e")[0], m.extract_item_members(packed, "e")) + packed = [hi, lo].pack("S*") + assert_equal(packed.unpack("g")[0], m.extract_item_members(packed, "g")) + end + + def test_rb_memory_view_extract_item_members_doble + m = MemoryViewTestUtils + packed = [1.23].pack("d") + assert_equal(1.23, m.extract_item_members(packed, "d")) + end + + def test_rb_memory_view_extract_item_members_doble_endianness + m = MemoryViewTestUtils + hi, lo = [1.23].pack("d").unpack("Q")[0].divmod(0x10000) + packed = [lo, hi].pack("L*") + assert_equal(packed.unpack("E")[0], m.extract_item_members(packed, "E")) + packed = [hi, lo].pack("L*") + assert_equal(packed.unpack("G")[0], m.extract_item_members(packed, "G")) + end + + def test_rb_memory_view_available_p + es = MemoryViewTestUtils::ExportableString.new("ruby") + assert_equal(true, MemoryViewTestUtils.available?(es)) + es = MemoryViewTestUtils::ExportableString.new(nil) + assert_equal(false, MemoryViewTestUtils.available?(es)) + end + + def test_ref_count_with_exported_object + es = MemoryViewTestUtils::ExportableString.new("ruby") + assert_equal(1, MemoryViewTestUtils.ref_count_while_exporting(es, 1)) + assert_equal(2, MemoryViewTestUtils.ref_count_while_exporting(es, 2)) + assert_equal(10, MemoryViewTestUtils.ref_count_while_exporting(es, 10)) + assert_nil(MemoryViewTestUtils.ref_count_while_exporting(es, 0)) + end + + def test_rb_memory_view_init_as_byte_array + # ExportableString's memory view is initialized by rb_memory_view_init_as_byte_array + es = MemoryViewTestUtils::ExportableString.new("ruby") + memory_view_info = MemoryViewTestUtils.get_memory_view_info(es) + assert_equal({ + obj: es, + byte_size: 4, + readonly: true, + format: nil, + item_size: 1, + ndim: 1, + shape: nil, + strides: nil, + sub_offsets: nil + }, + memory_view_info) + end + + def test_rb_memory_view_get_with_memory_view_unavailable_object + es = MemoryViewTestUtils::ExportableString.new(nil) + memory_view_info = MemoryViewTestUtils.get_memory_view_info(es) + assert_nil(memory_view_info) + end + + def test_rb_memory_view_fill_contiguous_strides + row_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], true) + assert_equal([96, 32, 8], + row_major_strides) + + column_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], false) + assert_equal([8, 16, 48], + column_major_strides) + end + + def test_rb_memory_view_get_item_pointer_single_member + buf = [ 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12 ].pack("l!*") + shape = [3, 4] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil) + assert_equal(1, mv[[0, 0]]) + assert_equal(4, mv[[0, 3]]) + assert_equal(6, mv[[1, 1]]) + assert_equal(10, mv[[2, 1]]) + end + + def test_rb_memory_view_get_item_pointer_multiple_members + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + -1, -2, -3, -4, -5, -6, -7, -8].pack("s*") + shape = [2, 4] + strides = [4*sizeof(:short)*2, sizeof(:short)*2] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides) + assert_equal([1, 2], mv[[0, 0]]) + assert_equal([5, 6], mv[[0, 2]]) + assert_equal([-1, -2], mv[[1, 0]]) + assert_equal([-7, -8], mv[[1, 3]]) + end + + def test_ractor + assert_in_out_err([], <<-"end;", ["[5, 6]", "[-7, -8]"], []) + require "-test-/memory_view" + require "rbconfig/sizeof" + $VERBOSE = nil + r = Ractor.new RbConfig::SIZEOF["short"] do |sizeof_short| + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + -1, -2, -3, -4, -5, -6, -7, -8].pack("s*") + shape = [2, 4] + strides = [4*sizeof_short*2, sizeof_short*2] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides) + p mv[[0, 2]] + mv[[1, 3]] + end + p r.take + end; + end +end diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index bb506f1258..ac50a9d0b0 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -5,7 +5,6 @@ require 'test/unit' class TestMethod < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -104,6 +103,12 @@ class TestMethod < Test::Unit::TestCase assert_raise(TypeError) do um.bind(Base.new) end + + # cleanup + Derived.class_eval do + remove_method :foo + def foo() :derived; end + end end def test_callee @@ -459,6 +464,23 @@ class TestMethod < Test::Unit::TestCase c3.class_eval { alias bar foo } m3 = c3.new.method(:bar) assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m3.inspect, bug7806) + + bug15608 = '[ruby-core:91570] [Bug #15608]' + c4 = Class.new(c) + c4.class_eval { alias bar foo } + o = c4.new + o.singleton_class + m4 = o.method(:bar) + assert_equal("#<Method: #{c4.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m4.inspect, bug15608) + + bug17428 = '[ruby-core:101635] [Bug #17428]' + assert_equal("#<Method: #<Class:String>(Module)#prepend(*)>", String.method(:prepend).inspect, bug17428) + + c5 = Class.new(String) + m = Module.new{def prepend; end; alias prep prepend}; line_no = __LINE__ + c5.extend(m) + c6 = Class.new(c5) + assert_equal("#<Method: #<Class:#{c6.inspect}>(#{m.inspect})#prep(prepend)() #{__FILE__}:#{line_no}>", c6.method(:prep).inspect, bug17428) end def test_callee_top_level @@ -556,7 +578,7 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters) assert_equal([[:nokey]], method(:mnk).parameters) # pending - assert_equal([[:rest, :*], [:block, :&]], method(:mf).parameters) + assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters) end def test_unbound_parameters @@ -582,7 +604,7 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters) assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters) # pending - assert_equal([[:rest, :*], [:block, :&]], self.class.instance_method(:mf).parameters) + assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters) end def test_bmethod_bound_parameters @@ -790,7 +812,9 @@ class TestMethod < Test::Unit::TestCase assert_instance_of String, __dir__ assert_equal(File.dirname(File.realpath(__FILE__)), __dir__) bug8436 = '[ruby-core:55123] [Bug #8436]' - assert_equal(__dir__, eval("__dir__", binding), bug8436) + file, line = *binding.source_location + file = File.realpath(file) + assert_equal(__dir__, eval("__dir__", binding, file, line), bug8436) bug8662 = '[ruby-core:56099] [Bug #8662]' assert_equal("arbitrary", eval("__dir__", binding, "arbitrary/file.rb"), bug8662) assert_equal("arbitrary", Object.new.instance_eval("__dir__", "arbitrary/file.rb"), bug8662) @@ -812,7 +836,7 @@ class TestMethod < Test::Unit::TestCase assert_equal(c, c.instance_method(:foo).owner) assert_equal(c, x.method(:foo).owner) assert_equal(x.singleton_class, x.method(:bar).owner) - assert_not_equal(x.method(:foo), x.method(:bar), bug7613) + assert_equal(x.method(:foo), x.method(:bar), bug7613) assert_equal(c, x.method(:zot).owner, bug7993) assert_equal(c, c.instance_method(:zot).owner, bug7993) end @@ -1021,7 +1045,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} @@ -1031,20 +1055,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 @@ -1064,6 +1103,258 @@ class TestMethod < Test::Unit::TestCase '[ruby-core:85231] [Bug #14421]' end + def test_super_method_alias + c0 = Class.new do + def m1 + [:C0_m1] + end + def m2 + [:C0_m2] + end + end + + c1 = Class.new(c0) do + def m1 + [:C1_m1] + super + end + alias m2 m1 + end + + c2 = Class.new(c1) do + def m2 + [:C2_m2] + super + end + end + o1 = c2.new + assert_equal([:C2_m2, :C1_m1, :C0_m1], o1.m2) + + m = o1.method(:m2) + assert_equal([:C2_m2, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C0_m1], m.call) + + assert_nil(m.super_method) + end + + def test_super_method_alias_to_prepended_module + m = Module.new do + def m1 + [:P_m1] + super + end + + def m2 + [:P_m2] + super + end + end + + c0 = Class.new do + def m1 + [:C0_m1] + end + end + + c1 = Class.new(c0) do + def m1 + [:C1_m1] + super + end + prepend m + alias m2 m1 + end + + o1 = c1.new + assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], o1.m2) + + m = o1.method(:m2) + assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:P_m1, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C0_m1], m.call) + + assert_nil(m.super_method) + end + + # Bug 17780 + def test_super_method_module_alias + m = Module.new do + def foo + end + alias :f :foo + end + + method = m.instance_method(:f) + super_method = method.super_method + assert_nil(super_method) + end + + def test_method_visibility_predicates + v = Visibility.new + assert_equal(true, v.method(:mv1).public?) + assert_equal(true, v.method(:mv2).private?) + assert_equal(true, v.method(:mv3).protected?) + assert_equal(false, v.method(:mv2).public?) + assert_equal(false, v.method(:mv3).private?) + assert_equal(false, v.method(:mv1).protected?) + end + + def test_unbound_method_visibility_predicates + assert_equal(true, Visibility.instance_method(:mv1).public?) + assert_equal(true, Visibility.instance_method(:mv2).private?) + assert_equal(true, Visibility.instance_method(:mv3).protected?) + assert_equal(false, Visibility.instance_method(:mv2).public?) + assert_equal(false, Visibility.instance_method(:mv3).private?) + assert_equal(false, Visibility.instance_method(:mv1).protected?) + 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: B(A)#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + + a.remove_method(:foo) + + assert_equal "#<UnboundMethod: B(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: B(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: B(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 + 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 @@ -1100,17 +1391,22 @@ class TestMethod < Test::Unit::TestCase assert_equal([:bar, :foo], b.local_variables.sort, bug11012) end - class MethodInMethodClass - def m1 - def m2 - end + MethodInMethodClass_Setup = -> do + remove_const :MethodInMethodClass if defined? MethodInMethodClass - self.class.send(:define_method, :m3){} # [Bug #11754] + class MethodInMethodClass + def m1 + def m2 + end + self.class.send(:define_method, :m3){} # [Bug #11754] + end + private end - private end def test_method_in_method_visibility_should_be_public + MethodInMethodClass_Setup.call + assert_equal([:m1].sort, MethodInMethodClass.public_instance_methods(false).sort) assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort) @@ -1160,6 +1456,42 @@ class TestMethod < Test::Unit::TestCase assert_separately [], "RubyVM::InstructionSequence.compile_option = {trace_instruction: false}\n" + body end + def test_zsuper_private_override_instance_method + assert_separately(%w(--disable-gems), <<-'end;', timeout: 30) + # Bug #16942 [ruby-core:98691] + module M + def x + end + end + + module M2 + prepend Module.new + include M + private :x + end + + ::Object.prepend(M2) + + m = Object.instance_method(:x) + assert_equal M2, m.owner + end; + end + + def test_override_optimized_method_on_class_using_prepend + assert_separately(%w(--disable-gems), <<-'end;', timeout: 30) + # Bug #17725 [ruby-core:102884] + $VERBOSE = nil + String.prepend(Module.new) + class String + def + other + 'blah blah' + end + end + + assert_equal('blah blah', 'a' + 'b') + end; + end + def test_eqq assert_operator(0.method(:<), :===, 5) assert_not_operator(0.method(:<), :===, -5) @@ -1266,4 +1598,8 @@ class TestMethod < Test::Unit::TestCase assert_operator nummodule, :>, 0 assert_operator nummethod, :>, 0 end + + def test_invalidating_CC_ASAN + assert_ruby_status(['-e', 'using Module.new']) + end end diff --git a/test/ruby/test_method_cache.rb b/test/ruby/test_method_cache.rb new file mode 100644 index 0000000000..2ed89e47bf --- /dev/null +++ b/test/ruby/test_method_cache.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestMethodCache < Test::Unit::TestCase + def test_undef + # clear same + c0 = Class.new do + def foo; end + undef foo + end + + assert_raise(NoMethodError) do + c0.new.foo + end + + c0.class_eval do + def foo; :ok; end + end + + assert_equal :ok, c0.new.foo + end + + def test_undef_with_subclasses + # with subclasses + c0 = Class.new do + def foo; end + undef foo + end + + _c1 = Class.new(c0) + + assert_raise(NoMethodError) do + c0.new.foo + end + + c0.class_eval do + def foo; :ok; end + end + + assert_equal :ok, c0.new.foo + end + + def test_undef_with_subclasses_complicated + c0 = Class.new{ def foo; end } + c1 = Class.new(c0){ undef foo } + c2 = Class.new(c1) + c3 = Class.new(c2) + _c4 = Class.new(c3) + + assert_raise(NoMethodError) do + c3.new.foo + end + + c2.class_eval do + def foo; :c2; end + end + + assert_raise(NoMethodError) do + c1.new.foo + end + + assert_equal :c2, c3.new.foo + end + + def test_negative_cache_with_and_without_subclasses + c0 = Class.new{} + c1 = Class.new(c0){} + c0.new.foo rescue nil + c1.new.foo rescue nil + c1.module_eval{ def foo = :c1 } + c0.module_eval{ def foo = :c0 } + + assert_equal :c0, c0.new.foo + end +end + diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3ddfcd9c4f..b5414d139e 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -27,11 +27,13 @@ class TestModule < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil + @deprecated = Warning[:deprecated] + Warning[:deprecated] = true end def teardown $VERBOSE = @verbose + Warning[:deprecated] = @deprecated end def test_LT_0 @@ -85,8 +87,11 @@ class TestModule < Test::Unit::TestCase private :user3 end - module Other - def other + OtherSetup = -> do + remove_const :Other if defined? ::TestModule::Other + module Other + def other + end end end @@ -220,6 +225,8 @@ class TestModule < Test::Unit::TestCase @@class_eval = 'b' def test_class_eval + OtherSetup.call + Other.class_eval("CLASS_EVAL = 1") assert_equal(1, Other::CLASS_EVAL) assert_include(Other.constants, :CLASS_EVAL) @@ -260,7 +267,7 @@ class TestModule < Test::Unit::TestCase ].each do |name, msg| expected = "wrong constant name %s" % name msg = "#{msg}#{': ' if msg}wrong constant name #{name.dump}" - assert_raise_with_message(NameError, expected, "#{msg} to #{m}") do + assert_raise_with_message(NameError, Regexp.compile(Regexp.quote(expected)), "#{msg} to #{m}") do yield name end end @@ -294,6 +301,8 @@ class TestModule < Test::Unit::TestCase end def test_nested_get + OtherSetup.call + assert_equal Other, Object.const_get([self.class, 'Other'].join('::')) assert_equal User::USER, self.class.const_get([User, 'USER'].join('::')) assert_raise(NameError) { @@ -302,6 +311,8 @@ class TestModule < Test::Unit::TestCase end def test_nested_get_symbol + OtherSetup.call + const = [self.class, Other].join('::').to_sym assert_raise(NameError) {Object.const_get(const)} @@ -328,6 +339,8 @@ class TestModule < Test::Unit::TestCase end def test_nested_defined + OtherSetup.call + assert_send([Object, :const_defined?, [self.class.name, 'Other'].join('::')]) assert_send([self.class, :const_defined?, 'User::USER']) assert_not_send([self.class, :const_defined?, 'User::Foo']) @@ -335,6 +348,8 @@ class TestModule < Test::Unit::TestCase end def test_nested_defined_symbol + OtherSetup.call + const = [self.class, Other].join('::').to_sym assert_raise(NameError) {Object.const_defined?(const)} @@ -360,6 +375,8 @@ class TestModule < Test::Unit::TestCase end def test_const_set + OtherSetup.call + assert_not_operator(Other, :const_defined?, :KOALA) Other.const_set(:KOALA, 99) assert_operator(Other, :const_defined?, :KOALA) @@ -387,19 +404,20 @@ class TestModule < Test::Unit::TestCase assert_equal([:MIXIN, :USER], User.constants.sort) end - def test_self_initialize_copy - bug9535 = '[ruby-dev:47989] [Bug #9535]' - m = Module.new do - def foo - :ok - end - initialize_copy(self) + def test_initialize_copy + mod = Module.new { define_method(:foo) {:first} } + klass = Class.new { include mod } + instance = klass.new + assert_equal(:first, instance.foo) + new_mod = Module.new { define_method(:foo) { :second } } + assert_raise(TypeError) do + mod.send(:initialize_copy, new_mod) end - assert_equal(:ok, Object.new.extend(m).foo, bug9535) + 4.times { GC.start } + assert_equal(:first, instance.foo) # [BUG] unreachable end def test_initialize_copy_empty - bug9813 = '[ruby-dev:48182] [Bug #9813]' m = Module.new do def x end @@ -409,15 +427,66 @@ class TestModule < Test::Unit::TestCase assert_equal([:x], m.instance_methods) assert_equal([:@x], m.instance_variables) assert_equal([:X], m.constants) - m.module_eval do - initialize_copy(Module.new) + assert_raise(TypeError) do + m.module_eval do + initialize_copy(Module.new) + end + end + + m = Class.new(Module) do + def initialize_copy(other) + # leave uninitialized + end + end.new.dup + c = Class.new + assert_operator(c.include(m), :<, m) + cp = Module.instance_method(:initialize_copy) + assert_raise(TypeError) do + cp.bind_call(m, Module.new) + end + end + + class Bug18185 < Module + module InstanceMethods + end + attr_reader :ancestor_list + def initialize + @ancestor_list = ancestors + include InstanceMethods end - assert_empty(m.instance_methods, bug9813) - assert_empty(m.instance_variables, bug9813) - assert_empty(m.constants, bug9813) + class Foo + attr_reader :key + def initialize(key:) + @key = key + end + end + end + + def test_module_subclass_initialize + mod = Bug18185.new + c = Class.new(Bug18185::Foo) do + include mod + end + anc = c.ancestors + assert_include(anc, mod) + assert_equal(1, anc.count(BasicObject), ->{anc.inspect}) + b = c.new(key: 1) + assert_equal(1, b.key) + 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 + bug6454 = '[ruby-core:45132]' a = Module.new @@ -459,6 +528,169 @@ 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_equal(true, m < Enumerable) + end + + def test_prepend_self + m = Module.new + assert_equal([m], m.ancestors) + m.prepend(m) rescue nil + assert_equal([m], m.ancestors) + end + + def test_bug17590 + m = Module.new + c = Class.new + c.prepend(m) + c.include(m) + m.prepend(m) rescue nil + m2 = Module.new + m2.prepend(m) + c.include(m2) + + assert_equal([m, c, m2] + Object.ancestors, c.ancestors) + end + + def test_prepend_works_with_duped_classes + m = Module.new + a = Class.new do + def b; 2 end + prepend m + end + a2 = a.dup.new + a.class_eval do + alias _b b + def b; 1 end + end + assert_equal(2, a2.b) + end + + def test_gc_prepend_chain + assert_separately([], <<-EOS) + 10000.times { |i| + m1 = Module.new do + def foo; end + end + m2 = Module.new do + prepend m1 + def bar; end + end + m3 = Module.new do + def baz; end + prepend m2 + end + Class.new do + prepend m3 + end + } + GC.start + EOS + end + + def test_refine_module_then_include + assert_separately([], "#{<<~"end;"}\n") + module M + end + class C + include M + end + module RefinementBug + refine M do + def refined_method + :rm + end + end + end + using RefinementBug + + class A + include M + end + + assert_equal(:rm, C.new.refined_method) + end; + end + + def test_include_into_module_already_included + c = Class.new{def foo; [:c] end} + modules = lambda do || + sub = Class.new(c){def foo; [:sc] + super end} + [ + Module.new{def foo; [:m1] + super end}, + Module.new{def foo; [:m2] + super end}, + Module.new{def foo; [:m3] + super end}, + sub, + sub.new + ] + end + + m1, m2, m3, sc, o = modules.call + assert_equal([:sc, :c], o.foo) + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + m1.include m2 + assert_equal([:sc, :m1, :m2, :c], o.foo) + m2.include m3 + assert_equal([:sc, :m1, :m2, :m3, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.prepend m1 + assert_equal([:m1, :sc, :c], o.foo) + m1.include m2 + assert_equal([:m1, :m2, :sc, :c], o.foo) + m2.include m3 + assert_equal([:m1, :m2, :m3, :sc, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.include m2 + assert_equal([:sc, :m2, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :m2, :c], o.foo) + m1.include m2 + assert_equal([:m1, :sc, :m2, :c], o.foo) + m1.include m3 + assert_equal([:m1, :m3, :sc, :m2, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.include m3 + sc.include m2 + assert_equal([:sc, :m2, :m3, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :m2, :m3, :c], o.foo) + m1.include m2 + m1.include m3 + assert_equal([:m1, :sc, :m2, :m3, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + assert_equal([:sc, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :c], o.foo) + m1.prepend m2 + assert_equal([:m2, :m1, :sc, :c], o.foo) + m2.prepend m3 + assert_equal([:m3, :m2, :m1, :sc, :c], o.foo) + m1, m2, m3, sc, o = modules.call + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + sc.prepend m2 + assert_equal([:m2, :sc, :m1, :c], o.foo) + sc.prepend m3 + assert_equal([:m3, :m2, :sc, :m1, :c], o.foo) + m1, m2, m3, sc, o = modules.call + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + m2.prepend m3 + m1.include m2 + assert_equal([:sc, :m1, :m3, :m2, :c], o.foo) + end + def test_included_modules assert_equal([], Mixin.included_modules) assert_equal([Mixin], User.included_modules) @@ -469,6 +701,61 @@ class TestModule < Test::Unit::TestCase assert_equal([Comparable, Kernel], String.included_modules - mixins) end + def test_included_modules_with_prepend + m1 = Module.new + m2 = Module.new + m3 = Module.new + + m2.prepend m1 + m3.include m2 + assert_equal([m1, m2], m3.included_modules) + end + + def test_include_with_prepend + c = Class.new{def m; [:c] end} + p = Module.new{def m; [:p] + super end} + q = Module.new{def m; [:q] + super end; include p} + r = Module.new{def m; [:r] + super end; prepend q} + s = Module.new{def m; [:s] + super end; include r} + a = Class.new(c){def m; [:a] + super end; prepend p; include s} + assert_equal([:p, :a, :s, :q, :r, :c], a.new.m) + end + + def test_prepend_after_include + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m = Module.new{def m; [:m] + super end} + sc.include m + sc.prepend m + sc.prepend m + assert_equal([:m, :sc, :m, :c], sc.new.m) + + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m0 = Module.new{def m; [:m0] + super end} + m1 = Module.new{def m; [:m1] + super end} + m1.prepend m0 + sc.include m1 + sc.prepend m1 + assert_equal([:m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + sc.prepend m + assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + sc.prepend m1 + assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + + + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m0 = Module.new{def m; [:m0] + super end} + m1 = Module.new{def m; [:m1] + super end} + m1.include m0 + sc.include m1 + sc.prepend m + sc.prepend m1 + sc.prepend m1 + assert_equal([:m1, :m0, :m, :sc, :m1, :m0, :c], sc.new.m) + 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) @@ -519,6 +806,11 @@ class TestModule < Test::Unit::TestCase assert !c.method_defined?(:userx, false) c.define_method(:userx){} assert c.method_defined?(:userx, false) + + # cleanup + User.class_eval do + remove_const :FOO + end end def module_exec_aux @@ -549,6 +841,14 @@ class TestModule < Test::Unit::TestCase def dynamically_added_method_4; end end assert_method_defined?(User, :dynamically_added_method_4) + + # cleanup + User.class_eval do + remove_method :dynamically_added_method_1 + remove_method :dynamically_added_method_2 + remove_method :dynamically_added_method_3 + remove_method :dynamically_added_method_4 + end end def test_module_eval @@ -592,13 +892,13 @@ class TestModule < Test::Unit::TestCase n = Module.new m.const_set(:N, n) assert_nil(m.name) - assert_nil(n.name) + assert_match(/::N$/, n.name) assert_equal([:N], m.constants) m.module_eval("module O end") assert_equal([:N, :O], m.constants.sort) m.module_eval("class C; end") assert_equal([:C, :N, :O], m.constants.sort) - assert_nil(m::N.name) + assert_match(/::N$/, m::N.name) assert_match(/\A#<Module:.*>::O\z/, m::O.name) assert_match(/\A#<Module:.*>::C\z/, m::C.name) self.class.const_set(:M, m) @@ -608,12 +908,19 @@ class TestModule < Test::Unit::TestCase assert_equal(prefix+"C", m.const_get(:C).name) c = m.class_eval("Bug15891 = Class.new.freeze") assert_equal(prefix+"Bug15891", c.name) + ensure + self.class.class_eval {remove_const(:M)} end def test_private_class_method assert_raise(ExpectedException) { AClass.cm1 } assert_raise(ExpectedException) { AClass.cm3 } assert_equal("cm1cm2cm3", AClass.cm2) + + c = Class.new(AClass) + c.class_eval {private_class_method [:cm1, :cm2]} + assert_raise(NoMethodError, /private method/) {c.cm1} + assert_raise(NoMethodError, /private method/) {c.cm2} end def test_private_instance_methods @@ -636,6 +943,11 @@ class TestModule < Test::Unit::TestCase assert_equal("cm1", MyClass.cm1) assert_equal("cm1cm2cm3", MyClass.cm2) assert_raise(ExpectedException) { eval "MyClass.cm3" } + + c = Class.new(AClass) + c.class_eval {public_class_method [:cm1, :cm2]} + assert_equal("cm1", c.cm1) + assert_equal("cm1cm2cm3", c.cm2) end def test_public_instance_methods @@ -643,12 +955,116 @@ class TestModule < Test::Unit::TestCase assert_equal([:bClass1], BClass.public_instance_methods(false)) end + def test_s_public + o = (c = Class.new(AClass)).new + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + c.class_eval {public :aClass1} + assert_equal(:aClass1, o.aClass1) + + o = (c = Class.new(AClass)).new + c.class_eval {public :aClass1, :aClass2} + assert_equal(:aClass1, o.aClass1) + assert_equal(:aClass2, o.aClass2) + + o = (c = Class.new(AClass)).new + c.class_eval {public [:aClass1, :aClass2]} + assert_equal(:aClass1, o.aClass1) + assert_equal(:aClass2, o.aClass2) + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + end + + def test_s_private + o = (c = Class.new(AClass)).new + assert_equal(:aClass, o.aClass) + c.class_eval {private :aClass} + assert_raise(NoMethodError, /private method/) {o.aClass} + + o = (c = Class.new(AClass)).new + c.class_eval {private :aClass, :aClass2} + assert_raise(NoMethodError, /private method/) {o.aClass} + assert_raise(NoMethodError, /private method/) {o.aClass2} + + o = (c = Class.new(AClass)).new + c.class_eval {private [:aClass, :aClass2]} + assert_raise(NoMethodError, /private method/) {o.aClass} + assert_raise(NoMethodError, /private method/) {o.aClass2} + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + end + + def test_s_protected + aclass = Class.new(AClass) do + def _aClass(o) o.aClass; end + def _aClass1(o) o.aClass1; end + def _aClass2(o) o.aClass2; end + end + + o = (c = Class.new(aclass)).new + assert_equal(:aClass, o.aClass) + c.class_eval {protected :aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_equal(:aClass, c.new._aClass(o)) + + o = (c = Class.new(aclass)).new + c.class_eval {protected :aClass, :aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass1} + assert_equal(:aClass, c.new._aClass(o)) + assert_equal(:aClass1, c.new._aClass1(o)) + + o = (c = Class.new(aclass)).new + c.class_eval {protected [:aClass, :aClass1]} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass1} + assert_equal(:aClass, c.new._aClass(o)) + assert_equal(:aClass1, c.new._aClass1(o)) + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + 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" c2 = Module.constants assert_equal([:WALTER], c2 - c1) + Object.class_eval do + remove_const :WALTER + end + assert_equal([], Module.constants(true)) assert_equal([], Module.constants(false)) @@ -711,14 +1127,19 @@ class TestModule < Test::Unit::TestCase end def test_attr_obsoleted_flag - c = Class.new - c.class_eval do + c = Class.new do + extend Test::Unit::Assertions + extend Test::Unit::CoreAssertions def initialize @foo = :foo @bar = :bar end - attr :foo, true - attr :bar, false + assert_deprecated_warning(/optional boolean argument/) do + attr :foo, true + end + assert_deprecated_warning(/optional boolean argument/) do + attr :bar, false + end end o = c.new assert_equal(true, o.respond_to?(:foo)) @@ -763,6 +1184,7 @@ class TestModule < Test::Unit::TestCase assert_equal(:foo, c2.const_get(:Foo)) assert_raise(NameError) { c2.const_get(:Foo, false) } + c1.__send__(:remove_const, :Foo) eval("c1::Foo = :foo") assert_raise(NameError) { c1::Bar } assert_raise(NameError) { c2::Bar } @@ -960,6 +1382,28 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError) do c.instance_eval { attr_reader :"." } end + + assert_equal([:a], c.class_eval { attr :a }) + assert_equal([:b, :c], c.class_eval { attr :b, :c }) + assert_equal([:d], c.class_eval { attr_reader :d }) + assert_equal([:e, :f], c.class_eval { attr_reader :e, :f }) + assert_equal([:g=], c.class_eval { attr_writer :g }) + assert_equal([:h=, :i=], c.class_eval { attr_writer :h, :i }) + assert_equal([:j, :j=], c.class_eval { attr_accessor :j }) + assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor :k, :l }) + end + + def test_alias_method + c = Class.new do + def foo; :foo end + end + o = c.new + assert_respond_to(o, :foo) + assert_not_respond_to(o, :bar) + r = c.class_eval {alias_method :bar, :foo} + assert_respond_to(o, :bar) + assert_equal(:foo, o.bar) + assert_equal(:bar, r) end def test_undef @@ -1109,13 +1553,25 @@ class TestModule < Test::Unit::TestCase end def test_top_public_private - assert_in_out_err([], <<-INPUT, %w([:foo] [:bar]), []) + assert_in_out_err([], <<-INPUT, %w([:foo] [:bar] [:bar,\ :foo] [] [:bar,\ :foo] []), []) private def foo; :foo; end public def bar; :bar; end p self.private_methods.grep(/^foo$|^bar$/) p self.methods.grep(/^foo$|^bar$/) + + private :foo, :bar + p self.private_methods.grep(/^foo$|^bar$/).sort + + public :foo, :bar + p self.private_methods.grep(/^foo$|^bar$/).sort + + private [:foo, :bar] + p self.private_methods.grep(/^foo$|^bar$/).sort + + public [:foo, :bar] + p self.private_methods.grep(/^foo$|^bar$/).sort INPUT end @@ -1576,21 +2032,31 @@ class TestModule < Test::Unit::TestCase c = Class.new c.const_set(:FOO, "foo") c.deprecate_constant(:FOO) - assert_warn(/deprecated/) {c::FOO} - assert_warn(/#{c}::FOO is deprecated/) {Class.new(c)::FOO} + assert_warn(/deprecated/) do + Warning[:deprecated] = true + c::FOO + end + assert_warn(/#{c}::FOO is deprecated/) do + Warning[:deprecated] = true + Class.new(c)::FOO + end bug12382 = '[ruby-core:75505] [Bug #12382]' - assert_warn(/deprecated/, bug12382) {c.class_eval "FOO"} - end - - NIL = nil - FALSE = false - deprecate_constant(:NIL, :FALSE) - - def test_deprecate_nil_constant - w = EnvUtil.verbose_warning {2.times {FALSE}} - assert_equal(1, w.scan("::FALSE").size, w) - w = EnvUtil.verbose_warning {2.times {NIL}} - assert_equal(1, w.scan("::NIL").size, w) + assert_warn(/deprecated/, bug12382) do + Warning[:deprecated] = true + c.class_eval "FOO" + end + assert_warn('') do + Warning[:deprecated] = false + c::FOO + end + assert_warn('') do + Warning[:deprecated] = false + Class.new(c)::FOO + end + assert_warn('') do + Warning[:deprecated] = false + c.class_eval "FOO" + end end def test_constants_with_private_constant @@ -1830,7 +2296,7 @@ class TestModule < Test::Unit::TestCase assert_equal([:c2, :m0, :m1, :m2, :c0], c2.new.x) m3 = labeled_module("m3") {include m1; prepend m1} - assert_equal([m3, m0, m1], m3.ancestors) + assert_equal([m0, m1, m3, m0, m1], m3.ancestors) m3 = labeled_module("m3") {prepend m1; include m1} assert_equal([m0, m1, m3], m3.ancestors) m3 = labeled_module("m3") {prepend m1; prepend m1} @@ -1911,6 +2377,137 @@ class TestModule < Test::Unit::TestCase assert_equal(0, 1 / 2) end + def test_visibility_after_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + Module.new do + refine sc do + def x; :z end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + def test_prepend_visibility bug8005 = '[ruby-core:53106] [Bug #8005]' c = Class.new do @@ -1953,6 +2550,33 @@ class TestModule < Test::Unit::TestCase assert_include(im, mixin, bug8025) end + def test_prepended_module_with_super_and_alias + bug16736 = '[Bug #16736]' + + a = labeled_class("A") do + def m; "A"; end + end + m = labeled_module("M") do + prepend Module.new + + def self.included(base) + base.alias_method :base_m, :m + end + + def m + super + "M" + end + + def m2 + base_m + end + end + b = labeled_class("B", a) do + include m + end + assert_equal("AM", b.new.m2, bug16736) + end + def test_prepend_super_in_alias bug7842 = '[Bug #7842]' @@ -2071,6 +2695,22 @@ class TestModule < Test::Unit::TestCase assert_equal([:@@bar], m2.class_variables(false)) end + def test_class_variable_in_dup_class + a = Class.new do + @@a = 'A' + def a=(x) + @@a = x + end + def a + @@a + end + end + + b = a.dup + b.new.a = 'B' + assert_equal 'A', a.new.a, '[ruby-core:17019]' + end + Bug6891 = '[ruby-core:47241]' def test_extend_module_with_protected_method @@ -2147,6 +2787,9 @@ class TestModule < Test::Unit::TestCase class AttrTest class << self attr_accessor :cattr + def reset + self.cattr = nil + end end attr_accessor :iattr def ivar @@ -2156,7 +2799,7 @@ class TestModule < Test::Unit::TestCase def test_uninitialized_instance_variable a = AttrTest.new - assert_warning(/instance variable @ivar not initialized/) do + assert_warning('') do assert_nil(a.ivar) end a.instance_variable_set(:@ivar, 42) @@ -2165,7 +2808,7 @@ class TestModule < Test::Unit::TestCase end name = "@\u{5909 6570}" - assert_warning(/instance variable #{name} not initialized/) do + assert_warning('') do assert_nil(a.instance_eval(name)) end end @@ -2189,6 +2832,8 @@ class TestModule < Test::Unit::TestCase assert_warning '' do assert_equal(42, AttrTest.cattr) end + + AttrTest.reset end def test_uninitialized_attr_non_object @@ -2292,31 +2937,6 @@ class TestModule < Test::Unit::TestCase assert_raise(NoMethodError, bug8284) {Object.remove_const} end - def test_include_module_with_constants_does_not_invalidate_method_cache - assert_in_out_err([], <<-RUBY, %w(123 456 true), []) - A = 123 - - class Foo - def self.a - A - end - end - - module M - A = 456 - end - - puts Foo.a - starting = RubyVM.stat[:global_method_state] - - Foo.send(:include, M) - - ending = RubyVM.stat[:global_method_state] - puts Foo.a - puts starting == ending - RUBY - end - def test_return_value_of_define_method retvals = [] Class.new.class_eval do @@ -2353,9 +2973,67 @@ class TestModule < Test::Unit::TestCase } end + def test_prepend_constant_lookup + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + prepend m + end + sc = Class.new(c) + # Situation from [Bug #17887] + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + assert_equal(:c, c::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Same ancestors, built with include instead of prepend + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + end + sc = Class.new(c) do + include m + end + + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Situation from [Bug #17887], but with modules + m = Module.new do + const_set(:C, :m) + end + m2 = Module.new do + const_set(:C, :m2) + prepend m + end + c = Class.new do + include m2 + end + assert_equal(c.ancestors.take(3), [c, m, m2]) + assert_equal(:m, c.const_get(:C)) + assert_equal(:m, c::C) + end + def test_inspect_segfault bug_10282 = '[ruby-core:65214] [Bug #10282]' - assert_separately [], <<-RUBY + assert_separately [], "#{<<~"begin;"}\n#{<<~'end;'}" + bug_10282 = "#{bug_10282}" + begin; + line = __LINE__ + 2 module ShallowInspect def shallow_inspect "foo" @@ -2372,9 +3050,9 @@ class TestModule < Test::Unit::TestCase A.prepend InspectIsShallow - expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)() -:7>" - assert_equal expect, A.new.method(:inspect).inspect, "#{bug_10282}" - RUBY + expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)() -:#{line}>" + assert_equal expect, A.new.method(:inspect).inspect, bug_10282 + end; end def test_define_method_with_unbound_method @@ -2486,6 +3164,62 @@ class TestModule < Test::Unit::TestCase assert_equal :M2, CloneTestC2.new.foo, '[Bug #15877]' end + def test_clone_freeze + m = Module.new.freeze + assert_predicate m.clone, :frozen? + assert_not_predicate m.clone(freeze: false), :frozen? + end + + def test_module_name_in_singleton_method + s = Object.new.singleton_class + mod = s.const_set(:Foo, Module.new) + 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] + 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 + 100_000.times(&code) + CODE + end + private def assert_top_method_is_private(method) diff --git a/test/ruby/test_name_error.rb b/test/ruby/test_name_error.rb new file mode 100644 index 0000000000..f0402de4b9 --- /dev/null +++ b/test/ruby/test_name_error.rb @@ -0,0 +1,156 @@ +require 'test/unit' + +class TestNameError < Test::Unit::TestCase + def test_new_default + error = NameError.new + assert_equal("NameError", error.message) + end + + def test_new_message + error = NameError.new("Message") + assert_equal("Message", error.message) + end + + def test_new_name + error = NameError.new("Message") + assert_nil(error.name) + + error = NameError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_new_receiver + receiver = Object.new + + error = NameError.new + assert_raise(ArgumentError) {error.receiver} + assert_equal("NameError", error.message) + + error = NameError.new(receiver: receiver) + assert_equal(["NameError", receiver], + [error.message, error.receiver]) + + error = NameError.new("Message", :foo, receiver: receiver) + assert_equal(["Message", receiver, :foo], + [error.message, error.receiver, error.name]) + end + + PrettyObject = + Class.new(BasicObject) do + alias object_id __id__ + def pretty_inspect; "`obj'"; end + alias inspect pretty_inspect + end + + def test_info_const + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval("Object") + } + assert_equal(:Object, e.name) + + e = assert_raise(NameError) { + BasicObject::X + } + assert_same(BasicObject, e.receiver) + assert_equal(:X, e.name) + end + + def test_info_const_name + mod = Module.new do + def self.name + "ModuleName" + end + + def self.inspect + raise "<unusable info>" + end + end + assert_raise_with_message(NameError, /ModuleName/) {mod::DOES_NOT_EXIST} + end + + def test_info_method + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval {foo} + } + assert_equal(:foo, e.name) + assert_same(obj, e.receiver) + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + + def test_info_local_variables + obj = PrettyObject.new + def obj.test(a, b=nil, *c, &d) + e = a + 1.times {|f| g = foo; g} + e + end + + e = assert_raise(NameError) { + obj.test(3) + } + assert_equal(:foo, e.name) + assert_same(obj, e.receiver) + assert_equal(%i[a b c d e f g], e.local_variables.sort) + end + + def test_info_method_missing + obj = PrettyObject.new + def obj.method_missing(*) + super + end + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + + def test_info_parent_iseq_mark + assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;') + -> {require ARGV[0]}.call + end; + end + + def test_large_receiver_inspect + receiver = Class.new do + def self.inspect + 'A' * 120 + end + end + + error = assert_raise(NameError) do + receiver::FOO + end + assert_match(/\Auninitialized constant #{'A' * 120}::FOO$/, error.message) + end +end diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb new file mode 100644 index 0000000000..321b7ccab2 --- /dev/null +++ b/test/ruby/test_nomethod_error.rb @@ -0,0 +1,109 @@ +require 'test/unit' + +class TestNoMethodError < Test::Unit::TestCase + def test_new_default + error = NoMethodError.new + assert_equal("NoMethodError", error.message) + end + + def test_new_message + error = NoMethodError.new("Message") + assert_equal("Message", error.message) + end + + def test_new_name + error = NoMethodError.new("Message") + assert_nil(error.name) + + error = NoMethodError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_new_name_args + error = NoMethodError.new("Message", :foo) + assert_nil(error.args) + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_equal([:foo, [1, 2]], [error.name, error.args]) + end + + def test_new_name_args_priv + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_not_predicate(error, :private_call?) + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_equal([:foo, [1, 2], true], + [error.name, error.args, error.private_call?]) + end + + def test_new_receiver + receiver = Object.new + + error = NoMethodError.new + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new(receiver: receiver) + assert_equal(receiver, error.receiver) + + error = NoMethodError.new("Message") + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", receiver: receiver) + assert_equal(["Message", receiver], + [error.message, error.receiver]) + + error = NoMethodError.new("Message", :foo) + assert_raise(ArgumentError) {error.receiver} + + msg = "Message" + + error = NoMethodError.new("Message", :foo, receiver: receiver) + assert_match msg, error.message + assert_equal :foo, error.name + assert_equal receiver, error.receiver + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver) + assert_match msg, error.message + assert_equal :foo, error.name + assert_equal [1, 2], error.args + assert_equal receiver, error.receiver + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver) + assert_equal :foo, error.name + assert_equal [1, 2], error.args + assert_equal receiver, error.receiver + assert error.private_call?, "private_call? was false." + end + + def test_message_encoding + bug3237 = '[ruby-core:29948]' + str = "\u2600" + id = :"\u2604" + msg = "undefined method `#{id}' for \"#{str}\":String" + assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do + str.__send__(id) + end + end + + def test_to_s + pre = Module.new do + def name + BasicObject.new + end + end + mod = Module.new + mod.singleton_class.prepend(pre) + + err = assert_raise(NoMethodError) do + mod.this_method_does_not_exist + end + + assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s) + end +end diff --git a/test/ruby/test_notimp.rb b/test/ruby/test_notimp.rb deleted file mode 100644 index b069154cfc..0000000000 --- a/test/ruby/test_notimp.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: false -require 'test/unit' -require 'timeout' -require 'tmpdir' - -class TestNotImplement < Test::Unit::TestCase - def test_respond_to_fork - assert_include(Process.methods, :fork) - if /linux/ =~ RUBY_PLATFORM - assert_equal(true, Process.respond_to?(:fork)) - end - end - - def test_respond_to_lchmod - assert_include(File.methods, :lchmod) - if /linux/ =~ RUBY_PLATFORM - assert_equal(false, File.respond_to?(:lchmod)) - end - if /freebsd/ =~ RUBY_PLATFORM - assert_equal(true, File.respond_to?(:lchmod)) - end - end - - def test_call_fork - GC.start - pid = nil - ps = - case RUBY_PLATFORM - when /linux/ # assume Linux Distribution uses procps - proc {`ps -eLf #{pid}`} - when /freebsd/ - proc {`ps -lH #{pid}`} - when /darwin/ - proc {`ps -lM #{pid}`} - else - proc {`ps -l #{pid}`} - end - assert_nothing_raised(Timeout::Error, ps) do - EnvUtil.timeout(20) { - pid = fork {} - Process.wait pid - pid = nil - } - end - ensure - if pid - Process.kill(:KILL, pid) - Process.wait pid - end - end if Process.respond_to?(:fork) - - def test_call_lchmod - if File.respond_to?(:lchmod) - Dir.mktmpdir {|d| - f = "#{d}/f" - g = "#{d}/g" - File.open(f, "w") {} - File.symlink f, g - newmode = 0444 - File.lchmod newmode, "#{d}/g" - snew = File.lstat(g) - assert_equal(newmode, snew.mode & 0777) - } - end - end - - def test_method_inspect_fork - m = Process.method(:fork) - if Process.respond_to?(:fork) - assert_not_match(/not-implemented/, m.inspect) - else - assert_match(/not-implemented/, m.inspect) - end - end - - def test_method_inspect_lchmod - m = File.method(:lchmod) - if File.respond_to?(:lchmod) - assert_not_match(/not-implemented/, m.inspect) - else - assert_match(/not-implemented/, m.inspect) - end - end - -end diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 797fd209af..ab492743f6 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -200,6 +200,14 @@ class TestNumeric < Test::Unit::TestCase assert_nil(a <=> :foo) end + def test_float_round_ndigits + bug14635 = "[ruby-core:86323]" + f = 0.5 + 31.times do |i| + assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})") + end + end + def test_floor_ceil_round_truncate a = Class.new(Numeric) do def to_f; 1.5; end @@ -229,6 +237,15 @@ class TestNumeric < Test::Unit::TestCase assert_equal(-1, a.truncate) end + def test_floor_ceil_ndigits + bug17183 = "[ruby-core:100090]" + f = 291.4 + 31.times do |i| + assert_equal(291.4, f.floor(i+1), bug17183) + assert_equal(291.4, f.ceil(i+1), bug17183) + end + end + def assert_step(expected, (from, *args), inf: false) kw = args.last.is_a?(Hash) ? args.pop : {} enum = from.step(*args, **kw) @@ -267,12 +284,7 @@ class TestNumeric < Test::Unit::TestCase assert_raise(ArgumentError) { 1.step(10, "1") { } } assert_raise(ArgumentError) { 1.step(10, "1").size } assert_raise(TypeError) { 1.step(10, nil) { } } - assert_nothing_raised { 1.step(10, 0).size } assert_nothing_raised { 1.step(10, nil).size } - assert_nothing_raised { 1.step(by: 0, to: nil) } - assert_nothing_raised { 1.step(by: 0, to: nil).size } - assert_nothing_raised { 1.step(by: 0) } - assert_nothing_raised { 1.step(by: 0).size } assert_nothing_raised { 1.step(by: nil) } assert_nothing_raised { 1.step(by: nil).size } @@ -292,13 +304,29 @@ class TestNumeric < Test::Unit::TestCase assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11) {} } assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11).size } - - e = assert_warn(/The last argument is used as the keyword parameter/) { - 1.step(10, {by: "1"}) - } - assert_warn('') { - assert_raise(ArgumentError) {e.size} - } + feature15573 = "[ruby-core:91324] [Feature #15573]" + assert_raise(ArgumentError, feature15573) { 1.step(10, 0) } + assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) } + assert_raise(ArgumentError, feature15573) { 1.step(10, 0) { break } } + assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) { break } } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: -Float::INFINITY) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: 42.5) } + assert_raise(ArgumentError, feature15573) { 4.2.step(by: 0.0) } + assert_raise(ArgumentError, feature15573) { 4.2.step(by: -0.0) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 44) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 0) } + assert_raise(ArgumentError, feature15573) { 42.step(by: -0.0, to: 44) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0.0) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: bignum+1) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: 0) } + + 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) @@ -325,8 +353,6 @@ class TestNumeric < Test::Unit::TestCase assert_step [], [2, 1, 3] assert_step [], [-2, -1, -3] - assert_step [3, 3, 3, 3], [3, by: 0], inf: true - assert_step [3, 3, 3, 3], [3, by: 0, to: 42], inf: true assert_step [10], [10, 1, -bignum] assert_step [], [1, 0, Float::INFINITY] @@ -336,19 +362,6 @@ class TestNumeric < Test::Unit::TestCase assert_step [10, 11, 12, 13], [10], inf: true assert_step [10, 9, 8, 7], [10, by: -1], inf: true assert_step [10, 9, 8, 7], [10, by: -1, to: nil], inf: true - - assert_step [42, 42, 42, 42], [42, by: 0, to: -Float::INFINITY], inf: true - assert_step [42, 42, 42, 42], [42, by: 0, to: 42.5], inf: true - assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: 0.0], inf: true - assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: -0.0], inf: true - assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 44], inf: true - assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 0], inf: true - assert_step [42.0, 42.0, 42.0, 42.0], [42, by: -0.0, to: 44], inf: true - - assert_step [bignum]*4, [bignum, by: 0], inf: true - assert_step [bignum]*4, [bignum, by: 0.0], inf: true - assert_step [bignum]*4, [bignum, by: 0, to: bignum+1], inf: true - assert_step [bignum]*4, [bignum, by: 0, to: 0], inf: true end def test_step_bug15537 @@ -391,6 +404,18 @@ class TestNumeric < Test::Unit::TestCase end; end + def test_remainder_infinity + assert_equal(4, 4.remainder(Float::INFINITY)) + assert_equal(4, 4.remainder(-Float::INFINITY)) + assert_equal(-4, -4.remainder(Float::INFINITY)) + assert_equal(-4, -4.remainder(-Float::INFINITY)) + + assert_equal(4.2, 4.2.remainder(Float::INFINITY)) + assert_equal(4.2, 4.2.remainder(-Float::INFINITY)) + assert_equal(-4.2, -4.2.remainder(Float::INFINITY)) + assert_equal(-4.2, -4.2.remainder(-Float::INFINITY)) + end + def test_comparison_comparable bug12864 = '[ruby-core:77713] [Bug #12864]' @@ -438,6 +463,26 @@ class TestNumeric < Test::Unit::TestCase assert_equal(12, 12.pow(1, 10000000001), '[Bug #14259]') assert_equal(12, 12.pow(1, 10000000002), '[Bug #14259]') assert_equal(17298641040, 12.pow(72387894339363242, 243682743764), '[Bug #14259]') + + integers = [-2, -1, 0, 1, 2, 3, 6, 1234567890123456789] + integers.each do |i| + assert_equal(0, i.pow(0, 1), '[Bug #17257]') + assert_equal(1, i.pow(0, 2)) + assert_equal(1, i.pow(0, 3)) + assert_equal(1, i.pow(0, 6)) + assert_equal(1, i.pow(0, 1234567890123456789)) + + assert_equal(0, i.pow(0, -1)) + assert_equal(-1, i.pow(0, -2)) + assert_equal(-2, i.pow(0, -3)) + assert_equal(-5, i.pow(0, -6)) + assert_equal(-1234567890123456788, i.pow(0, -1234567890123456789)) + end + + assert_equal(0, 0.pow(2, 1)) + assert_equal(0, 0.pow(3, 1)) + assert_equal(0, 2.pow(3, 1)) + assert_equal(0, -2.pow(3, 1)) end end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index add5b9fb15..83208bbcdb 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -5,7 +5,6 @@ require 'test/unit' class TestObject < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -47,15 +46,27 @@ class TestObject < Test::Unit::TestCase a = Object.new def a.b; 2 end + c = a.clone + assert_equal(false, c.frozen?) + assert_equal(false, a.frozen?) + assert_equal(2, c.b) + + c = a.clone(freeze: true) + assert_equal(true, c.frozen?) + assert_equal(false, a.frozen?) + assert_equal(2, c.b) + a.freeze c = a.clone assert_equal(true, c.frozen?) + assert_equal(true, a.frozen?) assert_equal(2, c.b) assert_raise(ArgumentError) {a.clone(freeze: [])} d = a.clone(freeze: false) def d.e; 3; end assert_equal(false, d.frozen?) + assert_equal(true, a.frozen?) assert_equal(2, d.b) assert_equal(3, d.e) @@ -75,6 +86,30 @@ class TestObject < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /\u{1f4a9}/) do Object.new.clone(freeze: x) end + + c = Class.new do + attr_reader :f + end + o = c.new + def o.initialize_clone(_, freeze: true) + @f = freeze + super + end + clone = o.clone + assert_kind_of c, clone + assert_equal true, clone.f + clone = o.clone(freeze: false) + assert_kind_of c, clone + assert_equal false, clone.f + + class << o + remove_method(:initialize_clone) + end + def o.initialize_clone(_) + super + end + assert_kind_of c, o.clone + assert_raise(ArgumentError) { o.clone(freeze: false) } end def test_init_dupclone @@ -324,6 +359,7 @@ class TestObject < Test::Unit::TestCase o = Object.new def o.to_s; 1; end assert_raise(TypeError) { String(o) } + o.singleton_class.remove_method(:to_s) def o.to_s; "o"; end assert_equal("o", String(o)) def o.to_str; "O"; end @@ -336,6 +372,7 @@ class TestObject < Test::Unit::TestCase o = Object.new def o.to_a; 1; end assert_raise(TypeError) { Array(o) } + o.singleton_class.remove_method(:to_a) def o.to_a; [1]; end assert_equal([1], Array(o)) def o.to_ary; [2]; end @@ -353,6 +390,7 @@ class TestObject < Test::Unit::TestCase o = Object.new def o.to_hash; {a: 1, b: 2}; end assert_equal({a: 1, b: 2}, Hash(o)) + o.singleton_class.remove_method(:to_hash) def o.to_hash; 9; end assert_raise(TypeError) { Hash(o) } end @@ -361,6 +399,7 @@ class TestObject < Test::Unit::TestCase o = Object.new def o.to_i; nil; end assert_raise(TypeError) { Integer(o) } + o.singleton_class.remove_method(:to_i) def o.to_i; 42; end assert_equal(42, Integer(o)) def o.respond_to?(*) false; end @@ -595,7 +634,7 @@ class TestObject < Test::Unit::TestCase called = [] p.singleton_class.class_eval do - define_method(:respond_to?) do |a| + define_method(:respond_to?) do |a, priv = false| called << [:respond_to?, a] false end @@ -738,7 +777,7 @@ class TestObject < Test::Unit::TestCase e = assert_raise(NoMethodError) { o.never_defined_test_no_superclass_method } - assert_equal(m1, e.message, bug2312) + assert_equal(m1.lines.first, e.message.lines.first, bug2312) end def test_superclass_method @@ -954,13 +993,4 @@ class TestObject < Test::Unit::TestCase end EOS end - - def test_matcher - assert_warning(/deprecated Object#=~ is called on Object/) do - assert_equal(Object.new =~ 42, nil) - end - assert_warning(/deprecated Object#=~ is called on Array/) do - assert_equal([] =~ 42, nil) - end - end end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index c352b75b70..e0f9eecd11 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -35,6 +35,36 @@ End deftest_id2ref(false) deftest_id2ref(nil) + def test_id2ref_liveness + assert_normal_exit <<-EOS + ids = [] + 10.times{ + 1_000.times{ + ids << 'hello'.object_id + } + objs = ids.map{|id| + begin + ObjectSpace._id2ref(id) + rescue RangeError + nil + end + } + GC.start + objs.each{|e| e.inspect} + } + EOS + end + + def test_id2ref_invalid_argument + msg = /no implicit conversion/ + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(nil)} + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(false)} + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(true)} + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(:a)} + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref("0")} + assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)} + end + def test_count_objects h = {} ObjectSpace.count_objects(h) @@ -131,6 +161,40 @@ End END end + def test_exception_in_finalizer + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], /finalizing \(RuntimeError\)/) + begin; + ObjectSpace.define_finalizer(Object.new) {raise "finalizing"} + end; + end + + def test_finalizer_thread_raise + GC.disable + fzer = proc do |id| + sleep 0.2 + end + 2.times do + o = Object.new + ObjectSpace.define_finalizer(o, fzer) + end + + my_error = Class.new(RuntimeError) + begin + main_th = Thread.current + Thread.new do + sleep 0.1 + main_th.raise(my_error) + end + GC.start + puts "After GC" + sleep(10) + assert(false) + rescue my_error + end + ensure + GC.enable + end + def test_each_object klass = Class.new new_obj = klass.new @@ -155,7 +219,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') @@ -203,4 +267,11 @@ End assert_kind_of(meta, sclass) assert_include(ObjectSpace.each_object(meta).to_a, sclass) end + + def test_each_object_with_allocation + assert_normal_exit(<<-End) + list = [] + ObjectSpace.each_object { |o| list << Object.new } + End + end end diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index b42314b765..43795d150c 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -150,6 +150,64 @@ class TestRubyOptimization < Test::Unit::TestCase assert_redefine_method('String', '-@', 'assert_nil(-"foo")') end + def test_array_min + assert_equal 1, [1, 2, 4].min + assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)') + assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)') + end + + def test_array_max + assert_equal 4, [1, 2, 4].max + assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)') + assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)') + end + + def test_trace_optimized_methods + bug14870 = "[ruby-core:87638]" + expected = [:-@, :max, :min, :+, :-, :*, :/, :%, :==, :<, :<=, :>, :>=, :<<, + :&, :|, :[], :[]=, :length, :empty?, :nil?, :succ, :!, :=~] + [:c_call, :c_return].each do |type| + methods = [] + tp = TracePoint.new(type) { |tp| methods << tp.method_id } + tp.enable do + x = "a"; x = -x + [1].max + [1].min + x = 42 + 2 + x = 42 - 2 + x = 42 * 2 + x = 42 / 2 + x = 42 % 2 + y = x == 42 + y = x < 42 + y = x <= 42 + y = x > 42 + y = x >= 42 + x = x << 1 + x = x & 1 + x = x | 1 + x = []; x[1] + x[1] = 2 + x.length + x.empty? + x.nil? + x = 1; x.succ + !x + x = 'a'; x =~ /a/ + x = y + end + assert_equal(expected, methods, bug14870) + end + + methods = [] + tp = TracePoint.new(:c_call, :c_return) { |tp| methods << tp.method_id } + tp.enable do + x = 1 + x != 42 + end + assert_equal([:!=, :==, :==, :!=], methods, bug14870) + end + def test_string_freeze_saves_memory n = 16384 data = '.'.freeze @@ -452,7 +510,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 RubyVM::MJIT.enabled? + skip '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;"}") @@ -701,16 +759,16 @@ class TestRubyOptimization < Test::Unit::TestCase def test_block_parameter_should_not_create_objects assert_separately [], <<-END - # def foo &b end h1 = {}; h2 = {} - ObjectSpace.count_objects(h1) # reharsal + ObjectSpace.count_objects(h1) # rehearsal + GC.start; GC.disable # to disable GC while foo{} ObjectSpace.count_objects(h1) foo{} ObjectSpace.count_objects(h2) - assert_equal 0, h2[:TOTAL] - h1[:TOTAL] + assert_equal 0, h2[:T_DATA] - h1[:T_DATA] # Proc is T_DATA END end @@ -845,4 +903,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 60edd00186..9738f82b7e 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -638,6 +638,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")) @@ -764,7 +772,7 @@ EXPECTED $VERBOSE = true - _, err = capture_io do + _, err = capture_output do assert_equal "\000", [0].pack("*U") end @@ -783,7 +791,7 @@ EXPECTED $VERBOSE = true - _, err = capture_io do + _, err = capture_output do assert_equal [0], "\000".unpack("*U") end @@ -869,4 +877,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 cb379ebe18..2841e20f6d 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -6,7 +6,6 @@ require 'stringio' class TestParse < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -399,7 +398,6 @@ class TestParse < Test::Unit::TestCase def test_arg2 o = Object.new - assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end @@ -411,6 +409,7 @@ class TestParse < Test::Unit::TestCase assert_equal(-42100, o.foo(1) {|x| -x }) assert_raise(ArgumentError) { o.foo() } + o = Object.new assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,z,&b); b.call(a*1000+z*100); end @@ -420,6 +419,7 @@ class TestParse < Test::Unit::TestCase assert_equal(-42100, o.foo(1) {|x| -x } ) assert_raise(ArgumentError) { o.foo() } + o = Object.new assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end @@ -562,6 +562,21 @@ 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') + + e = assert_syntax_error('"\c\u0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~'"\n", e.message.lines.last) + e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~'"\n", e.message.lines.last) + + e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) + e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax') + 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('"\M-\U0000"', 'Invalid escape character syntax') + assert_equal(' ^~~~~'"\n", e.message.lines.last) end def test_question @@ -599,6 +614,9 @@ class TestParse < Test::Unit::TestCase assert_syntax_error('%s', /unterminated quoted string/) assert_syntax_error('%ss', /unknown type/) assert_syntax_error('%z()', /unknown type/) + assert_syntax_error("%\u3042", /unknown type/) + assert_syntax_error("%q\u3042", /unknown type/) + assert_syntax_error("%", /unterminated quoted string/) end def test_symbol @@ -666,6 +684,15 @@ FOO def test_magic_comment x = nil + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding: utf-8 +x = __ENCODING__ + END + end + assert_equal(Encoding.find("UTF-8"), x) + assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 # coding = utf-8 @@ -680,6 +707,14 @@ x = __ENCODING__ x = __ENCODING__ END end + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 +# xxxx : coding sjis +x = __ENCODING__ + END + end + assert_equal(__ENCODING__, x) end def test_utf8_bom @@ -722,13 +757,13 @@ x = __ENCODING__ end def test_float - assert_equal(1.0/0, eval("1e10000")) + assert_predicate(assert_warning(/out of range/) {eval("1e10000")}, :infinite?) assert_syntax_error('1_E', /trailing `_'/) assert_syntax_error('1E1E1', /unexpected constant/) end def test_global_variable - assert_equal(nil, eval('$-x')) + assert_equal(nil, assert_warning(/not initialized/) {eval('$-x')}) assert_equal(nil, eval('alias $preserve_last_match $&')) assert_equal(nil, eval('alias $& $test_parse_foobarbazqux')) $test_parse_foobarbazqux = nil @@ -821,13 +856,13 @@ x = __ENCODING__ end def test_assign_in_conditional - assert_nothing_raised do + assert_warning(/`= literal' in conditional/) do eval <<-END, nil, __FILE__, __LINE__+1 (x, y = 1, 2) ? 1 : 2 END end - assert_nothing_raised do + assert_warning(/`= literal' in conditional/) do eval <<-END, nil, __FILE__, __LINE__+1 if @x = true 1 @@ -839,13 +874,13 @@ x = __ENCODING__ end def test_literal_in_conditional - assert_nothing_raised do + assert_warning(/string literal in condition/) do eval <<-END, nil, __FILE__, __LINE__+1 "foo" ? 1 : 2 END end - assert_nothing_raised do + assert_warning(/regex literal in condition/) do x = "bar" eval <<-END, nil, __FILE__, __LINE__+1 /foo#{x}baz/ ? 1 : 2 @@ -858,13 +893,13 @@ x = __ENCODING__ END end - assert_nothing_raised do + assert_warning(/string literal in flip-flop/) do eval <<-END, nil, __FILE__, __LINE__+1 ("foo".."bar") ? 1 : 2 END end - assert_nothing_raised do + assert_warning(/literal in condition/) do x = "bar" eval <<-END, nil, __FILE__, __LINE__+1 :"foo#{"x"}baz" ? 1 : 2 @@ -895,6 +930,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")} @@ -1009,7 +1048,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 @@ -1045,6 +1084,32 @@ x = __ENCODING__ end; end + def test_heredoc_interpolation + var = 1 + + v1 = <<~HEREDOC + something + #{"/#{var}"} + HEREDOC + + v2 = <<~HEREDOC + something + #{_other = "/#{var}"} + HEREDOC + + v3 = <<~HEREDOC + something + #{("/#{var}")} + HEREDOC + + assert_equal "something\n/1\n", v1 + assert_equal "something\n/1\n", v2 + assert_equal "something\n/1\n", v3 + assert_equal v1, v2 + assert_equal v2, v3 + assert_equal v1, v3 + end + def test_unexpected_token_error assert_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/) end @@ -1101,6 +1166,10 @@ x = __ENCODING__ assert_syntax_error("def m\n\C-z""end", /unexpected/) end + def test_unexpected_eof + assert_syntax_error('unless', /^ \^\Z/) + end + def test_location_of_invalid_token assert_syntax_error('class xxx end', /^ \^~~\Z/) end @@ -1166,6 +1235,130 @@ x = __ENCODING__ assert_valid_syntax('let () { m(a) do; end }') end + def test_void_value_in_rhs + w = "void value expression" + ["x = return 1", "x = return, 1", "x = 1, return", "x, y = return"].each do |code| + ex = assert_syntax_error(code, w) + assert_equal(1, ex.message.scan(w).size, ->{"same #{w.inspect} warning should be just once\n#{w.message}"}) + end + end + + def eval_separately(code) + Class.new.class_eval(code) + end + + def assert_raise_separately(error, message, code) + assert_raise_with_message(error, message) do + eval_separately(code) + end + end + + def assert_ractor_shareable(obj) + assert Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} to be ractor shareable"} + end + + def assert_not_ractor_shareable(obj) + assert !Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} not to be ractor shareable"} + end + + def test_shareable_constant_value_invalid + assert_warning(/invalid value/) do + assert_valid_syntax("# shareable_constant_value: invalid-option", verbose: true) + end + end + + def test_shareable_constant_value_ignored + assert_warning(/ignored/) do + assert_valid_syntax("nil # shareable_constant_value: true", verbose: true) + end + end + + def test_shareable_constant_value_simple + obj = [['unsharable_value']] + a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + A = [[1]] + # shareable_constant_value: none + B = [[2]] + # shareable_constant_value: literal + C = [["shareable", "constant#{nil}"]] + D = A + + [A, B, C] + end; + assert_ractor_shareable(a) + assert_not_ractor_shareable(b) + assert_ractor_shareable(c) + assert_equal([1], a[0]) + assert_ractor_shareable(a[0]) + + a, obj = eval_separately(<<~'end;') + # shareable_constant_value: experimental_copy + obj = [["unshareable"]] + A = obj + [A, obj] + end; + + assert_ractor_shareable(a) + assert_not_ractor_shareable(obj) + assert_equal obj, a + assert !obj.equal?(a) + end + + def test_shareable_constant_value_nested + a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: none + class X + # shareable_constant_value: experimental_everything + var = [[1]] + A = var + end + B = [] + [X::A, B] + end; + assert_ractor_shareable(a) + assert_not_ractor_shareable(b) + assert_equal([1], a[0]) + assert_ractor_shareable(a[0]) + end + + def test_shareable_constant_value_unshareable_literal + assert_raise_separately(Ractor::IsolationError, /unshareable/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + C = ["Not " + "shareable"] + end; + end + + def test_shareable_constant_value_nonliteral + assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + var = [:not_frozen] + C = var + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + D = begin [] end + end; + end + + def test_shareable_constant_value_unfrozen + assert_raise_separately(Ractor::Error, /does not freeze object correctly/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + o = Object.new + def o.freeze; self; end + C = [o] + end; + 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 5308ec3281..4c203fb4f9 100644 --- a/test/ruby/test_pattern_matching.rb +++ b/test/ruby/test_pattern_matching.rb @@ -1,9 +1,28 @@ # frozen_string_literal: true require 'test/unit' -verbose, $VERBOSE = $VERBOSE, nil # suppress "warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!" +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) + "" + end + end + + def setup + if defined?(DidYouMean) + @original_formatter = DidYouMean.formatter + DidYouMean.formatter = NullFormatter.new + end + end + + def teardown + if defined?(DidYouMean) + DidYouMean.formatter = @original_formatter + end + end + class C class << self attr_accessor :keys @@ -92,7 +111,8 @@ class TestPatternMatching < Test::Unit::TestCase end assert_block do - verbose, $VERBOSE = $VERBOSE, nil # suppress "warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!" + # suppress "warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!" + experimental, Warning[:experimental] = Warning[:experimental], false eval(%q{ case true in a @@ -100,7 +120,7 @@ class TestPatternMatching < Test::Unit::TestCase end }) ensure - $VERBOSE = verbose + Warning[:experimental] = experimental end assert_block do @@ -263,6 +283,7 @@ class TestPatternMatching < Test::Unit::TestCase assert_block do case 0 in a + assert_equal(0, a) true in a flunk @@ -270,7 +291,7 @@ class TestPatternMatching < Test::Unit::TestCase end assert_syntax_error(%q{ - 0 in [a, a] + 0 => [a, a] }, /duplicated variable name/) end @@ -398,6 +419,53 @@ END a == 0 end end + + assert_block do + @a = /a/ + case 'abc' + in ^@a + true + end + end + + assert_block do + @@TestPatternMatching = /a/ + case 'abc' + in ^@@TestPatternMatching + true + end + end + + assert_block do + $TestPatternMatching = /a/ + case 'abc' + in ^$TestPatternMatching + true + end + end + end + + def test_pin_operator_expr_pattern + assert_block do + case 'abc' + in ^(/a/) + true + end + end + + assert_block do + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end + end + + assert_block do + case 0 + in ^(0+0) + true + end + end end def test_array_pattern @@ -470,6 +538,7 @@ END [[0], C.new([0])].all? do |i| case i in *a, 0, 1 + raise a # suppress "unused variable: a" warning else true end @@ -636,6 +705,7 @@ END assert_block do case [] in [0, *a] + raise a # suppress "unused variable: a" warning else true end @@ -651,6 +721,7 @@ END assert_block do case [0] in [0, *a, 1] + raise a # suppress "unused variable: a" warning else true end @@ -695,6 +766,7 @@ END assert_block do case [] in [0, *a] + raise a # suppress "unused variable: a" warning else true end @@ -730,6 +802,74 @@ END end end + def test_find_pattern + [0, 1, 2] => [*, 1 => a, *] + assert_equal(1, a) + + [0, 1, 2] => [*a, 1 => b, *c] + assert_equal([0], a) + assert_equal(1, b) + assert_equal([2], c) + + assert_block do + case [0, 1, 2] + in [*, 9, *] + false + else + true + end + end + + assert_block do + case [0, 1, 2] + in [*, Integer, String, *] + false + else + true + end + end + + [0, 1, 2] => [*a, 1 => b, 2 => c, *d] + assert_equal([0], a) + assert_equal(1, b) + assert_equal(2, c) + assert_equal([], d) + + case [0, 1, 2] + in *, 1 => a, *; + assert_equal(1, a) + end + + assert_block do + case [0, 1, 2] + in String(*, 1, *) + false + in Array(*, 1, *) + true + end + end + + assert_block do + case [0, 1, 2] + in String[*, 1, *] + false + in Array[*, 1, *] + true + end + end + + # https://bugs.ruby-lang.org/issues/17534 + assert_block do + case [0, 1, 2] + in x + x = x # avoid a warning "assigned but unused variable - x" + true + in [*, 2, *] + false + end + end + end + def test_hash_pattern assert_block do [{}, C.new({})].all? do |i| @@ -791,6 +931,7 @@ END [{}, C.new({})].all? do |i| case i in a: + raise a # suppress "unused variable: a" warning else true end @@ -873,6 +1014,8 @@ END [{}, C.new({})].all? do |i| case i in a:, **b + raise a # suppress "unused variable: a" warning + raise b # suppress "unused variable: b" warning else true end @@ -920,6 +1063,7 @@ END [{a: 0}, C.new({a: 0})].all? do |i| case i in a:, **nil + assert_equal(0, a) true end end @@ -929,6 +1073,7 @@ END [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| case i in a:, **nil + assert_equal(0, a) else true end @@ -1031,6 +1176,34 @@ END end end + assert_block do + case {a: 0, b: 1} + in {a: 1,} + false + in {a:,} + _a = a + true + end + end + + assert_block do + case {a: 0} + in {a: 1 + } + false + in {a: + 2} + false + in a: {b:}, c: + _b = b + p c + in {a: + } + _a = a + true + end + end + assert_syntax_error(%q{ case _ in "a-b": @@ -1066,6 +1239,10 @@ END end end + def test_nomatchingpatternerror + assert_equal(StandardError, NoMatchingPatternError.superclass) + end + def test_invalid_syntax assert_syntax_error(%q{ case 0 @@ -1129,6 +1306,7 @@ END assert_block do case C.new({a: 0, b: 0, c: 0}) in {a: 0, b:} + assert_equal(0, b) C.keys == [:a, :b] end end @@ -1136,6 +1314,7 @@ END assert_block do case C.new({a: 0, b: 0, c: 0}) in {a: 0, b:, **} + assert_equal(0, b) C.keys == [:a, :b] end end @@ -1143,6 +1322,8 @@ END assert_block do case C.new({a: 0, b: 0, c: 0}) in {a: 0, b:, **r} + assert_equal(0, b) + assert_equal({c: 0}, r) C.keys == nil end end @@ -1157,6 +1338,7 @@ END assert_block do case C.new({a: 0, b: 0, c: 0}) in {**r} + assert_equal({a: 0, b: 0, c: 0}, r) C.keys == nil end end @@ -1164,6 +1346,94 @@ END ################################################################ + class CDeconstructCache + def initialize(v) + @v = v + end + + def deconstruct + @v.shift + end + end + + def test_deconstruct_cache + assert_block do + case CDeconstructCache.new([[0]]) + in [1] + in [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0, 1]]) + in [1,] + in [0,] + true + end + end + + assert_block do + case CDeconstructCache.new([[[0]]]) + in [[1]] + in [[*a]] + a == [0] + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [x] if x > 0 + in [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [] + in [1] | [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [1] => _ + in [0] => _ + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in C[0] + in CDeconstructCache[0] + true + end + end + + assert_block do + case [CDeconstructCache.new([[0], [1]])] + in [[1]] + false + in [[1]] + true + end + end + + assert_block do + case CDeconstructCache.new([[0, :a, 1]]) + in [*, String => x, *] + false + in [*, Symbol => x, *] + x == :a + end + end + end + + ################################################################ + class TestPatternMatchingRefinements < Test::Unit::TestCase class C1 def deconstruct @@ -1249,6 +1519,8 @@ END s = Struct.new(:a, :b, keyword_init: true) case s[a: 0, b: 1] in a:, c: + raise a # suppress "unused variable: a" warning + raise c # suppress "unused variable: c" warning flunk in a:, b:, c: flunk @@ -1260,20 +1532,178 @@ END ################################################################ - def test_modifier_in - 1 in a + def test_one_line + 1 => a assert_equal 1, a assert_raise(NoMatchingPatternError) do - {a: 1} in {a: 0} + {a: 1} => {a: 0} + end + + [1, 2] => a, b + assert_equal 1, a + assert_equal 2, b + + {a: 1} => a: + assert_equal 1, a + + assert_equal true, (1 in 1) + 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_experimental_warning + assert_experimental_warning("case [0]; in [*, 0, *]; end") + end + + def test_bug18990 + {a: 0} => a: + assert_equal 0, a + {a: 0} => a: + assert_equal 0, a + + {a: 0} in a: + assert_equal 0, a + {a: 0} in a: + assert_equal 0, a + end + + ################################################################ + + def test_single_pattern_error_value_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 1 === 0 does not return true") do + 0 => 1 + end + end + + def test_single_pattern_error_array_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] length mismatch (given 1, expected 0)") do + [0] => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [_, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [0, 1] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [*, 0, 1] + end + end + + def test_single_pattern_error_find_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, 1, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, {a:}, *] + raise a # suppress "unused variable: a" warning + end + end + + def test_single_pattern_error_hash_pattern + assert_raise_with_message(NoMatchingPatternError, "{}: Array === {} does not return true") do + {} => Array[a:] + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct_keys") do + 0 => {a:} + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{:a=>0}: key not found: :aa") do + {a: 0} => {aa:} + raise aa # suppress "unused variable: aa" warning + rescue NoMatchingPatternKeyError => e + assert_equal({a: 0}, e.matchee) + assert_equal(:aa, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{:a=>{:b=>0}}: key not found: :bb") do + {a: {b: 0}} => {a: {bb:}} + raise bb # suppress "unused variable: bb" warning + rescue NoMatchingPatternKeyError => e + assert_equal({b: 0}, e.matchee) + assert_equal(:bb, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternError, "{:a=>0}: 1 === 0 does not return true") do + {a: 0} => {a: 1} + end + + assert_raise_with_message(NoMatchingPatternError, "{:a=>0}: {:a=>0} is not empty") do + {a: 0} => {} + end + + assert_raise_with_message(NoMatchingPatternError, "[{:a=>0}]: rest of {:a=>0} is not empty") do + [{a: 0}] => [{**nil}] + end + end + + def test_single_pattern_error_as_pattern + assert_raise_with_message(NoMatchingPatternError, "[0]: 1 === 0 does not return true") do + case [0] + in [1] => _ + end + end + end + + def test_single_pattern_error_alternative_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 2 === 0 does not return true") do + 0 => 1 | 2 + end + end + + def test_single_pattern_error_guard_clause + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ if false + end + end + + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ unless true + end end - assert_syntax_error("if {} in {a:}; end", /void value expression/) - assert_syntax_error(%q{ - 1 in a, b - }, /unexpected/, '[ruby-core:95098]') - assert_syntax_error(%q{ - 1 in a: - }, /unexpected/, '[ruby-core:95098]') end end END_of_GUARD -$VERBOSE = verbose +Warning[:experimental] = experimental diff --git a/test/ruby/test_primitive.rb b/test/ruby/test_primitive.rb index 19af44ad32..f1db934000 100644 --- a/test/ruby/test_primitive.rb +++ b/test/ruby/test_primitive.rb @@ -26,24 +26,31 @@ class TestRubyPrimitive < Test::Unit::TestCase assert_equal 4, c end - C = 1 - class A - Const = 1 - class B - Const = 2 - class C - Const = 3 - def const - Const + C_Setup = -> do + remove_const :C if defined? ::TestRubyPrimitive::C + remove_const :A if defined? ::TestRubyPrimitive::A + + C = 1 + class A + Const = 1 + class B + Const = 2 + class C + Const = 3 + def const + Const + end end end end + (1..2).map { + A::B::C::Const + } end - (1..2).map { - A::B::C::Const - } def test_constant + C_Setup.call + assert_equal 1, C assert_equal 1, C assert_equal 1, A::Const @@ -145,42 +152,60 @@ class TestRubyPrimitive < Test::Unit::TestCase assert_equal 7, ($test_ruby_primitive_gvar = 7) end - class A7 - @@c = 1 - def m - @@c += 1 + A7_Setup = -> do + remove_const :A7 if defined? TestRubyPrimitive::A7 + + class A7 + @@c = 1 + def m + @@c += 1 + end end end def test_cvar_from_instance_method + A7_Setup.call + assert_equal 2, A7.new.m assert_equal 3, A7.new.m assert_equal 4, A7.new.m end - class A8 - @@c = 1 - class << self - def m - @@c += 1 + A8_Setup = -> do + remove_const :A8 if defined? TestRubyPrimitive::A8 + + class A8 + @@c = 1 + class << self + def m + @@c += 1 + end end end end def test_cvar_from_singleton_method + A8_Setup.call + assert_equal 2, A8.m assert_equal 3, A8.m assert_equal 4, A8.m end - class A9 - @@c = 1 - def self.m - @@c += 1 + A9_Setup = -> do + remove_const :A8 if defined? TestRubyPrimitive::A8 + + class A9 + @@c = 1 + def self.m + @@c += 1 + end end end def test_cvar_from_singleton_method2 + A9_Setup.call + assert_equal 2, A9.m assert_equal 3, A9.m assert_equal 4, A9.m @@ -199,6 +224,9 @@ class TestRubyPrimitive < Test::Unit::TestCase @iv += 2 assert_equal 4, @iv + # init @@cv + @@cv = nil + @@cv ||= 1 assert_equal 1, @@cv @@cv &&= 2 diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index ea3fe823f0..51872e49be 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -4,7 +4,6 @@ require 'test/unit' class TestProc < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -53,16 +52,14 @@ class TestProc < Test::Unit::TestCase assert_equal(5, x) end - def assert_arity(n) + def assert_arity(n, &block) meta = class << self; self; end - b = assert_warn(/Capturing the given block using Proc\.new is deprecated/) do - Proc.new - end + b = Proc.new(&block) meta.class_eval { - remove_method(:foo) if method_defined?(:foo) - define_method(:foo, b) + remove_method(:foo_arity) if method_defined?(:foo_arity) + define_method(:foo_arity, b) } - assert_equal(n, method(:foo).arity) + assert_equal(n, method(:foo_arity).arity) end def test_arity @@ -139,6 +136,14 @@ class TestProc < Test::Unit::TestCase lambda { x } end + def m_nest0(&block) + block + end + + def m_nest(&block) + [m_nest0(&block), m_nest0(&block)] + end + def test_eq a = m(1) b = m(2) @@ -150,6 +155,17 @@ class TestProc < Test::Unit::TestCase a = lambda {|x| lambda {} }.call(1) b = lambda {} assert_not_equal(a, b, "[ruby-dev:22601]") + + assert_equal(*m_nest{}, "[ruby-core:84583] Feature #14627") + end + + def test_hash + def self.capture(&block) + block + end + + procs = Array.new(1000){capture{:foo }} + assert_operator(procs.map(&:hash).uniq.size, :>=, 500) end def test_block_par @@ -263,7 +279,7 @@ class TestProc < Test::Unit::TestCase def test_curry_given_blocks b = lambda {|x, y, &blk| blk.call(x + y) }.curry - b = b.call(2) { raise } + b = assert_warning(/given block not used/) {b.call(2) { raise }} b = b.call(3) {|x| x + 4 } assert_equal(9, b) end @@ -273,7 +289,7 @@ class TestProc < Test::Unit::TestCase assert_equal(false, l.lambda?) assert_equal(false, l.curry.lambda?, '[ruby-core:24127]') assert_equal(false, proc(&l).lambda?) - assert_equal(false, lambda(&l).lambda?) + assert_equal(false, assert_deprecated_warning {lambda(&l)}.lambda?) assert_equal(false, Proc.new(&l).lambda?) l = lambda {} assert_equal(true, l.lambda?) @@ -283,6 +299,49 @@ class TestProc < Test::Unit::TestCase assert_equal(true, Proc.new(&l).lambda?) end + def self.helper_test_warn_lamda_with_passed_block &b + lambda(&b) + end + + def self.def_lambda_warning name, warn + define_method(name, proc do + prev = Warning[:deprecated] + assert_warn warn do + Warning[:deprecated] = true + yield + end + ensure + Warning[:deprecated] = prev + end) + end + + def_lambda_warning 'test_lambda_warning_normal', '' do + lambda{} + end + + def_lambda_warning 'test_lambda_warning_pass_lambda', '' do + b = lambda{} + lambda(&b) + end + + def_lambda_warning 'test_lambda_warning_pass_symbol_proc', '' do + lambda(&:to_s) + end + + def_lambda_warning 'test_lambda_warning_pass_proc', /deprecated/ do + b = proc{} + lambda(&b) + end + + def_lambda_warning 'test_lambda_warning_pass_block', /deprecated/ do + helper_test_warn_lamda_with_passed_block{} + end + + def_lambda_warning 'test_lambda_warning_pass_block_symbol_proc', '' do + # Symbol#to_proc returns lambda + helper_test_warn_lamda_with_passed_block(&:to_s) + end + def test_curry_ski_fib s = proc {|f, g, x| f[x][g[x]] }.curry k = proc {|x, y| x }.curry @@ -383,7 +442,7 @@ class TestProc < Test::Unit::TestCase def test_proc_lambda assert_raise(ArgumentError) { proc } - assert_raise(ArgumentError) { lambda } + assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} } o = Object.new def o.foo @@ -391,14 +450,18 @@ class TestProc < Test::Unit::TestCase 1.times { b = lambda } b end - assert_raise(ArgumentError) {o.foo { :foo }.call} + assert_raise(ArgumentError) do + assert_deprecated_warning {o.foo { :foo }}.call + end - def o.foo(&b) + def o.bar(&b) b = nil 1.times { b = lambda } b end - assert_raise(ArgumentError) {o.foo { :foo }.call} + assert_raise(ArgumentError) do + assert_deprecated_warning {o.bar { :foo }}.call + end end def test_arity2 @@ -784,6 +847,33 @@ class TestProc < Test::Unit::TestCase assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x}) end + def test_proc_args_only_rest + pr = proc {|*c| c } + assert_equal [], pr.call() + assert_equal [1], pr.call(1) + assert_equal [[1]], pr.call([1]) + assert_equal [1, 2], pr.call(1,2) + assert_equal [[1, 2]], pr.call([1,2]) + end + + def test_proc_args_rest_kw + pr = proc {|*c, a: 1| [c, a] } + assert_equal [[], 1], pr.call() + assert_equal [[1], 1], pr.call(1) + assert_equal [[[1]], 1], pr.call([1]) + assert_equal [[1, 2], 1], pr.call(1,2) + assert_equal [[[1, 2]], 1], pr.call([1,2]) + end + + def test_proc_args_rest_kwsplat + pr = proc {|*c, **kw| [c, kw] } + assert_equal [[], {}], pr.call() + assert_equal [[1], {}], pr.call(1) + assert_equal [[[1]], {}], pr.call([1]) + assert_equal [[1, 2], {}], pr.call(1,2) + assert_equal [[[1, 2]], {}], pr.call([1,2]) + end + def test_proc_args_pos_rest_post_block pr = proc {|a,b,*c,d,e,&f| [a, b, c, d, e, f.class, f&&f.call(:x)] @@ -1087,6 +1177,36 @@ class TestProc < Test::Unit::TestCase assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]") end + def test_proc_autosplat + def self.a(arg, kw) + yield arg + yield arg, **kw + yield arg, kw + end + + arr = [] + a([1,2,3], {}) do |arg1, arg2=0| + arr << [arg1, arg2] + end + assert_equal([[1, 2], [[1, 2, 3], 0], [[1, 2, 3], {}]], arr) + + arr = [] + a([1,2,3], a: 1) do |arg1, arg2=0| + arr << [arg1, arg2] + end + assert_equal([[1, 2], [[1, 2, 3], {a: 1}], [[1, 2, 3], {a: 1}]], arr) + end + + def test_proc_single_arg_with_keywords_accepted_and_yielded + def self.a + yield [], **{a: 1} + end + res = a do |arg, **opts| + [arg, opts] + end + assert_equal([[], {a: 1}], res) + end + def test_parameters assert_equal([], proc {}.parameters) assert_equal([], proc {||}.parameters) @@ -1376,16 +1496,6 @@ class TestProc < Test::Unit::TestCase end; end - def method_for_test_proc_without_block_for_symbol - assert_warn(/Capturing the given block using Kernel#proc is deprecated/) do - binding.eval('proc') - end - end - - def test_proc_without_block_for_symbol - assert_equal('1', method_for_test_proc_without_block_for_symbol(&:to_s).call(1), '[Bug #14782]') - end - def test_compose f = proc {|x| x * 2} g = proc {|x| x + 1} @@ -1413,9 +1523,13 @@ class TestProc < Test::Unit::TestCase def test_compose_with_lambda f = lambda {|x| x * 2} g = lambda {|x| x} + not_lambda = proc {|x| x} assert_predicate((f << g), :lambda?) assert_predicate((g >> f), :lambda?) + assert_predicate((not_lambda << f), :lambda?) + assert_not_predicate((f << not_lambda), :lambda?) + assert_not_predicate((not_lambda >> f), :lambda?) end def test_compose_with_method @@ -1427,6 +1541,7 @@ class TestProc < Test::Unit::TestCase assert_equal(6, (f << g).call(2)) assert_equal(5, (f >> g).call(2)) + assert_predicate((f << g), :lambda?) end def test_compose_with_callable @@ -1438,6 +1553,7 @@ class TestProc < Test::Unit::TestCase assert_equal(6, (f << g).call(2)) assert_equal(5, (f >> g).call(2)) + assert_predicate((f << g), :lambda?) end def test_compose_with_noncallable @@ -1477,6 +1593,63 @@ class TestProc < Test::Unit::TestCase assert_equal(42, Module.new { extend self def m1(&b) b end; def m2(); m1 { next 42 } end }.m2.call) end + + def test_isolate + assert_raise_with_message ArgumentError, /\(a\)/ do + a = :a + Proc.new{p a}.isolate + end + + assert_raise_with_message ArgumentError, /\(a\)/ do + a = :a + 1.times{ + Proc.new{p a}.isolate + } + end + + assert_raise_with_message ArgumentError, /yield/ do + 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 + + :a.tap{|a| + :b.tap{|b| + Proc.new{ + :c.tap{|c| + assert_equal :c, eval('c') + + assert_raise_with_message SyntaxError, /\`a\'/ do + eval('p a') + end + + assert_raise_with_message SyntaxError, /\`b\'/ do + eval('p b') + end + + assert_raise_with_message SyntaxError, /can not yield from isolated Proc/ do + eval('p yield') + end + + assert_equal :c, binding.local_variable_get(:c) + + assert_raise_with_message NameError, /local variable \`a\' is not defined/ do + binding.local_variable_get(:a) + end + + assert_equal [:c], local_variables + assert_equal [:c], binding.local_variables + } + }.isolate.call + } + } + end if proc{}.respond_to? :isolate end class TestProcKeywords < Test::Unit::TestCase @@ -1485,29 +1658,15 @@ class TestProcKeywords < Test::Unit::TestCase g = ->(kw) { kw.merge(:a=>2) } assert_equal(2, (f >> g).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call(a: 3)[:a]) - end + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } assert_equal(2, (f >> g).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (f >> g).call({a: 3})[:a]) - end + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } assert_equal(2, (g << f).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call(a: 3)[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (g << f).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call({a: 3})[:a]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.*The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call(**{})[:a]) - end + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } assert_equal(2, (f >> g).call(**{})[:a]) end @@ -1515,29 +1674,15 @@ class TestProcKeywords < Test::Unit::TestCase f = ->(**kw) { kw.merge(:a=>1) }.method(:call) g = ->(kw) { kw.merge(:a=>2) }.method(:call) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call(a: 3)[:a]) - end + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } assert_equal(2, (f >> g).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (f >> g).call({a: 3})[:a]) - end + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } assert_equal(2, (g << f).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call(a: 3)[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (g << f).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call({a: 3})[:a]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.*The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call(**{})[:a]) - end + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } assert_equal(2, (f >> g).call(**{})[:a]) end @@ -1549,29 +1694,15 @@ class TestProcKeywords < Test::Unit::TestCase def g.<<(f) to_proc << f end def g.>>(f) to_proc >> f end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call(a: 3)[:a]) - end + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } assert_equal(2, (f >> g).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (f << g).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (f >> g).call({a: 3})[:a]) - end + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } assert_equal(2, (g << f).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call(a: 3)[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(2, (g << f).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for method/m) do - assert_equal(1, (g >> f).call({a: 3})[:a]) - end - assert_warn(/The keyword argument is passed as the last hash parameter.*for `call'/m) do - assert_equal(1, (f << g).call(**{})[:a]) - end + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } assert_equal(2, (f >> g).call(**{})[:a]) f = ->(kw) { kw.merge(:a=>1) } @@ -1582,29 +1713,15 @@ class TestProcKeywords < Test::Unit::TestCase def g.>>(f) to_proc >> f end assert_equal(1, (f << g).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(2, (f >> g).call(a: 3)[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(1, (f << g).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(2, (f >> g).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(2, (g << f).call(a: 3)[:a]) - end + assert_raise(ArgumentError) { (f >> g).call(a: 3)[:a] } + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g << f).call(a: 3)[:a] } assert_equal(1, (g >> f).call(a: 3)[:a]) - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(2, (g << f).call({a: 3})[:a]) - end - assert_warn(/The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(1, (g >> f).call({a: 3})[:a]) - end + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } assert_equal(1, (f << g).call(**{})[:a]) - assert_warn(/The keyword argument is passed as the last hash parameter.*The last argument is used as the keyword parameter.*for `call'/m) do - assert_equal(2, (f >> g).call(**{})[:a]) - end + assert_raise(ArgumentError) { (f >> g).call(**{})[:a] } end end diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index e0cb49b8ef..30427aeec1 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -208,39 +208,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 @@ -261,6 +261,18 @@ class TestProcess < Test::Unit::TestCase } end + def test_overwrite_ENV + assert_separately([],"#{<<~"begin;"}\n#{<<~"end;"}") + BUG = "[ruby-core:105223] [Bug #18164]" + begin; + $VERBOSE = nil + ENV = {} + pid = spawn({}, *#{TRUECOMMAND.inspect}) + ENV.replace({}) + assert_kind_of(Integer, pid, BUG) + end; + end + MANDATORY_ENVS = %w[RUBYLIB MJIT_SEARCH_BUILD_DIR] case RbConfig::CONFIG['target_os'] when /linux/ @@ -338,6 +350,13 @@ class TestProcess < Test::Unit::TestCase ensure ENV["hmm"] = old end + + assert_raise_with_message(ArgumentError, /fo=fo/) { + system({"fo=fo"=>"ha"}, *ENVCOMMAND) + } + assert_raise_with_message(ArgumentError, /\u{30c0}=\u{30e1}/) { + system({"\u{30c0}=\u{30e1}"=>"ha"}, *ENVCOMMAND) + } end def test_execopt_env_path @@ -1418,6 +1437,8 @@ class TestProcess < Test::Unit::TestCase assert_equal(s.to_i >> 1, s >> 1) assert_equal(false, s.stopped?) assert_equal(nil, s.stopsig) + + assert_equal(s, Marshal.load(Marshal.dump(s))) end end @@ -1435,6 +1456,8 @@ class TestProcess < Test::Unit::TestCase assert_equal(expected, [s.exited?, s.signaled?, s.stopped?, s.success?], "[s.exited?, s.signaled?, s.stopped?, s.success?]") + + assert_equal(s, Marshal.load(Marshal.dump(s))) end end @@ -1449,6 +1472,27 @@ class TestProcess < Test::Unit::TestCase "[s.exited?, s.signaled?, s.stopped?, s.success?]") assert_equal("#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>", s.inspect.sub(/ \(core dumped\)(?=>\z)/, '')) + + assert_equal(s, Marshal.load(Marshal.dump(s))) + end + end + + def test_status_fail + ret = Process::Status.wait($$) + assert_instance_of(Process::Status, ret) + assert_equal(-1, ret.pid) + end + + + def test_status_wait + IO.popen([RUBY, "-e", "gets"], "w") do |io| + pid = io.pid + assert_nil(Process::Status.wait(pid, Process::WNOHANG)) + io.puts + ret = Process::Status.wait(pid) + assert_instance_of(Process::Status, ret) + assert_equal(pid, ret.pid) + assert_predicate(ret, :exited?) end end @@ -1598,6 +1642,34 @@ class TestProcess < Test::Unit::TestCase rescue NotImplementedError end + if Process::UID.respond_to?(:from_name) + def test_uid_from_name + if u = Etc.getpwuid(Process.uid) + assert_equal(Process.uid, Process::UID.from_name(u.name), u.name) + end + assert_raise_with_message(ArgumentError, /\u{4e0d 5b58 5728}/) { + Process::UID.from_name("\u{4e0d 5b58 5728}") + } + end + end + + if Process::GID.respond_to?(:from_name) && !RUBY_PLATFORM.include?("android") + def test_gid_from_name + if g = Etc.getgrgid(Process.gid) + assert_equal(Process.gid, Process::GID.from_name(g.name), g.name) + end + expected_excs = [ArgumentError] + expected_excs << Errno::ENOENT if defined?(Errno::ENOENT) + expected_excs << Errno::ESRCH if defined?(Errno::ESRCH) # WSL 2 actually raises Errno::ESRCH + expected_excs << Errno::EBADF if defined?(Errno::EBADF) + expected_excs << Errno::EPERM if defined?(Errno::EPERM) + exc = assert_raise(*expected_excs) do + Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji) + end + assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) + end + end + def test_uid_re_exchangeable_p r = Process::UID.re_exchangeable? assert_include([true, false], r) @@ -1646,7 +1718,7 @@ class TestProcess < Test::Unit::TestCase Process.wait pid assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable' end - if RubyVM::MJIT.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]" @@ -1660,6 +1732,9 @@ class TestProcess < Test::Unit::TestCase end def test_no_curdir + if /solaris/i =~ RUBY_PLATFORM + skip "Temporary skip to avoid CI failures after commit to use realpath on required files" + end with_tmpchdir {|d| Dir.mkdir("vd") status = nil @@ -1711,6 +1786,7 @@ class TestProcess < Test::Unit::TestCase min = 1_000 / (cmd.size + sep.size) cmds = Array.new(min, cmd) exs = [Errno::ENOENT] + exs << Errno::EINVAL if windows? exs << Errno::E2BIG if defined?(Errno::E2BIG) opts = {[STDOUT, STDERR]=>File::NULL} opts[:rlimit_nproc] = 128 if defined?(Process::RLIMIT_NPROC) @@ -1762,6 +1838,8 @@ class TestProcess < Test::Unit::TestCase end def test_daemon_noclose + pend "macOS 15 beta is not working with this test" if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion` + data = IO.popen("-", "r+") do |f| break f.read if f Process.daemon(false, true) @@ -2260,8 +2338,6 @@ EOS pid = fork {Process.kill(:QUIT, parent)} IO.popen([ruby, -'--disable=gems'], -'r+'){} Process.wait(pid) - $stdout.puts - $stdout.flush end INPUT end if defined?(fork) @@ -2374,7 +2450,7 @@ EOS rescue SystemCallError w.syswrite("exec failed\n") end - q = Queue.new + q = Thread::Queue.new th1 = Thread.new { i = 0; i += 1 while q.empty?; i } th2 = Thread.new { j = 0; j += 1 while q.empty? && Thread.pass.nil?; j } sleep 0.5 @@ -2383,7 +2459,7 @@ EOS end w.close assert_equal "exec failed\n", r.gets - vals = r.gets.chomp.split.map!(&:to_i) + vals = r.gets.split.map!(&:to_i) assert_operator vals[0], :>, vals[1], vals.inspect _, status = Process.waitpid2(pid) end @@ -2399,6 +2475,15 @@ EOS r.close if r end if defined?(fork) + def test_rescue_exec_fail + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise(Errno::ENOENT) do + exec("", in: "") + end + end; + end + def test_many_args bug11418 = '[ruby-core:70251] [Bug #11418]' assert_in_out_err([], <<-"end;", ["x"]*256, [], bug11418, timeout: 60) @@ -2449,4 +2534,164 @@ EOS Process.wait spawn(RUBY, "-e", "exit 13") assert_same(Process.last_status, $?) end + + def test_last_status_failure + assert_nil system("sad") + assert_not_predicate $?, :success? + assert_equal $?.exitstatus, 127 + end + + def test_exec_failure_leaves_no_child + assert_raise(Errno::ENOENT) do + spawn('inexistent_command') + 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) end diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index 939d17bdf7..13b7329269 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -2,14 +2,13 @@ require 'test/unit' class TestRand < Test::Unit::TestCase - def assert_random_int(ws, m, init = 0) + def assert_random_int(m, init = 0, iterate: 5) srand(init) rnds = [Random.new(init)] rnds2 = [rnds[0].dup] rnds3 = [rnds[0].dup] - ws.each_with_index do |w, i| - w = w.to_i - assert_equal(w, rand(m)) + iterate.times do |i| + w = rand(m) rnds.each do |rnd| assert_equal(w, rnd.rand(m)) end @@ -27,133 +26,97 @@ class TestRand < Test::Unit::TestCase end def test_mt - assert_random_int(%w(1067595299 955945823 477289528 4107218783 4228976476), - 0x100000000, 0x00000456_00000345_00000234_00000123) + assert_random_int(0x100000000, 0x00000456_00000345_00000234_00000123) end def test_0x3fffffff - assert_random_int(%w(209652396 398764591 924231285 404868288 441365315), - 0x3fffffff) + assert_random_int(0x3fffffff) end def test_0x40000000 - assert_random_int(%w(209652396 398764591 924231285 404868288 441365315), - 0x40000000) + assert_random_int(0x40000000) end def test_0x40000001 - assert_random_int(%w(209652396 398764591 924231285 441365315 192771779), - 0x40000001) + assert_random_int(0x40000001) end def test_0xffffffff - assert_random_int(%w(2357136044 2546248239 3071714933 3626093760 2588848963), - 0xffffffff) + assert_random_int(0xffffffff) end def test_0x100000000 - assert_random_int(%w(2357136044 2546248239 3071714933 3626093760 2588848963), - 0x100000000) + assert_random_int(0x100000000) end def test_0x100000001 - assert_random_int(%w(2546248239 1277901399 243580376 1171049868 2051556033), - 0x100000001) + assert_random_int(0x100000001) end def test_rand_0x100000000 - assert_random_int(%w(4119812344 3870378946 80324654 4294967296 410016213), - 0x100000001, 311702798) + assert_random_int(0x100000001, 311702798) end def test_0x1000000000000 - assert_random_int(%w(11736396900911 - 183025067478208 - 197104029029115 - 130583529618791 - 180361239846611), - 0x1000000000000) + assert_random_int(0x1000000000000) end def test_0x1000000000001 - assert_random_int(%w(187121911899765 - 197104029029115 - 180361239846611 - 236336749852452 - 208739549485656), - 0x1000000000001) + assert_random_int(0x1000000000001) end def test_0x3fffffffffffffff - assert_random_int(%w(900450186894289455 - 3969543146641149120 - 1895649597198586619 - 827948490035658087 - 3203365596207111891), - 0x3fffffffffffffff) + assert_random_int(0x3fffffffffffffff) end def test_0x4000000000000000 - assert_random_int(%w(900450186894289455 - 3969543146641149120 - 1895649597198586619 - 827948490035658087 - 3203365596207111891), - 0x4000000000000000) + assert_random_int(0x4000000000000000) end def test_0x4000000000000001 - assert_random_int(%w(900450186894289455 - 3969543146641149120 - 1895649597198586619 - 827948490035658087 - 2279347887019741461), - 0x4000000000000001) + assert_random_int(0x4000000000000001) end def test_0x10000000000 - ws = %w(455570294424 1073054410371 790795084744 2445173525 1088503892627) - assert_random_int(ws, 0x10000000000, 3) + assert_random_int(0x10000000000, 3) end def test_0x10000 - ws = %w(2732 43567 42613 52416 45891) - assert_random_int(ws, 0x10000) + assert_random_int(0x10000) + end + + def assert_same_numbers(type, *nums) + nums.each do |n| + assert_instance_of(type, n) + end + x = nums.shift + nums.each do |n| + assert_equal(x, n) + end + x end def test_types - srand(0) - rnd = Random.new(0) - assert_equal(44, rand(100.0)) - assert_equal(44, rnd.rand(100)) - assert_equal(1245085576965981900420779258691, rand((2**100).to_f)) - assert_equal(1245085576965981900420779258691, rnd.rand(2**100)) - assert_equal(914679880601515615685077935113, rand(-(2**100).to_f)) + o = Object.new + class << o + def to_int; 100; end + def class; Integer; end + end srand(0) - rnd = Random.new(0) - assert_equal(997707939797331598305742933184, rand(2**100)) - assert_equal(997707939797331598305742933184, rnd.rand(2**100)) - assert_in_delta(0.602763376071644, rand((2**100).coerce(0).first), - 0.000000000000001) - assert_raise(ArgumentError) {rnd.rand((2**100).coerce(0).first)} + nums = [100.0, (2**100).to_f, (2**100), o, o, o].map do |m| + k = Integer + assert_kind_of(k, x = rand(m), m.inspect) + [m, k, x] + end + assert_kind_of(Integer, rand(-(2**100).to_f)) srand(0) rnd = Random.new(0) - assert_in_delta(0.548813503927325, rand(nil), - 0.000000000000001) - assert_in_delta(0.548813503927325, rnd.rand(), - 0.000000000000001) - srand(0) - rnd = Random.new(0) - o = Object.new - def o.to_int; 100; end - assert_equal(44, rand(o)) - assert_equal(44, rnd.rand(o)) - assert_equal(47, rand(o)) - assert_equal(47, rnd.rand(o)) - assert_equal(64, rand(o)) - assert_equal(64, rnd.rand(o)) + rnd2 = Random.new(0) + nums.each do |m, k, x| + assert_same_numbers(m.class, Random.rand(m), rnd.rand(m), rnd2.rand(m)) + end end def test_srand @@ -163,10 +126,13 @@ class TestRand < Test::Unit::TestCase srand(2**100) rnd = Random.new(2**100) - %w(3258412053).each {|w| - assert_equal(w.to_i, rand(0x100000000)) - assert_equal(w.to_i, rnd.rand(0x100000000)) - } + r = 3.times.map do + assert_same_numbers(Integer, rand(0x100000000), rnd.rand(0x100000000)) + end + srand(2**100) + r.each do |n| + assert_same_numbers(Integer, n, rand(0x100000000)) + end end def test_shuffle @@ -177,17 +143,17 @@ class TestRand < Test::Unit::TestCase end def test_big_seed - assert_random_int(%w(2757555016), 0x100000000, 2**1000000-1) + assert_random_int(0x100000000, 2**1000000-1) end def test_random_gc r = Random.new(0) - %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r.rand(0x100000000)) + 3.times do + assert_kind_of(Integer, r.rand(0x100000000)) end GC.start - %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r.rand(0x100000000)) + 3.times do + assert_kind_of(Integer, r.rand(0x100000000)) end end @@ -223,179 +189,56 @@ class TestRand < Test::Unit::TestCase def test_random_dup r1 = Random.new(0) r2 = r1.dup - %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r1.rand(0x100000000)) - end - %w(2357136044 2546248239 3071714933).each do |w| - assert_equal(w.to_i, r2.rand(0x100000000)) + 3.times do + assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000)) end r2 = r1.dup - %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r1.rand(0x100000000)) - end - %w(3626093760 2588848963 3684848379).each do |w| - assert_equal(w.to_i, r2.rand(0x100000000)) + 3.times do + assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000)) end end - def test_random_state - state = <<END -3877134065023083674777481835852171977222677629000095857864323111193832400974413 -4782302161934463784850675209112299537259006497924090422596764895633625964527441 -6943943249411681406395713106007661119327771293929504639878577616749110507385924 -0173026285378896836022134086386136835407107422834685854738117043791709411958489 -3504364936306163473541948635570644161010981140452515307286926529085424765299100 -1255453260115310687580777474046203049197643434654645011966794531914127596390825 -0832232869378617194193100828000236737535657699356156021286278281306055217995213 -8911536025132779573429499813926910299964681785069915877910855089314686097947757 -2621451199734871158015842198110309034467412292693435515184023707918034746119728 -8223459645048255809852819129671833854560563104716892857257229121211527031509280 -2390605053896565646658122125171846129817536096211475312518457776328637574563312 -8113489216547503743508184872149896518488714209752552442327273883060730945969461 -6568672445225657265545983966820639165285082194907591432296265618266901318398982 -0560425129536975583916120558652408261759226803976460322062347123360444839683204 -9868507788028894111577023917218846128348302845774997500569465902983227180328307 -3735301552935104196244116381766459468172162284042207680945316590536094294865648 -5953156978630954893391701383648157037914019502853776972615500142898763385846315 -8457790690531675205213829055442306187692107777193071680668153335688203945049935 -3404449910419303330872435985327845889440458370416464132629866593538629877042969 -7589948685901343135964343582727302330074331803900821801076139161904900333497836 -6627028345784450211920229170280462474456504317367706849373957309797251052447898 -8436235456995644876515032202483449807136139063937892187299239634252391529822163 -9187055268750679730919937006195967184206072757082920250075756273182004964087790 -3812024063897424219316687828337007888030994779068081666133751070479581394604974 -6022215489604777611774245181115126041390161592199230774968475944753915533936834 -4740049163514318351045644344358598159463605453475585370226041981040238023241538 -4958436364776113598408428801867643946791659645708540669432995503575075657406359 -8086928867900590554805639837071298576728564946552163206007997000988745940681607 -4542883814997403673656291618517107133421335645430345871041730410669209035640945 -5024601618318371192091626092482640364669969307766919645222516407626038616667754 -5781148898846306894862390724358039251444333889446128074209417936830253204064223 -3424784857908022314095011879203259864909560830176189727132432100010493659154644 -8407326292884826469503093409465946018496358514999175268200846200025235441426140 -7783386235191526371372655894290440356560751680752191224383460972099834655086068 -9989413443881686756951804138910911737670495391762470293978321414964443502180391 -4665982575919524372985773336921990352313629822677022891307943536442258282401255 -5387646898976193134193506239982621725093291970351083631367582570375381334759004 -1784150668048523676387894646666460369896619585113435743180899362844070393586212 -5023920017185866399742380706352739465848708746963693663004068892056705603018655 -8686663087894205699555906146534549176352859823832196938386172810274748624517052 -8356758650653040545267425513047130342286119889879774951060662713807133125543465 -5104086026298674827575216701372513525846650644773241437066782037334367982012148 -7987782004646896468089089826267467005660035604553432197455616078731159778086155 -9443250946037119223468305483694093795324036812927501783593256716590840500905291 -2096608538205270323065573109227029887731553399324547696295234105140157179430410 -4003109602564833086703863221058381556776789018351726488797845637981974580864082 -1630093543020854542240690897858757985640869209737744458407777584279553258261599 -0246922348101147034463235613998979344685018577901996218099622190722307356620796 -5137485271371502385527388080824050288371607602101805675021790116223360483508538 -8832149997794718410946818866375912486788005950091851067237358294899771385995876 -7088239104394332452501033090159333224995108984871426750597513314521294001864578 -2353528356752869732412552685554334966798888534847483030947310518891788722418172 -6008607577773612004956373863580996793809969715725508939568919714424871639667201 -7922255031431159347210833846575355772055570279673262115911154370983086189948124 -4653677615895887099814174914248255026619941911735341818489822197472499295786997 -7728418516719104857455960900092226749725407204388193002835497055305427730656889 -1508308778869166073740855838213112709306743479676740893150000714099064468263284 -1873435518542972182497755500300784177067568586395485329021157235696300013490087 -2866571034916258390528533374944905429089028336079264760836949419754851422499614 -5732326011260304142074554782259843903215064144396140106592193961703288125005023 -5334375212799817540775536847622032852415253966587517800661605905489339306359573 -2234947905196298436841723673626428243649931398749552780311877734063703985375067 -1239508613417041942487245370152912391885566432830659640677893488723724763120121 -4111855277511356759926232894062814360449757490961653026194107761340614059045172 -1123363102660719217740126157997033682099769790976313166682432732518101889210276 -9574144065390305904944821051736021310524344626348851573631697771556587859836330 -6997324121866564283654784470215100159122764509197570402997911258816526554863326 -9877535269005418736225944874608987238997316999444215865249840762640949599725696 -0773083894168959823152054508672272612355108904098579447774398451678239199426513 -3439507737424049578587487505080347686371029156845461151278198605267053408259090 -3158676794894709281917034995611352710898103415304769654883981727681820369090169 -9295163908214854813365413456264812190842699054830709079275249714169405719140093 -1347572458245530016346604698682269779841803667099480215265926316505737171177810 -9969036572310084022695109125200937135540995157279354438704321290061646592229860 -0156566013602344870223183295508278359111174872740360473845615437106413256386849 -2286259982118315248148847764929974917157683083659364623458927512616369119194574 -2254080 -END - state = state.split.join.to_i - r = Random.new(0) + def test_random_bytes srand(0) - assert_equal(state, r.instance_eval { state }) - assert_equal(state, Random.instance_eval { state }) - r.rand(0x100) - assert_equal(state, r.instance_eval { state }) - end - - def test_random_left r = Random.new(0) - assert_equal(1, r.instance_eval { left }) - r.rand(0x100) - assert_equal(624, r.instance_eval { left }) - r.rand(0x100) - assert_equal(623, r.instance_eval { left }) - srand(0) - assert_equal(1, Random.instance_eval { left }) - rand(0x100) - assert_equal(624, Random.instance_eval { left }) - rand(0x100) - assert_equal(623, Random.instance_eval { left }) - end - def test_random_bytes - assert_random_bytes(Random.new(0)) - end - - def assert_random_bytes(r) - srand(0) assert_equal("", r.bytes(0)) assert_equal("", Random.bytes(0)) - x = "\xAC".force_encoding("ASCII-8BIT") - assert_equal(x, r.bytes(1)) + + x = r.bytes(1) + assert_equal(1, x.bytesize) assert_equal(x, Random.bytes(1)) - x = "/\xAA\xC4\x97u\xA6\x16\xB7\xC0\xCC".force_encoding("ASCII-8BIT") - assert_equal(x, r.bytes(10)) + + x = r.bytes(10) + assert_equal(10, x.bytesize) assert_equal(x, Random.bytes(10)) end def test_random_range srand(0) r = Random.new(0) - %w(9 5 8).each {|w| - assert_equal(w.to_i, rand(5..9)) - assert_equal(w.to_i, r.rand(5..9)) - } - %w(-237 731 383).each {|w| - assert_equal(w.to_i, rand(-1000..1000)) - assert_equal(w.to_i, r.rand(-1000..1000)) - } - %w(1267650600228229401496703205382 - 1267650600228229401496703205384 - 1267650600228229401496703205383).each do |w| - assert_equal(w.to_i, rand(2**100+5..2**100+9)) - assert_equal(w.to_i, r.rand(2**100+5..2**100+9)) - end - - v = rand(3.1..4) - assert_instance_of(Float, v, '[ruby-core:24679]') - assert_include(3.1..4, v) - - v = r.rand(3.1..4) - assert_instance_of(Float, v, '[ruby-core:24679]') - assert_include(3.1..4, v) - now = Time.now - assert_equal(now, rand(now..now)) - assert_equal(now, r.rand(now..now)) + [5..9, -1000..1000, 2**100+5..2**100+9, 3.1..4, now..(now+2)].each do |range| + 3.times do + x = rand(range) + assert_instance_of(range.first.class, x) + assert_equal(x, r.rand(range)) + assert_include(range, x) + end + end end def test_random_float r = Random.new(0) - assert_in_delta(0.5488135039273248, r.rand, 0.0001) - assert_in_delta(0.7151893663724195, r.rand, 0.0001) - assert_in_delta(0.6027633760716439, r.rand, 0.0001) - assert_in_delta(1.0897663659937937, r.rand(2.0), 0.0001) - assert_in_delta(5.3704626067153264e+29, r.rand((2**100).to_f), 10**25) + 3.times do + assert_include(0...1.0, r.rand) + end + [2.0, (2**100).to_f].each do |x| + range = 0...x + 3.times do + assert_include(range, r.rand(x), "rand(#{x})") + end + end assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) } assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) } @@ -403,10 +246,11 @@ END assert_raise(Errno::EDOM) {r.rand(..1)} r = Random.new(0) - assert_in_delta(1.5488135039273248, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') - assert_in_delta(1.7151893663724195, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') - assert_in_delta(7.027633760716439, r.rand(1.0...11.0), 0.0001, '[ruby-core:24655]') - assert_in_delta(3.0897663659937937, r.rand(2.0...4.0), 0.0001, '[ruby-core:24655]') + [1.0...2.0, 1.0...11.0, 2.0...4.0].each do |range| + 3.times do + assert_include(range, r.rand(range), "[ruby-core:24655] rand(#{range})") + end + end assert_nothing_raised {r.rand(-Float::MAX..Float::MAX)} end @@ -492,18 +336,6 @@ END } end - def test_default - r1 = Random::DEFAULT.dup - r2 = Random::DEFAULT.dup - 3.times do - x0 = rand - x1 = r1.rand - x2 = r2.rand - assert_equal(x0, x1) - assert_equal(x0, x2) - end - end - def test_marshal bug3656 = '[ruby-core:31622]' assert_raise(TypeError, bug3656) { @@ -560,16 +392,19 @@ END end def test_default_seed - assert_separately([], <<-End) + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + verbose, $VERBOSE = $VERBOSE, nil seed = Random::DEFAULT::seed rand1 = Random::DEFAULT::rand + $VERBOSE = verbose rand2 = Random.new(seed).rand assert_equal(rand1, rand2) srand seed rand3 = rand assert_equal(rand1, rand3) - End + end; end def test_urandom 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 800cee92cc..dc591b0604 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -81,6 +81,8 @@ class TestRange < Test::Unit::TestCase assert_equal(nil, (2..1).min) assert_equal(1, (1...2).min) assert_equal(1, (1..).min) + assert_raise(RangeError) { (..1).min } + assert_raise(RangeError) { (...1).min } assert_equal(1.0, (1.0..2.0).min) assert_equal(nil, (2.0..1.0).min) @@ -93,6 +95,8 @@ class TestRange < Test::Unit::TestCase assert_equal([0,1,2], (0..10).min(3)) assert_equal([0,1], (0..1).min(3)) assert_equal([0,1,2], (0..).min(3)) + assert_raise(RangeError) { (..1).min(3) } + assert_raise(RangeError) { (...1).min(3) } assert_raise(RangeError) { (0..).min {|a, b| a <=> b } } end @@ -119,6 +123,15 @@ class TestRange < Test::Unit::TestCase assert_equal([9,8,7], (0...10).max(3)) assert_raise(RangeError) { (1..).max(3) } assert_raise(RangeError) { (1...).max(3) } + + assert_raise(RangeError) { (..0).min {|a, b| a <=> b } } + + assert_equal(2, (..2).max) + assert_raise(TypeError) { (...2).max } + assert_raise(TypeError) { (...2.0).max } + + assert_equal(Float::INFINITY, (1..Float::INFINITY).max) + assert_nil((1..-Float::INFINITY).max) end def test_minmax @@ -140,12 +153,18 @@ class TestRange < Test::Unit::TestCase assert_equal([nil, nil], (0...0).minmax) assert_equal([2, 1], (1..2).minmax{|a, b| b <=> a}) + + assert_equal(['a', 'c'], ('a'..'c').minmax) + assert_equal(['a', 'b'], ('a'...'c').minmax) + + assert_equal([1, Float::INFINITY], (1..Float::INFINITY).minmax) + assert_equal([nil, nil], (1..-Float::INFINITY).minmax) end def test_initialize_twice r = eval("1..2") - assert_raise(NameError) { r.instance_eval { initialize 3, 4 } } - assert_raise(NameError) { r.instance_eval { initialize_copy 3..4 } } + assert_raise(FrozenError) { r.instance_eval { initialize 3, 4 } } + assert_raise(FrozenError) { r.instance_eval { initialize_copy 3..4 } } end def test_uninitialized_range @@ -252,8 +271,10 @@ class TestRange < Test::Unit::TestCase assert_kind_of(Enumerator::ArithmeticSequence, (1..).step(2)) assert_raise(ArgumentError) { (0..10).step(-1) { } } + assert_raise(ArgumentError) { (0..10).step(0) } assert_raise(ArgumentError) { (0..10).step(0) { } } assert_raise(ArgumentError) { (0..).step(-1) { } } + assert_raise(ArgumentError) { (0..).step(0) } assert_raise(ArgumentError) { (0..).step(0) { } } a = [] @@ -284,6 +305,7 @@ class TestRange < Test::Unit::TestCase (2**32-1 .. 2**32+1).step(2) {|x| a << x } assert_equal([4294967295, 4294967297], a) zero = (2**32).coerce(0).first + assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) } assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) { } } a = [] (2**32-1 .. ).step(2) {|x| a << x; break if a.size == 2 } @@ -644,6 +666,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 diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index 301890b620..fe9de64c4c 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -128,6 +128,13 @@ class Rational_Test < Test::Unit::TestCase assert_raise(TypeError){Rational(Object.new, Object.new)} assert_raise(TypeError){Rational(1, Object.new)} + bug12485 = '[ruby-core:75995] [Bug #12485]' + o = Object.new + def o.to_int; 1; end + assert_equal(1, Rational(o, 1), bug12485) + assert_equal(1, Rational(1, o), bug12485) + assert_equal(1, Rational(o, o), bug12485) + o = Object.new def o.to_r; 1/42r; end assert_equal(1/42r, Rational(o)) @@ -158,6 +165,14 @@ class Rational_Test < Test::Unit::TestCase if (1.0/0).infinite? assert_raise(FloatDomainError){Rational(1.0/0)} end + + bug16518 = "[ruby-core:96942] [Bug #16518]" + cls = Class.new(Numeric) do + def /(y); 42; end + def to_r; 1r; end + def to_int; 1; end + end + assert_equal(1/2r, Rational(cls.new, 2), bug16518) end def test_attr @@ -598,6 +613,13 @@ class Rational_Test < Test::Unit::TestCase assert_nothing_raised(TypeError, '[Bug #5020] [ruby-dev:44088]') do Rational(1,2).coerce(Complex(1,1)) end + + assert_raise(ZeroDivisionError) do + 1 / 0r.coerce(0+0i)[0] + end + assert_raise(ZeroDivisionError) do + 1 / 0r.coerce(0.0+0i)[0] + end end class ObjectX @@ -834,6 +856,18 @@ class Rational_Test < Test::Unit::TestCase assert_equal(nil, Rational(1, Object.new, exception: false)) } + bug12485 = '[ruby-core:75995] [Bug #12485]' + assert_nothing_raised(RuntimeError, bug12485) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(RuntimeError, bug12485) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Rational(1, o, exception: false)) + } + o = Object.new; def o.to_r; raise; end assert_nothing_raised(RuntimeError) { diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 6fb04de5d6..19857b035c 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -225,6 +225,8 @@ class TestRefinement < Test::Unit::TestCase end end def test_method_should_use_refinements + skip if Test::Unit::Runner.current_repeat_count > 0 + foo = Foo.new assert_raise(NameError) { foo.method(:z) } assert_equal("FooExt#z", FooExtClient.method_z(foo).call) @@ -246,6 +248,8 @@ class TestRefinement < Test::Unit::TestCase end end def test_instance_method_should_use_refinements + skip if Test::Unit::Runner.current_repeat_count > 0 + foo = Foo.new assert_raise(NameError) { Foo.instance_method(:z) } assert_equal("FooExt#z", FooExtClient.instance_method_z(foo).bind(foo).call) @@ -743,6 +747,13 @@ class TestRefinement < Test::Unit::TestCase end end + def self.suppress_verbose + verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = verbose + end + module IncludeIntoRefinement class C def bar @@ -770,7 +781,9 @@ class TestRefinement < Test::Unit::TestCase module M refine C do - include Mixin + TestRefinement.suppress_verbose do + include Mixin + end def baz return super << " M#baz" @@ -833,7 +846,9 @@ class TestRefinement < Test::Unit::TestCase module M refine C do - prepend Mixin + TestRefinement.suppress_verbose do + prepend Mixin + end def baz return super << " M#baz" @@ -916,7 +931,7 @@ class TestRefinement < Test::Unit::TestCase #{PrependAfterRefine_CODE} undef PrependAfterRefine } - }, timeout: 30 + }, timeout: 60 end def test_prepend_after_refine @@ -1648,7 +1663,6 @@ class TestRefinement < Test::Unit::TestCase def test_reopen_refinement_module assert_separately([], <<-"end;") - $VERBOSE = nil class C end @@ -1665,6 +1679,7 @@ class TestRefinement < Test::Unit::TestCase module R refine C do + alias m m def m :bar end @@ -2382,6 +2397,281 @@ class TestRefinement < Test::Unit::TestCase assert_equal(0, Bug13446::GenericEnumerable.new.sum) end + def test_unbound_refine_method + a = EnvUtil.labeled_class("A") do + def foo + self.class + end + end + b = EnvUtil.labeled_class("B") + bar = EnvUtil.labeled_module("R") do + break refine a do + def foo + super + end + end + end + assert_raise(TypeError) do + bar.instance_method(:foo).bind(b.new) + end + end + + def test_refine_frozen_class + verbose_bak, $VERBOSE = $VERBOSE, nil + singleton_class.instance_variable_set(:@x, self) + class << self + c = Class.new do + def foo + :cfoo + end + end + foo = Module.new do + refine c do + def foo + :rfoo + end + end + end + using foo + @x.assert_equal(:rfoo, c.new.foo) + c.freeze + foo.module_eval do + refine c do + def foo + :rfoo2 + end + def bar + :rbar + end + end + end + @x.assert_equal(:rfoo2, c.new.foo) + @x.assert_equal(:rbar, c.new.bar, '[ruby-core:71391] [Bug #11669]') + end + ensure + $VERBOSE = verbose_bak + end + + # [Bug #17386] + def test_prepended_with_method_cache + foo = Class.new do + def foo + :Foo + end + end + + code = Module.new do + def foo + :Code + end + end + + _ext = Module.new do + refine foo do + def foo; end + end + end + + obj = foo.new + + assert_equal :Foo, obj.foo + foo.prepend code + assert_equal :Code, obj.foo + end + + # [Bug #17417] + def test_prepended_with_method_cache_17417 + assert_normal_exit %q{ + module M + def hoge; end + end + + module R + refine Hash do + def except *args; end + end + end + + h = {} + h.method(:except) # put it on pCMC + Hash.prepend(M) + h.method(:except) + } + end + + def test_defining_after_cached + klass = Class.new + _refinement = Module.new { refine(klass) { def foo; end } } + klass.new.foo rescue nil # cache the refinement method entry + klass.define_method(:foo) { 42 } + assert_equal(42, klass.new.foo) + end + + # [Bug #17806] + def test_two_refinements_for_prepended_class + assert_normal_exit %q{ + module R1 + refine Hash do + def foo; :r1; end + end + end + + class Hash + prepend(Module.new) + end + + class Hash + def foo; end + end + + {}.method(:foo) # put it on pCMC + + module R2 + refine Hash do + def foo; :r2; end + end + end + + {}.foo + } + end + + # [Bug #17806] + def test_redefining_refined_for_prepended_class + klass = Class.new { def foo; end } + _refinement = Module.new do + refine(klass) { def foo; :refined; end } + end + klass.prepend(Module.new) + klass.new.foo # cache foo + klass.define_method(:foo) { :second } + assert_equal(:second, klass.new.foo) + end + + class Bug18180 + module M + refine Array do + def min; :min; end + def max; :max; end + end + end + + using M + + def t + [[1+0, 2, 4].min, [1, 2, 4].min, [1+0, 2, 4].max, [1, 2, 4].max] + end + end + + def test_refine_array_min_max + assert_equal([:min, :min, :max, :max], Bug18180.new.t) + end + + class Bug17822 + module Ext + refine(Bug17822) do + def foo = :refined + end + end + + private(def foo = :not_refined) + + module Client + using Ext + def self.call_foo + Bug17822.new.foo + end + end + end + + # [Bug #17822] + def test_privatizing_refined_method + assert_equal(:refined, Bug17822::Client.call_foo) + end + + def test_ancestors + refinement = nil + as = nil + Module.new do + refine Array do + refinement = self + as = ancestors + end + end + assert_equal([refinement], as, "[ruby-core:86949] [Bug #14744]") + end + + module TestImport + class A + def foo + "original" + end + end + + module B + BAR = "bar" + + def bar + "#{foo}:#{BAR}" + end + end + + module C + refine A do + import_methods B + + def foo + "refined" + end + end + end + + module D + refine A do + TestRefinement.suppress_verbose do + include B + end + + def foo + "refined" + end + end + end + + module UsingC + using C + + def self.call_bar + 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 + refine Integer do + import_methods Enumerable + end + end + 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 a1d49c595a..16a6ed921f 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -5,7 +5,6 @@ require 'test/unit' class TestRegexp < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -43,13 +42,14 @@ class TestRegexp < Test::Unit::TestCase def test_yoshidam_net_20041111_1 s = "[\xC2\xA0-\xC3\xBE]" - assert_match(Regexp.new(s, nil, "u"), "\xC3\xBE") + r = assert_deprecated_warning(/ignored/) {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") - Regexp.new(s, nil, "u") + assert_warning(/ignored/) {Regexp.new(s, nil, "u")} end end @@ -57,6 +57,17 @@ class TestRegexp < Test::Unit::TestCase assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-')) end + def test_premature_end_char_property + ["\\p{", + "\\p{".dup.force_encoding("UTF-8"), + "\\p{".dup.force_encoding("US-ASCII") + ].each do |string| + assert_raise(RegexpError) do + Regexp.new(string) + end + end + end + def test_assert_normal_exit # moved from knownbug. It caused core. Regexp.union("a", "a") @@ -161,6 +172,10 @@ class TestRegexp < Test::Unit::TestCase s = "foo" s[/(?<bar>o)/, "bar"] = "baz" assert_equal("fbazo", s) + + /.*/ =~ "abc" + "a".sub("a", "") + assert_raise(IndexError) {Regexp.last_match(:_id)} end def test_named_capture_with_nul @@ -214,6 +229,17 @@ class TestRegexp < Test::Unit::TestCase def test_assign_named_capture_to_reserved_word /(?<nil>.)/ =~ "a" assert_not_include(local_variables, :nil, "[ruby-dev:32675]") + + def (obj = Object.new).test(s, nil: :ng) + /(?<nil>.)/ =~ s + binding.local_variable_get(:nil) + end + assert_equal("b", obj.test("b")) + + tap do |nil: :ng| + /(?<nil>.)/ =~ "c" + assert_equal("c", binding.local_variable_get(:nil)) + end end def test_assign_named_capture_to_const @@ -239,6 +265,27 @@ class TestRegexp < Test::Unit::TestCase assert_equal(re, re.match("foo").regexp) end + def test_match_lambda_multithread + bug17507 = "[ruby-core:101901]" + str = "a-x-foo-bar-baz-z-b" + + worker = lambda do + m = /foo-([A-Za-z0-9_\.]+)-baz/.match(str) + assert_equal("bar", m[1], bug17507) + + # These two lines are needed to trigger the bug + File.exist? "/tmp" + str.gsub(/foo-bar-baz/, "foo-abc-baz") + end + + def self. threaded_test(worker) + 6.times.map {Thread.new {10_000.times {worker.call}}}.each(&:join) + end + + # The bug only occurs in a method calling a block/proc/lambda + threaded_test(worker) + end + def test_source bug5484 = '[ruby-core:40364]' assert_equal('', //.source) @@ -420,6 +467,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 @@ -447,6 +495,40 @@ class TestRegexp < Test::Unit::TestCase assert_equal("foobarbaz", m.string) end + def test_match_matchsubstring + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal("HX1138", m.match(0)) + assert_equal("8", m.match(4)) + assert_nil(m.match(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal("\u3043", m.match(1)) + assert_nil(m.match(2)) + assert_equal("\u3044", m.match(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal("h", m.match(:foo)) + assert_nil(m.match(:n)) + assert_equal("oge\u3042", m.match(:bar)) + end + + def test_match_match_length + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal(6, m.match_length(0)) + assert_equal(1, m.match_length(4)) + assert_nil(m.match_length(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal(1, m.match_length(1)) + assert_nil(m.match_length(2)) + assert_equal(1, m.match_length(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal(1, m.match_length(:foo)) + assert_nil(m.match_length(:n)) + assert_equal(4, m.match_length(:bar)) + end + def test_match_inspect m = /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect) @@ -454,7 +536,7 @@ class TestRegexp < Test::Unit::TestCase def test_initialize assert_raise(ArgumentError) { Regexp.new } - assert_equal(/foo/, Regexp.new(/foo/, Regexp::IGNORECASE)) + assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)}) assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding) assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")]) @@ -470,6 +552,24 @@ class TestRegexp < Test::Unit::TestCase assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") } end + def test_match_control_meta_escape + assert_equal(0, /\c\xFF/ =~ "\c\xFF") + assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF") + assert_equal(0, /\C-\xFF/ =~ "\C-\xFF") + assert_equal(0, /\C-\M-\xFF/ =~ "\C-\M-\xFF") + assert_equal(0, /\M-\xFF/ =~ "\M-\xFF") + assert_equal(0, /\M-\C-\xFF/ =~ "\M-\C-\xFF") + assert_equal(0, /\M-\c\xFF/ =~ "\M-\c\xFF") + + assert_nil(/\c\xFE/ =~ "\c\xFF") + assert_nil(/\c\M-\xFE/ =~ "\c\M-\xFF") + assert_nil(/\C-\xFE/ =~ "\C-\xFF") + assert_nil(/\C-\M-\xFE/ =~ "\C-\M-\xFF") + assert_nil(/\M-\xFE/ =~ "\M-\xFF") + assert_nil(/\M-\C-\xFE/ =~ "\M-\C-\xFF") + assert_nil(/\M-\c\xFE/ =~ "\M-\c\xFF") + end + def test_unescape assert_raise(ArgumentError) { s = '\\'; /#{ s }/ } assert_equal(/\xFF/n, /#{ s="\\xFF" }/n) @@ -546,7 +646,10 @@ class TestRegexp < Test::Unit::TestCase assert_equal("bc", /../.match('abc', -2)[0]) assert_nil(/../.match("abc", -4)) assert_nil(/../.match("abc", 4)) - assert_equal('\x', /../n.match("\u3042" + '\x', 1)[0]) + + # use eval because only one warning is shown for the same regexp literal + pat = eval('/../n') + assert_equal('\x', assert_warning(/binary regexp/) {pat.match("\u3042" + '\x', 1)}[0]) r = nil /.../.match("abc") {|m| r = m[0] } @@ -622,7 +725,7 @@ class TestRegexp < Test::Unit::TestCase def test_dup assert_equal(//, //.dup) - assert_raise(TypeError) { //.instance_eval { initialize_copy(nil) } } + assert_raise(TypeError) { //.dup.instance_eval { initialize_copy(nil) } } end def test_regsub @@ -646,21 +749,10 @@ class TestRegexp < Test::Unit::TestCase assert_equal('foobazquux/foobazquux', result, bug8856) end - def test_KCODE - assert_nil($KCODE) - assert_nothing_raised { $KCODE = nil } - assert_equal(false, $=) - assert_nothing_raised { $= = nil } - end - - def test_KCODE_warning - assert_warning(/variable \$KCODE is no longer effective; ignored/) { $KCODE = nil } - assert_warning(/variable \$KCODE is no longer effective/) { $KCODE = nil } - end - - def test_ignorecase_warning - assert_warning(/variable \$= is no longer effective; ignored/) { $= = nil } - assert_warning(/variable \$= is no longer effective/) { $= } + def test_ignorecase + v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= } + assert_equal(false, v) + assert_deprecated_warning(/variable \$= is no longer effective; ignored/) { $= = nil } end def test_match_setter @@ -676,11 +768,16 @@ class TestRegexp < Test::Unit::TestCase test = proc {|&blk| "abc".sub("a", ""); blk.call($~) } bug10877 = '[ruby-core:68209] [Bug #10877]' + bug18160 = '[Bug #18160]' test.call {|m| assert_raise_with_message(IndexError, /foo/, bug10877) {m["foo"]} } key = "\u{3042}" [Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc| idx = key.encode(enc) - test.call {|m| assert_raise_with_message(IndexError, /#{idx}/, bug10877) {m[idx]} } + pat = /#{idx}/ + test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} } end test.call {|m| assert_equal(/a/, m.regexp) } test.call {|m| assert_equal("abc", m.string) } @@ -722,11 +819,13 @@ class TestRegexp < Test::Unit::TestCase end def test_rindex_regexp - assert_equal(3, "foobarbaz\u3042".rindex(/b../n, 5)) + # use eval because only one warning is shown for the same regexp literal + pat = eval('/b../n') + assert_equal(3, assert_warning(/binary regexp/) {"foobarbaz\u3042".rindex(pat, 5)}) end def assert_regexp(re, ss, fs = [], msg = nil) - re = Regexp.new(re) unless re.is_a?(Regexp) + re = EnvUtil.suppress_warning {Regexp.new(re)} unless re.is_a?(Regexp) ss = [ss] unless ss.is_a?(Array) ss.each do |e, s| s ||= e @@ -759,7 +858,7 @@ class TestRegexp < Test::Unit::TestCase check(/\A\80\z/, "80", ["\100", ""]) check(/\A\77\z/, "?") check(/\A\78\z/, "\7" + '8', ["\100", ""]) - check(eval('/\A\Qfoo\E\z/'), "QfooE") + check(assert_warning(/Unknown escape/) {eval('/\A\Qfoo\E\z/')}, "QfooE") check(/\Aa++\z/, "aaa") check('\Ax]\z', "x]") check(/x#foo/x, "x", "#foo") @@ -803,8 +902,8 @@ class TestRegexp < Test::Unit::TestCase check(/^(A+|B(?>\g<1>)*)[AC]$/, %w(AAAC BBBAAAAC), %w(BBBAAA)) check(/^()(?>\g<1>)*$/, "", "a") check(/^(?>(?=a)(#{ "a" * 1000 }|))++$/, ["a" * 1000, "a" * 2000, "a" * 3000], ["", "a" * 500, "b" * 1000]) - check(eval('/^(?:a?)?$/'), ["", "a"], ["aa"]) - check(eval('/^(?:a+)?$/'), ["", "a", "aa"], ["ab"]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a?)?$/')}, ["", "a"], ["aa"]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a+)?$/')}, ["", "a", "aa"], ["ab"]) check(/^(?:a?)+?$/, ["", "a", "aa"], ["ab"]) check(/^a??[ab]/, [["a", "a"], ["a", "aa"], ["b", "b"], ["a", "ab"]], ["c"]) check(/^(?:a*){3,5}$/, ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"], ["b"]) @@ -933,13 +1032,13 @@ class TestRegexp < Test::Unit::TestCase def test_posix_bracket check(/\A[[:alpha:]0]\z/, %w(0 a), %w(1 .)) - check(eval('/\A[[:^alpha:]0]\z/'), %w(0 1 .), "a") - check(eval('/\A[[:alpha\:]]\z/'), %w(a l p h a :), %w(b 0 1 .)) - check(eval('/\A[[:alpha:foo]0]\z/'), %w(0 a), %w(1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:^alpha:]0]\z/')}, %w(0 1 .), "a") + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha\:]]\z/')}, %w(a l p h a :), %w(b 0 1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha:foo]0]\z/')}, %w(0 a), %w(1 .)) check(/\A[[:xdigit:]&&[:alpha:]]\z/, "a", %w(g 0)) check('\A[[:abcdefghijklmnopqrstu:]]+\z', "[]") failcheck('[[:alpha') - failcheck('[[:alpha:') + assert_warning(/duplicated range/) {failcheck('[[:alpha:')} failcheck('[[:alp:]]') assert_match(/\A[[:digit:]]+\z/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19") @@ -1106,13 +1205,17 @@ class TestRegexp < Test::Unit::TestCase end def test_regexp_popped - assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } - assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } + EnvUtil.suppress_warning do + assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } + assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } + end end def test_invalid_fragment bug2547 = '[ruby-core:27374]' - assert_raise(SyntaxError, bug2547) {eval('/#{"\\\\"}y/')} + assert_raise(SyntaxError, bug2547) do + assert_warning(/ignored/) {eval('/#{"\\\\"}y/')} + end end def test_dup_warn @@ -1152,7 +1255,8 @@ class TestRegexp < Test::Unit::TestCase def test_raw_hyphen_and_tk_char_type_after_range bug6853 = '[ruby-core:47115]' # use Regexp.new instead of literal to ignore a parser warning. - check(Regexp.new('[0-1-\\s]'), [' ', '-'], ['2', 'a'], bug6853) + re = assert_warning(/without escape/) {Regexp.new('[0-1-\\s]')} + check(re, [' ', '-'], ['2', 'a'], bug6853) end def test_error_message_on_failed_conversion @@ -1188,6 +1292,20 @@ class TestRegexp < Test::Unit::TestCase } end + def test_quantifier_reduction + assert_equal('aa', eval('/(a+?)*/').match('aa')[0]) + assert_equal('aa', eval('/(?:a+?)*/').match('aa')[0]) + + quantifiers = %w'? * + ?? *? +?' + quantifiers.product(quantifiers) do |q1, q2| + EnvUtil.suppress_warning do + r1 = eval("/(a#{q1})#{q2}/").match('aa')[0] + r2 = eval("/(?:a#{q1})#{q2}/").match('aa')[0] + assert_equal(r1, r2) + end + end + end + def test_once pr1 = proc{|i| /#{i}/o} assert_equal(/0/, pr1.call(0)) @@ -1273,6 +1391,27 @@ class TestRegexp < Test::Unit::TestCase assert_nil($1) end + def test_backref_overrun + assert_raise_with_message(SyntaxError, /invalid backref number/) do + eval(%["".match(/(())(?<X>)((?(90000)))/)]) + end + 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 + + 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 + # 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) diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 05dc18cd17..a71fe0932e 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 @@ -88,6 +105,7 @@ class TestRequire < Test::Unit::TestCase end def prepare_require_path(dir, encoding) + require 'enc/trans/single_byte' Dir.mktmpdir {|tmp| begin require_path = File.join(tmp, dir, 'foo.rb').encode(encoding) @@ -199,6 +217,7 @@ class TestRequire < Test::Unit::TestCase end def assert_syntax_error_backtrace + loaded_features = $LOADED_FEATURES.dup Dir.mktmpdir do |tmp| req = File.join(tmp, "test.rb") File.write(req, ",\n") @@ -208,12 +227,20 @@ class TestRequire < Test::Unit::TestCase assert_not_nil(bt = e.backtrace, "no backtrace") assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}, proc {bt.inspect}) end + $LOADED_FEATURES.replace loaded_features end def test_require_syntax_error assert_syntax_error_backtrace {|req| require req} end + def test_require_syntax_error_rescued + assert_syntax_error_backtrace do |req| + assert_raise_with_message(SyntaxError, /unexpected/) {require req} + require req + end + end + def test_load_syntax_error assert_syntax_error_backtrace {|req| load req} end @@ -357,19 +384,51 @@ 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) path = nil - Tempfile.create([base, ".rb"]) do |t| - path = t.path - + Dir.mktmpdir do |dir| + path = File.join(dir, base+".rb") assert_raise_with_message(LoadError, /#{base}/) { - load(File.join(File.dirname(path), base)) + load(File.join(dir, base)) } - t.puts "warn 'ok'" - t.close + File.open(path, "w+b") do |t| + t.puts "warn 'ok'" + end assert_include(path, base) assert_warn("ok\n", bug) { assert_nothing_raised(LoadError, bug) { @@ -381,6 +440,8 @@ class TestRequire < Test::Unit::TestCase def test_relative load_path = $:.dup + loaded_featrures = $LOADED_FEATURES.dup + $:.delete(".") Dir.mktmpdir do |tmp| Dir.chdir(tmp) do @@ -400,6 +461,7 @@ class TestRequire < Test::Unit::TestCase end ensure $:.replace(load_path) if load_path + $LOADED_FEATURES.replace loaded_featrures end def test_relative_symlink @@ -421,6 +483,32 @@ class TestRequire < Test::Unit::TestCase } end + def test_relative_symlink_realpath + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + Dir.mkdir "a" + File.open("a/a.rb", "w") {|f| f.puts 'require_relative "b"' } + File.open("a/b.rb", "w") {|f| f.puts '$t += 1' } + Dir.mkdir "b" + File.binwrite("c.rb", <<~RUBY) + $t = 0 + $:.unshift(File.expand_path('../b', __FILE__)) + require "b" + require "a" + print $t + RUBY + begin + File.symlink("../a/a.rb", "b/a.rb") + File.symlink("../a/b.rb", "b/b.rb") + 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" + end + } + } + end + def test_frozen_loaded_features bug3756 = '[ruby-core:31913]' assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "", @@ -491,9 +579,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 @@ -518,6 +603,28 @@ class TestRequire < Test::Unit::TestCase $".replace(features) end + def test_default_loaded_features_encoding + Dir.mktmpdir {|tmp| + Dir.mkdir("#{tmp}/1") + Dir.mkdir("#{tmp}/2") + File.write("#{tmp}/1/bug18191-1.rb", "") + File.write("#{tmp}/2/bug18191-2.rb", "") + assert_separately(%W[-Eutf-8 -I#{tmp}/1 -], "#{<<~"begin;"}\n#{<<~'end;'}") + tmp = #{tmp.dump}"/2" + begin; + $:.unshift(tmp) + require "bug18191-1" + require "bug18191-2" + encs = [Encoding::US_ASCII, Encoding.find("filesystem")] + message = -> { + require "pp" + {filesystem: encs[1], **$".group_by(&:encoding)}.pretty_inspect + } + assert($".all? {|n| encs.include?(n.encoding)}, message) + end; + } + end + def test_require_changed_current_dir bug7158 = '[ruby-core:47970]' Dir.mktmpdir {|tmp| @@ -698,8 +805,8 @@ class TestRequire < Test::Unit::TestCase assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7530, timeout: 60) begin; PATH = ARGV.shift - THREADS = 4 - ITERATIONS_PER_THREAD = 1000 + THREADS = 30 + ITERATIONS_PER_THREAD = 300 THREADS.times.map { Thread.new do @@ -757,6 +864,8 @@ 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 + Tempfile.create(%w'fifo .rb') {|f| f.close File.unlink(f.path) @@ -824,6 +933,23 @@ class TestRequire < Test::Unit::TestCase } end + def test_provide_in_required_file + paths, loaded = $:.dup, $".dup + Dir.mktmpdir do |tmp| + provide = File.realdirpath("provide.rb", tmp) + File.write(File.join(tmp, "target.rb"), "raise __FILE__\n") + File.write(provide, '$" << '"'target.rb'\n") + $:.replace([tmp]) + assert(require("provide")) + assert(!require("target")) + assert_equal($".pop, provide) + assert_equal($".pop, "target.rb") + end + ensure + $:.replace(paths) + $".replace(loaded) + end + if defined?($LOAD_PATH.resolve_feature_path) def test_resolve_feature_path paths, loaded = $:.dup, $".dup @@ -842,5 +968,9 @@ class TestRequire < Test::Unit::TestCase $:.replace(paths) $".replace(loaded) end + + def test_resolve_feature_path_with_missing_feature + assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso")) + end end end diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb index 4af57173b8..6b2846c8fd 100644 --- a/test/ruby/test_require_lib.rb +++ b/test/ruby/test_require_lib.rb @@ -8,8 +8,7 @@ class TestRequireLib < Test::Unit::TestCase # skip some problems next if %r!/lib/(?:bundler|rubygems)\b! =~ lib next if %r!/lib/(?:debug|mkmf)\.rb\z! =~ lib - # skip because "in `<module:Maker>': undefined method `add_maker' for RSS::Maker:Module (NoMethodError)" - next if %r!/lib/rss\b! =~ lib + next if %r!/lib/irb/ext/tracer\.rb\z! =~ lib # skip many files that almost use no threads next if TEST_RATIO < rand(0.0..1.0) define_method "test_thread_size:#{lib}" do diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 496a51b970..aae2522fc6 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -7,9 +7,13 @@ require 'tempfile' require_relative '../lib/jit_support' class TestRubyOptions < Test::Unit::TestCase + def self.yjit_enabled? = defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + NO_JIT_DESCRIPTION = - if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE - RUBY_DESCRIPTION.sub(/\+JIT /, '') + if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + RUBY_DESCRIPTION.sub(/\+MJIT /, '') + elsif yjit_enabled? # checking -DYJIT_FORCE_ENABLE + RUBY_DESCRIPTION.sub(/\+YJIT /, '') else RUBY_DESCRIPTION end @@ -66,15 +70,48 @@ class TestRubyOptions < Test::Unit::TestCase end end + def test_backtrace_limit + assert_in_out_err(%w(--backtrace-limit), "", [], /missing argument for --backtrace-limit/) + assert_in_out_err(%w(--backtrace-limit= 1), "", [], /missing argument for --backtrace-limit/) + assert_in_out_err(%w(--backtrace-limit=-1), "", [], /wrong limit for backtrace length/) + code = 'def f(n);n > 0 ? f(n-1) : raise;end;f(5)' + assert_in_out_err(%w(--backtrace-limit=1), code, [], + [/.*unhandled exception\n/, /^\tfrom .*\n/, + /^\t \.{3} \d+ levels\.{3}\n/]) + assert_in_out_err(%w(--backtrace-limit=3), code, [], + [/.*unhandled exception\n/, *[/^\tfrom .*\n/]*3, + /^\t \.{3} \d+ levels\.{3}\n/]) + assert_kind_of(Integer, Thread::Backtrace.limit) + assert_in_out_err(%w(--backtrace-limit=1), "p Thread::Backtrace.limit", ['1'], []) + end def test_warning save_rubyopt = ENV['RUBYOPT'] ENV['RUBYOPT'] = nil assert_in_out_err(%w(-W0 -e) + ['p $-W'], "", %w(0), []) assert_in_out_err(%w(-W1 -e) + ['p $-W'], "", %w(1), []) - assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(1), []) + assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(2), []) assert_in_out_err(%w(-W -e) + ['p $-W'], "", %w(2), []) + assert_in_out_err(%w(-We) + ['p $-W'], "", %w(2), []) assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), []) + assert_in_out_err(%w(-W:deprecated -e) + ['p Warning[:deprecated]'], "", %w(true), []) + assert_in_out_err(%w(-W:no-deprecated -e) + ['p Warning[:deprecated]'], "", %w(false), []) + assert_in_out_err(%w(-W:experimental -e) + ['p Warning[:experimental]'], "", %w(true), []) + assert_in_out_err(%w(-W:no-experimental -e) + ['p Warning[:experimental]'], "", %w(false), []) + assert_in_out_err(%w(-W:qux), "", [], /unknown warning category: `qux'/) + assert_in_out_err(%w(-w -e) + ['p Warning[:deprecated]'], "", %w(true), []) + assert_in_out_err(%w(-W -e) + ['p Warning[:deprecated]'], "", %w(true), []) + assert_in_out_err(%w(-We) + ['p Warning[:deprecated]'], "", %w(true), []) + assert_in_out_err(%w(-e) + ['p Warning[:deprecated]'], "", %w(false), []) + code = 'puts "#{$VERBOSE}:#{Warning[:deprecated]}:#{Warning[:experimental]}"' + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) do |t| + t.puts code + t.close + assert_in_out_err(["-r#{t.path}", '-e', code], "", %w(false:false:true false:false:true), []) + assert_in_out_err(["-r#{t.path}", '-w', '-e', code], "", %w(true:true:true true:true:true), []) + assert_in_out_err(["-r#{t.path}", '-W:deprecated', '-e', code], "", %w(false:true:true false:true:true), []) + assert_in_out_err(["-r#{t.path}", '-W:no-experimental', '-e', code], "", %w(false:false:false false:false:false), []) + end ensure ENV['RUBYOPT'] = save_rubyopt end @@ -100,16 +137,18 @@ 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 private_constant :VERSION_PATTERN_WITH_JIT def test_verbose - assert_in_out_err(["-vve", ""]) do |r, e| + assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e| assert_match(VERSION_PATTERN, r[0]) - if RubyVM::MJIT.enabled? && !mjit_force_enabled? # checking -DMJIT_FORCE_ENABLE + if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? && !mjit_force_enabled? # checking -DMJIT_FORCE_ENABLE + assert_equal(NO_JIT_DESCRIPTION, r[0]) + 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]) @@ -170,9 +209,12 @@ class TestRubyOptions < Test::Unit::TestCase end def test_version - assert_in_out_err(%w(--version)) do |r, e| + env = {'RUBY_YJIT_ENABLE' => nil} # unset in children + assert_in_out_err([env, '--version']) do |r, e| assert_match(VERSION_PATTERN, r[0]) - if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + if ENV['RUBY_YJIT_ENABLE'] == '1' + assert_equal(NO_JIT_DESCRIPTION, r[0]) + elsif defined?(RubyVM::MJIT) && RubyVM::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]) @@ -181,13 +223,19 @@ class TestRubyOptions < Test::Unit::TestCase end return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' + 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 RUBY_PLATFORM.start_with?('x86_64-') && RUBY_PLATFORM !~ /mswin|mingw|msys/), ].each do |args| - assert_in_out_err(args) do |r, e| + assert_in_out_err([env] + args) do |r, e| assert_match(VERSION_PATTERN, r[0]) assert_match(NO_JIT_DESCRIPTION, r[0]) assert_equal([], e) @@ -196,16 +244,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 RUBY_PLATFORM.start_with?('x86_64-') && RUBY_PLATFORM !~ /mswin|mingw|msys/), ].each do |args| - assert_in_out_err(args) do |r, e| + assert_in_out_err([env] + args) do |r, e| assert_match(VERSION_PATTERN_WITH_JIT, r[0]) - if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE assert_equal(RUBY_DESCRIPTION, r[0]) else - assert_equal(EnvUtil.invoke_ruby(['--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 @@ -280,7 +333,7 @@ class TestRubyOptions < Test::Unit::TestCase /unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/) if /mswin|mingw|aix|android/ =~ RUBY_PLATFORM && - (str = "\u3042".force_encoding(Encoding.find("locale"))).valid_encoding? + (str = "\u3042".force_encoding(Encoding.find("external"))).valid_encoding? # This result depends on locale because LANG=C doesn't affect locale # on Windows. # On AIX, the source encoding of stdin with LANG=C is ISO-8859-1, @@ -328,6 +381,20 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%w(), "p $VERBOSE", ["true"]) assert_in_out_err(%w(-W1), "p $VERBOSE", ["false"]) assert_in_out_err(%w(-W0), "p $VERBOSE", ["nil"]) + assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"]) + assert_in_out_err(%w(-W0), "p Warning[:deprecated]", ["false"]) + assert_in_out_err(%w(-W1), "p Warning[:deprecated]", ["false"]) + assert_in_out_err(%w(-W2), "p Warning[:deprecated]", ["true"]) + ENV['RUBYOPT'] = '-W:deprecated' + assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"]) + ENV['RUBYOPT'] = '-W:no-deprecated' + assert_in_out_err(%w(), "p Warning[:deprecated]", ["false"]) + ENV['RUBYOPT'] = '-W:experimental' + assert_in_out_err(%w(), "p Warning[:experimental]", ["true"]) + ENV['RUBYOPT'] = '-W:no-experimental' + assert_in_out_err(%w(), "p Warning[:experimental]", ["false"]) + ENV['RUBYOPT'] = '-W:qux' + assert_in_out_err(%w(), "", [], /unknown warning category: `qux'/) ensure if rubyopt_orig ENV['RUBYOPT'] = rubyopt_orig @@ -479,16 +546,18 @@ class TestRubyOptions < Test::Unit::TestCase ["case nil; when true", "end"], ["if false;", "end", "if true\nelse ", "end"], ["else", " end", "_ = if true\n"], + ["begin\n def f() = nil", "end"], + ["begin\n def self.f() = nil", "end"], ].each do |b, e = 'end', pre = nil, post = nil| src = ["#{pre}#{b}\n", " #{e}\n#{post}"] k = b[/\A\s*(\S+)/, 1] e = e[/\A\s*(\S+)/, 1] - n = 2 - n += pre.count("\n") if pre + n = 1 + src[0].count("\n") + n1 = 1 + (pre ? pre.count("\n") : 0) a.for("no directives with #{src}") do - err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n-1}"] + err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"] t.rewind t.truncate(0) t.puts src @@ -507,7 +576,7 @@ class TestRubyOptions < Test::Unit::TestCase end a.for("false and true directives with #{src}") do - err = ["#{t.path}:#{n+2}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n+1}"] + err = ["#{t.path}:#{n+2}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1+2}"] t.rewind t.truncate(0) t.puts "# -*- warn-indent: false -*-" @@ -529,7 +598,7 @@ class TestRubyOptions < Test::Unit::TestCase end a.for("BOM with #{src}") do - err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n-1}"] + err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"] t.rewind t.truncate(0) t.print "\u{feff}" @@ -691,6 +760,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, @@ -703,6 +773,8 @@ class TestRubyOptions < Test::Unit::TestCase end def assert_segv(args, message=nil) + skip if ENV['RUBY_ON_BUG'] + test_stdin = "" opt = SEGVTest::ExecOptions.dup list = SEGVTest::ExpectedStderrList @@ -821,11 +893,11 @@ class TestRubyOptions < Test::Unit::TestCase def test_command_line_glob_nonascii bug10555 = '[ruby-dev:48752] [Bug #10555]' name = "\u{3042}.txt" - expected = name.encode("locale") rescue "?.txt" + expected = name.encode("external") rescue "?.txt" with_tmpchdir do |dir| open(name, "w") {} assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [], - bug10555, encoding: "locale") + bug10555, encoding: "external") end end @@ -860,7 +932,7 @@ class TestRubyOptions < Test::Unit::TestCase with_tmpchdir do |dir| Ougai.each {|f| open(f, "w") {}} assert_in_out_err(["-Eutf-8", "-e", "puts ARGV", "*"], "", Ougai, encoding: "utf-8") - ougai = Ougai.map {|f| f.encode("locale", replace: "?")} + ougai = Ougai.map {|f| f.encode("external", replace: "?")} assert_in_out_err(["-e", "puts ARGV", "*.txt"], "", ougai) end end @@ -996,11 +1068,11 @@ class TestRubyOptions < Test::Unit::TestCase err = !freeze ? [] : debug ? with_debug_pat : wo_debug_pat [ ['"foo" << "bar"', err], - ['"foo#{123}bar" << "bar"', err], + ['"foo#{123}bar" << "bar"', []], ['+"foo#{123}bar" << "bar"', []], - ['-"foo#{123}bar" << "bar"', freeze && debug ? with_debug_pat : wo_debug_pat], + ['-"foo#{123}bar" << "bar"', wo_debug_pat], ].each do |code, expected| - assert_in_out_err(opt, code, [], expected, [opt, code]) + assert_in_out_err(opt, code, [], expected, "#{opt} #{code}") end end end @@ -1038,6 +1110,11 @@ class TestRubyOptions < Test::Unit::TestCase end end + def test_rubylib_invalid_encoding + env = {"RUBYLIB"=>"\xFF", "LOCALE"=>"en_US.UTF-8", "LC_ALL"=>"en_US.UTF-8"} + assert_ruby_status([env, "-e;"]) + end + def test_null_script skip "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL) assert_in_out_err([IO::NULL], success: true) @@ -1047,7 +1124,7 @@ class TestRubyOptions < Test::Unit::TestCase # 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/) env = { 'MJIT_SEARCH_BUILD_DIR' => 'true' } - assert_in_out_err([env, "--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 @@ -1056,4 +1133,8 @@ class TestRubyOptions < Test::Unit::TestCase 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 end diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb index 7673d8dfbe..7d4588a165 100644 --- a/test/ruby/test_rubyvm.rb +++ b/test/ruby/test_rubyvm.rb @@ -4,15 +4,68 @@ require 'test/unit' class TestRubyVM < Test::Unit::TestCase def test_stat assert_kind_of Hash, RubyVM.stat - assert_kind_of Integer, RubyVM.stat[:global_method_state] + assert_kind_of Integer, RubyVM.stat[:global_constant_state] RubyVM.stat(stat = {}) assert_not_empty stat - assert_equal stat[:global_method_state], RubyVM.stat(:global_method_state) + assert_equal stat[:global_constant_state], RubyVM.stat(:global_constant_state) end def test_stat_unknown assert_raise(ArgumentError){ RubyVM.stat(:unknown) } assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")} end + + def parse_and_compile + script = <<~RUBY + _a = 1 + def foo + _b = 2 + end + 1.times{ + _c = 3 + } + RUBY + + ast = RubyVM::AbstractSyntaxTree.parse(script) + iseq = RubyVM::InstructionSequence.compile(script) + + [ast, iseq] + end + + def test_keep_script_lines + prev_conf = RubyVM.keep_script_lines + + # keep + RubyVM.keep_script_lines = true + + ast, iseq = *parse_and_compile + + lines = ast.script_lines + assert_equal Array, lines.class + + lines = iseq.script_lines + assert_equal Array, lines.class + iseq.each_child{|child| + assert_equal lines, child.script_lines + } + assert lines.frozen? + + # don't keep + RubyVM.keep_script_lines = false + + ast, iseq = *parse_and_compile + + lines = ast.script_lines + assert_equal nil, lines + + lines = iseq.script_lines + assert_equal nil, lines + iseq.each_child{|child| + assert_equal lines, child.script_lines + } + + ensure + RubyVM.keep_script_lines = prev_conf + end end diff --git a/test/ruby/test_rubyvm_mjit.rb b/test/ruby/test_rubyvm_jit.rb index ef7475670c..a3558d791c 100644 --- a/test/ruby/test_rubyvm_mjit.rb +++ b/test/ruby/test_rubyvm_jit.rb @@ -4,7 +4,7 @@ require_relative '../lib/jit_support' return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' -class TestRubyVMMJIT < Test::Unit::TestCase +class TestRubyVMJIT < Test::Unit::TestCase include JITSupport def setup diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 316e14e1ef..73d8aee6a2 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -3,17 +3,21 @@ require 'test/unit' class TestSetTraceFunc < Test::Unit::TestCase def setup - @original_compile_option = RubyVM::InstructionSequence.compile_option - RubyVM::InstructionSequence.compile_option = { - :trace_instruction => true, - :specialized_instruction => false - } + if defined?(RubyVM) + @original_compile_option = RubyVM::InstructionSequence.compile_option + RubyVM::InstructionSequence.compile_option = { + :trace_instruction => true, + :specialized_instruction => false + } + end @target_thread = Thread.current end def teardown set_trace_func(nil) - RubyVM::InstructionSequence.compile_option = @original_compile_option + if defined?(RubyVM) + RubyVM::InstructionSequence.compile_option = @original_compile_option + end @target_thread = nil end @@ -46,6 +50,29 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal([], events) 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__}" @@ -137,6 +164,10 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal(["c-call", 9, :set_trace_func, Kernel], events.shift) assert_equal([], events) + + self.class.class_eval do + remove_const :Foo + end end def test_return # [ruby-dev:38701] @@ -362,6 +393,11 @@ class TestSetTraceFunc < Test::Unit::TestCase end assert_equal([], events[:set]) assert_equal([], events[:add]) + + # cleanup + self.class.class_eval do + remove_const :ThreadTraceInnerClass + end end def test_trace_defined_method @@ -380,7 +416,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) @@ -472,8 +508,6 @@ class TestSetTraceFunc < Test::Unit::TestCase [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], - [:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing], - [:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self], [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1], [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], @@ -499,8 +533,6 @@ class TestSetTraceFunc < Test::Unit::TestCase [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], - [:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing], - [:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy], [: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], @@ -555,6 +587,16 @@ 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 trace_by_set_trace_func events = [] trace = nil @@ -597,8 +639,6 @@ class TestSetTraceFunc < Test::Unit::TestCase [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], - [:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing], - [:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self], [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1], [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], @@ -624,8 +664,6 @@ class TestSetTraceFunc < Test::Unit::TestCase [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], - [:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing], - [:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy], [: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], @@ -826,6 +864,56 @@ class TestSetTraceFunc < Test::Unit::TestCase } end + def test_tracepoint_attr + c = Class.new do + attr_accessor :x + alias y x + alias y= x= + end + obj = c.new + + ar_meth = obj.method(:x) + aw_meth = obj.method(:x=) + aar_meth = obj.method(:y) + aaw_meth = obj.method(:y=) + events = [] + trace = TracePoint.new(:c_call, :c_return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + next if tp.method_id == :call + case tp.event + when :c_call + assert_raise(RuntimeError) {tp.return_value} + events << [tp.event, tp.method_id, tp.callee_id] + when :c_return + events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] + end + } + test_proc = proc do + obj.x = 1 + obj.x + obj.y = 2 + obj.y + aw_meth.call(1) + ar_meth.call + aaw_meth.call(2) + aar_meth.call + end + test_proc.call # populate call caches + trace.enable(&test_proc) + expected = [ + [:c_call, :x=, :x=], + [:c_return, :x=, :x=, 1], + [:c_call, :x, :x], + [:c_return, :x, :x, 1], + [:c_call, :x=, :y=], + [:c_return, :x=, :y=, 2], + [:c_call, :x, :y], + [:c_return, :x, :y, 2], + ] + assert_equal(expected*2, events) + end + class XYZZYException < Exception; end def method_test_tracepoint_raised_exception err raise err @@ -932,9 +1020,9 @@ class TestSetTraceFunc < Test::Unit::TestCase when :line assert_match(/ in /, str) when :call, :c_call - assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11> + assert_match(/call \`/, str) # #<TracePoint:c_call `inherited' ../trunk/test.rb:11> when :return, :c_return - assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3> + assert_match(/return \`/, str) # #<TracePoint:return `m' ../trunk/test.rb:3> when /thread/ assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>> else @@ -977,20 +1065,24 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_tracepoint_with_multithreads assert_nothing_raised do - TracePoint.new{ + TracePoint.new(:line){ 10.times{ Thread.pass } }.enable do (1..10).map{ Thread.new{ - 1000.times{ + 1_000.times{|i| + _a = i } } }.each{|th| th.join } end + _a = 1 + _b = 2 + _c = 3 # to make sure the deletion of unused TracePoints end end @@ -1588,6 +1680,33 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal ev, :fiber_switch } + # test for raise into resumable fiber + evs = [] + f = nil + TracePoint.new(:raise, :fiber_switch){|tp| + next unless target_thread? + evs << [tp.event, Fiber.current] + }.enable{ + f = Fiber.new{ + Fiber.yield # will raise + Fiber.yield # unreachable + } + begin + f.resume + f.raise StopIteration + rescue StopIteration + evs << :rescued + end + } + assert_equal [:fiber_switch, f], evs[0], "initial resume" + assert_equal [:fiber_switch, Fiber.current], evs[1], "Fiber.yield" + assert_equal [:fiber_switch, f], evs[2], "fiber.raise" + assert_equal [:raise, f], evs[3], "fiber.raise" + assert_equal [:fiber_switch, Fiber.current], evs[4], "terminated with raise" + assert_equal [:raise, Fiber.current], evs[5], "terminated with raise" + assert_equal :rescued, evs[6] + assert_equal 7, evs.size + # test for transfer evs = [] TracePoint.new(:fiber_switch){|tp| @@ -1610,6 +1729,41 @@ class TestSetTraceFunc < Test::Unit::TestCase evs.each{|ev| assert_equal ev, :fiber_switch } + + # test for raise and from transferring fibers + evs = [] + f1 = f2 = nil + TracePoint.new(:raise, :fiber_switch){|tp| + next unless target_thread? + evs << [tp.event, Fiber.current] + }.enable{ + f1 = Fiber.new{ + f2.transfer + f2.raise ScriptError + Fiber.yield :ok + } + f2 = Fiber.new{ + f1.transfer + f1.transfer + } + begin + f1.resume + rescue ScriptError + evs << :rescued + end + } + assert_equal [:fiber_switch, f1], evs[0], "initial resume" + assert_equal [:fiber_switch, f2], evs[1], "f2.transfer" + assert_equal [:fiber_switch, f1], evs[2], "f1.transfer" + assert_equal [:fiber_switch, f2], evs[3], "f2.raise ScriptError" + assert_equal [:raise, f2], evs[4], "f2.raise ScriptError" + assert_equal [:fiber_switch, f1], evs[5], "f2 unhandled exception" + assert_equal [:raise, f1], evs[6], "f2 unhandled exception" + assert_equal [:fiber_switch, Fiber.current], evs[7], "f1 unhandled exception" + assert_equal [:raise, Fiber.current], evs[8], "f1 unhandled exception" + assert_equal :rescued, evs[9], "rescued everything" + assert_equal 10, evs.size + end def test_tracepoint_callee_id @@ -1672,7 +1826,7 @@ class TestSetTraceFunc < Test::Unit::TestCase TracePoint.new(:return, &capture_events).enable{ o.alias_m } - assert_equal [[:return, :m, :alias_m]], events + assert_equal [[:return, :tap, :tap], [:return, :m, :alias_m]], events events.clear o = Class.new{ @@ -1696,13 +1850,13 @@ class TestSetTraceFunc < Test::Unit::TestCase events.clear o = Class.new{ - alias alias_tap tap - define_method(:m){alias_tap{return}} + alias alias_singleton_class singleton_class + define_method(:m){alias_singleton_class} }.new TracePoint.new(:c_return, &capture_events).enable{ o.m } - assert_equal [[:c_return, :tap, :alias_tap]], events + assert_equal [[:c_return, :singleton_class, :alias_singleton_class]], events events.clear c = Class.new{ @@ -1820,7 +1974,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 @@ -1841,27 +1995,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 @@ -1917,7 +2088,7 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_thread_add_trace_func events = [] base_line = __LINE__ - q = Queue.new + q = Thread::Queue.new t = Thread.new{ Thread.current.add_trace_func proc{|ev, file, line, *args| events << [ev, line] @@ -1943,7 +2114,7 @@ class TestSetTraceFunc < Test::Unit::TestCase # other thread events = [] - m2t_q = Queue.new + m2t_q = Thread::Queue.new t = Thread.new{ Thread.current.abort_on_exception = true @@ -1958,7 +2129,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 RubyVM::MJIT.enabled? + sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? t.add_trace_func proc{|ev, file, line, *args| if file == __FILE__ @@ -2170,6 +2341,31 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal 'target_line is specified, but line event is not specified', e.message end + def test_tracepoint_enable_with_target_line_two_times + events = [] + line_0 = __LINE__ + code1 = proc{ + events << 1 # tp1 + events << 2 + events << 3 # tp2 + } + + tp1 = TracePoint.new(:line) do |tp| + events << :tp1 + end + tp2 = TracePoint.new(:line) do |tp| + events << :tp2 + end + + tp1.enable(target: code1, target_line: line_0 + 2) do + tp2.enable(target: code1, target_line: line_0 + 4) do + # two hooks + code1.call + end + end + assert_equal [:tp1, 1, 2, :tp2, 3], events + end + def test_script_compiled events = [] tp = TracePoint.new(:script_compiled){|tp| @@ -2188,10 +2384,19 @@ class TestSetTraceFunc < Test::Unit::TestCase [__FILE__+"/instance_eval", eval_script], [__FILE__+"/class_eval", eval_script], ], events + events.clear + tp.enable{ + begin + eval('a=') + rescue SyntaxError + end + } + assert_equal [], events, 'script_compiled event should not be invoked on compile error' skip "TODO: test for requires" + events.clear tp.enable{ require '' require_relative '' @@ -2219,8 +2424,8 @@ class TestSetTraceFunc < Test::Unit::TestCase events << Thread.current end - q1 = Queue.new - q2 = Queue.new + q1 = Thread::Queue.new + q2 = Thread::Queue.new th = Thread.new{ q1 << :ok; q2.pop @@ -2238,6 +2443,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 @@ -2270,4 +2568,58 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_stat_exists assert_instance_of Hash, TracePoint.stat end + + def test_tracepoint_opt_invokebuiltin_delegate_leave + code = 'puts RubyVM::InstructionSequence.of("\x00".method(:unpack)).disasm' + out = EnvUtil.invoke_ruby(['-e', code], '', true).first + assert_match(/^0000 opt_invokebuiltin_delegate_leave /, out) + + event = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first) + TracePoint.new(:return) do |tp| + p [tp.event, tp.method_id] + end.enable do + "\x00".unpack("c") + end + EOS + assert_equal [:return, :unpack], event + end + + def test_while_in_while + lines = [] + + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + n = 3 + while n > 0 + n -= 1 while n > 0 + end + } + 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 end diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb index 7986e9d141..f2e73eb58d 100644 --- a/test/ruby/test_sprintf.rb +++ b/test/ruby/test_sprintf.rb @@ -528,19 +528,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_stack.rb b/test/ruby/test_stack.rb new file mode 100644 index 0000000000..763aeb6bc2 --- /dev/null +++ b/test/ruby/test_stack.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestStack < Test::Unit::TestCase + LARGE_VM_STACK_SIZE = 1024*1024*5 + LARGE_MACHINE_STACK_SIZE = 1024*1024*10 + + def initialize(*) + super + + @h_default = nil + @h_0 = nil + @h_large = nil + end + + def invoke_ruby script, vm_stack_size: nil, machine_stack_size: nil + env = {} + env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size + env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size + env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] + + stdout, stderr, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, timeout: 30) + assert(!status.signaled?, FailDesc[status, nil, stderr]) + + return stdout + end + + def h_default + @h_default ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS')) + end + + def h_0 + @h_0 ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS', + vm_stack_size: 0, + machine_stack_size: 0 + )) + end + + def h_large + @h_large ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS', + vm_stack_size: LARGE_VM_STACK_SIZE, + machine_stack_size: LARGE_MACHINE_STACK_SIZE + )) + end + + def test_relative_stack_sizes + assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size]) + end + + def test_vm_stack_size + script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume' + + size_default = invoke_ruby(script).bytesize + assert_operator(size_default, :>, 0) + + size_0 = invoke_ruby(script, vm_stack_size: 0).bytesize + assert_operator(size_default, :>, size_0) + + size_large = invoke_ruby(script, vm_stack_size: LARGE_VM_STACK_SIZE).bytesize + assert_operator(size_default, :<, size_large) + end + + # Depending on OS, machine stack size may not change size. + def test_machine_stack_size + return if /mswin|mingw/ =~ RUBY_PLATFORM + + script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume' + + vm_stack_size = 1024 * 1024 + size_default = invoke_ruby(script, vm_stack_size: vm_stack_size).bytesize + + size_0 = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: 0).bytesize + assert_operator(size_default, :>=, size_0) + + size_large = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: LARGE_MACHINE_STACK_SIZE).bytesize + assert_operator(size_default, :<=, size_large) + end +end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 0276d6a14d..6c00aa15f9 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2,8 +2,6 @@ require 'test/unit' class TestString < Test::Unit::TestCase - ENUMERATOR_WANTARRAY = RUBY_VERSION >= "3.0.0" - WIDE_ENCODINGS = [ Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE, @@ -107,6 +105,16 @@ PREP CODE end + # Bug #18154 + def test_initialize_nofree_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {0.to_s.__send__(:initialize, capacity: 10000)} +1_000.times(&code) +PREP +100_000.times(&code) +CODE + end + def test_AREF # '[]' assert_equal("A", S("AooBar")[0]) assert_equal("B", S("FooBaB")[-1]) @@ -257,6 +265,7 @@ CODE assert_not_equal(S("CAT"), S('cat')) assert_not_equal(S("CaT"), S('cAt')) + assert_not_equal(S("cat\0""dog"), S("cat\0")) o = Object.new def o.to_str; end @@ -387,6 +396,8 @@ CODE end def test_chomp + verbose, $VERBOSE = $VERBOSE, nil + assert_equal(S("hello"), S("hello").chomp("\n")) assert_equal(S("hello"), S("hello\n").chomp("\n")) save = $/ @@ -452,9 +463,12 @@ CODE assert_equal("foo", s.chomp("\n")) ensure $/ = save + $VERBOSE = verbose end def test_chomp! + verbose, $VERBOSE = $VERBOSE, nil + a = S("hello") a.chomp!(S("\n")) @@ -511,6 +525,7 @@ CODE s = S("").freeze assert_raise_with_message(FrozenError, /frozen/) {s.chomp!} + $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state s = S("ax") o = Struct.new(:s).new(s) @@ -519,6 +534,7 @@ CODE "x" end assert_raise_with_message(FrozenError, /frozen/) {s.chomp!(o)} + $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state s = S("hello") assert_equal("hel", s.chomp!('lo')) @@ -569,6 +585,7 @@ CODE assert_equal("foo", s.chomp!("\n")) ensure $/ = save + $VERBOSE = verbose end def test_chop @@ -688,6 +705,7 @@ CODE @cls == String and assert_no_memory_leak([], "s = ''; salt_proc = proc{#{(crypt_supports_des_crypt? ? '..' : good_salt).inspect}}", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; 1000.times { s.crypt(-salt_proc.call).clear } end; @@ -751,6 +769,7 @@ CODE assert_equal(S("hello"), S("hello").downcase) assert_equal(S("hello"), S("HELLO").downcase) assert_equal(S("abc hello 123"), S("abc HELLO 123").downcase) + assert_equal(S("h\0""ello"), S("h\0""ELLO").downcase) end def test_downcase! @@ -763,6 +782,12 @@ CODE a=S("hello") assert_nil(a.downcase!) assert_equal(S("hello"), a) + + a = S("h\0""ELLO") + b = a.dup + assert_equal(S("h\0""ello"), a.downcase!) + assert_equal(S("h\0""ello"), a) + assert_equal(S("h\0""ELLO"), b) end def test_dump @@ -859,6 +884,8 @@ CODE end def test_each + verbose, $VERBOSE = $VERBOSE, nil + save = $/ $/ = "\n" res=[] @@ -878,6 +905,7 @@ CODE assert_equal(S("world"), res[1]) ensure $/ = save + $VERBOSE = verbose end def test_each_byte @@ -896,21 +924,15 @@ CODE s = S("ABC") assert_equal [65, 66, 67], s.bytes - if ENUMERATOR_WANTARRAY - assert_warn(/block not used/) { - assert_equal [65, 66, 67], s.bytes {} - } - else - res = [] - assert_equal s.object_id, s.bytes {|x| res << x }.object_id - assert_equal(65, res[0]) - assert_equal(66, res[1]) - assert_equal(67, res[2]) - s = S("ABC") - res = [] - assert_same s, s.bytes {|x| res << x } - assert_equal [65, 66, 67], res - end + res = [] + assert_equal s.object_id, s.bytes {|x| res << x }.object_id + assert_equal(65, res[0]) + assert_equal(66, res[1]) + assert_equal(67, res[2]) + s = S("ABC") + res = [] + assert_same s, s.bytes {|x| res << x } + assert_equal [65, 66, 67], res end def test_each_codepoint @@ -935,21 +957,15 @@ CODE s = S("\u3042\u3044\u3046") assert_equal [0x3042, 0x3044, 0x3046], s.codepoints - if ENUMERATOR_WANTARRAY - assert_warn(/block not used/) { - assert_equal [0x3042, 0x3044, 0x3046], s.codepoints {} - } - else - res = [] - assert_equal s.object_id, s.codepoints {|x| res << x }.object_id - assert_equal(0x3042, res[0]) - assert_equal(0x3044, res[1]) - assert_equal(0x3046, res[2]) - s = S("ABC") - res = [] - assert_same s, s.codepoints {|x| res << x } - assert_equal [65, 66, 67], res - end + res = [] + assert_equal s.object_id, s.codepoints {|x| res << x }.object_id + assert_equal(0x3042, res[0]) + assert_equal(0x3044, res[1]) + assert_equal(0x3046, res[2]) + s = S("ABC") + res = [] + assert_same s, s.codepoints {|x| res << x } + assert_equal [65, 66, 67], res end def test_each_char @@ -968,17 +984,11 @@ CODE s = S("ABC") assert_equal ["A", "B", "C"], s.chars - if ENUMERATOR_WANTARRAY - assert_warn(/block not used/) { - assert_equal ["A", "B", "C"], s.chars {} - } - else - res = [] - assert_equal s.object_id, s.chars {|x| res << x }.object_id - assert_equal("A", res[0]) - assert_equal("B", res[1]) - assert_equal("C", res[2]) - end + res = [] + assert_equal s.object_id, s.chars {|x| res << x }.object_id + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) end def test_each_grapheme_cluster @@ -1039,22 +1049,18 @@ CODE end assert_equal ["a", "b", "c"], "abc".b.grapheme_clusters - if ENUMERATOR_WANTARRAY - assert_warn(/block not used/) { - assert_equal ["A", "B", "C"], "ABC".grapheme_clusters {} - } - else - s = "ABC".b - res = [] - assert_same s, s.grapheme_clusters {|x| res << x } - assert_equal(3, res.size) - assert_equal("A", res[0]) - assert_equal("B", res[1]) - assert_equal("C", res[2]) - end + s = "ABC".b + res = [] + assert_same s, s.grapheme_clusters {|x| res << x } + assert_equal(3, res.size) + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) end def test_each_line + verbose, $VERBOSE = $VERBOSE, nil + save = $/ $/ = "\n" res=[] @@ -1101,6 +1107,7 @@ CODE end ensure $/ = save + $VERBOSE = verbose end def test_each_line_chomp @@ -1158,16 +1165,10 @@ CODE assert_equal ["hello\n", "world"], s.lines assert_equal ["hello\nworld"], s.lines(nil) - if ENUMERATOR_WANTARRAY - assert_warn(/block not used/) { - assert_equal ["hello\n", "world"], s.lines {} - } - else - res = [] - assert_equal s.object_id, s.lines {|x| res << x }.object_id - assert_equal(S("hello\n"), res[0]) - assert_equal(S("world"), res[1]) - end + res = [] + assert_equal s.object_id, s.lines {|x| res << x }.object_id + assert_equal(S("hello\n"), res[0]) + assert_equal(S("world"), res[1]) end def test_empty? @@ -1179,6 +1180,8 @@ CODE assert_send([S("hello"), :end_with?, S("llo")]) assert_not_send([S("hello"), :end_with?, S("ll")]) assert_send([S("hello"), :end_with?, S("el"), S("lo")]) + assert_send([S("hello"), :end_with?, S("")]) + assert_not_send([S("hello"), :end_with?]) bug5536 = '[ruby-core:40623]' assert_raise(TypeError, bug5536) {S("str").end_with? :not_convertible_to_string} @@ -1331,6 +1334,8 @@ CODE assert_nil("foo".index(//, -100)) assert_nil($~) + + assert_equal(2, S("abcdbce").index(/b\Kc/)) end def test_insert @@ -1503,6 +1508,8 @@ CODE assert_equal(3, "foo".rindex(//)) assert_equal([3, 3], $~.offset(0)) + + assert_equal(5, S("abcdbce").rindex(/b\Kc/)) end def test_rjust @@ -1594,8 +1601,10 @@ CODE a = S("FooBar") if @aref_slicebang_silent assert_nil( a.slice!(6) ) + assert_nil( a.slice!(6r) ) else assert_raise(IndexError) { a.slice!(6) } + assert_raise(IndexError) { a.slice!(6r) } end assert_equal(S("FooBar"), a) @@ -1768,13 +1777,6 @@ CODE GC.start assert_equal([], "".split, bug) end; - - begin - fs = $; - assert_warn(/\$; will be deprecated/) {$; = " "} - ensure - EnvUtil.suppress_warning {$; = fs} - end end def test_split_encoding @@ -1816,6 +1818,11 @@ CODE assert_equal("abc", s) end + def test_split_lookbehind + assert_equal([S("ab"), S("d")], S("abcd").split(/(?<=b)c/)) + assert_equal([S("ab"), S("d")], S("abcd").split(/b\Kc/)) + end + def test_squeeze assert_equal(S("abc"), S("aaabbbbccc").squeeze) assert_equal(S("aa bb cc"), S("aa bb cc").squeeze(S(" "))) @@ -1858,6 +1865,7 @@ CODE def test_strip assert_equal(S("x"), S(" x ").strip) assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip) + assert_equal(S("x"), S("\x00x\x00").strip) assert_equal("0b0 ".force_encoding("UTF-16BE"), "\x00 0b0 ".force_encoding("UTF-16BE").strip) @@ -1876,6 +1884,10 @@ CODE assert_equal(S("x"), a.strip!) assert_equal(S("x"), a) + a = S("\x00x\x00") + assert_equal(S("x"), a.strip!) + assert_equal(S("x"), a) + a = S("x") assert_nil(a.strip!) assert_equal(S("x") ,a) @@ -2095,6 +2107,8 @@ CODE def test_swapcase assert_equal(S("hi&LOW"), S("HI&low").swapcase) + s = S("") + assert_not_same(s, s.swapcase) end def test_swapcase! @@ -2330,6 +2344,7 @@ CODE assert_equal(S("HELLO"), S("hello").upcase) 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) end def test_upcase! @@ -2342,6 +2357,12 @@ CODE a = S("HELLO") assert_nil(a.upcase!) assert_equal(S("HELLO"), a) + + a = S("H\0""ello") + b = a.dup + assert_equal(S("H\0""ELLO"), a.upcase!) + assert_equal(S("H\0""ELLO"), a) + assert_equal(S("H\0""ello"), b) end def test_upto @@ -2567,6 +2588,10 @@ CODE hello = "hello" hello.partition("hi").map(&:upcase!) assert_equal("hello", hello, bug) + + assert_equal(["", "", "foo"], "foo".partition(/^=*/)) + + assert_equal([S("ab"), S("c"), S("dbce")], S("abcdbce").partition(/b\Kc/)) end def test_rpartition @@ -2591,6 +2616,8 @@ CODE hello = "hello" hello.rpartition("hi").map(&:upcase!) assert_equal("hello", hello, bug) + + assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/)) end def test_setter @@ -2658,6 +2685,7 @@ CODE 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_nil("foo".casecmp(:foo)) assert_nil("foo".casecmp(Object.new)) @@ -2672,6 +2700,7 @@ CODE assert_equal(false, 'FoO'.casecmp?('BaR')) assert_equal(false, 'baR'.casecmp?('FoO')) assert_equal(true, 'äöü'.casecmp?('ÄÖÜ')) + assert_equal(false, "foo".casecmp?("foo\0")) assert_nil("foo".casecmp?(:foo)) assert_nil("foo".casecmp?(Object.new)) @@ -2692,6 +2721,7 @@ CODE 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 } end @@ -2712,12 +2742,17 @@ CODE assert_equal(nil, s4.rstrip!) assert_equal("\u3042", s4) + s5 = S("\u3042\u0000") + assert_equal("\u3042", s5.rstrip!) + assert_equal("\u3042", s5) + assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! } end def test_lstrip assert_equal("hello ", " hello ".lstrip) assert_equal("\u3042", " \u3042".lstrip) + assert_equal("hello ", "\x00hello ".lstrip) end def test_lstrip_bang @@ -2736,6 +2771,11 @@ CODE s4 = S("\u3042") assert_equal(nil, s4.lstrip!) assert_equal("\u3042", s4) + + s5 = S("\u0000\u3042") + assert_equal("\u3042", s5.lstrip!) + assert_equal("\u3042", s5) + end def test_delete_prefix @@ -3141,6 +3181,22 @@ CODE assert_same(str, -bar, "uminus deduplicates [Feature #13077]") end + def test_uminus_frozen + # embedded + str1 = ("foobar" * 3).freeze + str2 = ("foobar" * 3).freeze + assert_not_same str1, str2 + assert_same str1, -str1 + assert_same str1, -str2 + + # regular + str1 = ("foobar" * 4).freeze + str2 = ("foobar" * 4).freeze + assert_not_same str1, str2 + assert_same str1, -str1 + assert_same str1, -str2 + end + def test_uminus_no_freeze_not_bare str = @cls.new("foo") assert_instance_of(@cls, -str) @@ -3176,6 +3232,12 @@ CODE assert_not_predicate(data, :valid_encoding?) assert_predicate(data[100..-1], :valid_encoding?) end + + def test_slice_bang_code_range + str = "[Bug #19739] ABC OÜ" + str.slice!(/ oü$/i) + assert_predicate str, :ascii_only? + end end class TestString2 < TestString diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 884fbe00ed..176e2ac5de 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -23,6 +23,10 @@ module TestStruct test.bar = 47 assert_equal(47, test.bar) + + @Struct.class_eval do + remove_const :Test + end end # [ruby-dev:26247] more than 10 struct members causes segmentation fault @@ -113,11 +117,10 @@ module TestStruct assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect - # eval is neede to prevent the warning duplication filter - k = eval("Class.new(@Struct::KeywordInitFalse) {def initialize(**) end}") - assert_warn(/The last argument is used as the keyword parameter/) {k.new(a: 1, b: 2)} - k = Class.new(@Struct::KeywordInitTrue) {def initialize(**) end} - assert_warn('') {k.new(a: 1, b: 2)} + # eval is needed to prevent the warning duplication filter + k = Class.new(@Struct::KeywordInitTrue) {def initialize(b, options); super(a: options, b: b); end} + o = assert_warn('') { k.new(42, {foo: 1, bar: 2}) } + assert_equal(1, o.a[:foo]) @Struct.instance_eval do remove_const(:KeywordInitTrue) @@ -135,6 +138,17 @@ module TestStruct assert_equal(3, struct.new(a: 1, b: 2).c) end + def test_struct_keyword_init_p + struct = @Struct.new(:a, :b, keyword_init: true) + assert_equal(true, struct.keyword_init?) + + struct = @Struct.new(:a, :b, keyword_init: false) + assert_equal(false, struct.keyword_init?) + + struct = @Struct.new(:a, :b) + assert_nil(struct.keyword_init?) + end + def test_initialize klass = @Struct.new(:a) assert_raise(ArgumentError) { klass.new(1, 2) } @@ -146,6 +160,17 @@ module TestStruct assert_equal 3, klass.new(1,2).total end + def test_initialize_with_kw + klass = @Struct.new(:foo, :options) do + def initialize(foo, **options) + super(foo, options) + end + end + assert_equal({}, klass.new(42, **Hash.new).options) + x = assert_warn('') { klass.new(1, bar: 2) } + assert_equal 2, x.options[:bar] + end + def test_each klass = @Struct.new(:a, :b) o = klass.new(1, 2) @@ -325,15 +350,31 @@ module TestStruct end def test_redefinition_warning - @Struct.new("RedefinitionWarning") + @Struct.new(name = "RedefinitionWarning") e = EnvUtil.verbose_warning do @Struct.new("RedefinitionWarning") end assert_match(/redefining constant #@Struct::RedefinitionWarning/, e) + + @Struct.class_eval do + remove_const name + end + 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({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) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new({a: 1}).a) } end def test_nonascii - struct_test = @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") + struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]') a = struct_test.new(42) assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]') @@ -343,6 +384,10 @@ module TestStruct assert_nothing_raised(Encoding::CompatibilityError) do assert_match(/redefining constant #@Struct::R\u{e9}sum\u{e9}/, e) end + + @Struct.class_eval do + remove_const name + end end def test_junk @@ -444,6 +489,43 @@ 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 + class TopStruct < Test::Unit::TestCase include TestStruct diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index bbfc581500..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 @@ -583,4 +620,81 @@ class TestSuper < Test::Unit::TestCase def test_super_with_modified_rest_parameter assert_equal [13], TestFor_super_with_modified_rest_parameter.new.foo end + + def test_super_with_define_method + superklass1 = Class.new do + def foo; :foo; end + def bar; :bar; end + def boo; :boo; end + end + superklass2 = Class.new(superklass1) do + alias baz boo + def boo; :boo2; end + end + subklass = Class.new(superklass2) + [:foo, :bar, :baz, :boo].each do |sym| + subklass.define_method(sym){ super() } + end + assert_equal :foo, subklass.new.foo + assert_equal :bar, subklass.new.bar + assert_equal :boo, subklass.new.baz + assert_equal :boo2, subklass.new.boo + end + + def test_super_attr_writer # [Bug #16785] + writer_class = Class.new do + attr_writer :test + end + superwriter_class = Class.new(writer_class) do + def initialize + @test = 1 # index: 1 + end + + def test=(test) + super(test) + end + end + inherited_class = Class.new(superwriter_class) do + def initialize + @a = nil + @test = 2 # index: 2 + end + end + + superwriter = superwriter_class.new + superwriter.test = 3 # set ic->index of superwriter_class#test= to 1 + + inherited = inherited_class.new + inherited.test = 4 # it may set 4 to index=1 while it should be index=2 + + assert_equal 3, superwriter.instance_variable_get(:@test) + assert_equal 4, inherited.instance_variable_get(:@test) + end + + def test_super_attr_reader + reader_class = Class.new do + attr_reader :test + end + superreader_class = Class.new(reader_class) do + def initialize + @test = 1 # index: 1 + end + + def test + super + end + end + inherited_class = Class.new(superreader_class) do + def initialize + @a = nil + @test = 2 # index: 2 + end + end + + superreader = superreader_class.new + assert_equal 1, superreader.test # set ic->index of superreader_class#test to 1 + + inherited = inherited_class.new + assert_equal 2, inherited.test # it may read index=1 while it should be index=2 + end end diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb index 660f2e1574..f7f17b8d67 100644 --- a/test/ruby/test_symbol.rb +++ b/test/ruby/test_symbol.rb @@ -105,6 +105,12 @@ class TestSymbol < Test::Unit::TestCase end end + def test_name + assert_equal("foo", :foo.name) + assert_same(:foo.name, :foo.name) + assert_predicate(:foo.name, :frozen?) + end + def test_to_proc assert_equal %w(1 2 3), (1..3).map(&:to_s) [ @@ -153,6 +159,14 @@ class TestSymbol < Test::Unit::TestCase end; end + def test_to_proc_lambda? + assert_predicate(:itself.to_proc, :lambda?) + end + + def test_to_proc_arity + assert_equal(-2, :itself.to_proc.arity) + end + def test_to_proc_call_with_symbol_proc first = 1 bug11594 = "[ruby-core:71088] [Bug #11594] corrupted the first local variable" @@ -165,6 +179,9 @@ class TestSymbol < Test::Unit::TestCase def _test_to_proc_arg_with_refinements_call(&block) block.call TestToPRocArgWithRefinements.new end + def _test_to_proc_with_refinements_call(&block) + block + end using Module.new { refine TestToPRocArgWithRefinements do def hoge @@ -176,6 +193,14 @@ class TestSymbol < Test::Unit::TestCase assert_equal(:hoge, _test_to_proc_arg_with_refinements_call(&:hoge)) end + def test_to_proc_lambda_with_refinements + assert_predicate(_test_to_proc_with_refinements_call(&:hoge), :lambda?) + end + + def test_to_proc_arity_with_refinements + assert_equal(-2, _test_to_proc_with_refinements_call(&:hoge).arity) + end + def self._test_to_proc_arg_with_refinements_call(&block) block.call TestToPRocArgWithRefinements.new end @@ -219,11 +244,11 @@ class TestSymbol < Test::Unit::TestCase begin; bug11845 = '[ruby-core:72381] [Bug #11845]' assert_nil(:class.to_proc.source_location, bug11845) - assert_equal([[:rest]], :class.to_proc.parameters, bug11845) + assert_equal([[:req], [:rest]], :class.to_proc.parameters, bug11845) c = Class.new {define_method(:klass, :class.to_proc)} m = c.instance_method(:klass) assert_nil(m.source_location, bug11845) - assert_equal([[:rest]], m.parameters, bug11845) + assert_equal([[:req], [:rest]], m.parameters, bug11845) end; end @@ -504,12 +529,14 @@ class TestSymbol < Test::Unit::TestCase assert_nothing_raised(NoMethodError, bug10259) {obj.send("unagi=".intern, 1)} end - def test_symbol_fstr_leak + def test_symbol_fstr_memory_leak bug10686 = '[ruby-core:67268] [Bug #10686]' - x = x = 0 - assert_no_memory_leak([], '200_000.times { |i| i.to_s.to_sym }; GC.start', "#{<<-"begin;"}\n#{<<-"end;"}", bug10686, limit: 1.71, rss: true, timeout: 20) + assert_no_memory_leak([], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", bug10686, limit: 1.71, rss: true, timeout: 20) begin; - 200_000.times { |i| (i + 200_000).to_s.to_sym } + n = 100_000 + n.times { |i| i.to_s.to_sym } + else; + (2 * n).times { |i| (i + n).to_s.to_sym } end; end diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 286beb7074..53036cab3b 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -66,6 +66,81 @@ 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_newline_in_block_parameters bug = '[ruby-dev:45292]' ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| @@ -93,6 +168,17 @@ class TestSyntax < Test::Unit::TestCase assert_valid_syntax("tap (proc do end)", __FILE__, bug9726) end + def test_hash_kwsplat_hash + kw = {} + h = {a: 1} + assert_equal({}, {**{}}) + assert_equal({}, {**kw}) + assert_equal(h, {**h}) + assert_equal(false, {**{}}.frozen?) + assert_equal(false, {**kw}.equal?(kw)) + assert_equal(false, {**h}.equal?(h)) + end + def test_array_kwsplat_hash kw = {} h = {a: 1} @@ -182,26 +268,36 @@ class TestSyntax < Test::Unit::TestCase h = {k3: 31} assert_raise(ArgumentError) {o.kw(**h)} h = {"k1"=>11, k2: 12} - assert_warn(/The last argument is split into positional and keyword parameters.* for `kw'/m) do - assert_raise(ArgumentError) {o.kw(**h)} - end + assert_raise(ArgumentError) {o.kw(**h)} end def test_keyword_duplicated bug10315 = '[ruby-core:65625] [Bug #10315]' a = [] def a.add(x) push(x); x; end - def a.f(k:) k; end + b = a.clone + def a.f(k:, **) k; end + def b.f(k:) k; end a.clear r = nil - assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), k: a.add(2))")} + assert_warn(/duplicated/) {r = eval("b.f(k: b.add(1), k: b.add(2))")} assert_equal(2, r) - assert_equal([1, 2], a, bug10315) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4))")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) a.clear r = nil - assert_warn(/duplicated/) {r = eval("a.f({k: a.add(1), k: a.add(2)})")} + assert_warn(/duplicated/) {r = eval("b.f(**{k: b.add(1), k: b.add(2)})")} assert_equal(2, r) - assert_equal([1, 2], a, bug10315) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) end def test_keyword_empty_splat @@ -610,6 +706,11 @@ WARN def test_do_block_after_lambda bug11380 = '[ruby-core:70067] [Bug #11380]' assert_valid_syntax('p -> { :hello }, a: 1 do end', bug11380) + + assert_valid_syntax('->(opt = (foo.[] bar)) {}') + assert_valid_syntax('->(opt = (foo.[]= bar)) {}') + assert_valid_syntax('->(opt = (foo.[] bar)) do end') + assert_valid_syntax('->(opt = (foo.[]= bar)) do end') end def test_reserved_method_no_args @@ -951,9 +1052,14 @@ eom def test_warning_for_cr feature8699 = '[ruby-core:56240] [Feature #8699]' - assert_warning(/encountered \\r/, feature8699) do - eval("\r""__id__\r") + s = assert_warning(/encountered \\r/, feature8699) do + eval("'\r'\r") end + assert_equal("\r", s) + s = assert_warning('') do + eval("'\r'\r\n") + end + assert_equal("\r", s) end def test_unexpected_fraction @@ -984,6 +1090,19 @@ eom assert_warn('') {eval("(1...)")} assert_warn('') {eval("(1...\n2)")} assert_warn('') {eval("{a: 1...\n2}")} + + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[]= ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[] ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[]= bar, ...', /unexpected/, verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[] bar, ...', /unexpected/, verbose: true) + end end def test_too_big_nth_ref @@ -1029,19 +1148,19 @@ eom end def test_warning_literal_in_condition - assert_warn(/literal in condition/) do + assert_warn(/string literal in condition/) do eval('1 if ""') end - assert_warn(/literal in condition/) do + assert_warn(/regex literal in condition/) do eval('1 if //') end assert_warning(/literal in condition/) do eval('1 if 1') end - assert_warning(/literal in condition/) do + assert_warning(/symbol literal in condition/) do eval('1 if :foo') end - assert_warning(/literal in condition/) do + assert_warning(/symbol literal in condition/) do eval('1 if :"#{"foo".upcase}"') end @@ -1374,6 +1493,15 @@ eom assert_nil obj.test end + def test_assignment_return_in_loop + obj = Object.new + def obj.test + x = nil + _y = (return until x unless x) + end + assert_nil obj.test, "[Bug #16695]" + end + def test_method_call_location line = __LINE__+5 e = assert_raise(NoMethodError) do @@ -1400,6 +1528,52 @@ eom assert_equal(line, e.backtrace_locations[0].lineno) end + def test_methoddef_endless + assert_valid_syntax('private def foo = 42') + assert_valid_syntax('private def foo() = 42') + assert_valid_syntax('private def inc(x) = x + 1') + assert_valid_syntax('private def obj.foo = 42') + assert_valid_syntax('private def obj.foo() = 42') + assert_valid_syntax('private def obj.inc(x) = x + 1') + k = Class.new do + class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + error = /setter method cannot be defined in an endless method definition/ + assert_syntax_error('def foo=() = 42', error) + assert_syntax_error('def obj.foo=() = 42', error) + assert_syntax_error('def foo=() = 42 rescue nil', error) + assert_syntax_error('def obj.foo=() = 42 rescue nil', error) + end + + def test_methoddef_endless_command + assert_valid_syntax('def foo = puts "Hello"') + assert_valid_syntax('def foo() = puts "Hello"') + assert_valid_syntax('def foo(x) = puts x') + assert_valid_syntax('def obj.foo = puts "Hello"') + assert_valid_syntax('def obj.foo() = puts "Hello"') + assert_valid_syntax('def obj.foo(x) = puts x') + k = Class.new do + class_eval('def rescued(x) = raise "to be caught" rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise "to be caught" rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + # Current technical limitation: cannot prepend "private" or something for command endless def + error = /syntax error, unexpected string literal/ + error2 = /syntax error, unexpected local variable or method/ + assert_syntax_error('private def foo = puts "Hello"', error) + assert_syntax_error('private def foo() = puts "Hello"', error) + assert_syntax_error('private def foo(x) = puts x', error2) + assert_syntax_error('private def obj.foo = puts "Hello"', error) + assert_syntax_error('private def obj.foo() = puts "Hello"', error) + assert_syntax_error('private def obj.foo(x) = puts x', error2) + end + def test_methoddef_in_cond assert_valid_syntax('while def foo; tap do end; end; break; end') assert_valid_syntax('while def foo a = tap do end; end; break; end') @@ -1437,8 +1611,12 @@ eom assert_syntax_error('-> {_1; -> {_2}}', /numbered parameter is already used/) assert_syntax_error('-> {-> {_1}; _2}', /numbered parameter is already used/) assert_syntax_error('proc {_1; _1 = nil}', /Can't assign to numbered parameter _1/) - assert_warn(/`_1' is used as numbered parameter/) {eval('proc {_1 = nil}')} - assert_warn(/`_2' is used as numbered parameter/) {eval('_2=1')} + assert_syntax_error('proc {_1 = nil}', /_1 is reserved for numbered parameter/) + assert_syntax_error('_2=1', /_2 is reserved for numbered parameter/) + assert_syntax_error('proc {|_3|}', /_3 is reserved for numbered parameter/) + assert_syntax_error('def x(_4) end', /_4 is reserved for numbered parameter/) + assert_syntax_error('def _5; end', /_5 is reserved for numbered parameter/) + assert_syntax_error('def self._6; end', /_6 is reserved for numbered parameter/) assert_raise_with_message(NameError, /undefined local variable or method `_1'/) { eval('_1') } @@ -1446,6 +1624,14 @@ eom assert_valid_syntax("->{#{c};->{_1};end;_1}\n") assert_valid_syntax("->{_1;#{c};->{_1};end}\n") end + + 1.times { + [ + _1, + assert_equal([:a], eval("[:a].map{_1}")), + assert_raise(NameError) {eval("_1")}, + ] + } end def test_value_expr_in_condition @@ -1456,13 +1642,33 @@ eom assert_valid_syntax("tap {a = (break unless true)}") end + def test_tautological_condition + assert_valid_syntax("def f() return if false and invalid; nil end") + assert_valid_syntax("def f() return unless true or invalid; nil end") + end + 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') + assert_valid_syntax('def true(...) end') + assert_valid_syntax('def false(...) end') + unexpected = /unexpected \.{3}/ assert_syntax_error('iter do |...| end', /unexpected/) assert_syntax_error('iter {|...|}', /unexpected/) - assert_syntax_error('->... {}', /unexpected/) - assert_syntax_error('->(...) {}', /unexpected/) + assert_syntax_error('->... {}', unexpected) + assert_syntax_error('->(...) {}', unexpected) assert_syntax_error('def foo(x, y, z) bar(...); end', /unexpected/) assert_syntax_error('def foo(x, y, z) super(...); end', /unexpected/) assert_syntax_error('def foo(...) yield(...); end', /unexpected/) @@ -1482,7 +1688,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) @@ -1511,25 +1721,177 @@ 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}) } assert_warning('') { assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5)) } - warning = "warning: The last argument is used as the keyword parameter" - assert_warning(/\A\z|:(?!#{__LINE__+1})\d+: #{warning}/o) { - assert_equal([[], {}], obj.foo({}) {|*x| x}) + array = obj == obj3 ? [] : [{}] + assert_warning('') { + assert_equal([array, {}], obj.foo({}) {|*x| x}) } - assert_warning(/\A\z|:(?!#{__LINE__+1})\d+: #{warning}/o) { - assert_equal([[], {}], obj.foo({})) + assert_warning('') { + assert_equal([array, {}], obj.foo({})) } assert_equal(-1, obj.method(:foo).arity) parameters = obj.method(:foo).parameters assert_equal(:rest, parameters.dig(0, 0)) - assert_equal(:block, parameters.dig(1, 0)) + assert_equal(:keyrest, parameters.dig(1, 0)) + assert_equal(:block, parameters.dig(2, 0)) + end + end + + def test_argument_forwarding_with_leading_arguments + obj = Object.new + def obj.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end end + obj.instance_eval('def foo(_a, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(1) + assert_equal [[2], {}], obj.foo(1, 2) + assert_equal [[2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[], {a: 1}], obj.foo(1, a: 1) + assert_equal [[2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo + assert_equal [[1, 1], {}], obj.foo(1) + assert_equal [[1, 1, 2], {}], obj.foo(1, 2) + assert_equal [[1, 1, 2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(a: 1) + assert_equal [[1, 1], {a: 1}], obj.foo(1, a: 1) + assert_equal [[1, 1, 2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 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]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4) + assert_equal [[1, 2], {}], obj.foo(4, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[1, 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, _b, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(4, 5) + assert_equal [[2], {}], obj.foo(4, 5, 2) + assert_equal [[2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[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, _b, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4, 5) + assert_equal [[1, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 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, ...) bar(1, 2, ...) end', __FILE__, __LINE__) + assert_equal [[1, 2], {}], obj.foo(5) + assert_equal [[1, 2, 5], {}], obj.foo(4, 5) + assert_equal [[1, 2, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1, 2, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 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, b, ...) bar(b, a, ...) end', __FILE__, __LINE__) + assert_equal [[5, 4], {}], obj.foo(4, 5) + assert_equal [[5, 4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[5, 4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[5, 4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[5, 4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[5, 4, 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, _b, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4, 5) + assert_equal [[4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 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, ...) bar(a, 1, ...) end', __FILE__, __LINE__) + assert_equal [[4, 1], {}], obj.foo(4) + assert_equal [[4, 1, 5], {}], obj.foo(4, 5) + assert_equal [[4, 1, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 1, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4, 1, 5], {a: 1}], obj.foo(4, 5, a: 1) + 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_cdhash + assert_separately([], <<-RUBY) + n = case 1 when 2r then false else true end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 3/2r when 1.5r then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 1i when 1i then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY end private diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index bf61a28f4d..41cee124be 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -230,9 +230,17 @@ class TestThread < Test::Unit::TestCase assert_equal(t1, t3.value) ensure - t1&.kill - t2&.kill - t3&.kill + t1&.kill&.join + t2&.kill&.join + t3&.kill&.join + end + + def test_join_argument_conversion + t = Thread.new {} + assert_raise(TypeError) {t.join(:foo)} + + limit = Struct.new(:to_f, :count).new(0.05) + assert_same(t, t.join(limit)) end { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'], @@ -309,7 +317,7 @@ class TestThread < Test::Unit::TestCase s += 1 end Thread.pass until t.stop? - sleep 1 if RubyVM::MJIT.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? @@ -490,6 +498,19 @@ class TestThread < Test::Unit::TestCase end; end + def test_ignore_deadlock + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "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 + q = Thread::Queue.new + trap(:INT){q.push :sig} + Thread.ignore_deadlock = true + p q.pop + INPUT + end + def test_status_and_stop_p a = ::Thread.new { Thread.current.report_on_exception = false @@ -617,7 +638,7 @@ class TestThread < Test::Unit::TestCase Thread.pass until t.stop? assert_predicate(t, :alive?) ensure - t&.kill + t&.kill&.join end def test_mutex_deadlock @@ -718,8 +739,8 @@ class TestThread < Test::Unit::TestCase def make_handle_interrupt_test_thread1 flag r = [] - ready_q = Queue.new - done_q = Queue.new + ready_q = Thread::Queue.new + done_q = Thread::Queue.new th = Thread.new{ begin Thread.handle_interrupt(RuntimeError => flag){ @@ -795,14 +816,15 @@ class TestThread < Test::Unit::TestCase end def test_handle_interrupt_blocking - r=:ng - e=Class.new(Exception) + r = nil + q = Thread::Queue.new + e = Class.new(Exception) th_s = Thread.current - th = Thread.start{ + th = Thread.start { assert_raise(RuntimeError) { Thread.handle_interrupt(Object => :on_blocking){ begin - Thread.pass until r == :wait + q.pop Thread.current.raise RuntimeError, "will raise in sleep" r = :ok sleep @@ -812,15 +834,14 @@ class TestThread < Test::Unit::TestCase } } } - assert_raise(e) {r = :wait; sleep 0.2} - th.join - assert_equal(:ok,r) + assert_raise(e) {q << true; th.join} + assert_equal(:ok, r) end def test_handle_interrupt_and_io assert_in_out_err([], <<-INPUT, %w(ok), []) th_waiting = true - q = Queue.new + q = Thread::Queue.new t = Thread.new { Thread.current.report_on_exception = false @@ -1106,7 +1127,7 @@ q.pop Thread.pass until mutex.locked? assert_equal(mutex.owned?, false) ensure - th&.kill + th&.kill&.join end end @@ -1133,7 +1154,9 @@ q.pop env = {} env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size - out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + out, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + assert_not_predicate(status, :signaled?, err) + use_length ? out.length : out end @@ -1150,6 +1173,7 @@ q.pop "0 thread_machine_stack_size") assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size], "large thread_machine_stack_size") + assert_equal("ok", invoke_rec('print :ok', 1024 * 1024 * 100, nil, false)) end def test_vm_machine_stack_size @@ -1220,8 +1244,22 @@ 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 + end + end if Process.respond_to?(:fork) + def test_fork_while_locked - m = Mutex.new + m = Thread::Mutex.new thrs = [] 3.times do |i| thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } } @@ -1254,7 +1292,7 @@ q.pop def test_fork_while_mutex_locked_by_forker skip 'needs fork' unless Process.respond_to?(:fork) - m = Mutex.new + m = Thread::Mutex.new m.synchronize do pid = fork do exit!(2) unless m.locked? @@ -1320,11 +1358,32 @@ q.pop assert_equal("foo", c.new {Thread.current.name}.value, bug12290) end + def test_thread_native_thread_id + skip "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} + + # newly created thread which doesn't run yet returns nil or integer + assert_include [NilClass, Integer], th1.native_thread_id.class + + Thread.pass until th1.stop? + + # After a thread starts (and execute `sleep`), it returns native_thread_id + assert_instance_of Integer, th1.native_thread_id + + th1.wakeup + Thread.pass while th1.alive? + + # dead thread returns nil + assert_nil th1.native_thread_id + end + def test_thread_interrupt_for_killed_thread opts = { timeout: 5, timeout_error: nil } # prevent SIGABRT from slow shutdown with MJIT - opts[:reprieve] = 3 if RubyVM::MJIT.enabled? + opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? assert_normal_exit(<<-_end, '[Bug #8996]', **opts) Thread.report_on_exception = false diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb index 38bcc3b8fa..88733419da 100644 --- a/test/ruby/test_thread_cv.rb +++ b/test/ruby/test_thread_cv.rb @@ -13,9 +13,10 @@ class TestThreadConditionVariable < Test::Unit::TestCase end def test_condvar_signal_and_wait - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new result = [] + woken = nil mutex.synchronize do t = Thread.new do mutex.synchronize do @@ -25,18 +26,19 @@ class TestThreadConditionVariable < Test::Unit::TestCase end result << 0 - condvar.wait(mutex) + woken = condvar.wait(mutex) result << 2 t.join end assert_equal([0, 1, 2], result) + assert(woken) end def test_condvar_wait_exception_handling # Calling wait in the only thread running should raise a ThreadError of # 'stopping only thread' - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new locked = false thread = Thread.new do @@ -61,8 +63,8 @@ class TestThreadConditionVariable < Test::Unit::TestCase def test_condvar_wait_and_broadcast nr_threads = 3 threads = Array.new - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new result = [] nr_threads.times do |i| @@ -87,12 +89,15 @@ class TestThreadConditionVariable < Test::Unit::TestCase end assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result + ensure + threads.each(&:kill) + threads.each(&:join) end def test_condvar_wait_deadlock assert_in_out_err([], <<-INPUT, /\Afatal\nNo live threads left\. Deadlock/, []) - mutex = Mutex.new - cv = ConditionVariable.new + mutex = Thread::Mutex.new + cv = Thread::ConditionVariable.new klass = nil mesg = nil @@ -112,8 +117,8 @@ INPUT def test_condvar_wait_deadlock_2 nr_threads = 3 threads = Array.new - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new nr_threads.times do |i| if (i != 0) @@ -136,15 +141,16 @@ INPUT end def test_condvar_timed_wait - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new timeout = 0.3 locked = false + woken = true t0 = Time.now mutex.synchronize do begin - condvar.wait(mutex, timeout) + woken = condvar.wait(mutex, timeout) ensure locked = mutex.locked? end @@ -154,18 +160,19 @@ INPUT assert_operator(timeout*0.9, :<, t) assert(locked) + assert_nil(woken) end def test_condvar_nolock - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new assert_raise(ThreadError) {condvar.wait(mutex)} end def test_condvar_nolock_2 - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new Thread.new do assert_raise(ThreadError) {condvar.wait(mutex)} @@ -173,8 +180,8 @@ INPUT end def test_condvar_nolock_3 - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new Thread.new do assert_raise(ThreadError) {condvar.wait(mutex, 0.1)} @@ -182,22 +189,22 @@ INPUT end def test_condvar_empty_signal - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} } end def test_condvar_empty_broadcast - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new assert_nothing_raised(Exception) { mutex.synchronize {condvar.broadcast} } end def test_dup bug9440 = '[ruby-core:59961] [Bug #9440]' - condvar = ConditionVariable.new + condvar = Thread::ConditionVariable.new assert_raise(NoMethodError, bug9440) do condvar.dup end @@ -207,7 +214,7 @@ INPUT def test_dump bug9674 = '[ruby-core:61677] [Bug #9674]' - condvar = ConditionVariable.new + condvar = Thread::ConditionVariable.new assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do Marshal.dump(condvar) end @@ -219,8 +226,8 @@ INPUT end def test_condvar_fork - mutex = Mutex.new - condvar = ConditionVariable.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new thrs = (1..10).map do Thread.new { mutex.synchronize { condvar.wait(mutex) } } end diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb index e46f0b2e8e..3fa0eae2c1 100644 --- a/test/ruby/test_thread_queue.rb +++ b/test/ruby/test_thread_queue.rb @@ -54,15 +54,37 @@ class TestThreadQueue < Test::Unit::TestCase assert_equal 0, to_workers.size end + def test_queue_initialize + e = Class.new do + include Enumerable + def initialize(list) @list = list end + def each(&block) @list.each(&block) end + end + + all_assertions_foreach(nil, + [Array, "Array"], + [e, "Enumerable"], + [Struct.new(:to_a), "Array-like"], + ) do |a, type| + q = Thread::Queue.new(a.new([1,2,3])) + assert_equal(3, q.size, type) + assert_not_predicate(q, :empty?, type) + assert_equal(1, q.pop, type) + assert_equal(2, q.pop, type) + assert_equal(3, q.pop, type) + assert_predicate(q, :empty?, type) + end + end + def test_sized_queue_initialize - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) assert_equal 1, q.max - assert_raise(ArgumentError) { SizedQueue.new(0) } - assert_raise(ArgumentError) { SizedQueue.new(-1) } + assert_raise(ArgumentError) { Thread::SizedQueue.new(0) } + assert_raise(ArgumentError) { Thread::SizedQueue.new(-1) } end def test_sized_queue_assign_max - q = SizedQueue.new(2) + q = Thread::SizedQueue.new(2) assert_equal(2, q.max) q.max = 1 assert_equal(1, q.max) @@ -82,7 +104,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_pop_interrupt - q = Queue.new + q = Thread::Queue.new t1 = Thread.new { q.pop } sleep 0.01 until t1.stop? t1.kill.join @@ -90,14 +112,14 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_pop_non_block - q = Queue.new + q = Thread::Queue.new assert_raise_with_message(ThreadError, /empty/) do q.pop(true) end end def test_sized_queue_pop_interrupt - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) t1 = Thread.new { q.pop } sleep 0.01 until t1.stop? t1.kill.join @@ -105,14 +127,14 @@ class TestThreadQueue < Test::Unit::TestCase end def test_sized_queue_pop_non_block - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) assert_raise_with_message(ThreadError, /empty/) do q.pop(true) end end def test_sized_queue_push_interrupt - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) q.push(1) assert_raise_with_message(ThreadError, /full/) do q.push(2, true) @@ -120,7 +142,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_sized_queue_push_non_block - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) q.push(1) t1 = Thread.new { q.push(2) } sleep 0.01 until t1.stop? @@ -131,13 +153,13 @@ class TestThreadQueue < Test::Unit::TestCase def test_thr_kill bug5343 = '[ruby-core:39634]' Dir.mktmpdir {|d| - timeout = 60 + timeout = EnvUtil.apply_timeout_scale(60) total_count = 250 begin assert_normal_exit(<<-"_eom", bug5343, **{:timeout => timeout, :chdir=>d}) #{total_count}.times do |i| open("test_thr_kill_count", "w") {|f| f.puts i } - queue = Queue.new + queue = Thread::Queue.new r, w = IO.pipe th = Thread.start { queue.push(nil) @@ -156,20 +178,20 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_push_return_value - q = Queue.new + q = Thread::Queue.new retval = q.push(1) assert_same q, retval end def test_queue_clear_return_value - q = Queue.new + q = Thread::Queue.new retval = q.clear assert_same q, retval end def test_sized_queue_clear - # Fill queue, then test that SizedQueue#clear wakes up all waiting threads - sq = SizedQueue.new(2) + # Fill queue, then test that Thread::SizedQueue#clear wakes up all waiting threads + sq = Thread::SizedQueue.new(2) 2.times { sq << 1 } t1 = Thread.new do @@ -190,19 +212,19 @@ class TestThreadQueue < Test::Unit::TestCase end def test_sized_queue_push_return_value - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) retval = q.push(1) assert_same q, retval end def test_sized_queue_clear_return_value - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) retval = q.clear assert_same q, retval end def test_sized_queue_throttle - q = SizedQueue.new(1) + q = Thread::SizedQueue.new(1) i = 0 consumer = Thread.new do while q.pop @@ -225,7 +247,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_thread_raise - q = Queue.new + q = Thread::Queue.new th1 = Thread.new do begin q.pop @@ -255,7 +277,7 @@ class TestThreadQueue < Test::Unit::TestCase def test_dup bug9440 = '[ruby-core:59961] [Bug #9440]' - q = Queue.new + q = Thread::Queue.new assert_raise(NoMethodError, bug9440) do q.dup end @@ -265,12 +287,12 @@ class TestThreadQueue < Test::Unit::TestCase def test_dump bug9674 = '[ruby-core:61677] [Bug #9674]' - q = Queue.new + q = Thread::Queue.new assert_raise_with_message(TypeError, /#{Queue}/, bug9674) do Marshal.dump(q) end - sq = SizedQueue.new(1) + sq = Thread::SizedQueue.new(1) assert_raise_with_message(TypeError, /#{SizedQueue}/, bug9674) do Marshal.dump(sq) end @@ -282,7 +304,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_close - [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| q = qcreate.call assert_equal false, q.closed? q << :something @@ -321,15 +343,15 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_close_wakeup - close_wakeup(15, 18){Queue.new} + close_wakeup(15, 18){Thread::Queue.new} end def test_size_queue_close_wakeup - close_wakeup(5, 8){SizedQueue.new 9} + close_wakeup(5, 8){Thread::SizedQueue.new 9} end def test_sized_queue_one_closed_interrupt - q = SizedQueue.new 1 + q = Thread::SizedQueue.new 1 q << :one t1 = Thread.new { Thread.current.report_on_exception = false @@ -346,7 +368,7 @@ class TestThreadQueue < Test::Unit::TestCase # make sure that shutdown state is handled properly by empty? for the non-blocking case def test_empty_non_blocking - q = SizedQueue.new 3 + q = Thread::SizedQueue.new 3 3.times{|i| q << i} # these all block cos the queue is full @@ -372,13 +394,13 @@ class TestThreadQueue < Test::Unit::TestCase end def test_sized_queue_closed_push_non_blocking - q = SizedQueue.new 7 + q = Thread::SizedQueue.new 7 q.close assert_raise_with_message(ClosedQueueError, /queue closed/){q.push(non_block=true)} end def test_blocked_pushers - q = SizedQueue.new 3 + q = Thread::SizedQueue.new 3 prod_threads = 6.times.map do |i| thr = Thread.new{ Thread.current.report_on_exception = false @@ -424,9 +446,9 @@ class TestThreadQueue < Test::Unit::TestCase end def test_deny_pushers - [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| q = qcreate[] - synq = Queue.new + synq = Thread::Queue.new prod_threads = 20.times.map do |i| Thread.new { synq.pop @@ -444,7 +466,7 @@ class TestThreadQueue < Test::Unit::TestCase # size should account for waiting pushers during shutdown def sized_queue_size_close - q = SizedQueue.new 4 + q = Thread::SizedQueue.new 4 4.times{|i| q << i} Thread.new{ q << 5 } Thread.new{ q << 6 } @@ -456,7 +478,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_blocked_pushers_empty - q = SizedQueue.new 3 + q = Thread::SizedQueue.new 3 prod_threads = 6.times.map do |i| Thread.new{ Thread.current.report_on_exception = false @@ -488,14 +510,14 @@ class TestThreadQueue < Test::Unit::TestCase # test thread wakeup on one-element SizedQueue with close def test_one_element_sized_queue - q = SizedQueue.new 1 + q = Thread::SizedQueue.new 1 t = Thread.new{ q.pop } q.close assert_nil t.value end def test_close_twice - [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| q = qcreate[] q.close assert_nothing_raised(ClosedQueueError){q.close} @@ -503,7 +525,7 @@ class TestThreadQueue < Test::Unit::TestCase end def test_queue_close_multi_multi - q = SizedQueue.new rand(800..1200) + q = Thread::SizedQueue.new rand(800..1200) count_items = rand(3000..5000) count_producers = rand(10..20) @@ -560,14 +582,19 @@ class TestThreadQueue < Test::Unit::TestCase if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/) skip 'This test fails too often on AppVeyor vs140' end + if RUBY_PLATFORM.match?(/mingw/) + skip 'This test fails too often on MinGW' + end + assert_in_out_err([], <<-INPUT, %w(INT INT exit), []) - q = Queue.new + q = Thread::Queue.new trap(:INT){ q.push 'INT' } Thread.new{ loop{ Process.kill :INT, $$ + sleep 0.1 } } puts q.pop @@ -577,8 +604,8 @@ class TestThreadQueue < Test::Unit::TestCase end def test_fork_while_queue_waiting - q = Queue.new - sq = SizedQueue.new(1) + q = Thread::Queue.new + sq = Thread::SizedQueue.new(1) thq = Thread.new { q.pop } thsq = Thread.new { sq.pop } Thread.pass until thq.stop? && thsq.stop? diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 35e3172fb1..36c79273db 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -7,7 +7,6 @@ require 'delegate' class TestTime < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown @@ -38,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]' @@ -47,6 +49,13 @@ 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_time_add() @@ -108,6 +117,10 @@ class TestTime < Test::Unit::TestCase assert_equal(78796800, Time.utc(1972, 7, 1, 0, 0, 0).tv_sec) assert_equal(78796801, Time.utc(1972, 7, 1, 0, 0, 1).tv_sec) 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 + 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) else @@ -236,6 +249,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) @@ -375,6 +392,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)) @@ -417,8 +439,10 @@ class TestTime < Test::Unit::TestCase def o.to_int; 0; end def o.to_r; nil; end assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) } + class << o; remove_method(:to_r); end def o.to_r; ""; end assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) } + class << o; remove_method(:to_r); end def o.to_r; Rational(11); end assert_equal(11, Time.gm(2000, 1, 1, 0, 0, o).sec) o = Object.new @@ -431,6 +455,10 @@ class TestTime < Test::Unit::TestCase assert_equal(-4427700000, Time.utc(-4427700000,12,1).year) assert_equal(-2**30+10, Time.utc(-2**30+10,1,1).year) + + assert_raise(ArgumentError) { Time.gm(2000, 1, -1) } + assert_raise(ArgumentError) { Time.gm(2000, 1, 2**30 + 1) } + assert_raise(ArgumentError) { Time.gm(2000, 1, -2**30 + 1) } end def test_time_interval @@ -575,6 +603,10 @@ class TestTime < Test::Unit::TestCase t2000 = get_t2000.localtime(9*3600) + 1/10r assert_equal("2000-01-01 09:00:00.1 +0900", t2000.inspect) + + t2000 = get_t2000 + assert_equal("2000-01-01 09:12:00 +0912", t2000.localtime(9*3600+12*60).inspect) + assert_equal("2000-01-01 09:12:34 +091234", t2000.localtime(9*3600+12*60+34).inspect) end def assert_zone_encoding(time) @@ -596,13 +628,12 @@ class TestTime < Test::Unit::TestCase assert_nil(t.getlocal("+02:00").zone) end - def test_plus_minus_succ + def test_plus_minus t2000 = get_t2000 # assert_raise(RangeError) { t2000 + 10000000000 } # assert_raise(RangeError) t2000 - 3094168449 } # assert_raise(RangeError) { t2000 + 1200798848 } assert_raise(TypeError) { t2000 + Time.now } - assert_equal(t2000 + 1, t2000.succ) end def test_plus_type @@ -705,7 +736,9 @@ class TestTime < Test::Unit::TestCase assert_equal("12:00:00 AM", t2000.strftime("%r")) assert_equal("Sat 2000-01-01T00:00:00", t2000.strftime("%3a %FT%T")) - assert_equal("", t2000.strftime("")) + assert_warning(/strftime called with empty format string/) do + assert_equal("", t2000.strftime("")) + end assert_equal("foo\0bar\x0000\x0000\x0000", t2000.strftime("foo\0bar\0%H\0%M\0%S")) assert_equal("foo" * 1000, t2000.strftime("foo" * 1000)) @@ -885,6 +918,13 @@ class TestTime < Test::Unit::TestCase assert_equal(8192, Time.now.strftime('%8192z').size) end + def test_strftime_wide_precision + t2000 = get_t2000 + s = t2000.strftime("%28c") + assert_equal(28, s.size) + assert_equal(t2000.strftime("%c"), s.strip) + end + def test_strfimte_zoneoffset t2000 = get_t2000 t = t2000.getlocal("+09:00:00") @@ -898,6 +938,17 @@ class TestTime < Test::Unit::TestCase assert_equal("+09:00", t.strftime("%:z")) assert_equal("+09:00:01", t.strftime("%::z")) assert_equal("+09:00:01", t.strftime("%:::z")) + + assert_equal("+0000", t2000.strftime("%z")) + assert_equal("-0000", t2000.strftime("%-z")) + assert_equal("-00:00", t2000.strftime("%-:z")) + assert_equal("-00:00:00", t2000.strftime("%-::z")) + + t = t2000.getlocal("+00:00") + assert_equal("+0000", t.strftime("%z")) + assert_equal("+0000", t.strftime("%-z")) + assert_equal("+00:00", t.strftime("%-:z")) + assert_equal("+00:00:00", t.strftime("%-::z")) end def test_strftime_padding @@ -1054,6 +1105,11 @@ class TestTime < Test::Unit::TestCase t2 = (t+0.123456789).ceil(4) assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) assert_equal(Rational(1235,10000), t2.subsec) + + time = Time.utc(2016, 4, 23, 0, 0, 0.123456789r) + assert_equal(time, time.ceil(9)) + assert_equal(time, time.ceil(10)) + assert_equal(time, time.ceil(11)) end def test_getlocal_dont_share_eigenclass @@ -1129,6 +1185,9 @@ class TestTime < Test::Unit::TestCase end def test_2038 + # Giveup to try 2nd test because some state is changed. + skip 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) end @@ -1187,6 +1246,30 @@ class TestTime < Test::Unit::TestCase } end + def test_getlocal_utc + t = Time.gm(2000) + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("UTC")).to_a[0, 6] + assert_predicate t1, :utc? + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("-0000")).to_a[0, 6] + assert_predicate t1, :utc? + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("+0000")).to_a[0, 6] + assert_not_predicate t1, :utc? + end + + def test_getlocal_utc_offset + t = Time.gm(2000) + assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-02:30").to_a[0, 6] + assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+09:00").to_a[0, 6] + assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-02:30:40").to_a[0, 6] + assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+09:10:35").to_a[0, 6] + assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-0230").to_a[0, 6] + assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+0900").to_a[0, 6] + assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-023040").to_a[0, 6] + assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+091035").to_a[0, 6] + assert_raise(ArgumentError) {t.getlocal("-02:3040")} + assert_raise(ArgumentError) {t.getlocal("+0910:35")} + end + def test_getlocal_nil now = Time.now now2 = nil @@ -1216,22 +1299,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 @@ -1245,6 +1312,7 @@ 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] require 'objspace' t = Time.at(0) size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb index 83482eac65..26fe680acf 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 @@ -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 @@ -219,7 +225,6 @@ class TestTimeTZ < Test::Unit::TestCase def test_right_utc with_tz(tz="right/UTC") { - ::Bug::Time.reset_leap_second_info assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) @@ -229,25 +234,23 @@ class TestTimeTZ < Test::Unit::TestCase def test_right_utc_switching with_tz("UTC") { # ensure no leap second timezone - ::Bug::Time.reset_leap_second_info assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) with_tz(tz="right/UTC") { assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) - assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60]) + assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) - assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) } } with_tz("right/UTC") { - ::Bug::Time.reset_leap_second_info assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) with_tz(tz="UTC") { assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) - assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) } } end if has_right_tz @@ -264,6 +267,8 @@ class TestTimeTZ < Test::Unit::TestCase assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "UTC"), :utc?) assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "utc"), :utc?) assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "Z"), :utc?) + assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "-00:00"), :utc?) + assert_not_predicate(Time.new(2019, 1, 1, 0, 0, 0, "+00:00"), :utc?) end def test_military_names @@ -376,7 +381,6 @@ class TestTimeTZ < Test::Unit::TestCase mesg = "#{mesg_utc}.localtime" define_method(gen_test_name(tz)) { with_tz(tz) { - ::Bug::Time.reset_leap_second_info t = nil assert_nothing_raised(mesg) { t = Time.utc(*u) } assert_equal(expected_utc, time_to_s(t), mesg_utc) @@ -452,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 @@ -608,6 +615,15 @@ module TestTimeTZ::WithTZ assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) h, m = (-utc_offset / 60).divmod(60) assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i) + assert_equal(6, t.wday) + assert_equal(244, t.yday) + assert_equal(t, time_class.new(2018, 9, 1, 12, in: tzarg)) + 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) @@ -634,6 +650,7 @@ module TestTimeTZ::WithTZ h, m = (utc_offset.abs / 60).divmod(60) h = -h if utc_offset < 0 assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z")) + assert_equal("34 35 35", t.strftime("%U %V %W")) end def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset) @@ -662,6 +679,12 @@ module TestTimeTZ::WithTZ assert_equal(utc, t.to_i) end + def subtest_to_a(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + ary = t.to_a + assert_equal(ary, [t.sec, t.min, t.hour, t.mday, t.mon, t.year, t.wday, t.yday, t.isdst, t.zone]) + end + def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset) t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) t2 = Marshal.load(Marshal.dump(t)) @@ -747,6 +770,16 @@ class TestTimeTZ::DummyTZ < Test::Unit::TestCase def self.make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil) TestTimeTZ::TZ.new(tzname, abbr, utc_offset, abbr2, utc_offset2) end + + def test_fractional_second + x = Object.new + def x.local_to_utc(t); t + 8*3600; end + def x.utc_to_local(t); t - 8*3600; end + + t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00') + t2 = Time.new(2020,11,11,12,13,14.124r, x) + assert_equal(t1, t2) + end end begin diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index f405877dd5..c8b0034e06 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -126,6 +126,28 @@ class TestTranscode < Test::Unit::TestCase assert_equal("D\xFCrst".force_encoding('iso-8859-2'), "D\xFCrst".encode('iso-8859-2', 'iso-8859-1')) end + def test_encode_xml_multibyte + encodings = %w'UTF-8 UTF-16LE UTF-16BE UTF-32LE UTF-32BE' + encodings.each do |src_enc| + encodings.each do |dst_enc| + escaped = "<>".encode(src_enc).encode(dst_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).encode(dst_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + + escaped = "<>".encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + end + end + # regression test; U+6E7F (湿) uses the same bytes in ISO-2022-JP as "<>" + assert_equal( "<>\u6E7F", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:text).encode("UTF-8")) + assert_equal("\"<>\u6E7F\"", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:attr).encode("UTF-8")) + end + def test_ascii_range encodings = [ 'US-ASCII', 'ASCII-8BIT', @@ -469,6 +491,25 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u00A0", "\xFF", 'IBM437') # non-breaking space end + def test_IBM720 + assert_raise(Encoding::UndefinedConversionError) { "\x80".encode("utf-8", 'IBM720') } + assert_raise(Encoding::UndefinedConversionError) { "\x8F".encode("utf-8", 'IBM720') } + assert_raise(Encoding::UndefinedConversionError) { "\x90".encode("utf-8", 'IBM720') } + check_both_ways("\u0627", "\x9F", 'IBM720') # ا + check_both_ways("\u0628", "\xA0", 'IBM720') # ب + check_both_ways("\u00BB", "\xAF", 'IBM720') # » + check_both_ways("\u2591", "\xB0", 'IBM720') # ░ + check_both_ways("\u2510", "\xBF", 'IBM720') # ┐ + check_both_ways("\u2514", "\xC0", 'IBM720') # └ + check_both_ways("\u2567", "\xCF", 'IBM720') # ╧ + check_both_ways("\u2568", "\xD0", 'IBM720') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM720') # ▀ + check_both_ways("\u0636", "\xE0", 'IBM720') # ض + check_both_ways("\u064A", "\xEF", 'IBM720') # ي + check_both_ways("\u2261", "\xF0", 'IBM720') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM720') # non-breaking space + end + def test_IBM775 check_both_ways("\u0106", "\x80", 'IBM775') # Ć check_both_ways("\u00C5", "\x8F", 'IBM775') # Å @@ -2183,6 +2224,14 @@ class TestTranscode < Test::Unit::TestCase assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback.method(:escape))) end + def test_fallback_aref + fallback = Object.new + def fallback.[](x) + "U+%.4X" % x.unpack("U") + end + 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 @@ -2242,12 +2291,19 @@ class TestTranscode < Test::Unit::TestCase "#{bug} coderange should not have side effects") end - def test_universal_newline + def test_newline_options bug11324 = '[ruby-core:69841] [Bug #11324]' usascii = Encoding::US_ASCII s = "A\nB\r\nC".force_encoding(usascii) assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true), bug11324) assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace), bug11324) assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace, replace: ''), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace, replace: '')) + assert_equal("A\rB\r\rC", s.encode(usascii, cr_newline: true)) + 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)) 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 b053e11607..d425b43b0d 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -29,12 +29,39 @@ class TestVariable < Test::Unit::TestCase @@rule = "Cronus" # modifies @@rule in Gods include Olympians def ruler4 - EnvUtil.suppress_warning { - @@rule - } + @@rule 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) + mod.freeze + c = Class.new { include(mod) } + assert_raise(FrozenError) { c.class_variable_set(:@@foo, 2) } + assert_raise(FrozenError) { c.class_eval("@@foo = 2") } + 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) @@ -63,6 +90,67 @@ class TestVariable < Test::Unit::TestCase assert_equal(1, o.singleton_class.class_variable_get(:@@foo)) end + def test_cvar_overtaken_by_parent_class + error = eval <<~EORB + class Parent + end + + class Child < Parent + @@cvar = 1 + + def self.cvar + @@cvar + end + end + + assert_equal 1, Child.cvar + + class Parent + @@cvar = 2 + end + + assert_raise RuntimeError do + Child.cvar + end + EORB + + assert_equal "class variable @@cvar of TestVariable::Child is overtaken by TestVariable::Parent", error.message + ensure + TestVariable.send(:remove_const, :Child) rescue nil + TestVariable.send(:remove_const, :Parent) rescue nil + end + + def test_cvar_overtaken_by_module + error = eval <<~EORB + class ParentForModule + @@cvar = 1 + + def self.cvar + @@cvar + end + end + + assert_equal 1, ParentForModule.cvar + + module Mixin + @@cvar = 2 + end + + class ParentForModule + include Mixin + end + + assert_raise RuntimeError do + ParentForModule.cvar + end + EORB + + assert_equal "class variable @@cvar of TestVariable::ParentForModule is overtaken by TestVariable::Mixin", error.message + ensure + TestVariable.send(:remove_const, :Mixin) rescue nil + TestVariable.send(:remove_const, :ParentForModule) rescue nil + end + class IncludeRefinedModuleClassVariableNoWarning module Mod @@_test_include_refined_module_class_variable = true @@ -107,7 +195,7 @@ class TestVariable < Test::Unit::TestCase atlas = Titans.new assert_equal("Cronus", atlas.ruler0) assert_equal("Zeus", atlas.ruler3) - assert_equal("Cronus", atlas.ruler4) + assert_raise(RuntimeError) { atlas.ruler4 } assert_nothing_raised do class << Gods defined?(@@rule) && @@rule diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb index 68f0fa7f27..679ce94b91 100644 --- a/test/ruby/test_vm_dump.rb +++ b/test/ruby/test_vm_dump.rb @@ -16,6 +16,6 @@ class TestVMDump < Test::Unit::TestCase end def test_darwin_invalid_access - assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle.dlunwrap(100).class']) + assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle.dlunwrap(100).inspect']) end end diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index 3b9eef770a..5ca6b0fbdd 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -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,31 @@ 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 + 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 end diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb new file mode 100644 index 0000000000..48b3e4acea --- /dev/null +++ b/test/ruby/test_yjit.rb @@ -0,0 +1,703 @@ +# frozen_string_literal: true +require 'test/unit' +require 'envutil' +require 'tmpdir' + +return unless defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? + +# Tests for YJIT with assertions on compilation and side exits +# insipired by the MJIT tests in test/ruby/test_jit.rb +class TestYJIT < Test::Unit::TestCase + def test_yjit_in_ruby_description + assert_includes(RUBY_DESCRIPTION, '+YJIT') + end + + def test_yjit_in_version + [ + %w(--version --yjit), + %w(--version --disable-yjit --yjit), + %w(--version --disable-yjit --enable-yjit), + %w(--version --disable-yjit --enable=yjit), + %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), + ] if RUBY_PLATFORM.start_with?('x86_64-') && RUBY_PLATFORM !~ /mswin|mingw|msys/), + ].each do |version_args| + assert_in_out_err(version_args) do |stdout, stderr| + assert_equal(RUBY_DESCRIPTION, stdout.first) + assert_equal([], stderr) + end + end + end + + 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/) + 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'], '') 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 RubyVM::YJIT.enabled?'], '', ['true']) + end + + 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 + script = 'class Foo; @@foo = 1; def self.foo; @@foo; end; end; Foo.foo' + assert_compiles(script, insns: %i[getclassvariable], result: 1) + end + + def test_compile_putnil + assert_compiles('nil', insns: %i[putnil], result: nil) + end + + def test_compile_putobject + assert_compiles('true', insns: %i[putobject], result: true) + assert_compiles('123', insns: %i[putobject], result: 123) + assert_compiles(':foo', insns: %i[putobject], result: :foo) + end + + def test_compile_opt_not + assert_compiles('!false', insns: %i[opt_not], result: true) + assert_compiles('!nil', insns: %i[opt_not], result: true) + assert_compiles('!true', insns: %i[opt_not], result: false) + assert_compiles('![]', insns: %i[opt_not], result: false) + end + + def test_compile_opt_newarray + assert_compiles('[]', insns: %i[newarray], result: []) + assert_compiles('[1+1]', insns: %i[newarray opt_plus], result: [2]) + assert_compiles('[1,1+1,3,4,5,6]', insns: %i[newarray opt_plus], result: [1, 2, 3, 4, 5, 6]) + end + + def test_compile_opt_duparray + assert_compiles('[1]', insns: %i[duparray], result: [1]) + assert_compiles('[1, 2, 3]', insns: %i[duparray], result: [1, 2, 3]) + end + + def test_compile_newrange + assert_compiles('s = 1; (s..5)', insns: %i[newrange], result: 1..5) + assert_compiles('s = 1; e = 5; (s..e)', insns: %i[newrange], result: 1..5) + assert_compiles('s = 1; (s...5)', insns: %i[newrange], result: 1...5) + assert_compiles('s = 1; (s..)', insns: %i[newrange], result: 1..) + 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) + assert_compiles('true.nil?', insns: %i[opt_nil_p], result: false) + assert_compiles('(-"").nil?', insns: %i[opt_nil_p], result: false) + assert_compiles('123.nil?', insns: %i[opt_nil_p], result: false) + end + + def test_compile_eq_fixnum + assert_compiles('123 == 123', insns: %i[opt_eq], result: true) + assert_compiles('123 == 456', insns: %i[opt_eq], result: false) + end + + def test_compile_eq_string + assert_compiles('-"" == -""', insns: %i[opt_eq], result: true) + assert_compiles('-"foo" == -"foo"', insns: %i[opt_eq], result: true) + assert_compiles('-"foo" == -"bar"', insns: %i[opt_eq], result: false) + end + + def test_compile_eq_symbol + assert_compiles(':foo == :foo', insns: %i[opt_eq], result: true) + assert_compiles(':foo == :bar', insns: %i[opt_eq], result: false) + assert_compiles(':foo == "foo".to_sym', insns: %i[opt_eq], result: true) + end + + def test_compile_eq_object + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: false) + def eq(a, b) + a == b + end + + eq(Object.new, Object.new) + RUBY + + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: true) + def eq(a, b) + a == b + end + + obj = Object.new + eq(obj, obj) + RUBY + end + + def test_compile_eq_arbitrary_class + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: "yes") + def eq(a, b) + a == b + end + + class Foo + def ==(other) + "yes" + end + end + + eq(Foo.new, Foo.new) + eq(Foo.new, Foo.new) + RUBY + end + + def test_compile_opt_lt + assert_compiles('1 < 2', insns: %i[opt_lt]) + assert_compiles('"a" < "b"', insns: %i[opt_lt]) + end + + def test_compile_opt_le + assert_compiles('1 <= 2', insns: %i[opt_le]) + assert_compiles('"a" <= "b"', insns: %i[opt_le]) + end + + def test_compile_opt_gt + assert_compiles('1 > 2', insns: %i[opt_gt]) + assert_compiles('"a" > "b"', insns: %i[opt_gt]) + end + + def test_compile_opt_ge + assert_compiles('1 >= 2', insns: %i[opt_ge]) + assert_compiles('"a" >= "b"', insns: %i[opt_ge]) + end + + def test_compile_opt_plus + assert_compiles('1 + 2', insns: %i[opt_plus]) + assert_compiles('"a" + "b"', insns: %i[opt_plus]) + assert_compiles('[:foo] + [:bar]', insns: %i[opt_plus]) + end + + def test_compile_opt_minus + assert_compiles('1 - 2', insns: %i[opt_minus]) + assert_compiles('[:foo, :bar] - [:bar]', insns: %i[opt_minus]) + end + + def test_compile_opt_or + assert_compiles('1 | 2', insns: %i[opt_or]) + assert_compiles('[:foo] | [:bar]', insns: %i[opt_or]) + end + + def test_compile_opt_and + assert_compiles('1 & 2', insns: %i[opt_and]) + assert_compiles('[:foo, :bar] & [:bar]', insns: %i[opt_and]) + end + + def test_compile_set_and_get_global + assert_compiles('$foo = 123; $foo', insns: %i[setglobal], result: 123) + end + + def test_compile_putspecialobject + assert_compiles('-> {}', insns: %i[putspecialobject]) + end + + def test_compile_tostring + assert_no_exits('"i am a string #{true}"') + end + + def test_compile_opt_aset + assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset]) + assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset]) + assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset]) + assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset]) + end + + def test_compile_attr_set + assert_no_exits(<<~EORB) + class Foo + attr_accessor :bar + end + + foo = Foo.new + foo.bar = 3 + foo.bar = 3 + foo.bar = 3 + foo.bar = 3 + EORB + end + + def test_compile_regexp + 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) + [1].map do |x| + [1].map do |y| + foo + bar + end + end + end + + foo(5, 2) + RUBY + end + + def test_setlocal_with_level + assert_no_exits(<<~RUBY) + def sum(arr) + sum = 0 + arr.each do |x| + sum += x + end + sum + end + + sum([1,2,3]) + RUBY + end + + def test_string_then_nil + assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: true) + def foo(val) + val.nil? + end + + foo("foo") + foo(nil) + RUBY + end + + def test_nil_then_string + assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: false) + def foo(val) + val.nil? + end + + foo(nil) + foo("foo") + RUBY + end + + def test_opt_length_in_method + assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5) + def foo(str) + str.length + end + + foo("hello, ") + foo("world") + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles(<<~RUBY, insns: %i[opt_regexpmatch2], result: 0) + def foo(str) + str =~ /foo/ + end + + foo("foobar") + RUBY + end + + def test_expandarray + assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [1, 2]) + a, b = [1, 2] + RUBY + end + + def test_expandarray_nil + assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [nil, nil]) + a, b = nil + [a, b] + RUBY + end + + def test_getspecial_backref + assert_compiles("'foo' =~ /(o)./; $&", insns: %i[getspecial], result: "oo") + assert_compiles("'foo' =~ /(o)./; $`", insns: %i[getspecial], result: "f") + assert_compiles("'foo' =~ /(o)./; $'", insns: %i[getspecial], result: "") + assert_compiles("'foo' =~ /(o)./; $+", insns: %i[getspecial], result: "o") + assert_compiles("'foo' =~ /(o)./; $1", insns: %i[getspecial], result: "o") + 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 get_foo + FOO + end + + FOO = 123 + + get_foo # warm inline cache + get_foo + RUBY + end + + def test_opt_getinlinecache_slowpath + assert_compiles(<<~RUBY, exits: { opt_getinlinecache: 1 }, result: [42, 42, 1, 1], min_calls: 2) + class A + FOO = 42 + class << self + def foo + _foo = nil + FOO + end + end + end + + result = [] + + result << A.foo + result << A.foo + + class << A + FOO = 1 + end + + result << A.foo + result << A.foo + + result + RUBY + end + + def test_string_interpolation + assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", min_calls: 2) + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str("foo", "bar") + make_str("foo", "bar") + RUBY + end + + def test_string_interpolation_cast + assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "123") + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str(1, 23) + RUBY + end + + 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 + obj.bar = 123 + end + + Foo = Struct.new(:foo, :bar) + foo(Foo.new(123)) + foo(Foo.new(123)) + RUBY + end + + def test_super_iseq + assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15) + class A + def foo + 1 + 2 + end + end + + class B < A + def foo + super * 5 + end + end + + B.new.foo + RUBY + end + + def test_super_cfunc + assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Hello") + class Gnirts < String + def initialize + super(-"olleH") + end + + def to_s + super().reverse + end + end + + Gnirts.new.to_s + RUBY + end + + # 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) + ::Foo = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h) + + def build_foo + ::Foo.new(:a, :b, :c, :d, :e, :f, :g, :h) + end + + build_foo + build_foo + RUBY + end + + def test_fib_recursion + assert_compiles(<<~'RUBY', insns: %i[opt_le opt_minus opt_plus opt_send_without_block], result: 34) + def fib(n) + return n if n <= 1 + fib(n-1) + fib(n-2) + end + + fib(9) + 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_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(':') + end + + foo(nil) + foo("example.com") + RUBY + end + + def test_no_excessive_opt_getinlinecache_invalidation + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + objects = [Object.new, Object.new] + + objects.each do |o| + class << o + def foo + Object + end + end + end + + 9000.times { + objects[0].foo + objects[1].foo + } + + stats = RubyVM::YJIT.runtime_stats + return :ok unless stats[:all_stats] + return :ok if stats[:invalidation_count] < 10 + + :fail + 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) + reset_stats = <<~RUBY + RubyVM::YJIT.runtime_stats + RubyVM::YJIT.reset_stats! + RUBY + + write_results = <<~RUBY + stats = RubyVM::YJIT.runtime_stats + + def collect_blocks(blocks) + blocks.sort_by(&:address).map { |b| [b.iseq_start_index, b.iseq_end_index] } + end + + def collect_iseqs(iseq) + iseq_array = iseq.to_a + insns = iseq_array.last.grep(Array) + blocks = RubyVM::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 + end + + iseq = RubyVM::InstructionSequence.of(_test_proc) + IO.open(3).write Marshal.dump({ + result: #{result == ANY ? "nil" : "result"}, + stats: stats, + iseqs: collect_iseqs(iseq), + disasm: iseq.disasm + }) + RUBY + + script = <<~RUBY + #{"# frozen_string_literal: true" if frozen_string_literal} + _test_proc = -> { + #{test_script} + } + #{reset_stats} + result = _test_proc.call + #{write_results} + RUBY + + status, out, err, stats = eval_with_jit(script, min_calls: min_calls) + + assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}" + + assert_equal stdout.chomp, out.chomp if stdout + + unless ANY.equal?(result) + assert_equal result, stats[:result] + end + + runtime_stats = stats[:stats] + iseqs = stats[:iseqs] + disasm = stats[:disasm] + + # Only available when RUBY_DEBUG enabled + 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 + flunk "Expected #{exits.empty? ? "no" : exits.inspect} exits" \ + ", but got\n#{recorded_exits.inspect}" + end + end + + # Only available when RUBY_DEBUG enabled + 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 } + + # 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}" + end + end + end + + def eval_with_jit(script, min_calls: 1, timeout: 1000) + args = [ + "--disable-gems", + "--yjit-call-threshold=#{min_calls}", + "--yjit-stats" + ] + args << "-e" << script + stats_r, stats_w = IO.pipe + out, err, status = EnvUtil.invoke_ruby(args, + '', true, true, timeout: timeout, ios: {3 => stats_w} + ) + stats_w.close + stats = stats_r.read + stats = Marshal.load(stats) if !stats.empty? + stats_r.close + [status, out, err, stats] + 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]], exits: :any) + 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 +end |
