diff options
Diffstat (limited to 'test/ruby')
134 files changed, 35987 insertions, 3892 deletions
diff --git a/test/ruby/bug-13526.rb b/test/ruby/bug-13526.rb index f42e1913ce..50c6c67a7d 100644 --- a/test/ruby/bug-13526.rb +++ b/test/ruby/bug-13526.rb @@ -1,5 +1,7 @@ # From https://bugs.ruby-lang.org/issues/13526#note-1 +Thread.report_on_exception = true + sleep if $load $load = true @@ -7,7 +9,7 @@ n = 10 threads = Array.new(n) do Thread.new do begin - autoload :Foo, "#{File.dirname($0)}/#{$0}" + autoload :Foo, File.expand_path(__FILE__) Thread.pass Foo ensure @@ -16,5 +18,5 @@ threads = Array.new(n) do end end -Thread.pass while threads.all?(&:stop?) -100.times { Thread.pass } +Thread.pass until threads.all?(&:stop?) +1000.times { Thread.pass } diff --git a/test/ruby/enc/test_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb index 2a759cc54d..bc57d57ee4 100644 --- a/test/ruby/enc/test_case_comprehensive.rb +++ b/test/ruby/enc/test_case_comprehensive.rb @@ -5,7 +5,8 @@ require "test/unit" class TestComprehensiveCaseMapping < Test::Unit::TestCase UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION'] - UNICODE_DATA_PATH = "../../../enc/unicode/data/#{UNICODE_VERSION}" + path = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}", __dir__) + UNICODE_DATA_PATH = File.directory?("#{path}/ucd") ? "#{path}/ucd" : path def self.hex2utf8(s) s.split(' ').map { |c| c.to_i(16) }.pack('U*') @@ -23,7 +24,7 @@ class TestComprehensiveCaseMapping < Test::Unit::TestCase def test_data_files_available unless TestComprehensiveCaseMapping.data_files_available? - skip "Unicode data files not available in #{UNICODE_DATA_PATH}." + omit "Unicode data files not available in #{UNICODE_DATA_PATH}." end end end @@ -72,7 +73,11 @@ TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveC @@codepoints << code upcase[code] = hex2utf8 data[12] unless data[12].empty? downcase[code] = hex2utf8 data[13] unless data[13].empty? - titlecase[code] = hex2utf8 data[14] unless data[14].empty? + if code>="\u1C90" and code<="\u1CBF" # exception for Georgian: use lowercase for titlecase + titlecase[code] = hex2utf8(data[13]) unless data[13].empty? + else + titlecase[code] = hex2utf8 data[14] unless data[14].empty? + end end read_data_file('CaseFolding') do |code, data| casefold[code] = hex2utf8(data[2]) if data[1] =~ /^[CF]$/ diff --git a/test/ruby/enc/test_case_mapping.rb b/test/ruby/enc/test_case_mapping.rb index d095cd569c..31acdc4331 100644 --- a/test/ruby/enc/test_case_mapping.rb +++ b/test/ruby/enc/test_case_mapping.rb @@ -187,6 +187,39 @@ class TestCaseMappingPreliminary < Test::Unit::TestCase assert_equal 0, "\ua64A" =~ /\uA64B/i end + def test_georgian_canary + message = "Reexamine implementation of Georgian in String#capitalize" + assert_equal false, "\u1CBB".match?(/\p{assigned}/), message + assert_equal false, "\u1CBC".match?(/\p{assigned}/), message + end + + def test_georgian_unassigned + message = "Unassigned codepoints should not be converted" + assert_equal "\u1CBB", "\u1CBB".capitalize, message + assert_equal "\u1CBC", "\u1CBC".capitalize, message + end + + def test_georgian_capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u10D2".capitalize + end + + def test_shift_jis_downcase_ascii + s = ("A".."Z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS") + assert_equal s, s.downcase(:ascii) + end + + def test_shift_jis_upcase_ascii + s = ("a".."z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS") + assert_equal s, s.upcase(:ascii) + end + def no_longer_a_test_buffer_allocations assert_equal 'TURKISH*ı'*10, ('I'*10).downcase(:turkic) assert_equal 'TURKISH*ı'*100, ('I'*100).downcase(:turkic) diff --git a/test/ruby/enc/test_cesu8.rb b/test/ruby/enc/test_cesu8.rb new file mode 100644 index 0000000000..68a08389ea --- /dev/null +++ b/test/ruby/enc/test_cesu8.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestCESU8 < Test::Unit::TestCase + + def encdump(obj) + case obj + when String + obj.dump + when Regexp + "Regexp.new(#{encdump(obj.source)}, #{obj.options})" + else + raise Argument, "unexpected: #{obj.inspect}" + end + end + + def enccall(recv, meth, *args) + desc = '' + if String === recv + desc << encdump(recv) + else + desc << recv.inspect + end + desc << '.' << meth.to_s + if !args.empty? + desc << '(' + args.each_with_index {|a, i| + desc << ',' if 0 < i + if String === a + desc << encdump(a) + else + desc << a.inspect + end + } + desc << ')' + end + result = nil + assert_nothing_raised(desc) { + result = recv.send(meth, *args) + } + result + end + + def assert_str_equal(expected, actual, message=nil) + full_message = build_message(message, <<EOT) +#{encdump expected} expected but not equal to +#{encdump actual}. +EOT + assert_equal(expected, actual, full_message) + end + + # tests start + + def test_cesu8_valid_encoding + all_assertions do |a| + [ + "\x00", + "\x7f", + "\u0080", + "\u07ff", + "\u0800", + "\ud7ff", + "\xed\xa0\x80\xed\xb0\x80", + "\xed\xaf\xbf\xed\xbf\xbf", + "\ue000", + "\uffff", + ].each {|s| + s.force_encoding("cesu-8") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "\x80", + "\xc0\x80", + "\xc0", + "\xe0\x80\x80", + "\xed\xa0\x80", + "\xed\xb0\x80\xed\xb0\x80", + "\xe0", + "\xff", + ].each {|s| + s.force_encoding("cesu-8") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end + end + + def test_cesu8_ord + [ + ["\x00", 0], + ["\x7f", 0x7f], + ["\u0080", 0x80], + ["\u07ff", 0x7ff], + ["\u0800", 0x800], + ["\ud7ff", 0xd7ff], + ["\xed\xa0\x80\xed\xb0\x80", 0x10000], + ["\xed\xaf\xbf\xed\xbf\xbf", 0x10ffff], + ["\xee\x80\x80", 0xe000], + ["\xef\xbf\xbf", 0xffff], + ].each do |chr, ord| + chr.force_encoding("cesu-8") + assert_equal ord, chr.ord + 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 new file mode 100644 index 0000000000..3e2999d61e --- /dev/null +++ b/test/ruby/enc/test_emoji_breaks.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestEmojiBreaks < Test::Unit::TestCase + class BreakTest + attr_reader :string, :comment, :filename, :line_number, :type, :shortname + + def initialize(filename, line_number, data, comment='') + @filename = filename + @line_number = line_number + @comment = comment.gsub(/\s+/, ' ').strip + if filename=='emoji-test' or filename=='emoji-variation-sequences' + codes, @type = data.split(/\s*;\s*/) + @shortname = '' + else + codes, @type, @shortname = data.split(/\s*;\s*/) + end + @type = @type.gsub(/\s+/, ' ').strip + @shortname = @shortname.gsub(/\s+/, ' ').strip + @string = codes.split(/\s+/) + .map do |ch| + c = ch.to_i(16) + # eliminate cases with surrogates + # raise ArgumentError if 0xD800 <= c and c <= 0xDFFF + c.chr('UTF-8') + end.join + end + end + + class BreakFile + attr_reader :basename, :fullname, :version + FILES = [] + + def initialize(basename, path, version) + @basename = basename + @fullname = "#{path}/#{basename}.txt" # File.expand_path(path + version, __dir__) + @version = version + FILES << self + end + + def self.files + FILES + end + end + + 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__) + + 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) + EMOJI_DATA_FILES << UNICODE_DATA_FILE + + def self.data_files_available? + EMOJI_DATA_FILES.all? do |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? + omit "Emoji data files not available in #{EMOJI_DATA_PATH}." + end + end + + if data_files_available? + def read_data + tests = [] + EMOJI_DATA_FILES.each do |file| + version_mismatch = true + file_tests = [] + IO.foreach(file.fullname, encoding: Encoding::UTF_8) do |line| + line.chomp! + if $.==1 + if line=="# #{file.basename}-#{file.version}.txt" + version_mismatch = false + elsif line!="# #{file.basename}.txt" + raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt" + end + end + version_mismatch = false if line =~ /^# Version: #{file.version}/ # 13.0 and older + version_mismatch = false if line =~ /^# Used with Emoji Version #{EMOJI_VERSION}/ # 14.0 and newer + next if line.match?(/\A(#|\z)/) + if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028) + range_start = $1.to_i(16) + range_end = $2.to_i(16) + rest = $3 + (range_start..range_end).each do |code_point| + file_tests << BreakTest.new(file.basename, $., *(code_point.to_s(16)+rest).split('#', 2)) + end + else + file_tests << BreakTest.new(file.basename, $., *line.split('#', 2)) + end + end + raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch + tests += file_tests + end + tests + end + + def all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def test_single_emoji + all_tests.each do |test| + expected = [test.string] + actual = test.string.each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + def test_embedded_emoji + all_tests.each do |test| + expected = ["\t", test.string, "\t"] + actual = "\t#{test.string}\t".each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + # test some pseodorandom combinations of emoji + def test_mixed_emoji + srand 0 + length = all_tests.length + step = 503 # use a prime number + all_tests.each do |test1| + start = rand step + start.step(by: step, to: length-1) do |t2| + test2 = all_tests[t2] + # exclude skin tones, because they glue to previous grapheme clusters + next if (0x1F3FB..0x1F3FF).include? test2.string.ord + expected = [test1.string, test2.string] + actual = (test1.string+test2.string).each_grapheme_cluster.to_a + assert_equal expected, actual, + "file1: #{test1.filename}, line1 #{test1.line_number}, " + + "file2: #{test2.filename}, line2 #{test2.line_number},\n" + + "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" + + "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}" + end + end + end + end +end diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb new file mode 100644 index 0000000000..f4e3c93b47 --- /dev/null +++ b/test/ruby/enc/test_grapheme_breaks.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestGraphemeBreaksFromFile < Test::Unit::TestCase + 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 + end + end + + 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 + GRAPHEME_BREAK_TEST_FILE = File.expand_path("#{UNICODE_DATA_PATH}/GraphemeBreakTest.txt", __dir__) + + def self.file_available? + File.exist? GRAPHEME_BREAK_TEST_FILE + end + + def test_data_files_available + unless TestGraphemeBreaksFromFile.file_available? + omit "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}." + end + end + + 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 + 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}" + 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 + 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}, after last removal, expected '#{expected}', " + + "but got '#{string}', comment: #{test.comment}" + end + end + end +end diff --git a/test/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb index cc525c268e..eaabbc58a2 100644 --- a/test/ruby/enc/test_regex_casefold.rb +++ b/test/ruby/enc/test_regex_casefold.rb @@ -5,11 +5,13 @@ require "test/unit" class TestCaseFold < 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") ? "#{path}/ucd" : path CaseTest = Struct.new :source, :target, :kind, :line 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 @@ -17,7 +19,7 @@ class TestCaseFold < Test::Unit::TestCase end def read_tests - IO.readlines(File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}/CaseFolding.txt", __dir__), encoding: Encoding::ASCII_8BIT) + IO.readlines("#{UNICODE_DATA_PATH}/CaseFolding.txt", encoding: Encoding::ASCII_8BIT) .collect.with_index { |linedata, linenumber| [linenumber.to_i+1, linedata.chomp] } .reject { |number, data| data =~ /^(#|$)/ } .collect do |linenumber, linedata| @@ -37,7 +39,7 @@ class TestCaseFold < Test::Unit::TestCase @@tests ||= read_tests rescue Errno::ENOENT => e @@tests ||= [] - skip e.message + omit e.message end def self.generate_test_casefold(encoding) diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb index 358d3c5133..7f100b7873 100644 --- a/test/ruby/marshaltestlib.rb +++ b/test/ruby/marshaltestlib.rb @@ -110,7 +110,9 @@ module MarshalTestLib class MyException < Exception; def initialize(v, *args) super(*args); @v = v; end; attr_reader :v; end def test_exception marshal_equal(Exception.new('foo')) {|o| o.message} - marshal_equal(assert_raise(NoMethodError) {no_such_method()}) {|o| o.message} + obj = Object.new + e = assert_raise(NoMethodError) {obj.no_such_method()} + 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 e81636fa43..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,10 +59,18 @@ class TestAlias < Test::Unit::TestCase assert_raise(NoMethodError) { x.quux } end - class C - def m - $SAFE - 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 @@ -233,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 311469aad9..903cd62b02 100644 --- a/test/ruby/test_argf.rb +++ b/test/ruby/test_argf.rb @@ -35,8 +35,8 @@ class TestArgf < Test::Unit::TestCase open("#{@tmpdir}/#{basename}-#{@tmp_count}", "w") end - def make_tempfile - t = make_tempfile0("argf-qux") + def make_tempfile(basename = "argf-qux") + t = make_tempfile0(basename) t.puts "foo" t.puts "bar" t.puts "baz" @@ -143,6 +143,17 @@ class TestArgf < Test::Unit::TestCase }; end + def test_lineno_after_shebang + expected = %w"1 1 1 2 2 2 3 3 1 4 4 2" + assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected) + #!/usr/bin/env ruby + {# + ARGF.each do |line| + puts [$., ARGF.lineno, ARGF.file.lineno] + end + }; + end + def test_new_lineno_each f = ARGF.class.new(@t1.path, @t2.path, @t3.path) result = [] @@ -255,6 +266,27 @@ class TestArgf < Test::Unit::TestCase assert_warning(/#{base}/) {argf.gets} end + def test_inplace_nonascii + omit "not working on MinGW" if /mingw/ =~ RUBY_PLATFORM + ext = Encoding.default_external or + omit "no default external encoding" + t = nil + ["\u{3042}", "\u{e9}"].any? do |n| + t = make_tempfile(n.encode(ext)) + rescue Encoding::UndefinedConversionError + end + t or omit "no name to test" + assert_in_out_err(["-i.bak", "-", t.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + }; + assert_equal("foo.new\n""bar.new\n""baz.new\n", File.read(t.path)) + assert_equal("foo\n""bar\n""baz\n", File.read(t.path + ".bak")) + end + def test_inplace_no_backup t = make_tempfile @@ -367,6 +399,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| {# @@ -705,6 +752,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| {# @@ -951,53 +1017,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 @@ -1054,4 +1122,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 new file mode 100644 index 0000000000..5e2a825265 --- /dev/null +++ b/test/ruby/test_arithmetic_sequence.rb @@ -0,0 +1,491 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestArithmeticSequence < Test::Unit::TestCase + def test_new + assert_raise(NoMethodError) { Enumerator::ArithmeticSequence.new } + end + + def test_allocate + assert_raise(TypeError) { Enumerator::ArithmeticSequence.allocate } + end + + def test_begin + assert_equal(1, 1.step.begin) + assert_equal(1, 1.step(10).begin) + assert_equal(1, 1.step(to: 10).begin) + assert_equal(1, 1.step(nil).begin) + assert_equal(1, 1.step(to: nil).begin) + assert_equal(1, 1.step(by: 2).begin) + assert_equal(1, 1.step(by: -1).begin) + assert_equal(1, 1.step(by: nil).begin) + assert_equal(1, 1.step(10, 2).begin) + assert_equal(1, 1.step(10, by: 2).begin) + assert_equal(1, 1.step(to: 10, by: 2).begin) + assert_equal(10, 10.step(to: 1, by: -1).begin) + assert_equal(10, 10.step(to: 1, by: -2).begin) + assert_equal(10, 10.step(to: -1, by: -2).begin) + assert_equal(10.0, 10.0.step(to: -1.0, by: -2.0).begin) + + assert_equal(3, (3..).step(2).begin) + assert_equal(4, (4...).step(7).begin) + assert_equal(nil, (..10).step(9).begin) + assert_equal(nil, (...11).step(5).begin) + end + + def test_end + assert_equal(nil, 1.step.end) + assert_equal(10, 1.step(10).end) + assert_equal(10, 1.step(to: 10).end) + assert_equal(nil, 1.step(nil).end) + assert_equal(nil, 1.step(to: nil).end) + assert_equal(nil, 1.step(by: 2).end) + assert_equal(nil, 1.step(by: -1).end) + assert_equal(nil, 1.step(by: nil).end) + assert_equal(10, 1.step(10, 2).end) + assert_equal(10, 1.step(10, by: 2).end) + assert_equal(10, 1.step(to: 10, by: 2).end) + assert_equal(1, 10.step(to: 1, by: -1).end) + assert_equal(1, 10.step(to: 1, by: -2).end) + assert_equal(-1, 10.step(to: -1, by: -2).end) + assert_equal(-1.0, 10.0.step(to: -1.0, by: -2.0).end) + + assert_equal(nil, (3..).step(2).end) + assert_equal(nil, (4...).step(7).end) + assert_equal(10, (..10).step(9).end) + assert_equal(11, (...11).step(5).end) + end + + def test_exclude_end_p + assert_equal(false, 1.step.exclude_end?) + assert_equal(false, 1.step(10).exclude_end?) + assert_equal(false, 1.step(to: 10).exclude_end?) + assert_equal(false, 1.step(nil).exclude_end?) + assert_equal(false, 1.step(to: nil).exclude_end?) + assert_equal(false, 1.step(by: 2).exclude_end?) + assert_equal(false, 1.step(by: -1).exclude_end?) + assert_equal(false, 1.step(by: nil).exclude_end?) + assert_equal(false, 1.step(10, 2).exclude_end?) + assert_equal(false, 1.step(10, by: 2).exclude_end?) + assert_equal(false, 1.step(to: 10, by: 2).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -1).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -2).exclude_end?) + assert_equal(false, 10.step(to: -1, by: -2).exclude_end?) + + assert_equal(false, (3..).step(2).exclude_end?) + assert_equal(true, (4...).step(7).exclude_end?) + assert_equal(false, (..10).step(9).exclude_end?) + assert_equal(true, (...11).step(5).exclude_end?) + end + + def test_step + assert_equal(1, 1.step.step) + assert_equal(1, 1.step(10).step) + assert_equal(1, 1.step(to: 10).step) + assert_equal(1, 1.step(nil).step) + assert_equal(1, 1.step(to: nil).step) + assert_equal(2, 1.step(by: 2).step) + assert_equal(-1, 1.step(by: -1).step) + assert_equal(1, 1.step(by: nil).step) + assert_equal(2, 1.step(10, 2).step) + assert_equal(2, 1.step(10, by: 2).step) + assert_equal(2, 1.step(to: 10, by: 2).step) + assert_equal(-1, 10.step(to: 1, by: -1).step) + assert_equal(-2, 10.step(to: 1, by: -2).step) + assert_equal(-2, 10.step(to: -1, by: -2).step) + assert_equal(-2.0, 10.0.step(to: -1.0, by: -2.0).step) + + assert_equal(2, (3..).step(2).step) + assert_equal(7, (4...).step(7).step) + assert_equal(9, (..10).step(9).step) + assert_equal(5, (...11).step(5).step) + end + + def test_eq + seq = 1.step + assert_equal(seq, seq) + assert_equal(seq, 1.step) + assert_equal(seq, 1.step(nil)) + end + + def test_eqq + seq = 1.step + assert_operator(seq, :===, seq) + assert_operator(seq, :===, 1.step) + assert_operator(seq, :===, 1.step(nil)) + end + + def test_eql_p + seq = 1.step + assert_operator(seq, :eql?, seq) + assert_operator(seq, :eql?, 1.step) + assert_operator(seq, :eql?, 1.step(nil)) + end + + def test_hash + seq = 1.step + assert_equal(seq.hash, seq.hash) + assert_equal(seq.hash, 1.step.hash) + assert_equal(seq.hash, 1.step(nil).hash) + assert_kind_of(String, seq.hash.to_s) + end + + def test_first + seq = 1.step + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 2, 3], seq.first(3)) + + seq = 1.step(by: 2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = 10.step(by: -2) + assert_equal(10, seq.first) + assert_equal([10], seq.first(1)) + assert_equal([10, 8, 6], seq.first(3)) + + seq = 1.step(by: 4) + assert_equal([1, 5, 9], seq.first(3)) + + seq = 1.step(10, by: 4) + assert_equal([1, 5, 9], seq.first(5)) + + seq = 1.step(0) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 1.step(10, by: -1) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], 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)) + assert_equal([10.0, 8.0, 6.0], seq.first(3)) + + seq = (1..).step(2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = (..10).step(2) + assert_equal(nil, seq.first) + assert_raise(TypeError) { seq.first(1) } + assert_raise(TypeError) { seq.first(3) } + end + + def test_first_bug15518 + bug15518 = '[Bug #15518]' + seq = (1 .. 10.0).step(1) + five_float_classes = Array.new(5) { Float } + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1r) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + end + + def test_last + seq = 1.step(10) + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = 1.step(10, 2) + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([5, 7, 9], seq.last(3)) + + seq = 10.step(1, -2) + assert_equal(2, seq.last) + assert_equal([2], seq.last(1)) + assert_equal([6, 4, 2], seq.last(3)) + + seq = 10.step(-1, -2) + assert_equal(0, seq.last) + + seq = 1.step(10, 4) + assert_equal([1, 5, 9], seq.last(5)) + + seq = 10.step(1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = 1.step(10, -1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = (1..10).step + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = (1...10).step + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([7, 8, 9], seq.last(3)) + + seq = 10.0.step(-3.0, by: -2.0) + assert_equal(-2.0, seq.last) + assert_equal([-2.0], seq.last(1)) + assert_equal([2.0, 0.0, -2.0], seq.last(3)) + end + + def test_last_with_float + res = (1..3).step(2).last(2.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(5.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + end + + def test_last_with_rational + res = (1..3).step(2).last(2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(10/2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + 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) + assert_equal([1, 3, 5, 7, 9], (1..10).step(2).to_a) + assert_equal([10, 8, 6, 4, 2], 10.step(1, by: -2).to_a) + assert_equal([10, 8, 6, 4, 2], (10..1).step(-2).to_a) + assert_equal([10.0, 8.0, 6.0, 4.0, 2.0], (10.0..1.0).step(-2.0).to_a) + end + + def test_to_a_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_num_equal_type([1/10r, 1/5r, 3/10r, 2/5r, 1/2r], seq.to_a, + '[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) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [4, 2]], seq.each_slice(3).to_a) + end + + def test_cons + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [3, 5, 7], [5, 7, 9]], seq.each_cons(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [8, 6, 4], [6, 4, 2]], seq.each_cons(3).to_a) + end + + def test_with_index + seq = 1.step(6, 2) + assert_equal([[1, 0], [3, 1], [5, 2]], seq.with_index.to_a) + assert_equal([[1, 10], [3, 11], [5, 12]], seq.with_index(10).to_a) + + seq = 10.step(5, -2) + assert_equal([[10, 0], [8, 1], [6, 2]], seq.with_index.to_a) + assert_equal([[10, 10], [8, 11], [6, 12]], seq.with_index(10).to_a) + end + + def test_with_object + obj = [0, 1] + seq = 1.step(10, 2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([25, 945], ret) + + obj = [0, 1] + seq = 10.step(1, -2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([30, 3840], ret) + end + + def test_next + seq = 1.step(10, 2) + [1, 3, 5, 7, 9].each do |i| + assert_equal(i, seq.next) + end + + seq = 10.step(1, -2) + [10, 8, 6, 4, 2].each do |i| + assert_equal(i, seq.next) + end + end + + def test_next_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_equal(1/10r, seq.next, '[ruby-core:90648] [Bug #15444]') + end + + def test_next_rewind + seq = 1.step(6, 2) + assert_equal(1, seq.next) + assert_equal(3, seq.next) + seq.rewind + assert_equal(1, seq.next) + assert_equal(3, seq.next) + assert_equal(5, seq.next) + assert_raise(StopIteration) { seq.next } + + seq = 10.step(5, -2) + assert_equal(10, seq.next) + assert_equal(8, seq.next) + seq.rewind + assert_equal(10, seq.next) + assert_equal(8, seq.next) + assert_equal(6, seq.next) + assert_raise(StopIteration) { seq.next } + end + + def test_next_after_stopiteration + seq = 1.step(2, 2) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + seq.rewind + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + end + + def test_stop_result + seq = 1.step(2, 2) + res = seq.each {} + assert_equal(1, seq.next) + exc = assert_raise(StopIteration) { seq.next } + assert_equal(res, exc.result) + end + + def test_peek + seq = 1.step(2, 2) + assert_equal(1, seq.peek) + assert_equal(1, seq.peek) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + + seq = 10.step(9, -2) + assert_equal(10, seq.peek) + assert_equal(10, seq.peek) + assert_equal(10, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + end + + def test_next_values + seq = 1.step(2, 2) + assert_equal([1], seq.next_values) + end + + def test_peek_values + seq = 1.step(2, 2) + assert_equal([1], seq.peek_values) + end + + def test_num_step_inspect + assert_equal('(1.step)', 1.step.inspect) + assert_equal('(1.step(10))', 1.step(10).inspect) + assert_equal('(1.step(10, 2))', 1.step(10, 2).inspect) + assert_equal('(1.step(10, by: 2))', 1.step(10, by: 2).inspect) + assert_equal('(1.step(by: 2))', 1.step(by: 2).inspect) + end + + def test_range_step_inspect + assert_equal('((1..).step)', (1..).step.inspect) + assert_equal('((1..10).step)', (1..10).step.inspect) + assert_equal('((1..10).step(2))', (1..10).step(2).inspect) + end + + def test_num_step_size + assert_equal(10, 1.step(10).size) + assert_equal(5, 1.step(10, 2).size) + assert_equal(4, 1.step(10, 3).size) + assert_equal(1, 1.step(10, 10).size) + assert_equal(0, 1.step(0).size) + assert_equal(Float::INFINITY, 1.step.size) + + assert_equal(10, 10.step(1, -1).size) + assert_equal(5, 10.step(1, -2).size) + assert_equal(4, 10.step(1, -3).size) + assert_equal(1, 10.step(1, -10).size) + assert_equal(0, 1.step(2, -1).size) + assert_equal(Float::INFINITY, 1.step(by: -1).size) + end + + def test_range_step_size + assert_equal(10, (1..10).step.size) + assert_equal(9, (1...10).step.size) + assert_equal(5, (1..10).step(2).size) + assert_equal(5, (1...10).step(2).size) + assert_equal(4, (1...9).step(2).size) + assert_equal(Float::INFINITY, (1..).step.size) + + assert_equal(10, (10..1).step(-1).size) + assert_equal(9, (10...1).step(-1).size) + assert_equal(5, (10..1).step(-2).size) + assert_equal(5, (10...1).step(-2).size) + assert_equal(4, (10...2).step(-2).size) + assert_equal(Float::INFINITY, (1..).step(-1).size) + end + + def assert_num_equal_type(ary1, ary2, message=nil) + assert_equal(ary1.length, ary2.length, message) + ary1.zip(ary2) do |e1, e2| + assert_equal(e1.class, e2.class, message) + if e1.is_a? Complex + assert_equal(e1.real, e2.real, message) + assert_equal(e1.imag, e2.imag, message) + else + assert_equal(e1, e2, message) + end + end + end + + def test_complex + assert_num_equal_type([1, 1+1i, 1+2i], (1..).step(1i).take(3)) + assert_num_equal_type([1, 1+1.0i, 1+2.0i], (1..).step(1.0i).take(3)) + assert_num_equal_type([0.0, 0.0+1.0i, 0.0+2.0i], (0.0..).step(1.0i).take(3)) + assert_num_equal_type([0.0+0.0i, 0.0+1.0i, 0.0+2.0i], (0.0i..).step(1.0i).take(3)) + end + + def test_sum + assert_equal([1, 3, 5, 7, 9].sum, (1..10).step(2).sum) + assert_equal([1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0].sum, (1.0..10.0).step(1.5).sum) + assert_equal([1/2r, 1r, 3/2r, 2, 5/2r, 3, 7/2r, 4].sum, ((1/2r)...(9/2r)).step(1/2r).sum) + end +end diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb index b98248f603..d26338e0aa 100644 --- a/test/ruby/test_arity.rb +++ b/test/ruby/test_arity.rb @@ -2,7 +2,7 @@ require 'test/unit' class TestArity < Test::Unit::TestCase - def err_mess(method_proc = nil, argc = 0) + def assert_arity(expected, method_proc = nil, argc = 0) args = (1..argc).to_a assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b(\d+)\b.* (\d\S*?)\)/) do case method_proc @@ -14,7 +14,7 @@ class TestArity < Test::Unit::TestCase method_proc.call(*args) end end - [$1, $2] + assert_equal expected, [$1, $2] end def a @@ -36,22 +36,22 @@ class TestArity < Test::Unit::TestCase end def test_method_err_mess - assert_equal %w[1 0], err_mess(:a, 1) - assert_equal %w[10 7..9], err_mess(:b, 10) - assert_equal %w[2 3+], err_mess(:c, 2) - assert_equal %w[2 1], err_mess(:d, 2) - assert_equal %w[0 1], err_mess(:d, 0) - assert_equal %w[2 1], err_mess(:e, 2) - assert_equal %w[0 1], err_mess(:e, 0) - assert_equal %w[1 2+], err_mess(:f, 1) + assert_arity(%w[1 0], :a, 1) + assert_arity(%w[10 7..9], :b, 10) + assert_arity(%w[2 3+], :c, 2) + assert_arity(%w[2 1], :d, 2) + assert_arity(%w[0 1], :d, 0) + assert_arity(%w[2 1], :e, 2) + assert_arity(%w[0 1], :e, 0) + assert_arity(%w[1 2+], :f, 1) end def test_proc_err_mess - assert_equal %w[0 1..2], err_mess(->(b, c=42){}, 0) - assert_equal %w[1 2+], err_mess(->(a, b, c=42, *d){}, 1) - assert_equal %w[3 4+], err_mess(->(a, b, *c, d, e){}, 3) - assert_equal %w[3 1..2], err_mess(->(b, c=42){}, 3) - assert_equal %w[1 0], err_mess(->(&block){}, 1) + assert_arity(%w[0 1..2], ->(b, c=42){}, 0) + assert_arity(%w[1 2+], ->(a, b, c=42, *d){}, 1) + assert_arity(%w[3 4+], ->(a, b, *c, d, e){}, 3) + assert_arity(%w[3 1..2], ->(b, c=42){}, 3) + assert_arity(%w[1 0], ->(&block){}, 1) # Double checking: p = Proc.new{|b, c=42| :ok} assert_equal :ok, p.call(1, 2, 3) @@ -59,12 +59,12 @@ class TestArity < Test::Unit::TestCase end def test_message_change_issue_6085 - assert_equal %w[3 1..2], err_mess{ SignalException.new(1, "", nil) } - assert_equal %w[1 0], err_mess{ Hash.new(1){} } - assert_equal %w[3 1..2], err_mess{ Module.send :define_method, 1, 2, 3 } - assert_equal %w[1 2], err_mess{ "".sub!(//) } - assert_equal %w[0 1..2], err_mess{ "".sub!{} } - assert_equal %w[0 1+], err_mess{ exec } - assert_equal %w[0 1+], err_mess{ Struct.new } + assert_arity(%w[3 1..2]) { SignalException.new(1, "", nil) } + assert_arity(%w[1 0]) { Hash.new(1){} } + assert_arity(%w[3 1..2]) { Module.send :define_method, 1, 2, 3 } + assert_arity(%w[1 2]) { "".sub!(//) } + assert_arity(%w[0 1..2]) { "".sub!{} } + assert_arity(%w[0 1+]) { exec } + assert_arity(%w[0 1+]) { Struct.new } end end diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 508312e4a2..6c0db0832b 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]) @@ -41,6 +45,9 @@ class TestArray < Test::Unit::TestCase assert_equal(2, x[2]) assert_equal([1, 2, 3], x[1..3]) assert_equal([1, 2, 3], x[1,3]) + assert_equal([3, 4, 5], x[3..]) + assert_equal([0, 1, 2], x[..2]) + assert_equal([0, 1], x[...2]) x[0, 2] = 10 assert_equal([10, 2, 3, 4, 5], x) @@ -111,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) @@ -145,14 +155,17 @@ class TestArray < Test::Unit::TestCase assert_equal(1, x.first) assert_equal([1], x.first(1)) assert_equal([1, 2, 3], x.first(3)) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)} assert_equal(5, x.last) assert_equal([5], x.last(1)) assert_equal([3, 4, 5], x.last(3)) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.last(1, 2)} assert_equal(1, x.shift) assert_equal([2, 3, 4], x.shift(3)) assert_equal([5], x) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)} assert_equal([2, 3, 4, 5], x.unshift(2, 3, 4)) assert_equal([1, 2, 3, 4, 5], x.unshift(1)) @@ -161,6 +174,7 @@ class TestArray < Test::Unit::TestCase assert_equal(5, x.pop) assert_equal([3, 4], x.pop(2)) assert_equal([1, 2], x) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.pop(1, 2)} assert_equal([1, 2, 3, 4], x.push(3, 4)) assert_equal([1, 2, 3, 4, 5], x.push(5)) @@ -170,6 +184,7 @@ class TestArray < Test::Unit::TestCase def test_find_all_0 assert_respond_to([], :find_all) assert_respond_to([], :select) # Alias + assert_respond_to([], :filter) # Alias assert_equal([], [].find_all{ |obj| obj == "foo"}) x = ["foo", "bar", "baz", "baz", 1, 2, 3, 3, 4] @@ -198,6 +213,8 @@ class TestArray < Test::Unit::TestCase assert_equal([0, 1, 2, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(3...4){|i| i+10}) assert_equal([0, 1, 12, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(2..-2){|i| i+10}) assert_equal([0, 1, 12, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(2...-2){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3..){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3...){|i| i+10}) end # From rubicon @@ -231,6 +248,23 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[], @cls[ 1, 2, 3 ]*64 & @cls[ 4, 5, 6 ]*64) end + def test_intersection + assert_equal(@cls[1, 2], @cls[1, 2, 3].intersection(@cls[1, 2])) + assert_equal(@cls[ ], @cls[1].intersection(@cls[ ])) + assert_equal(@cls[ ], @cls[ ].intersection(@cls[1])) + assert_equal(@cls[1], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[1])) + assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[3])) + assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[4, 5, 6])) + end + + def test_intersection_big_array + assert_equal(@cls[1, 2], (@cls[1, 2, 3] * 64).intersection(@cls[1, 2] * 64)) + assert_equal(@cls[ ], (@cls[1] * 64).intersection(@cls[ ])) + assert_equal(@cls[ ], @cls[ ].intersection(@cls[1] * 64)) + assert_equal(@cls[1], (@cls[1, 2, 3] * 64).intersection((@cls[1, 2] * 64), (@cls[1] * 64))) + assert_equal(@cls[ ], (@cls[1, 2, 3] * 64).intersection(@cls[4, 5, 6] * 64)) + end + def test_MUL # '*' assert_equal(@cls[], @cls[]*3) assert_equal(@cls[1, 1, 1], @cls[1]*3) @@ -254,29 +288,39 @@ class TestArray < Test::Unit::TestCase def test_MINUS # '-' assert_equal(@cls[], @cls[1] - @cls[1]) assert_equal(@cls[1], @cls[1, 2, 3, 4, 5] - @cls[2, 3, 4, 5]) - # Ruby 1.8 feature change - #assert_equal(@cls[1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5]) assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5]) - a = @cls[] - 1000.times { a << 1 } - assert_equal(1000, a.length) - #assert_equal(@cls[1], a - @cls[2]) - assert_equal(@cls[1] * 1000, a - @cls[2]) - #assert_equal(@cls[1], @cls[1, 2, 1] - @cls[2]) assert_equal(@cls[1, 1], @cls[1, 2, 1] - @cls[2]) assert_equal(@cls[1, 2, 3], @cls[1, 2, 3] - @cls[4, 5, 6]) end def test_MINUS_big_array # '-' assert_equal(@cls[1]*64, @cls[1, 2, 3, 4, 5]*64 - @cls[2, 3, 4, 5]*64) - # Ruby 1.8 feature change - #assert_equal(@cls[1], @cls[1, 2, 1, 3, 1, 4, 1, 5]*64 - @cls[2, 3, 4, 5]*64) assert_equal(@cls[1, 1, 1, 1]*64, @cls[1, 2, 1, 3, 1, 4, 1, 5]*64 - @cls[2, 3, 4, 5]*64) a = @cls[] 1000.times { a << 1 } assert_equal(1000, a.length) - #assert_equal(@cls[1], a - @cls[2]) - assert_equal(@cls[1] * 1000, a - @cls[2]) + assert_equal(@cls[1] * 1000, a - @cls[2]) + end + + def test_difference + assert_equal(@cls[], @cls[1].difference(@cls[1])) + assert_equal(@cls[1], @cls[1, 2, 3, 4, 5].difference(@cls[2, 3, 4, 5])) + assert_equal(@cls[1, 1], @cls[1, 2, 1].difference(@cls[2])) + assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5].difference(@cls[2, 3, 4, 5])) + assert_equal(@cls[], @cls[1, 2, 3, 4].difference(@cls[1], @cls[2], @cls[3], @cls[4])) + a = [1] + assert_equal(@cls[1], a.difference(@cls[2], @cls[2])) + assert_equal(@cls[], a.difference(@cls[1])) + assert_equal(@cls[1], a) + end + + def test_difference_big_array + assert_equal(@cls[1]*64, (@cls[1, 2, 3, 4, 5] * 64).difference(@cls[2, 3, 4] * 64, @cls[3, 5] * 64)) + assert_equal(@cls[1, 1, 1, 1]*64, (@cls[1, 2, 1, 3, 1, 4, 1, 5] * 64).difference(@cls[2, 3, 4, 5] * 64)) + a = @cls[1] * 1000 + assert_equal(@cls[1] * 1000, a.difference(@cls[2], @cls[2])) + assert_equal(@cls[], a.difference(@cls[1])) + assert_equal(@cls[1] * 1000, a) end def test_LSHIFT # '<<' @@ -352,12 +396,15 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[99], a[-2..-2]) assert_equal(@cls[10, 11, 12], a[9..11]) + assert_equal(@cls[98, 99, 100], a[97..]) + assert_equal(@cls[1, 2, 3], a[..2]) + assert_equal(@cls[1, 2], a[...2]) assert_equal(@cls[10, 11, 12], a[-91..-89]) + assert_equal(@cls[98, 99, 100], a[-3..]) + assert_equal(@cls[1, 2, 3], a[..-98]) + assert_equal(@cls[1, 2], a[...-98]) assert_nil(a[10, -3]) - # Ruby 1.8 feature change: - # Array#[size..x] returns [] instead of nil. - #assert_nil(a[10..7]) assert_equal [], a[10..7] assert_raise(TypeError) {a['cat']} @@ -413,33 +460,41 @@ class TestArray < Test::Unit::TestCase assert_equal(b, a[10..19] = b) assert_equal(@cls[*(0..9).to_a] + b + @cls[*(20..99).to_a], a) - # Ruby 1.8 feature change: - # assigning nil does not remove elements. -=begin a = @cls[*(0..99).to_a] assert_equal(nil, a[0,1] = nil) - assert_equal(@cls[*(1..99).to_a], a) + assert_equal(@cls[nil] + @cls[*(1..99).to_a], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[10,10] = nil) - assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a) + assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[-1, 1] = nil) - assert_equal(@cls[*(0..98).to_a], a) + assert_equal(@cls[*(0..98).to_a] + @cls[nil], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[-10, 10] = nil) - assert_equal(@cls[*(0..89).to_a], a) + assert_equal(@cls[*(0..89).to_a] + @cls[nil], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[0,1000] = nil) - assert_equal(@cls[] , a) + assert_equal(@cls[nil] , a) a = @cls[*(0..99).to_a] assert_equal(nil, a[10..19] = nil) - assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a) -=end + assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[10..] = nil) + assert_equal(@cls[*(0..9).to_a] + @cls[nil], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[..10] = nil) + assert_equal(@cls[nil] + @cls[*(11..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[...10] = nil) + assert_equal(@cls[nil] + @cls[*(10..99).to_a], a) a = @cls[1, 2, 3] a[1, 0] = a @@ -508,18 +563,14 @@ class TestArray < Test::Unit::TestCase end def test_clone - for taint in [ false, true ] - for frozen in [ false, true ] - a = @cls[*(0..99).to_a] - a.taint if taint - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert_not_equal(a.__id__, b.__id__) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @cls[*(0..99).to_a] + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_equal(a.__id__, b.__id__) + assert_equal(a.frozen?, b.frozen?) end end @@ -530,10 +581,11 @@ class TestArray < Test::Unit::TestCase assert_equal([], @cls[].collect { 99 }) - # Ruby 1.9 feature change: - # Enumerable#collect without block returns an Enumerator. - #assert_equal([1, 2, 3], @cls[1, 2, 3].collect) assert_kind_of Enumerator, @cls[1, 2, 3].collect + + assert_raise(ArgumentError) { + assert_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]})) + } end # also update map! @@ -600,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(RuntimeError) { [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 @@ -609,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]' @@ -702,21 +763,26 @@ 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 - for taint in [ false, true ] - for frozen in [ false, true ] - a = @cls[*(0..99).to_a] - a.taint if taint - a.freeze if frozen - b = a.dup - - assert_equal(a, b) - assert_not_equal(a.__id__, b.__id__) - assert_equal(false, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @cls[*(0..99).to_a] + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_equal(a.__id__, b.__id__) + assert_equal(false, b.frozen?) end end @@ -732,7 +798,7 @@ class TestArray < Test::Unit::TestCase a = @cls[] i = 0 a.each { |e| - assert_equal(a[i], e) + assert(false, "Never get here") i += 1 } assert_equal(0, i) @@ -752,7 +818,7 @@ class TestArray < Test::Unit::TestCase a = @cls[] i = 0 a.each_index { |ind| - assert_equal(i, ind) + assert(false, "Never get here") i += 1 } assert_equal(0, i) @@ -801,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 @@ -816,13 +882,6 @@ class TestArray < Test::Unit::TestCase assert_raise(TypeError, "[ruby-dev:31197]") { [[]].flatten("") } end - def test_flatten_taint - a6 = @cls[[1, 2], 3] - a6.taint - a7 = a6.flatten - assert_equal(true, a7.tainted?) - end - def test_flatten_level0 a8 = @cls[[1, 2], 3] a9 = a8.flatten(0) @@ -852,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 ] @@ -1039,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')) @@ -1047,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 @@ -1058,56 +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(',')) - $, = "" - a = @cls[1, 2, 3] - a.taint - s = a.join - assert_equal(true, s.tainted?) - - bug5902 = '[ruby-core:42161]' - sep = ":".taint - - s = @cls[].join(sep) - assert_equal(false, s.tainted?, bug5902) - s = @cls[1].join(sep) - assert_equal(false, s.tainted?, bug5902) - s = @cls[1, 2].join(sep) - assert_equal(true, s.tainted?, bug5902) + 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 @@ -1225,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) @@ -1247,9 +1322,6 @@ class TestArray < Test::Unit::TestCase a = @cls[1, 2, 3] assert_equal(@cls[1, 2, 3, 4, 5], a.push(4, 5)) assert_equal(@cls[1, 2, 3, 4, 5, nil], a.push(nil)) - # Ruby 1.8 feature: - # Array#push accepts any number of arguments. - #assert_raise(ArgumentError, "a.push()") { a.push() } a.push assert_equal @cls[1, 2, 3, 4, 5, nil], a a.push 6, 7 @@ -1287,6 +1359,74 @@ 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! + c = [] + b = [1, 2, 3, 4] + 3.times do + a = b.dup + c << a.dup + + begin + a.reject! do |x| + case x + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + c << a.dup + end + + bug90781 = '[ruby-core:90781]' + assert_equal [[1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4]], c, bug90781 + end + + def test_iseq_shared_array_reject! + c = [] + 3.times do + a = [1, 2, 3, 4] + c << a.dup + + begin + a.reject! do |x| + case x + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + c << a.dup + end + + bug90781 = '[ruby-core:90781]' + assert_equal [[1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4]], c, bug90781 end def test_replace @@ -1299,10 +1439,18 @@ class TestArray < Test::Unit::TestCase fa = a.dup.freeze assert_nothing_raised(RuntimeError) { a.replace(a) } - assert_raise(RuntimeError) { fa.replace(fa) } + assert_raise(FrozenError) { fa.replace(fa) } assert_raise(ArgumentError) { fa.replace() } assert_raise(TypeError) { a.replace(42) } - assert_raise(RuntimeError) { fa.replace(42) } + assert_raise(FrozenError) { fa.replace(42) } + end + + def test_replace_wb_variable_width_alloc + small_embed = [] + 4.times { GC.start } # age small_embed + large_embed = [1, 2, 3, 4, 5, Array.new] # new young object + small_embed.replace(large_embed) # adds old to young reference + GC.verify_internal_consistency end def test_reverse @@ -1316,9 +1464,6 @@ class TestArray < Test::Unit::TestCase a = @cls[*%w( dog cat bee ant )] assert_equal(@cls[*%w(ant bee cat dog)], a.reverse!) assert_equal(@cls[*%w(ant bee cat dog)], a) - # Ruby 1.8 feature change: - # Array#reverse always returns self. - #assert_nil(@cls[].reverse!) assert_equal @cls[], @cls[].reverse! end @@ -1348,7 +1493,17 @@ 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 + def o2.==(other) + other.replace([]) if Array === other + false + end + a = Array.new(10) + a.fill(o2) + assert_nil(a.rindex(a), bug15951) end def test_shift @@ -1378,35 +1533,164 @@ 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_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)) - assert_equal(@cls[10, 11, 12], a.slice(9..11)) - assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) + a = [0, 1, 2, 3, 4, 5] + assert_equal([2, 1, 0], a.slice((2..).step(-1))) + assert_equal([2, 0], a.slice((2..).step(-2))) + assert_equal([2], a.slice((2..).step(-3))) + assert_equal([2], a.slice((2..).step(-4))) + + assert_equal([3, 2, 1, 0], a.slice((-3..).step(-1))) + assert_equal([3, 1], a.slice((-3..).step(-2))) + assert_equal([3, 0], a.slice((-3..).step(-3))) + assert_equal([3], a.slice((-3..).step(-4))) + assert_equal([3], a.slice((-3..).step(-5))) + + assert_equal([5, 4, 3, 2, 1, 0], a.slice((..0).step(-1))) + assert_equal([5, 3, 1], a.slice((..0).step(-2))) + assert_equal([5, 2], a.slice((..0).step(-3))) + assert_equal([5, 1], a.slice((..0).step(-4))) + assert_equal([5, 0], a.slice((..0).step(-5))) + assert_equal([5], a.slice((..0).step(-6))) + assert_equal([5], a.slice((..0).step(-7))) + + assert_equal([5, 4, 3, 2, 1], a.slice((...0).step(-1))) + assert_equal([5, 3, 1], a.slice((...0).step(-2))) + assert_equal([5, 2], a.slice((...0).step(-3))) + assert_equal([5, 1], a.slice((...0).step(-4))) + assert_equal([5], a.slice((...0).step(-5))) + assert_equal([5], a.slice((...0).step(-6))) + + assert_equal([5, 4, 3, 2], a.slice((...1).step(-1))) + assert_equal([5, 3], a.slice((...1).step(-2))) + assert_equal([5, 2], a.slice((...1).step(-3))) + assert_equal([5], a.slice((...1).step(-4))) + assert_equal([5], a.slice((...1).step(-5))) + + assert_equal([5, 4, 3, 2, 1], a.slice((..-5).step(-1))) + assert_equal([5, 3, 1], a.slice((..-5).step(-2))) + assert_equal([5, 2], a.slice((..-5).step(-3))) + assert_equal([5, 1], a.slice((..-5).step(-4))) + assert_equal([5], a.slice((..-5).step(-5))) + assert_equal([5], a.slice((..-5).step(-6))) + + assert_equal([5, 4, 3, 2], a.slice((...-5).step(-1))) + assert_equal([5, 3], a.slice((...-5).step(-2))) + assert_equal([5, 2], a.slice((...-5).step(-3))) + assert_equal([5], a.slice((...-5).step(-4))) + assert_equal([5], a.slice((...-5).step(-5))) + + assert_equal([4, 3, 2, 1], a.slice((4..1).step(-1))) + assert_equal([4, 2], a.slice((4..1).step(-2))) + assert_equal([4, 1], a.slice((4..1).step(-3))) + assert_equal([4], a.slice((4..1).step(-4))) + assert_equal([4], a.slice((4..1).step(-5))) + + assert_equal([4, 3, 2], a.slice((4...1).step(-1))) + assert_equal([4, 2], a.slice((4...1).step(-2))) + assert_equal([4], a.slice((4...1).step(-3))) + assert_equal([4], a.slice((4...1).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((-2..1).step(-1))) + assert_equal([4, 2], a.slice((-2..1).step(-2))) + assert_equal([4, 1], a.slice((-2..1).step(-3))) + assert_equal([4], a.slice((-2..1).step(-4))) + assert_equal([4], a.slice((-2..1).step(-5))) + + assert_equal([4, 3, 2], a.slice((-2...1).step(-1))) + assert_equal([4, 2], a.slice((-2...1).step(-2))) + assert_equal([4], a.slice((-2...1).step(-3))) + assert_equal([4], a.slice((-2...1).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((4..-5).step(-1))) + assert_equal([4, 2], a.slice((4..-5).step(-2))) + assert_equal([4, 1], a.slice((4..-5).step(-3))) + assert_equal([4], a.slice((4..-5).step(-4))) + assert_equal([4], a.slice((4..-5).step(-5))) + + assert_equal([4, 3, 2], a.slice((4...-5).step(-1))) + assert_equal([4, 2], a.slice((4...-5).step(-2))) + assert_equal([4], a.slice((4...-5).step(-3))) + assert_equal([4], a.slice((4...-5).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((-2..-5).step(-1))) + assert_equal([4, 2], a.slice((-2..-5).step(-2))) + assert_equal([4, 1], a.slice((-2..-5).step(-3))) + assert_equal([4], a.slice((-2..-5).step(-4))) + assert_equal([4], a.slice((-2..-5).step(-5))) + + assert_equal([4, 3, 2], a.slice((-2...-5).step(-1))) + assert_equal([4, 2], a.slice((-2...-5).step(-2))) + assert_equal([4], a.slice((-2...-5).step(-3))) + assert_equal([4], a.slice((-2...-5).step(-4))) + end + + def test_slice_out_of_range + 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)) - # Ruby 1.8 feature change: - # Array#slice[size..x] always returns []. - #assert_nil(a.slice(10..7)) assert_equal @cls[], a.slice(10..7) + + assert_equal([100], a.slice(-1, 1_000_000_000)) end def test_slice! @@ -1419,15 +1703,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] @@ -1446,10 +1733,27 @@ class TestArray < Test::Unit::TestCase assert_equal(nil, a.slice!(-6,2)) assert_equal(@cls[1, 2, 3, 4, 5], a) + assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + assert_raise(ArgumentError) { @cls[1].slice! } 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) @@ -1489,6 +1793,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 @@ -1509,13 +1844,21 @@ class TestArray < Test::Unit::TestCase end def test_sort_with_replace - xary = (1..100).to_a - 100.times do - ary = (1..100).to_a - ary.sort! {|a,b| ary.replace(xary); a <=> b} - GC.start - assert_equal(xary, ary, '[ruby-dev:34732]') - end + bug = '[ruby-core:34732]' + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + bug = "#{bug}" + begin; + xary = (1..100).to_a + 100.times do + ary = (1..100).to_a + ary.sort! {|a,b| ary.replace(xary); a <=> b} + GC.start + assert_equal(xary, ary, '[ruby-dev:34732]') + end + assert_nothing_raised(SystemStackError, bug) do + assert_equal(:ok, Array.new(100_000, nil).permutation {break :ok}) + end + end; end def test_sort_bang_with_freeze @@ -1530,7 +1873,7 @@ class TestArray < Test::Unit::TestCase o2 = o1.clone ary << o1 << o2 orig = ary.dup - assert_raise(RuntimeError, "frozen during comparison") {ary.sort!} + assert_raise(FrozenError, "frozen during comparison") {ary.sort!} assert_equal(orig, ary, "must not be modified once frozen") end @@ -1544,6 +1887,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__ @@ -1571,38 +1921,40 @@ class TestArray < Test::Unit::TestCase def o.to_ary foo_bar() end - assert_match(/foo_bar/, assert_raise(NoMethodError) {a.concat(o)}.message) + assert_raise_with_message(NoMethodError, /foo_bar/) {a.concat(o)} 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 $, = nil end + StubToH = [ + [:key, :value], + Object.new.tap do |kvp| + def kvp.to_ary + [:obtained, :via_to_ary] + end + end, + ] + def test_to_h - kvp = Object.new - def kvp.to_ary - [:obtained, :via_to_ary] - end - array = [ - [:key, :value], - kvp, - ] + array = StubToH assert_equal({key: :value, obtained: :via_to_ary}, array.to_h) e = assert_raise(TypeError) { @@ -1617,11 +1969,34 @@ class TestArray < Test::Unit::TestCase assert_equal "wrong array length at 2 (expected 2, was 1)", e.message end + def test_to_h_block + array = StubToH + assert_equal({"key" => "value", "obtained" => "via_to_ary"}, + array.to_h {|k, v| [k.to_s, v.to_s]}) + + assert_equal({first_one: :ok, not_ok: :ng}, + [[:first_one, :ok], :not_ok].to_h {|k, v| [k, v || :ng]}) + + e = assert_raise(TypeError) { + [[:first_one, :ok], :not_ok].to_h {|k, v| v ? [k, v] : k} + } + assert_equal "wrong element type Symbol at 1 (expected array)", e.message + array = [1] + k = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h {k}} + e = assert_raise(ArgumentError) { + [[:first_one, :ok], [1, 2], [:not_ok]].to_h {|kv| kv} + } + assert_equal "wrong array length at 2 (expected 2, was 1)", e.message + 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 }) @@ -1631,13 +2006,28 @@ class TestArray < Test::Unit::TestCase ary.min(2) {|a,b| a.length <=> b.length }) assert_equal([13, 14], [20, 32, 32, 21, 30, 25, 29, 13, 14].min(2)) assert_equal([2, 4, 6, 7], [2, 4, 8, 6, 7].min(4)) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + 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 }) @@ -1646,6 +2036,39 @@ class TestArray < Test::Unit::TestCase assert_equal(%w[albatross horse], ary.max(2) {|a,b| a.length <=> b.length }) assert_equal([3, 2], [0, 0, 0, 0, 0, 0, 1, 3, 2].max(2)) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + 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 } + assert_equal([[3, 2], [1, 3]], [1, 2, 3, 1, 2].each_with_index.minmax(&cond)) + ary = %w(albatross dog horse) + assert_equal(["albatross", "horse"], ary.minmax) + assert_equal(["dog", "albatross"], ary.minmax {|a,b| a.length <=> b.length }) + assert_equal([1, 3], [3,2,1].minmax) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + ary = [obj, 1.0].minmax + assert_same(obj, ary[0]) + assert_equal(obj, ary[1]) end def test_uniq @@ -1696,6 +2119,27 @@ class TestArray < Test::Unit::TestCase ary = [bug9340, bug9340.dup, bug9340.dup] assert_equal 1, ary.uniq.size assert_same bug9340, ary.uniq[0] + + sc = Class.new(@cls) + a = sc[] + b = a.dup + assert_equal_instance([], a.uniq) + assert_equal(b, a) + + a = sc[1] + b = a.dup + assert_equal_instance([1], a.uniq) + assert_equal(b, a) + + a = sc[1, 1] + b = a.dup + assert_equal_instance([1], a.uniq) + assert_equal(b, a) + + a = sc[1, 1] + b = a.dup + assert_equal_instance([1], a.uniq{|x| x}) + assert_equal(b, a) end def test_uniq_with_block @@ -1762,7 +2206,7 @@ class TestArray < Test::Unit::TestCase f = a.dup.freeze assert_raise(ArgumentError) { a.uniq!(1) } assert_raise(ArgumentError) { f.uniq!(1) } - assert_raise(RuntimeError) { f.uniq! } + assert_raise(FrozenError) { f.uniq! } assert_nothing_raised do a = [ {c: "b"}, {c: "r"}, {c: "w"}, {c: "g"}, {c: "g"} ] @@ -1808,7 +2252,7 @@ class TestArray < Test::Unit::TestCase def test_uniq_bang_with_freeze ary = [1,2] orig = ary.dup - assert_raise(RuntimeError, "frozen during comparison") { + assert_raise(FrozenError, "frozen during comparison") { ary.uniq! {|v| ary.freeze; 1} } assert_equal(orig, ary, "must not be modified once frozen") @@ -1822,6 +2266,17 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[@cls[1,2], nil, 'dog', 'cat'], a.unshift(@cls[1, 2])) end + def test_unshift_frozen + bug15952 = '[Bug #15952]' + assert_raise(FrozenError, bug15952) do + a = [1] * 100 + b = a[4..-1] + a.replace([1]) + b.freeze + b.unshift("a") + end + end + def test_OR # '|' assert_equal(@cls[], @cls[] | @cls[]) assert_equal(@cls[1], @cls[1] | @cls[]) @@ -1881,6 +2336,44 @@ class TestArray < Test::Unit::TestCase assert_equal((1..128).to_a, b) end + def test_union + assert_equal(@cls[], @cls[].union(@cls[])) + assert_equal(@cls[1], @cls[1].union(@cls[])) + assert_equal(@cls[1], @cls[].union(@cls[1])) + assert_equal(@cls[1], @cls[].union(@cls[], @cls[1])) + assert_equal(@cls[1], @cls[1].union(@cls[1])) + assert_equal(@cls[1], @cls[1].union(@cls[1], @cls[1], @cls[1])) + + assert_equal(@cls[1,2], @cls[1].union(@cls[2])) + assert_equal(@cls[1,2], @cls[1, 1].union(@cls[2, 2])) + assert_equal(@cls[1,2], @cls[1, 2].union(@cls[1, 2])) + assert_equal(@cls[1,2], @cls[1, 1].union(@cls[1, 1], @cls[1, 2], @cls[2, 1], @cls[2, 2, 2])) + + a = %w(a b c) + b = %w(a b c d e) + c = a.union(b) + assert_equal(c, b) + assert_not_same(c, b) + assert_equal(%w(a b c), a) + assert_equal(%w(a b c d e), b) + assert(a.none?(&:frozen?)) + assert(b.none?(&:frozen?)) + assert(c.none?(&:frozen?)) + end + + def test_union_big_array + assert_equal(@cls[1,2], (@cls[1]*64).union(@cls[2]*64)) + assert_equal(@cls[1,2,3], (@cls[1, 2]*64).union(@cls[1, 2]*64, @cls[3]*60)) + + a = (1..64).to_a + b = (1..128).to_a + c = a | b + assert_equal(c, b) + assert_not_same(c, b) + assert_equal((1..64).to_a, a) + assert_equal((1..128).to_a, b) + end + def test_combination a = @cls[] assert_equal(1, a.combination(0).size) @@ -2060,23 +2553,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| @@ -2101,13 +2594,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 @@ -2115,13 +2608,17 @@ class TestArray < Test::Unit::TestCase assert_raise(IndexError) { [0][LONGP] = 2 } assert_raise(IndexError) { [0][(LONGP + 1) / 2 - 1] = 2 } assert_raise(IndexError) { [0][LONGP..-1] = 2 } + assert_raise(IndexError) { [0][LONGP..] = 2 } a = [0] a[2] = 4 assert_equal([0, nil, 4], a) assert_raise(ArgumentError) { [0][0, 0, 0] = 0 } assert_raise(ArgumentError) { [0].freeze[0, 0, 0] = 0 } assert_raise(TypeError) { [0][:foo] = 0 } - assert_raise(RuntimeError) { [0].freeze[: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 @@ -2146,16 +2643,17 @@ class TestArray < Test::Unit::TestCase end def test_unshift_error - assert_raise(RuntimeError) { [].freeze.unshift('cat') } - assert_raise(RuntimeError) { [].freeze.unshift() } + assert_raise(FrozenError) { [].freeze.unshift('cat') } + assert_raise(FrozenError) { [].freeze.unshift() } end def test_aref assert_raise(ArgumentError) { [][0, 0, 0] } + 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) } @@ -2208,7 +2706,7 @@ class TestArray < Test::Unit::TestCase assert_equal([0, 1, 2], a.insert(-1, 2)) assert_equal([0, 1, 3, 2], a.insert(-2, 3)) assert_raise_with_message(IndexError, /-6/) { a.insert(-6, 4) } - assert_raise(RuntimeError) { [0].freeze.insert(0)} + assert_raise(FrozenError) { [0].freeze.insert(0)} assert_raise(ArgumentError) { [0].freeze.insert } end @@ -2224,6 +2722,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 @@ -2272,6 +2791,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! @@ -2287,6 +2815,34 @@ 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 + assert_equal([0, 2], [0, 1, 2, 3].filter {|x| x % 2 == 0 }) + end + + # alias for select! + def test_filter! + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(nil, a.filter! { true }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { false }) + assert_equal(@cls[], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { |i| i > 3 }) + assert_equal(@cls[4, 5], a) end def test_delete2 @@ -2333,13 +2889,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) @@ -2366,31 +2933,27 @@ 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) } assert_raise(ArgumentError) { f.flatten!(1, 2) } - assert_raise(RuntimeError) { f.flatten! } - assert_raise(RuntimeError) { f.flatten!(:foo) } + assert_raise(FrozenError) { f.flatten! } + assert_raise(FrozenError) { f.flatten!(:foo) } end def test_shuffle @@ -2423,7 +2986,9 @@ class TestArray < Test::Unit::TestCase assert_raise(RangeError) { [*0..2].shuffle(random: gen) } + end + def test_shuffle_random_clobbering ary = (0...10000).to_a gen = proc do ary.replace([]) @@ -2433,7 +2998,9 @@ class TestArray < Test::Unit::TestCase alias rand call end assert_raise(RuntimeError) {ary.shuffle!(random: gen)} + end + def test_shuffle_random_zero zero = Object.new def zero.to_int 0 @@ -2446,7 +3013,10 @@ class TestArray < Test::Unit::TestCase end ary = (0...10000).to_a assert_equal(ary.rotate, ary.shuffle(random: gen_to_int)) + end + def test_shuffle_random_invalid_generator + ary = (0...10).to_a assert_raise(NoMethodError) { ary.shuffle(random: Object.new) } @@ -2463,7 +3033,9 @@ class TestArray < Test::Unit::TestCase assert_include([0, 1, 2], sample) } end + end + def test_sample_statistics srand(0) a = (1..18).to_a (0..20).each do |n| @@ -2480,9 +3052,13 @@ class TestArray < Test::Unit::TestCase end assert_operator(h.values.min * 2, :>=, h.values.max) if n != 0 end + end + def test_sample_invalid_argument assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)} + end + def test_sample_random_srand0 gen = Random.new(0) srand(0) a = (1..18).to_a @@ -2491,13 +3067,15 @@ class TestArray < Test::Unit::TestCase assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}") end end + end + def test_sample_unknown_keyword assert_raise_with_message(ArgumentError, /unknown keyword/) do [0, 1, 2].sample(xawqij: "a") end end - def test_sample_random + def test_sample_random_generator ary = (0...10000).to_a assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)} gen0 = proc do |max| @@ -2540,7 +3118,9 @@ class TestArray < Test::Unit::TestCase assert_equal([5000, 0, 5001, 2, 5002, 4, 5003, 6, 5004, 8, 5005], ary.sample(11, random: gen0)) ary.sample(11, random: gen1) # implementation detail, may change in the future assert_equal([], ary) + end + def test_sample_random_generator_half half = Object.new def half.to_int 5000 @@ -2553,7 +3133,10 @@ class TestArray < Test::Unit::TestCase end ary = (0...10000).to_a assert_equal(5000, ary.sample(random: gen_to_int)) + end + def test_sample_random_invalid_generator + ary = (0..10).to_a assert_raise(NoMethodError) { ary.sample(random: Object.new) } @@ -2619,22 +3202,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_inspect - a = @cls[1, 2, 3] - a.taint - s = a.inspect - assert_equal(true, s.tainted?) - end - def test_initialize2 a = [1] * 1000 a.instance_eval { initialize } @@ -2728,7 +3295,7 @@ class TestArray < Test::Unit::TestCase assert_equal([], a.rotate!(13)) assert_equal([], a.rotate!(-13)) a = [].freeze - assert_raise_with_message(RuntimeError, /can\'t modify frozen/) {a.rotate!} + assert_raise_with_message(FrozenError, /can\'t modify frozen/) {a.rotate!} a = [1,2,3] assert_raise(ArgumentError) { a.rotate!(1, 1) } end @@ -2769,6 +3336,8 @@ class TestArray < Test::Unit::TestCase assert_equal(nil, a.bsearch {|x| 1 * (2**100) }) assert_equal(nil, a.bsearch {|x| (-1) * (2**100) }) + assert_equal(4, a.bsearch {|x| (4 - x).to_r }) + assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) end @@ -2804,6 +3373,8 @@ class TestArray < Test::Unit::TestCase assert_equal(nil, a.bsearch_index {|x| 1 * (2**100) }) assert_equal(nil, a.bsearch_index {|x| (-1) * (2**100) }) + assert_equal(1, a.bsearch_index {|x| (4 - x).to_r }) + assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) end @@ -2827,7 +3398,7 @@ class TestArray < Test::Unit::TestCase end EOS rescue Timeout::Error => e - skip e.message + omit e.message end end @@ -2838,7 +3409,7 @@ class TestArray < Test::Unit::TestCase Bug11235 = '[ruby-dev:49043] [Bug #11235]' def test_push_over_ary_max - assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 120) begin; a = Array.new(ARGV[0].to_i) assert_raise(IndexError, ARGV[1]) {0x1000.times {a.push(1)}} @@ -2944,6 +3515,13 @@ class TestArray < Test::Unit::TestCase assert_float_equal(large_number+(small_number*10), [large_number/1r, *[small_number]*10].sum) assert_float_equal(large_number+(small_number*11), [small_number, large_number/1r, *[small_number]*10].sum) assert_float_equal(small_number, [large_number, small_number, -large_number].sum) + assert_equal(+Float::INFINITY, [+Float::INFINITY].sum) + assert_equal(+Float::INFINITY, [0.0, +Float::INFINITY].sum) + assert_equal(+Float::INFINITY, [+Float::INFINITY, 0.0].sum) + assert_equal(-Float::INFINITY, [-Float::INFINITY].sum) + assert_equal(-Float::INFINITY, [0.0, -Float::INFINITY].sum) + assert_equal(-Float::INFINITY, [-Float::INFINITY, 0.0].sum) + assert_predicate([-Float::INFINITY, Float::INFINITY].sum, :nan?) assert_equal("abc", ["a", "b", "c"].sum("")) assert_equal([1, [2], 3], [[1], [[2]], [3]].sum([])) @@ -2952,6 +3530,14 @@ class TestArray < Test::Unit::TestCase assert_raise(TypeError) {[1].sum("")} end + def test_big_array_literal_with_kwsplat + lit = "[" + 10000.times { lit << "{}," } + lit << "**{}]" + + assert_equal(10000, eval(lit).size) + end + private def need_continuation unless respond_to?(:callcc, true) @@ -2959,3 +3545,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 45c4d6e058..3d0e773c82 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -81,6 +81,162 @@ 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_const_order + order = [] + + test_mod_class = Class.new(Module) do + define_method(:x1){order << :x1; self} + define_method(:y1){order << :y1; self} + define_method(:x2){order << :x2; self} + define_method(:x3){order << :x3; self} + define_method(:x4){order << :x4; self} + define_method(:[]){|*args| order << [:[], *args]; self} + define_method(:r1){order << :r1; :r1} + define_method(:r2){order << :r2; :r2} + + define_method(:constant_values) do + h = {} + constants.each do |sym| + h[sym] = const_get(sym) + end + h + end + + define_singleton_method(:run) do |code| + m = new + m.instance_eval(code) + ret = [order.dup, m.constant_values] + order.clear + ret + end + end + + ord, constants = test_mod_class.run( + "x1.y1::A, x2[1, 2, 3]::B, self[4]::C = r1, 6, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>6, :C=>:r2}, constants) + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, self[4]::C = r1, 6, 7, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants) + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C = r1, 6, 7, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants) + + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C, x4::D = r1, 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2, :D=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x2::B), _a = [r1, r2], 7" + ) + assert_equal([:x1, :y1, :x2, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>:r2}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C = [r1, 5], 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7, :r2, 8]}, constants) + + ord, constants = test_mod_class.run( + "*x2[1, 2, 3]::A, (x3[4]::B, x4::C) = 6, 7, [r2, 8]" + ) + assert_equal([:x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r2], ord) + assert_equal({:A=>[6, 7], :B=>:r2, :C=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, x3[4]::D, x4::E = [r1, 5], 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, (x3[4]::D, x4::E) = [r1, 5], 6, 7, [r2, 8]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((*x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>[:r2], :E=>8}, constants) + end + def test_massign_splat a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c]) @@ -92,6 +248,21 @@ class TestAssignment < Test::Unit::TestCase a,b,*c = *[*[1,2]]; assert_equal([1,2,[]], [a,b,c]) end + def test_massign_optimized_literal_bug_21012 + a = [] + def a.[]=(*args) + push args + end + a["a", "b"], = 1 + a["a", 10], = 2 + assert_equal [["a", "b", 1], ["a", 10, 2]], a + end + + def test_assign_rescue + a = raise rescue 2; assert_equal(2, a) + a, b = raise rescue [3,4]; assert_equal([3, 4], [a, b]) + end + def test_assign_abbreviated bug2050 = '[ruby-core:25629]' a = Hash.new {[]} @@ -114,32 +285,32 @@ class TestAssignment < Test::Unit::TestCase def []=(i, a); 42; end end - assert_raise(NoMethodError) { + assert_raise(NoMethodError, bug11096) { o.instance_eval {o.foo = 1} } - assert_nothing_raised(NoMethodError) { + assert_nothing_raised(NoMethodError, bug11096) { assert_equal(1, o.instance_eval {self.foo = 1}) } - assert_raise(NoMethodError) { + assert_raise(NoMethodError, bug11096) { o.instance_eval {o[0] = 1} } - assert_nothing_raised(NoMethodError) { + assert_nothing_raised(NoMethodError, bug11096) { assert_equal(1, o.instance_eval {self[0] = 1}) } - assert_raise(NoMethodError, bug11096) { - assert_equal(43, o.instance_eval {self.foo += 1}) + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self.foo += 1} } - assert_raise(NoMethodError, bug11096) { - assert_equal(1, o.instance_eval {self.foo &&= 1}) + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self.foo &&= 1} } - assert_raise(NoMethodError, bug11096) { - assert_equal(43, o.instance_eval {self[0] += 1}) + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self[0] += 1} } - assert_raise(NoMethodError, bug11096) { - assert_equal(1, o.instance_eval {self[0] &&= 1}) + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self[0] &&= 1} } end @@ -451,7 +622,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 @@ -480,11 +651,10 @@ class TestAssignment < Test::Unit::TestCase assert_equal 1, a assert_equal [2, 3], b - # not supported yet - #a, *b, c = 1, 2, 3, 4 - #assert_equal 1, a - #assert_equal [2,3], b - #assert_equal 4, c + a, *b, c = 1, 2, 3, 4 + assert_equal 1, a + assert_equal [2,3], b + assert_equal 4, c a = 1, 2 assert_equal [1, 2], a @@ -557,6 +727,16 @@ class TestAssignment < Test::Unit::TestCase result = eval("if (a, b = MyObj.new); [a, b]; end", nil, __FILE__, __LINE__) assert_equal [[1,2],[3,4]], result end + + def test_const_assign_order + assert_raise(RuntimeError) do + eval('raise("recv")::C = raise(ArgumentError, "bar")') + end + + assert_raise(RuntimeError) do + eval('m = 1; m::C = raise("bar")') + end + end end require_relative 'sentence' diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb new file mode 100644 index 0000000000..e43dac28b3 --- /dev/null +++ b/test/ruby/test_ast.rb @@ -0,0 +1,1063 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' +require 'pp' + +class RubyVM + module AbstractSyntaxTree + class Node + class CodePosition + include Comparable + attr_reader :lineno, :column + def initialize(lineno, column) + @lineno = lineno + @column = column + end + + def <=>(other) + case + when lineno < other.lineno + -1 + when lineno == other.lineno + column <=> other.column + when lineno > other.lineno + 1 + end + end + end + + def beg_pos + CodePosition.new(first_lineno, first_column) + end + + def end_pos + CodePosition.new(last_lineno, last_column) + end + + alias to_s inspect + end + end +end + +class TestAst < Test::Unit::TestCase + class Helper + attr_reader :errors + + def initialize(path, src: nil) + @path = path + @errors = [] + @debug = false + @ast = RubyVM::AbstractSyntaxTree.parse(src) if src + end + + def validate_range + @errors = [] + validate_range0(ast) + + @errors.empty? + end + + def validate_not_cared + @errors = [] + validate_not_cared0(ast) + + @errors.empty? + end + + def ast + return @ast if defined?(@ast) + @ast = RubyVM::AbstractSyntaxTree.parse_file(@path) + end + + private + + def validate_range0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + return true if children.empty? + # These NODE_D* has NODE_LIST as nd_next->nd_next whose last locations + # we can not update when item is appended. + return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type + + min = children.map(&:beg_pos).min + max = children.map(&:end_pos).max + + unless beg_pos <= min + @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node } + end + + unless max <= end_pos + @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node } + end + + p "#{node} => #{children}" if @debug + + children.each do |child| + p child if @debug + validate_range0(child) + end + end + + def validate_not_cared0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0 + @errors << { type: :first_column, node: node } if beg_pos.column == -1 + @errors << { type: :last_lineno, node: node } if end_pos.lineno == 0 + @errors << { type: :last_column, node: node } if end_pos.column == -1 + + children.each {|c| validate_not_cared0(c) } + end + end + + SRCDIR = File.expand_path("../../..", __FILE__) + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_ranges:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_range + + assert_equal([], helper.errors) + end + end + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_not_cared:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_not_cared + + assert_equal([], helper.errors) + end + end + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_all_tokens:#{path}") do + node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) + tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] } + tokens_bytes = tokens.map { _1[2]}.join.bytes + source_bytes = File.read("#{SRCDIR}/#{path}").bytes + + assert_equal(source_bytes, tokens_bytes) + + (tokens.count - 1).times do |i| + token_0 = tokens[i] + token_1 = tokens[i + 1] + end_pos = token_0.last[2..3] + beg_pos = token_1.last[0..1] + + if end_pos[0] == beg_pos[0] + # When both tokens are same line, column should be consecutives + assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}") + else + # Line should be next + assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}") + # It should be on the beginning of the line + assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}") + end + end + end + end + + private def parse(src) + EnvUtil.suppress_warning { + RubyVM::AbstractSyntaxTree.parse(src) + } + end + + def test_allocate + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate} + end + + def test_parse_argument_error + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)} + end + + def test_column_with_long_heredoc_identifier + term = "A"*257 + ast = parse("<<-#{term}\n""ddddddd\n#{term}\n") + node = ast.children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + end + + def test_column_of_heredoc + node = parse("<<-SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(6, node.last_column) + + node = parse("<<SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(5, node.last_column) + end + + def test_parse_raises_syntax_error + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse("end") + end + end + + def test_parse_file_raises_syntax_error + Tempfile.create(%w"test_ast .rb") do |f| + f.puts "end" + f.close + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse_file(f.path) + end + end + end + + def test_node_id_for_location + exception = begin + raise + rescue => e + e + end + loc = exception.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true) + + assert_equal node.node_id, node_id + end + + def test_node_id_for_backtrace_location_raises_argument_error + bug19262 = '[ruby-core:111435]' + + assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) } + end + + def test_of_proc_and_method + proc = Proc.new { 1 + 2 } + method = self.method(__method__) + + node_proc = RubyVM::AbstractSyntaxTree.of(proc) + node_method = RubyVM::AbstractSyntaxTree.of(method) + + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method) + + Tempfile.create(%w"test_of .rb") do |tmp| + tmp.print "#{<<-"begin;"}\n#{<<-'end;'}" + begin; + SCRIPT_LINES__ = {} + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x})) + end; + tmp.close + assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + load ARGV[0] + assert_empty(SCRIPT_LINES__) + end; + 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 + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + + method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("proc{}") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_backtrace_location_under_eval + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = false + + m = Module.new do + eval(<<-END, nil, __FILE__, __LINE__) + def self.sample_backtrace_location + caller_locations(0).first + end + END + end + backtrace_location = m.sample_backtrace_location + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) } + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_backtrace_location_under_eval_with_keep_script_lines + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + + m = Module.new do + eval(<<-END, nil, __FILE__, __LINE__) + def self.sample_backtrace_location + caller_locations(0).first + end + END + end + backtrace_location = m.sample_backtrace_location + node = RubyVM::AbstractSyntaxTree.of(backtrace_location) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node) + assert_equal(2, node.first_lineno) + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_c_method + 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 + assert_equal([:_x], lv) + assert_equal(:LASGN, body.type) + end + + def test_call + node = RubyVM::AbstractSyntaxTree.parse("nil.foo") + _, _, body = *node.children + assert_equal(:CALL, body.type) + recv, mid, args = body.children + assert_equal(:NIL, recv.type) + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_fcall + node = RubyVM::AbstractSyntaxTree.parse("foo()") + _, _, body = *node.children + assert_equal(:FCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_vcall + node = RubyVM::AbstractSyntaxTree.parse("foo") + _, _, body = *node.children + assert_equal(:VCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_defn + node = RubyVM::AbstractSyntaxTree.parse("def a; end") + _, _, 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_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 + node = RubyVM::AbstractSyntaxTree.parse("def a.b; end") + _, _, 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_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 + node = parse('"foo#{1}bar"') + _, _, body = *node.children + assert_equal(:DSTR, body.type) + head, body = body.children + assert_equal("foo", head) + assert_equal(:EVSTR, body.type) + body, = body.children + assert_equal(:LIT, body.type) + assert_equal([1], body.children) + end + + def test_while + node = RubyVM::AbstractSyntaxTree.parse('1 while qux') + _, _, body = *node.children + assert_equal(:WHILE, body.type) + type1 = body.children[2] + node = RubyVM::AbstractSyntaxTree.parse('begin 1 end while qux') + _, _, body = *node.children + assert_equal(:WHILE, body.type) + type2 = body.children[2] + assert_not_equal(type1, type2) + end + + def test_until + node = RubyVM::AbstractSyntaxTree.parse('1 until qux') + _, _, body = *node.children + assert_equal(:UNTIL, body.type) + type1 = body.children[2] + node = RubyVM::AbstractSyntaxTree.parse('begin 1 end until qux') + _, _, body = *node.children + assert_equal(:UNTIL, body.type) + type2 = body.children[2] + assert_not_equal(type1, type2) + end + + def test_rest_arg + rest_arg = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-4] + end + + assert_equal(nil, rest_arg.call('')) + assert_equal(:r, rest_arg.call('*r')) + assert_equal(:r, rest_arg.call('a, *r')) + assert_equal(:*, rest_arg.call('*')) + assert_equal(:*, rest_arg.call('a, *')) + end + + def test_block_arg + block_arg = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-1] + end + + assert_equal(nil, block_arg.call('')) + assert_equal(:block, block_arg.call('&block')) + assert_equal(:&, block_arg.call('&')) + end + + def test_keyword_rest + kwrest = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-2] + node ? node.children : node + end + + assert_equal(nil, kwrest.call('')) + assert_equal([:**], kwrest.call('**')) + assert_equal(false, kwrest.call('**nil')) + assert_equal([:a], kwrest.call('**a')) + end + + def test_argument_forwarding + forwarding = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1] + node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : [] + end + + assert_equal([:*, nil, :&], forwarding.call('...')) + end + + def test_ranges_numbered_parameter + helper = Helper.new(__FILE__, src: "1.times {_1}") + helper.validate_range + 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 + # Stop a warning "possibly useless use of a literal in void context" + verbose_bak, $VERBOSE = $VERBOSE, nil + + enc = Encoding::EUC_JP + code = "__ENCODING__".encode(enc) + + assert_equal(enc, eval(code)) + + node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false) + assert_equal(enc, node.children[2].children[0]) + + node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true) + assert_equal(enc, node.children[2].children[0]) + + ensure + $VERBOSE = verbose_bak + end + + def test_e_option + assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"], + "", [":SCOPE"], []) + end + + def test_error_tolerant + verbose_bak, $VERBOSE = $VERBOSE, false + node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true) + class A + def m + if; + a = 10 + end + end + STR + assert_nil($!) + + assert_equal(:SCOPE, node.type) + ensure + $VERBOSE = verbose_bak + end + + def test_error_tolerant_end_is_short_for_method_define + assert_error_tolerant(<<~STR, <<~EXP) + def m + m2 + STR + (SCOPE@1:0-2:4 + tbl: [] + args: nil + body: + (DEFN@1:0-2:4 + mid: :m + body: + (SCOPE@1:0-2:4 + tbl: [] + args: + (ARGS@1:5-1:5 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:4 :m2)))) + EXP + end + + def test_error_tolerant_end_is_short_for_singleton_method_define + assert_error_tolerant(<<~STR, <<~EXP) + def obj.m + m2 + STR + (SCOPE@1:0-2:4 + tbl: [] + args: nil + body: + (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m + (SCOPE@1:0-2:4 + tbl: [] + args: + (ARGS@1:9-1:9 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:4 :m2)))) + EXP + end + + def test_error_tolerant_end_is_short_for_begin + assert_error_tolerant(<<~STR, <<~EXP) + begin + a = 1 + STR + (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1))) + EXP + end + + def test_error_tolerant_end_is_short_for_if + assert_error_tolerant(<<~STR, <<~EXP) + if cond + a = 1 + STR + (SCOPE@1:0-2:7 + tbl: [:a] + args: nil + body: + (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)) nil)) + EXP + + assert_error_tolerant(<<~STR, <<~EXP) + if cond + a = 1 + else + STR + (SCOPE@1:0-3:4 + tbl: [:a] + args: nil + body: + (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)) + (BEGIN@3:4-3:4 nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_unless + assert_error_tolerant(<<~STR, <<~EXP) + unless cond + a = 1 + STR + (SCOPE@1:0-2:7 + tbl: [:a] + args: nil + body: + (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)) + nil)) + EXP + + assert_error_tolerant(<<~STR, <<~EXP) + unless cond + a = 1 + else + STR + (SCOPE@1:0-3:4 + tbl: [:a] + args: nil + body: + (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (LIT@2:6-2:7 1)) + (BEGIN@3:4-3:4 nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_while + assert_error_tolerant(<<~STR, <<~EXP) + while true + m + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true)) + EXP + end + + def test_error_tolerant_end_is_short_for_until + assert_error_tolerant(<<~STR, <<~EXP) + until true + m + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true)) + EXP + end + + def test_error_tolerant_end_is_short_for_case + assert_error_tolerant(<<~STR, <<~EXP) + case a + when 1 + STR + (SCOPE@1:0-2:6 + tbl: [] + args: nil + body: + (CASE@1:0-2:6 (VCALL@1:5-1:6 :a) + (WHEN@2:0-2:6 (LIST@2:5-2:6 (LIT@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil) + nil))) + EXP + + + assert_error_tolerant(<<~STR, <<~EXP) + case + when a == 1 + STR + (SCOPE@1:0-2:11 + tbl: [] + args: nil + body: + (CASE2@1:0-2:11 nil + (WHEN@2:0-2:11 + (LIST@2:5-2:11 + (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :== + (LIST@2:10-2:11 (LIT@2:10-2:11 1) nil)) nil) + (BEGIN@2:11-2:11 nil) nil))) + EXP + + + assert_error_tolerant(<<~STR, <<~EXP) + case a + in {a: String} + STR + (SCOPE@1:0-2:14 + tbl: [] + args: nil + body: + (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a) + (IN@2:0-2:14 + (HSHPTN@2:4-2:13 + const: nil + kw: + (HASH@2:4-2:13 + (LIST@2:4-2:13 (LIT@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil)) + kwrest: nil) (BEGIN@2:14-2:14 nil) nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_for + assert_error_tolerant(<<~STR, <<~EXP) + for i in ary + m + STR + (SCOPE@1:0-2:3 + tbl: [:i] + args: nil + body: + (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary) + (SCOPE@1:0-2:3 + tbl: [nil] + args: + (ARGS@1:4-1:5 + pre_num: 1 + pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil)) + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:3 :m)))) + EXP + end + + def test_error_tolerant_end_is_short_for_class + assert_error_tolerant(<<~STR, <<~EXP) + class C + STR + (SCOPE@1:0-1:7 + tbl: [] + args: nil + body: + (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil + (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil)))) + EXP + end + + def test_error_tolerant_end_is_short_for_module + assert_error_tolerant(<<~STR, <<~EXP) + module M + STR + (SCOPE@1:0-1:8 + tbl: [] + args: nil + body: + (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M) + (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do + assert_error_tolerant(<<~STR, <<~EXP) + m do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil) + (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do_block + assert_error_tolerant(<<~STR, <<~EXP) + m 1 do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (LIT@1:2-1:3 1) nil)) + (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do_LAMBDA + assert_error_tolerant(<<~STR, <<~EXP) + -> do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (LAMBDA@1:0-2:3 + (SCOPE@1:2-2:3 + tbl: [] + args: + (ARGS@1:2-1:2 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_treat_end_as_keyword_based_on_indent + assert_error_tolerant(<<~STR, <<~EXP) + module Z + class Foo + foo. + end + + def bar + end + end + STR + (SCOPE@1:0-8:3 + tbl: [] + args: nil + body: + (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z) + (SCOPE@1:0-8:3 + tbl: [] + args: nil + body: + (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil) + (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil + (SCOPE@2:2-4:5 + tbl: [] + args: nil + body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5)))) + (DEFN@6:2-7:5 + mid: :bar + body: + (SCOPE@6:2-7:5 + tbl: [] + args: + (ARGS@6:9-6:9 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: nil)))))) + EXP + end + + def test_error_tolerant_expr_value_can_be_error + assert_error_tolerant(<<~STR, <<~EXP) + def m + if + end + STR + (SCOPE@1:0-3:3 + tbl: [] + args: nil + body: + (DEFN@1:0-3:3 + mid: :m + body: + (SCOPE@1:0-3:3 + tbl: [] + args: + (ARGS@1:5-1:5 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil)))) + EXP + end + + def assert_error_tolerant(src, expected) + begin + verbose_bak, $VERBOSE = $VERBOSE, false + node = RubyVM::AbstractSyntaxTree.parse(src, error_tolerant: true) + ensure + $VERBOSE = verbose_bak + end + assert_nil($!) + str = "" + PP.pp(node, str, 80) + assert_equal(expected, str) + end +end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 9d16a45e23..e475520321 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -42,8 +42,10 @@ p Foo::Bar require 'tmpdir' Dir.mktmpdir('autoload') {|tmpdir| tmpfile = tmpdir + '/foo.rb' + tmpfile2 = tmpdir + '/bar.rb' a = Module.new do autoload :X, tmpfile + autoload :Y, tmpfile2 end b = Module.new do include a @@ -52,10 +54,38 @@ p Foo::Bar assert_equal(true, b.const_defined?(:X)) assert_equal(tmpfile, a.autoload?(:X), bug4565) assert_equal(tmpfile, b.autoload?(:X), bug4565) + assert_equal(tmpfile, a.autoload?(:X, false)) + assert_equal(tmpfile, a.autoload?(:X, nil)) + assert_nil(b.autoload?(:X, false)) + assert_nil(b.autoload?(:X, nil)) + assert_equal(true, a.const_defined?("Y")) + assert_equal(true, b.const_defined?("Y")) + assert_equal(tmpfile2, a.autoload?("Y")) + assert_equal(tmpfile2, b.autoload?("Y")) } 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 @@ -66,12 +96,12 @@ p Foo::Bar eval <<-END class ::Object module A - autoload :C, 'b' + autoload :C, 'test-ruby-core-69206' end end END - File.open('b.rb', 'w') {|file| file.puts 'module A; class C; end; end'} + File.write("test-ruby-core-69206.rb", 'module A; class C; end; end') assert_kind_of Class, ::A::C end } @@ -138,6 +168,7 @@ p Foo::Bar end def test_nameerror_when_autoload_did_not_define_the_constant + verbose_bak, $VERBOSE = $VERBOSE, nil Tempfile.create(['autoload', '.rb']) {|file| file.puts '' file.close @@ -150,6 +181,8 @@ p Foo::Bar remove_autoload_constant end } + ensure + $VERBOSE = verbose_bak end def test_override_autoload @@ -245,6 +278,248 @@ p Foo::Bar assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]') end + def test_autoload_private_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + private_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + deprecate_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_private_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {class AutoloadTest; ZZZ; end} + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_fork + EnvUtil.default_warning do + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'sleep 0.3; class AutoloadTest; end' + file.close + add_autoload(file.path) + begin + thrs = [] + 3.times do + thrs << Thread.new { AutoloadTest && nil } + thrs << Thread.new { fork { AutoloadTest } } + end + thrs.each(&:join) + thrs.each do |th| + pid = th.value or next + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end + ensure + remove_autoload_constant + assert_nil $!, '[ruby-core:86410] [Bug #14634]' + end + } + end + end if Process.respond_to?(:fork) + + def test_autoload_same_file + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-14742.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + module Foo; end + module Bar; end + end; + 3.times do # timing-dependent, needs a few times to hit [Bug #14742] + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-14742' + autoload :Bar, 'test-bug-14742' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + bug = '[ruby-core:86935] [Bug #14742]' + assert_instance_of Module, t1.value, bug + assert_instance_of Module, t2.value, bug + end; + end + end + end + + def test_autoload_same_file_with_raise + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-16177.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + raise '[ruby-core:95055] [Bug #16177]' + end; + assert_raise(RuntimeError, '[ruby-core:95055] [Bug #16177]') do + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-16177' + autoload :Bar, 'test-bug-16177' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + end; + end + end + end + + 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_source_location_after_require + bug = "Bug18624" + Dir.mktmpdir('autoload') do |tmpdir| + path = "#{tmpdir}/test-#{bug}.rb" + File.write(path, "C::#{bug} = __FILE__\n") + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class C; end + C.autoload(:Bug18624, #{path.dump}) + require #{path.dump} + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + assert_equal #{path.dump}, C.const_get(#{bug.dump}) + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + end; + end + end + + def test_no_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60) + begin; + 200000.times do |i| + m = Module.new + m.instance_eval do + autoload :Foo, 'x' + autoload :Bar, i.to_s + end + end + 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) + $VERBOSE = nil + 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)} @@ -252,6 +527,72 @@ 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 + + def test_autoload_module_gc + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_module_gc.rb") + File.write(autoload_path, "X = 1; Y = 2;") + + x = Module.new + x.autoload :X, "./feature.rb" + + 1000.times do + y = Module.new + y.autoload :Y, "./feature.rb" + end + + x = y = nil + + # Ensure the internal data structures are cleaned up correctly / don't crash: + GC.start + end + end + + def test_autoload_parallel_race + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_parallel_race.rb") + File.write(autoload_path, 'module Foo; end; module Bar; end') + + assert_separately([], <<-RUBY, timeout: 100) + autoload_path = #{File.realpath(autoload_path).inspect} + + # This should work with no errors or failures. + 1000.times do + autoload :Foo, autoload_path + autoload :Bar, autoload_path + + t1 = Thread.new {Foo} + t2 = Thread.new {Bar} + + t1.join + GC.start # force GC. + t2.join + + Object.send(:remove_const, :Foo) + Object.send(:remove_const, :Bar) + + $LOADED_FEATURES.delete(autoload_path) + end + RUBY + end + end + + def test_autoload_parent_namespace + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "some_const.rb") + File.write(autoload_path, 'class SomeConst; end') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + module SomeNamespace + autoload :SomeConst, #{File.realpath(autoload_path).inspect} + assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do + assert_not_nil SomeConst + end + end + RUBY + end end end diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index d38628cdb2..bb0562c0bb 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -138,10 +138,95 @@ 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_each_backtrace_location + i = 0 + cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1} + assert_equal(cl.to_s, ecl.to_s) + assert_kind_of(Thread::Backtrace::Location, ecl) + + i = 0 + ary = [] + cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2} + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + assert_kind_of(Thread::Backtrace::Location, last) + + i = 0 + ary = [] + ->{->{ + cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2} + }.()}.() + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + assert_kind_of(Thread::Backtrace::Location, last) + + cllr = caller_locations(1, 2); ary = Thread.to_enum(:each_caller_location).to_a[2..3] + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + + ecl = Thread.to_enum(:each_caller_location) + assert_raise(StopIteration) { + ecl.next + } + end + + def test_caller_locations_first_label + def self.label + caller_locations.first.label + 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 @@ -297,4 +382,50 @@ class TestBacktrace < Test::Unit::TestCase end assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label) end + + def test_notty_backtrace + err = ["-:1:in `<main>': unhandled exception"] + assert_in_out_err([], "raise", [], err) + + err = ["-:2:in `foo': foo! (RuntimeError)", + "\tfrom -:4:in `<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + foo + end; + + err = ["-:7:in `rescue in bar': bar! (RuntimeError)", + "\tfrom -:4:in `bar'", + "\tfrom -:9:in `<main>'", + "-:2:in `foo': foo! (RuntimeError)", + "\tfrom -:5:in `bar'", + "\tfrom -:9:in `<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + def bar + foo + rescue + raise "bar!" + end + bar + end; + end + + def test_caller_to_enum + err = ["-:3:in `foo': unhandled exception", "\tfrom -:in `each'"] + assert_in_out_err([], <<-"end;", [], err, "[ruby-core:91911]") + def foo + return to_enum(__method__) unless block_given? + raise + yield 1 + end + + enum = foo + enum.next + end; + end end diff --git a/test/ruby/test_basicinstructions.rb b/test/ruby/test_basicinstructions.rb index dd3ca4dd22..f6b69cc1e5 100644 --- a/test/ruby/test_basicinstructions.rb +++ b/test/ruby/test_basicinstructions.rb @@ -117,7 +117,6 @@ class TestBasicInstructions < Test::Unit::TestCase assert_equal({1=>2}, {1=>2}) assert_equal({1=>2, 3=>4}, {1=>2, 3=>4}) assert_equal({1=>2, 3=>4}, {3=>4, 1=>2}) - # assert_equal({1=>2, 3=>4}, {1,2, 3,4}) # 1.9 doesn't support assert_equal({"key"=>"val"}, {"key"=>"val"}) end @@ -429,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 @@ -450,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_beginendblock.rb b/test/ruby/test_beginendblock.rb index 9238a53354..eb8394864f 100644 --- a/test/ruby/test_beginendblock.rb +++ b/test/ruby/test_beginendblock.rb @@ -154,4 +154,26 @@ class TestBeginEndBlock < Test::Unit::TestCase end end; end + + if defined?(fork) + def test_internal_errinfo_at_exit + # TODO: use other than break-in-fork to throw an internal + # error info. + error, pid, status = IO.pipe do |r, w| + pid = fork do + r.close + STDERR.reopen(w) + at_exit do + $!.class + end + break + end + w.close + [r.read, *Process.wait2(pid)] + end + assert_not_predicate(status, :success?) + assert_not_predicate(status, :signaled?) + assert_match(/unexpected break/, error) + end + end end diff --git a/test/ruby/test_bignum.rb b/test/ruby/test_bignum.rb index 3ead77766d..065a944853 100644 --- a/test/ruby/test_bignum.rb +++ b/test/ruby/test_bignum.rb @@ -10,7 +10,6 @@ class TestBignum < Test::Unit::TestCase FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX'] BIGNUM_MIN = FIXNUM_MAX + 1 - b = BIGNUM_MIN f = BIGNUM_MIN n = 0 @@ -20,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 @@ -205,6 +203,15 @@ class TestBignum < Test::Unit::TestCase assert_equal(00_02, '00_02'.to_i) end + def test_very_big_str_to_inum + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + digits = [["3", 700], ["0", 2700], ["1", 1], ["0", 26599]] + num = digits.inject("") {|s,(c,n)|s << c*n}.to_i + assert_equal digits.sum {|c,n|n}, num.to_s.size + end; + end + def test_to_s2 assert_raise(ArgumentError) { T31P.to_s(37) } assert_equal("9" * 32768, (10**32768-1).to_s) @@ -215,9 +222,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 @@ -416,7 +425,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 @@ -467,8 +476,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" } @@ -506,39 +515,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 @@ -612,19 +639,21 @@ class TestBignum < Test::Unit::TestCase return # GMP doesn't support interrupt during an operation. end time = Time.now - start_flag = false end_flag = false num = (65536 ** 65536) + q = Thread::Queue.new thread = Thread.new do - start_flag = true - num.to_s - end_flag = true + assert_raise(RuntimeError) { + q << true + num.to_s + end_flag = true + } end - sleep 0.001 until start_flag + q.pop # sync thread.raise - thread.join rescue nil + thread.join time = Time.now - time - skip "too fast cpu" if end_flag + omit "too fast cpu" if end_flag assert_operator(time, :<, 10) end @@ -655,14 +684,14 @@ class TestBignum < Test::Unit::TestCase return end end - skip "cannot create suitable test case" + omit "cannot create suitable test case" ensure Signal.trap(:INT, oldtrap) if oldtrap end 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} @@ -745,7 +774,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)) @@ -758,13 +787,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..88e2ddcb7b 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -47,12 +47,19 @@ class TestCall < Test::Unit::TestCase assert_equal(5, o.y) o&.z ||= 6 assert_equal(6, o.z) + o&.z &&= 7 + assert_equal(7, o.z) o = nil assert_nil(o&.x) assert_nothing_raised(NoMethodError) {o&.x = raise} + assert_nothing_raised(NoMethodError) {o&.x = raise; nil} assert_nothing_raised(NoMethodError) {o&.x *= raise} assert_nothing_raised(NoMethodError) {o&.x *= raise; nil} + assert_nothing_raised(NoMethodError) {o&.x ||= raise} + assert_nothing_raised(NoMethodError) {o&.x ||= raise; nil} + assert_nothing_raised(NoMethodError) {o&.x &&= raise} + assert_nothing_raised(NoMethodError) {o&.x &&= raise; nil} end def test_safe_call_evaluate_arguments_only_method_call_is_made @@ -99,4 +106,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 0986e64fbb..300ab950ac 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -96,6 +96,13 @@ class TestClass < Test::Unit::TestCase def test_superclass_of_basicobject assert_equal(nil, BasicObject.superclass) + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + module Mod end + BasicObject.include(Mod) + assert_equal(nil, BasicObject.superclass) + end; end def test_module_function @@ -131,6 +138,48 @@ class TestClass < Test::Unit::TestCase [:module_function, :extend_object, :append_features, :prepend_features]) end + def test_visibility_inside_method + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + private + end + foo + end + end + + assert_warn(/calling protected without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + protected + end + foo + end + end + + assert_warn(/calling public without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + public + end + foo + end + end + + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + class << self + alias priv private + end + + def self.foo + priv + end + foo + end + end + end + def test_method_redefinition feature2155 = '[ruby-dev:39400]' @@ -232,6 +281,14 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { Class.allocate.superclass } bug6863 = '[ruby-core:47148]' assert_raise(TypeError, bug6863) { Class.new(Class.allocate) } + + allocator = Class.instance_method(:allocate) + assert_raise_with_message(TypeError, /prohibited/) { + allocator.bind(Rational).call + } + assert_raise_with_message(TypeError, /prohibited/) { + allocator.bind_call(Rational) + } end def test_nonascii_name @@ -241,20 +298,30 @@ class TestClass < Test::Unit::TestCase assert_equal("TestClass::C\u{df}", c.name, '[ruby-core:24600]') end - def test_invalid_jump_from_class_definition - assert_raise(SyntaxError) { eval("class C; next; end") } - assert_raise(SyntaxError) { eval("class C; break; end") } - assert_raise(SyntaxError) { eval("class C; redo; end") } - assert_raise(SyntaxError) { eval("class C; retry; end") } + def test_invalid_next_from_class_definition + assert_syntax_error("class C; next; end", /Invalid next/) + end + + def test_invalid_break_from_class_definition + assert_syntax_error("class C; break; end", /Invalid break/) + end + + def test_invalid_redo_from_class_definition + assert_syntax_error("class C; redo; end", /Invalid redo/) + end + + def test_invalid_retry_from_class_definition + assert_syntax_error("class C; retry; end", /Invalid retry/) end def test_invalid_return_from_class_definition - skip "Wrongly return from this method" - assert_raise(SyntaxError) { eval("class C; return; end") } + assert_syntax_error("class C; return; end", /Invalid return/) end def test_invalid_yield_from_class_definition - assert_raise(LocalJumpError) { eval("class C; yield; end") } + assert_raise(SyntaxError) { + EnvUtil.suppress_warning {eval("class C; yield; end")} + } end def test_clone @@ -319,18 +386,22 @@ class TestClass < Test::Unit::TestCase end; end - module M - C = 1 + class CloneTest + def foo; TEST; end + end - def self.m - C - end + CloneTest1 = CloneTest.clone + CloneTest2 = CloneTest.clone + class CloneTest1 + TEST = :C1 + end + class CloneTest2 + TEST = :C2 end - def test_constant_access_from_method_in_cloned_module # [ruby-core:47834] - m = M.dup - assert_equal 1, m::C - assert_equal 1, m.m + def test_constant_access_from_method_in_cloned_class + assert_equal :C1, CloneTest1.new.foo, '[Bug #15877]' + assert_equal :C2, CloneTest2.new.foo, '[Bug #15877]' end def test_invalid_superclass @@ -419,6 +490,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) @@ -435,14 +553,14 @@ class TestClass < Test::Unit::TestCase obj = Object.new c = obj.singleton_class obj.freeze - assert_raise_with_message(RuntimeError, /frozen object/) { + assert_raise_with_message(FrozenError, /frozen object/) { c.class_eval {def f; end} } end def test_singleton_class_message c = Class.new.freeze - assert_raise_with_message(RuntimeError, /frozen Class/) { + assert_raise_with_message(FrozenError, /frozen Class/) { def c.f; end } end @@ -577,15 +695,17 @@ class TestClass < Test::Unit::TestCase def test_redefinition_mismatch m = Module.new - m.module_eval "A = 1" - assert_raise_with_message(TypeError, /is not a class/) { + m.module_eval "A = 1", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /is not a class/) { m.module_eval "class A; end" } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") n = "M\u{1f5ff}" - m.module_eval "#{n} = 42" - assert_raise_with_message(TypeError, "#{n} is not a class") { + m.module_eval "#{n} = 42", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /#{n} is not a class/) { m.module_eval "class #{n}; end" } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") begin; @@ -617,4 +737,92 @@ 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_attached_object + c = Class.new + sc = c.singleton_class + obj = c.new + + assert_equal(obj, obj.singleton_class.attached_object) + assert_equal(c, sc.attached_object) + + assert_raise_with_message(TypeError, /is not a singleton class/) do + c.attached_object + end + + assert_raise_with_message(TypeError, /`NilClass' is not a singleton class/) do + nil.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /`FalseClass' is not a singleton class/) do + false.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /`TrueClass' is not a singleton class/) do + true.singleton_class.attached_object + end + end + + def test_subclass_gc + c = Class.new + 10_000.times do + cc = Class.new(c) + 100.times { Class.new(cc) } + end + assert(c.subclasses.size <= 10_000) + end + + def test_subclass_gc_stress + 10000.times do + c = Class.new + 100.times { Class.new(c) } + assert(c.subclasses.size <= 100) + end + end + + def test_classext_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { Class.new } +1_000.times(&code) +PREP +3_000_000.times(&code) +CODE + end + + def test_instance_freeze_dont_freeze_the_class_bug_19164 + klass = Class.new + klass.prepend(Module.new) + + klass.new.freeze + klass.define_method(:bar) {} + assert_equal klass, klass.remove_method(:bar), '[Bug #19164]' + end end diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb index 93ef438461..775c9ed848 100644 --- a/test/ruby/test_clone.rb +++ b/test/ruby/test_clone.rb @@ -26,4 +26,92 @@ class TestClone < Test::Unit::TestCase assert_equal([M003, M002, M001], M003.ancestors) end + + def test_frozen_properties_retained_on_clone + obj = Object.new.freeze + cloned_obj = obj.clone + + assert_predicate(obj, :frozen?) + assert_predicate(cloned_obj, :frozen?) + end + + def test_ivar_retained_on_clone + obj = Object.new + obj.instance_variable_set(:@a, 1) + cloned_obj = obj.clone + + assert_equal(obj.instance_variable_get(:@a), 1) + assert_equal(cloned_obj.instance_variable_get(:@a), 1) + end + + def test_ivars_retained_on_extended_obj_clone + ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 } + obj = Object.new + ivars.each do |ivar_name, val| + obj.instance_variable_set(ivar_name, val) + end + + cloned_obj = obj.clone + + ivars.each do |ivar_name, val| + assert_equal(obj.instance_variable_get(ivar_name), val) + assert_equal(cloned_obj.instance_variable_get(ivar_name), val) + end + end + + def test_frozen_properties_and_ivars_retained_on_clone_with_ivar + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + + cloned_obj = obj.clone + + assert_predicate(obj, :frozen?) + assert_equal(obj.instance_variable_get(:@a), 1) + + assert_predicate(cloned_obj, :frozen?) + assert_equal(cloned_obj.instance_variable_get(:@a), 1) + end + + def test_proc_obj_id_flag_reset + # [Bug #20250] + proc = Proc.new { } + proc.object_id + proc.clone.object_id # Would crash with RUBY_DEBUG=1 + end + + def test_user_flags + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1, 2, 3].clone + assert_equal [], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1,2,3,4,5,6,7][1..-2].clone + x.push(1,1,1,1,1) + assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Hash + undef initialize_copy + def initialize_copy(*); end + end + h = {} + h.default_proc = proc { raise } + h = h.clone + assert_equal nil, h[:not_exist], '[Bug #14847]' + EOS + end end diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb index 94c05d5f91..b849217b7d 100644 --- a/test/ruby/test_comparable.rb +++ b/test/ruby/test_comparable.rb @@ -88,7 +88,37 @@ class TestComparable < Test::Unit::TestCase assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') { @o.clamp(2, 1) } - end + end + + def test_clamp_with_range + cmp->(x) do 0 <=> x end + assert_equal(1, @o.clamp(1..2)) + assert_equal(-1, @o.clamp(-2..-1)) + assert_equal(@o, @o.clamp(-1..3)) + + assert_equal(1, @o.clamp(1..1)) + assert_equal(@o, @o.clamp(0..0)) + + assert_equal(1, @o.clamp(1..)) + assert_equal(1, @o.clamp(1...)) + assert_equal(@o, @o.clamp(0..)) + assert_equal(@o, @o.clamp(0...)) + assert_equal(@o, @o.clamp(..2)) + assert_equal(-1, @o.clamp(-2..-1)) + assert_equal(@o, @o.clamp(-2..0)) + assert_equal(@o, @o.clamp(-2..)) + assert_equal(@o, @o.clamp(-2...)) + + exc = [ArgumentError, 'cannot clamp with an exclusive range'] + assert_raise_with_message(*exc) {@o.clamp(1...2)} + assert_raise_with_message(*exc) {@o.clamp(0...2)} + assert_raise_with_message(*exc) {@o.clamp(-1...0)} + assert_raise_with_message(*exc) {@o.clamp(...2)} + + assert_raise_with_message(ArgumentError, 'min argument must be smaller than max argument') { + @o.clamp(2..1) + } + end def test_err assert_raise(ArgumentError) { 1.0 < nil } diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index ded316841a..b262b2c1c1 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -48,8 +48,12 @@ class Complex_Test < Test::Unit::TestCase end def test_hash - assert_kind_of(Integer, Complex(1,2).hash) - assert_kind_of(Integer, Complex(1.0,2.0).hash) + h = Complex(1,2).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} + h = Complex(1.0,2.0).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} h = {} h[Complex(0)] = 0 @@ -119,6 +123,10 @@ class Complex_Test < Test::Unit::TestCase assert_raise(TypeError){Complex(Object.new)} assert_raise(ArgumentError){Complex()} assert_raise(ArgumentError){Complex(1,2,3)} + c = Complex(1,0) + assert_same(c, Complex(c)) + assert_same(c, Complex(c, exception: false)) + assert_raise(ArgumentError){Complex(c, bad_keyword: true)} if (0.0/0).nan? assert_nothing_raised{Complex(0.0/0)} @@ -212,6 +220,17 @@ 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)) + + one = 1+0i + c = Complex.polar(0, one) + assert_equal(0, c) + assert_predicate(c.real, :real?) + c = Complex.polar(one, 0) + assert_equal(1, c) + assert_predicate(c.real, :real?) + c = Complex.polar(one) + assert_equal(1, c) + assert_predicate(c.real, :real?) end def test_uplus @@ -265,6 +284,39 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(Rational(5,3),Rational(2)), c + Rational(2,3)) end + def test_add_with_redefining_int_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :+ + def +(other); 42; end + end + a = Complex(1, 2) + Complex(0, 1) + puts a == Complex(42, 42) + end; + end + + def test_add_with_redefining_float_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :+ + def +(other); 42.0; end + end + a = Complex(1.0, 2.0) + Complex(0, 1) + puts a == Complex(42.0, 42.0) + end; + end + + def test_add_with_redefining_rational_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :+ + def +(other); 355/113r; end + end + a = Complex(1r, 2r) + Complex(0, 1) + puts a == Complex(355/113r, 355/113r) + end; + end + def test_sub c = Complex(1,2) c2 = Complex(2,3) @@ -278,6 +330,39 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(Rational(1,3),Rational(2)), c - Rational(2,3)) end + def test_sub_with_redefining_int_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :- + def -(other); 42; end + end + a = Complex(1, 2) - Complex(0, 1) + puts a == Complex(42, 42) + end; + end + + def test_sub_with_redefining_float_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :- + def -(other); 42.0; end + end + a = Complex(1.0, 2.0) - Complex(0, 1) + puts a == Complex(42.0, 42.0) + end; + end + + def test_sub_with_redefining_rational_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :- + def -(other); 355/113r; end + end + a = Complex(1r, 2r) - Complex(0, 1) + puts a == Complex(355/113r, 355/113r) + end; + end + def test_mul c = Complex(1,2) c2 = Complex(2,3) @@ -296,6 +381,42 @@ class Complex_Test < Test::Unit::TestCase c = Complex(0, Float::INFINITY) assert_equal(Complex(0, Float::INFINITY), c * Complex(1, 0)) assert_equal(Complex(-Float::INFINITY, 0), c * Complex(0, 1)) + + assert_equal(Complex(-0.0, -0.0), Complex(-0.0, 0) * Complex(0, 0)) + end + + def test_mul_with_redefining_int_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :* + def *(other); 42; end + end + a = Complex(2, 0) * Complex(1, 2) + puts a == Complex(0, 84) + end; + end + + def test_mul_with_redefining_float_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :* + def *(other); 42.0; end + end + a = Complex(2.0, 0.0) * Complex(1, 2) + puts a == Complex(0.0, 84.0) + end; + end + + + def test_mul_with_redefining_rational_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :* + def *(other); 355/113r; end + end + a = Complex(2r, 0r) * Complex(1, 2) + puts a == Complex(0r, 2*355/113r) + end; end def test_div @@ -319,6 +440,15 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) + + c = Complex(1) + [ 1, Rational(1), c ].each do |d| + r = c / d + assert_instance_of(Complex, r) + assert_equal(1, r) + assert_predicate(r.real, :integer?) + assert_predicate(r.imag, :integer?) + end end def test_quo @@ -396,12 +526,23 @@ class Complex_Test < Test::Unit::TestCase r = c ** Rational(-2,3) assert_in_delta(0.432, r.real, 0.001) assert_in_delta(-0.393, r.imag, 0.001) + + c = Complex(0.0, -888888888888888.0)**8888 + assert_not_predicate(c.real, :nan?) + assert_not_predicate(c.imag, :nan?) end def test_cmp - assert_raise(NoMethodError){1 <=> Complex(1,1)} - assert_raise(NoMethodError){Complex(1,1) <=> 1} - assert_raise(NoMethodError){Complex(1,1) <=> Complex(1,1)} + assert_nil(Complex(5, 1) <=> Complex(2)) + assert_nil(5 <=> Complex(2, 1)) + + assert_equal(1, Complex(5) <=> Complex(2)) + assert_equal(-1, Complex(2) <=> Complex(3)) + assert_equal(0, Complex(2) <=> Complex(2)) + + assert_equal(1, Complex(5) <=> 2) + assert_equal(-1, Complex(2) <=> 3) + assert_equal(0, Complex(2) <=> 2) end def test_eqeq @@ -432,20 +573,24 @@ class Complex_Test < Test::Unit::TestCase assert_raise_with_message(TypeError, /C\u{1f5ff}/) { Complex(1).coerce(obj) } end - class ObjectX - def +(x) Rational(1) end + class ObjectX < Numeric + def initialize(real = true, n = 1) @n = n; @real = real; end + def +(x) Rational(@n) end alias - + alias * + alias / + alias quo + alias ** + - def coerce(x) [x, Complex(1)] end + def coerce(x) [x, Complex(@n)] end + def real?; @real; end end def test_coerce2 x = ObjectX.new - %w(+ - * / quo **).each do |op| - assert_kind_of(Numeric, Complex(1).__send__(op, x)) + y = ObjectX.new(false) + %w(+ - * / quo ** <=>).each do |op| + assert_kind_of(Numeric, Complex(1).__send__(op, x), op) + assert_kind_of(Numeric, Complex(1).__send__(op, y), op) end end @@ -708,20 +853,42 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(0), '_5'.to_c) assert_equal(Complex(5), '5_'.to_c) assert_equal(Complex(5), '5x'.to_c) + assert_equal(Complex(51), '5_1'.to_c) + assert_equal(Complex(5), '5__1'.to_c) assert_equal(Complex(5), '5+_3i'.to_c) assert_equal(Complex(5), '5+3_i'.to_c) assert_equal(Complex(5,3), '5+3i_'.to_c) assert_equal(Complex(5,3), '5+3ix'.to_c) + assert_equal(Complex(5,31), '5+3_1i'.to_c) + assert_equal(Complex(5), '5+3__1i'.to_c) + assert_equal(Complex(51), Complex('5_1')) + assert_equal(Complex(5,31), Complex('5+3_1i')) + assert_equal(Complex(5,31), Complex('5+3_1I')) + assert_equal(Complex(5,31), Complex('5+3_1j')) + assert_equal(Complex(5,31), Complex('5+3_1J')) + assert_equal(Complex(0,31), Complex('3_1i')) + assert_equal(Complex(0,31), Complex('3_1I')) + assert_equal(Complex(0,31), Complex('3_1j')) + assert_equal(Complex(0,31), Complex('3_1J')) assert_raise(ArgumentError){ Complex('')} assert_raise(ArgumentError){ Complex('_')} assert_raise(ArgumentError){ Complex("\f\n\r\t\v5\0")} assert_raise(ArgumentError){ Complex('_5')} assert_raise(ArgumentError){ Complex('5_')} + assert_raise(ArgumentError){ Complex('5__1')} assert_raise(ArgumentError){ Complex('5x')} assert_raise(ArgumentError){ Complex('5+_3i')} assert_raise(ArgumentError){ Complex('5+3_i')} assert_raise(ArgumentError){ Complex('5+3i_')} assert_raise(ArgumentError){ Complex('5+3ix')} + assert_raise(ArgumentError){ Complex('5+3__1i')} + assert_raise(ArgumentError){ Complex('5+3__1I')} + assert_raise(ArgumentError){ Complex('5+3__1j')} + assert_raise(ArgumentError){ Complex('5+3__1J')} + assert_raise(ArgumentError){ Complex('3__1i')} + assert_raise(ArgumentError){ Complex('3__1I')} + assert_raise(ArgumentError){ Complex('3__1j')} + assert_raise(ArgumentError){ Complex('3__1J')} assert_equal(Complex(Rational(1,5)), '1/5'.to_c) assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c) @@ -742,10 +909,38 @@ class Complex_Test < Test::Unit::TestCase end + def test_Complex_with_invalid_exception + assert_raise(ArgumentError) { + Complex("0", exception: 1) + } + end + + def assert_complex_with_exception(error, *args, message: "") + assert_raise(error, message) do + Complex(*args, exception: true) + end + assert_nothing_raised(error, message) do + assert_nil(Complex(*args, exception: false)) + assert_nil($!) + end + end + + def test_Complex_with_exception + assert_complex_with_exception(ArgumentError, '5x') + assert_complex_with_exception(TypeError, nil) + assert_complex_with_exception(TypeError, Object.new) + assert_complex_with_exception(TypeError, 1, nil) + assert_complex_with_exception(TypeError, 1, Object.new) + + o = Object.new + def o.to_c; raise; end + assert_complex_with_exception(RuntimeError, o) + assert_complex_with_exception(TypeError, 1, o) + end + def test_respond c = Complex(1,1) assert_not_respond_to(c, :%) - assert_not_respond_to(c, :<=>) assert_not_respond_to(c, :div) assert_not_respond_to(c, :divmod) assert_not_respond_to(c, :floor) @@ -954,4 +1149,49 @@ class Complex_Test < Test::Unit::TestCase def test_known_bug end + def test_canonicalize_internal + obj = Class.new(Numeric) do + attr_accessor :real + alias real? real + end.new + obj.real = true + c = Complex.rect(obj, 1); + obj.real = false + c = c.conj + assert_equal(obj, c.real) + assert_equal(-1, c.imag) + end + + def test_canonicalize_polar + error = "not a real" + assert_raise_with_message(TypeError, error) do + Complex.polar(1i) + end + assert_raise_with_message(TypeError, error) do + Complex.polar(1i, 0) + end + assert_raise_with_message(TypeError, error) do + Complex.polar(0, 1i) + end + n = Class.new(Numeric) do + def initialize(x = 1) + @x = x + end + def real? + (@x -= 1) > 0 + end + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(obj) + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(obj, 0) + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(1, obj) + end + end end diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb index 594fc3f45a..b89e83efb2 100644 --- a/test/ruby/test_complex2.rb +++ b/test/ruby/test_complex2.rb @@ -4,7 +4,7 @@ require 'test/unit' class Complex_Test2 < Test::Unit::TestCase def test_kumi - skip unless defined?(Rational) + omit unless defined?(Rational) assert_equal(Complex(1, 0), +Complex(1, 0)) assert_equal(Complex(-1, 0), -Complex(1, 0)) diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb index 0360f5ee42..31d11fe317 100644 --- a/test/ruby/test_complexrational.rb +++ b/test/ruby/test_complexrational.rb @@ -4,7 +4,7 @@ require 'test/unit' class ComplexRational_Test < Test::Unit::TestCase def test_rat_srat - skip unless defined?(Rational) + omit unless defined?(Rational) c = SimpleRat(1,3) cc = Rational(3,2) @@ -89,7 +89,7 @@ class ComplexRational_Test < Test::Unit::TestCase end def test_comp_srat - skip unless defined?(Rational) + omit unless defined?(Rational) c = Complex(SimpleRat(2,3),SimpleRat(1,2)) cc = Complex(Rational(3,2),Rational(2,1)) @@ -102,7 +102,7 @@ class ComplexRational_Test < Test::Unit::TestCase assert_equal(Complex(SimpleRat(4,3),SimpleRat(1,1)), c * 2) assert_equal(Complex(SimpleRat(1,3),SimpleRat(1,4)), c / 2) assert_equal(Complex(SimpleRat(7,36),SimpleRat(2,3)), c ** 2) - assert_raise(NoMethodError){c <=> 2} + assert_nil(c <=> 2) assert_equal(Complex(SimpleRat(8,3),SimpleRat(1,2)), 2 + c) assert_equal(Complex(SimpleRat(4,3),SimpleRat(-1,2)), 2 - c) @@ -111,7 +111,7 @@ class ComplexRational_Test < Test::Unit::TestCase r = 2 ** c assert_in_delta(1.4940, r.real, 0.001) assert_in_delta(0.5392, r.imag, 0.001) - assert_raise(NoMethodError){2 <=> c} + assert_nil(2 <=> c) assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), c + cc) assert_equal(Complex(SimpleRat(-5,6),SimpleRat(-3,2)), c - cc) @@ -120,7 +120,7 @@ class ComplexRational_Test < Test::Unit::TestCase r = c ** cc assert_in_delta(0.1732, r.real, 0.001) assert_in_delta(0.1186, r.imag, 0.001) - assert_raise(NoMethodError){c <=> cc} + assert_nil(c <=> cc) assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), cc + c) assert_equal(Complex(SimpleRat(5,6),SimpleRat(3,2)), cc - c) @@ -129,7 +129,7 @@ class ComplexRational_Test < Test::Unit::TestCase r = cc ** c assert_in_delta(0.5498, r.real, 0.001) assert_in_delta(1.0198, r.imag, 0.001) - assert_raise(NoMethodError){cc <=> c} + assert_nil(cc <=> c) assert_equal([SimpleRat,SimpleRat], (+c).instance_eval{[real.class, imag.class]}) diff --git a/test/ruby/test_const.rb b/test/ruby/test_const.rb index 8784e0e988..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) @@ -48,6 +58,17 @@ class TestConst < Test::Unit::TestCase assert_equal 8, TEST4 end + def test_const_access_from_nil + assert_raise(TypeError) { eval("nil::Object") } + assert_nil eval("defined?(nil::Object)") + + assert_raise(TypeError) { eval("c = nil; c::Object") } + assert_nil eval("c = nil; defined?(c::Object)") + + assert_raise(TypeError) { eval("sc = Class.new; sc::C = nil; sc::C::Object") } + assert_nil eval("sc = Class.new; sc::C = nil; defined?(sc::C::Object)") + end + def test_redefinition c = Class.new name = "X\u{5b9a 6570}" diff --git a/test/ruby/test_continuation.rb b/test/ruby/test_continuation.rb index efc549b67a..8c62d20840 100644 --- a/test/ruby/test_continuation.rb +++ b/test/ruby/test_continuation.rb @@ -37,9 +37,11 @@ class TestContinuation < Test::Unit::TestCase def test_error cont = callcc{|c| c} - assert_raise(RuntimeError){ - Thread.new{cont.call}.join - } + Thread.new{ + assert_raise(RuntimeError){ + cont.call + } + }.join assert_raise(LocalJumpError){ callcc } @@ -86,11 +88,16 @@ class TestContinuation < Test::Unit::TestCase @memo += 1 c = cont cont = nil - c.call(nil) + begin + c.call(nil) + rescue RuntimeError + set_trace_func(nil) + end end end end cont = callcc { |cc| cc } + if cont set_trace_func(func) else @@ -98,12 +105,12 @@ class TestContinuation < Test::Unit::TestCase end end - def test_tracing_with_set_trace_func + def _test_tracing_with_set_trace_func @memo = 0 tracing_with_set_trace_func tracing_with_set_trace_func tracing_with_set_trace_func - assert_equal 3, @memo + assert_equal 0, @memo end def tracing_with_thread_set_trace_func @@ -113,7 +120,11 @@ class TestContinuation < Test::Unit::TestCase @memo += 1 c = cont cont = nil - c.call(nil) + begin + c.call(nil) + rescue RuntimeError + Thread.current.set_trace_func(nil) + end end end cont = callcc { |cc| cc } @@ -132,4 +143,3 @@ class TestContinuation < Test::Unit::TestCase assert_equal 3, @memo end end - diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb new file mode 100644 index 0000000000..3cafb365ed --- /dev/null +++ b/test/ruby/test_data.rb @@ -0,0 +1,249 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' +require 'timeout' + +class TestData < Test::Unit::TestCase + def test_define + klass = Data.define(:foo, :bar) + assert_kind_of(Class, klass) + assert_equal(%i[foo bar], klass.members) + + assert_raise(NoMethodError) { Data.new(:foo) } + assert_raise(TypeError) { Data.define(0) } + + # Because some code is shared with Struct, check we don't share unnecessary functionality + assert_raise(TypeError) { Data.define(:foo, keyword_init: true) } + + assert_not_respond_to(Data.define, :define, "Cannot define from defined Data class") + end + + def test_define_edge_cases + # non-ascii + klass = Data.define(:"r\u{e9}sum\u{e9}") + o = klass.new(1) + assert_equal(1, o.send(:"r\u{e9}sum\u{e9}")) + + # junk string + klass = Data.define(:"a\000") + o = klass.new(1) + assert_equal(1, o.send(:"a\000")) + + # special characters in attribute names + klass = Data.define(:a, :b?) + x = Object.new + o = klass.new("test", x) + assert_same(x, o.b?) + + klass = Data.define(:a, :b!) + x = Object.new + o = klass.new("test", x) + assert_same(x, o.b!) + + assert_raise(ArgumentError) { Data.define(:x=) } + assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) } + end + + def test_define_with_block + klass = Data.define(:a, :b) do + def c + a + b + end + end + + assert_equal(3, klass.new(1, 2).c) + end + + def test_initialize + klass = Data.define(:foo, :bar) + + # Regular + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(1, 2)) + assert_predicate(test, :frozen?) + + # Keywords + test_kw = klass.new(foo: 1, bar: 2) + assert_equal(1, test_kw.foo) + assert_equal(2, test_kw.bar) + assert_equal(test_kw, klass.new(foo: 1, bar: 2)) + assert_equal(test_kw, test) + + # Wrong protocol + assert_raise(ArgumentError) { klass.new(1) } + assert_raise(ArgumentError) { klass.new(1, 2, 3) } + assert_raise(ArgumentError) { klass.new(foo: 1) } + assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) } + # Could be converted to foo: 1, bar: 2, but too smart is confusing + assert_raise(ArgumentError) { klass.new(1, bar: 2) } + end + + def test_initialize_redefine + klass = Data.define(:foo, :bar) do + attr_reader :passed + + def initialize(*args, **kwargs) + @passed = [args, kwargs] + super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args + end + end + + assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed) + + # Positional arguments are converted to keyword ones + assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed) + + # Missing arguments can be fixed in initialize + assert_equal([[], {foo: 1}], klass.new(foo: 1).passed) + + # Extra keyword arguments can be dropped in initialize + assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed) + end + + def test_instance_behavior + klass = Data.define(:foo, :bar) + + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(%i[foo bar], test.members) + assert_equal(1, test.public_send(:foo)) + assert_equal(0, test.method(:foo).arity) + assert_equal([], test.method(:foo).parameters) + + assert_equal({foo: 1, bar: 2}, test.to_h) + assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] }) + + assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil)) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo])) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz])) + assert_raise(TypeError) { test.deconstruct_keys(0) } + + assert_kind_of(Integer, test.hash) + end + + def test_inspect + klass = Data.define(:a) + o = klass.new(1) + assert_equal("#<data a=1>", o.inspect) + + Object.const_set(:Foo, klass) + assert_equal("#<data Foo a=1>", o.inspect) + Object.instance_eval { remove_const(:Foo) } + + klass = Data.define(:@a) + o = klass.new(1) + assert_equal("#<data :@a=1>", o.inspect) + end + + def test_equal + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_equal(o1, o2) + assert_not_equal(o1, o3) + end + + def test_eql + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_operator(o1, :eql?, o2) + assert_not_operator(o1, :eql?, o3) + end + + def test_with + klass = Data.define(:foo, :bar) + source = klass.new(foo: 1, bar: 2) + + # Simple + test = source.with + assert_equal(source.object_id, test.object_id) + + # Changes + test = source.with(foo: 10) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 2)) + + test = source.with(foo: 10, bar: 20) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Keyword splat + changes = { foo: 10, bar: 20 } + test = source.with(**changes) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Wrong protocol + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(10) + end + assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do + source.with(foo: 1, bar: 2, baz: 3, quux: 4) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(1, bar: 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do + source.with(1, 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with({ bar: 2 }) + end + end + + def test_memberless + klass = Data.define + + test = klass.new + + assert_equal(klass.new, test) + assert_not_equal(Data.define.new, test) + + assert_equal('#<data >', test.inspect) + assert_equal([], test.members) + assert_equal({}, test.to_h) + end + + def test_dup + klass = Data.define(:foo, :bar) + test = klass.new(foo: 1, bar: 2) + assert_equal(klass.new(foo: 1, bar: 2), test.dup) + assert_predicate(test.dup, :frozen?) + end + + Klass = Data.define(:foo, :bar) + + def test_marshal + test = Klass.new(foo: 1, bar: 2) + loaded = Marshal.load(Marshal.dump(test)) + assert_equal(test, loaded) + assert_not_same(test, loaded) + assert_predicate(loaded, :frozen?) + end +end diff --git a/test/ruby/test_default_gems.rb b/test/ruby/test_default_gems.rb new file mode 100644 index 0000000000..467e5ecf83 --- /dev/null +++ b/test/ruby/test_default_gems.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: false +require 'rubygems' + +class TestDefaultGems < Test::Unit::TestCase + + def test_validate_gemspec + omit "git not found" unless system("git", "rev-parse", %i[out err]=>IO::NULL) + srcdir = File.expand_path('../../..', __FILE__) + Dir.glob("#{srcdir}/{lib,ext}/**/*.gemspec").map do |src| + assert_nothing_raised do + raise("invalid spec in #{src}") unless Gem::Specification.load(src) + end + end + end + +end diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index 54f461ff03..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 @@ -253,4 +363,57 @@ class TestDefined < Test::Unit::TestCase assert_equal(nil, obj.func_defined_non_existing_func, bug_11212) assert_equal(true, obj.called, bug_11212) end + + 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 cead4beb93..63ef99f159 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -8,21 +8,32 @@ class TestDir < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil @root = File.realpath(Dir.mktmpdir('__test_dir__')) @nodir = File.join(@root, "dummy") + @dirs = [] for i in "a".."z" if i.ord % 2 == 0 FileUtils.touch(File.join(@root, i)) else FileUtils.mkdir(File.join(@root, i)) + @dirs << File.join(i, "") end end + @envs = nil end def teardown $VERBOSE = @verbose FileUtils.remove_entry_secure @root if File.directory?(@root) + ENV.update(@envs) if @envs + end + + def setup_envs(envs = %w"HOME LOGDIR") + @envs ||= {} + envs.each do |e, v| + @envs[e] = ENV.delete(e) + ENV[e] = v if v + end end def test_seek @@ -86,40 +97,67 @@ class TestDir < Test::Unit::TestCase end def test_chdir - @pwd = Dir.pwd - @env_home = ENV["HOME"] - @env_logdir = ENV["LOGDIR"] - ENV.delete("HOME") - ENV.delete("LOGDIR") + pwd = Dir.pwd + setup_envs 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 + end + + def test_chdir_conflict + pwd = Dir.pwd + q = Thread::Queue.new + t = Thread.new do + q.pop + Dir.chdir(pwd) rescue $! end - if @env_home - ENV["HOME"] = @env_home - else - ENV.delete("HOME") + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) + end + + t = Thread.new do + q.pop + Dir.chdir(pwd){} rescue $! end - if @env_logdir - ENV["LOGDIR"] = @env_logdir - else - ENV.delete("LOGDIR") + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) end end def test_chroot_nodir + omit if RUBY_PLATFORM =~ /android/ assert_raise(NotImplementedError, Errno::ENOENT, Errno::EPERM ) { Dir.chroot(File.join(@nodir, "")) } end @@ -132,15 +170,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([@root] + ("a".."z").map {|f| File.join(@root, f) }.sort, - Dir.glob(@root + "\0\0\0" + File.join(@root, "*")).sort) - - assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") }.sort, - Dir.glob(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), "") }, + Dir.glob(File.join(@root, "*/"))) assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a')) FileUtils.touch(File.join(@root, "{}")) @@ -150,12 +199,15 @@ 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") {} assert_equal(%w(}}{} }}a).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '}}{\{\},a}'))) assert_equal(%w(}}{} }}a b c).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '{\}\}{\{\},a},b,c}'))) + assert_raise(ArgumentError) { + Dir.glob([[@root, File.join(@root, "*")].join("\0")]) + } end def test_glob_recursive @@ -164,6 +216,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) @@ -177,13 +232,63 @@ 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) end end + def test_glob_recursive_directory + Dir.chdir(@root) do + ['d', 'e'].each do |path| + FileUtils.mkdir_p("c/#{path}/a/b/c") + FileUtils.touch("c/#{path}/a/a.file") + FileUtils.touch("c/#{path}/a/b/b.file") + FileUtils.touch("c/#{path}/a/b/c/c.file") + end + 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 + + def test_glob_starts_with_brace + Dir.chdir(@root) do + bug15649 = '[ruby-core:91728] [Bug #15649]' + assert_equal(["#{@root}/a", "#{@root}/b"], + Dir.glob("{#{@root}/a,#{@root}/b}"), bug15649) + 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) @@ -205,18 +310,74 @@ class TestDir < Test::Unit::TestCase def test_glob_base files = %w[a/foo.c c/bar.c] files.each {|n| File.write(File.join(@root, n), "")} - 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}) + Dir.mkdir(File.join(@root, "a/dir")) + dirs = @dirs + %w[a/dir/] + dirs.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 files = %w[a/foo.c c/bar.c] files.each {|n| File.write(File.join(@root, n), "")} - assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}.sort) - assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*", base: d)}}) + 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)}) + 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)}) + 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) @@ -228,19 +389,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 @@ -281,10 +475,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 @@ -315,9 +509,9 @@ class TestDir < Test::Unit::TestCase def test_glob_legacy_short_name bug10819 = '[ruby-core:67954] [Bug #10819]' bug11206 = '[ruby-core:69435] [Bug #11206]' - skip unless /\A\w:/ =~ ENV["ProgramFiles"] + omit unless /\A\w:/ =~ ENV["ProgramFiles"] short = "#$&/PROGRA~1" - skip unless File.directory?(short) + omit unless File.directory?(short) entries = Dir.glob("#{short}/Common*") assert_not_empty(entries, bug10819) long = File.expand_path(short) @@ -327,29 +521,55 @@ class TestDir < Test::Unit::TestCase assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819) assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819) end + + def test_home_windows + setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH]) + + ENV['HOME'] = "C:\\ruby\\home" + assert_equal("C:/ruby/home", Dir.home) + + ENV['USERPROFILE'] = "C:\\ruby\\userprofile" + assert_equal("C:/ruby/home", Dir.home) + ENV.delete('HOME') + assert_equal("C:/ruby/userprofile", Dir.home) + + ENV['HOMEDRIVE'] = "C:" + ENV['HOMEPATH'] = "\\ruby\\homepath" + assert_equal("C:/ruby/userprofile", Dir.home) + ENV.delete('USERPROFILE') + assert_equal("C:/ruby/homepath", Dir.home) + end end def test_home - env_home = ENV["HOME"] - env_logdir = ENV["LOGDIR"] - ENV.delete("HOME") - ENV.delete("LOGDIR") + setup_envs ENV["HOME"] = @nodir - assert_nothing_raised(ArgumentError) { + assert_nothing_raised(ArgumentError) do assert_equal(@nodir, Dir.home) + end + assert_nothing_raised(ArgumentError) do assert_equal(@nodir, Dir.home("")) - if user = ENV["USER"] - ENV["HOME"] = env_home - assert_equal(File.expand_path(env_home), Dir.home(user)) + end + if user = ENV["USER"] + tilde = windows? ? "~" : "~#{user}" + assert_nothing_raised(ArgumentError) do + assert_equal(File.expand_path(tilde), Dir.home(user)) end - } + end %W[no:such:user \u{7559 5b88}:\u{756a}].each do |user| assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)} end - ensure - ENV["HOME"] = env_home - ENV["LOGDIR"] = env_logdir + end + + if Encoding.find("filesystem") == Encoding::UTF_8 + # On Windows and macOS, file system encoding is always UTF-8. + def test_home_utf8 + setup_envs + + ENV["HOME"] = "/\u{e4}~\u{1f3e0}" + assert_equal("/\u{e4}~\u{1f3e0}", Dir.home) + end end def test_symlinks_not_resolved @@ -364,8 +584,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 @@ -397,6 +617,7 @@ class TestDir < Test::Unit::TestCase end assert_raise(Errno::ENOENT) {Dir.empty?(@nodir)} assert_not_send([Dir, :empty?, File.join(@root, "b")]) + assert_raise(ArgumentError) {Dir.empty?(@root+"\0")} end def test_glob_gc_for_fd @@ -404,12 +625,27 @@ class TestDir < Test::Unit::TestCase begin; Process.setrlimit(Process::RLIMIT_NOFILE, 50) begin - tap {tap {tap {(0..100).map {open(IO::NULL)}}}} + fs = [] + tap {tap {tap {(0..100).each {fs << open(IO::NULL)}}}} rescue Errno::EMFILE + 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 7584074c7e..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 @@ -52,7 +44,7 @@ class TestDir_M17N < Test::Unit::TestCase filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename) EOS } @@ -67,7 +59,7 @@ class TestDir_M17N < Test::Unit::TestCase filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") assert_include(ents, filename) EOS @@ -75,7 +67,7 @@ class TestDir_M17N < Test::Unit::TestCase filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") assert_include(ents, filename) EOS @@ -88,7 +80,7 @@ class TestDir_M17N < Test::Unit::TestCase filename = "\xc2\xa1".force_encoding("utf-8") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) @@ -125,13 +117,13 @@ class TestDir_M17N < Test::Unit::TestCase filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename) EOS assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) @@ -151,7 +143,7 @@ class TestDir_M17N < Test::Unit::TestCase File.open(filename1, "w") {} File.open(filename2, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename1) assert_include(ents, filename2) EOS @@ -159,7 +151,7 @@ class TestDir_M17N < Test::Unit::TestCase filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP filename2 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) assert_include(ents, filename1) assert_include(ents, filename2) EOS @@ -183,7 +175,7 @@ class TestDir_M17N < Test::Unit::TestCase 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(".", **(opts||{})) ents.each {|e| e.force_encoding("ASCII-8BIT") } if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8") @@ -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) @@ -246,7 +234,7 @@ class TestDir_M17N < Test::Unit::TestCase 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(".", **(opts||{})) if /darwin/ =~ RUBY_PLATFORM filename = filename.encode("utf-8", "euc-jp").force_encoding("euc-jp") end @@ -255,7 +243,7 @@ class TestDir_M17N < Test::Unit::TestCase assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d) filename = "\u3042" opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) + ents = Dir.entries(".", **(opts||{})) if /darwin/ =~ RUBY_PLATFORM filename = filename.force_encoding("euc-jp") end @@ -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_dup.rb b/test/ruby/test_dup.rb new file mode 100644 index 0000000000..75c4fc0339 --- /dev/null +++ b/test/ruby/test_dup.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestDup < Test::Unit::TestCase + module M001; end + module M002; end + module M003; include M002; end + module M002; include M001; end + module M003; include M002; end + + def test_dup + foo = Object.new + def foo.test + "test" + end + bar = foo.dup + def bar.test2 + "test2" + end + + assert_equal("test2", bar.test2) + assert_raise(NoMethodError) { bar.test } + assert_equal("test", foo.test) + + assert_raise(NoMethodError) {foo.test2} + + assert_equal([M003, M002, M001], M003.ancestors) + end + + def test_frozen_properties_not_retained_on_dup + obj = Object.new.freeze + duped_obj = obj.dup + + assert_predicate(obj, :frozen?) + refute_predicate(duped_obj, :frozen?) + end + + def test_ivar_retained_on_dup + obj = Object.new + obj.instance_variable_set(:@a, 1) + duped_obj = obj.dup + + assert_equal(obj.instance_variable_get(:@a), 1) + assert_equal(duped_obj.instance_variable_get(:@a), 1) + end + + def test_ivars_retained_on_extended_obj_dup + ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 } + obj = Object.new + ivars.each do |ivar_name, val| + obj.instance_variable_set(ivar_name, val) + end + + duped_obj = obj.dup + + ivars.each do |ivar_name, val| + assert_equal(obj.instance_variable_get(ivar_name), val) + assert_equal(duped_obj.instance_variable_get(ivar_name), val) + end + end + + def test_frozen_properties_not_retained_on_dup_with_ivar + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + + duped_obj = obj.dup + + assert_predicate(obj, :frozen?) + assert_equal(obj.instance_variable_get(:@a), 1) + + refute_predicate(duped_obj, :frozen?) + assert_equal(duped_obj.instance_variable_get(:@a), 1) + end + + def test_user_flags + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1, 2, 3].dup + assert_equal [], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1,2,3,4,5,6,7][1..-2].dup + x.push(1,1,1,1,1) + assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Hash + undef initialize_copy + def initialize_copy(*); end + end + h = {} + h.default_proc = proc { raise } + h = h.dup + assert_equal nil, h[:not_exist], '[Bug #14847]' + EOS + end +end diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index 6f098db454..1aad0de347 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -3,7 +3,12 @@ require 'test/unit' class TestEncodingConverter < Test::Unit::TestCase def check_ec(edst, esrc, eres, dst, src, ec, off, len, opts=nil) - res = ec.primitive_convert(src, dst, off, len, opts) + case opts + when Hash + res = ec.primitive_convert(src, dst, off, len, **opts) + else + res = ec.primitive_convert(src, dst, off, len, opts) + end assert_equal([edst.b, esrc.b, eres], [dst.b, src.b, res]) end @@ -680,7 +685,6 @@ class TestEncodingConverter < Test::Unit::TestCase ec = Encoding::Converter.new("utf-8", "euc-jp") assert_raise(Encoding::InvalidByteSequenceError) { ec.convert("a\x80") } assert_raise(Encoding::UndefinedConversionError) { ec.convert("\ufffd") } - assert_predicate(ec.convert("abc".taint), :tainted?) ret = ec.primitive_convert(nil, "", nil, nil) assert_equal(:finished, ret) assert_raise(ArgumentError) { ec.convert("a") } @@ -799,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 @@ -840,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"), @@ -908,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 8f73a8fce1..cde2951413 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -34,9 +34,6 @@ class TestEncoding < Test::Unit::TestCase assert_raise(TypeError) { e.dup } assert_raise(TypeError) { e.clone } assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id) - assert_not_predicate(e, :tainted?) - Marshal.load(Marshal.dump(e).taint) - assert_not_predicate(e, :tainted?, '[ruby-core:71793] [Bug #11760]') end end @@ -59,11 +56,37 @@ 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;'}") + Warning[:deprecated] = false + assert_instance_of(Encoding, Encoding::UTF_8.replicate("UTF-8-ANOTHER#{Time.now.to_f}")) + assert_instance_of(Encoding, Encoding::ISO_2022_JP.replicate("ISO-2022-JP-ANOTHER#{Time.now.to_f}")) bug3127 = '[ruby-dev:40954]' 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; + 100.times {|i| + EnvUtil.suppress_warning { 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) { EnvUtil.suppress_warning { enc.replicate(name) } } + name.succ! + end + end; end def test_dummy_p @@ -120,9 +143,35 @@ class TestEncoding < Test::Unit::TestCase assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}") bug9038 = '[ruby-core:57949] [Bug #9038]' begin; - assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) { + e = assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) { eval("/regexp/sQ") } + assert_include(e.message, "/regexp/sQ\n") + end; + end + + def test_nonascii_library_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}".force_encoding("US-ASCII")) + begin; + assert_equal(Encoding::US_ASCII, __ENCODING__) + $:.unshift("/\x80") + assert_raise_with_message(LoadError, /\[Bug #16382\]/) do + require "[Bug #16382]" + end + end; + end + + def test_exceed_encoding_table_size + assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + begin + enc = Encoding::UTF_8 + 1_000.times{|i| EnvUtil.suppress_warning{ enc.replicate("R_#{i}") } } # now 256 entries + rescue EncodingError => e + assert_match(/too many encoding/, e.message) + else + assert false + end end; end end diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index ae119c73bf..cc217b05cc 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 @@ -144,8 +170,7 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([], inf.to_a) end - def test_to_h - obj = Object.new + StubToH = Object.new.tap do |obj| def obj.each(*args) yield(*args) yield [:key, :value] @@ -157,6 +182,12 @@ class TestEnumerable < Test::Unit::TestCase yield kvp end obj.extend Enumerable + obj.freeze + end + + def test_to_h + obj = StubToH + assert_equal({ :hello => :world, :key => :value, @@ -175,13 +206,36 @@ class TestEnumerable < Test::Unit::TestCase assert_equal "element has wrong array length (expected 2, was 1)", e.message end + def test_to_h_block + obj = StubToH + + assert_equal({ + "hello" => "world", + "key" => "value", + "other_key" => "other_value", + "obtained" => "via_to_ary", + }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]}) + + e = assert_raise(TypeError) { + obj.to_h {:not_an_array} + } + assert_equal "wrong element type Symbol (expected array)", e.message + + e = assert_raise(ArgumentError) { + obj.to_h {[1]} + } + assert_equal "element has wrong array length (expected 2, was 1)", e.message + end + def test_inject assert_equal(12, @obj.inject {|z, x| z * x }) 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}) + + assert_raise(ArgumentError) {@obj.inject} end FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] @@ -202,17 +256,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| @@ -253,6 +365,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 } @@ -268,6 +399,50 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(h, @obj.each_with_index.group_by(&cond)) end + 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 assert_equal(1, @obj.first) assert_equal([1, 2, 3], @obj.first(3)) @@ -288,6 +463,17 @@ class TestEnumerable < Test::Unit::TestCase empty.first empty.block.call end; + + bug18475 = '[ruby-dev:107059]' + assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475) + e = Enumerator.new do |g| + Thread.new do + g << 1 + end.join + end + + e.first + end; end def test_sort @@ -310,6 +496,23 @@ 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?(Integer)) + assert_equal(false, @obj.all?(1..2)) + end + + def test_all_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.all?([:b, 2]) {|x| x == 4 } + EOS end def test_any @@ -319,27 +522,73 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, [false, false, false].any?) assert_equal(false, [].any?) assert_equal(false, @empty.any?) + assert_equal(true, @obj.any?(1..2)) + assert_equal(false, @obj.any?(Float)) + assert_equal(false, [1, 42].any?(Float)) + assert_equal(true, [1, 4.2].any?(Float)) + assert_equal(false, {a: 1, b: 2}.any?(->(kv) { kv == [:foo, 42] })) + assert_equal(true, {a: 1, b: 2}.any?(->(kv) { kv == [:b, 2] })) + end + + def test_any_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 23].any?(1) {|x| x == 1 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).any?(34) {|x| x == 2 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.any?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.any?([:b, 2]) {|x| x == 4 } + EOS end def test_one assert(@obj.one? {|x| x == 3 }) assert(!(@obj.one? {|x| x == 1 })) assert(!(@obj.one? {|x| x == 4 })) + assert(@obj.one?(3..4)) + assert(!(@obj.one?(1..2))) + assert(!(@obj.one?(4..5))) assert(%w{ant bear cat}.one? {|word| word.length == 4}) assert(!(%w{ant bear cat}.one? {|word| word.length > 4})) assert(!(%w{ant bear cat}.one? {|word| word.length < 4})) + assert(%w{ant bear cat}.one?(/b/)) + assert(!(%w{ant bear cat}.one?(/t/))) assert(!([ nil, true, 99 ].one?)) assert([ nil, true, false ].one?) assert(![].one?) assert(!@empty.one?) + assert([ nil, true, 99 ].one?(Integer)) + end + + def test_one_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.one?([:b, 2]) {|x| x == 4 } + EOS end def test_none assert(@obj.none? {|x| x == 4 }) assert(!(@obj.none? {|x| x == 1 })) assert(!(@obj.none? {|x| x == 3 })) + assert(@obj.none?(4..5)) + assert(!(@obj.none?(1..3))) assert(%w{ant bear cat}.none? {|word| word.length == 5}) assert(!(%w{ant bear cat}.none? {|word| word.length >= 4})) + assert(%w{ant bear cat}.none?(/d/)) + assert(!(%w{ant bear cat}.none?(/b/))) assert([].none?) assert([nil].none?) assert([nil,false].none?) @@ -347,6 +596,21 @@ class TestEnumerable < Test::Unit::TestCase assert(@empty.none?) end + def test_none_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.none?([:b, 2]) {|x| x == 4 } + EOS + end + def test_min assert_equal(1, @obj.min) assert_equal(3, @obj.min {|a,b| b <=> a }) @@ -486,6 +750,9 @@ class TestEnumerable < Test::Unit::TestCase ary.clear (1..10).each_slice(11) {|a| ary << a} assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary) + + assert_equal(1..10, (1..10).each_slice(3) { }) + assert_equal([], [].each_slice(3) { }) end def test_each_cons @@ -505,6 +772,9 @@ class TestEnumerable < Test::Unit::TestCase ary.clear (1..5).each_cons(6) {|a| ary << a} assert_empty(ary) + + assert_equal(1..5, (1..5).each_cons(3) { }) + assert_equal([], [].each_cons(3) { }) end def test_zip @@ -626,6 +896,19 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([2,1,3,2,1], @obj.reverse_each.to_a) end + def test_reverse_each_memory_corruption + bug16354 = '[ruby-dev:50867]' + assert_normal_exit %q{ + size = 1000 + (0...size).reverse_each do |i| + i.inspect + ObjectSpace.each_object(Array) do |a| + a.clear if a.length == size + end + end + }, bug16354 + end + def test_chunk e = [].chunk {|elt| true } assert_equal([], e.to_a) @@ -948,6 +1231,21 @@ class TestEnumerable < Test::Unit::TestCase assert_float_equal(large_number+(small_number*11), [small_number, large_number/1r, *[small_number]*10].each.sum) assert_float_equal(small_number, [large_number, small_number, -large_number].each.sum) + k = Class.new do + include Enumerable + def initialize(*values) + @values = values + end + def each(&block) + @values.each(&block) + end + end + assert_equal(+Float::INFINITY, k.new(0.0, +Float::INFINITY).sum) + assert_equal(+Float::INFINITY, k.new(+Float::INFINITY, 0.0).sum) + assert_equal(-Float::INFINITY, k.new(0.0, -Float::INFINITY).sum) + assert_equal(-Float::INFINITY, k.new(-Float::INFINITY, 0.0).sum) + assert_predicate(k.new(-Float::INFINITY, Float::INFINITY).sum, :nan?) + assert_equal("abc", ["a", "b", "c"].each.sum("")) assert_equal([1, [2], 3], [[1], [[2]], [3]].each.sum([])) end @@ -996,4 +1294,44 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([1, 2, 3, 4, 5, 10], (1..100).uniq{|x| (x**2) % 10 }.first(6)) 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 + attr_reader :i + def initialize e + @i = e + end + def <=> other + GC.start + i <=> other.i + end + end + assert_equal [1, 2, 3, 4, 5], (1..5).sort_by{|e| klass.new e} + end + + def test_filter_map + @obj = (1..8).to_a + assert_equal([4, 8, 12, 16], @obj.filter_map { |i| i * 2 if i.even? }) + assert_equal([2, 4, 6, 8, 10, 12, 14, 16], @obj.filter_map { |i| i * 2 }) + assert_equal([0, 0, 0, 0, 0, 0, 0, 0], @obj.filter_map { 0 }) + assert_equal([], @obj.filter_map { false }) + assert_equal([], @obj.filter_map { nil }) + assert_instance_of(Enumerator, @obj.filter_map) + end end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 51f69be37d..9224506e44 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(RuntimeError) { - 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 @@ -113,6 +112,11 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([[1,2,3],[4,5,6],[7,8,9],[10]], (1..10).each_slice(3).to_a) end + def test_each_slice_size + assert_equal(4, (1..10).each_slice(3).size) + assert_equal(Float::INFINITY, 1.step.each_slice(3).size) + end + def test_cons a = [[1,2,3], [2,3,4], [3,4,5], [4,5,6], [5,6,7], [6,7,8], [7,8,9], [8,9,10]] assert_equal(a, (1..10).each_cons(3).to_a) @@ -240,6 +244,26 @@ class TestEnumerator < Test::Unit::TestCase assert_equal(res, exc.result) end + def test_stopiteration_rescue + e = [1].each + res = e.each {} + e.next + exc0 = assert_raise(StopIteration) { e.peek } + assert_include(exc0.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_nil(exc0.cause) + assert_equal(res, exc0.result) + + exc1 = assert_raise(StopIteration) { e.next } + assert_include(exc1.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_same(exc0, exc1.cause) + assert_equal(res, exc1.result) + + exc2 = assert_raise(StopIteration) { e.next } + assert_include(exc2.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_same(exc0, exc2.cause) + assert_equal(res, exc2.result) + end + def test_next_values o = Object.new def o.each @@ -301,8 +325,11 @@ class TestEnumerator < Test::Unit::TestCase yield end ary = [] - e = o.to_enum.each(ary) - e.next + e = o.to_enum { 1 } + assert_equal(1, e.size) + e_arg = e.each(ary) + assert_equal(nil, e_arg.size) + e_arg.next assert_equal([1], ary) end @@ -404,6 +431,12 @@ class TestEnumerator < Test::Unit::TestCase e = (0..10).each_cons(2) assert_equal("#<Enumerator: 0..10:each_cons(2)>", e.inspect) + e = (0..10).each_with_object({}) + assert_equal("#<Enumerator: 0..10:each_with_object({})>", e.inspect) + + e = (0..10).each_with_object(a: 1) + assert_equal("#<Enumerator: 0..10:each_with_object(a: 1)>", e.inspect) + e = Enumerator.new {|y| y.yield; 10 } assert_match(/\A#<Enumerator: .*:each>/, e.inspect) @@ -440,7 +473,7 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([1, 2, 3], a) g.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { g.__send__ :initialize, proc { |y| y << 4 << 5 } } @@ -476,8 +509,29 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([1], y.yield(1)) assert_equal([1, 2], y.yield(2)) assert_equal([1, 2, 3], y.yield(3)) + assert_equal([1, 2, 3, 4], y.yield(4, 5)) + + a = [] + y = Enumerator::Yielder.new {|*x| a.concat(x) } + assert_equal([1], y.yield(1)) + assert_equal([1, 2, 3], y.yield(2, 3)) assert_raise(LocalJumpError) { Enumerator::Yielder.new } + + # to_proc (explicit) + a = [] + y = Enumerator::Yielder.new {|x| a << x } + b = y.to_proc + assert_kind_of(Proc, b) + assert_equal([1], b.call(1)) + assert_equal([1], a) + + # to_proc (implicit) + e = Enumerator.new { |y| + assert_kind_of(Enumerator::Yielder, y) + [1, 2, 3].each(&y) + } + assert_equal([1, 2, 3], e.to_a) end def test_size @@ -505,14 +559,14 @@ class TestEnumerator < Test::Unit::TestCase def test_size_for_enum_created_from_array arr = %w[hello world] %i[each each_with_index reverse_each sort_by! sort_by map map! - keep_if reject! reject select! select delete_if].each do |method| + keep_if reject! reject select! select filter! filter delete_if].each do |method| assert_equal arr.size, arr.send(method).size end end def test_size_for_enum_created_from_enumerable %i[find_all reject map flat_map partition group_by sort_by min_by max_by - minmax_by each_with_index reverse_each each_entry].each do |method| + minmax_by each_with_index reverse_each each_entry filter_map].each do |method| assert_equal nil, @obj.send(method).size assert_equal 42, @sized.send(method).size end @@ -522,7 +576,7 @@ class TestEnumerator < Test::Unit::TestCase def test_size_for_enum_created_from_hash h = {a: 1, b: 2, c: 3} - methods = %i[delete_if reject reject! select select! keep_if each each_key each_pair] + methods = %i[delete_if reject reject! select select! filter filter! keep_if each each_key each_pair] enums = methods.map {|method| h.send(method)} s = enums.group_by(&:size) assert_equal([3], s.keys, ->{s.reject!{|k| k==3}.inspect}) @@ -532,7 +586,7 @@ class TestEnumerator < Test::Unit::TestCase end def test_size_for_enum_created_from_env - %i[each_pair reject! delete_if select select! keep_if].each do |method| + %i[each_pair reject! delete_if select select! filter filter! keep_if].each do |method| assert_equal ENV.size, ENV.send(method).size end end @@ -632,7 +686,7 @@ class TestEnumerator < Test::Unit::TestCase assert_equal 4, (1..10).step(3).size assert_equal 3, (1...10).step(3).size assert_equal Float::INFINITY, (42..Float::INFINITY).step(2).size - assert_raise(ArgumentError){ (1..10).step(-2).size } + assert_equal 0, (1..10).step(-2).size end def test_size_for_downup_to @@ -657,8 +711,316 @@ class TestEnumerator < Test::Unit::TestCase end def test_uniq - assert_equal([1, 2, 3, 4, 5, 10], - (1..Float::INFINITY).lazy.uniq{|x| (x**2) % 10 }.first(6)) + u = [0, 1, 0, 1].to_enum.lazy.uniq + assert_equal([0, 1], u.force) + 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 + + e1 = r.chain() + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = r.chain([6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = r.chain([6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + # `a + b + c` should not return `Enumerator::Chain.new(a, b, c)` + # because it is expected that `(a + b).each` be called. + e4 = e2.dup + class << e4 + attr_reader :each_is_called + def each + super + @each_is_called = true + end + end + e5 = e4 + 9.step + assert_kind_of(Enumerator::Chain, e5) + assert_equal(Float::INFINITY, e5.size) + ary = [] + e5.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + assert_equal(true, e4.each_is_called) + end + + def test_chained_enums + a = (1..5).each + + e0 = Enumerator::Chain.new() + assert_kind_of(Enumerator::Chain, e0) + assert_equal(0, e0.size) + ary = [] + e0.each { |x| ary << x } + assert_equal([], ary) + + e1 = Enumerator::Chain.new(a) + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = Enumerator::Chain.new(a, [6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = Enumerator::Chain.new(a, [6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + e4 = Enumerator::Chain.new(a, Enumerator.new { |y| y << 6 << 7 << 8 }) + assert_kind_of(Enumerator::Chain, e4) + assert_equal(nil, e4.size) + ary = [] + e4.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e5 = Enumerator::Chain.new(e1, e2) + assert_kind_of(Enumerator::Chain, e5) + assert_equal(13, e5.size) + ary = [] + e5.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8], ary) + + rewound = [] + e1.define_singleton_method(:rewind) { rewound << object_id } + e2.define_singleton_method(:rewind) { rewound << object_id } + e5.rewind + assert_equal(rewound, [e2.object_id, e1.object_id]) + + rewound = [] + a = [1] + e6 = Enumerator::Chain.new(a) + a.define_singleton_method(:rewind) { rewound << object_id } + e6.rewind + assert_equal(rewound, []) + + assert_equal( + '#<Enumerator::Chain: [' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>' + + ']>, ' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>, ' + + '[6, 7, 8]' + + ']>' + + ']>', + e5.inspect + ) + 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 -end + def test_produce + assert_raise(ArgumentError) { Enumerator.produce } + + # Without initial object + passed_args = [] + enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ } + assert_instance_of(Enumerator, enum) + assert_equal Float::INFINITY, enum.size + assert_equal [1, 2, 3], enum.take(3) + assert_equal [nil, 1, 2], passed_args + + # With initial object + passed_args = [] + enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ } + assert_instance_of(Enumerator, enum) + assert_equal Float::INFINITY, enum.size + assert_equal [1, 2, 3], enum.take(3) + assert_equal [1, 2], passed_args + + # With initial keyword arguments + passed_args = [] + enum = Enumerator.produce(a: 1, b: 1) { |obj| passed_args << obj; obj.shift if obj.respond_to?(:shift)} + assert_instance_of(Enumerator, enum) + assert_equal Float::INFINITY, enum.size + assert_equal [{b: 1}, [1], :a, nil], enum.take(4) + assert_equal [{b: 1}, [1], :a], passed_args + + # Raising StopIteration + words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/) + enum = Enumerator.produce { words.shift or raise StopIteration } + assert_equal Float::INFINITY, enum.size + assert_instance_of(Enumerator, enum) + assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a + + # Raising StopIteration + object = [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"] + enum = Enumerator.produce(object) { |obj| + obj.respond_to?(:first) or raise StopIteration + obj.first + } + assert_equal Float::INFINITY, enum.size + assert_instance_of(Enumerator, enum) + assert_nothing_raised { + assert_equal [ + [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"], + [[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], + [["abc", "def"], "ghi", "jkl"], + ["abc", "def"], + "abc", + ], enum.to_a + } + end + + def test_chain_each_lambda + c = Class.new do + include Enumerable + attr_reader :is_lambda + def each(&block) + return to_enum unless block + @is_lambda = block.lambda? + end + end + e = c.new + e.chain.each{} + assert_equal(false, e.is_lambda) + e.chain.each(&->{}) + assert_equal(true, e.is_lambda) + end + + def test_product + ## + ## Enumerator::Product + ## + + # 0-dimensional + e = Enumerator::Product.new + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(1, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [nil], heads + + # 1-dimensional + e = Enumerator::Product.new(1..3) + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(3, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[1], [2], [3]], elts + assert_equal elts, e.to_a + + # 2-dimensional + e = Enumerator::Product.new(1..3, %w[a b]) + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(3 * 2, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [1, 1, 2, 2, 3, 3], heads + + # Reject keyword arguments + assert_raise(ArgumentError) { + Enumerator::Product.new(1..3, foo: 1, bar: 2) + } + + ## + ## Enumerator.product + ## + + # without a block + e = Enumerator.product(1..3, %w[a b]) + assert_instance_of(Enumerator::Product, e) + + # with a block + elts = [] + ret = Enumerator.product(1..3) { |x| elts << x } + assert_equal(nil, ret) + assert_equal [[1], [2], [3]], elts + assert_equal elts, Enumerator.product(1..3).to_a + + # an infinite enumerator and a finite enumerable + e = Enumerator.product(1.., 'a'..'c') + assert_equal(Float::INFINITY, e.size) + assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4) + + # an infinite enumerator and an unknown enumerator + e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' }) + assert_equal(Float::INFINITY, e.size) + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + + # an infinite enumerator and an unknown enumerator + e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' }) + assert_equal(nil, e.size) + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + + # Reject keyword arguments + assert_raise(ArgumentError) { + Enumerator.product(1..3, foo: 1, bar: 2) + } + end + + def test_sum_of_numeric + num = Class.new(Numeric) do + attr_reader :to_f + def initialize(val) + @to_f = Float(val) + end + end + + ary = [5, 10, 20].map {|i| num.new(i)} + + assert_equal(35.0, ary.sum) + enum = ary.each + assert_equal(35.0, enum.sum) + end +end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 0ed88dd1d5..949913b590 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -2,7 +2,9 @@ require 'test/unit' class TestEnv < Test::Unit::TestCase - IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM + windows = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM + IGNORE_CASE = windows + ENCODING = windows ? Encoding::UTF_8 : Encoding.find("locale") PATH_ENV = "PATH" INVALID_ENVVARS = [ "foo\0bar", @@ -22,7 +24,6 @@ class TestEnv < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil @backup = ENV.to_hash ENV.delete('test') ENV.delete('TEST') @@ -46,7 +47,6 @@ class TestEnv < Test::Unit::TestCase end ENV['TEST'] = 'bar' assert_equal('bar', ENV['TEST']) - assert_predicate(ENV['TEST'], :tainted?) if IGNORE_CASE assert_equal('bar', ENV['test']) else @@ -64,6 +64,41 @@ class TestEnv < Test::Unit::TestCase } end + def test_dup + assert_raise(TypeError) { + ENV.dup + } + end + + def test_clone + message = /Cannot clone ENV/ + assert_raise_with_message(TypeError, message) { + ENV.clone + } + assert_raise_with_message(TypeError, message) { + ENV.clone(freeze: false) + } + assert_raise_with_message(TypeError, message) { + ENV.clone(freeze: nil) + } + assert_raise_with_message(TypeError, message) { + 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) @@ -85,7 +120,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 @@ -107,13 +141,13 @@ 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 assert_invalid_env {|v| ENV[v]} ENV[PATH_ENV] = "" assert_equal("", ENV[PATH_ENV]) - assert_predicate(ENV[PATH_ENV], :tainted?) assert_nil(ENV[""]) end @@ -122,19 +156,20 @@ class TestEnv < Test::Unit::TestCase assert_equal("foo", ENV.fetch("test")) ENV.delete("test") feature8649 = '[ruby-core:56062] [Feature #8649]' - e = assert_raise_with_message(KeyError, 'key not found: "test"', feature8649) do + e = assert_raise_with_message(KeyError, /key not found: "test"/, feature8649) do ENV.fetch("test") end assert_same(ENV, e.receiver) assert_equal("test", e.key) assert_equal("foo", ENV.fetch("test", "foo")) assert_equal("bar", ENV.fetch("test") { "bar" }) - assert_equal("bar", ENV.fetch("test", "foo") { "bar" }) + EnvUtil.suppress_warning do + assert_equal("bar", ENV.fetch("test", "foo") { "bar" }) + end assert_invalid_env {|v| ENV.fetch(v)} assert_nothing_raised { ENV.fetch(PATH_ENV, "foo") } ENV[PATH_ENV] = "" assert_equal("", ENV.fetch(PATH_ENV)) - assert_predicate(ENV.fetch(PATH_ENV), :tainted?) end def test_aset @@ -152,9 +187,6 @@ class TestEnv < Test::Unit::TestCase assert_equal("test", ENV["foo"]) rescue Errno::EINVAL end - - ENV[PATH_ENV] = "/tmp/".taint - assert_equal("/tmp/", ENV[PATH_ENV]) end def test_keys @@ -220,6 +252,18 @@ class TestEnv < Test::Unit::TestCase assert_nil(ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }) end + def test_filter_bang + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + assert_equal(h1, h2) + + assert_nil(ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }) + end + def test_keep_if h1 = {} ENV.each_pair {|k, v| h1[k] = v } @@ -252,6 +296,43 @@ class TestEnv < Test::Unit::TestCase end end + def test_filter + ENV["test"] = "foo" + h = ENV.filter {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } + assert_equal(1, h.size) + k = h.keys.first + v = h.values.first + if IGNORE_CASE + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end + + def test_slice + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + assert_equal({}, ENV.slice()) + assert_equal({}, ENV.slice("")) + assert_equal({}, ENV.slice("unknown")) + 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) @@ -274,6 +355,13 @@ class TestEnv < Test::Unit::TestCase end end + def test_inspect_encoding + ENV.clear + key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" + ENV[key] = "foo" + assert_equal(%{{#{(key.encode(ENCODING) rescue key.b).inspect}=>"foo"}}, ENV.inspect) + end + def test_to_a ENV.clear ENV["foo"] = "bar" @@ -324,8 +412,7 @@ class TestEnv < Test::Unit::TestCase assert_equal("foo", v) end assert_invalid_env {|var| ENV.assoc(var)} - assert_predicate(v, :tainted?) - assert_equal(Encoding.find("locale"), v.encoding) + assert_equal(ENCODING, v.encoding) end def test_has_value2 @@ -359,6 +446,8 @@ class TestEnv < Test::Unit::TestCase def test_to_h assert_equal(ENV.to_hash, ENV.to_h) + assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h, + ENV.to_h {|k, v| ["$#{k}", v.size]}) end def test_reject @@ -371,8 +460,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 @@ -398,6 +487,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 @@ -406,27 +497,39 @@ class TestEnv < Test::Unit::TestCase ENV["baz"] = "qux" ENV.update({"baz"=>"quux","a"=>"b"}) check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + ENV.update + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + ENV.update({"foo"=>"zot"}, {"c"=>"d"}) + check(ENV.to_hash.to_a, [%w(foo zot), %w(baz quux), %w(a b), %w(c d)]) ENV.clear ENV["foo"] = "bar" ENV["baz"] = "qux" - ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| v1 ? k + "_" + v1 + "_" + v2 : v2 } + ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + ENV.update {|k, v1, v2| k + "_" + v1 + "_" + v2 } + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + ENV.update({"foo"=>"zot"}, {"c"=>"d"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } + check(ENV.to_hash.to_a, [%w(foo foo_bar_zot), %w(baz baz_qux_quux), %w(a b), %w(c d)]) end def test_huge_value - huge_value = "bar" * 40960 - ENV["foo"] = "bar" - if /mswin|mingw/ =~ 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 + if windows + def windows_version + @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i + end + def test_win32_blocksize keys = [] len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1} @@ -436,14 +539,24 @@ class TestEnv < Test::Unit::TestCase keys << key ENV[key] = val end - 1.upto(12) {|i| - assert_raise(Errno::EINVAL) { ENV[key] = val } - } + if windows_version < 6 + 1.upto(12) {|i| + assert_raise(Errno::EINVAL) { ENV[key] = val } + } + else + 1.upto(12) {|i| + assert_nothing_raised(Errno::EINVAL) { ENV[key] = val } + } + end ensure keys.each {|k| ENV.delete(k)} end end + def test_frozen_env + assert_raise(TypeError) { ENV.freeze } + end + def test_frozen ENV[PATH_ENV] = "/" ENV.each do |k, v| @@ -476,6 +589,861 @@ class TestEnv < Test::Unit::TestCase assert_nil(e1, bug12475) end + def ignore_case_str + IGNORE_CASE ? "true" : "false" + end + + def str_for_yielding_exception_class(code_str, exception_var: "raised_exception") + <<-"end;" + #{exception_var} = nil + begin + #{code_str} + rescue Exception => e + #{exception_var} = e + end + Ractor.yield #{exception_var}.class + end; + end + + def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var) + <<-"end;" + error_class = #{ractor_var}.take + assert_raise(#{expected_error_class}) do + if error_class < Exception + raise error_class + end + end + end; + end + + def str_to_yield_invalid_envvar_errors(var_name, code_str) + <<-"end;" + envvars_to_check = [ + "foo\0bar", + "#{'\xa1\xa1'}".force_encoding(Encoding::UTF_16LE), + "foo".force_encoding(Encoding::ISO_2022_JP), + ] + envvars_to_check.each do |#{var_name}| + #{str_for_yielding_exception_class(code_str)} + end + end; + end + + def str_to_receive_invalid_envvar_errors(ractor_var) + <<-"end;" + 3.times do + #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, ractor_var)} + end + end; + end + + STR_DEFINITION_FOR_CHECK = %Q{ + def check(as, bs) + if #{IGNORE_CASE ? "true" : "false"} + as = as.map {|k, v| [k.upcase, v] } + bs = bs.map {|k, v| [k.upcase, v] } + end + assert_equal(as.sort, bs.sort) + end + } + + def test_bracket_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + Ractor.yield ENV['test'] + Ractor.yield ENV['TEST'] + ENV['test'] = 'foo' + Ractor.yield ENV['test'] + Ractor.yield ENV['TEST'] + ENV['TEST'] = 'bar' + Ractor.yield ENV['TEST'] + Ractor.yield ENV['test'] + #{str_for_yielding_exception_class("ENV[1]")} + #{str_for_yielding_exception_class("ENV[1] = 'foo'")} + #{str_for_yielding_exception_class("ENV['test'] = 0")} + end + assert_nil(r.take) + assert_nil(r.take) + assert_equal('foo', r.take) + if #{ignore_case_str} + assert_equal('foo', r.take) + else + assert_nil(r.take) + end + assert_equal('bar', r.take) + if #{ignore_case_str} + assert_equal('bar', r.take) + else + assert_equal('foo', r.take) + end + 3.times do + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + end + end; + end + + def test_dup_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + #{str_for_yielding_exception_class("ENV.dup")} + end + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + end; + end + + def test_has_value_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + Ractor.yield(ENV.has_value?(val)) + Ractor.yield(ENV.has_value?(val.upcase)) + ENV['test'] = val + Ractor.yield(ENV.has_value?(val)) + Ractor.yield(ENV.has_value?(val.upcase)) + ENV['test'] = val.upcase + Ractor.yield ENV.has_value?(val) + Ractor.yield ENV.has_value?(val.upcase) + end + assert_equal(false, r.take) + assert_equal(false, r.take) + assert_equal(true, r.take) + assert_equal(false, r.take) + assert_equal(false, r.take) + assert_equal(true, r.take) + end; + end + + def test_key_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + Ractor.yield ENV.key(val) + Ractor.yield ENV.key(val.upcase) + ENV['test'] = val + Ractor.yield ENV.key(val) + Ractor.yield ENV.key(val.upcase) + ENV['test'] = val.upcase + Ractor.yield ENV.key(val) + Ractor.yield ENV.key(val.upcase) + end + assert_nil(r.take) + assert_nil(r.take) + if #{ignore_case_str} + assert_equal('TEST', r.take.upcase) + else + assert_equal('test', r.take) + end + assert_nil(r.take) + assert_nil(r.take) + if #{ignore_case_str} + assert_equal('TEST', r.take.upcase) + else + assert_equal('test', r.take) + end + end; + + end + + def test_delete_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + #{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")} + Ractor.yield ENV.delete("TEST") + #{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")} + Ractor.yield(ENV.delete("TEST"){|name| "NO "+name}) + end + #{str_to_receive_invalid_envvar_errors("r")} + assert_nil(r.take) + exception_class = r.take + assert_equal(NilClass, exception_class) + assert_equal("NO TEST", r.take) + end; + end + + def test_getenv_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + #{str_to_yield_invalid_envvar_errors("v", "ENV[v]")} + ENV["#{PATH_ENV}"] = "" + Ractor.yield ENV["#{PATH_ENV}"] + Ractor.yield ENV[""] + end + #{str_to_receive_invalid_envvar_errors("r")} + assert_equal("", r.take) + assert_nil(r.take) + end; + end + + def test_fetch_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV["test"] = "foo" + Ractor.yield ENV.fetch("test") + ENV.delete("test") + #{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")} + Ractor.yield ex.receiver.object_id + Ractor.yield ex.key + Ractor.yield ENV.fetch("test", "foo") + Ractor.yield(ENV.fetch("test"){"bar"}) + #{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")} + #{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")} + ENV['#{PATH_ENV}'] = "" + Ractor.yield ENV.fetch('#{PATH_ENV}') + end + assert_equal("foo", r.take) + #{str_for_assert_raise_on_yielded_exception_class(KeyError, "r")} + assert_equal(ENV.object_id, r.take) + assert_equal("test", r.take) + assert_equal("foo", r.take) + assert_equal("bar", r.take) + #{str_to_receive_invalid_envvar_errors("r")} + exception_class = r.take + assert_equal(NilClass, exception_class) + assert_equal("", r.take) + end; + end + + def test_aset_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + #{str_for_yielding_exception_class("ENV['test'] = nil")} + ENV["test"] = nil + Ractor.yield ENV["test"] + #{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")} + #{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")} + end + exception_class = r.take + assert_equal(NilClass, exception_class) + assert_nil(r.take) + #{str_to_receive_invalid_envvar_errors("r")} + #{str_to_receive_invalid_envvar_errors("r")} + end; + end + + def test_keys_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + a = ENV.keys + Ractor.yield a + end + a = r.take + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end; + + end + + def test_each_key_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.each_key {|k| Ractor.yield(k)} + Ractor.yield "finished" + end + while((x=r.take) != "finished") + assert_kind_of(String, x) + end + end; + end + + def test_values_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + a = ENV.values + Ractor.yield a + end + a = r.take + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end; + end + + def test_each_value_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.each_value {|k| Ractor.yield(k)} + Ractor.yield "finished" + end + while((x=r.take) != "finished") + assert_kind_of(String, x) + end + end; + end + + def test_each_pair_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.each_pair {|k, v| Ractor.yield([k,v])} + Ractor.yield "finished" + end + while((k,v=r.take) != "finished") + assert_kind_of(String, k) + assert_kind_of(String, v) + end + end; + end + + def test_reject_bang_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + Ractor.yield [h1, h2] + Ractor.yield(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) + end + h1, h2 = r.take + assert_equal(h1, h2) + assert_nil(r.take) + end; + end + + def test_delete_if_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + Ractor.yield [h1, h2] + Ractor.yield (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }).object_id + end + h1, h2 = r.take + assert_equal(h1, h2) + assert_equal(ENV.object_id, r.take) + end; + end + + def test_select_bang_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + Ractor.yield [h1, h2] + Ractor.yield(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + end + h1, h2 = r.take + assert_equal(h1, h2) + assert_nil(r.take) + end; + end + + def test_filter_bang_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + Ractor.yield [h1, h2] + Ractor.yield(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + end + h1, h2 = r.take + assert_equal(h1, h2) + assert_nil(r.take) + end; + end + + def test_keep_if_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + Ractor.yield [h1, h2] + Ractor.yield (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }).object_id + end + h1, h2 = r.take + assert_equal(h1, h2) + assert_equal(ENV.object_id, r.take) + end; + end + + def test_values_at_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV["test"] = "foo" + Ractor.yield ENV.values_at("test", "test") + end + assert_equal(["foo", "foo"], r.take) + end; + end + + def test_select_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV["test"] = "foo" + h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + Ractor.yield h.size + k = h.keys.first + v = h.values.first + Ractor.yield [k, v] + end + assert_equal(1, r.take) + k, v = r.take + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_filter_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV["test"] = "foo" + h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + Ractor.yield(h.size) + k = h.keys.first + v = h.values.first + Ractor.yield [k, v] + end + assert_equal(1, r.take) + k, v = r.take + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_slice_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + Ractor.yield(ENV.slice()) + Ractor.yield(ENV.slice("")) + Ractor.yield(ENV.slice("unknown")) + Ractor.yield(ENV.slice("foo", "baz")) + end + assert_equal({}, r.take) + assert_equal({}, r.take) + assert_equal({}, r.take) + assert_equal({"foo"=>"bar", "baz"=>"qux"}, r.take) + end; + end + + def test_except_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + Ractor.yield ENV.except() + Ractor.yield ENV.except("") + Ractor.yield ENV.except("unknown") + Ractor.yield ENV.except("foo", "baz") + end + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, r.take) + assert_equal({"bar"=>"rab"}, r.take) + end; + end + + def test_clear_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + Ractor.yield ENV.size + end + assert_equal(0, r.take) + end; + end + + def test_to_s_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.to_s + end + assert_equal("ENV", r.take) + end; + end + + def test_inspect_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + s = ENV.inspect + Ractor.yield s + end + s = r.take + if #{ignore_case_str} + s = s.upcase + assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}') + else + assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}') + end + end; + end + + def test_to_a_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.to_a + Ractor.yield a + end + a = r.take + assert_equal(2, a.size) + if #{ignore_case_str} + a = a.map {|x| x.map {|y| y.upcase } } + assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)]) + else + assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)]) + end + end; + end + + def test_rehash_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.rehash + end + assert_nil(r.take) + end; + end + + def test_size_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + s = ENV.size + ENV["test"] = "foo" + Ractor.yield [s, ENV.size] + end + s, s2 = r.take + assert_equal(s + 1, s2) + end; + end + + def test_empty_p_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + Ractor.yield ENV.empty? + ENV["test"] = "foo" + Ractor.yield ENV.empty? + end + assert r.take + assert !r.take + end; + end + + def test_has_key_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + Ractor.yield ENV.has_key?("test") + ENV["test"] = "foo" + Ractor.yield ENV.has_key?("test") + #{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")} + end + assert !r.take + assert r.take + #{str_to_receive_invalid_envvar_errors("r")} + end; + end + + def test_assoc_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + Ractor.yield ENV.assoc("test") + ENV["test"] = "foo" + Ractor.yield ENV.assoc("test") + #{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")} + end + assert_nil(r.take) + k, v = r.take + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + #{str_to_receive_invalid_envvar_errors("r")} + encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale") + assert_equal(encoding, v.encoding) + end; + end + + def test_has_value2_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + Ractor.yield ENV.has_value?("foo") + ENV["test"] = "foo" + Ractor.yield ENV.has_value?("foo") + end + assert !r.take + assert r.take + end; + end + + def test_rassoc_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.clear + Ractor.yield ENV.rassoc("foo") + ENV["foo"] = "bar" + ENV["test"] = "foo" + ENV["baz"] = "qux" + Ractor.yield ENV.rassoc("foo") + end + assert_nil(r.take) + k, v = r.take + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_to_hash_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h = {} + ENV.each {|k, v| h[k] = v } + Ractor.yield [h, ENV.to_hash] + end + h, h2 = r.take + assert_equal(h, h2) + end; + end + + def test_to_h_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + Ractor.yield [ENV.to_hash, ENV.to_h] + Ractor.yield [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}] + end + a, b = r.take + assert_equal(a,b) + c, d = r.take + assert_equal(c,d) + end; + end + + def test_reject_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + Ractor.yield [h1, h2] + end + h1, h2 = r.take + assert_equal(h1, h2) + end; + end + + def test_shift_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.shift + b = ENV.shift + Ractor.yield [a,b] + Ractor.yield ENV.shift + end + a,b = r.take + check([a, b], [%w(foo bar), %w(baz qux)]) + assert_nil(r.take) + end; + end + + def test_invert_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + Ractor.yield(ENV.invert) + end + check(r.take.to_a, [%w(bar foo), %w(qux baz)]) + end; + end + + def test_replace_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + r = Ractor.new do + ENV["foo"] = "xxx" + ENV.replace({"foo"=>"bar", "baz"=>"qux"}) + Ractor.yield ENV.to_hash + ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"}) + Ractor.yield ENV.to_hash + end + check(r.take.to_a, [%w(foo bar), %w(baz qux)]) + check(r.take.to_a, [%w(Foo Bar), %w(Baz Qux)]) + end; + end + + def test_update_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + r = Ractor.new do + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) + Ractor.yield ENV.to_hash + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } + Ractor.yield ENV.to_hash + end + check(r.take.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + check(r.take.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + end; + end + + def test_huge_value_in_ractor + assert_ractor(<<-"end;") + huge_value = "bar" * 40960 + r = Ractor.new huge_value do |v| + ENV["foo"] = "bar" + #{str_for_yielding_exception_class("ENV['foo'] = v ")} + Ractor.yield ENV["foo"] + end + + if /mswin|ucrt/ =~ RUBY_PLATFORM + #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "r")} + result = r.take + assert_equal("bar", result) + else + exception_class = r.take + assert_equal(NilClass, exception_class) + result = r.take + assert_equal(huge_value, result) + end + end; + end + + def test_frozen_env_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + #{str_for_yielding_exception_class("ENV.freeze")} + end + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "r")} + end; + end + + def test_frozen_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV["#{PATH_ENV}"] = "/" + ENV.each do |k, v| + Ractor.yield [k.frozen?] + Ractor.yield [v.frozen?] + end + ENV.each_key do |k| + Ractor.yield [k.frozen?] + end + ENV.each_value do |v| + Ractor.yield [v.frozen?] + end + ENV.each_key do |k| + Ractor.yield [ENV[k].frozen?, "[\#{k.dump}]"] + Ractor.yield [ENV.fetch(k).frozen?, "fetch(\#{k.dump})"] + end + Ractor.yield "finished" + end + while((params=r.take) != "finished") + assert(*params) + end + end; + end + + def test_shared_substring_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + bug12475 = '[ruby-dev:49655] [Bug #12475]' + n = [*"0".."9"].join("")*3 + e0 = ENV[n0 = "E\#{n}"] + e1 = ENV[n1 = "E\#{n}."] + ENV[n0] = nil + ENV[n1] = nil + ENV[n1.chop] = "T\#{n}.".chop + ENV[n0], e0 = e0, ENV[n0] + ENV[n1], e1 = e1, ENV[n1] + Ractor.yield [n, e0, e1, bug12475] + end + n, e0, e1, bug12475 = r.take + assert_equal("T\#{n}", e0, bug12475) + assert_nil(e1, bug12475) + end; + end + + def test_ivar_in_env_should_not_be_access_from_non_main_ractors + assert_ractor <<~RUBY + ENV.instance_eval{ @a = "hello" } + assert_equal "hello", ENV.instance_variable_get(:@a) + + r_get = Ractor.new do + ENV.instance_variable_get(:@a) + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, r_get.take.class + + r_get = Ractor.new do + ENV.instance_eval{ @a } + rescue Ractor::IsolationError => e + e + end + + assert_equal Ractor::IsolationError, r_get.take.class + + r_set = Ractor.new do + ENV.instance_eval{ @b = "hello" } + rescue Ractor::IsolationError => e + e + end + + assert_equal Ractor::IsolationError, r_set.take.class + RUBY + end + if RUBY_PLATFORM =~ /bccwin|mswin|mingw/ def test_memory_leak_aset bug9977 = '[ruby-dev:48323] [Bug #9977]' @@ -517,15 +1485,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 9f25b771bf..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 # @@ -320,20 +390,6 @@ class TestEval < Test::Unit::TestCase end assert(!bad) - if false - # Ruby 2.0 doesn't see Proc as Binding - x = proc{} - eval "i4 = 1", x - assert_equal(1, eval("i4", x)) - x = proc{proc{}}.call - eval "i4 = 22", x - assert_equal(22, eval("i4", x)) - t = [] - x = proc{proc{}}.call - eval "(0..9).each{|i5| t[i5] = proc{i5*2}}", x - assert_equal(8, t[4].call) - end - x = binding eval "i = 1", x assert_equal(1, eval("i", x)) @@ -359,27 +415,11 @@ class TestEval < Test::Unit::TestCase # assert_equal(1, eval("foo11")) assert_equal(eval("foo22"), eval("foo22", p)) assert_equal(55, eval("foo22")) + assert_equal(55, foo22) }.call - if false - # Ruby 2.0 doesn't see Proc as Binding - p1 = proc{i7 = 0; proc{i7}}.call - assert_equal(0, p1.call) - eval "i7=5", p1 - assert_equal(5, p1.call) - assert(!defined?(i7)) - end - - if false - # Ruby 2.0 doesn't see Proc as Binding - p1 = proc{i7 = 0; proc{i7}}.call - i7 = nil - assert_equal(0, p1.call) - eval "i7=1", p1 - assert_equal(1, p1.call) - eval "i7=5", p1 - assert_equal(5, p1.call) - assert_nil(i7) + self.class.class_eval do + remove_const :EvTest end end @@ -503,10 +543,19 @@ class TestEval < Test::Unit::TestCase assert_same a, b end + def test_eval_location_binding + 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 bug = "[ruby-core:78116] [Bug #12930]".freeze assert_same bug, (bug.instance_eval {self}) - assert_raise(RuntimeError) { + assert_raise(FrozenError) { bug.instance_eval {@ivar = true} } end @@ -526,6 +575,17 @@ class TestEval < Test::Unit::TestCase }, '[Bug #10368]' end + def test_gced_eval_location + Dir.mktmpdir do |d| + File.write("#{d}/2.rb", "") + File.write("#{d}/1.rb", "require_relative '2'\n""__FILE__\n") + file = "1.rb" + path = File.expand_path(file, d) + assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d))) + assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d))) + end + end + def orphan_proc proc {eval("return :ng")} end diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 3622cae0d9..c2eada0bfb 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,14 +252,35 @@ class TestException < Test::Unit::TestCase } end + def test_catch_throw_in_require_cant_be_rescued + bug18562 = '[ruby-core:107403]' + Tempfile.create(["dep", ".rb"]) {|t| + t.puts("throw :extdep, 42") + t.close + + rescue_all = Class.new(Exception) + def rescue_all.===(_) + raise "should not reach here" + end + + v = assert_throw(:extdep, bug18562) do + require t.path + rescue rescue_all + assert(false, "should not reach here") + end + + assert_equal(42, v, bug18562) + } + end + def test_throw_false bug12743 = '[ruby-core:77229] [Bug #12743]' - e = assert_raise_with_message(UncaughtThrowError, /false/, bug12743) { - Thread.start { + Thread.start { + e = assert_raise_with_message(UncaughtThrowError, /false/, bug12743) { throw false - }.join - } - assert_same(false, e.tag, bug12743) + } + assert_same(false, e.tag, bug12743) + }.join end def test_else_no_exception @@ -352,8 +444,10 @@ class TestException < Test::Unit::TestCase end def test_thread_signal_location + # pend('TODO: a known bug [Bug #14474]') _, stderr, _ = EnvUtil.invoke_ruby(%w"--disable-gems -d", <<-RUBY, false, true) Thread.start do + Thread.current.report_on_exception = false begin Process.kill(:INT, $$) ensure @@ -384,6 +478,12 @@ end.join def to_s; ""; end end assert_equal(e.inspect, e.new.inspect) + + # https://bugs.ruby-lang.org/issues/18170#note-13 + assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect) + assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect) + assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect) + assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').inspect) end def test_to_s @@ -436,16 +536,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 @@ -486,20 +576,44 @@ end.join end def test_exception_in_name_error_to_str + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") bug5575 = '[ruby-core:41612]' - Tempfile.create(["test_exception_in_name_error_to_str", ".rb"]) do |t| - t.puts <<-EOC + begin; begin BasicObject.new.inspect rescue - $!.inspect - end - EOC - t.close - assert_nothing_raised(NameError, bug5575) do - load(t.path) + assert_nothing_raised(NameError, bug5575) {$!.inspect} end + end; + end + + def test_ensure_after_nomemoryerror + omit "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 @@ -509,19 +623,28 @@ end.join end def test_exception_in_exception_equal + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") bug5865 = '[ruby-core:41979]' - Tempfile.create(["test_exception_in_exception_equal", ".rb"]) do |t| - t.puts <<-EOC + begin; o = Object.new def o.exception(arg) end - _ = RuntimeError.new("a") == o - EOC - t.close assert_nothing_raised(ArgumentError, bug5865) do - load(t.path) + RuntimeError.new("a") == o end + end; + end + + def test_backtrace_by_exception + begin + line = __LINE__; raise "foo" + rescue => e end + e2 = e.exception("bar") + assert_not_equal(e.message, e2.message) + assert_equal(e.backtrace, e2.backtrace) + loc = e2.backtrace_locations[0] + assert_equal([__FILE__, line], [loc.path, loc.lineno]) end Bug4438 = '[ruby-core:35364]' @@ -544,28 +667,6 @@ end.join end end - def test_to_s_taintness_propagation - for exc in [Exception, NameError] - m = "abcdefg" - e = exc.new(m) - e.taint - s = e.to_s - assert_equal(false, m.tainted?, - "#{exc}#to_s should not propagate taintness") - assert_equal(false, s.tainted?, - "#{exc}#to_s should not propagate taintness") - end - - o = Object.new - def o.to_str - "foo" - end - o.taint - e = NameError.new(o) - s = e.to_s - assert_equal(false, s.tainted?) - end - def m m(&->{return 0}) 42 @@ -697,11 +798,22 @@ end.join assert_same(a, e.cause.cause) end + def test_cause_at_end + errs = [ + /-: unexpected return\n/, + /.*undefined local variable or method `n'.*\n/, + ] + assert_in_out_err([], <<-'end;', [], errs) + END{n}; END{return} + end; + end + def test_raise_with_cause msg = "[Feature #8257]" cause = ArgumentError.new("foobar") e = assert_raise(RuntimeError) {raise msg, cause: cause} assert_same(cause, e.cause) + assert_raise(TypeError) {raise msg, {cause: cause}} end def test_cause_with_no_arguments @@ -759,7 +871,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 @@ -796,7 +908,7 @@ end.join e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar"} assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug) - e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar", cause: "zzz"} + e = assert_raise(exc, bug) {raise exc, "foo" => "bar", foo: "bar", cause: RuntimeError.new("zzz")} assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug) e = assert_raise(exc, bug) {raise exc, {}} @@ -830,101 +942,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_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 - 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 + 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(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 @@ -952,6 +991,44 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end; end + def assert_null_char(src, *args, **opts) + begin + eval(src) + rescue => e + end + assert_not_nil(e) + assert_include(e.message, "\0") + # Disabled by [Feature #18367] + #assert_in_out_err([], src, [], [], *args, **opts) do |_, err,| + # err.each do |e| + # assert_not_include(e, "\0") + # end + #end + e + end + + def test_control_in_message + bug7574 = '[ruby-dev:46749]' + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_defined?("String\0") + end; + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_get("String\0") + end; + end + + def test_encoding_in_message + name = "\u{e9}t\u{e9}" + e = EnvUtil.with_default_external("US-ASCII") do + assert_raise(NameError) do + Object.const_get(name) + end + end + assert_include(e.message, name) + end + def test_method_missing_reason_clear bug10969 = '[ruby-core:68515] [Bug #10969]' a = Class.new {def method_missing(*) super end}.new @@ -967,25 +1044,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, category: nil| + 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 @@ -995,14 +1084,49 @@ $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 + omit "no method to test" + + warning = capture_warning_warn { } + + assert_match(/deprecated/, warning[0]) + end + + def test_warn_deprecated_category + omit "no method to test" + + warning = capture_warning_warn(category: true) { } + + 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]) + def (obj = Object.new).w(n) warn("test warning", uplevel: n) end + warning = capture_warning_warn {obj.w(0)} + assert_equal("#{__FILE__}:#{__LINE__-2}: warning: test warning\n", warning[0]) + warning = capture_warning_warn {obj.w(1)} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + 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_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}, **{})} + assert_equal("test warning\n{:uplevel=>0}\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", foo: 1)} + end + def test_warning_warn_invalid_argument assert_raise(TypeError) do ::Warning.warn nil @@ -1024,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) @@ -1035,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) @@ -1044,10 +1170,65 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end $VERBOSE = true - @a + $asdfiasdofa_test_warning_warn_super }; end + def test_warning_category + 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 assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; @@ -1080,6 +1261,28 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| end; end + def test_blocking_backtrace + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Bug < RuntimeError + def backtrace + IO.readlines(IO::NULL) + end + end + bug = Bug.new '[ruby-core:85939] [Bug #14577]' + n = 10000 + i = 0 + n.times do + begin + raise bug + rescue Bug + i += 1 + end + end + assert_equal(n, i) + end; + end + def test_wrong_backtrace assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; @@ -1093,5 +1296,230 @@ $stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| raise RuntimeError, "hello" } end; + + error_class = Class.new(StandardError) do + def backtrace; :backtrace; end + end + begin + raise error_class + rescue error_class => e + assert_raise(TypeError) {$@} + assert_raise(TypeError) {e.full_message} + end + end + + def test_backtrace_in_eval + bug = '[ruby-core:84434] [Bug #14229]' + assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval\):1:/, bug) + end + + def test_full_message + message = RuntimeError.new("testerror").full_message + assert_operator(message, :end_with?, "\n") + + test_method = "def foo; raise 'testerror'; end" + + out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true) + assert_predicate(status1, :success?) + assert_empty(err1, "expected nothing wrote to $stdout by #full_message") + + _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) + assert_equal(err2, out1) + + e = RuntimeError.new("a\n") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_operator(message, :end_with?, "\n") + message = message.gsub(/\e\[[\d;]*m/, '') + assert_not_operator(message, :end_with?, "\n\n") + e = RuntimeError.new("a\n\nb\n\nc") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_all?(message.lines) do |m| + /\e\[\d[;\d]*m[^\e]*\n/ !~ m + end + + e = RuntimeError.new("testerror") + message = e.full_message(highlight: false) + assert_not_match(/\e/, message) + + bt = ["test:100", "test:99", "test:98", "test:1"] + e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt} + + bottom = "test:100: testerror (RuntimeError)\n" + top = "test:1\n" + remark = "Traceback (most recent call last):" + + message = e.full_message(highlight: false, order: :top) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + + message = e.full_message(highlight: false, order: :bottom) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, remark) + assert_operator(message, :end_with?, bottom) + + assert_raise_with_message(ArgumentError, /:top or :bottom/) { + e.full_message(highlight: false, order: :middle) + } + + message = e.full_message(highlight: true) + assert_match(/\e/, message) + assert_not_match(/(\e\[1)m\1/, message) + e2 = assert_raise(RuntimeError) {raise RuntimeError, "", bt} + assert_not_match(/(\e\[1)m\1/, e2.full_message(highlight: true)) + + message = e.full_message + if Exception.to_tty? + assert_match(/\e/, message) + message = message.gsub(/\e\[[\d;]*m/, '') + else + assert_not_match(/\e/, message) + end + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + end + + def test_exception_in_message + code = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class Bug14566 < StandardError + def message; raise self.class; end + end + raise Bug14566 + end; + assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 2) + end + + def test_non_exception_cause + assert_raise_with_message(TypeError, /exception/) do + raise "foo", cause: 1 + end; + end + + def test_circular_cause_handle + assert_raise_with_message(ArgumentError, /circular cause/) do + begin + raise "error 1" + rescue => e1 + raise "error 2" rescue raise e1, cause: $! + end + 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; + $VERBOSE = nil + class Object + def method_missing(name, *args, &block) + super + end + end + + bug14670 = '[ruby-dev:50522] [Bug #14670]' + assert_raise_with_message(NoMethodError, /`foo'/, bug14670) do + Object.new.foo + end + end; + end + + def test_detailed_message + e = RuntimeError.new("message") + assert_equal("message (RuntimeError)", e.detailed_message) + assert_equal("\e[1mmessage (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new("foo\nbar\nbaz") + assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message) + assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new("") + assert_equal("unhandled exception", e.detailed_message) + assert_equal("\e[1;4munhandled exception\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new + assert_equal("RuntimeError (RuntimeError)", e.detailed_message) + assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true)) + end + + def test_full_message_with_custom_detailed_message + e = RuntimeError.new("message") + opt_ = nil + e.define_singleton_method(:detailed_message) do |**opt| + opt_ = opt + "BOO!" + end + assert_match("BOO!", e.full_message.lines.first) + assert_equal({ highlight: Exception.to_tty? }, opt_) + end + + def test_syntax_error_detailed_message + Dir.mktmpdir do |dir| + File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class SyntaxError + def detailed_message(**) + Thread.new {}.join + "<#{super}>\n""<#{File.basename(__FILE__)}>" + rescue ThreadError => e + e.message + end + end + end; + pattern = /^<detail\.rb>/ + assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern) + + File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 1 + + end; + assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,| + assert_empty(stdout) + assert_not_empty(stderr.grep(pattern)) + error, = stderr.grep(/unexpected end-of-input/) + assert_not_nil(error) + assert_match(/<.*unexpected end-of-input.*>/, error) + end + end + end + + def test_syntax_error_path + e = assert_raise(SyntaxError) { + eval("1+", nil, "test_syntax_error_path.rb") + } + assert_equal("test_syntax_error_path.rb", e.path) + + Dir.mktmpdir do |dir| + lib = File.join(dir, "syntax_error-path.rb") + File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class SyntaxError + def detailed_message(**) + STDERR.puts "\n""path=#{path}\n" + super + end + end + end; + main = File.join(dir, "syntax_error.rb") + File.write(main, "1+\n") + assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*]) + end end end diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index 92c9961259..5825aaf6cc 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -34,7 +34,8 @@ class TestFiber < Test::Unit::TestCase end def test_many_fibers - max = 10_000 + omit 'This is unstable on GitHub Actions --jit-wait. TODO: debug it' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + max = 1000 assert_equal(max, max.times{ Fiber.new{} }) @@ -49,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| @@ -70,10 +71,12 @@ class TestFiber < Test::Unit::TestCase assert_raise(ArgumentError){ Fiber.new # Fiber without block } - assert_raise(FiberError){ - f = Fiber.new{} - Thread.new{f.resume}.join # Fiber yielding across thread - } + f = Fiber.new{} + Thread.new{ + assert_raise(FiberError){ # Fiber yielding across thread + f.resume + } + }.join assert_raise(FiberError){ f = Fiber.new{} f.resume @@ -107,6 +110,15 @@ class TestFiber < Test::Unit::TestCase } fib.resume } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.raise "raise in unborn fiber" + } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.resume + fib.raise "raise in dead fiber" + } end def test_return @@ -125,6 +137,48 @@ class TestFiber < Test::Unit::TestCase } end + def test_raise + assert_raise(ZeroDivisionError){ + Fiber.new do + 1/0 + end.resume + } + assert_raise(RuntimeError){ + fib = Fiber.new{ Fiber.yield } + fib.resume + fib.raise "raise and propagate" + } + assert_nothing_raised{ + fib = Fiber.new do + begin + Fiber.yield + rescue + end + end + fib.resume + fib.raise "rescue in fiber" + } + fib = Fiber.new do + begin + Fiber.yield + rescue + Fiber.yield :ok + end + end + fib.resume + 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 @@ -140,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) @@ -199,11 +280,11 @@ class TestFiber < Test::Unit::TestCase end def test_resume_root_fiber - assert_raise(FiberError) do - Thread.new do + Thread.new do + assert_raise(FiberError) do Fiber.current.resume - end.join - end + end + end.join end def test_gc_root_fiber @@ -234,44 +315,103 @@ 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_fork_from_fiber - begin - pid = Process.fork{} - rescue NotImplementedError - return - else - Process.wait(pid) + 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 + omit 'fork not supported' unless Process.respond_to?(:fork) + pid = nil bug5700 = '[ruby-core:41456]' assert_nothing_raised(bug5700) do - Fiber.new{ pid = fork {} }.resume + Fiber.new do + pid = fork do + xpid = nil + Fiber.new { + xpid = fork do + # enough to trigger GC on old root fiber + count = 1000 + count.times do + Fiber.new {}.transfer + Fiber.new { Fiber.yield } + end + exit!(true) + end + }.transfer + _, status = Process.waitpid2(xpid) + exit!(status.success?) + end + 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 @@ -284,54 +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| - EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30) - } - 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 * 10, 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 * 10, 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" @@ -376,5 +474,23 @@ class TestFiber < Test::Unit::TestCase assert_match(/terminated/, f.to_s) assert_match(/resumed/, Fiber.current.to_s) end -end + def test_create_fiber_in_new_thread + ret = Thread.new{ + Thread.new{ + Fiber.new{Fiber.yield :ok}.resume + }.value + }.value + assert_equal :ok, ret, '[Bug #14642]' + end + + def test_machine_stack_gc + assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 10 + enum = Enumerator.new { |y| y << 1 } + thread = Thread.new { enum.peek } + thread.join + sleep 5 # pause until thread cache wait time runs out. Native thread exits. + GC.start + RUBY + end +end diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb index 9d29bbbb65..409d21fc4e 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -87,7 +87,7 @@ class TestFile < Test::Unit::TestCase end def test_bom_32le - assert_bom(["\xFF\xFE\0", "\0"], __method__) + assert_bom(["\xFF", "\xFE\0\0"], __method__) end def test_truncate_wbuf @@ -147,8 +147,8 @@ class TestFile < Test::Unit::TestCase end def test_read_all_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -158,8 +158,8 @@ class TestFile < Test::Unit::TestCase end def test_gets_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -169,8 +169,8 @@ class TestFile < Test::Unit::TestCase end def test_gets_para_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "\na" f.rewind @@ -180,8 +180,8 @@ class TestFile < Test::Unit::TestCase end def test_each_char_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -193,8 +193,8 @@ class TestFile < Test::Unit::TestCase end def test_each_byte_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -206,8 +206,8 @@ class TestFile < Test::Unit::TestCase end def test_getc_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -217,8 +217,8 @@ class TestFile < Test::Unit::TestCase end def test_getbyte_extended_file - [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - Tempfile.create("test-extended-file", mode) {|f| + [{}, {:textmode=>true}, {:binmode=>true}].each do |mode| + Tempfile.create("test-extended-file", **mode) {|f| assert_nil(f.getc) f.print "a" f.rewind @@ -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 @@ -252,6 +250,10 @@ class TestFile < Test::Unit::TestCase tst = realdir + (File::SEPARATOR*3 + ".") assert_equal(realdir, File.realpath(tst)) assert_equal(realdir, File.realpath(".", tst)) + assert_equal(realdir, Dir.chdir(realdir) {File.realpath(".")}) + realpath = File.join(realdir, "test") + File.write(realpath, "") + assert_equal(realpath, Dir.chdir(realdir) {File.realpath("test")}) if File::ALT_SEPARATOR bug2961 = '[ruby-core:28653]' assert_equal(realdir, File.realpath(realdir.tr(File::SEPARATOR, File::ALT_SEPARATOR)), bug2961) @@ -271,7 +273,7 @@ class TestFile < Test::Unit::TestCase begin File.symlink(tst, a) rescue Errno::EACCES, Errno::EPERM - skip "need privilege" + omit "need privilege" end assert_equal(File.join(realdir, tst), File.realpath(a)) File.unlink(a) @@ -283,24 +285,12 @@ class TestFile < Test::Unit::TestCase } end - def test_realpath_taintedness - Dir.mktmpdir('rubytest-realpath') {|tmpdir| - dir = File.realpath(tmpdir).untaint - File.write(File.join(dir, base = "test.file"), '') - base.taint - dir.taint - assert_predicate(File.realpath(base, dir), :tainted?) - base.untaint - dir.taint - assert_predicate(File.realpath(base, dir), :tainted?) - base.taint - dir.untaint - assert_predicate(File.realpath(base, dir), :tainted?) - base.untaint - dir.untaint - assert_not_predicate(File.realpath(base, dir), :tainted?) - assert_predicate(Dir.chdir(dir) {File.realpath(base)}, :tainted?) - } + def test_realpath_special_symlink + IO.pipe do |r, w| + if File.pipe?(path = "/dev/fd/#{r.fileno}") + assert_file.identical?(File.realpath(path), path) + end + end end def test_realdirpath @@ -310,6 +300,8 @@ class TestFile < Test::Unit::TestCase assert_equal(realdir, File.realdirpath(tst)) assert_equal(realdir, File.realdirpath(".", tst)) assert_equal(File.join(realdir, "foo"), File.realdirpath("foo", tst)) + assert_equal(realdir, Dir.chdir(realdir) {File.realdirpath(".")}) + assert_equal(File.join(realdir, "foo"), Dir.chdir(realdir) {File.realdirpath("foo")}) } begin result = File.realdirpath("bar", "//:/foo") @@ -325,7 +317,7 @@ class TestFile < Test::Unit::TestCase Dir.mktmpdir('rubytest-realpath') {|tmpdir| Dir.chdir(tmpdir) do Dir.mkdir('foo') - skip "cannot run mklink" unless system('mklink /j bar foo > nul') + omit "cannot run mklink" unless system('mklink /j bar foo > nul') assert_equal(File.realpath('foo'), File.realpath('bar')) end } @@ -457,17 +449,7 @@ class TestFile < Test::Unit::TestCase end end - def test_untainted_path - bug5374 = '[ruby-core:39745]' - cwd = ("./"*40+".".taint).dup.untaint - in_safe = proc {|safe| $SAFE = safe; File.stat(cwd)} - assert_not_send([cwd, :tainted?]) - (0..1).each do |level| - assert_nothing_raised(SecurityError, bug5374) {in_safe[level]} - end - end - - if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM + if /mswin|mingw/ =~ RUBY_PLATFORM def test_long_unc feature3399 = '[ruby-core:30623]' path = File.expand_path(__FILE__) @@ -478,6 +460,48 @@ class TestFile < Test::Unit::TestCase end end + def test_file_open_newline_option + Dir.mktmpdir(__method__.to_s) do |tmpdir| + path = File.join(tmpdir, "foo") + test = lambda do |newline| + File.open(path, "wt", newline: newline) do |f| + f.write "a\n" + f.puts "b" + end + File.binread(path) + end + assert_equal("a\nb\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\r\nb\r\n", test.(:crlf)) + assert_equal("a\rb\r", test.(:cr)) + + test = lambda do |newline| + File.open(path, "rt", newline: newline) do |f| + f.read + end + end + + File.binwrite(path, "a\nb\n") + assert_equal("a\nb\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\nb\n", test.(:crlf)) + assert_equal("a\nb\n", test.(:cr)) + + File.binwrite(path, "a\r\nb\r\n") + assert_equal("a\r\nb\r\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + # Work on both Windows and non-Windows + assert_include(["a\r\nb\r\n", "a\nb\n"], test.(:crlf)) + assert_equal("a\r\nb\r\n", test.(:cr)) + + File.binwrite(path, "a\rb\r") + assert_equal("a\rb\r", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\rb\r", test.(:crlf)) + assert_equal("a\rb\r", test.(:cr)) + end + end + def test_open_nul Dir.mktmpdir(__method__.to_s) do |tmpdir| path = File.join(tmpdir, "foo") @@ -493,16 +517,41 @@ class TestFile < Test::Unit::TestCase begin io = File.open(tmpdir, File::RDWR | File::TMPFILE) rescue Errno::EINVAL - skip 'O_TMPFILE not supported (EINVAL)' + omit 'O_TMPFILE not supported (EINVAL)' + rescue Errno::EISDIR + omit 'O_TMPFILE not supported (EISDIR)' + rescue Errno::EOPNOTSUPP + omit 'O_TMPFILE not supported (EOPNOTSUPP)' end io.write "foo" io.flush assert_equal 3, io.size - assert_raise(IOError) { io.path } + assert_nil io.path ensure io&.close end end if File::Constants.const_defined?(:TMPFILE) + def test_absolute_path? + assert_file.absolute_path?(File.absolute_path(__FILE__)) + assert_file.absolute_path?("//foo/bar\\baz") + assert_file.not_absolute_path?(File.basename(__FILE__)) + assert_file.not_absolute_path?("C:foo\\bar") + assert_file.not_absolute_path?("~") + assert_file.not_absolute_path?("~user") + + if /cygwin|mswin|mingw/ =~ RUBY_PLATFORM + assert_file.absolute_path?("C:\\foo\\bar") + assert_file.absolute_path?("C:/foo/bar") + else + assert_file.not_absolute_path?("C:\\foo\\bar") + assert_file.not_absolute_path?("C:/foo/bar") + end + if /mswin|mingw/ =~ RUBY_PLATFORM + assert_file.not_absolute_path?("/foo/bar\\baz") + else + assert_file.absolute_path?("/foo/bar\\baz") + end + end end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 4c5540eb4c..d0472a0081 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -18,7 +18,7 @@ class TestFileExhaustive < Test::Unit::TestCase end def setup - @dir = Dir.mktmpdir("rubytest-file") + @dir = Dir.mktmpdir("ruby-test") File.chown(-1, Process.gid, @dir) end @@ -70,7 +70,7 @@ class TestFileExhaustive < Test::Unit::TestCase def notownedfile return @notownedfile if defined? @notownedfile - if Process.euid != 0 + if Process.euid != File.stat("/").uid @notownedfile = '/' else @notownedfile = nil @@ -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,24 +504,44 @@ 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) + # avoid File.open since we do not want #to_path + io = IO.for_fd(IO.sysopen(file_name)) + yield io + ensure + io&.close + end + def test_suid assert_file.not_setuid?(regular_file) assert_file.not_setuid?(utf8_file) - assert_file.setuid?(suidfile) if suidfile + if suidfile + assert_file.setuid?(suidfile) + io_open(suidfile) { |io| assert_file.setuid?(io) } + end end def test_sgid assert_file.not_setgid?(regular_file) assert_file.not_setgid?(utf8_file) - assert_file.setgid?(sgidfile) if sgidfile + if sgidfile + assert_file.setgid?(sgidfile) + io_open(sgidfile) { |io| assert_file.setgid?(io) } + end end def test_sticky assert_file.not_sticky?(regular_file) assert_file.not_sticky?(utf8_file) - assert_file.sticky?(stickyfile) if stickyfile + if stickyfile + assert_file.sticky?(stickyfile) + io_open(stickyfile) { |io| assert_file.sticky?(io) } + end end def test_path_identical_p @@ -606,6 +637,23 @@ class TestFileExhaustive < Test::Unit::TestCase assert_raise(Errno::ENOENT) { File.ctime(nofile) } end + def test_birthtime + omit if RUBY_PLATFORM =~ /android/ + [regular_file, utf8_file].each do |file| + t1 = File.birthtime(file) + t2 = File.open(file) {|f| f.birthtime} + assert_kind_of(Time, t1) + assert_kind_of(Time, t2) + assert_equal(t1, t2) + rescue Errno::ENOSYS, NotImplementedError + # ignore unsupporting filesystems + rescue Errno::EPERM + # Docker prohibits statx syscall by the default. + omit("statx(2) is prohibited by seccomp") + end + assert_raise(Errno::ENOENT) { File.birthtime(nofile) } + end if File.respond_to?(:birthtime) + def test_chmod [regular_file, utf8_file].each do |file| assert_equal(1, File.chmod(0444, file)) @@ -645,6 +693,40 @@ 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 + return unless symlinkfile + t = Time.local(2000) + assert_equal(1, File.utime(t, t, symlinkfile)) + assert_equal(t, File.stat(regular_file).atime) + assert_equal(t, File.stat(regular_file).mtime) + end + + def test_lutime + return unless File.respond_to?(:lutime) + return unless symlinkfile + + r = File.stat(regular_file) + t = Time.local(2000) + File.lutime(t + 1, t + 2, symlinkfile) + rescue NotImplementedError => e + skip(e.message) + else + stat = File.stat(regular_file) + assert_equal(r.atime, stat.atime) + assert_equal(r.mtime, stat.mtime) + + stat = File.lstat(symlinkfile) + assert_equal(t + 1, stat.atime) + assert_equal(t + 2, stat.mtime) end def test_hardlink @@ -689,7 +771,7 @@ class TestFileExhaustive < Test::Unit::TestCase def test_readlink_junction base = File.basename(nofile) err = IO.popen(%W"cmd.exe /c mklink /j #{base} .", chdir: @dir, err: %i[child out], &:read) - skip err unless $?.success? + omit err unless $?.success? assert_equal(@dir, File.readlink(nofile)) end @@ -736,7 +818,10 @@ class TestFileExhaustive < Test::Unit::TestCase def test_expand_path assert_equal(regular_file, File.expand_path(File.basename(regular_file), File.dirname(regular_file))) assert_equal(utf8_file, File.expand_path(File.basename(utf8_file), File.dirname(utf8_file))) - if NTFS + end + + if NTFS + def test_expand_path_ntfs [regular_file, utf8_file].each do |file| assert_equal(file, File.expand_path(file + " ")) assert_equal(file, File.expand_path(file + ".")) @@ -747,8 +832,11 @@ class TestFileExhaustive < Test::Unit::TestCase assert_match(/\Ae:\//i, File.expand_path('e:foo', 'd:/bar')) assert_match(%r'\Ac:/bar/foo\z'i, File.expand_path('c:foo', 'c:/bar')) end - case RUBY_PLATFORM - when /darwin/ + end + + case RUBY_PLATFORM + when /darwin/ + def test_expand_path_hfs ["\u{feff}", *"\u{2000}"..."\u{2100}"].each do |c| file = regular_file + c full_path = File.expand_path(file) @@ -764,11 +852,16 @@ class TestFileExhaustive < Test::Unit::TestCase end end end - if DRIVE + end + + if DRIVE + def test_expand_path_absolute assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar")) assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar")) assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo')) - else + end + else + def test_expand_path_absolute assert_equal("/foo", File.expand_path('/foo')) end end @@ -777,9 +870,10 @@ class TestFileExhaustive < Test::Unit::TestCase bug9934 = '[ruby-core:63114] [Bug #9934]' require "objspace" path = File.expand_path("/foo") - assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934) + assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934) path = File.expand_path("/a"*25) - assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934) + assert_operator(ObjectSpace.memsize_of(path), :<=, + (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934) end def test_expand_path_encoding @@ -808,6 +902,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 @@ -863,6 +959,8 @@ class TestFileExhaustive < Test::Unit::TestCase assert_raise(ArgumentError) { File.expand_path(".", UnknownUserHome) } assert_nothing_raised(ArgumentError) { File.expand_path("#{DRIVE}/", UnknownUserHome) } + ENV["HOME"] = "#{DRIVE}UserHome" + assert_raise(ArgumentError) { File.expand_path("~") } ensure ENV["HOME"] = home end @@ -985,32 +1083,6 @@ class TestFileExhaustive < Test::Unit::TestCase assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo')) end - def test_expand_path_returns_tainted_strings_or_not - assert_equal(true, File.expand_path('foo').tainted?) - assert_equal(true, File.expand_path('foo'.taint).tainted?) - assert_equal(true, File.expand_path('/foo'.taint).tainted?) - assert_equal(true, File.expand_path('foo', 'bar').tainted?) - assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?) - assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?) - assert_equal(true, File.expand_path('~').tainted?) if ENV["HOME"] - - if DRIVE - assert_equal(true, File.expand_path('/foo').tainted?) - assert_equal(false, File.expand_path('//foo').tainted?) - assert_equal(true, File.expand_path('C:/foo'.taint).tainted?) - assert_equal(false, File.expand_path('C:/foo').tainted?) - assert_equal(true, File.expand_path('foo', '/bar').tainted?) - assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?) - assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?) - assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?) - assert_equal(false, File.expand_path('C:/foo/../bar').tainted?) - assert_equal(false, File.expand_path('foo', '//bar').tainted?) - else - assert_equal(false, File.expand_path('/foo').tainted?) - assert_equal(false, File.expand_path('foo', '/bar').tainted?) - end - end - def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base old_home = ENV["HOME"] home = ENV["HOME"] = "#{DRIVE}/UserHome" @@ -1046,7 +1118,7 @@ class TestFileExhaustive < Test::Unit::TestCase def test_expand_path_for_existent_username user = ENV['USER'] - skip "ENV['USER'] is not set" unless user + omit "ENV['USER'] is not set" unless user assert_equal(ENV['HOME'], File.expand_path("~#{user}")) end unless DRIVE @@ -1136,7 +1208,10 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal("foo", File.basename("foo", ".ext")) assert_equal("foo", File.basename("foo.ext", ".ext")) assert_equal("foo", File.basename("foo.ext", ".*")) - if NTFS + end + + if NTFS + def test_basename_strip [regular_file, utf8_file].each do |file| basename = File.basename(file) assert_equal(basename, File.basename(file + " ")) @@ -1150,7 +1225,9 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(basename, File.basename(file + ".", ".*")) assert_equal(basename, File.basename(file + "::$DATA", ".*")) end - else + end + else + def test_basename_strip [regular_file, utf8_file].each do |file| basename = File.basename(file) assert_equal(basename + " ", File.basename(file + " ")) @@ -1165,13 +1242,18 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(basename, File.basename(file + "::$DATA", ".*")) end end - if File::ALT_SEPARATOR == '\\' + end + + if File::ALT_SEPARATOR == '\\' + def test_basename_backslash a = "foo/\225\\\\" [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected| assert_equal(expected.force_encoding(cp), File.basename(a.dup.force_encoding(cp)), cp) end end + end + def test_basename_encoding assert_incompatible_encoding {|d| File.basename(d)} assert_incompatible_encoding {|d| File.basename(d, ".*")} assert_raise(Encoding::CompatibilityError) {File.basename("foo.ext", ".*".encode("utf-16le"))} @@ -1187,8 +1269,20 @@ 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 assert_incompatible_encoding {|d| File.dirname(d)} - if File::ALT_SEPARATOR == '\\' + end + + if File::ALT_SEPARATOR == '\\' + def test_dirname_backslash a = "\225\\\\foo" [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected| assert_equal(expected.force_encoding(cp), File.dirname(a.dup.force_encoding(cp)), cp) @@ -1200,21 +1294,23 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(".test", File.extname(regular_file)) assert_equal(".test", File.extname(utf8_file)) prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."] - infixes = ["", " ", "."] + infixes = ["", " "] infixes2 = infixes + [".ext "] appendixes = [""] if NTFS appendixes << " " << "." << "::$DATA" << "::$DATA.bar" + else + appendixes << [".", "."] end prefixes.each do |prefix| - appendixes.each do |appendix| + appendixes.each do |appendix, ext = ""| infixes.each do |infix| path = "#{prefix}foo#{infix}#{appendix}" - assert_equal("", File.extname(path), "File.extname(#{path.inspect})") + assert_equal(ext, File.extname(path), "File.extname(#{path.inspect})") end infixes2.each do |infix| path = "#{prefix}foo#{infix}.ext#{appendix}" - assert_equal(".ext", File.extname(path), "File.extname(#{path.inspect})") + assert_equal(ext.empty? ? ".ext" : appendix, File.extname(path), "File.extname(#{path.inspect})") end end end @@ -1311,21 +1407,26 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_flock_exclusive + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + timeout = EnvUtil.apply_timeout_scale(0.1).to_s File.open(regular_file, "r+") do |f| f.flock(File::LOCK_EX) - assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") - begin + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f open(ARGV[0], "r") do |f| - Timeout.timeout(0.1) do + Timeout.timeout(timeout) do assert(!f.flock(File::LOCK_SH|File::LOCK_NB)) end end end; - assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") - begin + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f open(ARGV[0], "r") do |f| assert_raise(Timeout::Error) do - Timeout.timeout(0.1) do + Timeout.timeout(timeout) do f.flock(File::LOCK_SH) end end @@ -1337,21 +1438,26 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_flock_shared + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + timeout = EnvUtil.apply_timeout_scale(0.1).to_s File.open(regular_file, "r+") do |f| f.flock(File::LOCK_SH) - assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") - begin + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f open(ARGV[0], "r") do |f| - Timeout.timeout(0.1) do + Timeout.timeout(timeout) do assert(f.flock(File::LOCK_SH)) end end end; - assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") - begin + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f open(ARGV[0], "r+") do |f| assert_raise(Timeout::Error) do - Timeout.timeout(0.1) do + Timeout.timeout(timeout) do f.flock(File::LOCK_EX) end end @@ -1373,6 +1479,7 @@ class TestFileExhaustive < Test::Unit::TestCase fn1, zerofile, notownedfile, + grpownedfile, suidfile, sgidfile, stickyfile, @@ -1383,31 +1490,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_bool_equal(stat.blockdev?, File.blockdev?(f), f) + assert_bool_equal(stat.chardev?, File.chardev?(f), f) + assert_bool_equal(stat.directory?, File.directory?(f), f) + assert_bool_equal(stat.file?, File.file?(f), f) + assert_bool_equal(stat.setgid?, File.setgid?(f), f) + assert_bool_equal(stat.grpowned?, File.grpowned?(f), f) + assert_bool_equal(stat.sticky?, File.sticky?(f), f) + assert_bool_equal(File.lstat(f).symlink?, File.symlink?(f), f) + assert_bool_equal(stat.owned?, File.owned?(f), f) + assert_bool_equal(stat.pipe?, File.pipe?(f), f) + assert_bool_equal(stat.readable?, File.readable?(f), f) + assert_bool_equal(stat.readable_real?, File.readable_real?(f), f) + assert_equal(stat.size?, File.size?(f), f) + assert_bool_equal(stat.socket?, File.socket?(f), f) + assert_bool_equal(stat.setuid?, File.setuid?(f), f) + assert_bool_equal(stat.writable?, File.writable?(f), f) + assert_bool_equal(stat.writable_real?, File.writable_real?(f), f) + assert_bool_equal(stat.executable?, File.executable?(f), f) + assert_bool_equal(stat.executable_real?, File.executable_real?(f), f) + assert_bool_equal(stat.zero?, File.zero?(f), f) end assert_equal(false, test(?-, @dir, fn1)) assert_equal(true, test(?-, fn1, fn1)) @@ -1447,11 +1579,7 @@ class TestFileExhaustive < Test::Unit::TestCase assert_integer_or_nil(fs1.rdev_minor) assert_integer(fs1.ino) assert_integer(fs1.mode) - unless /emx|mswin|mingw/ =~ RUBY_PLATFORM - # on Windows, nlink is always 1. but this behavior will be changed - # in the future. - assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) - end + assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) assert_integer(fs1.uid) assert_integer(fs1.gid) assert_equal(3, fs1.size) @@ -1521,7 +1649,7 @@ class TestFileExhaustive < Test::Unit::TestCase def test_stat_chardev_p assert_not_predicate(File::Stat.new(@dir), :chardev?) assert_not_predicate(File::Stat.new(regular_file), :chardev?) - assert_predicate(File::Stat.new(chardev), :chardev?) if chardev + assert_predicate(File::Stat.new(chardev), :chardev?) end def test_stat_readable_p @@ -1612,6 +1740,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 @@ -1661,4 +1792,8 @@ class TestFileExhaustive < Test::Unit::TestCase dir = File.expand_path("/bar") assert_equal(File.join(dir, "~foo"), File.absolute_path("~foo", dir)) end + + def assert_bool_equal(expected, result, *messages) + assert_equal(expected, true & result, *messages) + end end diff --git a/test/ruby/test_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_flip.rb b/test/ruby/test_flip.rb index b8b05aed6d..9c58f5f497 100644 --- a/test/ruby/test_flip.rb +++ b/test/ruby/test_flip.rb @@ -19,7 +19,7 @@ class TestFlip < Test::Unit::TestCase assert_nothing_raised(NotImplementedError, bug6899) do 2000.times {eval %[(foo..bar) ? 1 : 2]} end - foo = bar + [foo, bar] end def test_shared_eval @@ -28,6 +28,7 @@ class TestFlip < Test::Unit::TestCase vs.select {|n| if n==2..n==16 then 1 end} v = eval("vs.select {|n| if n==3..n==6 then 1 end}") assert_equal([*3..6], v, bug7671) + vs end def test_shared_thread diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index bddd10bf10..cee69b6018 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -141,6 +141,9 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError){Float("1__1")} assert_raise(ArgumentError){Float("1.")} assert_raise(ArgumentError){Float("1.e+00")} + assert_raise(ArgumentError){Float("0x.1")} + assert_raise(ArgumentError){Float("0x1.")} + assert_raise(ArgumentError){Float("0x1.0")} assert_raise(ArgumentError){Float("0x1.p+0")} # add expected behaviour here. assert_equal(10, Float("1_0")) @@ -163,6 +166,32 @@ class TestFloat < Test::Unit::TestCase assert_equal(-31.0*2**-1027, Float("-0x1f"+("0"*268)+".0p-2099")) assert_equal(-31.0*2**-1027, Float("-0x1f"+("0"*600)+".0p-3427")) end + + assert_equal(1.0e10, Float("1.0_"+"00000"*Float::DIG+"e10")) + + z = "0" * (Float::DIG * 4 + 10) + all_assertions_foreach("long invalid string", "1.0", "1.0e", "1.0e-", "1.0e+") do |n| + 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 @@ -297,6 +326,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 @@ -483,6 +513,10 @@ class TestFloat < Test::Unit::TestCase assert_raise(TypeError) {1.0.floor(nil)} def (prec = Object.new).to_int; 2; end assert_equal(0.99, 0.998.floor(prec)) + + assert_equal(-10000000000, -1.0.floor(-10), "[Bug #20654]") + assert_equal(-100000000000000000000, -1.0.floor(-20), "[Bug #20654]") + assert_equal(-100000000000000000000000000000000000000000000000000, -1.0.floor(-50), "[Bug #20654]") end def test_ceil_with_precision @@ -510,6 +544,10 @@ class TestFloat < Test::Unit::TestCase assert_raise(TypeError) {1.0.ceil(nil)} def (prec = Object.new).to_int; 2; end assert_equal(0.99, 0.981.ceil(prec)) + + assert_equal(10000000000, 1.0.ceil(-10), "[Bug #20654]") + assert_equal(100000000000000000000, 1.0.ceil(-20), "[Bug #20654]") + assert_equal(100000000000000000000000000000000000000000000000000, 1.0.ceil(-50), "[Bug #20654]") end def test_truncate_with_precision @@ -756,6 +794,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 @@ -780,12 +821,24 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError) { Float('0xf.p0') } assert_raise(ArgumentError) { Float('0xf.f') } assert_raise(ArgumentError) { Float('0xf.fp') } - assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) + ensure + $VERBOSE = verbose_bak + end assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?) assert_raise(TypeError) { Float(nil) } + assert_raise(TypeError) { Float(:test) } o = Object.new def o.to_f; inf = Float::INFINITY; inf/inf; end assert_predicate(Float(o), :nan?) + + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16be"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16le"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32be"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32le"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("iso-2022-jp"))} end def test_invalid_str @@ -793,6 +846,49 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}} end + def test_Float_with_invalid_exception + assert_raise(ArgumentError) { + Float("0", exception: 1) + } + end + + def test_Float_with_exception_keyword + assert_raise(ArgumentError) { + Float(".", exception: true) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Float(".", exception: false)) + } + assert_raise(RangeError) { + Float(1i, exception: true) + } + assert_nothing_raised(RangeError) { + assert_equal(nil, Float(1i, exception: false)) + } + assert_raise(TypeError) { + Float(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(:test, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + o = Object.new + def o.to_f; 3.14; end + assert_equal(3.14, Float(o, exception: false)) + } + assert_nothing_raised(RuntimeError) { + o = Object.new + def o.to_f; raise; end + assert_equal(nil, Float(o, exception: false)) + } + end + def test_num2dbl assert_raise(ArgumentError, "comparison of String with 0 failed") do 1.0.step(2.0, "0.5") {} @@ -821,6 +917,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 @@ -832,7 +933,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) @@ -841,6 +944,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_fnmatch.rb b/test/ruby/test_fnmatch.rb index ca01a28698..16f1076e48 100644 --- a/test/ruby/test_fnmatch.rb +++ b/test/ruby/test_fnmatch.rb @@ -10,6 +10,7 @@ class TestFnmatch < Test::Unit::TestCase assert_equal(t.include?(i.chr), !File.fnmatch("[!#{s}]", i.chr, File::FNM_DOTMATCH)) end end + def test_fnmatch assert_file.for("[ruby-dev:22819]").fnmatch('\[1\]' , '[1]') assert_file.for("[ruby-dev:22815]").fnmatch('*?', 'a') @@ -17,10 +18,16 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch('\[1\]' , '[1]', File::FNM_PATHNAME) assert_file.fnmatch('*?', 'a', File::FNM_PATHNAME) assert_file.fnmatch('*/', 'a/', File::FNM_PATHNAME) + end + + def test_text # text assert_file.fnmatch('cat', 'cat') assert_file.not_fnmatch('cat', 'category') assert_file.not_fnmatch('cat', 'wildcat') + end + + def test_any_one # '?' matches any one character assert_file.fnmatch('?at', 'cat') assert_file.fnmatch('c?t', 'cat') @@ -29,6 +36,9 @@ class TestFnmatch < Test::Unit::TestCase assert_file.not_fnmatch('c??t', 'cat') assert_file.not_fnmatch('??at', 'cat') assert_file.not_fnmatch('ca??', 'cat') + end + + def test_any_chars # '*' matches any number (including 0) of any characters assert_file.fnmatch('c*', 'cats') assert_file.fnmatch('c*ts', 'cats') @@ -40,9 +50,15 @@ class TestFnmatch < Test::Unit::TestCase assert_file.not_fnmatch('a*abc', 'abc') assert_file.fnmatch('a*bc', 'abc') assert_file.not_fnmatch('a*bc', 'abcd') + end + + def test_char_class # [seq] : matches any character listed between bracket # [!seq] or [^seq] : matches any character except those listed between bracket bracket_test("bd-gikl-mosv-x", "bdefgiklmosvwx") + end + + def test_escape # escaping character assert_file.fnmatch('\?', '?') assert_file.not_fnmatch('\?', '\?') @@ -59,6 +75,9 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch('[a\-c]', 'c') assert_file.not_fnmatch('[a\-c]', 'b') assert_file.not_fnmatch('[a\-c]', '\\') + end + + def test_fnm_escape # escaping character loses its meaning if FNM_NOESCAPE is set assert_file.not_fnmatch('\?', '?', File::FNM_NOESCAPE) assert_file.fnmatch('\?', '\?', File::FNM_NOESCAPE) @@ -75,6 +94,9 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch('[a\-c]', 'c', File::FNM_NOESCAPE) assert_file.fnmatch('[a\-c]', 'b', File::FNM_NOESCAPE) # '\\' < 'b' < 'c' assert_file.fnmatch('[a\-c]', '\\', File::FNM_NOESCAPE) + end + + def test_fnm_casefold # case is ignored if FNM_CASEFOLD is set assert_file.not_fnmatch('cat', 'CAT') assert_file.fnmatch('cat', 'CAT', File::FNM_CASEFOLD) @@ -82,11 +104,17 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch('[a-z]', 'D', File::FNM_CASEFOLD) assert_file.not_fnmatch('[abc]', 'B') assert_file.fnmatch('[abc]', 'B', File::FNM_CASEFOLD) + end + + def test_fnm_pathname # wildcard doesn't match '/' if FNM_PATHNAME is set assert_file.fnmatch('foo?boo', 'foo/boo') assert_file.fnmatch('foo*', 'foo/boo') assert_file.not_fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME) assert_file.not_fnmatch('foo*', 'foo/boo', File::FNM_PATHNAME) + end + + def test_fnm_dotmatch # wildcard matches leading period if FNM_DOTMATCH is set assert_file.not_fnmatch('*', '.profile') assert_file.fnmatch('*', '.profile', File::FNM_DOTMATCH) @@ -95,6 +123,9 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch('*/*', 'dave/.profile') assert_file.not_fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME) assert_file.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) + end + + def test_recursive # recursive matching assert_file.fnmatch('**/foo', 'a/b/c/foo', File::FNM_PATHNAME) assert_file.fnmatch('**/foo', '/foo', File::FNM_PATHNAME) @@ -129,4 +160,10 @@ class TestFnmatch < Test::Unit::TestCase assert_file.fnmatch("[a-\u3042]*", "\u3042") assert_file.not_fnmatch("[a-\u3042]*", "\u3043") end + + def test_nullchar + assert_raise(ArgumentError) { + File.fnmatch("a\0z", "a") + } + end end diff --git a/test/ruby/test_frozen.rb b/test/ruby/test_frozen.rb new file mode 100644 index 0000000000..2918a2afd8 --- /dev/null +++ b/test/ruby/test_frozen.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFrozen < Test::Unit::TestCase + def test_setting_ivar_on_frozen_obj + obj = Object.new + obj.freeze + assert_raise(FrozenError) { obj.instance_variable_set(:@a, 1) } + end + + def test_setting_ivar_on_frozen_obj_with_ivars + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + assert_raise(FrozenError) { obj.instance_variable_set(:@b, 1) } + end + + def test_setting_ivar_on_frozen_string + str = "str" + str.freeze + assert_raise(FrozenError) { str.instance_variable_set(:@a, 1) } + end + + def test_setting_ivar_on_frozen_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) } + end +end diff --git a/test/ruby/test_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 7a6309b6a3..bb136656ce 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -17,6 +17,7 @@ class TestGc < Test::Unit::TestCase 1.upto(10000) { tmp = [0,1,2,3,4,5,6,7,8,9] } + tmp end l=nil 100000.times { @@ -53,7 +54,9 @@ class TestGc < Test::Unit::TestCase def test_start_full_mark return unless use_rgengc? + omit '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) @@ -62,6 +65,8 @@ class TestGc < Test::Unit::TestCase end def test_start_immediate_sweep + omit 'stress' if GC.stress + GC.start(immediate_sweep: false) assert_equal false, GC.latest_gc_info(:immediate_sweep) @@ -87,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 @@ -105,12 +117,16 @@ class TestGc < Test::Unit::TestCase end def test_stat_single + omit 'stress' if GC.stress + stat = GC.stat assert_equal stat[:count], GC.stat(:count) assert_raise(ArgumentError){ GC.stat(:invalid) } end def test_stat_constraints + omit 'stress' if GC.stress + stat = GC.stat assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages] assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect @@ -123,7 +139,80 @@ class TestGc < Test::Unit::TestCase end end + def test_stat_heap + omit 'stress' if GC.stress + + stat_heap = {} + stat = {} + # Initialize to prevent GC in future calls + GC.stat_heap(0, stat_heap) + GC.stat(stat) + + GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i| + GC.stat_heap(i, stat_heap) + GC.stat(stat) + + assert_equal GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] * (2**i), stat_heap[:slot_size] + assert_operator stat_heap[:heap_allocatable_pages], :<=, stat[:heap_allocatable_pages] + assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] + assert_operator stat_heap[:heap_eden_slots], :>=, 0 + assert_operator stat_heap[:heap_tomb_pages], :<=, stat[:heap_tomb_pages] + assert_operator stat_heap[:heap_tomb_slots], :>=, 0 + assert_operator stat_heap[:total_allocated_pages], :>=, 0 + assert_operator stat_heap[:total_freed_pages], :>=, 0 + assert_operator stat_heap[:force_major_gc_count], :>=, 0 + end + + GC.stat_heap(0, stat_heap) + assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size) + assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size] + + assert_raise(ArgumentError) { GC.stat_heap(-1) } + assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT]) } + end + + def test_stat_heap_all + stat_heap_all = {} + stat_heap = {} + + 2.times do + GC.stat_heap(0, stat_heap) + GC.stat_heap(nil, stat_heap_all) + end + + GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i| + GC.stat_heap(i, stat_heap) + + assert_equal stat_heap, stat_heap_all[i] + end + + assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) } + end + + def test_stat_heap_constraints + omit 'stress' if GC.stress + + stat = GC.stat + stat_heap = GC.stat_heap + GC.stat(stat) + GC.stat_heap(nil, stat_heap) + + stat_heap_sum = Hash.new(0) + stat_heap.values.each do |hash| + hash.each { |k, v| stat_heap_sum[k] += v } + end + + assert_equal stat[:heap_allocatable_pages], stat_heap_sum[:heap_allocatable_pages] + assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] + assert_equal stat[:heap_tomb_pages], stat_heap_sum[:heap_tomb_pages] + assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + stat_heap_sum[:heap_tomb_slots] + assert_equal stat[:total_allocated_pages], stat_heap_sum[:total_allocated_pages] + assert_equal stat[:total_freed_pages], stat_heap_sum[:total_freed_pages] + end + def test_latest_gc_info + omit 'stress' if GC.stress + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom' GC.start count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] @@ -131,10 +220,16 @@ 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 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.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] + assert_equal true, h.key?(:need_major_by) GC.stress = true assert_equal :force, GC.latest_gc_info[:major_by] @@ -152,6 +247,35 @@ class TestGc < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")} end + def test_latest_gc_info_need_major_by + return unless use_rgengc? + omit 'stress' if GC.stress + + 3.times { GC.start } + assert_nil GC.latest_gc_info(:need_major_by) + + # allocate objects until need_major_by is set or major GC happens + major_count = GC.stat(:major_gc_count) + objects = [] + while GC.stat(:major_gc_count) == major_count && GC.latest_gc_info(:need_major_by).nil? + objects.append(100.times.map { '*' }) + end + + assert_not_nil GC.latest_gc_info(:need_major_by) + GC.start(full_mark: false) # should be upgraded to major + assert_not_nil GC.latest_gc_info(:major_by) + end + + def test_stress_compile_send + assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "") + GC.stress = true + 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 @@ -181,6 +305,11 @@ class TestGc < Test::Unit::TestCase def test_gc_parameter env = { + "RUBY_GC_HEAP_INIT_SLOTS" => "100" + } + assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[Bug #19284]") + + env = { "RUBY_GC_MALLOC_LIMIT" => "60000000", "RUBY_GC_HEAP_INIT_SLOTS" => "100000" } @@ -214,12 +343,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", @@ -253,6 +376,7 @@ class TestGc < Test::Unit::TestCase end def test_profiler_clear + omit "for now" assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30 GC::Profiler.enable @@ -303,6 +427,40 @@ class TestGc < Test::Unit::TestCase eom end + def test_thrashing_for_young_objects + # This test prevents bugs like [Bug #18929] + + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'RUBY' + # Grow the heap + @ary = 100_000.times.map { Object.new } + + # Warmup to make sure heap stabilizes + 1_000_000.times { Object.new } + + before_stats = GC.stat + before_stat_heap = GC.stat_heap + + 1_000_000.times { Object.new } + + # Previous loop may have caused GC to be in an intermediate state, + # running a minor GC here will guarantee that GC will be complete + GC.start(full_mark: false) + + after_stats = GC.stat + after_stat_heap = GC.stat_heap + + # Debugging output to for failures in trunk-repeat50@phosphorus-docker + debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}" + + # Should not be thrashing in page creation + assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg + assert_equal 0, after_stats[:heap_tomb_pages], debug_msg + assert_equal 0, after_stats[:total_freed_pages], debug_msg + # Only young objects, so should not trigger major GC + assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg + RUBY + end + def test_gc_internals assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] @@ -311,7 +469,7 @@ class TestGc < Test::Unit::TestCase def test_sweep_in_finalizer bug9205 = '[ruby-core:58833] [Bug #9205]' 2.times do - assert_ruby_status([], <<-'end;', bug9205, timeout: 60) + assert_ruby_status([], <<-'end;', bug9205, timeout: 120) raise_proc = proc do |id| GC.start end @@ -361,6 +519,14 @@ class TestGc < Test::Unit::TestCase assert_empty(out) end + def test_finalizer_passed_object_id + assert_in_out_err(%w[--disable-gems], <<-EOS, ["true"], []) + o = Object.new + obj_id = o.object_id + ObjectSpace.define_finalizer(o, ->(id){ p id == obj_id }) + EOS + end + def test_verify_internal_consistency assert_nil(GC.verify_internal_consistency) end @@ -383,6 +549,10 @@ class TestGc < Test::Unit::TestCase end; end + def test_gc_stress_at_startup + assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60) + end + def test_gc_disabled_start begin disabled = GC.disable @@ -402,48 +572,65 @@ 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 - } - } + end + end; + + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #20042]' + begin; + def (f = Object.new).call = nil # missing ID + o = Object.new + ObjectSpace.define_finalizer(o, f) + o = nil + GC.start + end; + end + + def test_object_ids_never_repeat + GC.start + a = 1000.times.map { Object.new.object_id } GC.start - skip "finalizers did not get run" if @result.empty? - assert_equal([:c1, :c2], @result) + 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 new file mode 100644 index 0000000000..3ccd2deb07 --- /dev/null +++ b/test/ruby/test_gc_compact.rb @@ -0,0 +1,456 @@ +# 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 CompactionSupportInspector + def supports_auto_compact? + GC::OPTS.include?("GC_COMPACTION_SUPPORTED") + end + end + + module OmitUnlessCompactSupported + include CompactionSupportInspector + + def setup + omit "autocompact not supported on this platform" unless supports_auto_compact? + super + end + end + + include OmitUnlessCompactSupported + + class AutoCompact < Test::Unit::TestCase + include OmitUnlessCompactSupported + + 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 omit "implicit compaction didn't happen within #{n} objects" + compact_stats = GC.latest_compact_info + refute_predicate compact_stats[:considered], :empty? + refute_predicate compact_stats[:moved], :empty? + ensure + GC.auto_compact = before + end + end + + class CompactMethodsNotImplemented < Test::Unit::TestCase + include CompactionSupportInspector + + def assert_not_implemented(method, *args) + omit "autocompact is supported on this platform" if supports_auto_compact? + + assert_raise(NotImplementedError) { GC.send(method, *args) } + refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement") + end + + def test_gc_compact_not_implemented + assert_not_implemented(:compact) + end + + def test_gc_auto_compact_get_not_implemented + assert_not_implemented(:auto_compact) + end + + def test_gc_auto_compact_set_not_implemented + assert_not_implemented(:auto_compact=, true) + end + + def test_gc_latest_compact_info_not_implemented + assert_not_implemented(:latest_compact_info) + end + + def test_gc_verify_compaction_references_not_implemented + assert_not_implemented(:verify_compaction_references) + end + end + + def os_page_size + return true unless defined?(Etc::SC_PAGE_SIZE) + 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 + + def big_list(level = 10) + if level > 0 + big_list(level - 1) + else + 1000.times.map { + # try to make some empty slots by allocating an object and discarding + Object.new + Object.new + } # likely next to each other + end + end + + # Find an object that's allocated in a slot that had a previous + # tenant, and that tenant moved and is still alive + def find_object_in_recycled_slot(addresses) + new_object = nil + + 100_000.times do + new_object = Object.new + if addresses.index memory_location(new_object) + break + end + end + + new_object + end + + def test_complex_hash_keys + list_of_objects = big_list + hash = list_of_objects.hash + GC.verify_compaction_references(toward: :empty) + assert_equal hash, list_of_objects.hash + GC.verify_compaction_references(expand_heap: false) + assert_equal hash, list_of_objects.hash + end + + def test_ast_compacts + 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 + count = GC.stat(:compact_count) + GC.compact + assert_equal count + 1, GC.stat(:compact_count) + end + + def test_compacting_from_trace_point + obj = Object.new + def obj.tracee + :ret # expected to emit both line and call event from one instruction + end + + results = [] + TracePoint.new(:call, :line) do |tp| + results << tp.event + GC.verify_compaction_references + end.enable(target: obj.method(:tracee)) do + obj.tracee + end + + assert_equal([:call, :line], results) + end + + def test_updating_references_for_heap_allocated_shared_arrays + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ary = [] + 50.times { |i| ary << i } + + # Pointer in slice should point to buffer of ary + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_embed_shared_arrays + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ary = Array.new(50) + 50.times { |i| ary[i] = i } + + # Ensure ary is embedded + assert_include(ObjectSpace.dump(ary), '"embedded":true') + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_heap_allocated_frozen_shared_arrays + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ary = [] + 50.times { |i| ary << i } + # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG + ary.freeze + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_embed_frozen_shared_arrays + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ary = Array.new(50) + 50.times { |i| ary[i] = i } + # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG + ary.freeze + + # Ensure ary is embedded + assert_include(ObjectSpace.dump(ary), '"embedded":true') + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_moving_arrays_down_size_pools + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ARY_COUNT = 500 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $arys = ARY_COUNT.times.map do + ary = "abbbbbbbbbb".chars + ary.uniq! + end + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT) + refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_arrays_up_size_pools + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + ARY_COUNT = 500 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + ary = "hello".chars + $arys = ARY_COUNT.times.map do + x = [] + ary.each { |e| x << e } + x + end + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, ARY_COUNT) + refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_objects_between_size_pools + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + class Foo + def add_ivars + 10.times do |i| + instance_variable_set("@foo" + i.to_s, 0) + end + end + end + + OBJ_COUNT = 500 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $ary = OBJ_COUNT.times.map { Foo.new } + $ary.each(&:add_ivars) + + GC.start + Foo.new.add_ivars + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT) + end; + end + + def test_moving_strings_up_size_pools + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + STR_COUNT = 500 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + $ary = STR_COUNT.times.map { "" << str } + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT) + refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_strings_down_size_pools + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV) + begin; + STR_COUNT = 500 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).squeeze! } + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT) + refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_objects_between_size_pools_keeps_shape_frozen_status + # [Bug #19536] + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class A + def add_ivars + @a = @b = @c = @d = 1 + end + + def set_a + @a = 10 + end + end + + a = A.new + a.add_ivars + a.freeze + + b = A.new + b.add_ivars + b.set_a # Set the inline cache in set_a + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_raise(FrozenError) { a.set_a } + end; + end +end diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 813afaeee0..58fbfea1de 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']) @@ -149,10 +178,9 @@ class TestHash < Test::Unit::TestCase assert_equal(nil, h['b']) assert_equal(300, h['c']) - h = @cls[[["a", 100], "b", ["c", 300]]] - assert_equal(100, h['a']) - assert_equal(nil, h['b']) - assert_equal(300, h['c']) + assert_raise(ArgumentError) do + @cls[[["a", 100], "b", ["c", 300]]] + end end def test_s_AREF_duplicated_key @@ -267,10 +295,27 @@ 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_AREF_fstring_key_default_proc + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + h = Hash.new do |h, k| + k.frozen? + end + + str = "foo" + refute str.frozen? # assumes this file is frozen_string_literal: false + refute h[str] + refute h["foo"] + end; end def test_ASET_fstring_key @@ -280,6 +325,24 @@ class TestHash < Test::Unit::TestCase assert_same a.keys[0], b.keys[0] end + def test_ASET_fstring_non_literal_key + underscore = "_" + non_literal_strings = Proc.new{ ["abc#{underscore}def", "abc" * 5, "abc" + "def", "" << "ghi" << "jkl"] } + + a, b = {}, {} + non_literal_strings.call.each do |string| + assert_equal 1, a[string] = 1 + end + + non_literal_strings.call.each do |string| + assert_equal 1, b[string] = 1 + end + + [a.keys, b.keys].transpose.each do |key_a, key_b| + assert_same key_a, key_b + end + end + def test_hash_aset_fstring_identity h = {}.compare_by_identity h['abc'] = 1 @@ -298,17 +361,9 @@ class TestHash < Test::Unit::TestCase b = {"ABC" => :t} assert_same a.keys[0], b.keys[0] assert_same "ABC".freeze, a.keys[0] - end - - def test_tainted_string_key - str = 'str'.taint - h = {} - h[str] = nil - key = h.keys.first - assert_equal true, str.tainted? - assert_equal false, str.frozen? - assert_equal true, key.tainted? - assert_equal true, key.frozen? + var = +'ABC' + c = { var => :t } + assert_same "ABC".freeze, c.keys[0] end def test_EQUAL # '==' @@ -333,18 +388,14 @@ class TestHash < Test::Unit::TestCase end def test_clone - for taint in [ false, true ] - for frozen in [ false, true ] - a = @h.clone - a.taint if taint - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert_not_same(a, b) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @h.clone + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) end end @@ -412,6 +463,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 @@ -419,6 +479,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 @@ -431,18 +499,14 @@ class TestHash < Test::Unit::TestCase end def test_dup - for taint in [ false, true ] - for frozen in [ false, true ] - a = @h.dup - a.taint if taint - a.freeze if frozen - b = a.dup - - assert_equal(a, b) - assert_not_same(a, b) - assert_equal(false, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @h.dup + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(false, b.frozen?) end end @@ -452,6 +516,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 @@ -650,12 +715,21 @@ class TestHash < Test::Unit::TestCase assert_not_send([@h, :member?, 'gumby']) end + def hash_hint hv + hv & 0xff + end + def test_rehash a = [ "a", "b" ] c = [ "c", "d" ] h = @cls[ a => 100, c => 300 ] assert_equal(100, h[a]) - a[0] = "z" + + hv = a.hash + begin + a[0] << "z" + end while hash_hint(a.hash) == hash_hint(hv) + assert_nil(h[a]) h.rehash assert_equal(100, h[a]) @@ -683,14 +757,39 @@ class TestHash < Test::Unit::TestCase h.instance_variable_set(:@foo, :foo) h.default = 42 - h.taint h = EnvUtil.suppress_warning {h.reject {false}} assert_instance_of(Hash, h) - assert_not_predicate(h, :tainted?) assert_nil(h.default) 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' ] @@ -712,6 +811,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 @@ -734,6 +842,14 @@ class TestHash < Test::Unit::TestCase assert_predicate(h, :compare_by_identity?) end + def test_replace_bug15358 + h1 = {} + h2 = {a:1,b:2,c:3,d:4,e:5} + h2.replace(h1) + GC.start + assert(true) + end + def test_shift h = @h.dup @@ -803,11 +919,6 @@ class TestHash < Test::Unit::TestCase assert_equal([3,4], a.delete([3,4])) assert_equal([5,6], a.delete([5,6])) assert_equal(0, a.length) - - h = @cls[ 1=>2, 3=>4, 5=>6 ] - h.taint - a = h.to_a - assert_equal(true, a.tainted?) end def test_to_hash @@ -844,6 +955,16 @@ class TestHash < Test::Unit::TestCase assert_equal("nope42", h[42]) end + def test_to_h_block + h = @h.to_h {|k, v| [k.to_s, v.to_s]} + assert_equal({ + "1"=>"one", "2"=>"two", "3"=>"three", to_s=>"self", + "true"=>"true", ""=>"nil", "nil"=>"" + }, + h) + assert_instance_of(Hash, h) + end + def test_nil_to_h h = nil.to_h assert_equal({}, h) @@ -854,7 +975,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) @@ -905,8 +1026,8 @@ class TestHash < Test::Unit::TestCase def test_create assert_equal({1=>2, 3=>4}, @cls[[[1,2],[3,4]]]) - assert_raise(ArgumentError) { Hash[0, 1, 2] } - assert_warning(/wrong element type Integer at 1 /) {@cls[[[1, 2], 3]]} + assert_raise(ArgumentError) { @cls[0, 1, 2] } + assert_raise(ArgumentError) { @cls[[[0, 1], 2]] } bug5406 = '[ruby-core:39945]' assert_raise(ArgumentError, bug5406) { @cls[[[1, 2], [3, 4, 5]]] } assert_equal({1=>2, 3=>4}, @cls[1,2,3,4]) @@ -923,7 +1044,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 @@ -941,14 +1062,14 @@ class TestHash < Test::Unit::TestCase h = @cls.new {|hh, k| :foo } h[1] = 2 assert_equal([1, 2], h.shift) - assert_equal(:foo, h.shift) - assert_equal(:foo, h.shift) + assert_nil(h.shift) + assert_nil(h.shift) h = @cls.new(:foo) h[1] = 2 assert_equal([1, 2], h.shift) - assert_equal(:foo, h.shift) - assert_equal(:foo, h.shift) + assert_nil(h.shift) + assert_nil(h.shift) h =@cls[1=>2] h.each { assert_equal([1, 2], h.shift) } @@ -959,7 +1080,20 @@ class TestHash < Test::Unit::TestCase def h.default(k = nil) super.upcase end - assert_equal("FOO", h.shift) + assert_nil(h.shift) + end + + def test_shift_for_empty_hash + # [ruby-dev:51159] + h = @cls[] + 100.times{|n| + while h.size < n + k = Random.rand 0..1<<30 + h[k] = 1 + end + 0 while h.shift + assert_equal({}, h) + } end def test_reject_bang2 @@ -990,20 +1124,53 @@ class TestHash < Test::Unit::TestCase h.instance_variable_set(:@foo, :foo) h.default = 42 - h.taint h = h.select {true} assert_instance_of(Hash, h) - assert_not_predicate(h, :tainted?) assert_nil(h.default) 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 @@ -1014,6 +1181,109 @@ 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 }) + + base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ '2' => false, 'cat' => 99 ] + h2 = @cls[ 1 => 'one', true => 'true' ] + h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ] + + h = base.dup + assert_equal(h, h.filter { true }) + assert_equal(@cls[], h.filter { false }) + + h = base.dup + assert_equal(h1, h.filter {|k,v| k.instance_of?(String) }) + + assert_equal(h2, h.filter {|k,v| v.instance_of?(String) }) + + assert_equal(h3, h.filter {|k,v| v }) + assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h = h.filter {true} + assert_instance_of(Hash, h) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) + end + + def test_filter! + h = @cls[1=>2,3=>4,5=>6] + assert_equal(h, h.filter! {|k, v| k + v >= 7 }) + 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 assert_equal({}, @cls[1=>2,3=>4,5=>6].clear) h = @cls[1=>2,3=>4,5=>6] @@ -1031,8 +1301,17 @@ class TestHash < Test::Unit::TestCase assert_raise(TypeError) { h2.replace(1) } h2.freeze assert_raise(ArgumentError) { h2.replace() } - assert_raise(RuntimeError) { h2.replace(h1) } - assert_raise(RuntimeError) { h2.replace(42) } + assert_raise(FrozenError) { h2.replace(h1) } + 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 @@ -1046,6 +1325,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) @@ -1062,6 +1342,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 @@ -1082,11 +1363,100 @@ class TestHash < Test::Unit::TestCase assert_equal({1=>6, 3=>4, 5=>7}, h1) end + def test_update3 + h1 = @cls[1=>2, 3=>4] + h1.update() + assert_equal({1=>2, 3=>4}, h1) + h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + h1.update(h2, h3) + assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1) + end + + def test_update4 + h1 = @cls[1=>2, 3=>4] + h1.update(){|k, v1, v2| k + v1 + v2 } + assert_equal({1=>2, 3=>4}, h1) + h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + h1.update(h2, h3){|k, v1, v2| k + v1 + v2 } + 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_update_on_identhash + key = +'a' + i = @cls[].compare_by_identity + i[key] = 0 + h = @cls[].update(i) + key.upcase! + assert_equal(0, h.fetch('a')) + end + def test_merge h1 = @cls[1=>2, 3=>4] h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + assert_equal({1=>2, 3=>4}, h1.merge()) assert_equal({1=>3, 3=>4, 5=>7}, h1.merge(h2)) assert_equal({1=>6, 3=>4, 5=>7}, h1.merge(h2) {|k, v1, v2| k + v1 + v2 }) + assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3)) + 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_predicate(h2, :compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_predicate(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_predicate(h2, :compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_predicate(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 @@ -1195,6 +1565,17 @@ class TestHash < Test::Unit::TestCase end end + def hash_iter_recursion(h, level) + return if level == 0 + h.each_key {} + h.each_value { hash_iter_recursion(h, level - 1) } + end + + def test_iterlevel_in_ivar_bug19589 + h = { a: nil } + hash_iter_recursion(h, 200) + end + def test_threaded_iter_level bug9105 = '[ruby-dev:47807] [Bug #9105]' h = @cls[1=>2] @@ -1386,6 +1767,15 @@ class TestHash < Test::Unit::TestCase assert_no_memory_leak([], prepare, code, bug9187) end + def test_memory_size_after_delete + require 'objspace' + h = {} + 1000.times {|i| h[i] = true} + big = ObjectSpace.memsize_of(h) + 1000.times {|i| h.delete(i)} + assert_operator ObjectSpace.memsize_of(h), :<, big/10 + end + def test_wrapper bug9381 = '[ruby-core:59638] [Bug #9381]' @@ -1545,6 +1935,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 @@ -1559,6 +1951,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 @@ -1573,18 +1986,62 @@ class TestHash < Test::Unit::TestCase x.transform_keys!.with_index {|k, i| "#{k}.#{i}" } assert_equal(%w(a!.0 b!.1 c!.2), x.keys) + + x = @cls[1 => :a, -1 => :b] + 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 } @@ -1592,15 +2049,70 @@ 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 + bug14218 = '[ruby-core:84395] [Bug #14218]' + + assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; a < 0 && b < 0 && a + b > 0}, bug14218) + assert_equal(0, 1_000_000.times.count{a=Object.new.hash; b=Object.new.hash; 0 + a + b != 0 + b + a}, bug14218) + end + + def test_reserved_hash_val + s = Struct.new(:hash) + h = {} + keys = [*0..8] + keys.each {|i| h[s.new(i)]=true} + msg = proc {h.inspect} + assert_equal(keys, h.keys.map(&:hash), msg) + end + + def hrec h, n, &b + if n > 0 + h.each{hrec(h, n-1, &b)} + else + yield + end + end + + def test_huge_iter_level + nrec = 200 + + h = @cls[a: 1] + hrec(h, nrec){} + h[:c] = 3 + assert_equal(3, h[:c]) + + h = @cls[a: 1] + h.freeze # set hidden attribute for a frozen object + hrec(h, nrec){} + assert_equal(1, h.size) + + h = @cls[a: 1] + assert_raise(RuntimeError){ + hrec(h, nrec){ h[:c] = 3 } + } + rescue SystemStackError + # ignore end class TestSubHash < TestHash class SubHash < Hash - def reject(*) - super - end end def setup @@ -1608,4 +2120,173 @@ 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 + + # Previously this test would fail because rb_hash inside opt_aref would look + # at the current method name + def test_hash_recursion_independent_of_mid + o = Class.new do + def hash(h, k) + h[k] + end + + def any_other_name(h, k) + h[k] + end + end.new + + rec = []; rec << rec + + h = @cls[] + h[rec] = 1 + assert o.hash(h, rec) + assert o.any_other_name(h, rec) + end + + def test_any_hash_fixable + 20.times do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + 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 + + def test_ar_hash_to_st_hash + assert_normal_exit("#{<<~"begin;"}\n#{<<~'end;'}", 'https://bugs.ruby-lang.org/issues/20050#note-5') + begin; + srand(0) + class Foo + def to_a + [] + end + def hash + $h.delete($h.keys.sample) if rand < 0.1 + to_a.hash + end + end + 1000.times do + $h = {} + (0..10).each {|i| $h[Foo.new] ||= {} } + end + end; + end + + def test_ar_to_st_reserved_value + klass = Class.new do + attr_reader :hash + def initialize(val) = @hash = val + end + + values = 0.downto(-16).to_a + hash = {} + values.each do |val| + hash[klass.new(val)] = val + end + assert_equal values, hash.values, "[ruby-core:121239] [Bug #21170]" + end end diff --git a/test/ruby/test_ifunless.rb b/test/ruby/test_ifunless.rb index d533e155bc..f68e5154a2 100644 --- a/test/ruby/test_ifunless.rb +++ b/test/ruby/test_ifunless.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'test/unit' -class TestIfunless < Test::Unit::TestCase +class TestIfUnless < Test::Unit::TestCase def test_if_unless x = 'test'; assert(if x == x then true else false 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 1b485760e3..2115883810 100644 --- a/test/ruby/test_integer.rb +++ b/test/ruby/test_integer.rb @@ -2,15 +2,47 @@ 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 + + [ + *-16..16, + *(FIXNUM_MIN-2)..(FIXNUM_MIN+2), + *(FIXNUM_MAX-2)..(FIXNUM_MAX+2), + ].each do |n| + (-64..64).each do |idx| + assert_equal((n >> idx) & 1, n[idx]) + end + [*-66..-62, *-34..-30, *-5..5, *30..34, *62..66].each do |idx| + (0..100).each do |len| + assert_equal((n >> idx) & ((1 << len) - 1), n[idx, len], "#{ n }[#{ idx }, #{ len }]") + end + (0..100).each do |len| + assert_equal((n >> idx) & ((1 << (len + 1)) - 1), n[idx..idx+len], "#{ n }[#{ idx }..#{ idx+len }]") + assert_equal((n >> idx) & ((1 << len) - 1), n[idx...idx+len], "#{ n }[#{ idx }...#{ idx+len }]") + end + + # endless + assert_equal((n >> idx), n[idx..], "#{ n }[#{ idx }..]") + assert_equal((n >> idx), n[idx...], "#{ n }[#{ idx }...#]") + + # beginless + if idx >= 0 && n & ((1 << (idx + 1)) - 1) != 0 + assert_raise(ArgumentError, "#{ n }[..#{ idx }]") { n[..idx] } + else + assert_equal(0, n[..idx], "#{ n }[..#{ idx }]") + end + if idx >= 0 && n & ((1 << idx) - 1) != 0 + assert_raise(ArgumentError, "#{ n }[...#{ idx }]") { n[...idx] } + else + assert_equal(0, n[...idx], "#{ n }[...#{ idx }]") + end + end + end + # assert_equal(1, (1 << 0x40000000)[0x40000000], "[ruby-dev:31271]") # assert_equal(0, (-1 << 0x40000001)[0x40000000], "[ruby-dev:31271]") big_zero = 0x40000000.coerce(0)[0] @@ -24,6 +56,32 @@ class TestInteger < Test::Unit::TestCase rescue nil end, "[ruby-dev:32084] [ruby-dev:34547]") + + x = EnvUtil.suppress_warning {2 ** -0x4000000000000000} + assert_in_delta(0.0, (x / 2), Float::EPSILON) + + <<~EXPRS.each_line.with_index(__LINE__+1) do |expr, line| + crash01: 111r+11**-11111161111111 + crash02: 1118111111111**-1111111111111111**1+1==11111 + crash03: -1111111**-1111*11 - -1111111** -111111111 + crash04: 1118111111111** -1111111111111111**1+11111111111**1 ===111 + crash05: 11** -111155555555555555 -55 !=5-555 + crash07: 1 + 111111111**-1111811111 + crash08: 18111111111**-1111111111111111**1 + 1111111111**-1111**1 + crash10: -7 - -1111111** -1111**11 + crash12: 1118111111111** -1111111111111111**1 + 1111 - -1111111** -1111*111111111119 + crash13: 1.0i - -1111111** -111111111 + crash14: 11111**111111111**111111 * -11111111111111111111**-111111111111 + crash15: ~1**1111 + -~1**~1**111 + crash17: 11** -1111111**1111 /11i + crash18: 5555i**-5155 - -9111111**-1111**11 + crash19: 111111*-11111111111111111111**-1111111111111111 + crash20: 1111**111-11**-11111**11 + crash21: 11**-10111111119-1i -1r + EXPRS + name, expr = expr.split(':', 2) + assert_ruby_status(%w"-W0", expr, name) + end end def test_lshift @@ -31,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?) @@ -92,6 +155,17 @@ class TestInteger < Test::Unit::TestCase assert_equal(2 ** 50, Integer(2.0 ** 50)) assert_raise(TypeError) { Integer(nil) } + bug14552 = '[ruby-core:85813]' + obj = Object.new + def obj.to_int; "str"; end + assert_raise(TypeError, bug14552) { Integer(obj) } + def obj.to_i; 42; end + assert_equal(42, Integer(obj), bug14552) + + obj = Object.new + def obj.to_i; "str"; end + assert_raise(TypeError) { Integer(obj) } + bug6192 = '[ruby-core:43566]' assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16be"))} assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16le"))} @@ -116,6 +190,68 @@ class TestInteger < Test::Unit::TestCase end; end + def test_Integer_with_invalid_exception + assert_raise(ArgumentError) { + Integer("0", exception: 1) + } + end + + def test_Integer_with_exception_keyword + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer("1z", exception: false)) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer(Object.new, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; 42.5; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(-Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::NAN, exception: false)) + } + + assert_raise(ArgumentError) { + Integer("1z", exception: true) + } + assert_raise(TypeError) { + Integer(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Integer(nil, exception: false)) + } + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Integer;def method_missing(*);"";end;end + assert_equal(0, Integer("0", 2)) + end; + end + + def test_Integer_when_to_str + def (obj = Object.new).to_str + "0x10" + end + assert_equal(16, Integer(obj)) + end + def test_int_p assert_not_predicate(1.0, :integer?) assert_predicate(1, :integer?) @@ -129,6 +265,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 @@ -167,6 +304,31 @@ class TestInteger < Test::Unit::TestCase end end + def test_times_bignum_redefine_plus_lt + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + called = false + Integer.class_eval do + alias old_plus + + undef + + define_method(:+){|x| called = true; 1} + alias old_lt < + undef < + define_method(:<){|x| called = true} + end + big = 2**65 + big.times{break 0} + Integer.class_eval do + undef + + alias + old_plus + undef < + alias < old_lt + end + bug18377 = "[ruby-core:106361]" + assert_equal(false, called, bug18377) + end; + end + def assert_int_equal(expected, result, mesg = nil) assert_kind_of(Integer, result, mesg) assert_equal(expected, result, mesg) @@ -280,6 +442,10 @@ class TestInteger < Test::Unit::TestCase assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.floor(1)) assert_int_equal(10**400, (10**400).floor(1)) + + assert_int_equal(-10000000000, -1.floor(-10), "[Bug #20654]") + assert_int_equal(-100000000000000000000, -1.floor(-20), "[Bug #20654]") + assert_int_equal(-100000000000000000000000000000000000000000000000000, -1.floor(-50), "[Bug #20654]") end def test_ceil @@ -308,6 +474,10 @@ class TestInteger < Test::Unit::TestCase assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.ceil(1)) assert_int_equal(10**400, (10**400).ceil(1)) + + assert_int_equal(10000000000, 1.ceil(-10), "[Bug #20654]") + assert_int_equal(100000000000000000000, 1.ceil(-20), "[Bug #20654]") + assert_int_equal(100000000000000000000000000000000000000000000000000, 1.ceil(-50), "[Bug #20654]") end def test_truncate @@ -444,6 +614,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 @@ -509,6 +681,9 @@ class TestInteger < Test::Unit::TestCase failures << n unless root*root <= n && (root+1)*(root+1) > n end assert_empty(failures, bug13440) + + x = 0xffff_ffff_ffff_ffff + assert_equal(x, Integer.sqrt(x ** 2), "[ruby-core:95453]") end def test_fdiv @@ -525,4 +700,38 @@ 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 + + def test_ceildiv + assert_equal(0, 0.ceildiv(3)) + assert_equal(1, 1.ceildiv(3)) + assert_equal(1, 3.ceildiv(3)) + assert_equal(2, 4.ceildiv(3)) + + assert_equal(-1, 4.ceildiv(-3)) + assert_equal(-1, -4.ceildiv(3)) + assert_equal(2, -4.ceildiv(-3)) + + assert_equal(3, 3.ceildiv(1.2)) + assert_equal(3, 3.ceildiv(6/5r)) + + assert_equal(10, (10**100-11).ceildiv(10**99-1)) + assert_equal(11, (10**100-9).ceildiv(10**99-1)) + end end diff --git a/test/ruby/test_integer_comb.rb b/test/ruby/test_integer_comb.rb index 80d08cac04..1ad13dd31b 100644 --- a/test/ruby/test_integer_comb.rb +++ b/test/ruby/test_integer_comb.rb @@ -457,6 +457,30 @@ class TestIntegerComb < Test::Unit::TestCase } end + def test_allbits_p + VS.each {|a| + VS.each {|b| + assert_equal((a & b) == b, a.allbits?(b), "(#{a}).allbits?(#{b}") + } + } + end + + def test_anybits_p + VS.each {|a| + VS.each {|b| + assert_equal((a & b) != 0, a.anybits?(b), "(#{a}).anybits?(#{b}") + } + } + end + + def test_nobits_p + VS.each {|a| + VS.each {|b| + assert_equal((a & b) == 0, a.nobits?(b), "(#{a}).nobits?(#{b}") + } + } + end + def test_to_s 2.upto(36) {|radix| VS.each {|a| diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index a0a574e69d..2e6347e7f4 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -49,8 +49,8 @@ class TestIO < Test::Unit::TestCase end flunk("timeout") unless wt.join(10) && rt.join(10) ensure - w.close unless !w || w.closed? - r.close unless !r || r.closed? + w&.close + r&.close (wt.kill; wt.join) if wt (rt.kill; rt.join) if rt raise we if we @@ -62,8 +62,8 @@ class TestIO < Test::Unit::TestCase begin yield r, w ensure - r.close unless r.closed? - w.close unless w.closed? + r.close + w.close end end @@ -84,12 +84,17 @@ class TestIO < Test::Unit::TestCase } end - def trapping_usr1 - @usr1_rcvd = 0 - trap(:USR1) { @usr1_rcvd += 1 } - yield + def trapping_usr2 + @usr2_rcvd = 0 + r, w = IO.pipe + trap(:USR2) do + w.write([@usr2_rcvd += 1].pack('L')) + end + yield r ensure - trap(:USR1, "DEFAULT") + trap(:USR2, "DEFAULT") + w&.close + r&.close end def test_pipe @@ -108,6 +113,65 @@ class TestIO < Test::Unit::TestCase ].each{|thr| thr.join} end + def test_binmode_pipe + EnvUtil.with_default_internal(Encoding::UTF_8) do + EnvUtil.with_default_external(Encoding::UTF_8) do + begin + reader0, writer0 = IO.pipe + reader0.binmode + writer0.binmode + + reader1, writer1 = IO.pipe + + reader2, writer2 = IO.pipe(binmode: true) + assert_predicate writer0, :binmode? + assert_predicate writer2, :binmode? + assert_equal writer0.binmode?, writer2.binmode? + assert_equal writer0.external_encoding, writer2.external_encoding + assert_equal writer0.internal_encoding, writer2.internal_encoding + assert_predicate reader0, :binmode? + assert_predicate reader2, :binmode? + assert_equal reader0.binmode?, reader2.binmode? + assert_equal reader0.external_encoding, reader2.external_encoding + assert_equal reader0.internal_encoding, reader2.internal_encoding + + reader3, writer3 = IO.pipe("UTF-8:UTF-8", binmode: true) + assert_predicate writer3, :binmode? + assert_equal writer1.external_encoding, writer3.external_encoding + assert_equal writer1.internal_encoding, writer3.internal_encoding + assert_predicate reader3, :binmode? + assert_equal reader1.external_encoding, reader3.external_encoding + assert_equal reader1.internal_encoding, reader3.internal_encoding + + reader4, writer4 = IO.pipe("UTF-8:UTF-8", binmode: true) + assert_predicate writer4, :binmode? + assert_equal writer1.external_encoding, writer4.external_encoding + assert_equal writer1.internal_encoding, writer4.internal_encoding + assert_predicate reader4, :binmode? + assert_equal reader1.external_encoding, reader4.external_encoding + assert_equal reader1.internal_encoding, reader4.internal_encoding + + reader5, writer5 = IO.pipe("UTF-8", "UTF-8", binmode: true) + assert_predicate writer5, :binmode? + assert_equal writer1.external_encoding, writer5.external_encoding + assert_equal writer1.internal_encoding, writer5.internal_encoding + assert_predicate reader5, :binmode? + assert_equal reader1.external_encoding, reader5.external_encoding + assert_equal reader1.internal_encoding, reader5.internal_encoding + ensure + [ + reader0, writer0, + reader1, writer1, + reader2, writer2, + reader3, writer3, + reader4, writer4, + reader5, writer5, + ].compact.map(&:close) + end + end + end + end + def test_pipe_block x = nil ret = IO.pipe {|r, w| @@ -228,6 +292,19 @@ class TestIO < Test::Unit::TestCase assert_nil r.gets r.close end) + + (0..3).each do |i| + pipe(proc do |w| + w.write("a" * ((4096 << i) - 4), "\r\n" "a\r\n") + w.close + end, + proc do |r| + r.gets + assert_equal "a", r.gets(chomp: true) + assert_nil r.gets + r.close + end) + end end def test_gets_chomp_rs_nil @@ -235,7 +312,7 @@ class TestIO < Test::Unit::TestCase w.print "a\n\nb\n\n" w.close end, proc do |r| - assert_equal "a\n\nb\n", r.gets(nil, chomp: true) + assert_equal("a\n\nb\n\n", r.gets(nil, chomp: true), "[Bug #18770]") assert_nil r.gets("") r.close end) @@ -317,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]' @@ -328,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 @@ -346,7 +446,7 @@ class TestIO < Test::Unit::TestCase path = t.path t.close! assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do - File.read(path, nil, nil, {}) + File.read(path, nil, nil, **{}) end end @@ -376,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| @@ -543,14 +655,14 @@ class TestIO < Test::Unit::TestCase if have_nonblock? def test_copy_stream_no_busy_wait + omit "MJIT has busy wait on GC. This sometimes fails with --jit." if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + omit "multiple threads already active" if Thread.list.size > 1 + msg = 'r58534 [ruby-core:80969] [Backport #13533]' IO.pipe do |r,w| r.nonblock = true - assert_cpu_usage_low(msg) do - th = Thread.new { IO.copy_stream(r, IO::NULL) } - sleep 0.1 - w.close - th.join + assert_cpu_usage_low(msg, stop: ->{w.close}) do + IO.copy_stream(r, IO::NULL) end end end @@ -563,8 +675,7 @@ class TestIO < Test::Unit::TestCase begin w2.nonblock = true rescue Errno::EBADF - skip "nonblocking IO for pipe is not implemented" - break + omit "nonblocking IO for pipe is not implemented" end s = w2.syswrite("a" * 100000) t = Thread.new { sleep 0.1; r2.read } @@ -630,7 +741,7 @@ class TestIO < Test::Unit::TestCase assert_equal(bigcontent[30, 40], File.read("bigdst")) assert_equal(0, f.pos) rescue NotImplementedError - #skip "pread(2) is not implemtented." + #skip "pread(2) is not implemented." end } } @@ -668,7 +779,7 @@ class TestIO < Test::Unit::TestCase r1.nonblock = true w2.nonblock = true rescue Errno::EBADF - skip "nonblocking IO for pipe is not implemented" + omit "nonblocking IO for pipe is not implemented" end t1 = Thread.new { w1 << megacontent; w1.close } t2 = Thread.new { r2.read } @@ -732,7 +843,7 @@ class TestIO < Test::Unit::TestCase assert_equal("bcd", r.read) end) rescue NotImplementedError - skip "pread(2) is not implemtented." + omit "pread(2) is not implemtented." end } end @@ -789,6 +900,10 @@ class TestIO < Test::Unit::TestCase end if defined? UNIXSocket def test_copy_stream_socket4 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(0, f.pos) @@ -805,9 +920,13 @@ class TestIO < Test::Unit::TestCase } } } - end if defined? UNIXSocket + end def test_copy_stream_socket5 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(bigcontent[0,100], f.read(100)) @@ -825,9 +944,13 @@ class TestIO < Test::Unit::TestCase } } } - end if defined? UNIXSocket + end def test_copy_stream_socket6 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } @@ -836,7 +959,7 @@ class TestIO < Test::Unit::TestCase begin s1.nonblock = true rescue Errno::EBADF - skip "nonblocking IO for pipe is not implemented" + omit "nonblocking IO for pipe is not implemented" end t1 = Thread.new { s2.read } t2 = Thread.new { @@ -848,9 +971,13 @@ class TestIO < Test::Unit::TestCase assert_equal(megacontent, result) } } - end if defined? UNIXSocket + end def test_copy_stream_socket7 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + GC.start mkcdtmpdir { megacontent = "abc" * 1234567 @@ -860,22 +987,23 @@ class TestIO < Test::Unit::TestCase begin s1.nonblock = true rescue Errno::EBADF - skip "nonblocking IO for pipe is not implemented" + omit "nonblocking IO for pipe is not implemented" end - trapping_usr1 do + trapping_usr2 do |rd| nr = 30 begin pid = fork do s1.close IO.select([s2]) - Process.kill(:USR1, Process.ppid) - s2.read + Process.kill(:USR2, Process.ppid) + buf = String.new(capacity: 16384) + nil while s2.read(16384, buf) end s2.close nr.times do assert_equal megacontent.bytesize, IO.copy_stream("megasrc", s1) end - assert_equal(1, @usr1_rcvd) + assert_equal(1, rd.read(4).unpack1('L')) ensure s1.close _, status = Process.waitpid2(pid) if pid @@ -884,7 +1012,7 @@ class TestIO < Test::Unit::TestCase end } } - end if defined? UNIXSocket and IO.method_defined?("nonblock=") + end def test_copy_stream_strio src = StringIO.new("abcd") @@ -988,6 +1116,34 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_dup_buffer + bug21131 = '[ruby-core:120961] [Bug #21131]' + mkcdtmpdir do + dst_class = Class.new do + def initialize(&block) + @block = block + end + + def write(data) + @block.call(data.dup) + data.bytesize + end + end + + rng = Random.new(42) + body = Tempfile.new("ruby-bug", binmode: true) + body.write(rng.bytes(16_385)) + body.rewind + + payload = [] + IO.copy_stream(body, dst_class.new{|data| payload << data}) + body.rewind + assert_equal(body.read, payload.join, bug21131) + ensure + body&.close + end + end + def test_copy_stream_write_in_binmode bug8767 = '[ruby-core:56518] [Bug #8767]' mkcdtmpdir { @@ -1179,10 +1335,11 @@ class TestIO < Test::Unit::TestCase def test_copy_stream_to_duplex_io result = IO.pipe {|a,w| - Thread.start {w.puts "yes"; w.close} + th = Thread.start {w.puts "yes"; w.close} IO.popen([EnvUtil.rubybin, '-pe$_="#$.:#$_"'], "r+") {|b| IO.copy_stream(a, b) b.close_write + assert_join_threads([th]) b.read } } @@ -1195,7 +1352,7 @@ class TestIO < Test::Unit::TestCase opts = {} if defined?(Process::RLIMIT_NPROC) lim = Process.getrlimit(Process::RLIMIT_NPROC)[1] - opts[:rlimit_nproc] = [lim, 1024].min + opts[:rlimit_nproc] = [lim, 2048].min end f = IO.popen([ruby] + args, 'r+', opts) pid = f.pid @@ -1283,6 +1440,13 @@ class TestIO < Test::Unit::TestCase assert_empty(err) end + def test_write_no_args + IO.pipe do |r, w| + assert_equal 0, w.write, '[ruby-core:86285] [Bug #14338]' + assert_equal :wait_readable, r.read_nonblock(1, exception: false) + end + end + def test_write_non_writable with_pipe do |r, w| assert_raise(IOError) do @@ -1310,7 +1474,7 @@ class TestIO < Test::Unit::TestCase def test_dup_many opts = {} opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE) - assert_separately([], <<-'End', opts) + assert_separately([], <<-'End', **opts) a = [] assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do loop {a << IO.pipe} @@ -1321,6 +1485,16 @@ class TestIO < Test::Unit::TestCase End end + def test_dup_timeout + with_pipe do |r, w| + r.timeout = 0.1 + r2 = r.dup + assert_equal(0.1, r2.timeout) + ensure + r2&.close + end + end + def test_inspect with_pipe do |r, w| assert_match(/^#<IO:fd \d+>$/, r.inspect) @@ -1374,6 +1548,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 = "" @@ -1419,6 +1600,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 = "" @@ -1456,6 +1644,35 @@ class TestIO < Test::Unit::TestCase } end + def test_read_nonblock_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.read_nonblock(0, s = "01234567")) + assert_empty(s) + end + end + + def test_read_nonblock_file + make_tempfile do |path| + File.open(path, 'r') do |file| + file.read_nonblock(4) + end + end + end + + def test_write_nonblock_file + make_tempfile do |path| + File.open(path, 'w') do |file| + file.write_nonblock("Ruby") + end + end + end + + def test_explicit_path + io = IO.for_fd(0, path: "Fake Path", autoclose: false) + assert_match %r"Fake Path", io.inspect + assert_equal "Fake Path", io.path + end + def test_write_nonblock_simple_no_exceptions pipe(proc do |w| w.write_nonblock('1', exception: false) @@ -1483,7 +1700,14 @@ class TestIO < Test::Unit::TestCase } end if have_nonblock? + def test_read_nonblock_invalid_exception + with_pipe {|r, w| + assert_raise(ArgumentError) {r.read_nonblock(4096, exception: 1)} + } + end if have_nonblock? + def test_read_nonblock_no_exceptions + omit '[ruby-core:90895] MJIT worker may leave fd open in a forked child' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # TODO: consider acquiring GVL from MJIT worker. with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, exception: false) w.puts "HI!" @@ -1518,6 +1742,12 @@ class TestIO < Test::Unit::TestCase } end if have_nonblock? + def test_write_nonblock_invalid_exception + with_pipe {|r, w| + assert_raise(ArgumentError) {w.write_nonblock(4096, exception: 1)} + } + end if have_nonblock? + def test_write_nonblock_no_exceptions with_pipe {|r, w| loop { @@ -1678,23 +1908,55 @@ class TestIO < Test::Unit::TestCase f.gets; assert_equal(3, $.) end SRC - - pipe(proc do |w| - w.puts "foo" - w.puts "bar" - w.puts "baz" - w.close - end, proc do |r| - r.gets; assert_equal(1, $.) - r.gets; assert_equal(2, $.) - r.lineno = 1000; assert_equal(2, $.) - r.gets; assert_equal(1001, $.) - r.gets; assert_equal(1001, $.) - end) } end - def test_readline + def test_set_lineno_gets + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + r.gets; assert_equal(1, $.) + r.gets; assert_equal(2, $.) + r.lineno = 1000; assert_equal(2, $.) + r.gets; assert_equal(1001, $.) + r.gets; assert_equal(1001, $.) + end) + end + + def test_readline_limit_nonascii + mkcdtmpdir do + i = 0 + + File.open("text#{i+=1}", "w+:utf-8") do |f| + f.write("Test\nok\u{bf}ok\n") + f.rewind + + assert_equal("Test\nok\u{bf}", f.readline("\u{bf}")) + assert_equal("ok\n", f.readline("\u{bf}")) + end + + File.open("text#{i+=1}", "w+b:utf-32le") do |f| + f.write("0123456789") + f.rewind + + assert_equal(4, f.readline(4).bytesize) + assert_equal(4, f.readline(3).bytesize) + end + + File.open("text#{i+=1}", "w+:utf-8:utf-32le") do |f| + f.write("0123456789") + f.rewind + + assert_equal(4, f.readline(4).bytesize) + assert_equal(4, f.readline(3).bytesize) + end + end + end + + def test_set_lineno_readline pipe(proc do |w| w.puts "foo" w.puts "bar" @@ -1722,8 +1984,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" @@ -1731,20 +1992,31 @@ 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 + + pipe(proc do |w| + w.write "foo\n" + w.close + end, proc do |r| + assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]") + end) + + pipe(proc do |w| + w.write "foo\n" + w.close + end, proc do |r| + assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]") + end) end - def test_bytes - verbose, $VERBOSE = $VERBOSE, nil + def test_each_byte2 pipe(proc do |w| w.binmode w.puts "foo" @@ -1753,20 +2025,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" @@ -1774,16 +2043,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 @@ -1848,7 +2115,6 @@ class TestIO < Test::Unit::TestCase def test_pos make_tempfile {|t| - open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| f.write "Hello" assert_equal(5, f.pos) @@ -2053,6 +2319,14 @@ class TestIO < Test::Unit::TestCase end) end + def test_sysread_with_negative_length + make_tempfile {|t| + open(t.path) do |f| + assert_raise(ArgumentError) { f.sysread(-1) } + end + } + end + def test_flag make_tempfile {|t| assert_raise(ArgumentError) do @@ -2131,6 +2405,10 @@ class TestIO < Test::Unit::TestCase end 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 + omit 'this randomly fails with MJIT' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + feature2250 = '[ruby-core:26222]' pre = 'ft2250' t = Tempfile.new(pre) @@ -2141,12 +2419,12 @@ class TestIO < Test::Unit::TestCase t.close rescue Errno::EBADF end - skip "expect IO object was GC'ed but not recycled yet" + omit "expect IO object was GC'ed but not recycled yet" rescue WeakRef::RefError assert_raise(Errno::EBADF, feature2250) {t.close} end ensure - t.close! + t&.close! end def test_autoclose_false_closed_by_finalizer @@ -2157,7 +2435,7 @@ class TestIO < Test::Unit::TestCase begin w.close t.close - skip "expect IO object was GC'ed but not recycled yet" + omit "expect IO object was GC'ed but not recycled yet" rescue WeakRef::RefError assert_nothing_raised(Errno::EBADF, feature2250) {t.close} end @@ -2176,6 +2454,21 @@ class TestIO < Test::Unit::TestCase assert_equal(o, o2) end + def test_open_redirect_keyword + o = Object.new + def o.to_open(**kw); kw; end + assert_equal({:a=>1}, open(o, a: 1)) + + 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)) + assert_equal({:a=>1}, open(o, {a: 1})) + end + def test_open_pipe open("|" + EnvUtil.rubybin, "r+") do |f| f.puts "puts 'foo'" @@ -2184,6 +2477,25 @@ class TestIO < Test::Unit::TestCase end end + def test_read_command + assert_equal("foo\n", IO.read("|echo foo")) + assert_raise(Errno::ENOENT, Errno::EINVAL) do + File.read("|#{EnvUtil.rubybin} -e puts") + end + assert_raise(Errno::ENOENT, Errno::EINVAL) do + File.binread("|#{EnvUtil.rubybin} -e puts") + end + assert_raise(Errno::ENOENT, Errno::EINVAL) do + Class.new(IO).read("|#{EnvUtil.rubybin} -e puts") + end + assert_raise(Errno::ENOENT, Errno::EINVAL) do + Class.new(IO).binread("|#{EnvUtil.rubybin} -e puts") + end + assert_raise(Errno::ESPIPE) do + IO.read("|echo foo", 1, 1) + end + end + def test_reopen make_tempfile {|t| open(__FILE__) do |f| @@ -2225,13 +2537,13 @@ class TestIO < Test::Unit::TestCase def test_reopen_inherit mkcdtmpdir { - system(EnvUtil.rubybin, '-e', <<"End") + system(EnvUtil.rubybin, '-e', <<-"End") f = open("out", "w") STDOUT.reopen(f) STDERR.reopen(f) system(#{EnvUtil.rubybin.dump}, '-e', 'STDOUT.print "out"') system(#{EnvUtil.rubybin.dump}, '-e', 'STDERR.print "err"') -End + End assert_equal("outerr", File.read("out")) } end @@ -2292,7 +2604,7 @@ End t end ensure - t.close(true) if t and block_given? + t&.close(true) if block_given? end def test_reopen_encoding @@ -2352,26 +2664,41 @@ End 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 } assert_equal(["foo\n", "bar\n", "baz\n"], a) + a = [] + IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x } + assert_equal(["zot\n"], a) + make_tempfile {|t| a = [] IO.foreach(t.path) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:mode => "r" }) {|x| a << x } + IO.foreach(t.path, :mode => "r") {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:open_args => [] }) {|x| a << x } + IO.foreach(t.path, :open_args => []) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] - IO.foreach(t.path, {:open_args => ["r"] }) {|x| a << x } + IO.foreach(t.path, :open_args => ["r"]) {|x| a << x } assert_equal(["foo\n", "bar\n", "baz\n"], a) a = [] @@ -2389,6 +2716,8 @@ End bug = '[ruby-dev:31525]' assert_raise(ArgumentError, bug) {IO.foreach} + assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}} + a = nil assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a} assert_equal(["foo\n", "bar\n", "baz\n"], a, bug) @@ -2397,6 +2726,8 @@ End assert_raise_with_message(IOError, /not opened for reading/, bug6054) do IO.foreach(t.path, mode:"w").next end + + assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}} } end @@ -2406,6 +2737,7 @@ End assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b")) assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2)) assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2)) + assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}} } end @@ -2427,11 +2759,13 @@ End end def test_print_separators - $, = ':' - $\ = "\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) @@ -2469,7 +2803,7 @@ End end def test_puts_parallel - skip "not portable" + omit "not portable" pipe(proc do |w| threads = [] 100.times do @@ -2489,7 +2823,7 @@ End end capture.clear - assert_warning(/[.#]write is outdated/) do + assert_deprecated_warning(/[.#]write is outdated/) do stdout, $stdout = $stdout, capture puts "hey" ensure @@ -2514,7 +2848,8 @@ End assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo)) - assert_separately(%w[-Eutf-8], <<-"end;") # do + assert_separately(%w[-Eutf-8], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; alias $\u{6a19 6e96 51fa 529b} $stdout x = eval("class X\u{307b 3052}; self; end".encode("euc-jp")) assert_raise_with_message(TypeError, /\\$\u{6a19 6e96 51fa 529b} must.*, X\u{307b 3052} given/) do @@ -2527,7 +2862,6 @@ End return unless defined?(Fcntl::F_GETFL) make_tempfile {|t| - fd = IO.sysopen(t.path, "w") assert_kind_of(Integer, fd) %w[r r+ w+ a+].each do |mode| @@ -2615,13 +2949,6 @@ End } end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW - def test_tainted - make_tempfile {|t| - assert_predicate(File.read(t.path, 4), :tainted?, '[ruby-dev:38826]') - assert_predicate(File.open(t.path) {|f| f.read(4)}, :tainted?, '[ruby-dev:38826]') - } - end - def test_binmode_after_closed make_tempfile {|t| assert_raise(IOError) {t.binmode} @@ -2645,7 +2972,7 @@ __END__ end; 10.times.map do Thread.start do - assert_in_out_err([], src) {|stdout, stderr| + assert_in_out_err([], src, timeout: 20) {|stdout, stderr| assert_no_match(/hi.*hi/, stderr.join, bug3585) } end @@ -2653,7 +2980,6 @@ __END__ end def test_flush_in_finalizer1 - require 'tempfile' bug3910 = '[ruby-dev:42341]' tmp = Tempfile.open("bug3910") {|t| path = t.path @@ -2679,9 +3005,8 @@ __END__ end def test_flush_in_finalizer2 - require 'tempfile' bug3910 = '[ruby-dev:42341]' - Tempfile.open("bug3910") {|t| + Tempfile.create("bug3910") {|t| path = t.path t.close begin @@ -2700,7 +3025,6 @@ __END__ end } end - t.close! } end @@ -2813,7 +3137,7 @@ __END__ end def test_fcntl_lock_linux - pad=0 + pad = 0 Tempfile.create(self.class.name) do |f| r, w = IO.pipe pid = fork do @@ -2906,16 +3230,22 @@ __END__ end def test_cross_thread_close_stdio + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + assert_separately([], <<-'end;') IO.pipe do |r,w| $stdin.reopen(r) r.close read_thread = Thread.new do - $stdin.read(1) + begin + $stdin.read(1) + rescue IOError => e + e + end end sleep(0.1) until read_thread.stop? $stdin.close - assert_raise(IOError) {read_thread.join} + assert_kind_of(IOError, read_thread.value) end end; end @@ -2990,9 +3320,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) { - File.write(path, "foo", Object.new => Object.new) - } + File.write(path, "foo", Object.new => Object.new) + assert_equal("foo", File.read(path)) end end @@ -3110,7 +3439,7 @@ __END__ begin f = File.open('/dev/tty') rescue Errno::ENOENT, Errno::ENXIO => e - skip e.message + omit e.message else tiocgwinsz=0x5413 winsize="" @@ -3118,7 +3447,7 @@ __END__ f.ioctl(tiocgwinsz, winsize) } ensure - f.close if f + f&.close end end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM @@ -3191,11 +3520,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) @@ -3214,17 +3549,12 @@ __END__ assert_equal 100, buf.bytesize - begin + msg = /can't modify string; temporarily locked/ + assert_raise_with_message(RuntimeError, msg) do buf.replace("") - rescue RuntimeError => e - assert_match(/can't modify string; temporarily locked/, e.message) - Thread.pass - end until buf.empty? - - assert_empty(buf, bug6099) + end assert_predicate(th, :alive?) w.write(data) - Thread.pass while th.alive? th.join end assert_equal(data, buf, bug6099) @@ -3235,10 +3565,10 @@ __END__ with_pipe do |r,w| # Linux 2.6.15 and earlier returned EINVAL instead of ESPIPE assert_raise(Errno::ESPIPE, Errno::EINVAL) { - r.advise(:willneed) or skip "fadvise(2) is not implemented" + r.advise(:willneed) or omit "fadvise(2) is not implemented" } assert_raise(Errno::ESPIPE, Errno::EINVAL) { - w.advise(:willneed) or skip "fadvise(2) is not implemented" + w.advise(:willneed) or omit "fadvise(2) is not implemented" } end end if /linux/ =~ RUBY_PLATFORM @@ -3321,11 +3651,18 @@ __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) - }, bug8080, timeout: 50 + begin + IO.select(tempfiles) + ensure + tempfiles.each { |t| + t.close + File.unlink(t.path) + } + end + }, bug8080, timeout: 100 end if defined?(Process::RLIMIT_NOFILE) def test_read_32bit_boundary @@ -3371,14 +3708,14 @@ __END__ f.write('1') pos = f.tell rescue Errno::ENOSPC - skip "non-sparse file system" + omit "non-sparse file system" rescue SystemCallError else assert_equal(0x1_0000_0000, pos, msg) end end; rescue Timeout::Error - skip "Timeout because of slow file writing" + omit "Timeout because of slow file writing" end } end if /mswin|mingw/ =~ RUBY_PLATFORM @@ -3388,12 +3725,16 @@ __END__ str = "" IO.pipe {|r,| - t = Thread.new { r.read(nil, str) } + t = Thread.new { + assert_raise(RuntimeError) { + r.read(nil, str) + } + } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } - assert_raise(RuntimeError) { t.join } + t.join } end if /cygwin/ !~ RUBY_PLATFORM @@ -3402,12 +3743,16 @@ __END__ str = "" IO.pipe {|r, w| - t = Thread.new { r.readpartial(4096, str) } + t = Thread.new { + assert_raise(RuntimeError) { + r.readpartial(4096, str) + } + } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } - assert_raise(RuntimeError) { t.join } + t.join } end if /cygwin/ !~ RUBY_PLATFORM @@ -3427,12 +3772,16 @@ __END__ str = "" IO.pipe {|r, w| - t = Thread.new { r.sysread(4096, str) } + t = Thread.new { + assert_raise(RuntimeError) { + r.sysread(4096, str) + } + } sleep 0.1 until t.stop? t.raise sleep 0.1 while t.alive? assert_nothing_raised(RuntimeError, bug8669) { str.clear } - assert_raise(RuntimeError) { t.join } + t.join } end if /cygwin/ !~ RUBY_PLATFORM @@ -3486,21 +3835,42 @@ __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 + def test_exclusive_mode + make_tempfile do |t| + assert_raise(Errno::EEXIST){ open(t.path, 'wx'){} } + assert_raise(ArgumentError){ open(t.path, 'rx'){} } + assert_raise(ArgumentError){ open(t.path, 'ax'){} } + end + end + def test_race_gets_and_close - assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + opt = { signal: :ABRT, timeout: 200 } + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", **opt) bug13076 = '[ruby-core:78845] [Bug #13076]' begin; 10.times do |i| @@ -3522,24 +3892,28 @@ __END__ w.close r.close end - assert_nothing_raised(IOError, bug13076) { - t.each(&:join) - } + t.each do |th| + assert_same(th, th.join(2), bug13076) + end end end; end def test_race_closed_stream + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") begin; bug13158 = '[ruby-core:79262] [Bug #13158]' closed = nil - q = Queue.new + q = Thread::Queue.new IO.pipe do |r, w| thread = Thread.new do begin q << true - while r.gets + assert_raise_with_message(IOError, /stream closed/) do + while r.gets + end end ensure closed = r.closed? @@ -3548,9 +3922,7 @@ __END__ q.pop sleep 0.01 until thread.stop? r.close - assert_raise_with_message(IOError, /stream closed/) do - thread.join - end + thread.join assert_equal(true, closed, bug13158 + ': stream should be closed') end end; @@ -3624,6 +3996,8 @@ __END__ end def test_closed_stream_in_rescue + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") begin; 10.times do @@ -3645,65 +4019,156 @@ __END__ end end; end + end - def test_write_no_garbage - 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 + def test_pread + make_tempfile { |t| + open(t.path) do |f| + assert_equal("bar", f.pread(3, 4)) + buf = "asdf" + assert_equal("bar", f.pread(3, 4, buf)) + assert_equal("bar", buf) + assert_raise(EOFError) { f.pread(1, f.size) } end - end - - def test_pread - make_tempfile { |t| - open(t.path) do |f| - assert_equal("bar", f.pread(3, 4)) - buf = "asdf" - assert_equal("bar", f.pread(3, 4, buf)) - assert_equal("bar", buf) - assert_raise(EOFError) { f.pread(1, f.size) } - end - } - end if IO.method_defined?(:pread) + } + end if IO.method_defined?(:pread) - def test_pwrite - make_tempfile { |t| - open(t.path, IO::RDWR) do |f| - assert_equal(3, f.pwrite("ooo", 4)) - assert_equal("ooo", f.pread(3, 4)) - end - } - end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite) - end + def test_pwrite + make_tempfile { |t| + open(t.path, IO::RDWR) do |f| + assert_equal(3, f.pwrite("ooo", 4)) + assert_equal("ooo", f.pread(3, 4)) + end + } + end if IO.method_defined?(:pread) and IO.method_defined?(:pwrite) def test_select_exceptfds - if Etc.uname[:sysname] == 'SunOS' && Etc.uname[:release] == '5.11' - skip "Solaris 11 fails this" + if Etc.uname[:sysname] == 'SunOS' + str = 'h'.freeze #(???) Only 1 byte with MSG_OOB on Solaris + else + str = 'hello'.freeze end TCPServer.open('localhost', 0) do |svr| con = TCPSocket.new('localhost', svr.addr[1]) acc = svr.accept - assert_equal 5, con.send('hello', Socket::MSG_OOB) + assert_equal str.length, con.send(str, Socket::MSG_OOB) set = IO.select(nil, nil, [acc], 30) assert_equal([[], [], [acc]], set, 'IO#select exceptions array OK') acc.close con.close end end if Socket.const_defined?(:MSG_OOB) + + def test_recycled_fd_close + dot = -'.' + IO.pipe do |sig_rd, sig_wr| + noex = Thread.new do # everything right and never see exceptions :) + until sig_rd.wait_readable(0) + IO.pipe do |r, w| + assert_nil r.timeout + assert_nil w.timeout + + th = Thread.new { r.read(1) } + w.write(dot) + + assert_same th, th.join(15), '"good" reader timeout' + assert_equal(dot, th.value) + end + end + sig_rd.read(4) + end + 1000.times do |i| # stupid things and make exceptions: + IO.pipe do |r,w| + th = Thread.new do + begin + while r.gets + end + rescue IOError => e + e + end + end + Thread.pass until th.stop? + + r.close + assert_same th, th.join(30), '"bad" reader timeout' + assert_match(/stream closed/, th.value.message) + end + end + sig_wr.write 'done' + assert_same noex, noex.join(20), '"good" writer timeout' + assert_equal 'done', noex.value ,'r63216' + end + end + + 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], "#{<<~"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 + else; + th = Thread.new do + Thread.handle_interrupt(StandardError => :on_blocking) do + begin + IO.select(rset, wset) + rescue + retry + end while true + end + end + 50_000.times do + Thread.pass until th.stop? + th.raise(exc) + end + th.kill + th.join + end; + end + + def test_external_encoding_index + IO.pipe {|r, w| + assert_raise(TypeError) {Marshal.dump(r)} + 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..1c76403b2d --- /dev/null +++ b/test/ruby/test_io_buffer.rb @@ -0,0 +1,562 @@ +# frozen_string_literal: false + +require 'tempfile' + +class TestIOBuffer < Test::Unit::TestCase + experimental = Warning[:experimental] + begin + Warning[:experimental] = false + IO::Buffer.new(0) + ensure + Warning[:experimental] = experimental + end + + def assert_negative(value) + assert(value < 0, "Expected #{value} to be negative!") + end + + def assert_positive(value) + assert(value > 0, "Expected #{value} to be positive!") + end + + def test_flags + assert_equal 1, IO::Buffer::EXTERNAL + assert_equal 2, IO::Buffer::INTERNAL + assert_equal 4, IO::Buffer::MAPPED + + assert_equal 32, IO::Buffer::LOCKED + assert_equal 64, IO::Buffer::PRIVATE + + assert_equal 128, IO::Buffer::READONLY + end + + def test_endian + assert_equal 4, IO::Buffer::LITTLE_ENDIAN + assert_equal 8, IO::Buffer::BIG_ENDIAN + assert_equal 8, IO::Buffer::NETWORK_ENDIAN + + assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN + end + + def test_default_size + assert_equal IO::Buffer::DEFAULT_SIZE, IO::Buffer.new.size + end + + def test_new_internal + buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL) + assert_equal 1024, buffer.size + refute buffer.external? + assert buffer.internal? + refute buffer.mapped? + end + + def test_new_mapped + buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED) + assert_equal 1024, buffer.size + refute buffer.external? + refute buffer.internal? + assert buffer.mapped? + end + + def test_new_readonly + buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY) + assert buffer.readonly? + + assert_raise IO::Buffer::AccessError do + buffer.set_string("") + end + + assert_raise IO::Buffer::AccessError do + buffer.set_string("!", 1) + end + end + + def test_file_mapped + buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)} + contents = buffer.get_string + + assert_include contents, "Hello World" + assert_equal Encoding::BINARY, contents.encoding + end + + def test_file_mapped_invalid + assert_raise NoMethodError do + IO::Buffer.map("foobar") + end + end + + def test_string_mapped + string = "Hello World" + buffer = IO::Buffer.for(string) + assert buffer.readonly? + end + + def test_string_mapped_frozen + string = "Hello World".freeze + buffer = IO::Buffer.for(string) + assert buffer.readonly? + end + + def test_string_mapped_mutable + string = "Hello World" + IO::Buffer.for(string) do |buffer| + refute buffer.readonly? + + # Cannot modify string as it's locked by the buffer: + assert_raise RuntimeError do + string[0] = "h" + end + + buffer.set_value(:U8, 0, "h".ord) + + # Buffer releases it's ownership of the string: + buffer.free + + assert_equal "hello World", string + end + end + + def test_non_string + not_string = Object.new + + assert_raise TypeError do + IO::Buffer.for(not_string) + end + end + + def test_resize_mapped + buffer = IO::Buffer.new + + buffer.resize(2048) + assert_equal 2048, buffer.size + + buffer.resize(4096) + assert_equal 4096, buffer.size + end + + def test_resize_preserve + message = "Hello World" + buffer = IO::Buffer.new(1024) + buffer.set_string(message) + buffer.resize(2048) + assert_equal message, buffer.get_string(0, message.bytesize) + end + + def test_resize_zero_internal + buffer = IO::Buffer.new(1) + + buffer.resize(0) + assert_equal 0, buffer.size + + buffer.resize(1) + assert_equal 1, buffer.size + end + + def test_resize_zero_external + buffer = IO::Buffer.for('1') + + assert_raise IO::Buffer::AccessError do + buffer.resize(0) + end + end + + def test_compare_same_size + buffer1 = IO::Buffer.new(1) + assert_equal buffer1, buffer1 + + buffer2 = IO::Buffer.new(1) + buffer1.set_value(:U8, 0, 0x10) + buffer2.set_value(:U8, 0, 0x20) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + + def test_compare_different_size + buffer1 = IO::Buffer.new(3) + buffer2 = IO::Buffer.new(5) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + + def test_slice + buffer = IO::Buffer.new(128) + slice = buffer.slice(8, 32) + slice.set_string("Hello World") + assert_equal("Hello World", buffer.get_string(8, 11)) + end + + def test_slice_arguments + buffer = IO::Buffer.for("Hello World") + + slice = buffer.slice + assert_equal "Hello World", slice.get_string + + slice = buffer.slice(2) + assert_equal("llo World", slice.get_string) + end + + def test_slice_bounds_error + buffer = IO::Buffer.new(128) + + assert_raise ArgumentError do + buffer.slice(128, 10) + end + + assert_raise ArgumentError do + buffer.slice(-10, 10) + end + end + + def test_slice_readonly + hello = %w"Hello World".join(" ").freeze + buffer = IO::Buffer.for(hello) + slice = buffer.slice + assert_predicate slice, :readonly? + assert_raise IO::Buffer::AccessError do + # This breaks the literal in string pool and many other tests in this file. + slice.set_string("Adios", 0, 5) + end + assert_equal "Hello World", hello + end + + def test_transfer + hello = %w"Hello World".join(" ") + buffer = IO::Buffer.for(hello) + transferred = buffer.transfer + assert_equal "Hello World", transferred.get_string + assert_predicate buffer, :null? + assert_raise IO::Buffer::AccessError do + transferred.set_string("Goodbye") + end + assert_equal "Hello World", hello + end + + def test_transfer_in_block + hello = %w"Hello World".join(" ") + buffer = IO::Buffer.for(hello, &:transfer) + assert_equal "Hello World", buffer.get_string + buffer.set_string("Ciao!") + assert_equal "Ciao! World", hello + hello.freeze + assert_raise IO::Buffer::AccessError do + buffer.set_string("Hola") + end + assert_equal "Ciao! World", hello + end + + def test_locked + buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::LOCKED) + + assert_raise IO::Buffer::LockedError do + buffer.resize(256) + end + + assert_equal 128, buffer.size + + assert_raise IO::Buffer::LockedError do + buffer.free + end + + assert_equal 128, buffer.size + end + + def test_get_string + message = "Hello World 🤓" + + buffer = IO::Buffer.new(128) + buffer.set_string(message) + + chunk = buffer.get_string(0, message.bytesize, Encoding::UTF_8) + assert_equal message, chunk + assert_equal Encoding::UTF_8, chunk.encoding + + chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY) + assert_equal Encoding::BINARY, chunk.encoding + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(0, 129) + end + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(129) + end + + assert_raise_with_message(ArgumentError, /Offset can't be negative/) do + buffer.get_string(-1) + end + end + + # We check that values are correctly round tripped. + RANGES = { + :U8 => [0, 2**8-1], + :S8 => [-2**7, 0, 2**7-1], + + :U16 => [0, 2**16-1], + :S16 => [-2**15, 0, 2**15-1], + :u16 => [0, 2**16-1], + :s16 => [-2**15, 0, 2**15-1], + + :U32 => [0, 2**32-1], + :S32 => [-2**31, 0, 2**31-1], + :u32 => [0, 2**32-1], + :s32 => [-2**31, 0, 2**31-1], + + :U64 => [0, 2**64-1], + :S64 => [-2**63, 0, 2**63-1], + :u64 => [0, 2**64-1], + :s64 => [-2**63, 0, 2**63-1], + + :F32 => [-1.0, 0.0, 0.5, 1.0, 128.0], + :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0], + } + + def test_get_set_value + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + values.each do |value| + buffer.set_value(data_type, 0, value) + assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}." + end + end + end + + def test_get_set_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}." + end + end + + def test_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}." + end + end + + def test_each + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + data_type_size = IO::Buffer.size_of(data_type) + values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]} + + buffer.set_values(format, 0, values) + assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}." + end + end + + def test_each_byte + string = "The quick brown fox jumped over the lazy dog." + buffer = IO::Buffer.for(string) + + assert_equal string.bytes, buffer.each_byte.to_a + end + + def test_clear + buffer = IO::Buffer.new(16) + buffer.set_string("Hello World!") + end + + def test_invalidation + input, output = IO.pipe + + # (1) rb_write_internal creates IO::Buffer object, + buffer = IO::Buffer.new(128) + + # (2) it is passed to (malicious) scheduler + # (3) scheduler starts a thread which call system call with the buffer object + thread = Thread.new{buffer.locked{input.read}} + + Thread.pass until thread.stop? + + # (4) scheduler returns + # (5) rb_write_internal invalidate the buffer object + assert_raise IO::Buffer::LockedError do + buffer.free + end + + # (6) the system call access the memory area after invalidation + output.write("Hello World") + output.close + thread.join + + input.close + end + + def hello_world_tempfile(repeats = 1) + io = Tempfile.new + repeats.times do + io.write("Hello World") + end + io.seek(0) + + yield io + ensure + io&.close! + end + + def test_read + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_length + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, 5) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_offset + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, nil, 6) + assert_equal "Hello", buffer.get_string(6, 5) + end + end + + def test_read_with_length_and_offset + hello_world_tempfile(100) do |io| + buffer = IO::Buffer.new(1024) + # Only read 24 bytes from the file, as we are starting at offset 1000 in the buffer. + assert_equal 24, buffer.read(io, 0, 1000) + assert_equal "Hello World", buffer.get_string(1000, 11) + end + end + + def test_write + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello") + buffer.write(io) + + io.seek(0) + assert_equal "Hello", io.read(5) + ensure + io.close! + end + + def test_write_with_length_and_offset + io = Tempfile.new + + buffer = IO::Buffer.new(5) + buffer.set_string("Hello") + buffer.write(io, 4, 1) + + io.seek(0) + assert_equal "ello", io.read(4) + ensure + io.close! + end + + def test_pread + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5) + + assert_equal "World", buffer.get_string(0, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pread_offset + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5, 6) + + assert_equal "World", buffer.get_string(6, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pwrite + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("World") + buffer.pwrite(io, 6, 5) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_pwrite_offset + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello World") + buffer.pwrite(io, 6, 5, 6) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), (source & mask) + assert_equal IO::Buffer.for("1334133413"), (source | mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), (source ^ mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), ~source + end + + def test_inplace_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), source.dup.and!(mask) + assert_equal IO::Buffer.for("1334133413"), source.dup.or!(mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not! + end + + def test_shared + message = "Hello World" + buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED) + + pid = fork do + buffer.set_string(message) + end + + Process.wait(pid) + string = buffer.get_string(0, message.bytesize) + assert_equal message, string + rescue NotImplementedError + omit "Fork/shared memory is not supported." + end +end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index ccb124a46b..9c14087eba 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -23,7 +23,8 @@ class TestIO_M17N < Test::Unit::TestCase def pipe(*args, wp, rp) re, we = nil, nil - r, w = IO.pipe(*args) + kw = args.last.is_a?(Hash) ? args.pop : {} + r, w = IO.pipe(*args, **kw) rt = Thread.new do begin rp.call(r) @@ -775,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 @@ -1141,8 +1142,18 @@ EOT IO.pipe do |r, w| assert_nothing_raised(bug5567) do assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx")} + w.puts("foo") + assert_equal("foo\n", r.gets) assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")} + w.puts("bar") + assert_equal("bar\n", r.gets) assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")} + w.puts("zot") + begin + assert_equal("zot\n", r.gets) + rescue Encoding::ConverterNotFoundError => e + assert_match(/\((\S+) to \1\)/, e.message) + end end end end @@ -2046,19 +2057,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 @@ -2080,26 +2091,53 @@ EOT } end - def test_strip_bom - with_tmpdir { - text = "\uFEFFa" - stripped = "a" - %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name| - path = '%s-bom.txt' % name + %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name| + define_method("test_strip_bom:#{name}") do + path = "#{name}-bom.txt" + with_tmpdir { + text = "\uFEFF\u0100a" + stripped = "\u0100a" content = text.encode(name) generate_file(path, content) result = File.read(path, mode: 'rb:BOM|UTF-8') - assert_equal(content[1].force_encoding("ascii-8bit"), - result.force_encoding("ascii-8bit")) - result = File.read(path, mode: 'rb:BOM|UTF-8:UTF-8') - assert_equal(Encoding::UTF_8, result.encoding) - assert_equal(stripped, result) - end + assert_equal(Encoding.find(name), result.encoding, name) + assert_equal(content[1..-1].b, result.b, name) + %w[rb rt r].each do |mode| + message = "#{name}, mode: #{mode.dump}" + result = File.read(path, mode: "#{mode}:BOM|UTF-8:UTF-8") + assert_equal(Encoding::UTF_8, result.encoding, message) + assert_equal(stripped, result, message) + end - bug3407 = '[ruby-core:30641]' + File.open(path, "rb") {|f| + assert_equal(Encoding.find(name), f.set_encoding_by_bom) + } + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } + } + end + end + + def test_strip_bom_no_conv + with_tmpdir { path = 'UTF-8-bom.txt' + generate_file(path, "\uFEFFa") + + bug3407 = '[ruby-core:30641]' result = File.read(path, encoding: 'BOM|UTF-8') - assert_equal("a", result.force_encoding("ascii-8bit"), bug3407) + assert_equal("a", result.b, bug3407) + + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } + } + end + + def test_strip_bom_invalid + with_tmpdir { + path = 'UTF-8-bom.txt' + generate_file(path, "\uFEFFa") bug8323 = '[ruby-core:54563] [Bug #8323]' expected = "a\xff".force_encoding("utf-8") @@ -2110,23 +2148,38 @@ EOT result = File.read(path, encoding: 'BOM|UTF-8:UTF-8') assert_not_predicate(result, :valid_encoding?, bug8323) assert_equal(expected, result, bug8323) + } + end + def test_strip_bom_no_bom + with_tmpdir { + bug8323 = '[ruby-core:54563] [Bug #8323]' path = 'ascii.txt' + stripped = "a" generate_file(path, stripped) result = File.read(path, encoding: 'BOM|UTF-8') assert_equal(stripped, result, bug8323) result = File.read(path, encoding: 'BOM|UTF-8:UTF-8') assert_equal(stripped, result, bug8323) + + File.open(path, "rb") {|f| + assert_nil(f.set_encoding_by_bom) + } + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } } end def test_bom_too_long_utfname - assert_separately([], <<-'end;') # do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; assert_warn(/Unsupported encoding/) { open(IO::NULL, "r:bom|utf-" + "x" * 10000) {} } end; - assert_separately([], <<-'end;') # do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; assert_warn(/Unsupported encoding/) { open(IO::NULL, encoding: "bom|utf-" + "x" * 10000) {} } @@ -2168,6 +2221,20 @@ EOT assert_nil(enc) end + def test_bom_non_reading + with_tmpdir { + enc = nil + assert_nothing_raised(IOError) { + open("test", "w:bom|utf-8") {|f| + enc = f.external_encoding + f.print("abc") + } + } + assert_equal(Encoding::UTF_8, enc) + assert_equal("abc", File.binread("test")) + } + end + def test_cbuf with_tmpdir { fn = "tst" diff --git a/test/ruby/test_io_timeout.rb b/test/ruby/test_io_timeout.rb new file mode 100644 index 0000000000..e017395980 --- /dev/null +++ b/test/ruby/test_io_timeout.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: false + +require 'io/nonblock' + +class TestIOTimeout < Test::Unit::TestCase + def with_pipe + omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + + begin + i, o = UNIXSocket.pair + + yield i, o + ensure + i.close + o.close + end + end + + def test_timeout_attribute + with_pipe do |i, o| + assert_nil i.timeout + + i.timeout = 10 + assert_equal 10, i.timeout + assert_nil o.timeout + + o.timeout = 20 + assert_equal 20, o.timeout + assert_equal 10, i.timeout + end + end + + def test_timeout_read_exception + with_pipe do |i, o| + i.timeout = 0.0001 + + assert_raise(IO::TimeoutError) {i.read} + end + end + + def test_timeout_gets_exception + with_pipe do |i, o| + i.timeout = 0.0001 + + assert_raise(IO::TimeoutError) {i.gets} + end + end + + def test_timeout_puts + with_pipe do |i, o| + i.timeout = 0.0001 + o.puts("Hello World") + o.close + + assert_equal "Hello World", i.gets.chomp + end + end +end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 4de2aa7169..f5147831e4 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,99 @@ 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, /refer unshareable object \[\] from variable `\*'/) do + Ractor.make_shareable(obj.foo) + end + end + + def test_ractor_shareable_value_frozen_core + iseq = RubyVM::InstructionSequence.compile(<<~'RUBY') + # shareable_constant_value: literal + REGEX = /#{}/ # [Bug #20569] + RUBY + assert_includes iseq.to_binary, "REGEX".b + end + def test_disasm_encoding src = "\u{3042} = 1; \u{3042}; \u{3043}" asm = compile(src).disasm @@ -91,6 +184,27 @@ class TestISeq < Test::Unit::TestCase asm = compile(src).disasm assert_equal(src.encoding, asm.encoding) assert_predicate(asm, :valid_encoding?) + + obj = Object.new + name = "\u{2603 26a1}" + obj.instance_eval("def #{name}; tap {}; end") + 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__ @@ -103,16 +217,16 @@ class TestISeq < Test::Unit::TestCase end def test_line_trace - iseq = compile \ - %q{ a = 1 + iseq = compile(<<~EOF, __LINE__+1) + a = 1 b = 2 c = 3 # d = 4 e = 5 # f = 6 g = 7 + EOF - } assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all) iseq.line_trace_specify(1, true) # line 2 iseq.line_trace_specify(3, true) # line 5 @@ -182,8 +296,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. @@ -241,9 +355,19 @@ class TestISeq < Test::Unit::TestCase end end assert_equal([m1, e1.message], [m2, e2.message], feature11951) - e1, e2 = e1.message.lines - assert_send([e1, :start_with?, __FILE__]) - assert_send([e2, :start_with?, __FILE__]) + message = e1.message.each_line + message.with_index(1) do |line, i| + next if /^ / =~ line + assert_send([line, :start_with?, __FILE__], + proc {message.map {|l, j| (i == j ? ">" : " ") + l}.join("")}) + end + end + + # [Bug #19173] + def test_compile_error + assert_raise SyntaxError do + RubyVM::InstructionSequence.compile 'using Module.new; yield' + end end def test_compile_file_error @@ -251,7 +375,7 @@ class TestISeq < Test::Unit::TestCase f.puts "end" f.close path = f.path - assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /keyword_end/, [], success: true) + assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /unexpected `end'/, [], success: true) begin; path = ARGV[0] begin @@ -275,9 +399,407 @@ class TestISeq < Test::Unit::TestCase def test_inspect %W[foo \u{30d1 30b9}].each do |name| - assert_match /@#{name}/, ISeq.compile("", name).inspect, name + assert_match(/@#{name}/, ISeq.compile("", name).inspect, name) m = ISeq.compile("class TestISeq::Inspect; def #{name}; end; instance_method(:#{name}); end").eval - assert_match /:#{name}@/, ISeq.of(m).inspect, name + assert_match(/:#{name}@/, ISeq.of(m).inspect, name) + end + end + + def anon_star(*); end + + def test_anon_rest_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_star)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:*], param_names + end + + def anon_keyrest(**); end + + def test_anon_keyrest_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:**], param_names + end + + def anon_block(&); end + + def test_anon_block_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_block)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:&], param_names + end + + def strip_lineno(source) + source.gsub(/^.*?: /, "") + end + + def sample_iseq + ISeq.compile(strip_lineno(<<-EOS)) + 1: class C + 2: def foo + 3: begin + 4: rescue + 5: p :rescue + 6: ensure + 7: p :ensure + 8: end + 9: end + 10: def bar + 11: 1.times{ + 12: 2.times{ + 13: } + 14: } + 15: end + 16: end + 17: class D < C + 18: end + EOS + end + + def test_each_child + iseq = sample_iseq + + collect_iseq = lambda{|iseq| + iseqs = [] + iseq.each_child{|child_iseq| + iseqs << collect_iseq.call(child_iseq) + } + ["#{iseq.label}@#{iseq.first_lineno}", *iseqs.sort_by{|k, *| k}] + } + + expected = ["<compiled>@1", + ["<class:C>@1", + ["bar@10", ["block in bar@11", + ["block (2 levels) in bar@12"]]], + ["foo@2", ["ensure in foo@2"], + ["rescue in foo@4"]]], + ["<class:D>@17"]] + + assert_equal expected, collect_iseq.call(iseq) + end + + def test_trace_points + collect_iseq = lambda{|iseq| + iseqs = [] + iseq.each_child{|child_iseq| + iseqs << collect_iseq.call(child_iseq) + } + [["#{iseq.label}@#{iseq.first_lineno}", iseq.trace_points], *iseqs.sort_by{|k, *| k}] + } + assert_equal [["<compiled>@1", [[1, :line], + [17, :line]]], + [["<class:C>@1", [[1, :class], + [2, :line], + [10, :line], + [16, :end]]], + [["bar@10", [[10, :call], + [11, :line], + [15, :return]]], + [["block in bar@11", [[11, :b_call], + [12, :line], + [14, :b_return]]], + [["block (2 levels) in bar@12", [[12, :b_call], + [13, :b_return]]]]]], + [["foo@2", [[2, :call], + [4, :line], + [7, :line], + [9, :return]]], + [["ensure in foo@2", [[7, :line]]]], + [["rescue in foo@4", [[5, :line]]]]]], + [["<class:D>@17", [[17, :class], + [18, :end]]]]], collect_iseq.call(sample_iseq) + end + + def test_empty_iseq_lineno + iseq = ISeq.compile(<<-EOS) + # 1 + # 2 + def foo # line 3 empty method + end # line 4 + 1.time do # line 5 empty block + end # line 6 + class C # line 7 empty class + end + EOS + + iseq.each_child{|ci| + ary = ci.to_a + type = ary[9] + name = ary[5] + line = ary[13].first + case type + when :method + assert_equal "foo", name + assert_equal 3, line + when :class + assert_equal '<class:C>', name + assert_equal 7, line + when :block + assert_equal 'block in <compiled>', name + assert_equal 5, line + else + raise "unknown ary: " + ary.inspect + end + } + end + + def hexdump(bin) + bin.unpack1("H*").gsub(/.{1,32}/) {|s| + "#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n" + } + end + + def assert_iseq_to_binary(code, mesg = nil) + iseq = RubyVM::InstructionSequence.compile(code) + bin = assert_nothing_raised(mesg) do + iseq.to_binary + rescue RuntimeError => e + omit e.message if /compile with coverage/ =~ e.message + raise + end + 10.times do + bin2 = iseq.to_binary + assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)}) + end + iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) + 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 + + def test_to_binary_with_objects + assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join) + assert_iseq_to_binary("@x ||= (1..2)") + end + + def test_to_binary_pattern_matching + code = "case foo; in []; end" + iseq = compile(code) + assert_include(iseq.disasm, "TypeError") + assert_include(iseq.disasm, "NoMatchingPatternError") + EnvUtil.suppress_warning do + assert_iseq_to_binary(code, "[Feature #14912]") + end + end + + def test_to_binary_dumps_nokey + iseq = assert_iseq_to_binary(<<-RUBY) + o = Object.new + class << o + def foo(**nil); end + end + o + RUBY + assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters) + end + + def test_to_binary_line_info + assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval + begin; + class P + def p; end + def q; end + E = "" + N = "#{E}" + attr_reader :i + end + end; + + # cleanup + ::Object.class_eval do + remove_const :P + end + end + + def collect_from_binary_tracepoint_lines(tracepoint_type, filename) + iseq = RubyVM::InstructionSequence.compile(strip_lineno(<<-RUBY), filename) + class A + class B + 2.times { + def self.foo + _a = 'good day' + raise + rescue + 'dear reader' + end + } + end + B.foo + end + RUBY + + iseq_bin = iseq.to_binary + iseq = ISeq.load_from_binary(iseq_bin) + lines = [] + TracePoint.new(tracepoint_type){|tp| + next unless tp.path == filename + lines << tp.lineno + }.enable{ + EnvUtil.suppress_warning {iseq.eval} + } + + lines + end + + def test_to_binary_line_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:line, filename) + + assert_equal [1, 2, 3, 4, 4, 12, 5, 6, 8], lines, '[Bug #14702]' + end + + def test_to_binary_class_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:class, filename) + + assert_equal [1, 2], lines, '[Bug #14702]' + end + + def test_to_binary_end_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:end, filename) + + assert_equal [11, 13], lines, '[Bug #14702]' + end + + def test_to_binary_return_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:return, filename) + + assert_equal [9], lines, '[Bug #14702]' + end + + def test_to_binary_b_call_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:b_call, filename) + + assert_equal [3, 3], lines, '[Bug #14702]' + end + + def test_to_binary_b_return_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:b_return, filename) + + assert_equal [10, 10], lines, '[Bug #14702]' + end + + def test_iseq_of + [proc{}, + method(:test_iseq_of), + RubyVM::InstructionSequence.compile("p 1", __FILE__)].each{|src| + iseq = RubyVM::InstructionSequence.of(src) + assert_equal __FILE__, iseq.path + } + end + + def test_iseq_of_twice_for_same_code + [proc{}, + method(:test_iseq_of_twice_for_same_code), + RubyVM::InstructionSequence.compile("p 1")].each{|src| + iseq1 = RubyVM::InstructionSequence.of(src) + iseq2 = RubyVM::InstructionSequence.of(src) + + # ISeq objects should be same for same src + assert_equal iseq1.object_id, iseq2.object_id + } + end + + def test_iseq_builtin_to_a + 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 + + def test_unreachable_pattern_matching + assert_in_out_err([], "true or 1 in 1") + assert_in_out_err([], "true or (case 1; in 1; 1; in 2; 2; end)") + end + + def test_unreachable_pattern_matching_in_if_condition + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[1]) + begin; + if true or {a: 0} in {a:} + p 1 + else + p a + end + end; + end + + def test_unreachable_next_in_block + bug20344 = '[ruby-core:117210] [Bug #20344]' + assert_nothing_raised(SyntaxError, bug20344) do + compile(<<~RUBY) + proc do + next + + case nil + when "a" + next + when "b" + when "c" + proc {} + end + + next + end + RUBY end end end diff --git a/test/ruby/test_iterator.rb b/test/ruby/test_iterator.rb index 9bfa947607..820d5591c1 100644 --- a/test/ruby/test_iterator.rb +++ b/test/ruby/test_iterator.rb @@ -12,17 +12,12 @@ class Array end class TestIterator < Test::Unit::TestCase - def ttt - assert(iterator?) - end - - def test_iterator - assert(!iterator?) - - ttt{} - - # yield at top level !! here's not toplevel - assert(!defined?(yield)) + def test_yield_at_toplevel + assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert(!block_given?) + assert(!defined?(yield)) + end; end def test_array @@ -107,6 +102,16 @@ class TestIterator < Test::Unit::TestCase assert_equal([1, 2, 3, 4, 5, 6, 7], x) end + def test_array_for_masgn + a = [Struct.new(:to_ary).new([1,2])] + x = [] + a.each {|i,j|x << [i,j]} + assert_equal([[1,2]], x) + x = [] + for i,j in a; x << [i,j]; end + assert_equal([[1,2]], x) + end + def test_append_method_to_built_in_class x = [[1,2],[3,4],[5,6]] assert_equal(x.iter_test1{|e|e}, x.iter_test2{|e|e}) @@ -334,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_key_error.rb b/test/ruby/test_key_error.rb new file mode 100644 index 0000000000..fe1d5bb5ab --- /dev/null +++ b/test/ruby/test_key_error.rb @@ -0,0 +1,42 @@ +require 'test/unit' + +class TestKeyError < Test::Unit::TestCase + def test_default + error = KeyError.new + assert_equal("KeyError", error.message) + end + + def test_message + error = KeyError.new("Message") + assert_equal("Message", error.message) + end + + def test_receiver + receiver = Object.new + error = KeyError.new(receiver: receiver) + assert_equal(receiver, error.receiver) + error = KeyError.new + assert_raise(ArgumentError) {error.receiver} + end + + def test_key + error = KeyError.new(key: :key) + assert_equal(:key, error.key) + error = KeyError.new + assert_raise(ArgumentError) {error.key} + end + + def test_receiver_and_key + receiver = Object.new + error = KeyError.new(receiver: receiver, key: :key) + assert_equal([receiver, :key], + [error.receiver, error.key]) + end + + def test_all + receiver = Object.new + error = KeyError.new("Message", receiver: receiver, key: :key) + assert_equal(["Message", receiver, :key], + [error.message, error.receiver, error.key]) + end +end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index f4cc7bc9d6..94b5323ab0 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -1,5 +1,7 @@ # frozen_string_literal: false require 'test/unit' +require '-test-/rb_call_super_kw' +require '-test-/iter' class TestKeywordArguments < Test::Unit::TestCase def f1(str: "foo", num: 424242) @@ -22,7 +24,7 @@ class TestKeywordArguments < Test::Unit::TestCase def test_f2 assert_equal([:xyz, "foo", 424242], f2(:xyz)) - assert_equal([{"bar"=>42}, "foo", 424242], f2("bar"=>42)) + assert_raise(ArgumentError) { f2("bar"=>42) } end @@ -126,6 +128,47 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(1, f10(b: 42)) end + def f11(**nil) + local_variables + end + + def test_f11 + h = {} + + assert_equal([], f11) + assert_equal([], f11(**{})) + assert_equal([], f11(**h)) + end + + def f12(**nil, &b) + [b, local_variables] + end + + def test_f12 + h = {} + b = proc{} + + assert_equal([nil, [:b]], f12) + assert_equal([nil, [:b]], f12(**{})) + assert_equal([nil, [:b]], f12(**h)) + assert_equal([b, [:b]], f12(&b)) + assert_equal([b, [:b]], f12(**{}, &b)) + assert_equal([b, [:b]], f12(**h, &b)) + end + + def f13(a, **nil) + a + end + + def test_f13 + assert_equal(1, f13(1)) + assert_equal(1, f13(1, **{})) + assert_raise(ArgumentError) { f13(a: 1) } + assert_raise(ArgumentError) { f13(1, a: 1) } + assert_raise(ArgumentError) { f13(**{a: 1}) } + assert_raise(ArgumentError) { f13(1, **{a: 1}) } + end + def test_method_parameters assert_equal([[:key, :str], [:key, :num]], method(:f1).parameters); assert_equal([[:req, :x], [:key, :str], [:key, :num]], method(:f2).parameters); @@ -147,6 +190,3261 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal(["bar", 111111], f[str: "bar", num: 111111]) end + def test_unset_hash_flag + bug18625 = "[ruby-core: 107847]" + singleton_class.class_eval do + ruby2_keywords def foo(*args) + args + end + + def single(arg) + arg + end + + def splat(*args) + args.last + end + + def kwargs(**kw) + kw + end + end + + h = { a: 1 } + args = foo(**h) + marked = args.last + assert_equal(true, Hash.ruby2_keywords_hash?(marked)) + + after_usage = single(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage)) + + after_usage = splat(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage, bug18625) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage), bug18625) + + after_usage = kwargs(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage, bug18625) + assert_not_same(marked, after_usage) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage)) + + assert_equal(true, Hash.ruby2_keywords_hash?(marked)) + end + + def test_keyword_splat_new + kw = {} + h = {a: 1} + + 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.yo(**kw) kw end + m = method(:yo) + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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.(:yo, **{}).frozen?) + assert_equal_not_same(kw, m.(:yo, **kw)) + assert_equal_not_same(h, m.(:yo, **h)) + assert_equal(false, m.send(:call, :yo, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, :yo, **kw)) + assert_equal_not_same(h, m.send(:call, :yo, **h)) + + singleton_class.send(:remove_method, :yo) + define_singleton_method(:yo) { |**kw| kw } + m = method(:yo) + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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)) + + yo = lambda { |**kw| kw } + m = yo.method(:call) + assert_equal(false, yo.(**{}).frozen?) + assert_equal_not_same(kw, yo.(**kw)) + assert_equal_not_same(h, yo.(**h)) + assert_equal(false, yo.send(:call, **{}).frozen?) + assert_equal_not_same(kw, yo.send(:call, **kw)) + assert_equal_not_same(h, yo.send(:call, **h)) + assert_equal(false, yo.public_send(:call, **{}).frozen?) + assert_equal_not_same(kw, yo.public_send(:call, **kw)) + assert_equal_not_same(h, yo.public_send(:call, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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)) + + yo = :yo.to_proc + m = yo.method(:call) + assert_equal(false, yo.(self, **{}).frozen?) + assert_equal_not_same(kw, yo.(self, **kw)) + assert_equal_not_same(h, yo.(self, **h)) + assert_equal(false, yo.send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, yo.send(:call, self, **kw)) + assert_equal_not_same(h, yo.send(:call, self, **h)) + assert_equal(false, yo.public_send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, yo.public_send(:call, self, **kw)) + assert_equal_not_same(h, yo.public_send(:call, self, **h)) + assert_equal(false, m.(self, **{}).frozen?) + assert_equal_not_same(kw, m.(self, **kw)) + assert_equal_not_same(h, m.(self, **h)) + 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 yo(**kw) kw end + end + o = c.new + def o.yo(**kw) super end + m = o.method(:yo) + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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, :yo) + def o.yo(**kw) super(**kw) end + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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.yo(**kw) super end + m = o.method(:yo) + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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, :yo) + def o.yo(**kw) super(**kw) end + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + 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, :yo) + def self.method_missing(_, **kw) kw end + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + end + + def test_regular_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.m(**{})) + assert_nil(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(:m) + def c.m(args) + args + 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)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c.singleton_class.remove_method(:m) + def c.m(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) } + assert_equal([h, kw], c.m(h)) + assert_equal([h2, kw], c.m(h2)) + assert_equal([h3, kw], c.m(h3)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], 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)) + end + + def test_implicit_super_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new + c = sc.new + def c.m(*args, **kw) + super(*args, **kw) + end + sc.class_eval do + def m(*args) + args + end + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m; end + end + assert_nil(c.m(**{})) + assert_nil(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) + def m(args) + args + end + 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m(**args) + args + end + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + sc.class_eval do + remove_method(:m) + def m(arg, **args) + [arg, args] + end + 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) + def m(arg=1, **args) + [arg, args] + end + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_explicit_super_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new + c = sc.new + def c.m(*args, **kw) + super(*args, **kw) + end + sc.class_eval do + def m(*args) + args + end + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m; end + end + assert_nil(c.m(**{})) + assert_nil(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) + def m(args) + args + end + 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m(**args) + args + end + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + sc.class_eval do + remove_method(:m) + def m(arg, **args) + [arg, args] + end + 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) + def m(arg=1, **args) + [arg, args] + end + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_lambda_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + f = -> { true } + assert_equal(true, f[**{}]) + assert_equal(true, f[**kw]) + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + + f = ->(a) { a } + 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]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + + f = ->(**x) { x } + assert_equal(kw, f[**{}]) + assert_equal(kw, f[**kw]) + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + assert_raise(ArgumentError) { f[h] } + assert_raise(ArgumentError) { f[h2] } + assert_raise(ArgumentError) { f[h3] } + + f = ->(a, **x) { [a,x] } + 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[**{}]) + assert_equal([1, kw], f[**kw]) + assert_equal([1, h], f[**h]) + assert_equal([1, h], f[a: 1]) + assert_equal([1, h2], f[**h2]) + assert_equal([1, h3], f[**h3]) + assert_equal([1, h3], f[a: 1, **h2]) + end + + def test_lambda_method_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + f = -> { true } + f = f.method(:call) + assert_equal(true, f[**{}]) + assert_equal(true, f[**kw]) + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + + f = ->(a) { a } + f = f.method(:call) + 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]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + + f = ->(**x) { x } + f = f.method(:call) + assert_equal(kw, f[**{}]) + assert_equal(kw, f[**kw]) + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + assert_raise(ArgumentError) { f[h] } + assert_raise(ArgumentError) { f[h2] } + assert_raise(ArgumentError) { f[h3] } + + f = ->(a, **x) { [a,x] } + f = f.method(:call) + 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) + assert_equal([1, kw], f[**{}]) + assert_equal([1, kw], f[**kw]) + assert_equal([1, h], f[**h]) + assert_equal([1, h], f[a: 1]) + assert_equal([1, h2], f[**h2]) + assert_equal([1, h3], f[**h3]) + assert_equal([1, h3], f[a: 1, **h2]) + end + + def test_Thread_new_kwsplat + Thread.report_on_exception = false + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + t = Thread + f = -> { true } + assert_equal(true, t.new(**{}, &f).value) + assert_equal(true, 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 } + + f = ->(a) { a } + 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) + assert_equal(h3, t.new(**h3, &f).value) + assert_equal(h3, t.new(a: 1, **h2, &f).value) + + f = ->(**x) { x } + assert_equal(kw, t.new(**{}, &f).value) + assert_equal(kw, 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) + assert_equal(h3, t.new(**h3, &f).value) + assert_equal(h3, t.new(a: 1, **h2, &f).value) + assert_raise(ArgumentError) { t.new(h, &f).value } + assert_raise(ArgumentError) { t.new(h2, &f).value } + assert_raise(ArgumentError) { t.new(h3, &f).value } + + f = ->(a, **x) { [a,x] } + 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) + assert_equal([1, kw], t.new(**kw, &f).value) + assert_equal([1, h], t.new(**h, &f).value) + assert_equal([1, h], t.new(a: 1, &f).value) + assert_equal([1, h2], t.new(**h2, &f).value) + assert_equal([1, h3], t.new(**h3, &f).value) + assert_equal([1, h3], t.new(a: 1, **h2, &f).value) + ensure + Thread.report_on_exception = true + end + + def test_Fiber_resume_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + t = Fiber + f = -> { true } + assert_equal(true, t.new(&f).resume(**{})) + assert_equal(true, 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) } + + f = ->(a) { a } + 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)) + assert_equal(h3, t.new(&f).resume(**h3)) + assert_equal(h3, t.new(&f).resume(a: 1, **h2)) + + f = ->(**x) { x } + assert_equal(kw, t.new(&f).resume(**{})) + assert_equal(kw, 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)) + assert_equal(h3, t.new(&f).resume(**h3)) + assert_equal(h3, t.new(&f).resume(a: 1, **h2)) + assert_raise(ArgumentError) { t.new(&f).resume(h) } + assert_raise(ArgumentError) { t.new(&f).resume(h2) } + assert_raise(ArgumentError) { t.new(&f).resume(h3) } + + f = ->(a, **x) { [a,x] } + 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(**{})) + assert_equal([1, kw], t.new(&f).resume(**kw)) + assert_equal([1, h], t.new(&f).resume(**h)) + assert_equal([1, h], t.new(&f).resume(a: 1)) + assert_equal([1, h2], t.new(&f).resume(**h2)) + assert_equal([1, h3], t.new(&f).resume(**h3)) + assert_equal([1, h3], t.new(&f).resume(a: 1, **h2)) + end + + def test_Enumerator_Generator_each_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + g = Enumerator::Generator + f = ->(_) { true } + assert_equal(true, g.new(&f).each(**{})) + assert_equal(true, 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) } + + f = ->(_, a) { a } + 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)) + assert_equal(h3, g.new(&f).each(**h3)) + assert_equal(h3, g.new(&f).each(a: 1, **h2)) + + f = ->(_, **x) { x } + assert_equal(kw, g.new(&f).each(**{})) + assert_equal(kw, 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)) + assert_equal(h3, g.new(&f).each(**h3)) + assert_equal(h3, g.new(&f).each(a: 1, **h2)) + assert_raise(ArgumentError) { g.new(&f).each(h) } + assert_raise(ArgumentError) { g.new(&f).each(h2) } + assert_raise(ArgumentError) { g.new(&f).each(h3) } + + f = ->(_, a, **x) { [a,x] } + 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(**{})) + assert_equal([1, kw], g.new(&f).each(**kw)) + assert_equal([1, h], g.new(&f).each(**h)) + assert_equal([1, h], g.new(&f).each(a: 1)) + assert_equal([1, h2], g.new(&f).each(**h2)) + assert_equal([1, h3], g.new(&f).each(**h3)) + assert_equal([1, h3], g.new(&f).each(a: 1, **h2)) + end + + def test_Enumerator_Yielder_yield_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + g = Enumerator::Generator + f = -> { true } + assert_equal(true, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(true, 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) } + + f = ->(a) { a } + 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)) + assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + + f = ->(**x) { x } + assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(kw, 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)) + 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_raise(ArgumentError) { g.new{|y| y.yield(h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) } + + f = ->(a, **x) { [a,x] } + 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)) + assert_equal([1, kw], g.new{|y| y.yield(**kw)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(**h)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal([1, h2], g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + end + + def test_Class_new_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new do + attr_reader :args + class << self + alias [] new + end + end + + c = Class.new(sc) do + def initialize(*args) + @args = args + end + end + assert_equal([], c[**{}].args) + assert_equal([], c[**kw].args) + assert_equal([h], c[**h].args) + assert_equal([h], c[a: 1].args) + assert_equal([h2], c[**h2].args) + assert_equal([h3], c[**h3].args) + assert_equal([h3], c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize; end + end + assert_nil(c[**{}].args) + assert_nil(c[**kw].args) + assert_raise(ArgumentError) { c[**h] } + assert_raise(ArgumentError) { c[a: 1] } + assert_raise(ArgumentError) { c[**h2] } + assert_raise(ArgumentError) { c[**h3] } + assert_raise(ArgumentError) { c[a: 1, **h2] } + + c = Class.new(sc) do + def initialize(args) + @args = args + end + 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) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize(**args) + @args = args + end + end + assert_equal(kw, c[**{}].args) + assert_equal(kw, c[**kw].args) + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + assert_raise(ArgumentError) { c[h].args } + assert_raise(ArgumentError) { c[h2].args } + assert_raise(ArgumentError) { c[h3].args } + + c = Class.new(sc) do + def initialize(arg, **args) + @args = [arg, args] + end + 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) + @args = [arg, args] + end + end + assert_equal([1, kw], c[**{}].args) + assert_equal([1, kw], c[**kw].args) + assert_equal([1, h], c[**h].args) + assert_equal([1, h], c[a: 1].args) + assert_equal([1, h2], c[**h2].args) + assert_equal([1, h3], c[**h3].args) + assert_equal([1, h3], c[a: 1, **h2].args) + end + + def test_Class_new_method_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new do + attr_reader :args + end + + c = Class.new(sc) do + def initialize(*args) + @args = args + end + end.method(:new) + assert_equal([], c[**{}].args) + assert_equal([], c[**kw].args) + assert_equal([h], c[**h].args) + assert_equal([h], c[a: 1].args) + assert_equal([h2], c[**h2].args) + assert_equal([h3], c[**h3].args) + assert_equal([h3], c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize; end + end.method(:new) + assert_nil(c[**{}].args) + assert_nil(c[**kw].args) + assert_raise(ArgumentError) { c[**h] } + assert_raise(ArgumentError) { c[a: 1] } + assert_raise(ArgumentError) { c[**h2] } + assert_raise(ArgumentError) { c[**h3] } + assert_raise(ArgumentError) { c[a: 1, **h2] } + + c = Class.new(sc) do + def initialize(args) + @args = args + end + end.method(:new) + 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) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize(**args) + @args = args + end + end.method(:new) + assert_equal(kw, c[**{}].args) + assert_equal(kw, c[**kw].args) + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + assert_raise(ArgumentError) { c[h].args } + assert_raise(ArgumentError) { c[h2].args } + assert_raise(ArgumentError) { c[h3].args } + + c = Class.new(sc) do + def initialize(arg, **args) + @args = [arg, args] + end + end.method(:new) + 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) + @args = [arg, args] + end + end.method(:new) + assert_equal([1, kw], c[**{}].args) + assert_equal([1, kw], c[**kw].args) + assert_equal([1, h], c[**h].args) + assert_equal([1, h], c[a: 1].args) + assert_equal([1, h2], c[**h2].args) + assert_equal([1, h3], c[**h3].args) + assert_equal([1, h3], c[a: 1, **h2].args) + end + + def test_Method_call_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.method(:m)[**{}]) + assert_equal([], 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]) + assert_equal([h3], c.method(:m)[**h3]) + assert_equal([h3], c.method(:m)[a: 1, **h2]) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.method(:m)[**{}]) + assert_nil(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(args) + args + 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]) + assert_equal(h3, c.method(:m)[**h3]) + assert_equal(h3, c.method(:m)[a: 1, **h2]) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.method(:m)[**{}]) + assert_equal(kw, 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]) + assert_equal(h3, c.method(:m)[**h3]) + assert_equal(h3, c.method(:m)[a: 1, **h2]) + assert_raise(ArgumentError) { c.method(:m)[h] } + assert_raise(ArgumentError) { c.method(:m)[h2] } + assert_raise(ArgumentError) { c.method(:m)[h3] } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], c.method(:m)[**{}]) + assert_equal([1, kw], c.method(:m)[**kw]) + assert_equal([1, h], c.method(:m)[**h]) + assert_equal([1, h], c.method(:m)[a: 1]) + assert_equal([1, h2], c.method(:m)[**h2]) + assert_equal([1, h3], c.method(:m)[**h3]) + assert_equal([1, h3], c.method(:m)[a: 1, **h2]) + end + + def test_UnboundMethod_bindcall_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + sc = c.singleton_class + def c.m(*args) + args + end + assert_equal([], sc.instance_method(:m).bind_call(c, **{})) + assert_equal([], 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)) + assert_equal([h3], sc.instance_method(:m).bind_call(c, **h3)) + assert_equal([h3], sc.instance_method(:m).bind_call(c, a: 1, **h2)) + + sc.remove_method(:m) + def c.m; end + assert_nil(sc.instance_method(:m).bind_call(c, **{})) + assert_nil(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(args) + args + 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)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2)) + + sc.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, sc.instance_method(:m).bind_call(c, **{})) + assert_equal(kw, 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)) + 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_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h2) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h3) } + + sc.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **{})) + assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **kw)) + assert_equal([1, h], sc.instance_method(:m).bind_call(c, **h)) + assert_equal([1, h], sc.instance_method(:m).bind_call(c, a: 1)) + assert_equal([1, h2], sc.instance_method(:m).bind_call(c, **h2)) + assert_equal([1, h3], sc.instance_method(:m).bind_call(c, **h3)) + assert_equal([1, h3], sc.instance_method(:m).bind_call(c, a: 1, **h2)) + end + + def test_send_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.send(:m, **{})) + assert_equal([], 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)) + assert_equal([h3], c.send(:m, **h3)) + assert_equal([h3], c.send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.send(:m, **{})) + assert_nil(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(args) + args + 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)) + assert_equal(h3, c.send(:m, **h3)) + assert_equal(h3, c.send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.send(:m, **{})) + assert_equal(kw, 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)) + assert_equal(h3, c.send(:m, **h3)) + assert_equal(h3, c.send(:m, a: 1, **h2)) + assert_raise(ArgumentError) { c.send(:m, h) } + assert_raise(ArgumentError) { c.send(:m, h2) } + assert_raise(ArgumentError) { c.send(:m, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], c.send(:m, **{})) + assert_equal([1, kw], c.send(:m, **kw)) + assert_equal([1, h], c.send(:m, **h)) + assert_equal([1, h], c.send(:m, a: 1)) + assert_equal([1, h2], c.send(:m, **h2)) + assert_equal([1, h3], c.send(:m, **h3)) + assert_equal([1, h3], c.send(:m, a: 1, **h2)) + end + + def test_public_send_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.public_send(:m, **{})) + assert_equal([], 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)) + assert_equal([h3], c.public_send(:m, **h3)) + assert_equal([h3], c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.public_send(:m, **{})) + assert_nil(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(args) + args + 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)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.public_send(:m, **{})) + assert_equal(kw, 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)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + assert_raise(ArgumentError) { c.public_send(:m, h) } + assert_raise(ArgumentError) { c.public_send(:m, h2) } + assert_raise(ArgumentError) { c.public_send(:m, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], c.public_send(:m, **{})) + assert_equal([1, kw], c.public_send(:m, **kw)) + assert_equal([1, h], c.public_send(:m, **h)) + assert_equal([1, h], c.public_send(:m, a: 1)) + assert_equal([1, h2], c.public_send(:m, **h2)) + assert_equal([1, h3], c.public_send(:m, **h3)) + assert_equal([1, h3], c.public_send(:m, a: 1, **h2)) + end + + def test_send_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = c.method(:send) + assert_equal([], m.call(:m, **{})) + assert_equal([], 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)) + assert_equal([h3], m.call(:m, **h3)) + assert_equal([h3], m.call(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + m = c.method(:send) + assert_nil(m.call(:m, **{})) + assert_nil(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(args) + args + end + m = c.method(:send) + 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)) + assert_equal(h3, m.call(:m, **h3)) + assert_equal(h3, m.call(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + m = c.method(:send) + assert_equal(kw, m.call(:m, **{})) + assert_equal(kw, 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)) + assert_equal(h3, m.call(:m, **h3)) + assert_equal(h3, m.call(:m, a: 1, **h2)) + assert_raise(ArgumentError) { m.call(:m, h) } + assert_raise(ArgumentError) { m.call(:m, h2) } + 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_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) + [arg, args] + end + m = c.method(:send) + assert_equal([1, kw], m.call(:m, **{})) + assert_equal([1, kw], m.call(:m, **kw)) + assert_equal([1, h], m.call(:m, **h)) + assert_equal([1, h], m.call(:m, a: 1)) + assert_equal([1, h2], m.call(:m, **h2)) + assert_equal([1, h3], m.call(:m, **h3)) + assert_equal([1, h3], m.call(:m, a: 1, **h2)) + end + + def test_sym_proc_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], :m.to_proc.call(c, **{})) + assert_equal([], :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)) + assert_equal([h3], :m.to_proc.call(c, **h3)) + assert_equal([h3], :m.to_proc.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(:m.to_proc.call(c, **{})) + assert_nil(: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(args) + args + 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)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + assert_equal(h3, :m.to_proc.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, :m.to_proc.call(c, **{})) + assert_equal(kw, :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)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + assert_equal(h3, :m.to_proc.call(c, a: 1, **h2)) + assert_raise(ArgumentError) { :m.to_proc.call(c, h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], :m.to_proc.call(c, **{})) + assert_equal([1, kw], :m.to_proc.call(c, **kw)) + assert_equal([1, h], :m.to_proc.call(c, **h)) + assert_equal([1, h], :m.to_proc.call(c, a: 1)) + assert_equal([1, h2], :m.to_proc.call(c, **h2)) + assert_equal([1, h3], :m.to_proc.call(c, **h3)) + assert_equal([1, h3], :m.to_proc.call(c, a: 1, **h2)) + end + + def test_sym_proc_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = :m.to_proc.method(:call) + assert_equal([], m.call(c, **{})) + assert_equal([], 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)) + assert_equal([h3], m.call(c, **h3)) + assert_equal([h3], m.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(m.call(c, **{})) + assert_nil(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(args) + args + 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)) + assert_equal(h3, m.call(c, **h3)) + assert_equal(h3, m.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, m.call(c, **{})) + assert_equal(kw, 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)) + assert_equal(h3, m.call(c, **h3)) + assert_equal(h3, m.call(c, a: 1, **h2)) + assert_raise(ArgumentError) { m.call(c, h) } + assert_raise(ArgumentError) { m.call(c, h2) } + assert_raise(ArgumentError) { m.call(c, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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) + [arg, args] + end + assert_equal([1, kw], m.call(c, **{})) + assert_equal([1, kw], m.call(c, **kw)) + assert_equal([1, h], m.call(c, **h)) + assert_equal([1, h], m.call(c, a: 1)) + assert_equal([1, h2], m.call(c, **h2)) + assert_equal([1, h3], m.call(c, **h3)) + assert_equal([1, h3], m.call(c, a: 1, **h2)) + end + + def test_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(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(_, 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + 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) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_super_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Class.new do + def m(*args, **kw) + super + end + end.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(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(_, 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + 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) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_rb_call_super_kw_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.extend Bug::RbCallSuperKw + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(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(_, 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + 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) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_define_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + define_method(:m) { } + end + assert_nil(c.m(**{})) + assert_nil(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 + define_method(:m) {|arg| arg } + 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)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args| args } + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|**opt| opt} + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c = Object.new + class << c + define_method(:m) {|arg, **opt| [arg, opt] } + 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 + define_method(:m) {|arg=1, **opt| [arg, opt] } + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args, **opt| [args, opt] } + 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_equal([h3, nil], c.m(h3)) + assert_raise(ArgumentError) { c.m(**h3) } + end + + def test_define_method_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + define_method(:m) { } + end + m = c.method(:m) + assert_nil(m.call(**{})) + assert_nil(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 + define_method(:m) {|arg| arg } + end + m = c.method(:m) + 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)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args| args } + end + m = c.method(:m) + assert_equal([], m.call(**{})) + assert_equal([], m.call(**kw)) + assert_equal([h], m.call(**h)) + assert_equal([h], m.call(a: 1)) + assert_equal([h2], m.call(**h2)) + assert_equal([h3], m.call(**h3)) + assert_equal([h3], m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|**opt| opt} + end + m = c.method(:m) + assert_equal(kw, m.call(**{})) + assert_equal(kw, m.call(**kw)) + assert_equal(h, m.call(**h)) + assert_equal(h, m.call(a: 1)) + assert_equal(h2, m.call(**h2)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + assert_raise(ArgumentError) { m.call(h) } + assert_raise(ArgumentError) { m.call(h2) } + assert_raise(ArgumentError) { m.call(h3) } + + c = Object.new + class << c + define_method(:m) {|arg, **opt| [arg, opt] } + end + m = c.method(:m) + 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 + define_method(:m) {|arg=1, **opt| [arg, opt] } + end + m = c.method(:m) + assert_equal([1, kw], m.call(**{})) + assert_equal([1, kw], m.call(**kw)) + assert_equal([1, h], m.call(**h)) + assert_equal([1, h], m.call(a: 1)) + assert_equal([1, h2], m.call(**h2)) + assert_equal([1, h3], m.call(**h3)) + assert_equal([1, h3], m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args, **opt| [args, opt] } + end + m = c.method(:m) + 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_equal([h3, nil], m.call(h3)) + assert_raise(ArgumentError) { m.call(**h3) } + end + + def test_attr_reader_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_reader :m + end + assert_nil(c.m(**{})) + assert_nil(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) } + end + + def test_attr_reader_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_reader :m + end + m = c.method(:m) + assert_nil(m.call(**{})) + assert_nil(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) } + end + + def test_attr_writer_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_writer :m + 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)) + assert_equal(h3, c.send(:m=, **h3)) + assert_equal(h3, c.send(:m=, a: 1, **h2)) + + assert_equal(42, c.send(:m=, 42, **{})) + assert_equal(42, c.send(:m=, 42, **kw)) + assert_raise(ArgumentError) { c.send(:m=, 42, **h) } + assert_raise(ArgumentError) { c.send(:m=, 42, a: 1) } + assert_raise(ArgumentError) { c.send(:m=, 42, **h2) } + assert_raise(ArgumentError) { c.send(:m=, 42, **h3) } + assert_raise(ArgumentError) { c.send(:m=, 42, a: 1, **h2) } + end + + def test_attr_writer_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_writer :m + end + m = c.method(:m=) + 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)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + + assert_equal(42, m.call(42, **{})) + assert_equal(42, m.call(42, **kw)) + assert_raise(ArgumentError) { m.call(42, **h) } + assert_raise(ArgumentError) { m.call(42, a: 1) } + assert_raise(ArgumentError) { m.call(42, **h2) } + assert_raise(ArgumentError) { m.call(42, **h3) } + assert_raise(ArgumentError) { m.call(42, a: 1, **h2) } + end + + def test_proc_ruby2_keywords + h1 = {:a=>1} + foo = ->(*args, &block){block.call(*args)} + assert_same(foo, foo.ruby2_keywords) + + 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_equal([[1, h1], {}], foo.call(1, {:a=>1}, &->(*args, **kw){[args, kw]})) + assert_equal([1, h1], foo.call(1, {:a=>1}, &->(*args){args})) + 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| + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or proc does not accept argument splat\)/) do + pr.ruby2_keywords + end + end + + o = Object.new + def o.foo(*args) + yield(*args) + end + foo = o.method(:foo).to_proc + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc created from method\)/) do + foo.ruby2_keywords + end + + foo = :foo.to_proc + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc not defined in Ruby\)/) do + foo.ruby2_keywords + end + + assert_raise(FrozenError) { ->(*args){}.freeze.ruby2_keywords } + 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) + end + + ruby2_keywords(define_method(:bfoo) do |meth, *args| + send(meth, *args) + end) + + ruby2_keywords def foo_bar(*args) + bar(*args) + end + + ruby2_keywords def foo_baz(*args) + 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) + end + + ruby2_keywords def foo_foo_bar(meth, *args) + foo_bar(meth, *args) + end + + ruby2_keywords def foo_foo_baz(meth, *args) + foo_baz(meth, *args) + end + + ruby2_keywords def foo_mod(meth, *args) + args << 1 + send(meth, *args) + end + + ruby2_keywords def foo_bar_mod(*args) + args << 1 + bar(*args) + end + + ruby2_keywords def foo_baz_mod(*args) + args << 1 + baz(*args) + end + + def pass_bar(*args) + bar(*args) + end + + def bar(*args, **kw) + [args, kw] + end + + def baz(*args) + args + end + + def empty_method + end + + def opt(arg = :opt) + arg + end + + ruby2_keywords def foo_dbar(*args) + dbar(*args) + end + + ruby2_keywords def foo_dbaz(*args) + 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 + + define_method(:dbaz) do |*args| + args + end + + def pass_cfunc(*args) + self.class.new(*args).init_args + end + + ruby2_keywords def block(*args) + ->(*args, **kw){[args, kw]}.(*args) + end + + ruby2_keywords def cfunc(*args) + self.class.new(*args).init_args + end + + ruby2_keywords def store_foo(meth, *args) + @stored_args = args + use(meth) + end + def use(meth) + send(meth, *@stored_args) + end + + attr_reader :init_args + def initialize(*args, **kw) + @init_args = [args, kw] + end + end + + mmkw = Class.new do + def method_missing(*args, **kw) + [args, kw] + end + end + + mmnokw = Class.new do + def method_missing(*args) + args + end + end + + implicit_super = Class.new(c) do + ruby2_keywords def bar(*args) + super + end + + ruby2_keywords def baz(*args) + super + end + end + + explicit_super = Class.new(c) do + ruby2_keywords def bar(*args) + super(*args) + end + + ruby2_keywords def baz(*args) + super(*args) + end + end + + h1 = {a: 1} + o = c.new + + assert_equal([1, h1], o.foo_baz2(1, :a=>1)) + assert_equal([1], o.foo_baz2(1, **{})) + assert_equal([h1], o.foo_baz2(h1, **{})) + + assert_equal([[1], h1], o.foo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.store_foo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.store_foo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.foo_bar(1, :a=>1)) + assert_equal([1, h1], o.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(:foo_bar, 1, :a=>1)) + 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)) + assert_equal([[1], h1], o.bfoo(:bar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:baz, 1, **h1)) + assert_equal([[1], h1], o.store_foo(:bar, 1, **h1)) + assert_equal([1, h1], o.store_foo(:baz, 1, **h1)) + assert_equal([[1], h1], o.foo_bar(1, **h1)) + assert_equal([1, h1], o.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(:foo_bar, 1, **h1)) + 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, **{})) + assert_equal([[h1], {}], o.bfoo(:bar, h1, **{})) + assert_equal([h1], o.bfoo(:baz, h1, **{})) + assert_equal([[h1], {}], o.store_foo(:bar, h1, **{})) + assert_equal([h1], o.store_foo(:baz, h1, **{})) + assert_equal([[h1], {}], o.foo_bar(h1, **{})) + assert_equal([h1], o.foo_baz(h1, **{})) + assert_equal([[h1], {}], o.foo(:foo, :bar, h1, **{})) + 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_foo_bar(h1, **{})) + assert_equal([h1], o.foo_foo_baz(h1, **{})) + assert_equal([[h1], {}], o.foo_bar_after_bmethod(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:bar, 1, h1)) + assert_equal([1, h1], o.foo(:baz, 1, h1)) + assert_equal([[1, h1], {}], o.bfoo(:bar, 1, h1)) + assert_equal([1, h1], o.bfoo(:baz, 1, h1)) + assert_equal([[1, h1], {}], o.store_foo(:bar, 1, h1)) + assert_equal([1, h1], o.store_foo(:baz, 1, h1)) + 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)) + assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, :a=>1)) + assert_equal([1, h1, 1], o.foo_baz_mod(1, :a=>1)) + + assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, **h1)) + assert_equal([1, h1, 1], o.foo_mod(:baz, 1, **h1)) + 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([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1)) + assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1)) + assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, h1)) + assert_equal([1, h1, 1], o.foo_baz_mod(1, h1)) + + assert_equal([[1], h1], o.foo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.store_foo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.foo_dbar(1, :a=>1)) + assert_equal([1, h1], o.foo_dbaz(1, :a=>1)) + + assert_equal([[1], h1], o.foo(:dbar, 1, **h1)) + assert_equal([1, h1], o.foo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.store_foo(:dbar, 1, **h1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.foo_dbar(1, **h1)) + assert_equal([1, h1], o.foo_dbaz(1, **h1)) + + assert_equal([[h1], {}], o.foo(:dbar, h1, **{})) + assert_equal([h1], o.foo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.bfoo(:dbar, h1, **{})) + assert_equal([h1], o.bfoo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.store_foo(:dbar, h1, **{})) + assert_equal([h1], o.store_foo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.foo_dbar(h1, **{})) + assert_equal([h1], o.foo_dbaz(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:dbar, 1, h1)) + assert_equal([1, h1], o.foo(:dbaz, 1, h1)) + assert_equal([[1, h1], {}], o.bfoo(:dbar, 1, h1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, h1)) + assert_equal([[1, h1], {}], o.store_foo(:dbar, 1, h1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, h1)) + 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_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_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_equal([[:b, 1, h1], {}], o.b(1, h1)) + assert_equal([[:b, h1], {}], o.b(h1, **{})) + + o = mmnokw.new + assert_equal([:b, 1, h1], o.b(1, :a=>1)) + assert_equal([:b, 1, h1], o.b(1, **h1)) + assert_equal([:b, 1, h1], o.b(1, h1)) + assert_equal([:b, h1], o.b(h1, **{})) + + o = implicit_super.new + assert_equal([[1], h1], o.bar(1, :a=>1)) + assert_equal([[1], h1], o.bar(1, **h1)) + assert_equal([[1, h1], {}], o.bar(1, h1)) + assert_equal([[h1], {}], o.bar(h1, **{})) + + assert_equal([1, h1], o.baz(1, :a=>1)) + assert_equal([1, h1], o.baz(1, **h1)) + assert_equal([1, h1], o.baz(1, h1)) + assert_equal([h1], o.baz(h1, **{})) + + o = explicit_super.new + assert_equal([[1], h1], o.bar(1, :a=>1)) + assert_equal([[1], h1], o.bar(1, **h1)) + assert_equal([[1, h1], {}], o.bar(1, h1)) + assert_equal([[h1], {}], o.bar(h1, **{})) + + assert_equal([1, h1], o.baz(1, :a=>1)) + assert_equal([1, h1], o.baz(1, **h1)) + assert_equal([1, h1], o.baz(1, h1)) + assert_equal([h1], o.baz(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1)) + assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1)) + + 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)) + end + + o = Object.new + class << o + alias bar p + end + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method not defined in Ruby\)/) do + assert_nil(o.singleton_class.send(:ruby2_keywords, :bar)) + end + sc = Class.new(c) + assert_warn(/Skipping set of ruby2_keywords flag for bar \(can only set in method defining module\)/) do + sc.send(:ruby2_keywords, :bar) + end + m = Module.new + assert_warn(/Skipping set of ruby2_keywords flag for system \(can only set in method defining module\)/) do + m.send(:ruby2_keywords, :system) + end + + assert_raise(NameError) { c.send(:ruby2_keywords, "a5e36ccec4f5080a1d5e63f8") } + assert_raise(NameError) { c.send(:ruby2_keywords, :quux) } + + c.freeze + assert_raise(FrozenError) { c.send(:ruby2_keywords, :baz) } + end + + def test_top_ruby2_keywords + assert_in_out_err([], <<-INPUT, ["[1, 2, 3]", "{:k=>1}"], []) + def bar(*a, **kw) + p a, kw + end + ruby2_keywords def foo(*a) + bar(*a) + end + foo(1, 2, 3, k:1) + INPUT + end + + def test_dig_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.dig(*args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h], [c].dig(0, **h)) + assert_equal([h], [c].dig(0, a: 1)) + assert_equal([h2], [c].dig(0, **h2)) + assert_equal([h3], [c].dig(0, **h3)) + assert_equal([h3], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig; end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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) } + + c.singleton_class.remove_method(:dig) + def c.dig(args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal(kw, [c].dig(0, kw, **kw)) + assert_equal(h, [c].dig(0, **h)) + assert_equal(h, [c].dig(0, a: 1)) + assert_equal(h2, [c].dig(0, **h2)) + assert_equal(h3, [c].dig(0, **h3)) + assert_equal(h3, [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig(**args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_raise(ArgumentError) { [c].dig(0, h3) } + + c.singleton_class.remove_method(:dig) + def c.dig(arg, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig(arg=1, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_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_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, **{})) + end + + def test_dig_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.method_missing(_, *args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h], [c].dig(0, **h)) + assert_equal([h], [c].dig(0, a: 1)) + assert_equal([h2], [c].dig(0, **h2)) + assert_equal([h3], [c].dig(0, **h3)) + assert_equal([h3], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing; end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal(kw, [c].dig(0, kw, **kw)) + assert_equal(h, [c].dig(0, **h)) + assert_equal(h, [c].dig(0, a: 1)) + assert_equal(h2, [c].dig(0, **h2)) + assert_equal(h3, [c].dig(0, **h3)) + assert_equal(h3, [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_raise(ArgumentError) { [c].dig(0, h3) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + 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_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_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, **{})) + end + + def test_enumerator_size_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.to_enum(:each){|*args| args}.size + m = ->(*args){ args } + assert_equal([], c.to_enum(:each, **{}, &m).size) + assert_equal([], c.to_enum(:each, **kw, &m).size) + assert_equal([h], c.to_enum(:each, **h, &m).size) + assert_equal([h], c.to_enum(:each, a: 1, &m).size) + 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) + + m = ->(){ } + assert_nil(c.to_enum(:each, **{}, &m).size) + assert_nil(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 } + + m = ->(args){ args } + 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) + 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) + + m = ->(**args){ args } + assert_equal(kw, c.to_enum(:each, **{}, &m).size) + assert_equal(kw, c.to_enum(:each, **kw, &m).size) + assert_equal(h, c.to_enum(:each, **h, &m).size) + assert_equal(h, c.to_enum(:each, a: 1, &m).size) + 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_raise(ArgumentError) { c.to_enum(:each, h, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, h2, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, h3, &m).size } + + m = ->(arg, **args){ [arg, args] } + 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) + + m = ->(arg=1, **args){ [arg, args] } + assert_equal([1, kw], c.to_enum(:each, **{}, &m).size) + assert_equal([1, kw], c.to_enum(:each, **kw, &m).size) + assert_equal([1, h], c.to_enum(:each, **h, &m).size) + assert_equal([1, h], c.to_enum(:each, a: 1, &m).size) + 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_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) + end + + def test_instance_exec_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + m = ->(*args) { args } + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + 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)) + + m = ->() { nil } + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(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) } + + m = ->(args) { args } + 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)) + 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)) + + m = ->(**args) { args } + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + 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_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + + m = ->(arg, **args) { [arg, args] } + 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)) + + m = ->(arg=1, **args) { [arg, args] } + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + 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_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)) + end + + def test_instance_exec_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + 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)) + + c.singleton_class.remove_method(:m) + def c.m + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(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) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + m = c.method(:m) + 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)) + 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)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + 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_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + 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_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)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + 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_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)) + end + + def test_instance_exec_define_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.define_singleton_method(:m) do |*args| + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + 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)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(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) } + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |args| + args + end + m = c.method(:m) + 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)) + 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)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |**args| + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + 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_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + 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_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)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |arg=1, **args| + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + 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_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)) + end + + def test_instance_exec_sym_proc_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.instance_exec(c, **{}, &:m)) + assert_equal([], c.instance_exec(c, **kw, &:m)) + assert_equal([h], c.instance_exec(c, **h, &:m)) + assert_equal([h], c.instance_exec(c, a: 1, &:m)) + 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)) + + c.singleton_class.remove_method(:m) + def c.m + end + assert_nil(c.instance_exec(c, **{}, &:m)) + assert_nil(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) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + 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)) + 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)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.instance_exec(c, **{}, &:m)) + assert_equal(kw, c.instance_exec(c, **kw, &:m)) + assert_equal(h, c.instance_exec(c, **h, &:m)) + assert_equal(h, c.instance_exec(c, a: 1, &:m)) + 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_raise(ArgumentError) { c.instance_exec(c, h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + 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)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.instance_exec(c, **{}, &:m)) + assert_equal([1, kw], c.instance_exec(c, **kw, &:m)) + assert_equal([1, h], c.instance_exec(c, **h, &:m)) + assert_equal([1, h], c.instance_exec(c, a: 1, &:m)) + 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_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)) + end + + def test_rb_yield_block_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.extend Bug::Iter::Yield + class << c + alias m yield_block + end + def c.c(*args) + args + end + assert_equal([], c.m(:c, **{})) + assert_equal([], c.m(:c, **kw)) + assert_equal([h], c.m(:c, **h)) + assert_equal([h], c.m(:c, a: 1)) + assert_equal([h2], c.m(:c, **h2)) + assert_equal([h3], c.m(:c, **h3)) + assert_equal([h3], c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c; end + assert_nil(c.m(:c, **{})) + assert_nil(c.m(:c, **kw)) + assert_raise(ArgumentError) { c.m(:c, **h) } + assert_raise(ArgumentError) { c.m(:c, a: 1) } + assert_raise(ArgumentError) { c.m(:c, **h2) } + assert_raise(ArgumentError) { c.m(:c, **h3) } + assert_raise(ArgumentError) { c.m(:c, a: 1, **h2) } + + c.singleton_class.remove_method(:c) + def c.c(args) + args + 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)) + assert_equal(h2, c.m(:c, **h2)) + assert_equal(h3, c.m(:c, **h3)) + assert_equal(h3, c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c(**args) + [args, yield(**args)] + end + m = ->(**args){ args } + assert_equal([kw, kw], c.m(:c, **{}, &m)) + assert_equal([kw, kw], c.m(:c, **kw, &m)) + assert_equal([h, h], c.m(:c, **h, &m)) + assert_equal([h, h], c.m(:c, a: 1, &m)) + 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_raise(ArgumentError) { c.m(:c, h, &m) } + assert_raise(ArgumentError) { c.m(:c, h2, &m) } + assert_raise(ArgumentError) { c.m(:c, h3, &m) } + + c.singleton_class.remove_method(:c) + def c.c(arg, **args) + [arg, args] + 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)) + + c.singleton_class.remove_method(:c) + def c.c(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(:c, **{})) + assert_equal([1, kw], c.m(:c, **kw)) + assert_equal([1, h], c.m(:c, **h)) + assert_equal([1, h], c.m(:c, a: 1)) + 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_equal([h, kw], c.m(:c, h)) + assert_equal([h2, kw], c.m(:c, h2)) + assert_equal([h3, kw], c.m(:c, h3)) + end def p1 Proc.new do |str: "foo", num: 424242| @@ -254,8 +3552,8 @@ class TestKeywordArguments < Test::Unit::TestCase [:keyrest, :kw], [:block, :b]], p6.parameters) end - def m1(*args) - yield(*args) + def m1(*args, **options) + yield(*args, **options) end def test_block @@ -273,15 +3571,56 @@ 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_equal(expect, rest_keyrest(*expect), bug7665) + 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_equal(expect, pr.call(*expect), bug7665) - assert_equal(expect, pr.call(expect), bug8463) + 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_equal(expect, pr.call(expect), bug8463) + assert_equal(splat_expect, pr.call(a), bug8463) + pr = proc {|a, **opt| next a, opt} - assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463) + assert_equal([splat_expect, {}], pr.call(splat_expect), bug8463) + end + + def req_plus_keyword(x, **h) + [x, h] + end + + def opt_plus_keyword(x=1, **h) + [x, h] + end + + def splat_plus_keyword(*a, **h) + [a, h] + 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})) + + 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_equal([{:a=>1}, {}], opt_plus_keyword({:a=>1})) + assert_equal([{"a"=>1}, {}], opt_plus_keyword({"a"=>1})) + 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_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})) end def test_bare_kwrest @@ -418,6 +3757,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 @@ -426,11 +3784,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 @@ -460,13 +3822,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_equal([1, 2, 1, [], {:f=>5}, 2, {}], a.new.foo(1, 2, f:5), bug8993) + assert_raise(ArgumentError) { a.new.foo(1, 2, f:5) } end def test_splat_keyword_nondestructive @@ -493,33 +3854,49 @@ 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_equal([1, 9], m1(1, o) {|a, k: 0| break [a, k]}, bug10016) - assert_equal([1, 9], m1(1, o, &->(a, k: 0) {break [a, k]}), bug10016) + 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 m = Object.new def m.f() :ok; end + def m.f1(a) a; end def m.f2(a = nil) a; end + 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 = {} assert_equal(:ok, m.f(**o), '[ruby-core:68124] [Bug #10856]') a = [] assert_equal(:ok, m.f(*a, **o), '[ruby-core:83638] [Bug #10856]') + assert_equal(:OK, m.f1(*a, :OK, **o), '[ruby-core:91825] [Bug #10856]') + assert_equal({}, m.f1(*a, o), '[ruby-core:91825] [Bug #10856]') o = {a: 42} - assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]') + assert_warning('', 'splat to mandatory') do + assert_equal({a: 42}, m.f1(**o)) + end + assert_warning('') do + assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]') + end + assert_warning('', 'splat to kwrest') do + assert_equal({a: 42}, m.f3(**o)) + end + assert_warning('', 'splat to rest') do + assert_equal([{a: 42}], m.f4(**o)) + end - assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]') + assert_warning('') do + assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]') + end o = {} a = [:ok] @@ -547,7 +3924,8 @@ class TestKeywordArguments < Test::Unit::TestCase def test_dynamic_symbol_keyword bug10266 = '[ruby-dev:48564] [Bug #10266]' - assert_separately(['-', bug10266], <<-'end;') # do + assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; bug = ARGV.shift "hoge".to_sym assert_nothing_raised(bug) {eval("def a(hoge:); end")} @@ -564,14 +3942,14 @@ class TestKeywordArguments < Test::Unit::TestCase bar(k1: 1) end end - assert_raise_with_message(ArgumentError, /unknown keyword: k1/, bug10413) { + assert_raise_with_message(ArgumentError, /unknown keyword: :k1/, bug10413) { o.foo {raise "unreachable"} } end def test_unknown_keyword bug13004 = '[ruby-dev:49893] [Bug #13004]' - assert_raise_with_message(ArgumentError, /unknown keyword: invalid-argument/, bug13004) { + assert_raise_with_message(ArgumentError, /unknown keyword: :"invalid-argument"/, bug13004) { [].sample(random: nil, "invalid-argument": nil) } end @@ -600,7 +3978,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 @@ -677,4 +4055,364 @@ class TestKeywordArguments < Test::Unit::TestCase obj.t(42) end end + + def many_kwargs(a0: '', a1: '', a2: '', a3: '', a4: '', a5: '', a6: '', a7: '', + b0: '', b1: '', b2: '', b3: '', b4: '', b5: '', b6: '', b7: '', + c0: '', c1: '', c2: '', c3: '', c4: '', c5: '', c6: '', c7: '', + d0: '', d1: '', d2: '', d3: '', d4: '', d5: '', d6: '', d7: '', + e0: '') + [a0, a1, a2, a3, a4, a5, a6, a7, + b0, b1, b2, b3, b4, b5, b6, b7, + c0, c1, c2, c3, c4, c5, c6, c7, + d0, d1, d2, d3, d4, d5, d6, d7, + e0] + end + + def test_many_kwargs + i = 0 + assert_equal(:ok, many_kwargs(a0: :ok)[i], "#{i}: a0"); i+=1 + assert_equal(:ok, many_kwargs(a1: :ok)[i], "#{i}: a1"); i+=1 + assert_equal(:ok, many_kwargs(a2: :ok)[i], "#{i}: a2"); i+=1 + assert_equal(:ok, many_kwargs(a3: :ok)[i], "#{i}: a3"); i+=1 + assert_equal(:ok, many_kwargs(a4: :ok)[i], "#{i}: a4"); i+=1 + assert_equal(:ok, many_kwargs(a5: :ok)[i], "#{i}: a5"); i+=1 + assert_equal(:ok, many_kwargs(a6: :ok)[i], "#{i}: a6"); i+=1 + assert_equal(:ok, many_kwargs(a7: :ok)[i], "#{i}: a7"); i+=1 + + assert_equal(:ok, many_kwargs(b0: :ok)[i], "#{i}: b0"); i+=1 + assert_equal(:ok, many_kwargs(b1: :ok)[i], "#{i}: b1"); i+=1 + assert_equal(:ok, many_kwargs(b2: :ok)[i], "#{i}: b2"); i+=1 + assert_equal(:ok, many_kwargs(b3: :ok)[i], "#{i}: b3"); i+=1 + assert_equal(:ok, many_kwargs(b4: :ok)[i], "#{i}: b4"); i+=1 + assert_equal(:ok, many_kwargs(b5: :ok)[i], "#{i}: b5"); i+=1 + assert_equal(:ok, many_kwargs(b6: :ok)[i], "#{i}: b6"); i+=1 + assert_equal(:ok, many_kwargs(b7: :ok)[i], "#{i}: b7"); i+=1 + + assert_equal(:ok, many_kwargs(c0: :ok)[i], "#{i}: c0"); i+=1 + assert_equal(:ok, many_kwargs(c1: :ok)[i], "#{i}: c1"); i+=1 + assert_equal(:ok, many_kwargs(c2: :ok)[i], "#{i}: c2"); i+=1 + assert_equal(:ok, many_kwargs(c3: :ok)[i], "#{i}: c3"); i+=1 + assert_equal(:ok, many_kwargs(c4: :ok)[i], "#{i}: c4"); i+=1 + assert_equal(:ok, many_kwargs(c5: :ok)[i], "#{i}: c5"); i+=1 + assert_equal(:ok, many_kwargs(c6: :ok)[i], "#{i}: c6"); i+=1 + assert_equal(:ok, many_kwargs(c7: :ok)[i], "#{i}: c7"); i+=1 + + assert_equal(:ok, many_kwargs(d0: :ok)[i], "#{i}: d0"); i+=1 + assert_equal(:ok, many_kwargs(d1: :ok)[i], "#{i}: d1"); i+=1 + assert_equal(:ok, many_kwargs(d2: :ok)[i], "#{i}: d2"); i+=1 + assert_equal(:ok, many_kwargs(d3: :ok)[i], "#{i}: d3"); i+=1 + assert_equal(:ok, many_kwargs(d4: :ok)[i], "#{i}: d4"); i+=1 + assert_equal(:ok, many_kwargs(d5: :ok)[i], "#{i}: d5"); i+=1 + assert_equal(:ok, many_kwargs(d6: :ok)[i], "#{i}: d6"); i+=1 + assert_equal(:ok, many_kwargs(d7: :ok)[i], "#{i}: d7"); i+=1 + + assert_equal(:ok, many_kwargs(e0: :ok)[i], "#{i}: e0"); i+=1 + end + + 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 + class C + def call(*args, **kw) + yield(self, *args, **kw) + end + end + using(Module.new do + refine C do + def m(*args, **kw) + super + end + end + end) + + def test_sym_proc_refine_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.m(*args) + args + end + assert_equal([], c.call(**{}, &:m)) + assert_equal([], c.call(**kw, &:m)) + assert_equal([h], c.call(**h, &:m)) + assert_equal([h], c.call(h, **{}, &:m)) + assert_equal([h], c.call(a: 1, &:m)) + assert_equal([h2], c.call(**h2, &:m)) + assert_equal([h3], c.call(**h3, &:m)) + assert_equal([h3], c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.call(**{}, &:m)) + assert_nil(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(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)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.call(**{}, &:m)) + assert_equal(kw, 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)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + assert_raise(ArgumentError) { c.call(h, &:m) } + assert_raise(ArgumentError) { c.call(h2, &:m) } + assert_raise(ArgumentError) { c.call(h3, &: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) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m)) + assert_equal([1, kw], c.call(**kw, &:m)) + assert_equal([1, h], c.call(**h, &:m)) + assert_equal([1, h], c.call(a: 1, &:m)) + assert_equal([1, h2], c.call(**h2, &:m)) + assert_equal([1, h3], c.call(**h3, &:m)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m)) + end + + def test_sym_proc_refine_super_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.call(**{}, &:m)) + assert_equal([], c.call(**kw, &:m)) + assert_equal([h], c.call(**h, &:m)) + assert_equal([h], c.call(h, **{}, &:m)) + assert_equal([h], c.call(a: 1, &:m)) + assert_equal([h2], c.call(**h2, &:m)) + assert_equal([h3], c.call(**h3, &:m)) + assert_equal([h3], c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_) end + assert_nil(c.call(**{}, &:m)) + assert_nil(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(: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)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.call(**{}, &:m)) + assert_equal(kw, 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)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + assert_raise(ArgumentError) { c.call(h, &:m2) } + assert_raise(ArgumentError) { c.call(h2, &:m2) } + assert_raise(ArgumentError) { c.call(h3, &: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) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m)) + assert_equal([1, kw], c.call(**kw, &:m)) + assert_equal([1, h], c.call(**h, &:m)) + assert_equal([1, h], c.call(a: 1, &:m)) + assert_equal([1, h2], c.call(**h2, &:m)) + assert_equal([1, h3], c.call(**h3, &:m)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m)) + end + + def test_sym_proc_refine_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.call(**{}, &:m2)) + assert_equal([], c.call(**kw, &:m2)) + assert_equal([h], c.call(**h, &:m2)) + assert_equal([h], c.call(h, **{}, &:m2)) + assert_equal([h], c.call(a: 1, &:m2)) + assert_equal([h2], c.call(**h2, &:m2)) + assert_equal([h3], c.call(**h3, &:m2)) + assert_equal([h3], c.call(a: 1, **h2, &:m2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_) end + assert_nil(c.call(**{}, &:m2)) + assert_nil(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(_, 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)) + assert_equal(h3, c.call(**h3, &:m2)) + assert_equal(h3, c.call(a: 1, **h2, &:m2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.call(**{}, &:m2)) + assert_equal(kw, 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)) + assert_equal(h3, c.call(**h3, &:m2)) + assert_equal(h3, c.call(a: 1, **h2, &:m2)) + assert_raise(ArgumentError) { c.call(h, &:m2) } + assert_raise(ArgumentError) { c.call(h2, &:m2) } + assert_raise(ArgumentError) { c.call(h3, &: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) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m2)) + assert_equal([1, kw], c.call(**kw, &:m2)) + assert_equal([1, h], c.call(**h, &:m2)) + assert_equal([1, h], c.call(a: 1, &:m2)) + assert_equal([1, h2], c.call(**h2, &:m2)) + assert_equal([1, h3], c.call(**h3, &:m2)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m2)) + end + + def test_protected_kwarg + mock = Class.new do + def foo + bar('x', y: 'z') + end + protected + def bar(x, y) + nil + end + end + + assert_nothing_raised do + 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 3ac2e4cb98..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 @@ -157,6 +286,21 @@ class TestLambdaParameters < Test::Unit::TestCase assert_equal(42, return_in_callee(42), feature8693) end + def break_in_current(val) + 1.tap(&->(*) {break 0}) + val + end + + def break_in_callee(val) + yield_block(&->(*) {break 0}) + val + end + + def test_break + assert_equal(42, break_in_current(42)) + assert_equal(42, break_in_callee(42)) + end + def test_do_lambda_source_location exp_lineno = __LINE__ + 3 lmd = ->(x, @@ -180,4 +324,31 @@ class TestLambdaParameters < Test::Unit::TestCase assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) assert_equal(exp_lineno, lineno, "must be at the beginning of the block") end + + def test_not_orphan_return + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { return 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { return 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { return 42 }) end }.m2.call) + end + + def test_not_orphan_break + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { break 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { break 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { break 42 }) end }.m2.call) + end + + def test_not_orphan_next + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { next 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { next 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { next 42 }) end }.m2.call) + end end diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 56890f4b6e..32bf097318 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -17,9 +17,9 @@ class TestLazyEnumerator < Test::Unit::TestCase @enum.each do |v| @current = v if v.is_a? Enumerable - yield *v + yield(*v) else - yield v + yield(v) end end end @@ -32,7 +32,7 @@ class TestLazyEnumerator < Test::Unit::TestCase a = [1, 2, 3].lazy a.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { a.__send__ :initialize, [4, 5], &->(y, *v) { y << yield(*v) } } end @@ -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) @@ -291,6 +295,26 @@ class TestLazyEnumerator < Test::Unit::TestCase assert_equal(nil, a.current) end + def test_take_0_bug_18971 + def (bomb = Object.new.extend(Enumerable)).each + raise + end + [2..10, bomb].each do |e| + assert_equal([], e.lazy.take(0).map(&:itself).to_a) + assert_equal([], e.lazy.take(0).select(&:even?).to_a) + assert_equal([], e.lazy.take(0).select(&:odd?).to_a) + assert_equal([], e.lazy.take(0).reject(&:even?).to_a) + assert_equal([], e.lazy.take(0).reject(&:odd?).to_a) + assert_equal([], e.lazy.take(0).take(1).to_a) + assert_equal([], e.lazy.take(0).take(0).take(1).to_a) + assert_equal([], e.lazy.take(0).drop(0).to_a) + assert_equal([], e.lazy.take(0).find_all {|_| true}.to_a) + assert_equal([], e.lazy.take(0).zip((12..20)).to_a) + assert_equal([], e.lazy.take(0).uniq.to_a) + assert_equal([], e.lazy.take(0).sort.to_a) + end + end + def test_take_bad_arg a = Step.new(1..10) assert_raise(ArgumentError) { a.lazy.take(-1) } @@ -452,6 +476,14 @@ class TestLazyEnumerator < Test::Unit::TestCase EOS end + def test_lazy_eager + lazy = [1, 2, 3].lazy.map { |x| x * 2 } + enum = lazy.eager + assert_equal Enumerator, enum.class + assert_equal 3, enum.size + assert_equal [1, 2, 3], enum.map { |x| x / 2 } + end + def test_lazy_to_enum lazy = [1, 2, 3].lazy def lazy.foo(*args) @@ -466,6 +498,54 @@ EOS assert_equal [1, 2, 3], lazy.to_enum.to_a end + def test_lazy_to_enum_lazy_methods + a = [1, 2, 3].to_enum + pr = proc{|x| [x, x * 2]} + selector = proc{|x| x*2 if x % 2 == 0} + + [ + [:with_index, nil], + [:with_index, 10, nil], + [:with_index, 10, pr], + [:map, nil], + [:map, pr], + [:collect, nil], + [:flat_map, nil], + [:flat_map, pr], + [:collect_concat, nil], + [:select, nil], + [:select, selector], + [:find_all, nil], + [:filter, nil], + [:filter_map, selector], + [:filter_map, nil], + [:reject, selector], + [:grep, selector, nil], + [:grep, selector, pr], + [:grep_v, selector, nil], + [:grep_v, selector, pr], + [:zip, a, nil], + [:take, 3, nil], + [:take_while, nil], + [:take_while, selector], + [:drop, 1, nil], + [:drop_while, nil], + [:drop_while, selector], + [:uniq, nil], + [:uniq, proc{|x| x.odd?}], + ].each do |args| + block = args.pop + assert_equal [1, 2, 3].to_enum.to_enum(*args).first(2).to_a, [1, 2, 3].to_enum.lazy.to_enum(*args).first(2).to_a + assert_equal (0..50).to_enum.to_enum(*args).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).first(2).to_a + if block + assert_equal [1, 2, 3, 4].to_enum.to_enum(*args).map(&block).first(2).to_a, [1, 2, 3, 4].to_enum.lazy.to_enum(*args).map(&block).first(2).to_a + unless args.first == :take_while || args.first == :drop_while + assert_equal (0..50).to_enum.to_enum(*args).map(&block).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).map(&block).first(2).to_a + end + end + end + end + def test_size lazy = [1, 2, 3].lazy assert_equal 3, lazy.size @@ -569,4 +649,61 @@ EOS [1, 2, 3].lazy.map(&:undefined).map(&:to_s).force end end + + def test_uniq + u = (1..Float::INFINITY).lazy.uniq do |x| + raise "too big" if x > 10000 + (x**2) % 10 + end + assert_equal([1, 2, 3, 4, 5, 10], u.first(6)) + assert_equal([1, 2, 3, 4, 5, 10], u.first(6)) + end + + def test_filter_map + e = (1..Float::INFINITY).lazy.filter_map do |x| + raise "too big" if x > 10000 + (x**2) % 10 if x.even? + end + assert_equal([4, 6, 6, 4, 0, 4], e.first(6)) + assert_equal([4, 6, 6, 4, 0, 4], e.first(6)) + end + + def test_with_index + feature7877 = '[ruby-dev:47025] [Feature #7877]' + leibniz = ->(n) { + (0..Float::INFINITY).lazy.with_index.map {|i, j| + raise IndexError, "limit exceeded (#{n})" unless j < n + ((-1) ** j) / (2*i+1).to_f + }.take(n).reduce(:+) + } + assert_nothing_raised(IndexError, feature7877) { + assert_in_epsilon(Math::PI/4, leibniz[1000]) + } + + a = [] + ary = (0..Float::INFINITY).lazy.with_index(2) {|i, j| a << [i-1, j] }.take(2).to_a + assert_equal([[-1, 2], [0, 3]], a) + assert_equal([0, 1], ary) + + a = [] + ary = (0..Float::INFINITY).lazy.with_index(2, &->(i,j) { a << [i-1, j] }).take(2).to_a + assert_equal([[-1, 2], [0, 3]], a) + assert_equal([0, 1], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).map {|i, j| [i-1, j] }.take(2).to_a + assert_equal([[-1, 2], [0, 3]], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).map(&->(i, j) { [i-1, j] }).take(2).to_a + assert_equal([[-1, 2], [0, 3]], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).take(2).to_a + assert_equal([[0, 2], [1, 3]], ary) + + 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 9645e3234b..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 @@ -45,6 +45,7 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal "A", ?A assert_instance_of String, ?\n assert_equal "\n", ?\n + assert_equal " ", ?\s assert_equal " ", ?\ # space assert_equal '', '' assert_equal 'string', 'string' @@ -177,6 +178,37 @@ class TestRubyLiteral < Test::Unit::TestCase end end + def test_frozen_string_in_array_literal + list = eval("# frozen-string-literal: true\n""['foo', 'bar']") + assert_equal 2, list.length + list.each { |str| assert_predicate str, :frozen? } + end + + if defined?(RubyVM::InstructionSequence.compile_option) and + RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal) + def test_debug_frozen_string + 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 + src = '["foo"]'; f = "test.rb"; n = 1 + opt = {frozen_string_literal: true, debug_frozen_string_literal: true} + ary = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval + assert_equal("foo", ary.first) + assert_predicate(ary.first, :frozen?) + assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { + ary.first << "x" + } unless ENV['RUBY_ISEQ_DUMP_DEBUG'] + end + end + def test_regexp assert_instance_of Regexp, // assert_match(//, 'a') @@ -252,6 +284,24 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal "literal", h["string"] end + def test_hash_literal_frozen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def frozen_hash_literal_arg + {0=>1,1=>4,2=>17} + end + + ObjectSpace.each_object(Hash) do |a| + if a.class == Hash and !a.default_proc and a.size == 3 && + a[0] == 1 && a[1] == 4 && a[2] == 17 + # should not be found. + raise + end + end + assert_not_include frozen_hash_literal_arg, 3 + end; + end + def test_big_array_and_hash_literal assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).map{'x'}.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).to_a.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] @@ -411,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 @@ -438,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) @@ -502,15 +605,12 @@ class TestRubyLiteral < Test::Unit::TestCase } } bug2407 = '[ruby-dev:39798]' - head.each {|h| - if /^0/ =~ h - begin - eval("#{h}_") - rescue SyntaxError => e - assert_match(/numeric literal without digits\Z/, e.message, bug2407) - end + head.grep_v(/^0/) do |s| + head.grep(/^0/) do |h| + h = "#{s}#{h}_" + assert_syntax_error(h, /numeric literal without digits\Z/, "#{bug2407}: #{h.inspect}") end - } + end end def test_float diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index e2c0b7633a..28293ffffc 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -226,38 +226,16 @@ class TestM17N < Test::Unit::TestCase end end - STR_WITHOUT_BOM = "\u3042".freeze - STR_WITH_BOM = "\uFEFF\u3042".freeze - bug8940 = '[ruby-core:59757] [Bug #8940]' - bug9415 = '[ruby-dev:47895] [Bug #9415]' - %w/UTF-16 UTF-32/.each do |enc| - %w/BE LE/.each do |endian| - bom = "\uFEFF".encode("#{enc}#{endian}").force_encoding(enc) - - define_method("test_utf_16_32_inspect(#{enc}#{endian})") do - s = STR_WITHOUT_BOM.encode(enc + endian) - # When a UTF-16/32 string doesn't have a BOM, - # inspect as a dummy encoding string. - assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, - s.dup.force_encoding(enc).inspect) - assert_normal_exit("#{bom.b.dump}.force_encoding('#{enc}').inspect", bug8940) - end - - define_method("test_utf_16_32_codepoints(#{enc}#{endian})") do - assert_equal([0xFEFF], bom.codepoints, bug9415) - end - - define_method("test_utf_16_32_ord(#{enc}#{endian})") do - assert_equal(0xFEFF, bom.ord, bug9415) - end - - define_method("test_utf_16_32_inspect(#{enc}#{endian}-BOM)") do - s = STR_WITH_BOM.encode(enc + endian) - # When a UTF-16/32 string has a BOM, - # inspect as a particular encoding string. - assert_equal(s.inspect, - s.dup.force_encoding(enc).inspect) - end + def test_utf_dummy_are_like_regular_dummy_encodings + [Encoding::UTF_16, Encoding::UTF_32].each do |enc| + s = "\u3042".encode("UTF-32BE") + assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect) + s = "\x00\x00\xFE\xFF" + assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect) + + assert_equal [0, 0, 254, 255], "\x00\x00\xFE\xFF".force_encoding(enc).codepoints + assert_equal 0, "\x00\x00\xFE\xFF".force_encoding(enc).ord + assert_equal 255, "\xFF\xFE\x00\x00".force_encoding(enc).ord end end @@ -269,6 +247,13 @@ class TestM17N < Test::Unit::TestCase assert_empty(encs, bug10598) end + def test_utf_without_bom_valid + encs = [Encoding::UTF_16, Encoding::UTF_32].find_all {|enc| + !(+"abcd").encode!(enc).force_encoding(enc).valid_encoding? + } + assert_empty(encs) + end + def test_object_utf16_32_inspect EnvUtil.suppress_warning do begin @@ -292,6 +277,9 @@ class TestM17N < Test::Unit::TestCase orig_v, $VERBOSE = $VERBOSE, false orig_int, Encoding.default_internal = Encoding.default_internal, nil orig_ext = Encoding.default_external + + omit "https://bugs.ruby-lang.org/issues/18338" + o = Object.new Encoding.default_external = Encoding::UTF_16BE @@ -303,6 +291,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 @@ -358,7 +347,10 @@ class TestM17N < Test::Unit::TestCase "\u3042".encode("UTF-16LE"), "\u3042".encode("UTF-16BE"), ].each do |str| - assert_equal(str, eval(str.dump), "[ruby-dev:33142]") + dump = str.dump + assert_equal(str, eval(dump), "[ruby-dev:33142]") + assert_equal(str, dump.undump) + assert_equal(str, eval("# frozen-string-literal: true\n#{dump}"), '[Bug #14687]') end end @@ -605,6 +597,8 @@ class TestM17N < Test::Unit::TestCase r1 = Regexp.new('\xa1'.force_encoding("ascii-8bit")) r2 = eval('/\xa1#{r1}/'.force_encoding('ascii-8bit')) assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + [r1, r2] end def test_regexp_named_class @@ -876,10 +870,22 @@ class TestM17N < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError) { "%s%s" % [s("\xc2\xa1"), e("\xc2\xa1")] } + + assert_equal("\u3042".encode('Windows-31J'), "%c" % "\u3042\u3044".encode('Windows-31J')) end def test_sprintf_p Encoding.list.each do |e| + unless e.ascii_compatible? + format = e.dummy? ? "%p".force_encoding(e) : "%p".encode(e) + assert_raise(Encoding::CompatibilityError) do + sprintf(format, nil) + end + assert_raise(Encoding::CompatibilityError) do + format % nil + end + next + end format = "%p".force_encoding(e) ['', 'a', "\xC2\xA1", "\x00"].each do |s| s.force_encoding(e) @@ -1312,10 +1318,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 @@ -1521,6 +1531,17 @@ class TestM17N < Test::Unit::TestCase } end + def test_setbyte_range + s = u("\xE3\x81\x82\xE3\x81\x84") + assert_nothing_raised { s.setbyte(0, -1) } + assert_nothing_raised { s.setbyte(0, 0x00) } + assert_nothing_raised { s.setbyte(0, 0x7F) } + assert_nothing_raised { s.setbyte(0, 0x80) } + assert_nothing_raised { s.setbyte(0, 0xff) } + assert_nothing_raised { s.setbyte(0, 0x100) } + assert_nothing_raised { s.setbyte(0, 0x4f7574206f6620636861722072616e6765) } + end + def test_compatible assert_nil Encoding.compatible?("",0) assert_equal(Encoding::UTF_8, Encoding.compatible?(u(""), ua("abc"))) @@ -1559,8 +1580,6 @@ class TestM17N < Test::Unit::TestCase s = "\u3042" assert_equal(a("\xE3\x81\x82"), s.b) assert_equal(Encoding::ASCII_8BIT, s.b.encoding) - s.taint - assert_predicate(s.b, :tainted?) s = "abc".b assert_predicate(s.b, :ascii_only?) end @@ -1569,23 +1588,31 @@ class TestM17N < Test::Unit::TestCase str = "foo" assert_equal(str, str.scrub) assert_not_same(str, str.scrub) - assert_predicate(str.dup.taint.scrub, :tainted?) str = "\u3042\u3044" assert_equal(str, str.scrub) assert_not_same(str, str.scrub) - assert_predicate(str.dup.taint.scrub, :tainted?) str.force_encoding(Encoding::ISO_2022_JP) # dummy encoding assert_equal(str, str.scrub) assert_not_same(str, str.scrub) assert_nothing_raised(ArgumentError) {str.scrub(nil)} - assert_predicate(str.dup.taint.scrub, :tainted?) + end + + def test_scrub_modification_inside_block + str = ("abc\u3042".b << "\xE3\x80".b).force_encoding('UTF-8') + assert_raise(RuntimeError) {str.scrub{|_| str << "1234567890"; "?" }} + + str = "\x00\xD8\x42\x30".force_encoding(Encoding::UTF_16LE) + assert_raise(RuntimeError) do + str.scrub do |_| + str << "1\x002\x00".force_encoding('UTF-16LE') + "?\x00".force_encoding('UTF-16LE') + end + end end def test_scrub_replace_default assert_equal("\uFFFD\uFFFD\uFFFD", u("\x80\x80\x80").scrub) assert_equal("\uFFFDA", u("\xF4\x80\x80A").scrub) - assert_predicate(u("\x80\x80\x80").taint.scrub, :tainted?) - assert_predicate(u("\xF4\x80\x80A").taint.scrub, :tainted?) # examples in Unicode 6.1.0 D93b assert_equal("\x41\uFFFD\uFFFD\x41\uFFFD\x41", @@ -1600,14 +1627,8 @@ class TestM17N < Test::Unit::TestCase def test_scrub_replace_argument assert_equal("foo", u("foo").scrub("\u3013")) - assert_predicate(u("foo").taint.scrub("\u3013"), :tainted?) - assert_not_predicate(u("foo").scrub("\u3013".taint), :tainted?) assert_equal("\u3042\u3044", u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013")) - assert_predicate(u("\xE3\x81\x82\xE3\x81\x84").taint.scrub("\u3013"), :tainted?) - assert_not_predicate(u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013".taint), :tainted?) assert_equal("\u3042\u3013", u("\xE3\x81\x82\xE3\x81").scrub("\u3013")) - assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub("\u3013"), :tainted?) - assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub("\u3013".taint), :tainted?) assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub(e("\xA4\xA2")) } assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub(1) } assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub(u("\x81")) } @@ -1616,8 +1637,6 @@ class TestM17N < Test::Unit::TestCase def test_scrub_replace_block assert_equal("\u3042<e381>", u("\xE3\x81\x82\xE3\x81").scrub{|x|'<'+x.unpack('H*')[0]+'>'}) - assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub{|x|'<'+x.unpack('H*')[0]+'>'}, :tainted?) - assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub{|x|('<'+x.unpack('H*')[0]+'>').taint}, :tainted?) assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub{e("\xA4\xA2")} } assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub{1} } assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub{u("\x81")} } diff --git a/test/ruby/test_m17n_comb.rb b/test/ruby/test_m17n_comb.rb index 99c162a92f..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) @@ -744,11 +759,21 @@ class TestM17NComb < Test::Unit::TestCase } end + def crypt_supports_des_crypt? + /openbsd/ !~ RUBY_PLATFORM + end + # 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 @@ -760,7 +785,7 @@ class TestM17NComb < Test::Unit::TestCase } end - if !strict_crypt + if !strict_crypt && /openbsd/ !~ RUBY_PLATFORM def test_str_crypt_nonstrict combination(STRINGS, STRINGS) {|str, salt| # only test input other than [0-9A-Za-z./] to confirm non-strict behavior @@ -772,9 +797,14 @@ class TestM17NComb < Test::Unit::TestCase end private def confirm_crypt_result(str, salt) - if b(salt).length < 2 - assert_raise(ArgumentError) { str.crypt(salt) } - return + if crypt_supports_des_crypt? + if b(salt).length < 2 + assert_raise(ArgumentError) { str.crypt(salt) } + return + end + else + return if b(salt).length < 2 + salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmH#{salt}" end t = str.crypt(salt) assert_equal(b(str).crypt(b(salt)), t, "#{encdump(str)}.crypt(#{encdump(salt)})") diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index 48a7d03428..6cd0b9acc3 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 @@ -34,7 +33,7 @@ class TestMarshal < Test::Unit::TestCase end def test_marshal - a = [1, 2, 3, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)] + a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)] assert_equal a, Marshal.load(Marshal.dump(a)) [[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z| @@ -48,6 +47,26 @@ class TestMarshal < Test::Unit::TestCase } end + def test_marshal_integers + a = [] + [-2, -1, 0, 1, 2].each do |i| + 0.upto(65).map do |exp| + a << 2**exp + i + end + end + assert_equal a, Marshal.load(Marshal.dump(a)) + + a = [2**32, []]*2 + assert_equal a, Marshal.load(Marshal.dump(a)) + + a = [2**32, 2**32, []]*2 + assert_equal a, Marshal.load(Marshal.dump(a)) + end + + def test_marshal_small_bignum_backref + assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06") + end + StrClone = String.clone def test_marshal_cloned_class assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc")))) @@ -59,6 +78,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 +88,16 @@ 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 + + def test_load_range_as_struct + assert_raise(TypeError, 'GH-6832') do + # Can be obtained with: + # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))' + Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0") + end end class C @@ -157,20 +188,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 @@ -189,57 +229,6 @@ class TestMarshal < Test::Unit::TestCase end end - def test_taint - x = Object.new - x.taint - s = Marshal.dump(x) - assert_equal(true, s.tainted?) - y = Marshal.load(s) - assert_equal(true, y.tainted?) - end - - def test_taint_each_object - x = Object.new - obj = [[x]] - - # clean object causes crean stream - assert_equal(false, obj.tainted?) - assert_equal(false, obj.first.tainted?) - assert_equal(false, obj.first.first.tainted?) - s = Marshal.dump(obj) - assert_equal(false, s.tainted?) - - # tainted object causes tainted stream - x.taint - assert_equal(false, obj.tainted?) - assert_equal(false, obj.first.tainted?) - assert_equal(true, obj.first.first.tainted?) - t = Marshal.dump(obj) - assert_equal(true, t.tainted?) - - # clean stream causes clean objects - assert_equal(false, s.tainted?) - y = Marshal.load(s) - assert_equal(false, y.tainted?) - assert_equal(false, y.first.tainted?) - assert_equal(false, y.first.first.tainted?) - - # tainted stream causes tainted objects - assert_equal(true, t.tainted?) - y = Marshal.load(t) - assert_equal(true, y.tainted?) - assert_equal(true, y.first.tainted?) - assert_equal(true, y.first.first.tainted?) - - # same tests by different senario - s.taint - assert_equal(true, s.tainted?) - y = Marshal.load(s) - assert_equal(true, y.tainted?) - assert_equal(true, y.first.tainted?) - assert_equal(true, y.first.first.tainted?) - end - def test_symbol2 [:ruby, :"\u{7d05}\u{7389}"].each do |sym| assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]') @@ -499,16 +488,6 @@ class TestMarshal < Test::Unit::TestCase module TestModule end - def test_marshal_load_should_not_taint_classes - bug7325 = '[ruby-core:49198]' - for c in [TestClass, TestModule] - assert_not_predicate(c, :tainted?) - c2 = Marshal.load(Marshal.dump(c).taint) - assert_same(c, c2) - assert_not_predicate(c, :tainted?, bug7325) - end - end - class Bug7627 < Struct.new(:bar) attr_accessor :foo @@ -568,7 +547,7 @@ class TestMarshal < Test::Unit::TestCase s.instance_variable_set(:@t, 42) t = Bug8276.new(s) s = Marshal.dump(t) - assert_raise(RuntimeError) {Marshal.load(s)} + assert_raise(FrozenError) {Marshal.load(s)} end def test_marshal_load_ivar @@ -603,7 +582,7 @@ class TestMarshal < Test::Unit::TestCase end class TestForRespondToFalse - def respond_to?(a) + def respond_to?(a, priv = false) false end end @@ -620,15 +599,6 @@ class TestMarshal < Test::Unit::TestCase assert_equal(Marshal.dump(bare), Marshal.dump(packed)) end - def test_untainted_numeric - bug8945 = '[ruby-core:57346] [Bug #8945] Numerics never be tainted' - b = RbConfig::LIMITS['FIXNUM_MAX'] + 1 - tainted = [0, 1.0, 1.72723e-77, b].select do |x| - Marshal.load(Marshal.dump(x).taint).tainted? - end - assert_empty(tainted.map {|x| [x, x.class]}, bug8945) - end - class Bug9523 attr_reader :cc def marshal_dump @@ -640,7 +610,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) @@ -666,7 +636,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 @@ -675,10 +646,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 @@ -686,6 +663,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 @@ -701,15 +683,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) @@ -721,6 +704,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; @@ -772,4 +772,187 @@ class TestMarshal < Test::Unit::TestCase Marshal.dump(Bug12974.new) end end + + Bug14314 = Struct.new(:foo, keyword_init: true) + + def test_marshal_keyword_init_struct + obj = Bug14314.new(foo: 42) + assert_equal obj, Marshal.load(Marshal.dump(obj)) + end + + class Bug15968 + attr_accessor :bar, :baz + + def initialize + self.bar = Bar.new(self) + end + + class Bar + attr_accessor :foo + + def initialize(foo) + self.foo = foo + end + + def marshal_dump + if self.foo.baz + self.foo.remove_instance_variable(:@baz) + else + self.foo.baz = :problem + end + {foo: self.foo} + end + + def marshal_load(data) + self.foo = data[:foo] + end + end + end + + def test_marshal_dump_adding_instance_variable + obj = Bug15968.new + assert_raise_with_message(RuntimeError, /instance variable added/) do + Marshal.dump(obj) + end + end + + def test_marshal_dump_removing_instance_variable + obj = Bug15968.new + obj.baz = :Bug15968 + assert_raise_with_message(RuntimeError, /instance variable removed/) do + 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 f226287442..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,7 +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 @@ -210,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 @@ -227,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 @@ -256,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 @@ -276,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 778fcdc9bc..aa564cde25 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 @@ -22,6 +21,7 @@ class TestMethod < Test::Unit::TestCase def mo5(a, *b, c) end def mo6(a, *b, c, &d) end def mo7(a, b = nil, *c, d, &e) end + def mo8(a, b = nil, *, d, &e) end def ma1((a), &b) nil && a end def mk1(**) end def mk2(**o) nil && o end @@ -30,6 +30,9 @@ class TestMethod < Test::Unit::TestCase def mk5(a, b = nil, **o) nil && o end def mk6(a, b = nil, c, **o) nil && o end def mk7(a, b = nil, *c, d, **o) nil && o end + def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end + def mnk(**nil) end + def mf(...) end class Base def foo() :base end @@ -100,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 @@ -197,6 +206,28 @@ class TestMethod < Test::Unit::TestCase def o.foo; end assert_kind_of(Integer, o.method(:foo).hash) assert_equal(Array.instance_method(:map).hash, Array.instance_method(:collect).hash) + assert_kind_of(String, o.method(:foo).hash.to_s) + end + + def test_hash_does_not_change_after_compaction + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + # iseq backed method + assert_separately([], <<~RUBY) + def a; end + + # Need this method here because otherwise the iseq may be on the C stack + # which would get pinned and not move during compaction + def get_hash + method(:a).hash + end + + hash = get_hash + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(hash, get_hash) + RUBY end def test_owner @@ -308,6 +339,17 @@ class TestMethod < Test::Unit::TestCase assert_equal(:foo, o.foo) end + PUBLIC_SINGLETON_TEST = Object.new + class << PUBLIC_SINGLETON_TEST + private + PUBLIC_SINGLETON_TEST.define_singleton_method(:dsm){} + def PUBLIC_SINGLETON_TEST.def; end + end + def test_define_singleton_method_public + assert_nil(PUBLIC_SINGLETON_TEST.dsm) + assert_nil(PUBLIC_SINGLETON_TEST.def) + end + def test_define_singleton_method_no_proc o = Object.new assert_raise(ArgumentError) { @@ -431,32 +473,46 @@ class TestMethod < Test::Unit::TestCase def test_inspect o = Object.new - def o.foo; end + def o.foo; end; line_no = __LINE__ m = o.method(:foo) - assert_equal("#<Method: #{ o.inspect }.foo>", m.inspect) + assert_equal("#<Method: #{ o.inspect }.foo() #{__FILE__}:#{line_no}>", m.inspect) m = o.method(:foo) - assert_equal("#<UnboundMethod: #{ class << o; self; end.inspect }#foo>", m.unbind.inspect) + assert_match("#<UnboundMethod: #{ class << o; self; end.inspect }#foo() #{__FILE__}:#{line_no}", m.unbind.inspect) c = Class.new - c.class_eval { def foo; end; } + c.class_eval { def foo; end; }; line_no = __LINE__ m = c.new.method(:foo) - assert_equal("#<Method: #{ c.inspect }#foo>", m.inspect) + assert_equal("#<Method: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect) m = c.instance_method(:foo) - assert_equal("#<UnboundMethod: #{ c.inspect }#foo>", m.inspect) + assert_equal("#<UnboundMethod: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect) c2 = Class.new(c) c2.class_eval { private :foo } m2 = c2.new.method(:foo) - assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo>", m2.inspect) + assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo() #{__FILE__}:#{line_no}>", m2.inspect) bug7806 = '[ruby-core:52048] [Bug #7806]' c3 = Class.new(c) c3.class_eval { alias bar foo } m3 = c3.new.method(:bar) - assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)>", m3.inspect, bug7806) + 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) - m.taint - assert_predicate(m.inspect, :tainted?, "inspect result should be infected") + 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 @@ -494,6 +550,22 @@ class TestMethod < Test::Unit::TestCase assert_include mmethods, :meth, 'normal methods are public by default' end + def test_respond_to_missing_argument + obj = Struct.new(:mid).new + def obj.respond_to_missing?(id, *) + self.mid = id + true + end + assert_kind_of(Method, obj.method("bug15640")) + assert_kind_of(Symbol, obj.mid) + assert_equal("bug15640", obj.mid.to_s) + + arg = Struct.new(:to_str).new("bug15640_2") + assert_kind_of(Method, obj.method(arg)) + assert_kind_of(Symbol, obj.mid) + assert_equal("bug15640_2", obj.mid.to_s) + end + define_method(:pm0) {||} define_method(:pm1) {|a|} define_method(:pm2) {|a, b|} @@ -512,6 +584,8 @@ class TestMethod < Test::Unit::TestCase define_method(:pmk5) {|a, b = nil, **o|} define_method(:pmk6) {|a, b = nil, c, **o|} define_method(:pmk7) {|a, b = nil, *c, d, **o|} + define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|} + define_method(:pmnk) {|**nil|} def test_bound_parameters assert_equal([], method(:m0).parameters) @@ -524,14 +598,19 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters) assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters) assert_equal([[:req], [:block, :b]], method(:ma1).parameters) - assert_equal([[:keyrest]], method(:mk1).parameters) + assert_equal([[:keyrest, :**]], method(:mk1).parameters) assert_equal([[:keyrest, :o]], method(:mk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters) assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:mk5).parameters) assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:mk6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters) + 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, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters) end def test_unbound_parameters @@ -545,14 +624,19 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters) assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters) - assert_equal([[:keyrest]], self.class.instance_method(:mk1).parameters) + assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters) assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters) assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:mk5).parameters) assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:mk6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters) + 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, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters) end def test_bmethod_bound_parameters @@ -567,13 +651,15 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], method(:pma1).parameters) - assert_equal([[:keyrest]], method(:pmk1).parameters) + assert_equal([[:keyrest, :**]], method(:pmk1).parameters) assert_equal([[:keyrest, :o]], method(:pmk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters) assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:pmk5).parameters) assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:pmk6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters) + assert_equal([[:nokey]], method(:pmnk).parameters) end def test_bmethod_unbound_parameters @@ -589,13 +675,70 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) - assert_equal([[:keyrest]], self.class.instance_method(:pmk1).parameters) + assert_equal([[:keyrest, :**]], self.class.instance_method(:pmk1).parameters) assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters) assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters) assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters) assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:pmk5).parameters) assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:pmk6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters) + assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters) + end + + def test_hidden_parameters + instance_eval("def m((_)"+",(_)"*256+");end") + assert_empty(method(:m).parameters.map{|_,n|n}.compact) + end + + def test_method_parameters_inspect + assert_include(method(:m0).inspect, "()") + assert_include(method(:m1).inspect, "(a)") + assert_include(method(:m2).inspect, "(a, b)") + assert_include(method(:mo1).inspect, "(a=..., &b)") + assert_include(method(:mo2).inspect, "(a, b=...)") + assert_include(method(:mo3).inspect, "(*a)") + assert_include(method(:mo4).inspect, "(a, *b, &c)") + assert_include(method(:mo5).inspect, "(a, *b, c)") + assert_include(method(:mo6).inspect, "(a, *b, c, &d)") + assert_include(method(:mo7).inspect, "(a, b=..., *c, d, &e)") + assert_include(method(:mo8).inspect, "(a, b=..., *, d, &e)") + assert_include(method(:ma1).inspect, "(_, &b)") + assert_include(method(:mk1).inspect, "(**)") + assert_include(method(:mk2).inspect, "(**o)") + assert_include(method(:mk3).inspect, "(a, **o)") + assert_include(method(:mk4).inspect, "(a=..., **o)") + assert_include(method(:mk5).inspect, "(a, b=..., **o)") + assert_include(method(:mk6).inspect, "(a, b=..., c, **o)") + assert_include(method(:mk7).inspect, "(a, b=..., *c, d, **o)") + assert_include(method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)") + assert_include(method(:mnk).inspect, "(**nil)") + assert_include(method(:mf).inspect, "(...)") + end + + def test_unbound_method_parameters_inspect + assert_include(self.class.instance_method(:m0).inspect, "()") + assert_include(self.class.instance_method(:m1).inspect, "(a)") + assert_include(self.class.instance_method(:m2).inspect, "(a, b)") + assert_include(self.class.instance_method(:mo1).inspect, "(a=..., &b)") + assert_include(self.class.instance_method(:mo2).inspect, "(a, b=...)") + assert_include(self.class.instance_method(:mo3).inspect, "(*a)") + assert_include(self.class.instance_method(:mo4).inspect, "(a, *b, &c)") + assert_include(self.class.instance_method(:mo5).inspect, "(a, *b, c)") + assert_include(self.class.instance_method(:mo6).inspect, "(a, *b, c, &d)") + assert_include(self.class.instance_method(:mo7).inspect, "(a, b=..., *c, d, &e)") + assert_include(self.class.instance_method(:mo8).inspect, "(a, b=..., *, d, &e)") + assert_include(self.class.instance_method(:ma1).inspect, "(_, &b)") + assert_include(self.class.instance_method(:mk1).inspect, "(**)") + assert_include(self.class.instance_method(:mk2).inspect, "(**o)") + assert_include(self.class.instance_method(:mk3).inspect, "(a, **o)") + assert_include(self.class.instance_method(:mk4).inspect, "(a=..., **o)") + assert_include(self.class.instance_method(:mk5).inspect, "(a, b=..., **o)") + assert_include(self.class.instance_method(:mk6).inspect, "(a, b=..., c, **o)") + assert_include(self.class.instance_method(:mk7).inspect, "(a, b=..., *c, d, **o)") + assert_include(self.class.instance_method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)") + assert_include(self.class.instance_method(:mnk).inspect, "(**nil)") + assert_include(self.class.instance_method(:mf).inspect, "(...)") end def test_public_method_with_zsuper_method @@ -645,7 +788,8 @@ class TestMethod < Test::Unit::TestCase assert_nothing_raised { mv3 } assert_nothing_raised { self.mv1 } - assert_raise(NoMethodError) { self.mv2 } + assert_nothing_raised { self.mv2 } + assert_raise(NoMethodError) { (self).mv2 } assert_nothing_raised { self.mv3 } v = Visibility.new @@ -700,7 +844,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) @@ -722,7 +868,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 @@ -782,6 +928,19 @@ class TestMethod < Test::Unit::TestCase assert_equal(:bar, m.call, feature8391) end + def test_singleton_method_prepend + bug14658 = '[Bug #14658]' + c1 = Class.new + o = c1.new + def o.bar; :bar; end + class << o; prepend Module.new; end + m = assert_nothing_raised(NameError, bug14658) {o.singleton_method(:bar)} + assert_equal(:bar, m.call, bug14658) + + o = Object.new + assert_raise(NameError, bug14658) {o.singleton_method(:bar)} + end + Feature9783 = '[ruby-core:62212] [Feature #9783]' def assert_curry_three_args(m) @@ -888,7 +1047,37 @@ class TestMethod < Test::Unit::TestCase assert_nil(m.super_method) end - def test_super_method_removed + def test_super_method_bind_unbind_clone + bug15629_m1 = Module.new do + def foo; end + end + + bug15629_m2 = Module.new do + def foo; end + end + + bug15629_c = Class.new do + include bug15629_m1 + include bug15629_m2 + end + + o = bug15629_c.new + m = o.method(:foo) + sm = m.super_method + im = bug15629_c.instance_method(:foo) + sim = im.super_method + + assert_equal(sm, m.clone.super_method) + assert_equal(sim, m.unbind.super_method) + assert_equal(sim, m.unbind.clone.super_method) + assert_equal(sim, im.clone.super_method) + assert_equal(sm, m.unbind.bind(o).super_method) + assert_equal(sm, m.unbind.clone.bind(o).super_method) + assert_equal(sm, im.bind(o).super_method) + assert_equal(sm, im.clone.bind(o).super_method) + end + + 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} @@ -898,20 +1087,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 @@ -926,11 +1130,254 @@ class TestMethod < Test::Unit::TestCase assert_equal(m1.source_location, m2.source_location, bug) end + def test_super_method_after_bind + assert_nil String.instance_method(:length).bind(String.new).super_method, + '[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 + + # Bug 18435 + def test_instance_methods_owner_consistency + a = Module.new { def method1; end } + + b = Class.new do + include a + protected :method1 + end + + assert_equal [:method1], b.instance_methods(false) + assert_equal b, b.instance_method(:method1).owner + end + + def test_zsuper_method_removed + a = EnvUtil.labeled_class('A') do + private + def foo(arg = nil) + 1 + end + end + line = __LINE__ - 4 + + b = EnvUtil.labeled_class('B', a) do + public :foo + end + + unbound = b.instance_method(:foo) + + assert_equal unbound, b.public_instance_method(:foo) + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + + a.remove_method(:foo) + + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + + obj = b.new + assert_equal 1, unbound.bind_call(obj) + + assert_include b.instance_methods(false), :foo + link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088' + assert_raise(NameError, link) { b.instance_method(:foo) } + # For #test_method_list below, otherwise we get the same error as just above + b.remove_method(:foo) + end + + def test_zsuper_method_removed_higher_method + a0 = EnvUtil.labeled_class('A0') do + def foo(arg1 = nil, arg2 = nil) + 0 + end + end + line0 = __LINE__ - 4 + a0_foo = a0.instance_method(:foo) + + a = EnvUtil.labeled_class('A', a0) do + private + def foo(arg = nil) + 1 + end + end + line = __LINE__ - 4 + + b = EnvUtil.labeled_class('B', a) do + public :foo + end + + unbound = b.instance_method(:foo) + + assert_equal a0_foo, unbound.super_method + + a.remove_method(:foo) + + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + assert_equal a0_foo, unbound.super_method + + obj = b.new + assert_equal 1, unbound.bind_call(obj) + + assert_include b.instance_methods(false), :foo + assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect + end + + def test_zsuper_method_redefined_bind_call + c0 = EnvUtil.labeled_class('C0') do + def foo + [:foo] + end + end + + c1 = EnvUtil.labeled_class('C1', c0) do + def foo + super + [:bar] + end + end + m1 = c1.instance_method(:foo) + + c2 = EnvUtil.labeled_class('C2', c1) do + private :foo + end + + assert_equal [:foo], c2.private_instance_methods(false) + m2 = c2.instance_method(:foo) + + c1.class_exec do + remove_method :foo + def foo + [:bar2] + end + end + + m3 = c2.instance_method(:foo) + c = c2.new + assert_equal [:foo, :bar], m1.bind_call(c) + assert_equal c1, m1.owner + assert_equal [:foo, :bar], m2.bind_call(c) + assert_equal c2, m2.owner + assert_equal [:bar2], m3.bind_call(c) + assert_equal c2, m3.owner + end + + # Bug #18751 + def method_equality_visbility_alias + c = Class.new do + class << self + alias_method :n, :new + private :new + end + end + + assert_equal c.method(:n), c.method(:new) + + assert_not_equal c.method(:n), Class.method(:new) + assert_equal c.method(:n) == Class.instance_method(:new).bind(c) + + assert_not_equal c.method(:new), Class.method(:new) + assert_equal c.method(:new), Class.instance_method(:new).bind(c) + end + def rest_parameter(*rest) rest end def test_splat_long_array + if File.exist?('/etc/os-release') && File.read('/etc/os-release').include?('openSUSE Leap') + # For RubyCI's openSUSE machine http://rubyci.s3.amazonaws.com/opensuseleap/ruby-trunk/recent.html, which tends to die with NoMemoryError here. + omit 'do not exhaust memory on RubyCI openSUSE Leap machine' + end n = 10_000_000 assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]' end @@ -958,17 +1405,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) @@ -1017,4 +1469,151 @@ class TestMethod < Test::Unit::TestCase # without trace insn 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) + end + + def test_compose_with_method + c = Class.new { + def f(x) x * 2 end + def g(x) x + 1 end + } + f = c.new.method(:f) + g = c.new.method(:g) + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_proc + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_callable + c = Class.new { + def f(x) x * 2 end + } + c2 = Class.new { + def call(x) x + 1 end + } + f = c.new.method(:f) + g = c2.new + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + end + + def test_compose_with_noncallable + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + + assert_raise(TypeError) { + f << 5 + } + assert_raise(TypeError) { + f >> 5 + } + end + + def test_umethod_bind_call + foo = Base.instance_method(:foo) + assert_equal(:base, foo.bind_call(Base.new)) + assert_equal(:base, foo.bind_call(Derived.new)) + + plus = Integer.instance_method(:+) + assert_equal(3, plus.bind_call(1, 2)) + end + + def test_method_list + # chkbuild lists all methods. + # The following code emulate this listing. + + # use_symbol = Object.instance_methods[0].is_a?(Symbol) + nummodule = nummethod = 0 + mods = [] + ObjectSpace.each_object(Module) {|m| mods << m if String === m.name } + mods = mods.sort_by {|m| m.name } + mods.each {|mod| + nummodule += 1 + mc = mod.kind_of?(Class) ? "class" : "module" + puts_line = "#{mc} #{mod.name} #{(mod.ancestors - [mod]).inspect}" + puts_line = puts_line # prevent unused var warning + mod.singleton_methods(false).sort.each {|methname| + nummethod += 1 + meth = mod.method(methname) + line = "#{mod.name}.#{methname} #{meth.arity}" + line << " not-implemented" if !mod.respond_to?(methname) + # puts line + } + ms = mod.instance_methods(false) + if true or use_symbol + ms << :initialize if mod.private_instance_methods(false).include? :initialize + else + ms << "initialize" if mod.private_instance_methods(false).include? "initialize" + end + + ms.sort.each {|methname| + nummethod += 1 + meth = mod.instance_method(methname) + line = "#{mod.name}\##{methname} #{meth.arity}" + line << " not-implemented" if /\(not-implemented\)/ =~ meth.inspect + # puts line + } + } + # puts "#{nummodule} modules, #{nummethod} methods" + + 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_mixed_unicode_escapes.rb b/test/ruby/test_mixed_unicode_escapes.rb index 09240d8ab2..f0b4cc691f 100644 --- a/test/ruby/test_mixed_unicode_escapes.rb +++ b/test/ruby/test_mixed_unicode_escapes.rb @@ -13,14 +13,18 @@ class TestMixedUnicodeEscape < Test::Unit::TestCase # 8-bit character escapes are okay. assert_equal("B\xFF", "\u0042\xFF") - # sjis mb chars mixed with Unicode shound not work + # sjis mb chars mixed with Unicode should not work assert_raise(SyntaxError) { eval %q("\u1234")} assert_raise(SyntaxError) { eval %q("\u{1234}")} + # also should not work for Regexp + assert_raise(SyntaxError) { eval %q(/#{"\u1234"}#{""}/)} + assert_raise(RegexpError) { eval %q(/\u{1234}#{nil}/)} + assert_raise(RegexpError) { eval %q(/#{nil}\u1234/)} + # String interpolation turns into an expression and we get # a different kind of error, but we still can't mix these assert_raise(Encoding::CompatibilityError) { eval %q("\u{1234}#{nil}")} assert_raise(Encoding::CompatibilityError) { eval %q("#{nil}\u1234")} - end end diff --git a/test/ruby/test_mjit.rb b/test/ruby/test_mjit.rb new file mode 100644 index 0000000000..a97747eb88 --- /dev/null +++ b/test/ruby/test_mjit.rb @@ -0,0 +1,1327 @@ +# frozen_string_literal: true +require 'test/unit' +require 'tmpdir' +require_relative '../lib/jit_support' + +# Test for --mjit option +class TestMJIT < Test::Unit::TestCase + include JITSupport + + IGNORABLE_PATTERNS = [ + /\AJIT recompile: .+\n\z/, + /\AJIT inline: .+\n\z/, + /\AJIT cancel: .+\n\z/, + /\AJIT batch \([^)]+ms\): .+\n\z/, + /\ASuccessful MJIT finish\n\z/, + ] + MAX_CACHE_PATTERNS = [ + /\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, + + # never used + :opt_invokebuiltin_delegate, + ].each do |insn| + if !RubyVM::INSTRUCTION_NAMES.include?(insn.to_s) + warn "instruction #{insn.inspect} is not defined but included in TestMJIT::TEST_PENDING_INSNS" + end + end + + def self.untested_insns + @untested_insns ||= (RubyVM::INSTRUCTION_NAMES.map(&:to_sym) - TEST_PENDING_INSNS) + end + + 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_mjit.rb: removing #{file}" + File.unlink(file) + end + end + + # ruby -w -Itest/lib test/ruby/test_mjit.rb + if $VERBOSE + pid = $$ + at_exit do + if pid == $$ && !TestMJIT.untested_insns.empty? + warn "you may want to add tests for following insns, when you have a chance: #{TestMJIT.untested_insns.join(' ')}" + end + end + end + end + + def setup + unless JITSupport.supported? + omit 'JIT seems not supported on this platform' + end + self.class.setup + end + + def test_compile_insn_nop + assert_compile_twice('nil rescue true', result_inspect: 'nil', insns: %i[nop]) + end + + def test_compile_insn_local + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0]) + begin; + foo = 1 + foo + end; + + insns = %i[setlocal getlocal setlocal_WC_0 getlocal_WC_0 setlocal_WC_1 getlocal_WC_1] + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, stdout: '168', insns: insns) + begin; + def foo + a = 0 + [1, 2].each do |i| + a += i + [3, 4].each do |j| + a *= j + end + end + a + end + + print foo + end; + end + + def test_compile_insn_blockparam + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, insns: %i[getblockparam setblockparam]) + begin; + def foo(&b) + a = b + b = 2 + a.call + 2 + end + + print foo { 1 } + print foo { 1 } + end; + end + + def test_compile_insn_getblockparamproxy + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 3, insns: %i[getblockparamproxy]) + begin; + def bar(&b) + b.call + end + + def foo(&b) + bar(&b) * bar(&b) + end + + print foo { 2 } + end; + end + + def test_compile_insn_getspecial + assert_compile_twice('$1', result_inspect: 'nil', insns: %i[getspecial]) + end + + def test_compile_insn_setspecial + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial]) + begin; + true if nil.nil?..nil.nil? + end; + end + + def test_compile_insn_instancevariable + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable]) + begin; + @foo = 1 + @foo + end; + + # optimized getinstancevariable call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2, call_threshold: 2) + begin; + class A + def initialize + @a = 1 + @b = 2 + end + + def three + @a + @b + end + end + + a = A.new + print(a.three) # set ic + print(a.three) # inlined ic + end; + end + + def test_compile_insn_classvariable + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, insns: %i[getclassvariable setclassvariable]) + begin; + class Foo + def self.foo + @@foo = 1 + @@foo + end + end + + print Foo.foo + print Foo.foo + end; + end + + def test_compile_insn_constant + EnvUtil.suppress_warning do + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_getconstant_path setconstant]) + begin; + FOO = 1 + FOO + end; + end + end + + def test_compile_insn_global + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal]) + begin; + $foo = 1 + $foo + end; + end + + def test_compile_insn_putnil + assert_compile_twice('nil', result_inspect: 'nil', insns: %i[putnil]) + end + + def test_compile_insn_putself + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1, insns: %i[putself]) + begin; + 2.times { print "hello" } + end; + end + + def test_compile_insn_putobject + assert_compile_twice('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_]) + assert_compile_twice('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_]) + assert_compile_twice('2', result_inspect: '2', insns: %i[putobject]) + end + + def test_compile_insn_definemethod_definesmethod + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'helloworldhelloworld', success_count: 3, insns: %i[definemethod definesmethod]) + begin; + print 2.times.map { + def method_definition + 'hello' + end + + def self.smethod_definition + 'world' + end + + method_definition + smethod_definition + }.join + end; + ensure + $VERBOSE = verbose_bak + end + + def test_compile_insn_putspecialobject + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'aa', success_count: 2, insns: %i[putspecialobject]) + begin; + print 2.times.map { + def a + 'a' + end + + alias :b :a + + b + }.join + end; + end + + def test_compile_insn_putstring_concatstrings_objtostring + assert_compile_twice('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings objtostring]) + end + + def test_compile_insn_toregexp + assert_compile_twice('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp]) + end + + def test_compile_insn_newarray + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray]) + begin; + a, b, c = 1, 2, 3 + [a, b, c] + end; + end + + def test_compile_insn_newarraykwsplat + assert_compile_twice('[**{ x: 1 }]', result_inspect: '[{:x=>1}]', insns: %i[newarraykwsplat]) + end + + def test_compile_insn_intern_duparray + assert_compile_twice('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray]) + end + + def test_compile_insn_expandarray + assert_compile_twice('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray]) + end + + def test_compile_insn_concatarray + assert_compile_twice('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray]) + end + + def test_compile_insn_splatarray + assert_compile_twice('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray]) + end + + def test_compile_insn_newhash + assert_compile_twice('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash]) + end + + def test_compile_insn_duphash + assert_compile_twice('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash]) + end + + def test_compile_insn_newrange + assert_compile_twice('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange]) + end + + def test_compile_insn_pop + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop]) + begin; + a = false + b = 1 + a || b + end; + end + + def test_compile_insn_dup + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup]) + begin; + a = 1 + a&.+(2) + end; + end + + def test_compile_insn_dupn + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn]) + begin; + klass = Class.new + klass::X ||= true + end; + end + + def test_compile_insn_swap_topn + assert_compile_twice('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn]) + end + + def test_compile_insn_reput + omit "write test" + end + + def test_compile_insn_setn + assert_compile_twice('[nil][0] = 1', result_inspect: '1', insns: %i[setn]) + end + + def test_compile_insn_adjuststack + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack]) + begin; + x = [true] + x[0] ||= nil + x[0] + end; + end + + def test_compile_insn_defined + assert_compile_twice('defined?(a)', result_inspect: 'nil', insns: %i[defined]) + end + + def test_compile_insn_checkkeyword + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'truetrue', success_count: 1, insns: %i[checkkeyword]) + begin; + def test(x: rand) + x + end + print test(x: true) + print test(x: true) + end; + end + + def test_compile_insn_tracecoverage + omit "write test" + end + + def test_compile_insn_defineclass + omit "support this in mjit_compile (low priority)" + end + + def test_compile_insn_send + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4, insns: %i[send]) + begin; + print proc { yield_self { 1 } }.call + print proc { yield_self { 1 } }.call + end; + end + + def test_compile_insn_opt_str_freeze + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze]) + begin; + 'foo'.freeze + end; + end + + def test_compile_insn_opt_nil_p + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'false', insns: %i[opt_nil_p]) + begin; + nil.nil?.nil? + end; + end + + def test_compile_insn_opt_str_uminus + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus]) + begin; + -'bar' + end; + end + + def test_compile_insn_opt_newarray_max + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max]) + begin; + a = 1 + b = 2 + [a, b].max + end; + end + + def test_compile_insn_opt_newarray_min + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min]) + begin; + a = 1 + b = 2 + [a, b].min + end; + end + + def test_compile_insn_opt_send_without_block + assert_compile_twice('print', result_inspect: 'nil', insns: %i[opt_send_without_block]) + end + + def test_compile_insn_invokesuper + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 4, insns: %i[invokesuper]) + begin; + mod = Module.new { + def test + super + 2 + end + } + klass = Class.new { + prepend mod + def test + 1 + end + } + print klass.new.test + print klass.new.test + end; + end + + def test_compile_insn_invokeblock_leave + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '22', success_count: 2, insns: %i[invokeblock leave]) + begin; + def foo + yield + end + print foo { 2 } + print foo { 2 } + end; + end + + def test_compile_insn_throw + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '44', success_count: 2, insns: %i[throw]) + begin; + def test + proc do + if 1+1 == 1 + return 3 + else + return 4 + end + 5 + end.call + end + print test + print test + end; + end + + def test_compile_insn_jump_branchif + assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif]) + begin; + a = false + 1 + 1 while a + end; + end + + def test_compile_insn_branchunless + assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless]) + begin; + a = true + if a + 1 + else + 2 + end + end; + end + + def test_compile_insn_branchnil + assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil]) + begin; + a = 2 + a&.+(1) + end; + end + + def test_compile_insn_objtostring + assert_compile_twice("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[objtostring]) + begin; + a = '2' + "4#{a}" + end; + end + + def test_compile_insn_getconstant_path + assert_compile_twice('Struct', result_inspect: 'Struct', insns: %i[opt_getconstant_path]) + end + + def test_compile_insn_once + assert_compile_twice('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once]) + end + + def test_compile_insn_checkmatch_opt_case_dispatch + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch]) + begin; + case 'hello' + when 'hello' + 'world' + end + end; + end + + def test_compile_multiple_values_case + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[opt_case_dispatch]) + begin; + case 'hello' + when 'hello', 'world' + 'world' + end + end; + end + + def test_compile_insn_opt_calc + assert_compile_twice('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod]) + assert_compile_twice('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod]) + assert_compile_twice('4 + 2', result_inspect: '6') + end + + def test_compile_insn_opt_cmp + assert_compile_twice('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq]) + end + + def test_compile_insn_opt_rel + assert_compile_twice('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge]) + end + + def test_compile_insn_opt_ltlt + assert_compile_twice('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt]) + end + + def test_compile_insn_opt_and + assert_compile_twice('1 & 3', result_inspect: '1', insns: %i[opt_and]) + end + + def test_compile_insn_opt_or + assert_compile_twice('1 | 3', result_inspect: '3', insns: %i[opt_or]) + end + + def test_compile_insn_opt_aref + # optimized call (optimized JIT) -> send call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '221', success_count: 1, call_threshold: 2, insns: %i[opt_aref]) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call({ 1 => 2 }) + print block.call({ 1 => 2 }) + print block.call(obj) + end; + + # send call -> optimized call (send JIT) -> optimized call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 3, call_threshold: 2) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call(obj) + print block.call({ 1 => 2 }) + print block.call({ 1 => 2 }) + end; + end + + def test_compile_insn_opt_aref_with + assert_compile_twice("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with]) + end + + def test_compile_insn_opt_aset + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with]) + begin; + hash = { '1' => 2 } + (hash['2'] = 2) + (hash[1.to_s] = 3) + end; + end + + def test_compile_insn_opt_length_size + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size]) + begin; + array = [1, 2] + array.length + array.size + end; + end + + def test_compile_insn_opt_empty_p + assert_compile_twice('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p]) + end + + def test_compile_insn_opt_succ + assert_compile_twice('1.succ', result_inspect: '2', insns: %i[opt_succ]) + end + + def test_compile_insn_opt_not + assert_compile_twice('!!true', result_inspect: 'true', insns: %i[opt_not]) + end + + def test_compile_insn_opt_regexpmatch2 + assert_compile_twice("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch2]) + assert_compile_twice("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2]) + end + + def test_compile_insn_invokebuiltin + iseq = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first) + p RubyVM::InstructionSequence.of([].method(:sample)).to_a + EOS + insns = collect_insns(iseq) + mark_tested_insn(:invokebuiltin, used_insns: insns) + assert_eval_with_jit('print [].sample(1); print [].sample(1)', stdout: '[][]', success_count: 1) + end + + def test_compile_insn_opt_invokebuiltin_delegate_leave + 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");print "\x00".unpack("c")', stdout: '[0][0]', success_count: 1) + end + + def test_compile_insn_checkmatch + assert_compile_twice("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch]) + begin; + ary = %w(hello good-bye) + case 'hello' + when *ary + 'world' + end + end; + end + + def test_compile_opt_pc + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 1) + begin; + def test(arg = 'hello') + print arg + end + test + test + end; + end + + def test_mjit_output + out, err = eval_with_jit('4.times { puts "MJIT" }', verbose: 1, call_threshold: 2) + assert_equal("MJIT\n" * 4, out) + assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1$/, err) + assert_match(/^Successful MJIT finish$/, err) + end + + def test_nothing_to_unload_with_jit_wait + omit 'unload_units is removed for now' + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS) + begin; + def a1() a2() end + 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 + omit 'unload_units is removed for now' + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS) + begin; + def a1() a2(false); a2(true) end + 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 + omit 'unload_units is removed for now' + Dir.mktmpdir("jit_test_unload_units_") do |dir| + # MIN_CACHE_SIZE is 10 + out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, call_threshold: 2, max_cache: 10) + begin; + i = 0 + while i < 11 + eval(<<-EOS) + def mjit#{i} + print #{i} + end + mjit#{i} + EOS + i += 1 + end + + if defined?(fork) + # test the child does not try to delete files which are deleted by parent, + # and test possible deadlock on fork during MJIT unload and JIT compaction on child + Process.waitpid(Process.fork {}) + end + end; + + 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 /) + end + 10.times do |i| + assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit#{i}@\(eval\):/, errs[i], debug_info) + end + + 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(1, compactions.size, debug_info) + end + + if RUBY_PLATFORM.match?(/mswin/) + # "Permission Denied" error is preventing to remove so file on AppVeyor/RubyCI. + omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.' + end + if RUBY_PLATFORM.match?(/darwin/) + omit '.bundle.dSYM directory is left but removing it is not supported for now' + end + # verify .c files are deleted on unload_units + assert_send([Dir, :empty?, dir], debug_info) + end + end + + 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 + arr + p arr + end; + end + + def test_local_stack_on_exception + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 2) + begin; + def b + raise + rescue + 2 + end + + def a + # Calling #b should be vm_exec, not direct jit_exec. + # Otherwise `1` on local variable would be purged. + 1 + b + end + + print a + print a + end; + end + + def test_local_stack_with_sp_motion_by_blockargs + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 2) + begin; + def b(base) + 1 + end + + # This method is simple enough to have false in catch_except_p. + # So local_stack_p would be true in JIT compiler. + def a + m = method(:b) + + # ci->flag has VM_CALL_ARGS_BLOCKARG and cfp->sp is moved in vm_caller_setup_arg_block. + # So, for this send insn, JIT-ed code should use cfp->sp instead of local variables for stack. + Module.module_eval(&m) + end + + print a + print a + end; + end + + def test_catching_deep_exception + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 4) + begin; + def catch_true(paths, prefixes) # catch_except_p: true + prefixes.each do |prefix| # catch_except_p: true + paths.each do |path| # catch_except_p: false + return path + end + end + end + + def wrapper(paths, prefixes) + catch_true(paths, prefixes) + end + + print wrapper(['1'], ['2']) + print wrapper(['1'], ['2']) + end; + end + + def test_inlined_builtin_methods + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 1, call_threshold: 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: 1, recompile_count: 1, call_threshold: 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: 4, recompile_count: 2, call_threshold: 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: 4, call_threshold: 2) + begin; + class Foo + def initialize + @a = :a + end + + def bar + if @b.nil? + @b = :b + end + end + end + + print(Foo.new.bar) + print(Foo.new.bar) + print(Foo.new.bar) + end; + end + + def test_inlined_setivar_frozen + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 1, call_threshold: 2) + begin; + class A + def a + @a = 1 + end + end + + a = A.new + a.a + a.a + a.a + a.freeze + begin + a.a + rescue FrozenError => e + p e.class + end + end; + end + + def test_inlined_getconstant + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '11', success_count: 1, call_threshold: 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: 3, call_threshold: 2) + begin; + class A + attr_reader :a, :b + + def initialize + @a = 2 + end + + def test + a + end + + def undefined + b + end + end + + a = A.new + print(a.test * a.test) + p(a.undefined) + p(a.undefined) + + # redefinition + def a.test + 3 + end + + print(2 * a.test) + end; + + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 3, call_threshold: 2) + begin; + class Hoge + attr_reader :foo + + def initialize + @foo = [] + @bar = nil + end + end + + class Fuga < Hoge + def initialize + @bar = nil + @foo = [] + end + end + + def test(recv) + recv.foo.empty? + end + + hoge = Hoge.new + fuga = Fuga.new + + test(hoge) # VM: cc set index=1 + test(hoge) # JIT: compile with index=1 + test(fuga) # JIT -> VM: cc set index=2 + print test(hoge) # JIT: should use index=1, not index=2 in cc + end; + end + + def test_heap_promotion_of_ivar_in_the_middle_of_jit + omit if GC.using_rvargc? + + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\n", success_count: 2, call_threshold: 2) + begin; + class A + def initialize + @iv0 = nil + @iv1 = [] + end + + def test(add) + @iv0.nil? + add_ivar if add + @iv1.empty? + end + + def add_ivar + @iv2 = nil + @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.0", success_count: 1, call_threshold: 2) + begin; + def test(foo) + ".#{foo unless foo == 1}" if true + end + print test(0) + print test(0) + end; + end + + def test_clean_so + if RUBY_PLATFORM.match?(/mswin/) + omit 'Removing so file is randomly failing on AppVeyor/RubyCI mswin due to Permission Denied.' + end + if RUBY_PLATFORM.match?(/darwin/) + omit '.bundle.dSYM directory is left but removing it is not supported for now' + end + Dir.mktmpdir("jit_test_clean_so_") do |dir| + code = "x = 0; 10.times {|i|x+=i}" + eval_with_jit({"TMPDIR"=>dir}, code) + assert_send([Dir, :empty?, dir], "Directory #{dir} was not empty:\n#{Dir.glob("#{dir}/*").join("\n")}\n") + eval_with_jit({"TMPDIR"=>dir}, code, save_temps: true) + assert_not_send([Dir, :empty?, dir]) + end + end + + def test_clean_objects_on_exec + if /mswin|mingw/ =~ RUBY_PLATFORM + # TODO: check call stack and close handle of code which is not on stack, and remove objects on best-effort basis + omit 'Removing so file being used does not work on Windows' + end + if RUBY_PLATFORM.match?(/darwin/) + omit '.bundle.dSYM directory is left but removing it is not supported for now' + end + Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir| + eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2) + begin; + def a; end; a + exec "true" + end; + error_message = "Undeleted files:\n #{Dir.glob("#{dir}/*").join("\n ")}\n" + assert_send([Dir, :empty?, dir], error_message) + end + end + + def test_lambda_longjmp + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '5', success_count: 1) + begin; + fib = lambda do |x| + return x if x == 0 || x == 1 + fib.call(x-1) + fib.call(x-2) + end + print fib.call(5) + end; + end + + def test_stack_pointer_with_assignment + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1) + begin; + 2.times do + a, _ = nil + p a + end + end; + end + + def test_frame_omitted_inlining + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true\ntrue\ntrue\ntrue\n", success_count: 2, call_threshold: 2) + begin; + class Integer + remove_method :zero? + def zero? + self == 0 + end + end + + 4.times do + p 0.zero? + end + end; + end + + def test_block_handler_with_possible_frame_omitted_inlining + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "70.0\n70.0\n70.0\n70.0\n", success_count: 2, call_threshold: 2) + begin; + def multiply(a, b) + a *= b + end + + 4.times do + p multiply(7.0, 10.0) + end + end; + end + + def test_builtin_frame_omitted_inlining + assert_eval_with_jit('0.zero?; 0.zero?; 3.times { p 0.zero? }', stdout: "true\ntrue\ntrue\n", success_count: 1, call_threshold: 2) + end + + def test_program_counter_with_regexpmatch + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aa", success_count: 1) + begin; + 2.times do + break if /a/ =~ "ab" && !$~[0] + print $~[0] + end + end; + end + + def test_pushed_values_with_opt_aset_with + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "{}{}", success_count: 1) + begin; + 2.times do + print(Thread.current["a"] = {}) + end + end; + end + + def test_pushed_values_with_opt_aref_with + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1) + begin; + 2.times do + p(Thread.current["a"]) + end + end; + end + + def test_mjit_pause_wait + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '', success_count: 0, call_threshold: 2) + begin; + RubyVM::MJIT.pause + proc {}.call + end; + end + + def test_not_cancel_by_tracepoint_class + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, call_threshold: 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, call_threshold: 2) + begin; + TracePoint.new(:line) {}.enable + 2.times {} + end; + end + + def test_cancel_by_bop_redefinition + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 0, call_threshold: 2) + begin; + class Integer + def <(x) + true + end + end + 2.times {} + end; + end + + def test_caller_locations_without_catch_table + out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2) + begin; + def b # 2 + caller_locations.first # 3 + end # 4 + # 5 + def a # 6 + print # <-- don't leave PC here # 7 + b # 8 + end + puts a + puts a + end; + lines = out.lines + assert_equal("-e:8:in `a'\n", lines[0]) + assert_equal("-e:8:in `a'\n", lines[1]) + end + + def test_jit_failure + _, err = Dir.mktmpdir("jit_test_jit_failure") do |dir| + eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", call_threshold: 2, verbose: 1) + begin; + 2.times do + class A + end + end + end; + end + assert_match(/^MJIT warning: .+ unsupported instruction: defineclass/, err) + end + + private + + # The shortest way to test one proc + def assert_compile_twice(script, result_inspect:, insns: [], uplevel: 1) + if script.match?(/\A\n.+\n\z/m) + script = script.gsub(/^/, ' ') + else + script = " #{script} " + end + assert_eval_with_jit("test = proc {#{script}}; p test.call; p test.call", stdout: "#{result_inspect}\n#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1) + end + + # Shorthand for normal test cases + def assert_eval_with_jit(script, stdout: nil, success_count:, recompile_count: nil, call_threshold: 2, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: []) + out, err = eval_with_jit(script, verbose: 1, call_threshold: call_threshold, max_cache: max_cache) + success_actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size + recompile_actual = err.scan(/^#{JIT_RECOMPILE_PREFIX}:/).size + # Add --mjit-verbose=2 logs for cl.exe because compiler's error message is suppressed + # for cl.exe with --mjit-verbose=1. See `start_process` in mjit.c. + if RUBY_PLATFORM.match?(/mswin/) && success_count != success_actual + out2, err2 = eval_with_jit(script, verbose: 2, call_threshold: call_threshold, max_cache: max_cache) + end + + # Make sure that the script has insns expected to be tested + used_insns = method_insns(script) + insns.each do |insn| + 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, 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 + ignorable_patterns).any? { |pat| pat.match?(l) } + end + unless err_lines.empty? + warn err_lines.join(''), uplevel: uplevel + end + end + + def mark_tested_insn(insn, used_insns:, uplevel: 1) + # 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 + TestMJIT.untested_insns.delete(insn) + end + + # Collect block's insns or defined method's insns, which are expected to be JIT-ed. + # Note that this intentionally excludes insns in script's toplevel because they are not JIT-ed. + def method_insns(script) + insns = [] + RubyVM::InstructionSequence.compile(script).to_a.last.each do |(insn, *args)| + case insn + when :send + insns += collect_insns(args.last) + when :definemethod, :definesmethod + insns += collect_insns(args[1]) + when :defineclass + insns += collect_insns(args[1]) + end + end + insns.uniq + end + + # Recursively collect insns in iseq_array + def collect_insns(iseq_array) + return [] if iseq_array.nil? + + insns = iseq_array.last.select { |x| x.is_a?(Array) }.map(&:first) + iseq_array.last.each do |(insn, *args)| + case insn + when :definemethod, :definesmethod, :send + insns += collect_insns(args.last) + end + end + insns + end +end diff --git a/test/ruby/test_mjit_debug.rb b/test/ruby/test_mjit_debug.rb new file mode 100644 index 0000000000..0b50acc68d --- /dev/null +++ b/test/ruby/test_mjit_debug.rb @@ -0,0 +1,17 @@ +require_relative 'test_mjit' + +return unless defined?(TestMJIT) +return if ENV.key?('APPVEYOR') +return if ENV.key?('RUBYCI_NICKNAME') +return if ENV['RUBY_DEBUG']&.include?('ci') # ci.rvm.jp +return if /mswin/ =~ RUBY_PLATFORM + +class TestMJITDebug < TestMJIT + @@test_suites.delete TestMJIT if self.respond_to? :on_parallel_worker? + + def setup + super + # let `#eval_with_jit` use --mjit-debug + @mjit_debug = true + end +end diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index d1c5d53822..24785affa0 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) @@ -246,6 +253,14 @@ class TestModule < Test::Unit::TestCase assert_operator(Math, :const_defined?, "PI") assert_not_operator(Math, :const_defined?, :IP) assert_not_operator(Math, :const_defined?, "IP") + + # Test invalid symbol name + # [Bug #20245] + EnvUtil.under_gc_stress do + assert_raise(EncodingError) do + Math.const_defined?("\xC3") + end + end end def each_bad_constants(m, &b) @@ -260,7 +275,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,11 +309,18 @@ class TestModule < Test::Unit::TestCase end def test_nested_get - assert_equal Other, Object.const_get([self.class, Other].join('::')) + 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) { + Object.const_get([self.class.name, 'String'].join('::')) + } end def test_nested_get_symbol + OtherSetup.call + const = [self.class, Other].join('::').to_sym assert_raise(NameError) {Object.const_get(const)} @@ -325,12 +347,17 @@ 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']) + assert_not_send([Object, :const_defined?, [self.class.name, 'String'].join('::')]) end def test_nested_defined_symbol + OtherSetup.call + const = [self.class, Other].join('::').to_sym assert_raise(NameError) {Object.const_defined?(const)} @@ -338,6 +365,17 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError) {self.class.const_defined?(const)} end + def test_nested_defined_inheritance + assert_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::')]) + assert_send([self.class, :const_defined?, 'User::MIXIN']) + assert_send([Object, :const_defined?, 'File::SEEK_SET']) + + # const_defined? with `false` + assert_not_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::'), false]) + assert_not_send([self.class, :const_defined?, 'User::MIXIN', false]) + assert_not_send([Object, :const_defined?, 'File::SEEK_SET', false]) + end + def test_nested_defined_bad_class assert_raise(TypeError) do self.class.const_defined?('User::USER::Foo') @@ -345,6 +383,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) @@ -372,19 +412,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 @@ -394,15 +435,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 - assert_empty(m.instance_methods, bug9813) - assert_empty(m.instance_variables, bug9813) - assert_empty(m.constants, bug9813) + end + + class Bug18185 < Module + module InstanceMethods + end + attr_reader :ancestor_list + def initialize + @ancestor_list = ancestors + include InstanceMethods + end + 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 @@ -444,6 +536,189 @@ class TestModule < Test::Unit::TestCase assert_raise(ArgumentError) { Module.new { include } } end + def test_include_before_initialize + m = Class.new(Module) do + def initialize(...) + include Enumerable + super + end + end.new + assert_operator(m, :<, Enumerable) + end + + def test_prepend_self + m = Module.new + assert_equal([m], m.ancestors) + 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_ancestry_of_duped_classes + m = Module.new + sc = Class.new + a = Class.new(sc) do + def b; 2 end + prepend m + end + + a2 = a.dup.new + + assert_kind_of Object, a2 + assert_kind_of sc, a2 + refute_kind_of a, a2 + assert_kind_of m, a2 + + assert_kind_of Class, a2.class + assert_kind_of sc.singleton_class, a2.class + assert_same sc, a2.class.superclass + end + + def test_gc_prepend_chain + assert_separately([], <<-EOS) + 10000.times { |i| + 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) @@ -454,6 +729,92 @@ 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_include_into_module_after_prepend_bug_20871 + bar = Module.new{def bar; 'bar'; end} + foo = Module.new{def foo; 'foo'; end} + m = Module.new + c = Class.new{include m} + m.prepend bar + Class.new{include m} + m.include foo + assert_include c.ancestors, foo + assert_equal "foo", c.new.foo + end + + def test_protected_include_into_included_module + m1 = Module.new do + def other_foo(other) + other.foo + end + + protected + def foo + :ok + end + end + m2 = Module.new + c1 = Class.new { include m2 } + c2 = Class.new { include m2 } + m2.include(m1) + + assert_equal :ok, c1.new.other_foo(c2.new) + end + def test_instance_methods assert_equal([:user, :user2], User.instance_methods(false).sort) assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort) @@ -462,26 +823,53 @@ class TestModule < Test::Unit::TestCase assert_equal([:cClass], (class << CClass; self; end).instance_methods(false)) assert_equal([], (class << BClass; self; end).instance_methods(false)) assert_equal([:cm2], (class << AClass; self; end).instance_methods(false)) - # Ruby 1.8 feature change: - # #instance_methods includes protected methods. - #assert_equal([:aClass], AClass.instance_methods(false)) assert_equal([:aClass, :aClass2], AClass.instance_methods(false).sort) assert_equal([:aClass, :aClass2], (AClass.instance_methods(true) - Object.instance_methods(true)).sort) end def test_method_defined? - assert !User.method_defined?(:wombat) - assert User.method_defined?(:mixin) - assert User.method_defined?(:user) - assert User.method_defined?(:user2) - assert !User.method_defined?(:user3) + [User, Class.new{include User}, Class.new{prepend User}].each do |klass| + [[], [true]].each do |args| + assert !klass.method_defined?(:wombat, *args) + assert klass.method_defined?(:mixin, *args) + assert klass.method_defined?(:user, *args) + assert klass.method_defined?(:user2, *args) + assert !klass.method_defined?(:user3, *args) - assert !User.method_defined?("wombat") - assert User.method_defined?("mixin") - assert User.method_defined?("user") - assert User.method_defined?("user2") - assert !User.method_defined?("user3") + assert !klass.method_defined?("wombat", *args) + assert klass.method_defined?("mixin", *args) + assert klass.method_defined?("user", *args) + assert klass.method_defined?("user2", *args) + assert !klass.method_defined?("user3", *args) + end + end + end + + def test_method_defined_without_include_super + assert User.method_defined?(:user, false) + assert !User.method_defined?(:mixin, false) + assert Mixin.method_defined?(:mixin, false) + + User.const_set(:FOO, c = Class.new) + + c.prepend(User) + assert !c.method_defined?(:user, false) + c.define_method(:user){} + assert c.method_defined?(:user, false) + + assert !c.method_defined?(:mixin, false) + c.define_method(:mixin){} + assert c.method_defined?(:mixin, false) + + 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 @@ -512,6 +900,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 @@ -526,6 +922,28 @@ class TestModule < Test::Unit::TestCase assert_equal("Integer", Integer.name) assert_equal("TestModule::Mixin", Mixin.name) assert_equal("TestModule::User", User.name) + + assert_predicate Integer.name, :frozen? + assert_predicate Mixin.name, :frozen? + assert_predicate User.name, :frozen? + end + + def test_accidental_singleton_naming_with_module + o = Object.new + assert_nil(o.singleton_class.name) + class << o + module Hi; end + end + assert_nil(o.singleton_class.name) + end + + def test_accidental_singleton_naming_with_class + o = Object.new + assert_nil(o.singleton_class.name) + class << o + class Hi; end + end + assert_nil(o.singleton_class.name) end def test_classpath @@ -533,13 +951,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) @@ -547,12 +965,21 @@ class TestModule < Test::Unit::TestCase assert_equal(prefix+"N", m.const_get(:N).name) assert_equal(prefix+"O", m.const_get(:O).name) 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 @@ -575,6 +1002,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 @@ -582,12 +1014,125 @@ class TestModule < Test::Unit::TestCase assert_equal([:bClass1], BClass.public_instance_methods(false)) end + def test_undefined_instance_methods + assert_equal([], AClass.undefined_instance_methods) + assert_equal([], BClass.undefined_instance_methods) + c = Class.new(AClass) {undef aClass} + assert_equal([:aClass], c.undefined_instance_methods) + c = Class.new(c) + assert_equal([], c.undefined_instance_methods) + end + + def test_s_public + o = (c = Class.new(AClass)).new + assert_raise(NoMethodError, /private method/) {o.aClass1} + 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)) @@ -636,28 +1181,33 @@ class TestModule < Test::Unit::TestCase def bar; end end m.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.module_eval do def foo; end end end - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.__send__ :private, :bar end - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.private_class_method :baz end 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)) @@ -666,6 +1216,32 @@ class TestModule < Test::Unit::TestCase assert_equal(false, o.respond_to?(:bar=)) end + def test_attr_public_at_toplevel + s = Object.new + TOPLEVEL_BINDING.eval(<<-END).call(s.singleton_class) + proc do |c| + c.send(:attr_accessor, :x) + c.send(:attr, :y) + c.send(:attr_reader, :z) + c.send(:attr_writer, :w) + end + END + assert_nil s.x + s.x = 1 + assert_equal 1, s.x + + assert_nil s.y + s.instance_variable_set(:@y, 2) + assert_equal 2, s.y + + assert_nil s.z + s.instance_variable_set(:@z, 3) + assert_equal 3, s.z + + s.w = 4 + assert_equal 4, s.instance_variable_get(:@w) + end + def test_const_get_evaled c1 = Class.new c2 = Class.new(c1) @@ -676,6 +1252,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 } @@ -723,10 +1300,6 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError) { c1.const_get(:foo) } bug5084 = '[ruby-dev:44200]' assert_raise(TypeError, bug5084) { c1.const_get(1) } - bug7574 = '[ruby-dev:46749]' - assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { - Object.const_get("String\0") - } end def test_const_defined_invalid_name @@ -734,10 +1307,6 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError) { c1.const_defined?(:foo) } bug5084 = '[ruby-dev:44200]' assert_raise(TypeError, bug5084) { c1.const_defined?(1) } - bug7574 = '[ruby-dev:46749]' - assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { - Object.const_defined?("String\0") - } end def test_const_get_no_inherited @@ -779,8 +1348,6 @@ class TestModule < Test::Unit::TestCase end end include LangModuleSpecInObject - module LangModuleTop - end puts "ok" if LangModuleSpecInObject::LangModuleTop == LangModuleTop INPUT @@ -881,6 +1448,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 @@ -949,7 +1538,7 @@ class TestModule < Test::Unit::TestCase def test_frozen_module m = Module.new m.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.instance_eval { undef_method(:foo) } end end @@ -957,7 +1546,7 @@ class TestModule < Test::Unit::TestCase def test_frozen_class c = Class.new c.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do c.instance_eval { undef_method(:foo) } end end @@ -967,7 +1556,7 @@ class TestModule < Test::Unit::TestCase o = klass.new c = class << o; self; end c.freeze - assert_raise_with_message(RuntimeError, /frozen/) do + assert_raise_with_message(FrozenError, /frozen/) do c.instance_eval { undef_method(:foo) } end klass.class_eval do @@ -977,8 +1566,8 @@ class TestModule < Test::Unit::TestCase end def test_method_defined - c = Class.new - c.class_eval do + cl = Class.new + def_methods = proc do def foo; end def bar; end def baz; end @@ -986,43 +1575,69 @@ class TestModule < Test::Unit::TestCase protected :bar private :baz end + cl.class_eval(&def_methods) + sc = Class.new(cl) + mod = Module.new(&def_methods) + only_prepend = Class.new{prepend(mod)} + empty_prepend = cl.clone + empty_prepend.prepend(Module.new) + overlap_prepend = cl.clone + overlap_prepend.prepend(mod) + + [[], [true], [false]].each do |args| + [cl, sc, only_prepend, empty_prepend, overlap_prepend].each do |c| + always_false = [sc, only_prepend].include?(c) && args == [false] - assert_equal(true, c.public_method_defined?(:foo)) - assert_equal(false, c.public_method_defined?(:bar)) - assert_equal(false, c.public_method_defined?(:baz)) + assert_equal(always_false ? false : true, c.public_method_defined?(:foo, *args)) + assert_equal(always_false ? false : false, c.public_method_defined?(:bar, *args)) + assert_equal(always_false ? false : false, c.public_method_defined?(:baz, *args)) - # Test if string arguments are converted to symbols - assert_equal(true, c.public_method_defined?("foo")) - assert_equal(false, c.public_method_defined?("bar")) - assert_equal(false, c.public_method_defined?("baz")) + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : true, c.public_method_defined?("foo", *args)) + assert_equal(always_false ? false : false, c.public_method_defined?("bar", *args)) + assert_equal(always_false ? false : false, c.public_method_defined?("baz", *args)) - assert_equal(false, c.protected_method_defined?(:foo)) - assert_equal(true, c.protected_method_defined?(:bar)) - assert_equal(false, c.protected_method_defined?(:baz)) + assert_equal(always_false ? false : false, c.protected_method_defined?(:foo, *args)) + assert_equal(always_false ? false : true, c.protected_method_defined?(:bar, *args)) + assert_equal(always_false ? false : false, c.protected_method_defined?(:baz, *args)) - # Test if string arguments are converted to symbols - assert_equal(false, c.protected_method_defined?("foo")) - assert_equal(true, c.protected_method_defined?("bar")) - assert_equal(false, c.protected_method_defined?("baz")) + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : false, c.protected_method_defined?("foo", *args)) + assert_equal(always_false ? false : true, c.protected_method_defined?("bar", *args)) + assert_equal(always_false ? false : false, c.protected_method_defined?("baz", *args)) - assert_equal(false, c.private_method_defined?(:foo)) - assert_equal(false, c.private_method_defined?(:bar)) - assert_equal(true, c.private_method_defined?(:baz)) + assert_equal(always_false ? false : false, c.private_method_defined?(:foo, *args)) + assert_equal(always_false ? false : false, c.private_method_defined?(:bar, *args)) + assert_equal(always_false ? false : true, c.private_method_defined?(:baz, *args)) - # Test if string arguments are converted to symbols - assert_equal(false, c.private_method_defined?("foo")) - assert_equal(false, c.private_method_defined?("bar")) - assert_equal(true, c.private_method_defined?("baz")) + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : false, c.private_method_defined?("foo", *args)) + assert_equal(always_false ? false : false, c.private_method_defined?("bar", *args)) + assert_equal(always_false ? false : true, c.private_method_defined?("baz", *args)) + end + end 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 @@ -1135,6 +1750,45 @@ class TestModule < Test::Unit::TestCase assert_match(/::X\u{df}:/, c.new.to_s) end + + def test_const_added + eval(<<~RUBY) + module TestConstAdded + @memo = [] + class << self + attr_accessor :memo + + def const_added(sym) + memo << sym + end + end + CONST = 1 + module SubModule + end + + class SubClass + end + end + TestConstAdded::OUTSIDE_CONST = 2 + module TestConstAdded::OutsideSubModule; end + class TestConstAdded::OutsideSubClass; end + RUBY + TestConstAdded.const_set(:CONST_SET, 3) + assert_equal [ + :CONST, + :SubModule, + :SubClass, + :OUTSIDE_CONST, + :OutsideSubModule, + :OutsideSubClass, + :CONST_SET, + ], TestConstAdded.memo + ensure + if self.class.const_defined? :TestConstAdded + self.class.send(:remove_const, :TestConstAdded) + end + end + def test_method_added memo = [] mod = Module.new do @@ -1301,6 +1955,17 @@ class TestModule < Test::Unit::TestCase assert_match(/: warning: previous definition of foo/, stderr) end + def test_module_function_inside_method + assert_warn(/calling module_function without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Module.new do + def self.foo + module_function + end + foo + end + end + end + def test_protected_singleton_method klass = Class.new x = klass.new @@ -1352,21 +2017,55 @@ class TestModule < Test::Unit::TestCase assert_raise(ArgumentError, bug8540) { c.new.send :foo= } end - def test_private_constant + def test_private_constant_in_class c = Class.new c.const_set(:FOO, "foo") assert_equal("foo", c::FOO) c.private_constant(:FOO) - assert_raise(NameError) { c::FOO } + e = assert_raise(NameError) {c::FOO} + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) assert_equal("foo", c.class_eval("FOO")) assert_equal("foo", c.const_get("FOO")) $VERBOSE, verbose = nil, $VERBOSE c.const_set(:FOO, "foo") $VERBOSE = verbose - assert_raise(NameError) { c::FOO } - assert_raise_with_message(NameError, /#{c}::FOO/) do + e = assert_raise(NameError) {c::FOO} + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise_with_message(NameError, /#{c}::FOO/) do Class.new(c)::FOO end + assert_equal(c, e.receiver) + assert_equal(:FOO, e.name) + end + + def test_private_constant_in_module + m = Module.new + m.const_set(:FOO, "foo") + assert_equal("foo", m::FOO) + m.private_constant(:FOO) + e = assert_raise(NameError) {m::FOO} + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + assert_equal("foo", m.class_eval("FOO")) + assert_equal("foo", m.const_get("FOO")) + $VERBOSE, verbose = nil, $VERBOSE + m.const_set(:FOO, "foo") + $VERBOSE = verbose + e = assert_raise(NameError) {m::FOO} + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise(NameError, /#{m}::FOO/) do + Module.new {include m}::FOO + end + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) + e = assert_raise(NameError, /#{m}::FOO/) do + Class.new {include m}::FOO + end + assert_equal(m, e.receiver) + assert_equal(:FOO, e.name) end def test_private_constant2 @@ -1391,6 +2090,21 @@ class TestModule < Test::Unit::TestCase RUBY end + def test_private_constant_const_missing + c = Class.new + c.const_set(:FOO, "foo") + c.private_constant(:FOO) + class << c + attr_reader :const_missing_arg + def const_missing(name) + @const_missing_arg = name + name == :FOO ? const_get(:FOO) : super + end + end + assert_equal("foo", c::FOO) + assert_equal(:FOO, c.const_missing_arg) + end + class PrivateClass end private_constant :PrivateClass @@ -1423,10 +2137,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"} + 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 @@ -1635,6 +2370,18 @@ class TestModule < Test::Unit::TestCase assert_equal(:foo, removed) end + def test_frozen_prepend_remove_method + [Module, Class].each do |klass| + mod = klass.new do + prepend(Module.new) + def foo; end + end + mod.freeze + assert_raise(FrozenError, '[Bug #19166]') { mod.send(:remove_method, :foo) } + assert_equal([:foo], mod.instance_methods(false)) + end + end + def test_prepend_class_ancestors bug6658 = '[ruby-core:45919]' m = labeled_module("m") @@ -1666,7 +2413,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} @@ -1747,6 +2494,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 @@ -1789,6 +2667,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]' @@ -1876,6 +2781,25 @@ class TestModule < Test::Unit::TestCase assert_raise(ArgumentError) { Module.new { prepend } } end + def test_prepend_private_super + wrapper = Module.new do + def wrapped + super + 1 + end + end + + klass = Class.new do + prepend wrapper + + def wrapped + 1 + end + private :wrapped + end + + assert_equal(2, klass.new.wrapped) + end + def test_class_variables m = Module.new m.class_variable_set(:@@foo, 1) @@ -1888,6 +2812,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 @@ -1954,20 +2894,19 @@ class TestModule < Test::Unit::TestCase $foo \u3042$ ].each do |name| - assert_raise_with_message(NameError, /#{Regexp.quote(quote(name))}/) do + e = assert_raise(NameError) do Module.new { attr_accessor name.to_sym } end + assert_equal(name, e.name.to_s) end end - private def quote(name) - encoding = Encoding.default_internal || Encoding.default_external - (name.encoding == encoding || name.ascii_only?) ? name : name.inspect - end - class AttrTest class << self attr_accessor :cattr + def reset + self.cattr = nil + end end attr_accessor :iattr def ivar @@ -1977,7 +2916,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) @@ -1986,7 +2925,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 @@ -2010,6 +2949,8 @@ class TestModule < Test::Unit::TestCase assert_warning '' do assert_equal(42, AttrTest.cattr) end + + AttrTest.reset end def test_uninitialized_attr_non_object @@ -2030,6 +2971,22 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError){ m.instance_eval { remove_const(:__FOO__) } } end + def test_public_methods + public_methods = %i[ + include + prepend + attr + attr_accessor + attr_reader + attr_writer + define_method + alias_method + undef_method + remove_method + ] + assert_equal public_methods.sort, (Module.public_methods & public_methods).sort + end + def test_private_top_methods assert_top_method_is_private(:include) assert_top_method_is_private(:public) @@ -2061,17 +3018,17 @@ class TestModule < Test::Unit::TestCase bug11532 = '[ruby-core:70828] [Bug #11532]' c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {private_constant :A} } c = Class.new {const_set(:A, 1); private_constant :A}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {public_constant :A} } c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {deprecate_constant :A} } end @@ -2092,34 +3049,9 @@ class TestModule < Test::Unit::TestCase def test_visibility_by_public_class_method bug8284 = '[ruby-core:54404] [Bug #8284]' - assert_raise(NoMethodError) {Object.define_method} - Module.new.public_class_method(:define_method) - assert_raise(NoMethodError, bug8284) {Object.define_method} - 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 + assert_raise(NoMethodError) {Object.remove_const} + Module.new.public_class_method(:remove_const) + assert_raise(NoMethodError, bug8284) {Object.remove_const} end def test_return_value_of_define_method @@ -2158,9 +3090,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" @@ -2177,9 +3167,9 @@ class TestModule < Test::Unit::TestCase A.prepend InspectIsShallow - expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)>" - 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 @@ -2202,16 +3192,19 @@ class TestModule < Test::Unit::TestCase end def test_redefinition_mismatch + omit "Investigating trunk-mjit failure on ci.rvm.jp" if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? m = Module.new - m.module_eval "A = 1" - assert_raise_with_message(TypeError, /is not a module/) { + m.module_eval "A = 1", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /is not a module/) { m.module_eval "module A; end" } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") n = "M\u{1f5ff}" - m.module_eval "#{n} = 42" - assert_raise_with_message(TypeError, "#{n} is not a module") { + m.module_eval "#{n} = 42", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /#{n} is not a module/) { m.module_eval "module #{n}; end" } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") assert_separately([], <<-"end;") Etc = (class C\u{1f5ff}; self; end).new @@ -2221,6 +3214,142 @@ class TestModule < Test::Unit::TestCase end; end + def test_private_extended_module + assert_separately [], %q{ + class Object + def bar; "Object#bar"; end + end + module M1 + def bar; super; end + end + module M2 + include M1 + private(:bar) + def foo; bar; end + end + extend M2 + assert_equal 'Object#bar', foo + } + end + + ConstLocation = [__FILE__, __LINE__] + + def test_const_source_location + assert_equal(ConstLocation, self.class.const_source_location(:ConstLocation)) + assert_equal(ConstLocation, self.class.const_source_location("ConstLocation")) + assert_equal(ConstLocation, Object.const_source_location("#{self.class.name}::ConstLocation")) + assert_raise(TypeError) { + self.class.const_source_location(nil) + } + assert_raise_with_message(NameError, /wrong constant name/) { + self.class.const_source_location("xxx") + } + assert_raise_with_message(TypeError, %r'does not refer to class/module') { + self.class.const_source_location("ConstLocation::FILE") + } + end + + module CloneTestM_simple + C = 1 + def self.m; C; end + end + + module CloneTestM0 + def foo; TEST; end + end + + CloneTestM1 = CloneTestM0.clone + CloneTestM2 = CloneTestM0.clone + module CloneTestM1 + TEST = :M1 + end + module CloneTestM2 + TEST = :M2 + end + class CloneTestC1 + include CloneTestM1 + end + class CloneTestC2 + include CloneTestM2 + end + + def test_constant_access_from_method_in_cloned_module + m = CloneTestM_simple.dup + assert_equal 1, m::C, '[ruby-core:47834]' + assert_equal 1, m.m, '[ruby-core:47834]' + + assert_equal :M1, CloneTestC1.new.foo, '[Bug #15877]' + 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] [Bug #19896] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + $c = Class.new do + def foo; end + end + + $m = Module.new do + refine $c do + def foo; end + end + end + + Class.new do + using $m + + def initialize + o = $c.new + o.method(:foo).unbind + end + end.new + end + 1_000.times(&code) + PREP + 300_000.times(&code) + CODE + end + + def test_module_clone_memory_leak + # [Bug #19901] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + Module.new.clone + end + 1_000.times(&code) + PREP + 1_000_000.times(&code) + CODE + end + private def assert_top_method_is_private(method) @@ -2229,7 +3358,8 @@ class TestModule < Test::Unit::TestCase assert_include(methods, :#{method}, ":#{method} should be private") assert_raise_with_message(NoMethodError, "private method `#{method}' called for main:Object") { - self.#{method} + recv = self + recv.#{method} } } end 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_not.rb b/test/ruby/test_not.rb index 721f868a5a..12e4c4b696 100644 --- a/test/ruby/test_not.rb +++ b/test/ruby/test_not.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'test/unit' -class TestIfunless < Test::Unit::TestCase +class TestNot < Test::Unit::TestCase def test_not_with_grouped_expression assert_equal(false, (not (true))) assert_equal(true, (not (false))) diff --git a/test/ruby/test_notimp.rb b/test/ruby/test_notimp.rb deleted file mode 100644 index ddebb657bf..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 - Timeout.timeout(EnvUtil.apply_timeout_scale(5)) { - 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 49008247ed..ab492743f6 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -26,6 +26,10 @@ class TestNumeric < Test::Unit::TestCase assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"} assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"} assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym} bug10711 = '[ruby-core:67405] [Bug #10711]' exp = "1.2 can't be coerced into Integer" @@ -52,7 +56,6 @@ class TestNumeric < Test::Unit::TestCase end.new assert_equal(-1, -a) - bug7688 = '[ruby-core:51389] [Bug #7688]' a = Class.new(Numeric) do def coerce(x); raise StandardError, "my error"; end end.new @@ -197,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 @@ -226,8 +237,18 @@ 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) - enum = from.step(*args) + kw = args.last.is_a?(Hash) ? args.pop : {} + enum = from.step(*args, **kw) size = enum.size xsize = expected.size @@ -236,7 +257,7 @@ class TestNumeric < Test::Unit::TestCase assert_send [size, :>, 0], "step size: +infinity" a = [] - from.step(*args) { |x| a << x; break if a.size == xsize } + from.step(*args, **kw) { |x| a << x; break if a.size == xsize } assert_equal expected, a, "step" a = [] @@ -246,7 +267,7 @@ class TestNumeric < Test::Unit::TestCase assert_equal expected.size, size, "step size" a = [] - from.step(*args) { |x| a << x } + from.step(*args, **kw) { |x| a << x } assert_equal expected, a, "step" a = [] @@ -260,18 +281,21 @@ class TestNumeric < Test::Unit::TestCase assert_raise(ArgumentError) { 1.step(10, 1, 0) { } } assert_raise(ArgumentError) { 1.step(10, 1, 0).size } assert_raise(ArgumentError) { 1.step(10, 0) { } } - assert_raise(ArgumentError) { 1.step(10, 0).size } assert_raise(ArgumentError) { 1.step(10, "1") { } } assert_raise(ArgumentError) { 1.step(10, "1").size } assert_raise(TypeError) { 1.step(10, nil) { } } - assert_raise(TypeError) { 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(10, nil).size } assert_nothing_raised { 1.step(by: nil) } assert_nothing_raised { 1.step(by: nil).size } + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: nil)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: 10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: -1)) + bug9811 = '[ruby-dev:48177] [Bug #9811]' assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} } assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size } @@ -280,6 +304,30 @@ 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 } + 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) @@ -289,7 +337,6 @@ class TestNumeric < Test::Unit::TestCase i <<= 1 until (bigflo - i).to_i < bignum bigflo -= i >> 1 assert_equal(bigflo.to_i, (0.0).step(bigflo-1.0, 1.0).size) - assert_operator((0.0).step(bignum.to_f, 1.0).size, :>=, bignum) # may loose precision assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 10] assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, to: 10] @@ -306,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] @@ -317,19 +362,20 @@ 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 + end - 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 + def test_step_bug15537 + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, to: 1, by: -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, to: 1.0, by: -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, 1.0, -2] + + assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0, to: nil], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10, nil, -1.0], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10.0, by: -1], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10.0, nil, -1], inf: true end def test_num2long @@ -358,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]' @@ -384,4 +442,47 @@ class TestNumeric < Test::Unit::TestCase end end end + + def test_pow + assert_equal(2**3, 2.pow(3)) + assert_equal(2**-1, 2.pow(-1)) + assert_equal(2**0.5, 2.pow(0.5)) + assert_equal((-1)**0.5, -1.pow(0.5)) + assert_equal(3**3 % 8, 3.pow(3, 8)) + assert_equal(3**3 % -8, 3.pow(3,-8)) + assert_equal(3**2 % -2, 3.pow(2,-2)) + assert_equal((-3)**3 % 8, -3.pow(3,8)) + assert_equal((-3)**3 % -8, -3.pow(3,-8)) + assert_equal(5**2 % -8, 5.pow(2,-8)) + assert_equal(4481650795473624846969600733813414725093, + 2120078484650058507891187874713297895455. + pow(5478118174010360425845660566650432540723, + 5263488859030795548286226023720904036518)) + + assert_equal(12, 12.pow(1, 10000000000), '[Bug #14259]') + 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 e55a09dc23..26256b80db 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 @@ -96,17 +131,6 @@ class TestObject < Test::Unit::TestCase assert_raise(TypeError) { 1.kind_of?(1) } end - def test_taint_frozen_obj - o = Object.new - o.freeze - assert_raise(RuntimeError) { o.taint } - - o = Object.new - o.taint - o.freeze - assert_raise(RuntimeError) { o.untaint } - end - def test_freeze_immediate assert_equal(true, 1.frozen?) 1.freeze @@ -123,7 +147,7 @@ class TestObject < Test::Unit::TestCase attr_accessor :foo } obj = klass.new.freeze - assert_raise_with_message(RuntimeError, /#{name}/) { + assert_raise_with_message(FrozenError, /#{name}/) { obj.foo = 1 } end @@ -132,6 +156,27 @@ class TestObject < Test::Unit::TestCase assert_equal(0.0, nil.to_f) end + def test_nil_to_s + str = nil.to_s + assert_equal("", str) + assert_predicate(str, :frozen?) + assert_same(str, nil.to_s) + end + + def test_true_to_s + str = true.to_s + assert_equal("true", str) + assert_predicate(str, :frozen?) + assert_same(str, true.to_s) + end + + def test_false_to_s + str = false.to_s + assert_equal("false", str) + assert_predicate(str, :frozen?) + assert_same(str, false.to_s) + end + def test_not assert_equal(false, Object.new.send(:!)) assert_equal(true, nil.send(:!)) @@ -227,6 +272,14 @@ class TestObject < Test::Unit::TestCase assert_equal([:foo], o.methods(false), bug8044) end + def test_methods_prepend_singleton + c = Class.new(Module) {private def foo; end} + k = c.new + k.singleton_class + c.module_eval {prepend(Module.new)} + assert_equal([:foo], k.private_methods(false)) + end + def test_instance_variable_get o = Object.new o.instance_eval { @foo = :foo } @@ -306,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 @@ -318,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 @@ -335,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 @@ -343,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 @@ -399,7 +456,7 @@ class TestObject < Test::Unit::TestCase def test_remove_method c = Class.new c.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do c.instance_eval { remove_method(:foo) } end @@ -577,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 @@ -720,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 @@ -765,36 +822,7 @@ class TestObject < Test::Unit::TestCase end end - def test_untrusted - verbose = $VERBOSE - $VERBOSE = false - begin - obj = Object.new - assert_equal(false, obj.untrusted?) - assert_equal(false, obj.tainted?) - obj.untrust - assert_equal(true, obj.untrusted?) - assert_equal(true, obj.tainted?) - obj.trust - assert_equal(false, obj.untrusted?) - assert_equal(false, obj.tainted?) - obj.taint - assert_equal(true, obj.untrusted?) - assert_equal(true, obj.tainted?) - obj.untaint - assert_equal(false, obj.untrusted?) - assert_equal(false, obj.tainted?) - ensure - $VERBOSE = verbose - end - end - def test_to_s - x = Object.new - x.taint - s = x.to_s - assert_equal(true, s.tainted?) - x = eval(<<-EOS) class ToS\u{3042} new.to_s @@ -803,14 +831,10 @@ class TestObject < Test::Unit::TestCase assert_match(/\bToS\u{3042}:/, x) name = "X".freeze - x = Object.new.taint + x = Object.new class<<x;self;end.class_eval {define_method(:to_s) {name}} assert_same(name, x.to_s) - assert_not_predicate(name, :tainted?) - assert_raise(RuntimeError) {name.taint} assert_equal("X", [x].join("")) - assert_not_predicate(name, :tainted?) - assert_not_predicate(eval('"X".freeze'), :tainted?) end def test_inspect @@ -829,6 +853,15 @@ class TestObject < Test::Unit::TestCase x.instance_variable_set(:@bar, 42) assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect) + # Bug: [ruby-core:19167] + x = Object.new + x.instance_variable_set(:@foo, NilClass) + assert_match(/\A#<Object:0x\h+ @foo=NilClass>\z/, x.inspect) + x.instance_variable_set(:@foo, TrueClass) + assert_match(/\A#<Object:0x\h+ @foo=TrueClass>\z/, x.inspect) + x.instance_variable_set(:@foo, FalseClass) + assert_match(/\A#<Object:0x\h+ @foo=FalseClass>\z/, x.inspect) + # #inspect does not call #to_s anymore feature6130 = '[ruby-core:43238]' x = Object.new @@ -857,6 +890,29 @@ class TestObject < Test::Unit::TestCase assert_match(/@\u{3046}=6\b/, x.inspect) end + def test_singleton_methods + assert_equal([], Object.new.singleton_methods) + assert_equal([], Object.new.singleton_methods(false)) + c = Class.new + def c.foo; end + assert_equal([:foo], c.singleton_methods - [:yaml_tag]) + assert_equal([:foo], c.singleton_methods(false)) + assert_equal([], c.singleton_class.singleton_methods(false)) + c.singleton_class.singleton_class + assert_equal([], c.singleton_class.singleton_methods(false)) + + o = c.new.singleton_class + assert_equal([:foo], o.singleton_methods - [:yaml_tag]) + assert_equal([], o.singleton_methods(false)) + o.singleton_class + assert_equal([:foo], o.singleton_methods - [:yaml_tag]) + assert_equal([], o.singleton_methods(false)) + + c.extend(Module.new{def bar; end}) + assert_equal([:bar, :foo], c.singleton_methods.sort - [:yaml_tag]) + assert_equal([:foo], c.singleton_methods(false)) + end + def test_singleton_class x = Object.new xs = class << x; self; end @@ -878,11 +934,25 @@ class TestObject < Test::Unit::TestCase end end + def test_singleton_class_freeze + x = Object.new + xs = x.singleton_class + x.freeze + assert_predicate(xs, :frozen?) + + y = Object.new + ys = y.singleton_class + ys.prepend(Module.new) + y.freeze + assert_predicate(ys, :frozen?, '[Bug #19169]') + end + def test_redef_method_missing bug5473 = '[ruby-core:40287]' ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code| exc = code[/\A[A-Z]\w+/] || 'RuntimeError' assert_separately([], <<-SRC) + $VERBOSE = nil class ::Object def method_missing(m, *a, &b) raise #{code} @@ -899,8 +969,8 @@ class TestObject < Test::Unit::TestCase b = yield assert_nothing_raised("copy") {a.instance_eval {initialize_copy(b)}} c = a.dup.freeze - assert_raise(RuntimeError, "frozen") {c.instance_eval {initialize_copy(b)}} - d = a.dup.trust + assert_raise(FrozenError, "frozen") {c.instance_eval {initialize_copy(b)}} + d = a.dup [a, b, c, d] end @@ -945,4 +1015,13 @@ class TestObject < Test::Unit::TestCase end EOS end + + def test_frozen_inspect + obj = Object.new + obj.instance_variable_set(:@a, "a") + ins = obj.inspect + obj.freeze + + assert_equal(ins, obj.inspect) + end end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index c352b75b70..a7cfb064a8 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -35,6 +35,41 @@ 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_id2ref_invalid_symbol_id + msg = /is not symbol id value/ + assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]) } + end + def test_count_objects h = {} ObjectSpace.count_objects(h) @@ -131,6 +166,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 +224,7 @@ End assert_same(new_obj, found[0]) end - def test_each_object_no_gabage + def test_each_object_no_garbage assert_separately([], <<-End) GC.disable eval('begin; 1.times{}; rescue; ensure; end') @@ -203,4 +272,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 40015c57dd..88de3fa8b9 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -45,6 +45,26 @@ class TestRubyOptimization < Test::Unit::TestCase assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7') end + def test_fixnum_lt + assert_equal true, 1 < 2 + assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2') + end + + def test_fixnum_le + assert_equal true, 1 <= 2 + assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2') + end + + def test_fixnum_gt + assert_equal false, 1 > 2 + assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2') + end + + def test_fixnum_ge + assert_equal false, 1 >= 2 + assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2') + end + def test_float_plus assert_equal 4.0, 2.0 + 2.0 assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') @@ -65,6 +85,26 @@ class TestRubyOptimization < Test::Unit::TestCase assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]") end + def test_float_lt + assert_equal true, 1.1 < 2.2 + assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2') + end + + def test_float_le + assert_equal true, 1.1 <= 2.2 + assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2') + end + + def test_float_gt + assert_equal false, 1.1 > 2.2 + assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2') + end + + def test_float_ge + assert_equal false, 1.1 >= 2.2 + assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2') + end + def test_string_length assert_equal 6, "string".length assert_redefine_method('String', 'length', 'assert_nil "string".length') @@ -110,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 @@ -147,6 +245,16 @@ class TestRubyOptimization < Test::Unit::TestCase assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"') end + def test_fixnum_and + assert_equal 1, 1&3 + assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3') + end + + def test_fixnum_or + assert_equal 3, 1|3 + assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1') + end + def test_array_plus assert_equal [1,2], [1]+[2] assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') @@ -222,7 +330,7 @@ class TestRubyOptimization < Test::Unit::TestCase unless file loc, = caller_locations(1, 1) file = loc.path - line ||= loc.lineno + line ||= loc.lineno + 1 end RubyVM::InstructionSequence.new("proc {|_|_.class_eval {#{src}}}", file, (path || file), line, @@ -297,7 +405,7 @@ class TestRubyOptimization < Test::Unit::TestCase def test_tailcall_inhibited_by_rescue bug12082 = '[ruby-core:73871] [Bug #12082]' - tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + EnvUtil.suppress_warning {tailcall("#{<<-"begin;"}\n#{<<~"end;"}")} begin; def to_be_rescued return do_raise @@ -329,6 +437,20 @@ class TestRubyOptimization < Test::Unit::TestCase message(bug12565) {disasm(:add_one_and_two)}) end + def test_c_func_with_sp_offset_under_tailcall + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def calc_one_plus_two + 1 + 2.abs + end + + def one_plus_two + calc_one_plus_two + end + end; + assert_equal(3, one_plus_two) + end + def test_tailcall_interrupted_by_sigint bug12576 = 'ruby-core:76327' script = "#{<<-"begin;"}\n#{<<~'end;'}" @@ -348,7 +470,7 @@ class TestRubyOptimization < Test::Unit::TestCase foo end;1 end; - status, _err = EnvUtil.invoke_ruby([], "", true, true, {}) { + status, _err = EnvUtil.invoke_ruby([], "", true, true, **{}) { |in_p, out_p, err_p, pid| in_p.write(script) in_p.close @@ -375,7 +497,7 @@ class TestRubyOptimization < Test::Unit::TestCase def test_tailcall_condition_block bug = '[ruby-core:78015] [Bug #12905]' - src = "#{<<-"begin;"}\n#{<<~"end;"}" + src = "#{<<-"begin;"}\n#{<<~"end;"}", __FILE__, nil, __LINE__+1 begin; def run(current, final) if current < final @@ -387,13 +509,13 @@ class TestRubyOptimization < Test::Unit::TestCase end; obj = Object.new - self.class.tailcall(obj.singleton_class, src, tailcall: false) + self.class.tailcall(obj.singleton_class, *src, tailcall: false) e = assert_raise(SystemStackError) { obj.run(1, Float::INFINITY) } level = e.backtrace_locations.size obj = Object.new - self.class.tailcall(obj.singleton_class, src, tailcall: true) + self.class.tailcall(obj.singleton_class, *src, tailcall: true) level *= 2 mesg = message {"#{bug}: #{$!.backtrace_locations.size} / #{level} stack levels"} assert_nothing_raised(SystemStackError, mesg) { @@ -401,6 +523,22 @@ class TestRubyOptimization < Test::Unit::TestCase } end + def test_tailcall_not_to_grow_stack + omit 'currently JIT-ed code always creates a new stack frame' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + bug16161 = '[ruby-core:94881]' + + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def foo(n) + return :ok if n < 1 + foo(n - 1) + end + end; + assert_nothing_raised(SystemStackError, bug16161) do + assert_equal(:ok, foo(1_000_000), bug16161) + end + end + class Bug10557 def [](_) block_given? @@ -517,12 +655,30 @@ class TestRubyOptimization < Test::Unit::TestCase when "1.8.0"..."1.8.8" then :bar end end; - iseq = RubyVM::InstructionSequence.compile(code) - insn = iseq.disasm - assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn - assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn - assert_no_match(/putstring/, insn) - assert_no_match(/newrange/, insn) + [ true, false ].each do |opt| + iseq = RubyVM::InstructionSequence.compile(code, + frozen_string_literal: opt) + insn = iseq.disasm + assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn + assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn + assert_no_match(/putstring/, insn) + assert_no_match(/newrange/, insn) + end + end + + def test_peephole_dstr + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + exp = -'a' + z = 'a' + [exp, -"#{z}"] + end; + [ false, true ].each do |fsl| + iseq = RubyVM::InstructionSequence.compile(code, + frozen_string_literal: fsl) + assert_same(*iseq.eval, + "[ruby-core:85542] [Bug #14475] fsl: #{fsl}") + end end def test_branch_condition_backquote @@ -617,27 +773,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] - END - end - - def test_block_parameter_should_restore_safe_level - assert_separately [], <<-END - # - def foo &b - $SAFE = 1 - b.call - end - assert_equal 0, foo{$SAFE} + assert_equal 0, h2[:T_DATA] - h1[:T_DATA] # Proc is T_DATA END end @@ -647,4 +792,159 @@ class TestRubyOptimization < Test::Unit::TestCase eval "def foo; 1.times{|(a), &b| nil && a}; end" END end + + def test_clear_unreachable_keyword_args + assert_separately [], <<-END, timeout: 60 + script = <<-EOS + if true + else + foo(k1:1) + end + EOS + GC.stress = true + 30.times{ + RubyVM::InstructionSequence.compile(script) + } + END + end + + def test_callinfo_unreachable_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + iseq = RubyVM::InstructionSequence.compile("if false; foo(bar: :baz); else :ok end") + bin = iseq.to_binary + iseq = RubyVM::InstructionSequence.load_from_binary(bin) + assert_instance_of(RubyVM::InstructionSequence, iseq) + assert_equal(:ok, iseq.eval) + end; + end + + def test_side_effect_in_popped_splat + bug = '[ruby-core:84340] [Bug #14201]' + eval("{**(bug = nil; {})};42") + assert_nil(bug) + + bug = '[ruby-core:85486] [Bug #14459]' + h = {} + assert_equal(bug, eval('{ok: 42, **h}; bug')) + assert_equal(:ok, eval('{ok: bug = :ok, **h}; bug')) + assert_empty(h) + end + + def test_overwritten_blockparam + obj = Object.new + def obj.a(&block) + block = 1 + return :ok if block + :ng + end + assert_equal(:ok, obj.a()) + end + + def test_blockparam_in_rescue + obj = Object.new + def obj.foo(&b) + raise + rescue + b.call + end + result = nil + assert_equal(42, obj.foo {result = 42}) + assert_equal(42, result) + end + + def test_unconditional_branch_to_leave_block + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + tap {true || tap {}} + end; + end + + def test_jump_elimination_with_optimized_out_block + x = Object.new + def x.bug(obj) + if obj || obj + obj = obj + else + raise "[ruby-core:87830] [Bug #14897]" + end + obj + end + assert_equal(:ok, x.bug(:ok)) + end + + def test_jump_elimination_with_optimized_out_block_2 + x = Object.new + def x.bug + a = "aaa" + ok = :NG + if a == "bbb" || a == "ccc" then + a = a + else + ok = :ok + end + ok + end + assert_equal(:ok, x.bug) + end + + def test_peephole_jump_after_newarray + i = 0 + %w(1) || 2 while (i += 1) < 100 + assert_equal(100, i) + end + + def test_optimized_empty_ensure + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 10) + begin; + assert_raise(RuntimeError) { + begin raise ensure nil if nil end + } + end; + end + + def test_optimized_rescue + assert_in_out_err("", "#{<<~"begin;"}\n#{<<~'end;'}", [], /END \(RuntimeError\)/) + begin; + if false + begin + require "some_mad_stuff" + rescue LoadError + puts "no mad stuff loaded" + end + end + + 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 f5b3266803..0ee36857ba 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -4,22 +4,36 @@ require 'test/unit' class TestPack < Test::Unit::TestCase def test_pack - $format = "c2x5CCxsdils_l_a6"; + format = "c2x5CCxsdils_l_a6"; # Need the expression in here to force ary[5] to be numeric. This avoids # test2 failing because ary2 goes str->numeric->str and ary does not. ary = [1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,-32767,-123456,"abcdef"] - $x = ary.pack($format) - ary2 = $x.unpack($format) + x = ary.pack(format) + ary2 = x.unpack(format) assert_equal(ary.length, ary2.length) assert_equal(ary.join(':'), ary2.join(':')) - assert_match(/def/, $x) + assert_match(/def/, x) - $x = [-1073741825] - assert_equal($x, $x.pack("q").unpack("q")) + x = [-1073741825] + assert_equal(x, x.pack("q").unpack("q")) - $x = [-1] - assert_equal($x, $x.pack("l").unpack("l")) + x = [-1] + assert_equal(x, x.pack("l").unpack("l")) + end + + def test_ascii_incompatible + assert_raise(Encoding::CompatibilityError) do + ["foo"].pack("u".encode("UTF-32BE")) + end + + assert_raise(Encoding::CompatibilityError) do + "foo".unpack("C".encode("UTF-32BE")) + end + + assert_raise(Encoding::CompatibilityError) do + "foo".unpack1("C".encode("UTF-32BE")) + end end def test_pack_n @@ -428,6 +442,7 @@ class TestPack < Test::Unit::TestCase assert_operator(4, :<=, [1].pack("L!").bytesize) end + require 'rbconfig' def test_pack_unpack_qQ s1 = [578437695752307201, -506097522914230529].pack("q*") s2 = [578437695752307201, 17940646550795321087].pack("Q*") @@ -437,6 +452,7 @@ class TestPack < Test::Unit::TestCase # Note: q! and Q! should not work on platform which has no long long type. # Is there a such platform now? + # @shyouhei: Yes. gcc -ansi is one of such platform. s1 = [578437695752307201, -506097522914230529].pack("q!*") s2 = [578437695752307201, 17940646550795321087].pack("Q!*") assert_equal([578437695752307201, -506097522914230529], s2.unpack("q!*")) @@ -446,7 +462,7 @@ class TestPack < Test::Unit::TestCase assert_equal(8, [1].pack("Q").bytesize) assert_operator(8, :<=, [1].pack("q!").bytesize) assert_operator(8, :<=, [1].pack("Q!").bytesize) - end + end if RbConfig::CONFIG['HAVE_LONG_LONG'] def test_pack_unpack_jJ # Note: we assume that the size of intptr_t and uintptr_t equals to the size @@ -548,6 +564,9 @@ class TestPack < Test::Unit::TestCase assert_equal([1, 2], "\x01\x00\x00\x02".unpack("C@3C")) assert_equal([nil], "\x00".unpack("@1C")) # is it OK? assert_raise(ArgumentError) { "\x00".unpack("@2C") } + + pos = RbConfig::LIMITS["UINTPTR_MAX"] - 99 # -100 + assert_raise(RangeError) {"0123456789".unpack("@#{pos}C10")} end def test_pack_unpack_percent @@ -633,6 +652,14 @@ EXPECTED end; end + def test_bug_18343 + bug18343 = '[ruby-core:106096] [Bug #18343]' + assert_separately(%W[- #{bug18343}], <<-'end;') + bug = ARGV.shift + assert_raise(ArgumentError, bug){[0].pack('c', {})} + end; + end + def test_pack_unpack_m0 assert_equal("", [""].pack("m0")) assert_equal("AA==", ["\0"].pack("m0")) @@ -750,41 +777,15 @@ EXPECTED end def test_pack_garbage - verbose = $VERBOSE - $VERBOSE = false - - assert_silent do - assert_equal "\000", [0].pack("*U") - end - - $VERBOSE = true - - _, err = capture_io do + assert_warn(%r%unknown pack directive '\*' in '\*U'$%) do assert_equal "\000", [0].pack("*U") end - - assert_match %r%unknown pack directive '\*' in '\*U'$%, err - ensure - $VERBOSE = verbose end def test_unpack_garbage - verbose = $VERBOSE - $VERBOSE = false - - assert_silent do + assert_warn(%r%unknown unpack directive '\*' in '\*U'$%) do assert_equal [0], "\000".unpack("*U") end - - $VERBOSE = true - - _, err = capture_io do - assert_equal [0], "\000".unpack("*U") - end - - assert_match %r%unknown unpack directive '\*' in '\*U'$%, err - ensure - $VERBOSE = verbose end def test_invalid_warning @@ -797,6 +798,13 @@ EXPECTED assert_warning(/\A(.* in '\u{3042}'\n)+\z/) { [].pack("\u{3042}") } + + assert_warning(/\A.* in '.*U'\Z/) { + assert_equal "\000", [0].pack("\0U") + } + assert_warning(/\A.* in '.*U'\Z/) { + "\000".unpack("\0U") + } end def test_pack_resize @@ -821,7 +829,7 @@ EXPECTED def test_pack_with_buffer buf = String.new(capacity: 100) - assert_raise_with_message(RuntimeError, /frozen/) { + assert_raise_with_message(FrozenError, /frozen/) { [0xDEAD_BEEF].pack('N', buffer: 'foo'.freeze) } assert_raise_with_message(TypeError, /must be String/) { @@ -857,4 +865,30 @@ EXPECTED assert_equal "hogefuga", "aG9nZWZ1Z2E=".unpack1("m") assert_equal "01000001", "A".unpack1("B*") end + + def test_unpack1_offset + assert_equal 65, "ZA".unpack1("C", offset: 1) + assert_equal "01000001", "YZA".unpack1("B*", offset: 2) + assert_nil "abc".unpack1("C", offset: 3) + assert_raise_with_message(ArgumentError, /offset can't be negative/) { + "a".unpack1("C", offset: -1) + } + assert_raise_with_message(ArgumentError, /offset outside of string/) { + "a".unpack1("C", offset: 2) + } + assert_nil "a".unpack1("C", offset: 1) + end + + def test_unpack_offset + assert_equal [65], "ZA".unpack("C", offset: 1) + assert_equal ["01000001"], "YZA".unpack("B*", offset: 2) + assert_equal [nil, nil, nil], "abc".unpack("CCC", offset: 3) + assert_raise_with_message(ArgumentError, /offset can't be negative/) { + "a".unpack("C", offset: -1) + } + assert_raise_with_message(ArgumentError, /offset outside of string/) { + "a".unpack("C", offset: 2) + } + assert_equal [nil], "a".unpack("C", offset: 1) + end end diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 8830dbec30..ca9c305639 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -6,28 +6,30 @@ require 'stringio' class TestParse < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown $VERBOSE = @verbose end + def test_error_line + assert_syntax_error('------,,', /\n\z/, 'Message to pipe should end with a newline') + end + def test_else_without_rescue - x = eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error(<<-END, %r":#{__LINE__+2}: else without rescue"o, [__FILE__, __LINE__+1]) begin else 42 end END - assert_equal(42, x) end def test_alias_backref - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't make alias/) do + begin; alias $foo $1 - END + end; end end @@ -82,10 +84,10 @@ class TestParse < Test::Unit::TestCase assert_equal([42, 42], [o.Foo, o.Bar]) assert_equal([42, 42], [o::baz, o::qux]) - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; $1 ||= t.foo 42 - END + end; end def t.bar(x); x + yield; end @@ -150,67 +152,65 @@ class TestParse < Test::Unit::TestCase end def test_dynamic_constant_assignment - assert_raise(SyntaxError) do - Object.new.instance_eval <<-END, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do + begin; def foo self::FOO, self::BAR = 1, 2 ::FOO, ::BAR = 1, 2 end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; $1, $2 = 1, 2 - END + end; end - assert_raise(SyntaxError) do - Object.new.instance_eval <<-END, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do + begin; def foo ::FOO = 1 end - END + end; end c = Class.new c.freeze - assert_nothing_raised(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - if false + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do + begin; c::FOO &= 1 ::FOO &= 1 - end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; $1 &= 1 - END + end; end end def test_class_module - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /must be CONSTANT/) do + begin; class foo; end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /in method body/) do + begin; def foo class Foo; end module Bar; end end - END + end; end - assert_nothing_raised(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do + begin; class Foo 1; end - END + end; end end @@ -270,37 +270,34 @@ class TestParse < Test::Unit::TestCase end def test_bad_arg - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a constant/) do + begin; def foo(FOO); end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do + begin; def foo(@foo); end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a global variable/) do + begin; def foo($foo); end - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a class variable/) do + begin; def foo(@@foo); end - END + end; end - o = Object.new - def o.foo(*r); yield(*r); end - - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - o.foo 1 {|; @a| @a = 42 } - END + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do + begin; + o.foo {|; @a| @a = 42 } + end; end end @@ -352,6 +349,7 @@ class TestParse < Test::Unit::TestCase def test_words assert_equal([], %W( )) + assert_syntax_error('%w[abc', /unterminated list/) end def test_dstr @@ -359,11 +357,12 @@ class TestParse < Test::Unit::TestCase assert_equal("foo 1 bar", "foo #@@foo bar") "1" =~ /(.)/ assert_equal("foo 1 bar", "foo #$1 bar") + assert_equal('foo #@1 bar', eval('"foo #@1 bar"')) end def test_dstr_disallowed_variable bug8375 = '[ruby-core:54885] [Bug #8375]' - %w[@ @1 @. @@ @@1 @@. $ $%].each do |src| + %w[@ @. @@ @@1 @@. $ $%].each do |src| src = '#'+src+' ' str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do break eval('"'+src+'"') @@ -376,28 +375,29 @@ class TestParse < Test::Unit::TestCase assert_nothing_raised { eval(':""') } end - def assert_disallowed_variable(type, noname, *invalid) - assert_syntax_error(noname, "`#{noname}' without identifiers is not allowed as #{type} variable name") + def assert_disallowed_variable(type, noname, invalid) + noname.each do |name| + assert_syntax_error("proc{a = #{name} }", "`#{noname[0]}' without identifiers is not allowed as #{type} variable name") + end invalid.each do |name| - assert_syntax_error(name, "`#{name}' is not allowed as #{type} variable name") + assert_syntax_error("proc {a = #{name} }", "`#{name}' is not allowed as #{type} variable name") end end def test_disallowed_instance_variable - assert_disallowed_variable("an instance", *%w[@ @1 @.]) + assert_disallowed_variable("an instance", %w[@ @.], %w[]) end def test_disallowed_class_variable - assert_disallowed_variable("a class", *%w[@@ @@1 @@.]) + assert_disallowed_variable("a class", %w[@@ @@.], %w[@@1]) end def test_disallowed_gloal_variable - assert_disallowed_variable("a global", *%w[$ $%]) + assert_disallowed_variable("a global", %w[$], %w[$%]) end 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 @@ -409,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 @@ -418,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 @@ -430,31 +432,56 @@ class TestParse < Test::Unit::TestCase end def test_duplicate_argument - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", '') do + begin; 1.times {|&b?| } - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do + begin; 1.times {|a, a|} - END + end; end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do + begin; def foo(a, a); end - END + end; end end def test_define_singleton_error - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /singleton method for literals/) do + begin; def ("foo").foo; end - END + end; + end + end + + def test_op_asgn1_with_block + t = Object.new + a = [] + blk = proc {|x| a << x } + def t.[](_) + yield(:aref) + nil + end + def t.[]=(_, _) + yield(:aset) end + def t.dummy(_) + end + eval <<-END, nil, __FILE__, __LINE__+1 + t[42, &blk] ||= 42 + END + assert_equal([:aref, :aset], a) + a.clear + eval <<-END, nil, __FILE__, __LINE__+1 + t[42, &blk] ||= t.dummy 42 # command_asgn test + END + assert_equal([:aref, :aset], a) + blk end def test_backquote @@ -487,13 +514,13 @@ class TestParse < Test::Unit::TestCase mesg = 'from the backslash through the invalid char' e = assert_syntax_error('"\xg1"', /hex escape/) - assert_equal(' ^', e.message.lines.last, mesg) + assert_equal(' ^~'"\n", e.message.lines.last, mesg) e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape') - assert_equal(' ^', e.message.lines.last, mesg) + assert_equal(' ^'"\n", e.message.lines.last, mesg) e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape') - assert_equal(' ^', e.message.lines.last, mesg) + assert_equal(' ^'"\n", e.message.lines.last, mesg) e = assert_syntax_error('"\u{xxxx', 'Unicode escape') assert_pattern_list([ @@ -504,43 +531,98 @@ class TestParse < Test::Unit::TestCase / \^/, /\n/, /.*: unterminated string.*\n.*\n/, - / \^/, + / \^\n/, ], e.message) e = assert_syntax_error('"\M1"', /escape character syntax/) - assert_equal(' ^~~', e.message.lines.last, mesg) + assert_equal(' ^~~'"\n", e.message.lines.last, mesg) e = assert_syntax_error('"\C1"', /escape character syntax/) - assert_equal(' ^~~', e.message.lines.last, mesg) + assert_equal(' ^~~'"\n", e.message.lines.last, mesg) src = '"\xD0\u{90'"\n""000000000000000000000000" assert_syntax_error(src, /:#{__LINE__}: unterminated/o) assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/) + assert_equal("", eval('"\u{}"')) + assert_equal("", eval('"\u{ }"')) assert_equal("\x81", eval('"\C-\M-a"')) assert_equal("\177", eval('"\c?"')) + + assert_warning(/use \\C-\\s/) {assert_equal("\x00", eval('"\C- "'))} + assert_warning(/use \\M-\\s/) {assert_equal("\xa0", eval('"\M- "'))} + assert_warning(/use \\M-\\C-\\s/) {assert_equal("\x80", eval('"\M-\C- "'))} + assert_warning(/use \\C-\\M-\\s/) {assert_equal("\x80", eval('"\C-\M- "'))} + assert_warning(/use \\t/) {assert_equal("\x09", eval("\"\\C-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\\C-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\C-\\M-\t\""))} + assert_syntax_error("\"\\C-\x01\"", 'Invalid escape character syntax') + 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) + + e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax') + assert_match(/^\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )$/x, e.message.lines.last) + assert_not_include(e.message, "invalid multibyte char") end def test_question - assert_raise(SyntaxError) { eval('?') } - assert_raise(SyntaxError) { eval('? ') } - assert_raise(SyntaxError) { eval("?\n") } - assert_raise(SyntaxError) { eval("?\t") } - assert_raise(SyntaxError) { eval("?\v") } - assert_raise(SyntaxError) { eval("?\r") } - assert_raise(SyntaxError) { eval("?\f") } - assert_raise(SyntaxError) { eval("?\f") } - assert_raise(SyntaxError) { eval(" ?a\x8a".force_encoding("utf-8")) } + assert_syntax_error('?', /incomplete/) + assert_syntax_error('? ', /unexpected/) + assert_syntax_error("?\n", /unexpected/) + assert_syntax_error("?\t", /unexpected/) + assert_syntax_error("?\v", /unexpected/) + assert_syntax_error("?\r", /unexpected/) + assert_syntax_error("?\f", /unexpected/) + assert_syntax_error(" ?a\x8a".force_encoding("utf-8"), /invalid multibyte/) assert_equal("\u{1234}", eval("?\u{1234}")) assert_equal("\u{1234}", eval('?\u{1234}')) + assert_equal("\u{1234}", eval('?\u1234')) + assert_syntax_error('?\u{41 42}', 'Multiple codepoints at single character literal') + e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape') + assert_not_match(/end-of-input/, e.message) + + assert_warning(/use ?\\C-\\s/) {assert_equal("\x00", eval('?\C- '))} + assert_warning(/use ?\\M-\\s/) {assert_equal("\xa0", eval('?\M- '))} + assert_warning(/use ?\\M-\\C-\\s/) {assert_equal("\x80", eval('?\M-\C- '))} + assert_warning(/use ?\\C-\\M-\\s/) {assert_equal("\x80", eval('?\C-\M- '))} + assert_warning(/use ?\\t/) {assert_equal("\x09", eval("?\\C-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\\C-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\C-\\M-\t"))} + assert_syntax_error("?\\C-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax') + + assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF")) end def test_percent assert_equal(:foo, eval('%s(foo)')) - assert_raise(SyntaxError) { eval('%s') } - assert_raise(SyntaxError) { eval('%ss') } - assert_raise(SyntaxError) { eval('%z()') } + 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 @@ -555,24 +637,25 @@ class TestParse < Test::Unit::TestCase assert_nothing_raised(SyntaxError, bug) do assert_equal(sym, eval(':"foo\u{0}bar"')) end + assert_nothing_raised(SyntaxError) do + assert_equal(:foobar, eval(':"foo\u{}bar"')) + assert_equal(:foobar, eval(':"foo\u{ }bar"')) + end + + assert_syntax_error(':@@', /is not allowed/) + assert_syntax_error(':@@1', /is not allowed/) + assert_syntax_error(':@', /is not allowed/) + assert_syntax_error(':@1', /is not allowed/) end def test_parse_string - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 -/ - END - end + assert_syntax_error("/\n", /unterminated/) end def test_here_document x = nil - assert_raise(SyntaxError) do - eval %Q( -<\<FOO - ) - end + assert_syntax_error("<\<FOO\n", /can't find string "FOO"/) assert_nothing_raised(SyntaxError) do x = eval %q( @@ -583,23 +666,11 @@ FOO end assert_equal "\#$\n", x - assert_raise(SyntaxError) do - eval %Q( -<\<\" - ) - end + assert_syntax_error("<\<\"\n", /unterminated here document identifier/) - assert_raise(SyntaxError) do - eval %q( -<<`` - ) - end + assert_syntax_error("<<``\n", /can't find string ""/) - assert_raise(SyntaxError) do - eval %q( -<<-- - ) - end + assert_syntax_error("<<--\n", /unexpected <</) assert_nothing_raised(SyntaxError) do x = eval %q( @@ -619,6 +690,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 @@ -633,6 +713,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 @@ -662,37 +750,44 @@ x = __ENCODING__ end def test_embedded_rd - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 -=begin - END - end + assert_valid_syntax("=begin\n""=end") + assert_valid_syntax("=begin\n""=end\0") + assert_valid_syntax("=begin\n""=end\C-d") + assert_valid_syntax("=begin\n""=end\C-z") + end + + def test_embedded_rd_error + error = 'embedded document meets end of file' + assert_syntax_error("=begin\n", error) + assert_syntax_error("=begin", error) end def test_float - assert_equal(1.0/0, eval("1e10000")) - assert_raise(SyntaxError) { eval('1_E') } - assert_raise(SyntaxError) { eval('1E1E1') } + 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 assert_equal(nil, $&) assert_equal(nil, eval('alias $& $preserve_last_match')) - assert_raise(SyntaxError) { eval('$#') } + assert_syntax_error('a = $#', /as a global variable name\na = \$\#\n \^~$/) end def test_invalid_instance_variable - assert_raise(SyntaxError) { eval('@#') } - assert_raise(SyntaxError) { eval('@') } + pattern = /without identifiers is not allowed as an instance variable name/ + assert_syntax_error('@%', pattern) + assert_syntax_error('@', pattern) end def test_invalid_class_variable - assert_raise(SyntaxError) { eval('@@1') } - assert_raise(SyntaxError) { eval('@@') } + pattern = /without identifiers is not allowed as a class variable name/ + assert_syntax_error('@@%', pattern) + assert_syntax_error('@@', pattern) end def test_invalid_char @@ -712,50 +807,23 @@ x = __ENCODING__ end def test_unassignable - assert_raise(SyntaxError) do - eval %q(self = 1) - end - assert_raise(SyntaxError) do - eval %q(nil = 1) - end - assert_raise(SyntaxError) do - eval %q(true = 1) - end - assert_raise(SyntaxError) do - eval %q(false = 1) - end - assert_raise(SyntaxError) do - eval %q(__FILE__ = 1) - end - assert_raise(SyntaxError) do - eval %q(__LINE__ = 1) - end - assert_raise(SyntaxError) do - eval %q(__ENCODING__ = 1) - end - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - def foo - FOO = 1 - end - END - end + assert_syntax_error(%q(self = 1), /Can't change the value of self/) + assert_syntax_error(%q(nil = 1), /Can't assign to nil/) + assert_syntax_error(%q(true = 1), /Can't assign to true/) + assert_syntax_error(%q(false = 1), /Can't assign to false/) + assert_syntax_error(%q(__FILE__ = 1), /Can't assign to __FILE__/) + assert_syntax_error(%q(__LINE__ = 1), /Can't assign to __LINE__/) + assert_syntax_error(%q(__ENCODING__ = 1), /Can't assign to __ENCODING__/) + assert_syntax_error("def foo; FOO = 1; end", /dynamic constant assignment/) + assert_syntax_error("x, true", /Can't assign to true/) end def test_block_dup - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - foo(&proc{}) {} - END - end + assert_syntax_error("foo(&proc{}) {}", /both block arg and actual block/) end def test_set_backref - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - $& = 1 - END - end + assert_syntax_error("$& = 1", /Can't set variable/) end def test_arg_concat @@ -773,42 +841,34 @@ x = __ENCODING__ end def test_void_expr_stmts_value - # This test checks if void contexts are warned correctly. - # Thus, warnings MUST NOT be suppressed. - $VERBOSE = true - stderr = $stderr - $stderr = StringIO.new("") x = 1 - assert_nil eval("x; nil") - assert_nil eval("1+1; nil") - assert_nil eval("1.+(1); nil") - assert_nil eval("TestParse; nil") - assert_nil eval("::TestParse; nil") - assert_nil eval("x..x; nil") - assert_nil eval("x...x; nil") - assert_nil eval("self; nil") - assert_nil eval("nil; nil") - assert_nil eval("true; nil") - assert_nil eval("false; nil") - assert_nil eval("defined?(1); nil") + useless_use = /useless use/ + unused = /unused/ + assert_nil assert_warning(useless_use) {eval("x; nil")} + assert_nil assert_warning(useless_use) {eval("1+1; nil")} + assert_nil assert_warning('') {eval("1.+(1); nil")} + assert_nil assert_warning(useless_use) {eval("TestParse; nil")} + assert_nil assert_warning(useless_use) {eval("::TestParse; nil")} + assert_nil assert_warning(useless_use) {eval("x..x; nil")} + assert_nil assert_warning(useless_use) {eval("x...x; nil")} + assert_nil assert_warning(unused) {eval("self; nil")} + assert_nil assert_warning(unused) {eval("nil; nil")} + assert_nil assert_warning(unused) {eval("true; nil")} + assert_nil assert_warning(unused) {eval("false; nil")} + assert_nil assert_warning(useless_use) {eval("defined?(1); nil")} assert_equal 1, x - assert_raise(SyntaxError) do - eval %q(1; next; 2) - end - - assert_equal(13, $stderr.string.lines.to_a.size) - $stderr = stderr + assert_syntax_error("1; next; 2", /Invalid next/) 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 @@ -820,13 +880,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 @@ -839,13 +899,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 @@ -855,11 +915,7 @@ x = __ENCODING__ end def test_no_blockarg - assert_raise(SyntaxError) do - eval <<-END, nil, __FILE__, __LINE__+1 - yield(&:+) - END - end + assert_syntax_error("yield(&:+)", /block argument should not be given/) end def test_method_block_location @@ -876,10 +932,12 @@ x = __ENCODING__ assert_equal(expected, actual, bug5614) end - def test_shadowing_variable - assert_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")} - a = "\u{3042}" - assert_warning(/#{a}/o) {eval("#{a}=1; tap {|#{a}|}")} + def test_no_shadowing_variable_warning + 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 @@ -887,10 +945,12 @@ x = __ENCODING__ assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; a=1; nil; end")} assert_warning(/assigned but unused variable/) {o.instance_eval("def bar; a=1; a(); end")} a = "\u{3042}" - assert_warning(/#{a}/) {o.instance_eval("def foo; #{a}=1; nil; end")} - o = Object.new - assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; tap {a=1; a()}; end")} - assert_warning('') {o.instance_eval("def bar; a=a=1; nil; end")} + assert_warning(/#{a}/) {o.instance_eval("def foo0; #{a}=1; nil; end")} + assert_warning(/assigned but unused variable/) {o.instance_eval("def foo1; tap {a=1; a()}; end")} + assert_warning('') {o.instance_eval("def bar1; a=a=1; nil; end")} + assert_warning(/assigned but unused variable/) {o.instance_eval("def bar2; a, = 1, 2; end")} + assert_warning('') {o.instance_eval("def marg1(a); nil; end")} + assert_warning('') {o.instance_eval("def marg2((a)); nil; end")} end def test_named_capture_conflict @@ -987,6 +1047,22 @@ x = __ENCODING__ assert_syntax_error(" 0b\n", /\^/) end + def test_unclosed_unicode_escape_at_eol_bug_19750 + assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}") + begin; + assert_syntax_error("/\\u", /too short escape sequence/) + assert_syntax_error("/\\u{", /unterminated regexp meets end of file/) + assert_syntax_error("/\\u{\\n", /invalid Unicode list/) + assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/) + re = eval("/a#\\u{\n$/x") + assert_match(re, 'a') + assert_not_match(re, 'a#') + re = eval("/a#\\u\n$/x") + assert_match(re, 'a') + assert_not_match(re, 'a#') + end; + end + def test_error_def_in_argument assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") begin; @@ -994,7 +1070,7 @@ x = __ENCODING__ end; assert_syntax_error("def\nf(000)end", /^ \^~~/) - assert_syntax_error("def\nf(&)end", /^ \^/) + assert_syntax_error("def\nf(&0)end", /^ \^/) end def test_method_location_in_rescue @@ -1030,36 +1106,53 @@ x = __ENCODING__ end; end - def test_unexpected_token_error - assert_raise(SyntaxError) do - eval('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') + 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 def test_unexpected_token_after_numeric - assert_raise_with_message(SyntaxError, /^ \^~~\z/) do - eval('0000xyz') - end - assert_raise_with_message(SyntaxError, /^ \^~~\z/) do - eval('1.2i1.1') - end + assert_syntax_error('0000xyz', /^ \^~~\Z/) + assert_syntax_error('1.2i1.1', /^ \^~~\Z/) + assert_syntax_error('1.2.3', /^ \^~\Z/) end def test_truncated_source_line - e = assert_raise_with_message(SyntaxError, /unexpected tIDENTIFIER/) do - eval("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789") - end + e = assert_syntax_error("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789", + /unexpected local variable or method/) line = e.message.lines[1] assert_operator(line, :start_with?, "...") assert_operator(line, :end_with?, "...\n") end def test_unterminated_regexp_error - e = assert_raise(SyntaxError) do - eval("/x") - end.message - assert_match(/unterminated regexp meets end of file/, e) - assert_not_match(/unexpected tSTRING_END/, e) + e = assert_syntax_error("/x", /unterminated regexp meets end of file/) + assert_not_match(/unexpected tSTRING_END/, e.message) end def test_lparenarg @@ -1069,7 +1162,8 @@ x = __ENCODING__ end o.instance_eval {i (-1.3).abs} assert_equal(1.3, o.x) - o.instance_eval {i = 0; i (-1.3).abs} + o.i(nil) + o.instance_eval {i = 0; i (-1.3).abs; i} assert_equal(1.3, o.x) end @@ -1078,9 +1172,261 @@ x = __ENCODING__ $VERBOSE = true x = 1 eval("if false; 0 < x < 2; end") + x + end + end + + def test_eof + assert_equal(42, eval("42\0""end")) + assert_equal(42, eval("42\C-d""end")) + assert_equal(42, eval("42\C-z""end")) + end + + def test_eof_in_def + assert_syntax_error("def m\n\0""end", /unexpected/) + assert_syntax_error("def m\n\C-d""end", /unexpected/) + 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 + + def test_whitespace_warning + assert_syntax_error("\\foo", /backslash/) + assert_syntax_error("\\ ", /escaped space/) + assert_syntax_error("\\\t", /escaped horizontal tab/) + assert_syntax_error("\\\f", /escaped form feed/) + assert_syntax_error("\\\r", /escaped carriage return/) + assert_warn(/middle of line/) {eval(" \r ")} + assert_syntax_error("\\\v", /escaped vertical tab/) + end + + def test_command_def_cmdarg + assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}") + begin; + m def x(); end + 1.tap do end + end; + end + + NONASCII_CONSTANTS = [ + *%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]}, + "\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}", + *%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]}, + ] + + def assert_nonascii_const + assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n| + m = Module.new + assert_not_operator(m, :const_defined?, n) + assert_raise_with_message(NameError, /uninitialized/) do + m.const_get(n) + end + assert_nil(eval("defined?(m::#{n})")) + + v = yield m, n + + assert_operator(m, :const_defined?, n) + assert_equal("constant", eval("defined?(m::#{n})")) + assert_same(v, m.const_get(n)) + + m.__send__(:remove_const, n) + assert_not_operator(m, :const_defined?, n) + assert_nil(eval("defined?(m::#{n})")) + end + end + + def test_nonascii_const_set + assert_nonascii_const do |m, n| + m.const_set(n, 42) end end + def test_nonascii_constant + assert_nonascii_const do |m, n| + m.module_eval("class #{n}; self; end") + end + end + + def test_cdmarg_after_command_args_and_tlbrace_arg + 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 + + def test_if_after_class + assert_valid_syntax('module if true; Object end::Kernel; end') + assert_valid_syntax('module while true; break Object end::Kernel; end') + assert_valid_syntax('class if true; Object end::Kernel; end') + assert_valid_syntax('class while true; break Object end::Kernel; end') + end + + def test_escaped_space + assert_syntax_error('x = \ 42', /escaped space/) + end + + def test_label + expected = {:foo => 1} + + code = '{"foo": 1}' + assert_valid_syntax(code) + assert_equal(expected, eval(code)) + + code = '{foo: 1}' + assert_valid_syntax(code) + assert_equal(expected, eval(code)) + + class << (obj = Object.new) + attr_reader :arg + def set(arg) + @arg = arg + end + end + + assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}") + do; + obj.set foo: + 1 + end; + assert_equal(expected, eval(code)) + assert_equal(expected, obj.arg) + + assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}") + do; + obj.set "foo": + 1 + end; + assert_equal(expected, eval(code)) + assert_equal(expected, obj.arg) + end + =begin def test_past_scope_variable assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}} diff --git a/test/ruby/test_path.rb b/test/ruby/test_path.rb index 6af4fb6ac0..b35e942a2a 100644 --- a/test/ruby/test_path.rb +++ b/test/ruby/test_path.rb @@ -236,10 +236,14 @@ class TestPath < Test::Unit::TestCase assert_equal(ext, File.extname('.a/b/d/test.rb')) unless /mswin|bccwin|mingw/ =~ RUBY_PLATFORM # trailing spaces and dots are ignored on NTFS. - ext = '' + ext = '.' end assert_equal(ext, File.extname('a.rb.')) - assert_equal('', File.extname('a.')) + if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + # trailing spaces and dots are ignored on NTFS. + ext = '' + end + assert_equal(ext, File.extname('a.')) assert_equal('', File.extname('.x')) assert_equal('', File.extname('..x')) end diff --git a/test/ruby/test_pattern_matching.rb b/test/ruby/test_pattern_matching.rb new file mode 100644 index 0000000000..0337e5d945 --- /dev/null +++ b/test/ruby/test_pattern_matching.rb @@ -0,0 +1,1713 @@ +# frozen_string_literal: true +require 'test/unit' + +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 + end + + def initialize(obj) + @obj = obj + end + + def deconstruct + @obj + end + + def deconstruct_keys(keys) + C.keys = keys + @obj + end + end + + def test_basic + assert_block do + case 0 + in 0 + true + else + false + end + end + + assert_block do + case 0 + in 1 + false + else + true + end + end + + assert_raise(NoMatchingPatternError) do + case 0 + in 1 + false + end + end + + begin + o = [0] + case o + in 1 + false + end + rescue => e + assert_match o.inspect, e.message + end + + assert_block do + begin + true + ensure + case 0 + in 0 + false + end + end + end + + assert_block do + begin + true + ensure + case 0 + in 1 + else + false + end + end + end + + assert_raise(NoMatchingPatternError) do + begin + ensure + case 0 + in 1 + end + end + end + + assert_block do + # 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 + a + end + }) + ensure + Warning[:experimental] = experimental + end + + assert_block do + tap do |a| + tap do + case true + in a + a + end + end + end + end + + assert_raise(NoMatchingPatternError) do + o = BasicObject.new + def o.match + case 0 + in 1 + end + end + o.match + end + end + + def test_modifier + assert_block do + case 0 + in a if a == 0 + true + end + end + + assert_block do + case 0 + in a if a != 0 + else + true + end + end + + assert_block do + case 0 + in a unless a != 0 + true + end + end + + assert_block do + case 0 + in a unless a == 0 + else + true + end + end + end + + def test_as_pattern + assert_block do + case 0 + in 0 => a + a == 0 + end + end + end + + def test_alternative_pattern + assert_block do + [0, 1].all? do |i| + case i + in 0 | 1 + true + end + end + end + + assert_block do + case 0 + in _ | _a + true + end + end + + assert_syntax_error(%q{ + case 0 + in a | 0 + end + }, /illegal variable in alternative pattern/) + end + + def test_var_pattern + # NODE_DASGN_CURR + assert_block do + case 0 + in a + a == 0 + end + end + + # NODE_DASGN + b = 0 + assert_block do + case 1 + in b + b == 1 + end + end + + # NODE_LASGN + case 0 + in c + assert_equal(0, c) + else + flunk + end + + assert_syntax_error(%q{ + case 0 + in ^a + end + }, /no such local variable/) + + assert_syntax_error(%q{ + case 0 + in a, a + end + }, /duplicated variable name/) + + assert_block do + case [0, 1, 2, 3] + in _, _, _a, _a + true + end + end + + assert_syntax_error(%q{ + case 0 + in a, {a:} + end + }, /duplicated variable name/) + + assert_syntax_error(%q{ + case 0 + in a, {"a":} + end + }, /duplicated variable name/) + + assert_block do + case [0, "1"] + in a, "#{case 1; in a; a; end}" + true + end + end + + assert_syntax_error(%q{ + case [0, "1"] + in a, "#{case 1; in a; a; end}", a + end + }, /duplicated variable name/) + + assert_block do + case 0 + in a + assert_equal(0, a) + true + in a + flunk + end + end + + assert_syntax_error(%q{ + 0 => [a, a] + }, /duplicated variable name/) + end + + def test_literal_value_pattern + assert_block do + case [nil, self, true, false] + in [nil, self, true, false] + true + end + end + + assert_block do + case [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + in [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + true + end + + case [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + in [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + true + end + end + + assert_block do + x = 'x' + case ['a', 'a', x] + in ['a', "a", "#{x}"] + true + end + end + + assert_block do + case ["a\n"] + in [<<END] +a +END + true + end + end + + assert_block do + case [:a, :"a"] + in [:a, :"a"] + true + end + end + + assert_block do + case [0, 1, 2, 3, 4, 5] + in [0..1, 0...2, 0.., 0..., (...5), (..5)] + true + end + end + + assert_syntax_error(%q{ + case 0 + in a..b + end + }, /unexpected/) + + assert_block do + case 'abc' + in /a/ + true + end + end + + assert_block do + case 0 + in ->(i) { i == 0 } + true + end + end + + assert_block do + case [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), 'a'] + in [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), %r(a)] + true + end + end + + assert_block do + case [__FILE__, __LINE__ + 1, __ENCODING__] + in [__FILE__, __LINE__, __ENCODING__] + true + end + end + end + + def test_constant_value_pattern + assert_block do + case 0 + in Integer + true + end + end + + assert_block do + case 0 + in Object::Integer + true + end + end + + assert_block do + case 0 + in ::Object::Integer + true + end + end + end + + def test_pin_operator_value_pattern + assert_block do + a = /a/ + case 'abc' + in ^a + true + end + end + + assert_block do + case [0, 0] + in a, ^a + 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 + + assert_valid_syntax("1 in ^(1\n)") + end + + def test_array_pattern + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in 0,; + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0, 1 + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0, 1 + else + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *a + a == [] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a + a == [0] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a, 0, 1 + raise a # suppress "unused variable: a" warning + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *a, 0, 1 + a == [] + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *a, 1, 2 + a == [0] + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *; + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *, 0, 1 + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *, 0, 1 + true + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *, 1, 2 + true + end + end + end + + assert_block do + case C.new([0]) + in C(0) + true + end + end + + assert_block do + case C.new([0]) + in Array(0) + else + true + end + end + + assert_block do + case C.new([]) + in C() + true + end + end + + assert_block do + case C.new([]) + in Array() + else + true + end + end + + assert_block do + case C.new([0]) + in C[0] + true + end + end + + assert_block do + case C.new([0]) + in Array[0] + else + true + end + end + + assert_block do + case C.new([]) + in C[] + true + end + end + + assert_block do + case C.new([]) + in Array[] + else + true + end + end + + assert_block do + case [] + in [] + true + end + end + + assert_block do + case C.new([]) + in [] + true + end + end + + assert_block do + case [0] + in [0] + true + end + end + + assert_block do + case C.new([0]) + in [0] + true + end + end + + assert_block do + case [0] + in [0,] + true + end + end + + assert_block do + case [0, 1] + in [0,] + true + end + end + + assert_block do + case [] + in [0, *a] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0] + in [0, *a, 1] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0, 1] + in [0, *a, 1] + a == [] + end + end + + assert_block do + case [0, 1, 2] + in [0, *a, 2] + a == [1] + end + end + + assert_block do + case [] + in [0, *] + else + true + end + end + + assert_block do + case [0] + in [0, *] + true + end + end + + assert_block do + case [0, 1] + in [0, *] + true + end + end + + assert_block do + case [] + in [0, *a] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0, 1] + in [0, *a] + a == [1] + end + end + + assert_block do + case [0] + in [0, *, 1] + else + true + end + end + + assert_block do + case [0, 1] + in [0, *, 1] + true + 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| + case i + in a: 0 + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0, b: 1 + else + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{a: 0, b: 1, c: 2}, C.new({a: 0, b: 1, c: 2})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in a: + raise a # suppress "unused variable: a" warning + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a": 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a":; + a == 0 + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **a + a == {} + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **a + a == {a: 0} + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{}, 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 + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **b + a == 0 && b == {} + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a:, **b + a == 0 && b == {b: 1} + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **nil + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **nil + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **nil + assert_equal(0, a) + true + end + end + end + + assert_block do + [{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 + end + end + + assert_block do + case C.new({a: 0}) + in C(a: 0) + true + end + end + + assert_block do + case {a: 0} + in C(a: 0) + else + true + end + end + + assert_block do + case C.new({a: 0}) + in C[a: 0] + true + end + end + + assert_block do + case {a: 0} + in C[a: 0] + else + true + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {a: 0} + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {} + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {} + else + true + end + end + end + + bug18890 = assert_warning(/(?:.*:[47]: warning: unused literal ignored\n){2}/) do + eval("#{<<~';;;'}") + proc do |i| + case i + in a: + 0 # line 4 + a + in "b": + 0 # line 7 + b + else + false + end + end + ;;; + end + [{a: 42}, {b: 42}].each do |i| + assert_block('newline should be significant after pattern label') do + bug18890.call(i) + end + end + + assert_syntax_error(%q{ + case _ + in a:, a: + end + }, /duplicated key name/) + + assert_syntax_error(%q{ + case _ + in a?: + end + }, /key must be valid as local variables/) + + assert_block do + case {a?: true} + in a?: true + true + 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": + end + }, /key must be valid as local variables/) + + assert_block do + case {"a-b": true} + in "a-b": true + true + end + end + + assert_syntax_error(%q{ + case _ + in "#{a}": a + end + }, /symbol literal with interpolation is not allowed/) + + assert_syntax_error(%q{ + case _ + in "#{a}": + end + }, /symbol literal with interpolation is not allowed/) + end + + def test_paren + assert_block do + case 0 + in (0) + true + end + end + end + + def test_nomatchingpatternerror + assert_equal(StandardError, NoMatchingPatternError.superclass) + end + + def test_invalid_syntax + assert_syntax_error(%q{ + case 0 + in a, b: + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in [a:] + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {a} + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {0 => a} + end + }, /unexpected/) + end + + ################################################################ + + class CTypeError + def deconstruct + nil + end + + def deconstruct_keys(keys) + nil + end + end + + def test_deconstruct + assert_raise(TypeError) do + case CTypeError.new + in [] + end + end + end + + def test_deconstruct_keys + assert_raise(TypeError) do + case CTypeError.new + in {} + end + end + + assert_block do + case {} + in {} + C.keys == nil + end + 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 + + 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 + + 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 + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {**} + C.keys == [] + end + 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 + 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 + [:C1] + end + end + + class C2 + end + + module M + refine Array do + def deconstruct + [0] + end + end + + refine Hash do + def deconstruct_keys(_) + {a: 0} + end + end + + refine C2.singleton_class do + def ===(obj) + obj.kind_of?(C1) + end + end + end + + using M + + def test_refinements + assert_block do + case [] + in [0] + true + end + end + + assert_block do + case {} + in {a: 0} + true + end + end + + assert_block do + case C1.new + in C2(:C1) + true + end + end + end + end + + ################################################################ + + def test_struct + assert_block do + s = Struct.new(:a, :b) + case s[0, 1] + in 0, 1 + true + end + end + + s = Struct.new(:a, :b, keyword_init: true) + assert_block do + case s[a: 0, b: 1] + in **r + r == {a: 0, b: 1} + end + end + assert_block do + s = Struct.new(:a, :b, keyword_init: true) + case s[a: 0, b: 1] + in a:, b: + a == 0 && b == 1 + end + end + assert_block do + 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 + in b: + b == 1 + end + end + end + + ################################################################ + + def test_one_line + 1 => a + assert_equal 1, a + assert_raise(NoMatchingPatternError) do + {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 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 + end +end diff --git a/test/ruby/test_pipe.rb b/test/ruby/test_pipe.rb index efca8f28c1..9fa42fd375 100644 --- a/test/ruby/test_pipe.rb +++ b/test/ruby/test_pipe.rb @@ -27,4 +27,23 @@ class TestPipe < Test::Unit::TestCase end end end + + def test_stdout_epipe + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + io = STDOUT + begin + save = io.dup + IO.popen("echo", "w", out: IO::NULL) do |f| + io.reopen(f) + Process.wait(f.pid) + assert_raise(Errno::EPIPE) do + io.print "foo\n" + end + end + ensure + io.reopen(save) + end + end; + end end 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 68455c1bcc..35455ecd5e 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,10 +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 - meta.class_eval {define_method(:foo, Proc.new)} - assert_equal(n, method(:foo).arity) + b = Proc.new(&block) + meta.class_eval { + remove_method(:foo_arity) if method_defined?(:foo_arity) + define_method(:foo_arity, b) + } + assert_equal(n, method(:foo_arity).arity) end def test_arity @@ -133,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) @@ -144,42 +155,40 @@ class TestProc < Test::Unit::TestCase a = lambda {|x| lambda {} }.call(1) b = lambda {} assert_not_equal(a, b, "[ruby-dev:22601]") - end - def test_block_par - assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x}) - assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x}) + assert_equal(*m_nest{}, "[ruby-core:84583] Feature #14627") end - def test_safe - safe = $SAFE - c = Class.new - x = c.new + def test_hash + def self.capture(&block) + block + end - p = proc { - $SAFE += 1 - proc {$SAFE} - }.call - assert_equal(safe, $SAFE) - assert_equal(safe + 1, p.call) - assert_equal(safe, $SAFE) + procs = Array.new(1000){capture{:foo }} + assert_operator(procs.map(&:hash).uniq.size, :>=, 500) + end + + def test_hash_does_not_change_after_compaction + # [Bug #20853] + [ + "proc {}", # iseq backed proc + "{}.to_proc", # ifunc backed proc + ":hello.to_proc", # symbol backed proc + ].each do |proc| + assert_separately([], <<~RUBY) + p1 = #{proc} + hash = p1.hash - c.class_eval {define_method(:safe, p)} - assert_equal(safe, x.safe) - assert_equal(safe, x.method(:safe).call) - assert_equal(safe, x.method(:safe).to_proc.call) + GC.verify_compaction_references(expand_heap: true, toward: :empty) - p = proc {$SAFE += 1} - assert_equal(safe + 1, p.call) - assert_equal(safe, $SAFE) + assert_equal(hash, p1.hash, "proc is `#{proc}`") + RUBY + end + end - c.class_eval {define_method(:inc, p)} - assert_equal(safe + 1, proc {x.inc; $SAFE}.call) - assert_equal(safe, $SAFE) - assert_equal(safe + 1, proc {x.method(:inc).call; $SAFE}.call) - assert_equal(safe, $SAFE) - assert_equal(safe + 1, proc {x.method(:inc).to_proc.call; $SAFE}.call) - assert_equal(safe, $SAFE) + def test_block_par + assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x}) + assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x}) end def m2 @@ -288,7 +297,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 @@ -298,7 +307,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?) @@ -308,6 +317,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 @@ -378,6 +430,11 @@ class TestProc < Test::Unit::TestCase assert_equal(:foo, bc.foo) end + def test_dup_subclass + c1 = Class.new(Proc) + assert_equal c1, c1.new{}.dup.class, '[Bug #17545]' + end + def test_binding b = proc {|x, y, z| proc {}.binding }.call(1, 2, 3) class << b; attr_accessor :foo; end @@ -397,9 +454,23 @@ class TestProc < Test::Unit::TestCase assert_equal([1, 2, 3], b.eval("[x, y, z]")) end + def test_binding_source_location + b, expected_location = binding, [__FILE__, __LINE__] + assert_equal(expected_location, b.source_location) + + file, lineno = method(:source_location_test).to_proc.binding.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + end + + def test_binding_error_unless_ruby_frame + define_singleton_method :binding_from_c!, method(:binding).to_proc >> ->(bndg) {bndg} + assert_raise(RuntimeError) { binding_from_c! } + end + def test_proc_lambda assert_raise(ArgumentError) { proc } - assert_raise(ArgumentError) { lambda } + assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} } o = Object.new def o.foo @@ -407,14 +478,18 @@ class TestProc < Test::Unit::TestCase 1.times { b = lambda } b end - assert_equal(:foo, 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_equal(:foo, o.foo { :foo }.call) + assert_raise(ArgumentError) do + assert_deprecated_warning {o.bar { :foo }}.call + end end def test_arity2 @@ -800,6 +875,115 @@ class TestProc < Test::Unit::TestCase assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x}) end + def test_proc_args_single_kw_no_autosplat + pr = proc {|c, a: 1| [c, a] } + assert_equal [nil, 1], pr.call() + assert_equal [1, 1], pr.call(1) + assert_equal [[1], 1], pr.call([1]) + assert_equal [1, 1], pr.call(1,2) + assert_equal [[1, 2], 1], pr.call([1,2]) + + assert_equal [nil, 3], pr.call(a: 3) + assert_equal [1, 3], pr.call(1, a: 3) + assert_equal [[1], 3], pr.call([1], a: 3) + assert_equal [1, 3], pr.call(1,2, a: 3) + assert_equal [[1, 2], 3], pr.call([1,2], a: 3) + end + + def test_proc_args_single_kwsplat_no_autosplat + pr = proc {|c, **kw| [c, kw] } + assert_equal [nil, {}], pr.call() + assert_equal [1, {}], pr.call(1) + assert_equal [[1], {}], pr.call([1]) + assert_equal [1, {}], pr.call(1,2) + assert_equal [[1, 2], {}], pr.call([1,2]) + + assert_equal [nil, {a: 3}], pr.call(a: 3) + assert_equal [1, {a: 3}], pr.call(1, a: 3) + assert_equal [[1], {a: 3}], pr.call([1], a: 3) + assert_equal [1, {a: 3}], pr.call(1,2, a: 3) + assert_equal [[1, 2], {a: 3}], pr.call([1,2], a: 3) + end + + def test_proc_args_multiple_kw_autosplat + pr = proc {|c, b, a: 1| [c, b, a] } + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c=nil, b=nil, a: 1| [c, b, a] } + assert_equal [nil, nil, 1], pr.call([]) + assert_equal [1, nil, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c, b=nil, a: 1| [c, b, a] } + assert_equal [1, nil, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c=nil, b, a: 1| [c, b, a] } + assert_equal [nil, 1, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c, *b, a: 1| [c, b, a] } + assert_equal [1, [], 1], pr.call([1]) + assert_equal [1, [2], 1], pr.call([1,2]) + + pr = proc {|*c, b, a: 1| [c, b, a] } + assert_equal [[], 1, 1], pr.call([1]) + assert_equal [[1], 2, 1], pr.call([1,2]) + end + + def test_proc_args_multiple_kwsplat_autosplat + pr = proc {|c, b, **kw| [c, b, kw] } + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c=nil, b=nil, **kw| [c, b, kw] } + assert_equal [nil, nil, {}], pr.call([]) + assert_equal [1, nil, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c, b=nil, **kw| [c, b, kw] } + assert_equal [1, nil, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c=nil, b, **kw| [c, b, kw] } + assert_equal [nil, 1, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c, *b, **kw| [c, b, kw] } + assert_equal [1, [], {}], pr.call([1]) + assert_equal [1, [2], {}], pr.call([1,2]) + + pr = proc {|*c, b, **kw| [c, b, kw] } + assert_equal [[], 1, {}], pr.call([1]) + assert_equal [[1], 2, {}], pr.call([1,2]) + end + + def test_proc_args_only_rest + pr = proc {|*c| c } + assert_equal [], pr.call() + 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)] @@ -1103,6 +1287,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) @@ -1120,6 +1334,57 @@ class TestProc < Test::Unit::TestCase assert_equal([[:req]], method(:putc).parameters) assert_equal([[:rest]], method(:p).parameters) + + pr = eval("proc{|"+"(_),"*30+"|}") + assert_empty(pr.parameters.map{|_,n|n}.compact) + end + + def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759 + def self.yielder_ab(splat) + yield([:a, :b], *splat) + end + + res = yielder_ab([[:aa, :bb], Hash.ruby2_keywords_hash({k: :k})]) do |a, b, k:| + [a, b, k] + end + assert_equal([[:a, :b], [:aa, :bb], :k], res) + + def self.yielder(splat) + yield(*splat) + end + res = yielder([ [:a, :b] ]){|a, b, **| [a, b]} + assert_equal([:a, :b], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **| [a, b]} + assert_equal([[:a, :b], nil], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({c: 1}) ]){|a, b, **| [a, b]} + assert_equal([[:a, :b], nil], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **nil| [a, b]} + assert_equal([[:a, :b], nil], res) + end + + def test_parameters_lambda + assert_equal([], proc {}.parameters(lambda: true)) + assert_equal([], proc {||}.parameters(lambda: true)) + assert_equal([[:req, :a]], proc {|a|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters(lambda: true)) + assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:opt, :b]], proc {|a, b=:b|}.parameters(lambda: true)) + assert_equal([[:rest, :a]], proc {|*a|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, c|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters(lambda: true)) + assert_equal([[:req], [:block, :b]], proc {|(a), &b|a}.parameters(lambda: true)) + assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: true)) + + pr = eval("proc{|"+"(_),"*30+"|}") + assert_empty(pr.parameters(lambda: true).map{|_,n|n}.compact) + + assert_equal([[:opt, :a]], lambda {|a|}.parameters(lambda: false)) + assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: false)) end def pm0() end @@ -1154,7 +1419,7 @@ class TestProc < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters) assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters) - assert_equal([[:keyrest]], method(:pmk1).to_proc.parameters) + assert_equal([[:keyrest, :**]], method(:pmk1).to_proc.parameters) assert_equal([[:keyrest, :o]], method(:pmk2).to_proc.parameters) assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).to_proc.parameters) assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).to_proc.parameters) @@ -1168,12 +1433,9 @@ class TestProc < Test::Unit::TestCase end def test_to_s - assert_match(/^#<Proc:0x\h+@#{ Regexp.quote(__FILE__) }:\d+>$/, proc {}.to_s) - assert_match(/^#<Proc:0x\h+@#{ Regexp.quote(__FILE__) }:\d+ \(lambda\)>$/, lambda {}.to_s) + assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+>$/, proc {}.to_s) + assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+ \(lambda\)>$/, lambda {}.to_s) assert_match(/^#<Proc:0x\h+ \(lambda\)>$/, method(:p).to_proc.to_s) - x = proc {} - x.taint - assert_predicate(x.to_s, :tainted?) name = "Proc\u{1f37b}" assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name) end @@ -1282,7 +1544,7 @@ class TestProc < Test::Unit::TestCase def test_local_variables b = get_binding assert_equal(%i'if case when begin end a', b.local_variables) - a = tap {|;x, y| x = y; break binding.local_variables} + a = tap {|;x, y| x = y = x; break binding.local_variables} assert_equal(%i[a b x y], a.sort) end @@ -1325,7 +1587,7 @@ class TestProc < Test::Unit::TestCase end def test_local_variable_set_wb - assert_ruby_status([], <<-'end;', '[Bug #13605]') + assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30) b = binding n = 20_000 @@ -1373,4 +1635,251 @@ class TestProc < Test::Unit::TestCase e.each {} EOS end + + def test_prepended_call + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"]) + begin; + Proc.prepend Module.new {def call() puts "call"; super; end} + def m(&blk) blk.call; end + m {} + end; + end + + def test_refined_call + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"]) + begin; + using Module.new {refine(Proc) {def call() puts "call"; super; end}} + def m(&blk) blk.call; end + m {} + end; + end + + def test_compose + f = proc {|x| x * 2} + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_multiple_args + f = proc {|x| x * 2} + g = proc {|x, y| x + y} + + assert_equal(6, (f << g).call(1, 2)) + assert_equal(6, (g >> f).call(1, 2)) + end + + def test_compose_with_block + f = proc {|x| x * 2} + g = proc {|&blk| blk.call(1) } + + assert_equal(8, (f << g).call { |x| x + 3 }) + assert_equal(8, (g >> f).call { |x| x + 3 }) + end + + 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 + f = proc {|x| x * 2} + c = Class.new { + def g(x) x + 1 end + } + g = c.new.method(:g) + + 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 + f = proc {|x| x * 2} + c = Class.new { + def call(x) x + 1 end + } + g = c.new + + 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 + f = proc {|x| x * 2} + + assert_raise(TypeError) { + (f << 5).call(2) + } + assert_raise(TypeError) { + (f >> 5).call(2) + } + end + + def test_orphan_return + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { return 42 } end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1 { return 42 }.call end }.m2) + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { return 42 } end }.m2.call } + end + + def test_orphan_break + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { break 42 } end }.m2 ) + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { break 42 }.call end }.m2 } + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { break 42 } end }.m2.call } + end + + def test_not_orphan_next + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { next 42 } end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1 { next 42 }.call end }.m2) + 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 + def test_compose_keywords + f = ->(**kw) { kw.merge(:a=>1) } + g = ->(kw) { kw.merge(:a=>2) } + + assert_equal(2, (f >> g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (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_equal(2, (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) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } + assert_equal(2, (f >> g).call(**{})[:a]) + end + + def test_compose_keywords_method + f = ->(**kw) { kw.merge(:a=>1) }.method(:call) + g = ->(kw) { kw.merge(:a=>2) }.method(:call) + + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (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_equal(2, (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) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } + assert_equal(2, (f >> g).call(**{})[:a]) + end + + def test_compose_keywords_non_proc + f = ->(**kw) { kw.merge(:a=>1) } + g = Object.new + def g.call(kw) kw.merge(:a=>2) end + def g.to_proc; method(:call).to_proc; end + def g.<<(f) to_proc << f end + def g.>>(f) to_proc >> f end + + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (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_equal(2, (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) { (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) } + g = Object.new + def g.call(**kw) kw.merge(:a=>2) end + def g.to_proc; method(:call).to_proc; end + def g.<<(f) to_proc << f end + def g.>>(f) to_proc >> f end + + assert_equal(1, (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) { (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_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_raise(ArgumentError) { (f >> g).call(**{})[:a] } + end +end + diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index f300444ecc..f48c4982c2 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -3,7 +3,6 @@ require 'test/unit' require 'tempfile' require 'timeout' -require 'io/wait' require 'rbconfig' class TestProcess < Test::Unit::TestCase @@ -169,7 +168,7 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_pgroup - skip "system(:pgroup) is not supported" if windows? + omit "system(:pgroup) is not supported" if windows? assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) } io = IO.popen([RUBY, "-e", "print Process.getpgrp"]) @@ -208,59 +207,72 @@ 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 system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) end - assert_separately([],<<-"end;") # [ruby-core:82033] [Bug #13744] - assert(system("#{RUBY}", "-e", - "exit([3600,3600] == Process.getrlimit(:CPU))", - 'rlimit_cpu'.to_sym => 3600)) - assert_raise(ArgumentError) do - system("#{RUBY}", '-e', 'exit', :rlimit_bogus => 123) - end + assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) + BUG = "[ruby-core:82033] [Bug #13744]" + begin; + assert_equal([3600,3600], Process.getrlimit(:CPU), BUG) end; - assert_raise(ArgumentError, /rlimit_cpu/) { + assert_raise_with_message(ArgumentError, /bogus/) do + system(RUBY, '-e', 'exit', :rlimit_bogus => 123) + end + + assert_raise_with_message(ArgumentError, /rlimit_cpu/) { system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) } end - MANDATORY_ENVS = %w[RUBYLIB] + 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/ MANDATORY_ENVS << 'LD_PRELOAD' @@ -272,6 +284,9 @@ class TestProcess < Test::Unit::TestCase if e = RbConfig::CONFIG['LIBPATHENV'] MANDATORY_ENVS << e end + if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty? + MANDATORY_ENVS << e + end PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"] ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }'] ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG) @@ -334,6 +349,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 @@ -488,7 +510,7 @@ class TestProcess < Test::Unit::TestCase UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask'] def test_execopts_umask - skip "umask is not supported" if windows? + omit "umask is not supported" if windows? IO.popen([*UMASK, :umask => 0]) {|io| assert_equal("0000", io.read.chomp) } @@ -630,7 +652,7 @@ class TestProcess < Test::Unit::TestCase rescue NotImplementedError return end - assert(FileTest.pipe?("fifo"), "should be pipe") + assert_file.pipe?("fifo") t1 = Thread.new { system(*ECHO["output to fifo"], :out=>"fifo") } @@ -676,14 +698,17 @@ class TestProcess < Test::Unit::TestCase return end IO.popen([RUBY, '-e', <<-'EOS']) {|io| + STDOUT.sync = true trap(:USR1) { print "trap\n" } + puts "start" system("cat", :in => "fifo") EOS - sleep 1 + assert_equal("start\n", io.gets) + sleep 0.2 # wait for the child to stop at opening "fifo" Process.kill(:USR1, io.pid) - sleep 1 + assert_equal("trap\n", io.readpartial(8)) File.write("fifo", "ok\n") - assert_equal("trap\nok\n", io.read) + assert_equal("ok\n", io.read) } } end unless windows? # does not support fifo @@ -756,6 +781,15 @@ class TestProcess < Test::Unit::TestCase Process.wait pid end } + + # ensure standard FDs we redirect to are blocking for compatibility + with_pipes(3) do |pipes| + src = 'p [STDIN,STDOUT,STDERR].map(&:nonblock?)' + rdr = { 0 => pipes[0][0], 1 => pipes[1][1], 2 => pipes[2][1] } + pid = spawn(RUBY, '-rio/nonblock', '-e', src, rdr) + assert_equal("[false, false, false]\n", pipes[1][0].gets) + Process.wait pid + end end end @@ -802,7 +836,7 @@ class TestProcess < Test::Unit::TestCase STDERR=>"out", STDOUT=>[:child, STDERR]) assert_equal("errout", File.read("out")) - skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", STDOUT=>"out", STDERR=>[:child, 3], @@ -854,7 +888,7 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_popen_extra_fd - skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? with_tmpchdir {|d| with_pipe {|r, w| IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io| @@ -871,19 +905,33 @@ class TestProcess < Test::Unit::TestCase } end - def test_popen_fork - IO.popen("-") {|io| - if !io - puts "fooo" - else - assert_equal("fooo\n", io.read) + if Process.respond_to?(:fork) + def test_popen_fork + IO.popen("-") do |io| + if !io + puts "fooo" + else + assert_equal("fooo\n", io.read) + end end - } - rescue NotImplementedError + end + + def test_popen_fork_ensure + IO.popen("-") do |io| + if !io + STDERR.reopen(STDOUT) + raise "fooo" + else + assert_empty io.read + end + end + rescue RuntimeError + abort "[Bug #20995] should not reach here" + end end def test_fd_inheritance - skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? with_pipe {|r, w| system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w) w.close @@ -929,7 +977,7 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_close_others - skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? with_tmpchdir {|d| with_pipe {|r, w| system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true) @@ -1003,6 +1051,15 @@ class TestProcess < Test::Unit::TestCase } end + def test_close_others_default_false + IO.pipe do |r,w| + w.close_on_exec = false + src = "IO.new(#{w.fileno}).puts(:hi)" + assert_equal true, system(*%W(#{RUBY} --disable=gems -e #{src})) + assert_equal "hi\n", r.gets + end + end unless windows? # passing non-stdio fds is not supported on Windows + def test_execopts_redirect_self begin with_pipe {|r, w| @@ -1014,7 +1071,7 @@ class TestProcess < Test::Unit::TestCase } } rescue NotImplementedError - skip "IO#close_on_exec= is not supported" + omit "IO#close_on_exec= is not supported" end end unless windows? # passing non-stdio fds is not supported on Windows @@ -1373,6 +1430,14 @@ class TestProcess < Test::Unit::TestCase } end + def test_argv0_keep_alive + assert_in_out_err([], <<~REPRO, ['-'], [], "[Bug #15887]") + $0 = "diverge" + 4.times { GC.start } + puts Process.argv0 + REPRO + end + def test_status with_tmpchdir do s = run_in_child("exit 1") @@ -1385,6 +1450,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 @@ -1402,6 +1469,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 @@ -1416,6 +1485,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 @@ -1450,7 +1540,9 @@ class TestProcess < Test::Unit::TestCase def test_wait_exception bug11340 = '[ruby-dev:49176] [Bug #11340]' t0 = t1 = nil - IO.popen([RUBY, '-e', 'puts;STDOUT.flush;Thread.start{gets;exit};sleep(3)'], 'r+') do |f| + sec = 3 + code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})" + IO.popen([RUBY, '-e', code], 'r+') do |f| pid = f.pid f.gets t0 = Time.now @@ -1464,21 +1556,35 @@ class TestProcess < Test::Unit::TestCase th.kill.join end t1 = Time.now + diff = t1 - t0 + assert_operator(diff, :<, sec, + ->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"}) f.puts end - assert_operator(t1 - t0, :<, 3, - ->{"#{bug11340}: #{t1-t0} seconds to interrupt Process.wait"}) end def test_abort with_tmpchdir do s = run_in_child("abort") - assert_not_equal(0, s.exitstatus) + assert_not_predicate(s, :success?) + write_file("test-script", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + STDERR.reopen(STDOUT) + begin + raise "[Bug #16424]" + rescue + abort + end + end; + assert_include(IO.popen([RUBY, "test-script"], &:read), "[Bug #16424]") end end def test_sleep assert_raise(ArgumentError) { sleep(1, 1) } + [-1, -1.0, -1r].each do |sec| + assert_raise_with_message(ArgumentError, /not.*negative/) { sleep(sec) } + end end def test_getpgid @@ -1512,8 +1618,16 @@ class TestProcess < Test::Unit::TestCase end def test_maxgroups - assert_kind_of(Integer, Process.maxgroups) + max = Process.maxgroups rescue NotImplementedError + else + assert_kind_of(Integer, max) + assert_predicate(max, :positive?) + omit "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM + gs = Process.groups + assert_operator(gs.size, :<=, max) + gs[0] ||= 0 + assert_raise(ArgumentError) {Process.groups = gs * (max / gs.size + 1)} end def test_geteuid @@ -1526,7 +1640,7 @@ class TestProcess < Test::Unit::TestCase end def test_seteuid_name - user = ENV["USER"] or return + user = (Etc.getpwuid(Process.euid).name rescue ENV["USER"]) or return assert_nothing_raised(TypeError) {Process.euid = user} rescue NotImplementedError end @@ -1536,10 +1650,39 @@ class TestProcess < Test::Unit::TestCase end def test_setegid + omit "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/ assert_nothing_raised(TypeError) {Process.egid += 0} 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) @@ -1568,22 +1711,31 @@ class TestProcess < Test::Unit::TestCase if /freebsd|openbsd/ =~ RUBY_PLATFORM # this relates #4173 # When ruby can use 2 cores, signal and wait4 may miss the signal. - skip "this fails on FreeBSD and OpenBSD on multithreaded environment" + omit "this fails on FreeBSD and OpenBSD on multithreaded environment" end signal_received = [] - Signal.trap(:CHLD) { signal_received << true } - pid = nil - IO.pipe do |r, w| - pid = fork { r.read(1); exit } - Thread.start { raise } - w.puts + IO.pipe do |sig_r, sig_w| + Signal.trap(:CHLD) do + signal_received << true + sig_w.write('?') + end + pid = nil + IO.pipe do |r, w| + pid = fork { r.read(1); exit } + Thread.start { + Thread.current.report_on_exception = false + raise + } + w.puts + end + Process.wait pid + assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable' end - Process.wait pid - 10.times do - break unless signal_received.empty? - sleep 0.01 + 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]" end - assert_equal [true], signal_received, " [ruby-core:19744]" rescue NotImplementedError, ArgumentError ensure begin @@ -1593,6 +1745,9 @@ class TestProcess < Test::Unit::TestCase end def test_no_curdir + if /solaris/i =~ RUBY_PLATFORM + omit "Temporary omit to avoid CI failures after commit to use realpath on required files" + end with_tmpchdir {|d| Dir.mkdir("vd") status = nil @@ -1632,6 +1787,9 @@ class TestProcess < Test::Unit::TestCase end def test_aspawn_too_long_path + if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC) + omit "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10" + end bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613' assert_fail_too_long_path(%w"echo |", bug4315) end @@ -1641,6 +1799,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) @@ -1671,7 +1830,7 @@ class TestProcess < Test::Unit::TestCase with_tmpchdir do assert_nothing_raised('[ruby-dev:12261]') do - Timeout.timeout(3) do + EnvUtil.timeout(10) do pid = spawn('yes | ls') Process.waitpid pid end @@ -1692,6 +1851,8 @@ class TestProcess < Test::Unit::TestCase end def test_daemon_noclose + pend "macOS 15 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) @@ -1730,6 +1891,28 @@ class TestProcess < Test::Unit::TestCase assert_not_equal(cpid, dpid) end + def test_daemon_detached + IO.popen("-", "r+") do |f| + if f + assert_equal(f.pid, Process.wait(f.pid)) + + dpid, ppid, dsid = 3.times.map {Integer(f.gets)} + + message = "daemon #{dpid} should be detached" + assert_not_equal($$, ppid, message) # would be 1 almost always + assert_raise(Errno::ECHILD, message) {Process.wait(dpid)} + assert_kind_of(Integer, Process.kill(0, dpid), message) + assert_equal(dpid, dsid) + + break # close f, and let the daemon resume and exit + end + Process.setsid rescue nil + Process.daemon(false, true) + puts $$, Process.ppid, Process.getsid + $stdin.gets # wait for the above assertions using signals + end + end + if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM def test_daemon_no_threads pid, data = IO.popen("-", "r+") do |f| @@ -1738,12 +1921,12 @@ class TestProcess < Test::Unit::TestCase puts Dir.entries("/proc/self/task") - %W[. ..] end bug4920 = '[ruby-dev:43873]' - assert_equal(2, data.size, bug4920) + assert_include(1..2, data.size, bug4920) assert_not_include(data.map(&:to_i), pid) end else # darwin def test_daemon_no_threads - data = Timeout.timeout(3) do + data = EnvUtil.timeout(3) do IO.popen("-") do |f| break f.readlines.map(&:chomp) if f th = Thread.start {sleep 3} @@ -1792,6 +1975,16 @@ class TestProcess < Test::Unit::TestCase end end + def test_popen_reopen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + io = File.open(IO::NULL) + io2 = io.dup + IO.popen("echo") {|f| io.reopen(f)} + io.reopen(io2) + end; + end + def test_execopts_new_pgroup return unless windows? @@ -1802,22 +1995,23 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_uid + omit "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/ feature6975 = '[ruby-core:47414]' [30000, [Process.uid, ENV["USER"]]].each do |uid, user| if user assert_nothing_raised(feature6975) do begin - system(*TRUECOMMAND, uid: user) - rescue Errno::EPERM, NotImplementedError + system(*TRUECOMMAND, uid: user, exception: true) + rescue Errno::EPERM, Errno::EACCES, NotImplementedError end end end assert_nothing_raised(feature6975) do begin - system(*TRUECOMMAND, uid: uid) - rescue Errno::EPERM, NotImplementedError + system(*TRUECOMMAND, uid: uid, exception: true) + rescue Errno::EPERM, Errno::EACCES, NotImplementedError end end @@ -1825,17 +2019,23 @@ class TestProcess < Test::Unit::TestCase begin u = IO.popen([RUBY, "-e", "print Process.uid", uid: user||uid], &:read) assert_equal(uid.to_s, u, feature6975) - rescue Errno::EPERM, NotImplementedError + rescue Errno::EPERM, Errno::EACCES, NotImplementedError end end end end def test_execopts_gid - skip "Process.groups not implemented on Windows platform" if windows? + omit "Process.groups not implemented on Windows platform" if windows? + omit "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/ feature6975 = '[ruby-core:47414]' - [30000, *Process.groups.map {|g| g = Etc.getgrgid(g); [g.name, g.gid]}].each do |group, gid| + groups = Process.groups.map do |g| + g = Etc.getgrgid(g) rescue next + [g.name, g.gid] + end + groups.compact! + [30000, *groups].each do |group, gid| assert_nothing_raised(feature6975) do begin system(*TRUECOMMAND, gid: group) @@ -2011,7 +2211,11 @@ EOS def test_clock_gettime_GETTIMEOFDAY_BASED_CLOCK_REALTIME n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME - t = Process.clock_gettime(n) + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") end @@ -2089,7 +2293,11 @@ EOS def test_clock_getres_GETTIMEOFDAY_BASED_CLOCK_REALTIME n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME - t = Process.clock_getres(n) + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end assert_kind_of(Float, t, "Process.clock_getres(:#{n})") assert_equal(1000, Process.clock_getres(n, :nanosecond)) end @@ -2155,7 +2363,7 @@ EOS end def test_deadlock_by_signal_at_forking - assert_separately(["-", RUBY], <<-INPUT, timeout: 80) + assert_separately(%W(--disable=gems - #{RUBY}), <<-INPUT, timeout: 100) ruby = ARGV.shift GC.start # reduce garbage GC.disable # avoid triggering CoW after forks @@ -2163,10 +2371,8 @@ EOS parent = $$ 100.times do |i| pid = fork {Process.kill(:QUIT, parent)} - IO.popen(ruby, 'r+'){} + IO.popen([ruby, -'--disable=gems'], -'r+'){} Process.wait(pid) - $stdout.puts - $stdout.flush end INPUT end if defined?(fork) @@ -2176,7 +2382,7 @@ EOS th = Process.detach(pid) assert_equal pid, th.pid status = th.value - assert status.success?, status.inspect + assert_predicate status, :success? end if defined?(fork) def test_kill_at_spawn_failure @@ -2184,7 +2390,9 @@ EOS th = nil x = with_tmpchdir {|d| prog = "#{d}/notexist" - th = Thread.start {system(prog);sleep} + q = Thread::Queue.new + th = Thread.start {system(prog);q.push(nil);sleep} + q.pop th.kill th.join(0.1) } @@ -2235,7 +2443,7 @@ EOS def test_signals_work_after_exec_fail r, w = IO.pipe pid = status = nil - Timeout.timeout(30) do + EnvUtil.timeout(30) do pid = fork do r.close begin @@ -2269,7 +2477,7 @@ EOS def test_threading_works_after_exec_fail r, w = IO.pipe pid = status = nil - Timeout.timeout(30) do + EnvUtil.timeout(90) do pid = fork do r.close begin @@ -2277,16 +2485,16 @@ EOS rescue SystemCallError w.syswrite("exec failed\n") end - run = true - th1 = Thread.new { i = 0; i += 1 while run; i } - th2 = Thread.new { j = 0; j += 1 while run && Thread.pass.nil?; j } + 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 - run = false + q << true w.syswrite "#{th1.value} #{th2.value}\n" 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 @@ -2302,6 +2510,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) @@ -2331,7 +2548,7 @@ EOS end def test_forked_child_handles_signal - skip "fork not supported" unless Process.respond_to?(:fork) + omit "fork not supported" unless Process.respond_to?(:fork) assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]') require 'timeout' pid = fork { sleep } @@ -2347,4 +2564,214 @@ EOS end end end + + def test_last_status + 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(nohang) + # 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? + assert_nil Process.waitpid(-1, Process::WNOHANG) + short_wpipe.close # Make short_pid exit + + # The short pid has exited, so waitpid(-1) should pick that up. + exited_pid = + unless nohang + Process.waitpid(-1) + else + EnvUtil.timeout(5) do + loop do + pid = Process.waitpid(-1, Process::WNOHANG) + + break pid if pid + + sleep 0.1 + end + end + end + + assert_equal short_pid, exited_pid + + # Terminate t1 for the next phase of the test. + t1.kill + t1.join + + t2 = Thread.new do + Process.waitpid -1 + rescue Errno::ECHILD + nil + end + Thread.pass until t2.stop? + t3 = Thread.new do + Process.waitpid long_pid + rescue Errno::ECHILD + nil + end + Thread.pass until t3.stop? + + # it's actually nondeterministic which of t2 or t3 will receive the wait (this + # nondeterminism comes from the behaviour of the underlying system calls) + long_wpipe.close + assert_equal [long_pid], [t2, t3].map(&:value).compact + end + ensure + [t1, t2, t3].each { _1&.kill rescue nil } + [t1, t2, t3].each { _1&.join rescue nil } + [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil } + end if defined?(fork) + + def test_concurrent_group_and_pid_wait + _test_concurrent_group_and_pid_wait(false) + end if defined?(fork) + + def test_concurrent_group_and_pid_wait_nohang + _test_concurrent_group_and_pid_wait(true) + end if defined?(fork) + + def test_handle_interrupt_with_fork + Thread.handle_interrupt(RuntimeError => :never) do + Thread.current.raise(RuntimeError, "Queued error") + + assert_predicate Thread, :pending_interrupt? + + pid = Process.fork do + if Thread.pending_interrupt? + exit 1 + end + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + + assert_predicate Thread, :pending_interrupt? + end + rescue RuntimeError + # Ignore. + end if defined?(fork) end diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index af771a2a8f..a4beffd689 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,183 +189,68 @@ 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) assert_equal("", r.bytes(0)) - assert_equal("\xAC".force_encoding("ASCII-8BIT"), r.bytes(1)) - assert_equal("/\xAA\xC4\x97u\xA6\x16\xB7\xC0\xCC".force_encoding("ASCII-8BIT"), - r.bytes(10)) + assert_equal("", Random.bytes(0)) + + x = r.bytes(1) + assert_equal(1, x.bytesize) + assert_equal(x, Random.bytes(1)) + + 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) } + assert_raise(Errno::EDOM) {r.rand(1..)} + 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 @@ -466,7 +317,7 @@ END assert_equal(r1, r2, bug5661) assert_fork_status(1, '[ruby-core:82100] [Bug #13753]') do - Random::DEFAULT.rand(4) + Random.rand(4) end rescue NotImplementedError end @@ -485,15 +336,11 @@ 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) + def test_seed_leading_zero_guard + guard = 1<<32 + range = 0...(1<<32) + all_assertions_foreach(nil, 0, 1, 2) do |i| + assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range)) end end @@ -507,7 +354,7 @@ END def test_initialize_frozen r = Random.new(0) r.freeze - assert_raise(RuntimeError, '[Bug #6540]') do + assert_raise(FrozenError, '[Bug #6540]') do r.__send__(:initialize, r) end end @@ -516,7 +363,7 @@ END r = Random.new(0) d = r.__send__(:marshal_dump) r.freeze - assert_raise(RuntimeError, '[Bug #6540]') do + assert_raise(FrozenError, '[Bug #6540]') do r.__send__(:marshal_load, d) end end @@ -553,16 +400,19 @@ END end def test_default_seed - assert_separately([], <<-End) - seed = Random::DEFAULT::seed - rand1 = Random::DEFAULT::rand + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + verbose, $VERBOSE = $VERBOSE, nil + seed = Random.seed + rand1 = Random.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 77d026c912..e64a93d9c2 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -3,6 +3,8 @@ require 'test/unit' require 'delegate' require 'timeout' require 'bigdecimal' +require 'date' +require 'rbconfig/sizeof' class TestRange < Test::Unit::TestCase def test_new @@ -12,6 +14,9 @@ class TestRange < Test::Unit::TestCase assert_raise(ArgumentError) { (1.."3") } + assert_equal((0..nil), Range.new(0, nil, false)) + assert_equal((0...nil), Range.new(0, nil, true)) + obj = Object.new def obj.<=>(other) raise RuntimeError, "cmp" @@ -22,7 +27,7 @@ class TestRange < Test::Unit::TestCase def test_frozen_initialize r = Range.allocate r.freeze - assert_raise(RuntimeError){r.__send__(:initialize, 1, 2)} + assert_raise(FrozenError){r.__send__(:initialize, 1, 2)} end def test_range_string @@ -31,14 +36,17 @@ class TestRange < Test::Unit::TestCase assert_equal(["a"], ("a" .. "a").to_a) assert_equal(["a"], ("a" ... "b").to_a) assert_equal(["a", "b"], ("a" .. "b").to_a) + assert_equal([*"a".."z", "aa"], ("a"..).take(27)) end def test_range_numeric_string assert_equal(["6", "7", "8"], ("6".."8").to_a, "[ruby-talk:343187]") assert_equal(["6", "7"], ("6"..."8").to_a) assert_equal(["9", "10"], ("9".."10").to_a) + assert_equal(["9", "10"], ("9"..).take(2)) assert_equal(["09", "10"], ("09".."10").to_a, "[ruby-dev:39361]") assert_equal(["9", "10"], (SimpleDelegator.new("9").."10").to_a) + assert_equal(["9", "10"], (SimpleDelegator.new("9")..).take(2)) assert_equal(["9", "10"], ("9"..SimpleDelegator.new("10")).to_a) end @@ -73,22 +81,33 @@ class TestRange < Test::Unit::TestCase assert_equal(1, (1..2).min) 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) assert_equal(1, (1.0...2.0).min) + assert_equal(1, (1.0..).min) assert_equal(0, (0..0).min) assert_equal(nil, (0...0).min) 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 def test_max assert_equal(2, (1..2).max) assert_equal(nil, (2..1).max) assert_equal(1, (1...2).max) + assert_raise(RangeError) { (1..).max } + assert_raise(RangeError) { (1...).max } assert_equal(2.0, (1.0..2.0).max) assert_equal(nil, (2.0..1.0).max) @@ -103,12 +122,50 @@ class TestRange < Test::Unit::TestCase assert_equal([10,9,8], (0..10).max(3)) 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 + assert_equal([1, 2], (1..2).minmax) + assert_equal([nil, nil], (2..1).minmax) + assert_equal([1, 1], (1...2).minmax) + assert_raise(RangeError) { (1..).minmax } + assert_raise(RangeError) { (1...).minmax } + + assert_equal([1.0, 2.0], (1.0..2.0).minmax) + assert_equal([nil, nil], (2.0..1.0).minmax) + assert_raise(TypeError) { (1.0...2.0).minmax } + assert_raise(TypeError) { (1...1.5).minmax } + assert_raise(TypeError) { (1.5...2).minmax } + + assert_equal([-0x80000002, -0x80000002], ((-0x80000002)...(-0x80000001)).minmax) + + assert_equal([0, 0], (0..0).minmax) + 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 @@ -123,9 +180,10 @@ class TestRange < Test::Unit::TestCase assert_equal(r, Marshal.load(Marshal.dump(r))) r = 1...2 assert_equal(r, Marshal.load(Marshal.dump(r))) - s = Marshal.dump(r) - s.sub!(/endi./n, 'end0') - assert_raise(ArgumentError) {Marshal.load(s)} + r = (1..) + assert_equal(r, Marshal.load(Marshal.dump(r))) + r = (1...) + assert_equal(r, Marshal.load(Marshal.dump(r))) end def test_bad_value @@ -135,6 +193,8 @@ class TestRange < Test::Unit::TestCase def test_exclude_end assert_not_predicate(0..1, :exclude_end?) assert_predicate(0...1, :exclude_end?) + assert_not_predicate(0.., :exclude_end?) + assert_predicate(0..., :exclude_end?) end def test_eq @@ -145,8 +205,17 @@ class TestRange < Test::Unit::TestCase assert_not_equal(r, (1..2)) assert_not_equal(r, (0..2)) assert_not_equal(r, (0...1)) + assert_not_equal(r, (0..nil)) subclass = Class.new(Range) assert_equal(r, subclass.new(0,1)) + + r = (0..nil) + assert_equal(r, r) + assert_equal(r, (0..nil)) + assert_not_equal(r, 0) + assert_not_equal(r, (0...nil)) + subclass = Class.new(Range) + assert_equal(r, subclass.new(0,nil)) end def test_eql @@ -159,12 +228,23 @@ class TestRange < Test::Unit::TestCase assert_not_operator(r, :eql?, 0...1) subclass = Class.new(Range) assert_operator(r, :eql?, subclass.new(0,1)) + + r = (0..nil) + assert_operator(r, :eql?, r) + assert_operator(r, :eql?, 0..nil) + assert_not_operator(r, :eql?, 0) + assert_not_operator(r, :eql?, 0...nil) + subclass = Class.new(Range) + assert_operator(r, :eql?, subclass.new(0,nil)) end def test_hash assert_kind_of(Integer, (0..1).hash) assert_equal((0..1).hash, (0..1).hash) assert_not_equal((0..1).hash, (0...1).hash) + assert_equal((0..nil).hash, (0..nil).hash) + assert_not_equal((0..nil).hash, (0...nil).hash) + assert_kind_of(String, (0..1).hash.to_s) end def test_step @@ -173,31 +253,79 @@ class TestRange < Test::Unit::TestCase assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) a = [] + (0..).step {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + + a = [] (0..10).step(2) {|x| a << x } assert_equal([0, 2, 4, 6, 8, 10], a) + a = [] + (0..).step(2) {|x| a << x; break if a.size == 10 } + assert_equal([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], a) + + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(2)) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(0.5)) + assert_kind_of(Enumerator::ArithmeticSequence, (10..0).step(-1)) + assert_kind_of(Enumerator::ArithmeticSequence, (..10).step(2)) + 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 = [] ("a" .. "z").step(2) {|x| a << x } assert_equal(%w(a c e g i k m o q s u w y), a) a = [] + ("a" .. ).step(2) {|x| a << x; break if a.size == 13 } + assert_equal(%w(a c e g i k m o q s u w y), a) + + a = [] ("a" .. "z").step(2**32) {|x| a << x } assert_equal(["a"], a) a = [] + (:a .. :z).step(2) {|x| a << x } + assert_equal(%i(a c e g i k m o q s u w y), a) + + a = [] + (:a .. ).step(2) {|x| a << x; break if a.size == 13 } + assert_equal(%i(a c e g i k m o q s u w y), a) + + a = [] + (:a .. :z).step(2**32) {|x| a << x } + assert_equal([:a], a) + + a = [] (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 } + assert_equal([4294967295, 4294967297], a) + + max = RbConfig::LIMITS["FIXNUM_MAX"] + a = [] + (max..).step {|x| a << x; break if a.size == 2 } + assert_equal([max, max+1], a) + a = [] + (max..).step(max) {|x| a << x; break if a.size == 4 } + assert_equal([max, 2*max, 3*max, 4*max], a) o1 = Object.new o2 = Object.new def o1.<=>(x); -1; end def o2.<=>(x); 0; end assert_raise(TypeError) { (o1..o2).step(1) { } } + assert_raise(TypeError) { (o1..).step(1) { } } class << o1; self; end.class_eval do define_method(:succ) { o2 } @@ -217,12 +345,43 @@ class TestRange < Test::Unit::TestCase assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) a = [] + (0..).step(0.5) {|x| a << x; break if a.size == 5 } + assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) + + a = [] (0x40000000..0x40000002).step(0.5) {|x| a << x } assert_equal([1073741824, 1073741824.5, 1073741825.0, 1073741825.5, 1073741826], a) o = Object.new def o.to_int() 1 end assert_nothing_raised("[ruby-dev:34558]") { (0..2).step(o) {|x| } } + + o = Object.new + class << o + def to_str() "a" end + def <=>(other) to_str <=> other end + end + + a = [] + (o.."c").step(1) {|x| a << x} + assert_equal(["a", "b", "c"], a) + a = [] + (o..).step(1) {|x| a << x; break if a.size >= 3} + assert_equal(["a", "b", "c"], a) + end + + def test_step_bug15537 + assert_equal([10.0, 9.0, 8.0, 7.0], (10 ..).step(-1.0).take(4)) + assert_equal([10.0, 9.0, 8.0, 7.0], (10.0 ..).step(-1).take(4)) + end + + def test_percent_step + aseq = (1..10) % 2 + assert_equal(Enumerator::ArithmeticSequence, aseq.class) + assert_equal(1, aseq.begin) + assert_equal(10, aseq.end) + assert_equal(2, aseq.step) + assert_equal([1, 3, 5, 7, 9], aseq.to_a) end def test_step_ruby_core_35753 @@ -234,11 +393,35 @@ class TestRange < Test::Unit::TestCase assert_equal(4, (1.0...5.6).step(1.5).to_a.size) end + def test_step_with_succ + c = Struct.new(:i) do + def succ; self.class.new(i+1); end + def <=>(other) i <=> other.i;end + end.new(0) + + result = [] + (c..c.succ).step(2) do |d| + result << d.i + end + assert_equal([0], result) + + result = [] + (c..).step(2) do |d| + result << d.i + break if d.i >= 4 + end + assert_equal([0, 2, 4], result) + end + def test_each a = [] (0..10).each {|x| a << x } assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + a = [] + (0..).each {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + o1 = Object.new o2 = Object.new def o1.setcmp(v) @cmpresult = v end @@ -279,12 +462,48 @@ class TestRange < Test::Unit::TestCase a = [] r2.each {|x| a << x } assert_equal([], a) + + o = Object.new + class << o + def to_str() "a" end + def <=>(other) to_str <=> other end + end + + a = [] + (o.."c").each {|x| a << x} + assert_equal(["a", "b", "c"], a) + a = [] + (o..).each {|x| a << x; break if a.size >= 3} + assert_equal(["a", "b", "c"], a) + end + + def test_each_with_succ + c = Struct.new(:i) do + def succ; self.class.new(i+1); end + def <=>(other) i <=> other.i;end + end.new(0) + + result = [] + (c..c.succ).each do |d| + result << d.i + end + assert_equal([0, 1], result) + + result = [] + (c..).each do |d| + result << d.i + break if d.i >= 4 + end + assert_equal([0, 1, 2, 3, 4], result) end def test_begin_end assert_equal(0, (0..1).begin) assert_equal(1, (0..1).end) assert_equal(1, (0...1).end) + assert_equal(0, (0..nil).begin) + assert_equal(nil, (0..nil).end) + assert_equal(nil, (0...nil).end) end def test_first_last @@ -303,31 +522,74 @@ class TestRange < Test::Unit::TestCase assert_equal("a", ("a"..."c").first) assert_equal("c", ("a"..."c").last) assert_equal(0, (2...0).last) + + assert_equal([0, 1, 2], (0..nil).first(3)) + assert_equal(0, (0..nil).first) + assert_equal("a", ("a"..nil).first) + assert_raise(RangeError) { (0..nil).last } + assert_raise(RangeError) { (0..nil).last(3) } + assert_raise(RangeError) { (nil..0).first } + assert_raise(RangeError) { (nil..0).first(3) } + + assert_equal([0, 1, 2], (0..10).first(3.0)) + assert_equal([8, 9, 10], (0..10).last(3.0)) + assert_raise(TypeError) { (0..10).first("3") } + assert_raise(TypeError) { (0..10).last("3") } + class << (o = Object.new) + def to_int; 3; end + end + assert_equal([0, 1, 2], (0..10).first(o)) + assert_equal([8, 9, 10], (0..10).last(o)) + + assert_raise(ArgumentError) { (0..10).first(-1) } + assert_raise(ArgumentError) { (0..10).last(-1) } + end + + def test_last_with_redefine_each + assert_in_out_err([], <<-'end;', ['true'], []) + class Range + remove_method :each + def each(&b) + [1, 2, 3, 4, 5].each(&b) + end + end + puts [3, 4, 5] == (1..10).last(3) + end; end def test_to_s assert_equal("0..1", (0..1).to_s) assert_equal("0...1", (0...1).to_s) - - bug11767 = '[ruby-core:71811] [Bug #11767]' - assert_predicate(("0".taint.."1").to_s, :tainted?, bug11767) - assert_predicate(("0".."1".taint).to_s, :tainted?, bug11767) - assert_predicate(("0".."1").taint.to_s, :tainted?, bug11767) + assert_equal("0..", (0..nil).to_s) + assert_equal("0...", (0...nil).to_s) end def test_inspect assert_equal("0..1", (0..1).inspect) assert_equal("0...1", (0...1).inspect) - - bug11767 = '[ruby-core:71811] [Bug #11767]' - assert_predicate(("0".taint.."1").inspect, :tainted?, bug11767) - assert_predicate(("0".."1".taint).inspect, :tainted?, bug11767) - assert_predicate(("0".."1").taint.inspect, :tainted?, bug11767) + assert_equal("0..", (0..nil).inspect) + assert_equal("0...", (0...nil).inspect) + assert_equal("..1", (nil..1).inspect) + assert_equal("...1", (nil...1).inspect) + assert_equal("nil..nil", (nil..nil).inspect) + assert_equal("nil...nil", (nil...nil).inspect) end def test_eqq assert_operator(0..10, :===, 5) assert_not_operator(0..10, :===, 11) + assert_operator(5..nil, :===, 11) + assert_not_operator(5..nil, :===, 0) + assert_operator(nil..10, :===, 0) + assert_operator(nil..nil, :===, 0) + end + + def test_eqq_string + assert_operator('A'..'Z', :===, 'ANA') + assert_not_operator('A'..'Z', :===, 'ana') + assert_operator('A'.., :===, 'ANA') + assert_operator(..'Z', :===, 'ANA') + assert_raise(TypeError) {(nil..nil) === 'ANA'} end def test_eqq_time @@ -335,6 +597,8 @@ class TestRange < Test::Unit::TestCase t = Time.now assert_nothing_raised(TypeError, bug11113) { assert_operator(t..(t+10), :===, t+5) + assert_operator(t.., :===, t+5) + assert_not_operator(t.., :===, t-5) } end @@ -362,13 +626,54 @@ class TestRange < Test::Unit::TestCase assert_operator(c.new(0)..c.new(10), :===, c.new(5), bug12003) end + def test_eqq_unbounded_ruby_bug_19864 + t1 = Date.today + t2 = t1 + 1 + assert_equal(true, (..t1) === t1) + assert_equal(false, (..t1) === t2) + assert_equal(true, (..t2) === t1) + assert_equal(true, (..t2) === t2) + assert_equal(false, (...t1) === t1) + assert_equal(false, (...t1) === t2) + assert_equal(true, (...t2) === t1) + assert_equal(false, (...t2) === t2) + + assert_equal(true, (t1..) === t1) + assert_equal(true, (t1..) === t2) + assert_equal(false, (t2..) === t1) + assert_equal(true, (t2..) === t2) + assert_equal(true, (t1...) === t1) + assert_equal(true, (t1...) === t2) + assert_equal(false, (t2...) === t1) + assert_equal(true, (t2...) === t2) + end + + def test_eqq_non_iteratable + k = Class.new do + include Comparable + attr_reader :i + def initialize(i) @i = i; end + def <=>(o); i <=> o.i; end + end + assert_operator(k.new(0)..k.new(2), :===, k.new(1)) + end + def test_include assert_include("a".."z", "c") assert_not_include("a".."z", "5") assert_include("a"..."z", "y") assert_not_include("a"..."z", "z") assert_not_include("a".."z", "cc") + assert_raise(TypeError) {("a"..).include?("c")} + assert_raise(TypeError) {("a"..).include?("5")} + assert_include(0...10, 5) + assert_include(5..., 10) + assert_not_include(5..., 0) + assert_raise(TypeError) {(.."z").include?("z")} + assert_raise(TypeError) {(..."z").include?("z")} + assert_include(..10, 10) + assert_not_include(...10, 10) end def test_cover @@ -377,6 +682,89 @@ class TestRange < Test::Unit::TestCase assert_operator("a"..."z", :cover?, "y") assert_not_operator("a"..."z", :cover?, "z") assert_operator("a".."z", :cover?, "cc") + assert_not_operator(5..., :cover?, 0) + assert_not_operator(5..., :cover?, "a") + assert_operator(5.., :cover?, 10) + + assert_operator(2..5, :cover?, 2..5) + assert_operator(2...6, :cover?, 2...6) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..5, :cover?, 2...6) + assert_operator(2..5, :cover?, 2..4) + assert_operator(2..5, :cover?, 2...4) + assert_operator(2..5, :cover?, 2...5) + assert_operator(2..5, :cover?, 3..5) + assert_operator(2..5, :cover?, 3..4) + assert_operator(2..5, :cover?, 3...6) + assert_operator(2...6, :cover?, 2...5) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..6, :cover?, 2...6) + assert_operator(2.., :cover?, 2..) + assert_operator(2.., :cover?, 3..) + assert_operator(1.., :cover?, 1..10) + assert_operator(..2, :cover?, ..2) + assert_operator(..2, :cover?, ..1) + assert_operator(..2, :cover?, 0..1) + assert_operator(2.0..5.0, :cover?, 2..3) + assert_operator(2..5, :cover?, 2.0..3.0) + assert_operator(2..5, :cover?, 2.0...3.0) + assert_operator(2..5, :cover?, 2.0...5.0) + assert_operator(2.0..5.0, :cover?, 2.0...3.0) + assert_operator(2.0..5.0, :cover?, 2.0...5.0) + assert_operator('aa'..'zz', :cover?, 'aa'...'bb') + + assert_not_operator(2..5, :cover?, 1..5) + assert_not_operator(2...6, :cover?, 1..5) + assert_not_operator(2..5, :cover?, 1...6) + assert_not_operator(1..3, :cover?, 1...6) + assert_not_operator(2..5, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2...7) + assert_not_operator(2..3, :cover?, 1..4) + assert_not_operator(1..2, :cover?, 1.0..3.0) + assert_not_operator(1.0..2.9, :cover?, 1.0..3.0) + assert_not_operator(1..2, :cover?, 4..3) + assert_not_operator(2..1, :cover?, 1..2) + assert_not_operator(1...2, :cover?, 1...3) + assert_not_operator(2.., :cover?, 1..) + assert_not_operator(2.., :cover?, 1..10) + assert_not_operator(2.., :cover?, ..10) + assert_not_operator(1..10, :cover?, 1..) + assert_not_operator(1..10, :cover?, ..1) + assert_not_operator(1..5, :cover?, 3..2) + assert_not_operator(1..10, :cover?, 3...2) + 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 @@ -454,6 +842,16 @@ class TestRange < Test::Unit::TestCase assert_equal 6, (1...6.3).size assert_equal 5, (1.1...6).size assert_equal 42, (1..42).each.size + assert_nil ("a"..."z").size + assert_nil ("a"...).size + assert_nil (..."z").size # [Bug #18983] + assert_nil (nil...nil).size # [Bug #18983] + + assert_equal Float::INFINITY, (1...).size + assert_equal Float::INFINITY, (1.0...).size + assert_equal Float::INFINITY, (...1).size + assert_equal Float::INFINITY, (...1.0).size + assert_nil ("a"...).size end def test_bsearch_typechecks_return_values @@ -495,6 +893,9 @@ class TestRange < Test::Unit::TestCase ary = [0, 100, 100, 100, 200] assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 }) + + assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 }) + assert_equal( -999_999, (...0).bsearch {|i| i > -1_000_000 }) end def test_bsearch_for_float @@ -546,6 +947,9 @@ class TestRange < Test::Unit::TestCase assert_in_delta(1.0, (0.0..inf).bsearch {|x| Math.log(x) >= 0 }) assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x }) + + assert_equal( 1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 }) + assert_equal(-1_000_000.0.next_float, (..0.0).bsearch {|x| x > -1_000_000 }) end def check_bsearch_values(range, search, a) @@ -647,6 +1051,8 @@ class TestRange < Test::Unit::TestCase assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 100 }) assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true }) assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false }) + assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 }) + assert_equal(-bignum * 2 + 1, (...-bignum).bsearch {|i| i > -bignum * 2 }) assert_raise(TypeError) { ("a".."z").bsearch {} } end @@ -658,4 +1064,18 @@ class TestRange < Test::Unit::TestCase end (a.."c").each {|x, &b| assert_nil(b)} end + + def test_to_a + assert_equal([1,2,3,4,5], (1..5).to_a) + assert_equal([1,2,3,4], (1...5).to_a) + assert_raise(RangeError) { (1..).to_a } + end + + def test_beginless_range_iteration + assert_raise(TypeError) { (..1).each { } } + end + + def test_count + assert_equal(Float::INFINITY, (1..).count) + end end diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index d94ded3fe3..a51ce3dc99 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -42,7 +42,9 @@ class Rational_Test < Test::Unit::TestCase end def test_hash - assert_kind_of(Integer, Rational(1,2).hash) + h = Rational(1,2).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} h = {} h[Rational(0)] = 0 @@ -108,12 +110,52 @@ class Rational_Test < Test::Unit::TestCase assert_equal(Rational(3),Rational('3')) assert_equal(Rational(1),Rational('3.0','3.0')) assert_equal(Rational(1),Rational('3/3','3/3')) + assert_equal(Rational(111, 1), Rational('1.11e+2')) + assert_equal(Rational(111, 10), Rational('1.11e+1')) + assert_equal(Rational(111, 10), Rational('1.11e1')) + assert_equal(Rational(111, 100), Rational('1.11e0')) + assert_equal(Rational(111, 1000), Rational('1.11e-1')) assert_raise(TypeError){Rational(nil)} assert_raise(ArgumentError){Rational('')} assert_raise_with_message(ArgumentError, /\u{221a 2668}/) { Rational("\u{221a 2668}") } + assert_warning('') { + assert_predicate(Rational('1e-99999999999999999999'), :zero?) + } + assert_raise(TypeError){Rational(Object.new)} + 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)) + assert_equal(1/84r, Rational(o, 2)) + assert_equal(42, Rational(1, o)) + assert_equal(1, Rational(o, o)) + + o = Object.new + def o.to_r; nil; end + assert_raise(TypeError) { Rational(o) } + assert_raise(TypeError) { Rational(o, 2) } + assert_raise(TypeError) { Rational(1, o) } + assert_raise(TypeError) { Rational(o, o) } + + o = Object.new + def o.to_r; raise; end + assert_raise(RuntimeError) { Rational(o) } + assert_raise(RuntimeError) { Rational(o, 2) } + assert_raise(RuntimeError) { Rational(1, o) } + assert_raise(RuntimeError) { Rational(o, o) } + assert_raise(ArgumentError){Rational()} assert_raise(ArgumentError){Rational(1,2,3)} @@ -123,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 @@ -560,9 +610,16 @@ class Rational_Test < Test::Unit::TestCase assert_equal([Rational(2.2),Rational(1)], Rational(1).coerce(2.2)) assert_equal([Rational(2),Rational(1)], Rational(1).coerce(Rational(2))) - assert_nothing_raised(TypeError, '[Bug #5020] [ruby-devl:44088]') do + 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 @@ -766,6 +823,8 @@ class Rational_Test < Test::Unit::TestCase ng[5, 1, '5/_3'] ng[5, 3, '5/3_'] ng[5, 3, '5/3x'] + + ng[5, 1, '5/-3'] end def test_parse_zero_denominator @@ -773,6 +832,54 @@ class Rational_Test < Test::Unit::TestCase assert_raise(ZeroDivisionError) {Rational("1/0")} end + def test_Rational_with_invalid_exception + assert_raise(ArgumentError) { + Rational("1/1", exception: 1) + } + end + + def test_Rational_without_exception + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Rational("5/3x", exception: false)) + } + assert_nothing_raised(ZeroDivisionError) { + assert_equal(nil, Rational("1/0", exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, nil, exception: false)) + } + assert_nothing_raised(TypeError) { + 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) { + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, o, exception: false)) + } + end + def test_to_i assert_equal(1, Rational(3,2).to_i) assert_equal(1, Integer(Rational(3,2))) @@ -781,6 +888,7 @@ class Rational_Test < Test::Unit::TestCase def test_to_f assert_equal(1.5, Rational(3,2).to_f) assert_equal(1.5, Float(Rational(3,2))) + assert_equal(1e-23, Rational(1, 10**23).to_f, "Bug #14637") end def test_to_c diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 5fba208fb7..56f33ae00a 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -19,6 +19,10 @@ class TestRefinement < Test::Unit::TestCase return "Foo#a" end + def b + return "Foo#b" + end + def call_x return x end @@ -41,6 +45,10 @@ class TestRefinement < Test::Unit::TestCase def a return "FooExt#a" end + + private def b + return "FooExt#b" + end end end @@ -94,10 +102,26 @@ class TestRefinement < Test::Unit::TestCase return foo.send(:z) end + def self.send_b_on(foo) + return foo.send(:b) + end + + def self.public_send_z_on(foo) + return foo.public_send(:z) + end + + def self.public_send_b_on(foo) + return foo.public_send(:b) + end + def self.method_z(foo) return foo.method(:z) end + def self.instance_method_z(foo) + return foo.class.instance_method(:z) + end + def self.invoke_call_x_on(foo) return foo.call_x end @@ -179,16 +203,63 @@ class TestRefinement < Test::Unit::TestCase foo = Foo.new assert_raise(NoMethodError) { foo.send(:z) } assert_equal("FooExt#z", FooExtClient.send_z_on(foo)) + assert_equal("FooExt#b", FooExtClient.send_b_on(foo)) assert_raise(NoMethodError) { foo.send(:z) } assert_equal(true, RespondTo::Sub.new.respond_to?(:foo)) end - def test_method_should_not_use_refinements + def test_public_send_should_use_refinements + foo = Foo.new + assert_raise(NoMethodError) { foo.public_send(:z) } + assert_equal("FooExt#z", FooExtClient.public_send_z_on(foo)) + assert_equal("Foo#b", foo.public_send(:b)) + assert_raise(NoMethodError) { FooExtClient.public_send_b_on(foo) } + end + + module MethodIntegerPowEx + refine Integer do + def pow(*) + :refine_pow + end + end + end + def test_method_should_use_refinements + omit if Test::Unit::Runner.current_repeat_count > 0 + foo = Foo.new assert_raise(NameError) { foo.method(:z) } - assert_raise(NameError) { FooExtClient.method_z(foo) } + assert_equal("FooExt#z", FooExtClient.method_z(foo).call) assert_raise(NameError) { foo.method(:z) } + assert_equal(8, eval(<<~EOS, Sandbox::BINDING)) + meth = 2.method(:pow) + using MethodIntegerPowEx + meth.call(3) + EOS + assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.pow(3)")) + assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.method(:pow).(3)")) + end + + module InstanceMethodIntegerPowEx + refine Integer do + def abs + :refine_abs + end + end + end + def test_instance_method_should_use_refinements + omit 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) + assert_raise(NameError) { Foo.instance_method(:z) } + assert_equal(4, eval(<<~EOS, Sandbox::BINDING)) + meth = Integer.instance_method(:abs) + using InstanceMethodIntegerPowEx + meth.bind(-4).call + EOS + assert_equal(:refine_abs, eval_using(InstanceMethodIntegerPowEx, "Integer.instance_method(:abs).bind(-4).call")) end def test_no_local_rebinding @@ -297,9 +368,9 @@ class TestRefinement < Test::Unit::TestCase end end - def test_respond_to_should_not_use_refinements + def test_respond_to_should_use_refinements assert_equal(false, 1.respond_to?(:foo)) - assert_equal(false, eval_using(IntegerFooExt, "1.respond_to?(:foo)")) + assert_equal(true, eval_using(IntegerFooExt, "1.respond_to?(:foo)")) end module StringCmpExt @@ -471,7 +542,7 @@ class TestRefinement < Test::Unit::TestCase def test_main_using_is_private assert_raise(NoMethodError) do - eval("self.using Module.new", Sandbox::BINDING) + eval("recv = self; recv.using Module.new", Sandbox::BINDING) end end @@ -676,130 +747,37 @@ class TestRefinement < Test::Unit::TestCase end end - module IncludeIntoRefinement - class C - def bar - return "C#bar" - end - - def baz - return "C#baz" - end - end - - module Mixin - def foo - return "Mixin#foo" - end - - def bar - return super << " Mixin#bar" - end - - def baz - return super << " Mixin#baz" - end - end - - module M - refine C do - include Mixin - - def baz - return super << " M#baz" - end - end - end + def self.suppress_verbose + verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = verbose end - eval <<-EOF, Sandbox::BINDING - using TestRefinement::IncludeIntoRefinement::M - - module TestRefinement::IncludeIntoRefinement::User - def self.invoke_foo_on(x) - x.foo - end - - def self.invoke_bar_on(x) - x.bar - end - - def self.invoke_baz_on(x) - x.baz - end - end - EOF - def test_include_into_refinement - x = IncludeIntoRefinement::C.new - assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x)) - assert_equal("C#bar Mixin#bar", - IncludeIntoRefinement::User.invoke_bar_on(x)) - assert_equal("C#baz Mixin#baz M#baz", - IncludeIntoRefinement::User.invoke_baz_on(x)) - end - - module PrependIntoRefinement - class C - def bar - return "C#bar" - end - - def baz - return "C#baz" - end - end - - module Mixin - def foo - return "Mixin#foo" - end - - def bar - return super << " Mixin#bar" - end - - def baz - return super << " Mixin#baz" - end - end - - module M - refine C do - prepend Mixin + assert_raise(TypeError) do + c = Class.new + mixin = Module.new - def baz - return super << " M#baz" + Module.new do + refine c do + include mixin end end end end - eval <<-EOF, Sandbox::BINDING - using TestRefinement::PrependIntoRefinement::M - - module TestRefinement::PrependIntoRefinement::User - def self.invoke_foo_on(x) - x.foo - end - - def self.invoke_bar_on(x) - x.bar - end + def test_prepend_into_refinement + assert_raise(TypeError) do + c = Class.new + mixin = Module.new - def self.invoke_baz_on(x) - x.baz + Module.new do + refine c do + prepend mixin + end end end - EOF - - def test_prepend_into_refinement - x = PrependIntoRefinement::C.new - assert_equal("Mixin#foo", PrependIntoRefinement::User.invoke_foo_on(x)) - assert_equal("C#bar Mixin#bar", - PrependIntoRefinement::User.invoke_bar_on(x)) - assert_equal("C#baz M#baz Mixin#baz", - PrependIntoRefinement::User.invoke_baz_on(x)) end PrependAfterRefine_CODE = <<-EOC @@ -841,7 +819,7 @@ class TestRefinement < Test::Unit::TestCase def test_prepend_after_refine_wb_miss if /\A(arm|mips)/ =~ RUBY_PLATFORM - skip "too slow cpu" + omit "too slow cpu" end assert_normal_exit %Q{ GC.stress = true @@ -849,7 +827,7 @@ class TestRefinement < Test::Unit::TestCase #{PrependAfterRefine_CODE} undef PrependAfterRefine } - } + }, timeout: 60 end def test_prepend_after_refine @@ -907,6 +885,10 @@ class TestRefinement < Test::Unit::TestCase return foo.send(:z) end + def self.public_send_z_on(foo) + return foo.public_send(:z) + end + def self.method_z(foo) return foo.method(:z) end @@ -1224,6 +1206,41 @@ class TestRefinement < Test::Unit::TestCase INPUT end + def test_refined_protected_methods + assert_separately([], <<-"end;") + bug18806 = '[ruby-core:108705] [Bug #18806]' + class C; end + + module R + refine C do + def refined_call_foo = foo + def refined_call_foo_on(other) = other.foo + + protected + + def foo = :foo + end + end + + class C + using R + + def call_foo = foo + def call_foo_on(other) = other.foo + end + + c = C.new + assert_equal :foo, c.call_foo, bug18806 + assert_equal :foo, c.call_foo_on(c), bug18806 + assert_equal :foo, c.call_foo_on(C.new), bug18806 + + using R + assert_equal :foo, c.refined_call_foo, bug18806 + assert_equal :foo, c.refined_call_foo_on(c), bug18806 + assert_equal :foo, c.refined_call_foo_on(C.new), bug18806 + end; + end + def test_refine_basic_object assert_separately([], <<-"end;") bug10106 = '[ruby-core:64166] [Bug #10106]' @@ -1491,6 +1508,7 @@ class TestRefinement < Test::Unit::TestCase undef :foo end end + ext end def test_call_refined_method_in_duplicate_module @@ -1576,7 +1594,6 @@ class TestRefinement < Test::Unit::TestCase def test_reopen_refinement_module assert_separately([], <<-"end;") - $VERBOSE = nil class C end @@ -1593,6 +1610,7 @@ class TestRefinement < Test::Unit::TestCase module R refine C do + alias m m def m :bar end @@ -1704,6 +1722,8 @@ class TestRefinement < Test::Unit::TestCase refine Object do def in_ref_a end + + RefA.const_set(:REF, self) end end @@ -1711,6 +1731,8 @@ class TestRefinement < Test::Unit::TestCase refine Object do def in_ref_b end + + RefB.const_set(:REF, self) end end @@ -1720,23 +1742,28 @@ class TestRefinement < Test::Unit::TestCase refine Object do def in_ref_c end + + RefC.const_set(:REF, self) end end module Foo using RefB USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements end module Bar using RefC USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements end module Combined using RefA using RefB USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements end end @@ -1748,6 +1775,41 @@ class TestRefinement < Test::Unit::TestCase assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS end + def test_used_refinements + ref = VisibleRefinements + assert_equal [], Module.used_refinements + assert_equal [ref::RefB::REF], ref::Foo::USED_REFS + assert_equal [ref::RefC::REF], ref::Bar::USED_REFS + assert_equal [ref::RefB::REF, ref::RefA::REF], ref::Combined::USED_REFS + end + + def test_refinements + int_refinement = nil + str_refinement = nil + m = Module.new { + refine Integer do + int_refinement = self + end + + refine String do + str_refinement = self + end + } + assert_equal([int_refinement, str_refinement], m.refinements) + end + + def test_refined_class + refinements = Module.new { + refine Integer do + end + + refine String do + end + }.refinements + assert_equal(Integer, refinements[0].refined_class) + assert_equal(String, refinements[1].refined_class) + end + def test_warn_setconst_in_refinmenet bug10103 = '[ruby-core:64143] [Bug #10103]' warnings = [ @@ -1892,10 +1954,10 @@ class TestRefinement < Test::Unit::TestCase m = Module.new do r = refine(String) {def test;:ok end} end - assert_raise_with_message(ArgumentError, /refinement/, bug) do + assert_raise_with_message(TypeError, /refinement/, bug) do m.module_eval {include r} end - assert_raise_with_message(ArgumentError, /refinement/, bug) do + assert_raise_with_message(TypeError, /refinement/, bug) do m.module_eval {prepend r} end end @@ -1992,7 +2054,6 @@ class TestRefinement < Test::Unit::TestCase def test_tostring assert_equal("ok", ToString.new.string) - assert_predicate(ToString.new.taint.string, :tainted?) end class ToSymbol @@ -2010,6 +2071,561 @@ class TestRefinement < Test::Unit::TestCase assert_equal(:foo, ToSymbol.new("foo").symbol) end + module ToProc + def self.call &block + block.call + end + + class ReturnProc + c = self + using Module.new { + refine c do + def to_proc + proc { "to_proc" } + end + end + } + def call + ToProc.call(&self) + end + end + + class ReturnNoProc + c = self + using Module.new { + refine c do + def to_proc + true + end + end + } + + def call + ToProc.call(&self) + end + end + + class PrivateToProc + c = self + using Module.new { + refine c do + private + def to_proc + proc { "private_to_proc" } + end + end + } + + def call + ToProc.call(&self) + end + end + + + class NonProc + def call + ToProc.call(&self) + end + end + + class MethodMissing + def method_missing *args + proc { "method_missing" } + end + + def call + ToProc.call(&self) + end + end + + class ToProcAndMethodMissing + def method_missing *args + proc { "method_missing" } + end + + c = self + using Module.new { + refine c do + def to_proc + proc { "to_proc" } + end + end + } + + def call + ToProc.call(&self) + end + end + + class ToProcAndRefinements + def to_proc + proc { "to_proc" } + end + + c = self + using Module.new { + refine c do + def to_proc + proc { "refinements_to_proc" } + end + end + } + + def call + ToProc.call(&self) + end + end + end + + def test_to_proc + assert_equal("to_proc", ToProc::ReturnProc.new.call) + assert_equal("private_to_proc", ToProc::PrivateToProc.new.call) + assert_raise(TypeError){ ToProc::ReturnNoProc.new.call } + assert_raise(TypeError){ ToProc::NonProc.new.call } + assert_equal("method_missing", ToProc::MethodMissing.new.call) + assert_equal("to_proc", ToProc::ToProcAndMethodMissing.new.call) + assert_equal("refinements_to_proc", ToProc::ToProcAndRefinements.new.call) + end + + def test_unused_refinement_for_module + bug14068 = '[ruby-core:83613] [Bug #14068]' + assert_in_out_err([], <<-INPUT, ["M1#foo"], [], bug14068) + module M1 + def foo + puts "M1#foo" + end + end + + module M2 + end + + module UnusedRefinement + refine(M2) do + def foo + puts "M2#foo" + end + end + end + + include M1 + include M2 + foo() + INPUT + end + + def test_refining_module_repeatedly + bug14070 = '[ruby-core:83617] [Bug #14070]' + assert_in_out_err([], <<-INPUT, ["ok"], [], bug14070) + 1000.times do + Class.new do + include Enumerable + end + + Module.new do + refine Enumerable do + def foo + end + end + end + end + puts "ok" + INPUT + end + + def test_call_method_in_unused_refinement + bug15720 = '[ruby-core:91916] [Bug #15720]' + assert_in_out_err([], <<-INPUT, ["ok"], [], bug15720) + module M1 + refine Kernel do + def foo + 'foo called!' + end + end + end + + module M2 + refine Kernel do + def bar + 'bar called!' + end + end + end + + using M1 + + foo + + begin + bar + rescue NameError + end + + puts "ok" + INPUT + end + + def test_super_from_refined_module + a = EnvUtil.labeled_module("A") do + def foo;"[A#{super}]";end + end + b = EnvUtil.labeled_class("B") do + def foo;"[B]";end + end + c = EnvUtil.labeled_class("C", b) do + include a + def foo;"[C#{super}]";end + end + d = EnvUtil.labeled_module("D") do + refine(a) do + def foo;end + end + end + assert_equal("[C[A[B]]]", c.new.foo, '[ruby-dev:50390] [Bug #14232]') + d + end + + class RefineInUsing + module M1 + refine RefineInUsing do + def foo + :ok + end + end + end + + module M2 + using M1 + refine RefineInUsing do + def call_foo + RefineInUsing.new.foo + end + end + end + + using M2 + def self.test + new.call_foo + end + end + + def test_refine_in_using + assert_equal(:ok, RefineInUsing.test) + end + + class Bug16242 + module OtherM + end + + module M + prepend OtherM + + refine M do + def refine_method + "refine_method" + end + end + using M + + def hoge + refine_method + end + end + + class X + include M + end + end + + def test_refine_prepended_module + assert_equal("refine_method", Bug16242::X.new.hoge) + end + + module Bug13446 + module Enumerable + def sum(*args) + i = 0 + args.each { |arg| i += a } + i + end + end + + using Module.new { + refine Enumerable do + alias :orig_sum :sum + end + } + + module Enumerable + def sum(*args) + orig_sum(*args) + end + end + + class GenericEnumerable + include Enumerable + end + + Enumerable.prepend(Module.new) + end + + def test_prepend_refined_module + 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 UsingC + using C + + def self.call_bar + A.new.bar + end + end + end + + def test_import_methods + assert_equal("refined:bar", TestImport::UsingC.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 549b21c84e..b385760b42 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(/3\.3/) {Regexp.new(s, nil, "u")} + assert_match(r, "\xC3\xBE") end def test_yoshidam_net_20041111_2 assert_raise(RegexpError) do s = "[\xFF-\xFF]".force_encoding("utf-8") - Regexp.new(s, nil, "u") + assert_warning(/3\.3/) {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") @@ -80,6 +91,122 @@ class TestRegexp < Test::Unit::TestCase assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s} end + def test_extended_comment_invalid_escape_bug_18294 + assert_separately([], <<-RUBY) + re = / C:\\\\[a-z]{5} # e.g. C:\\users /x + assert_match(re, 'C:\\users') + assert_not_match(re, 'C:\\user') + + re = / + foo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[#o]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[[:alnum:]#]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f(?# \\M-ca)oo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# \\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /[-(?# fca)]oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# ca\0\\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + RUBY + + assert_raise(SyntaxError) {eval "/\\users/x"} + assert_raise(SyntaxError) {eval "/[\\users]/x"} + assert_raise(SyntaxError) {eval "/(?<\\users)/x"} + assert_raise(SyntaxError) {eval "/# \\users/"} + end + + def test_nonextended_section_of_extended_regexp_bug_19379 + assert_separately([], <<-'RUBY') + re = /(?-x:#)/x + assert_match(re, '#') + assert_not_match(re, '-') + + re = /(?xi:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?mix:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?x-im:# + y)/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + y))/x + assert_match(re, 'y') + assert_not_match(re, '-') + + re = /(?x)# + y/ + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?mx-i)# + y/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + (?-x)y#))/x + assert_match(re, 'Y#') + assert_not_match(re, '-#') + + re = /(?imx:# + (?-xim:#(?im)#(?x)# + )# + (?x)# + y)/ + assert_match(re, '###Y') + assert_not_match(re, '###-') + + re = %r{#c-\w+/comment/[\w-]+} + re = %r{https?://[^/]+#{re}}x + assert_match(re, 'http://foo#c-x/comment/bar') + assert_not_match(re, 'http://foo#cx/comment/bar') + RUBY + end + + def test_utf8_comment_in_usascii_extended_regexp_bug_19455 + assert_separately([], <<-RUBY) + assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding) + assert_equal(Encoding::UTF_8, /#\u1000/x.encoding) + RUBY + end + def test_union assert_equal :ok, begin Regexp.union( @@ -90,6 +217,11 @@ class TestRegexp < Test::Unit::TestCase rescue ArgumentError :ok end + re = Regexp.union(/\//, "") + re2 = eval(re.inspect) + assert_equal(re.to_s, re2.to_s) + assert_equal(re.source, re2.source) + assert_equal(re, re2) end def test_word_boundary @@ -156,6 +288,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 @@ -209,6 +345,23 @@ 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 + %W[C \u{1d402}].each do |name| + assert_equal(:ok, Class.new.class_eval("#{name} = :ok; /(?<#{name}>.*)/ =~ 'ng'; #{name}")) + end end def test_assign_named_capture_trace @@ -228,6 +381,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) @@ -366,6 +540,27 @@ class TestRegexp < Test::Unit::TestCase assert_equal([2, 3], m.offset(3)) end + def test_match_byteoffset_begin_end + m = /(?<x>b..)/.match("foobarbaz") + assert_equal([3, 6], m.byteoffset("x")) + assert_equal(3, m.begin("x")) + assert_equal(6, m.end("x")) + assert_raise(IndexError) { m.byteoffset("y") } + assert_raise(IndexError) { m.byteoffset(2) } + assert_raise(IndexError) { m.begin(2) } + assert_raise(IndexError) { m.end(2) } + + m = /(?<x>q..)?/.match("foobarbaz") + assert_equal([nil, nil], m.byteoffset("x")) + assert_equal(nil, m.begin("x")) + assert_equal(nil, m.end("x")) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal([3, 6], m.byteoffset(1)) + assert_equal([nil, nil], m.byteoffset(2)) + assert_equal([6, 9], m.byteoffset(3)) + end + def test_match_to_s m = /(?<x>b..)/.match("foobarbaz") assert_equal("bar", m.to_s) @@ -409,6 +604,7 @@ class TestRegexp < Test::Unit::TestCase assert_nil(m[5]) assert_raise(IndexError) { m[:foo] } assert_raise(TypeError) { m[nil] } + assert_equal(["baz", nil], m[-2, 3]) end def test_match_values_at @@ -436,22 +632,119 @@ 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) end + def test_match_data_deconstruct + m = /foo.+/.match("foobarbaz") + assert_equal([], m.deconstruct) + + m = /(foo).+(baz)/.match("foobarbaz") + assert_equal(["foo", "baz"], m.deconstruct) + + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal(["foo", "bar", "baz", nil], m.deconstruct) + end + + def test_match_data_deconstruct_keys + m = /foo.+/.match("foobarbaz") + assert_equal({}, m.deconstruct_keys([:a])) + + m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz") + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil)) + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b])) + assert_equal({b: "baz"}, m.deconstruct_keys([:b])) + assert_equal({}, m.deconstruct_keys([:c, :a])) + assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c])) + assert_equal({}, m.deconstruct_keys([:a, :b, :c])) + + assert_raise(TypeError) { + m.deconstruct_keys(0) + } + + assert_raise(TypeError) { + m.deconstruct_keys(["a", "b"]) + } + end + def test_initialize assert_raise(ArgumentError) { Regexp.new } - assert_equal(/foo/, 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")]) - assert_equal(//n, Regexp.new("", nil, "n")) + assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)}) + + arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value + + assert_deprecated_warning('') do + assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding) + assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)]) + assert_equal(//, Regexp.new("")) + assert_equal(//, Regexp.new("", timeout: 1)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1)) + + assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options) + + assert_nil(Regexp.new("").timeout) + assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout) + assert_nil(Regexp.compile("").timeout) + assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout) + end - arg_encoding_none = 32 # ARG_ENCODING_NONE is implementation defined value - assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options) - assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options) + assert_deprecated_warning(/3\.3/) do + assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding) + end + assert_deprecated_warning(/3\.3/) do + assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n", timeout: 1).encoding) + end + assert_deprecated_warning(/3\.3/) do + assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")]) + end + assert_deprecated_warning(/3\.3/) do + assert_equal(//n, Regexp.new("", nil, "n")) + end + assert_deprecated_warning(/3\.3/) do + assert_equal(arg_encoding_none, Regexp.new("", nil, "n").options) + end + assert_deprecated_warning(/3\.3/) do + assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options) + end assert_raise(RegexpError) { Regexp.new(")(") } assert_raise(RegexpError) { Regexp.new('[\\40000000000') } @@ -459,6 +752,47 @@ class TestRegexp < Test::Unit::TestCase assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") } end + def test_initialize_bool_warning + assert_warning(/expected true or false as ignorecase/) do + Regexp.new("foo", :i) + end + end + + def test_initialize_option + assert_equal(//i, Regexp.new("", "i")) + assert_equal(//m, Regexp.new("", "m")) + assert_equal(//x, Regexp.new("", "x")) + assert_equal(//imx, Regexp.new("", "imx")) + assert_equal(//, Regexp.new("", "")) + assert_equal(//imx, Regexp.new("", "mimix")) + + assert_raise(ArgumentError) { Regexp.new("", "e") } + assert_raise(ArgumentError) { Regexp.new("", "n") } + assert_raise(ArgumentError) { Regexp.new("", "s") } + assert_raise(ArgumentError) { Regexp.new("", "u") } + assert_raise(ArgumentError) { Regexp.new("", "o") } + assert_raise(ArgumentError) { Regexp.new("", "j") } + assert_raise(ArgumentError) { Regexp.new("", "xmen") } + end + + def test_match_control_meta_escape + assert_equal(0, /\c\xFF/ =~ "\c\xFF") + assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF") + 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) @@ -515,6 +849,8 @@ class TestRegexp < Test::Unit::TestCase s = ".........." 5.times { s.sub!(".", "") } assert_equal(".....", s) + + assert_equal("\\\u{3042}", Regexp.new("\\\u{3042}").source) end def test_equal @@ -533,7 +869,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] } @@ -609,7 +948,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 @@ -633,21 +972,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 @@ -663,11 +991,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) } @@ -709,11 +1042,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 @@ -746,7 +1081,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") @@ -790,8 +1125,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"]) @@ -920,13 +1255,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") @@ -937,24 +1272,31 @@ class TestRegexp < Test::Unit::TestCase end def test_cclass_R - assert_match /\A\R\z/, "\r" - assert_match /\A\R\z/, "\n" - assert_match /\A\R\z/, "\r\n" + assert_match(/\A\R\z/, "\r") + assert_match(/\A\R\z/, "\n") + assert_match(/\A\R\z/, "\f") + assert_match(/\A\R\z/, "\v") + assert_match(/\A\R\z/, "\r\n") + assert_match(/\A\R\z/, "\u0085") + assert_match(/\A\R\z/, "\u2028") + assert_match(/\A\R\z/, "\u2029") end def test_cclass_X - assert_match /\A\X\z/, "\u{20 200d}" - assert_match /\A\X\z/, "\u{600 600}" - assert_match /\A\X\z/, "\u{600 20}" - assert_match /\A\X\z/, "\u{261d 1F3FB}" - assert_match /\A\X\z/, "\u{1f600}" - assert_match /\A\X\z/, "\u{20 308}" - assert_match /\A\X\X\z/, "\u{a 308}" - assert_match /\A\X\X\z/, "\u{d 308}" - assert_match /\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}" - assert_match /\A\X\z/, "\u{1F468 200D 1F393}" - assert_match /\A\X\z/, "\u{1F46F 200D 2642 FE0F}" - assert_match /\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}" + assert_match(/\A\X\z/, "\u{20 200d}") + assert_match(/\A\X\z/, "\u{600 600}") + assert_match(/\A\X\z/, "\u{600 20}") + assert_match(/\A\X\z/, "\u{261d 1F3FB}") + assert_match(/\A\X\z/, "\u{1f600}") + assert_match(/\A\X\z/, "\u{20 324}") + assert_match(/\A\X\X\z/, "\u{a 324}") + assert_match(/\A\X\X\z/, "\u{d 324}") + assert_match(/\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}") + assert_match(/\A\X\z/, "\u{1F468 200D 1F393}") + assert_match(/\A\X\z/, "\u{1F46F 200D 2642 FE0F}") + assert_match(/\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}") + + assert_warning('') {/\X/ =~ "\u{a0}"} end def test_backward @@ -982,6 +1324,8 @@ class TestRegexp < Test::Unit::TestCase assert_raise(TypeError) { Regexp.allocate.names } assert_raise(TypeError) { Regexp.allocate.named_captures } + assert_not_respond_to(MatchData, :allocate) +=begin assert_raise(TypeError) { MatchData.allocate.hash } assert_raise(TypeError) { MatchData.allocate.regexp } assert_raise(TypeError) { MatchData.allocate.names } @@ -1004,6 +1348,7 @@ class TestRegexp < Test::Unit::TestCase assert_raise(TypeError) { $` } assert_raise(TypeError) { $' } assert_raise(TypeError) { $+ } +=end end def test_unicode @@ -1060,6 +1405,14 @@ class TestRegexp < Test::Unit::TestCase assert_no_match(/^\p{age=3.0}$/u, "\u2754") assert_no_match(/^\p{age=2.0}$/u, "\u2754") assert_no_match(/^\p{age=1.1}$/u, "\u2754") + + assert_no_match(/^\p{age=12.0}$/u, "\u32FF") + assert_match(/^\p{age=12.1}$/u, "\u32FF") + assert_no_match(/^\p{age=13.0}$/u, "\u{10570}") + assert_match(/^\p{age=14.0}$/u, "\u{10570}") + assert_match(/^\p{age=14.0}$/u, "\u9FFF") + assert_match(/^\p{age=14.0}$/u, "\u{2A6DF}") + assert_match(/^\p{age=14.0}$/u, "\u{2B738}") end MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end") @@ -1070,7 +1423,9 @@ class TestRegexp < Test::Unit::TestCase assert_equal(a, b, '[ruby-core:24748]') h = {a => 42} assert_equal(42, h[b], '[ruby-core:24748]') +=begin assert_match(/#<TestRegexp::MatchData_\u{3042}:/, MatchData_A.allocate.inspect) +=end h = /^(?<@time>\d+): (?<body>.*)/.match("123456: hoge fuga") assert_equal("123456", h["@time"]) @@ -1078,13 +1433,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 @@ -1106,6 +1465,8 @@ class TestRegexp < Test::Unit::TestCase bug8151 = '[ruby-core:53649]' assert_warning(/\A\z/, bug8151) { Regexp.new('(?:[\u{33}])').to_s } + + assert_warning(%r[/.*/\Z]) { Regexp.new("[\n\n]") } end def test_property_warn @@ -1122,7 +1483,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 @@ -1158,6 +1520,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)) @@ -1232,6 +1608,41 @@ class TestRegexp < Test::Unit::TestCase end; end + def test_absent + assert_equal(0, /(?~(a|c)c)/ =~ "abb") + assert_equal("abb", $&) + + assert_equal(0, /\/\*((?~\*\/))\*\// =~ "/*abc*def/xyz*/ /* */") + assert_equal("abc*def/xyz", $1) + + assert_equal(0, /(?~(a)c)/ =~ "abb") + assert_nil($1) + + assert_equal(0, /(?~(a))/ =~ "") + 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_bug18631 + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaab") + end + + def test_invalid_group + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_raise_with_message(RegexpError, /invalid conditional pattern/) do + Regexp.new("((?(1)x|x|)x)+") + end + end; + end + # This assertion is for porting x2() tests in testpy.py of Onigmo. def assert_match_at(re, str, positions, msg = nil) re = Regexp.new(re) unless re.is_a?(Regexp) @@ -1261,4 +1672,170 @@ class TestRegexp < Test::Unit::TestCase } assert_empty(errs, msg) end + + def test_s_timeout + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect } + begin; + Regexp.timeout = timeout + assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON) + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + # A typical ReDoS case + /^(a*)*\1$/ =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_in_delta(timeout, t, timeout / 2) + end; + end + + def test_s_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(Regexp.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + Regexp.timeout = Float::INFINITY + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + Regexp.timeout = 1e300 + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + assert_raise(ArgumentError) { Regexp.timeout = 0 } + assert_raise(ArgumentError) { Regexp.timeout = -1 } + + Regexp.timeout = nil + assert_nil(Regexp.timeout) + end; + end + + def test_bug_20453 + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + Regexp.timeout = 0.001 + + assert_raise(Regexp::TimeoutError) do + /^(a*)x$/ =~ "a" * 1000000 + "x" + end + end; + end + + def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect } + per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect } + expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect } + begin; + Regexp.timeout = global_timeout + + re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout) + if per_instance_timeout + assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON) + else + assert_nil(re.timeout) + end + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + re =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4) + end; + end + + def test_timeout_shorter_than_global + per_instance_redos_test(10, 0.2, 0.2) + end + + def test_timeout_longer_than_global + per_instance_redos_test(0.01, 0.5, 0.5) + end + + def test_timeout_nil + per_instance_redos_test(0.5, nil, 0.5) + end + + def test_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(//.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout) + + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout) + + assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) } + assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) } + end; + end + + def test_cache_optimization_exponential + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + + assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_cache_optimization_square + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + + assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_cache_index_initialize + str = 'test1-test2-test3-test4-test_5' + re = '^([0-9a-zA-Z\-/]*){1,256}$' + 100.times do + assert !Regexp.new(re).match?(str) + end + end + + def test_bug_19273 # [Bug #19273] + pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/ + assert_equal("10:0:0".match(pattern)[0], "10:0:0") + end + + def test_bug_19467 + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + + assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y") + end; + end + + def test_bug_19476 # [Bug #19476] + assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789") + assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789") + end + + def test_linear_time_p + assert_send [Regexp, :linear_time?, /a/] + assert_send [Regexp, :linear_time?, 'a'] + assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE] + assert_not_send [Regexp, :linear_time?, /(a)\1/] + assert_not_send [Regexp, :linear_time?, "(a)\\1"] + + assert_raise(TypeError) {Regexp.linear_time?(nil)} + assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)} + end end diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 28cf686a26..1b93a42e42 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -6,11 +6,27 @@ require 'tmpdir' class TestRequire < Test::Unit::TestCase def test_load_error_path - filename = "should_not_exist" - error = assert_raise(LoadError) do - require filename - end - assert_equal filename, error.path + Tempfile.create(["should_not_exist", ".rb"]) {|t| + filename = t.path + t.close + File.unlink(filename) + + error = assert_raise(LoadError) do + require filename + end + assert_equal filename, error.path + + # with --disable=gems + assert_separately(["-", filename], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + filename = ARGV[0] + path = Struct.new(:to_path).new(filename) + error = assert_raise(LoadError) do + require path + end + assert_equal filename, error.path + end; + } end def test_require_invalid_shared_object @@ -52,7 +68,8 @@ class TestRequire < Test::Unit::TestCase def test_require_nonascii bug3758 = '[ruby-core:31915]' ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path| - assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path} + e = assert_raise(LoadError, bug3758) {require path} + assert_operator(e.message, :end_with?, path, bug3758) end end @@ -62,12 +79,6 @@ class TestRequire < Test::Unit::TestCase assert_require_nonascii_path(encoding, bug8165) end - def test_require_insecure_path - assert_require_insecure_path("foo") - encoding = 'filesystem' - assert_require_insecure_path(nil, encoding) - end - def test_require_nonascii_path_utf8 bug8676 = '[ruby-core:56136] [Bug #8676]' encoding = Encoding::UTF_8 @@ -75,12 +86,6 @@ class TestRequire < Test::Unit::TestCase assert_require_nonascii_path(encoding, bug8676) end - def test_require_insecure_path_utf8 - encoding = Encoding::UTF_8 - return if Encoding.find('filesystem') == encoding - assert_require_insecure_path(nil, encoding) - end - def test_require_nonascii_path_shift_jis bug8676 = '[ruby-core:56136] [Bug #8676]' encoding = Encoding::Shift_JIS @@ -88,12 +93,6 @@ class TestRequire < Test::Unit::TestCase assert_require_nonascii_path(encoding, bug8676) end - def test_require_insecure_path_shift_jis - encoding = Encoding::Shift_JIS - return if Encoding.find('filesystem') == encoding - assert_require_insecure_path(nil, encoding) - end - case RUBY_PLATFORM when /cygwin/, /mswin/, /mingw/, /darwin/ def self.ospath_encoding(path) @@ -105,22 +104,13 @@ class TestRequire < Test::Unit::TestCase end end - SECURITY_WARNING = - if /mswin|mingw/ =~ RUBY_PLATFORM - nil - else - proc do |require_path| - $SAFE = 1 - require(require_path) - end - 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) rescue - skip "cannot convert path encoding to #{encoding}" + omit "cannot convert path encoding to #{encoding}" end Dir.mkdir(File.dirname(require_path)) open(require_path, "wb") {|f| f.puts '$:.push __FILE__'} @@ -151,31 +141,6 @@ class TestRequire < Test::Unit::TestCase } end - def assert_require_insecure_path(dirname, encoding = nil) - return unless SECURITY_WARNING - dirname ||= "\u3042" * 5 - encoding ||= dirname.encoding - prepare_require_path(dirname, encoding) {|require_path| - require_path.untaint - require(require_path) - $".pop - File.chmod(0777, File.dirname(require_path)) - ospath = (require_path.encode('filesystem') rescue - require_path.encode(self.class.ospath_encoding(require_path))) - e = nil - stderr = EnvUtil.verbose_warning do - e = assert_raise(SecurityError) do - SECURITY_WARNING.call(require_path) - end - end - assert_include(e.message, "loading from unsafe path") - assert_include(stderr, "Insecure world writable dir") - require_path = require_path.encode(self.class.ospath_encoding(require_path)) - assert_include(e.message, require_path) - assert_include(stderr, File.dirname(require_path)) - } - end - def test_require_path_home_1 env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m @@ -227,7 +192,7 @@ class TestRequire < Test::Unit::TestCase t.close path = File.expand_path(t.path).sub(/\A(\w):/, '//127.0.0.1/\1$') - skip "local drive #$1: is not shared" unless File.exist?(path) + omit "local drive #$1: is not shared" unless File.exist?(path) args = ['--disable-gems', "-I#{File.dirname(path)}"] assert_in_out_err(args, "#{<<~"END;"}", [path], []) begin @@ -252,21 +217,30 @@ 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") - e = assert_raise_with_message(SyntaxError, /unterminated/) { + File.write(req, ",\n") + e = assert_raise_with_message(SyntaxError, /unexpected/) { yield req } - assert_not_nil(bt = e.backtrace) - assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}) + 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 @@ -382,6 +356,39 @@ class TestRequire < Test::Unit::TestCase } end + def test_require_in_wrapped_load + Dir.mktmpdir do |tmp| + File.write("#{tmp}/1.rb", "require_relative '2'\n") + File.write("#{tmp}/2.rb", "class Foo\n""end\n") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + path = ""#{tmp.dump}"/1.rb" + begin; + load path, true + assert_instance_of(Class, Foo) + end; + end + end + + def test_public_in_wrapped_load + Tempfile.create(["test_public_in_wrapped_load", ".rb"]) do |t| + t.puts "def foo; end", "public :foo" + t.close + assert_warning(/main\.public/) do + assert load(t.path, true) + end + end + end + + def test_private_in_wrapped_load + Tempfile.create(["test_private_in_wrapped_load", ".rb"]) do |t| + t.puts "def foo; end", "private :foo" + t.close + assert_warning(/main\.private/) do + assert load(t.path, true) + end + end + end + def test_load_scope bug1982 = '[ruby-core:25039] [Bug #1982]' Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| @@ -397,19 +404,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) { @@ -419,40 +458,10 @@ class TestRequire < Test::Unit::TestCase end end - def test_tainted_loadpath - Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| - abs_dir, file = File.split(t.path) - abs_dir = File.expand_path(abs_dir).untaint - - assert_separately([], <<-INPUT) - abs_dir = "#{ abs_dir }" - $: << abs_dir - assert_nothing_raised {require "#{ file }"} - INPUT - - assert_separately([], <<-INPUT) - abs_dir = "#{ abs_dir }" - $: << abs_dir.taint - assert_nothing_raised {require "#{ file }"} - INPUT - - assert_separately([], <<-INPUT) - abs_dir = "#{ abs_dir }" - $: << abs_dir.taint - $SAFE = 1 - assert_raise(SecurityError) {require "#{ file }"} - INPUT - - assert_separately([], <<-INPUT) - abs_dir = "#{ abs_dir }" - $: << abs_dir << 'elsewhere'.taint - assert_nothing_raised {require "#{ file }"} - INPUT - } - end - def test_relative load_path = $:.dup + loaded_featrures = $LOADED_FEATURES.dup + $:.delete(".") Dir.mktmpdir do |tmp| Dir.chdir(tmp) do @@ -472,6 +481,7 @@ class TestRequire < Test::Unit::TestCase end ensure $:.replace(load_path) if load_path + $LOADED_FEATURES.replace loaded_featrures end def test_relative_symlink @@ -487,7 +497,33 @@ class TestRequire < Test::Unit::TestCase result = IO.popen([EnvUtil.rubybin, "b/tst.rb"], &:read) assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]") rescue NotImplementedError, Errno::EACCES - skip "File.symlink is not implemented" + omit "File.symlink is not implemented" + end + } + } + 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 + omit "File.symlink is not implemented" end } } @@ -537,7 +573,7 @@ class TestRequire < Test::Unit::TestCase t1 = Thread.new do Thread.pass until start begin - require(path) + Kernel.send(:require, path) rescue RuntimeError end @@ -546,7 +582,7 @@ class TestRequire < Test::Unit::TestCase t2 = Thread.new do Thread.pass until scratch[0] - t2_res = require(path) + t2_res = Kernel.send(:require, path) end t1[:scratch] = t2[:scratch] = scratch @@ -563,9 +599,6 @@ class TestRequire < Test::Unit::TestCase assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}") assert_equal([:pre, :post], scratch, bug5754) - - assert_match(/circular require/, output) - assert_match(/in #{__method__}'$/o, output) } ensure $VERBOSE = verbose @@ -590,6 +623,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| @@ -770,8 +825,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 @@ -803,6 +858,8 @@ class TestRequire < Test::Unit::TestCase end if File.respond_to?(:mkfifo) def test_loading_fifo_threading_success + omit "[Bug #18613]" if /freebsd/=~ RUBY_PLATFORM + Tempfile.create(%w'fifo .rb') {|f| f.close File.unlink(f.path) @@ -829,6 +886,8 @@ class TestRequire < Test::Unit::TestCase end if File.respond_to?(:mkfifo) def test_loading_fifo_fd_leak + omit if RUBY_PLATFORM =~ /android/ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/android29-x86_64/ruby-master/log/20200419T124100Z.fail.html.gz + Tempfile.create(%w'fifo .rb') {|f| f.close File.unlink(f.path) @@ -888,11 +947,67 @@ class TestRequire < Test::Unit::TestCase begin File.symlink "real", File.join(tmp, "symlink") rescue NotImplementedError, Errno::EACCES - skip "File.symlink is not implemented" + omit "File.symlink is not implemented" end - File.write(File.join(tmp, "real/a.rb"), "print __FILE__") - result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'a.rb'"], &:read) - assert_operator(result, :end_with?, "/real/a.rb") + File.write(File.join(tmp, "real/test_symlink_load_path.rb"), "print __FILE__") + result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'test_symlink_load_path.rb'"], &:read) + assert_operator(result, :end_with?, "/real/test_symlink_load_path.rb") } 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 + Dir.mktmpdir do |tmp| + Tempfile.create(%w[feature .rb], tmp) do |file| + file.close + path = File.realpath(file.path) + dir, base = File.split(path) + $:.unshift(dir) + assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base)) + $".push(path) + assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base)) + end + end + ensure + $:.replace(paths) + $".replace(loaded) + end + + def test_resolve_feature_path_with_missing_feature + assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso")) + end + end + + def test_require_with_public_method_missing + # [Bug #19793] + assert_separately(["-W0", "--disable-gems", "-rtempfile"], __FILE__, __LINE__, <<~RUBY) + GC.stress = true + + class Object + public :method_missing + end + + Tempfile.create(["empty", ".rb"]) do |file| + require file.path + end + RUBY + end end diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb new file mode 100644 index 0000000000..95fa3f29e1 --- /dev/null +++ b/test/ruby/test_require_lib.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRequireLib < Test::Unit::TestCase + TEST_RATIO = ENV["TEST_REQUIRE_THREAD_RATIO"]&.tap {|s|break s.to_f} || 0.05 # testing all files needs too long time... + + Dir.glob(File.expand_path('../../lib/**/*.rb', __dir__)).each do |lib| + # skip some problems + next if %r!/lib/(?:bundler|rubygems)\b! =~ lib + next if %r!/lib/(?:debug|mkmf)\.rb\z! =~ 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 + assert_separately(['--disable-gems', '-W0'], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + n = Thread.list.size + begin + require #{lib.dump} + rescue Exception + omit $! + end + assert_equal n, Thread.list.size + end; + end + end +end diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 8cb74edb32..b8e22b7c7d 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1,10 +1,24 @@ # -*- coding: us-ascii -*- require 'test/unit' +require 'timeout' require 'tmpdir' require 'tempfile' +require_relative '../lib/jit_support' class TestRubyOptions < Test::Unit::TestCase + def self.mjit_enabled? = defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + def self.yjit_enabled? = defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + + NO_JIT_DESCRIPTION = + if mjit_enabled? + RUBY_DESCRIPTION.sub(/\+MJIT /, '') + elsif yjit_enabled? + RUBY_DESCRIPTION.sub(/\+YJIT( (dev|dev_nodebug|stats))? /, '') + else + RUBY_DESCRIPTION + end + def write_file(filename, content) File.open(filename, "w") {|f| f << content @@ -26,7 +40,7 @@ class TestRubyOptions < Test::Unit::TestCase def test_usage assert_in_out_err(%w(-h)) do |r, e| - assert_operator(r.size, :<=, 24) + assert_operator(r.size, :<=, 25) longer = r[1..-1].select {|x| x.size > 80} assert_equal([], longer) assert_equal([], e) @@ -57,32 +71,59 @@ 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 - def test_safe_level - assert_in_out_err(%w(-T -e) + [""], "", [], - /no -e allowed in tainted mode \(SecurityError\)/) - - assert_in_out_err(%w(-T4 -S foo.rb), "", [], - /no -S allowed in tainted mode \(SecurityError\)/) - end - def test_debug assert_in_out_err(["--disable-gems", "-de", "p $DEBUG"], "", %w(true), []) assert_in_out_err(["--disable-gems", "--debug", "-e", "p $DEBUG"], "", %w(true), []) + + assert_in_out_err(["--disable-gems", "--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/) end q = Regexp.method(:quote) @@ -96,10 +137,25 @@ class TestRubyOptions < Test::Unit::TestCase end private_constant :VERSION_PATTERN + VERSION_PATTERN_WITH_JIT = + case RUBY_ENGINE + when 'ruby' + /^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]) - assert_equal(RUBY_DESCRIPTION, r[0]) + if self.class.mjit_enabled? && !JITSupport.mjit_force_enabled? + 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]) + end assert_equal([], e) end @@ -116,9 +172,11 @@ class TestRubyOptions < Test::Unit::TestCase end def test_enable - assert_in_out_err(%w(--enable all -e) + [""], "", [], []) - assert_in_out_err(%w(--enable-all -e) + [""], "", [], []) - assert_in_out_err(%w(--enable=all -e) + [""], "", [], []) + if JITSupport.supported? + assert_in_out_err(%w(--enable all -e) + [""], "", [], []) + assert_in_out_err(%w(--enable-all -e) + [""], "", [], []) + assert_in_out_err(%w(--enable=all -e) + [""], "", [], []) + end assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [], /unknown argument for --enable: `foobarbazqux'/) assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/) @@ -133,11 +191,12 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%w(--disable), "", [], /missing argument for --disable/) assert_in_out_err(%w(--disable-gems -e) + ['p defined? Gem'], "", ["nil"], []) assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], []) + assert_in_out_err(%w(--disable-gems -e) + ['p defined? DidYouMean'], "", ["nil"], []) end def test_kanji assert_in_out_err(%w(-KU), "p '\u3042'") do |r, e| - assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8)) + assert_equal("\"\u3042\"", r.join('').force_encoding(Encoding::UTF_8)) end line = '-eputs"\xc2\xa1".encoding' env = {'RUBYOPT' => nil} @@ -153,11 +212,61 @@ 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]) - assert_equal(RUBY_DESCRIPTION, r[0]) + if ENV['RUBY_YJIT_ENABLE'] == '1' + assert_equal(NO_JIT_DESCRIPTION, r[0]) + elsif self.class.mjit_enabled? || self.class.yjit_enabled? # checking -D(M|Y)JIT_FORCE_ENABLE + assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) + else + assert_equal(RUBY_DESCRIPTION, r[0]) + end assert_equal([], e) end + + return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' + return if yjit_force_enabled? + + [ + %w(--version --mjit --disable=mjit), + %w(--version --enable=mjit --disable=mjit), + %w(--version --enable-mjit --disable-mjit), + *([ + %w(--version --jit --disable=jit), + %w(--version --enable=jit --disable=jit), + %w(--version --enable-jit --disable-jit), + ] unless JITSupport.yjit_supported?), + ].each do |args| + assert_in_out_err([env] + args) do |r, e| + assert_match(VERSION_PATTERN, r[0]) + assert_match(NO_JIT_DESCRIPTION, r[0]) + assert_equal([], e) + end + end + + if JITSupport.supported? + [ + %w(--version --mjit), + %w(--version --enable=mjit), + %w(--version --enable-mjit), + *([ + %w(--version --jit), + %w(--version --enable=jit), + %w(--version --enable-jit), + ] unless JITSupport.yjit_supported?), + ].each do |args| + assert_in_out_err([env] + args) do |r, e| + assert_match(VERSION_PATTERN_WITH_JIT, r[0]) + if JITSupport.mjit_force_enabled? + assert_equal(RUBY_DESCRIPTION, r[0]) + else + assert_equal(EnvUtil.invoke_ruby([env, '--mjit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) + end + assert_equal([], e) + end + end + end end def test_eval @@ -192,20 +301,32 @@ class TestRubyOptions < Test::Unit::TestCase end def test_autosplit - assert_in_out_err(%w(-an -F: -e) + ["p $F"], "foo:bar:baz\nqux:quux:quuux\n", + assert_in_out_err(%w(-W0 -an -F: -e) + ["p $F"], "foo:bar:baz\nqux:quux:quuux\n", ['["foo", "bar", "baz\n"]', '["qux", "quux", "quuux\n"]'], []) end def test_chdir + omit "not working on MinGW" if /mingw/ =~ RUBY_PLATFORM assert_in_out_err(%w(-C), "", [], /Can't chdir/) assert_in_out_err(%w(-C test_ruby_test_rubyoptions_foobarbazqux), "", [], /Can't chdir/) d = Dir.tmpdir assert_in_out_err(["-C", d, "-e", "puts Dir.pwd"]) do |r, e| - assert_file.identical?(r.join, d) + assert_file.identical?(r.join(''), d) assert_equal([], e) end + + Dir.mktmpdir(d) do |base| + # "test" in Japanese and N'Ko + test = base + "/\u{30c6 30b9 30c8}_\u{7e1 7ca 7dd 7cc 7df 7cd 7eb}" + Dir.mkdir(test) + assert_in_out_err(["-C", base, "-C", File.basename(test), "-e", "puts Dir.pwd"]) do |r, e| + assert_file.identical?(r.join(''), test) + assert_equal([], e) + end + Dir.rmdir(test) + end end def test_yydebug @@ -226,8 +347,8 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%w(--encoding test_ruby_test_rubyoptions_foobarbazqux), "", [], /unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/) - if /mswin|mingw|aix/ =~ RUBY_PLATFORM && - (str = "\u3042".force_encoding(Encoding.find("locale"))).valid_encoding? + if /mswin|mingw|aix|android/ =~ RUBY_PLATFORM && + (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, @@ -249,9 +370,9 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%W(-\r -e) + [""], "", [], []) - assert_in_out_err(%W(-\rx), "", [], /invalid option -\\x0D \(-h will show valid options\) \(RuntimeError\)/) + assert_in_out_err(%W(-\rx), "", [], /invalid option -[\r\n] \(-h will show valid options\) \(RuntimeError\)/) - assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/) + assert_in_out_err(%W(-\x01), "", [], /invalid option -\x01 \(-h will show valid options\) \(RuntimeError\)/) assert_in_out_err(%w(-Z), "", [], /invalid option -Z \(-h will show valid options\) \(RuntimeError\)/) end @@ -265,12 +386,6 @@ class TestRubyOptions < Test::Unit::TestCase ENV['RUBYOPT'] = '-e "p 1"' assert_in_out_err([], "", [], /invalid switch in RUBYOPT: -e \(RuntimeError\)/) - ENV['RUBYOPT'] = '-T1' - assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) - - ENV['RUBYOPT'] = '-T4' - assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) - ENV['RUBYOPT'] = '-Eus-ascii -KN' assert_in_out_err(%w(-Eutf-8 -KU), "p '\u3042'") do |r, e| assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8)) @@ -281,6 +396,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 @@ -344,6 +473,9 @@ class TestRubyOptions < Test::Unit::TestCase %w[4], [], bug4118) assert_ruby_status(%w[], "#! ruby -- /", '[ruby-core:82267] [Bug #13786]') + + assert_ruby_status(%w[], "#!") + assert_in_out_err(%w[-c], "#!", ["Syntax OK"]) end def test_flag_in_shebang @@ -383,7 +515,7 @@ class TestRubyOptions < Test::Unit::TestCase t.puts " end" t.puts "end" t.flush - warning = ' warning: found = in conditional, should be ==' + warning = ' warning: found `= literal\' in conditional, should be ==' err = ["#{t.path}:1:#{warning}", "#{t.path}:4:#{warning}", ] @@ -420,13 +552,27 @@ class TestRubyOptions < Test::Unit::TestCase "begin", "if false", "for _ in []", "while false", "def foo", "class X", "module M", ["-> do", "end"], ["-> {", "}"], + ["if false;", "else ; end"], + ["if false;", "elsif false ; end"], + ["begin", "rescue ; end"], + ["begin rescue", "else ; end"], + ["begin", "ensure ; end"], + [" case nil", "when true; end"], + ["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'| - src = ["#{b}\n", " #{e}\n"] - k = b[/\A\S+/] - - a.for("no directives with #{b}") do - err = ["#{t.path}:2: warning: mismatched indentations at '#{e}' with '#{k}' at 1"] + |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 = 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 #{n1}"] t.rewind t.truncate(0) t.puts src @@ -435,7 +581,7 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err) end - a.for("false directive with #{b}") do + a.for("false directive with #{src}") do t.rewind t.truncate(0) t.puts "# -*- warn-indent: false -*-" @@ -444,8 +590,8 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]') end - a.for("false and true directives with #{b}") do - err = ["#{t.path}:4: warning: mismatched indentations at '#{e}' with '#{k}' at 3"] + a.for("false and true directives with #{src}") do + 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 -*-" @@ -455,7 +601,7 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(["-w", t.path], "", [], err, '[ruby-core:25442]') end - a.for("false directives after #{b}") do + a.for("false directives after #{src}") do t.rewind t.truncate(0) t.puts "# -*- warn-indent: true -*-" @@ -466,8 +612,8 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]') end - a.for("BOM with #{b}") do - err = ["#{t.path}:2: warning: mismatched indentations at '#{e}' with '#{k}' at 1"] + a.for("BOM with #{src}") do + err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"] t.rewind t.truncate(0) t.print "\u{feff}" @@ -539,30 +685,34 @@ class TestRubyOptions < Test::Unit::TestCase if /linux|freebsd|netbsd|openbsd|darwin/ =~ RUBY_PLATFORM PSCMD = EnvUtil.find_executable("ps", "-o", "command", "-p", $$.to_s) {|out| /ruby/=~out} - PSCMD.pop if PSCMD + PSCMD&.pop end def test_set_program_name - skip "platform dependent feature" unless defined?(PSCMD) and PSCMD + omit "platform dependent feature" unless defined?(PSCMD) and PSCMD with_tmpchdir do write_file("test-script", "$0 = 'hello world'; /test-script/ =~ Process.argv0 or $0 = 'Process.argv0 changed!'; sleep 60") pid = spawn(EnvUtil.rubybin, "test-script") ps = nil - 10.times do + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 + begin sleep 0.1 ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps - end + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop assert_match(/hello world/, ps) + assert_operator now, :<, stop Process.kill :KILL, pid - Process.wait(pid) + EnvUtil.timeout(5) { Process.wait(pid) } end end def test_setproctitle - skip "platform dependent feature" unless defined?(PSCMD) and PSCMD + omit "platform dependent feature" unless defined?(PSCMD) and PSCMD assert_separately([], "#{<<-"{#"}\n#{<<-'};'}") {# @@ -576,24 +726,25 @@ class TestRubyOptions < Test::Unit::TestCase pid = spawn(EnvUtil.rubybin, "test-script") ps = nil - 10.times do + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 + begin sleep 0.1 ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps - end + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop assert_match(/hello world/, ps) + assert_operator now, :<, stop Process.kill :KILL, pid - Process.wait(pid) + Timeout.timeout(5) { Process.wait(pid) } end end module SEGVTest opts = {} - if /mswin|mingw/ =~ RUBY_PLATFORM - additional = /[\s\w\.\']*/ - else + unless /mswin|mingw/ =~ RUBY_PLATFORM opts[:rlimit_core] = 0 - additional = nil end ExecOptions = opts.freeze @@ -602,45 +753,49 @@ class TestRubyOptions < Test::Unit::TestCase -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n )x, %r( - #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n + #{ Regexp.quote((TestRubyOptions.mjit_enabled? && !JITSupport.mjit_force_enabled?) ? NO_JIT_DESCRIPTION : RUBY_DESCRIPTION) }\n\n )x, %r( (?:--\s(?:.+\n)*\n)? --\sControl\sframe\sinformation\s-+\n - (?:c:.*\n)* + (?:(?:c:.*\n)|(?:^\s+.+\n))* + \n )x, %r( (?: --\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n - -e:1:in\s\`<main>\'\n + (?:-e:1:in\s\`(?:block\sin\s)?<main>\'\n)* -e:1:in\s\`kill\'\n + \n )? )x, %r( + (?:--\sMachine(?:.+\n)*\n)? + )x, + %r( (?: --\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n - (?:(?:.*\s)?\[0x\h+\]\n)*\n + (?:Un(?:expected|supported|known)\s.*\n)* + (?:(?:.*\s)?\[0x\h+\].*\n|.*:\d+\n)*\n )? )x, - :*, %r( - \[NOTE\]\n - You\smay\shave\sencountered\sa\sbug\sin\sthe\sRuby\sinterpreter\sor\sextension\slibraries.\n - Bug\sreports\sare\swelcome.\n - (?:.*\n)? - For\sdetails:\shttp:\/\/.*\.ruby-lang\.org/.*\n - \n - (?: - \[IMPORTANT\]\n - (?:.+\n)+ - \n + (?:--\sOther\sruntime\sinformation\s-+\n + (?:.*\n)* )? )x, ] - ExpectedStderrList << additional if additional end def assert_segv(args, message=nil) + pend "macOS 15 is not working with this assertion" if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion` + + omit if ENV['RUBY_ON_BUG'] + + # We want YJIT to be enabled in the subprocess if it's enabled for us + # so that the Ruby description matches. + args.unshift("--yjit") if self.class.yjit_enabled? + test_stdin = "" opt = SEGVTest::ExecOptions.dup list = SEGVTest::ExpectedStderrList @@ -702,20 +857,6 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(["-w", "-"], "eval('a=1')", [], [], feature7730) end - def test_shadowing_variable - bug4130 = '[ruby-dev:42718]' - assert_in_out_err(["-we", "def foo\n"" a=1\n"" 1.times do |a| end\n"" a\n""end"], - "", [], ["-e:3: warning: shadowing outer local variable - a"], bug4130) - assert_in_out_err(["-we", "def foo\n"" a=1\n"" 1.times do |a| end\n""end"], - "", [], - ["-e:3: warning: shadowing outer local variable - a", - "-e:2: warning: assigned but unused variable - a", - ], bug4130) - feature6693 = '[ruby-core:46160]' - assert_in_out_err(["-we", "def foo\n"" _a=1\n"" 1.times do |_a| end\n""end"], - "", [], [], feature6693) - end - def test_script_from_stdin begin require 'pty' @@ -733,12 +874,12 @@ class TestRubyOptions < Test::Unit::TestCase pid = spawn(EnvUtil.rubybin, :in => s, :out => w) w.close assert_nothing_raised('[ruby-dev:37798]') do - result = Timeout.timeout(3) {r.read} + result = EnvUtil.timeout(3) {r.read} end Process.wait pid } rescue RuntimeError - skip $! + omit $! end } assert_equal("", result, '[ruby-dev:37798]') @@ -773,11 +914,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 @@ -788,7 +929,7 @@ class TestRubyOptions < Test::Unit::TestCase name = c.chr(Encoding::UTF_8) expected = name.encode("locale") rescue nil } - skip "can't make locale name" + omit "can't make locale name" end name << ".rb" expected << ".rb" @@ -812,7 +953,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 @@ -833,6 +974,7 @@ class TestRubyOptions < Test::Unit::TestCase # Since the codepage is shared all processes per conhost.exe, do # not chcp, or parallel test may break. def test_locale_codepage + omit "not working on MinGW" if /mingw/ =~ RUBY_PLATFORM locale = Encoding.find("locale") list = %W"\u{c7} \u{452} \u{3066 3059 3068}" list.each do |s| @@ -874,8 +1016,7 @@ class TestRubyOptions < Test::Unit::TestCase stderr = [] Tempfile.create(%w"bug10435- .rb") do |script| dir, base = File.split(script.path) - script.puts "abort ':run'" - script.close + File.write(script, "abort ':run'\n") opts = ['-C', dir, '-r', "./#{base}", *opt] _, e = assert_in_out_err([*opts, '-ep'], "", //) stderr.concat(e) if e @@ -899,6 +1040,8 @@ class TestRubyOptions < Test::Unit::TestCase def test_dump_parsetree_with_rflag assert_norun_with_rflag('--dump=parsetree') assert_norun_with_rflag('--dump=parsetree', '-e', '#frozen-string-literal: true') + assert_norun_with_rflag('--dump=parsetree+error_tolerant') + assert_norun_with_rflag('--dump=parse+error_tolerant') end def test_dump_insns_with_rflag @@ -910,8 +1053,10 @@ class TestRubyOptions < Test::Unit::TestCase [["disable", "false"], ["enable", "true"]].each do |opt, exp| %W[frozen_string_literal frozen-string-literal].each do |arg| key = "#{opt}=#{arg}" + negopt = exp == "true" ? "disable" : "enable" + env = {"RUBYOPT"=>"--#{negopt}=#{arg}"} a.for(key) do - assert_in_out_err(["--disable=gems", "--#{key}"], 'p("foo".frozen?)', [exp]) + assert_in_out_err([env, "--disable=gems", "--#{key}"], 'p("foo".frozen?)', [exp]) end end end @@ -928,7 +1073,7 @@ class TestRubyOptions < Test::Unit::TestCase def test_frozen_string_literal_debug with_debug_pat = /created at/ - wo_debug_pat = /can\'t modify frozen String \(RuntimeError\)\n\z/ + wo_debug_pat = /can\'t modify frozen String: "\w+" \(FrozenError\)\n\z/ frozen = [ ["--enable-frozen-string-literal", true], ["--disable-frozen-string-literal", false], @@ -944,26 +1089,70 @@ class TestRubyOptions < Test::Unit::TestCase frozen.product(debugs) do |(opt1, freeze), (opt2, debug)| opt = opts + [opt1, opt2].compact err = !freeze ? [] : debug ? with_debug_pat : wo_debug_pat - assert_in_out_err(opt, '"foo" << "bar"', [], err) - if freeze - assert_in_out_err(opt, '"foo#{123}bar" << "bar"', [], err) + [ + ['"foo" << "bar"', err], + ['"foo#{123}bar" << "bar"', []], + ['+"foo#{123}bar" << "bar"', []], + ['-"foo#{123}bar" << "bar"', wo_debug_pat], + ].each do |code, expected| + assert_in_out_err(opt, code, [], expected, "#{opt} #{code}") end end end def test___dir__encoding + lang = {"LC_ALL"=>ENV["LC_ALL"]||ENV["LANG"]} with_tmpchdir do testdir = "\u30c6\u30b9\u30c8" Dir.mkdir(testdir) Dir.chdir(testdir) do open("test.rb", "w") do |f| f.puts <<-END - p __FILE__.encoding == __dir__.encoding + if __FILE__.encoding == __dir__.encoding + p true + else + puts "__FILE__: \#{__FILE__.encoding}, __dir__: \#{__dir__.encoding}" + end END end - r, = EnvUtil.invoke_ruby("test.rb", "", true) + r, = EnvUtil.invoke_ruby([lang, "test.rb"], "", true) assert_equal "true", r.chomp, "the encoding of __FILE__ and __dir__ should be same" end end end + + def test_cwd_encoding + with_tmpchdir do + testdir = "\u30c6\u30b9\u30c8" + Dir.mkdir(testdir) + Dir.chdir(testdir) do + File.write("a.rb", "require './b'") + File.write("b.rb", "puts 'ok'") + assert_ruby_status([{"RUBYLIB"=>"."}, *%w[-E cp932:utf-8 a.rb]]) + end + 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 + omit "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL) + assert_in_out_err([IO::NULL], success: true) + end + + def test_mjit_debug + if JITSupport.supported? + env = { 'MJIT_SEARCH_BUILD_DIR' => 'true' } + assert_in_out_err([env, "--disable-yjit", "--mjit-debug=-O0 -O1", "--mjit-verbose=2", "" ], "", [], /-O0 -O1/) + end + end + + private + + 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..d729aa5af8 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] RubyVM.stat(stat = {}) assert_not_empty stat - assert_equal stat[:global_method_state], RubyVM.stat(:global_method_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 + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + 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_mjit.rb new file mode 100644 index 0000000000..73f43af7f9 --- /dev/null +++ b/test/ruby/test_rubyvm_mjit.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true +require 'test/unit' +require_relative '../lib/jit_support' + +return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' + +class TestRubyVMMJIT < Test::Unit::TestCase + include JITSupport + + def setup + unless JITSupport.supported? + omit 'JIT seems not supported on this platform' + end + end + + def test_pause + out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false) + i = 0 + while i < 5 + eval("def mjit#{i}; end; mjit#{i}; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause + print RubyVM::MJIT.pause + while i < 10 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause # no JIT here + EOS + assert_equal('truefalsefalse', out) + assert_equal( + 5, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size, + "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```", + ) + end + + def test_pause_waits_until_compaction + out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false) + def a() end; a; a + def b() end; b; b + RubyVM::MJIT.pause + EOS + assert_equal( + 2, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size, + "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```", + ) + end + + def test_pause_after_waitall + out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false) + def test() = nil + test + test + Process.waitall + print RubyVM::MJIT.pause + EOS + assert_equal( + 1, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size, + "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```", + ) + end + + def test_pause_does_not_hang_on_full_units + out, _ = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, max_cache: 10, wait: false) + i = 0 + while i < 11 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause + EOS + assert_equal('true', out) + end + + def test_pause_wait_false + out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false) + i = 0 + while i < 10 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause(wait: false) + print RubyVM::MJIT.pause(wait: false) + EOS + assert_equal('truefalse', out) + if RUBY_PLATFORM.match?(/mswin|mingw/) # MJIT synchronously compiles methods on Windows + assert_equal(10, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size) + else + assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10) + end + end + + def test_resume + out, err = eval_with_jit(<<~'EOS', verbose: 1, call_threshold: 2, wait: false) + print RubyVM::MJIT.resume + print RubyVM::MJIT.pause + print RubyVM::MJIT.resume + print RubyVM::MJIT.resume + print RubyVM::MJIT.pause + EOS + assert_equal('falsetruetruefalsetrue', out) + assert_equal(0, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size) + end +end diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 74c092c460..0e77d22b03 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,65 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal([], events) end + def test_c_return_no_binding + binding = :none + TracePoint.new(:c_return){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_no_binding + binding = :none + TracePoint.new(:c_call){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_removed_method + # [Bug #19305] + klass = Class.new do + attr_writer :bar + alias_method :set_bar, :bar= + remove_method :bar= + end + + obj = klass.new + method_id = nil + parameters = nil + + TracePoint.new(:c_call) { |tp| + method_id = tp.method_id + parameters = tp.parameters + }.enable { + obj.set_bar(1) + } + + assert_equal(:bar=, method_id) + assert_equal([[:req]], parameters) + end + + def test_c_call_aliased_method + # [Bug #20915] + klass = Class.new do + alias_method :new_method, :method + end + + instance = klass.new + parameters = nil + + TracePoint.new(:c_call) do |tp| + parameters = tp.parameters + end.enable { instance.new_method(:to_s) } + + assert_equal([[:req]], parameters) + end + def test_call events = [] name = "#{self.class}\##{__method__}" @@ -104,6 +167,10 @@ class TestSetTraceFunc < Test::Unit::TestCase events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) + assert_equal(["c-call", 4, :const_added, Module], + events.shift) + assert_equal(["c-return", 4, :const_added, Module], + events.shift) assert_equal(["c-call", 4, :inherited, Class], events.shift) assert_equal(["c-return", 4, :inherited, Class], @@ -137,6 +204,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] @@ -239,8 +310,6 @@ class TestSetTraceFunc < Test::Unit::TestCase EOF assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) - assert_equal(["line", 4, __method__, self.class], - events.shift) assert_equal(["line", 5, __method__, self.class], events.shift) assert_equal(["c-call", 5, :raise, Kernel], @@ -289,8 +358,8 @@ class TestSetTraceFunc < Test::Unit::TestCase ["line", 4, __method__, self.class], ["c-return", 4, :any?, Array], ["line", 5, __method__, self.class], - ["c-call", 5, :set_trace_func, Kernel]].each{|e| - assert_equal(e, events.shift) + ["c-call", 5, :set_trace_func, Kernel]].each.with_index{|e, i| + assert_equal(e, events.shift, "mismatch on #{i}th trace") } end @@ -339,6 +408,8 @@ class TestSetTraceFunc < Test::Unit::TestCase [["c-return", 2, :add_trace_func, Thread], ["line", 3, __method__, self.class], + ["c-call", 3, :const_added, Module], + ["c-return", 3, :const_added, Module], ["c-call", 3, :inherited, Class], ["c-return", 3, :inherited, Class], ["class", 3, nil, nil], @@ -364,6 +435,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 @@ -382,7 +458,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) @@ -442,7 +518,7 @@ class TestSetTraceFunc < Test::Unit::TestCase begin eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' 1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread? - 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' + 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding&.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' 3: } 4: 1.times{|;_local_var| _local_var = :inner 5: tap{} @@ -470,31 +546,30 @@ class TestSetTraceFunc < Test::Unit::TestCase answer_events = [ # - [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace], [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], - [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], + [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, :nothing], [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], - [:c_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], + [:c_return, 4, "xyzzy", Integer, :times, 1, nil, 1], [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], - [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], - [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil], + [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing], + [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil], + [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing], + [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil], [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], - [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], - [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], - [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], - [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], - [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing], - [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing], - [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil], - [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy], [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], @@ -502,29 +577,83 @@ 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], - [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing], - [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing], - [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing], - [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc], - [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc], - [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil], - [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing], - [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil], + [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc], + [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], - [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing], - [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing], + [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true], [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], - [:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing], ] return events, answer_events end + def test_tracepoint + events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise) + + ms = [events1, answer_events].map{|evs| + evs.map{|e| + "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" + } + } + + if false # show all events + printf(" %-60s | %-60s\n", "actual", "expected") + ms[0].zip(ms[1]){|a, b| + printf("%s%-60s | %-60s\n", a==b ? ' ' : '!', a, b) + } + end + + mesg = ms[0].zip(ms[1]).map{|a, b| + if a != b + "actual: #{a} <-> expected: #{b}" + end + }.compact.join("\n") + + answer_events.zip(events1){|answer, event| + assert_equal answer, event, mesg + } + + [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event| + events1, answer_events = *trace_by_tracepoint(event) + answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line| + assert_equal answer_line, event_line + } + } + end + + # Bug #18264 + def test_tracepoint_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { TracePoint.new(:line) { } } +1_000.times(&code) +PREP +1_000_000.times(&code) +CODE + end + + def test_tracepoint_bmethod_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true) + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + tp = TracePoint.new(:return) {} + begin; + 1_000_000.times do + tp.enable(target: bmethod) {} + end + end; + end + def trace_by_set_trace_func events = [] trace = nil @@ -532,9 +661,12 @@ class TestSetTraceFunc < Test::Unit::TestCase xyzzy = nil xyzzy = xyzzy _local_var = :outer + method = :trace_by_set_trace_func + raised_exc = nil + eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' 1: set_trace_func(lambda{|event, file, line, id, binding, klass| - 2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy' + 2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy' 3: }) 4: 1.times{|;_local_var| _local_var = :inner 5: tap{} @@ -556,44 +688,77 @@ class TestSetTraceFunc < Test::Unit::TestCase 21: set_trace_func(nil) EOF self.class.class_eval{remove_const(:XYZZY)} - return events - end - def test_tracepoint - events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise) - - ms = [events1, answer_events].map{|evs| - evs.map{|e| - "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" - } - } - - mesg = ms[0].zip(ms[1]).map{|a, b| - if a != b - "#{a} <-> #{b}" - end - }.compact.join("\n") + answer_events = [ + # + [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, nil, nil], + [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, nil], + [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], + [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], + [:c_return, 4, "xyzzy", Integer, :times, 1, nil, nil], + [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, nil], + [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil], + [:c_call, 7, "xyzzy", Class, :const_added, Object, nil, nil], + [:c_return, 7, "xyzzy", Class, :const_added, Object, nil, nil], + [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, nil], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, nil], + [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing], + [: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], + [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], + [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], + [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, nil], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, nil], + [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], + [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, nil], + [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, nil], + [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 21, "xyzzy", TracePoint, :disable, trace, nil, nil], + ] + return events, answer_events + end - answer_events.zip(events1){|answer, event| - assert_equal answer, event, mesg - } + def test_set_trace_func_curry_argument_error + b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) }.curry[1, 2] + set_trace_func(proc {}) + assert_raise(ArgumentError) {b[3, 4]} + end - events2 = trace_by_set_trace_func - events1.zip(events2){|ev1, ev2| - ev2[0] = ev2[0].sub('-', '_').to_sym - assert_equal ev1[0..2], ev2[0..2], ev1.inspect + def test_set_trace_func + actual_events, expected_events = trace_by_set_trace_func + expected_events.zip(actual_events){|e, a| + a[0] = a[0].to_s.sub('-', '_').to_sym + assert_equal e[0..2], a[0..2], a.inspect # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var") - assert_equal ev1[3].nil?, ev2[3].nil? # klass - assert_equal ev1[4].nil?, ev2[4].nil? # id - assert_equal ev1[6], ev2[6] # _local_var - } - - [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event| - events1, answer_events = *trace_by_tracepoint(event) - answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line| - assert_equal answer_line, event_line - } + assert_equal e[3].nil?, a[3].nil? # klass + assert_equal e[4].nil?, a[4].nil? # id + assert_equal e[6], a[6] # _local_var } end @@ -640,25 +805,30 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_tracepoint_enable ary = [] args = nil - trace = TracePoint.new(:call){|tp| - next if !target_thread? - ary << tp.method_id - } - foo - trace.enable{|*a| - args = a + begin + trace = TracePoint.new(:call){|tp| + next if !target_thread? + ary << tp.method_id + } foo - } - foo - assert_equal([:foo], ary) - assert_equal([], args) + trace.enable(target_thread: nil){|*a| + args = a + foo + } + foo + assert_equal([:foo], ary) + assert_equal([], args) + ensure + trace&.disable + end trace = TracePoint.new{} begin assert_equal(false, trace.enable) assert_equal(true, trace.enable) - trace.enable{} - assert_equal(true, trace.enable) + trace.enable(target_thread: nil){} + trace.disable + assert_equal(false, trace.enable) ensure trace.disable end @@ -678,7 +848,7 @@ class TestSetTraceFunc < Test::Unit::TestCase } foo trace.disable - assert_equal([:foo, :foo], ary) + assert_equal([:foo, :disable, :foo, :disable], ary) assert_equal([], args) trace = TracePoint.new{} @@ -705,6 +875,45 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal(false, trace.enabled?) end + def parameter_test(a, b, c) + yield + end + + def test_tracepoint_parameters + trace = TracePoint.new(:line, :class, :end, :call, :return, :b_call, :b_return, :c_call, :c_return, :raise){|tp| + next if !target_thread? + next if tp.path != __FILE__ + case tp.event + when :call, :return + assert_equal([[:req, :a], [:req, :b], [:req, :c]], tp.parameters) + when :b_call, :b_return + next if tp.parameters == [] + if tp.parameters.first == [:opt, :x] + assert_equal([[:opt, :x], [:opt, :y], [:opt, :z]], tp.parameters) + else + assert_equal([[:req, :p], [:req, :q], [:req, :r]], tp.parameters) + end + when :c_call, :c_return + assert_equal([[:req]], tp.parameters) if tp.method_id == :getbyte + when :line, :class, :end, :raise + assert_raise(RuntimeError) { tp.parameters } + end + } + obj = Object.new + trace.enable{ + parameter_test(1, 2, 3) {|x, y, z| + } + lambda {|p, q, r| }.call(4, 5, 6) + "".getbyte(0) + class << obj + end + begin + raise + rescue + end + } + end + def method_test_tracepoint_return_value obj obj end @@ -725,19 +934,69 @@ 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 end def test_tracepoint_raised_exception - trace = TracePoint.new(:call, :return){|tp| + trace = TracePoint.new(:call, :return, :raise){|tp| next if !target_thread? case tp.event when :call, :return assert_raise(RuntimeError) { tp.raised_exception } when :raise - assert_equal(XYZZYError, tp.raised_exception) + assert_kind_of(XYZZYException, tp.raised_exception) end } trace.enable{ @@ -801,7 +1060,7 @@ class TestSetTraceFunc < Test::Unit::TestCase tp.defined_class, #=> nil, tp.self.class # tp.self return creating/ending thread ] - }.enable{ + }.enable(target_thread: nil){ created_thread = Thread.new{thread_self = self} created_thread.join } @@ -814,14 +1073,16 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_tracepoint_inspect events = [] + th = nil trace = TracePoint.new{|tp| - next if !target_thread? + next if !target_thread? && th != Thread.current events << [tp.event, tp.inspect] } assert_equal("#<TracePoint:disabled>", trace.inspect) trace.enable{ assert_equal("#<TracePoint:enabled>", trace.inspect) - Thread.new{}.join + th = Thread.new{} + th.join } assert_equal("#<TracePoint:disabled>", trace.inspect) events.each{|(ev, str)| @@ -829,9 +1090,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 @@ -857,22 +1118,41 @@ class TestSetTraceFunc < Test::Unit::TestCase end end + def test_tracepoint_exception_at_c_return + assert_nothing_raised(Timeout::Error, 'infinite trace') do + assert_normal_exit %q{ + begin + TracePoint.new(:c_return){|tp| + raise + }.enable{ + tap{ itself } + } + rescue + end + }, '', timeout: 3 + end + end + 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 @@ -1013,7 +1293,7 @@ class TestSetTraceFunc < Test::Unit::TestCase }.enable{ 3.times{ next - } # 3 times b_retun + } # 3 times b_return } # 1 time b_return assert_equal 4, n @@ -1431,7 +1711,6 @@ class TestSetTraceFunc < Test::Unit::TestCase end def test_throwing_return_with_finish_frame - target_th = Thread.current evs = [] TracePoint.new(:call, :return){|tp| @@ -1471,6 +1750,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| @@ -1493,6 +1799,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 @@ -1555,7 +1896,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{ @@ -1579,13 +1920,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{ @@ -1618,6 +1959,25 @@ class TestSetTraceFunc < Test::Unit::TestCase ary end + def test_single_raise_inside_load + events = [] + tmpdir = Dir.mktmpdir + path = "#{tmpdir}/hola.rb" + File.open(path, "w") { |f| f.write("raise") } + tp = TracePoint.new(:raise) {|tp| events << [tp.event] if target_thread?} + tp.enable{ + load path rescue nil + } + assert_equal [[:raise]], events + events.clear + tp.enable{ + require path rescue nil + } + assert_equal [[:raise]], events + ensure + FileUtils.rmtree(tmpdir) + end + def f_raise raise rescue @@ -1684,7 +2044,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 @@ -1705,27 +2065,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 @@ -1755,7 +2132,6 @@ class TestSetTraceFunc < Test::Unit::TestCase define_method(:m) {} tp = TracePoint.new(:call) do - next unless target_thread? raise '' end @@ -1774,4 +2150,656 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_trace_point_require_block assert_raise(ArgumentError) { TracePoint.new(:return) } end + + def method_for_test_thread_add_trace_func + + end + + def test_thread_add_trace_func + events = [] + base_line = __LINE__ + q = Thread::Queue.new + t = Thread.new{ + Thread.current.add_trace_func proc{|ev, file, line, *args| + events << [ev, line] + } # do not stop trace. They will be stopped at Thread termination. + q.push 1 + _x = 1 + method_for_test_thread_add_trace_func + _y = 2 + } + q.pop + method_for_test_thread_add_trace_func + t.join + assert_equal ["c-return", base_line + 3], events[0] + assert_equal ["line", base_line + 6], events[1] + assert_equal ["c-call", base_line + 6], events[2] + assert_equal ["c-return", base_line + 6], events[3] + assert_equal ["line", base_line + 7], events[4] + assert_equal ["line", base_line + 8], events[5] + assert_equal ["call", base_line + -6], events[6] + assert_equal ["return", base_line + -4], events[7] + assert_equal ["line", base_line + 9], events[8] + assert_equal nil, events[9] + + # other thread + events = [] + m2t_q = Thread::Queue.new + + t = Thread.new{ + Thread.current.abort_on_exception = true + assert_equal 1, m2t_q.pop + _x = 1 + method_for_test_thread_add_trace_func + _y = 2 + Thread.current.set_trace_func(nil) + method_for_test_thread_add_trace_func + } + # it is dirty hack. usually we shouldn't use such technique + Thread.pass until t.status == 'sleep' + # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. + # This sleep forces it to reach m2t_q.pop for --jit-wait. + sleep 1 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + + t.add_trace_func proc{|ev, file, line, *args| + if file == __FILE__ + events << [ev, line] + end + } + + method_for_test_thread_add_trace_func + + m2t_q.push 1 + t.join + + assert_equal ["line", base_line + 32], events[0] + assert_equal ["line", base_line + 33], events[1] + assert_equal ["call", base_line + -6], events[2] + assert_equal ["return", base_line + -4], events[3] + assert_equal ["line", base_line + 34], events[4] + assert_equal ["line", base_line + 35], events[5] + assert_equal ["c-call", base_line + 35], events[6] # Thread.current + assert_equal ["c-return", base_line + 35], events[7] # Thread.current + assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func + assert_equal nil, events[9] + end + + def test_lineno_in_optimized_insn + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $loc = nil + class String + undef -@ + def -@ + $loc = caller_locations(1, 1)[0].lineno + end + end + + assert_predicate(-"", :frozen?) + assert_equal(__LINE__-1, $loc, '[Bug #14809]') + end; + end + + def method_for_enable_target1 + a = 1 + b = 2 + 1.times{|i| + _x = i + } + _c = a + b + end + + def method_for_enable_target2 + a = 1 + b = 2 + 1.times{|i| + _x = i + } + _c = a + b + end + + def check_with_events *trace_events + all_events = [[:call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_return, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:return, :method_for_enable_target1], + # repeat + [:call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_return, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:return, :method_for_enable_target1], + ] + events = [] + TracePoint.new(*trace_events) do |tp| + next unless target_thread? + events << [tp.event, tp.method_id] + end.enable(target: method(:method_for_enable_target1)) do + method_for_enable_target1 + method_for_enable_target2 + method_for_enable_target1 + end + assert_equal all_events.find_all{|(ev)| trace_events.include? ev}, events + end + + def test_tracepoint_enable_target + check_with_events :line + check_with_events :call, :return + check_with_events :line, :call, :return + check_with_events :call, :return, :b_call, :b_return + check_with_events :line, :call, :return, :b_call, :b_return + end + + def test_tracepoint_nested_enabled_with_target + code1 = proc{ + _a = 1 + } + code2 = proc{ + _b = 2 + } + + ## error + + # targeted TP and targeted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # global TP and targeted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target_thread: nil){ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # targeted TP and global TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable{} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # targeted TP and disable + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.disable{} + } + end + assert_equal "can't disable a targeting TracePoint in a block", ex.message + + ## success with two nesting targeting tracepoints + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp1, :___], events + + # success with two tracepoints (global/targeting) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events + + # success with two tracepoints (targeting/global) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events + end + + def test_tracepoint_enable_with_target_line + events = [] + line_0 = __LINE__ + code1 = proc{ + events << 1 + events << 2 + events << 3 + } + tp = TracePoint.new(:line) do |tp| + events << :tp + end + tp.enable(target: code1, target_line: line_0 + 3) do + code1.call + end + assert_equal [1, :tp, 2, 3], events + + + e = assert_raise(ArgumentError) do + TracePoint.new(:line){}.enable(target_line: 10){} + end + assert_equal 'only target_line is specified', e.message + + e = assert_raise(ArgumentError) do + TracePoint.new(:call){}.enable(target: code1, target_line: 10){} + end + 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_multiple_tracepoints_same_bmethod + events = [] + tp1 = TracePoint.new(:return) do |tp| + events << :tp1 + end + tp2 = TracePoint.new(:return) do |tp| + events << :tp2 + end + + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + + tp1.enable(target: bmethod) do + tp2.enable(target: bmethod) do + obj.foo + end + end + + assert_equal([:tp2, :tp1], events, '[Bug #18031]') + end + + def test_script_compiled + events = [] + tp = TracePoint.new(:script_compiled){|tp| + next unless target_thread? + events << [tp.instruction_sequence.path, + tp.eval_script] + } + + eval_script = 'a = 1' + tp.enable{ + eval(eval_script, nil, __FILE__+"/eval") + nil.instance_eval(eval_script, __FILE__+"/instance_eval") + Object.class_eval(eval_script, __FILE__+"/class_eval") + } + assert_equal [[__FILE__+"/eval", eval_script], + [__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' + + omit "TODO: test for requires" + + events.clear + tp.enable{ + require '' + require_relative '' + load '' + } + assert_equal [], events + end + + def test_enable_target_thread + events = [] + TracePoint.new(:line) do |tp| + events << Thread.current + end.enable(target_thread: Thread.current) do + _a = 1 + Thread.new{ + _b = 2 + _c = 3 + }.join + _d = 4 + end + assert_equal Array.new(3){Thread.current}, events + + events = [] + tp = TracePoint.new(:line) do |tp| + events << Thread.current + end + + q1 = Thread::Queue.new + q2 = Thread::Queue.new + + th = Thread.new{ + q1 << :ok; q2.pop + _t1 = 1 + _t2 = 2 + } + q1.pop + tp.enable(target_thread: th) do + q2 << 1 + _a = 1 + _b = 2 + th.join + end + + 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 + 1 if 1 == 1 + rescue + end + ok = false + tp = TracePoint.new(:return) {ok = true} + tp.enable {obj.example} + assert ok, "return event should be emitted" + end + + def test_disable_local_tracepoint_in_trace + assert_normal_exit <<-EOS + def foo + trace = TracePoint.new(:b_return){|tp| + tp.disable + } + trace.enable(target: method(:bar)) + end + def bar + 100.times{|i| + foo; foo + } + end + bar + EOS + + assert_normal_exit(<<-EOS, 'Bug #18730') + def bar + 42 + end + tp_line = TracePoint.new(:line) do |tp0| + tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp| + tp0.disable + end + tp_multi1.enable + end + tp_line.enable(target: method(:bar)) + bar + EOS + end + + def test_stat_exists + 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 + + def test_raising_from_b_return_tp_tracing_bmethod + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.trace(:b_return) do |tp| + raise + end + + Foo.foo + RUBY + + # Same thing but with a target + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.new(:b_return) do |tp| + raise + end.enable(target: Foo.method(:foo)) + + Foo.foo + RUBY + end + + def test_tracepoint_thread_begin + target_thread = nil + + trace = TracePoint.new(:thread_begin) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end_with_exception + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + thread = Thread.new do + Thread.current.report_on_exception = false + raise + end + + # Ignore the exception raised by the thread: + thread.join rescue nil + end + + assert_kind_of(Thread, target_thread) + end end diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb new file mode 100644 index 0000000000..7db4d0998d --- /dev/null +++ b/test/ruby/test_shapes.rb @@ -0,0 +1,441 @@ +# frozen_string_literal: false +require 'test/unit' + +# These test the functionality of object shapes +class TestShapes < Test::Unit::TestCase + class IVOrder + def expected_ivs + %w{ @a @b @c @d @e @f @g @h @i @j @k } + end + + def set_ivs + expected_ivs.each { instance_variable_set(_1, 1) } + self + end + end + + class ShapeOrder + def initialize + @b = :b # 5 => 6 + end + + def set_b + @b = :b # 5 => 6 + end + + def set_c + @c = :c # 5 => 7 + end + end + + class Example + def initialize + @a = 1 + end + end + + class RemoveAndAdd + def add_foo + @foo = 1 + end + + def remove_foo + remove_instance_variable(:@foo) + end + + def add_bar + @bar = 1 + end + end + + class TooComplex + attr_reader :hopefully_unique_name, :b + + def initialize + @hopefully_unique_name = "a" + @b = "b" + end + + # Make enough lazily defined accessors to allow us to force + # polymorphism + class_eval (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map { + "def a#{_1}_m; @a#{_1} ||= #{_1}; end" + }.join(" ; ") + + class_eval "attr_accessor " + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map { + ":a#{_1}" + }.join(", ") + + def iv_not_defined; @not_defined; end + + def write_iv_method + self.a3 = 12345 + end + + def write_iv + @a3 = 12345 + end + end + + # RubyVM::Shape.of returns new instances of shape objects for + # each call. This helper method allows us to define equality for + # shapes + def assert_shape_equal(shape1, shape2) + assert_equal(shape1.id, shape2.id) + assert_equal(shape1.parent_id, shape2.parent_id) + assert_equal(shape1.depth, shape2.depth) + assert_equal(shape1.type, shape2.type) + end + + def refute_shape_equal(shape1, shape2) + refute_equal(shape1.id, shape2.id) + end + + def test_iv_order_correct_on_complex_objects + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times { + IVOrder.new.instance_variable_set("@a#{_1}", 1) + } + + obj = IVOrder.new + iv_list = obj.set_ivs.instance_variables + assert_equal obj.expected_ivs, iv_list.map(&:to_s) + end + + def test_too_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + end + + def test_too_complex_ractor + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.new(tc) { |x| Ractor.yield(x.very_unique) }.take + assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| Ractor.yield(x.instance_variables) }.take.sort + end; + end + + def test_too_complex_ractor_shareable + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_read_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + end + + def test_read_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + assert_equal 3, tc.a3 + end + + def test_write_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.write_iv_method + tc.write_iv_method + assert_equal 12345, tc.a3_m + assert_equal 12345, tc.a3 + end + + def test_write_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.write_iv + tc.write_iv + assert_equal 12345, tc.a3_m + assert_equal 12345, tc.a3 + end + + def test_iv_read_via_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + assert_equal 3, tc.instance_variable_get(:@a3) + end + + def test_delete_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.remove_instance_variable(:@a3) + assert_nil tc.a3 + end + + def test_delete_undefined_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + refute tc.instance_variable_defined?(:@a3) + assert_raise(NameError) do + tc.remove_instance_variable(:@a3) + end + assert_nil tc.a3 + end + + def test_freeze_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.freeze + assert_raise(FrozenError) { tc.a3_m } + end + + def test_read_undefined_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal nil, tc.iv_not_defined + end + + def test_shape_order + bar = ShapeOrder.new # 0 => 1 + bar.set_c # 1 => 2 + bar.set_b # 2 => 2 + + foo = ShapeOrder.new # 0 => 1 + shape_id = RubyVM::Shape.of(foo).id + foo.set_b # should not transition + assert_equal shape_id, RubyVM::Shape.of(foo).id + end + + def test_iv_index + example = RemoveAndAdd.new + initial_shape = RubyVM::Shape.of(example) + assert_equal 0, initial_shape.next_iv_index + + example.add_foo # makes a transition + add_foo_shape = RubyVM::Shape.of(example) + assert_equal([:@foo], example.instance_variables) + assert_equal(initial_shape.id, add_foo_shape.parent.id) + assert_equal(1, add_foo_shape.next_iv_index) + + example.remove_foo # makes a transition + remove_foo_shape = RubyVM::Shape.of(example) + assert_equal([], example.instance_variables) + assert_shape_equal(initial_shape, remove_foo_shape) + + example.add_bar # makes a transition + bar_shape = RubyVM::Shape.of(example) + assert_equal([:@bar], example.instance_variables) + assert_equal(initial_shape.id, bar_shape.parent_id) + assert_equal(1, bar_shape.next_iv_index) + end + + def test_remove_then_add_again + example = RemoveAndAdd.new + _initial_shape = RubyVM::Shape.of(example) + + example.add_foo # makes a transition + add_foo_shape = RubyVM::Shape.of(example) + example.remove_foo # makes a transition + example.add_foo # makes a transition + assert_shape_equal(add_foo_shape, RubyVM::Shape.of(example)) + end + + class TestObject; end + + def test_new_obj_has_t_object_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of(TestObject.new).parent) + end + + def test_str_has_root_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of("")) + end + + def test_array_has_root_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([])) + end + + def test_hash_has_root_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of({})) + end + + def test_true_has_special_const_shape_id + assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(true).id) + end + + def test_nil_has_special_const_shape_id + assert_equal(RubyVM::Shape::SPECIAL_CONST_SHAPE_ID, RubyVM::Shape.of(nil).id) + end + + def test_basic_shape_transition + obj = Example.new + shape = RubyVM::Shape.of(obj) + refute_equal(RubyVM::Shape.root_shape, shape) + assert_equal :@a, shape.edge_name + assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type + + shape = shape.parent + assert_equal RubyVM::Shape::SHAPE_T_OBJECT, shape.type + + shape = shape.parent + assert_equal(RubyVM::Shape.root_shape.id, shape.id) + assert_equal(obj.instance_variable_get(:@a), 1) + end + + def test_different_objects_make_same_transition + obj = [] + obj2 = "" + obj.instance_variable_set(:@a, 1) + obj2.instance_variable_set(:@a, 1) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_duplicating_objects + obj = Example.new + obj2 = obj.dup + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_duplicating_too_complex_objects_memory_leak + assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true) + RubyVM::Shape.exhaust_shapes + + o = Object.new + o.instance_variable_set(:@a, 0) + begin; + 1_000_000.times do + o.dup + end + end; + end + + def test_freezing_and_duplicating_object + obj = Object.new.freeze + obj2 = obj.dup + refute_predicate(obj2, :frozen?) + # dup'd objects shouldn't be frozen, and the shape should be the + # parent shape of the copied object + assert_equal(RubyVM::Shape.of(obj).parent.id, RubyVM::Shape.of(obj2).id) + end + + def test_freezing_and_duplicating_object_with_ivars + obj = Example.new.freeze + obj2 = obj.dup + refute_predicate(obj2, :frozen?) + refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_equal(obj2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_duplicating_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + str2 = str.dup + refute_predicate(str2, :frozen?) + refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id) + assert_equal(str2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_cloning_objects + obj = Object.new.freeze + obj2 = obj.clone(freeze: true) + assert_predicate(obj2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_freezing_and_cloning_object_with_ivars + obj = Example.new.freeze + obj2 = obj.clone(freeze: true) + assert_predicate(obj2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_equal(obj2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_cloning_string + str = "str".freeze + str2 = str.clone(freeze: true) + assert_predicate(str2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) + end + + def test_freezing_and_cloning_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + str2 = str.clone(freeze: true) + assert_predicate(str2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) + assert_equal(str2.instance_variable_get(:@a), 1) + end + + def test_out_of_bounds_shape + assert_raise ArgumentError do + RubyVM::Shape.find_by_id(RubyVM.stat[:next_shape_id]) + end + assert_raise ArgumentError do + RubyVM::Shape.find_by_id(-1) + end + end + + def ensure_complex + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + tc = TooComplex.new + tc.send("a#{_1}_m") + end + end +end if defined?(RubyVM::Shape) diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index 2d98b0b564..c5043eea59 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -28,7 +28,8 @@ class TestSignal < Test::Unit::TestCase def test_signal_process_group bug4362 = '[ruby-dev:43169]' assert_nothing_raised(bug4362) do - pid = Process.spawn(EnvUtil.rubybin, '-e', 'sleep 10', :pgroup => true) + cmd = [ EnvUtil.rubybin, '--disable=gems' '-e', 'sleep 10' ] + pid = Process.spawn(*cmd, :pgroup => true) Process.kill(:"-TERM", pid) Process.waitpid(pid) assert_equal(true, $?.signaled?) @@ -44,7 +45,7 @@ class TestSignal < Test::Unit::TestCase sig = "INT" term = :KILL end - IO.popen([EnvUtil.rubybin, '-e', <<-"End"], 'r+') do |io| + IO.popen([EnvUtil.rubybin, '--disable=gems', '-e', <<-"End"], 'r+') do |io| Signal.trap(:#{sig}, "EXIT") STDOUT.syswrite("a") Thread.start { sleep(2) } @@ -83,10 +84,12 @@ class TestSignal < Test::Unit::TestCase assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { SignalException.new("\u{30eb 30d3 30fc}") } Signal.list.each do |signm, signo| next if signm == "EXIT" - assert_equal(SignalException.new(signm).signo, signo) - assert_equal(SignalException.new(signm.to_sym).signo, signo) - assert_equal(SignalException.new(signo).signo, signo) + assert_equal(signo, SignalException.new(signm).signo, signm) + assert_equal(signo, SignalException.new(signm.to_sym).signo, signm) + assert_equal(signo, SignalException.new(signo).signo, signo) end + e = assert_raise(ArgumentError) {SignalException.new("-SIGEXIT")} + assert_not_match(/SIG-SIG/, e.message) end def test_interrupt @@ -134,11 +137,6 @@ class TestSignal < Test::Unit::TestCase assert_raise(ArgumentError) { Signal.trap } - assert_raise(SecurityError) do - s = proc {}.taint - Signal.trap(:INT, s) - end - # FIXME! Signal.trap(:INT, nil) Signal.trap(:INT, "") @@ -164,6 +162,8 @@ class TestSignal < Test::Unit::TestCase assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") } assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Signal.trap("\u{30eb 30d3 30fc}", "SIG_DFL") } + + assert_raise(ArgumentError) { Signal.trap("EXIT\0") {} } ensure Signal.trap(:INT, oldtrap) if oldtrap end @@ -228,20 +228,20 @@ class TestSignal < Test::Unit::TestCase end def test_signame_delivered - 10.times do - IO.popen([EnvUtil.rubybin, "-e", <<EOS, :err => File::NULL]) do |child| - Signal.trap("INT") do |signo| - signame = Signal.signame(signo) - Marshal.dump(signame, STDOUT) - STDOUT.flush - exit 0 - end - Process.kill("INT", $$) - sleep 1 # wait signal deliver -EOS + args = [EnvUtil.rubybin, "--disable=gems", "-e", <<"", :err => File::NULL] + Signal.trap("INT") do |signo| + signame = Signal.signame(signo) + Marshal.dump(signame, STDOUT) + STDOUT.flush + exit 0 + end + Process.kill("INT", $$) + sleep 1 # wait signal deliver + 10.times do + IO.popen(args) do |child| signame = Marshal.load(child) - assert_equal(signame, "INT") + assert_equal("INT", signame) end end end if Process.respond_to?(:kill) @@ -291,7 +291,8 @@ EOS if trap = Signal.list['TRAP'] bug9820 = '[ruby-dev:48592] [Bug #9820]' - status = assert_in_out_err(['-e', 'Process.kill(:TRAP, $$)']) + no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE) + status = assert_in_out_err(['-e', "#{no_core}Process.kill(:TRAP, $$)"]) assert_predicate(status, :signaled?, bug9820) assert_equal(trap, status.termsig, bug9820) end @@ -321,4 +322,67 @@ EOS end end; end + + def test_sigchld_ignore + omit 'no SIGCHLD' unless Signal.list['CHLD'] + old = trap(:CHLD, 'IGNORE') + cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ] + assert(system(*cmd, 'exit!(0)'), 'no ECHILD') + IO.pipe do |r, w| + pid = spawn(*cmd, "STDIN.read", in: r) + nb = Process.wait(pid, Process::WNOHANG) + th = Thread.new(Thread.current) do |parent| + Thread.pass until parent.stop? # wait for parent to Process.wait + w.close + end + assert_raise(Errno::ECHILD) { Process.wait(pid) } + th.join + assert_nil nb + end + + IO.pipe do |r, w| + pids = 3.times.map { spawn(*cmd, 'exit!', out: w) } + w.close + zombies = pids.dup + assert_nil r.read(1), 'children dead' + + Timeout.timeout(10) do + zombies.delete_if do |pid| + begin + Process.kill(0, pid) + false + rescue Errno::ESRCH + true + end + end while zombies[0] + end + assert_predicate zombies, :empty?, 'zombies leftover' + + pids.each do |pid| + assert_raise(Errno::ECHILD) { Process.waitpid(pid) } + end + end + ensure + trap(:CHLD, old) if Signal.list['CHLD'] + end + + def test_sigwait_fd_unused + t = EnvUtil.apply_timeout_scale(0.1) + assert_separately([], <<-End) + tgt = $$ + trap(:TERM) { exit(0) } + e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})" + term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ] + t2 = Thread.new { sleep } # grab sigwait_fd + Thread.pass until t2.stop? + Thread.new do + sleep #{t} + t2.kill + t2.join + end + Process.spawn(*term) + # last thread remaining, ensure it can react to SIGTERM + loop { sleep } + End + end if Process.respond_to?(:kill) && Process.respond_to?(:daemon) end diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb index a07ac7908b..3f821f7a5d 100644 --- a/test/ruby/test_sprintf.rb +++ b/test/ruby/test_sprintf.rb @@ -227,6 +227,10 @@ class TestSprintf < Test::Unit::TestCase bug11766 = '[ruby-core:71806] [Bug #11766]' assert_equal("x"*10+" 1.0", sprintf("x"*10+"%8.1f", 1r), bug11766) + + require 'rbconfig/sizeof' + fmin, fmax = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX") + assert_match(/\A-\d+\.\d+\z/, sprintf("%f", Rational(fmin, fmax))) end def test_rational_precision @@ -238,6 +242,12 @@ class TestSprintf < Test::Unit::TestCase assert_equal("with options {:capture=>/\\d+/}", sprintf("with options %p" % options)) end + def test_inspect + obj = Object.new + def obj.inspect; "TEST"; end + assert_equal("<TEST>", sprintf("<%p>", obj)) + end + def test_invalid # Star precision before star width: assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%.**d", 5, 10, 1)} @@ -356,11 +366,16 @@ class TestSprintf < Test::Unit::TestCase def test_char assert_equal("a", sprintf("%c", 97)) assert_equal("a", sprintf("%c", ?a)) - assert_raise(ArgumentError) { sprintf("%c", sprintf("%c%c", ?a, ?a)) } + assert_equal("a", sprintf("%c", "a")) + assert_equal("a", sprintf("%c", sprintf("%c%c", ?a, ?a))) assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%c", ?a)) assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%-1c", ?a)) assert_equal(" " * BSIZ + "a", sprintf("%#{ BSIZ + 1 }c", ?a)) assert_equal("a" + " " * BSIZ, sprintf("%-#{ BSIZ + 1 }c", ?a)) + assert_raise(ArgumentError) { sprintf("%c", -1) } + s = sprintf("%c".encode(Encoding::US_ASCII), 0x80) + assert_equal("\x80".b, s) + assert_predicate(s, :valid_encoding?) end def test_string @@ -501,6 +516,16 @@ class TestSprintf < Test::Unit::TestCase end end + def test_coderange + format_str = "wrong constant name %s" + interpolated_str = "\u3042" + assert_predicate format_str, :ascii_only? + refute_predicate interpolated_str, :ascii_only? + + str = format_str % interpolated_str + refute_predicate str, :ascii_only? + end + def test_named_default h = Hash.new('world') assert_equal("hello world", "hello %{location}" % h) @@ -523,13 +548,11 @@ class TestSprintf < Test::Unit::TestCase end end - def test_no_hidden_garbage - fmt = [4, 2, 2].map { |x| "%0#{x}d" }.join('-') # defeats optimization - ObjectSpace.count_objects(res = {}) # creates strings on first call - 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 + def test_binary_format_coderange + 1.upto(500) do |i| + str = sprintf("%*s".b, i, "\xe2".b) + refute_predicate str, :ascii_only? + assert_equal i, str.bytesize + end 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 7dd76f76f7..f4ef6ccb6b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2,7 +2,10 @@ 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, + ] def initialize(*args) @cls = String @@ -12,8 +15,8 @@ class TestString < Test::Unit::TestCase super end - def S(*args) - @cls.new(*args) + def S(*args, **kw) + @cls.new(*args, **kw) end def test_s_new @@ -62,12 +65,26 @@ class TestString < Test::Unit::TestCase def test_initialize str = S("").freeze assert_equal("", str.__send__(:initialize)) - assert_raise(RuntimeError){ str.__send__(:initialize, 'abc') } - assert_raise(RuntimeError){ str.__send__(:initialize, capacity: 1000) } - assert_raise(RuntimeError){ str.__send__(:initialize, 'abc', capacity: 1000) } - assert_raise(RuntimeError){ str.__send__(:initialize, encoding: 'euc-jp') } - assert_raise(RuntimeError){ str.__send__(:initialize, 'abc', encoding: 'euc-jp') } - assert_raise(RuntimeError){ str.__send__(:initialize, 'abc', capacity: 1000, encoding: 'euc-jp') } + assert_raise(FrozenError){ str.__send__(:initialize, 'abc') } + assert_raise(FrozenError){ str.__send__(:initialize, capacity: 1000) } + assert_raise(FrozenError){ str.__send__(:initialize, 'abc', capacity: 1000) } + assert_raise(FrozenError){ str.__send__(:initialize, encoding: 'euc-jp') } + assert_raise(FrozenError){ str.__send__(:initialize, 'abc', encoding: 'euc-jp') } + assert_raise(FrozenError){ str.__send__(:initialize, 'abc', capacity: 1000, encoding: 'euc-jp') } + + str = S("") + assert_equal("mystring", str.__send__(:initialize, "mystring")) + str = S("mystring") + assert_equal("mystring", str.__send__(:initialize, str)) + str = S("") + assert_equal("mystring", str.__send__(:initialize, "mystring", capacity: 1000)) + str = S("mystring") + assert_equal("mystring", str.__send__(:initialize, str, capacity: 1000)) + end + + def test_initialize_shared + S(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize) + assert_equal("mystring", str[0, 8]) end def test_initialize_nonstring @@ -80,6 +97,8 @@ class TestString < Test::Unit::TestCase end def test_initialize_memory_leak + return unless @cls == String + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) code = proc {('x'*100000).__send__(:initialize, '')} 1_000.times(&code) @@ -88,6 +107,18 @@ PREP CODE end + # Bug #18154 + def test_initialize_nofree_memory_leak + return unless @cls == String + + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {0.to_s.__send__(:initialize, capacity: 10000)} +1_000.times(&code) +PREP +100_000.times(&code) +CODE + end + def test_AREF # '[]' assert_equal("A", S("AooBar")[0]) assert_equal("B", S("FooBaB")[-1]) @@ -131,6 +162,15 @@ CODE assert_raise(ArgumentError) { "foo"[] } end + def test_AREF_underflow + require "rbconfig/sizeof" + assert_equal(nil, S("\u{3042 3044 3046}")[RbConfig::LIMITS["LONG_MIN"], 1]) + end + + def test_AREF_invalid_encoding + assert_equal(S("\x80"), S("A"*39+"\x80")[-1, 1]) + end + def test_ASET # '[]=' s = S("FooBar") s[0] = S('A') @@ -213,23 +253,23 @@ CODE assert_equal(-1, S("ABCDEF") <=> S("abcdef")) - assert_nil("foo" <=> Object.new) + assert_nil(S("foo") <=> Object.new) o = Object.new def o.to_str; "bar"; end - assert_equal(1, "foo" <=> o) + assert_equal(1, S("foo") <=> o) class << o;remove_method :to_str;end def o.<=>(x); nil; end - assert_nil("foo" <=> o) + assert_nil(S("foo") <=> o) class << o;remove_method :<=>;end def o.<=>(x); 1; end - assert_equal(-1, "foo" <=> o) + assert_equal(-1, S("foo") <=> o) class << o;remove_method :<=>;end def o.<=>(x); 2**100; end - assert_equal(-1, "foo" <=> o) + assert_equal(-1, S("foo") <=> o) end def test_EQUAL # '==' @@ -238,14 +278,15 @@ 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 def o.==(x); false; end - assert_equal(false, "foo" == o) + assert_equal(false, S("foo") == o) class << o;remove_method :==;end def o.==(x); true; end - assert_equal(true, "foo" == o) + assert_equal(true, S("foo") == o) end def test_LSHIFT # '<<' @@ -269,6 +310,9 @@ CODE assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -1} assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << 0x81308130} assert_nothing_raised {S("a".force_encoding(Encoding::GB18030)) << 0x81308130} + + s = "\x95".force_encoding(Encoding::SJIS).tap(&:valid_encoding?) + assert_predicate(s << 0x5c, :valid_encoding?) end def test_MATCH # '=~' @@ -368,6 +412,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 = $/ @@ -433,9 +479,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")) @@ -491,7 +540,8 @@ CODE assert_equal(S("a").hash, S("a\u0101").chomp!(S("\u0101")).hash, '[ruby-core:22414]') s = S("").freeze - assert_raise_with_message(RuntimeError, /frozen/) {s.chomp!} + 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) @@ -499,7 +549,8 @@ CODE s.freeze "x" end - assert_raise_with_message(RuntimeError, /frozen/) {s.chomp!(o)} + 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')) @@ -550,6 +601,7 @@ CODE assert_equal("foo", s.chomp!("\n")) ensure $/ = save + $VERBOSE = verbose end def test_chop @@ -588,18 +640,14 @@ CODE end def test_clone - for taint in [ false, true ] - for frozen in [ false, true ] - a = S("Cool") - a.taint if taint - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert_not_same(a, b) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = S("Cool") + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) end assert_equal("", File.read(IO::NULL).clone, '[ruby-dev:32819] reported by Kazuhiro NISHIYAMA') @@ -616,12 +664,12 @@ CODE result << 0x0300 expected = S("\u0300".encode(Encoding::UTF_16LE)) assert_equal(expected, result, bug7090) - assert_raise(TypeError) { 'foo' << :foo } - assert_raise(RuntimeError) { 'foo'.freeze.concat('bar') } + assert_raise(TypeError) { S('foo') << :foo } + assert_raise(FrozenError) { S('foo').freeze.concat('bar') } end def test_concat_literals - s="." * 50 + s=S("." * 50) assert_equal(Encoding::UTF_8, "#{s}x".encoding) end @@ -632,35 +680,50 @@ CODE assert_equal(4, a.count(S("hello"), S("^l"))) assert_equal(4, a.count(S("ej-m"))) assert_equal(0, S("y").count(S("a\\-z"))) - assert_equal(5, "abc\u{3042 3044 3046}".count("^a")) - assert_equal(1, "abc\u{3042 3044 3046}".count("\u3042")) - assert_equal(5, "abc\u{3042 3044 3046}".count("^\u3042")) - assert_equal(2, "abc\u{3042 3044 3046}".count("a-z", "^a")) - assert_equal(0, "abc\u{3042 3044 3046}".count("a", "\u3042")) - assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "a")) - assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "\u3044")) - assert_equal(4, "abc\u{3042 3044 3046}".count("^a", "^\u3044")) - assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3044", "^a")) - assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3042", "^\u3044")) + assert_equal(5, S("abc\u{3042 3044 3046}").count("^a")) + assert_equal(1, S("abc\u{3042 3044 3046}").count("\u3042")) + assert_equal(5, S("abc\u{3042 3044 3046}").count("^\u3042")) + assert_equal(2, S("abc\u{3042 3044 3046}").count("a-z", "^a")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("a", "\u3042")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "a")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "\u3044")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^a", "^\u3044")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3044", "^a")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3042", "^\u3044")) - assert_raise(ArgumentError) { "foo".count } + assert_raise(ArgumentError) { S("foo").count } + end + + def crypt_supports_des_crypt? + /openbsd/ !~ RUBY_PLATFORM end def test_crypt - assert_equal(S('aaGUC/JkO9/Sc'), S("mypassword").crypt(S("aa"))) - assert_not_equal(S('aaGUC/JkO9/Sc'), S("mypassword").crypt(S("ab"))) + if crypt_supports_des_crypt? + pass = "aaGUC/JkO9/Sc" + good_salt = "aa" + bad_salt = "ab" + else + pass = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2." + good_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWu" + bad_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHXu" + end + assert_equal(S(pass), S("mypassword").crypt(S(good_salt))) + assert_not_equal(S(pass), S("mypassword").crypt(S(bad_salt))) assert_raise(ArgumentError) {S("mypassword").crypt(S(""))} assert_raise(ArgumentError) {S("mypassword").crypt(S("\0a"))} assert_raise(ArgumentError) {S("mypassword").crypt(S("a\0"))} assert_raise(ArgumentError) {S("poison\u0000null").crypt(S("aa"))} - [Encoding::UTF_16BE, Encoding::UTF_16LE, - Encoding::UTF_32BE, Encoding::UTF_32LE].each do |enc| + WIDE_ENCODINGS.each do |enc| assert_raise(ArgumentError) {S("mypassword").crypt(S("aa".encode(enc)))} assert_raise(ArgumentError) {S("mypassword".encode(enc)).crypt(S("aa"))} end - @cls == String and assert_no_memory_leak([], 's = ""', <<~'end;') # do - 1000.times { s.crypt(-"..").clear } + @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; end @@ -670,17 +733,17 @@ CODE assert_equal(S("hell"), S("hello").delete(S("aeiou"), S("^e"))) assert_equal(S("ho"), S("hello").delete(S("ej-m"))) - assert_equal("a".hash, "a\u0101".delete("\u0101").hash, '[ruby-talk:329267]') - assert_equal(true, "a\u0101".delete("\u0101").ascii_only?) - assert_equal(true, "a\u3041".delete("\u3041").ascii_only?) - assert_equal(false, "a\u3041\u3042".delete("\u3041").ascii_only?) + assert_equal(S("a").hash, S("a\u0101").delete("\u0101").hash, '[ruby-talk:329267]') + assert_equal(true, S("a\u0101").delete("\u0101").ascii_only?) + assert_equal(true, S("a\u3041").delete("\u3041").ascii_only?) + assert_equal(false, S("a\u3041\u3042").delete("\u3041").ascii_only?) - assert_equal("a", "abc\u{3042 3044 3046}".delete("^a")) - assert_equal("bc\u{3042 3044 3046}", "abc\u{3042 3044 3046}".delete("a")) - assert_equal("\u3042", "abc\u{3042 3044 3046}".delete("^\u3042")) + assert_equal("a", S("abc\u{3042 3044 3046}").delete("^a")) + assert_equal("bc\u{3042 3044 3046}", S("abc\u{3042 3044 3046}").delete("a")) + assert_equal("\u3042", S("abc\u{3042 3044 3046}").delete("^\u3042")) bug6160 = '[ruby-dev:45374]' - assert_equal("", '\\'.delete('\\'), bug6160) + assert_equal("", S('\\').delete('\\'), bug6160) end def test_delete! @@ -722,6 +785,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! @@ -734,6 +798,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 @@ -753,23 +823,97 @@ CODE assert_equal(S('"\\u{10ABCD}"'), b.dump) end + def test_undump + a = S("Test") << 1 << 2 << 3 << 9 << 13 << 10 + assert_equal(a, S('"Test\\x01\\x02\\x03\\t\\r\\n"').undump) + assert_equal(S("\\ca"), S('"\\ca"').undump) + assert_equal(S("\u{7F}"), S('"\\x7F"').undump) + assert_equal(S("\u{7F}A"), S('"\\x7FA"').undump) + assert_equal(S("\u{AB}"), S('"\\u00AB"').undump) + assert_equal(S("\u{ABC}"), S('"\\u0ABC"').undump) + assert_equal(S("\uABCD"), S('"\\uABCD"').undump) + assert_equal(S("\uABCD"), S('"\\uABCD"').undump) + assert_equal(S("\u{ABCDE}"), S('"\\u{ABCDE}"').undump) + assert_equal(S("\u{10ABCD}"), S('"\\u{10ABCD}"').undump) + assert_equal(S("\u{ABCDE 10ABCD}"), S('"\\u{ABCDE 10ABCD}"').undump) + assert_equal(S(""), S('"\\u{}"').undump) + assert_equal(S(""), S('"\\u{ }"').undump) + + assert_equal(S("\u3042".encode("sjis")), S('"\x82\xA0"'.force_encoding("sjis")).undump) + assert_equal(S("\u8868".encode("sjis")), S("\"\\x95\\\\\"".force_encoding("sjis")).undump) + + assert_equal(S("äöü"), S('"\u00E4\u00F6\u00FC"').undump) + assert_equal(S("äöü"), S('"\xC3\xA4\xC3\xB6\xC3\xBC"').undump) + + assert_equal(Encoding::UTF_8, S('"\\u3042"').encode(Encoding::EUC_JP).undump.encoding) + + assert_equal("abc".encode(Encoding::UTF_16LE), + S('"a\x00b\x00c\x00".force_encoding("UTF-16LE")').undump) + + assert_equal('\#', S('"\\\\#"').undump) + assert_equal('\#{', S('"\\\\\#{"').undump) + + assert_raise(RuntimeError) { S('\u3042').undump } + assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump } + assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump } + assert_raise(RuntimeError) { S('"".force_encoding()').undump } + assert_raise(RuntimeError) { S('"".force_encoding("').undump } + assert_raise(RuntimeError) { S('"".force_encoding("UNKNOWN")').undump } + assert_raise(RuntimeError) { S('"\u3042".force_encoding("UTF-16LE")').undump } + assert_raise(RuntimeError) { S('"\x00\x00".force_encoding("UTF-16LE")"').undump } + assert_raise(RuntimeError) { S('"\x00\x00".force_encoding("'+("a"*9999999)+'")"').undump } + assert_raise(RuntimeError) { S(%("\u00E4")).undump } + assert_raise(RuntimeError) { S('"').undump } + assert_raise(RuntimeError) { S('"""').undump } + assert_raise(RuntimeError) { S('""""').undump } + + assert_raise(RuntimeError) { S('"a').undump } + assert_raise(RuntimeError) { S('"\u"').undump } + assert_raise(RuntimeError) { S('"\u{"').undump } + assert_raise(RuntimeError) { S('"\u304"').undump } + assert_raise(RuntimeError) { S('"\u304Z"').undump } + assert_raise(RuntimeError) { S('"\udfff"').undump } + assert_raise(RuntimeError) { S('"\u{dfff}"').undump } + assert_raise(RuntimeError) { S('"\u{3042"').undump } + assert_raise(RuntimeError) { S('"\u{3042 "').undump } + assert_raise(RuntimeError) { S('"\u{110000}"').undump } + assert_raise(RuntimeError) { S('"\u{1234567}"').undump } + assert_raise(RuntimeError) { S('"\x"').undump } + assert_raise(RuntimeError) { S('"\xA"').undump } + assert_raise(RuntimeError) { S('"\\"').undump } + assert_raise(RuntimeError) { S(%("\0")).undump } + assert_raise_with_message(RuntimeError, /invalid/) { + S('"\\u{007F}".xxxxxx').undump + } + end + def test_dup - for taint in [ false, true ] - for frozen in [ false, true ] - a = S("hello") - a.taint if taint - a.freeze if frozen - b = a.dup - - assert_equal(a, b) - assert_not_same(a, b) - assert_not_predicate(b, :frozen?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = S("hello") + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_not_predicate(b, :frozen?) + end + end + + class StringWithIVSet < String + def set_iv + @foo = 1 end end + def test_ivar_set_after_frozen_dup + str = StringWithIVSet.new.freeze + str.dup.set_iv + assert_raise(FrozenError) { str.set_iv } + end + def test_each + verbose, $VERBOSE = $VERBOSE, nil + save = $/ $/ = "\n" res=[] @@ -789,6 +933,7 @@ CODE assert_equal(S("world"), res[1]) ensure $/ = save + $VERBOSE = verbose end def test_each_byte @@ -807,26 +952,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 - warning = /passing a block to String#bytes is deprecated/ - assert_warning(warning) { - 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]) - } - assert_warning(warning) { - 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 @@ -851,26 +985,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 - warning = /passing a block to String#codepoints is deprecated/ - assert_warning(warning) { - 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]) - } - assert_warning(warning) { - 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 @@ -889,24 +1012,16 @@ 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 - warning = /passing a block to String#chars is deprecated/ - assert_warning(warning) { - 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 [ + "\u{0D 0A}", "\u{20 200d}", "\u{600 600}", "\u{600 20}", @@ -919,11 +1034,18 @@ CODE "\u{1f469 200d 2764 fe0f 200d 1f469}", ].each do |g| assert_equal [g], g.each_grapheme_cluster.to_a + assert_equal 1, g.each_grapheme_cluster.size + end + + [ + ["\u{a 324}", ["\u000A", "\u0324"]], + ["\u{d 324}", ["\u000D", "\u0324"]], + ["abc", ["a", "b", "c"]], + ].each do |str, grapheme_clusters| + assert_equal grapheme_clusters, str.each_grapheme_cluster.to_a + assert_equal grapheme_clusters.size, str.each_grapheme_cluster.size end - assert_equal ["\u000A", "\u0308"], "\u{a 308}".each_grapheme_cluster.to_a - assert_equal ["\u000D", "\u0308"], "\u{d 308}".each_grapheme_cluster.to_a - assert_equal ["a", "b", "c"], "abc".b.each_grapheme_cluster.to_a s = ("x"+"\u{10ABCD}"*250000) assert_empty(s.each_grapheme_cluster {s.clear}) end @@ -940,32 +1062,44 @@ CODE "\u{1F468 200D 1F393}", "\u{1F46F 200D 2642 FE0F}", "\u{1f469 200d 2764 fe0f 200d 1f469}", - ].each do |g| + ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc| + g = g.encode(enc) assert_equal [g], g.grapheme_clusters end - assert_equal ["\u000A", "\u0308"], "\u{a 308}".grapheme_clusters - assert_equal ["\u000D", "\u0308"], "\u{d 308}".grapheme_clusters - 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 - warning = /passing a block to String#grapheme_clusters is deprecated/ - assert_warning(warning) { - s = "ABC".b - res = [] - assert_same s, s.grapheme_clusters {|x| res << x } - assert_equal("A", res[0]) - assert_equal("B", res[1]) - assert_equal("C", res[2]) - } + [ + "\u{a 324}", + "\u{d 324}", + "abc", + ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc| + g = g.encode(enc) + assert_equal g.chars, g.grapheme_clusters end + assert_equal ["a", "b", "c"], S("abc").b.grapheme_clusters + + s = 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_grapheme_clusters_memory_leak + assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #todo]", rss: true) + begin; + str = "hello world".encode(Encoding::UTF_32LE) + + 10_000.times do + str.grapheme_clusters + end + end; end def test_each_line + verbose, $VERBOSE = $VERBOSE, nil + save = $/ $/ = "\n" res=[] @@ -1000,7 +1134,7 @@ CODE $/ = save s = nil - "foo\nbar".each_line(nil) {|s2| s = s2 } + S("foo\nbar").each_line(nil) {|s2| s = s2 } assert_equal("foo\nbar", s) assert_equal "hello\n", S("hello\nworld").each_line.next @@ -1008,10 +1142,11 @@ CODE bug7646 = "[ruby-dev:46827]" assert_nothing_raised(bug7646) do - "\n\u0100".each_line("\n") {} + S("\n\u0100").each_line("\n") {} end ensure $/ = save + $VERBOSE = verbose end def test_each_line_chomp @@ -1021,14 +1156,19 @@ CODE assert_equal(S("world"), res[1]) res = [] - S("hello\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x} - assert_equal(S("hello\n"), res[0]) - assert_equal(S("world"), res[1]) + S("hello\n\n\nworld\n").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world\n"), res[1]) res = [] - S("hello\r\n\r\nworld").each_line(S(''), chomp: true) {|x| res << x} - assert_equal(S("hello\r\n"), res[0]) - assert_equal(S("world"), res[1]) + S("hello\r\n\r\nworld\r\n").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world\r\n"), res[1]) + + res = [] + S("hello\r\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world"), res[1]) res = [] S("hello!world").each_line(S('!'), chomp: true) {|x| res << x} @@ -1041,7 +1181,7 @@ CODE assert_equal(S("a"), res[0]) s = nil - "foo\nbar".each_line(nil, chomp: true) {|s2| s = s2 } + S("foo\nbar").each_line(nil, chomp: true) {|s2| s = s2 } assert_equal("foo\nbar", s) assert_equal "hello", S("hello\nworld").each_line(chomp: true).next @@ -1058,6 +1198,10 @@ CODE res = [] S("\r\n").each_line(chomp: true) {|x| res << x} assert_equal([S("")], res) + + res = [] + S("a\n b\n").each_line(" ", chomp: true) {|x| res << x} + assert_equal([S("a\n"), S("b\n")], res) end def test_lines @@ -1065,18 +1209,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 - assert_warning(/passing a block to String#lines is deprecated/) { - 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? @@ -1088,6 +1224,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} @@ -1108,13 +1246,9 @@ CODE S("hello").gsub(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 }) assert_equal(S("<>h<>e<>l<>l<>o<>"), S("hello").gsub(S(''), S('<\0>'))) - a = S("hello") - a.taint - assert_predicate(a.gsub(/./, S('X')), :tainted?) + assert_equal("z", S("abc").gsub(/./, "a" => "z"), "moved from btest/knownbug") - assert_equal("z", "abc".gsub(/./, "a" => "z"), "moved from btest/knownbug") - - assert_raise(ArgumentError) { "foo".gsub } + assert_raise(ArgumentError) { S("foo").gsub } end def test_gsub_encoding @@ -1156,33 +1290,28 @@ CODE a.gsub!(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 } assert_equal(S("HELL-o"), a) - r = S('X') - r.taint - a.gsub!(/./, r) - assert_predicate(a, :tainted?) - a = S("hello") assert_nil(a.sub!(S('X'), S('Y'))) end def test_sub_hash - assert_equal('azc', 'abc'.sub(/b/, "b" => "z")) - assert_equal('ac', 'abc'.sub(/b/, {})) - assert_equal('a1c', 'abc'.sub(/b/, "b" => 1)) - assert_equal('aBc', 'abc'.sub(/b/, Hash.new {|h, k| k.upcase })) - assert_equal('a[\&]c', 'abc'.sub(/b/, "b" => '[\&]')) - assert_equal('aBcabc', 'abcabc'.sub(/b/, Hash.new {|h, k| h[k] = k.upcase })) - assert_equal('aBcdef', 'abcdef'.sub(/de|b/, "b" => "B", "de" => "DE")) + assert_equal('azc', S('abc').sub(/b/, "b" => "z")) + assert_equal('ac', S('abc').sub(/b/, {})) + assert_equal('a1c', S('abc').sub(/b/, "b" => 1)) + assert_equal('aBc', S('abc').sub(/b/, Hash.new {|h, k| k.upcase })) + assert_equal('a[\&]c', S('abc').sub(/b/, "b" => '[\&]')) + assert_equal('aBcabc', S('abcabc').sub(/b/, Hash.new {|h, k| h[k] = k.upcase })) + assert_equal('aBcdef', S('abcdef').sub(/de|b/, "b" => "B", "de" => "DE")) end def test_gsub_hash - assert_equal('azc', 'abc'.gsub(/b/, "b" => "z")) - assert_equal('ac', 'abc'.gsub(/b/, {})) - assert_equal('a1c', 'abc'.gsub(/b/, "b" => 1)) - assert_equal('aBc', 'abc'.gsub(/b/, Hash.new {|h, k| k.upcase })) - assert_equal('a[\&]c', 'abc'.gsub(/b/, "b" => '[\&]')) - assert_equal('aBcaBc', 'abcabc'.gsub(/b/, Hash.new {|h, k| h[k] = k.upcase })) - assert_equal('aBcDEf', 'abcdef'.gsub(/de|b/, "b" => "B", "de" => "DE")) + assert_equal('azc', S('abc').gsub(/b/, "b" => "z")) + assert_equal('ac', S('abc').gsub(/b/, {})) + assert_equal('a1c', S('abc').gsub(/b/, "b" => 1)) + assert_equal('aBc', S('abc').gsub(/b/, Hash.new {|h, k| k.upcase })) + assert_equal('a[\&]c', S('abc').gsub(/b/, "b" => '[\&]')) + assert_equal('aBcaBc', S('abcabc').gsub(/b/, Hash.new {|h, k| h[k] = k.upcase })) + assert_equal('aBcDEf', S('abcdef').gsub(/de|b/, "b" => "B", "de" => "DE")) end def test_hash @@ -1244,11 +1373,22 @@ CODE o = Object.new def o.to_str; "bar"; end - assert_equal(3, "foobarbarbaz".index(o)) - assert_raise(TypeError) { "foo".index(Object.new) } + assert_equal(3, S("foobarbarbaz").index(o)) + assert_raise(TypeError) { S("foo").index(Object.new) } - assert_nil("foo".index(//, -100)) + assert_nil(S("foo").index(//, -100)) assert_nil($~) + + assert_equal(2, S("abcdbce").index(/b\Kc/)) + + assert_equal(0, S("こんにちは").index(?こ)) + assert_equal(1, S("こんにちは").index(S("んにち"))) + assert_equal(2, S("こんにちは").index(/にち./)) + + assert_equal(0, S("にんにちは").index(?に, 0)) + assert_equal(2, S("にんにちは").index(?に, 1)) + assert_equal(2, S("にんにちは").index(?に, 2)) + assert_nil(S("にんにちは").index(?に, 3)) end def test_insert @@ -1352,25 +1492,23 @@ CODE assert_equal(S("foobar"), a.replace(S("foobar"))) a = S("foo") - a.taint b = a.replace(S("xyz")) assert_equal(S("xyz"), b) - assert_predicate(b, :tainted?) - s = "foo" * 100 + s = S("foo") * 100 s2 = ("bar" * 100).dup s.replace(s2) assert_equal(s2, s) - s2 = ["foo"].pack("p") + s2 = [S("foo")].pack("p") s.replace(s2) assert_equal(s2, s) - fs = "".freeze - assert_raise(RuntimeError) { fs.replace("a") } - assert_raise(RuntimeError) { fs.replace(fs) } + fs = S("").freeze + assert_raise(FrozenError) { fs.replace("a") } + assert_raise(FrozenError) { fs.replace(fs) } assert_raise(ArgumentError) { fs.replace() } - assert_raise(RuntimeError) { fs.replace(42) } + assert_raise(FrozenError) { fs.replace(42) } end def test_reverse @@ -1413,16 +1551,41 @@ CODE assert_nil(S("hello").rindex(S("z"))) assert_nil(S("hello").rindex(/z./)) + assert_equal(5, S("hello").rindex(S(""))) + assert_equal(5, S("hello").rindex(S(""), 5)) + assert_equal(4, S("hello").rindex(S(""), 4)) + assert_equal(0, S("hello").rindex(S(""), 0)) + o = Object.new def o.to_str; "bar"; end - assert_equal(6, "foobarbarbaz".rindex(o)) - assert_raise(TypeError) { "foo".rindex(Object.new) } + assert_equal(6, S("foobarbarbaz").rindex(o)) + assert_raise(TypeError) { S("foo").rindex(Object.new) } - assert_nil("foo".rindex(//, -100)) + assert_nil(S("foo").rindex(//, -100)) assert_nil($~) - assert_equal(3, "foo".rindex(//)) + assert_equal(3, S("foo").rindex(//)) assert_equal([3, 3], $~.offset(0)) + + assert_equal(5, S("abcdbce").rindex(/b\Kc/)) + + assert_equal(2, S("こんにちは").rindex(?に)) + assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"))) + assert_equal(6, S("にちは、こんにちは").rindex(/にち./)) + + assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 7)) + assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -2)) + assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), 6)) + assert_equal(6, S("にちは、こんにちは").rindex(S("にちは"), -3)) + assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 5)) + assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), -4)) + assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 1)) + assert_equal(0, S("にちは、こんにちは").rindex(S("にちは"), 0)) + + assert_equal(0, S("こんにちは").rindex(S("こんにちは"))) + assert_nil(S("こんにち").rindex(S("こんにちは"))) + assert_nil(S("こ").rindex(S("こんにちは"))) + assert_nil(S("").rindex(S("こんにちは"))) end def test_rjust @@ -1450,12 +1613,6 @@ CODE a.scan(/(...)/) { |w| res << w } assert_equal([[S("cru")], [S("el ")], [S("wor")]],res) - a = S("hello") - a.taint - res = [] - a.scan(/./) { |w| res << w } - assert_predicate(res[0], :tainted?, '[ruby-core:33338] #4087') - /h/ =~ a a.scan(/x/) assert_nil($~) @@ -1464,7 +1621,16 @@ CODE a.scan('x') assert_nil($~) - assert_equal(3, S("hello hello hello").scan("hello".taint).count(&:tainted?)) + assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./)) + end + + def test_scan_segv + bug19159 = '[Bug #19159]' + assert_nothing_raised(Exception, bug19159) do + ObjectSpace.each_object(MatchData).to_a + "".scan(//) + ObjectSpace.each_object(MatchData).to_a.inspect + end end def test_size @@ -1520,8 +1686,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) @@ -1606,7 +1774,8 @@ CODE assert_equal(S("Bar"), a.slice!(S("Bar"))) assert_equal(S("Foo"), a) - assert_raise(ArgumentError) { "foo".slice! } + a = S("foo") + assert_raise(ArgumentError) { a.slice! } end def test_split @@ -1631,19 +1800,62 @@ CODE assert_equal([S("a"), S(""), S("b"), S("c")], S("a||b|c|").split(S('|'))) assert_equal([S("a"), S(""), S("b"), S("c"), S("")], S("a||b|c|").split(S('|'), -1)) - assert_equal([], "".split(//, 1)) + assert_equal([], S("").split(//, 1)) + ensure + EnvUtil.suppress_warning {$; = fs} + end - assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + def test_split_with_block + fs, $; = $;, nil + result = []; S(" a b\t c ").split {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + result = []; S(" a b\t c ").split(S(" ")) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S(" a | b | c ").split(S("|")) {|s| result << s} + assert_equal([S(" a "), S(" b "), S(" c ")], result) + + result = []; S("aXXbXXcXX").split(/X./) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("abc").split(//) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("a|b|c").split(S('|'), 1) {|s| result << s} + assert_equal([S("a|b|c")], result) + + result = []; S("a|b|c").split(S('|'), 2) {|s| result << s} + assert_equal([S("a"), S("b|c")], result) + result = []; S("a|b|c").split(S('|'), 3) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("a|b|c|").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S("b"), S("c"), S("")], result) + result = []; S("a|b|c||").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S("b"), S("c"), S(""), S("")], result) + + result = []; S("a||b|c|").split(S('|')) {|s| result << s} + assert_equal([S("a"), S(""), S("b"), S("c")], result) + result = []; S("a||b|c|").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S(""), S("b"), S("c"), S("")], result) + + result = []; S("").split(//, 1) {|s| result << s} + assert_equal([], result) + + result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")} + assert_equal(["AAA"]*4, result) ensure - $; = fs + EnvUtil.suppress_warning {$; = fs} end def test_fs + return unless @cls == String + assert_raise_with_message(TypeError, /\$;/) { $; = [] } - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}") bug = '[ruby-core:79582] $; must not be GCed' begin; $; = " " @@ -1666,10 +1878,7 @@ CODE def test_split_wchar bug8642 = '[ruby-core:56036] [Bug #8642]' - [ - Encoding::UTF_16BE, Encoding::UTF_16LE, - Encoding::UTF_32BE, Encoding::UTF_32LE, - ].each do |enc| + WIDE_ENCODINGS.each do |enc| s = S("abc,def".encode(enc)) assert_equal(["abc", "def"].map {|c| c.encode(enc)}, s.split(",".encode(enc)), @@ -1696,6 +1905,12 @@ CODE s.split("b", 1).map(&:upcase!) 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(" "))) @@ -1729,20 +1944,21 @@ CODE bug5536 = '[ruby-core:40623]' assert_raise(TypeError, bug5536) {S("str").start_with? :not_convertible_to_string} - assert_equal(true, "hello".start_with?(/hel/)) + assert_equal(true, S("hello").start_with?(/hel/)) assert_equal("hel", $&) - assert_equal(false, "hello".start_with?(/el/)) + assert_equal(false, S("hello").start_with?(/el/)) assert_nil($&) end 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) + S("\x00 0b0 ").force_encoding("UTF-16BE").strip) assert_equal("0\x000b0 ".force_encoding("UTF-16BE"), - "0\x000b0 ".force_encoding("UTF-16BE").strip) + S("0\x000b0 ").force_encoding("UTF-16BE").strip) end def test_strip! @@ -1756,6 +1972,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) @@ -1801,24 +2021,19 @@ CODE assert_equal(S("a\\&aba"), S("ababa").sub(/b/, '\\\\&')) assert_equal(S("a\\baba"), S("ababa").sub(/b/, '\\\\\&')) - a = S("hello") - a.taint - x = a.sub(/./, S('X')) - assert_predicate(x, :tainted?) - o = Object.new def o.to_str; "bar"; end - assert_equal("fooBARbaz", "foobarbaz".sub(o, "BAR")) + assert_equal("fooBARbaz", S("foobarbaz").sub(o, "BAR")) - assert_raise(TypeError) { "foo".sub(Object.new, "") } + assert_raise(TypeError) { S("foo").sub(Object.new, "") } - assert_raise(ArgumentError) { "foo".sub } + assert_raise(ArgumentError) { S("foo").sub } assert_raise(IndexError) { "foo"[/(?:(o$)|(x))/, 2] = 'bar' } o = Object.new def o.to_s; self; end - assert_match(/^foo#<Object:0x.*>baz$/, "foobarbaz".sub("bar") { o }) + assert_match(/^foo#<Object:0x.*>baz$/, S("foobarbaz").sub("bar") { o }) assert_equal(S("Abc"), S("abc").sub("a", "A")) m = nil @@ -1827,7 +2042,7 @@ CODE assert_equal(/a/, m.regexp) bug = '[ruby-core:78686] [Bug #13042] other than regexp has no name references' assert_raise_with_message(IndexError, /oops/, bug) { - 'hello'.gsub('hello', '\k<oops>') + S('hello').gsub('hello', '\k<oops>') } end @@ -1853,10 +2068,11 @@ CODE a=S("hello") assert_nil(a.sub!(/X/, S('Y'))) - r = S('X') - r.taint - a.sub!(/./, r) - assert_predicate(a, :tainted?) + bug16105 = '[Bug #16105] heap-use-after-free' + a = S("ABCDEFGHIJKLMNOPQRSTUVWXYZ012345678") + b = a.dup + c = a.slice(1, 100) + assert_equal("AABCDEFGHIJKLMNOPQRSTUVWXYZ012345678", b.sub!(c, b), bug16105) end def test_succ @@ -1873,18 +2089,18 @@ CODE assert_equal(S("AAAAA000"), S("ZZZZ999").succ) assert_equal(S("*+"), S("**").succ) - assert_equal("abce", "abcd".succ) - assert_equal("THX1139", "THX1138".succ) - assert_equal("<\<koalb>>", "<\<koala>>".succ) - assert_equal("2000aaa", "1999zzz".succ) - assert_equal("AAAA0000", "ZZZ9999".succ) - assert_equal("**+", "***".succ) + assert_equal("abce", S("abcd").succ) + assert_equal("THX1139", S("THX1138").succ) + assert_equal("<\<koalb>>", S("<\<koala>>").succ) + assert_equal("2000aaa", S("1999zzz").succ) + assert_equal("AAAA0000", S("ZZZ9999").succ) + assert_equal("**+", S("***").succ) - assert_equal("!", " ".succ) - assert_equal("", "".succ) + assert_equal("!", S(" ").succ) + assert_equal("", S("").succ) bug = '[ruby-core:83062] [Bug #13952]' - s = "\xff".b + s = S("\xff").b assert_not_predicate(s, :ascii_only?) assert_predicate(s.succ, :ascii_only?, bug) end @@ -1936,8 +2152,8 @@ CODE assert_equal(S(""), a.succ!) assert_equal(S(""), a) - assert_equal("aaaaaaaaaaaa", "zzzzzzzzzzz".succ!) - assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", "zzzzzzzzzzzzzzzzzzzzzzz".succ!) + assert_equal("aaaaaaaaaaaa", S("zzzzzzzzzzz").succ!) + assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", S("zzzzzzzzzzzzzzzzzzzzzzz").succ!) end def test_sum @@ -1959,8 +2175,8 @@ CODE end def test_sum_2 - assert_equal(0, "".sum) - assert_equal(294, "abc".sum) + assert_equal(0, S("").sum) + assert_equal(294, S("abc").sum) check_sum("abc") check_sum("\x80") -3.upto(70) {|bits| @@ -1979,6 +2195,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! @@ -2005,39 +2223,39 @@ CODE def test_to_i assert_equal(1480, S("1480ft/sec").to_i) assert_equal(0, S("speed of sound in water @20C = 1480ft/sec)").to_i) - assert_equal(0, " 0".to_i) - assert_equal(0, "+0".to_i) - assert_equal(0, "-0".to_i) - assert_equal(0, "--0".to_i) - assert_equal(16, "0x10".to_i(0)) - assert_equal(16, "0X10".to_i(0)) - assert_equal(2, "0b10".to_i(0)) - assert_equal(2, "0B10".to_i(0)) - assert_equal(8, "0o10".to_i(0)) - assert_equal(8, "0O10".to_i(0)) - assert_equal(10, "0d10".to_i(0)) - assert_equal(10, "0D10".to_i(0)) - assert_equal(8, "010".to_i(0)) - assert_raise(ArgumentError) { "010".to_i(-10) } + assert_equal(0, S(" 0").to_i) + assert_equal(0, S("+0").to_i) + assert_equal(0, S("-0").to_i) + assert_equal(0, S("--0").to_i) + assert_equal(16, S("0x10").to_i(0)) + assert_equal(16, S("0X10").to_i(0)) + assert_equal(2, S("0b10").to_i(0)) + assert_equal(2, S("0B10").to_i(0)) + assert_equal(8, S("0o10").to_i(0)) + assert_equal(8, S("0O10").to_i(0)) + assert_equal(10, S("0d10").to_i(0)) + assert_equal(10, S("0D10").to_i(0)) + assert_equal(8, S("010").to_i(0)) + assert_raise(ArgumentError) { S("010").to_i(-10) } 2.upto(36) {|radix| - assert_equal(radix, "10".to_i(radix)) - assert_equal(radix**2, "100".to_i(radix)) + assert_equal(radix, S("10").to_i(radix)) + assert_equal(radix**2, S("100").to_i(radix)) } - assert_raise(ArgumentError) { "0".to_i(1) } - assert_raise(ArgumentError) { "0".to_i(37) } - assert_equal(0, "z".to_i(10)) - assert_equal(12, "1_2".to_i(10)) - assert_equal(0x40000000, "1073741824".to_i(10)) - assert_equal(0x4000000000000000, "4611686018427387904".to_i(10)) - assert_equal(1, "1__2".to_i(10)) - assert_equal(1, "1_z".to_i(10)) + assert_raise(ArgumentError) { S("0").to_i(1) } + assert_raise(ArgumentError) { S("0").to_i(37) } + assert_equal(0, S("z").to_i(10)) + assert_equal(12, S("1_2").to_i(10)) + assert_equal(0x40000000, S("1073741824").to_i(10)) + assert_equal(0x4000000000000000, S("4611686018427387904").to_i(10)) + assert_equal(1, S("1__2").to_i(10)) + assert_equal(1, S("1_z").to_i(10)) bug6192 = '[ruby-core:43566]' - assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16be").to_i} - assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16le").to_i} - assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32be").to_i} - assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32le").to_i} - assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("iso-2022-jp").to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16be")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16le")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32be")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32le")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("iso-2022-jp")).to_i} end def test_to_s @@ -2069,13 +2287,13 @@ CODE assert_equal(S("*e**o"), S("hello").tr(S("^aeiou"), S("*"))) assert_equal(S("hal"), S("ibm").tr(S("b-z"), S("a-z"))) - a = "abc".force_encoding(Encoding::US_ASCII) + a = S("abc".force_encoding(Encoding::US_ASCII)) assert_equal(Encoding::US_ASCII, a.tr(S("z"), S("\u0101")).encoding, '[ruby-core:22326]') - assert_equal("a".hash, "a".tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]') - assert_equal(true, "\u0101".tr("\u0101", "a").ascii_only?) - assert_equal(true, "\u3041".tr("\u3041", "a").ascii_only?) - assert_equal(false, "\u3041\u3042".tr("\u3041", "a").ascii_only?) + assert_equal("a".hash, S("a").tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]') + assert_equal(true, S("\u0101").tr("\u0101", "a").ascii_only?) + assert_equal(true, S("\u3041").tr("\u3041", "a").ascii_only?) + assert_equal(false, S("\u3041\u3042").tr("\u3041", "a").ascii_only?) bug6156 = '[ruby-core:43335]' bug13950 = '[ruby-core:83056] [Bug #13950]' @@ -2106,7 +2324,7 @@ CODE assert_nil(a.tr!(S("B-Z"), S("A-Z"))) assert_equal(S("ibm"), a) - a = "abc".force_encoding(Encoding::US_ASCII) + a = S("abc".force_encoding(Encoding::US_ASCII)) assert_nil(a.tr!(S("z"), S("\u0101")), '[ruby-core:22326]') assert_equal(Encoding::US_ASCII, a.encoding, '[ruby-core:22326]') end @@ -2114,8 +2332,8 @@ CODE def test_tr_s assert_equal(S("hypo"), S("hello").tr_s(S("el"), S("yp"))) assert_equal(S("h*o"), S("hello").tr_s(S("el"), S("*"))) - assert_equal("a".hash, "\u0101\u0101".tr_s("\u0101", "a").hash) - assert_equal(true, "\u3041\u3041".tr("\u3041", "a").ascii_only?) + assert_equal("a".hash, S("\u0101\u0101").tr_s("\u0101", "a").hash) + assert_equal(true, S("\u3041\u3041").tr("\u3041", "a").ascii_only?) end def test_tr_s! @@ -2214,6 +2432,8 @@ 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) + assert_equal(S("\u{10574}"), S("\u{1059B}").upcase) end def test_upcase! @@ -2226,6 +2446,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 @@ -2272,7 +2498,7 @@ CODE end def test_frozen_check - assert_raise(RuntimeError) { + assert_raise(FrozenError) { s = "" s.sub!(/\A/) { s.freeze; "zzz" } } @@ -2281,6 +2507,8 @@ CODE class S2 < String end def test_str_new4 + return unless @cls == String + s = (0..54).to_a.join # length = 100 s2 = S2.new(s[10,90]) s3 = s2[10,80] @@ -2289,7 +2517,7 @@ CODE end def test_rb_str_new4 - s = "a" * 100 + s = S("a" * 100) s2 = s[10,90] assert_equal("a" * 90, s2) s3 = s2[10,80] @@ -2307,11 +2535,11 @@ CODE end def test_rb_str_to_str - assert_equal("ab", "a" + StringLike.new("b")) + assert_equal("ab", S("a") + StringLike.new("b")) end def test_rb_str_shared_replace - s = "a" * 100 + s = S("a" * 100) s.succ! assert_equal("a" * 99 + "b", s) s = "" @@ -2335,12 +2563,12 @@ CODE def test_times2 s1 = '' 100.times {|n| - s2 = "a" * n + s2 = S("a") * n assert_equal(s1, s2) s1 << 'a' } - assert_raise(ArgumentError) { "foo" * (-1) } + assert_raise(ArgumentError) { S("foo") * (-1) } end def test_respond_to @@ -2348,41 +2576,41 @@ CODE def o.respond_to?(arg) [:to_str].include?(arg) ? nil : super end def o.to_str() "" end def o.==(other) "" == other end - assert_equal(false, "" == o) + assert_equal(false, S("") == o) end def test_match_method - assert_equal("bar", "foobarbaz".match(/bar/).to_s) + assert_equal("bar", S("foobarbaz").match(/bar/).to_s) o = Regexp.new('foo') def o.match(x, y, z); x + y + z; end - assert_equal("foobarbaz", "foo".match(o, "bar", "baz")) + assert_equal("foobarbaz", S("foo").match(o, "bar", "baz")) x = nil - "foo".match(o, "bar", "baz") {|y| x = y } + S("foo").match(o, "bar", "baz") {|y| x = y } assert_equal("foobarbaz", x) - assert_raise(ArgumentError) { "foo".match } + assert_raise(ArgumentError) { S("foo").match } end def test_match_p_regexp /backref/ =~ 'backref' # must match here, but not in a separate method, e.g., assert_send, # to check if $~ is affected or not. - assert_equal(true, "".match?(//)) + assert_equal(true, S("").match?(//)) assert_equal(true, :abc.match?(/.../)) - assert_equal(true, 'abc'.match?(/b/)) - assert_equal(true, 'abc'.match?(/b/, 1)) - assert_equal(true, 'abc'.match?(/../, 1)) - assert_equal(true, 'abc'.match?(/../, -2)) - assert_equal(false, 'abc'.match?(/../, -4)) - assert_equal(false, 'abc'.match?(/../, 4)) - assert_equal(true, "\u3042xx".match?(/../, 1)) - assert_equal(false, "\u3042x".match?(/../, 1)) - assert_equal(true, ''.match?(/\z/)) - assert_equal(true, 'abc'.match?(/\z/)) - assert_equal(true, 'Ruby'.match?(/R.../)) - assert_equal(false, 'Ruby'.match?(/R.../, 1)) - assert_equal(false, 'Ruby'.match?(/P.../)) + assert_equal(true, S('abc').match?(/b/)) + assert_equal(true, S('abc').match?(/b/, 1)) + assert_equal(true, S('abc').match?(/../, 1)) + assert_equal(true, S('abc').match?(/../, -2)) + assert_equal(false, S('abc').match?(/../, -4)) + assert_equal(false, S('abc').match?(/../, 4)) + assert_equal(true, S("\u3042xx").match?(/../, 1)) + assert_equal(false, S("\u3042x").match?(/../, 1)) + assert_equal(true, S('').match?(/\z/)) + assert_equal(true, S('abc').match?(/\z/)) + assert_equal(true, S('Ruby').match?(/R.../)) + assert_equal(false, S('Ruby').match?(/R.../, 1)) + assert_equal(false, S('Ruby').match?(/P.../)) assert_equal('backref', $&) end @@ -2390,21 +2618,21 @@ CODE /backref/ =~ 'backref' # must match here, but not in a separate method, e.g., assert_send, # to check if $~ is affected or not. - assert_equal(true, "".match?('')) + assert_equal(true, S("").match?('')) assert_equal(true, :abc.match?('...')) - assert_equal(true, 'abc'.match?('b')) - assert_equal(true, 'abc'.match?('b', 1)) - assert_equal(true, 'abc'.match?('..', 1)) - assert_equal(true, 'abc'.match?('..', -2)) - assert_equal(false, 'abc'.match?('..', -4)) - assert_equal(false, 'abc'.match?('..', 4)) - assert_equal(true, "\u3042xx".match?('..', 1)) - assert_equal(false, "\u3042x".match?('..', 1)) - assert_equal(true, ''.match?('\z')) - assert_equal(true, 'abc'.match?('\z')) - assert_equal(true, 'Ruby'.match?('R...')) - assert_equal(false, 'Ruby'.match?('R...', 1)) - assert_equal(false, 'Ruby'.match?('P...')) + assert_equal(true, S('abc').match?('b')) + assert_equal(true, S('abc').match?('b', 1)) + assert_equal(true, S('abc').match?('..', 1)) + assert_equal(true, S('abc').match?('..', -2)) + assert_equal(false, S('abc').match?('..', -4)) + assert_equal(false, S('abc').match?('..', 4)) + assert_equal(true, S("\u3042xx").match?('..', 1)) + assert_equal(false, S("\u3042x").match?('..', 1)) + assert_equal(true, S('').match?('\z')) + assert_equal(true, S('abc').match?('\z')) + assert_equal(true, S('Ruby').match?('R...')) + assert_equal(false, S('Ruby').match?('R...', 1)) + assert_equal(false, S('Ruby').match?('P...')) assert_equal('backref', $&) end @@ -2424,18 +2652,23 @@ CODE def test_inspect_nul bug8290 = '[ruby-core:54458]' - s = "\0" + "12" + s = S("\0") + "12" assert_equal '"\u000012"', s.inspect, bug8290 - s = "\0".b + "12" + s = S("\0".b) + "12" assert_equal '"\x0012"', s.inspect, bug8290 end + def test_inspect_next_line + bug16842 = '[ruby-core:98231]' + assert_equal '"\\u0085"', 0x85.chr(Encoding::UTF_8).inspect, bug16842 + end + def test_partition - assert_equal(%w(he l lo), "hello".partition(/l/)) - assert_equal(%w(he l lo), "hello".partition("l")) - assert_raise(TypeError) { "hello".partition(1) } + assert_equal(%w(he l lo), S("hello").partition(/l/)) + assert_equal(%w(he l lo), S("hello").partition("l")) + assert_raise(TypeError) { S("hello").partition(1) } def (hyphen = Object.new).to_str; "-"; end - assert_equal(%w(foo - bar), "foo-bar".partition(hyphen), '[ruby-core:23540]') + assert_equal(%w(foo - bar), S("foo-bar").partition(hyphen), '[ruby-core:23540]') bug6206 = '[ruby-dev:45441]' Encoding.list.each do |enc| @@ -2445,20 +2678,24 @@ CODE end assert_equal(["\u30E6\u30FC\u30B6", "@", "\u30C9\u30E1.\u30A4\u30F3"], - "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".partition(/[@.]/)) + S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").partition(/[@.]/)) bug = '[ruby-core:82911]' - hello = "hello" + hello = S("hello") hello.partition("hi").map(&:upcase!) assert_equal("hello", hello, bug) + + assert_equal(["", "", "foo"], S("foo").partition(/^=*/)) + + assert_equal([S("ab"), S("c"), S("dbce")], S("abcdbce").partition(/b\Kc/)) end def test_rpartition - assert_equal(%w(hel l o), "hello".rpartition(/l/)) - assert_equal(%w(hel l o), "hello".rpartition("l")) - assert_raise(TypeError) { "hello".rpartition(1) } + assert_equal(%w(hel l o), S("hello").rpartition(/l/)) + assert_equal(%w(hel l o), S("hello").rpartition("l")) + assert_raise(TypeError) { S("hello").rpartition(1) } def (hyphen = Object.new).to_str; "-"; end - assert_equal(%w(foo - bar), "foo-bar".rpartition(hyphen), '[ruby-core:23540]') + assert_equal(%w(foo - bar), S("foo-bar").rpartition(hyphen), '[ruby-core:23540]') bug6206 = '[ruby-dev:45441]' Encoding.list.each do |enc| @@ -2469,18 +2706,23 @@ CODE bug8138 = '[ruby-dev:47183]' assert_equal(["\u30E6\u30FC\u30B6@\u30C9\u30E1", ".", "\u30A4\u30F3"], - "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".rpartition(/[@.]/), bug8138) + S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").rpartition(/[@.]/), bug8138) bug = '[ruby-core:82911]' hello = "hello" 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 + def test_fs_setter + return unless @cls == String + assert_raise(TypeError) { $/ = 1 } name = "\u{5206 884c}" - assert_separately([], <<-"end;") # do + assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") + do; alias $#{name} $/ assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } end; @@ -2513,16 +2755,19 @@ CODE end def test_gsub_enumerator - assert_normal_exit %q{"abc".gsub(/./).next}, "[ruby-dev:34828]" + e = S("abc").gsub(/./) + assert_equal("a", e.next, "[ruby-dev:34828]") + assert_equal("b", e.next) + assert_equal("c", e.next) end def test_clear_nonasciicompat - assert_equal("", "\u3042".encode("ISO-2022-JP").clear) + assert_equal("", S("\u3042".encode("ISO-2022-JP")).clear) end def test_try_convert - assert_equal(nil, String.try_convert(1)) - assert_equal("foo", String.try_convert("foo")) + assert_equal(nil, @cls.try_convert(1)) + assert_equal("foo", @cls.try_convert("foo")) end def test_substr_negative_begin @@ -2531,52 +2776,55 @@ CODE =begin def test_compare_different_encoding_string - s1 = "\xff".force_encoding("UTF-8") - s2 = "\xff".force_encoding("ISO-2022-JP") + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort) end =end def test_casecmp - assert_equal(0, "FoO".casecmp("fOO")) - assert_equal(1, "FoO".casecmp("BaR")) - assert_equal(-1, "baR".casecmp("FoO")) - assert_equal(1, "\u3042B".casecmp("\u3042a")) + assert_equal(0, S("FoO").casecmp("fOO")) + assert_equal(1, S("FoO").casecmp("BaR")) + assert_equal(-1, S("baR").casecmp("FoO")) + assert_equal(1, S("\u3042B").casecmp("\u3042a")) + assert_equal(-1, S("foo").casecmp("foo\0")) - assert_nil("foo".casecmp(:foo)) - assert_nil("foo".casecmp(Object.new)) + assert_nil(S("foo").casecmp(:foo)) + assert_nil(S("foo").casecmp(Object.new)) o = Object.new def o.to_str; "fOO"; end - assert_equal(0, "FoO".casecmp(o)) + assert_equal(0, S("FoO").casecmp(o)) end def test_casecmp? - assert_equal(true, 'FoO'.casecmp?('fOO')) - assert_equal(false, 'FoO'.casecmp?('BaR')) - assert_equal(false, 'baR'.casecmp?('FoO')) - assert_equal(true, 'äöü'.casecmp?('ÄÖÜ')) + assert_equal(true, S('FoO').casecmp?('fOO')) + assert_equal(false, S('FoO').casecmp?('BaR')) + assert_equal(false, S('baR').casecmp?('FoO')) + assert_equal(true, S('äöü').casecmp?('ÄÖÜ')) + assert_equal(false, S("foo").casecmp?("foo\0")) - assert_nil("foo".casecmp?(:foo)) - assert_nil("foo".casecmp?(Object.new)) + assert_nil(S("foo").casecmp?(:foo)) + assert_nil(S("foo").casecmp?(Object.new)) o = Object.new def o.to_str; "fOO"; end - assert_equal(true, "FoO".casecmp?(o)) + assert_equal(true, S("FoO").casecmp?(o)) end def test_upcase2 - assert_equal("\u3042AB", "\u3042aB".upcase) + assert_equal("\u3042AB", S("\u3042aB").upcase) end def test_downcase2 - assert_equal("\u3042ab", "\u3042aB".downcase) + assert_equal("\u3042ab", S("\u3042aB").downcase) end def test_rstrip - assert_equal(" hello", " hello ".rstrip) - assert_equal("\u3042", "\u3042 ".rstrip) - assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip } + assert_equal(" hello", S(" hello ").rstrip) + assert_equal("\u3042", S("\u3042 ").rstrip) + assert_equal("\u3042", S("\u3042\u0000").rstrip) + assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip } end def test_rstrip_bang @@ -2596,12 +2844,22 @@ CODE assert_equal(nil, s4.rstrip!) assert_equal("\u3042", s4) - assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! } + s5 = S("\u3042\u0000") + assert_equal("\u3042", s5.rstrip!) + assert_equal("\u3042", s5) + + assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc \x80 ".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc\x80 ".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc \x80".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("\x80".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S(" \x80 ".force_encoding('UTF-8')).rstrip! } end def test_lstrip - assert_equal("hello ", " hello ".lstrip) - assert_equal("\u3042", " \u3042".lstrip) + assert_equal("hello ", S(" hello ").lstrip) + assert_equal("\u3042", S(" \u3042").lstrip) + assert_equal("hello ", S("\x00hello ").lstrip) end def test_lstrip_bang @@ -2620,12 +2878,17 @@ 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 - assert_raise(TypeError) { 'hello'.delete_prefix(nil) } - assert_raise(TypeError) { 'hello'.delete_prefix(1) } - assert_raise(TypeError) { 'hello'.delete_prefix(/hel/) } + assert_raise(TypeError) { S('hello').delete_prefix(nil) } + assert_raise(TypeError) { S('hello').delete_prefix(1) } + assert_raise(TypeError) { S('hello').delete_prefix(/hel/) } s = S("hello") assert_equal("lo", s.delete_prefix('hel')) @@ -2669,9 +2932,9 @@ CODE end def test_delete_prefix_bang - assert_raise(TypeError) { 'hello'.delete_prefix!(nil) } - assert_raise(TypeError) { 'hello'.delete_prefix!(1) } - assert_raise(TypeError) { 'hello'.delete_prefix!(/hel/) } + assert_raise(TypeError) { S('hello').delete_prefix!(nil) } + assert_raise(TypeError) { S('hello').delete_prefix!(1) } + assert_raise(TypeError) { S('hello').delete_prefix!(/hel/) } s = S("hello") assert_equal("lo", s.delete_prefix!('hel')) @@ -2710,7 +2973,7 @@ CODE assert_equal("bba", s) s = S("ax").freeze - assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!("a")} + assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!("a")} s = S("ax") o = Struct.new(:s).new(s) @@ -2718,13 +2981,13 @@ CODE s.freeze "a" end - assert_raise_with_message(RuntimeError, /frozen/) {s.delete_prefix!(o)} + assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!(o)} end def test_delete_suffix - assert_raise(TypeError) { 'hello'.delete_suffix(nil) } - assert_raise(TypeError) { 'hello'.delete_suffix(1) } - assert_raise(TypeError) { 'hello'.delete_suffix(/hel/) } + assert_raise(TypeError) { S('hello').delete_suffix(nil) } + assert_raise(TypeError) { S('hello').delete_suffix(1) } + assert_raise(TypeError) { S('hello').delete_suffix(/hel/) } s = S("hello") assert_equal("hel", s.delete_suffix('lo')) @@ -2773,12 +3036,12 @@ CODE end def test_delete_suffix_bang - assert_raise(TypeError) { 'hello'.delete_suffix!(nil) } - assert_raise(TypeError) { 'hello'.delete_suffix!(1) } - assert_raise(TypeError) { 'hello'.delete_suffix!(/hel/) } + assert_raise(TypeError) { S('hello').delete_suffix!(nil) } + assert_raise(TypeError) { S('hello').delete_suffix!(1) } + assert_raise(TypeError) { S('hello').delete_suffix!(/hel/) } s = S("hello").freeze - assert_raise_with_message(RuntimeError, /frozen/) {s.delete_suffix!('lo')} + assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!('lo')} s = S("ax") o = Struct.new(:s).new(s) @@ -2786,7 +3049,7 @@ CODE s.freeze "x" end - assert_raise_with_message(RuntimeError, /frozen/) {s.delete_suffix!(o)} + assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!(o)} s = S("hello") assert_equal("hel", s.delete_suffix!('lo')) @@ -2846,8 +3109,25 @@ CODE end =end + def test_nesting_shared + a = ('a' * 24).encode(Encoding::ASCII).gsub('x', '') + hash = {} + hash[a] = true + assert_equal(('a' * 24), a) + 4.times { GC.start } + assert_equal(('a' * 24), a, '[Bug #15792]') + end + + def test_nesting_shared_b + a = ('j' * 24).b.b + eval('', binding, a) + assert_equal(('j' * 24), a) + 4.times { GC.start } + assert_equal(('j' * 24), a, '[Bug #15934]') + end + def test_shared_force_encoding - s = "\u{3066}\u{3059}\u{3068}".gsub(//, '') + s = S("\u{3066}\u{3059}\u{3068}").gsub(//, '') h = {} h[s] = nil k = h.keys[0] @@ -2861,18 +3141,17 @@ CODE def test_ascii_incomat_inspect bug4081 = '[ruby-core:33283]' - [Encoding::UTF_16LE, Encoding::UTF_16BE, - Encoding::UTF_32LE, Encoding::UTF_32BE].each do |e| - assert_equal('"abc"', "abc".encode(e).inspect) - assert_equal('"\\u3042\\u3044\\u3046"', "\u3042\u3044\u3046".encode(e).inspect) - assert_equal('"ab\\"c"', "ab\"c".encode(e).inspect, bug4081) + WIDE_ENCODINGS.each do |e| + assert_equal('"abc"', S("abc".encode(e)).inspect) + assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect) + assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081) end begin verbose, $VERBOSE = $VERBOSE, nil ext = Encoding.default_external Encoding.default_external = "us-ascii" $VERBOSE = verbose - i = "abc\"\\".force_encoding("utf-8").inspect + i = S("abc\"\\".force_encoding("utf-8")).inspect ensure $VERBOSE = nil Encoding.default_external = ext @@ -2883,11 +3162,11 @@ CODE def test_dummy_inspect assert_equal('"\e\x24\x42\x22\x4C\x22\x68\e\x28\x42"', - "\u{ffe2}\u{2235}".encode("cp50220").inspect) + S("\u{ffe2}\u{2235}".encode("cp50220")).inspect) end def test_prepend - assert_equal(S("hello world!"), "!".prepend("hello ", "world")) + assert_equal(S("hello world!"), S("!").prepend("hello ", "world")) b = S("ue") assert_equal(S("ueueue"), b.prepend(b, b)) @@ -2895,7 +3174,7 @@ CODE def foo.to_str "b" end - assert_equal(S("ba"), "a".prepend(foo)) + assert_equal(S("ba"), S("a").prepend(foo)) a = S("world") b = S("hello ") @@ -2909,34 +3188,34 @@ CODE end def test_byteslice - assert_equal("h", "hello".byteslice(0)) - assert_equal(nil, "hello".byteslice(5)) - assert_equal("o", "hello".byteslice(-1)) - assert_equal(nil, "hello".byteslice(-6)) - - assert_equal("", "hello".byteslice(0, 0)) - assert_equal("hello", "hello".byteslice(0, 6)) - assert_equal("hello", "hello".byteslice(0, 6)) - assert_equal("", "hello".byteslice(5, 1)) - assert_equal("o", "hello".byteslice(-1, 6)) - assert_equal(nil, "hello".byteslice(-6, 1)) - assert_equal(nil, "hello".byteslice(0, -1)) - - assert_equal("h", "hello".byteslice(0..0)) - assert_equal("", "hello".byteslice(5..0)) - assert_equal("o", "hello".byteslice(4..5)) - assert_equal(nil, "hello".byteslice(6..0)) - assert_equal("", "hello".byteslice(-1..0)) - assert_equal("llo", "hello".byteslice(-3..5)) - - assert_equal(u("\x81"), "\u3042".byteslice(1)) - assert_equal(u("\x81\x82"), "\u3042".byteslice(1, 2)) - assert_equal(u("\x81\x82"), "\u3042".byteslice(1..2)) - - assert_equal(u("\x82")+("\u3042"*9), ("\u3042"*10).byteslice(2, 28)) + assert_equal("h", S("hello").byteslice(0)) + assert_equal(nil, S("hello").byteslice(5)) + assert_equal("o", S("hello").byteslice(-1)) + assert_equal(nil, S("hello").byteslice(-6)) + + assert_equal("", S("hello").byteslice(0, 0)) + assert_equal("hello", S("hello").byteslice(0, 6)) + assert_equal("hello", S("hello").byteslice(0, 6)) + assert_equal("", S("hello").byteslice(5, 1)) + assert_equal("o", S("hello").byteslice(-1, 6)) + assert_equal(nil, S("hello").byteslice(-6, 1)) + assert_equal(nil, S("hello").byteslice(0, -1)) + + assert_equal("h", S("hello").byteslice(0..0)) + assert_equal("", S("hello").byteslice(5..0)) + assert_equal("o", S("hello").byteslice(4..5)) + assert_equal(nil, S("hello").byteslice(6..0)) + assert_equal("", S("hello").byteslice(-1..0)) + assert_equal("llo", S("hello").byteslice(-3..5)) + + assert_equal(u("\x81"), S("\u3042").byteslice(1)) + assert_equal(u("\x81\x82"), S("\u3042").byteslice(1, 2)) + assert_equal(u("\x81\x82"), S("\u3042").byteslice(1..2)) + + assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28)) bug7954 = '[ruby-dev:47108]' - assert_equal(false, "\u3042".byteslice(0, 2).valid_encoding?, bug7954) + assert_equal(false, S("\u3042").byteslice(0, 2).valid_encoding?, bug7954) assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954) end @@ -2951,6 +3230,8 @@ CODE end def test_eq_tilde_can_be_overridden + return unless @cls == String + assert_separately([], <<-RUBY) class String undef =~ @@ -2968,7 +3249,7 @@ CODE end def test_regexp_match_subclass - s = Bug9581.new("abc") + s = Bug9581.new(S("abc")) r = /abc/ assert_equal(:foo, s =~ r) assert_equal(:foo, s.send(:=~, r)) @@ -2978,6 +3259,7 @@ CODE def test_LSHIFT_neary_long_max return unless @cls == String + assert_ruby_status([], <<-'end;', '[ruby-core:61886] [Bug #9709]', timeout: 20) begin a = "a" * 0x4000_0000 @@ -2989,6 +3271,8 @@ CODE # enable only when string size range is smaller than memory space def test_uplus_minus + return unless @cls == String + str = "foo" assert_not_predicate(str, :frozen?) assert_not_predicate(+str, :frozen?) @@ -3009,18 +3293,213 @@ CODE assert_same(str, -bar, "uminus deduplicates [Feature #13077]") end + def test_uminus_frozen + return unless @cls == String + + # 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 = S("foo") + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + + str = S("foo") + str.instance_variable_set(:@iv, 1) + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + assert_equal(1, str.instance_variable_get(:@iv)) + + str = S("foo") + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + end + def test_ord - assert_equal(97, "a".ord) - assert_equal(97, "abc".ord) - assert_equal(0x3042, "\u3042\u3043".ord) - assert_raise(ArgumentError) { "".ord } + assert_equal(97, S("a").ord) + assert_equal(97, S("abc").ord) + assert_equal(0x3042, S("\u3042\u3043").ord) + assert_raise(ArgumentError) { S("").ord } end def test_chr - assert_equal("a", "abcde".chr) - assert_equal("a", "a".chr) - assert_equal("\u3042", "\u3042\u3043".chr) - assert_equal('', ''.chr) + assert_equal("a", S("abcde").chr) + assert_equal("a", S("a").chr) + assert_equal("\u3042", S("\u3042\u3043").chr) + assert_equal('', S('').chr) + end + + def test_substr_code_range + data = S("\xff" + "a"*200) + assert_not_predicate(data, :valid_encoding?) + assert_predicate(data[100..-1], :valid_encoding?) + end + + def test_byteindex + assert_equal(0, S("hello").byteindex(?h)) + assert_equal(1, S("hello").byteindex(S("ell"))) + assert_equal(2, S("hello").byteindex(/ll./)) + + assert_equal(3, S("hello").byteindex(?l, 3)) + assert_equal(3, S("hello").byteindex(S("l"), 3)) + assert_equal(3, S("hello").byteindex(/l./, 3)) + + assert_nil(S("hello").byteindex(?z, 3)) + assert_nil(S("hello").byteindex(S("z"), 3)) + assert_nil(S("hello").byteindex(/z./, 3)) + + assert_nil(S("hello").byteindex(?z)) + assert_nil(S("hello").byteindex(S("z"))) + assert_nil(S("hello").byteindex(/z./)) + + assert_equal(0, S("").byteindex(S(""))) + assert_equal(0, S("").byteindex(//)) + assert_nil(S("").byteindex(S("hello"))) + assert_nil(S("").byteindex(/hello/)) + assert_equal(0, S("hello").byteindex(S(""))) + assert_equal(0, S("hello").byteindex(//)) + + s = S("long") * 1000 << "x" + assert_nil(s.byteindex(S("y"))) + assert_equal(4 * 1000, s.byteindex(S("x"))) + s << "yx" + assert_equal(4 * 1000, s.byteindex(S("x"))) + assert_equal(4 * 1000, s.byteindex(S("xyx"))) + + o = Object.new + def o.to_str; "bar"; end + assert_equal(3, S("foobarbarbaz").byteindex(o)) + assert_raise(TypeError) { S("foo").byteindex(Object.new) } + + assert_nil(S("foo").byteindex(//, -100)) + assert_nil($~) + + assert_equal(2, S("abcdbce").byteindex(/b\Kc/)) + + assert_equal(0, S("こんにちは").byteindex(?こ)) + assert_equal(3, S("こんにちは").byteindex(S("んにち"))) + assert_equal(6, S("こんにちは").byteindex(/にち./)) + + assert_equal(0, S("にんにちは").byteindex(?に, 0)) + assert_raise(IndexError) { S("にんにちは").byteindex(?に, 1) } + assert_raise(IndexError) { S("にんにちは").byteindex(?に, 5) } + assert_equal(6, S("にんにちは").byteindex(?に, 6)) + assert_equal(6, S("にんにちは").byteindex(S("に"), 6)) + assert_equal(6, S("にんにちは").byteindex(/に./, 6)) + assert_raise(IndexError) { S("にんにちは").byteindex(?に, 7) } + + s = S("foobarbarbaz") + assert !1000.times.any? {s.byteindex("", 100_000_000)} + end + + def test_byterindex + assert_equal(3, S("hello").byterindex(?l)) + assert_equal(6, S("ell, hello").byterindex(S("ell"))) + assert_equal(7, S("ell, hello").byterindex(/ll./)) + + assert_equal(3, S("hello,lo").byterindex(?l, 3)) + assert_equal(3, S("hello,lo").byterindex(S("l"), 3)) + assert_equal(3, S("hello,lo").byterindex(/l./, 3)) + + assert_nil(S("hello").byterindex(?z, 3)) + assert_nil(S("hello").byterindex(S("z"), 3)) + assert_nil(S("hello").byterindex(/z./, 3)) + + assert_nil(S("hello").byterindex(?z)) + assert_nil(S("hello").byterindex(S("z"))) + assert_nil(S("hello").byterindex(/z./)) + + assert_equal(5, S("hello").byterindex(S(""))) + assert_equal(5, S("hello").byterindex(S(""), 5)) + assert_equal(4, S("hello").byterindex(S(""), 4)) + assert_equal(0, S("hello").byterindex(S(""), 0)) + + o = Object.new + def o.to_str; "bar"; end + assert_equal(6, S("foobarbarbaz").byterindex(o)) + assert_raise(TypeError) { S("foo").byterindex(Object.new) } + + assert_nil(S("foo").byterindex(//, -100)) + assert_nil($~) + + assert_equal(3, S("foo").byterindex(//)) + assert_equal([3, 3], $~.offset(0)) + + assert_equal(5, S("abcdbce").byterindex(/b\Kc/)) + + assert_equal(6, S("こんにちは").byterindex(?に)) + assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"))) + assert_equal(18, S("にちは、こんにちは").byterindex(/にち./)) + + assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 19) } + assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -2) } + assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), 18)) + assert_equal(18, S("にちは、こんにちは").byterindex(S("にちは"), -3)) + assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 17) } + assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), -4) } + assert_raise(IndexError) { S("にちは、こんにちは").byterindex(S("にちは"), 1) } + assert_equal(0, S("にちは、こんにちは").byterindex(S("にちは"), 0)) + + assert_equal(0, S("こんにちは").byterindex(S("こんにちは"))) + assert_nil(S("こんにち").byterindex(S("こんにちは"))) + assert_nil(S("こ").byterindex(S("こんにちは"))) + assert_nil(S("").byterindex(S("こんにちは"))) + end + + def test_bytesplice + assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "xxx") + assert_bytesplice_result("xxxhello", S("hello"), -5, 0, "xxx") + assert_bytesplice_result("xxxhello", S("hello"), 0, 0, "xxx") + assert_bytesplice_result("xxxello", S("hello"), 0, 1, "xxx") + assert_bytesplice_result("xxx", S("hello"), 0, 5, "xxx") + assert_bytesplice_result("xxx", S("hello"), 0, 6, "xxx") + + assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "xxx") + assert_bytesplice_result("xxxhello", S("hello"), -5...-5, "xxx") + assert_bytesplice_result("xxxhello", S("hello"), 0...0, "xxx") + assert_bytesplice_result("xxxello", S("hello"), 0..0, "xxx") + assert_bytesplice_result("xxxello", S("hello"), 0...1, "xxx") + assert_bytesplice_result("xxxllo", S("hello"), 0..1, "xxx") + assert_bytesplice_result("xxx", S("hello"), 0..-1, "xxx") + assert_bytesplice_result("xxx", S("hello"), 0...5, "xxx") + assert_bytesplice_result("xxx", S("hello"), 0...6, "xxx") + + assert_bytesplice_raise(TypeError, S("hello"), 0, "xxx") + + assert_bytesplice_raise(IndexError, S("こんにちは"), -16, 0, "xxx") + assert_bytesplice_result("xxxこんにちは", S("こんにちは"), -15, 0, "xxx") + assert_bytesplice_result("xxxこんにちは", S("こんにちは"), 0, 0, "xxx") + assert_bytesplice_raise(IndexError, S("こんにちは"), 1, 0, "xxx") + assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 1, "xxx") + assert_bytesplice_raise(IndexError, S("こんにちは"), 0, 2, "xxx") + assert_bytesplice_result("xxxんにちは", S("こんにちは"), 0, 3, "xxx") + assert_bytesplice_result("こんにちはxxx", S("こんにちは"), 15, 0, "xxx") + + assert_bytesplice_result("", S(""), 0, 0, "") + assert_bytesplice_result("xxx", S(""), 0, 0, "xxx") + end + + private + + def assert_bytesplice_result(expected, s, *args) + assert_equal(expected, s.send(:bytesplice, *args)) + assert_equal(expected, s) + end + + def assert_bytesplice_raise(e, s, *args) + assert_raise(e) { s.send(:bytesplice, *args) } end end diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 573f65d0b9..8a5706b18c 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 @@ -60,6 +64,10 @@ module TestStruct assert_equal(1, o.a) end + def test_attrset_id + assert_raise(ArgumentError) { Struct.new(:x=) } + end + def test_members klass = @Struct.new(:a) o = klass.new(1) @@ -92,6 +100,56 @@ module TestStruct assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members) end + def test_struct_new_with_hash + assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {})} + assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {name: "b"})} + end + + def test_struct_new_with_keyword_init + @Struct.new("KeywordInitTrue", :a, :b, keyword_init: true) + @Struct.new("KeywordInitFalse", :a, :b, keyword_init: false) + + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new({a: 100}, 2) } + assert_nothing_raised { @Struct::KeywordInitFalse.new(1, 2) } + assert_nothing_raised { @Struct::KeywordInitTrue.new(a: 1, b: 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, b: 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(a: 1, b: 2, c: 3) } + 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 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) + remove_const(:KeywordInitFalse) + end + end + + def test_struct_new_with_keyword_init_and_block + struct = @Struct.new(:a, :b, keyword_init: true) do + def c + a + b + end + end + + 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) } @@ -103,6 +161,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) @@ -193,6 +262,13 @@ module TestStruct assert_raise(ArgumentError) { o.select(1) } end + def test_filter + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal([1, 3, 5], o.filter {|v| v % 2 != 0 }) + assert_raise(ArgumentError) { o.filter(1) } + end + def test_big_struct klass1 = @Struct.new(*('a'..'z').map(&:to_sym)) o = klass1.new @@ -249,6 +325,7 @@ module TestStruct klass = @Struct.new(:a) o = klass.new(1) assert_kind_of(Integer, o.hash) + assert_kind_of(String, o.hash.to_s) end def test_eql @@ -274,15 +351,30 @@ 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 + assert_warn('') { assert_equal(1, @Struct.new(:a).new(a: 1).a) } + assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: nil).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) } + assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) } + 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]') @@ -292,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 @@ -336,6 +432,13 @@ module TestStruct assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h) end + def test_to_h_block + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36}, + o.to_h {|k, v| [k.to_s, v*v]}) + end + def test_question_mark_in_member klass = @Struct.new(:a, :b?) x = Object.new @@ -367,13 +470,76 @@ module TestStruct assert_nil(o.dig(:b, 0)) end - def test_new_dupilicate + def test_new_duplicate bug12291 = '[ruby-core:74971] [Bug #12291]' assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) { @Struct.new(:a, :a) } end + def test_deconstruct_keys + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + assert_equal({a: 1, b: 2}, o.deconstruct_keys(nil)) + assert_equal({a: 1, b: 2}, o.deconstruct_keys([:b, :a])) + assert_equal({a: 1}, o.deconstruct_keys([:a])) + assert_not_send([o.deconstruct_keys([:a, :c]), :key?, :c]) + assert_raise(TypeError) { + o.deconstruct_keys(0) + } + end + + def test_public_send + klass = @Struct.new(:a) + x = klass.new(1) + assert_equal(1, x.public_send("a")) + assert_equal(42, x.public_send("a=", 42)) + assert_equal(42, x.public_send("a")) + end + + def test_arity + klass = @Struct.new(:a) + assert_equal 0, klass.instance_method(:a).arity + assert_equal 1, klass.instance_method(:a=).arity + + klass.module_eval do + define_method(:b=, instance_method(:a=)) + alias c= a= + end + + assert_equal 1, klass.instance_method(:b=).arity + assert_equal 1, klass.instance_method(:c=).arity + end + + def test_parameters + klass = @Struct.new(:a) + assert_equal [], klass.instance_method(:a).parameters + # NOTE: :_ may not be a spec. + assert_equal [[:req, :_]], klass.instance_method(:a=).parameters + + klass.module_eval do + define_method(:b=, instance_method(:a=)) + alias c= a= + end + + assert_equal [[:req, :_]], klass.instance_method(:b=).parameters + assert_equal [[:req, :_]], klass.instance_method(:c=).parameters + end + + def test_named_structs_are_not_rooted + # [Bug #20311] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + Struct.new("A") + Struct.send(:remove_const, :A) + end + + 1_000.times(&code) + PREP + 50_000.times(&code) + CODE + end + class TopStruct < Test::Unit::TestCase include TestStruct diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index 9691116fb4..08be568660 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -102,11 +102,11 @@ class TestSuper < Test::Unit::TestCase def test_optional2 assert_raise(ArgumentError) do # call Base#optional with 2 arguments; the 2nd arg is supplied - assert_equal(9, Optional2.new.optional(9)) + Optional2.new.optional(9) end assert_raise(ArgumentError) do # call Base#optional with 2 arguments - assert_equal(9, Optional2.new.optional(9, 2)) + Optional2.new.optional(9, 2) end end def test_optional3 @@ -307,6 +307,29 @@ class TestSuper < Test::Unit::TestCase end end + def test_super_in_instance_eval_in_module + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + mod = EnvUtil.labeled_module("Mod\u{30af 30e9 30b9}") { + def foo + x = Object.new + x.instance_eval do + super() + end + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + include mod + } + obj = sub_class.new + assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do + obj.foo + end + end + def test_super_in_orphan_block super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { def foo @@ -498,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 @@ -544,4 +604,126 @@ class TestSuper < Test::Unit::TestCase c.new } end + + def test_super_with_included_prepended_module_method_caching_bug_20716 + a = Module.new do + def test(*args) + super + end + end + + b = Module.new do + def test(a) + a + end + end + + c = Class.new + + b.prepend(a) + c.include(b) + + assert_equal(1, c.new.test(1)) + + b.class_eval do + def test + :test + end + end + + assert_equal(:test, c.new.test) + end + + class TestFor_super_with_modified_rest_parameter_base + def foo *args + args + end + end + + class TestFor_super_with_modified_rest_parameter < TestFor_super_with_modified_rest_parameter_base + def foo *args + args = 13 + super + end + end + 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 36cbf4a710..1d2a18d734 100644 --- a/test/ruby/test_symbol.rb +++ b/test/ruby/test_symbol.rb @@ -36,6 +36,19 @@ class TestSymbol < Test::Unit::TestCase assert_eval_inspected(:"@@1", false) assert_eval_inspected(:"@", false) assert_eval_inspected(:"@@", false) + assert_eval_inspected(:"[]=") + assert_eval_inspected(:"[][]", false) + assert_eval_inspected(:"[][]=", false) + assert_eval_inspected(:"@=", false) + assert_eval_inspected(:"@@=", false) + assert_eval_inspected(:"@x=", false) + assert_eval_inspected(:"@@x=", false) + assert_eval_inspected(:"$$=", false) + assert_eval_inspected(:"$==", false) + assert_eval_inspected(:"$x=", false) + assert_eval_inspected(:"$$$=", false) + assert_eval_inspected(:"foo?=", false) + assert_eval_inspected(:"foo!=", false) end def assert_inspect_evaled(n) @@ -105,6 +118,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 +172,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" @@ -161,6 +188,53 @@ class TestSymbol < Test::Unit::TestCase assert_equal(1, first, bug11594) end + class TestToPRocArgWithRefinements; end + 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 + :hoge + end + end + } + def test_to_proc_arg_with_refinements + 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 + _test_to_proc_arg_with_refinements_call(&:hoge) + using Module.new { + refine TestToPRocArgWithRefinements do + def hoge + :hogehoge + end + end + } + def test_to_proc_arg_with_refinements_override + assert_equal(:hogehoge, _test_to_proc_arg_with_refinements_call(&:hoge)) + end + + def test_to_proc_arg_with_refinements_undefined + assert_raise(NoMethodError) do + _test_to_proc_arg_with_refinements_call(&:foo) + end + end + private def return_from_proc Proc.new { return 1 }.tap(&:call) end @@ -183,11 +257,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 @@ -468,12 +542,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 @@ -497,14 +573,6 @@ class TestSymbol < Test::Unit::TestCase end; end - def test_not_freeze - bug11721 = '[ruby-core:71611] [Bug #11721]' - str = "\u{1f363}".taint - assert_not_predicate(str, :frozen?) - assert_equal str, str.to_sym.to_s - assert_not_predicate(str, :frozen?, bug11721) - end - def test_hash_nondeterministic ruby = EnvUtil.rubybin assert_not_equal :foo.hash, `#{ruby} -e 'puts :foo.hash'`.to_i, @@ -527,4 +595,27 @@ class TestSymbol < Test::Unit::TestCase puts :a == :a RUBY end + + def test_start_with? + assert_equal(true, :hello.start_with?("hel")) + assert_equal(false, :hello.start_with?("el")) + assert_equal(true, :hello.start_with?("el", "he")) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {:str.start_with? :not_convertible_to_string} + + assert_equal(true, :hello.start_with?(/hel/)) + assert_equal("hel", $&) + assert_equal(false, :hello.start_with?(/el/)) + assert_nil($&) + end + + def test_end_with? + assert_equal(true, :hello.end_with?("llo")) + assert_equal(false, :hello.end_with?("ll")) + assert_equal(true, :hello.end_with?("el", "lo")) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {:str.end_with? :not_convertible_to_string} + end end diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 2014e289d3..7553cd74a6 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -46,7 +46,7 @@ class TestSyntax < Test::Unit::TestCase assert_raise(ArgumentError, enc.name) {load(f.path)} end ensure - f.close! if f + f&.close! end def test_script_lines @@ -63,7 +63,120 @@ class TestSyntax < Test::Unit::TestCase end end ensure - f.close! if f + f&.close! + end + + def test_script_lines_encoding + require 'tmpdir' + Dir.mktmpdir do |dir| + File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n") + assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name", + %w"EUC-JP", /-K is specified/, chdir: dir) + end + end + + def test_anonymous_block_forwarding + assert_syntax_error("def b; c(&); end", /no anonymous block parameter/) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(&); c(&) end + def c(&); yield 1 end + a = nil + b{|c| a = c} + assert_equal(1, a) + + def inner + yield + end + + def block_only(&) + inner(&) + end + assert_equal(1, block_only{1}) + + def pos(arg1, &) + inner(&) + end + assert_equal(2, pos(nil){2}) + + def pos_kwrest(arg1, **kw, &) + inner(&) + end + assert_equal(3, pos_kwrest(nil){3}) + + def no_kw(arg1, **nil, &) + inner(&) + end + assert_equal(4, no_kw(nil){4}) + + def rest_kw(*a, kwarg: 1, &) + inner(&) + end + assert_equal(5, rest_kw{5}) + + def kw(kwarg:1, &) + inner(&) + end + assert_equal(6, kw{6}) + + def pos_kw_kwrest(arg1, kwarg:1, **kw, &) + inner(&) + end + assert_equal(7, pos_kw_kwrest(nil){7}) + + def pos_rkw(arg1, kwarg1:, &) + inner(&) + end + assert_equal(8, pos_rkw(nil, kwarg1: nil){8}) + + def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &) + inner(&) + end + assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9}) + + def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &) + inner(&) + end + assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) + end; + end + + def test_anonymous_rest_forwarding + assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/) + assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(*); c(*) end + def c(*a); a end + def d(*); b(*, *) end + assert_equal([1, 2], b(1, 2)) + assert_equal([1, 2, 1, 2], d(1, 2)) + end; + end + + def test_anonymous_keyword_rest_forwarding + assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(**); c(**) end + def c(**kw); kw end + def d(**); b(k: 1, **) end + def e(**); b(**, k: 1) end + def f(a: nil, **); b(**) end + assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) + assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) + assert_equal({k: 3}, f(a: 1, k: 3)) + end; + end + + def test_argument_forwarding_with_anon_rest_kwrest_and_block + assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./) + assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/) end def test_newline_in_block_parameters @@ -93,6 +206,66 @@ 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} + a = [] + assert_equal([], [**{}]) + assert_equal([], [**kw]) + assert_equal([h], [**h]) + assert_equal([{}], [{}]) + assert_equal([kw], [kw]) + assert_equal([h], [h]) + + assert_equal([1], [1, **{}]) + assert_equal([1], [1, **kw]) + assert_equal([1, h], [1, **h]) + assert_equal([1, {}], [1, {}]) + assert_equal([1, kw], [1, kw]) + assert_equal([1, h], [1, h]) + + assert_equal([], [*a, **{}]) + assert_equal([], [*a, **kw]) + assert_equal([h], [*a, **h]) + assert_equal([{}], [*a, {}]) + assert_equal([kw], [*a, kw]) + assert_equal([h], [*a, h]) + + assert_equal([1], [1, *a, **{}]) + assert_equal([1], [1, *a, **kw]) + assert_equal([1, h], [1, *a, **h]) + assert_equal([1, {}], [1, *a, {}]) + assert_equal([1, kw], [1, *a, kw]) + assert_equal([1, h], [1, *a, h]) + + assert_equal([], [**kw, **kw]) + assert_equal([], [**kw, **{}, **kw]) + assert_equal([1], [1, **kw, **{}, **kw]) + + assert_equal([{}], [{}, **kw, **kw]) + assert_equal([kw], [kw, **kw, **kw]) + assert_equal([h], [h, **kw, **kw]) + assert_equal([h, h], [h, **kw, **kw, **h]) + + assert_equal([h, {:a=>2}], [h, **{}, **h, a: 2]) + assert_equal([h, h], [h, **{}, a: 2, **h]) + assert_equal([h, h], [h, a: 2, **{}, **h]) + assert_equal([h, h], [h, a: 2, **h, **{}]) + assert_equal([h, {:a=>2}], [h, **h, a: 2, **{}]) + assert_equal([h, {:a=>2}], [h, **h, **{}, a: 2]) + end + def test_normal_argument assert_valid_syntax('def foo(x) end') assert_syntax_error('def foo(X) end', /constant/) @@ -148,24 +321,36 @@ class TestSyntax < Test::Unit::TestCase h = {k3: 31} assert_raise(ArgumentError) {o.kw(**h)} h = {"k1"=>11, k2: 12} - assert_raise(TypeError) {o.kw(**h)} + 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 @@ -179,36 +364,33 @@ class TestSyntax < Test::Unit::TestCase bug13756 = '[ruby-core:82113] [Bug #13756]' assert_valid_syntax("defined? foo(**{})", bug13756) end; + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bug15271 = '[ruby-core:89648] [Bug #15271]' + assert_valid_syntax("a **{}", bug15271) + end; end def test_keyword_self_reference - bug9593 = '[ruby-core:61299] [Bug #9593]' - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var: defined?(var)) var end") - end - assert_equal(42, o.foo(var: 42)) - assert_equal("local-variable", o.foo, bug9593) + message = /circular argument reference - var/ + assert_syntax_error("def foo(var: defined?(var)) var end", message) + assert_syntax_error("def foo(var: var) var end", message) + assert_syntax_error("def foo(var: bar(var)) var end", message) + assert_syntax_error("def foo(var: bar {var}) var end", message) o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var: var) var end") - end - assert_nil(o.foo, bug9593) - - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var: bar(var)) var end") + assert_warn("") do + o.instance_eval("def foo(var: bar {|var| var}) var end") end o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var: bar {var}) var end") + assert_warn("") do + o.instance_eval("def foo(var: bar {| | var}) var end") end o = Object.new assert_warn("") do - o.instance_eval("def foo(var: bar {|var| var}) var end") + o.instance_eval("def foo(var: bar {|| var}) var end") end o = Object.new @@ -225,56 +407,55 @@ class TestSyntax < Test::Unit::TestCase def test_keyword_invalid_name bug11663 = '[ruby-core:71356] [Bug #11663]' - o = o = Object.new - assert_syntax_error('def o.foo(arg1?:) end', /arg1\?/, bug11663) - assert_syntax_error('def o.foo(arg1?:, arg2:) end', /arg1\?/, bug11663) + assert_syntax_error('def foo(arg1?:) end', /arg1\?/, bug11663) + assert_syntax_error('def foo(arg1?:, arg2:) end', /arg1\?/, bug11663) assert_syntax_error('proc {|arg1?:|}', /arg1\?/, bug11663) assert_syntax_error('proc {|arg1?:, arg2:|}', /arg1\?/, bug11663) bug10545 = '[ruby-dev:48742] [Bug #10545]' - assert_syntax_error('def o.foo(FOO: a) end', /constant/, bug10545) - assert_syntax_error('def o.foo(@foo: a) end', /instance variable/) - assert_syntax_error('def o.foo(@@foo: a) end', /class variable/) + assert_syntax_error('def foo(FOO: a) end', /constant/, bug10545) + assert_syntax_error('def foo(@foo: a) end', /instance variable/) + assert_syntax_error('def foo(@@foo: a) end', /class variable/) end - def test_optional_self_reference - bug9593 = '[ruby-core:61299] [Bug #9593]' - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = defined?(var)) var end") - end - assert_equal(42, o.foo(42)) - assert_equal("local-variable", o.foo, bug9593) - - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = var) var end") - end - assert_nil(o.foo, bug9593) + def test_keywords_specified_and_not_accepted + assert_syntax_error('def foo(a:, **nil) end', /unexpected/) + assert_syntax_error('def foo(a:, **nil, &b) end', /unexpected/) + assert_syntax_error('def foo(**a, **nil) end', /unexpected/) + assert_syntax_error('def foo(**a, **nil, &b) end', /unexpected/) + assert_syntax_error('def foo(**nil, **a) end', /unexpected/) + assert_syntax_error('def foo(**nil, **a, &b) end', /unexpected/) - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = bar(var)) var end") - end + assert_syntax_error('proc do |a:, **nil| end', /unexpected/) + assert_syntax_error('proc do |a:, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a, &b| end', /unexpected/) + end - o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = bar {var}) var end") - end + def test_optional_self_reference + message = /circular argument reference - var/ + assert_syntax_error("def foo(var = defined?(var)) var end", message) + assert_syntax_error("def foo(var = var) var end", message) + assert_syntax_error("def foo(var = bar(var)) var end", message) + assert_syntax_error("def foo(var = bar {var}) var end", message) + assert_syntax_error("def foo(var = (def bar;end; var)) var end", message) + assert_syntax_error("def foo(var = (def self.bar;end; var)) var end", message) o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = (def bar;end; var)) var end") + assert_warn("") do + o.instance_eval("def foo(var = bar {|var| var}) var end") end o = Object.new - assert_warn(/circular argument reference - var/) do - o.instance_eval("def foo(var = (def self.bar;end; var)) var end") + assert_warn("") do + o.instance_eval("def foo(var = bar {| | var}) var end") end o = Object.new assert_warn("") do - o.instance_eval("def foo(var = bar {|var| var}) var end") + o.instance_eval("def foo(var = bar {|| var}) var end") end o = Object.new @@ -401,21 +582,30 @@ WARN def test_duplicated_arg assert_syntax_error("def foo(a, a) end", /duplicated argument name/) assert_valid_syntax("def foo(_, _) end") + (obj = Object.new).instance_eval("def foo(_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) end def test_duplicated_rest assert_syntax_error("def foo(a, *a) end", /duplicated argument name/) assert_valid_syntax("def foo(_, *_) end") + (obj = Object.new).instance_eval("def foo(_, x, *_) x end") + assert_equal(2, obj.foo(1, 2, 3)) end def test_duplicated_opt assert_syntax_error("def foo(a, a=1) end", /duplicated argument name/) assert_valid_syntax("def foo(_, _=1) end") + (obj = Object.new).instance_eval("def foo(_, x, _=42) x end") + assert_equal(2, obj.foo(1, 2)) end def test_duplicated_opt_rest assert_syntax_error("def foo(a=1, *a) end", /duplicated argument name/) assert_valid_syntax("def foo(_=1, *_) end") + (obj = Object.new).instance_eval("def foo(_, x=42, *_) x end") + assert_equal(42, obj.foo(1)) + assert_equal(2, obj.foo(1, 2)) end def test_duplicated_rest_opt @@ -424,46 +614,85 @@ WARN def test_duplicated_rest_post assert_syntax_error("def foo(*a, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, _) end") + (obj = Object.new).instance_eval("def foo(*_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(*_, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) end def test_duplicated_opt_post assert_syntax_error("def foo(a=1, a) end", /duplicated argument name/) assert_valid_syntax("def foo(_=1, _) end") + (obj = Object.new).instance_eval("def foo(_=1, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(_=1, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) end def test_duplicated_kw assert_syntax_error("def foo(a, a: 1) end", /duplicated argument name/) assert_valid_syntax("def foo(_, _: 1) end") + (obj = Object.new).instance_eval("def foo(_, x, _: 1) x end") + assert_equal(3, obj.foo(2, 3)) + assert_equal(3, obj.foo(2, 3, _: 42)) + (obj = Object.new).instance_eval("def foo(x, _, _: 1) x end") + assert_equal(2, obj.foo(2, 3)) + assert_equal(2, obj.foo(2, 3, _: 42)) end def test_duplicated_rest_kw assert_syntax_error("def foo(*a, a: 1) end", /duplicated argument name/) assert_nothing_raised {def foo(*_, _: 1) end} + (obj = Object.new).instance_eval("def foo(*_, x: 42, _: 1) x end") + assert_equal(42, obj.foo(42)) + assert_equal(42, obj.foo(2, _: 0)) + assert_equal(2, obj.foo(x: 2, _: 0)) end def test_duplicated_opt_kw assert_syntax_error("def foo(a=1, a: 1) end", /duplicated argument name/) assert_valid_syntax("def foo(_=1, _: 1) end") + (obj = Object.new).instance_eval("def foo(_=42, x, _: 1) x end") + assert_equal(0, obj.foo(0)) + assert_equal(0, obj.foo(0, _: 3)) end def test_duplicated_kw_kwrest assert_syntax_error("def foo(a: 1, **a) end", /duplicated argument name/) assert_valid_syntax("def foo(_: 1, **_) end") + (obj = Object.new).instance_eval("def foo(_: 1, x: 42, **_) x end") + assert_equal(42, obj.foo()) + assert_equal(42, obj.foo(a: 0)) + assert_equal(42, obj.foo(_: 0, a: 0)) + assert_equal(1, obj.foo(_: 0, x: 1, a: 0)) end def test_duplicated_rest_kwrest assert_syntax_error("def foo(*a, **a) end", /duplicated argument name/) assert_valid_syntax("def foo(*_, **_) end") + (obj = Object.new).instance_eval("def foo(*_, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(2, obj.foo(1, 2, a: 0)) end def test_duplicated_opt_kwrest assert_syntax_error("def foo(a=1, **a) end", /duplicated argument name/) assert_valid_syntax("def foo(_=1, **_) end") + (obj = Object.new).instance_eval("def foo(_=42, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(1, obj.foo(0, 1, a: 0)) end def test_duplicated_when - w = 'warning: duplicated when clause is ignored' - assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ + w = 'warning: duplicated `when\' clause with line 3 is ignored' + assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m) { eval %q{ case 1 when 1, 1 @@ -472,7 +701,7 @@ WARN end } } - assert_warning(/#{w}/){#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ + assert_warning(/#{w}/) {#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ a = a = 1 eval %q{ case 1 @@ -484,6 +713,17 @@ WARN } end + def test_duplicated_when_check_option + w = /duplicated `when\' clause with line 3 is ignored/ + assert_in_out_err(%[-wc], "#{<<~"begin;"}\n#{<<~'end;'}", ["Syntax OK"], w) + begin; + case 1 + when 1 + when 1 + end + end; + end + def test_invalid_break assert_syntax_error("def m; break; end", /Invalid break/) assert_syntax_error('/#{break}/', /Invalid break/) @@ -519,6 +759,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 @@ -529,7 +774,7 @@ WARN def test_unassignable gvar = global_variables %w[self nil true false __FILE__ __LINE__ __ENCODING__].each do |kwd| - assert_raise(SyntaxError) {eval("#{kwd} = nil")} + assert_syntax_error("#{kwd} = nil", /Can't .* #{kwd}$/) assert_equal(gvar, global_variables) end end @@ -667,6 +912,32 @@ e" assert_dedented_heredoc(expected, result) end + def test_dedented_heredoc_expr_string + result = ' one#{" two "}'"\n" + expected = 'one#{" two "}'"\n" + assert_dedented_heredoc(expected, result) + end + + def test_dedented_heredoc_continued_line + result = " 1\\\n" " 2\n" + expected = "1\\\n" "2\n" + assert_dedented_heredoc(expected, result) + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't find string "TEXT"/) + begin; + <<-TEXT + \ + TEXT + end; + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't find string "TEXT"/) + begin; + <<~TEXT + \ + TEXT + end; + + assert_equal(" TEXT\n", eval("<<~eos\n" " \\\n" "TEXT\n" "eos\n")) + end + def test_lineno_after_heredoc bug7559 = '[ruby-dev:46737]' expected, _, actual = __LINE__, <<eom, __LINE__ @@ -682,6 +953,46 @@ eom assert_syntax_error('<<~ "#{}"', /unexpected <</) end + def test_dedented_heredoc_concatenation + assert_equal("\n0\n1", eval("<<~0 '1'\n \n0\#{}\n0")) + end + + def test_heredoc_mixed_encoding + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d\u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d + \u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234\xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234 + \xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + end + def test_lineno_operation_brace_block expected = __LINE__ + 1 actual = caller_lineno\ @@ -771,9 +1082,20 @@ eom assert_syntax_error("puts <<""EOS\n""ng\n""EOS\r""NO\n", /can't find string "EOS" anywhere before EOF/) end - def test_heredoc_newline - assert_warn(/ends with a newline/) do - eval("<<\"EOS\n\"\nEOS\n") + def test_heredoc_no_terminator + assert_syntax_error("puts <<""A\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "B" anywhere before EOF/) + end + + def test_unterminated_heredoc + assert_syntax_error("<<\"EOS\n\nEOS\n", /unterminated/) + assert_syntax_error("<<\"EOS\n\"\nEOS\n", /unterminated/) + end + + def test_unterminated_heredoc_cr + %W[\r\n \n].each do |nl| + assert_syntax_error("<<\"\r\"#{nl}\r#{nl}", /unterminated/, nil, "CR with #{nl.inspect}") end end @@ -783,9 +1105,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 @@ -807,8 +1134,28 @@ eom bug10957 = '[ruby-core:68477] [Bug #10957]' assert_ruby_status(['-c', '-e', 'p ()..0'], "", bug10957) assert_ruby_status(['-c', '-e', 'p ()...0'], "", bug10957) - assert_syntax_error('0..%w.', /unterminated string/, bug10957) - assert_syntax_error('0...%w.', /unterminated string/, bug10957) + assert_syntax_error('0..%q.', /unterminated string/, bug10957) + assert_syntax_error('0...%q.', /unterminated string/, bug10957) + end + + def test_range_at_eol + assert_warn(/\.\.\. at EOL/) {eval("1...\n2")} + 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 @@ -824,9 +1171,35 @@ eom assert_syntax_error(":#\n foo", /unexpected ':'/) end + def test_invalid_literal_message + assert_syntax_error("def :foo", /unexpected symbol literal/) + assert_syntax_error("def 'foo'", /unexpected string literal/) + end + def test_fluent_dot assert_valid_syntax("a\n.foo") assert_valid_syntax("a\n&.foo") + assert_valid_syntax("a #\n#\n.foo\n") + assert_valid_syntax("a #\n#\n&.foo\n") + end + + def test_safe_call_in_massign_lhs + assert_syntax_error("*a&.x=0", /multiple assignment destination/) + assert_syntax_error("a&.x,=0", /multiple assignment destination/) + end + + def test_safe_call_in_for_variable + assert_valid_syntax("for x&.bar in []; end") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + foo = nil + for foo&.bar in [1]; end + assert_nil(foo) + + foo = Struct.new(:bar).new + for foo&.bar in [1]; end + assert_equal(1, foo.bar) + end; end def test_no_warning_logop_literal @@ -842,22 +1215,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_warn(/literal in condition/) do - eval('1 if true..false') - 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 @@ -881,6 +1251,27 @@ eom end end + def test_warning_literal_in_flip_flop + assert_warn(/literal in flip-flop/) do + eval('1 if ""..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"..false') + end + assert_warn(/literal in flip-flop/) do + eval('1 if ""...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"...false') + end + end + def test_alias_symbol bug8851 = '[ruby-dev:47681] [Bug #8851]' formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] @@ -906,7 +1297,7 @@ eom end def test_parenthesised_statement_argument - assert_syntax_error("foo(bar rescue nil)", /unexpected modifier_rescue/) + assert_syntax_error("foo(bar rescue nil)", /unexpected `rescue' modifier/) assert_valid_syntax("foo (bar rescue nil)") end @@ -1006,6 +1397,9 @@ eom begin raise; ensure return; end; self begin raise; ensure return; end and self nil&defined?0--begin e=no_method_error(); return; 0;end + return puts('ignored') #=> ignored + BEGIN {return} + END {return if false} end; .split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]} failed = proc do |n, s| @@ -1014,17 +1408,39 @@ eom Tempfile.create(%w"test_return_ .rb") do |lib| lib.close args = %W[-W0 -r#{lib.path}] - all_assertions_foreach(feature4840, *[true, false].product(code)) do |main, (n, s, *ex)| - if main - assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true) + all_assertions_foreach(feature4840, *[:main, :lib].product([:class, :top], code)) do |main, klass, (n, s, *ex)| + if klass == :class + s = "class X; #{s}; end" + if main == :main + assert_in_out_err(%[-W0], s, [], /return/, proc {failed[n, s]}, success: false) + else + File.write(lib, s) + assert_in_out_err(args, "", [], /return/, proc {failed[n, s]}, success: false) + end else - File.write(lib, s) - assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true) + if main == :main + assert_in_out_err(%[-W0], s, ex, [], proc {failed[n, s]}, success: true) + else + File.write(lib, s) + assert_in_out_err(args, "", ex, [], proc {failed[n, s]}, success: true) + end end end end end + def test_return_toplevel_with_argument + assert_warn(/argument of top-level return is ignored/) {eval("return 1")} + end + + def test_return_in_proc_in_class + assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/) + end + + def test_return_in_END + assert_normal_exit('END {return}') + end + def test_syntax_error_in_rescue bug12613 = '[ruby-core:76531] [Bug #12613]' assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613) @@ -1042,10 +1458,20 @@ eom end; end + def test_syntax_error_at_newline + expected = "\n ^" + assert_syntax_error("%[abcdef", expected) + assert_syntax_error("%[abcdef\n", expected) + end + def test_invalid_jump assert_in_out_err(%w[-e redo], "", [], /^-e:1: /) end + def test_keyword_not_parens + assert_valid_syntax("not()") + end + def test_rescue_do_end_raised result = [] assert_raise(RuntimeError) do @@ -1113,6 +1539,454 @@ eom assert_equal(:begin, result) end + def test_rescue_do_end_ensure_in_lambda + result = [] + eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + -> do + result << :begin + raise "An exception occurred!" + rescue + result << :rescue + else + result << :else + ensure + result << :ensure + end.call + end; + assert_equal([:begin, :rescue, :ensure], result) + end + + def test_return_in_loop + obj = Object.new + def obj.test + x = nil + return until x unless x + end + 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 + 1.upto(0) do + end + . + foo( + 1, + 2, + ) + end + assert_equal(line, e.backtrace_locations[0].lineno) + + line = __LINE__+5 + e = assert_raise(NoMethodError) do + 1.upto 0 do + end + . + foo( + 1, + 2, + ) + end + 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') + end + + def test_classdef_in_cond + assert_valid_syntax('while class Foo; tap do end; end; break; end') + assert_valid_syntax('while class Foo a = tap do end; end; break; end') + end + + def test_command_with_cmd_brace_block + assert_valid_syntax('obj.foo (1) {}') + assert_valid_syntax('obj::foo (1) {}') + end + + def test_numbered_parameter + assert_valid_syntax('proc {_1}') + assert_equal(3, eval('[1,2].then {_1+_2}')) + assert_equal("12", eval('[1,2].then {"#{_1}#{_2}"}')) + assert_equal([1, 2], eval('[1,2].then {_1}')) + assert_equal(3, eval('->{_1+_2}.call(1,2)')) + assert_equal(4, eval('->(a=->{_1}){a}.call.call(4)')) + assert_equal(5, eval('-> a: ->{_1} {a}.call.call(5)')) + assert_syntax_error('proc {|| _1}', /ordinary parameter is defined/) + assert_syntax_error('proc {|;a| _1}', /ordinary parameter is defined/) + assert_syntax_error("proc {|\n| _1}", /ordinary parameter is defined/) + assert_syntax_error('proc {|x| _1}', /ordinary parameter is defined/) + assert_syntax_error('proc {_1; proc {_2}}', /numbered parameter is already used/) + assert_syntax_error('proc {proc {_1}; _2}', /numbered parameter is already used/) + assert_syntax_error('->(){_1}', /ordinary parameter is defined/) + assert_syntax_error('->(x){_1}', /ordinary parameter is defined/) + assert_syntax_error('->x{_1}', /ordinary parameter is defined/) + assert_syntax_error('->x:_2{}', /ordinary parameter is defined/) + assert_syntax_error('->x=_1{}', /ordinary parameter is defined/) + 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_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') + } + ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c| + 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")}, + ] + } + + assert_valid_syntax("proc {def foo(_);end;_1}") + assert_valid_syntax("p { [_1 **2] }") + end + + def test_value_expr_in_condition + mesg = /void value expression/ + assert_syntax_error("tap {a = (true ? next : break)}", mesg) + assert_valid_syntax("tap {a = (true ? true : break)}") + assert_valid_syntax("tap {a = (break if false)}") + 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('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/) + assert_syntax_error('def foo(...) return(...); end', /unexpected/) + assert_syntax_error('def foo(...) a = (...); end', /unexpected/) + assert_syntax_error('def foo(...) [...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/) + assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/) + assert_syntax_error('def foo(...) defined?(...); end', /unexpected/) + assert_syntax_error('def foo(*rest, ...) end', '... after rest argument') + assert_syntax_error('def foo(*, ...) end', '... after rest argument') + + obj1 = Object.new + def obj1.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [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) + if block + block.call(args, kws) + else + [args, kws] + end + end + } + obj2 = klass.new + obj2.instance_eval('def foo(...) super(...) end', __FILE__, __LINE__) + + obj3 = Object.new + def obj3.bar(*args, &block) + if kws = Hash.try_convert(args.last) + args.pop + else + kws = {} + end + if block + block.call(args, kws) + else + [args, kws] + end + end + obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) + + [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)) + } + array = obj == obj3 ? [] : [{}] + assert_warning('') { + assert_equal([array, {}], obj.foo({}) {|*x| x}) + } + 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(: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_class_module_Object_ancestors + assert_separately([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + class Bug18832; end + RUBY + assert_separately([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + module Bug18832; end + RUBY + end + + def test_cdhash + 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 def not_label(x) @result = x; @not_label ||= nil end diff --git a/test/ruby/test_system.rb b/test/ruby/test_system.rb index 60037ab044..31c9cd7654 100644 --- a/test/ruby/test_system.rb +++ b/test/ruby/test_system.rb @@ -160,4 +160,45 @@ class TestSystem < Test::Unit::TestCase assert_equal(true, system(tmpfilename), '[ruby-core:32745]') } end if File.executable?("/bin/sh") + + def test_system_exception + ruby = EnvUtil.rubybin + assert_nothing_raised do + system('feature_14235', exception: false) + end + assert_nothing_raised do + system(ruby, "-e", "abort", exception: false) + end + assert_nothing_raised do + system("'#{ruby}' -e abort", exception: false) + end + assert_raise(Errno::ENOENT) do + system('feature_14235', exception: true) + end + assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do + system(ruby, "-e", "abort", exception: true) + end + assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do + system("'#{ruby}' -e abort", exception: true) + end + end + + def test_system_exception_nonascii + Dir.mktmpdir("ruby_script_tmp") do |tmpdir| + name = "\u{30c6 30b9 30c8}" + tmpfilename = "#{tmpdir}/#{name}.cmd" + message = /#{name}\.cmd/ + assert_raise_with_message(Errno::ENOENT, message) do + system(tmpfilename, exception: true) + end + open(tmpfilename, "w") {|f| + f.print "@" if /mingw|mswin/ =~ RUBY_PLATFORM + f.puts "exit 127" + f.chmod(0755) + } + assert_raise_with_message(RuntimeError, message) do + system(tmpfilename, exception: true) + end + end + end end diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 6e2f594970..85fbde2de8 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1,6 +1,8 @@ # -*- coding: us-ascii -*- # frozen_string_literal: false require 'test/unit' +require "rbconfig/sizeof" +require "timeout" class TestThread < Test::Unit::TestCase class Thread < ::Thread @@ -27,9 +29,19 @@ class TestThread < Test::Unit::TestCase end def test_inspect - th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start{} - assert_match(/::C\u{30b9 30ec 30c3 30c9}:/, th.inspect) + m = Thread::Mutex.new + m.lock + line = __LINE__+1 + th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do + m.synchronize {} + end + Thread.pass until th.stop? + s = th.inspect + assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:") + assert_include(s, " #{__FILE__}:#{line} ") + assert_equal(s, th.to_s) ensure + m.unlock th.join end @@ -102,7 +114,7 @@ class TestThread < Test::Unit::TestCase def test_thread_variable_frozen t = Thread.new { }.join t.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do t.thread_variable_set(:foo, "bar") end end @@ -166,6 +178,8 @@ class TestThread < Test::Unit::TestCase t1.kill t2.kill assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + t1.join + t2.join end def test_new @@ -184,8 +198,8 @@ class TestThread < Test::Unit::TestCase end ensure - t1.kill if t1 - t2.kill if t2 + t1&.kill&.join + t2&.kill&.join end def test_new_symbol_proc @@ -201,7 +215,7 @@ class TestThread < Test::Unit::TestCase assert_nil(t.join(0.05)) ensure - t.kill if t + t&.kill&.join end def test_join2 @@ -222,9 +236,47 @@ class TestThread < Test::Unit::TestCase assert_equal(t1, t3.value) ensure - t1.kill if t1 - t2.kill if t2 - t3.kill if t3 + 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'], + 'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'], + 'INFINITY' => Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_#{name}") do + t = Thread.new {} + assert_same t, t.join(limit), "limit=#{limit.inspect}" + end + end + + { 'minus_1' => -1, + 'minus_0_1' => -0.1, + 'FIXNUM_MIN' => RbConfig::LIMITS['FIXNUM_MIN'], + 'INT64_MIN' => RbConfig::LIMITS['INT64_MIN'], + 'minus_INFINITY' => -Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_negative_#{name}") do + t = Thread.new { sleep } + begin + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(30) do + assert_nil t.join(limit), "limit=#{limit.inspect}" + end + end + ensure + t.kill + end + end end def test_kill_main_thread @@ -271,14 +323,14 @@ class TestThread < Test::Unit::TestCase s += 1 end Thread.pass until t.stop? + 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? assert_equal(2, s) assert_raise(ThreadError) { t.wakeup } - ensure - t.kill if t + t&.kill&.join end def test_stop @@ -317,7 +369,10 @@ class TestThread < Test::Unit::TestCase assert_in_out_err([], <<-INPUT, %w(false 1), []) p Thread.abort_on_exception begin - t = Thread.new { raise } + t = Thread.new { + Thread.current.report_on_exception = false + raise + } Thread.pass until t.stop? p 1 rescue @@ -329,7 +384,10 @@ class TestThread < Test::Unit::TestCase Thread.abort_on_exception = true p Thread.abort_on_exception begin - Thread.new { raise } + Thread.new { + Thread.current.report_on_exception = false + raise + } sleep 0.5 p 1 rescue @@ -352,7 +410,11 @@ class TestThread < Test::Unit::TestCase p Thread.abort_on_exception begin ok = false - t = Thread.new { Thread.pass until ok; raise } + t = Thread.new { + Thread.current.report_on_exception = false + Thread.pass until ok + raise + } t.abort_on_exception = true p t.abort_on_exception ok = 1 @@ -370,35 +432,40 @@ class TestThread < Test::Unit::TestCase q1 = Thread::Queue.new q2 = Thread::Queue.new - assert_equal(false, Thread.report_on_exception, - "global flags is false by default") - assert_equal(false, Thread.current.report_on_exception) + assert_equal(true, Thread.report_on_exception, + "global flag is true by default") + assert_equal(true, Thread.current.report_on_exception, + "the main thread has report_on_exception=true") - Thread.current.report_on_exception = true - assert_equal(false, + Thread.report_on_exception = true + Thread.current.report_on_exception = false + assert_equal(true, Thread.start {Thread.current.report_on_exception}.value, - "should not inherit from the parent thread") + "should not inherit from the parent thread but from the global flag") - assert_warn("", "exception should be ignored silently") { + assert_warn("", "exception should be ignored silently when false") { th = Thread.start { + Thread.current.report_on_exception = false q1.push(Thread.current.report_on_exception) raise "report 1" } assert_equal(false, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_warn(/report 2/, "exception should be reported") { + assert_warn(/report 2/, "exception should be reported when true") { th = Thread.start { q1.push(Thread.current.report_on_exception = true) raise "report 2" } assert_equal(true, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_equal(false, Thread.report_on_exception) assert_warn("", "the global flag should not affect already started threads") { + Thread.report_on_exception = false th = Thread.start { q2.pop q1.push(Thread.current.report_on_exception) @@ -407,19 +474,21 @@ class TestThread < Test::Unit::TestCase q2.push(Thread.report_on_exception = true) assert_equal(false, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_equal(true, Thread.report_on_exception) assert_warn(/report 4/, "should defaults to the global flag at the start") { + Thread.report_on_exception = true th = Thread.start { q1.push(Thread.current.report_on_exception) raise "report 4" } assert_equal(true, q1.pop) Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } } - assert_warn(/report 5/, "should defaults to the global flag at the start") { + assert_warn(/report 5/, "should first report and then raise with report_on_exception + abort_on_exception") { th = Thread.start { Thread.current.report_on_exception = true Thread.current.abort_on_exception = true @@ -430,12 +499,29 @@ class TestThread < Test::Unit::TestCase q2.push(true) Thread.pass while th.alive? } + assert_raise(RuntimeError) { th.join } } end; end + def test_ignore_deadlock + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "can't trap a signal from another process on Windows" + end + assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil) + p Thread.ignore_deadlock + 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 { raise("die now") } + a = ::Thread.new { + Thread.current.report_on_exception = false + raise("die now") + } b = Thread.new { Thread.stop } c = Thread.new { Thread.exit } e = Thread.current @@ -454,11 +540,10 @@ class TestThread < Test::Unit::TestCase es1 = e.status es2 = e.stop? assert_equal(["run", false], [es1, es2]) - + assert_raise(RuntimeError) { a.join } ensure - a.kill if a - b.kill if b - c.kill if c + b&.kill&.join + c&.join end def test_switch_while_busy_loop @@ -476,24 +561,7 @@ class TestThread < Test::Unit::TestCase end assert(!flag, bug1402) ensure - waiter.kill.join - end - - def test_safe_level - ok = false - t = Thread.new do - EnvUtil.suppress_warning do - $SAFE = 1 - end - ok = true - sleep - end - Thread.pass until ok - assert_equal(0, Thread.current.safe_level) - assert_equal(1, t.safe_level) - - ensure - t.kill if t + waiter&.kill&.join end def test_thread_local @@ -513,7 +581,7 @@ class TestThread < Test::Unit::TestCase assert_equal([:foo, :bar, :baz].sort, t.keys.sort) ensure - t.kill if t + t&.kill&.join end def test_thread_local_fetch @@ -541,19 +609,21 @@ class TestThread < Test::Unit::TestCase assert_equal(3, t.fetch("qux") {x = 3}) assert_equal(3, x) - assert_raise(KeyError) {t.fetch(:qux)} + e = assert_raise(KeyError) {t.fetch(:qux)} + assert_equal(:qux, e.key) + assert_equal(t, e.receiver) ensure - t.kill if t + t&.kill&.join end def test_thread_local_security - assert_raise(RuntimeError) do - Thread.new do - Thread.current[:foo] = :bar - Thread.current.freeze + Thread.new do + Thread.current[:foo] = :bar + Thread.current.freeze + assert_raise(FrozenError) do Thread.current[:foo] = :baz - end.join - end + end + end.join end def test_thread_local_dynamic_symbol @@ -573,7 +643,8 @@ class TestThread < Test::Unit::TestCase end Thread.pass until t.stop? assert_predicate(t, :alive?) - t.kill + ensure + t&.kill&.join end def test_mutex_deadlock @@ -602,11 +673,11 @@ class TestThread < Test::Unit::TestCase def test_mutex_illegal_unlock m = Thread::Mutex.new m.lock - assert_raise(ThreadError) do - Thread.new do + Thread.new do + assert_raise(ThreadError) do m.unlock - end.join - end + end + end.join end def test_mutex_fifo_like_lock @@ -666,7 +737,7 @@ class TestThread < Test::Unit::TestCase end def test_no_valid_cfp - skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE) + omit 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE) bug5083 = '[ruby-dev:44208]' assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083) assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083) @@ -674,8 +745,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){ @@ -751,15 +822,16 @@ 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 - begin - th = Thread.start{ + th = Thread.start { + assert_raise(RuntimeError) { Thread.handle_interrupt(Object => :on_blocking){ begin - Thread.pass until r == :wait - Thread.current.raise RuntimeError + q.pop + Thread.current.raise RuntimeError, "will raise in sleep" r = :ok sleep ensure @@ -767,27 +839,27 @@ class TestThread < Test::Unit::TestCase end } } - assert_raise(e) {r = :wait; sleep 0.2} - assert_raise(RuntimeError) {th.join(0.2)} - ensure - th.kill - end - 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 = Thread::Queue.new t = Thread.new { + Thread.current.report_on_exception = false Thread.handle_interrupt(RuntimeError => :on_blocking) { + q << true nil while th_waiting # async interrupt should be raised _before_ writing puts arguments puts "ng" } } - Thread.pass while t.stop? + q.pop t.raise RuntimeError th_waiting = false t.join rescue nil @@ -800,6 +872,7 @@ class TestThread < Test::Unit::TestCase th_waiting = false t = Thread.new { + Thread.current.report_on_exception = false Thread.handle_interrupt(RuntimeError => :on_blocking) { th_waiting = true nil while th_waiting @@ -825,15 +898,15 @@ class TestThread < Test::Unit::TestCase begin begin Thread.pass until done - rescue => e + rescue q.push :ng1 end begin Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt? - rescue RuntimeError => e + rescue RuntimeError q.push :ok end - rescue => e + rescue q.push :ng2 ensure q.push :ng3 @@ -899,17 +972,25 @@ _eom end def test_thread_timer_and_interrupt + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + bug5757 = '[ruby-dev:44985]' pid = nil - cmd = 'Signal.trap(:INT, "DEFAULT"); r,=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; r.read' + cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read' opt = {} opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM - s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, opt) do |in_p, out_p, err_p, cpid| + s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, **opt) do |in_p, out_p, err_p, cpid| + assert IO.select([out_p], nil, nil, 10), 'subprocess not ready' out_p.gets pid = cpid t0 = Time.now.to_f Process.kill(:SIGINT, pid) - Process.wait(pid) + begin + Timeout.timeout(10) { Process.wait(pid) } + rescue Timeout::Error + EnvUtil.terminate(pid) + raise + end t1 = Time.now.to_f [$?, t1 - t0, err_p.read] end @@ -956,11 +1037,11 @@ _eom end def test_thread_join_main_thread - assert_raise(ThreadError) do - Thread.new(Thread.current) {|t| + Thread.new(Thread.current) {|t| + assert_raise(ThreadError) do t.join - }.join - end + end + }.join end def test_main_thread_status_at_exit @@ -995,7 +1076,7 @@ q.pop puts mth.status Process.kill(:INT, $$) } - sleep 0.1 + sleep INPUT end @@ -1004,31 +1085,30 @@ q.pop ary = [] t = Thread.new { - begin - ary << Thread.current.status - sleep #1 - ensure + assert_raise(RuntimeError) do begin ary << Thread.current.status - sleep #2 + sleep #1 ensure - ary << Thread.current.status + begin + ary << Thread.current.status + sleep #2 + ensure + ary << Thread.current.status + end end end } - begin - Thread.pass until ary.size >= 1 - Thread.pass until t.stop? - t.kill # wake up sleep #1 - Thread.pass until ary.size >= 2 - Thread.pass until t.stop? - t.raise "wakeup" # wake up sleep #2 - Thread.pass while t.alive? - assert_equal(ary, ["run", "aborting", "aborting"]) - ensure - t.join rescue nil - end + Thread.pass until ary.size >= 1 + Thread.pass until t.stop? + t.kill # wake up sleep #1 + Thread.pass until ary.size >= 2 + Thread.pass until t.stop? + t.raise "wakeup" # wake up sleep #2 + Thread.pass while t.alive? + assert_equal(ary, ["run", "aborting", "aborting"]) + t.join end def test_mutex_owned @@ -1055,7 +1135,7 @@ q.pop Thread.pass until mutex.locked? assert_equal(mutex.owned?, false) ensure - th.kill if th + th&.kill&.join end end @@ -1082,7 +1162,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 @@ -1099,6 +1181,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 @@ -1127,12 +1210,10 @@ q.pop bug8433 = '[ruby-core:55102] [Bug #8433]' mutex = Thread::Mutex.new - flag = false mutex.lock th = Thread.new do mutex.synchronize do - flag = true sleep end end @@ -1171,6 +1252,68 @@ q.pop assert_predicate(status, :success?, bug9751) end if Process.respond_to?(:fork) + def test_fork_value + bug18902 = "[Bug #18902]" + th = Thread.start { sleep 2 } + begin + pid = fork do + th.value + end + _, status = Process.wait2(pid) + assert_predicate(status, :success?, bug18902) + ensure + th.kill + th.join + end + end if Process.respond_to?(:fork) + + def test_fork_while_locked + m = Thread::Mutex.new + thrs = [] + 3.times do |i| + thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } } + end + thrs.each do |t| + assert_predicate t.value, :success?, '[ruby-core:85940] [Bug #14578]' + end + end if Process.respond_to?(:fork) + + def test_fork_while_parent_locked + omit 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + nr = 1 + thrs = [] + m.synchronize do + thrs = nr.times.map { Thread.new { m.synchronize {} } } + thrs.each { Thread.pass } + pid = fork do + m.locked? or exit!(2) + thrs = nr.times.map { Thread.new { m.synchronize {} } } + m.unlock + thrs.each { |t| t.join(1) == t or exit!(1) } + exit!(0) + end + _, st = Process.waitpid2(pid) + assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]' + end + thrs.each { |t| assert_same t, t.join(1) } + end + + def test_fork_while_mutex_locked_by_forker + omit 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + m.synchronize do + pid = fork do + exit!(2) unless m.locked? + m.unlock rescue exit!(3) + m.synchronize {} rescue exit!(4) + exit!(0) + end + _, st = Timeout.timeout(30) { Process.waitpid2(pid) } + assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]' + end + end + def test_subclass_no_initialize t = Module.new do break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") @@ -1218,19 +1361,167 @@ q.pop t.join end + def test_yield_across_thread_through_enum + bug18649 = '[ruby-core:107980] [Bug #18649]' + @log = [] + + def self.p(arg) + @log << arg + end + + def self.synchronize + yield + end + + def self.execute(task) + success = true + value = reason = nil + end_sync = false + + synchronize do + begin + p :before + value = task.call + p :never_reached + success = true + rescue StandardError => ex + ex = ex.class + p [:rescue, ex] + reason = ex + success = false + end + + end_sync = true + p :end_sync + end + + p :should_not_reach_here! unless end_sync + [success, value, reason] + end + + def self.foo + Thread.new do + result = execute(-> { yield 42 }) + p [:result, result] + end.join + end + + value = to_enum(:foo).first + expected = [:before, + [:rescue, LocalJumpError], + :end_sync, + [:result, [false, nil, LocalJumpError]]] + + assert_equal(expected, @log, bug18649) + assert_equal(42, value, bug18649) + end + def test_thread_setname_in_initialize bug12290 = '[ruby-core:74963] [Bug #12290]' c = Class.new(Thread) {def initialize() self.name = "foo"; super; end} assert_equal("foo", c.new {Thread.current.name}.value, bug12290) end + def test_thread_native_thread_id + omit "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id) + assert_instance_of Integer, Thread.main.native_thread_id + + th1 = Thread.start{sleep} + + # 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 - assert_normal_exit(<<-_end, '[Bug #8996]', timeout: 5, timeout_error: nil) + opts = { timeout: 5, timeout_error: nil } + + # prevent SIGABRT from slow shutdown with MJIT + opts[:reprieve] = 3 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? + + assert_normal_exit(<<-_end, '[Bug #8996]', **opts) + Thread.report_on_exception = false trap(:TERM){exit} while true t = Thread.new{sleep 0} t.raise Interrupt + Thread.pass # allow t to finish end _end end + + def test_signal_at_join + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "can't trap a signal from another process on Windows" + # opt = {new_pgroup: true} + end + + if /freebsd/ =~ RUBY_PLATFORM + omit "[Bug #18613]" + end + + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120) + {# + n = 1000 + sig = :INT + trap(sig) {} + IO.popen([EnvUtil.rubybin, "-e", "#{<<~"{#1"}\n#{<<~'};#1'}"], "r+") do |f| + tpid = #{$$} + sig = :#{sig} + {#1 + STDOUT.sync = true + while gets + puts + Process.kill(sig, tpid) + end + };#1 + assert_nothing_raised do + n.times do + w = Thread.start do + sleep 30 + end + begin + f.puts + f.gets + ensure + w.kill + w.join + end + end + end + n.times do + w = Thread.start { sleep 30 } + begin + f.puts + f.gets + ensure + w.kill + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + w.join(30) + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + diff = t1 - t0 + assert_operator diff, :<=, 2 + end + end + end + }; + end + + def test_pending_interrupt? + t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } } + t.raise(StandardError) + assert_equal(true, t.pending_interrupt?) + assert_equal(true, t.pending_interrupt?(Exception)) + assert_equal(false, t.pending_interrupt?(ArgumentError)) + end end diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb new file mode 100644 index 0000000000..88733419da --- /dev/null +++ b/test/ruby/test_thread_cv.rb @@ -0,0 +1,252 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestThreadConditionVariable < Test::Unit::TestCase + ConditionVariable = Thread::ConditionVariable + Mutex = Thread::Mutex + + def test_initialized + assert_raise(TypeError) { + ConditionVariable.allocate.wait(nil) + } + end + + def test_condvar_signal_and_wait + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + result = [] + woken = nil + mutex.synchronize do + t = Thread.new do + mutex.synchronize do + result << 1 + condvar.signal + end + end + + result << 0 + 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 = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + locked = false + thread = Thread.new do + Thread.current.abort_on_exception = false + mutex.synchronize do + assert_raise(Interrupt) { + condvar.wait(mutex) + } + locked = mutex.locked? + end + end + + until thread.stop? + sleep(0.1) + end + + thread.raise Interrupt, "interrupt a dead condition variable" + thread.join + assert(locked) + end + + def test_condvar_wait_and_broadcast + nr_threads = 3 + threads = Array.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + result = [] + + nr_threads.times do |i| + threads[i] = Thread.new do + mutex.synchronize do + result << "C1" + condvar.wait mutex + result << "C2" + end + end + end + sleep 0.1 + mutex.synchronize do + result << "P1" + condvar.broadcast + result << "P2" + end + Timeout.timeout(5) do + nr_threads.times do |i| + threads[i].join + end + 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 = Thread::Mutex.new + cv = Thread::ConditionVariable.new + + klass = nil + mesg = nil + begin + mutex.lock + cv.wait mutex + mutex.unlock + rescue Exception => e + klass = e.class + mesg = e.message + end + puts klass + print mesg +INPUT + end + + def test_condvar_wait_deadlock_2 + nr_threads = 3 + threads = Array.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + nr_threads.times do |i| + if (i != 0) + mutex.unlock + end + threads[i] = Thread.new do + mutex.synchronize do + condvar.wait mutex + end + end + mutex.lock + end + + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) { condvar.wait mutex } + end + mutex.unlock + threads.each(&:kill) + threads.each(&:join) + end + + def test_condvar_timed_wait + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + timeout = 0.3 + locked = false + woken = true + + t0 = Time.now + mutex.synchronize do + begin + woken = condvar.wait(mutex, timeout) + ensure + locked = mutex.locked? + end + end + t1 = Time.now + t = t1-t0 + + assert_operator(timeout*0.9, :<, t) + assert(locked) + assert_nil(woken) + end + + def test_condvar_nolock + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + assert_raise(ThreadError) {condvar.wait(mutex)} + end + + def test_condvar_nolock_2 + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex)} + end.join + end + + def test_condvar_nolock_3 + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex, 0.1)} + end.join + end + + def test_condvar_empty_signal + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} } + end + + def test_condvar_empty_broadcast + 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 = Thread::ConditionVariable.new + assert_raise(NoMethodError, bug9440) do + condvar.dup + end + end + + (DumpableCV = ConditionVariable.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + condvar = Thread::ConditionVariable.new + assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do + Marshal.dump(condvar) + end + + condvar = DumpableCV.new + assert_raise(TypeError, bug9674) do + Marshal.dump(condvar) + end + end + + def test_condvar_fork + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + thrs = (1..10).map do + Thread.new { mutex.synchronize { condvar.wait(mutex) } } + end + thrs.each { 3.times { Thread.pass } } + pid = fork do + th = Thread.new do + mutex.synchronize { condvar.wait(mutex) } + :ok + end + until th.join(0.01) + mutex.synchronize { condvar.broadcast } + end + exit!(th.value == :ok ? 0 : 1) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + until thrs.empty? + mutex.synchronize { condvar.broadcast } + thrs.delete_if { |t| t.join(0.01) } + end + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb new file mode 100644 index 0000000000..723450ad23 --- /dev/null +++ b/test/ruby/test_thread_queue.rb @@ -0,0 +1,708 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' +require 'timeout' + +class TestThreadQueue < Test::Unit::TestCase + Queue = Thread::Queue + SizedQueue = Thread::SizedQueue + + def test_queue_initialized + assert_raise(TypeError) { + Queue.allocate.push(nil) + } + end + + def test_sized_queue_initialized + assert_raise(TypeError) { + SizedQueue.allocate.push(nil) + } + end + + def test_queue + grind(5, 1000, 15, Queue) + end + + def test_sized_queue + grind(5, 1000, 15, SizedQueue, 1000) + end + + def grind(num_threads, num_objects, num_iterations, klass, *args) + from_workers = klass.new(*args) + to_workers = klass.new(*args) + + workers = (1..num_threads).map { + Thread.new { + while object = to_workers.pop + from_workers.push object + end + } + } + + Thread.new { + num_iterations.times { + num_objects.times { to_workers.push 99 } + num_objects.times { from_workers.pop } + } + }.join + + # close the queue the old way to test for backwards-compatibility + num_threads.times { to_workers.push nil } + workers.each { |t| t.join } + + assert_equal 0, from_workers.size + 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 = Thread::SizedQueue.new(1) + assert_equal 1, q.max + assert_raise(ArgumentError) { Thread::SizedQueue.new(0) } + assert_raise(ArgumentError) { Thread::SizedQueue.new(-1) } + end + + def test_sized_queue_assign_max + q = Thread::SizedQueue.new(2) + assert_equal(2, q.max) + q.max = 1 + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = 0 } + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = -1 } + assert_equal(1, q.max) + + before = q.max + q.max.times { q << 1 } + t1 = Thread.new { q << 1 } + sleep 0.01 until t1.stop? + q.max = q.max + 1 + assert_equal before + 1, q.max + ensure + t1.join if t1 + end + + def test_queue_pop_interrupt + q = Thread::Queue.new + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_queue_pop_timeout + q = Thread::Queue.new + q << 1 + assert_equal 1, q.pop(timeout: 1) + + t1 = Thread.new { q.pop(timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.pop(timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_queue_pop_non_block + q = Thread::Queue.new + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_pop_interrupt + q = Thread::SizedQueue.new(1) + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_sized_queue_pop_timeout + q = Thread::SizedQueue.new(1) + + q << 1 + assert_equal 1, q.pop(timeout: 1) + + t1 = Thread.new { q.pop(timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.pop(timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_sized_queue_pop_non_block + q = Thread::SizedQueue.new(1) + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_push_timeout + q = Thread::SizedQueue.new(1) + + q << 1 + assert_equal 1, q.size + + t1 = Thread.new { q.push(2, timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.push(2, timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_sized_queue_push_interrupt + q = Thread::SizedQueue.new(1) + q.push(1) + assert_raise_with_message(ThreadError, /full/) do + q.push(2, true) + end + end + + def test_sized_queue_push_non_block + q = Thread::SizedQueue.new(1) + q.push(1) + t1 = Thread.new { q.push(2) } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_thr_kill + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + bug5343 = '[ruby-core:39634]' + Dir.mktmpdir {|d| + timeout = EnvUtil.apply_timeout_scale(60) + 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 = Thread::Queue.new + r, w = IO.pipe + th = Thread.start { + queue.push(nil) + r.read 1 + } + queue.pop + th.kill + th.join + end + _eom + rescue Timeout::Error + count = File.read("#{d}/test_thr_kill_count").to_i + flunk "only #{count}/#{total_count} done in #{timeout} seconds." + end + } + end + + def test_queue_push_return_value + q = Thread::Queue.new + retval = q.push(1) + assert_same q, retval + end + + def test_queue_clear_return_value + q = Thread::Queue.new + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_clear + # 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 + sq << 1 + end + + t2 = Thread.new do + sq << 1 + end + + t3 = Thread.new do + Thread.pass + sq.clear + end + + [t3, t2, t1].each(&:join) + assert_equal sq.length, 2 + end + + def test_sized_queue_push_return_value + q = Thread::SizedQueue.new(1) + retval = q.push(1) + assert_same q, retval + end + + def test_sized_queue_clear_return_value + q = Thread::SizedQueue.new(1) + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_throttle + q = Thread::SizedQueue.new(1) + i = 0 + consumer = Thread.new do + while q.pop + i += 1 + Thread.pass + end + end + nprod = 4 + npush = 100 + + producer = nprod.times.map do + Thread.new do + npush.times { q.push(true) } + end + end + producer.each(&:join) + q.push(nil) + consumer.join + assert_equal(nprod * npush, i) + end + + def test_queue_thread_raise + q = Thread::Queue.new + th1 = Thread.new do + begin + q.pop + rescue RuntimeError + sleep + end + end + th2 = Thread.new do + sleep 0.1 + q.pop + end + sleep 0.1 + th1.raise + sleep 0.1 + q << :s + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(1) { th2.join } + end + ensure + [th1, th2].each do |th| + if th and th.alive? + th.wakeup + th.join + end + end + end + + def test_dup + bug9440 = '[ruby-core:59961] [Bug #9440]' + q = Thread::Queue.new + assert_raise(NoMethodError, bug9440) do + q.dup + end + end + + (DumpableQueue = Queue.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + q = Thread::Queue.new + assert_raise_with_message(TypeError, /#{Queue}/, bug9674) do + Marshal.dump(q) + end + + sq = Thread::SizedQueue.new(1) + assert_raise_with_message(TypeError, /#{SizedQueue}/, bug9674) do + Marshal.dump(sq) + end + + q = DumpableQueue.new + assert_raise(TypeError, bug9674) do + Marshal.dump(q) + end + end + + def test_close + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate.call + assert_equal false, q.closed? + q << :something + assert_equal q, q.close + assert q.closed? + assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing} + assert_equal q.pop, :something + assert_nil q.pop + assert_nil q.pop + # non-blocking + assert_raise_with_message(ThreadError, /queue empty/){q.pop(non_block=true)} + end + end + + # test that waiting producers are woken up on close + def close_wakeup( num_items, num_threads, &qcreate ) + raise "This test won't work with num_items(#{num_items}) >= num_threads(#{num_threads})" if num_items >= num_threads + + # create the Queue + q = yield + threads = num_threads.times.map{Thread.new{q.pop}} + num_items.times{|i| q << i} + + # wait until queue empty + (Thread.pass; sleep 0.01) until q.size == 0 + + # close the queue so remaining threads will wake up + q.close + + # wait for them to go away + Thread.pass until threads.all?{|thr| thr.status == false} + + # check that they've gone away. Convert nil to -1 so we can sort and do the comparison + expected_values = [-1] * (num_threads - num_items) + num_items.times.to_a + assert_equal expected_values, threads.map{|thr| thr.value || -1 }.sort + end + + def test_queue_close_wakeup + close_wakeup(15, 18){Thread::Queue.new} + end + + def test_size_queue_close_wakeup + close_wakeup(5, 8){Thread::SizedQueue.new 9} + end + + def test_sized_queue_one_closed_interrupt + q = Thread::SizedQueue.new 1 + q << :one + t1 = Thread.new { + Thread.current.report_on_exception = false + q << :two + } + sleep 0.01 until t1.stop? + q.close + assert_raise(ClosedQueueError) {t1.join} + + assert_equal 1, q.size + assert_equal :one, q.pop + assert q.empty?, "queue not empty" + end + + # make sure that shutdown state is handled properly by empty? for the non-blocking case + def test_empty_non_blocking + q = Thread::SizedQueue.new 3 + 3.times{|i| q << i} + + # these all block cos the queue is full + prod_threads = 4.times.map {|i| + Thread.new { + Thread.current.report_on_exception = false + q << 3+i + } + } + sleep 0.01 until prod_threads.all?{|thr| thr.stop?} + + items = [] + # sometimes empty? is false but pop will raise ThreadError('empty'), + # meaning a value is not immediately available but will be soon. + while prod_threads.any?(&:alive?) or !q.empty? + items << q.pop(true) rescue nil + end + assert_join_threads(prod_threads) + items.compact! + + assert_equal 7.times.to_a, items.sort + assert q.empty? + end + + def test_sized_queue_closed_push_non_blocking + 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 = Thread::SizedQueue.new 3 + prod_threads = 6.times.map do |i| + thr = Thread.new{ + Thread.current.report_on_exception = false + q << i + } + thr[:pc] = i + thr + end + + # wait until some producer threads have finished, and the other 3 are blocked + sleep 0.01 while prod_threads.reject{|t| t.status}.count < 3 + # this would ensure that all producer threads call push before close + # sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + # more than prod_threads + cons_threads = 10.times.map do |i| + thr = Thread.new{q.pop}; thr[:pc] = i; thr + end + + # values that came from the queue + popped_values = cons_threads.map &:value + + # wait untl all threads have finished + sleep 0.01 until prod_threads.find_all{|t| t.status}.count == 0 + + # pick only the producer threads that got in before close + successful_prod_threads = prod_threads.reject{|thr| thr.status == nil} + assert_nothing_raised{ successful_prod_threads.map(&:value) } + + # the producer threads that tried to push after q.close should all fail + unsuccessful_prod_threads = prod_threads - successful_prod_threads + unsuccessful_prod_threads.each do |thr| + assert_raise(ClosedQueueError){ thr.value } + end + + assert_equal cons_threads.size, popped_values.size + assert_equal 0, q.size + + # check that consumer threads with values match producers that called push before close + assert_equal successful_prod_threads.map{|thr| thr[:pc]}, popped_values.compact.sort + assert_nil q.pop + end + + def test_deny_pushers + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + synq = Thread::Queue.new + prod_threads = 20.times.map do |i| + Thread.new { + synq.pop + assert_raise(ClosedQueueError) { + q << i + } + } + end + q.close + synq.close # start producer threads + + prod_threads.each(&:join) + end + end + + # size should account for waiting pushers during shutdown + def sized_queue_size_close + q = Thread::SizedQueue.new 4 + 4.times{|i| q << i} + Thread.new{ q << 5 } + Thread.new{ q << 6 } + assert_equal 4, q.size + assert_equal 4, q.items + q.close + assert_equal 6, q.size + assert_equal 4, q.items + end + + def test_blocked_pushers_empty + q = Thread::SizedQueue.new 3 + prod_threads = 6.times.map do |i| + Thread.new{ + Thread.current.report_on_exception = false + q << i + } + end + + # this ensures that all producer threads call push before close + sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + ary = [] + until q.empty? + ary << q.pop + end + assert_equal 0, q.size + + assert_equal 3, ary.size + ary.each{|e| assert [0,1,2,3,4,5].include?(e)} + assert_nil q.pop + + prod_threads.each{|t| + begin + t.join + rescue + end + } + end + + # test thread wakeup on one-element SizedQueue with close + def test_one_element_sized_queue + q = Thread::SizedQueue.new 1 + t = Thread.new{ q.pop } + q.close + assert_nil t.value + end + + def test_close_twice + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + q.close + assert_nothing_raised(ClosedQueueError){q.close} + end + end + + def test_queue_close_multi_multi + q = Thread::SizedQueue.new rand(800..1200) + + count_items = rand(3000..5000) + count_producers = rand(10..20) + + producers = count_producers.times.map do + Thread.new do + sleep(rand / 100) + count_items.times{|i| q << [i,"#{i} for #{Thread.current.inspect}"]} + end + end + + consumers = rand(7..12).times.map do + Thread.new do + count = 0 + while e = q.pop + i, st = e + count += 1 if i.is_a?(Integer) && st.is_a?(String) + end + count + end + end + + # No dead or finished threads, give up to 10 seconds to start running + t = Time.now + Thread.pass until Time.now - t > 10 || (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/} + + assert (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/}, 'no threads running' + + # just exercising the concurrency of the support methods. + counter = Thread.new do + until q.closed? && q.empty? + raise if q.size > q.max + # otherwise this exercise causes too much contention on the lock + sleep 0.01 + end + end + + producers.each &:join + q.close + + # results not randomly distributed. Not sure why. + # consumers.map{|thr| thr.value}.each do |x| + # assert_not_equal 0, x + # end + + all_items_count = consumers.map{|thr| thr.value}.inject(:+) + assert_equal count_items * count_producers, all_items_count + + # don't leak this thread + assert_nothing_raised{counter.join} + end + + def test_queue_with_trap + if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/) + omit 'This test fails too often on AppVeyor vs140' + end + if RUBY_PLATFORM.match?(/mingw/) + omit 'This test fails too often on MinGW' + end + + assert_in_out_err([], <<-INPUT, %w(INT INT exit), []) + q = Thread::Queue.new + trap(:INT){ + q.push 'INT' + } + Thread.new{ + loop{ + Process.kill :INT, $$ + sleep 0.1 + } + } + puts q.pop + puts q.pop + puts 'exit' + INPUT + end + + def test_fork_while_queue_waiting + 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? + + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(6) unless q.empty? + exit!(7) unless sq.empty? + q.push :child_q + sq.push :child_sq + exit!(3) if q.pop != :child_q + exit!(4) if sq.pop != :child_sq + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + q.push :thq + sq.push :thsq + assert_equal :thq, thq.value + assert_equal :thsq, thsq.value + + sq.push(1) + th = Thread.new { q.pop; sq.pop } + thsq = Thread.new { sq.push(2) } + Thread.pass until th.stop? && thsq.stop? + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(3) unless q.empty? + exit!(4) if sq.empty? + exit!(5) if sq.pop != 1 + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + assert_predicate thsq, :stop? + assert_equal 1, sq.pop + assert_same sq, thsq.value + q.push('restart th') + assert_equal 2, th.value + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index faeff8ded3..0faad878fc 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,108 @@ class TestTime < Test::Unit::TestCase t = Time.new(*tm, "-12:00") assert_equal([2001,2,28,23,59,30,-43200], [t.year, t.month, t.mday, t.hour, t.min, t.sec, t.gmt_offset], bug4090) assert_raise(ArgumentError) { Time.new(2000,1,1, 0,0,0, "+01:60") } + msg = /invalid value for Integer/ + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, 1, "+09:99") } + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, "+09:99") } + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, "+09:99") } + + assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], Time.new(2000, 1, 1, 0, 0, 0, "-00:00").to_a) + assert_equal([0, 0, 0, 2, 1, 2000, 0, 2, false, "UTC"], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a) + end + + def test_new_from_string + assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") } + + t = Time.utc(2020, 12, 24, 15, 56, 17) + assert_equal(t, Time.new("2020-12-24T15:56:17Z")) + assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00")) + assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30")) + assert_equal(t, Time.new("2020-12-25 00:56:17 +0900")) + assert_equal(t, Time.new("2020-12-25 00:57:47 +090130")) + assert_equal(t, Time.new("2020-12-25T00:56:17+09:00")) + assert_raise_with_message(ArgumentError, /missing sec part/) { + Time.new("2020-12-25 00:56 +09:00") + } + assert_raise_with_message(ArgumentError, /missing min part/) { + Time.new("2020-12-25 00 +09:00") + } + + assert_equal(Time.new(2021), Time.new("2021")) + assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00")) + + assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec) + assert_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec) + assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec) + assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec) + assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") { + Time.new("2020-12-25 00:56:17. +0900") + } + assert_raise_with_message(ArgumentError, /year must be 4 or more/) { + Time.new("021-12-25 00:00:00.123456 +09:00") + } + assert_raise_with_message(ArgumentError, /fraction min is.*56\./) { + Time.new("2020-12-25 00:56. +0900") + } + assert_raise_with_message(ArgumentError, /fraction hour is.*00\./) { + Time.new("2020-12-25 00. +0900") + } + assert_raise_with_message(ArgumentError, /two digits sec.*:017\b/) { + Time.new("2020-12-25 00:56:017 +0900") + } + assert_raise_with_message(ArgumentError, /two digits sec.*:9\b/) { + Time.new("2020-12-25 00:56:9 +0900") + } + assert_raise_with_message(ArgumentError, /sec out of range/) { + Time.new("2020-12-25 00:56:64 +0900") + } + assert_raise_with_message(ArgumentError, /two digits min.*:056\b/) { + Time.new("2020-12-25 00:056:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits min.*:5\b/) { + Time.new("2020-12-25 00:5:17 +0900") + } + assert_raise_with_message(ArgumentError, /min out of range/) { + Time.new("2020-12-25 00:64:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits hour.*\b000\b/) { + Time.new("2020-12-25 000:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits hour.*\b0\b/) { + Time.new("2020-12-25 0:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /hour out of range/) { + Time.new("2020-12-25 33:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mday.*\b025\b/) { + Time.new("2020-12-025 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mday.*\b5\b/) { + Time.new("2020-12-5 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /mday out of range/) { + Time.new("2020-12-33 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mon.*\b012\b/) { + Time.new("2020-012-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mon.*\b1\b/) { + Time.new("2020-1-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /mon out of range/) { + Time.new("2020-17-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /no time information/) { + Time.new("2020-12") + } + assert_raise_with_message(ArgumentError, /no time information/) { + Time.new("2020-12-02") + } + assert_raise_with_message(ArgumentError, /can't parse/) { + Time.new(" 2020-12-02 00:00:00") + } + assert_raise_with_message(ArgumentError, /can't parse/) { + Time.new("2020-12-02 00:00:00 ") + } end def test_time_add() @@ -108,6 +212,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. + omit if Test::Unit::Runner.current_repeat_count > 0 + assert_equal(0x7fffffff, Time.utc(2038, 1, 19, 3, 14, 7).tv_sec) assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) else @@ -173,7 +281,7 @@ class TestTime < Test::Unit::TestCase assert_equal(10000, Time.at(0.00001).nsec) assert_equal(3000, Time.at(0.000003).nsec) assert_equal(200, Time.at(0.0000002r).nsec) - assert_equal(199, Time.at(0.0000002).nsec) + assert_in_delta(200, Time.at(0.0000002).nsec, 1, "should be within FP error") assert_equal(10, Time.at(0.00000001).nsec) assert_equal(1, Time.at(0.000000001).nsec) @@ -236,6 +344,10 @@ class TestTime < Test::Unit::TestCase assert_equal(1, Time.at(0, 0.001).nsec) end + def test_at_splat + assert_equal(Time.at(1, 2), Time.at(*[1, 2])) + end + def test_at_with_unit assert_equal(123456789, Time.at(0, 123456789, :nanosecond).nsec) assert_equal(123456789, Time.at(0, 123456789, :nsec).nsec) @@ -375,6 +487,21 @@ 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)) + end + + def test_marshal_distant_future + assert_marshal_roundtrip(Time.utc(30000, 1, 1)) + assert_marshal_roundtrip(Time.utc(5.67e9, 4, 8)) + end + def test_at3 t2000 = get_t2000 assert_equal(t2000, Time.at(t2000)) @@ -407,8 +534,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 @@ -421,6 +550,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 @@ -543,6 +676,34 @@ class TestTime < Test::Unit::TestCase assert_equal(Time.at(946684800).getlocal.to_s, Time.at(946684800).to_s) end + def test_inspect + t2000 = get_t2000 + assert_equal("2000-01-01 00:00:00 UTC", t2000.inspect) + assert_equal(Encoding::US_ASCII, t2000.inspect.encoding) + assert_kind_of(String, Time.at(946684800).getlocal.inspect) + assert_equal(Time.at(946684800).getlocal.inspect, Time.at(946684800).inspect) + + t2000 = get_t2000 + 1/10r + assert_equal("2000-01-01 00:00:00.1 UTC", t2000.inspect) + t2000 = get_t2000 + 1/1000000000r + assert_equal("2000-01-01 00:00:00.000000001 UTC", t2000.inspect) + t2000 = get_t2000 + 1/10000000000r + assert_equal("2000-01-01 00:00:00 1/10000000000 UTC", t2000.inspect) + t2000 = get_t2000 + 0.1 + assert_equal("2000-01-01 00:00:00 3602879701896397/36028797018963968 UTC", t2000.inspect) + + t2000 = get_t2000 + t2000 = t2000.localtime(9*3600) + assert_equal("2000-01-01 09:00:00 +0900", t2000.inspect) + + 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) zone = time.zone assert_predicate(zone, :valid_encoding?) @@ -556,15 +717,18 @@ class TestTime < Test::Unit::TestCase def test_zone assert_zone_encoding Time.now + t = Time.now.utc + assert_equal("UTC", t.zone) + assert_nil(t.getlocal(0).zone) + 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 @@ -667,7 +831,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)) @@ -847,6 +1013,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") @@ -860,6 +1033,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 @@ -966,6 +1150,63 @@ class TestTime < Test::Unit::TestCase } end + def test_floor + t = Time.utc(1999,12,31, 23,59,59) + t2 = (t+0.4).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.49).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.5).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.4).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.49).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.5).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + + t2 = (t+0.123456789).floor(4) + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(Rational(1234,10000), t2.subsec) + end + + def test_ceil + t = Time.utc(1999,12,31, 23,59,59) + t2 = (t+0.4).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.49).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.5).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.4).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.49).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.5).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + + 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 bug5012 = "[ruby-dev:44071]" @@ -1039,6 +1280,9 @@ class TestTime < Test::Unit::TestCase end def test_2038 + # Giveup to try 2nd test because some state is changed. + omit if Test::Unit::Runner.current_repeat_count > 0 + if no_leap_seconds? assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) end @@ -1056,6 +1300,29 @@ class TestTime < Test::Unit::TestCase assert_equal(min, t.min) assert_equal(sec, t.sec) } + assert_equal(Time.local(2038,3,1), Time.local(2038,2,29)) + assert_equal(Time.local(2038,3,2), Time.local(2038,2,30)) + assert_equal(Time.local(2038,3,3), Time.local(2038,2,31)) + assert_equal(Time.local(2040,2,29), Time.local(2040,2,29)) + assert_equal(Time.local(2040,3,1), Time.local(2040,2,30)) + assert_equal(Time.local(2040,3,2), Time.local(2040,2,31)) + n = 2 ** 64 + n += 400 - n % 400 # n is over 2^64 and multiple of 400 + assert_equal(Time.local(n,2,29),Time.local(n,2,29)) + assert_equal(Time.local(n,3,1), Time.local(n,2,30)) + assert_equal(Time.local(n,3,2), Time.local(n,2,31)) + n += 100 + assert_equal(Time.local(n,3,1), Time.local(n,2,29)) + assert_equal(Time.local(n,3,2), Time.local(n,2,30)) + assert_equal(Time.local(n,3,3), Time.local(n,2,31)) + n += 4 + assert_equal(Time.local(n,2,29),Time.local(n,2,29)) + assert_equal(Time.local(n,3,1), Time.local(n,2,30)) + assert_equal(Time.local(n,3,2), Time.local(n,2,31)) + n += 1 + assert_equal(Time.local(n,3,1), Time.local(n,2,29)) + assert_equal(Time.local(n,3,2), Time.local(n,2,30)) + assert_equal(Time.local(n,3,3), Time.local(n,2,31)) end def test_future @@ -1074,6 +1341,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 @@ -1103,17 +1394,6 @@ class TestTime < Test::Unit::TestCase assert_equal("366", t.strftime("%j")) end - def test_strftime_no_hidden_garbage - 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 - 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 - end - def test_num_exact_error bad = EnvUtil.labeled_class("BadValue").new x = EnvUtil.labeled_class("Inexact") do @@ -1126,18 +1406,38 @@ class TestTime < Test::Unit::TestCase def test_memsize # Time objects are common in some code, try to keep them small - skip "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM + omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM + omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:DEBUG] require 'objspace' t = Time.at(0) - size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] - case size - when 20 then expect = 50 - when 40 then expect = 86 - else - flunk "Unsupported RVALUE_SIZE=#{size}, update test_memsize" - end + sizeof_timew = + if RbConfig::SIZEOF.key?("uint64_t") && RbConfig::SIZEOF["long"] * 2 <= RbConfig::SIZEOF["uint64_t"] + RbConfig::SIZEOF["uint64_t"] + else + RbConfig::SIZEOF["void*"] # Same size as VALUE + end + sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8 + expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm assert_equal expect, ObjectSpace.memsize_of(t) rescue LoadError => e - skip "failed to load objspace: #{e.message}" + omit "failed to load objspace: #{e.message}" + end + + def test_deconstruct_keys + t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) } + assert_equal( + {year: 2022, month: 10, day: 16, wday: 0, yday: 289, + hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'}, + t.deconstruct_keys(nil) + ) + + assert_equal( + {year: 2022, month: 10, sec: 30}, + t.deconstruct_keys(%i[year month sec nonexistent]) + ) + end + + def test_parse_zero_bigint + assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]' end end diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb index 20a57fe7dd..f66cd9bec2 100644 --- a/test/ruby/test_time_tz.rb +++ b/test/ruby/test_time_tz.rb @@ -1,14 +1,15 @@ # frozen_string_literal: false require 'test/unit' +require '-test-/time' class TestTimeTZ < Test::Unit::TestCase has_right_tz = true 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/ + when /freebsd|openbsd/ has_lisbon_tz = false force_tz_test = true end @@ -84,6 +85,19 @@ class TestTimeTZ < Test::Unit::TestCase has_right_tz &&= have_tz_offset?("right/America/Los_Angeles") has_lisbon_tz &&= have_tz_offset?("Europe/Lisbon") + CORRECT_TOKYO_DST_1951 = with_tz("Asia/Tokyo") { + if Time.local(1951, 5, 6, 12, 0, 0).dst? # noon, DST + if Time.local(1951, 5, 6, 1, 0, 0).dst? # DST with fixed tzdata + Time.local(1951, 9, 8, 23, 0, 0).dst? ? "2018f" : "2018e" + end + end + } + 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 @@ -97,6 +111,18 @@ class TestTimeTZ < Test::Unit::TestCase assert_equal(expected, real, m) end + def test_localtime_zone + t = with_tz("America/Los_Angeles") { + Time.local(2000, 1, 1) + } + omit "force_tz_test is false on this environment" unless t + z1 = t.zone + z2 = with_tz(tz="Asia/Singapore") { + t.localtime.zone + } + assert_equal(z2, z1) + end + def test_america_los_angeles with_tz(tz="America/Los_Angeles") { assert_time_constructor(tz, "2007-03-11 03:00:00 -0700", :local, [2007,3,11,2,0,0]) @@ -117,21 +143,31 @@ 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 def test_asia_tokyo with_tz(tz="Asia/Tokyo") { - assert_time_constructor(tz, "1951-05-06 03:00:00 +1000", :local, [1951,5,6,2,0,0]) - assert_time_constructor(tz, "1951-05-06 03:59:59 +1000", :local, [1951,5,6,2,59,59]) + h = CORRECT_TOKYO_DST_1951 ? 0 : 2 + assert_time_constructor(tz, "1951-05-06 0#{h+1}:00:00 +1000", :local, [1951,5,6,h,0,0]) + assert_time_constructor(tz, "1951-05-06 0#{h+1}:59:59 +1000", :local, [1951,5,6,h,59,59]) assert_time_constructor(tz, "2010-06-10 06:13:28 +0900", :local, [2010,6,10,6,13,28]) } end + def test_asia_kuala_lumpur + with_tz(tz="Asia/Kuala_Lumpur") { + assert_time_constructor(tz, "1933-01-01 00:20:00 +0720", :local, [1933]) + } + end + def test_canada_newfoundland with_tz(tz="America/St_Johns") { assert_time_constructor(tz, "2007-11-03 23:00:59 -0230", :new, [2007,11,3,23,0,59,:dst]) @@ -166,15 +202,23 @@ 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 def test_pacific_kiritimati with_tz(tz="Pacific/Kiritimati") { - assert_time_constructor(tz, "1994-12-31 23:59:59 -1000", :local, [1994,12,31,23,59,59]) - assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,1,0,0,0]) - assert_time_constructor(tz, "1995-01-02 23:59:59 +1400", :local, [1995,1,1,23,59,59]) + assert_time_constructor(tz, "1994-12-30 00:00:00 -1000", :local, [1994,12,30,0,0,0]) + assert_time_constructor(tz, "1994-12-30 23:59:59 -1000", :local, [1994,12,30,23,59,59]) + if CORRECT_KIRITIMATI_SKIP_1994 + assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1994,12,31,0,0,0]) + assert_time_constructor(tz, "1995-01-01 23:59:59 +1400", :local, [1994,12,31,23,59,59]) + assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1995,1,1,0,0,0]) + else + assert_time_constructor(tz, "1994-12-31 23:59:59 -1000", :local, [1994,12,31,23,59,59]) + assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,1,0,0,0]) + assert_time_constructor(tz, "1995-01-02 23:59:59 +1400", :local, [1995,1,1,23,59,59]) + end assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,2,0,0,0]) } end @@ -188,6 +232,29 @@ class TestTimeTZ < Test::Unit::TestCase } end if has_right_tz + def test_right_utc_switching + with_tz("UTC") { # ensure no leap second timezone + 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, "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_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + with_tz("right/UTC") { + 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_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + end if has_right_tz + def test_right_america_los_angeles with_tz(tz="right/America/Los_Angeles") { assert_time_constructor(tz, "2008-12-31 15:59:59 -0800", :local, [2008,12,31,15,59,59]) @@ -196,6 +263,68 @@ class TestTimeTZ < Test::Unit::TestCase } end if has_right_tz + def test_utc_names + 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 + assert_equal( +1*3600, Time.new(2019, 1, 1, 0, 0, 0, "A").gmtoff) + assert_equal( +2*3600, Time.new(2019, 1, 1, 0, 0, 0, "B").gmtoff) + assert_equal( +3*3600, Time.new(2019, 1, 1, 0, 0, 0, "C").gmtoff) + assert_equal( +4*3600, Time.new(2019, 1, 1, 0, 0, 0, "D").gmtoff) + assert_equal( +5*3600, Time.new(2019, 1, 1, 0, 0, 0, "E").gmtoff) + assert_equal( +6*3600, Time.new(2019, 1, 1, 0, 0, 0, "F").gmtoff) + assert_equal( +7*3600, Time.new(2019, 1, 1, 0, 0, 0, "G").gmtoff) + assert_equal( +8*3600, Time.new(2019, 1, 1, 0, 0, 0, "H").gmtoff) + assert_equal( +9*3600, Time.new(2019, 1, 1, 0, 0, 0, "I").gmtoff) + assert_equal(+10*3600, Time.new(2019, 1, 1, 0, 0, 0, "K").gmtoff) + assert_equal(+11*3600, Time.new(2019, 1, 1, 0, 0, 0, "L").gmtoff) + assert_equal(+12*3600, Time.new(2019, 1, 1, 0, 0, 0, "M").gmtoff) + assert_equal( -1*3600, Time.new(2019, 1, 1, 0, 0, 0, "N").gmtoff) + assert_equal( -2*3600, Time.new(2019, 1, 1, 0, 0, 0, "O").gmtoff) + assert_equal( -3*3600, Time.new(2019, 1, 1, 0, 0, 0, "P").gmtoff) + assert_equal( -4*3600, Time.new(2019, 1, 1, 0, 0, 0, "Q").gmtoff) + assert_equal( -5*3600, Time.new(2019, 1, 1, 0, 0, 0, "R").gmtoff) + assert_equal( -6*3600, Time.new(2019, 1, 1, 0, 0, 0, "S").gmtoff) + assert_equal( -7*3600, Time.new(2019, 1, 1, 0, 0, 0, "T").gmtoff) + assert_equal( -8*3600, Time.new(2019, 1, 1, 0, 0, 0, "U").gmtoff) + assert_equal( -9*3600, Time.new(2019, 1, 1, 0, 0, 0, "V").gmtoff) + assert_equal(-10*3600, Time.new(2019, 1, 1, 0, 0, 0, "W").gmtoff) + assert_equal(-11*3600, Time.new(2019, 1, 1, 0, 0, 0, "X").gmtoff) + assert_equal(-12*3600, Time.new(2019, 1, 1, 0, 0, 0, "Y").gmtoff) + assert_equal( 0, Time.new(2019, 1, 1, 0, 0, 0, "Z").gmtoff) + + assert_equal( +1*3600, Time.at(0, in: "A").gmtoff) + assert_equal( +2*3600, Time.at(0, in: "B").gmtoff) + assert_equal( +3*3600, Time.at(0, in: "C").gmtoff) + assert_equal( +4*3600, Time.at(0, in: "D").gmtoff) + assert_equal( +5*3600, Time.at(0, in: "E").gmtoff) + assert_equal( +6*3600, Time.at(0, in: "F").gmtoff) + assert_equal( +7*3600, Time.at(0, in: "G").gmtoff) + assert_equal( +8*3600, Time.at(0, in: "H").gmtoff) + assert_equal( +9*3600, Time.at(0, in: "I").gmtoff) + assert_equal(+10*3600, Time.at(0, in: "K").gmtoff) + assert_equal(+11*3600, Time.at(0, in: "L").gmtoff) + assert_equal(+12*3600, Time.at(0, in: "M").gmtoff) + assert_equal( -1*3600, Time.at(0, in: "N").gmtoff) + assert_equal( -2*3600, Time.at(0, in: "O").gmtoff) + assert_equal( -3*3600, Time.at(0, in: "P").gmtoff) + assert_equal( -4*3600, Time.at(0, in: "Q").gmtoff) + assert_equal( -5*3600, Time.at(0, in: "R").gmtoff) + assert_equal( -6*3600, Time.at(0, in: "S").gmtoff) + assert_equal( -7*3600, Time.at(0, in: "T").gmtoff) + assert_equal( -8*3600, Time.at(0, in: "U").gmtoff) + assert_equal( -9*3600, Time.at(0, in: "V").gmtoff) + assert_equal(-10*3600, Time.at(0, in: "W").gmtoff) + assert_equal(-11*3600, Time.at(0, in: "X").gmtoff) + assert_equal(-12*3600, Time.at(0, in: "Y").gmtoff) + assert_equal( 0, Time.at(0, in: "Z").gmtoff) + end + MON2NUM = { "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12 @@ -327,12 +456,28 @@ 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 +End +Asia/Tokyo Sat Sep 8 13:59:59 1951 UTC = Sat Sep 8 23:59:59 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Sat Sep 8 14:00:00 1951 UTC = Sat Sep 8 23:00:00 1951 JST isdst=0 gmtoff=32400 +2018e +Asia/Tokyo Sat Sep 8 14:59:59 1951 UTC = Sun Sep 9 00:59:59 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Sat Sep 8 15:00:00 1951 UTC = Sun Sep 9 00:00:00 1951 JST isdst=0 gmtoff=32400 +2018f Asia/Tokyo Sat May 5 16:59:59 1951 UTC = Sun May 6 01:59:59 1951 JST isdst=0 gmtoff=32400 Asia/Tokyo Sat May 5 17:00:00 1951 UTC = Sun May 6 03:00:00 1951 JDT isdst=1 gmtoff=36000 Asia/Tokyo Fri Sep 7 15:59:59 1951 UTC = Sat Sep 8 01:59:59 1951 JDT isdst=1 gmtoff=36000 Asia/Tokyo Fri Sep 7 16:00:00 1951 UTC = Sat Sep 8 01:00:00 1951 JST isdst=0 gmtoff=32400 +End + gen_zdump_test <<'End' America/St_Johns Sun Mar 11 03:30:59 2007 UTC = Sun Mar 11 00:00:59 2007 NST isdst=0 gmtoff=-12600 America/St_Johns Sun Mar 11 03:31:00 2007 UTC = Sun Mar 11 01:01:00 2007 NDT isdst=1 gmtoff=-9000 America/St_Johns Sun Nov 4 02:30:59 2007 UTC = Sun Nov 4 00:00:59 2007 NDT isdst=1 gmtoff=-9000 @@ -349,9 +494,18 @@ Europe/London Sun Aug 10 00:59:59 1947 UTC = Sun Aug 10 02:59:59 1947 BDST isds Europe/London Sun Aug 10 01:00:00 1947 UTC = Sun Aug 10 02:00:00 1947 BST isdst=1 gmtoff=3600 Europe/London Sun Nov 2 01:59:59 1947 UTC = Sun Nov 2 02:59:59 1947 BST isdst=1 gmtoff=3600 Europe/London Sun Nov 2 02:00:00 1947 UTC = Sun Nov 2 02:00:00 1947 GMT isdst=0 gmtoff=0 +End + if CORRECT_KIRITIMATI_SKIP_1994 + gen_zdump_test <<'End' +Pacific/Kiritimati Sat Dec 31 09:59:59 1994 UTC = Fri Dec 30 23:59:59 1994 LINT isdst=0 gmtoff=-36000 +Pacific/Kiritimati Sat Dec 31 10:00:00 1994 UTC = Sun Jan 1 00:00:00 1995 LINT isdst=0 gmtoff=50400 +End + else + gen_zdump_test <<'End' Pacific/Kiritimati Sun Jan 1 09:59:59 1995 UTC = Sat Dec 31 23:59:59 1994 LINT isdst=0 gmtoff=-36000 Pacific/Kiritimati Sun Jan 1 10:00:00 1995 UTC = Mon Jan 2 00:00:00 1995 LINT isdst=0 gmtoff=50400 End + end gen_zdump_test <<'End' if has_right_tz right/America/Los_Angeles Fri Jun 30 23:59:60 1972 UTC = Fri Jun 30 16:59:60 1972 PDT isdst=1 gmtoff=-25200 right/America/Los_Angeles Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800 @@ -399,5 +553,277 @@ End gen_variational_zdump_test "lisbon", <<'End' if has_lisbon_tz Europe/Lisbon Mon Jan 1 00:36:31 1912 UTC = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2192 Europe/Lisbon Mon Jan 1 00:36:44 1912 UT = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2205 +Europe/Lisbon Sun Dec 31 23:59:59 1911 UT = Sun Dec 31 23:23:14 1911 LMT isdst=0 gmtoff=-2205 End + + class TZ + attr_reader :name + + def initialize(name, abbr, offset, abbr2 = nil, offset2 = nil) + @name = name + @abbr = abbr + @offset = offset + @abbr2 = abbr2 + @offset2 = offset2 + end + + def dst?(t) + return false unless @offset2 + case t when Integer + return nil + end + case t.mon + when 4..9 + true + else + false + end + end + + def offset(t) + (dst?(t) ? @offset2 : @offset) + end + + def local_to_utc(t) + t - offset(t) + end + + def utc_to_local(t) + t + offset(t) + end + + def abbr(t) + dst?(t) ? @abbr2 : @abbr + end + + def ==(other) + @name == other.name and abbr(0) == other.abbr(0) and offset(0) == other.offset(0) + end + + def inspect + "#<TZ: #@name #@abbr #@offset>" + end + end +end + +module TestTimeTZ::WithTZ + def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + 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) + t = time_class.now(in: tzarg) + assert_equal(tz, t.zone) + end + + def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + utc = time_class.utc(2018, 9, 1, 12, 0, 0) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc) + t = utc.getlocal(tzarg) + h, m = (utc_offset / 60).divmod(60) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t) + end + + def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + 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) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000 + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + m, s = (4000-utc_offset).divmod(60) + h, m = m.divmod(60) + assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t) + end + + def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + utc = time_class.utc(2018, 9, 1, 12, 0, 0) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc) + h, m = (utc_offset / 60).divmod(60) + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(utc.to_i, t.to_i) + utc = utc.to_i + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + 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)) + assert_equal(t, t2) + assert_equal(t.utc_offset, t2.utc_offset) + assert_equal(t.utc_offset, (t2+1).utc_offset) + assert_instance_of(t.zone.class, t2.zone) + assert_equal(t.dst?, t2.dst?) + end + + def subtest_fractional_second(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2024, 1, 1, 23, 59, 59.9r, tzarg) + assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset) + t = time_class.new(2024, 7, 1, 23, 59, 59.9r, tzarg) + assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset) + end + + def test_invalid_zone + make_timezone("INVALID", "INV", 0) + rescue => e + assert_kind_of(StandardError, e) + else + assert false, "ArgumentError expected but nothing was raised." + end + + def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset) + data = [ + "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1], + "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b, + Marshal.dump({offset: utc_offset, zone: abbr})[3..-1], + ].join('') + t = Marshal.load(data) + assert_equal(utc_offset, t.utc_offset) + assert_equal(utc_offset, (t+1).utc_offset) + # t.zone may be a mere String or timezone object. + end + + ZONES = { + "Asia/Tokyo" => ["JST", +9*3600], + "America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600], + "Africa/Ndjamena" => ["WAT", +1*3600], + "Etc/UTC" => ["UTC", 0], + } + + def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil) + self.class::TIME_CLASS.find_timezone(tzname) + end + + def subtest_dst?(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 6, 22, 12, 0, 0, tzarg) + return unless tz.dst?(t) + assert_predicate t, :dst? + t = time_class.new(2018, 12, 22, 12, 0, 0, tzarg) + assert_not_predicate t, :dst? + end + + instance_methods(false).grep(/\Asub(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset, abbr2, utc_offset2)| + define_method("#{test}@#{tzname}") do + tz = make_timezone(tzname, abbr, utc_offset, abbr2, utc_offset2) + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tz, tz, tzname, [abbr, abbr2], [utc_offset, utc_offset2]) + __send__(subtest, time_class, tz, tzname, tzname, [abbr, abbr2], [utc_offset, utc_offset2]) + end + end + end + + instance_methods(false).grep(/\Aname(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset)| + define_method("#{test}@#{tzname}") do + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tzname, abbr, utc_offset) + end + end + end +end + +class TestTimeTZ::DummyTZ < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + ZONES = TestTimeTZ::WithTZ::ZONES + def self.find_timezone(tzname) + tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}" + TestTimeTZ::TZ.new(tzname, *tz) + end + end + + 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 + require "tzinfo" +rescue LoadError +else + class TestTimeTZ::GemTZInfo < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(tzname) + TZInfo::Timezone.get(tzname) + end + end + + def tz + @tz ||= TZInfo::Timezone.get(tzname) + end + end +end + +begin + require "timezone" +rescue LoadError +else + class TestTimeTZ::GemTimezone < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(name) + Timezone.fetch(name) + end + end + + def tz + @tz ||= Timezone[tzname] + end + end end diff --git a/test/ruby/test_trace.rb b/test/ruby/test_trace.rb index 77be94e9be..5842f11aee 100644 --- a/test/ruby/test_trace.rb +++ b/test/ruby/test_trace.rb @@ -20,17 +20,6 @@ class TestTrace < Test::Unit::TestCase untrace_var :$x end - def test_trace_tainted_proc - $x = 1234 - s = proc { $y = :foo } - trace_var :$x, s - s.taint - $x = 42 - assert_equal(:foo, $y) - ensure - untrace_var :$x - end - def test_trace_proc_that_raises_exception $x = 1234 trace_var :$x, proc { raise } diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index 0ec9e6e7b9..24ee9b9533 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -13,8 +13,8 @@ class TestTranscode < Test::Unit::TestCase assert_raise(Encoding::UndefinedConversionError) { "\x80".encode('utf-8','ASCII-8BIT') } assert_raise(Encoding::InvalidByteSequenceError) { "\x80".encode('utf-8','US-ASCII') } assert_raise(Encoding::UndefinedConversionError) { "\xA5".encode('utf-8','iso-8859-3') } - assert_raise(RuntimeError) { 'hello'.freeze.encode!('iso-8859-1') } - assert_raise(RuntimeError) { '\u3053\u3093\u306b\u3061\u306f'.freeze.encode!('iso-8859-1') } # こんにちは + assert_raise(FrozenError) { 'hello'.freeze.encode!('iso-8859-1') } + assert_raise(FrozenError) { '\u3053\u3093\u306b\u3061\u306f'.freeze.encode!('iso-8859-1') } # こんにちは end def test_arguments @@ -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') # Å @@ -2116,6 +2157,28 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("D\u00FCrst", "\xC4\xDC\x99\xA2\xA3", 'IBM037') # Dürst end + def test_CESU_8 + check_both_ways("aijrszAIJRSZ09", "aijrszAIJRSZ09", 'CESU-8') # single bytes + + # check NULL explicitly + # this is different in CESU-8 and in Java modified UTF-8 strings + check_both_ways("\0", "\0", 'CESU-8') + + # U+0080 U+00FC U+00FF U+0100 U+0400 U+0700 U+07FF + two_byte_chars = "\xC2\x80\x20\xC3\xBC\x20\xC3\xBF\x20\xC4\x80\x20\xD0\x80\x20\xDC\x80\x20\xDF\xBF" + check_both_ways(two_byte_chars, two_byte_chars, 'CESU-8') + + # U+0800 U+2200 U+4E00 U+D7FF U+E000 U+FFFF + three_byte_chars = "\xE0\xA0\x80\x20\xE2\x88\x80\x20\xE4\xB8\x80\x20\xED\x9F\xBF\x20\xEE\x80\x80\x20\xEF\xBF\xBF" + check_both_ways(three_byte_chars, three_byte_chars, 'CESU-8') + + # characters outside BMP (double surrogates in CESU-8) + # U+10000 U+20000 U+50000 U+10FFFF + utf8 = "\xF0\x90\x80\x80 \xF0\xA0\x80\x80 \xF1\x90\x80\x80 \xF4\x8F\xBF\xBF" + cesu = "\xED\xA0\x80\xED\xB0\x80 \xED\xA1\x80\xED\xB0\x80 \xED\xA4\x80\xED\xB0\x80 \xED\xAF\xBF\xED\xBF\xBF" + check_both_ways(utf8, cesu, 'CESU-8') + end + def test_nothing_changed a = "James".force_encoding("US-ASCII") b = a.encode("Shift_JIS") @@ -2161,12 +2224,20 @@ class TestTranscode < Test::Unit::TestCase assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback.method(:escape))) end - bug8940 = '[ruby-core:57318] [Bug #8940]' - %w[UTF-32 UTF-16].each do |enc| - define_method("test_pseudo_encoding_inspect(#{enc})") do - assert_normal_exit("'aaa'.encode('#{enc}').inspect", bug8940) - assert_equal(4, 'aaa'.encode(enc).length, "should count in #{enc} with BOM") + 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 + + def test_pseudo_encoding_inspect + s = 'aaa'.encode "UTF-16" + assert_equal '"\xFE\xFF\x00\x61\x00\x61\x00\x61"', s.inspect + + s = 'aaa'.encode "UTF-32" + assert_equal '"\x00\x00\xFE\xFF\x00\x00\x00\x61\x00\x00\x00\x61\x00\x00\x00\x61"', s.inspect end def test_encode_with_invalid_chars @@ -2220,12 +2291,21 @@ 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)) + assert_equal("A\nB\nC", s.encode(usascii, lf_newline: true)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :lf)) end end diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb index 6d513a238f..074b92be55 100644 --- a/test/ruby/test_undef.rb +++ b/test/ruby/test_undef.rb @@ -25,7 +25,7 @@ class TestUndef < Test::Unit::TestCase y = Undef1.new assert_equal "bar", y.bar z = Undef2.new - assert_raise(NoMethodError) { z.foo } + assert_raise(NoMethodError) { z.bar } end def test_special_const_undef @@ -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 3a55189ed7..5e8abb0734 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -29,9 +29,148 @@ 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) + assert_empty(c.singleton_class.class_variables) + assert_raise(NameError){ c.singleton_class.class_variable_get(:@@rule) } + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo], c.singleton_class.class_variables) + assert_equal(1, c.singleton_class.class_variable_get(:@@foo)) + + c = Class.new + c.extend(Olympians) + sc = Class.new(c) + assert_empty(sc.singleton_class.class_variables) + assert_raise(NameError){ sc.singleton_class.class_variable_get(:@@rule) } + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo], sc.singleton_class.class_variables) + assert_equal(1, sc.singleton_class.class_variable_get(:@@foo)) + + c = Class.new + o = c.new + o.extend(Olympians) + assert_equal([:@@rule], o.singleton_class.class_variables) + assert_equal("Zeus", o.singleton_class.class_variable_get(:@@rule)) + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo, :@@rule], o.singleton_class.class_variables.sort) + 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 + end + + module Mod2 + refine Mod do + end + end + + include Mod + + def t + @@_test_include_refined_module_class_variable + end + end + + def test_include_refined_module_class_variable + assert_warning('') do + IncludeRefinedModuleClassVariableNoWarning.new.t end end @@ -56,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 @@ -135,21 +274,46 @@ class TestVariable < Test::Unit::TestCase def test_special_constant_ivars [ true, false, :symbol, "dsym#{rand(9999)}".to_sym, 1, 1.0 ].each do |v| assert_empty v.instance_variables - msg = "can't modify frozen #{v.class}" + msg = "can't modify frozen #{v.class}: #{v.inspect}" - assert_raise_with_message(RuntimeError, msg) do + assert_raise_with_message(FrozenError, msg) do v.instance_variable_set(:@foo, :bar) end + assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do + v.instance_eval do + @a = 1 + end + end + assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)} assert_not_send([v, :instance_variable_defined?, :@foo]) - assert_raise_with_message(RuntimeError, msg) do + assert_raise_with_message(FrozenError, msg) do v.remove_instance_variable(:@foo) end end end + class ExIvar < Hash + def initialize + @a = 1 + @b = 2 + @c = 3 + end + + def ivars + [@a, @b, @c] + end + end + + def test_external_ivars + 3.times{ + # check inline cache for external ivar access + assert_equal [1, 2, 3], ExIvar.new.ivars + } + end + def test_local_variables_with_kwarg bug11674 = '[ruby-core:71437] [Bug #11674]' v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11) diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb index 7144a0cbc6..7efb0e0cd8 100644 --- a/test/ruby/test_vm_dump.rb +++ b/test/ruby/test_vm_dump.rb @@ -3,8 +3,10 @@ require 'test/unit' class TestVMDump < Test::Unit::TestCase def assert_darwin_vm_dump_works(args) - skip if RUBY_PLATFORM !~ /darwin/ - assert_in_out_err(args, "", [], [:*, /^.* main \+ \d+$/, :*, /^\[IMPORTANT\]/, :*]) + pend "macOS 15 is not working with this assertion" if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion` + + omit if RUBY_PLATFORM !~ /darwin/ + assert_in_out_err(args, "", [], /^\[IMPORTANT\]/) end def test_darwin_invalid_call @@ -16,6 +18,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 cde186c5f3..7fc956dfae 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -16,30 +16,50 @@ class TestWeakMap < Test::Unit::TestCase def test_aset_const x = Object.new - assert_raise(ArgumentError) {@wm[true] = x} - assert_raise(ArgumentError) {@wm[false] = x} - assert_raise(ArgumentError) {@wm[nil] = x} - assert_raise(ArgumentError) {@wm[42] = x} - assert_raise(ArgumentError) {@wm[:foo] = x} - assert_raise(ArgumentError) {@wm[x] = true} - assert_raise(ArgumentError) {@wm[x] = false} - assert_raise(ArgumentError) {@wm[x] = nil} - assert_raise(ArgumentError) {@wm[x] = 42} - assert_raise(ArgumentError) {@wm[x] = :foo} + @wm[true] = x + assert_same(x, @wm[true]) + @wm[false] = x + assert_same(x, @wm[false]) + @wm[nil] = x + assert_same(x, @wm[nil]) + @wm[42] = x + assert_same(x, @wm[42]) + @wm[:foo] = x + assert_same(x, @wm[:foo]) + + @wm[x] = true + assert_same(true, @wm[x]) + @wm[x] = false + assert_same(false, @wm[x]) + @wm[x] = nil + assert_same(nil, @wm[x]) + @wm[x] = 42 + assert_same(42, @wm[x]) + @wm[x] = :foo + assert_same(:foo, @wm[x]) end - def test_include? - m = __callee__[/test_(.*)/, 1] - k = "foo" + def assert_weak_include(m, k, n = 100) + if n > 0 + return assert_weak_include(m, k, n-1) + end 1.times do x = Object.new @wm[k] = x assert_send([@wm, m, k]) assert_not_send([@wm, m, "FOO".downcase]) - x = nil + x = Object.new + end + end + + def test_include? + m = __callee__[/test_(.*)/, 1] + k = "foo" + 1.times do + assert_weak_include(m, k) end GC.start - skip # TODO: failure introduced from r60440 + pend('TODO: failure introduced from 837fd5e494731d7d44786f29e7d6e8c27029806f') assert_not_send([@wm, m, k]) end alias test_member? test_include? @@ -53,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 @@ -132,4 +161,56 @@ class TestWeakMap < Test::Unit::TestCase assert_equal(2, @wm.__send__(m)) end alias test_length test_size + + def test_frozen_object + o = Object.new.freeze + assert_nothing_raised(FrozenError) {@wm[o] = 'foo'} + assert_nothing_raised(FrozenError) {@wm['foo'] = o} + end + + def test_no_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #19398]", rss: true, limit: 1.5, timeout: 60) + begin; + 1_000_000.times do + ObjectSpace::WeakMap.new + end + end; + end + + def test_compaction_bug_19529 + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + obj = Object.new + 100.times do |i| + GC.compact + @wm[i] = obj + end + + assert_separately(%w(--disable-gems), <<-'end;') + wm = ObjectSpace::WeakMap.new + obj = Object.new + 100.times do + wm[Object.new] = obj + GC.start + end + GC.compact + end; + end + + def test_replaced_values_bug_19531 + a = "A".dup + b = "B".dup + + @wm[1] = a + @wm[1] = a + @wm[1] = a + + @wm[1] = b + assert_equal b, @wm[1] + + a = nil + GC.start + + assert_equal b, @wm[1] + end end diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb new file mode 100644 index 0000000000..b90235cd7d --- /dev/null +++ b/test/ruby/test_yjit.rb @@ -0,0 +1,1333 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS='test/ruby/test_yjit.rb' + +require 'test/unit' +require 'envutil' +require 'tmpdir' +require_relative '../lib/jit_support' + +return unless JITSupport.yjit_supported? + +# Tests for YJIT with assertions on compilation and side exits +# insipired by the MJIT tests in test/ruby/test_mjit.rb +class TestYJIT < Test::Unit::TestCase + running_with_yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? + + def test_yjit_in_ruby_description + assert_includes(RUBY_DESCRIPTION, '+YJIT') + end if running_with_yjit + + # Check that YJIT is in the version string + 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), + ].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 if running_with_yjit + + def test_command_line_switches + assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/) + assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/) + #assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/) + #assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/) + 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 if running_with_yjit + + def test_compile_setclassvariable + script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo' + assert_compiles(script, insns: %i[setclassvariable], result: 1) + end + + def test_compile_getclassvariable + 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_succ + assert_compiles('1.succ', insns: %i[opt_succ], result: 2) + end + + def test_compile_opt_not + assert_compiles('!false', insns: %i[opt_not], result: true) + assert_compiles('!nil', insns: %i[opt_not], result: true) + 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_string_concat_utf8 + assert_compiles(<<~RUBY, frozen_string_literal: true, result: true) + def str_cat_utf8 + s = String.new + 10.times { s << "✅" } + s + end + + str_cat_utf8 == "✅" * 10 + RUBY + end + + def test_string_concat_ascii + # Constant-get for classes (e.g. String, Encoding) can cause a side-exit in getinlinecache. For now, ignore exits. + assert_compiles(<<~RUBY, exits: :any) + str_arg = "b".encode(Encoding::ASCII) + def str_cat_ascii(arg) + s = String.new(encoding: Encoding::ASCII) + 10.times { s << arg } + s + end + + str_cat_ascii(str_arg) == str_arg * 10 + RUBY + end + + def test_opt_length_in_method + assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5) + def foo(str) + 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_getconstant + assert_compiles(<<~RUBY, insns: %i[getconstant], result: [], call_threshold: 1) + def get_argv(klass) + klass::ARGV + end + + get_argv(Object) + RUBY + end + + def test_compile_getconstant_with_sp_offset + assert_compiles(<<~RUBY, insns: %i[getconstant], result: 2, call_threshold: 1) + class Foo + Bar = 1 + end + + 2.times do + s = Foo # this opt_getconstant_path needs warmup, so 2.times is needed + Class.new(Foo).const_set(:Bar, s::Bar) + end + RUBY + end + + def test_compile_opt_getconstant_path + assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2) + def get_foo + FOO + end + + FOO = 123 + + get_foo # warm inline cache + get_foo + RUBY + end + + def test_opt_getconstant_path_slowpath + assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2) + class A + FOO = 42 + class << self + 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", call_threshold: 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_getblockparam + assert_compiles(<<~'RUBY', insns: [:getblockparam]) + def foo &blk + 2.times do + blk + end + end + + foo {} + foo {} + RUBY + end + + def test_getblockparamproxy + # Currently two side exits as OPTIMIZED_METHOD_TYPE_CALL is unimplemented + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: { opt_send_without_block: 2 }) + def foo &blk + p blk.call + p blk.call + end + + foo { 1 } + foo { 2 } + RUBY + end + + def test_send_blockarg + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {}) + def bar + end + + def foo &blk + bar(&blk) + bar(&blk) + end + + foo + foo + + foo { } + foo { } + RUBY + end + + def test_send_splat + assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {}) + def internal_method(*args) + "#{args.size}##{args.join(",")}" + end + + def jit_method + send(:internal_method, *[1, 2, 3]) + "/P" + end + + jit_method + RUBY + end + + def test_send_multiarg + assert_compiles(<<~'RUBY', result: "3#1,2,3/Q") + def internal_method(*args) + "#{args.size}##{args.join(",")}" + end + + def jit_method + send(:internal_method, 1, 2, 3) + "/Q" + end + + jit_method + RUBY + end + + def test_send_kwargs + # For now, this side-exits when calls include keyword args + assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A", exits: {opt_send_without_block: 1}) + def internal_method(**kw) + "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}" + end + + def jit_method + send(:internal_method, a: 1, b: 2) + "/A" + end + jit_method + RUBY + end + + def test_send_kwargs_in_receiver_only + assert_compiles(<<~'RUBY', result: "0/RK", exits: {}) + def internal_method(**kw) + "#{kw.size}" + end + + def jit_method + send(:internal_method) + "/RK" + end + jit_method + RUBY + end + + def test_send_with_underscores + assert_compiles(<<~'RUBY', result: "0/RK", exits: {}) + def internal_method(**kw) + "#{kw.size}" + end + + def jit_method + __send__(:internal_method) + "/RK" + end + jit_method + RUBY + end + + def test_send_kwargs_splat + # For now, this side-exits when calling with a splat + assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B", exits: {opt_send_without_block: 1}) + def internal_method(**kw) + "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}" + end + + def jit_method + send(:internal_method, **{ a: 1, b: 2 }) + "/B" + end + jit_method + RUBY + end + + def test_send_block + # Setlocal_wc_0 sometimes side-exits on write barrier + assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n", exits: { :setlocal_WC_0 => 0..1 }) + def internal_method(&b) + "b:#{block_given? ? "y" : "n"}" + end + + def jit_method + b7 = proc { 7 } + [ + send(:internal_method), + send(:internal_method, &b7), + send(:internal_method) { 7 }, + send(:internal_method, &nil), + ].join("/") + end + jit_method + RUBY + end + + def test_send_block_calling + assert_compiles(<<~'RUBY', result: "1a2", exits: {}) + def internal_method + out = yield + "1" + out + "2" + end + + def jit_method + __send__(:internal_method) { "a" } + end + jit_method + RUBY + end + + def test_send_block_only_receiver + assert_compiles(<<~'RUBY', result: "b:n", exits: {}) + def internal_method(&b) + "b:#{block_given? ? "y" : "n"}" + end + + def jit_method + send(:internal_method) + end + jit_method + RUBY + end + + def test_send_block_only_sender + assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {}) + def internal_method + "Y" + end + + def jit_method + b7 = proc { 7 } + [ + send(:internal_method), + send(:internal_method, &b7), + send(:internal_method) { 7 }, + send(:internal_method, &nil), + ].join("/") + end + jit_method + RUBY + end + + def test_multisend + assert_compiles(<<~'RUBY', result: "77") + def internal_method + "7" + end + + def jit_method + send(:send, :internal_method) + send(:send, :send, :internal_method) + end + jit_method + RUBY + end + + def test_getivar_opt_plus + assert_no_exits(<<~RUBY) + class TheClass + def initialize + @levar = 1 + end + + def get_sum + sum = 0 + # The type of levar is unknown, + # but this still should not exit + sum += @levar + sum + end + end + + obj = TheClass.new + obj.get_sum + RUBY + end + + def test_super_iseq + assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15) + class A + 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], call_threshold: 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_cfunc_kwarg + assert_no_exits('{}.store(:value, foo: 123)') + assert_no_exits('{}.store(:value, foo: 123, bar: 456, baz: 789)') + assert_no_exits('{}.merge(foo: 123)') + assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)') + end + + # regression test simplified from URI::Generic#hostname= + def test_ctx_different_mappings + 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 test_int_equal + assert_compiles(<<~'RUBY', exits: :any, result: [true, false, true, false, true, false, true, false]) + def eq(a, b) + a == b + end + + def eqq(a, b) + a === b + end + + big1 = 2 ** 65 + big2 = big1 + 1 + [eq(1, 1), eq(1, 2), eq(big1, big1), eq(big1, big2), eqq(1, 1), eqq(1, 2), eqq(big1, big1), eqq(big1, big2)] + RUBY + end + + def test_opt_case_dispatch + assert_compiles(<<~'RUBY', exits: :any, result: [:"1", "2", 3]) + def case_dispatch(val) + case val + when 1 + :"#{val}" + when 2 + "#{val}" + else + val + end + end + + [case_dispatch(1), case_dispatch(2), case_dispatch(3)] + RUBY + end + + def test_code_gc + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + return :not_paged unless add_pages(100) # prepare freeable pages + RubyVM::YJIT.code_gc # first code GC + return :not_compiled1 unless compiles { nil } # should be JITable again + + RubyVM::YJIT.code_gc # second code GC + return :not_compiled2 unless compiles { nil } # should be JITable again + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 2 + + :ok + RUBY + end + + def test_on_stack_code_gc_call + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume != 0 # JIT the fiber + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page + return :broken_resume2 if fiber.resume != 0 # The code should be still callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 1 + + :ok + RUBY + end + + def test_on_stack_code_gc_twice + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while Fiber.yield(nil.to_i); end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page + + return :not_paged2 unless add_pages(300) # add some stuff to be freed + # Not calling fiber.resume here to test the case that the YJIT payload loses some + # information at the previous code GC. The payload should still be there, and + # thus we could know the fiber ISEQ is still on stack on this second code GC. + RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page + + return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't) + return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine + + return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber + RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack + + return :not_paged4 unless add_pages(100) # check everything still works + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 3 + + :ok + RUBY + end + + def test_code_gc_with_many_iseqs + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(250) # use some pages + return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well + + add_pages(2000) # use a whole lot of pages to run out of 1MiB + return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count == 0 + + :ok + RUBY + end + + def test_code_gc_partial_last_page + # call_threshold: 2 to avoid JIT-ing code_gc itself. If code_gc were JITed right before + # code_gc is called, the last page would be on stack. + assert_compiles(<<~'RUBY', exits: :any, result: :ok, call_threshold: 2) + # Leave a bunch of off-stack pages + i = 0 + while i < 1000 + eval("x = proc { 1.to_s }; x.call; x.call") + i += 1 + end + + # On Linux, memory page size != code page size. So the last code page could be partially + # mapped. This call tests that assertions and other things work fine under the situation. + RubyVM::YJIT.code_gc + + :ok + RUBY + end + + def test_trace_script_compiled # not ISEQ_TRACE_EVENTS + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + @eval_counter = 0 + def eval_script + eval('@eval_counter += 1') + end + + @trace_counter = 0 + trace = TracePoint.new(:script_compiled) do |t| + @trace_counter += 1 + end + + eval_script # JIT without TracePoint + trace.enable + eval_script # call with TracePoint + trace.disable + + return :"eval_#{@eval_counter}" if @eval_counter != 2 + return :"trace_#{@trace_counter}" if @trace_counter != 1 + + :ok + RUBY + end + + def test_trace_b_call # ISEQ_TRACE_EVENTS + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + @call_counter = 0 + def block_call + 1.times { @call_counter += 1 } + end + + @trace_counter = 0 + trace = TracePoint.new(:b_call) do |t| + @trace_counter += 1 + end + + block_call # JIT without TracePoint + trace.enable + block_call # call with TracePoint + trace.disable + + return :"call_#{@call_counter}" if @call_counter != 2 + return :"trace_#{@trace_counter}" if @trace_counter != 1 + + :ok + RUBY + end + + def test_send_to_call + assert_compiles(<<~'RUBY', result: :ok) + ->{ :ok }.send(:call) + RUBY + end + + def test_invokeblock_many_locals + # [Bug #19299] + assert_compiles(<<~'RUBY', result: :ok) + def foo + yield + end + + foo do + a1=a2=a3=a4=a5=a6=a7=a8=a9=a10=a11=a12=a13=a14=a15=a16=a17=a18=a19=a20=a21=a22=a23=a24=a25=a26=a27=a28=a29=a30 = :ok + a30 + end + RUBY + end + + def test_bug_19316 + n = 2 ** 64 + # foo's extra param and the splats are relevant + assert_compiles(<<~'RUBY', result: [[n, -n], [n, -n]]) + def foo(_, a, b, c) + [a & b, ~c] + end + + n = 2 ** 64 + args = [0, -n, n, n-1] + + GC.stress = true + [foo(*args), foo(*args)] + RUBY + end + + def test_tracing_str_uplus + assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok) + def str_uplus + _ = 1 + _ = 2 + ret = [+"frfr", __LINE__] + _ = 3 + _ = 4 + + ret + end + + str_uplus + require 'objspace' + ObjectSpace.trace_object_allocations_start + + str, expected_line = str_uplus + alloc_line = ObjectSpace.allocation_sourceline(str) + + if expected_line == alloc_line + :ok + else + [expected_line, alloc_line] + end + RUBY + end + + def test_str_uplus_subclass + assert_compiles(<<~RUBY, frozen_string_literal: true, result: :subclass) + class S < String + def encoding + :subclass + end + end + + def test(str) + (+str).encoding + end + + test "" + test S.new + RUBY + end + + def test_nested_send + #[Bug #19464] + assert_compiles(<<~RUBY, result: [:ok, :ok]) + klass = Class.new do + class << self + alias_method :my_send, :send + + def bar = :ok + + def foo = bar + end + end + + with_break = -> { break klass.send(:my_send, :foo) } + wo_break = -> { klass.send(:my_send, :foo) } + + [with_break[], wo_break[]] + RUBY + end + + def test_return_to_invalidated_block + # [Bug #19463] + assert_compiles(<<~RUBY, result: [1, 1, :ugokanai]) + klass = Class.new do + def self.lookup(hash, key) = hash[key] + + def self.foo(a, b) = [] + + def self.test(hash, key) + [lookup(hash, key), key, "".freeze] + # 05 opt_send_without_block :lookup + # 07 getlocal_WC_0 :hash + # 09 opt_str_freeze "" + # 12 newarray 3 + # 14 leave + # + # YJIT will put instructions (07..14) into a block. + # When String#freeze is redefined from within lookup(), + # the return address to the block is still on-stack. We rely + # on invalidation patching the code at the return address + # to service this situation correctly. + end + end + + # get YJIT to compile test() + hash = { 1 => [] } + 31.times { klass.test(hash, 1) } + + # inject invalidation into lookup() + evil_hash = Hash.new do |_, key| + class String + undef :freeze + def freeze = :ugokanai + end + + key + end + klass.test(evil_hash, 1) + RUBY + end + + def test_str_concat_encoding_mismatch + assert_compiles(<<~'RUBY', result: "incompatible character encodings: ASCII-8BIT and EUC-JP") + def bar(a, b) + a << b + rescue => e + e.message + end + + def foo(a, b, h) + h[nil] + bar(a, b) # Ruby call, not set cfp->pc + end + + h = Hash.new { nil } + foo("\x80".b, "\xA1A1".force_encoding("EUC-JP"), h) + foo("\x80".b, "\xA1A1".force_encoding("EUC-JP"), h) + RUBY + end + + private + + def code_gc_helpers + <<~'RUBY' + def compiles(&block) + failures = RubyVM::YJIT.runtime_stats[:compilation_failure] + block.call + failures == RubyVM::YJIT.runtime_stats[:compilation_failure] + end + + def add_pages(num_jits) + pages = RubyVM::YJIT.runtime_stats[:compiled_page_count] + num_jits.times { return false unless eval('compiles { nil.to_i }') } + pages.nil? || pages < RubyVM::YJIT.runtime_stats[:compiled_page_count] + end + RUBY + end + + def assert_no_exits(script) + assert_compiles(script) + end + + ANY = Object.new + def assert_compiles(test_script, insns: [], call_threshold: 1, stdout: nil, exits: {}, result: ANY, frozen_string_literal: nil, mem_size: nil) + reset_stats = <<~RUBY + RubyVM::YJIT.runtime_stats + RubyVM::YJIT.reset_stats! + RUBY + + write_results = <<~RUBY + stats = RubyVM::YJIT.runtime_stats + + def collect_insns(iseq) + insns = RubyVM::YJIT.insns_compiled(iseq) + iseq.each_child { |c| insns.concat collect_insns(c) } + insns + end + + iseq = RubyVM::InstructionSequence.of(_test_proc) + IO.open(3).write Marshal.dump({ + result: #{result == ANY ? "nil" : "result"}, + stats: stats, + insns: collect_insns(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, call_threshold:, mem_size:) + + 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] + insns_compiled = stats[:insns] + disasm = stats[:disasm] + + # Check that exit counts are as expected + # Full stats are only available when --enable-yjit=dev + if runtime_stats[:all_stats] + recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") } + recorded_exits = recorded_exits.reject { |k, v| v == 0 } + + recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym } + # Exits can be specified as a hash of stat-name symbol to integer for exact exits. + # or stat-name symbol to range if the number of side exits might vary (e.g. write + # barriers, cache misses.) + if exits != :any && + exits != recorded_exits && + !exits.all? { |k, v| v === recorded_exits[k] } # triple-equal checks range membership or integer equality + flunk "Expected #{exits.empty? ? "no" : exits.inspect} exits" \ + ", but got\n#{recorded_exits.inspect}" + end + end + + # Only available when --enable-yjit=dev + if runtime_stats[:all_stats] + missed_insns = insns.dup + + insns_compiled.each do |op| + if missed_insns.include?(op) + # This instruction was compiled + missed_insns.delete(op) + end + end + + unless missed_insns.empty? + flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}" + end + end + end + + def script_shell_encode(s) + # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants. + s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join + end + + def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil) + args = [ + "--disable-gems", + "--yjit-call-threshold=#{call_threshold}", + "--yjit-stats" + ] + args << "--yjit-exec-mem-size=#{mem_size}" if mem_size + args << "-e" << script_shell_encode(script) + stats_r, stats_w = IO.pipe + out, err, status = EnvUtil.invoke_ruby(args, + '', true, true, timeout: timeout, ios: {3 => stats_w} + ) + stats_w.close + stats = stats_r.read + stats = Marshal.load(stats) if !stats.empty? + stats_r.close + [status, out, err, stats] + end +end diff --git a/test/ruby/test_yjit_exit_locations.rb b/test/ruby/test_yjit_exit_locations.rb new file mode 100644 index 0000000000..5421f9c804 --- /dev/null +++ b/test/ruby/test_yjit_exit_locations.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS='test/ruby/test_yjit_exit_locations.rb' + +require 'test/unit' +require 'envutil' +require 'tmpdir' +require_relative '../lib/jit_support' + +return unless JITSupport.yjit_supported? + +# Tests for YJIT with assertions on tracing exits +# insipired by the MJIT tests in test/ruby/test_yjit.rb +class TestYJITExitLocations < Test::Unit::TestCase + def test_yjit_trace_exits_and_v_no_error + _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-trace-exits), '', true, true) + refute_includes(stderr, "NoMethodError") + end + + def test_trace_exits_setclassvariable + script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo' + assert_exit_locations(script) + end + + def test_trace_exits_putobject + assert_exit_locations('true') + assert_exit_locations('123') + assert_exit_locations(':foo') + end + + def test_trace_exits_opt_not + assert_exit_locations('!false') + assert_exit_locations('!nil') + assert_exit_locations('!true') + assert_exit_locations('![]') + end + + private + + def assert_exit_locations(test_script) + write_results = <<~RUBY + IO.open(3).write Marshal.dump({ + enabled: RubyVM::YJIT.trace_exit_locations_enabled?, + exit_locations: RubyVM::YJIT.exit_locations + }) + RUBY + + script = <<~RUBY + _test_proc = -> { + #{test_script} + } + result = _test_proc.call + #{write_results} + RUBY + + run_script = eval_with_jit(script) + # If stats are disabled when configuring, --yjit-exit-locations + # can't be true. We don't want to check if exit_locations hash + # is not empty because that could indicate a bug in the exit + # locations collection. + return unless run_script[:enabled] + exit_locations = run_script[:exit_locations] + + assert exit_locations.key?(:raw) + assert exit_locations.key?(:frames) + assert exit_locations.key?(:lines) + assert exit_locations.key?(:samples) + assert exit_locations.key?(:missed_samples) + assert exit_locations.key?(:gc_samples) + + assert_equal 0, exit_locations[:missed_samples] + assert_equal 0, exit_locations[:gc_samples] + + assert_not_empty exit_locations[:raw] + assert_not_empty exit_locations[:frames] + assert_not_empty exit_locations[:lines] + + exit_locations[:frames].each do |frame_id, frame| + assert frame.key?(:name) + assert frame.key?(:file) + assert frame.key?(:samples) + assert frame.key?(:total_samples) + assert frame.key?(:edges) + end + end + + def eval_with_jit(script) + args = [ + "--disable-gems", + "--yjit-call-threshold=1", + "--yjit-trace-exits" + ] + args << "-e" << script_shell_encode(script) + stats_r, stats_w = IO.pipe + _out, _err, _status = EnvUtil.invoke_ruby(args, + '', true, true, timeout: 1000, ios: { 3 => stats_w } + ) + stats_w.close + stats = stats_r.read + stats = Marshal.load(stats) if !stats.empty? + stats_r.close + stats + end + + def script_shell_encode(s) + # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants. + s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join + end +end |
