diff options
Diffstat (limited to 'test/ruby')
138 files changed, 18662 insertions, 4620 deletions
diff --git a/test/ruby/allpairs.rb b/test/ruby/allpairs.rb index 27b6f5988f..e5893e252a 100644 --- a/test/ruby/allpairs.rb +++ b/test/ruby/allpairs.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false module AllPairs module_function diff --git a/test/ruby/beginmainend.rb b/test/ruby/beginmainend.rb index 6cdfb15ea6..b6de5d65fd 100644 --- a/test/ruby/beginmainend.rb +++ b/test/ruby/beginmainend.rb @@ -1,8 +1,7 @@ -errout = ARGV.shift - +# frozen_string_literal: false BEGIN { puts "b1" - local_begin1 = "local_begin1" + # local_begin1 = "local_begin1" $global_begin1 = "global_begin1" ConstBegin1 = "ConstBegin1" } diff --git a/test/ruby/bug-11928.rb b/test/ruby/bug-11928.rb new file mode 100644 index 0000000000..72b3b0f8ed --- /dev/null +++ b/test/ruby/bug-11928.rb @@ -0,0 +1,14 @@ +class Segfault + at_exit { Segfault.new.segfault } + + define_method 'segfault' do + n = 11928 + v = nil + i = 0 + while i < n + i += 1 + v = (foo rescue $!).local_variables + end + assert_equal(%i[i n v], v.sort) + end +end diff --git a/test/ruby/bug-13526.rb b/test/ruby/bug-13526.rb new file mode 100644 index 0000000000..50c6c67a7d --- /dev/null +++ b/test/ruby/bug-13526.rb @@ -0,0 +1,22 @@ +# From https://bugs.ruby-lang.org/issues/13526#note-1 + +Thread.report_on_exception = true + +sleep if $load +$load = true + +n = 10 +threads = Array.new(n) do + Thread.new do + begin + autoload :Foo, File.expand_path(__FILE__) + Thread.pass + Foo + ensure + Thread.pass + end + end +end + +Thread.pass until threads.all?(&:stop?) +1000.times { Thread.pass } diff --git a/test/ruby/enc/test_big5.rb b/test/ruby/enc/test_big5.rb index e8fe0270a8..5dcf93e8e3 100644 --- a/test/ruby/enc/test_big5.rb +++ b/test/ruby/enc/test_big5.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestBig5 < Test::Unit::TestCase diff --git a/test/ruby/enc/test_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb new file mode 100644 index 0000000000..cd6447e928 --- /dev/null +++ b/test/ruby/enc/test_case_comprehensive.rb @@ -0,0 +1,303 @@ +# frozen_string_literal: true +# Copyright © 2016 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestComprehensiveCaseMapping < 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 + + def self.hex2utf8(s) + s.split(' ').map { |c| c.to_i(16) }.pack('U*') + end + + def self.expand_filename(basename) + File.expand_path("#{UNICODE_DATA_PATH}/#{basename}.txt", __dir__) + end + + def self.data_files_available? + %w[UnicodeData CaseFolding SpecialCasing].all? do |f| + File.exist?(expand_filename(f)) + end + end + + def test_data_files_available + unless TestComprehensiveCaseMapping.data_files_available? + skip "Unicode data files not available in #{UNICODE_DATA_PATH}." + end + end +end + +TestComprehensiveCaseMapping.data_files_available? and class TestComprehensiveCaseMapping + (CaseTest = Struct.new(:method_name, :attributes, :first_data, :follow_data)).class_eval do + def initialize(method_name, attributes, first_data, follow_data=first_data) + super + end + end + + def self.read_data_file(filename) + IO.foreach(expand_filename(filename), encoding: Encoding::ASCII_8BIT) do |line| + if $. == 1 + if filename == 'UnicodeData' + elsif line.start_with?("# #{filename}-#{UNICODE_VERSION}.txt") + else + raise "File Version Mismatch" + end + end + next if /\A(?:[\#@]|\s*\z)|Surrogate/.match?(line) + data = line.chomp.split('#')[0].split(/;\s*/, 15) + code = data[0].to_i(16).chr(Encoding::UTF_8) + yield code, data + end + end + + def self.read_data + @@codepoints = [] + + downcase = Hash.new { |h, c| c } + upcase = Hash.new { |h, c| c } + titlecase = Hash.new { |h, c| c } + casefold = Hash.new { |h, c| c } + swapcase = Hash.new { |h, c| c } + turkic_upcase = Hash.new { |h, c| upcase[c] } + turkic_downcase = Hash.new { |h, c| downcase[c] } + turkic_titlecase = Hash.new { |h, c| titlecase[c] } + turkic_swapcase = Hash.new { |h, c| swapcase[c] } + ascii_upcase = Hash.new { |h, c| /\A[a-zA-Z]\z/.match?(c) ? upcase[c] : c } + ascii_downcase = Hash.new { |h, c| /\A[a-zA-Z]\z/.match?(c) ? downcase[c] : c } + ascii_titlecase = Hash.new { |h, c| /\A[a-zA-Z]\z/.match?(c) ? titlecase[c] : c } + ascii_swapcase = Hash.new { |h, c| /\A[a-z]\z/.match?(c) ? upcase[c] : (/\A[A-Z]\z/.match?(c) ? downcase[c] : c) } + + read_data_file('UnicodeData') do |code, data| + @@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? + end + read_data_file('CaseFolding') do |code, data| + casefold[code] = hex2utf8(data[2]) if data[1] =~ /^[CF]$/ + end + + read_data_file('SpecialCasing') do |code, data| + case data[4] + when '' + upcase[code] = hex2utf8 data[3] + downcase[code] = hex2utf8 data[1] + titlecase[code] = hex2utf8 data[2] + when /\Atr\s*/ + if data[4]!='tr After_I' + turkic_upcase[code] = hex2utf8 data[3] + turkic_downcase[code] = hex2utf8 data[1] + turkic_titlecase[code] = hex2utf8 data[2] + end + end + end + + @@codepoints.each do |c| + if upcase[c] != c + if downcase[c] != c + swapcase[c] = turkic_swapcase[c] = + case c + when "\u01C5" then "\u0064\u017D" + when "\u01C8" then "\u006C\u004A" + when "\u01CB" then "\u006E\u004A" + when "\u01F2" then "\u0064\u005A" + else # Greek + downcase[upcase[c][0]] + "\u0399" + end + else + swapcase[c] = upcase[c] + turkic_swapcase[c] = turkic_upcase[c] + end + else + if downcase[c] != c + swapcase[c] = downcase[c] + turkic_swapcase[c] = turkic_downcase[c] + end + end + end + + [ + CaseTest.new(:downcase, [], downcase), + CaseTest.new(:upcase, [], upcase), + CaseTest.new(:capitalize, [], titlecase, downcase), + CaseTest.new(:swapcase, [], swapcase), + CaseTest.new(:downcase, [:fold], casefold), + CaseTest.new(:upcase, [:turkic], turkic_upcase), + CaseTest.new(:downcase, [:turkic], turkic_downcase), + CaseTest.new(:capitalize, [:turkic], turkic_titlecase, turkic_downcase), + CaseTest.new(:swapcase, [:turkic], turkic_swapcase), + CaseTest.new(:upcase, [:ascii], ascii_upcase), + CaseTest.new(:downcase, [:ascii], ascii_downcase), + CaseTest.new(:capitalize, [:ascii], ascii_titlecase, ascii_downcase), + CaseTest.new(:swapcase, [:ascii], ascii_swapcase), + ] + end + + def self.all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def self.generate_unicode_case_mapping_tests(encoding) + all_tests.each do |test| + attributes = test.attributes.map(&:to_s).join '-' + attributes.prepend '_' unless attributes.empty? + define_method "test_#{encoding}_#{test.method_name}#{attributes}" do + @@codepoints.each do |code| + source = code.encode(encoding) * 5 + target = "#{test.first_data[code]}#{test.follow_data[code]*4}".encode(encoding) + result = source.__send__(test.method_name, *test.attributes) + assert_equal target, result, + proc{"from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}"} + end + end + end + end + + def self.generate_case_mapping_tests(encoding) + all_tests + # preselect codepoints to speed up testing for small encodings + codepoints = @@codepoints.select do |code| + begin + code.encode(encoding) + true + rescue Encoding::UndefinedConversionError + false + end + end + all_tests.each do |test| + attributes = test.attributes.map(&:to_s).join '-' + attributes.prepend '_' unless attributes.empty? + define_method "test_#{encoding}_#{test.method_name}#{attributes}" do + codepoints.each do |code| + begin + source = code.encode(encoding) * 5 + begin + target = "#{test.first_data[code]}#{test.follow_data[code]*4}".encode(encoding) + rescue Encoding::UndefinedConversionError + if test.first_data[code]=="i\u0307" or test.follow_data[code]=="i\u0307" # explicit dot above + first_data = test.first_data[code]=="i\u0307" ? 'i' : test.first_data[code] + follow_data = test.follow_data[code]=="i\u0307" ? 'i' : test.follow_data[code] + target = "#{first_data}#{follow_data*4}".encode(encoding) + elsif code =~ /i|I/ # special case for Turkic + raise + else + target = source + end + end + result = source.send(test.method_name, *test.attributes) + assert_equal target, result, + proc{"from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}"} + rescue Encoding::UndefinedConversionError + end + end + end + end + end + + # test for encodings that don't yet (or will never) deal with non-ASCII characters + def self.generate_ascii_only_case_mapping_tests(encoding) + all_tests + # preselect codepoints to speed up testing for small encodings + codepoints = @@codepoints.select do |code| + begin + code.encode(encoding) + true + rescue Encoding::UndefinedConversionError + false + end + end + define_method "test_#{encoding}_upcase" do + codepoints.each do |code| + begin + source = code.encode(encoding) * 5 + target = source.tr 'a-z', 'A-Z' + result = source.upcase + assert_equal target, result, + "from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}" + rescue Encoding::UndefinedConversionError + end + end + end + define_method "test_#{encoding}_downcase" do + codepoints.each do |code| + begin + source = code.encode(encoding) * 5 + target = source.tr 'A-Z', 'a-z' + result = source.downcase + assert_equal target, result, + "from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}" + rescue Encoding::UndefinedConversionError + end + end + end + define_method "test_#{encoding}_capitalize" do + codepoints.each do |code| + begin + source = code.encode(encoding) * 5 + target = source[0].tr('a-z', 'A-Z') + source[1..-1].tr('A-Z', 'a-z') + result = source.capitalize + assert_equal target, result, + "from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}" + rescue Encoding::UndefinedConversionError + end + end + end + define_method "test_#{encoding}_swapcase" do + codepoints.each do |code| + begin + source = code.encode(encoding) * 5 + target = source.tr('a-zA-Z', 'A-Za-z') + result = source.swapcase + assert_equal target, result, + "from #{code*5} (#{source.dump}) expected #{target.dump} but was #{result.dump}" + rescue Encoding::UndefinedConversionError + end + end + end + end + + generate_case_mapping_tests 'US-ASCII' + generate_case_mapping_tests 'ASCII-8BIT' + generate_case_mapping_tests 'ISO-8859-1' + generate_case_mapping_tests 'ISO-8859-2' + generate_case_mapping_tests 'ISO-8859-3' + generate_case_mapping_tests 'ISO-8859-4' + generate_case_mapping_tests 'ISO-8859-5' + generate_case_mapping_tests 'ISO-8859-6' + generate_case_mapping_tests 'ISO-8859-7' + generate_case_mapping_tests 'ISO-8859-8' + generate_case_mapping_tests 'ISO-8859-9' + generate_case_mapping_tests 'ISO-8859-10' + generate_case_mapping_tests 'ISO-8859-11' + generate_case_mapping_tests 'ISO-8859-13' + generate_case_mapping_tests 'ISO-8859-14' + generate_case_mapping_tests 'ISO-8859-15' + generate_case_mapping_tests 'ISO-8859-16' + generate_ascii_only_case_mapping_tests 'KOI8-R' + generate_ascii_only_case_mapping_tests 'KOI8-U' + generate_ascii_only_case_mapping_tests 'Big5' + generate_ascii_only_case_mapping_tests 'EUC-JP' + generate_ascii_only_case_mapping_tests 'EUC-KR' + generate_ascii_only_case_mapping_tests 'GB18030' + generate_ascii_only_case_mapping_tests 'GB2312' + generate_ascii_only_case_mapping_tests 'GBK' + generate_ascii_only_case_mapping_tests 'Shift_JIS' + generate_ascii_only_case_mapping_tests 'Windows-31J' + generate_case_mapping_tests 'Windows-1250' + generate_case_mapping_tests 'Windows-1251' + generate_case_mapping_tests 'Windows-1252' + generate_case_mapping_tests 'Windows-1253' + generate_case_mapping_tests 'Windows-1254' + generate_case_mapping_tests 'Windows-1255' + generate_ascii_only_case_mapping_tests 'Windows-1256' + generate_case_mapping_tests 'Windows-1257' + generate_unicode_case_mapping_tests 'UTF-8' + generate_unicode_case_mapping_tests 'UTF-16BE' + generate_unicode_case_mapping_tests 'UTF-16LE' + generate_unicode_case_mapping_tests 'UTF-32BE' + generate_unicode_case_mapping_tests 'UTF-32LE' +end diff --git a/test/ruby/enc/test_case_mapping.rb b/test/ruby/enc/test_case_mapping.rb new file mode 100644 index 0000000000..d095cd569c --- /dev/null +++ b/test/ruby/enc/test_case_mapping.rb @@ -0,0 +1,198 @@ +# Copyright © 2016 Kimihito Matsui (松井 仁人) and Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +# preliminary tests, using as a guard +# to test new implementation strategy +class TestCaseMappingPreliminary < Test::Unit::TestCase + # checks, including idempotence and non-modification; not always guaranteed + def check_upcase_properties(expected, start, *flags) + assert_equal expected, start.upcase(*flags) + temp = start.dup + assert_equal expected, temp.upcase!(*flags) unless expected==temp + assert_equal nil, temp.upcase!(*flags) if expected==temp + assert_equal expected, expected.upcase(*flags) + temp = expected.dup + assert_nil temp.upcase!(*flags) + end + + def check_downcase_properties(expected, start, *flags) + assert_equal expected, start.downcase(*flags) + temp = start.dup + assert_equal expected, temp.downcase!(*flags) unless expected==temp + assert_equal nil, temp.downcase!(*flags) if expected==temp + assert_equal expected, expected.downcase(*flags) + temp = expected.dup + assert_nil temp.downcase!(*flags) + end + + def check_capitalize_properties(expected, start, *flags) + assert_equal expected, start.capitalize(*flags) + temp = start.dup + assert_equal expected, temp.capitalize!(*flags) unless expected==temp + assert_equal nil, temp.capitalize!(*flags) if expected==temp + assert_equal expected, expected.capitalize(*flags) + temp = expected.dup + assert_nil temp.capitalize!(*flags) + end + + def check_capitalize_suffixes(lower, upper) + while upper.length > 1 + lower = lower[1..-1] + check_capitalize_properties upper[0]+lower, upper + upper = upper[1..-1] + end + end + + # different properties; careful: roundtrip isn't always guaranteed + def check_swapcase_properties(expected, start, *flags) + assert_equal expected, start.swapcase(*flags) + temp = start + assert_equal expected, temp.swapcase!(*flags) + assert_equal start, start.swapcase(*flags).swapcase(*flags) + assert_equal expected, expected.swapcase(*flags).swapcase(*flags) + end + + def test_ascii + check_downcase_properties 'yukihiro matsumoto (matz)', 'Yukihiro MATSUMOTO (MATZ)' + check_upcase_properties 'YUKIHIRO MATSUMOTO (MATZ)', 'yukihiro matsumoto (matz)' + check_capitalize_properties 'Yukihiro matsumoto (matz)', 'yukihiro MATSUMOTO (MATZ)' + check_swapcase_properties 'yUKIHIRO matsumoto (MAtz)', 'Yukihiro MATSUMOTO (maTZ)' + end + + def test_invalid + assert_raise(ArgumentError, "Should not be possible to upcase invalid string.") { "\xEB".force_encoding('UTF-8').upcase } + assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".force_encoding('UTF-8').downcase } + assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".force_encoding('UTF-8').capitalize } + assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".force_encoding('UTF-8').swapcase } + end + + def test_general + check_downcase_properties 'résumé dürst ĭñŧėřŋãţijňőńæłĩżàťïōņ', 'RÉSUMÉ DÜRST ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤÏŌŅ' + check_upcase_properties 'RÉSUMÉ DÜRST ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤÏŌŅ', 'résumé dürst ĭñŧėřŋãţijňőńæłĩżàťïōņ' + check_capitalize_suffixes 'résumé dürst ĭñŧėřŋãţijňőńæłĩżàťïōņ', 'RÉSUMÉ DÜRST ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤÏŌŅ' + check_swapcase_properties 'résumé DÜRST ĭñŧėřŊÃŢIJŇŐŃæłĩżàťïōņ', 'RÉSUMÉ dürst ĬÑŦĖŘŋãţijňőńÆŁĨŻÀŤÏŌŅ' + end + + def test_one_way_upcase + check_upcase_properties 'ΜΜΜΜΜ', 'µµµµµ' # MICRO SIGN -> Greek Mu + check_downcase_properties 'µµµµµ', 'µµµµµ' # MICRO SIGN -> Greek Mu + check_capitalize_properties 'Μµµµµ', 'µµµµµ' # MICRO SIGN -> Greek Mu + check_capitalize_properties 'Μµµµµ', 'µµµµµ', :turkic # MICRO SIGN -> Greek Mu + check_capitalize_properties 'H̱ẖẖẖẖ', 'ẖẖẖẖẖ' + check_capitalize_properties 'Βϐϐϐϐ', 'ϐϐϐϐϐ' + check_capitalize_properties 'Θϑϑϑϑ', 'ϑϑϑϑϑ' + check_capitalize_properties 'Φϕ', 'ϕϕ' + check_capitalize_properties 'Πϖ', 'ϖϖ' + check_capitalize_properties 'Κϰ', 'ϰϰ' + check_capitalize_properties 'Ρϱϱ', 'ϱϱϱ' + check_capitalize_properties 'Εϵ', 'ϵϵ' + check_capitalize_properties 'Ιͅͅͅͅ', 'ͅͅͅͅͅ' + check_capitalize_properties 'Sſſſſ', 'ſſſſſ' + end + + def test_various + check_upcase_properties 'Μ', 'µ' # MICRO SIGN -> Greek Mu + check_downcase_properties 'µµµµµ', 'µµµµµ' # MICRO SIGN + check_capitalize_properties 'Ss', 'ß' + check_upcase_properties 'SS', 'ß' + end + + def test_cherokee + check_downcase_properties "\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79", 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ' + check_upcase_properties 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ', "\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79" + check_capitalize_suffixes "\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79", 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ' + assert_equal 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ', 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ'.downcase(:fold) + assert_equal 'ᎠᎡᎢᎣᎤᎥᎦᎧᎨᎩ', "\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79".downcase(:fold) + end + + def test_titlecase + check_downcase_properties 'dz dž lj nj', 'Dz Dž Lj Nj' + check_downcase_properties 'dz dž lj nj', 'DZ DŽ LJ NJ' + check_upcase_properties 'DZ DŽ LJ NJ', 'Dz Dž Lj Nj' + check_upcase_properties 'DZ DŽ LJ NJ', 'dz dž lj nj' + check_capitalize_properties 'Dz', 'DZ' + check_capitalize_properties 'Dž', 'DŽ' + check_capitalize_properties 'Lj', 'LJ' + check_capitalize_properties 'Nj', 'NJ' + check_capitalize_properties 'Dz', 'dz' + check_capitalize_properties 'Dž', 'dž' + check_capitalize_properties 'Lj', 'lj' + check_capitalize_properties 'Nj', 'nj' + end + + def test_swapcase + assert_equal 'dZ', 'Dz'.swapcase + assert_equal 'dŽ', 'Dž'.swapcase + assert_equal 'lJ', 'Lj'.swapcase + assert_equal 'nJ', 'Nj'.swapcase + assert_equal 'ἀΙ', 'ᾈ'.swapcase + assert_equal 'ἣΙ', 'ᾛ'.swapcase + assert_equal 'ὧΙ', 'ᾯ'.swapcase + assert_equal 'αΙ', 'ᾼ'.swapcase + assert_equal 'ηΙ', 'ῌ'.swapcase + assert_equal 'ωΙ', 'ῼ'.swapcase + end + + def test_ascii_option + check_downcase_properties 'yukihiro matsumoto (matz)', 'Yukihiro MATSUMOTO (MATZ)', :ascii + check_upcase_properties 'YUKIHIRO MATSUMOTO (MATZ)', 'yukihiro matsumoto (matz)', :ascii + check_capitalize_properties 'Yukihiro matsumoto (matz)', 'yukihiro MATSUMOTO (MATZ)', :ascii + check_swapcase_properties 'yUKIHIRO matsumoto (MAtz)', 'Yukihiro MATSUMOTO (maTZ)', :ascii + check_downcase_properties 'yukİhİro matsumoto (matz)', 'YUKİHİRO MATSUMOTO (MATZ)', :ascii + check_downcase_properties 'rÉsumÉ dÜrst ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤĬŌŅ', 'RÉSUMÉ DÜRST ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤĬŌŅ', :ascii + check_swapcase_properties 'rÉsumÉ dÜrst ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤĬŌŅ', 'RÉSUMÉ DÜRST ĬÑŦĖŘŊÃŢIJŇŐŃÆŁĨŻÀŤĬŌŅ', :ascii + end + + def test_fold_option + check_downcase_properties 'ss', 'ß', :fold + check_downcase_properties 'fifl', 'fifl', :fold + check_downcase_properties 'σ', 'ς', :fold + check_downcase_properties 'μ', 'µ', :fold # MICRO SIGN -> Greek mu + end + + def test_turcic + check_downcase_properties 'yukihiro matsumoto (matz)', 'Yukihiro MATSUMOTO (MATZ)', :turkic + check_upcase_properties 'YUKİHİRO MATSUMOTO (MATZ)', 'Yukihiro Matsumoto (matz)', :turkic + check_downcase_properties "yuki\u0307hi\u0307ro matsumoto (matz)", 'YUKİHİRO MATSUMOTO (MATZ)' + end + + def test_greek + check_downcase_properties 'αβγδεζηθικλμνξοπρστυφχψω', 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ' + check_upcase_properties 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ', 'αβγδεζηθικλμνξοπρστυφχψω' + end + + # This test checks against problems when changing the order of mapping results + # in some of the entries of the unfolding table (related to + # https://bugs.ruby-lang.org/issues/12990). + def test_reorder_unfold + # GREEK SMALL LETTER IOTA + assert_equal 0, "\u03B9" =~ /\u0345/i + assert_equal 0, "\u0345" =~ /\u03B9/i + assert_equal 0, "\u03B9" =~ /\u0399/i + assert_equal 0, "\u0399" =~ /\u03B9/i + assert_equal 0, "\u03B9" =~ /\u1fbe/i + assert_equal 0, "\u1fbe" =~ /\u03B9/i + + # GREEK SMALL LETTER MU + assert_equal 0, "\u03BC" =~ /\u00B5/i + assert_equal 0, "\u00B5" =~ /\u03BC/i + assert_equal 0, "\u03BC" =~ /\u039C/i + assert_equal 0, "\u039C" =~ /\u03BC/i + + # CYRILLIC SMALL LETTER MONOGRAPH UK + assert_equal 0, "\uA64B" =~ /\u1c88/i + assert_equal 0, "\u1c88" =~ /\uA64B/i + assert_equal 0, "\uA64B" =~ /\ua64A/i + assert_equal 0, "\ua64A" =~ /\uA64B/i + end + + def no_longer_a_test_buffer_allocations + assert_equal 'TURKISH*ı'*10, ('I'*10).downcase(:turkic) + assert_equal 'TURKISH*ı'*100, ('I'*100).downcase(:turkic) + assert_equal 'TURKISH*ı'*1_000, ('I'*1_000).downcase(:turkic) + assert_equal 'TURKISH*ı'*10_000, ('I'*10_000).downcase(:turkic) + assert_equal 'TURKISH*ı'*100_000, ('I'*100_000).downcase(:turkic) + assert_equal 'TURKISH*ı'*1_000_000, ('I'*1_000_000).downcase(:turkic) + end +end diff --git a/test/ruby/enc/test_case_options.rb b/test/ruby/enc/test_case_options.rb new file mode 100644 index 0000000000..e9bf50fcfc --- /dev/null +++ b/test/ruby/enc/test_case_options.rb @@ -0,0 +1,81 @@ +# Copyright © 2016 Kimihito Matsui (松井 仁人) and Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestCaseOptions < Test::Unit::TestCase + def assert_raise_functional_operations(arg, *options) + assert_raise(ArgumentError) { arg.upcase(*options) } + assert_raise(ArgumentError) { arg.downcase(*options) } + assert_raise(ArgumentError) { arg.capitalize(*options) } + assert_raise(ArgumentError) { arg.swapcase(*options) } + end + + def assert_raise_bang_operations(arg, *options) + assert_raise(ArgumentError) { arg.upcase!(*options) } + assert_raise(ArgumentError) { arg.downcase!(*options) } + assert_raise(ArgumentError) { arg.capitalize!(*options) } + assert_raise(ArgumentError) { arg.swapcase!(*options) } + end + + def assert_raise_both_types(*options) + assert_raise_functional_operations 'a', *options + assert_raise_bang_operations 'a', *options + assert_raise_functional_operations :a, *options + end + + def test_option_errors + assert_raise_both_types :invalid + assert_raise_both_types :lithuanian, :turkic, :fold + assert_raise_both_types :fold, :fold + assert_raise_both_types :ascii, :fold + assert_raise_both_types :fold, :ascii + assert_raise_both_types :ascii, :turkic + assert_raise_both_types :turkic, :ascii + assert_raise_both_types :ascii, :lithuanian + assert_raise_both_types :lithuanian, :ascii + end + + def assert_okay_functional_operations(arg, *options) + assert_nothing_raised { arg.upcase(*options) } + assert_nothing_raised { arg.downcase(*options) } + assert_nothing_raised { arg.capitalize(*options) } + assert_nothing_raised { arg.swapcase(*options) } + end + + def assert_okay_bang_operations(arg, *options) + assert_nothing_raised { arg.upcase!(*options) } + assert_nothing_raised { arg.downcase!(*options) } + assert_nothing_raised { arg.capitalize!(*options) } + assert_nothing_raised { arg.swapcase!(*options) } + end + + def assert_okay_both_types(*options) + assert_okay_functional_operations 'a', *options + assert_okay_bang_operations 'a', *options + assert_okay_functional_operations :a, *options + end + + def test_options_okay + assert_okay_both_types + assert_okay_both_types :ascii + assert_okay_both_types :turkic + assert_okay_both_types :lithuanian + assert_okay_both_types :turkic, :lithuanian + assert_okay_both_types :lithuanian, :turkic + end + + def test_operation_specific # :fold option only allowed on downcase + assert_nothing_raised { 'a'.downcase :fold } + assert_raise(ArgumentError) { 'a'.upcase :fold } + assert_raise(ArgumentError) { 'a'.capitalize :fold } + assert_raise(ArgumentError) { 'a'.swapcase :fold } + assert_nothing_raised { 'a'.downcase! :fold } + assert_raise(ArgumentError) { 'a'.upcase! :fold } + assert_raise(ArgumentError) { 'a'.capitalize! :fold } + assert_raise(ArgumentError) { 'a'.swapcase! :fold } + assert_nothing_raised { :a.downcase :fold } + assert_raise(ArgumentError) { :a.upcase :fold } + assert_raise(ArgumentError) { :a.capitalize :fold } + assert_raise(ArgumentError) { :a.swapcase :fold } + end +end diff --git a/test/ruby/enc/test_cp949.rb b/test/ruby/enc/test_cp949.rb index e675c7b80c..0684162d5b 100644 --- a/test/ruby/enc/test_cp949.rb +++ b/test/ruby/enc/test_cp949.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestCP949 < Test::Unit::TestCase diff --git a/test/ruby/enc/test_emoji.rb b/test/ruby/enc/test_emoji.rb index 1f80c5a79e..330ff70cb9 100644 --- a/test/ruby/enc/test_emoji.rb +++ b/test/ruby/enc/test_emoji.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' module Emoji diff --git a/test/ruby/enc/test_euc_jp.rb b/test/ruby/enc/test_euc_jp.rb index 510ee4611e..4aec69e4db 100644 --- a/test/ruby/enc/test_euc_jp.rb +++ b/test/ruby/enc/test_euc_jp.rb @@ -1,4 +1,5 @@ # vim: set fileencoding=euc-jp +# frozen_string_literal: false require "test/unit" diff --git a/test/ruby/enc/test_euc_kr.rb b/test/ruby/enc/test_euc_kr.rb index 5413fa6062..c9de2cc4e1 100644 --- a/test/ruby/enc/test_euc_kr.rb +++ b/test/ruby/enc/test_euc_kr.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestEucKr < Test::Unit::TestCase diff --git a/test/ruby/enc/test_euc_tw.rb b/test/ruby/enc/test_euc_tw.rb index f36d86b088..649b1b81c6 100644 --- a/test/ruby/enc/test_euc_tw.rb +++ b/test/ruby/enc/test_euc_tw.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestEucTw < Test::Unit::TestCase diff --git a/test/ruby/enc/test_gb18030.rb b/test/ruby/enc/test_gb18030.rb index f379504d48..76ac785951 100644 --- a/test/ruby/enc/test_gb18030.rb +++ b/test/ruby/enc/test_gb18030.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestGB18030 < Test::Unit::TestCase diff --git a/test/ruby/enc/test_gbk.rb b/test/ruby/enc/test_gbk.rb index d6dc5d6d1b..2e541b5821 100644 --- a/test/ruby/enc/test_gbk.rb +++ b/test/ruby/enc/test_gbk.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestGBK < Test::Unit::TestCase diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb new file mode 100644 index 0000000000..7f6c776113 --- /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 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 + +class TestGraphemeBreaksFromFile < Test::Unit::TestCase + UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION'] + path = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}", __dir__) + UNICODE_DATA_PATH = File.directory?("#{path}/ucd/auxiliary") ? "#{path}/ucd/auxiliary" : path + 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? + skip "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}." + end + end +end + +TestGraphemeBreaksFromFile.file_available? and class TestGraphemeBreaksFromFile + def read_data + tests = [] + IO.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line| + if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt") + raise "File Version Mismatch" + 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 diff --git a/test/ruby/enc/test_iso_8859.rb b/test/ruby/enc/test_iso_8859.rb index 64cc7cd76d..ed663be243 100644 --- a/test/ruby/enc/test_iso_8859.rb +++ b/test/ruby/enc/test_iso_8859.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestISO8859 < Test::Unit::TestCase @@ -28,13 +29,15 @@ class TestISO8859 < Test::Unit::TestCase end def test_iso_8859_3 + # todo: decide on behavior, test, and fix implementation re. İ and ı (0xA9/0xB9) + # treating them as case equivalents is definitely an error eval(%q(# encoding: iso8859-3 assert_match(/^(\xdf)\1$/i, "\xdf\xdf") assert_match(/^(\xdf)\1$/i, "ssss") assert_match(/^[\xdfz]+$/i, "sszzsszz") assert_match(/^SS$/i, "\xdf") assert_match(/^Ss$/i, "\xdf") - [0xa1, 0xa6, *(0xa9..0xac), 0xaf].each do |c| + [0xa1, 0xa6, *(0xaa..0xac), 0xaf].each do |c| c1 = c.chr("iso8859-3") c2 = (c + 0x10).chr("iso8859-3") assert_match(/^(#{ c1 })\1$/i, c2 + c1) @@ -120,7 +123,7 @@ class TestISO8859 < Test::Unit::TestCase assert_match(/^[\xdfz]+$/i, "sszzsszz") assert_match(/^SS$/i, "\xdf") assert_match(/^Ss$/i, "\xdf") - ([*(0xc0..0xdc)] - [0xd7]).each do |c| + ([*(0xc0..0xde)] - [0xd7, 0xdd]).each do |c| c1 = c.chr("iso8859-9") c2 = (c + 0x20).chr("iso8859-9") assert_match(/^(#{ c1 })\1$/i, c2 + c1) diff --git a/test/ruby/enc/test_koi8.rb b/test/ruby/enc/test_koi8.rb index ce2d8925ea..4a4d233e8d 100644 --- a/test/ruby/enc/test_koi8.rb +++ b/test/ruby/enc/test_koi8.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require "test/unit" class TestKOI8 < Test::Unit::TestCase diff --git a/test/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb new file mode 100644 index 0000000000..2b252bd441 --- /dev/null +++ b/test/ruby/enc/test_regex_casefold.rb @@ -0,0 +1,120 @@ +# Copyright Kimihito Matsui (松井 仁人) and Martin J. Dürst (duerst@it.aoyama.ac.jp) + +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 + assert_equal expected, temp.downcase!(*flags) + assert_equal expected, expected.downcase(*flags) + temp = expected + assert_nil temp.downcase!(*flags) + end + + def read_tests + 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| + data, _ = linedata.split(/#\s*/) + code, kind, result, _ = data.split(/;\s*/) + CaseTest.new code.to_i(16).chr('UTF-8'), + result.split(/ /).collect { |hex| hex.to_i(16) }.pack('U*'), + kind, linenumber + end.select { |test| test.kind=='C' } + end + + def to_codepoints(string) + string.codepoints.collect { |cp| cp.to_s(16).upcase.rjust(4, '0') } + end + + def setup + @@tests ||= read_tests + rescue Errno::ENOENT => e + @@tests ||= [] + skip e.message + end + + def self.generate_test_casefold(encoding) + define_method "test_mbc_case_fold_#{encoding}" do + @@tests.each do |test| + begin + source = test.source.encode encoding + target = test.target.encode encoding + assert_equal 5, "12345#{target}67890" =~ /#{source}/i, + "12345#{to_codepoints(target)}67890 and /#{to_codepoints(source)}/ do not match case-insensitive " + + "(CaseFolding.txt line #{test[:line]})" + rescue Encoding::UndefinedConversionError + end + end + end + + define_method "test_get_case_fold_codes_by_str_#{encoding}" do + @@tests.each do |test| + begin + source = test.source.encode encoding + target = test.target.encode encoding + assert_equal 5, "12345#{source}67890" =~ /#{target}/i, + "12345#{to_codepoints(source)}67890 and /#{to_codepoints(target)}/ do not match case-insensitive " + + "(CaseFolding.txt line #{test[:line]}), " + + "error may also be triggered by mbc_case_fold" + rescue Encoding::UndefinedConversionError + end + end + end + + define_method "test_apply_all_case_fold_#{encoding}" do + @@tests.each do |test| + begin + source = test.source.encode encoding + target = test.target.encode encoding + reg = '\p{Upper}' + regexp = Regexp.compile reg.encode(encoding) + regexpi = Regexp.compile reg.encode(encoding), Regexp::IGNORECASE + assert_equal 5, "12345#{target}67890" =~ regexpi, + "12345#{to_codepoints(target)}67890 and /#{reg}/i do not match " + + "(CaseFolding.txt line #{test[:line]})" + rescue Encoding::UndefinedConversionError + source = source + regexp = regexp + end + end + end + end + + def test_downcase_fold + @@tests.each do |test| + check_downcase_properties test.target, test.source, :fold + end + end + + # start with good encodings only + generate_test_casefold 'US-ASCII' + generate_test_casefold 'ISO-8859-1' + generate_test_casefold 'ISO-8859-2' + generate_test_casefold 'ISO-8859-3' + generate_test_casefold 'ISO-8859-4' + generate_test_casefold 'ISO-8859-5' + generate_test_casefold 'ISO-8859-6' + # generate_test_casefold 'ISO-8859-7' + generate_test_casefold 'ISO-8859-8' + generate_test_casefold 'ISO-8859-9' + generate_test_casefold 'ISO-8859-10' + generate_test_casefold 'ISO-8859-11' + generate_test_casefold 'ISO-8859-13' + generate_test_casefold 'ISO-8859-14' + generate_test_casefold 'ISO-8859-15' + generate_test_casefold 'ISO-8859-16' + generate_test_casefold 'Windows-1250' + # generate_test_casefold 'Windows-1251' + generate_test_casefold 'Windows-1252' + generate_test_casefold 'koi8-r' + generate_test_casefold 'koi8-u' +end diff --git a/test/ruby/enc/test_shift_jis.rb b/test/ruby/enc/test_shift_jis.rb index 1bd47fa859..059992d167 100644 --- a/test/ruby/enc/test_shift_jis.rb +++ b/test/ruby/enc/test_shift_jis.rb @@ -1,4 +1,5 @@ # vim: set fileencoding=shift_jis +# frozen_string_literal: false require "test/unit" diff --git a/test/ruby/enc/test_utf16.rb b/test/ruby/enc/test_utf16.rb index 63929c6f4b..e08f2ea14e 100644 --- a/test/ruby/enc/test_utf16.rb +++ b/test/ruby/enc/test_utf16.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestUTF16 < Test::Unit::TestCase @@ -55,59 +56,71 @@ EOT # tests start def test_utf16be_valid_encoding - [ - "\x00\x00", - "\xd7\xff", - "\xd8\x00\xdc\x00", - "\xdb\xff\xdf\xff", - "\xe0\x00", - "\xff\xff", - ].each {|s| - s.force_encoding("utf-16be") - assert_equal(true, s.valid_encoding?, "#{encdump s}.valid_encoding?") - } - [ - "\x00", - "\xd7", - "\xd8\x00", - "\xd8\x00\xd8\x00", - "\xdc\x00", - "\xdc\x00\xd8\x00", - "\xdc\x00\xdc\x00", - "\xe0", - "\xff", - ].each {|s| - s.force_encoding("utf-16be") - assert_equal(false, s.valid_encoding?, "#{encdump s}.valid_encoding?") - } + all_assertions do |a| + [ + "\x00\x00", + "\xd7\xff", + "\xd8\x00\xdc\x00", + "\xdb\xff\xdf\xff", + "\xe0\x00", + "\xff\xff", + ].each {|s| + s.force_encoding("utf-16be") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "\x00", + "\xd7", + "\xd8\x00", + "\xd8\x00\xd8\x00", + "\xdc\x00", + "\xdc\x00\xd8\x00", + "\xdc\x00\xdc\x00", + "\xe0", + "\xff", + ].each {|s| + s.force_encoding("utf-16be") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end end def test_utf16le_valid_encoding - [ - "\x00\x00", - "\xff\xd7", - "\x00\xd8\x00\xdc", - "\xff\xdb\xff\xdf", - "\x00\xe0", - "\xff\xff", - ].each {|s| - s.force_encoding("utf-16le") - assert_equal(true, s.valid_encoding?, "#{encdump s}.valid_encoding?") - } - [ - "\x00", - "\xd7", - "\x00\xd8", - "\x00\xd8\x00\xd8", - "\x00\xdc", - "\x00\xdc\x00\xd8", - "\x00\xdc\x00\xdc", - "\xe0", - "\xff", - ].each {|s| - s.force_encoding("utf-16le") - assert_equal(false, s.valid_encoding?, "#{encdump s}.valid_encoding?") - } + all_assertions do |a| + [ + "\x00\x00", + "\xff\xd7", + "\x00\xd8\x00\xdc", + "\xff\xdb\xff\xdf", + "\x00\xe0", + "\xff\xff", + ].each {|s| + s.force_encoding("utf-16le") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "\x00", + "\xd7", + "\x00\xd8", + "\x00\xd8\x00\xd8", + "\x00\xdc", + "\x00\xdc\x00\xd8", + "\x00\xdc\x00\xdc", + "\xe0", + "\xff", + ].each {|s| + s.force_encoding("utf-16le") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end end def test_strftime diff --git a/test/ruby/enc/test_utf32.rb b/test/ruby/enc/test_utf32.rb index 29a2240598..76379abca0 100644 --- a/test/ruby/enc/test_utf32.rb +++ b/test/ruby/enc/test_utf32.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestUTF32 < Test::Unit::TestCase @@ -89,5 +90,73 @@ EOT assert_equal(sl, "a".ord.chr("utf-32le")) assert_equal(sb, "a".ord.chr("utf-32be")) end + + def test_utf32be_valid_encoding + all_assertions do |a| + [ + "\x00\x00\x00\x00", + "\x00\x00\x00a", + "\x00\x00\x30\x40", + "\x00\x00\xd7\xff", + "\x00\x00\xe0\x00", + "\x00\x00\xff\xff", + "\x00\x10\xff\xff", + ].each {|s| + s.force_encoding("utf-32be") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "a", + "\x00a", + "\x00\x00a", + "\x00\x00\xd8\x00", + "\x00\x00\xdb\xff", + "\x00\x00\xdc\x00", + "\x00\x00\xdf\xff", + "\x00\x11\x00\x00", + ].each {|s| + s.force_encoding("utf-32be") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end + end + + def test_utf32le_valid_encoding + all_assertions do |a| + [ + "\x00\x00\x00\x00", + "a\x00\x00\x00", + "\x40\x30\x00\x00", + "\xff\xd7\x00\x00", + "\x00\xe0\x00\x00", + "\xff\xff\x00\x00", + "\xff\xff\x10\x00", + ].each {|s| + s.force_encoding("utf-32le") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "a", + "a\x00", + "a\x00\x00", + "\x00\xd8\x00\x00", + "\xff\xdb\x00\x00", + "\x00\xdc\x00\x00", + "\xff\xdf\x00\x00", + "\x00\x00\x11\x00", + ].each {|s| + s.force_encoding("utf-32le") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end + end end diff --git a/test/ruby/enc/test_windows_1251.rb b/test/ruby/enc/test_windows_1251.rb index 6fbf3159a1..002dbaa3cc 100644 --- a/test/ruby/enc/test_windows_1251.rb +++ b/test/ruby/enc/test_windows_1251.rb @@ -1,4 +1,5 @@ # encoding:windows-1251 +# frozen_string_literal: false require "test/unit" diff --git a/test/ruby/enc/test_windows_1252.rb b/test/ruby/enc/test_windows_1252.rb index 72ee3d201a..f264cba759 100644 --- a/test/ruby/enc/test_windows_1252.rb +++ b/test/ruby/enc/test_windows_1252.rb @@ -1,4 +1,5 @@ # encoding:windows-1252 +# frozen_string_literal: false require "test/unit" diff --git a/test/ruby/endblockwarn_rb b/test/ruby/endblockwarn_rb deleted file mode 100644 index 7b7f97f597..0000000000 --- a/test/ruby/endblockwarn_rb +++ /dev/null @@ -1,12 +0,0 @@ -def end1 - END {} -end - -end1 - -eval <<EOE - def end2 - END {} - end -EOE - diff --git a/test/ruby/envutil.rb b/test/ruby/envutil.rb deleted file mode 100644 index 1fe706df49..0000000000 --- a/test/ruby/envutil.rb +++ /dev/null @@ -1,509 +0,0 @@ -# -*- coding: us-ascii -*- -require "open3" -require "timeout" -require "test/unit" - -module EnvUtil - def rubybin - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - rubyexe = ruby+".exe" - 3.times do - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - ruby = File.join("..", ruby) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, - stdout_filter: nil, stderr_filter: nil, - **opt) - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - args = [args] if args.kind_of?(String) - pid = spawn(child_env, EnvUtil.rubybin, *args, **opt) - in_c.close - out_c.close if capture_stdout - err_c.close if capture_stderr && capture_stderr != :merge_to_stdout - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - else - signal = /mswin|mingw/ =~ RUBY_PLATFORM ? :KILL : :TERM - begin - Process.kill signal, pid - Timeout.timeout((reprieve unless signal == :KILL)) do - Process.wait(pid) - end - rescue Errno::ESRCH - break - rescue Timeout::Error - raise if signal == :KILL - signal = :KILL - else - break - end while true - bt = caller_locations - raise Timeout::Error, "execution of #{bt.shift.label} expired", bt.map(&:to_s) - end - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - Process.wait pid - status = $? - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io.close if io && !io.closed? - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - alias rubyexec invoke_ruby - class << self - alias rubyexec invoke_ruby - end - - def verbose_warning - class << (stderr = "") - alias write << - end - stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true - yield stderr - return $stderr - ensure - stderr, $stderr, $VERBOSE = $stderr, stderr, verbose - end - module_function :verbose_warning - - def suppress_warning - verbose, $VERBOSE = $VERBOSE, nil - yield - ensure - $VERBOSE = verbose - end - module_function :suppress_warning - - def under_gc_stress(stress = true) - stress, GC.stress = GC.stress, stress - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress - - def with_default_external(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_external = Encoding.default_external, enc - $VERBOSE = verbose - yield - ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_external = origenc - $VERBOSE = verbose - end - module_function :with_default_external - - def with_default_internal(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_internal = Encoding.default_internal, enc - $VERBOSE = verbose - yield - ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_internal = origenc - $VERBOSE = verbose - end - module_function :with_default_internal - - def labeled_module(name, &block) - Module.new do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} - class_eval(&block) if block - end - end - module_function :labeled_module - - def labeled_class(name, superclass = Object, &block) - Class.new(superclass) do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} - class_eval(&block) if block - end - end - module_function :labeled_class - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - def self.diagnostic_reports(signame, cmd, pid, now) - return unless %w[ABRT QUIT SEGV ILL].include?(signame) - cmd = File.basename(cmd) - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - end - end - nil - end - else - def self.diagnostic_reports(signame, cmd, pid, now) - end - end -end - -module Test - module Unit - module Assertions - public - def assert_valid_syntax(code, fname = caller_locations(1, 1)[0], mesg = fname.to_s) - code = code.dup.force_encoding("ascii-8bit") - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" - } - code.force_encoding(Encoding::UTF_8) - verbose, $VERBOSE = $VERBOSE, nil - yield if defined?(yield) - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 0 - end - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, catch {|tag| eval(code, binding, fname, line)}, mesg) - end - ensure - $VERBOSE = verbose - end - - def assert_syntax_error(code, error, fname = caller_locations(1, 1)[0], mesg = fname.to_s) - code = code.dup.force_encoding("ascii-8bit") - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ng}\n" - } - code.force_encoding("us-ascii") - verbose, $VERBOSE = $VERBOSE, nil - yield if defined?(yield) - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 0 - end - e = assert_raise(SyntaxError, mesg) do - catch {|tag| eval(code, binding, fname, line)} - end - assert_match(error, e.message, mesg) - ensure - $VERBOSE = verbose - end - - def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) - assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) - if child_env - child_env = [child_env] - else - child_env = [] - end - out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) - assert !status.signaled?, FailDesc[status, message, out] - end - - FailDesc = proc do |status, message = "", out = ""| - pid = status.pid - now = Time.now - faildesc = proc do - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = EnvUtil.diagnostic_reports(signame, EnvUtil.rubybin, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc << " (core dumped)" - end - full_message = '' - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid} killed by #{sigdesc}" - if out and !out.empty? - full_message << "\n#{out.gsub(/^/, '| ')}" - full_message << "\n" if /\n\z/ !~ full_message - end - if log - full_message << "\n#{log.gsub(/^/, '| ')}" - end - full_message - end - faildesc - end - - def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, **opt) - stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) - if signo = status.termsig - EnvUtil.diagnostic_reports(Signal.signame(signo), EnvUtil.rubybin, status.pid, Time.now) - end - if block_given? - raise "test_stdout ignored, use block only or without block" if test_stdout != [] - raise "test_stderr ignored, use block only or without block" if test_stderr != [] - yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status) - else - errs = [] - [[test_stdout, stdout], [test_stderr, stderr]].each do |exp, act| - begin - if exp.is_a?(Regexp) - assert_match(exp, act, message) - else - assert_equal(exp, act.lines.map {|l| l.chomp }, message) - end - rescue MiniTest::Assertion => e - errs << e.message - message = nil - end - end - raise MiniTest::Assertion, errs.join("\n---\n") unless errs.empty? - status - end - end - - def assert_ruby_status(args, test_stdin="", message=nil, **opt) - out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) - assert(!status.signaled?, FailDesc[status, message, out]) - message ||= "ruby exit status is not success:" - assert(status.success?, "#{message} (#{status.inspect})") - end - - ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV") - - def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) - unless file and line - loc, = caller_locations(1,1) - file ||= loc.path - line ||= loc.lineno - end - line -= 2 - src = <<eom -# -*- coding: #{src.encoding}; -*- - require #{__dir__.dump}'/envutil';include Test::Unit::Assertions - END { - puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" - } -#{src} - class Test::Unit::Runner - @@stop_auto_run = true - end -eom - args = args.dup - args.insert((Hash === args.first ? 1 : 0), "--disable=gems", *$:.map {|l| "-I#{l}"}) - stdout, stderr, status = EnvUtil.invoke_ruby(args, src, true, true, **opt) - abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig)) - assert(!abort, FailDesc[status, nil, stderr]) - self._assertions += stdout[/^assertions=(\d+)/, 1].to_i - begin - res = Marshal.load(stdout.unpack("m")[0]) - rescue => marshal_error - ignore_stderr = nil - end - if res - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - end - raise res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect errror - assert_equal("", stderr, "assert_separately failed with error message") - end - assert_equal(0, status, "assert_separately failed: '#{stderr}'") - raise marshal_error if marshal_error - end - - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { yield } - msg = message(msg) {diff stderr, pat} - assert(pat === stderr, msg) - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_no_memory_leak(args, prepare, code, message=nil, limit: 1.5, rss: false, **opt) - require_relative 'memory_status' - token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" - token_dump = token.dump - token_re = Regexp.quote(token) - envs = args.shift if Array === args and Hash === args.first - args = [ - "--disable=gems", - "-r", File.expand_path("../memory_status", __FILE__), - *args, - "-v", "-", - ] - args.unshift(envs) if envs - cmd = [ - 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', - prepare, - 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', - '$initial_size = $initial_status.size', - code, - 'GC.start', - ].join("\n") - _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) - before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) - after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) - assert_equal([true, ""], [status.success?, err], message) - ([:size, (rss && :rss)] & after.members).each do |n| - b = before[n] - a = after[n] - next unless a > 0 and b > 0 - assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) - end - end - - def assert_is_minus_zero(f) - assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0") - end - - def assert_file - AssertFile - end - - # threads should respond to shift method. - # Array and Queue can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << $! - end - end - if !errs.empty? - msg = errs.map {|err| - err.backtrace.map.with_index {|line, i| - if i == 0 - "#{line}: #{err.message} (#{err.class})" - else - "\tfrom #{line}" - end - }.join("\n") - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise MiniTest::Assertion, msg - end - values - end - - class << (AssertFile = Struct.new(:failure_message).new) - include Assertions - def assert_file_predicate(predicate, *args) - if /\Anot_/ =~ predicate - predicate = $' - neg = " not" - end - result = File.__send__(predicate, *args) - result = !result if neg - mesg = "Expected file " << args.shift.inspect - mesg << "#{neg} to be #{predicate}" - mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? - mesg << " #{failure_message}" if failure_message - assert(result, mesg) - end - alias method_missing assert_file_predicate - - def for(message) - clone.tap {|a| a.failure_message = message} - end - end - end - end -end - -begin - require 'rbconfig' -rescue LoadError -else - module RbConfig - @ruby = EnvUtil.rubybin - class << self - undef ruby if method_defined?(:ruby) - attr_reader :ruby - end - dir = File.dirname(ruby) - name = File.basename(ruby, CONFIG['EXEEXT']) - CONFIG['bindir'] = dir - CONFIG['ruby_install_name'] = name - CONFIG['RUBY_INSTALL_NAME'] = name - Gem::ConfigMap[:bindir] = dir if defined?(Gem::ConfigMap) - end -end diff --git a/test/ruby/lbtest.rb b/test/ruby/lbtest.rb index ae047fb187..c7822c9e9a 100644 --- a/test/ruby/lbtest.rb +++ b/test/ruby/lbtest.rb @@ -1,9 +1,9 @@ -require 'thread' +# frozen_string_literal: false class LocalBarrier def initialize(n) - @wait = Queue.new - @done = Queue.new + @wait = Thread::Queue.new + @done = Thread::Queue.new @keeper = begin_keeper(n) end diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb index 665d365a9a..358d3c5133 100644 --- a/test/ruby/marshaltestlib.rb +++ b/test/ruby/marshaltestlib.rb @@ -1,4 +1,5 @@ # coding: utf-8 +# frozen_string_literal: false module MarshalTestLib # include this module to a Test::Unit::TestCase and define encode(o) and # decode(s) methods. e.g. diff --git a/test/ruby/memory_status.rb b/test/ruby/memory_status.rb deleted file mode 100644 index bfbfbd6e88..0000000000 --- a/test/ruby/memory_status.rb +++ /dev/null @@ -1,127 +0,0 @@ -module Memory - keys = [] - vals = [] - - case - when File.exist?(procfile = "/proc/self/status") && (pat = /^Vm(\w+):\s+(\d+)/) =~ File.binread(procfile) - PROC_FILE = procfile - VM_PAT = pat - def self.read_status - IO.foreach(PROC_FILE, encoding: Encoding::ASCII_8BIT) do |l| - yield($1.downcase.intern, $2.to_i * 1024) if VM_PAT =~ l - end - end - - read_status {|k, v| keys << k; vals << v} - - when /mswin|mingw/ =~ RUBY_PLATFORM - begin - require 'fiddle/import' - rescue LoadError - EnvUtil.suppress_warning do - require 'dl/import' - end - end - begin - require 'fiddle/types' - rescue LoadError - EnvUtil.suppress_warning do - require 'dl/types' - end - end - - module Win32 - begin - extend Fiddle::Importer - rescue NameError - extend DL::Importer - end - dlload "kernel32.dll", "psapi.dll" - begin - include Fiddle::Win32Types - rescue NameError - include DL::Win32Types - end - typealias "SIZE_T", "size_t" - - PROCESS_MEMORY_COUNTERS = struct [ - "DWORD cb", - "DWORD PageFaultCount", - "SIZE_T PeakWorkingSetSize", - "SIZE_T WorkingSetSize", - "SIZE_T QuotaPeakPagedPoolUsage", - "SIZE_T QuotaPagedPoolUsage", - "SIZE_T QuotaPeakNonPagedPoolUsage", - "SIZE_T QuotaNonPagedPoolUsage", - "SIZE_T PagefileUsage", - "SIZE_T PeakPagefileUsage", - ] - - typealias "PPROCESS_MEMORY_COUNTERS", "PROCESS_MEMORY_COUNTERS*" - - extern "HANDLE GetCurrentProcess()", :stdcall - extern "BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)", :stdcall - - module_function - def memory_info - size = PROCESS_MEMORY_COUNTERS.size - data = PROCESS_MEMORY_COUNTERS.malloc - data.cb = size - data if GetProcessMemoryInfo(GetCurrentProcess(), data, size) - end - end - - keys << :peak << :size - def self.read_status - if info = Win32.memory_info - yield :peak, info.PeakPagefileUsage - yield :size, info.PagefileUsage - end - end - else - PSCMD = ["ps", "-ovsz=","-orss=", "-p"] - PAT = /^\s*(\d+)\s+(\d+)$/ - - keys << :size << :rss - def self.read_status - if PAT =~ IO.popen(PSCMD + [$$.to_s], "r", err: [:child, :out], &:read) - yield :size, $1.to_i*1024 - yield :rss, $2.to_i*1024 - end - end - end - - Status = Struct.new(*keys) - - class Status - def _update - Memory.read_status do |key, val| - self[key] = val - end - end - end - - class Status - Header = members.map {|k| k.to_s.upcase.rjust(6)}.join('') - Format = "%6d" - - def initialize - _update - end - - def to_s - status = each_pair.map {|n,v| - "#{n}:#{v}" - } - "{#{status.join(",")}}" - end - - def self.parse(str) - status = allocate - str.scan(/(?:\A\{|\G,)(#{members.join('|')}):(\d+)(?=,|\}\z)/) do - status[$1] = $2.to_i - end - status - end - end -end diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb index 50f42d6885..28fb5d1cf8 100644 --- a/test/ruby/sentence.rb +++ b/test/ruby/sentence.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false # == sentence library # # = Features diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb index dec61f6d63..e81636fa43 100644 --- a/test/ruby/test_alias.rb +++ b/test/ruby/test_alias.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestAlias < Test::Unit::TestCase class Alias0 @@ -122,7 +122,8 @@ class TestAlias < Test::Unit::TestCase end def test_alias_wb_miss - assert_normal_exit %q{ + assert_normal_exit "#{<<-"begin;"}\n#{<<-'end;'}" + begin; require 'stringio' GC.verify_internal_consistency GC.start @@ -130,7 +131,7 @@ class TestAlias < Test::Unit::TestCase alias_method :read_nonblock, :sysread end GC.verify_internal_consistency - } + end; end def test_cyclic_zsuper @@ -183,7 +184,8 @@ class TestAlias < Test::Unit::TestCase def test_alias_in_module bug9663 = '[ruby-core:61635] [Bug #9663]' - assert_separately(['-', bug9663], <<-'end;') + assert_separately(['-', bug9663], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; bug = ARGV[0] m = Module.new do @@ -194,4 +196,41 @@ class TestAlias < Test::Unit::TestCase assert_equal(o.to_s, o.orig_to_s, bug) end; end + + class C0; def foo; end; end + class C1 < C0; alias bar foo; end + + def test_alias_method_equation + obj = C1.new + assert_equal(obj.method(:bar), obj.method(:foo)) + assert_equal(obj.method(:foo), obj.method(:bar)) + end + + def test_alias_class_method_added + name = nil + k = Class.new { + def foo;end + def self.method_added(mid) + @name = instance_method(mid).original_name + end + alias bar foo + name = @name + } + assert_equal(:foo, k.instance_method(:bar).original_name) + assert_equal(:foo, name) + end + + def test_alias_module_method_added + name = nil + k = Module.new { + def foo;end + def self.method_added(mid) + @name = instance_method(mid).original_name + end + alias bar foo + name = @name + } + assert_equal(:foo, k.instance_method(:bar).original_name) + assert_equal(:foo, name) + end end diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb index 104a681ee0..311469aad9 100644 --- a/test/ruby/test_argf.rb +++ b/test/ruby/test_argf.rb @@ -1,9 +1,9 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' require 'tmpdir' require 'tempfile' require 'fileutils' -require_relative 'envutil' class TestArgf < Test::Unit::TestCase def setup @@ -57,7 +57,7 @@ class TestArgf < Test::Unit::TestCase /cygwin|mswin|mingw|bccwin/ =~ RUBY_PLATFORM end - def assert_src_expected(line, src, args = nil) + def assert_src_expected(src, args = nil, line: caller_locations(1, 1)[0].lineno+1) args ||= [@t1.path, @t2.path, @t3.path] expected = src.split(/^/) ruby('-e', src, *args) do |f| @@ -71,79 +71,108 @@ class TestArgf < Test::Unit::TestCase end def test_argf - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# a = ARGF b = a.dup p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["1", 1, "1", 1] p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["2", 2, "2", 2] a.rewind b.rewind - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["1", 1, "1", 3] - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["2", 2, "2", 4] - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["3", 3, "3", 5] - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["4", 4, "4", 6] - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 7] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["1", 1, "1", 1] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["2", 2, "2", 2] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["3", 3, "3", 3] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["4", 4, "4", 4] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 5] a.rewind b.rewind - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 8] - p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["6", 6, "6", 9] - SRC + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 5] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["6", 6, "6", 6] + }; end def test_lineno - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# a = ARGF - a.gets; p $. #=> 1 - a.gets; p $. #=> 2 - a.gets; p $. #=> 3 - a.rewind; p $. #=> 3 - a.gets; p $. #=> 3 - a.gets; p $. #=> 4 - a.rewind; p $. #=> 4 - a.gets; p $. #=> 3 - a.lineno = 1000; p $. #=> 1000 - a.gets; p $. #=> 1001 - a.gets; p $. #=> 1002 + a.gets; p($.) #=> 1 + a.gets; p($.) #=> 2 + a.gets; p($.) #=> 3 + a.rewind; p($.) #=> 3 + a.gets; p($.) #=> 3 + a.gets; p($.) #=> 4 + a.rewind; p($.) #=> 4 + a.gets; p($.) #=> 3 + a.lineno = 1000; p($.) #=> 1000 + a.gets; p($.) #=> 1001 + a.gets; p($.) #=> 1002 $. = 2000 - a.gets; p $. #=> 2001 - a.gets; p $. #=> 2001 - SRC + a.gets; p($.) #=> 2001 + a.gets; p($.) #=> 2001 + }; end def test_lineno2 - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# a = ARGF.dup - a.gets; p $. #=> 1 - a.gets; p $. #=> 2 - a.gets; p $. #=> 1 - a.rewind; p $. #=> 1 - a.gets; p $. #=> 1 - a.gets; p $. #=> 2 - a.gets; p $. #=> 1 - a.lineno = 1000; p $. #=> 1 - a.gets; p $. #=> 2 - a.gets; p $. #=> 2 + a.gets; p($.) #=> 1 + a.gets; p($.) #=> 2 + a.gets; p($.) #=> 1 + a.rewind; p($.) #=> 1 + a.gets; p($.) #=> 1 + a.gets; p($.) #=> 2 + a.gets; p($.) #=> 1 + a.lineno = 1000; p($.) #=> 1 + a.gets; p($.) #=> 2 + a.gets; p($.) #=> 2 $. = 2000 - a.gets; p $. #=> 2000 - a.gets; p $. #=> 2000 - SRC + a.gets; p($.) #=> 2000 + a.gets; p($.) #=> 2000 + }; end def test_lineno3 - assert_in_out_err(["-", @t1.path, @t2.path], <<-INPUT, %w"1 1 1 2 2 2 3 3 1 4 4 2", [], "[ruby-core:25205]") + expected = %w"1 1 1 2 2 2 3 3 1 4 4 2" + assert_in_out_err(["-", @t1.path, @t2.path], + "#{<<~"{#"}\n#{<<~'};'}", expected, [], "[ruby-core:25205]") + {# ARGF.each do |line| puts [$., ARGF.lineno, ARGF.file.lineno] end - INPUT + }; + end + + def test_new_lineno_each + f = ARGF.class.new(@t1.path, @t2.path, @t3.path) + result = [] + f.each {|line| result << [f.lineno, line]; break if result.size == 3} + assert_equal(3, f.lineno) + assert_equal((1..3).map {|i| [i, "#{i}\n"]}, result) + + f.rewind + assert_equal(2, f.lineno) + ensure + f.close + end + + def test_new_lineno_each_char + f = ARGF.class.new(@t1.path, @t2.path, @t3.path) + f.each_char.to_a + assert_equal(0, f.lineno) + ensure + f.close end def test_inplace - assert_in_out_err(["-", @t1.path, @t2.path, @t3.path], <<-INPUT, [], []) + assert_in_out_err(["-", @t1.path, @t2.path, @t3.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# ARGF.inplace_mode = '.bak' while line = ARGF.gets puts line.chomp + '.new' end - INPUT + }; assert_equal("1.new\n2.new\n", File.read(@t1.path)) assert_equal("3.new\n4.new\n", File.read(@t2.path)) assert_equal("5.new\n6.new\n", File.read(@t3.path)) @@ -153,7 +182,9 @@ class TestArgf < Test::Unit::TestCase end def test_inplace2 - assert_in_out_err(["-", @t1.path, @t2.path, @t3.path], <<-INPUT, [], []) + assert_in_out_err(["-", @t1.path, @t2.path, @t3.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# ARGF.inplace_mode = '.bak' puts ARGF.gets.chomp + '.new' puts ARGF.gets.chomp + '.new' @@ -167,7 +198,7 @@ class TestArgf < Test::Unit::TestCase p ARGF.inplace_mode ARGF.inplace_mode = nil puts ARGF.gets.chomp + '.new' - INPUT + }; assert_equal("1.new\n2.new\n\".bak\"\n3.new\n4.new\nnil\n", File.read(@t1.path)) assert_equal("3\n4\n", File.read(@t2.path)) assert_equal("5.new\n\".bak\"\n6.new\n", File.read(@t3.path)) @@ -177,7 +208,9 @@ class TestArgf < Test::Unit::TestCase end def test_inplace3 - assert_in_out_err(["-i.bak", "-", @t1.path, @t2.path, @t3.path], <<-INPUT, [], []) + assert_in_out_err(["-i.bak", "-", @t1.path, @t2.path, @t3.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# puts ARGF.gets.chomp + '.new' puts ARGF.gets.chomp + '.new' p $-i @@ -190,7 +223,7 @@ class TestArgf < Test::Unit::TestCase p $-i $-i = nil puts ARGF.gets.chomp + '.new' - INPUT + }; assert_equal("1.new\n2.new\n\".bak\"\n3.new\n4.new\nnil\n", File.read(@t1.path)) assert_equal("3\n4\n", File.read(@t2.path)) assert_equal("5.new\n\".bak\"\n6.new\n", File.read(@t3.path)) @@ -202,27 +235,36 @@ class TestArgf < Test::Unit::TestCase def test_inplace_rename_impossible t = make_tempfile - assert_in_out_err(["-", t.path], <<-INPUT) do |r, e| - ARGF.inplace_mode = '/\\\\:' - while line = ARGF.gets - puts line.chomp + '.new' - end - INPUT + assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}") do |r, e| + {# + ARGF.inplace_mode = '/\\\\:' + while line = ARGF.gets + puts line.chomp + '.new' + end + }; assert_match(/Can't rename .* to .*: .*. skipping file/, e.first) #' assert_equal([], r) assert_equal("foo\nbar\nbaz\n", File.read(t.path)) end + + base = "argf-\u{30c6 30b9 30c8}" + name = "#{@tmpdir}/#{base}" + File.write(name, "foo") + argf = ARGF.class.new(name) + argf.inplace_mode = '/\\:' + assert_warning(/#{base}/) {argf.gets} end def test_inplace_no_backup t = make_tempfile - assert_in_out_err(["-", t.path], <<-INPUT) do |r, e| - ARGF.inplace_mode = '' - while line = ARGF.gets - puts line.chomp + '.new' - end - INPUT + assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}") do |r, e| + {# + ARGF.inplace_mode = '' + while line = ARGF.gets + puts line.chomp + '.new' + end + }; if no_safe_rename assert_match(/Can't do inplace edit without backup/, e.join) #' else @@ -236,63 +278,125 @@ class TestArgf < Test::Unit::TestCase def test_inplace_dup t = make_tempfile - assert_in_out_err(["-", t.path], <<-INPUT, [], []) + assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}", [], []) + {# ARGF.inplace_mode = '.bak' f = ARGF.dup while line = f.gets puts line.chomp + '.new' end - INPUT + }; assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path)) end def test_inplace_stdin - assert_in_out_err(["-", "-"], <<-INPUT, [], /Can't do inplace edit for stdio; skipping/) + assert_in_out_err(["-", "-"], "#{<<~"{#"}\n#{<<~'};'}", [], /Can't do inplace edit for stdio; skipping/) + {# ARGF.inplace_mode = '.bak' f = ARGF.dup while line = f.gets puts line.chomp + '.new' end - INPUT + }; end def test_inplace_stdin2 - assert_in_out_err(["-"], <<-INPUT, [], /Can't do inplace edit for stdio/) + assert_in_out_err(["-"], "#{<<~"{#"}\n#{<<~'};'}", [], /Can't do inplace edit for stdio/) + {# ARGF.inplace_mode = '.bak' while line = ARGF.gets puts line.chomp + '.new' end - INPUT + }; + end + + def test_inplace_invalid_backup + assert_raise(ArgumentError, '[ruby-dev:50272] [Bug #13960]') { + ARGF.inplace_mode = "a\0" + } + end + + def test_inplace_to_path + base = "argf-test" + name = "#{@tmpdir}/#{base}" + File.write(name, "foo") + stdout = $stdout + argf = ARGF.class.new(Struct.new(:to_path).new(name)) + begin + result = argf.gets + ensure + $stdout = stdout + argf.close + end + assert_equal("foo", result) + end + + def test_inplace_ascii_incompatible_path + base = "argf-\u{30c6 30b9 30c8}" + name = "#{@tmpdir}/#{base}" + File.write(name, "foo") + stdout = $stdout + argf = ARGF.class.new(name.encode(Encoding::UTF_16LE)) + assert_raise(Encoding::CompatibilityError) do + argf.gets + end + ensure + $stdout = stdout + end + + def test_inplace_suffix_encoding + base = "argf-\u{30c6 30b9 30c8}" + name = "#{@tmpdir}/#{base}" + suffix = "-bak" + File.write(name, "foo") + stdout = $stdout + argf = ARGF.class.new(name) + argf.inplace_mode = suffix.encode(Encoding::UTF_16LE) + begin + argf.each do |s| + puts "+"+s + end + ensure + $stdout.close unless $stdout == stdout + $stdout = stdout + end + assert_file.exist?(name) + assert_equal("+foo\n", File.read(name)) + assert_file.not_exist?(name+"-") + assert_file.exist?(name+suffix) + assert_equal("foo", File.read(name+suffix)) end def test_encoding - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - p ARGF.external_encoding.is_a?(Encoding) - p ARGF.internal_encoding.is_a?(Encoding) - ARGF.gets - p ARGF.external_encoding.is_a?(Encoding) - p ARGF.internal_encoding - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + p ARGF.external_encoding.is_a?(Encoding) + p ARGF.internal_encoding.is_a?(Encoding) + ARGF.gets + p ARGF.external_encoding.is_a?(Encoding) + p ARGF.internal_encoding + }; assert_equal("true\ntrue\ntrue\nnil\n", f.read) end end def test_tell - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin - ARGF.binmode - loop do - p ARGF.tell - p ARGF.gets + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + ARGF.binmode + loop do + p ARGF.tell + p ARGF.gets + end + rescue ArgumentError + puts "end" end - rescue ArgumentError - puts "end" - end - SRC + }; a = f.read.split("\n") [0, 2, 4, 2, 4, 2, 4].map {|i| i.to_s }. - zip((1..6).map {|i| '"' + i.to_s + '\n"' } + ["nil"]).flatten. - each do |x| + zip((1..6).map {|i| '"' + i.to_s + '\n"' } + ["nil"]).flatten. + each do |x| assert_equal(x, a.shift) end assert_equal('end', a.shift) @@ -300,7 +404,8 @@ class TestArgf < Test::Unit::TestCase end def test_seek - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# ARGF.seek(4) p ARGF.gets #=> "3\n" ARGF.seek(0, IO::SEEK_END) @@ -312,11 +417,12 @@ class TestArgf < Test::Unit::TestCase rescue puts "end" #=> end end - SRC + }; end def test_set_pos - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# ARGF.pos = 4 p ARGF.gets #=> "3\n" ARGF.pos = 4 @@ -328,11 +434,12 @@ class TestArgf < Test::Unit::TestCase rescue puts "end" #=> end end - SRC + }; end def test_rewind - assert_src_expected(__LINE__+1, <<-'SRC') + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# ARGF.pos = 4 ARGF.rewind p ARGF.gets #=> "1\n" @@ -347,28 +454,29 @@ class TestArgf < Test::Unit::TestCase rescue puts "end" #=> end end - SRC + }; end def test_fileno - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - p ARGF.fileno - ARGF.gets - ARGF.gets - p ARGF.fileno - ARGF.gets - ARGF.gets - p ARGF.fileno - ARGF.gets - ARGF.gets - p ARGF.fileno - ARGF.gets - begin - ARGF.fileno - rescue - puts "end" - end - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + p ARGF.fileno + ARGF.gets + ARGF.gets + p ARGF.fileno + ARGF.gets + ARGF.gets + p ARGF.fileno + ARGF.gets + ARGF.gets + p ARGF.fileno + ARGF.gets + begin + ARGF.fileno + rescue + puts "end" + end + }; a = f.read.split("\n") fd1, fd2, fd3, fd4, tag = a assert_match(/^\d+$/, fd1) @@ -380,12 +488,13 @@ class TestArgf < Test::Unit::TestCase end def test_to_io - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - 8.times do - p ARGF.to_io - ARGF.gets - end - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + 8.times do + p ARGF.to_io + ARGF.gets + end + }; a = f.read.split("\n") f11, f12, f13, f21, f22, f31, f32, f4 = a assert_equal(f11, f12) @@ -399,16 +508,17 @@ class TestArgf < Test::Unit::TestCase end def test_eof - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin - 8.times do - p ARGF.eof? - ARGF.gets + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + 8.times do + p ARGF.eof? + ARGF.gets + end + rescue IOError + puts "end" end - rescue IOError - puts "end" - end - SRC + }; a = f.read.split("\n") (%w(false) + (%w(false true) * 3) + %w(end)).each do |x| assert_equal(x, a.shift) @@ -435,66 +545,71 @@ class TestArgf < Test::Unit::TestCase end def test_read2 - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "" - ARGF.read(8, s) - p s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "" + ARGF.read(8, s) + p s + }; assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read) end end def test_read2_with_not_empty_buffer - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "0123456789" - ARGF.read(8, s) - p s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "0123456789" + ARGF.read(8, s) + p s + }; assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read) end end def test_read3 - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - nil while ARGF.gets - p ARGF.read - p ARGF.read(0, "") - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + nil while ARGF.gets + p ARGF.read + p ARGF.read(0, "") + }; assert_equal("nil\n\"\"\n", f.read) end end def test_readpartial - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "" - begin - loop do - s << ARGF.readpartial(1) - t = ""; ARGF.readpartial(1, t); s << t - # not empty buffer - u = "abcdef"; ARGF.readpartial(1, u); s << u + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "" + begin + loop do + s << ARGF.readpartial(1) + t = ""; ARGF.readpartial(1, t); s << t + # not empty buffer + u = "abcdef"; ARGF.readpartial(1, u); s << u + end + rescue EOFError + puts s end - rescue EOFError - puts s - end - SRC + }; assert_equal("1\n2\n3\n4\n5\n6\n", f.read) end end def test_readpartial2 - ruby('-e', <<-SRC) do |f| - s = "" - begin - loop do - s << ARGF.readpartial(1) - t = ""; ARGF.readpartial(1, t); s << t + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f| + {# + s = "" + begin + loop do + s << ARGF.readpartial(1) + t = ""; ARGF.readpartial(1, t); s << t + end + rescue EOFError + $stdout.binmode + puts s end - rescue EOFError - $stdout.binmode - puts s - end - SRC + }; f.binmode f.puts("foo") f.puts("bar") @@ -505,76 +620,82 @@ class TestArgf < Test::Unit::TestCase end def test_readpartial_eof_twice - ruby('-W1', '-e', <<-SRC, @t1.path) do |f| - $stderr = $stdout - print ARGF.readpartial(256) - ARGF.readpartial(256) rescue p($!.class) - ARGF.readpartial(256) rescue p($!.class) - SRC + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path) do |f| + {# + $stderr = $stdout + print ARGF.readpartial(256) + ARGF.readpartial(256) rescue p($!.class) + ARGF.readpartial(256) rescue p($!.class) + }; assert_equal("1\n2\nEOFError\nEOFError\n", f.read) end end def test_getc - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "" - while c = ARGF.getc - s << c - end - puts s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "" + while c = ARGF.getc + s << c + end + puts s + }; assert_equal("1\n2\n3\n4\n5\n6\n", f.read) end end def test_getbyte - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = [] - while c = ARGF.getbyte - s << c - end - p s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = [] + while c = ARGF.getbyte + s << c + end + p s + }; assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read) end end def test_readchar - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "" - begin - while c = ARGF.readchar - s << c + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "" + begin + while c = ARGF.readchar + s << c + end + rescue EOFError + puts s end - rescue EOFError - puts s - end - SRC + }; assert_equal("1\n2\n3\n4\n5\n6\n", f.read) end end def test_readbyte - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin - s = [] - while c = ARGF.readbyte - s << c + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + s = [] + while c = ARGF.readbyte + s << c + end + rescue EOFError + p s end - rescue EOFError - p s - end - SRC + }; assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read) end end def test_each_line - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = [] - ARGF.each_line {|l| s << l } - p s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = [] + ARGF.each_line {|l| s << l } + p s + }; assert_equal("[\"1\\n\", \"2\\n\", \"3\\n\", \"4\\n\", \"5\\n\", \"6\\n\"]\n", f.read) end end @@ -585,32 +706,35 @@ class TestArgf < Test::Unit::TestCase end def test_each_byte - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = [] - ARGF.each_byte {|c| s << c } - p s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = [] + ARGF.each_byte {|c| s << c } + p s + }; assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read) end end def test_each_char - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - s = "" - ARGF.each_char {|c| s << c } - puts s - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = "" + ARGF.each_char {|c| s << c } + puts s + }; assert_equal("1\n2\n3\n4\n5\n6\n", f.read) end end def test_filename - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + puts ARGF.filename.dump + end while ARGF.gets puts ARGF.filename.dump - end while ARGF.gets - puts ARGF.filename.dump - SRC + }; a = f.read.split("\n") assert_equal(@t1.path.dump, a.shift) assert_equal(@t1.path.dump, a.shift) @@ -624,12 +748,13 @@ class TestArgf < Test::Unit::TestCase end def test_filename2 - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + puts $FILENAME.dump + end while ARGF.gets puts $FILENAME.dump - end while ARGF.gets - puts $FILENAME.dump - SRC + }; a = f.read.split("\n") assert_equal(@t1.path.dump, a.shift) assert_equal(@t1.path.dump, a.shift) @@ -643,12 +768,13 @@ class TestArgf < Test::Unit::TestCase end def test_file - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - begin + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + puts ARGF.file.path.dump + end while ARGF.gets puts ARGF.file.path.dump - end while ARGF.gets - puts ARGF.file.path.dump - SRC + }; a = f.read.split("\n") assert_equal(@t1.path.dump, a.shift) assert_equal(@t1.path.dump, a.shift) @@ -680,33 +806,37 @@ class TestArgf < Test::Unit::TestCase end unless IO::BINARY.zero? def test_skip - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.skip - puts ARGF.gets - ARGF.skip - puts ARGF.read - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.skip + puts ARGF.gets + ARGF.skip + puts ARGF.read + }; assert_equal("1\n3\n4\n5\n6\n", f.read) end end def test_skip_in_each_line - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.each_line {|l| print l; ARGF.skip} - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.each_line {|l| print l; ARGF.skip} + }; assert_equal("1\n3\n5\n", f.read, '[ruby-list:49185]') end - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.each_line {|l| ARGF.skip; puts [l, ARGF.gets].map {|s| s ? s.chomp : s.inspect}.join("+")} - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.each_line {|l| ARGF.skip; puts [l, ARGF.gets].map {|s| s ? s.chomp : s.inspect}.join("+")} + }; assert_equal("1+3\n4+5\n6+nil\n", f.read, '[ruby-list:49185]') end end def test_skip_in_each_byte - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.each_byte {|l| print l; ARGF.skip} - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.each_byte {|l| print l; ARGF.skip} + }; assert_equal("135".unpack("C*").join(""), f.read, '[ruby-list:49185]') end end @@ -715,9 +845,10 @@ class TestArgf < Test::Unit::TestCase [[@t1, "\u{3042}"], [@t2, "\u{3044}"], [@t3, "\u{3046}"]].each do |f, s| File.write(f.path, s, mode: "w:utf-8") end - ruby('-Eutf-8', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.each_char {|l| print l; ARGF.skip} - SRC + ruby('-Eutf-8', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.each_char {|l| print l; ARGF.skip} + }; assert_equal("\u{3042 3044 3046}", f.read, '[ruby-list:49185]') end end @@ -726,43 +857,48 @@ class TestArgf < Test::Unit::TestCase [[@t1, "\u{3042}"], [@t2, "\u{3044}"], [@t3, "\u{3046}"]].each do |f, s| File.write(f.path, s, mode: "w:utf-8") end - ruby('-Eutf-8', '-Eutf-8', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.each_codepoint {|l| printf "%x:", l; ARGF.skip} - SRC + ruby('-Eutf-8', '-Eutf-8', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.each_codepoint {|l| printf "%x:", l; ARGF.skip} + }; assert_equal("3042:3044:3046:", f.read, '[ruby-list:49185]') end end def test_close - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - ARGF.close - puts ARGF.read - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + ARGF.close + puts ARGF.read + }; assert_equal("3\n4\n5\n6\n", f.read) end end def test_close_replace - ruby('-e', <<-SRC) do |f| - ARGF.close - ARGV.replace ['#{@t1.path}', '#{@t2.path}', '#{@t3.path}'] - puts ARGF.read - SRC + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f| + paths = ['#{@t1.path}', '#{@t2.path}', '#{@t3.path}'] + {# + ARGF.close + ARGV.replace paths + puts ARGF.read + }; assert_equal("1\n2\n3\n4\n5\n6\n", f.read) end end def test_closed - ruby('-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - 3.times do + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + 3.times do + p ARGF.closed? + ARGF.gets + ARGF.gets + end p ARGF.closed? ARGF.gets - ARGF.gets - end - p ARGF.closed? - ARGF.gets - p ARGF.closed? - SRC + p ARGF.closed? + }; assert_equal("false\nfalse\nfalse\nfalse\ntrue\n", f.read) end end @@ -821,44 +957,101 @@ class TestArgf < Test::Unit::TestCase end def test_lines - ruby('-W1', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - $stderr = $stdout - s = [] - ARGF.lines {|l| s << l } - p s - SRC + 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) end end def test_bytes - ruby('-W1', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - $stderr = $stdout - print Marshal.dump(ARGF.bytes.to_a) - SRC + 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)) end end def test_chars - ruby('-W1', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - $stderr = $stdout - print [Marshal.dump(ARGF.chars.to_a)].pack('m') - SRC - 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)) + 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 end def test_codepoints - ruby('-W1', '-e', <<-SRC, @t1.path, @t2.path, @t3.path) do |f| - $stderr = $stdout - print Marshal.dump(ARGF.codepoints.to_a) - SRC + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + $stderr = $stdout + print Marshal.dump(ARGF.codepoints.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 + + def test_read_nonblock + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f| + {# + $stdout.sync = true + :wait_readable == ARGF.read_nonblock(1, "", exception: false) or + abort "did not return :wait_readable" + + begin + ARGF.read_nonblock(1) + abort 'fail to raise IO::WaitReadable' + rescue IO::WaitReadable + end + puts 'starting select' + + IO.select([ARGF]) == [[ARGF], [], []] or + abort 'did not awaken for readability (before byte)' + + buf = '' + buf.object_id == ARGF.read_nonblock(1, buf).object_id or + abort "read destination buffer failed" + print buf + + IO.select([ARGF]) == [[ARGF], [], []] or + abort 'did not awaken for readability (before EOF)' + + ARGF.read_nonblock(1, buf, exception: false) == nil or + abort "EOF should return nil if exception: false" + + begin + ARGF.read_nonblock(1, buf) + abort 'fail to raise IO::WaitReadable' + rescue EOFError + puts 'done with eof' + end + }; + f.sync = true + assert_equal "starting select\n", f.gets + f.write('.') # wake up from IO.select + assert_equal '.', f.read(1) + f.close_write + assert_equal "done with eof\n", f.gets + end + end + + def test_wrong_type + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}") + {# + bug11610 = '[ruby-core:71140] [Bug #11610]' + ARGV[0] = nil + assert_raise(TypeError, bug11610) {gets} + }; + end end diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb index e026841749..b98248f603 100644 --- a/test/ruby/test_arity.rb +++ b/test/ruby/test_arity.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class TestArity < Test::Unit::TestCase def err_mess(method_proc = nil, argc = 0) args = (1..argc).to_a - assert_raise_with_message(ArgumentError, /wrong number of arguments \((.*)\)/) do + assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b(\d+)\b.* (\d\S*?)\)/) do case method_proc when nil yield @@ -13,7 +14,7 @@ class TestArity < Test::Unit::TestCase method_proc.call(*args) end end - $1 + [$1, $2] end def a @@ -35,22 +36,22 @@ class TestArity < Test::Unit::TestCase end def test_method_err_mess - assert_equal "1 for 0", err_mess(:a, 1) - assert_equal "10 for 7..9", err_mess(:b, 10) - assert_equal "2 for 3+", err_mess(:c, 2) - assert_equal "2 for 1", err_mess(:d, 2) - assert_equal "0 for 1", err_mess(:d, 0) - assert_equal "2 for 1", err_mess(:e, 2) - assert_equal "0 for 1", err_mess(:e, 0) - assert_equal "1 for 2+", err_mess(:f, 1) + 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) end def test_proc_err_mess - assert_equal "0 for 1..2", err_mess(->(b, c=42){}, 0) - assert_equal "1 for 2+", err_mess(->(a, b, c=42, *d){}, 1) - assert_equal "3 for 4+", err_mess(->(a, b, *c, d, e){}, 3) - assert_equal "3 for 1..2", err_mess(->(b, c=42){}, 3) - assert_equal "1 for 0", err_mess(->(&block){}, 1) + 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) # Double checking: p = Proc.new{|b, c=42| :ok} assert_equal :ok, p.call(1, 2, 3) @@ -58,12 +59,12 @@ class TestArity < Test::Unit::TestCase end def test_message_change_issue_6085 - assert_equal "3 for 1..2", err_mess{ SignalException.new(1, "", nil) } - assert_equal "1 for 0", err_mess{ Hash.new(1){} } - assert_equal "3 for 1..2", err_mess{ Module.send :define_method, 1, 2, 3 } - assert_equal "1 for 2", err_mess{ "".sub!(//) } - assert_equal "0 for 1..2", err_mess{ "".sub!{} } - assert_equal "0 for 1+", err_mess{ exec } - assert_equal "0 for 1+", err_mess{ Struct.new } + 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 } end end diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index f10023edfd..3212ed3aca 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -1,6 +1,8 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' +require "delegate" +require "rbconfig/sizeof" class TestArray < Test::Unit::TestCase def setup @@ -222,6 +224,13 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[], @cls[ 1, 2, 3 ] & @cls[ 4, 5, 6 ]) end + def test_AND_big_array # '&' + assert_equal(@cls[1, 3], @cls[ 1, 1, 3, 5 ]*64 & @cls[ 1, 2, 3 ]*64) + assert_equal(@cls[], @cls[ 1, 1, 3, 5 ]*64 & @cls[ ]) + assert_equal(@cls[], @cls[ ] & @cls[ 1, 2, 3 ]*64) + assert_equal(@cls[], @cls[ 1, 2, 3 ]*64 & @cls[ 4, 5, 6 ]*64) + end + def test_MUL # '*' assert_equal(@cls[], @cls[]*3) assert_equal(@cls[1, 1, 1], @cls[1]*3) @@ -258,6 +267,18 @@ class TestArray < Test::Unit::TestCase 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]) + end + def test_LSHIFT # '<<' a = @cls[] a << 1 @@ -441,6 +462,17 @@ class TestArray < Test::Unit::TestCase assert_equal([1, 2], a) end + def test_append + a = @cls[1, 2, 3] + assert_equal(@cls[1, 2, 3, 4, 5], a.append(4, 5)) + assert_equal(@cls[1, 2, 3, 4, 5, nil], a.append(nil)) + + a.append + assert_equal @cls[1, 2, 3, 4, 5, nil], a + a.append 6, 7 + assert_equal @cls[1, 2, 3, 4, 5, nil, 6, 7], a + end + def test_assoc a1 = @cls[*%w( cat feline )] a2 = @cls[*%w( dog canine )] @@ -493,7 +525,7 @@ class TestArray < Test::Unit::TestCase def test_collect a = @cls[ 1, 'cat', 1..1 ] - assert_equal([ Fixnum, String, Range], a.collect {|e| e.class} ) + assert_equal([ Integer, String, Range], a.collect {|e| e.class} ) assert_equal([ 99, 99, 99], a.collect { 99 } ) assert_equal([], @cls[].collect { 99 }) @@ -502,13 +534,15 @@ class TestArray < Test::Unit::TestCase # 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_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]})) end # also update map! def test_collect! a = @cls[ 1, 'cat', 1..1 ] - assert_equal([ Fixnum, String, Range], a.collect! {|e| e.class} ) - assert_equal([ Fixnum, String, Range], a) + assert_equal([ Integer, String, Range], a.collect! {|e| e.class} ) + assert_equal([ Integer, String, Range], a) a = @cls[ 1, 'cat', 1..1 ] assert_equal([ 99, 99, 99], a.collect! { 99 } ) @@ -554,7 +588,9 @@ class TestArray < Test::Unit::TestCase def test_concat assert_equal(@cls[1, 2, 3, 4], @cls[1, 2].concat(@cls[3, 4])) assert_equal(@cls[1, 2, 3, 4], @cls[].concat(@cls[1, 2, 3, 4])) + assert_equal(@cls[1, 2, 3, 4], @cls[1].concat(@cls[2, 3], [4])) assert_equal(@cls[1, 2, 3, 4], @cls[1, 2, 3, 4].concat(@cls[])) + assert_equal(@cls[1, 2, 3, 4], @cls[1, 2, 3, 4].concat()) assert_equal(@cls[], @cls[].concat(@cls[])) assert_equal(@cls[@cls[1, 2], @cls[3, 4]], @cls[@cls[1, 2]].concat(@cls[@cls[3, 4]])) @@ -562,8 +598,12 @@ class TestArray < Test::Unit::TestCase a.concat(a) assert_equal([1, 2, 3, 1, 2, 3], a) + b = @cls[4, 5] + 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(FrozenError) { [0].freeze.concat(:foo) } end def test_count @@ -586,7 +626,7 @@ class TestArray < Test::Unit::TestCase assert_in_out_err [], <<-EOS, ["[]", "0"], [], bug8654 ARY = Array.new(100) { |i| i } - class Fixnum + class Integer alias old_equal == def == other ARY.replace([]) if self.equal?(0) @@ -662,7 +702,7 @@ class TestArray < Test::Unit::TestCase bug2545 = '[ruby-core:27366]' a = @cls[ 5, 6, 7, 8, 9, 10 ] - assert_equal(9, a.delete_if {|i| break i if i > 8; assert_equal(a[0], i) || true if i < 7}) + assert_equal(9, a.delete_if {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) end @@ -768,23 +808,52 @@ class TestArray < Test::Unit::TestCase 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[], @cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten) + end + def test_flatten_wrong_argument 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) assert_equal(a8, a9) assert_not_same(a8, a9) end + def test_flatten_splat + bug10748 = '[ruby-core:67637] [Bug #10748]' + o = Object.new + o.singleton_class.class_eval do + define_method(:to_ary) do + raise bug10748 + end + end + a = @cls[@cls[o]] + assert_raise_with_message(RuntimeError, bug10748) {a.flatten} + assert_nothing_raised(RuntimeError, bug10748) {a.flatten(1)} + end + + def test_flattern_singleton_class + bug12738 = '[ruby-dev:49781] [Bug #12738]' + a = [[0]] + class << a + def m; end + end + assert_raise(NoMethodError, bug12738) { a.flatten.m } + end + def test_flatten! a1 = @cls[ 1, 2, 3] a2 = @cls[ 5, 6 ] @@ -797,16 +866,42 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[1, 2, 3, 4, 5, 6], a5.flatten!) assert_nil(a5.flatten!(0), '[ruby-core:23382]') assert_equal(@cls[1, 2, 3, 4, 5, 6], a5) + end - assert_equal(@cls[], @cls[].flatten) + def test_flatten_empty! + assert_nil(@cls[].flatten!) assert_equal(@cls[], - @cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten) + @cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten!) + end + def test_flatten_level0! assert_nil(@cls[].flatten!(0), '[ruby-core:23382]') end + def test_flatten_splat! + bug10748 = '[ruby-core:67637] [Bug #10748]' + o = Object.new + o.singleton_class.class_eval do + define_method(:to_ary) do + raise bug10748 + end + end + a = @cls[@cls[o]] + assert_raise_with_message(RuntimeError, bug10748) {a.flatten!} + assert_nothing_raised(RuntimeError, bug10748) {a.flatten!(1)} + end + + def test_flattern_singleton_class! + bug12738 = '[ruby-dev:49781] [Bug #12738]' + a = [[0]] + class << a + def m; end + end + assert_nothing_raised(NameError, bug12738) { a.flatten!.m } + end + def test_flatten_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation o = Object.new def o.to_ary() callcc {|k| @cont = k; [1,2,3]} end begin @@ -819,8 +914,30 @@ class TestArray < Test::Unit::TestCase assert_match(/reentered/, e.message, '[ruby-dev:34798]') end + def test_flatten_respond_to_missing + bug11465 = '[ruby-core:70460] [Bug #11465]' + + obj = Class.new do + def respond_to_missing?(method, stuff) + return false if method == :to_ary + super + end + + def method_missing(*args) + super + end + end.new + + ex = nil + trace = TracePoint.new(:raise) do |tp| + ex = tp.raised_exception + end + trace.enable {[obj].flatten} + assert_nil(ex, bug11465) + end + def test_permutation_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = [1,2,3] @@ -837,7 +954,7 @@ class TestArray < Test::Unit::TestCase end def test_product_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = [1,2,3] @@ -854,7 +971,7 @@ class TestArray < Test::Unit::TestCase end def test_combination_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = [1,2,3] @@ -871,7 +988,7 @@ class TestArray < Test::Unit::TestCase end def test_repeated_permutation_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = [1,2,3] @@ -888,7 +1005,7 @@ class TestArray < Test::Unit::TestCase end def test_repeated_combination_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = [1,2,3] @@ -989,6 +1106,7 @@ class TestArray < Test::Unit::TestCase 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) bug5379 = '[ruby-core:39776]' assert_equal(Encoding::US_ASCII, [[], u, nil].join.encoding, bug5379) assert_equal(Encoding::UTF_8, [[], "\u3042", nil].join.encoding, bug5379) @@ -1013,8 +1131,8 @@ class TestArray < Test::Unit::TestCase # also update collect! def test_map! a = @cls[ 1, 'cat', 1..1 ] - assert_equal(@cls[ Fixnum, String, Range], a.map! {|e| e.class} ) - assert_equal(@cls[ Fixnum, String, Range], a) + assert_equal(@cls[ Integer, String, Range], a.map! {|e| e.class} ) + assert_equal(@cls[ Integer, String, Range], a) a = @cls[ 1, 'cat', 1..1 ] assert_equal(@cls[ 99, 99, 99], a.map! { 99 } ) @@ -1119,6 +1237,14 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[], a) end + def test_prepend + a = @cls[] + assert_equal(@cls['cat'], a.prepend('cat')) + assert_equal(@cls['dog', 'cat'], a.prepend('dog')) + assert_equal(@cls[nil, 'dog', 'cat'], a.prepend(nil)) + assert_equal(@cls[@cls[1,2], nil, 'dog', 'cat'], a.prepend(@cls[1, 2])) + end + def test_push a = @cls[1, 2, 3] assert_equal(@cls[1, 2, 3, 4, 5], a.push(4, 5)) @@ -1161,10 +1287,69 @@ class TestArray < Test::Unit::TestCase bug2545 = '[ruby-core:27366]' a = @cls[ 5, 6, 7, 8, 9, 10 ] - assert_equal(9, a.reject! {|i| break i if i > 8; assert_equal(a[0], i) || true if i < 7}) + assert_equal(9, a.reject! {|i| break i if i > 8; i < 7}) assert_equal(@cls[7, 8, 9, 10], a, bug2545) 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 a = @cls[ 1, 2, 3] a_id = a.__id__ @@ -1175,10 +1360,10 @@ 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_reverse @@ -1366,7 +1551,7 @@ class TestArray < Test::Unit::TestCase end def test_sort_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation n = 1000 cont = nil ary = (1..100).to_a @@ -1403,13 +1588,23 @@ class TestArray < Test::Unit::TestCase 1 } } - o2 = o1.dup + 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 + def test_short_heap_array_sort_bang_memory_leak + bug11332 = '[ruby-dev:49166] [Bug #11332]' + assert_no_memory_leak([], <<-PREP, <<-TEST, bug11332, limit: 1.3, timeout: 60) + def t; ary = [*1..5]; ary.pop(2); ary.sort!; end + 1.times {t} + PREP + 500000.times {t} + TEST + end + def test_to_a a = @cls[ 1, 2, 3 ] a_id = a.__id__ @@ -1475,12 +1670,57 @@ class TestArray < Test::Unit::TestCase [[:first_one, :ok], :not_ok].to_h } assert_equal "wrong element type Symbol at 1 (expected array)", e.message + array = [eval("class C\u{1f5ff}; self; end").new] + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h} e = assert_raise(ArgumentError) { [[:first_one, :ok], [1, 2], [:not_ok]].to_h } assert_equal "wrong array length at 2 (expected 2, was 1)", e.message end + def test_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)) + ary = %w(albatross dog horse) + assert_equal("albatross", ary.min) + assert_equal("dog", ary.min {|a,b| a.length <=> b.length }) + assert_equal(1, [3,2,1].min) + assert_equal(%w[albatross dog], ary.min(2)) + assert_equal(%w[dog horse], + 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_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)) + ary = %w(albatross dog horse) + assert_equal("horse", ary.max) + assert_equal("albatross", ary.max {|a,b| a.length <=> b.length }) + assert_equal(1, [3,2,1].max{|a,b| b <=> a }) + assert_equal(%w[horse dog], ary.max(2)) + 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_uniq a = [] b = a.uniq @@ -1595,7 +1835,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"} ] @@ -1641,7 +1881,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") @@ -1655,6 +1895,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[]) @@ -1689,13 +1940,48 @@ class TestArray < Test::Unit::TestCase assert_equal([obj1], [obj1]|[obj2]) end + def test_OR_big_in_order + obj1, obj2 = Class.new do + attr_reader :name + def initialize(name) @name = name; end + def inspect; "test_OR_in_order(#{@name})"; end + def hash; 0; end + def eql?(a) true; end + break [new("1"), new("2")] + end + assert_equal([obj1], [obj1]*64|[obj2]*64) + end + + def test_OR_big_array # '|' + assert_equal(@cls[1,2], @cls[1]*64 | @cls[2]*64) + assert_equal(@cls[1,2], @cls[1, 2]*64 | @cls[1, 2]*64) + + 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 - assert_equal(@cls[[]], @cls[1,2,3,4].combination(0).to_a) - assert_equal(@cls[[1],[2],[3],[4]], @cls[1,2,3,4].combination(1).to_a) - assert_equal(@cls[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]], @cls[1,2,3,4].combination(2).to_a) - assert_equal(@cls[[1,2,3],[1,2,4],[1,3,4],[2,3,4]], @cls[1,2,3,4].combination(3).to_a) - assert_equal(@cls[[1,2,3,4]], @cls[1,2,3,4].combination(4).to_a) - assert_equal(@cls[], @cls[1,2,3,4].combination(5).to_a) + a = @cls[] + assert_equal(1, a.combination(0).size) + assert_equal(0, a.combination(1).size) + a = @cls[1,2,3,4] + assert_equal(1, a.combination(0).size) + assert_equal(4, a.combination(1).size) + assert_equal(6, a.combination(2).size) + assert_equal(4, a.combination(3).size) + assert_equal(1, a.combination(4).size) + assert_equal(0, a.combination(5).size) + assert_equal(@cls[[]], a.combination(0).to_a) + assert_equal(@cls[[1],[2],[3],[4]], a.combination(1).to_a) + assert_equal(@cls[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]], a.combination(2).to_a) + assert_equal(@cls[[1,2,3],[1,2,4],[1,3,4],[2,3,4]], a.combination(3).to_a) + assert_equal(@cls[[1,2,3,4]], a.combination(4).to_a) + assert_equal(@cls[], a.combination(5).to_a) end def test_product @@ -1727,7 +2013,16 @@ class TestArray < Test::Unit::TestCase end def test_permutation + a = @cls[] + assert_equal(1, a.permutation(0).size) + assert_equal(0, a.permutation(1).size) a = @cls[1,2,3] + assert_equal(1, a.permutation(0).size) + assert_equal(3, a.permutation(1).size) + assert_equal(6, a.permutation(2).size) + assert_equal(6, a.permutation(3).size) + assert_equal(0, a.permutation(4).size) + assert_equal(6, a.permutation.size) assert_equal(@cls[[]], a.permutation(0).to_a) assert_equal(@cls[[1],[2],[3]], a.permutation(1).to_a.sort) assert_equal(@cls[[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]], @@ -1750,8 +2045,26 @@ class TestArray < Test::Unit::TestCase assert_equal(b, @cls[0, 1, 2, 3, 4][1, 4].permutation.to_a, bug3708) end + def test_permutation_stack_error + bug9932 = '[ruby-core:63103] [Bug #9932]' + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + bug = "#{bug9932}" + begin; + assert_nothing_raised(SystemStackError, bug) do + assert_equal(:ok, Array.new(100_000, nil).permutation {break :ok}) + end + end; + end + def test_repeated_permutation + a = @cls[] + assert_equal(1, a.repeated_permutation(0).size) + assert_equal(0, a.repeated_permutation(1).size) a = @cls[1,2] + assert_equal(1, a.repeated_permutation(0).size) + assert_equal(2, a.repeated_permutation(1).size) + assert_equal(4, a.repeated_permutation(2).size) + assert_equal(8, a.repeated_permutation(3).size) assert_equal(@cls[[]], a.repeated_permutation(0).to_a) assert_equal(@cls[[1],[2]], a.repeated_permutation(1).to_a.sort) assert_equal(@cls[[1,1],[1,2],[2,1],[2,2]], @@ -1775,8 +2088,25 @@ class TestArray < Test::Unit::TestCase assert_empty(a.reject {|x| !x.include?(0)}) end + def test_repeated_permutation_stack_error + assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}", timeout: 30) + begin; + assert_nothing_raised(SystemStackError) do + assert_equal(:ok, Array.new(100_000, nil).repeated_permutation(500_000) {break :ok}) + end + end; + end + def test_repeated_combination + a = @cls[] + assert_equal(1, a.repeated_combination(0).size) + assert_equal(0, a.repeated_combination(1).size) a = @cls[1,2,3] + assert_equal(1, a.repeated_combination(0).size) + assert_equal(3, a.repeated_combination(1).size) + assert_equal(6, a.repeated_combination(2).size) + assert_equal(10, a.repeated_combination(3).size) + assert_equal(15, a.repeated_combination(4).size) assert_equal(@cls[[]], a.repeated_combination(0).to_a) assert_equal(@cls[[1],[2],[3]], a.repeated_combination(1).to_a.sort) assert_equal(@cls[[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]], @@ -1804,6 +2134,15 @@ class TestArray < Test::Unit::TestCase assert_empty(a.reject {|x| !x.include?(0)}) end + def test_repeated_combination_stack_error + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 20) + begin; + assert_nothing_raised(SystemStackError) do + assert_equal(:ok, Array.new(100_000, nil).repeated_combination(500_000) {break :ok}) + end + end; + end + def test_take assert_equal([1,2,3], [1,2,3,4,5,0].take(3)) assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].take(-1) } @@ -1866,7 +2205,7 @@ class TestArray < Test::Unit::TestCase 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 } end def test_first2 @@ -1886,11 +2225,13 @@ class TestArray < Test::Unit::TestCase a[3] = 3 a.shift(2) assert_equal([2, 3], a) + + assert_equal([1,1,1], ([1] * 100).shift(3)) 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 @@ -1947,9 +2288,11 @@ class TestArray < Test::Unit::TestCase assert_equal([0], a.insert(1)) assert_equal([0, 1], a.insert(1, 1)) assert_raise(ArgumentError) { a.insert } + assert_raise(TypeError) { a.insert(Object.new) } assert_equal([0, 1, 2], a.insert(-1, 2)) assert_equal([0, 1, 3, 2], a.insert(-2, 3)) - assert_raise(RuntimeError) { [0].freeze.insert(0)} + assert_raise_with_message(IndexError, /-6/) { a.insert(-6, 4) } + assert_raise(FrozenError) { [0].freeze.insert(0)} assert_raise(ArgumentError) { [0].freeze.insert } end @@ -1989,7 +2332,6 @@ class TestArray < Test::Unit::TestCase def test_select! a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(nil, a.select! { true }) - assert_equal(a, a.keep_if { true }) assert_equal(@cls[1, 2, 3, 4, 5], a) a = @cls[ 1, 2, 3, 4, 5 ] @@ -1999,6 +2341,36 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(a, a.select! { |i| i > 3 }) assert_equal(@cls[4, 5], a) + + bug10722 = '[ruby-dev:48805] [Bug #10722]' + a = @cls[ 5, 6, 7, 8, 9, 10 ] + r = a.select! {|i| + break i if i > 8 + # assert_equal(a[0], i, "should be selected values only") if i == 7 + i >= 7 + } + assert_equal(9, r) + assert_equal(@cls[7, 8, 9, 10], a, bug10722) + + bug13053 = '[ruby-core:78739] [Bug #13053] Array#select! can resize to negative size' + a = @cls[ 1, 2, 3, 4, 5 ] + a.select! {|i| a.clear if i == 5; false } + assert_equal(0, a.size, bug13053) + end + + # also select! + def test_keep_if + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.keep_if { true }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.keep_if { false }) + assert_equal(@cls[], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.keep_if { |i| i > 3 }) + assert_equal(@cls[4, 5], a) end def test_delete2 @@ -2012,7 +2384,7 @@ class TestArray < Test::Unit::TestCase end def test_reject_with_callcc - respond_to?(:callcc, true) or require 'continuation' + need_continuation bug9727 = '[ruby-dev:48101] [Bug #9727]' cont = nil a = [*1..10].reject do |i| @@ -2101,8 +2473,8 @@ class TestArray < Test::Unit::TestCase 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 @@ -2158,6 +2530,13 @@ class TestArray < Test::Unit::TestCase end ary = (0...10000).to_a assert_equal(ary.rotate, ary.shuffle(random: gen_to_int)) + + assert_raise(NoMethodError) { + ary.shuffle(random: Object.new) + } + assert_raise(NoMethodError) { + ary.shuffle!(random: Object.new) + } end def test_sample @@ -2258,6 +2637,10 @@ class TestArray < Test::Unit::TestCase end ary = (0...10000).to_a assert_equal(5000, ary.sample(random: gen_to_int)) + + assert_raise(NoMethodError) { + ary.sample(random: Object.new) + } end def test_cycle @@ -2274,6 +2657,9 @@ class TestArray < Test::Unit::TestCase a = [] [0, 1, 2].cycle(3) {|i| a << i } assert_equal([0, 1, 2, 0, 1, 2, 0, 1, 2], a) + + assert_equal(Float::INFINITY, a.cycle.size) + assert_equal(27, a.cycle(3).size) end def test_reverse_each2 @@ -2293,11 +2679,18 @@ class TestArray < Test::Unit::TestCase def test_combination_clear bug9939 = '[ruby-core:63149] [Bug #9939]' - assert_separately([], <<-'end;') - 100_000.times {Array.new(1000)} + assert_nothing_raised(bug9939) { a = [*0..100] a.combination(3) {|*,x| a.clear} - end; + } + + bug13052 = '[ruby-core:78738] [Bug #13052] Array#combination segfaults if the Array is modified during iteration' + assert_nothing_raised(bug13052) { + a = [*0..100] + a.combination(1) { a.clear } + a = [*0..100] + a.repeated_combination(1) { a.clear } + } end def test_product2 @@ -2315,8 +2708,8 @@ class TestArray < Test::Unit::TestCase 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) # embeded - assert_equal(Array2, Array2[*(1..100)][1..99].class) #not embeded + 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 @@ -2338,6 +2731,11 @@ class TestArray < Test::Unit::TestCase b.replace(a) assert_equal((1..10).to_a, a.shift(10)) assert_equal((11..100).to_a, a) + + a = (1..30).to_a + assert_equal((1..3).to_a, a.shift(3)) + # occupied + assert_equal((4..6).to_a, a.shift(3)) end def test_replace_shared_ary @@ -2414,7 +2812,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 @@ -2423,6 +2821,10 @@ class TestArray < Test::Unit::TestCase assert_raise(TypeError) do [1, 2, 42, 100, 666].bsearch{ "not ok" } end + c = eval("class C\u{309a 26a1 26c4 1f300};self;end") + assert_raise_with_message(TypeError, /C\u{309a 26a1 26c4 1f300}/) do + [0,1].bsearch {c.new} + end assert_equal [1, 2, 42, 100, 666].bsearch{}, [1, 2, 42, 100, 666].bsearch{false} end @@ -2454,6 +2856,41 @@ class TestArray < Test::Unit::TestCase assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) end + def test_bsearch_index_typechecks_return_values + assert_raise(TypeError) do + [1, 2, 42, 100, 666].bsearch_index {"not ok"} + end + assert_equal [1, 2, 42, 100, 666].bsearch_index {}, [1, 2, 42, 100, 666].bsearch_index {false} + end + + def test_bsearch_index_with_no_block + enum = [1, 2, 42, 100, 666].bsearch_index + assert_nil enum.size + assert_equal 2, enum.each{|x| x >= 33 } + end + + def test_bsearch_index_in_find_minimum_mode + a = [0, 4, 7, 10, 12] + assert_equal(1, a.bsearch_index {|x| x >= 4 }) + assert_equal(2, a.bsearch_index {|x| x >= 6 }) + assert_equal(0, a.bsearch_index {|x| x >= -1 }) + assert_equal(nil, a.bsearch_index {|x| x >= 100 }) + end + + def test_bsearch_index_in_find_any_mode + a = [0, 4, 7, 10, 12] + assert_include([1, 2], a.bsearch_index {|x| 1 - x / 4 }) + assert_equal(nil, a.bsearch_index {|x| 4 - x / 2 }) + assert_equal(nil, a.bsearch_index {|x| 1 }) + assert_equal(nil, a.bsearch_index {|x| -1 }) + + assert_include([1, 2], a.bsearch_index {|x| (1 - x / 4) * (2**100) }) + assert_equal(nil, a.bsearch_index {|x| 1 * (2**100) }) + assert_equal(nil, a.bsearch_index {|x| (-1) * (2**100) }) + + assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) + end + def test_shared_marking reduce = proc do |s| s.gsub(/(verify_internal_consistency_reachable_i:\sWB\smiss\s\S+\s\(T_ARRAY\)\s->\s)\S+\s\((proc|T_NONE)\)\n @@ -2485,24 +2922,131 @@ class TestArray < Test::Unit::TestCase Bug11235 = '[ruby-dev:49043] [Bug #11235]' def test_push_over_ary_max - assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;") + assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + begin; a = Array.new(ARGV[0].to_i) assert_raise(IndexError, ARGV[1]) {0x1000.times {a.push(1)}} end; end def test_unshift_over_ary_max - assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;") + assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; a = Array.new(ARGV[0].to_i) assert_raise(IndexError, ARGV[1]) {0x1000.times {a.unshift(1)}} end; end def test_splice_over_ary_max - assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;") + assert_separately(['-', ARY_MAX.to_s, Bug11235], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; a = Array.new(ARGV[0].to_i) assert_raise(IndexError, ARGV[1]) {a[0, 0] = Array.new(0x1000)} end; end end + + def test_dig + h = @cls[@cls[{a: 1}], 0] + assert_equal(1, h.dig(0, 0, :a)) + assert_nil(h.dig(2, 0)) + assert_raise(TypeError) {h.dig(1, 0)} + end + + FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] + FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX'] + + def assert_typed_equal(e, v, cls, msg=nil) + assert_kind_of(cls, v, msg) + assert_equal(e, v, msg) + end + + def assert_int_equal(e, v, msg=nil) + assert_typed_equal(e, v, Integer, msg) + end + + def assert_rational_equal(e, v, msg=nil) + assert_typed_equal(e, v, Rational, msg) + end + + def assert_float_equal(e, v, msg=nil) + assert_typed_equal(e, v, Float, msg) + end + + def assert_complex_equal(e, v, msg=nil) + assert_typed_equal(e, v, Complex, msg) + end + + def test_sum + assert_int_equal(0, [].sum) + assert_int_equal(3, [3].sum) + assert_int_equal(8, [3, 5].sum) + assert_int_equal(15, [3, 5, 7].sum) + assert_rational_equal(8r, [3, 5r].sum) + assert_float_equal(15.0, [3, 5, 7.0].sum) + assert_float_equal(15.0, [3, 5r, 7.0].sum) + assert_complex_equal(8r + 1i, [3, 5r, 1i].sum) + assert_complex_equal(15.0 + 1i, [3, 5r, 7.0, 1i].sum) + + assert_int_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).sum) + assert_int_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).sum) + assert_int_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).sum) + assert_int_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).sum) + assert_int_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).sum) + assert_int_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).sum) + + assert_float_equal(0.0, [].sum(0.0)) + assert_float_equal(3.0, [3].sum(0.0)) + assert_float_equal(3.5, [3].sum(0.5)) + assert_float_equal(8.5, [3.5, 5].sum) + assert_float_equal(10.5, [2, 8.5].sum) + assert_float_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].sum) + assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].sum) + + assert_rational_equal(3/2r, [1/2r, 1].sum) + assert_rational_equal(5/6r, [1/2r, 1/3r].sum) + + assert_equal(2.0+3.0i, [2.0, 3.0i].sum) + + assert_int_equal(13, [1, 2].sum(10)) + assert_int_equal(16, [1, 2].sum(10) {|v| v * 2 }) + + yielded = [] + three = SimpleDelegator.new(3) + ary = [1, 2.0, three] + assert_float_equal(12.0, ary.sum {|x| yielded << x; x * 2 }) + assert_equal(ary, yielded) + + assert_raise(TypeError) { [Object.new].sum } + + large_number = 100000000 + small_number = 1e-9 + until (large_number + small_number) == large_number + small_number /= 10 + end + assert_float_equal(large_number+(small_number*10), [large_number, *[small_number]*10].sum) + 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([])) + + assert_raise(TypeError) {[0].sum("")} + assert_raise(TypeError) {[1].sum("")} + end + + private + def need_continuation + unless respond_to?(:callcc, true) + EnvUtil.suppress_warning {require 'continuation'} + end + end end diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index e38b20b285..45c4d6e058 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestAssignment < Test::Unit::TestCase @@ -17,7 +18,9 @@ class TestAssignment < Test::Unit::TestCase cc = 5 cc &&=44 assert_equal(44, cc) + end + def test_assign_simple a = nil; assert_nil(a) a = 1; assert_equal(1, a) a = []; assert_equal([], a) @@ -28,7 +31,9 @@ class TestAssignment < Test::Unit::TestCase a = [*[]]; assert_equal([], a) a = [*[1]]; assert_equal([1], a) a = [*[1,2]]; assert_equal([1,2], a) + end + def test_assign_splat a = *[]; assert_equal([], a) a = *[1]; assert_equal([1], a) a = *[nil]; assert_equal([nil], a) @@ -37,7 +42,9 @@ class TestAssignment < Test::Unit::TestCase a = *[*[]]; assert_equal([], a) a = *[*[1]]; assert_equal([1], a) a = *[*[1,2]]; assert_equal([1,2], a) + end + def test_assign_ary *a = nil; assert_equal([nil], a) *a = 1; assert_equal([1], a) *a = []; assert_equal([], a) @@ -48,7 +55,9 @@ class TestAssignment < Test::Unit::TestCase *a = [*[]]; assert_equal([], a) *a = [*[1]]; assert_equal([1], a) *a = [*[1,2]]; assert_equal([1,2], a) + end + def test_assign_ary_splat *a = *[]; assert_equal([], a) *a = *[1]; assert_equal([1], a) *a = *[nil]; assert_equal([nil], a) @@ -57,7 +66,9 @@ class TestAssignment < Test::Unit::TestCase *a = *[*[]]; assert_equal([], a) *a = *[*[1]]; assert_equal([1], a) *a = *[*[1,2]]; assert_equal([1,2], a) + end + def test_massign_simple a,b,*c = nil; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = 1; assert_equal([1,nil,[]], [a,b,c]) a,b,*c = []; assert_equal([nil,nil,[]], [a,b,c]) @@ -68,7 +79,9 @@ class TestAssignment < Test::Unit::TestCase a,b,*c = [*[]]; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = [*[1]]; assert_equal([1,nil,[]], [a,b,c]) a,b,*c = [*[1,2]]; assert_equal([1,2,[]], [a,b,c]) + 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]) a,b,*c = *[nil]; assert_equal([nil,nil,[]], [a,b,c]) @@ -77,7 +90,9 @@ class TestAssignment < Test::Unit::TestCase a,b,*c = *[*[]]; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = *[*[1]]; assert_equal([1,nil,[]], [a,b,c]) a,b,*c = *[*[1,2]]; assert_equal([1,2,[]], [a,b,c]) + end + def test_assign_abbreviated bug2050 = '[ruby-core:25629]' a = Hash.new {[]} b = [1, 2] @@ -87,6 +102,47 @@ class TestAssignment < Test::Unit::TestCase assert_equal([1, 2, 3, [1, 2, 3]], a[:x], bug2050) end + def test_assign_private_self + bug11096 = '[ruby-core:68984] [Bug #11096]' + + o = Object.new + class << o + private + def foo; 42; end + def [](i); 42; end + def foo=(a); 42; end + def []=(i, a); 42; end + end + + assert_raise(NoMethodError) { + o.instance_eval {o.foo = 1} + } + assert_nothing_raised(NoMethodError) { + assert_equal(1, o.instance_eval {self.foo = 1}) + } + + assert_raise(NoMethodError) { + o.instance_eval {o[0] = 1} + } + assert_nothing_raised(NoMethodError) { + assert_equal(1, o.instance_eval {self[0] = 1}) + } + + assert_raise(NoMethodError, bug11096) { + assert_equal(43, o.instance_eval {self.foo += 1}) + } + assert_raise(NoMethodError, bug11096) { + assert_equal(1, o.instance_eval {self.foo &&= 1}) + } + + assert_raise(NoMethodError, bug11096) { + assert_equal(43, o.instance_eval {self[0] += 1}) + } + assert_raise(NoMethodError, bug11096) { + assert_equal(1, o.instance_eval {self[0] &&= 1}) + } + end + def test_yield def f; yield(nil); end; f {|a| assert_nil(a)}; undef f def f; yield(1); end; f {|a| assert_equal(1, a)}; undef f @@ -496,6 +552,11 @@ class TestAssignment < Test::Unit::TestCase a, b = Base::A, Base::B assert_equal [3,4], [a,b] end + + def test_massign_in_cond + result = eval("if (a, b = MyObj.new); [a, b]; end", nil, __FILE__, __LINE__) + assert_equal [[1,2],[3,4]], result + end end require_relative 'sentence' @@ -692,4 +753,32 @@ class TestAssignmentGen < Test::Unit::TestCase check(assign) } end + + def test_optimized_aset + bug9448 = Class.new do + def []=(key, new_value) + '[ruby-core:60071] [Bug #9448]' + end + end + o = bug9448.new + assert_equal("ok", o['current'] = "ok") + end + + def test_massign_aref_lhs_splat + bug11970 = '[ruby-core:72777] [Bug #11970]' + h = {} + k = [:key] + h[*k], = ["ok", "ng"] + assert_equal("ok", h[:key], bug11970) + end + + def test_chainged_assign_command + all_assertions do |a| + asgn = %w'= +=' + asgn.product(asgn) do |a1, a2| + stmt = "a #{a1} b #{a2} raise 'x'" + a.for(stmt) {assert_valid_syntax(stmt)} + end + end + end end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index f88bcac579..3095052a81 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -1,14 +1,13 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require 'thread' -require_relative 'envutil' class TestAutoload < Test::Unit::TestCase def test_autoload_so - # Continuation is always available, unless excluded intentionally. + # Date is always available, unless excluded intentionally. assert_in_out_err([], <<-INPUT, [], []) - autoload :Continuation, "continuation" - begin Continuation; rescue LoadError; end + autoload :Date, "date" + begin Date; rescue LoadError; end INPUT end @@ -99,36 +98,42 @@ p Foo::Bar end def test_threaded_accessing_constant - Tempfile.create(['autoload', '.rb']) {|file| - file.puts 'sleep 0.5; class AutoloadTest; X = 1; end' - file.close - add_autoload(file.path) - begin - assert_nothing_raised do - t1 = Thread.new { ::AutoloadTest::X } - t2 = Thread.new { ::AutoloadTest::X } - [t1, t2].each(&:join) + # Suppress "warning: loading in progress, circular require considered harmful" + EnvUtil.default_warning { + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'sleep 0.5; class AutoloadTest; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant end - ensure - remove_autoload_constant - end + } } end def test_threaded_accessing_inner_constant - Tempfile.create(['autoload', '.rb']) {|file| - file.puts 'class AutoloadTest; sleep 0.5; X = 1; end' - file.close - add_autoload(file.path) - begin - assert_nothing_raised do - t1 = Thread.new { ::AutoloadTest::X } - t2 = Thread.new { ::AutoloadTest::X } - [t1, t2].each(&:join) + # Suppress "warning: loading in progress, circular require considered harmful" + EnvUtil.default_warning { + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; sleep 0.5; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant end - ensure - remove_autoload_constant - end + } } end @@ -182,21 +187,137 @@ p Foo::Bar } end + def ruby_impl_require + Kernel.module_eval do + alias old_require require + end + called_with = [] + Kernel.send :define_method, :require do |path| + called_with << path + old_require path + end + yield called_with + ensure + Kernel.module_eval do + undef require + alias require old_require + undef old_require + end + end + + def test_require_implemented_in_ruby_is_called + ruby_impl_require do |called_with| + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; end' + file.close + add_autoload(file.path) + begin + assert(Object::AutoloadTest) + ensure + remove_autoload_constant + end + assert_equal [file.path], called_with + } + end + end + + def test_autoload_while_autoloading + ruby_impl_require do |called_with| + Tempfile.create(%w(a .rb)) do |a| + Tempfile.create(%w(b .rb)) do |b| + a.puts "require '#{b.path}'; class AutoloadTest; end" + b.puts "class AutoloadTest; module B; end; end" + [a, b].each(&:flush) + add_autoload(a.path) + begin + assert(Object::AutoloadTest) + ensure + remove_autoload_constant + end + assert_equal [a.path, b.path], called_with + end + end + end + end + + def test_bug_13526 + script = File.join(__dir__, 'bug-13526.rb') + assert_ruby_status([script], '', '[ruby-core:81016] [Bug #13526]') + end + + def test_autoload_private_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/zzz.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, "zzz.rb" + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/zzz.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, "zzz.rb" + 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 add_autoload(path) (@autoload_paths ||= []) << path - eval <<-END - class ::Object - autoload :AutoloadTest, #{path.dump} - end - END + ::Object.class_eval {autoload(:AutoloadTest, path)} end def remove_autoload_constant $".replace($" - @autoload_paths) - eval <<-END - class ::Object - remove_const(:AutoloadTest) - end - END + ::Object.class_eval {remove_const(:AutoloadTest)} end end diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb index ed36fe95f6..d38628cdb2 100644 --- a/test/ruby/test_backtrace.rb +++ b/test/ruby/test_backtrace.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: false require 'test/unit' -require 'thread' +require 'tempfile' class TestBacktrace < Test::Unit::TestCase def test_exception @@ -78,10 +79,10 @@ class TestBacktrace < Test::Unit::TestCase cs << caller(5) }.call }.resume - assert_equal(3, cs[0].size) - assert_equal(2, cs[1].size) - assert_equal(1, cs[2].size) - assert_equal(0, cs[3].size) + assert_equal(2, cs[0].size) + assert_equal(1, cs[1].size) + assert_equal(0, cs[2].size) + assert_equal(nil, cs[3]) assert_equal(nil, cs[4]) # @@ -101,7 +102,7 @@ class TestBacktrace < Test::Unit::TestCase } end } - bt = Fiber.new{ + Fiber.new{ rec[max] }.resume end @@ -111,21 +112,21 @@ class TestBacktrace < Test::Unit::TestCase rec = lambda{|n| if n < 0 (m*6).times{|lev| - (m*6).times{|n| + (m*6).times{|i| t = caller(0).size - r = caller(lev, n) + r = caller(lev, i) r = r.size if r.respond_to? :size - # STDERR.puts [t, lev, n, r].inspect - if n == 0 - assert_equal(0, r, [t, lev, n, r].inspect) + # STDERR.puts [t, lev, i, r].inspect + if i == 0 + assert_equal(0, r, [t, lev, i, r].inspect) elsif t < lev - assert_equal(nil, r, [t, lev, n, r].inspect) + assert_equal(nil, r, [t, lev, i, r].inspect) else - if t - lev > n - assert_equal(n, r, [t, lev, n, r].inspect) + if t - lev > i + assert_equal(i, r, [t, lev, i, r].inspect) else - assert_equal(t - lev, r, [t, lev, n, r].inspect) + assert_equal(t - lev, r, [t, lev, i, r].inspect) end end } @@ -163,6 +164,59 @@ class TestBacktrace < Test::Unit::TestCase } end + def test_caller_locations_path + loc, = caller_locations(0, 1) + assert_equal(__FILE__, loc.path) + Tempfile.create(%w"caller_locations .rb") do |f| + f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.path}" + f.close + dir, base = File.split(f.path) + assert_in_out_err(["-C", dir, base], "", [base]) + end + end + + def test_caller_locations_absolute_path + loc, = caller_locations(0, 1) + assert_equal(__FILE__, loc.absolute_path) + Tempfile.create(%w"caller_locations .rb") do |f| + f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.absolute_path}" + f.close + assert_in_out_err(["-C", *File.split(f.path)], "", [File.realpath(f.path)]) + end + end + + def test_caller_locations_lineno + loc, = caller_locations(0, 1) + assert_equal(__LINE__-1, loc.lineno) + Tempfile.create(%w"caller_locations .rb") do |f| + f.puts "caller_locations(0, 1)[0].tap {|loc| puts loc.lineno}" + f.close + assert_in_out_err(["-C", *File.split(f.path)], "", ["1"]) + end + end + + def test_caller_locations_base_label + assert_equal("#{__method__}", caller_locations(0, 1)[0].base_label) + loc, = tap {break caller_locations(0, 1)} + assert_equal("#{__method__}", loc.base_label) + begin + raise + rescue + assert_equal("#{__method__}", caller_locations(0, 1)[0].base_label) + end + end + + def test_caller_locations_label + assert_equal("#{__method__}", caller_locations(0, 1)[0].label) + loc, = tap {break caller_locations(0, 1)} + assert_equal("block in #{__method__}", loc.label) + begin + raise + rescue + assert_equal("rescue in #{__method__}", caller_locations(0, 1)[0].label) + end + end + def th_rec q, n=10 if n > 1 th_rec q, n-1 @@ -173,7 +227,7 @@ class TestBacktrace < Test::Unit::TestCase def test_thread_backtrace begin - q = Queue.new + q = Thread::Queue.new th = Thread.new{ th_rec q } @@ -195,12 +249,13 @@ class TestBacktrace < Test::Unit::TestCase assert_equal(n, th.backtrace_locations(0, n + 1).size) ensure q << true + th.join end end def test_thread_backtrace_locations_with_range begin - q = Queue.new + q = Thread::Queue.new th = Thread.new{ th_rec q } @@ -212,6 +267,7 @@ class TestBacktrace < Test::Unit::TestCase assert_equal(bt, locs) ensure q << true + th.join end end diff --git a/test/ruby/test_basicinstructions.rb b/test/ruby/test_basicinstructions.rb index 4a1dc9ce12..dd3ca4dd22 100644 --- a/test/ruby/test_basicinstructions.rb +++ b/test/ruby/test_basicinstructions.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' ConstTest = 3 @@ -210,9 +211,9 @@ class TestBasicInstructions < Test::Unit::TestCase assert_raise(NameError) { a } assert_raise(NameError) { b } assert_raise(NameError) { c } - a = "NOT OK" - b = "NOT OK" - c = "NOT OK" + a = a = "NOT OK" + b = b = "NOT OK" + c = c = "NOT OK" end class Const @@ -610,8 +611,8 @@ class TestBasicInstructions < Test::Unit::TestCase x = OP.new assert_equal 42, x.foo = 42, bug7773 assert_equal 42, x.foo, bug7773 - assert_equal -6, x.send(:foo=, -6), bug7773 - assert_equal -6, x.foo, bug7773 + assert_equal (-6), x.send(:foo=, -6), bug7773 + assert_equal (-6), x.foo, bug7773 assert_equal :Bug1996, x.send(:x=, :case_when_setter_returns_other_value), bug7773 assert_equal :case_when_setter_returns_other_value, x.x, bug7773 end @@ -697,4 +698,26 @@ class TestBasicInstructions < Test::Unit::TestCase assert_equal [], [*a] assert_equal [1], [1, *a] end + + def test_special_const_instance_variables + assert_separately(%w(-W0), <<-INPUT, timeout: 60) + module M + def get + # we can not set instance variables on special const objects. + # However, we can access instance variables with default value (nil). + @ivar + end + end + class Integer; include M; end + class Float; include M; end + class Symbol; include M; end + class FalseClass; include M; end + class TrueClass; include M; end + class NilClass; include M; end + + [123, 1.2, :sym, false, true, nil].each{|obj| + assert_equal(nil, obj.get) + } + INPUT + end end diff --git a/test/ruby/test_beginendblock.rb b/test/ruby/test_beginendblock.rb index d9c1f56916..eb8394864f 100644 --- a/test/ruby/test_beginendblock.rb +++ b/test/ruby/test_beginendblock.rb @@ -1,34 +1,17 @@ +# frozen_string_literal: false require 'test/unit' -require 'tempfile' -require 'timeout' -require_relative 'envutil' class TestBeginEndBlock < Test::Unit::TestCase DIR = File.dirname(File.expand_path(__FILE__)) - def q(content) - "\"#{content}\"" - end - def test_beginendblock - ruby = EnvUtil.rubybin target = File.join(DIR, 'beginmainend.rb') - result = IO.popen([ruby, target]){|io|io.read} - assert_equal(%w(b1 b2-1 b2 main b3-1 b3 b4 e1 e1-1 e4 e4-2 e4-1 e4-1-1 e3 e2), result.split) - - Tempfile.create(self.class.name) {|input| - inputpath = input.path - result = IO.popen([ruby, "-n", "-eBEGIN{p :begin}", "-eEND{p :end}", inputpath]){|io|io.read} - assert_equal(%w(:begin), result.split) - result = IO.popen([ruby, "-p", "-eBEGIN{p :begin}", "-eEND{p :end}", inputpath]){|io|io.read} - assert_equal(%w(:begin), result.split) - input.puts "foo\nbar" - input.close - result = IO.popen([ruby, "-n", "-eBEGIN{p :begin}", "-eEND{p :end}", inputpath]){|io|io.read} - assert_equal(%w(:begin :end), result.split) - result = IO.popen([ruby, "-p", "-eBEGIN{p :begin}", "-eEND{p :end}", inputpath]){|io|io.read} - assert_equal(%w(:begin foo bar :end), result.split) - } + assert_in_out_err([target], '', %w(b1 b2-1 b2 main b3-1 b3 b4 e1 e1-1 e4 e4-2 e4-1 e4-1-1 e3 e2)) + + assert_in_out_err(["-n", "-eBEGIN{p :begin}", "-eEND{p :end}"], '', %w(:begin)) + assert_in_out_err(["-p", "-eBEGIN{p :begin}", "-eEND{p :end}"], '', %w(:begin)) + assert_in_out_err(["-n", "-eBEGIN{p :begin}", "-eEND{p :end}"], "foo\nbar\n", %w(:begin :end)) + assert_in_out_err(["-p", "-eBEGIN{p :begin}", "-eEND{p :end}"], "foo\nbar\n", %w(:begin foo bar :end)) end def test_begininmethod @@ -48,56 +31,38 @@ class TestBeginEndBlock < Test::Unit::TestCase end def test_endblockwarn - ruby = EnvUtil.rubybin - # Use Tempfile to create temporary file path. - Tempfile.create(self.class.name) {|launcher| - Tempfile.create(self.class.name) {|errout| - - launcher << <<EOF -# -*- coding: #{ruby.encoding.name} -*- -errout = ARGV.shift -STDERR.reopen(File.open(errout, "w")) -STDERR.sync = true -Dir.chdir(#{q(DIR)}) -system("#{ruby}", "endblockwarn_rb") -EOF - launcher.close - launcherpath = launcher.path - errout.close - erroutpath = errout.path - system(ruby, launcherpath, erroutpath) - expected = <<EOW -endblockwarn_rb:2: warning: END in method; use at_exit -(eval):2: warning: END in method; use at_exit -EOW - assert_equal(expected, File.read(erroutpath)) - } - } + assert_in_out_err([], "#{<<~"begin;"}#{<<~'end;'}", [], ['-:2: warning: END in method; use at_exit']) + begin; + def end1 + END {} + end + end; + end + + def test_endblockwarn_in_eval + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], ['(eval):2: warning: END in method; use at_exit']) + begin; + eval <<-EOE + def end2 + END {} + end + EOE + end; end def test_raise_in_at_exit - ruby = EnvUtil.rubybin - out = IO.popen([ruby, '-e', 'STDERR.reopen(STDOUT)', - '-e', 'at_exit{raise %[SomethingBad]}', - '-e', 'raise %[SomethingElse]']) {|f| - f.read - } - status = $? - assert_match(/SomethingBad/, out, "[ruby-core:9675]") - assert_match(/SomethingElse/, out, "[ruby-core:9675]") + args = ['-e', 'at_exit{raise %[SomethingBad]}', + '-e', 'raise %[SomethingElse]'] + expected = [:*, /SomethingBad/, :*, /SomethingElse/, :*] + status = assert_in_out_err(args, '', [], expected, "[ruby-core:9675]") assert_not_predicate(status, :success?) end def test_exitcode_in_at_exit bug8501 = '[ruby-core:55365] [Bug #8501]' - ruby = EnvUtil.rubybin - out = IO.popen([ruby, '-e', 'STDERR.reopen(STDOUT)', - '-e', 'o = Object.new; def o.inspect; raise "[Bug #8501]"; end', - '-e', 'at_exit{o.nope}']) {|f| - f.read - } - status = $? - assert_match(/undefined method `nope'/, out, bug8501) + args = ['-e', 'o = Object.new; def o.inspect; raise "[Bug #8501]"; end', + '-e', 'at_exit{o.nope}'] + status = assert_in_out_err(args, '', [], /undefined method `nope'/, bug8501) assert_not_predicate(status, :success?, bug8501) end @@ -109,57 +74,42 @@ EOW end def test_propagate_signaled - ruby = EnvUtil.rubybin - out = IO.popen( - [ruby, - '-e', 'trap(:INT, "DEFAULT")', - '-e', 'STDERR.reopen(STDOUT)', - '-e', 'at_exit{Process.kill(:INT, $$); sleep 5 }']) {|f| - timeout(10) { - f.read - } - } - assert_match(/Interrupt$/, out) + status = assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], /Interrupt$/) + begin; + trap(:INT, "DEFAULT") + at_exit{Process.kill(:INT, $$)} + end; Process.kill(0, 0) rescue return # check if signal works - assert_nil $?.exitstatus - assert_equal Signal.list["INT"], $?.termsig + assert_nil status.exitstatus + assert_equal Signal.list["INT"], status.termsig end def test_endblock_raise - ruby = EnvUtil.rubybin - out = IO.popen( - [ruby, - '-e', 'class C; def write(x); puts x; STDOUT.flush; sleep 0.01; end; end', - '-e', '$stderr = C.new', - '-e', 'END {raise "e1"}; END {puts "e2"}', - '-e', 'END {raise "e3"}; END {puts "e4"}', - '-e', 'END {raise "e5"}; END {puts "e6"}']) {|f| - Thread.new {sleep 5; Process.kill :KILL, f.pid} - f.read - } - assert_match(/e1/, out) - assert_match(/e6/, out) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w(e6 e4 e2), [:*, /e5/, :*, /e3/, :*, /e1/, :*]) + begin; + END {raise "e1"}; END {puts "e2"} + END {raise "e3"}; END {puts "e4"} + END {raise "e5"}; END {puts "e6"} + end; end def test_nested_at_exit - Tempfile.create(["test_nested_at_exit_", ".rb"]) {|t| - t.puts "at_exit { puts :outer0 }" - t.puts "at_exit { puts :outer1_begin; at_exit { puts :inner1 }; puts :outer1_end }" - t.puts "at_exit { puts :outer2_begin; at_exit { puts :inner2 }; puts :outer2_end }" - t.puts "at_exit { puts :outer3 }" - t.flush - - expected = [ "outer3", - "outer2_begin", - "outer2_end", - "inner2", - "outer1_begin", - "outer1_end", - "inner1", - "outer0" ] - - assert_in_out_err(t.path, "", expected, [], "[ruby-core:35237]") - } + expected = [ "outer3", + "outer2_begin", + "outer2_end", + "inner2", + "outer1_begin", + "outer1_end", + "inner1", + "outer0" ] + + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", expected, [], "[ruby-core:35237]") + begin; + at_exit { puts :outer0 } + at_exit { puts :outer1_begin; at_exit { puts :inner1 }; puts :outer1_end } + at_exit { puts :outer2_begin; at_exit { puts :inner2 }; puts :outer2_end } + at_exit { puts :outer3 } + end; end def test_rescue_at_exit @@ -177,12 +127,53 @@ EOW def test_callcc_at_exit bug9110 = '[ruby-core:58329][Bug #9110]' - script = <<EOS -require "continuation" -c = nil -at_exit { c.call } -at_exit { callcc {|_c| c = _c } } -EOS - assert_normal_exit(script, bug9110) + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}", bug9110) + begin; + require "continuation" + c = nil + at_exit { c.call } + at_exit { callcc {|_c| c = _c } } + end; + end + + def test_errinfo_at_exit + bug12302 = '[ruby-core:75038] [Bug #12302]' + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[2:exit 1:exit], [], bug12302) + begin; + at_exit do + puts "1:#{$!}" + end + + at_exit do + puts "2:#{$!}" + raise 'x' rescue nil + end + + at_exit do + exit + 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 2c6eda0d8e..65d974005e 100644 --- a/test/ruby/test_bignum.rb +++ b/test/ruby/test_bignum.rb @@ -1,12 +1,45 @@ +# frozen_string_literal: false require 'test/unit' +begin + require '-test-/integer' +rescue LoadError +else class TestBignum < Test::Unit::TestCase + FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] + FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX'] + + BIGNUM_MIN = FIXNUM_MAX + 1 + b = BIGNUM_MIN + + f = BIGNUM_MIN + n = 0 + until f == 0 + f >>= 1 + n += 1 + 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 + def setup @verbose = $VERBOSE $VERBOSE = nil @fmax = Float::MAX.to_i @fmax2 = @fmax * 2 - @big = (1 << 63) - 1 + @big = (1 << BIGNUM_MIN_BITS) - 1 end def teardown @@ -23,6 +56,21 @@ class TestBignum < Test::Unit::TestCase return f end + def test_prepare + assert_bignum(@big) + assert_bignum(T_ZERO) + assert_bignum(T_ONE) + assert_bignum(T_MONE) + assert_bignum(T31) + assert_bignum(T31P) + assert_bignum(T32) + assert_bignum(T32P) + assert_bignum(T64) + assert_bignum(T64P) + assert_bignum(T1024) + assert_bignum(T1024P) + end + def test_bignum $x = fact(40) assert_equal($x, $x) @@ -35,8 +83,10 @@ class TestBignum < Test::Unit::TestCase assert_equal(335367096786357081410764800000, $x/fact(20)) $x = -$x assert_equal(-815915283247897734345611269596115894272000000000, $x) - assert_equal(2-(2**32), -(2**32-2)) - assert_equal(2**32 - 5, (2**32-3)-2) + + b = 2*BIGNUM_MIN + assert_equal(2-b, -(b-2)) + assert_equal(b - 5, (b-3)-2) for i in 1000..1014 assert_equal(2 ** i, 1 << i) @@ -110,41 +160,6 @@ class TestBignum < Test::Unit::TestCase assert_match(/\A10{900}9{100}\z/, (10**1000+(10**100-1)).to_s) end - b = 2**64 - b *= b until Bignum === b - - T_ZERO = b.coerce(0).first - T_ONE = b.coerce(1).first - T_MONE = b.coerce(-1).first - T31 = b.coerce(2**31).first # 2147483648 - T31P = b.coerce(T31 - 1).first # 2147483647 - T32 = b.coerce(2**32).first # 4294967296 - T32P = b.coerce(T32 - 1).first # 4294967295 - T64 = b.coerce(2**64).first # 18446744073709551616 - T64P = b.coerce(T64 - 1).first # 18446744073709551615 - T1024 = b.coerce(2**1024).first - T1024P = b.coerce(T1024 - 1).first - - f = b - while Bignum === f-1 - f = f >> 1 - end - FIXNUM_MAX = f-1 - - def test_prepare - assert_instance_of(Bignum, T_ZERO) - assert_instance_of(Bignum, T_ONE) - assert_instance_of(Bignum, T_MONE) - assert_instance_of(Bignum, T31) - assert_instance_of(Bignum, T31P) - assert_instance_of(Bignum, T32) - assert_instance_of(Bignum, T32P) - assert_instance_of(Bignum, T64) - assert_instance_of(Bignum, T64P) - assert_instance_of(Bignum, T1024) - assert_instance_of(Bignum, T1024P) - end - def test_big_2comp assert_equal("-4294967296", (~T32P).to_s) assert_equal("..f00000000", "%x" % -T32) @@ -268,6 +283,18 @@ class TestBignum < Test::Unit::TestCase assert_equal(2**180, (2**80) * o) end + def test_positive_p + assert_predicate(T_ONE, :positive?) + assert_not_predicate(T_MONE, :positive?) + assert_not_predicate(T_ZERO, :positive?) + end + + def test_negative_p + assert_not_predicate(T_ONE, :negative?) + assert_predicate(T_MONE, :negative?) + assert_not_predicate(T_ZERO, :negative?) + end + def test_mul_balance assert_equal(3**7000, (3**5000) * (3**2000)) end @@ -448,7 +475,7 @@ class TestBignum < Test::Unit::TestCase assert_raise(TypeError, ArgumentError) { T32**"foo" } feature3429 = '[ruby-core:30735]' - assert_instance_of(Bignum, (2 ** 7830457), feature3429) + assert_kind_of(Integer, (2 ** 7830457), feature3429) end def test_and @@ -515,24 +542,26 @@ class TestBignum < Test::Unit::TestCase end def test_shift2 - assert_equal(2**33, (2**32) << 1) - assert_equal(2**31, (2**32) << -1) - assert_equal(2**33, (2**32) << 1.0) - assert_equal(2**31, (2**32) << -1.0) - assert_equal(2**33, (2**32) << T_ONE) - assert_equal(2**31, (2**32) << T_MONE) - assert_equal(2**31, (2**32) >> 1) - assert_equal(2**33, (2**32) >> -1) - assert_equal(2**31, (2**32) >> 1.0) - assert_equal(2**33, (2**32) >> -1.0) - assert_equal(2**31, (2**32) >> T_ONE) - assert_equal(2**33, (2**32) >> T_MONE) - assert_equal( 0, (2**32) >> (2**32)) - assert_equal(-1, -(2**32) >> (2**32)) - assert_equal( 0, (2**32) >> 128) - assert_equal(-1, -(2**32) >> 128) - assert_equal( 0, (2**31) >> 32) - assert_equal(-1, -(2**31) >> 32) + b = BIGNUM_MIN_BITS + n = BIGNUM_MIN << 1 + assert_equal(2**(b+1), n << 1) + assert_equal(2**(b-1), n << -1) + assert_equal(2**(b+1), n << 1.0) + assert_equal(2**(b-1), n << -1.0) + assert_equal(2**(b+1), n << T_ONE) + assert_equal(2**(b-1), n << T_MONE) + assert_equal(2**(b-1), n >> 1) + assert_equal(2**(b+1), n >> -1) + assert_equal(2**(b-1), n >> 1.0) + assert_equal(2**(b+1), n >> -1.0) + assert_equal(2**(b-1), n >> T_ONE) + assert_equal(2**(b+1), n >> T_MONE) + assert_equal( 0, n >> n) + assert_equal(-1, -n >> n) + assert_equal( 0, n >> (b*4)) + assert_equal(-1, -n >> (b*4)) + assert_equal( 0, (n/2) >> b) + assert_equal(-1, -(n/2) >> b) end def test_shift_bigshift @@ -541,12 +570,12 @@ class TestBignum < Test::Unit::TestCase end def test_aref - assert_equal(0, (2**32)[0]) - assert_equal(0, (2**32)[2**32]) - assert_equal(0, (2**32)[-(2**32)]) - assert_equal(0, (2**32)[T_ZERO]) - assert_equal(0, (-(2**64))[0]) - assert_equal(1, (-2**256)[256]) + assert_equal(0, BIGNUM_MIN[0]) + assert_equal(0, BIGNUM_MIN[BIGNUM_MIN]) + assert_equal(0, BIGNUM_MIN[-BIGNUM_MIN]) + assert_equal(0, BIGNUM_MIN[T_ZERO]) + assert_equal(0, (-(BIGNUM_MIN*BIGNUM_MIN))[0]) + assert_equal(1, (-2**(BIGNUM_MIN_BITS*4))[BIGNUM_MIN_BITS*4]) end def test_hash @@ -556,6 +585,8 @@ class TestBignum < Test::Unit::TestCase def test_coerce assert_equal([T64P, T31P], T31P.coerce(T64P)) assert_raise(TypeError) { T31P.coerce(nil) } + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { T31P.coerce(obj) } end def test_abs @@ -567,17 +598,17 @@ class TestBignum < Test::Unit::TestCase end def test_odd - assert_equal(true, (2**32+1).odd?) - assert_equal(false, (2**32).odd?) + assert_equal(true, (BIGNUM_MIN+1).odd?) + assert_equal(false, BIGNUM_MIN.odd?) end def test_even - assert_equal(false, (2**32+1).even?) - assert_equal(true, (2**32).even?) + assert_equal(false, (BIGNUM_MIN+1).even?) + assert_equal(true, BIGNUM_MIN.even?) end def test_interrupt_during_to_s - if defined?(Bignum::GMP_VERSION) + if defined?(Integer::GMP_VERSION) return # GMP doesn't support interrupt during an operation. end time = Time.now @@ -586,19 +617,21 @@ class TestBignum < Test::Unit::TestCase num = (65536 ** 65536) thread = Thread.new do start_flag = true - num.to_s - end_flag = true + assert_raise(RuntimeError) { + num.to_s + end_flag = true + } end sleep 0.001 until start_flag thread.raise - thread.join rescue nil + thread.join time = Time.now - time skip "too fast cpu" if end_flag assert_operator(time, :<, 10) end def test_interrupt_during_bigdivrem - if defined?(Bignum::GMP_VERSION) + if defined?(Integer::GMP_VERSION) return # GMP doesn't support interrupt during an operation. end return unless Process.respond_to?(:kill) @@ -631,7 +664,7 @@ class TestBignum < Test::Unit::TestCase end def test_too_big_to_s - if (big = 2**31-1).is_a?(Fixnum) + if (big = 2**31-1).fixnum? return end assert_raise_with_message(RangeError, /too big to convert/) {(1 << big).to_s} @@ -662,6 +695,10 @@ class TestBignum < Test::Unit::TestCase o = Object.new def o.coerce(x); [x, 2**100]; end assert_equal((2**200).to_f, (2**300).fdiv(o)) + o = Object.new + def o.coerce(x); [self, x]; end + def o.fdiv(x); 1; end + assert_equal(1.0, (2**300).fdiv(o)) end def test_singleton_method @@ -708,4 +745,54 @@ class TestBignum < Test::Unit::TestCase end assert_equal(T1024 ^ 10, T1024 ^ obj) end + + def test_digits + assert_equal([90, 78, 56, 34, 12], 1234567890.to_bignum.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)) + end + + def test_digits_for_negative_numbers + assert_raise(Math::DomainError) { -11.digits(T1024P) } + assert_raise(Math::DomainError) { (-T1024P).digits } + assert_raise(Math::DomainError) { (-T1024P).digits(T1024P) } + 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) } + end + + def test_digits_for_non_integral_base_numbers + assert_equal([11], 11.digits(T128P.to_r)) + assert_equal([11], 11.digits(T128P.to_f)) + + t1024p_digits_in_t32 = [T32P]*32 + assert_equal(t1024p_digits_in_t32, T1024P.digits(T32.to_r)) + assert_equal(t1024p_digits_in_t32, T1024P.digits(T32.to_f)) + + assert_raise(RangeError) { T128P.digits(10+1i) } + end + + def test_digits_for_non_numeric_base_argument + assert_raise(TypeError) { T1024P.digits("10") } + assert_raise(TypeError) { T1024P.digits("a") } + end + + def test_finite_p + assert_predicate(T1024P, :finite?) + assert_predicate(-T1024P, :finite?) + end + + def test_infinite_p + assert_nil(T1024P.infinite?) + assert_nil((-T1024P).infinite?) + end +end end diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb index 5b81eb187a..2a1b671cac 100644 --- a/test/ruby/test_call.rb +++ b/test/ruby/test_call.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestCall < Test::Unit::TestCase @@ -31,4 +32,71 @@ class TestCall < Test::Unit::TestCase assert_nothing_raised(ArgumentError) {o.foo} assert_raise_with_message(ArgumentError, e.message, bug9622) {o.foo(100)} end + + def test_safe_call + s = Struct.new(:x, :y, :z) + o = s.new("x") + assert_equal("X", o.x&.upcase) + assert_nil(o.y&.upcase) + assert_equal("x", o.x) + o&.x = 6 + assert_equal(6, o.x) + o&.x *= 7 + assert_equal(42, o.x) + o&.y = 5 + assert_equal(5, o.y) + o&.z ||= 6 + assert_equal(6, o.z) + + o = nil + assert_nil(o&.x) + assert_nothing_raised(NoMethodError) {o&.x = raise} + 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 + count = 0 + proc = proc { count += 1; 1 } + s = Struct.new(:x, :y) + o = s.new(["a", "b", "c"]) + + o.y&.at(proc.call) + assert_equal(0, count) + + o.x&.at(proc.call) + assert_equal(1, count) + end + + def test_safe_call_block_command + assert_nil(("a".sub! "b" do end&.foo 1)) + end + + def test_safe_call_block_call + assert_nil(("a".sub! "b" do end&.foo)) + end + + def test_safe_call_block_call_brace + assert_nil(("a".sub! "b" do end&.foo {})) + assert_nil(("a".sub! "b" do end&.foo do end)) + end + + def test_safe_call_block_call_command + assert_nil(("a".sub! "b" do end&.foo 1 do end)) + end + + def test_invalid_safe_call + h = nil + assert_raise(NoMethodError) { + h[:foo] = nil + } + end + + def test_call_splat_order + bug12860 = '[ruby-core:77701] [Bug# 12860]' + ary = [1, 2] + assert_equal([1, 2, 1], aaa(*ary, ary.shift), bug12860) + ary = [1, 2] + assert_equal([0, 1, 2, 1], aaa(0, *ary, ary.shift), bug12860) + end end diff --git a/test/ruby/test_case.rb b/test/ruby/test_case.rb index f17f9aad9b..77612a8945 100644 --- a/test/ruby/test_case.rb +++ b/test/ruby/test_case.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil.rb' class TestCase < Test::Unit::TestCase def test_case @@ -81,7 +81,7 @@ class TestCase < Test::Unit::TestCase EOS assert_in_out_err(['-e', <<-EOS], '', %w[42], []) - class Fixnum; undef ===; def ===(o); p 42; true; end; end; case 1; when 1; end + class Integer; undef ===; def ===(o); p 42; true; end; end; case 1; when 1; end EOS end @@ -122,4 +122,25 @@ class TestCase < Test::Unit::TestCase end } end + + module NilEqq + refine NilClass do + def === other + false + end + end + end + + class NilEqqClass + using NilEqq + + def eqq(a) + case a; when nil then nil; else :not_nil; end + end + end + + + def test_deoptimize_nil + assert_equal :not_nil, NilEqqClass.new.eqq(nil) + end end diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index b40cab8050..ad2caeb8be 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestClass < Test::Unit::TestCase # ------------------ @@ -46,9 +46,9 @@ class TestClass < Test::Unit::TestCase assert_same(Class, c.class) assert_same(Object, c.superclass) - c = Class.new(Fixnum) + c = Class.new(Integer) assert_same(Class, c.class) - assert_same(Fixnum, c.superclass) + assert_same(Integer, c.superclass) end def test_00_new_basic @@ -89,7 +89,7 @@ class TestClass < Test::Unit::TestCase end end - def test_instanciate_singleton_class + def test_instantiate_singleton_class c = class << Object.new; self; end assert_raise(TypeError) { c.new } end @@ -194,6 +194,9 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { Class.new(c) } assert_raise(TypeError) { Class.new(Class) } assert_raise(TypeError) { eval("class Foo < Class; end") } + m = "M\u{1f5ff}" + o = Class.new {break eval("class #{m}; self; end.new")} + assert_raise_with_message(TypeError, /#{m}/) {Class.new(o)} end def test_initialize_copy @@ -238,13 +241,28 @@ class TestClass < Test::Unit::TestCase assert_equal("TestClass::C\u{df}", c.name, '[ruby-core:24600]') end - def test_invalid_jump_from_class_definition + def test_invalid_next_from_class_definition assert_raise(SyntaxError) { eval("class C; next; end") } + end + + def test_invalid_break_from_class_definition assert_raise(SyntaxError) { eval("class C; break; end") } + end + + def test_invalid_redo_from_class_definition assert_raise(SyntaxError) { eval("class C; redo; end") } + end + + def test_invalid_retry_from_class_definition assert_raise(SyntaxError) { eval("class C; retry; end") } + end + + def test_invalid_return_from_class_definition assert_raise(SyntaxError) { eval("class C; return; end") } - assert_raise(SyntaxError) { eval("class C; yield; end") } + end + + def test_invalid_yield_from_class_definition + assert_raise(LocalJumpError) { eval("class C; yield; end") } end def test_clone @@ -294,7 +312,8 @@ class TestClass < Test::Unit::TestCase end def test_cannot_reinitialize_class_with_initialize_copy # [ruby-core:50869] - assert_in_out_err([], <<-'end;', ["Object"], []) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["Object"], []) + begin; class Class def initialize_copy(*); super; end end @@ -357,6 +376,41 @@ class TestClass < Test::Unit::TestCase end end; end + + m = Module.new + n = "M\u{1f5ff}" + c = m.module_eval "class #{n}; new; end" + assert_raise_with_message(TypeError, /#{n}/) { + eval <<-"end;" + class C < c + end + end; + } + assert_raise_with_message(TypeError, /#{n}/) { + Class.new(c) + } + assert_raise_with_message(TypeError, /#{n}/) { + m.module_eval "class #{n} < Class.new; end" + } + end + + define_method :test_invalid_reset_superclass do + class A; end + class SuperclassCannotBeReset < A + end + assert_equal A, SuperclassCannotBeReset.superclass + + assert_raise_with_message(TypeError, /superclass mismatch/) { + class SuperclassCannotBeReset < String + end + } + + assert_raise_with_message(TypeError, /superclass mismatch/, "[ruby-core:75446]") { + class SuperclassCannotBeReset < Object + end + } + + assert_equal A, SuperclassCannotBeReset.superclass end def test_cloned_singleton_method_added @@ -378,4 +432,197 @@ class TestClass < Test::Unit::TestCase assert_predicate(self.singleton_class, :singleton_class?, feature7609) assert_not_predicate(self.class, :singleton_class?, feature7609) end + + def test_freeze_to_s + assert_nothing_raised("[ruby-core:41858] [Bug #5828]") { + Class.new.freeze.clone.to_s + } + end + + def test_singleton_class_of_frozen_object + obj = Object.new + c = obj.singleton_class + obj.freeze + 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(FrozenError, /frozen Class/) { + def c.f; end + } + end + + def test_singleton_class_should_has_own_namespace + # CONST in singleton class + objs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + CONST = ($i += 1) + def foo + CONST + end + end + } + assert_equal(1, objs[0].foo, '[Bug #10943]') + assert_equal(2, objs[1].foo, '[Bug #10943]') + + # CONST in block in singleton class + objs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + 1.times{ + CONST = ($i += 1) + } + def foo + [nil].map{ + CONST + } + end + end + } + assert_equal([1], objs[0].foo, '[Bug #10943]') + assert_equal([2], objs[1].foo, '[Bug #10943]') + + # class def in singleton class + objs = [] + $xs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + CONST = ($i += 1) + class X + $xs << self + CONST = ($i += 1) + def foo + CONST + end + end + + def x + X + end + end + } + assert_not_equal($xs[0], $xs[1], '[Bug #10943]') + assert_not_equal(objs[0].x, objs[1].x, '[Bug #10943]') + assert_equal(2, $xs[0]::CONST, '[Bug #10943]') + assert_equal(2, $xs[0].new.foo, '[Bug #10943]') + assert_equal(4, $xs[1]::CONST, '[Bug #10943]') + assert_equal(4, $xs[1].new.foo, '[Bug #10943]') + + # class def in block in singleton class + objs = [] + $xs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + 1.times{ + CONST = ($i += 1) + } + 1.times{ + class X + $xs << self + CONST = ($i += 1) + def foo + CONST + end + end + + def x + X + end + } + end + } + assert_not_equal($xs[0], $xs[1], '[Bug #10943]') + assert_not_equal(objs[0].x, objs[1].x, '[Bug #10943]') + assert_equal(2, $xs[0]::CONST, '[Bug #10943]') + assert_equal(2, $xs[0].new.foo, '[Bug #10943]') + assert_equal(4, $xs[1]::CONST, '[Bug #10943]') + assert_equal(4, $xs[1].new.foo, '[Bug #10943]') + + # method def in singleton class + ms = [] + ps = $test_singleton_class_shared_cref_ps = [] + 2.times{ + ms << Module.new do + class << self + $test_singleton_class_shared_cref_ps << Proc.new{ + def xyzzy + self + end + } + end + end + } + + ps.each{|p| p.call} # define xyzzy methods for each singleton classes + ms.each{|m| + assert_equal(m, m.xyzzy, "Bug #10871") + } + end + + def test_namescope_error_message + m = Module.new + o = m.module_eval "class A\u{3042}; self; end.new" + assert_raise_with_message(TypeError, /A\u{3042}/) { + o::Foo + } + end + + def test_redefinition_mismatch + m = Module.new + m.module_eval "A = 1" + assert_raise_with_message(TypeError, /is not a class/) { + m.module_eval "class A; end" + } + n = "M\u{1f5ff}" + m.module_eval "#{n} = 42" + assert_raise_with_message(TypeError, "#{n} is not a class") { + m.module_eval "class #{n}; end" + } + + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + Date = (class C\u{1f5ff}; self; end).new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + require 'date' + } + end; + end + + def test_should_not_expose_singleton_class_without_metaclass + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #11740]' + begin; + klass = Class.new(Array) + # The metaclass of +klass+ should handle #bla since it should inherit methods from meta:meta:Array + def (Array.singleton_class).bla; :bla; end + hidden = ObjectSpace.each_object(Class).find { |c| klass.is_a? c and c.inspect.include? klass.inspect } + raise unless hidden.nil? + end; + + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #11740]' + begin; + klass = Class.new(Array) + klass.singleton_class + # The metaclass of +klass+ should handle #bla since it should inherit methods from meta:meta:Array + def (Array.singleton_class).bla; :bla; end + hidden = ObjectSpace.each_object(Class).find { |c| klass.is_a? c and c.inspect.include? klass.inspect } + raise if hidden.nil? + end; + + end end diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb index c5e2469d10..93ef438461 100644 --- a/test/ruby/test_clone.rb +++ b/test/ruby/test_clone.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestClone < Test::Unit::TestCase diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb index 747f1e29a7..94c05d5f91 100644 --- a/test/ruby/test_comparable.rb +++ b/test/ruby/test_comparable.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestComparable < Test::Unit::TestCase @@ -17,8 +18,18 @@ class TestComparable < Test::Unit::TestCase assert_equal(true, @o == nil) cmp->(x) do 1; end assert_equal(false, @o == nil) - cmp->(x) do raise; end + cmp->(x) do nil; end assert_equal(false, @o == nil) + + cmp->(x) do raise NotImplementedError, "Not a RuntimeError" end + assert_raise(NotImplementedError) { @o == nil } + + bug7688 = 'Comparable#== should not silently rescue' \ + 'any Exception [ruby-core:51389] [Bug #7688]' + cmp->(x) do raise StandardError end + assert_raise(StandardError, bug7688) { @o == nil } + cmp->(x) do "bad value"; end + assert_raise(ArgumentError, bug7688) { @o == nil } end def test_gt @@ -65,9 +76,27 @@ class TestComparable < Test::Unit::TestCase assert_equal(true, @o.between?(0, 0)) end + def test_clamp + 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_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 } assert_raise(ArgumentError) { 1.0 < Object.new } + e = EnvUtil.labeled_class("E\u{30a8 30e9 30fc}") + assert_raise_with_message(ArgumentError, /E\u{30a8 30e9 30fc}/) { + 1.0 < e.new + } end def test_inversed_compare diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index aaad5ee088..316e3e21ff 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -1,18 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class ComplexSub < Complex; end class Complex_Test < Test::Unit::TestCase - def setup - @rational = defined?(Rational) - if @rational - @keiju = Rational.instance_variables.include?(:@RCS_ID) - end - seps = [File::SEPARATOR, File::ALT_SEPARATOR].compact.map{|x| Regexp.escape(x)}.join("|") - @unify = $".grep(/(?:^|#{seps})mathn(?:\.(?:rb|so))?/).size != 0 - end - def test_rationalize assert_equal(1.quo(3), Complex(1/3.0, 0).rationalize, '[ruby-core:38885]') assert_equal(1.quo(5), Complex(0.2, 0).rationalize, '[ruby-core:38885]') @@ -24,24 +16,20 @@ class Complex_Test < Test::Unit::TestCase assert_kind_of(Numeric, c) - if @unify - assert_instance_of(Fixnum, c) - else - assert_instance_of(ComplexSub, c) + assert_instance_of(ComplexSub, c) - c2 = c + 1 - assert_instance_of(ComplexSub, c2) - c2 = c - 1 - assert_instance_of(ComplexSub, c2) + c2 = c + 1 + assert_instance_of(ComplexSub, c2) + c2 = c - 1 + assert_instance_of(ComplexSub, c2) - c3 = c - c2 - assert_instance_of(ComplexSub, c3) + c3 = c - c2 + assert_instance_of(ComplexSub, c3) - s = Marshal.dump(c) - c5 = Marshal.load(s) - assert_equal(c, c5) - assert_instance_of(ComplexSub, c5) - end + s = Marshal.dump(c) + c5 = Marshal.load(s) + assert_equal(c, c5) + assert_instance_of(ComplexSub, c5) c1 = Complex(1) assert_equal(c1.hash, c.hash, '[ruby-dev:38850]') @@ -53,19 +41,19 @@ class Complex_Test < Test::Unit::TestCase c2 = Complex(0) c3 = Complex(1) - assert_equal(true, c.eql?(c2)) - assert_equal(false, c.eql?(c3)) + assert_operator(c, :eql?, c2) + assert_not_operator(c, :eql?, c3) - if @unify - assert_equal(true, c.eql?(0)) - else - assert_equal(false, c.eql?(0)) - end + assert_not_operator(c, :eql?, 0) end def test_hash - assert_instance_of(Fixnum, Complex(1,2).hash) - assert_instance_of(Fixnum, 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 @@ -91,10 +79,7 @@ class Complex_Test < Test::Unit::TestCase def test_freeze c = Complex(1) - c.freeze - unless @unify - assert_equal(true, c.frozen?) - end + assert_predicate(c, :frozen?) assert_instance_of(String, c.to_s) end @@ -133,9 +118,7 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(1),Complex(1)) assert_equal(Complex(1),Complex('1')) assert_equal(Complex(3.0,3.0),Complex('3.0','3.0')) - if @rational && !@keiju - assert_equal(Complex(1,1),Complex('3/3','3/3')) - end + assert_equal(Complex(1,1),Complex('3/3','3/3')) assert_raise(TypeError){Complex(nil)} assert_raise(TypeError){Complex(Object.new)} assert_raise(ArgumentError){Complex()} @@ -209,49 +192,14 @@ class Complex_Test < Test::Unit::TestCase def test_attr2 c = Complex(1) - if @unify -=begin - assert_equal(true, c.finite?) - assert_equal(false, c.infinite?) - assert_equal(false, c.nan?) - assert_equal(true, c.integer?) - assert_equal(false, c.float?) - assert_equal(true, c.rational?) -=end - assert_equal(true, c.real?) -=begin - assert_equal(false, c.complex?) - assert_equal(true, c.exact?) - assert_equal(false, c.inexact?) -=end - else -=begin - assert_equal(true, c.finite?) - assert_equal(false, c.infinite?) - assert_equal(false, c.nan?) - assert_equal(false, c.integer?) - assert_equal(false, c.float?) - assert_equal(false, c.rational?) -=end - assert_equal(false, c.real?) -=begin - assert_equal(true, c.complex?) - assert_equal(true, c.exact?) - assert_equal(false, c.inexact?) -=end - end - -=begin - assert_equal(0, Complex(0).sign) - assert_equal(1, Complex(2).sign) - assert_equal(-1, Complex(-2).sign) -=end + assert_not_predicate(c, :integer?) + assert_not_predicate(c, :real?) - assert_equal(true, Complex(0).zero?) - assert_equal(true, Complex(0,0).zero?) - assert_equal(false, Complex(1,0).zero?) - assert_equal(false, Complex(0,1).zero?) - assert_equal(false, Complex(1,1).zero?) + assert_predicate(Complex(0), :zero?) + assert_predicate(Complex(0,0), :zero?) + assert_not_predicate(Complex(1,0), :zero?) + assert_not_predicate(Complex(0,1), :zero?) + assert_not_predicate(Complex(1,1), :zero?) assert_equal(nil, Complex(0).nonzero?) assert_equal(nil, Complex(0,0).nonzero?) @@ -267,6 +215,7 @@ 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)) end def test_uplus @@ -305,12 +254,6 @@ class Complex_Test < Test::Unit::TestCase assert_equal('0.0', c.real.to_s) assert_equal('0.0', c.imag.to_s) end - -=begin - assert_equal(0, Complex(0).negate) - assert_equal(-2, Complex(2).negate) - assert_equal(2, Complex(-2).negate) -=end end def test_add @@ -322,10 +265,8 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(3,2), c + 2) assert_equal(Complex(3.0,2), c + 2.0) - if @rational - assert_equal(Complex(Rational(3,1),Rational(2)), c + Rational(2)) - assert_equal(Complex(Rational(5,3),Rational(2)), c + Rational(2,3)) - end + assert_equal(Complex(Rational(3,1),Rational(2)), c + Rational(2)) + assert_equal(Complex(Rational(5,3),Rational(2)), c + Rational(2,3)) end def test_sub @@ -337,10 +278,8 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(-1,2), c - 2) assert_equal(Complex(-1.0,2), c - 2.0) - if @rational - assert_equal(Complex(Rational(-1,1),Rational(2)), c - Rational(2)) - assert_equal(Complex(Rational(1,3),Rational(2)), c - Rational(2,3)) - end + assert_equal(Complex(Rational(-1,1),Rational(2)), c - Rational(2)) + assert_equal(Complex(Rational(1,3),Rational(2)), c - Rational(2,3)) end def test_mul @@ -352,24 +291,22 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(2,4), c * 2) assert_equal(Complex(2.0,4.0), c * 2.0) - if @rational - assert_equal(Complex(Rational(2,1),Rational(4)), c * Rational(2)) - assert_equal(Complex(Rational(2,3),Rational(4,3)), c * Rational(2,3)) - end + assert_equal(Complex(Rational(2,1),Rational(4)), c * Rational(2)) + assert_equal(Complex(Rational(2,3),Rational(4,3)), c * Rational(2,3)) + c = Complex(Float::INFINITY, 0) + assert_equal(Complex(Float::INFINITY, 0), c * Complex(1, 0)) + assert_equal(Complex(0, Float::INFINITY), c * Complex(0, 1)) + 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)) end def test_div c = Complex(1,2) c2 = Complex(2,3) - if @rational - assert_equal(Complex(Rational(8,13),Rational(1,13)), c / c2) - else - r = c / c2 - assert_in_delta(0.615, r.real, 0.001) - assert_in_delta(0.076, r.imag, 0.001) - end + assert_equal(Complex(Rational(8,13),Rational(1,13)), c / c2) c = Complex(1.0,2.0) c2 = Complex(2.0,3.0) @@ -381,30 +318,18 @@ class Complex_Test < Test::Unit::TestCase c = Complex(1,2) c2 = Complex(2,3) - if @rational - assert_equal(Complex(Rational(1,2),1), c / 2) - else - assert_equal(Complex(0.5,1.0), c / 2) - end + assert_equal(Complex(Rational(1,2),1), c / 2) assert_equal(Complex(0.5,1.0), c / 2.0) - if @rational - assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) - assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) - end + assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) + assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) end def test_quo c = Complex(1,2) c2 = Complex(2,3) - if @rational - assert_equal(Complex(Rational(8,13),Rational(1,13)), c.quo(c2)) - else - r = c.quo(c2) - assert_in_delta(0.615, r.real, 0.001) - assert_in_delta(0.076, r.imag, 0.001) - end + assert_equal(Complex(Rational(8,13),Rational(1,13)), c.quo(c2)) c = Complex(1.0,2.0) c2 = Complex(2.0,3.0) @@ -416,17 +341,11 @@ class Complex_Test < Test::Unit::TestCase c = Complex(1,2) c2 = Complex(2,3) - if @rational - assert_equal(Complex(Rational(1,2),1), c.quo(2)) - else - assert_equal(Complex(0.5,1.0), c.quo(2)) - end + assert_equal(Complex(Rational(1,2),1), c.quo(2)) assert_equal(Complex(0.5,1.0), c.quo(2.0)) - if @rational - assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) - assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) - end + assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) + assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) end def test_fdiv @@ -460,13 +379,8 @@ class Complex_Test < Test::Unit::TestCase assert_in_delta(-0.179, r.imag, 0.001) assert_equal(Complex(-3,4), c ** 2) - if @rational && !@keiju - assert_equal(Complex(Rational(-3,25),Rational(-4,25)), c ** -2) - else - r = c ** -2 - assert_in_delta(-0.12, r.real, 0.001) - assert_in_delta(-0.16, r.imag, 0.001) - end + assert_equal(Complex(Rational(-3,25),Rational(-4,25)), c ** -2) + r = c ** 2.0 assert_in_delta(-3.0, r.real, 0.001) assert_in_delta(4.0, r.imag, 0.001) @@ -475,21 +389,17 @@ class Complex_Test < Test::Unit::TestCase assert_in_delta(-0.12, r.real, 0.001) assert_in_delta(-0.16, r.imag, 0.001) - if @rational && !@keiju - assert_equal(Complex(-3,4), c ** Rational(2)) -#=begin - assert_equal(Complex(Rational(-3,25),Rational(-4,25)), - c ** Rational(-2)) # why failed? -#=end + assert_equal(Complex(-3,4), c ** Rational(2)) + assert_equal(Complex(Rational(-3,25),Rational(-4,25)), + c ** Rational(-2)) # why failed? - r = c ** Rational(2,3) - assert_in_delta(1.264, r.real, 0.001) - assert_in_delta(1.150, r.imag, 0.001) + r = c ** Rational(2,3) + assert_in_delta(1.264, r.real, 0.001) + assert_in_delta(1.150, r.imag, 0.001) - r = c ** Rational(-2,3) - assert_in_delta(0.432, r.real, 0.001) - assert_in_delta(-0.393, r.imag, 0.001) - end + r = c ** Rational(-2,3) + assert_in_delta(0.432, r.real, 0.001) + assert_in_delta(-0.393, r.imag, 0.001) end def test_cmp @@ -521,10 +431,13 @@ class Complex_Test < Test::Unit::TestCase assert_equal([Complex(Rational(2)),Complex(1)], Complex(1).coerce(Rational(2))) assert_equal([Complex(2),Complex(1)], Complex(1).coerce(Complex(2))) + + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { Complex(1).coerce(obj) } end class ObjectX - def + (x) Rational(1) end + def +(x) Rational(1) end alias - + alias * + alias / + @@ -540,18 +453,6 @@ class Complex_Test < Test::Unit::TestCase end end - def test_unify - if @unify - assert_instance_of(Fixnum, Complex(1,2) + Complex(-1,-2)) - assert_instance_of(Fixnum, Complex(1,2) - Complex(1,2)) - assert_instance_of(Fixnum, Complex(1,2) * 0) - assert_instance_of(Fixnum, Complex(1,2) / Complex(1,2)) -# assert_instance_of(Fixnum, Complex(1,2).div(Complex(1,2))) - assert_instance_of(Fixnum, Complex(1,2).quo(Complex(1,2))) -# assert_instance_of(Fixnum, Complex(1,2) ** 0) # mathn's bug - end - end - def test_math c = Complex(1,2) @@ -573,8 +474,6 @@ class Complex_Test < Test::Unit::TestCase assert_in_delta(1.107, r[1], 0.001) assert_equal(Complex(1,-2), c.conjugate) assert_equal(Complex(1,-2), c.conj) -# assert_equal(Complex(1,-2), ~c) -# assert_equal(5, c * ~c) assert_equal(Complex(1,2), c.numerator) assert_equal(1, c.denominator) @@ -602,23 +501,21 @@ class Complex_Test < Test::Unit::TestCase assert_equal('1.0-2.0i', Complex(1.0,-2.0).to_s) assert_equal('-1.0-2.0i', Complex(-1.0,-2.0).to_s) - if @rational && !@unify && !@keiju - assert_equal('0+2/1i', Complex(0,Rational(2)).to_s) - assert_equal('0-2/1i', Complex(0,Rational(-2)).to_s) - assert_equal('1+2/1i', Complex(1,Rational(2)).to_s) - assert_equal('-1+2/1i', Complex(-1,Rational(2)).to_s) - assert_equal('-1-2/1i', Complex(-1,Rational(-2)).to_s) - assert_equal('1-2/1i', Complex(1,Rational(-2)).to_s) - assert_equal('-1-2/1i', Complex(-1,Rational(-2)).to_s) - - assert_equal('0+2/3i', Complex(0,Rational(2,3)).to_s) - assert_equal('0-2/3i', Complex(0,Rational(-2,3)).to_s) - assert_equal('1+2/3i', Complex(1,Rational(2,3)).to_s) - assert_equal('-1+2/3i', Complex(-1,Rational(2,3)).to_s) - assert_equal('-1-2/3i', Complex(-1,Rational(-2,3)).to_s) - assert_equal('1-2/3i', Complex(1,Rational(-2,3)).to_s) - assert_equal('-1-2/3i', Complex(-1,Rational(-2,3)).to_s) - end + assert_equal('0+2/1i', Complex(0,Rational(2)).to_s) + assert_equal('0-2/1i', Complex(0,Rational(-2)).to_s) + assert_equal('1+2/1i', Complex(1,Rational(2)).to_s) + assert_equal('-1+2/1i', Complex(-1,Rational(2)).to_s) + assert_equal('-1-2/1i', Complex(-1,Rational(-2)).to_s) + assert_equal('1-2/1i', Complex(1,Rational(-2)).to_s) + assert_equal('-1-2/1i', Complex(-1,Rational(-2)).to_s) + + assert_equal('0+2/3i', Complex(0,Rational(2,3)).to_s) + assert_equal('0-2/3i', Complex(0,Rational(-2,3)).to_s) + assert_equal('1+2/3i', Complex(1,Rational(2,3)).to_s) + assert_equal('-1+2/3i', Complex(-1,Rational(2,3)).to_s) + assert_equal('-1-2/3i', Complex(-1,Rational(-2,3)).to_s) + assert_equal('1-2/3i', Complex(1,Rational(-2,3)).to_s) + assert_equal('-1-2/3i', Complex(-1,Rational(-2,3)).to_s) nan = 0.0 / 0 inf = 1.0 / 0 @@ -640,26 +537,21 @@ class Complex_Test < Test::Unit::TestCase def test_marshal c = Complex(1,2) - c.instance_eval{@ivar = 9} s = Marshal.dump(c) c2 = Marshal.load(s) assert_equal(c, c2) - assert_equal(9, c2.instance_variable_get(:@ivar)) assert_instance_of(Complex, c2) - if @rational - c = Complex(Rational(1,2),Rational(2,3)) + c = Complex(Rational(1,2),Rational(2,3)) - s = Marshal.dump(c) - c2 = Marshal.load(s) - assert_equal(c, c2) - assert_instance_of(Complex, c2) - end + s = Marshal.dump(c) + c2 = Marshal.load(s) + assert_equal(c, c2) + assert_instance_of(Complex, c2) bug3656 = '[ruby-core:31622]' c = Complex(1,2) - c.freeze assert_predicate(c, :frozen?) result = c.marshal_load([2,3]) rescue :fail assert_equal(:fail, result, bug3656) @@ -835,57 +727,54 @@ class Complex_Test < Test::Unit::TestCase assert_raise(ArgumentError){ Complex('5+3i_')} assert_raise(ArgumentError){ Complex('5+3ix')} - if @rational && defined?(''.to_r) - assert_equal(Complex(Rational(1,5)), '1/5'.to_c) - assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c) - assert_equal(Complex(Rational(1,5),3), '1/5+3i'.to_c) - assert_equal(Complex(Rational(1,5),-3), '1/5-3i'.to_c) - assert_equal(Complex(Rational(-1,5),3), '-1/5+3i'.to_c) - assert_equal(Complex(Rational(-1,5),-3), '-1/5-3i'.to_c) - assert_equal(Complex(Rational(1,5),Rational(3,2)), '1/5+3/2i'.to_c) - assert_equal(Complex(Rational(1,5),Rational(-3,2)), '1/5-3/2i'.to_c) - assert_equal(Complex(Rational(-1,5),Rational(3,2)), '-1/5+3/2i'.to_c) - assert_equal(Complex(Rational(-1,5),Rational(-3,2)), '-1/5-3/2i'.to_c) - assert_equal(Complex(Rational(1,5),Rational(3,2)), '1/5+3/2i'.to_c) - assert_equal(Complex(Rational(1,5),Rational(-3,2)), '1/5-3/2i'.to_c) - assert_equal(Complex(Rational(-1,5),Rational(3,2)), '-1/5+3/2i'.to_c) - assert_equal(Complex(Rational(-1,5),Rational(-3,2)), '-1/5-3/2i'.to_c) - assert_equal(Complex.polar(Rational(1,5),Rational(3,2)), Complex('1/5@3/2')) - assert_equal(Complex.polar(Rational(-1,5),Rational(-3,2)), Complex('-1/5@-3/2')) - end + assert_equal(Complex(Rational(1,5)), '1/5'.to_c) + assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c) + assert_equal(Complex(Rational(1,5),3), '1/5+3i'.to_c) + assert_equal(Complex(Rational(1,5),-3), '1/5-3i'.to_c) + assert_equal(Complex(Rational(-1,5),3), '-1/5+3i'.to_c) + assert_equal(Complex(Rational(-1,5),-3), '-1/5-3i'.to_c) + assert_equal(Complex(Rational(1,5),Rational(3,2)), '1/5+3/2i'.to_c) + assert_equal(Complex(Rational(1,5),Rational(-3,2)), '1/5-3/2i'.to_c) + assert_equal(Complex(Rational(-1,5),Rational(3,2)), '-1/5+3/2i'.to_c) + assert_equal(Complex(Rational(-1,5),Rational(-3,2)), '-1/5-3/2i'.to_c) + assert_equal(Complex(Rational(1,5),Rational(3,2)), '1/5+3/2i'.to_c) + assert_equal(Complex(Rational(1,5),Rational(-3,2)), '1/5-3/2i'.to_c) + assert_equal(Complex(Rational(-1,5),Rational(3,2)), '-1/5+3/2i'.to_c) + assert_equal(Complex(Rational(-1,5),Rational(-3,2)), '-1/5-3/2i'.to_c) + assert_equal(Complex.polar(Rational(1,5),Rational(3,2)), Complex('1/5@3/2')) + assert_equal(Complex.polar(Rational(-1,5),Rational(-3,2)), Complex('-1/5@-3/2')) end def test_respond c = Complex(1,1) - assert_equal(false, c.respond_to?(:%)) - assert_equal(false, c.respond_to?(:<)) - assert_equal(false, c.respond_to?(:<=)) - assert_equal(false, c.respond_to?(:<=>)) - assert_equal(false, c.respond_to?(:>)) - assert_equal(false, c.respond_to?(:>=)) - assert_equal(false, c.respond_to?(:between?)) - assert_equal(false, c.respond_to?(:div)) - assert_equal(false, c.respond_to?(:divmod)) - assert_equal(false, c.respond_to?(:floor)) - assert_equal(false, c.respond_to?(:ceil)) - assert_equal(false, c.respond_to?(:modulo)) - assert_equal(false, c.respond_to?(:remainder)) - assert_equal(false, c.respond_to?(:round)) - assert_equal(false, c.respond_to?(:step)) - assert_equal(false, c.respond_to?(:tunrcate)) - - assert_equal(false, c.respond_to?(:positive?)) - assert_equal(false, c.respond_to?(:negative?)) -# assert_equal(false, c.respond_to?(:sign)) - - assert_equal(false, c.respond_to?(:quotient)) - assert_equal(false, c.respond_to?(:quot)) - assert_equal(false, c.respond_to?(:quotrem)) - - assert_equal(false, c.respond_to?(:gcd)) - assert_equal(false, c.respond_to?(:lcm)) - assert_equal(false, c.respond_to?(:gcdlcm)) + 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) + assert_not_respond_to(c, :ceil) + assert_not_respond_to(c, :modulo) + assert_not_respond_to(c, :remainder) + assert_not_respond_to(c, :round) + assert_not_respond_to(c, :step) + assert_not_respond_to(c, :tunrcate) + + assert_not_respond_to(c, :positive?) + assert_not_respond_to(c, :negative?) + assert_not_respond_to(c, :sign) + + assert_not_respond_to(c, :quotient) + assert_not_respond_to(c, :quot) + assert_not_respond_to(c, :quotrem) + + assert_not_respond_to(c, :gcd) + assert_not_respond_to(c, :lcm) + assert_not_respond_to(c, :gcdlcm) + + (Comparable.instance_methods(false) - Complex.instance_methods(false)).each do |n| + assert_not_respond_to(c, n, "Complex##{n}") + end end def test_to_i @@ -903,12 +792,10 @@ class Complex_Test < Test::Unit::TestCase end def test_to_r - if @rational && !@keiju - assert_equal(Rational(3), Complex(3).to_r) - assert_equal(Rational(3), Rational(Complex(3))) - assert_raise(RangeError){Complex(3,2).to_r} -# assert_raise(RangeError){Rational(Complex(3,2))} - end + assert_equal(Rational(3), Complex(3).to_r) + assert_equal(Rational(3), Rational(Complex(3))) + assert_raise(RangeError){Complex(3,2).to_r} + assert_raise(RangeError){Rational(Complex(3,2))} end def test_to_c @@ -924,10 +811,8 @@ class Complex_Test < Test::Unit::TestCase c = 1.1.to_c assert_equal([1.1, 0], [c.real, c.imag]) - if @rational - c = Rational(1,2).to_c - assert_equal([Rational(1,2), 0], [c.real, c.imag]) - end + c = Rational(1,2).to_c + assert_equal([Rational(1,2), 0], [c.real, c.imag]) c = Complex(1,2).to_c assert_equal([1, 2], [c.real, c.imag]) @@ -940,9 +825,45 @@ class Complex_Test < Test::Unit::TestCase end end + def test_finite_p + assert_predicate(1+1i, :finite?) + assert_predicate(1-1i, :finite?) + assert_predicate(-1+1i, :finite?) + assert_predicate(-1-1i, :finite?) + assert_not_predicate(Float::INFINITY + 1i, :finite?) + assert_not_predicate(Complex(1, Float::INFINITY), :finite?) + assert_predicate(Complex(Float::MAX, 0.0), :finite?) + assert_predicate(Complex(0.0, Float::MAX), :finite?) + assert_predicate(Complex(Float::MAX, Float::MAX), :finite?) + assert_not_predicate(Complex(Float::NAN, 0), :finite?) + assert_not_predicate(Complex(0, Float::NAN), :finite?) + assert_not_predicate(Complex(Float::NAN, Float::NAN), :finite?) + end + + def test_infinite_p + assert_nil((1+1i).infinite?) + assert_nil((1-1i).infinite?) + assert_nil((-1+1i).infinite?) + assert_nil((-1-1i).infinite?) + assert_equal(1, (Float::INFINITY + 1i).infinite?) + assert_equal(1, (Float::INFINITY - 1i).infinite?) + assert_equal(1, (-Float::INFINITY + 1i).infinite?) + assert_equal(1, (-Float::INFINITY - 1i).infinite?) + assert_equal(1, Complex(1, Float::INFINITY).infinite?) + assert_equal(1, Complex(-1, Float::INFINITY).infinite?) + assert_equal(1, Complex(1, -Float::INFINITY).infinite?) + assert_equal(1, Complex(-1, -Float::INFINITY).infinite?) + assert_nil(Complex(Float::MAX, 0.0).infinite?) + assert_nil(Complex(0.0, Float::MAX).infinite?) + assert_nil(Complex(Float::MAX, Float::MAX).infinite?) + assert_nil(Complex(Float::NAN, 0).infinite?) + assert_nil(Complex(0, Float::NAN).infinite?) + assert_nil(Complex(Float::NAN, Float::NAN).infinite?) + end + def test_supp - assert_equal(true, 1.real?) - assert_equal(true, 1.1.real?) + assert_predicate(1, :real?) + assert_predicate(1.1, :real?) assert_equal(1, 1.real) assert_equal(0, 1.imag) @@ -1011,134 +932,13 @@ class Complex_Test < Test::Unit::TestCase assert_equal(1.1, 1.1.conj) assert_equal(-1.1, -1.1.conj) - if @rational - assert_equal(Complex(Rational(1,2),Rational(1)), Complex(1,2).quo(2)) - else - assert_equal(Complex(0.5,1.0), Complex(1,2).quo(2)) - end - -=begin - if @rational && !@keiju - assert_equal(Complex(Rational(1,2),Rational(1)), Complex(1,2).quo(2)) - end -=end + assert_equal(Complex(Rational(1,2),Rational(1)), Complex(1,2).quo(2)) assert_equal(0.5, 1.fdiv(2)) assert_equal(5000000000.0, 10000000000.fdiv(2)) assert_equal(0.5, 1.0.fdiv(2)) - if @rational - assert_equal(0.25, Rational(1,2).fdiv(2)) - end + assert_equal(0.25, Rational(1,2).fdiv(2)) assert_equal(Complex(0.5,1.0), Complex(1,2).quo(2)) - - unless $".grep(/(?:\A|(?<!add)\/)complex/).empty? - assert_equal(Complex(0,2), Math.sqrt(-4.0)) -# assert_equal(true, Math.sqrt(-4.0).inexact?) - assert_equal(Complex(0,2), Math.sqrt(-4)) -# assert_equal(true, Math.sqrt(-4).exact?) - if @rational - assert_equal(Complex(0,2), Math.sqrt(Rational(-4))) -# assert_equal(true, Math.sqrt(Rational(-4)).exact?) - end - - assert_equal(Complex(0,3), Math.sqrt(-9.0)) -# assert_equal(true, Math.sqrt(-9.0).inexact?) - assert_equal(Complex(0,3), Math.sqrt(-9)) -# assert_equal(true, Math.sqrt(-9).exact?) - if @rational - assert_equal(Complex(0,3), Math.sqrt(Rational(-9))) -# assert_equal(true, Math.sqrt(Rational(-9)).exact?) - end - - c = Math.sqrt(Complex(1, 2)) - assert_in_delta(1.272, c.real, 0.001) - assert_in_delta(0.786, c.imag, 0.001) - - c = Math.sqrt(-9) - assert_in_delta(0.0, c.real, 0.001) - assert_in_delta(3.0, c.imag, 0.001) - - c = Math.exp(Complex(1, 2)) - assert_in_delta(-1.131, c.real, 0.001) - assert_in_delta(2.471, c.imag, 0.001) - - c = Math.sin(Complex(1, 2)) - assert_in_delta(3.165, c.real, 0.001) - assert_in_delta(1.959, c.imag, 0.001) - - c = Math.cos(Complex(1, 2)) - assert_in_delta(2.032, c.real, 0.001) - assert_in_delta(-3.051, c.imag, 0.001) - - c = Math.tan(Complex(1, 2)) - assert_in_delta(0.033, c.real, 0.001) - assert_in_delta(1.014, c.imag, 0.001) - - c = Math.sinh(Complex(1, 2)) - assert_in_delta(-0.489, c.real, 0.001) - assert_in_delta(1.403, c.imag, 0.001) - - c = Math.cosh(Complex(1, 2)) - assert_in_delta(-0.642, c.real, 0.001) - assert_in_delta(1.068, c.imag, 0.001) - - c = Math.tanh(Complex(1, 2)) - assert_in_delta(1.166, c.real, 0.001) - assert_in_delta(-0.243, c.imag, 0.001) - - c = Math.log(Complex(1, 2)) - assert_in_delta(0.804, c.real, 0.001) - assert_in_delta(1.107, c.imag, 0.001) - - c = Math.log(Complex(1, 2), Math::E) - assert_in_delta(0.804, c.real, 0.001) - assert_in_delta(1.107, c.imag, 0.001) - - c = Math.log(-1) - assert_in_delta(0.0, c.real, 0.001) - assert_in_delta(Math::PI, c.imag, 0.001) - - c = Math.log(8, 2) - assert_in_delta(3.0, c.real, 0.001) - assert_in_delta(0.0, c.imag, 0.001) - - c = Math.log(-8, -2) - assert_in_delta(1.092, c.real, 0.001) - assert_in_delta(-0.420, c.imag, 0.001) - - c = Math.log10(Complex(1, 2)) - assert_in_delta(0.349, c.real, 0.001) - assert_in_delta(0.480, c.imag, 0.001) - - c = Math.asin(Complex(1, 2)) - assert_in_delta(0.427, c.real, 0.001) - assert_in_delta(1.528, c.imag, 0.001) - - c = Math.acos(Complex(1, 2)) - assert_in_delta(1.143, c.real, 0.001) - assert_in_delta(-1.528, c.imag, 0.001) - - c = Math.atan(Complex(1, 2)) - assert_in_delta(1.338, c.real, 0.001) - assert_in_delta(0.402, c.imag, 0.001) - - c = Math.atan2(Complex(1, 2), 1) - assert_in_delta(1.338, c.real, 0.001) - assert_in_delta(0.402, c.imag, 0.001) - - c = Math.asinh(Complex(1, 2)) - assert_in_delta(1.469, c.real, 0.001) - assert_in_delta(1.063, c.imag, 0.001) - - c = Math.acosh(Complex(1, 2)) - assert_in_delta(1.528, c.real, 0.001) - assert_in_delta(1.143, c.imag, 0.001) - - c = Math.atanh(Complex(1, 2)) - assert_in_delta(0.173, c.real, 0.001) - assert_in_delta(1.178, c.imag, 0.001) - end - end def test_ruby19 @@ -1148,9 +948,7 @@ class Complex_Test < Test::Unit::TestCase end def test_fixed_bug - if @rational && !@keiju - assert_equal(Complex(1), 1 ** Complex(1)) - end + assert_equal(Complex(1), 1 ** Complex(1)) assert_equal('-1.0-0.0i', Complex(-1.0, -0.0).to_s) assert_in_delta(Math::PI, Complex(-0.0).arg, 0.001) assert_equal(Complex(2e3, 2e4), '2e3+2e4i'.to_c) diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb index 3ee7810dc6..594fc3f45a 100644 --- a/test/ruby/test_complex2.rb +++ b/test/ruby/test_complex2.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class Complex_Test2 < Test::Unit::TestCase diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb index cef4074afa..0360f5ee42 100644 --- a/test/ruby/test_complexrational.rb +++ b/test/ruby/test_complexrational.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class ComplexRational_Test < Test::Unit::TestCase @@ -213,10 +214,10 @@ class SimpleRat < Numeric def numerator() @num end def denominator() @den end - def +@ () self end - def -@ () self.class.new(-@num, @den) end + def +@() self end + def -@() self.class.new(-@num, @den) end - def + (o) + def +(o) case o when SimpleRat, Rational a = @num * o.denominator @@ -232,7 +233,7 @@ class SimpleRat < Numeric end end - def - (o) + def -(o) case o when SimpleRat, Rational a = @num * o.denominator @@ -248,7 +249,7 @@ class SimpleRat < Numeric end end - def * (o) + def *(o) case o when SimpleRat, Rational a = @num * o.numerator @@ -272,7 +273,7 @@ class SimpleRat < Numeric self.class.new(a, b) when Integer if o == 0 - raise raise ZeroDivisionError, "divided by zero" + raise ZeroDivisionError, "divided by zero" end self.quo(self.class.new(o)) when Float @@ -333,7 +334,7 @@ class SimpleRat < Numeric def divmod(o) [div(o), modulo(o)] end def quotrem(o) [quot(o), remainder(o)] end - def ** (o) + def **(o) case o when SimpleRat, Rational Float(self) ** o @@ -356,7 +357,7 @@ class SimpleRat < Numeric end end - def <=> (o) + def <=>(o) case o when SimpleRat, Rational a = @num * o.denominator @@ -372,7 +373,7 @@ class SimpleRat < Numeric end end - def == (o) + def ==(o) begin (self <=> o) == 0 rescue diff --git a/test/ruby/test_condition.rb b/test/ruby/test_condition.rb index ba2e0688f3..ab0ffc4b6a 100644 --- a/test/ruby/test_condition.rb +++ b/test/ruby/test_condition.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestCondition < Test::Unit::TestCase diff --git a/test/ruby/test_const.rb b/test/ruby/test_const.rb index c4a4d93249..8784e0e988 100644 --- a/test/ruby/test_const.rb +++ b/test/ruby/test_const.rb @@ -1,6 +1,6 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestConst < Test::Unit::TestCase TEST1 = 1 @@ -37,7 +37,7 @@ class TestConst < Test::Unit::TestCase self.class.class_eval { include Const2 } - STDERR.print "intentionally redefines TEST3, TEST4\n" if $VERBOSE + # STDERR.print "intentionally redefines TEST3, TEST4\n" if $VERBOSE assert defined?(TEST1) assert_equal 1, TEST1 assert defined?(TEST2) @@ -50,17 +50,23 @@ class TestConst < Test::Unit::TestCase def test_redefinition c = Class.new - c.const_set(:X, 1) - assert_output(nil, <<-WARNING) {c.const_set(:X, 2)} -#{__FILE__}:#{__LINE__-1}: warning: already initialized constant #{c}::X -#{__FILE__}:#{__LINE__-3}: warning: previous definition of X was here + name = "X\u{5b9a 6570}" + c.const_set(name, 1) + prev_line = __LINE__ - 1 + assert_warning(<<-WARNING) {c.const_set(name, 2)} +#{__FILE__}:#{__LINE__-1}: warning: already initialized constant #{c}::#{name} +#{__FILE__}:#{prev_line}: warning: previous definition of #{name} was here WARNING + end + + def test_redefinition_memory_leak code = <<-PRE -olderr = $stderr.dup -$stderr.reopen(File::NULL, "wb") 350000.times { FOO = :BAR } -$stderr.reopen(olderr) PRE - assert_no_memory_leak([], '', code, 'redefined constant') + assert_no_memory_leak(%w[-W0 -], '', code, 'redefined constant', timeout: 30) + end + + def test_toplevel_lookup + assert_raise(NameError, '[Feature #11547]') {TestConst::Object} end end diff --git a/test/ruby/test_continuation.rb b/test/ruby/test_continuation.rb index 52d8de482c..a06ac98c8c 100644 --- a/test/ruby/test_continuation.rb +++ b/test/ruby/test_continuation.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: false require 'test/unit' -require 'continuation' +EnvUtil.suppress_warning {require 'continuation'} require 'fiber' -require_relative 'envutil' class TestContinuation < Test::Unit::TestCase def test_create @@ -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 } @@ -132,4 +134,3 @@ class TestContinuation < Test::Unit::TestCase assert_equal 3, @memo end end - diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index fdae6789f1..9976db3b6f 100644 --- a/test/ruby/test_defined.rb +++ b/test/ruby/test_defined.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestDefined < Test::Unit::TestCase class Foo @@ -99,6 +99,10 @@ class TestDefined < Test::Unit::TestCase end end + def test_defined_empty_paren_arg + assert_nil(defined?(p () + 1)) + end + def test_defined_impl_specific feature7035 = '[ruby-core:47558]' # not spec assert_predicate(defined?(Foo), :frozen?, feature7035) @@ -203,7 +207,7 @@ class TestDefined < Test::Unit::TestCase o = c.new o.extend(m) - assert_equal("super", o.x) + assert_equal("super", o.x, bug8367) end def test_super_toplevel @@ -224,6 +228,14 @@ class TestDefined < Test::Unit::TestCase def existing_method end + + def func_defined_existing_func + defined?(existing_method()) + end + + def func_defined_non_existing_func + defined?(non_existing_method()) + end end def test_method_by_respond_to_missing @@ -233,5 +245,16 @@ class TestDefined < Test::Unit::TestCase assert_equal(false, obj.called, bug_11211) assert_equal(nil, defined?(obj.non_existing_method), bug_11211) assert_equal(true, obj.called, bug_11211) + + bug_11212 = '[Bug #11212]' + obj = ExampleRespondToMissing.new + assert_equal("method", obj.func_defined_existing_func, bug_11212) + assert_equal(false, obj.called, bug_11212) + 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 end diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb index 6054b8f7fe..000bc24e85 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' @@ -10,7 +11,7 @@ class TestDir < Test::Unit::TestCase $VERBOSE = nil @root = File.realpath(Dir.mktmpdir('__test_dir__')) @nodir = File.join(@root, "dummy") - for i in ?a..?z + for i in "a".."z" if i.ord % 2 == 0 FileUtils.touch(File.join(@root, i)) else @@ -126,19 +127,21 @@ class TestDir < Test::Unit::TestCase def test_close d = Dir.open(@root) d.close + assert_nothing_raised(IOError) { d.close } assert_raise(IOError) { d.read } end def test_glob - assert_equal((%w(. ..) + (?a..?z).to_a).map{|f| File.join(@root, f) }, + 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, + 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, + 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, + assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") }.sort, Dir.glob(File.join(@root, "*/")).sort) + assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a')) FileUtils.touch(File.join(@root, "{}")) assert_equal(%w({} a).map{|f| File.join(@root, f) }, @@ -147,7 +150,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) }.sort, Dir.glob(File.join(@root, '[abc/def]')).sort) + + 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 @@ -176,8 +187,68 @@ class TestDir < Test::Unit::TestCase end end + if Process.const_defined?(:RLIMIT_NOFILE) + def test_glob_too_may_open_files + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", chdir: @root) + begin; + n = 16 + Process.setrlimit(Process::RLIMIT_NOFILE, n) + files = [] + begin + n.times {files << File.open('b')} + rescue Errno::EMFILE, Errno::ENFILE => e + end + assert_raise(e.class) { + Dir.glob('*') + } + end; + end + end + + 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}) + 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)}}) + end + + def assert_entries(entries, children_only = false) + entries.sort! + expected = ("a".."z").to_a + expected = %w(. ..) + expected unless children_only + assert_equal(expected, entries) + end + + def test_entries + assert_entries(Dir.open(@root) {|dir| dir.entries}) + assert_entries(Dir.entries(@root).to_a) + assert_raise(ArgumentError) {Dir.entries(@root+"\0")} + end + def test_foreach - assert_equal(Dir.foreach(@root).to_a.sort, %w(. ..) + (?a..?z).to_a) + 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} + end + + def test_children + assert_entries(Dir.children(@root), true) + assert_raise(ArgumentError) {Dir.children(@root+"\0")} + end + + def test_each_child + assert_entries(Dir.each_child(@root).to_a, true) + assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a} end def test_dir_enc @@ -209,18 +280,19 @@ class TestDir < Test::Unit::TestCase def test_symlink begin - ["dummy", *?a..?z].each do |f| + ["dummy", *"a".."z"].each do |f| File.symlink(File.join(@root, f), File.join(@root, "symlink-#{ f }")) end - rescue NotImplementedError + rescue NotImplementedError, Errno::EACCES return end - assert_equal([*?a..?z, *"symlink-a".."symlink-z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort, + 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) end def test_glob_metachar @@ -228,18 +300,57 @@ class TestDir < Test::Unit::TestCase assert_empty(Dir.glob(File.join(@root, "<")), bug8597) end + def test_glob_cases + feature5994 = "[ruby-core:42469] [Feature #5994]" + feature5994 << "\nDir.glob should return the filename with actual cases on the filesystem" + Dir.chdir(File.join(@root, "a")) do + open("FileWithCases", "w") {} + return unless File.exist?("filewithcases") + assert_equal(%w"FileWithCases", Dir.glob("filewithcases"), feature5994) + end + Dir.chdir(@root) do + assert_equal(%w"a/FileWithCases", Dir.glob("A/filewithcases"), feature5994) + end + end + + def test_glob_super_root + bug9648 = '[ruby-core:61552] [Bug #9648]' + roots = Dir.glob("/*") + assert_equal(roots.map {|n| "/..#{n}"}, Dir.glob("/../*"), bug9648) + end + + if /mswin|mingw/ =~ RUBY_PLATFORM + def test_glob_legacy_short_name + bug10819 = '[ruby-core:67954] [Bug #10819]' + bug11206 = '[ruby-core:69435] [Bug #11206]' + skip unless /\A\w:/ =~ ENV["ProgramFiles"] + short = "#$&/PROGRA~1" + skip unless File.directory?(short) + entries = Dir.glob("#{short}/Common*") + assert_not_empty(entries, bug10819) + long = File.expand_path(short) + assert_equal(Dir.glob("#{long}/Common*"), entries, bug10819) + wild = short.sub(/1\z/, '*') + assert_not_include(Dir.glob(wild), long, bug11206) + assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819) + assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819) + end + end + def test_home env_home = ENV["HOME"] env_logdir = ENV["LOGDIR"] ENV.delete("HOME") ENV.delete("LOGDIR") - assert_raise(ArgumentError) { Dir.home } - assert_raise(ArgumentError) { Dir.home("") } ENV["HOME"] = @nodir assert_nothing_raised(ArgumentError) { assert_equal(@nodir, Dir.home) assert_equal(@nodir, Dir.home("")) + if user = ENV["USER"] + ENV["HOME"] = env_home + assert_equal(File.expand_path(env_home), Dir.home(user)) + end } %W[no:such:user \u{7559 5b88}:\u{756a}].each do |user| assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)} @@ -254,7 +365,7 @@ class TestDir < Test::Unit::TestCase Dir.chdir(dirname) do begin File.symlink('some-dir', 'dir-symlink') - rescue NotImplementedError + rescue NotImplementedError, Errno::EACCES return end @@ -266,4 +377,48 @@ class TestDir < Test::Unit::TestCase end end end + + def test_fileno + Dir.open(".") {|d| + if d.respond_to? :fileno + assert_kind_of(Integer, d.fileno) + else + assert_raise(NotImplementedError) { d.fileno } + end + } + end + + def test_empty? + assert_not_send([Dir, :empty?, @root]) + a = File.join(@root, "a") + assert_send([Dir, :empty?, a]) + %w[A .dot].each do |tmp| + tmp = File.join(a, tmp) + open(tmp, "w") {} + assert_not_send([Dir, :empty?, a]) + File.delete(tmp) + assert_send([Dir, :empty?, a]) + Dir.mkdir(tmp) + assert_not_send([Dir, :empty?, a]) + Dir.rmdir(tmp) + assert_send([Dir, :empty?, a]) + 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 + assert_separately(["-C", @root], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 3) + begin; + Process.setrlimit(Process::RLIMIT_NOFILE, 50) + begin + tap {tap {tap {(0..100).map {open(IO::NULL)}}}} + rescue Errno::EMFILE + end + list = Dir.glob("*").sort + assert_not_empty(list) + assert_equal([*"a".."z"], list) + end; + end if defined?(Process::RLIMIT_NOFILE) end diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb index a8fab3d216..7584074c7e 100644 --- a/test/ruby/test_dir_m17n.rb +++ b/test/ruby/test_dir_m17n.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' -require_relative 'envutil' +require '-test-/file' class TestDir_M17N < Test::Unit::TestCase def with_tmpdir @@ -11,7 +12,7 @@ class TestDir_M17N < Test::Unit::TestCase } end - def create_and_check_raw_file_name(code, encoding) + def assert_raw_file_name(code, encoding) with_tmpdir { |dir| assert_separately(["-E#{encoding}"], <<-EOS, :chdir=>dir) filename = #{code}.chr('UTF-8').force_encoding("#{encoding}") @@ -21,6 +22,7 @@ class TestDir_M17N < Test::Unit::TestCase 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 @@ -57,6 +59,9 @@ class TestDir_M17N < Test::Unit::TestCase end def test_filename_extutf8_invalid + return if /cygwin/ =~ RUBY_PLATFORM + # High Sierra's APFS cannot use invalid filenames + return if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs" with_tmpdir {|d| assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8 @@ -172,6 +177,7 @@ class TestDir_M17N < Test::Unit::TestCase ## others def test_filename_bytes_euc_jp + return if /cygwin/ =~ RUBY_PLATFORM with_tmpdir {|d| assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") @@ -188,6 +194,7 @@ class TestDir_M17N < Test::Unit::TestCase end def test_filename_euc_jp + return if /cygwin/ =~ RUBY_PLATFORM with_tmpdir {|d| assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") @@ -209,7 +216,7 @@ class TestDir_M17N < Test::Unit::TestCase when /darwin/ filename = filename.encode("utf-8", "euc-jp").b when /mswin|mingw/ - if ents.include?(win_expected_filename.dup.force_encoding("ASCII-8BIT")) + if ents.include?(win_expected_filename.b) ents = Dir.entries(".", {:encoding => Encoding.find("filesystem")}) filename = win_expected_filename end @@ -221,18 +228,19 @@ class TestDir_M17N < Test::Unit::TestCase end def test_filename_utf8_raw_jp_name - create_and_check_raw_file_name(0x3042, "UTF-8") + assert_raw_file_name(0x3042, "UTF-8") end def test_filename_utf8_raw_windows_1251_name - create_and_check_raw_file_name(0x0424, "UTF-8") + assert_raw_file_name(0x0424, "UTF-8") end def test_filename_utf8_raw_windows_1252_name - create_and_check_raw_file_name(0x00c6, "UTF-8") + assert_raw_file_name(0x00c6, "UTF-8") end def test_filename_ext_euc_jp_and_int_utf_8 + return if /cygwin/ =~ RUBY_PLATFORM with_tmpdir {|d| assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") @@ -304,28 +312,138 @@ class TestDir_M17N < Test::Unit::TestCase } end - def test_entries_compose - bug7267 = '[ruby-core:48745] [Bug #7267]' + def with_enc_path + with_tmpdir do |d| + names = %W"\u{391 392 393 394 395} \u{3042 3044 3046 3048 304a}" + names.each do |dir| + EnvUtil.with_default_external(Encoding::UTF_8) do + Dir.mkdir(dir) rescue next + begin + yield(dir) + ensure + File.chmod(0700, dir) + end + end + end + end + end - pp = Object.new.extend(Test::Unit::Assertions) - def pp.mu_pp(ary) #:nodoc: - '[' << ary.map {|str| "#{str.dump}(#{str.encoding})"}.join(', ') << ']' + def test_glob_warning_opendir + with_enc_path do |dir| + open("#{dir}/x", "w") {} + File.chmod(0300, dir) + next if File.readable?(dir) + assert_warning(/#{dir}/) do + Dir.glob("#{dir}/*") + end end + end + + def test_glob_warning_match_all + with_enc_path do |dir| + open("#{dir}/x", "w") {} + File.chmod(0000, dir) + next if File.readable?(dir) + assert_warning(/#{dir}/) do + Dir.glob("#{dir}/x") + end + end + end + + def test_glob_warning_match_dir + with_enc_path do |dir| + Dir.mkdir("#{dir}/x") + File.chmod(0000, dir) + next if File.readable?(dir) + assert_warning(/#{dir}/) do + Dir.glob("#{dir}/x/") + end + end + end + + def test_glob_escape_multibyte + name = "\x81\\".force_encoding(Encoding::Shift_JIS) + with_tmpdir do + open(name, "w") {} rescue next + match, = Dir.glob("#{name}*") + next unless match and match.encoding == Encoding::Shift_JIS + assert_equal([name], Dir.glob("\\#{name}*")) + end + end + + def test_glob_encoding + with_tmpdir do + list = %W"file_one.ext file_two.ext \u{6587 4ef6}1.txt \u{6587 4ef6}2.txt" + list.each {|f| open(f, "w") {}} + a = "file_one*".force_encoding Encoding::IBM437 + b = "file_two*".force_encoding Encoding::EUC_JP + assert_equal([a, b].map(&:encoding), Dir[a, b].map(&:encoding)) + if Bug::File::Fs.fsname(Dir.pwd) == "apfs" + # High Sierra's APFS cannot use filenames with undefined character + dir = "\u{76EE}" + else + dir = "\u{76EE 5F551}" + end + Dir.mkdir(dir) + list << dir + bug12081 = '[ruby-core:73868] [Bug #12081]' + a = "*".force_encoding("us-ascii") + result = Dir[a].map {|n| + if n.encoding == Encoding::ASCII_8BIT || + n.encoding == Encoding::ISO_8859_1 || + !n.valid_encoding? + n.force_encoding(Encoding::UTF_8) + else + n.encode(Encoding::UTF_8) + end + } + assert_equal(list, result.sort!, bug12081) + end + end + + PP = Object.new.extend(Test::Unit::Assertions) + def PP.mu_pp(ary) #:nodoc: + '[' << ary.map {|str| "#{str.dump}(#{str.encoding})"}.join(', ') << ']' + end + + def test_entries_compose + bug7267 = '[ruby-core:48745] [Bug #7267]' with_tmpdir {|d| orig = %W"d\u{e9}tente x\u{304c 304e 3050 3052 3054}" 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 => Encoding.default_external} - orig.map! {|o| o.encode(Encoding.find("filesystem")) rescue o.tr("^a-z", "?")} + opts = {:encoding => enc} + orig.map! {|o| o.encode("filesystem") rescue o.tr("^a-z", "?")} else - enc = Encoding.find("filesystem") - enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII orig.each {|o| o.force_encoding(enc) } end ents = Dir.entries(".", opts).reject {|n| /\A\./ =~ n} ents.sort! - pp.assert_equal(orig, ents, bug7267) + PP.assert_equal(orig, ents, bug7267) + } + end + + def test_pwd + orig = %W"d\u{e9}tente x\u{304c 304e 3050 3052 3054}" + 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 + expected << n + with_tmpdir { + Dir.mkdir(o) + results << File.basename(Dir.chdir(o) {Dir.pwd}) + } } + PP.assert_equal(expected, results) end end diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index b2425ee7ea..6f098db454 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -1,15 +1,11 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' 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) - assert_equal([edst.dup.force_encoding("ASCII-8BIT"), - esrc.dup.force_encoding("ASCII-8BIT"), - eres], - [dst.dup.force_encoding("ASCII-8BIT"), - src.dup.force_encoding("ASCII-8BIT"), - res]) + assert_equal([edst.b, esrc.b, eres], + [dst.b, src.b, res]) end def assert_econv(converted, eres, obuf_bytesize, ec, consumed, rest, opts=nil) @@ -23,8 +19,8 @@ class TestEncodingConverter < Test::Unit::TestCase def assert_errinfo(e_res, e_enc1, e_enc2, e_error_bytes, e_readagain_bytes, ec) assert_equal([e_res, e_enc1, e_enc2, - e_error_bytes && e_error_bytes.dup.force_encoding("ASCII-8BIT"), - e_readagain_bytes && e_readagain_bytes.dup.force_encoding("ASCII-8BIT")], + e_error_bytes&.b, + e_readagain_bytes&.b], ec.primitive_errinfo) end @@ -909,6 +905,9 @@ class TestEncodingConverter < Test::Unit::TestCase ec1 = Encoding::Converter.new("", "", universal_newline: true) ec2 = Encoding::Converter.new("", "", newline: :universal) assert_equal(ec1, ec2) + assert_raise_with_message(ArgumentError, /\u{3042}/) { + Encoding::Converter.new("", "", newline: "\u{3042}".to_sym) + } end def test_default_external diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index bae8e763b3..8f73a8fce1 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestEncoding < Test::Unit::TestCase @@ -110,12 +110,17 @@ class TestEncoding < Test::Unit::TestCase bin = "a".force_encoding(Encoding::ASCII_8BIT) asc = "b".force_encoding(Encoding::US_ASCII) assert_equal(Encoding::ASCII_8BIT, Encoding.compatible?(bin, asc)) + bin = "\xff".force_encoding(Encoding::ASCII_8BIT).to_sym + asc = "b".force_encoding(Encoding::ASCII_8BIT) + assert_equal(Encoding::ASCII_8BIT, Encoding.compatible?(bin, asc)) + assert_equal(Encoding::UTF_8, Encoding.compatible?("\u{3042}".to_sym, ua.to_sym)) end def test_errinfo_after_autoload + assert_separately(%w[--disable=gems], "#{<<~"begin;"}\n#{<<~'end;'}") bug9038 = '[ruby-core:57949] [Bug #9038]' - assert_separately(%w[--disable=gems], <<-"end;") - assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, #{bug9038.dump}) { + begin; + assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) { eval("/regexp/sQ") } end; diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index 3ee6846b33..2167271886 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: false require 'test/unit' -require 'continuation' +EnvUtil.suppress_warning {require 'continuation'} require 'stringio' class TestEnumerable < Test::Unit::TestCase @@ -33,18 +34,16 @@ class TestEnumerable < Test::Unit::TestCase $VERBOSE = @verbose end - def assert_not_warn - begin - org_stderr = $stderr - v = $VERBOSE - $stderr = StringIO.new(warn = '') - $VERBOSE = true - yield - ensure - $stderr = org_stderr - $VERBOSE = v - end - assert_equal("", warn) + def test_grep_v + assert_equal([3], @obj.grep_v(1..2)) + a = [] + @obj.grep_v(2) {|x| a << x } + assert_equal([1, 3, 1], a) + + a = [] + lambda = ->(x, i) {a << [x, i]} + @obj.each_with_index.grep_v(proc{|x,i|x!=2}, &lambda) + assert_equal([[2, 1], [2, 4]], a) end def test_grep @@ -55,7 +54,13 @@ class TestEnumerable < Test::Unit::TestCase bug5801 = '[ruby-dev:45041]' @empty.grep(//) - assert_nothing_raised(bug5801) {100.times {@empty.block.call}} + block = @empty.block + assert_nothing_raised(bug5801) {100.times {block.call}} + + a = [] + lambda = ->(x, i) {a << [x, i]} + @obj.each_with_index.grep(proc{|x,i|x==2}, &lambda) + assert_equal([[2, 1], [2, 4]], a) end def test_count @@ -81,6 +86,8 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(2, @obj.find {|x| x % 2 == 0 }) assert_equal(nil, @obj.find {|x| false }) assert_equal(:foo, @obj.find(proc { :foo }) {|x| false }) + cond = ->(x, i) { x % 2 == 0 } + assert_equal([2, 1], @obj.each_with_index.find(&cond)) end def test_find_index @@ -93,20 +100,54 @@ class TestEnumerable < Test::Unit::TestCase def test_find_all assert_equal([1, 3, 1], @obj.find_all {|x| x % 2 == 1 }) + cond = ->(x, i) { x % 2 == 1 } + assert_equal([[1, 0], [3, 2], [1, 3]], @obj.each_with_index.find_all(&cond)) end def test_reject assert_equal([2, 3, 2], @obj.reject {|x| x < 2 }) + cond = ->(x, i) {x < 2} + assert_equal([[2, 1], [3, 2], [2, 4]], @obj.each_with_index.reject(&cond)) end def test_to_a assert_equal([1, 2, 3, 1, 2], @obj.to_a) end + def test_to_a_size_symbol + sym = Object.new + class << sym + include Enumerable + def each + self + end + + def size + :size + end + end + assert_equal([], sym.to_a) + end + + def test_to_a_size_infinity + inf = Object.new + class << inf + include Enumerable + def each + self + end + + def size + Float::INFINITY + end + end + assert_equal([], inf.to_a) + end + def test_to_h obj = Object.new def obj.each(*args) - yield *args + yield(*args) yield [:key, :value] yield :other_key, :other_value kvp = Object.new @@ -140,30 +181,126 @@ class TestEnumerable < Test::Unit::TestCase 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(nil, @empty.inject() {9}) + end + + FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] + FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX'] + + def test_inject_array_mul + assert_equal(nil, [].inject(:*)) + assert_equal(5, [5].inject(:*)) + assert_equal(35, [5, 7].inject(:*)) + assert_equal(3, [].inject(3, :*)) + assert_equal(15, [5].inject(3, :*)) + assert_equal(105, [5, 7].inject(3, :*)) + end + + def test_inject_array_plus + assert_equal(3, [3].inject(:+)) + assert_equal(8, [3, 5].inject(:+)) + 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(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((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_array_op_redefined + assert_separately([], "#{<<~"end;"}\n""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, [1,2,3].inject(op), bug) + ensure + Integer.class_eval do + undef_method op + alias_method op, :orig + end + end + end; + end + + def test_inject_array_op_private + assert_separately([], "#{<<~"end;"}\n""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 + [1,2,3].inject(op) + ensure + Integer.class_eval do + public op + end + end + end + end; end def test_partition assert_equal([[1, 3, 1], [2, 2]], @obj.partition {|x| x % 2 == 1 }) + cond = ->(x, i) { x % 2 == 1 } + assert_equal([[[1, 0], [3, 2], [1, 3]], [[2, 1], [2, 4]]], @obj.each_with_index.partition(&cond)) end def test_group_by h = { 1 => [1, 1], 2 => [2, 2], 3 => [3] } assert_equal(h, @obj.group_by {|x| x }) + + h = {1=>[[1, 0], [1, 3]], 2=>[[2, 1], [2, 4]], 3=>[[3, 2]]} + cond = ->(x, i) { x } + assert_equal(h, @obj.each_with_index.group_by(&cond)) end def test_first assert_equal(1, @obj.first) assert_equal([1, 2, 3], @obj.first(3)) assert_nil(@empty.first) + assert_equal([], @empty.first(10)) + + bug5801 = '[ruby-dev:45041]' + assert_in_out_err([], <<-'end;', [], /unexpected break/, bug5801) + empty = Object.new + class << empty + attr_reader :block + include Enumerable + def each(&block) + @block = block + self + end + end + empty.first + empty.block.call + end; end def test_sort assert_equal([1, 1, 2, 2, 3], @obj.sort) + assert_equal([3, 2, 2, 1, 1], @obj.sort {|x, y| y <=> x }) end def test_sort_by assert_equal([3, 2, 2, 1, 1], @obj.sort_by {|x| -x }) assert_equal((1..300).to_a.reverse, (1..300).sort_by {|x| -x }) + + cond = ->(x, i) { [-x, i] } + assert_equal([[3, 2], [2, 1], [2, 4], [1, 0], [1, 3]], @obj.each_with_index.sort_by(&cond)) end def test_all @@ -171,6 +308,10 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, @obj.all? {|x| x < 3 }) assert_equal(true, @obj.all?) assert_equal(false, [true, true, false].all?) + assert_equal(true, [].all?) + assert_equal(true, @empty.all?) + assert_equal(true, @obj.all?(Fixnum)) + assert_equal(false, @obj.all?(1..2)) end def test_any @@ -178,46 +319,81 @@ class TestEnumerable < Test::Unit::TestCase assert_equal(false, @obj.any? {|x| x > 3 }) assert_equal(true, @obj.any?) 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_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_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?) + assert(![nil,false,true].none?) + assert(@empty.none?) end def test_min assert_equal(1, @obj.min) assert_equal(3, @obj.min {|a,b| b <=> a }) - ary = %w(albatross dog horse) - assert_equal("albatross", ary.min) - assert_equal("dog", ary.min {|a,b| a.length <=> b.length }) - assert_equal(1, [3,2,1].min) + cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } + assert_equal([3, 2], @obj.each_with_index.min(&cond)) + enum = %w(albatross dog horse).to_enum + assert_equal("albatross", enum.min) + assert_equal("dog", enum.min {|a,b| a.length <=> b.length }) + assert_equal(1, [3,2,1].to_enum.min) + assert_equal(%w[albatross dog], enum.min(2)) + assert_equal(%w[dog horse], + enum.min(2) {|a,b| a.length <=> b.length }) + assert_equal([13, 14], [20, 32, 32, 21, 30, 25, 29, 13, 14].to_enum.min(2)) + assert_equal([2, 4, 6, 7], [2, 4, 8, 6, 7].to_enum.min(4)) end def test_max assert_equal(3, @obj.max) assert_equal(1, @obj.max {|a,b| b <=> a }) - ary = %w(albatross dog horse) - assert_equal("horse", ary.max) - assert_equal("albatross", ary.max {|a,b| a.length <=> b.length }) - assert_equal(1, [3,2,1].max{|a,b| b <=> a }) + cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } + assert_equal([1, 3], @obj.each_with_index.max(&cond)) + enum = %w(albatross dog horse).to_enum + assert_equal("horse", enum.max) + assert_equal("albatross", enum.max {|a,b| a.length <=> b.length }) + assert_equal(1, [3,2,1].to_enum.max{|a,b| b <=> a }) + assert_equal(%w[horse dog], enum.max(2)) + assert_equal(%w[albatross horse], + enum.max(2) {|a,b| a.length <=> b.length }) + assert_equal([3, 2], [0, 0, 0, 0, 0, 0, 1, 3, 2].to_enum.max(2)) end def test_minmax @@ -229,24 +405,35 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([1, 3], [2,3,1].minmax) assert_equal([3, 1], [2,3,1].minmax {|a,b| b <=> a }) assert_equal([1, 3], [2,2,3,3,1,1].minmax) + assert_equal([nil, nil], [].minmax) end def test_min_by assert_equal(3, @obj.min_by {|x| -x }) + cond = ->(x, i) { -x } + assert_equal([3, 2], @obj.each_with_index.min_by(&cond)) a = %w(albatross dog horse) assert_equal("dog", a.min_by {|x| x.length }) assert_equal(3, [2,3,1].min_by {|x| -x }) + assert_equal(%w[dog horse], a.min_by(2) {|x| x.length }) + assert_equal([13, 14], [20, 32, 32, 21, 30, 25, 29, 13, 14].min_by(2) {|x| x}) end def test_max_by assert_equal(1, @obj.max_by {|x| -x }) + cond = ->(x, i) { -x } + assert_equal([1, 0], @obj.each_with_index.max_by(&cond)) a = %w(albatross dog horse) assert_equal("albatross", a.max_by {|x| x.length }) assert_equal(1, [2,3,1].max_by {|x| -x }) + assert_equal(%w[albatross horse], a.max_by(2) {|x| x.length }) + assert_equal([3, 2], [0, 0, 0, 0, 0, 0, 1, 3, 2].max_by(2) {|x| x}) end def test_minmax_by assert_equal([3, 1], @obj.minmax_by {|x| -x }) + cond = ->(x, i) { -x } + assert_equal([[3, 2], [1, 0]], @obj.each_with_index.minmax_by(&cond)) a = %w(albatross dog horse) assert_equal(["dog", "albatross"], a.minmax_by {|x| x.length }) assert_equal([3, 1], [2,3,1].minmax_by {|x| -x }) @@ -294,15 +481,65 @@ class TestEnumerable < Test::Unit::TestCase def test_each_entry assert_equal([1, 2, 3], [1, 2, 3].each_entry.to_a) assert_equal([1, [1, 2]], Foo.new.each_entry.to_a) + a = [] + cond = ->(x, i) { a << x } + @obj.each_with_index.each_entry(&cond) + assert_equal([1, 2, 3, 1, 2], a) + end + + def test_each_slice + ary = [] + (1..10).each_slice(3) {|a| ary << a} + assert_equal([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]], ary) + + bug9749 = '[ruby-core:62060] [Bug #9749]' + ary.clear + (1..10).each_slice(3, &lambda {|a, *| ary << a}) + assert_equal([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]], ary, bug9749) + + ary.clear + (1..10).each_slice(10) {|a| ary << a} + assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary) + + ary.clear + (1..10).each_slice(11) {|a| ary << a} + assert_equal([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]], ary) + end + + def test_each_cons + ary = [] + (1..5).each_cons(3) {|a| ary << a} + assert_equal([[1, 2, 3], [2, 3, 4], [3, 4, 5]], ary) + + bug9749 = '[ruby-core:62060] [Bug #9749]' + ary.clear + (1..5).each_cons(3, &lambda {|a, *| ary << a}) + assert_equal([[1, 2, 3], [2, 3, 4], [3, 4, 5]], ary, bug9749) + + ary.clear + (1..5).each_cons(5) {|a| ary << a} + assert_equal([[1, 2, 3, 4, 5]], ary) + + ary.clear + (1..5).each_cons(6) {|a| ary << a} + assert_empty(ary) end def test_zip assert_equal([[1,1],[2,2],[3,3],[1,1],[2,2]], @obj.zip(@obj)) + assert_equal([["a",1],["b",2],["c",3]], ["a", "b", "c"].zip(@obj)) + a = [] - @obj.zip([:a, :b, :c]) {|x,y| a << [x, y] } + result = @obj.zip([:a, :b, :c]) {|x,y| a << [x, y] } + assert_nil result assert_equal([[1,:a],[2,:b],[3,:c],[1,nil],[2,nil]], a) a = [] + cond = ->((x, i), y) { a << [x, y, i] } + @obj.each_with_index.zip([:a, :b, :c], &cond) + assert_equal([[1,:a,0],[2,:b,1],[3,:c,2],[1,nil,3],[2,nil,4]], a) + + a = [] @obj.zip({a: "A", b: "B", c: "C"}) {|x,y| a << [x, y] } assert_equal([[1,[:a,"A"]],[2,[:b,"B"]],[3,[:c,"C"]],[1,nil],[2,nil]], a) @@ -313,6 +550,8 @@ class TestEnumerable < Test::Unit::TestCase assert_equal([[1, 3], [2, 4], [3, nil], [1, nil], [2, nil]], @obj.zip(ary)) def ary.to_ary; [5, 6]; end assert_equal([[1, 5], [2, 6], [3, nil], [1, nil], [2, nil]], @obj.zip(ary)) + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {(1..1).zip(obj)} end def test_take @@ -321,10 +560,13 @@ class TestEnumerable < Test::Unit::TestCase def test_take_while assert_equal([1,2], @obj.take_while {|x| x <= 2}) + cond = ->(x, i) {x <= 2} + assert_equal([[1, 0], [2, 1]], @obj.each_with_index.take_while(&cond)) bug5801 = '[ruby-dev:45040]' @empty.take_while {true} - assert_nothing_raised(bug5801) {100.times {@empty.block.call}} + block = @empty.block + assert_nothing_raised(bug5801) {100.times {block.call}} end def test_drop @@ -333,10 +575,19 @@ class TestEnumerable < Test::Unit::TestCase def test_drop_while assert_equal([3,1,2], @obj.drop_while {|x| x <= 2}) + cond = ->(x, i) {x <= 2} + assert_equal([[3, 2], [1, 3], [2, 4]], @obj.each_with_index.drop_while(&cond)) end def test_cycle assert_equal([1,2,3,1,2,1,2,3,1,2], @obj.cycle.take(10)) + a = [] + @obj.cycle(2) {|x| a << x} + assert_equal([1,2,3,1,2,1,2,3,1,2], a) + a = [] + cond = ->(x, i) {a << x} + @obj.each_with_index.cycle(2, &cond) + assert_equal([1,2,3,1,2,1,2,3,1,2], a) end def test_callcc @@ -371,6 +622,22 @@ class TestEnumerable < Test::Unit::TestCase [o, o, o].sort_by {|x| x } c.call end + + assert_raise_with_message(RuntimeError, /reentered/) do + i = 0 + c = nil + o = Object.new + class << o; self; end.class_eval do + define_method(:<=>) do |x| + callcc {|c2| c ||= c2 } + i += 1 + 0 + end + end + [o, o].min(1) + assert_operator(i, :<=, 5, "infinite loop") + c.call + end end def test_reverse_each @@ -384,22 +651,6 @@ class TestEnumerable < Test::Unit::TestCase e = @obj.chunk {|elt| elt & 2 == 0 ? false : true } assert_equal([[false, [1]], [true, [2, 3]], [false, [1]], [true, [2]]], e.to_a) - e = @obj.chunk(acc: 0) {|elt, h| h[:acc] += elt; h[:acc].even? } - assert_equal([[false, [1,2]], [true, [3]], [false, [1,2]]], e.to_a) - assert_equal([[false, [1,2]], [true, [3]], [false, [1,2]]], e.to_a) # this tests h is duplicated. - - hs = [{}] - e = [:foo].chunk(hs[0]) {|elt, h| - hs << h - true - } - assert_equal([[true, [:foo]]], e.to_a) - assert_equal([[true, [:foo]]], e.to_a) - assert_equal([{}, {}, {}], hs) - assert_not_same(hs[0], hs[1]) - assert_not_same(hs[0], hs[2]) - assert_not_same(hs[1], hs[2]) - e = @obj.chunk {|elt| elt < 3 ? :_alone : true } assert_equal([[:_alone, [1]], [:_alone, [2]], @@ -417,6 +668,12 @@ class TestEnumerable < Test::Unit::TestCase e = @obj.chunk {|elt| :_foo } assert_raise(RuntimeError) { e.to_a } + + e = @obj.chunk.with_index {|elt, i| elt - i } + assert_equal([[1, [1, 2, 3]], + [-2, [1, 2]]], e.to_a) + + assert_equal(4, (0..3).chunk.size) end def test_slice_before @@ -429,26 +686,347 @@ class TestEnumerable < Test::Unit::TestCase e = @obj.slice_before {|elt| elt.odd? } assert_equal([[1,2], [3], [1,2]], e.to_a) - e = @obj.slice_before(acc: 0) {|elt, h| h[:acc] += elt; h[:acc].even? } - assert_equal([[1,2], [3,1,2]], e.to_a) - assert_equal([[1,2], [3,1,2]], e.to_a) # this tests h is duplicated. + ss = %w[abc defg h ijk l mno pqr st u vw xy z] + assert_equal([%w[abc defg h], %w[ijk l], %w[mno], %w[pqr st u vw xy z]], + ss.slice_before(/\A...\z/).to_a) + assert_warning("") {ss.slice_before(/\A...\z/).to_a} + end - hs = [{}] - e = [:foo].slice_before(hs[0]) {|elt, h| - hs << h + def test_slice_after0 + assert_raise(ArgumentError) { [].slice_after } + end + + def test_slice_after1 + e = [].slice_after {|a| flunk "should not be called" } + assert_equal([], e.to_a) + + e = [1,2].slice_after(1) + assert_equal([[1], [2]], e.to_a) + + e = [1,2].slice_after(3) + assert_equal([[1, 2]], e.to_a) + + [true, false].each {|b| + block_results = [true, b] + e = [1,2].slice_after {|a| block_results.shift } + assert_equal([[1], [2]], e.to_a) + assert_equal([], block_results) + + block_results = [false, b] + e = [1,2].slice_after {|a| block_results.shift } + assert_equal([[1, 2]], e.to_a) + assert_equal([], block_results) + } + end + + def test_slice_after_both_pattern_and_block + assert_raise(ArgumentError) { [].slice_after(1) {|a| true } } + end + + def test_slice_after_continuation_lines + lines = ["foo\n", "bar\\\n", "baz\n", "\n", "qux\n"] + e = lines.slice_after(/[^\\]\n\z/) + assert_equal([["foo\n"], ["bar\\\n", "baz\n"], ["\n", "qux\n"]], e.to_a) + end + + def test_slice_before_empty_line + lines = ["foo", "", "bar"] + e = lines.slice_after(/\A\s*\z/) + assert_equal([["foo", ""], ["bar"]], e.to_a) + end + + def test_slice_when_0 + e = [].slice_when {|a, b| flunk "should not be called" } + assert_equal([], e.to_a) + end + + def test_slice_when_1 + e = [1].slice_when {|a, b| flunk "should not be called" } + assert_equal([[1]], e.to_a) + end + + def test_slice_when_2 + e = [1,2].slice_when {|a,b| + assert_equal(1, a) + assert_equal(2, b) true } - assert_equal([[:foo]], e.to_a) - assert_equal([[:foo]], e.to_a) - assert_equal([{}, {}, {}], hs) - assert_not_same(hs[0], hs[1]) - assert_not_same(hs[0], hs[2]) - assert_not_same(hs[1], hs[2]) + assert_equal([[1], [2]], e.to_a) - ss = %w[abc defg h ijk l mno pqr st u vw xy z] - assert_equal([%w[abc defg h], %w[ijk l], %w[mno], %w[pqr st u vw xy z]], - ss.slice_before(/\A...\z/).to_a) - assert_not_warn{ss.slice_before(/\A...\z/).to_a} + e = [1,2].slice_when {|a,b| + assert_equal(1, a) + assert_equal(2, b) + false + } + assert_equal([[1, 2]], e.to_a) + end + + def test_slice_when_3 + block_invocations = [ + lambda {|a, b| + assert_equal(1, a) + assert_equal(2, b) + true + }, + lambda {|a, b| + assert_equal(2, a) + assert_equal(3, b) + false + } + ] + e = [1,2,3].slice_when {|a,b| + block_invocations.shift.call(a, b) + } + assert_equal([[1], [2, 3]], e.to_a) + assert_equal([], block_invocations) + end + + def test_slice_when_noblock + assert_raise(ArgumentError) { [].slice_when } end + def test_slice_when_contiguously_increasing_integers + e = [1,4,9,10,11,12,15,16,19,20,21].slice_when {|i, j| i+1 != j } + assert_equal([[1], [4], [9,10,11,12], [15,16], [19,20,21]], e.to_a) + end + + def test_chunk_while_contiguously_increasing_integers + e = [1,4,9,10,11,12,15,16,19,20,21].chunk_while {|i, j| i+1 == j } + assert_equal([[1], [4], [9,10,11,12], [15,16], [19,20,21]], e.to_a) + end + + def test_detect + @obj = ('a'..'z') + assert_equal('c', @obj.detect {|x| x == 'c' }) + + proc = Proc.new {|x| x == 'c' } + assert_equal('c', @obj.detect(&proc)) + + lambda = ->(x) { x == 'c' } + assert_equal('c', @obj.detect(&lambda)) + + assert_equal(['c',2], @obj.each_with_index.detect {|x, i| x == 'c' }) + + proc2 = Proc.new {|x, i| x == 'c' } + assert_equal(['c',2], @obj.each_with_index.detect(&proc2)) + + bug9605 = '[ruby-core:61340]' + lambda2 = ->(x, i) { x == 'c' } + assert_equal(['c',2], @obj.each_with_index.detect(&lambda2), bug9605) + end + + def test_select + @obj = ('a'..'z') + assert_equal(['c'], @obj.select {|x| x == 'c' }) + + proc = Proc.new {|x| x == 'c' } + assert_equal(['c'], @obj.select(&proc)) + + lambda = ->(x) { x == 'c' } + assert_equal(['c'], @obj.select(&lambda)) + + assert_equal([['c',2]], @obj.each_with_index.select {|x, i| x == 'c' }) + + proc2 = Proc.new {|x, i| x == 'c' } + assert_equal([['c',2]], @obj.each_with_index.select(&proc2)) + + bug9605 = '[ruby-core:61340]' + lambda2 = ->(x, i) { x == 'c' } + assert_equal([['c',2]], @obj.each_with_index.select(&lambda2), bug9605) + end + + def test_map + @obj = ('a'..'e') + assert_equal(['A', 'B', 'C', 'D', 'E'], @obj.map {|x| x.upcase }) + + proc = Proc.new {|x| x.upcase } + assert_equal(['A', 'B', 'C', 'D', 'E'], @obj.map(&proc)) + + lambda = ->(x) { x.upcase } + assert_equal(['A', 'B', 'C', 'D', 'E'], @obj.map(&lambda)) + + assert_equal([['A',0], ['B',1], ['C',2], ['D',3], ['E',4]], + @obj.each_with_index.map {|x, i| [x.upcase, i] }) + + proc2 = Proc.new {|x, i| [x.upcase, i] } + assert_equal([['A',0], ['B',1], ['C',2], ['D',3], ['E',4]], + @obj.each_with_index.map(&proc2)) + + lambda2 = ->(x, i) { [x.upcase, i] } + assert_equal([['A',0], ['B',1], ['C',2], ['D',3], ['E',4]], + @obj.each_with_index.map(&lambda2)) + + hash = { a: 'hoge', b: 'fuga' } + lambda = -> (k, v) { "#{k}:#{v}" } + assert_equal ["a:hoge", "b:fuga"], hash.map(&lambda) + end + + def test_flat_map + @obj = [[1,2], [3,4]] + assert_equal([2,4,6,8], @obj.flat_map {|i| i.map{|j| j*2} }) + + proc = Proc.new {|i| i.map{|j| j*2} } + assert_equal([2,4,6,8], @obj.flat_map(&proc)) + + lambda = ->(i) { i.map{|j| j*2} } + assert_equal([2,4,6,8], @obj.flat_map(&lambda)) + + assert_equal([[1,2],0,[3,4],1], + @obj.each_with_index.flat_map {|x, i| [x,i] }) + + proc2 = Proc.new {|x, i| [x,i] } + assert_equal([[1,2],0,[3,4],1], + @obj.each_with_index.flat_map(&proc2)) + + lambda2 = ->(x, i) { [x,i] } + assert_equal([[1,2],0,[3,4],1], + @obj.each_with_index.flat_map(&lambda2)) + end + + def assert_typed_equal(e, v, cls, msg=nil) + assert_kind_of(cls, v, msg) + assert_equal(e, v, msg) + end + + def assert_int_equal(e, v, msg=nil) + assert_typed_equal(e, v, Integer, msg) + end + + def assert_rational_equal(e, v, msg=nil) + assert_typed_equal(e, v, Rational, msg) + end + + def assert_float_equal(e, v, msg=nil) + assert_typed_equal(e, v, Float, msg) + end + + def assert_complex_equal(e, v, msg=nil) + assert_typed_equal(e, v, Complex, msg) + end + + def test_sum + class << (enum = Object.new) + include Enumerable + def each + yield 3 + yield 5 + yield 7 + end + end + assert_int_equal(15, enum.sum) + + assert_int_equal(0, [].each.sum) + assert_int_equal(3, [3].each.sum) + assert_int_equal(8, [3, 5].each.sum) + assert_int_equal(15, [3, 5, 7].each.sum) + assert_rational_equal(8r, [3, 5r].each.sum) + assert_float_equal(15.0, [3, 5, 7.0].each.sum) + assert_float_equal(15.0, [3, 5r, 7.0].each.sum) + assert_complex_equal(8r + 1i, [3, 5r, 1i].each.sum) + assert_complex_equal(15.0 + 1i, [3, 5r, 7.0, 1i].each.sum) + + assert_int_equal(2*FIXNUM_MAX, Array.new(2, FIXNUM_MAX).each.sum) + assert_int_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).each.sum) + assert_int_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).each.sum) + assert_int_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).each.sum) + assert_int_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).each.sum) + assert_int_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).each.sum) + + assert_float_equal(0.0, [].each.sum(0.0)) + assert_float_equal(3.0, [3].each.sum(0.0)) + assert_float_equal(3.5, [3].each.sum(0.5)) + assert_float_equal(8.5, [3.5, 5].each.sum) + assert_float_equal(10.5, [2, 8.5].each.sum) + assert_float_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].each.sum) + assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].each.sum) + + assert_rational_equal(3/2r, [1/2r, 1].each.sum) + assert_rational_equal(5/6r, [1/2r, 1/3r].each.sum) + + assert_equal(2.0+3.0i, [2.0, 3.0i].each.sum) + + assert_int_equal(13, [1, 2].each.sum(10)) + assert_int_equal(16, [1, 2].each.sum(10) {|v| v * 2 }) + + yielded = [] + three = SimpleDelegator.new(3) + ary = [1, 2.0, three] + assert_float_equal(12.0, ary.each.sum {|x| yielded << x; x * 2 }) + assert_equal(ary, yielded) + + assert_raise(TypeError) { [Object.new].each.sum } + + large_number = 100000000 + small_number = 1e-9 + until (large_number + small_number) == large_number + small_number /= 10 + end + assert_float_equal(large_number+(small_number*10), [large_number, *[small_number]*10].each.sum) + assert_float_equal(large_number+(small_number*10), [large_number/1r, *[small_number]*10].each.sum) + 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 + + def test_hash_sum + histogram = { 1 => 6, 2 => 4, 3 => 3, 4 => 7, 5 => 5, 6 => 4 } + assert_equal(100, histogram.sum {|v, n| v * n }) + end + + def test_range_sum + assert_int_equal(55, (1..10).sum) + assert_float_equal(55.0, (1..10).sum(0.0)) + assert_int_equal(90, (5..10).sum {|v| v * 2 }) + assert_float_equal(90.0, (5..10).sum(0.0) {|v| v * 2 }) + assert_int_equal(0, (2..0).sum) + assert_int_equal(5, (2..0).sum(5)) + assert_int_equal(2, (2..2).sum) + assert_int_equal(42, (2...2).sum(42)) + + not_a_range = Class.new do + include Enumerable # Defines the `#sum` method + def each + yield 2 + yield 4 + yield 6 + end + + def begin; end + def end; end + end + assert_equal(12, not_a_range.new.sum) + end + + def test_uniq + src = [1, 1, 1, 1, 2, 2, 3, 4, 5, 6] + assert_equal([1, 2, 3, 4, 5, 6], src.uniq.to_a) + olympics = { + 1896 => 'Athens', + 1900 => 'Paris', + 1904 => 'Chicago', + 1906 => 'Athens', + 1908 => 'Rome', + } + assert_equal([[1896, "Athens"], [1900, "Paris"], [1904, "Chicago"], [1908, "Rome"]], + olympics.uniq{|k,v| v}) + 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 end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 981d8bd434..66a45cc14e 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestEnumerator < Test::Unit::TestCase def setup @@ -25,7 +25,7 @@ class TestEnumerator < Test::Unit::TestCase def test_iterators assert_equal [0, 1, 2], enum_test(3.times) assert_equal [:x, :y, :z], enum_test([:x, :y, :z].each) - assert_equal [[:x, 1], [:y, 2]], enum_test({:x=>1, :y=>2}) + assert_equal [[:x, 1], [:y, 2]], enum_test({:x=>1, :y=>2}.each) end ## Enumerator as Iterator @@ -47,6 +47,14 @@ class TestEnumerator < Test::Unit::TestCase } end + def test_loop_return_value + assert_equal nil, loop { break } + assert_equal 42, loop { break 42 } + + e = Enumerator.new { |y| y << 1; y << 2; :stopped } + assert_equal :stopped, loop { e.next while true } + end + def test_nested_iteration def (o = Object.new).each yield :ok1 @@ -71,7 +79,7 @@ class TestEnumerator < Test::Unit::TestCase enum = @obj.to_enum assert_raise(NoMethodError) { enum.each {} } enum.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { capture_io do # warning: Enumerator.new without a block is deprecated; use Object#to_enum enum.__send__(:initialize, @obj, :foo) @@ -95,6 +103,7 @@ class TestEnumerator < Test::Unit::TestCase 1.times do foo = [1,2,3].to_enum GC.start + foo end GC.start end @@ -431,9 +440,21 @@ 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 } } + + g = Enumerator::Generator.new(proc {|y| y << 4 << 5; :foo }) + a = [] + assert_equal(:foo, g.each {|x| a << x }) + assert_equal([4, 5], a) + + assert_raise(LocalJumpError) {Enumerator::Generator.new} + assert_raise(TypeError) {Enumerator::Generator.new(1)} + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + Enumerator::Generator.new(obj) + } end def test_generator_args @@ -555,13 +576,22 @@ class TestEnumerator < Test::Unit::TestCase assert_equal Float::INFINITY, [:foo].cycle.size assert_equal 10, [:foo, :bar].cycle(5).size assert_equal 0, [:foo, :bar].cycle(-10).size + assert_equal Float::INFINITY, {foo: 1}.cycle.size + assert_equal 10, {foo: 1, bar: 2}.cycle(5).size + assert_equal 0, {foo: 1, bar: 2}.cycle(-10).size assert_equal 0, [].cycle.size assert_equal 0, [].cycle(5).size + assert_equal 0, {}.cycle.size + assert_equal 0, {}.cycle(5).size assert_equal nil, @obj.cycle.size assert_equal nil, @obj.cycle(5).size assert_equal Float::INFINITY, @sized.cycle.size assert_equal 126, @sized.cycle(3).size + assert_equal Float::INFINITY, [].to_enum { 42 }.cycle.size + assert_equal 0, [].to_enum { 0 }.cycle.size + + assert_raise(TypeError) {[].to_enum { 0 }.cycle("").size} end def test_size_for_loops @@ -625,5 +655,11 @@ class TestEnumerator < Test::Unit::TestCase e.next assert_raise(StopIteration) { e.peek } end + + def test_uniq + u = [0, 1, 0, 1].to_enum.lazy.uniq + assert_equal([0, 1], u.force) + assert_equal([0, 1], u.force) + end end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index ddbdcf24bc..ffed94efa6 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -1,9 +1,24 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestEnv < Test::Unit::TestCase IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM PATH_ENV = "PATH" + INVALID_ENVVARS = [ + "foo\0bar", + "\xa1\xa1".force_encoding(Encoding::UTF_16LE), + "foo".force_encoding(Encoding::ISO_2022_JP), + ] + + def assert_invalid_env(msg = nil) + all_assertions(msg) do |a| + INVALID_ENVVARS.each do |v| + a.for(v) do + assert_raise(ArgumentError) {yield v} + end + end + end + end def setup @verbose = $VERBOSE @@ -31,6 +46,7 @@ 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 @@ -38,7 +54,7 @@ class TestEnv < Test::Unit::TestCase end assert_raise(TypeError) { - tmp = ENV[1] + ENV[1] } assert_raise(TypeError) { ENV[1] = 'foo' @@ -88,15 +104,16 @@ class TestEnv < Test::Unit::TestCase end def test_delete - assert_raise(ArgumentError) { ENV.delete("foo\0bar") } + assert_invalid_env {|v| ENV.delete(v)} assert_nil(ENV.delete("TEST")) assert_nothing_raised { ENV.delete(PATH_ENV) } end def test_getenv - assert_raise(ArgumentError) { ENV["foo\0bar"] } + assert_invalid_env {|v| ENV[v]} ENV[PATH_ENV] = "" assert_equal("", ENV[PATH_ENV]) + assert_predicate(ENV[PATH_ENV], :tainted?) assert_nil(ENV[""]) end @@ -105,23 +122,28 @@ class TestEnv < Test::Unit::TestCase assert_equal("foo", ENV.fetch("test")) ENV.delete("test") feature8649 = '[ruby-core:56062] [Feature #8649]' - 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" }) - assert_raise(ArgumentError) { ENV.fetch("foo\0bar") } + 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 assert_nothing_raised { ENV["test"] = nil } assert_equal(nil, ENV["test"]) - assert_raise(ArgumentError) { ENV["foo\0bar"] = "test" } - assert_raise(ArgumentError) { ENV["test"] = "foo\0bar" } + assert_invalid_env {|v| ENV[v] = "test"} + assert_invalid_env {|v| ENV["test"] = v} begin # setenv(3) allowed the name includes '=', @@ -173,6 +195,10 @@ class TestEnv < Test::Unit::TestCase ENV.each_pair {|k, v| h2[k] = v } assert_equal(h1, h2) + assert_nil(ENV.reject! {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }) + end + + def test_delete_if h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" @@ -180,6 +206,8 @@ class TestEnv < Test::Unit::TestCase h2 = {} ENV.each_pair {|k, v| h2[k] = v } assert_equal(h1, h2) + + assert_equal(ENV, ENV.delete_if {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }) end def test_select_bang @@ -191,6 +219,10 @@ class TestEnv < Test::Unit::TestCase ENV.each_pair {|k, v| h2[k] = v } assert_equal(h1, h2) + assert_nil(ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }) + end + + def test_keep_if h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" @@ -198,6 +230,8 @@ class TestEnv < Test::Unit::TestCase h2 = {} ENV.each_pair {|k, v| h2[k] = v } assert_equal(h1, h2) + + assert_equal(ENV, ENV.keep_if {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }) end def test_values_at @@ -277,7 +311,7 @@ class TestEnv < Test::Unit::TestCase assert_not_send([ENV, :has_key?, "test"]) ENV["test"] = "foo" assert_send([ENV, :has_key?, "test"]) - assert_raise(ArgumentError) { ENV.has_key?("foo\0bar") } + assert_invalid_env {|v| ENV.has_key?(v)} end def test_assoc @@ -291,7 +325,9 @@ class TestEnv < Test::Unit::TestCase assert_equal("test", k) assert_equal("foo", v) end - assert_raise(ArgumentError) { ENV.assoc("foo\0bar") } + assert_invalid_env {|var| ENV.assoc(var)} + assert_predicate(v, :tainted?) + assert_equal(Encoding.find("locale"), v.encoding) end def test_has_value2 @@ -395,7 +431,7 @@ class TestEnv < Test::Unit::TestCase if /mswin|mingw/ =~ RUBY_PLATFORM def test_win32_blocksize keys = [] - len = 32767 - ENV.to_a.flatten.inject(0) {|r,e| r + e.bytesize + 1} + len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1} val = "bar" * 1000 key = nil while (len -= val.size + (key="foo#{len}").size + 2) > 0 @@ -410,6 +446,38 @@ class TestEnv < Test::Unit::TestCase end end + def test_frozen + ENV[PATH_ENV] = "/" + ENV.each do |k, v| + assert_predicate(k, :frozen?) + assert_predicate(v, :frozen?) + end + ENV.each_key do |k| + assert_predicate(k, :frozen?) + end + ENV.each_value do |v| + assert_predicate(v, :frozen?) + end + ENV.each_key do |k| + assert_predicate(ENV[k], :frozen?, "[#{k.dump}]") + assert_predicate(ENV.fetch(k), :frozen?, "fetch(#{k.dump})") + end + end + + def test_shared_substring + 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] + assert_equal("T#{n}", e0, bug12475) + assert_nil(e1, bug12475) + end + if RUBY_PLATFORM =~ /bccwin|mswin|mingw/ def test_memory_leak_aset bug9977 = '[ruby-dev:48323] [Bug #9977]' @@ -450,86 +518,16 @@ class TestEnv < Test::Unit::TestCase 500.times(&doit) end; end - end - - def test_taint_aref - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV["FOO".taint] - end.call - end - end - - def test_taint_fetch - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.fetch("FOO".taint) - end.call - end - end - - def test_taint_assoc - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.assoc("FOO".taint) - end.call - end - end - - def test_taint_rassoc - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.rassoc("FOO".taint) - end.call - end - end - - def test_taint_key - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.key("FOO".taint) - end.call - end - end - - def test_taint_key_p - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.key?("FOO".taint) - end.call - end - end - def test_taint_value_p - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV.value?("FOO".taint) - end.call - end - end - - def test_taint_aset_value - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV["FOO"] = "BAR".taint - end.call - end - end - - def test_taint_aset_key - assert_raise(SecurityError) do - proc do - $SAFE = 2 - ENV["FOO".taint] = "BAR" - end.call + 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 end end end diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb index 28d797e2c8..0bc0390d64 100644 --- a/test/ruby/test_eval.rb +++ b/test/ruby/test_eval.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestEval < Test::Unit::TestCase @@ -127,10 +127,14 @@ class TestEval < Test::Unit::TestCase } end + def test_module_eval_block_symbol + assert_equal "Math", Math.module_eval(&:to_s) + end + def forall_TYPE objects = [Object.new, [], nil, true, false] # TODO: check objects.each do |obj| - obj.instance_variable_set :@ivar, 12 + obj.instance_variable_set :@ivar, 12 unless obj.frozen? yield obj end end @@ -145,7 +149,7 @@ class TestEval < Test::Unit::TestCase assert_equal :sym, o.instance_eval(":sym") assert_equal 11, o.instance_eval("11") - assert_equal 12, o.instance_eval("@ivar") + assert_equal 12, o.instance_eval("@ivar") unless o.frozen? assert_equal 13, o.instance_eval("@@cvar") assert_equal 14, o.instance_eval("$gvar__eval") assert_equal 15, o.instance_eval("Const") @@ -155,7 +159,7 @@ class TestEval < Test::Unit::TestCase assert_equal "19", o.instance_eval(%q("1#{9}")) 1.times { - assert_equal 12, o.instance_eval("@ivar") + assert_equal 12, o.instance_eval("@ivar") unless o.frozen? assert_equal 13, o.instance_eval("@@cvar") assert_equal 14, o.instance_eval("$gvar__eval") assert_equal 15, o.instance_eval("Const") @@ -173,7 +177,7 @@ class TestEval < Test::Unit::TestCase assert_equal :sym, o.instance_eval { :sym } assert_equal 11, o.instance_eval { 11 } - assert_equal 12, o.instance_eval { @ivar } + assert_equal 12, o.instance_eval { @ivar } unless o.frozen? assert_equal 13, o.instance_eval { @@cvar } assert_equal 14, o.instance_eval { $gvar__eval } assert_equal 15, o.instance_eval { Const } @@ -183,7 +187,7 @@ class TestEval < Test::Unit::TestCase assert_equal "19", o.instance_eval { "1#{9}" } 1.times { - assert_equal 12, o.instance_eval { @ivar } + assert_equal 12, o.instance_eval { @ivar } unless o.frozen? assert_equal 13, o.instance_eval { @@cvar } assert_equal 14, o.instance_eval { $gvar__eval } assert_equal 15, o.instance_eval { Const } @@ -191,6 +195,21 @@ class TestEval < Test::Unit::TestCase end end + def test_instance_eval_block_self + # instance_eval(&block)'s self must not be sticky (jruby/jruby#2060) + pr = proc { self } + assert_equal self, pr.call + o = Object.new + assert_equal o, o.instance_eval(&pr) + assert_equal self, pr.call + end + + def test_instance_eval_block_symbol + forall_TYPE do |o| + assert_equal o.to_s, o.instance_eval(&:to_s) + end + end + def test_instance_eval_cvar [Object.new, [], 7, :sym, true, false, nil].each do |obj| assert_equal(13, obj.instance_eval("@@cvar")) @@ -238,10 +257,10 @@ class TestEval < Test::Unit::TestCase # From ruby/test/ruby/test_eval.rb # - def test_ev - local1 = "local1" + def make_test_binding + local1 = local1 = "local1" lambda { - local2 = "local2" + local2 = local2 = "local2" return binding }.call end @@ -252,11 +271,9 @@ class TestEval < Test::Unit::TestCase eval 'while false; bad = true; print "foo\n" end' assert(!bad) - assert(eval('TRUE')) + assert(eval('Object')) assert(eval('true')) - assert(!eval('NIL')) assert(!eval('nil')) - assert(!eval('FALSE')) assert(!eval('false')) $foo = 'assert(true)' @@ -268,12 +285,12 @@ class TestEval < Test::Unit::TestCase assert_equal('assert(true)', eval("$foo")) assert_equal(true, eval("true")) - i = 5 + i = i = 5 assert(eval("i == 5")) assert_equal(5, eval("i")) assert(eval("defined? i")) - x = test_ev + x = make_test_binding assert_equal("local1", eval("local1", x)) # normal local var assert_equal("local2", eval("local2", x)) # nested local var bad = true @@ -289,6 +306,7 @@ class TestEval < Test::Unit::TestCase module EvTest EVTEST1 = 25 evtest2 = 125 + evtest2 = evtest2 binding end ) @@ -335,7 +353,7 @@ class TestEval < Test::Unit::TestCase p = binding eval "foo11 = 1", p foo22 = 5 - proc{foo11=22}.call + proc{foo11=22;foo11}.call proc{foo22=55}.call # assert_equal(eval("foo11"), eval("foo11", p)) # assert_equal(1, eval("foo11")) @@ -382,10 +400,10 @@ class TestEval < Test::Unit::TestCase def test_cvar_scope_with_instance_eval # TODO: check - Fixnum.class_eval "@@test_cvar_scope_with_instance_eval = 1" # depends on [ruby-dev:24229] + Integer.class_eval "@@test_cvar_scope_with_instance_eval = 1" # depends on [ruby-dev:24229] @@test_cvar_scope_with_instance_eval = 4 assert_equal(4, 1.instance_eval("@@test_cvar_scope_with_instance_eval"), "[ruby-dev:24223]") - Fixnum.__send__(:remove_class_variable, :@@test_cvar_scope_with_instance_eval) + Integer.__send__(:remove_class_variable, :@@test_cvar_scope_with_instance_eval) end def test_eval_and_define_method @@ -485,6 +503,14 @@ class TestEval < Test::Unit::TestCase assert_same a, b end + def test_fstring_instance_eval + bug = "[ruby-core:78116] [Bug #12930]".freeze + assert_same bug, (bug.instance_eval {self}) + assert_raise(FrozenError) { + bug.instance_eval {@ivar = true} + } + end + def test_gced_binding_block assert_normal_exit %q{ def m @@ -499,4 +525,22 @@ class TestEval < Test::Unit::TestCase b.eval('yield') }, '[Bug #10368]' end + + def orphan_proc + proc {eval("return :ng")} + end + + def orphan_lambda + lambda {eval("return :ok")} + end + + def test_return_in_eval_proc + x = orphan_proc + assert_raise(LocalJumpError) {x.call} + end + + def test_return_in_eval_lambda + x = orphan_lambda + assert_equal(:ok, x.call) + end end diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index c3314568c1..605248aebd 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -1,6 +1,6 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require_relative 'envutil' class TestException < Test::Unit::TestCase def test_exception_rescued @@ -20,8 +20,8 @@ class TestException < Test::Unit::TestCase if bad bad = false retry - assert(false) end + assert(!bad) end assert(true) end @@ -100,6 +100,23 @@ class TestException < Test::Unit::TestCase assert_include(err, bug9568.to_s) end + def test_errinfo_encoding_in_debug + exc = Module.new {break class_eval("class C\u{30a8 30e9 30fc} < RuntimeError; self; end".encode(Encoding::EUC_JP))} + exc.inspect + + err = EnvUtil.verbose_warning do + assert_raise(exc) do + $DEBUG, debug = true, $DEBUG + begin + raise exc + ensure + $DEBUG = debug + end + end + end + assert_include(err, exc.to_s) + end + def test_break_ensure bad = true while true @@ -112,18 +129,47 @@ class TestException < Test::Unit::TestCase assert(!bad) end + def test_catch_no_throw + assert_equal(:foo, catch {:foo}) + end + def test_catch_throw - assert(catch(:foo) { - loop do - loop do - throw :foo, true - break - end - break - assert(false) # should no reach here - end - false - }) + result = catch(:foo) { + loop do + loop do + throw :foo, true + break + end + assert(false, "should not reach here") + end + false + } + assert(result) + end + + def test_catch_throw_noarg + assert_nothing_raised(UncaughtThrowError) { + result = catch {|obj| + throw obj, :ok + assert(false, "should not reach here") + } + assert_equal(:ok, result) + } + end + + def test_uncaught_throw + tag = nil + e = assert_raise_with_message(UncaughtThrowError, /uncaught throw/) { + catch("foo") {|obj| + tag = obj.dup + throw tag, :ok + assert(false, "should not reach here") + } + assert(false, "should not reach here") + } + assert_not_nil(tag) + assert_same(tag, e.tag) + assert_equal(:ok, e.value) end def test_catch_throw_in_require @@ -131,10 +177,20 @@ class TestException < Test::Unit::TestCase Tempfile.create(["dep", ".rb"]) {|t| t.puts("throw :extdep, 42") t.close - assert_equal(42, catch(:extdep) {require t.path}, bug7185) + assert_equal(42, assert_throw(:extdep, bug7185) {require t.path}, bug7185) } end + def test_throw_false + bug12743 = '[ruby-core:77229] [Bug #12743]' + Thread.start { + e = assert_raise_with_message(UncaughtThrowError, /false/, bug12743) { + throw false + } + assert_same(false, e.tag, bug12743) + }.join + end + def test_else_no_exception begin assert(true) @@ -243,6 +299,26 @@ class TestException < Test::Unit::TestCase assert_raise(ArgumentError) { raise 1, 1, 1, 1 } end + def test_type_error_message_encoding + c = eval("Module.new do break class C\u{4032}; self; end; end") + o = c.new + assert_raise_with_message(TypeError, /C\u{4032}/) do + ""[o] + end + c.class_eval {def to_int; self; end} + assert_raise_with_message(TypeError, /C\u{4032}/) do + ""[o] + end + c.class_eval {def to_a; self; end} + assert_raise_with_message(TypeError, /C\u{4032}/) do + [*o] + end + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) do + Class.new {include obj} + end + end + def test_errat assert_in_out_err([], "p $@", %w(nil), []) @@ -276,8 +352,9 @@ class TestException < Test::Unit::TestCase end def test_thread_signal_location - _, stderr, _ = EnvUtil.invoke_ruby("--disable-gems -d", <<-RUBY, false, true) + _, 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 @@ -364,7 +441,7 @@ end.join bug3237 = '[ruby-core:29948]' str = "\u2600" id = :"\u2604" - msg = "undefined method `#{id}' for #{str.inspect}:String" + msg = "undefined method `#{id}' for \"#{str}\":String" assert_raise_with_message(NoMethodError, msg, bug3237) do str.__send__(id) end @@ -490,18 +567,25 @@ end.join assert_equal(false, s.tainted?) end - def m; - m &->{return 0}; - 42; + def m + m(&->{return 0}) + 42 end def test_stackoverflow - assert_raise(SystemStackError){m} + feature6216 = '[ruby-core:43794] [Feature #6216]' + e = assert_raise(SystemStackError, feature6216) {m} + level = e.backtrace.size + assert_operator(level, :>, 10, feature6216) + + feature6216 = '[ruby-core:63377] [Feature #6216]' + e = assert_raise(SystemStackError, feature6216) {raise e} + assert_equal(level, e.backtrace.size, feature6216) end def test_machine_stackoverflow bug9109 = '[ruby-dev:47804] [Bug #9109]' - assert_separately([], <<-SRC) + assert_separately(%w[--disable-gem], <<-SRC) assert_raise(SystemStackError, #{bug9109.dump}) { h = {a: ->{h[:a].call}} h[:a].call @@ -512,7 +596,7 @@ end.join def test_machine_stackoverflow_by_define_method bug9454 = '[ruby-core:60113] [Bug #9454]' - assert_separately([], <<-SRC) + assert_separately(%w[--disable-gem], <<-SRC) assert_raise(SystemStackError, #{bug9454.dump}) { define_method(:foo) {self.foo} self.foo @@ -521,6 +605,30 @@ end.join rescue SystemStackError end + def test_machine_stackoverflow_by_trace + assert_normal_exit("#{<<-"begin;"}\n#{<<~"end;"}", timeout: 60) + begin; + require 'timeout' + require 'tracer' + class HogeError < StandardError + def to_s + message.upcase # disable tailcall optimization + end + end + Tracer.stdout = open(IO::NULL, "w") + begin + Timeout.timeout(5) do + Tracer.on + HogeError.new.to_s + end + rescue Timeout::Error + # ok. there are no SEGV or critical error + rescue SystemStackError => e + # ok. + end + end; + end + def test_cause msg = "[Feature #8257]" cause = nil @@ -540,7 +648,6 @@ end.join def test_cause_reraised msg = "[Feature #8257]" - cause = nil e = assert_raise(RuntimeError) { begin raise msg @@ -550,4 +657,531 @@ end.join } assert_not_same(e, e.cause, "#{msg}: should not be recursive") end + + def test_cause_raised_in_rescue + a = nil + e = assert_raise_with_message(RuntimeError, 'b') { + begin + raise 'a' + rescue => a + begin + raise 'b' + rescue => b + assert_same(a, b.cause) + begin + raise 'c' + rescue + raise b + end + end + end + } + assert_same(a, e.cause, 'cause should not be overwritten by reraise') + end + + def test_cause_at_raised + a = nil + e = assert_raise_with_message(RuntimeError, 'b') { + begin + raise 'a' + rescue => a + b = RuntimeError.new('b') + assert_nil(b.cause) + begin + raise 'c' + rescue + raise b + end + end + } + assert_equal('c', e.cause.message, 'cause should be the exception at raised') + assert_same(a, e.cause.cause) + end + + def test_cause_at_end + assert_in_out_err([], <<-'end;', [], [/-: unexpected return\n/, /.*undefined local variable or method `n'.*\n/]) + 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) + end + + def test_cause_with_no_arguments + cause = ArgumentError.new("foobar") + assert_raise_with_message(ArgumentError, /with no arguments/) do + raise cause: cause + end + end + + def test_raise_with_cause_in_rescue + e = assert_raise_with_message(RuntimeError, 'b') { + begin + raise 'a' + rescue => a + begin + raise 'b' + rescue => b + assert_same(a, b.cause) + begin + raise 'c' + rescue + raise b, cause: ArgumentError.new('d') + end + end + end + } + assert_equal('d', e.cause.message, 'cause option should be honored always') + assert_nil(e.cause.cause) + end + + def test_cause_thread_no_cause + bug12741 = '[ruby-core:77222] [Bug #12741]' + + x = Thread.current + a = false + y = Thread.start do + Thread.pass until a + x.raise "stop" + end + + begin + raise bug12741 + rescue + e = assert_raise_with_message(RuntimeError, "stop") do + a = true + sleep 1 + end + end + assert_nil(e.cause) + ensure + y.join + end + + def test_cause_thread_with_cause + bug12741 = '[ruby-core:77222] [Bug #12741]' + + x = Thread.current + q = Queue.new + y = Thread.start do + q.pop + begin + raise "caller's cause" + rescue + x.raise "stop" + end + end + + begin + raise bug12741 + rescue + e = assert_raise_with_message(RuntimeError, "stop") do + q.push(true) + sleep 1 + end + ensure + y.join + end + assert_equal("caller's cause", e.cause.message) + end + + def test_unknown_option + bug = '[ruby-core:63203] [Feature #8257] should pass unknown options' + + exc = Class.new(RuntimeError) do + attr_reader :arg + def initialize(msg = nil) + @arg = msg + super(msg) + end + end + + 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"} + assert_equal({"foo" => "bar", foo: "bar"}, e.arg, bug) + + e = assert_raise(exc, bug) {raise exc, {}} + assert_equal({}, e.arg, bug) + end + + def test_circular_cause + bug13043 = '[ruby-core:78688] [Bug #13043]' + begin + begin + raise "error 1" + ensure + orig_error = $! + begin + raise "error 2" + rescue => err + raise orig_error + end + end + rescue => x + end + assert_equal(orig_error, x) + assert_equal(orig_error, err.cause) + assert_nil(orig_error.cause, bug13043) + end + + def test_cause_with_frozen_exception + exc = ArgumentError.new("foo").freeze + assert_raise_with_message(ArgumentError, exc.message) { + raise exc, cause: RuntimeError.new("bar") + } + 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 + 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; + end + + def test_output_string_encoding + # "\x82\xa0" in cp932 is "\u3042" (Japanese hiragana 'a') + # change $stderr to force calling rb_io_write() instead of fwrite() + assert_in_out_err(["-Eutf-8:cp932"], '# coding: cp932 +$stderr = $stdout; raise "\x82\xa0"') do |outs, errs, status| + assert_equal 1, outs.size + assert_equal 0, errs.size + err = outs.first.force_encoding('utf-8') + assert err.valid_encoding?, 'must be valid encoding' + assert_match %r/\u3042/, err + end + end + + def test_multibyte_and_newline + bug10727 = '[ruby-core:67473] [Bug #10727]' + assert_in_out_err([], <<-'end;', [], /\u{306b 307b 3093 3054} \(E\)\n\u{6539 884c}/, bug10727, encoding: "UTF-8") + class E < StandardError + def initialize + super("\u{306b 307b 3093 3054}\n\u{6539 884c}") + end + end + raise E + end; + end + + def test_method_missing_reason_clear + bug10969 = '[ruby-core:68515] [Bug #10969]' + a = Class.new {def method_missing(*) super end}.new + assert_raise(NameError) {a.instance_eval("foo")} + assert_raise(NoMethodError, bug10969) {a.public_send("bar", true)} + end + + def test_message_of_name_error + assert_raise_with_message(NameError, /\Aundefined method `foo' for module `#<Module:.*>'$/) do + Module.new do + module_function :foo + end + end + end + + def capture_warning_warn + verbose = $VERBOSE + warning = [] + + ::Warning.class_eval do + alias_method :warn2, :warn + remove_method :warn + + define_method(:warn) do |str| + warning << str + end + end + + $VERBOSE = true + yield + + return warning + ensure + $VERBOSE = verbose + + ::Warning.class_eval do + remove_method :warn + alias_method :warn, :warn2 + remove_method :warn2 + end + end + + def test_warning_warn + warning = capture_warning_warn {@a} + assert_match(/instance variable @a 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_kernel_warn_uplevel + warning = capture_warning_warn {warn("test warning", uplevel: 0)} + 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:/) + end + + def test_warning_warn_invalid_argument + assert_raise(TypeError) do + ::Warning.warn nil + end + assert_raise(TypeError) do + ::Warning.warn 1 + end + assert_raise(Encoding::CompatibilityError) do + ::Warning.warn "\x00a\x00b\x00c".force_encoding("utf-16be") + end + end + + def test_warning_warn_circular_require_backtrace + warning = nil + path = nil + Tempfile.create(%w[circular .rb]) do |t| + path = File.realpath(t.path) + basename = File.basename(path) + t.puts "require '#{basename}'" + t.close + $LOAD_PATH.push(File.dirname(t)) + warning = capture_warning_warn {require basename} + ensure + $LOAD_PATH.pop + $LOADED_FEATURES.delete(t) + end + assert_equal(1, warning.size) + assert_match(/circular require/, warning.first) + assert_match(/^\tfrom #{Regexp.escape(path)}:1:/, warning.first) + end + + def test_warning_warn_super + assert_in_out_err(%[-W0], "#{<<~"{#"}\n#{<<~'};'}", [], /instance variable @a not initialized/) + {# + module Warning + def warn(message) + super + end + end + + $VERBOSE = true + @a + }; + end + + def test_undefined_backtrace + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class Exception + undef backtrace + end + + assert_raise(RuntimeError) { + raise RuntimeError, "hello" + } + end; + end + + def test_redefined_backtrace + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + $exc = nil + + class Exception + undef backtrace + def backtrace + $exc = self + end + end + + e = assert_raise(RuntimeError) { + raise RuntimeError, "hello" + } + assert_same(e, $exc) + 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; + class Exception + undef backtrace + def backtrace(a) + end + end + + assert_raise(RuntimeError) { + 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_full_message + 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(status1.success?) + assert(err1.empty?, "expected nothing wrote to $stdout by #long_message") + + _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) + assert_equal(err2, out1) + + 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} + + message = e.full_message(highlight: false, order: :top) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, "test:100: testerror (RuntimeError)\n") + assert_operator(message, :end_with?, "test:1\n") + + message = e.full_message(highlight: false, order: :bottom) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, "Traceback (most recent call last):") + assert_operator(message, :end_with?, "test:100: testerror (RuntimeError)\n") + + message = e.full_message(highlight: true) + assert_match(/\e/, message) + 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: 1) + end end diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index 3f620c69f8..0d070dcbc4 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -1,8 +1,8 @@ +# frozen_string_literal: false require 'test/unit' require 'fiber' -require 'continuation' +EnvUtil.suppress_warning {require 'continuation'} require 'tmpdir' -require_relative './envutil' class TestFiber < Test::Unit::TestCase def test_normal @@ -70,10 +70,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 @@ -118,7 +120,7 @@ class TestFiber < Test::Unit::TestCase end def test_throw - assert_raise(ArgumentError){ + assert_raise(UncaughtThrowError){ Fiber.new do throw :a end.resume @@ -199,11 +201,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 @@ -217,13 +219,24 @@ class TestFiber < Test::Unit::TestCase }, bug4612 end + def test_mark_fiber + bug13875 = '[ruby-core:82681]' + + assert_normal_exit %q{ + GC.stress = true + up = 1.upto(10) + down = 10.downto(1) + up.zip(down) {|a, b| a + b == 11 or fail 'oops'} + }, bug13875 + end + def test_no_valid_cfp bug5083 = '[ruby-dev:44208]' - assert_equal([], Fiber.new(&Module.method(:nesting)).resume) - assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s)) + assert_equal([], Fiber.new(&Module.method(:nesting)).resume, bug5083) + assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s), bug5083) end - def test_prohibit_resume_transfered_fiber + def test_prohibit_resume_transferred_fiber assert_raise(FiberError){ root_fiber = Fiber.current f = Fiber.new{ @@ -256,7 +269,11 @@ class TestFiber < Test::Unit::TestCase end bug5700 = '[ruby-core:41456]' assert_nothing_raised(bug5700) do - Fiber.new{ pid = fork {} }.resume + Fiber.new do + pid = fork do + Fiber.new {}.transfer + end + end.resume end pid, status = Process.waitpid2(pid) assert_equal(0, status.exitstatus, bug5700) @@ -283,8 +300,8 @@ class TestFiber < Test::Unit::TestCase 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, err = Dir.mktmpdir("test_fiber") {|tmpdir| - EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir) + out, _ = Dir.mktmpdir("test_fiber") {|tmpdir| + EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30) } use_length ? out.length : out end @@ -344,5 +361,35 @@ class TestFiber < Test::Unit::TestCase assert_equal("inner", s2) assert_equal(s1, $_, bug7678) end -end + def test_new_symbol_proc + bug = '[ruby-core:80147] [Bug #13313]' + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug) + begin; + exit("1" == Fiber.new(&:to_s).resume(1)) + end; + end + + def test_to_s + f = Fiber.new do + assert_match(/resumed/, f.to_s) + Fiber.yield + end + assert_match(/created/, f.to_s) + f.resume + assert_match(/suspended/, f.to_s) + f.resume + assert_match(/terminated/, f.to_s) + assert_match(/resumed/, Fiber.current.to_s) + 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 01992a83d4..10bfbd9ae0 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require "thread" -require_relative 'envutil' +require "-test-/file" require_relative 'ut_eof' class TestFile < Test::Unit::TestCase @@ -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 @@ -121,10 +121,10 @@ class TestFile < Test::Unit::TestCase def test_truncate_size Tempfile.create("test-truncate") do |f| - q1 = Queue.new - q2 = Queue.new + q1 = Thread::Queue.new + q2 = Thread::Queue.new - Thread.new do + th = Thread.new do data = '' 64.times do |i| data << i.to_s @@ -142,6 +142,7 @@ class TestFile < Test::Unit::TestCase assert_equal size, f.size q2.push true end + th.join end end @@ -258,6 +259,58 @@ class TestFile < Test::Unit::TestCase } end + def test_realpath_encoding + fsenc = Encoding.find("filesystem") + nonascii = "\u{0391 0410 0531 10A0 05d0 2C00 3042}" + tst = "A" + nonascii.each_char {|c| tst << c.encode(fsenc) rescue nil} + Dir.mktmpdir('rubytest-realpath') {|tmpdir| + realdir = File.realpath(tmpdir) + open(File.join(tmpdir, tst), "w") {} + a = File.join(tmpdir, "x") + begin + File.symlink(tst, a) + rescue Errno::EACCES, Errno::EPERM + skip "need privilege" + end + assert_equal(File.join(realdir, tst), File.realpath(a)) + File.unlink(a) + + tst = "A" + nonascii + open(File.join(tmpdir, tst), "w") {} + File.symlink(tst, a) + assert_equal(File.join(realdir, tst), File.realpath(a.encode("UTF-8"))) + } + 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_predicate(File.realpath(base, dir), :tainted?) + assert_predicate(Dir.chdir(dir) {File.realpath(base)}, :tainted?) + } + end + + 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 Dir.mktmpdir('rubytest-realdirpath') {|tmpdir| realdir = File.realpath(tmpdir) @@ -276,6 +329,16 @@ class TestFile < Test::Unit::TestCase end end + def test_realdirpath_junction + Dir.mktmpdir('rubytest-realpath') {|tmpdir| + Dir.chdir(tmpdir) do + Dir.mkdir('foo') + skip "cannot run mklink" unless system('mklink /j bar foo > nul') + assert_equal(File.realpath('foo'), File.realpath('bar')) + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + def test_utime_with_minus_time_segv bug5596 = '[ruby-dev:44838]' assert_in_out_err([], <<-EOS, [bug5596], []) @@ -311,6 +374,42 @@ class TestFile < Test::Unit::TestCase assert_equal(mod_time_contents, stats.mtime, bug6385) end + def test_stat + tb = Process.clock_gettime(Process::CLOCK_REALTIME) + Tempfile.create("stat") {|file| + tb = (tb + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2 + file.close + path = file.path + + t0 = Process.clock_gettime(Process::CLOCK_REALTIME) + File.write(path, "foo") + sleep 2 + File.write(path, "bar") + sleep 2 + File.read(path) + File.chmod(0644, path) + sleep 2 + File.read(path) + + delta = 1 + stat = File.stat(path) + assert_in_delta tb, stat.birthtime.to_f, delta + assert_in_delta t0+2, stat.mtime.to_f, delta + if stat.birthtime != stat.ctime + assert_in_delta t0+4, stat.ctime.to_f, delta + end + if /mswin|mingw/ !~ RUBY_PLATFORM && !Bug::File::Fs.noatime?(path) + # Windows delays updating atime + assert_in_delta t0+6, stat.atime.to_f, delta + end + } + rescue NotImplementedError + end + + def test_stat_inode + assert_not_equal 0, File.stat(__FILE__).ino + end + def test_chmod_m17n bug5671 = '[ruby-dev:44898]' Dir.mktmpdir('test-file-chmod-m17n-') do |tmpdir| @@ -344,6 +443,19 @@ class TestFile < Test::Unit::TestCase } end + def test_file_share_delete + Dir.mktmpdir(__method__.to_s) do |tmpdir| + tmp = File.join(tmpdir, 'x') + File.open(tmp, mode: IO::WRONLY | IO::CREAT | IO::BINARY | IO::SHARE_DELETE) do |f| + assert_file.exist?(tmp) + assert_nothing_raised do + File.unlink(tmp) + end + end + assert_file.not_exist?(tmp) + end + end + def test_conflicting_encodings Dir.mktmpdir(__method__.to_s) do |tmpdir| tmp = File.join(tmpdir, 'x') @@ -383,4 +495,24 @@ class TestFile < Test::Unit::TestCase assert_file.not_exist?(path) end end + + def test_open_tempfile_path + Dir.mktmpdir(__method__.to_s) do |tmpdir| + begin + io = File.open(tmpdir, File::RDWR | File::TMPFILE) + rescue Errno::EINVAL + skip 'O_TMPFILE not supported (EINVAL)' + rescue Errno::EOPNOTSUPP + skip 'O_TMPFILE not supported (EOPNOTSUPP)' + end + + io.write "foo" + io.flush + assert_equal 3, io.size + assert_raise(IOError) { io.path } + ensure + io&.close + end + end if File::Constants.const_defined?(:TMPFILE) + end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index c0f92ec820..817c3580d1 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -1,10 +1,14 @@ +# frozen_string_literal: false require "test/unit" require "fileutils" require "tmpdir" -require_relative "envutil" +require "socket" +require '-test-/file' class TestFileExhaustive < Test::Unit::TestCase DRIVE = Dir.pwd[%r'\A(?:[a-z]:|//[^/]+/[^/]+)'i] + POSIX = /cygwin|mswin|bccwin|mingw|emx/ !~ RUBY_PLATFORM + NTFS = !(/mingw|mswin|bccwin/ !~ RUBY_PLATFORM) def assert_incompatible_encoding d = "\u{3042}\u{3044}".encode("utf-16le") @@ -15,51 +19,172 @@ class TestFileExhaustive < Test::Unit::TestCase def setup @dir = Dir.mktmpdir("rubytest-file") - @rootdir = "#{DRIVE}/" File.chown(-1, Process.gid, @dir) - @file = make_tmp_filename("file") - @zerofile = make_tmp_filename("zerofile") + end + + def teardown + GC.start + FileUtils.remove_entry_secure @dir + end + + def make_tmp_filename(prefix) + "#{@dir}/#{prefix}.test" + end + + def rootdir + return @rootdir if defined? @rootdir + @rootdir = "#{DRIVE}/" + @rootdir + end + + def nofile + return @nofile if defined? @nofile @nofile = make_tmp_filename("nofile") - @symlinkfile = make_tmp_filename("symlinkfile") - @hardlinkfile = make_tmp_filename("hardlinkfile") - make_file("foo", @file) + @nofile + end + + def make_file(content, file) + open(file, "w") {|fh| fh << content } + end + + def zerofile + return @zerofile if defined? @zerofile + @zerofile = make_tmp_filename("zerofile") make_file("", @zerofile) - @time = Time.now + @zerofile + end + + def regular_file + return @file if defined? @file + @file = make_tmp_filename("file") + make_file("foo", @file) + @file + end + + def utf8_file + return @utf8file if defined? @utf8file + @utf8file = make_tmp_filename("\u3066\u3059\u3068") + make_file("foo", @utf8file) + @utf8file + end + + def notownedfile + return @notownedfile if defined? @notownedfile + if Process.euid != 0 + @notownedfile = '/' + else + @notownedfile = nil + end + @notownedfile + end + + def suidfile + return @suidfile if defined? @suidfile + if POSIX + @suidfile = make_tmp_filename("suidfile") + make_file("", @suidfile) + File.chmod 04500, @suidfile + @suidfile + else + @suidfile = nil + end + end + + def sgidfile + return @sgidfile if defined? @sgidfile + if POSIX + @sgidfile = make_tmp_filename("sgidfile") + make_file("", @sgidfile) + File.chmod 02500, @sgidfile + @sgidfile + else + @sgidfile = nil + end + end + + def stickyfile + return @stickyfile if defined? @stickyfile + if POSIX + @stickyfile = make_tmp_filename("stickyfile") + Dir.mkdir(@stickyfile) + File.chmod 01500, @stickyfile + @stickyfile + else + @stickyfile = nil + end + end + + def symlinkfile + return @symlinkfile if defined? @symlinkfile + @symlinkfile = make_tmp_filename("symlinkfile") begin - File.symlink(@file, @symlinkfile) - rescue NotImplementedError + File.symlink(regular_file, @symlinkfile) + rescue NotImplementedError, Errno::EACCES, Errno::EPERM @symlinkfile = nil end + @symlinkfile + end + + def hardlinkfile + return @hardlinkfile if defined? @hardlinkfile + @hardlinkfile = make_tmp_filename("hardlinkfile") begin - File.link(@file, @hardlinkfile) + File.link(regular_file, @hardlinkfile) rescue NotImplementedError, Errno::EINVAL # EINVAL for Windows Vista @hardlinkfile = nil end + @hardlinkfile end - def teardown - GC.start - FileUtils.remove_entry_secure @dir + def fifo + return @fifo if defined? @fifo + if POSIX + fn = make_tmp_filename("fifo") + File.mkfifo(fn) + @fifo = fn + else + @fifo = nil + end + @fifo end - def make_file(content, file = @file) - open(file, "w") {|fh| fh << content } + def socket + return @socket if defined? @socket + if defined? UNIXServer + socket = make_tmp_filename("s") + UNIXServer.open(socket).close + @socket = socket + else + @socket = nil + end end - def make_tmp_filename(prefix) - @hardlinkfile = @dir + "/" + prefix + File.basename(__FILE__) + ".#{$$}.test" + def chardev + return @chardev if defined? @chardev + @chardev = File::NULL == "/dev/null" ? "/dev/null" : nil + @chardev end - def test_path - file = @file + def blockdev + return @blockdev if defined? @blockdev + if /linux/ =~ RUBY_PLATFORM + @blockdev = %w[/dev/loop0 /dev/sda /dev/vda /dev/xvda1].find {|f| File.exist? f } + else + @blockdev = nil + end + @blockdev + end - assert_equal(file, File.open(file) {|f| f.path}) - assert_equal(file, File.path(file)) - o = Object.new - class << o; self; end.class_eval do - define_method(:to_path) { file } + def test_path + [regular_file, utf8_file].each do |file| + assert_equal(file, File.open(file) {|f| f.path}) + assert_equal(file, File.path(file)) + o = Object.new + class << o; self; end.class_eval do + define_method(:to_path) { file } + end + assert_equal(file, File.path(o)) end - assert_equal(file, File.path(o)) end def assert_integer(n) @@ -67,7 +192,7 @@ class TestFileExhaustive < Test::Unit::TestCase end def assert_integer_or_nil(n) - msg = ->{"#{n.inspect} is neither Fixnum nor nil."} + msg = ->{"#{n.inspect} is neither Integer nor nil."} if n assert_kind_of(Integer, n, msg) else @@ -76,9 +201,12 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_stat - sleep(@time - Time.now + 1.1) - make_file("foo", @file + "2") - fs1, fs2 = File.stat(@file), File.stat(@file + "2") + fn1 = regular_file + hardlinkfile + sleep(1.1) + fn2 = fn1 + "2" + make_file("foo", fn2) + fs1, fs2 = File.stat(fn1), File.stat(fn2) assert_nothing_raised do assert_equal(0, fs1 <=> fs1) assert_equal(-1, fs1 <=> fs2) @@ -95,7 +223,7 @@ class TestFileExhaustive < Test::Unit::TestCase 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) + assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) end assert_integer(fs1.uid) assert_integer(fs1.gid) @@ -107,10 +235,10 @@ class TestFileExhaustive < Test::Unit::TestCase assert_kind_of(Time, fs1.ctime) assert_kind_of(String, fs1.inspect) end - assert_raise(Errno::ENOENT) { File.stat(@nofile) } - assert_kind_of(File::Stat, File.open(@file) {|f| f.stat}) - assert_raise(Errno::ENOENT) { File.lstat(@nofile) } - assert_kind_of(File::Stat, File.open(@file) {|f| f.lstat}) + assert_raise(Errno::ENOENT) { File.stat(nofile) } + assert_kind_of(File::Stat, File.open(fn1) {|f| f.stat}) + assert_raise(Errno::ENOENT) { File.lstat(nofile) } + assert_kind_of(File::Stat, File.open(fn1) {|f| f.lstat}) end def test_stat_drive_root @@ -133,232 +261,371 @@ class TestFileExhaustive < Test::Unit::TestCase assert_nothing_raised { File.stat(File.basename(prefix)) } end end - end if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM + end if NTFS + + def test_lstat + return unless symlinkfile + assert_equal(false, File.stat(symlinkfile).symlink?) + assert_equal(true, File.lstat(symlinkfile).symlink?) + f = File.new(symlinkfile) + assert_equal(false, f.stat.symlink?) + assert_equal(true, f.lstat.symlink?) + f.close + end def test_directory_p assert_file.directory?(@dir) assert_file.not_directory?(@dir+"/...") - assert_file.not_directory?(@file) - assert_file.not_directory?(@nofile) + assert_file.not_directory?(regular_file) + assert_file.not_directory?(utf8_file) + assert_file.not_directory?(nofile) end - def test_pipe_p ## xxx + def test_pipe_p assert_file.not_pipe?(@dir) - assert_file.not_pipe?(@file) - assert_file.not_pipe?(@nofile) + assert_file.not_pipe?(regular_file) + assert_file.not_pipe?(utf8_file) + assert_file.not_pipe?(nofile) + assert_file.pipe?(fifo) if fifo end def test_symlink_p assert_file.not_symlink?(@dir) - assert_file.not_symlink?(@file) - assert_file.symlink?(@symlinkfile) if @symlinkfile - assert_file.not_symlink?(@hardlinkfile) if @hardlinkfile - assert_file.not_symlink?(@nofile) + assert_file.not_symlink?(regular_file) + assert_file.not_symlink?(utf8_file) + assert_file.symlink?(symlinkfile) if symlinkfile + assert_file.not_symlink?(hardlinkfile) if hardlinkfile + assert_file.not_symlink?(nofile) end - def test_socket_p ## xxx + def test_socket_p assert_file.not_socket?(@dir) - assert_file.not_socket?(@file) - assert_file.not_socket?(@nofile) + assert_file.not_socket?(regular_file) + assert_file.not_socket?(utf8_file) + assert_file.not_socket?(nofile) + assert_file.socket?(socket) if socket end - def test_blockdev_p ## xxx + def test_blockdev_p assert_file.not_blockdev?(@dir) - assert_file.not_blockdev?(@file) - assert_file.not_blockdev?(@nofile) + assert_file.not_blockdev?(regular_file) + assert_file.not_blockdev?(utf8_file) + assert_file.not_blockdev?(nofile) + assert_file.blockdev?(blockdev) if blockdev end - def test_chardev_p ## xxx + def test_chardev_p assert_file.not_chardev?(@dir) - assert_file.not_chardev?(@file) - assert_file.not_chardev?(@nofile) + assert_file.not_chardev?(regular_file) + assert_file.not_chardev?(utf8_file) + assert_file.not_chardev?(nofile) + assert_file.chardev?(chardev) if chardev end def test_exist_p assert_file.exist?(@dir) - assert_file.exist?(@file) - assert_file.not_exist?(@nofile) + assert_file.exist?(regular_file) + assert_file.exist?(utf8_file) + assert_file.not_exist?(nofile) end def test_readable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0200, @file) - assert_file.not_readable?(@file) - File.chmod(0600, @file) - assert_file.readable?(@file) - assert_file.not_readable?(@nofile) - end + File.chmod(0200, regular_file) + assert_file.not_readable?(regular_file) + File.chmod(0600, regular_file) + assert_file.readable?(regular_file) + + File.chmod(0200, utf8_file) + assert_file.not_readable?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.readable?(utf8_file) + + assert_file.not_readable?(nofile) + end if POSIX def test_readable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0200, @file) - assert_file.not_readable_real?(@file) - File.chmod(0600, @file) - assert_file.readable_real?(@file) - assert_file.not_readable_real?(@nofile) - end + File.chmod(0200, regular_file) + assert_file.not_readable_real?(regular_file) + File.chmod(0600, regular_file) + assert_file.readable_real?(regular_file) + + File.chmod(0200, utf8_file) + assert_file.not_readable_real?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.readable_real?(utf8_file) + + assert_file.not_readable_real?(nofile) + end if POSIX def test_world_readable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0006, @file) - assert_file.world_readable?(@file) - File.chmod(0060, @file) - assert_file.not_world_readable?(@file) - File.chmod(0600, @file) - assert_file.not_world_readable?(@file) - assert_file.not_world_readable?(@nofile) - end + File.chmod(0006, regular_file) + assert_file.world_readable?(regular_file) + File.chmod(0060, regular_file) + assert_file.not_world_readable?(regular_file) + File.chmod(0600, regular_file) + assert_file.not_world_readable?(regular_file) + + File.chmod(0006, utf8_file) + assert_file.world_readable?(utf8_file) + File.chmod(0060, utf8_file) + assert_file.not_world_readable?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.not_world_readable?(utf8_file) + + assert_file.not_world_readable?(nofile) + end if POSIX def test_writable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0400, @file) - assert_file.not_writable?(@file) - File.chmod(0600, @file) - assert_file.writable?(@file) - assert_file.not_writable?(@nofile) - end + File.chmod(0400, regular_file) + assert_file.not_writable?(regular_file) + File.chmod(0600, regular_file) + assert_file.writable?(regular_file) + + File.chmod(0400, utf8_file) + assert_file.not_writable?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.writable?(utf8_file) + + assert_file.not_writable?(nofile) + end if POSIX def test_writable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0400, @file) - assert_file.not_writable_real?(@file) - File.chmod(0600, @file) - assert_file.writable_real?(@file) - assert_file.not_writable_real?(@nofile) - end + File.chmod(0400, regular_file) + assert_file.not_writable_real?(regular_file) + File.chmod(0600, regular_file) + assert_file.writable_real?(regular_file) + + File.chmod(0400, utf8_file) + assert_file.not_writable_real?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.writable_real?(utf8_file) + + assert_file.not_writable_real?(nofile) + end if POSIX def test_world_writable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0006, @file) - assert_file.world_writable?(@file) - File.chmod(0060, @file) - assert_file.not_world_writable?(@file) - File.chmod(0600, @file) - assert_file.not_world_writable?(@file) - assert_file.not_world_writable?(@nofile) - end + File.chmod(0006, regular_file) + assert_file.world_writable?(regular_file) + File.chmod(0060, regular_file) + assert_file.not_world_writable?(regular_file) + File.chmod(0600, regular_file) + assert_file.not_world_writable?(regular_file) + + File.chmod(0006, utf8_file) + assert_file.world_writable?(utf8_file) + File.chmod(0060, utf8_file) + assert_file.not_world_writable?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.not_world_writable?(utf8_file) + + assert_file.not_world_writable?(nofile) + end if POSIX def test_executable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0100, @file) - assert_file.executable?(@file) - File.chmod(0600, @file) - assert_file.not_executable?(@file) - assert_file.not_executable?(@nofile) - end + File.chmod(0100, regular_file) + assert_file.executable?(regular_file) + File.chmod(0600, regular_file) + assert_file.not_executable?(regular_file) + + File.chmod(0100, utf8_file) + assert_file.executable?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.not_executable?(utf8_file) + + assert_file.not_executable?(nofile) + end if POSIX def test_executable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0100, @file) - assert_file.executable_real?(@file) - File.chmod(0600, @file) - assert_file.not_executable_real?(@file) - assert_file.not_executable_real?(@nofile) - end + File.chmod(0100, regular_file) + assert_file.executable_real?(regular_file) + File.chmod(0600, regular_file) + assert_file.not_executable_real?(regular_file) + + File.chmod(0100, utf8_file) + assert_file.executable_real?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.not_executable_real?(utf8_file) + + assert_file.not_executable_real?(nofile) + end if POSIX def test_file_p assert_file.not_file?(@dir) - assert_file.file?(@file) - assert_file.not_file?(@nofile) + assert_file.file?(regular_file) + assert_file.file?(utf8_file) + assert_file.not_file?(nofile) end def test_zero_p assert_nothing_raised { File.zero?(@dir) } - assert_file.not_zero?(@file) - assert_file.zero?(@zerofile) - assert_file.not_zero?(@nofile) + assert_file.not_zero?(regular_file) + assert_file.not_zero?(utf8_file) + assert_file.zero?(zerofile) + assert_file.not_zero?(nofile) + end + + def test_empty_p + assert_nothing_raised { File.empty?(@dir) } + assert_file.not_empty?(regular_file) + assert_file.not_empty?(utf8_file) + assert_file.empty?(zerofile) + assert_file.not_empty?(nofile) end def test_size_p assert_nothing_raised { File.size?(@dir) } - assert_equal(3, File.size?(@file)) - assert_file.not_size?(@zerofile) - assert_file.not_size?(@nofile) + assert_equal(3, File.size?(regular_file)) + assert_equal(3, File.size?(utf8_file)) + assert_file.not_size?(zerofile) + assert_file.not_size?(nofile) end - def test_owned_p ## xxx - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - assert_file.owned?(@file) - assert_file.grpowned?(@file) + def test_owned_p + assert_file.owned?(regular_file) + assert_file.owned?(utf8_file) + assert_file.not_owned?(notownedfile) if notownedfile + end if POSIX + + def test_grpowned_p ## xxx + assert_file.grpowned?(regular_file) + assert_file.grpowned?(utf8_file) + end if POSIX + + def test_suid + assert_file.not_setuid?(regular_file) + assert_file.not_setuid?(utf8_file) + assert_file.setuid?(suidfile) if suidfile + end + + def test_sgid + assert_file.not_setgid?(regular_file) + assert_file.not_setgid?(utf8_file) + assert_file.setgid?(sgidfile) if sgidfile + end + + def test_sticky + assert_file.not_sticky?(regular_file) + assert_file.not_sticky?(utf8_file) + assert_file.sticky?(stickyfile) if stickyfile end - def test_suid_sgid_sticky ## xxx - assert_file.not_setuid?(@file) - assert_file.not_setgid?(@file) - assert_file.not_sticky?(@file) + def test_path_identical_p + assert_file.identical?(regular_file, regular_file) + assert_file.not_identical?(regular_file, zerofile) + assert_file.not_identical?(regular_file, nofile) + assert_file.not_identical?(nofile, regular_file) end - def test_identical_p - assert_file.identical?(@file, @file) - assert_file.not_identical?(@file, @zerofile) - assert_file.not_identical?(@file, @nofile) - assert_file.not_identical?(@nofile, @file) + def path_identical_p(file) + [regular_file, utf8_file].each do |file| + assert_file.identical?(file, file) + assert_file.not_identical?(file, zerofile) + assert_file.not_identical?(file, nofile) + assert_file.not_identical?(nofile, file) + end + end + + def test_io_identical_p + [regular_file, utf8_file].each do |file| + open(file) {|f| + assert_file.identical?(f, f) + assert_file.identical?(file, f) + assert_file.identical?(f, file) + } + end + end + + def test_closed_io_identical_p + [regular_file, utf8_file].each do |file| + io = open(file) {|f| f} + assert_raise(IOError) { + File.identical?(file, io) + } + File.unlink(file) + assert_file.not_exist?(file) + end end def test_s_size assert_integer(File.size(@dir)) - assert_equal(3, File.size(@file)) - assert_equal(0, File.size(@zerofile)) - assert_raise(Errno::ENOENT) { File.size(@nofile) } + assert_equal(3, File.size(regular_file)) + assert_equal(3, File.size(utf8_file)) + assert_equal(0, File.size(zerofile)) + assert_raise(Errno::ENOENT) { File.size(nofile) } end def test_ftype assert_equal("directory", File.ftype(@dir)) - assert_equal("file", File.ftype(@file)) - assert_equal("link", File.ftype(@symlinkfile)) if @symlinkfile - assert_equal("file", File.ftype(@hardlinkfile)) if @hardlinkfile - assert_raise(Errno::ENOENT) { File.ftype(@nofile) } + assert_equal("file", File.ftype(regular_file)) + assert_equal("file", File.ftype(utf8_file)) + assert_equal("link", File.ftype(symlinkfile)) if symlinkfile + assert_equal("file", File.ftype(hardlinkfile)) if hardlinkfile + assert_raise(Errno::ENOENT) { File.ftype(nofile) } end def test_atime - t1 = File.atime(@file) - t2 = File.open(@file) {|f| f.atime} - assert_kind_of(Time, t1) - assert_kind_of(Time, t2) - assert_equal(t1, t2) - assert_raise(Errno::ENOENT) { File.atime(@nofile) } + [regular_file, utf8_file].each do |file| + t1 = File.atime(file) + t2 = File.open(file) {|f| f.atime} + assert_kind_of(Time, t1) + assert_kind_of(Time, t2) + # High Sierra's APFS can handle nano-sec precise. + # t1 value is difference from t2 on APFS. + if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs" + assert_equal(t1.to_i, t2.to_i) + else + assert_equal(t1, t2) + end + end + assert_raise(Errno::ENOENT) { File.atime(nofile) } end def test_mtime - t1 = File.mtime(@file) - t2 = File.open(@file) {|f| f.mtime} - assert_kind_of(Time, t1) - assert_kind_of(Time, t2) - assert_equal(t1, t2) - assert_raise(Errno::ENOENT) { File.mtime(@nofile) } + [regular_file, utf8_file].each do |file| + t1 = File.mtime(file) + t2 = File.open(file) {|f| f.mtime} + assert_kind_of(Time, t1) + assert_kind_of(Time, t2) + assert_equal(t1, t2) + end + assert_raise(Errno::ENOENT) { File.mtime(nofile) } end def test_ctime - t1 = File.ctime(@file) - t2 = File.open(@file) {|f| f.ctime} - assert_kind_of(Time, t1) - assert_kind_of(Time, t2) - assert_equal(t1, t2) - assert_raise(Errno::ENOENT) { File.ctime(@nofile) } + [regular_file, utf8_file].each do |file| + t1 = File.ctime(file) + t2 = File.open(file) {|f| f.ctime} + assert_kind_of(Time, t1) + assert_kind_of(Time, t2) + assert_equal(t1, t2) + end + assert_raise(Errno::ENOENT) { File.ctime(nofile) } end def test_chmod - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - assert_equal(1, File.chmod(0444, @file)) - assert_equal(0444, File.stat(@file).mode % 01000) - assert_equal(0, File.open(@file) {|f| f.chmod(0222)}) - assert_equal(0222, File.stat(@file).mode % 01000) - File.chmod(0600, @file) - assert_raise(Errno::ENOENT) { File.chmod(0600, @nofile) } - end + [regular_file, utf8_file].each do |file| + assert_equal(1, File.chmod(0444, file)) + assert_equal(0444, File.stat(file).mode % 01000) + assert_equal(0, File.open(file) {|f| f.chmod(0222)}) + assert_equal(0222, File.stat(file).mode % 01000) + File.chmod(0600, file) + end + assert_raise(Errno::ENOENT) { File.chmod(0600, nofile) } + end if POSIX def test_lchmod - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - assert_equal(1, File.lchmod(0444, @file)) - assert_equal(0444, File.stat(@file).mode % 01000) - File.lchmod(0600, @file) - assert_raise(Errno::ENOENT) { File.lchmod(0600, @nofile) } + [regular_file, utf8_file].each do |file| + assert_equal(1, File.lchmod(0444, file)) + assert_equal(0444, File.stat(file).mode % 01000) + File.lchmod(0600, regular_file) + end + assert_raise(Errno::ENOENT) { File.lchmod(0600, nofile) } rescue NotImplementedError - end + end if POSIX def test_chown ## xxx end @@ -367,39 +634,71 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_symlink - return unless @symlinkfile - assert_equal("link", File.ftype(@symlinkfile)) - assert_raise(Errno::EEXIST) { File.symlink(@file, @file) } + return unless symlinkfile + assert_equal("link", File.ftype(symlinkfile)) + assert_raise(Errno::EEXIST) { File.symlink(regular_file, regular_file) } + assert_raise(Errno::EEXIST) { File.symlink(utf8_file, utf8_file) } end def test_utime t = Time.local(2000) - File.utime(t + 1, t + 2, @zerofile) - assert_equal(t + 1, File.atime(@zerofile)) - assert_equal(t + 2, File.mtime(@zerofile)) + File.utime(t + 1, t + 2, zerofile) + assert_equal(t + 1, File.atime(zerofile)) + assert_equal(t + 2, File.mtime(zerofile)) + end + + def test_utime_symlinkfile + return unless symlinkfile + t = Time.local(2000) + stat = File.lstat(symlinkfile) + 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 - return unless @hardlinkfile - assert_equal("file", File.ftype(@hardlinkfile)) - assert_raise(Errno::EEXIST) { File.link(@file, @file) } + return unless hardlinkfile + assert_equal("file", File.ftype(hardlinkfile)) + assert_raise(Errno::EEXIST) { File.link(regular_file, regular_file) } + assert_raise(Errno::EEXIST) { File.link(utf8_file, utf8_file) } end def test_readlink - return unless @symlinkfile - assert_equal(@file, File.readlink(@symlinkfile)) - assert_raise(Errno::EINVAL) { File.readlink(@file) } - assert_raise(Errno::ENOENT) { File.readlink(@nofile) } + return unless symlinkfile + assert_equal(regular_file, File.readlink(symlinkfile)) + assert_raise(Errno::EINVAL) { File.readlink(regular_file) } + assert_raise(Errno::EINVAL) { File.readlink(utf8_file) } + assert_raise(Errno::ENOENT) { File.readlink(nofile) } if fs = Encoding.find("filesystem") - assert_equal(fs, File.readlink(@symlinkfile).encoding) + assert_equal(fs, File.readlink(symlinkfile).encoding) end rescue NotImplementedError end def test_readlink_long_path - return unless @symlinkfile + return unless symlinkfile bug9157 = '[ruby-core:58592] [Bug #9157]' - assert_separately(["-", @symlinkfile, bug9157], <<-"end;") + assert_separately(["-", symlinkfile, bug9157], "#{<<~begin}#{<<~"end;"}") + begin symlinkfile, bug9157 = *ARGV 100.step(1000, 100) do |n| File.unlink(symlinkfile) @@ -414,42 +713,85 @@ class TestFileExhaustive < Test::Unit::TestCase end; end + if NTFS + 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? + assert_equal(@dir, File.readlink(nofile)) + end + + def test_realpath_mount_point + vol = IO.popen(["mountvol", DRIVE, "/l"], &:read).strip + Dir.mkdir(mnt = File.join(@dir, mntpnt = "mntpnt")) + system("mountvol", mntpnt, vol, chdir: @dir) + assert_equal(mnt, File.realpath(mnt)) + ensure + system("mountvol", mntpnt, "/d", chdir: @dir) + end + end + def test_unlink - assert_equal(1, File.unlink(@file)) - make_file("foo", @file) - assert_raise(Errno::ENOENT) { File.unlink(@nofile) } + assert_equal(1, File.unlink(regular_file)) + make_file("foo", regular_file) + + assert_equal(1, File.unlink(utf8_file)) + make_file("foo", utf8_file) + + assert_raise(Errno::ENOENT) { File.unlink(nofile) } end def test_rename - assert_equal(0, File.rename(@file, @nofile)) - assert_file.not_exist?(@file) - assert_file.exist?(@nofile) - assert_equal(0, File.rename(@nofile, @file)) - assert_raise(Errno::ENOENT) { File.rename(@nofile, @file) } + [regular_file, utf8_file].each do |file| + assert_equal(0, File.rename(file, nofile)) + assert_file.not_exist?(file) + assert_file.exist?(nofile) + assert_equal(0, File.rename(nofile, file)) + assert_raise(Errno::ENOENT) { File.rename(nofile, file) } + end end def test_umask - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM prev = File.umask(0777) assert_equal(0777, File.umask) - open(@nofile, "w") { } - assert_equal(0, File.stat(@nofile).mode % 01000) - File.unlink(@nofile) + open(nofile, "w") { } + assert_equal(0, File.stat(nofile).mode % 01000) + File.unlink(nofile) assert_equal(0777, File.umask(prev)) assert_raise(ArgumentError) { File.umask(0, 1, 2) } - end + end if POSIX def test_expand_path - assert_equal(@file, File.expand_path(File.basename(@file), File.dirname(@file))) - if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM - assert_equal(@file, File.expand_path(@file + " ")) - assert_equal(@file, File.expand_path(@file + ".")) - assert_equal(@file, File.expand_path(@file + "::$DATA")) + 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 + [regular_file, utf8_file].each do |file| + assert_equal(file, File.expand_path(file + " ")) + assert_equal(file, File.expand_path(file + ".")) + assert_equal(file, File.expand_path(file + "::$DATA")) + end assert_match(/\Ac:\//i, File.expand_path('c:'), '[ruby-core:31591]') assert_match(/\Ac:\//i, File.expand_path('c:foo', 'd:/bar')) 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/ + ["\u{feff}", *"\u{2000}"..."\u{2100}"].each do |c| + file = regular_file + c + full_path = File.expand_path(file) + mesg = proc {File.basename(full_path).dump} + begin + open(file) {} + rescue + # High Sierra's APFS cannot use filenames with undefined character + next if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs" + assert_equal(file, full_path, mesg) + else + assert_equal(regular_file, full_path, mesg) + end + end + end if DRIVE 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")) @@ -463,9 +805,9 @@ 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, bug9934) + assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934) path = File.expand_path("/a"*25) - assert_equal(path.bytesize+1, ObjectSpace.memsize_of(path), bug9934) + assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934) end def test_expand_path_encoding @@ -479,6 +821,8 @@ class TestFileExhaustive < Test::Unit::TestCase a = "#{drive}/\225\\\\" if File::ALT_SEPARATOR == '\\' [%W"cp437 #{drive}/\225", %W"cp932 #{drive}/\225\\"] + elsif File.directory?("#{@dir}/\\") + [%W"cp437 /\225", %W"cp932 /\225\\"] else [["cp437", a], ["cp932", a]] end.each do |cp, expected| @@ -524,7 +868,6 @@ class TestFileExhaustive < Test::Unit::TestCase ENV["HOMEDRIVE"] = nil ENV["HOMEPATH"] = nil ENV["USERPROFILE"] = nil - assert_raise(ArgumentError) { File.expand_path("~") } ENV["HOME"] = "~" assert_raise(ArgumentError, bug3630) { File.expand_path("~") } ENV["HOME"] = "." @@ -548,6 +891,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 @@ -580,9 +925,9 @@ class TestFileExhaustive < Test::Unit::TestCase def test_expand_path_remove_trailing_alternative_data - assert_equal File.join(@rootdir, "aaa"), File.expand_path("#{@rootdir}/aaa::$DATA") - assert_equal File.join(@rootdir, "aa:a"), File.expand_path("#{@rootdir}/aa:a:$DATA") - assert_equal File.join(@rootdir, "aaa:$DATA"), File.expand_path("#{@rootdir}/aaa:$DATA") + assert_equal File.join(rootdir, "aaa"), File.expand_path("#{rootdir}/aaa::$DATA") + assert_equal File.join(rootdir, "aa:a"), File.expand_path("#{rootdir}/aa:a:$DATA") + assert_equal File.join(rootdir, "aaa:$DATA"), File.expand_path("#{rootdir}/aaa:$DATA") end if DRIVE def test_expand_path_resolve_empty_string_current_directory @@ -626,20 +971,20 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(@dir, File.expand_path("", "#{@dir}")) assert_equal(File.join(@dir, "a"), File.expand_path("a", "#{@dir}")) assert_equal(File.join(@dir, "a"), File.expand_path("../a", "#{@dir}/xxx")) - assert_equal(@rootdir, File.expand_path(".", "#{@rootdir}")) + assert_equal(rootdir, File.expand_path(".", "#{rootdir}")) end def test_expand_path_ignores_supplied_dir_if_path_contains_a_drive_letter - assert_equal(@rootdir, File.expand_path(@rootdir, "D:/")) + assert_equal(rootdir, File.expand_path(rootdir, "D:/")) end if DRIVE def test_expand_path_removes_trailing_slashes_from_absolute_path - assert_equal(File.join(@rootdir, "foo"), File.expand_path("#{@rootdir}foo/")) - assert_equal(File.join(@rootdir, "foo.rb"), File.expand_path("#{@rootdir}foo.rb/")) + assert_equal(File.join(rootdir, "foo"), File.expand_path("#{rootdir}foo/")) + assert_equal(File.join(rootdir, "foo.rb"), File.expand_path("#{rootdir}foo.rb/")) end def test_expand_path_removes_trailing_spaces_from_absolute_path - assert_equal(File.join(@rootdir, "a"), File.expand_path("#{@rootdir}a ")) + assert_equal(File.join(rootdir, "a"), File.expand_path("#{rootdir}a ")) end if DRIVE def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_dir_s_drive @@ -791,27 +1136,64 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal('z:/bar/foo', File.expand_path('z:foo', '/bar'), bug10858) end if DRIVE + if /darwin/ =~ RUBY_PLATFORM and Encoding.find("filesystem") == Encoding::UTF_8 + def test_expand_path_compose + pp = Object.new.extend(Test::Unit::Assertions) + def pp.mu_pp(str) #:nodoc: + str.dump + end + + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + orig = %W"d\u{e9}tente x\u{304c 304e 3050 3052 3054}" + orig.each do |o| + Dir.mkdir(o) + n = Dir.chdir(o) {File.expand_path(".")} + pp.assert_equal(o, File.basename(n)) + end + end + end + end + end + def test_basename - assert_equal(File.basename(@file).sub(/\.test$/, ""), File.basename(@file, ".test")) + assert_equal(File.basename(regular_file).sub(/\.test$/, ""), File.basename(regular_file, ".test")) + assert_equal(File.basename(utf8_file).sub(/\.test$/, ""), File.basename(utf8_file, ".test")) assert_equal("", s = File.basename("")) - assert(!s.frozen?, '[ruby-core:24199]') + assert_not_predicate(s, :frozen?, '[ruby-core:24199]') assert_equal("foo", s = File.basename("foo")) - assert(!s.frozen?, '[ruby-core:24199]') + assert_not_predicate(s, :frozen?, '[ruby-core:24199]') assert_equal("foo", File.basename("foo", ".ext")) assert_equal("foo", File.basename("foo.ext", ".ext")) assert_equal("foo", File.basename("foo.ext", ".*")) - if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM - basename = File.basename(@file) - assert_equal(basename, File.basename(@file + " ")) - assert_equal(basename, File.basename(@file + ".")) - assert_equal(basename, File.basename(@file + "::$DATA")) - basename.chomp!(".test") - assert_equal(basename, File.basename(@file + " ", ".test")) - assert_equal(basename, File.basename(@file + ".", ".test")) - assert_equal(basename, File.basename(@file + "::$DATA", ".test")) - assert_equal(basename, File.basename(@file + " ", ".*")) - assert_equal(basename, File.basename(@file + ".", ".*")) - assert_equal(basename, File.basename(@file + "::$DATA", ".*")) + if NTFS + [regular_file, utf8_file].each do |file| + basename = File.basename(file) + assert_equal(basename, File.basename(file + " ")) + assert_equal(basename, File.basename(file + ".")) + assert_equal(basename, File.basename(file + "::$DATA")) + basename.chomp!(".test") + assert_equal(basename, File.basename(file + " ", ".test")) + assert_equal(basename, File.basename(file + ".", ".test")) + assert_equal(basename, File.basename(file + "::$DATA", ".test")) + assert_equal(basename, File.basename(file + " ", ".*")) + assert_equal(basename, File.basename(file + ".", ".*")) + assert_equal(basename, File.basename(file + "::$DATA", ".*")) + end + else + [regular_file, utf8_file].each do |file| + basename = File.basename(file) + assert_equal(basename + " ", File.basename(file + " ")) + assert_equal(basename + ".", File.basename(file + ".")) + assert_equal(basename + "::$DATA", File.basename(file + "::$DATA")) + assert_equal(basename + " ", File.basename(file + " ", ".test")) + assert_equal(basename + ".", File.basename(file + ".", ".test")) + assert_equal(basename + "::$DATA", File.basename(file + "::$DATA", ".test")) + assert_equal(basename, File.basename(file + ".", ".*")) + basename.chomp!(".test") + assert_equal(basename, File.basename(file + " ", ".*")) + assert_equal(basename, File.basename(file + "::$DATA", ".*")) + end end if File::ALT_SEPARATOR == '\\' a = "foo/\225\\\\" @@ -832,7 +1214,8 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_dirname - assert(@file.start_with?(File.dirname(@file))) + assert_equal(@dir, File.dirname(regular_file)) + assert_equal(@dir, File.dirname(utf8_file)) assert_equal(".", File.dirname("")) assert_incompatible_encoding {|d| File.dirname(d)} if File::ALT_SEPARATOR == '\\' @@ -844,12 +1227,13 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_extname - assert_equal(".test", File.extname(@file)) + assert_equal(".test", File.extname(regular_file)) + assert_equal(".test", File.extname(utf8_file)) prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."] infixes = ["", " ", "."] infixes2 = infixes + [".ext "] appendixes = [""] - if /cygwin|mingw|mswin|bccwin/ =~ RUBY_PLATFORM + if NTFS appendixes << " " << "." << "::$DATA" << "::$DATA.bar" end prefixes.each do |prefix| @@ -871,9 +1255,11 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_split - d, b = File.split(@file) - assert_equal(File.dirname(@file), d) - assert_equal(File.basename(@file), b) + [regular_file, utf8_file].each do |file| + d, b = File.split(file) + assert_equal(File.dirname(file), d) + assert_equal(File.basename(file), b) + end end def test_join @@ -915,43 +1301,118 @@ class TestFileExhaustive < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError, bug7168) {File.join(names)} end + def test_join_with_changed_separator + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + bug = '[ruby-core:79579] [Bug #13223]' + begin; + class File + remove_const :Separator + remove_const :SEPARATOR + end + GC.start + assert_equal("hello/world", File.join("hello", "world"), bug) + end; + end + def test_truncate - assert_equal(0, File.truncate(@file, 1)) - assert_file.exist?(@file) - assert_equal(1, File.size(@file)) - assert_equal(0, File.truncate(@file, 0)) - assert_file.exist?(@file) - assert_file.zero?(@file) - make_file("foo", @file) - assert_raise(Errno::ENOENT) { File.truncate(@nofile, 0) } - - f = File.new(@file, "w") - assert_equal(0, f.truncate(2)) - assert_file.exist?(@file) - assert_equal(2, File.size(@file)) - assert_equal(0, f.truncate(0)) - assert_file.exist?(@file) - assert_file.zero?(@file) - f.close - make_file("foo", @file) + [regular_file, utf8_file].each do |file| + assert_equal(0, File.truncate(file, 1)) + assert_file.exist?(file) + assert_equal(1, File.size(file)) + assert_equal(0, File.truncate(file, 0)) + assert_file.exist?(file) + assert_file.zero?(file) + make_file("foo", file) + assert_raise(Errno::ENOENT) { File.truncate(nofile, 0) } + + f = File.new(file, "w") + assert_equal(0, f.truncate(2)) + assert_file.exist?(file) + assert_equal(2, File.size(file)) + assert_equal(0, f.truncate(0)) + assert_file.exist?(file) + assert_file.zero?(file) + f.close + make_file("foo", file) + + assert_raise(IOError) { File.open(file) {|ff| ff.truncate(0)} } + end + rescue NotImplementedError + end - assert_raise(IOError) { File.open(@file) {|ff| ff.truncate(0)} } + def test_flock_exclusive + File.open(regular_file, "r+") do |f| + f.flock(File::LOCK_EX) + assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") + begin + open(ARGV[0], "r") do |f| + Timeout.timeout(0.1) do + assert(!f.flock(File::LOCK_SH|File::LOCK_NB)) + end + end + end; + assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") + begin + open(ARGV[0], "r") do |f| + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) do + f.flock(File::LOCK_SH) + end + end + end + end; + f.flock(File::LOCK_UN) + end rescue NotImplementedError end - def test_flock ## xxx - f = File.new(@file, "r+") - f.flock(File::LOCK_EX) - f.flock(File::LOCK_SH) - f.flock(File::LOCK_UN) - f.close + def test_flock_shared + File.open(regular_file, "r+") do |f| + f.flock(File::LOCK_SH) + assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") + begin + open(ARGV[0], "r") do |f| + Timeout.timeout(0.1) do + assert(f.flock(File::LOCK_SH)) + end + end + end; + assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}") + begin + open(ARGV[0], "r+") do |f| + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) do + f.flock(File::LOCK_EX) + end + end + end + end; + f.flock(File::LOCK_UN) + end rescue NotImplementedError end def test_test - sleep(@time - Time.now + 1.1) - make_file("foo", @file + "2") - [@dir, @file, @zerofile, @symlinkfile, @hardlinkfile].compact.each do |f| + fn1 = regular_file + hardlinkfile + sleep(1.1) + fn2 = fn1 + "2" + make_file("foo", fn2) + [ + @dir, + fn1, + zerofile, + notownedfile, + suidfile, + sgidfile, + stickyfile, + symlinkfile, + hardlinkfile, + chardev, + blockdev, + 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)) @@ -978,28 +1439,31 @@ class TestFileExhaustive < Test::Unit::TestCase assert_equal(File.executable_real?(f), test(?X, f)) assert_equal(File.zero?(f), test(?z, f)) end - assert_equal(false, test(?-, @dir, @file)) - assert_equal(true, test(?-, @file, @file)) - assert_equal(true, test(?=, @file, @file)) - assert_equal(false, test(?>, @file, @file)) - assert_equal(false, test(?<, @file, @file)) + assert_equal(false, test(?-, @dir, fn1)) + assert_equal(true, test(?-, fn1, fn1)) + assert_equal(true, test(?=, fn1, fn1)) + assert_equal(false, test(?>, fn1, fn1)) + assert_equal(false, test(?<, fn1, fn1)) unless /cygwin/ =~ RUBY_PLATFORM - assert_equal(false, test(?=, @file, @file + "2")) - assert_equal(false, test(?>, @file, @file + "2")) - assert_equal(true, test(?>, @file + "2", @file)) - assert_equal(true, test(?<, @file, @file + "2")) - assert_equal(false, test(?<, @file + "2", @file)) + assert_equal(false, test(?=, fn1, fn2)) + assert_equal(false, test(?>, fn1, fn2)) + assert_equal(true, test(?>, fn2, fn1)) + assert_equal(true, test(?<, fn1, fn2)) + assert_equal(false, test(?<, fn2, fn1)) end assert_raise(ArgumentError) { test } - assert_raise(Errno::ENOENT) { test(?A, @nofile) } + assert_raise(Errno::ENOENT) { test(?A, nofile) } assert_raise(ArgumentError) { test(?a) } assert_raise(ArgumentError) { test("\0".ord) } end def test_stat_init - sleep(@time - Time.now + 1.1) - make_file("foo", @file + "2") - fs1, fs2 = File::Stat.new(@file), File::Stat.new(@file + "2") + fn1 = regular_file + hardlinkfile + sleep(1.1) + fn2 = fn1 + "2" + make_file("foo", fn2) + fs1, fs2 = File::Stat.new(fn1), File::Stat.new(fn2) assert_nothing_raised do assert_equal(0, fs1 <=> fs1) assert_equal(-1, fs1 <=> fs2) @@ -1016,7 +1480,7 @@ class TestFileExhaustive < Test::Unit::TestCase 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) + assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) end assert_integer(fs1.uid) assert_integer(fs1.gid) @@ -1028,159 +1492,177 @@ class TestFileExhaustive < Test::Unit::TestCase assert_kind_of(Time, fs1.ctime) assert_kind_of(String, fs1.inspect) end - assert_raise(Errno::ENOENT) { File::Stat.new(@nofile) } - assert_kind_of(File::Stat, File::Stat.new(@file).dup) + assert_raise(Errno::ENOENT) { File::Stat.new(nofile) } + assert_kind_of(File::Stat, File::Stat.new(fn1).dup) assert_raise(TypeError) do - File::Stat.new(@file).instance_eval { initialize_copy(0) } + File::Stat.new(fn1).instance_eval { initialize_copy(0) } + end + end + + def test_stat_new_utf8 + assert_nothing_raised do + File::Stat.new(utf8_file) end end def test_stat_ftype assert_equal("directory", File::Stat.new(@dir).ftype) - assert_equal("file", File::Stat.new(@file).ftype) + assert_equal("file", File::Stat.new(regular_file).ftype) # File::Stat uses stat - assert_equal("file", File::Stat.new(@symlinkfile).ftype) if @symlinkfile - assert_equal("file", File::Stat.new(@hardlinkfile).ftype) if @hardlinkfile + assert_equal("file", File::Stat.new(symlinkfile).ftype) if symlinkfile + assert_equal("file", File::Stat.new(hardlinkfile).ftype) if hardlinkfile end def test_stat_directory_p - assert(File::Stat.new(@dir).directory?) - assert(!(File::Stat.new(@file).directory?)) + assert_predicate(File::Stat.new(@dir), :directory?) + assert_not_predicate(File::Stat.new(regular_file), :directory?) end - def test_stat_pipe_p ## xxx - assert(!(File::Stat.new(@dir).pipe?)) - assert(!(File::Stat.new(@file).pipe?)) + def test_stat_pipe_p + assert_not_predicate(File::Stat.new(@dir), :pipe?) + assert_not_predicate(File::Stat.new(regular_file), :pipe?) + assert_predicate(File::Stat.new(fifo), :pipe?) if fifo + IO.pipe {|r, w| + assert_predicate(r.stat, :pipe?) + assert_predicate(w.stat, :pipe?) + } end def test_stat_symlink_p - assert(!(File::Stat.new(@dir).symlink?)) - assert(!(File::Stat.new(@file).symlink?)) + assert_not_predicate(File::Stat.new(@dir), :symlink?) + assert_not_predicate(File::Stat.new(regular_file), :symlink?) # File::Stat uses stat - assert(!(File::Stat.new(@symlinkfile).symlink?)) if @symlinkfile - assert(!(File::Stat.new(@hardlinkfile).symlink?)) if @hardlinkfile + assert_not_predicate(File::Stat.new(symlinkfile), :symlink?) if symlinkfile + assert_not_predicate(File::Stat.new(hardlinkfile), :symlink?) if hardlinkfile end - def test_stat_socket_p ## xxx - assert(!(File::Stat.new(@dir).socket?)) - assert(!(File::Stat.new(@file).socket?)) + def test_stat_socket_p + assert_not_predicate(File::Stat.new(@dir), :socket?) + assert_not_predicate(File::Stat.new(regular_file), :socket?) + assert_predicate(File::Stat.new(socket), :socket?) if socket end - def test_stat_blockdev_p ## xxx - assert(!(File::Stat.new(@dir).blockdev?)) - assert(!(File::Stat.new(@file).blockdev?)) + def test_stat_blockdev_p + assert_not_predicate(File::Stat.new(@dir), :blockdev?) + assert_not_predicate(File::Stat.new(regular_file), :blockdev?) + assert_predicate(File::Stat.new(blockdev), :blockdev?) if blockdev end - def test_stat_chardev_p ## xxx - assert(!(File::Stat.new(@dir).chardev?)) - assert(!(File::Stat.new(@file).chardev?)) + 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 end def test_stat_readable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0200, @file) - assert(!(File::Stat.new(@file).readable?)) - File.chmod(0600, @file) - assert(File::Stat.new(@file).readable?) - end + File.chmod(0200, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :readable?) + File.chmod(0600, regular_file) + assert_predicate(File::Stat.new(regular_file), :readable?) + end if POSIX def test_stat_readable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0200, @file) - assert(!(File::Stat.new(@file).readable_real?)) - File.chmod(0600, @file) - assert(File::Stat.new(@file).readable_real?) - end + File.chmod(0200, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :readable_real?) + File.chmod(0600, regular_file) + assert_predicate(File::Stat.new(regular_file), :readable_real?) + end if POSIX def test_stat_world_readable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0006, @file) - assert(File::Stat.new(@file).world_readable?) - File.chmod(0060, @file) - assert(!(File::Stat.new(@file).world_readable?)) - File.chmod(0600, @file) - assert(!(File::Stat.new(@file).world_readable?)) - end + File.chmod(0006, regular_file) + assert_predicate(File::Stat.new(regular_file), :world_readable?) + File.chmod(0060, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :world_readable?) + File.chmod(0600, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :world_readable?) + end if POSIX def test_stat_writable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0400, @file) - assert(!(File::Stat.new(@file).writable?)) - File.chmod(0600, @file) - assert(File::Stat.new(@file).writable?) - end + File.chmod(0400, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :writable?) + File.chmod(0600, regular_file) + assert_predicate(File::Stat.new(regular_file), :writable?) + end if POSIX def test_stat_writable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM return if Process.euid == 0 - File.chmod(0400, @file) - assert(!(File::Stat.new(@file).writable_real?)) - File.chmod(0600, @file) - assert(File::Stat.new(@file).writable_real?) - end + File.chmod(0400, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :writable_real?) + File.chmod(0600, regular_file) + assert_predicate(File::Stat.new(regular_file), :writable_real?) + end if POSIX def test_stat_world_writable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0006, @file) - assert(File::Stat.new(@file).world_writable?) - File.chmod(0060, @file) - assert(!(File::Stat.new(@file).world_writable?)) - File.chmod(0600, @file) - assert(!(File::Stat.new(@file).world_writable?)) - end + File.chmod(0006, regular_file) + assert_predicate(File::Stat.new(regular_file), :world_writable?) + File.chmod(0060, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :world_writable?) + File.chmod(0600, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :world_writable?) + end if POSIX def test_stat_executable_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0100, @file) - assert(File::Stat.new(@file).executable?) - File.chmod(0600, @file) - assert(!(File::Stat.new(@file).executable?)) - end + File.chmod(0100, regular_file) + assert_predicate(File::Stat.new(regular_file), :executable?) + File.chmod(0600, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :executable?) + end if POSIX def test_stat_executable_real_p - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - File.chmod(0100, @file) - assert(File::Stat.new(@file).executable_real?) - File.chmod(0600, @file) - assert(!(File::Stat.new(@file).executable_real?)) - end + File.chmod(0100, regular_file) + assert_predicate(File::Stat.new(regular_file), :executable_real?) + File.chmod(0600, regular_file) + assert_not_predicate(File::Stat.new(regular_file), :executable_real?) + end if POSIX def test_stat_file_p - assert(!(File::Stat.new(@dir).file?)) - assert(File::Stat.new(@file).file?) + assert_not_predicate(File::Stat.new(@dir), :file?) + assert_predicate(File::Stat.new(regular_file), :file?) end def test_stat_zero_p assert_nothing_raised { File::Stat.new(@dir).zero? } - assert(!(File::Stat.new(@file).zero?)) - assert(File::Stat.new(@zerofile).zero?) + assert_not_predicate(File::Stat.new(regular_file), :zero?) + assert_predicate(File::Stat.new(zerofile), :zero?) end def test_stat_size_p assert_nothing_raised { File::Stat.new(@dir).size? } - assert_equal(3, File::Stat.new(@file).size?) - assert(!(File::Stat.new(@zerofile).size?)) + assert_equal(3, File::Stat.new(regular_file).size?) + assert_not_predicate(File::Stat.new(zerofile), :size?) end - def test_stat_owned_p ## xxx - return if /cygwin|mswin|bccwin|mingw|emx/ =~ RUBY_PLATFORM - assert(File::Stat.new(@file).owned?) - assert(File::Stat.new(@file).grpowned?) + def test_stat_owned_p + assert_predicate(File::Stat.new(regular_file), :owned?) + assert_not_predicate(File::Stat.new(notownedfile), :owned?) if notownedfile + end if POSIX + + def test_stat_grpowned_p ## xxx + assert_predicate(File::Stat.new(regular_file), :grpowned?) + end if POSIX + + def test_stat_suid + assert_not_predicate(File::Stat.new(regular_file), :setuid?) + assert_predicate(File::Stat.new(suidfile), :setuid?) if suidfile end - def test_stat_suid_sgid_sticky ## xxx - assert(!(File::Stat.new(@file).setuid?)) - assert(!(File::Stat.new(@file).setgid?)) - assert(!(File::Stat.new(@file).sticky?)) + def test_stat_sgid + assert_not_predicate(File::Stat.new(regular_file), :setgid?) + assert_predicate(File::Stat.new(sgidfile), :setgid?) if sgidfile + end + + def test_stat_sticky + assert_not_predicate(File::Stat.new(regular_file), :sticky?) + assert_predicate(File::Stat.new(stickyfile), :sticky?) if stickyfile end def test_stat_size assert_integer(File::Stat.new(@dir).size) - assert_equal(3, File::Stat.new(@file).size) - assert_equal(0, File::Stat.new(@zerofile).size) + assert_equal(3, File::Stat.new(regular_file).size) + assert_equal(0, File::Stat.new(zerofile).size) end def test_stat_special_file @@ -1195,10 +1677,12 @@ class TestFileExhaustive < Test::Unit::TestCase end def test_size - assert_equal(3, File.open(@file) {|f| f.size }) - File.open(@file, "a") do |f| - f.write("bar") - assert_equal(6, f.size) + [regular_file, utf8_file].each do |file| + assert_equal(3, File.open(file) {|f| f.size }) + File.open(file, "a") do |f| + f.write("bar") + assert_equal(6, f.size) + end end end diff --git a/test/ruby/test_fixnum.rb b/test/ruby/test_fixnum.rb index c86cba3b7c..bd18067dda 100644 --- a/test/ruby/test_fixnum.rb +++ b/test/ruby/test_fixnum.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestFixnum < Test::Unit::TestCase @@ -36,10 +37,14 @@ class TestFixnum < Test::Unit::TestCase def test_plus assert_equal(0x40000000, 0x3fffffff+1) + assert_equal(0x7ffffffe, 0x3fffffff+0x3fffffff) assert_equal(0x4000000000000000, 0x3fffffffffffffff+1) + assert_equal(0x7ffffffffffffffe, 0x3fffffffffffffff+0x3fffffffffffffff) assert_equal(-0x40000001, (-0x40000000)+(-1)) assert_equal(-0x4000000000000001, (-0x4000000000000000)+(-1)) + assert_equal(-0x7ffffffe, (-0x3fffffff)+(-0x3fffffff)) assert_equal(-0x80000000, (-0x40000000)+(-0x40000000)) + assert_equal(-0x8000000000000000, (-0x4000000000000000)+(-0x4000000000000000)) end def test_sub @@ -48,6 +53,8 @@ class TestFixnum < Test::Unit::TestCase assert_equal(-0x40000001, (-0x40000000)-1) assert_equal(-0x4000000000000001, (-0x4000000000000000)-1) assert_equal(-0x80000000, (-0x40000000)-0x40000000) + assert_equal(0x7fffffffffffffff, 0x3fffffffffffffff-(-0x4000000000000000)) + assert_equal(-0x8000000000000000, -0x4000000000000000-0x4000000000000000) end def test_mult @@ -74,6 +81,7 @@ class TestFixnum < Test::Unit::TestCase assert_equal(-0x4000000000000001, 0xc000000000000003/(-3)) assert_equal(0x40000000, (-0x40000000)/(-1), "[ruby-dev:31210]") assert_equal(0x4000000000000000, (-0x4000000000000000)/(-1)) + assert_raise(FloatDomainError) { 2.div(Float::NAN).nan? } end def test_mod @@ -101,6 +109,7 @@ class TestFixnum < Test::Unit::TestCase assert_equal(r, a.modulo(b)) } } + assert_raise(FloatDomainError) { 2.divmod(Float::NAN) } end def test_not @@ -205,6 +214,7 @@ class TestFixnum < Test::Unit::TestCase assert_equal(0, 1 <=> 1) assert_equal(-1, 1 <=> 4294967296) + assert_equal(-1, 1 <=> 1 << 100) assert_equal(0, 1 <=> 1.0) assert_nil(1 <=> nil) @@ -296,7 +306,7 @@ class TestFixnum < Test::Unit::TestCase big = 1 << 66 assert_eql 1, 1 ** -big , bug5715 assert_eql 1, (-1) ** -big , bug5715 - assert_eql -1, (-1) ** -(big+1) , bug5715 + assert_eql (-1), (-1) ** -(big+1), bug5715 end def test_power_of_0 @@ -305,4 +315,38 @@ class TestFixnum < Test::Unit::TestCase assert_raise(ZeroDivisionError, bug5713) { 0 ** -big } assert_raise(ZeroDivisionError, bug5713) { 0 ** Rational(-2,3) } end + + def test_remainder + assert_equal(1, 5.remainder(4)) + assert_predicate(4.remainder(Float::NAN), :nan?) + end + + def test_zero_p + assert_predicate(0, :zero?) + assert_not_predicate(1, :zero?) + end + + def test_positive_p + assert_predicate(1, :positive?) + assert_not_predicate(0, :positive?) + assert_not_predicate(-1, :positive?) + end + + def test_negative_p + assert_predicate(-1, :negative?) + assert_not_predicate(0, :negative?) + assert_not_predicate(1, :negative?) + end + + def test_finite_p + assert_predicate(1, :finite?) + assert_predicate(0, :finite?) + assert_predicate(-1, :finite?) + end + + def test_infinite_p + assert_nil(1.infinite?) + assert_nil(0.infinite?) + assert_nil(-1.infinite?) + end end diff --git a/test/ruby/test_flip.rb b/test/ruby/test_flip.rb index fc62d93ae6..b8b05aed6d 100644 --- a/test/ruby/test_flip.rb +++ b/test/ruby/test_flip.rb @@ -1,7 +1,17 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestFlip < Test::Unit::TestCase + def test_flip_flop + assert_equal [4,5], (1..9).select {|n| true if (n==4)..(n==5)} + assert_equal [4,5], (1..9).select {|n| true if (n==4)...(n==5)} + assert_equal [2], (1..9).select {|n| true if (n==2)..(n%2).zero?} + assert_equal [2,3,4], (1..9).select {|n| true if (n==2)...(n%2).zero?} + assert_equal [4,5,7,8], (1..9).select {|n| true if (n==4)...(n==5) or (n==7)...(n==8)} + assert_equal [nil, 2, 3, 4, nil], (1..5).map {|x| x if (x==2..x==4)} + assert_equal [1, nil, nil, nil, 5], (1..5).map {|x| x if !(x==2..x==4)} + end + def test_hidden_key bug6899 = '[ruby-core:47253]' foo = "foor" @@ -9,6 +19,7 @@ class TestFlip < Test::Unit::TestCase assert_nothing_raised(NotImplementedError, bug6899) do 2000.times {eval %[(foo..bar) ? 1 : 2]} end + foo = bar end def test_shared_eval @@ -39,4 +50,25 @@ class TestFlip < Test::Unit::TestCase assert_equal(expected, v1, mesg) assert_equal(expected, v2, mesg) end + + def test_input_line_number_range + bug12947 = '[ruby-core:78162] [Bug #12947]' + ary = b1 = b2 = nil + EnvUtil.suppress_warning do + b1 = eval("proc {|i| i if 2..4}") + b2 = eval("proc {|i| if 2..4; i; end}") + end + IO.pipe {|r, w| + th = Thread.start {(1..5).each {|i| w.puts i};w.close} + ary = r.map {|i| b1.call(i.chomp)} + th.join + } + assert_equal([nil, "2", "3", "4", nil], ary, bug12947) + IO.pipe {|r, w| + th = Thread.start {(1..5).each {|i| w.puts i};w.close} + ary = r.map {|i| b2.call(i.chomp)} + th.join + } + assert_equal([nil, "2", "3", "4", nil], ary, bug12947) + end end diff --git a/test/ruby/test_float.rb b/test/ruby/test_float.rb index a959f60a69..7fabfd351d 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestFloat < Test::Unit::TestCase include EnvUtil @@ -16,6 +16,8 @@ class TestFloat < Test::Unit::TestCase assert_in_delta(13.4 % 1, 0.4, 0.0001) assert_equal(36893488147419111424, 36893488147419107329.0.to_i) + assert_equal(1185151044158398820374743613440, + 1.1851510441583988e+30.to_i) end def nan_test(x,y) @@ -161,6 +163,14 @@ 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 end def test_divmod @@ -168,6 +178,8 @@ class TestFloat < Test::Unit::TestCase assert_equal([-3, -0.5], 11.5.divmod(-4)) assert_equal([-3, 0.5], (-11.5).divmod(4)) assert_equal([2, -3.5], (-11.5).divmod(-4)) + assert_raise(FloatDomainError) { Float::NAN.divmod(2) } + assert_raise(FloatDomainError) { Float::INFINITY.divmod(2) } end def test_div @@ -175,6 +187,9 @@ class TestFloat < Test::Unit::TestCase assert_equal(-3, 11.5.div(-4)) assert_equal(-3, (-11.5).div(4)) assert_equal(2, (-11.5).div(-4)) + assert_raise(FloatDomainError) { 11.5.div(Float::NAN).nan? } + assert_raise(FloatDomainError) { Float::NAN.div(2).nan? } + assert_raise(FloatDomainError) { Float::NAN.div(11.5).nan? } end def test_modulo @@ -189,6 +204,8 @@ class TestFloat < Test::Unit::TestCase assert_equal(3.5, 11.5.remainder(-4)) assert_equal(-3.5, (-11.5).remainder(4)) assert_equal(-3.5, (-11.5).remainder(-4)) + assert_predicate(Float::NAN.remainder(4), :nan?) + assert_predicate(4.remainder(Float::NAN), :nan?) end def test_to_s @@ -215,6 +232,8 @@ class TestFloat < Test::Unit::TestCase assert_equal(4.0, 2.0.send(:+, 2)) assert_equal(4.0, 2.0.send(:+, (2**32).coerce(2).first)) assert_equal(4.0, 2.0.send(:+, 2.0)) + assert_equal(Float::INFINITY, 2.0.send(:+, Float::INFINITY)) + assert_predicate(2.0.send(:+, Float::NAN), :nan?) assert_raise(TypeError) { 2.0.send(:+, nil) } end @@ -222,6 +241,8 @@ class TestFloat < Test::Unit::TestCase assert_equal(0.0, 2.0.send(:-, 2)) assert_equal(0.0, 2.0.send(:-, (2**32).coerce(2).first)) assert_equal(0.0, 2.0.send(:-, 2.0)) + assert_equal(-Float::INFINITY, 2.0.send(:-, Float::INFINITY)) + assert_predicate(2.0.send(:-, Float::NAN), :nan?) assert_raise(TypeError) { 2.0.send(:-, nil) } end @@ -229,6 +250,7 @@ class TestFloat < Test::Unit::TestCase assert_equal(4.0, 2.0.send(:*, 2)) assert_equal(4.0, 2.0.send(:*, (2**32).coerce(2).first)) assert_equal(4.0, 2.0.send(:*, 2.0)) + assert_equal(Float::INFINITY, 2.0.send(:*, Float::INFINITY)) assert_raise(TypeError) { 2.0.send(:*, nil) } end @@ -236,6 +258,7 @@ class TestFloat < Test::Unit::TestCase assert_equal(1.0, 2.0.send(:/, 2)) assert_equal(1.0, 2.0.send(:/, (2**32).coerce(2).first)) assert_equal(1.0, 2.0.send(:/, 2.0)) + assert_equal(0.0, 2.0.send(:/, Float::INFINITY)) assert_raise(TypeError) { 2.0.send(:/, nil) } end @@ -248,14 +271,20 @@ class TestFloat < Test::Unit::TestCase def test_modulo3 bug6048 = '[ruby-core:42726]' - assert_equal(4.2, 4.2.send(:%, Float::INFINITY)) - assert_equal(4.2, 4.2 % Float::INFINITY) + assert_equal(4.2, 4.2.send(:%, Float::INFINITY), bug6048) + assert_equal(4.2, 4.2 % Float::INFINITY, bug6048) assert_is_minus_zero(-0.0 % 4.2) assert_is_minus_zero(-0.0.send :%, 4.2) - assert_raise(ZeroDivisionError) { 4.2.send(:%, 0.0) } - assert_raise(ZeroDivisionError) { 4.2 % 0.0 } - assert_raise(ZeroDivisionError) { 42.send(:%, 0) } - assert_raise(ZeroDivisionError) { 42 % 0 } + assert_raise(ZeroDivisionError, bug6048) { 4.2.send(:%, 0.0) } + assert_raise(ZeroDivisionError, bug6048) { 4.2 % 0.0 } + assert_raise(ZeroDivisionError, bug6048) { 42.send(:%, 0) } + assert_raise(ZeroDivisionError, bug6048) { 42 % 0 } + end + + def test_modulo4 + assert_predicate((0.0).modulo(Float::NAN), :nan?) + assert_predicate((1.0).modulo(Float::NAN), :nan?) + assert_predicate(Float::INFINITY.modulo(1), :nan?) end def test_divmod2 @@ -338,6 +367,30 @@ class TestFloat < Test::Unit::TestCase assert_not_predicate(1.0, :zero?) end + def test_positive_p + assert_predicate(+1.0, :positive?) + assert_not_predicate(+0.0, :positive?) + assert_not_predicate(-0.0, :positive?) + assert_not_predicate(-1.0, :positive?) + assert_predicate(+(0.0.next_float), :positive?) + assert_not_predicate(-(0.0.next_float), :positive?) + assert_predicate(Float::INFINITY, :positive?) + assert_not_predicate(-Float::INFINITY, :positive?) + assert_not_predicate(Float::NAN, :positive?) + end + + def test_negative_p + assert_predicate(-1.0, :negative?) + assert_not_predicate(-0.0, :negative?) + assert_not_predicate(+0.0, :negative?) + assert_not_predicate(+1.0, :negative?) + assert_predicate(-(0.0.next_float), :negative?) + assert_not_predicate(+(0.0.next_float), :negative?) + assert_predicate(-Float::INFINITY, :negative?) + assert_not_predicate(Float::INFINITY, :negative?) + assert_not_predicate(Float::NAN, :negative?) + end + def test_infinite_p inf = Float::INFINITY assert_equal(1, inf.infinite?) @@ -385,6 +438,11 @@ class TestFloat < Test::Unit::TestCase assert_equal(1.110, 1.111.round(2)) assert_equal(11110.0, 11111.1.round(-1)) assert_equal(11100.0, 11111.1.round(-2)) + assert_equal(-1.100, -1.111.round(1)) + assert_equal(-1.110, -1.111.round(2)) + assert_equal(-11110.0, -11111.1.round(-1)) + assert_equal(-11100.0, -11111.1.round(-2)) + assert_equal(0, 11111.1.round(-5)) assert_equal(10**300, 1.1e300.round(-300)) assert_equal(-10**300, -1.1e300.round(-300)) @@ -399,6 +457,93 @@ class TestFloat < Test::Unit::TestCase assert_raise(TypeError) {1.0.round(nil)} def (prec = Object.new).to_int; 2; end assert_equal(1.0, 0.998.round(prec)) + + assert_equal(+5.02, +5.015.round(2)) + assert_equal(-5.02, -5.015.round(2)) + assert_equal(+1.26, +1.255.round(2)) + assert_equal(-1.26, -1.255.round(2)) + end + + def test_floor_with_precision + assert_equal(+0.0, +0.001.floor(1)) + assert_equal(-0.1, -0.001.floor(1)) + assert_equal(1.100, 1.111.floor(1)) + assert_equal(1.110, 1.111.floor(2)) + assert_equal(11110, 11119.9.floor(-1)) + assert_equal(11100, 11100.0.floor(-2)) + assert_equal(11100, 11199.9.floor(-2)) + assert_equal(-1.200, -1.111.floor(1)) + assert_equal(-1.120, -1.111.floor(2)) + assert_equal(-11120, -11119.9.floor(-1)) + assert_equal(-11100, -11100.0.floor(-2)) + assert_equal(-11200, -11199.9.floor(-2)) + assert_equal(0, 11111.1.floor(-5)) + + assert_equal(10**300, 1.1e300.floor(-300)) + assert_equal(-2*10**300, -1.1e300.floor(-300)) + assert_equal(1.0e-300, 1.1e-300.floor(300)) + assert_equal(-2.0e-300, -1.1e-300.floor(300)) + + assert_equal(42.0, 42.0.floor(308)) + assert_equal(1.0e307, 1.0e307.floor(2)) + + assert_raise(TypeError) {1.0.floor("4")} + assert_raise(TypeError) {1.0.floor(nil)} + def (prec = Object.new).to_int; 2; end + assert_equal(0.99, 0.998.floor(prec)) + end + + def test_ceil_with_precision + assert_equal(+0.1, +0.001.ceil(1)) + assert_equal(-0.0, -0.001.ceil(1)) + assert_equal(1.200, 1.111.ceil(1)) + assert_equal(1.120, 1.111.ceil(2)) + assert_equal(11120, 11111.1.ceil(-1)) + assert_equal(11200, 11111.1.ceil(-2)) + assert_equal(-1.100, -1.111.ceil(1)) + assert_equal(-1.110, -1.111.ceil(2)) + assert_equal(-11110, -11111.1.ceil(-1)) + assert_equal(-11100, -11111.1.ceil(-2)) + assert_equal(100000, 11111.1.ceil(-5)) + + assert_equal(2*10**300, 1.1e300.ceil(-300)) + assert_equal(-10**300, -1.1e300.ceil(-300)) + assert_equal(2.0e-300, 1.1e-300.ceil(300)) + assert_equal(-1.0e-300, -1.1e-300.ceil(300)) + + assert_equal(42.0, 42.0.ceil(308)) + assert_equal(1.0e307, 1.0e307.ceil(2)) + + assert_raise(TypeError) {1.0.ceil("4")} + assert_raise(TypeError) {1.0.ceil(nil)} + def (prec = Object.new).to_int; 2; end + assert_equal(0.99, 0.981.ceil(prec)) + end + + def test_truncate_with_precision + assert_equal(1.100, 1.111.truncate(1)) + assert_equal(1.110, 1.111.truncate(2)) + assert_equal(11110, 11119.9.truncate(-1)) + assert_equal(11100, 11100.0.truncate(-2)) + assert_equal(11100, 11199.9.truncate(-2)) + assert_equal(-1.100, -1.111.truncate(1)) + assert_equal(-1.110, -1.111.truncate(2)) + assert_equal(-11110, -11111.1.truncate(-1)) + assert_equal(-11100, -11111.1.truncate(-2)) + assert_equal(0, 11111.1.truncate(-5)) + + assert_equal(10**300, 1.1e300.truncate(-300)) + assert_equal(-10**300, -1.1e300.truncate(-300)) + assert_equal(1.0e-300, 1.1e-300.truncate(300)) + assert_equal(-1.0e-300, -1.1e-300.truncate(300)) + + assert_equal(42.0, 42.0.truncate(308)) + assert_equal(1.0e307, 1.0e307.truncate(2)) + + assert_raise(TypeError) {1.0.truncate("4")} + assert_raise(TypeError) {1.0.truncate(nil)} + def (prec = Object.new).to_int; 2; end + assert_equal(0.99, 0.998.truncate(prec)) end VS = [ @@ -528,9 +673,108 @@ class TestFloat < Test::Unit::TestCase } end + def test_round_half_even + assert_equal(12.0, 12.5.round(half: :even)) + assert_equal(14.0, 13.5.round(half: :even)) + + assert_equal(2.2, 2.15.round(1, half: :even)) + assert_equal(2.2, 2.25.round(1, half: :even)) + assert_equal(2.4, 2.35.round(1, half: :even)) + + assert_equal(-2.2, -2.15.round(1, half: :even)) + assert_equal(-2.2, -2.25.round(1, half: :even)) + assert_equal(-2.4, -2.35.round(1, half: :even)) + + assert_equal(7.1364, 7.13645.round(4, half: :even)) + assert_equal(7.1365, 7.1364501.round(4, half: :even)) + assert_equal(7.1364, 7.1364499.round(4, half: :even)) + + assert_equal(-7.1364, -7.13645.round(4, half: :even)) + assert_equal(-7.1365, -7.1364501.round(4, half: :even)) + assert_equal(-7.1364, -7.1364499.round(4, half: :even)) + end + + def test_round_half_up + assert_equal(13.0, 12.5.round(half: :up)) + assert_equal(14.0, 13.5.round(half: :up)) + + assert_equal(2.2, 2.15.round(1, half: :up)) + assert_equal(2.3, 2.25.round(1, half: :up)) + assert_equal(2.4, 2.35.round(1, half: :up)) + + assert_equal(-2.2, -2.15.round(1, half: :up)) + assert_equal(-2.3, -2.25.round(1, half: :up)) + assert_equal(-2.4, -2.35.round(1, half: :up)) + + assert_equal(7.1365, 7.13645.round(4, half: :up)) + assert_equal(7.1365, 7.1364501.round(4, half: :up)) + assert_equal(7.1364, 7.1364499.round(4, half: :up)) + + assert_equal(-7.1365, -7.13645.round(4, half: :up)) + assert_equal(-7.1365, -7.1364501.round(4, half: :up)) + assert_equal(-7.1364, -7.1364499.round(4, half: :up)) + end + + def test_round_half_down + assert_equal(12.0, 12.5.round(half: :down)) + assert_equal(13.0, 13.5.round(half: :down)) + + assert_equal(2.1, 2.15.round(1, half: :down)) + assert_equal(2.2, 2.25.round(1, half: :down)) + assert_equal(2.3, 2.35.round(1, half: :down)) + + assert_equal(-2.1, -2.15.round(1, half: :down)) + assert_equal(-2.2, -2.25.round(1, half: :down)) + assert_equal(-2.3, -2.35.round(1, half: :down)) + + assert_equal(7.1364, 7.13645.round(4, half: :down)) + assert_equal(7.1365, 7.1364501.round(4, half: :down)) + assert_equal(7.1364, 7.1364499.round(4, half: :down)) + + assert_equal(-7.1364, -7.13645.round(4, half: :down)) + assert_equal(-7.1365, -7.1364501.round(4, half: :down)) + assert_equal(-7.1364, -7.1364499.round(4, half: :down)) + end + + def test_round_half_nil + assert_equal(13.0, 12.5.round(half: nil)) + assert_equal(14.0, 13.5.round(half: nil)) + + assert_equal(2.2, 2.15.round(1, half: nil)) + assert_equal(2.3, 2.25.round(1, half: nil)) + assert_equal(2.4, 2.35.round(1, half: nil)) + + assert_equal(-2.2, -2.15.round(1, half: nil)) + assert_equal(-2.3, -2.25.round(1, half: nil)) + assert_equal(-2.4, -2.35.round(1, half: nil)) + + assert_equal(7.1365, 7.13645.round(4, half: nil)) + assert_equal(7.1365, 7.1364501.round(4, half: nil)) + assert_equal(7.1364, 7.1364499.round(4, half: nil)) + + assert_equal(-7.1365, -7.13645.round(4, half: nil)) + assert_equal(-7.1365, -7.1364501.round(4, half: nil)) + assert_equal(-7.1364, -7.1364499.round(4, half: nil)) + end + + def test_round_half_invalid + assert_raise_with_message(ArgumentError, /Object/) { + 1.0.round(half: Object) + } + assert_raise_with_message(ArgumentError, /xxx/) { + 1.0.round(half: "\0xxx") + } + end + def test_Float assert_in_delta(0.125, Float("0.1_2_5"), 0.00001) assert_in_delta(0.125, "0.1_2_5__".to_f, 0.00001) + assert_in_delta(0.0, "0_.125".to_f, 0.00001) + assert_in_delta(0.0, "0._125".to_f, 0.00001) + assert_in_delta(0.1, "0.1__2_5".to_f, 0.00001) + assert_in_delta(0.1, "0.1_e10".to_f, 0.00001) + assert_in_delta(0.1, "0.1e_10".to_f, 0.00001) + assert_in_delta(1.0, "0.1e1__0".to_f, 0.00001) assert_equal(1, suppress_warning {Float(([1] * 10000).join)}.infinite?) assert_not_predicate(Float(([1] * 10000).join("_")), :infinite?) # is it really OK? assert_raise(ArgumentError) { Float("1.0\x001") } @@ -558,7 +802,7 @@ class TestFloat < Test::Unit::TestCase end def test_num2dbl - assert_raise(ArgumentError) do + assert_raise(ArgumentError, "comparison of String with 0 failed") do 1.0.step(2.0, "0.5") {} end assert_raise(TypeError) do @@ -620,6 +864,41 @@ class TestFloat < Test::Unit::TestCase end; end + def test_next_float + smallest = 0.0.next_float + assert_equal(-Float::MAX, (-Float::INFINITY).next_float) + assert_operator(-Float::MAX, :<, (-Float::MAX).next_float) + assert_equal(Float::EPSILON/2, (-1.0).next_float + 1.0) + assert_operator(0.0, :<, smallest) + assert_operator([0.0, smallest], :include?, smallest/2) + assert_equal(Float::EPSILON, 1.0.next_float - 1.0) + assert_equal(Float::INFINITY, Float::MAX.next_float) + assert_equal(Float::INFINITY, Float::INFINITY.next_float) + assert_predicate(Float::NAN.next_float, :nan?) + end + + def test_prev_float + smallest = 0.0.next_float + assert_equal(-Float::INFINITY, (-Float::INFINITY).prev_float) + assert_equal(-Float::INFINITY, (-Float::MAX).prev_float) + assert_equal(-Float::EPSILON, (-1.0).prev_float + 1.0) + assert_equal(-smallest, 0.0.prev_float) + assert_operator([0.0, 0.0.prev_float], :include?, 0.0.prev_float/2) + assert_equal(-Float::EPSILON/2, 1.0.prev_float - 1.0) + assert_operator(Float::MAX, :>, Float::MAX.prev_float) + assert_equal(Float::MAX, Float::INFINITY.prev_float) + assert_predicate(Float::NAN.prev_float, :nan?) + end + + def test_next_prev_float_zero + z = 0.0.next_float.prev_float + assert_equal(0.0, z) + assert_equal(Float::INFINITY, 1.0/z) + z = 0.0.prev_float.next_float + assert_equal(0.0, z) + assert_equal(-Float::INFINITY, 1.0/z) + end + def test_hash_0 bug10979 = '[ruby-core:68541] [Bug #10979]' assert_equal(+0.0.hash, -0.0.hash) @@ -627,4 +906,21 @@ class TestFloat < Test::Unit::TestCase h = {0.0 => bug10979} assert_equal(bug10979, h[-0.0]) end + + def test_aliased_quo_recursion + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class Float + $VERBOSE = nil + alias / quo + end + assert_raise(NameError) do + begin + 1.0/2.0 + rescue SystemStackError => e + raise SystemStackError, e.message + end + end + end; + end end diff --git a/test/ruby/test_fnmatch.rb b/test/ruby/test_fnmatch.rb index 15e5d79e35..30250b5a19 100644 --- a/test/ruby/test_fnmatch.rb +++ b/test/ruby/test_fnmatch.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestFnmatch < Test::Unit::TestCase @@ -129,4 +129,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_gc.rb b/test/ruby/test_gc.rb index 402d560bff..b3528ddacd 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -1,7 +1,6 @@ +# frozen_string_literal: false require 'test/unit' -require_relative "envutil" - class TestGc < Test::Unit::TestCase class S def initialize(a) @@ -14,10 +13,10 @@ class TestGc < Test::Unit::TestCase GC.stress = false assert_nothing_raised do + tmp = nil 1.upto(10000) { tmp = [0,1,2,3,4,5,6,7,8,9] } - tmp = nil end l=nil 100000.times { @@ -35,6 +34,10 @@ class TestGc < Test::Unit::TestCase GC.stress = prev_stress end + def use_rgengc? + GC::OPTS.include? 'USE_RGENGC'.freeze + end + def test_enable_disable GC.enable assert_equal(false, GC.enable) @@ -49,6 +52,8 @@ class TestGc < Test::Unit::TestCase end def test_start_full_mark + return unless use_rgengc? + GC.start(full_mark: false) assert_nil GC.latest_gc_info(:major_by) @@ -85,14 +90,18 @@ class TestGc < Test::Unit::TestCase GC.start GC.stat(stat) ObjectSpace.count_objects(count) - assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slot]) - assert_equal(count[:FREE], stat[:heap_free_slot]) + 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) - assert_equal(count[:FREE], stat[:heap_free_slot]) + assert_equal(count[:FREE], stat[:heap_free_slots]) + end + + def test_stat_argument + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.stat(:"\u{30eb 30d3 30fc}")} end def test_stat_single @@ -101,18 +110,34 @@ class TestGc < Test::Unit::TestCase assert_raise(ArgumentError){ GC.stat(:invalid) } end + def test_stat_constraints + 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 + assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots] + assert_equal stat[:heap_live_slots], stat[:total_allocated_objects] - stat[:total_freed_objects] - stat[:heap_final_slots] + assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_tomb_pages] + + if use_rgengc? + assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count] + end + end + def test_latest_gc_info + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom' GC.start - GC.stat[:heap_free_slot].times{ "a" + "b" } + count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] + count.times{ "a" + "b" } assert_equal :newobj, GC.latest_gc_info[:gc_by] + eom GC.start - assert_equal :nofree, GC.latest_gc_info[:major_by] + 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.stress = true - assert_equal :stress, GC.latest_gc_info[:major_by] + assert_equal :force, GC.latest_gc_info[:major_by] ensure GC.stress = false end @@ -123,7 +148,8 @@ class TestGc < Test::Unit::TestCase assert_not_empty info assert_equal info[:gc_by], GC.latest_gc_info(:gc_by) - assert_raises(ArgumentError){ GC.latest_gc_info(:invalid) } + assert_raise(ArgumentError){ GC.latest_gc_info(:invalid) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")} end def test_singleton_method @@ -184,8 +210,9 @@ class TestGc < Test::Unit::TestCase } assert_normal_exit("exit", "", :child_env => env) assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0\.9/, "") + # 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'], //, "") + 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'], '', [], @@ -203,15 +230,17 @@ class TestGc < Test::Unit::TestCase assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_MAX=16000000/, "") assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=2.0/, "") - env = { - "RUBY_GC_OLDMALLOC_LIMIT" => "60000000", - "RUBY_GC_OLDMALLOC_LIMIT_MAX" => "160000000", - "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR" => "2.0" - } - assert_normal_exit("exit", "", :child_env => env) - assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT=6000000/, "") - assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "") - assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "") + if use_rgengc? + env = { + "RUBY_GC_OLDMALLOC_LIMIT" => "60000000", + "RUBY_GC_OLDMALLOC_LIMIT_MAX" => "160000000", + "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR" => "2.0" + } + assert_normal_exit("exit", "", :child_env => env) + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT=6000000/, "") + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "") + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "") + end end def test_profiler_enabled @@ -224,7 +253,7 @@ class TestGc < Test::Unit::TestCase end def test_profiler_clear - assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom' + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30 GC::Profiler.enable GC.start @@ -258,28 +287,31 @@ class TestGc < Test::Unit::TestCase def test_expand_heap assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom' GC.start - base_length = GC.stat[:heap_eden_page_length] + base_length = GC.stat[:heap_eden_pages] + (base_length * 500).times{ 'a' } + GC.start + base_length = GC.stat[:heap_eden_pages] (base_length * 500).times{ 'a' } GC.start - assert_in_delta base_length, (v = GC.stat[:heap_eden_page_length]), 1, - "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_page_length]: #{v})" + assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r, + "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})" a = [] (base_length * 500).times{ a << 'a'; nil } GC.start - assert_operator base_length, :<, GC.stat[:heap_eden_page_length] + 1 + assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1 eom end def test_gc_internals - assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_OBJ_LIMIT] + assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] end def test_sweep_in_finalizer bug9205 = '[ruby-core:58833] [Bug #9205]' 2.times do - assert_ruby_status([], <<-'end;', bug9205, timeout: 30) + assert_ruby_status([], <<-'end;', bug9205, timeout: 60) raise_proc = proc do |id| GC.start end @@ -292,7 +324,7 @@ class TestGc < Test::Unit::TestCase def test_exception_in_finalizer bug9168 = '[ruby-core:58652] [Bug #9168]' - assert_normal_exit(<<-'end;', bug9168) + assert_normal_exit(<<-'end;', bug9168, encoding: Encoding::ASCII_8BIT) raise_proc = proc {raise} 10000.times do ObjectSpace.define_finalizer(Object.new, raise_proc) @@ -303,7 +335,120 @@ class TestGc < Test::Unit::TestCase end; end + def test_interrupt_in_finalizer + bug10595 = '[ruby-core:66825] [Bug #10595]' + src = <<-'end;' + Signal.trap(:INT, 'DEFAULT') + pid = $$ + Thread.start do + 10.times { + sleep 0.1 + Process.kill("INT", pid) rescue break + } + end + f = proc {1000.times {}} + loop do + ObjectSpace.define_finalizer(Object.new, f) + end + end; + out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result| + break result + end + unless /mswin|mingw/ =~ RUBY_PLATFORM + assert_equal("INT", Signal.signame(status.termsig), bug10595) + end + assert_match(/Interrupt/, err.first, proc {err.join("\n")}) + assert_empty(out) + end + def test_verify_internal_consistency assert_nil(GC.verify_internal_consistency) end + + def test_gc_stress_on_realloc + assert_normal_exit(<<-'end;', '[Bug #9859]') + class C + def initialize + @a = nil + @b = nil + @c = nil + @d = nil + @e = nil + @f = nil + end + end + + GC.stress = true + C.new + end; + end + + def test_gc_stress_at_startup + skip # it'll be fixed later + assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true) + end + + def test_gc_disabled_start + begin + disabled = GC.disable + c = GC.count + GC.start + assert_equal 1, GC.count - c + ensure + GC.enable unless disabled + end + end + + def test_vm_object + assert_normal_exit <<-'end', '[Bug #12583]' + ObjectSpace.each_object{|o| o.singleton_class rescue 0} + ObjectSpace.each_object{|o| case o when Module then o.instance_methods end} + end + end + + def test_exception_in_finalizer_procs + result = [] + c1 = proc do + result << :c1 + raise + end + c2 = proc do + result << :c2 + raise + end + tap { + tap { + 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 + + def test_exception_in_finalizer_method + @result = [] + def self.c1(x) + @result << :c1 + raise + end + def self.c2(x) + @result << :c2 + raise + end + tap { + tap { + obj = Object.new + ObjectSpace.define_finalizer(obj, method(:c1)) + ObjectSpace.define_finalizer(obj, method(:c2)) + obj = nil + } + } + GC.start + skip "finalizers did not get run" if @result.empty? + assert_equal([:c1, :c2], @result) + end end diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 69783981d5..bdcf022668 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -1,7 +1,7 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require 'continuation' -require_relative 'envutil' +EnvUtil.suppress_warning {require 'continuation'} class TestHash < Test::Unit::TestCase @@ -133,6 +133,50 @@ class TestHash < Test::Unit::TestCase assert_equal(100, h['a']) assert_equal(200, h['b']) assert_nil(h['c']) + + h = @cls["a", 100, "b", 200] + assert_equal(100, h['a']) + assert_equal(200, h['b']) + assert_nil(h['c']) + + h = @cls[[["a", 100], ["b", 200]]] + assert_equal(100, h['a']) + assert_equal(200, h['b']) + assert_nil(h['c']) + + h = @cls[[["a", 100], ["b"], ["c", 300]]] + assert_equal(100, h['a']) + 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']) + end + + def test_s_AREF_duplicated_key + alist = [["a", 100], ["b", 200], ["a", 300], ["a", 400]] + h = @cls[alist] + 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')) + end + + def test_s_AREF_frozen_key_id + key = "a".freeze + h = @cls[key, 100] + assert_equal(100, h['a']) + assert_same(key, *h.keys) + end + + def test_s_AREF_key_tampering + key = "a".dup + h = @cls[key, 100] + key.upcase! + assert_equal(100, h['a']) end def test_s_new @@ -145,7 +189,14 @@ class TestHash < Test::Unit::TestCase assert_instance_of(@cls, h) assert_equal('default', h.default) assert_equal('default', h['spurious']) + end + def test_try_convert + assert_equal({1=>2}, Hash.try_convert({1=>2})) + assert_equal(nil, Hash.try_convert("1=>2")) + o = Object.new + def o.to_hash; {3=>4} end + assert_equal({3=>4}, Hash.try_convert(o)) end def test_AREF # '[]' @@ -215,6 +266,33 @@ class TestHash < Test::Unit::TestCase assert_equal(256, h[z]) end + def test_AREF_fstring_key + h = {"abc" => 1} + before = GC.stat(:total_allocated_objects) + 5.times{ h["abc"] } + assert_equal before, GC.stat(:total_allocated_objects) + end + + def test_ASET_fstring_key + a, b = {}, {} + assert_equal 1, a["abc"] = 1 + assert_equal 1, b["abc"] = 1 + assert_same a.keys[0], b.keys[0] + end + + def test_hash_aset_fstring_identity + h = {}.compare_by_identity + h['abc'] = 1 + h['abc'] = 2 + assert_equal 2, h.size, '[ruby-core:78783] [Bug #12855]' + end + + def test_hash_aref_fstring_identity + h = {}.compare_by_identity + h['abc'] = 1 + assert_nil h['abc'], '[ruby-core:78783] [Bug #12855]' + end + def test_NEWHASH_fstring_key a = {"ABC" => :t} b = {"ABC" => :t} @@ -222,6 +300,17 @@ class TestHash < Test::Unit::TestCase 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? + end + def test_EQUAL # '==' h1 = @cls[ "a" => 1, "c" => 2 ] h2 = @cls[ "a" => 1, "c" => 2, 7 => 35 ] @@ -332,6 +421,15 @@ class TestHash < Test::Unit::TestCase assert_equal({1=>2,3=>4,5=>6}, h.keep_if{true}) end + def test_compact + h = @cls[a: 1, b: nil, c: false, d: true, e: nil] + assert_equal({a: 1, c: false, d: true}, h.compact) + assert_equal({a: 1, b: nil, c: false, d: true, e: nil}, h) + assert_same(h, h.compact!) + assert_equal({a: 1, c: false, d: true}, h) + assert_nil(h.compact!) + end + def test_dup for taint in [ false, true ] for frozen in [ false, true ] @@ -433,6 +531,8 @@ class TestHash < Test::Unit::TestCase e = assert_raise(KeyError) { @h.fetch('gumby'*20) } assert_match(/key not found: "gumbygumby/, e.message) assert_match(/\.\.\.\z/, e.message) + assert_same(@h, e.receiver) + assert_equal('gumby'*20, e.key) end def test_key2? @@ -485,6 +585,23 @@ class TestHash < Test::Unit::TestCase assert_equal ['three', nil, 'one', 'nil'], res end + def test_fetch_values + res = @h.fetch_values + assert_equal(0, res.length) + + res = @h.fetch_values(3, 2, 1, nil) + assert_equal(4, res.length) + assert_equal %w( three two one nil ), res + + e = assert_raise KeyError do + @h.fetch_values(3, 'invalid') + end + assert_same(@h, e.receiver) + assert_equal('invalid', e.key) + + res = @h.fetch_values(3, 'invalid') { |k| k.upcase } + assert_equal %w( three INVALID ), res + end def test_invert h = @h.invert @@ -564,19 +681,6 @@ class TestHash < Test::Unit::TestCase assert_equal(h3, h.reject {|k,v| v }) assert_equal(base, h) - unless RUBY_VERSION >= "2.2.0" - # [ruby-core:59154] [Bug #9223] - if @cls == Hash - assert_empty(EnvUtil.verbose_warning {h.reject {false}}) - bug9275 = '[ruby-core:59254] [Bug #9275]' - c = Class.new(Hash) - assert_empty(EnvUtil.verbose_warning {c.new.reject {false}}, bug9275) - else - assert_match(/extra states/, EnvUtil.verbose_warning {h.reject {false}}) - end - return - end - h.instance_variable_set(:@foo, :foo) h.default = 42 h.taint @@ -718,6 +822,28 @@ class TestHash < Test::Unit::TestCase assert_instance_of(Hash, h) end + def test_to_h_instance_variable + @h.instance_variable_set(:@x, 42) + h = @h.to_h + if @cls == Hash + assert_equal(42, h.instance_variable_get(:@x)) + else + assert_not_send([h, :instance_variable_defined?, :@x]) + end + end + + def test_to_h_default_value + @h.default = :foo + h = @h.to_h + assert_equal(:foo, h.default) + end + + def test_to_h_default_proc + @h.default_proc = ->(_,k) {"nope#{k}"} + h = @h.to_h + assert_equal("nope42", h[42]) + end + def test_nil_to_h h = nil.to_h assert_equal({}, h) @@ -771,7 +897,7 @@ class TestHash < Test::Unit::TestCase assert_equal([], expected - vals) end - def test_intialize_wrong_arguments + def test_initialize_wrong_arguments assert_raise(ArgumentError) do Hash.new(0) { } end @@ -780,7 +906,7 @@ 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 Fixnum at 1 /) {@cls[[[1, 2], 3]]} + assert_warning(/wrong element type Integer at 1 /) {@cls[[[1, 2], 3]]} 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]) @@ -805,7 +931,7 @@ class TestHash < Test::Unit::TestCase assert_equal("foobarbaz", h.default_proc.call("foo", "bar")) assert_nil(h.default_proc = nil) assert_nil(h.default_proc) - h.default_proc = ->(h, k){ true } + h.default_proc = ->(_,_){ true } assert_equal(true, h[:nope]) h = @cls[] assert_nil(h.default_proc) @@ -880,6 +1006,14 @@ class TestHash < Test::Unit::TestCase assert_equal(nil, h.select!{true}) end + def test_slice + h = @cls[1=>2,3=>4,5=>6] + assert_equal({1=>2, 3=>4}, h.slice(1, 3)) + assert_equal({}, h.slice(7)) + assert_equal({}, h.slice) + assert_equal({}, {}.slice) + end + def test_clear2 assert_equal({}, @cls[1=>2,3=>4,5=>6].clear) h = @cls[1=>2,3=>4,5=>6] @@ -897,8 +1031,8 @@ 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_size2 @@ -965,7 +1099,7 @@ class TestHash < Test::Unit::TestCase h = @cls[] h.compare_by_identity h["a"] = 1 - h["a"] = 2 + h["a".dup] = 2 assert_equal(["a",1], h.assoc("a")) end @@ -984,7 +1118,12 @@ class TestHash < Test::Unit::TestCase assert_equal([1, "one", 2, 2, "two", 3, 3, ["three"]], a.flatten(2)) assert_equal([1, "one", 2, 2, "two", 3, 3, "three"], a.flatten(3)) assert_equal([1, "one", 2, 2, "two", 3, 3, "three"], a.flatten(-1)) - assert_raise(TypeError){ a.flatten(Object) } + assert_raise(TypeError){ a.flatten(nil) } + end + + def test_flatten_arity + a = @cls[1=> "one", 2 => [2,"two"], 3 => [3, ["three"]]] + assert_raise(ArgumentError){ a.flatten(1, 2) } end def test_callcc @@ -1030,7 +1169,7 @@ class TestHash < Test::Unit::TestCase assert_nothing_raised(RuntimeError, bug9105) do h=@cls[] cnt=0 - c = callcc {|c|c} + c = callcc {|cc|cc} h[cnt] = true h.each{|i| cnt+=1 @@ -1128,7 +1267,7 @@ class TestHash < Test::Unit::TestCase assert_equal({o=>1}.hash, @cls[o=>1].hash) end - def test_hash_poped + def test_hash_popped assert_nothing_raised { eval("a = 1; @cls[a => a]; a") } end @@ -1217,7 +1356,9 @@ class TestHash < Test::Unit::TestCase end end - def test_exception_in_rehash + def test_exception_in_rehash_memory_leak + return unless @cls == Hash + bug9187 = '[ruby-core:58728] [Bug #9187]' prepare = <<-EOS @@ -1232,10 +1373,10 @@ class TestHash < Test::Unit::TestCase return 0 end end + h = {Foo.new => true} EOS code = <<-EOS - h = {Foo.new => true} 10_0000.times do h.rehash rescue nil end @@ -1245,7 +1386,7 @@ class TestHash < Test::Unit::TestCase assert_no_memory_leak([], prepare, code, bug9187) end - def test_wrapper_of_special_const + def test_wrapper bug9381 = '[ruby-core:59638] [Bug #9381]' wrapper = Class.new do @@ -1265,6 +1406,8 @@ class TestHash < Test::Unit::TestCase bad = [ 5, true, false, nil, 0.0, 1.72723e-77, + :foo, "dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym, + "str", ].select do |x| hash = {x => bug9381} hash[wrapper.new(x)] != bug9381 @@ -1272,6 +1415,202 @@ class TestHash < Test::Unit::TestCase assert_empty(bad, bug9381) end + def assert_hash_random(obj, dump = obj.inspect) + a = [obj.hash.to_s] + 3.times { + assert_in_out_err(["-e", "print (#{dump}).hash"], "") do |r, e| + a += r + assert_equal([], e) + end + } + assert_not_equal([obj.hash.to_s], a.uniq) + assert_operator(a.uniq.size, :>, 2, proc {a.inspect}) + end + + def test_string_hash_random + assert_hash_random('abc') + end + + def test_symbol_hash_random + assert_hash_random(:-) + assert_hash_random(:foo) + assert_hash_random("dsym_#{self.object_id.to_s(16)}_#{Time.now.to_i.to_s(16)}".to_sym) + end + + def test_integer_hash_random + assert_hash_random(0) + assert_hash_random(+1) + assert_hash_random(-1) + assert_hash_random(+(1<<100)) + assert_hash_random(-(1<<100)) + end + + def test_float_hash_random + assert_hash_random(0.0) + assert_hash_random(+1.0) + assert_hash_random(-1.0) + assert_hash_random(1.72723e-77) + assert_hash_random(Float::INFINITY, "Float::INFINITY") + end + + def test_label_syntax + return unless @cls == Hash + + feature4935 = '[ruby-core:37553] [Feature #4935]' + x = 'world' + hash = assert_nothing_raised(SyntaxError, feature4935) do + break eval(%q({foo: 1, "foo-bar": 2, "hello-#{x}": 3, 'hello-#{x}': 4, 'bar': {}})) + end + assert_equal({:foo => 1, :'foo-bar' => 2, :'hello-world' => 3, :'hello-#{x}' => 4, :bar => {}}, hash, feature4935) + x = x + end + + def test_dig + h = @cls[a: @cls[b: [1, 2, 3]], c: 4] + assert_equal(1, h.dig(:a, :b, 0)) + assert_nil(h.dig(:b, 1)) + assert_raise(TypeError) {h.dig(:c, 1)} + o = Object.new + def o.dig(*args) + {dug: args} + end + h[:d] = o + assert_equal({dug: [:foo, :bar]}, h.dig(:d, :foo, :bar)) + end + + def test_dig_with_respond_to + bug12030 = '[ruby-core:73556] [Bug #12030]' + o = Object.new + def o.respond_to?(*args) + super + end + assert_raise(TypeError, bug12030) {{foo: o}.dig(:foo, :foo)} + end + + def test_cmp + h1 = {a:1, b:2} + h2 = {a:1, b:2, c:3} + + assert_operator(h1, :<=, h1) + assert_operator(h1, :<=, h2) + assert_not_operator(h2, :<=, h1) + assert_operator(h2, :<=, h2) + + assert_operator(h1, :>=, h1) + assert_not_operator(h1, :>=, h2) + assert_operator(h2, :>=, h1) + assert_operator(h2, :>=, h2) + + assert_not_operator(h1, :<, h1) + assert_operator(h1, :<, h2) + assert_not_operator(h2, :<, h1) + assert_not_operator(h2, :<, h2) + + assert_not_operator(h1, :>, h1) + assert_not_operator(h1, :>, h2) + assert_operator(h2, :>, h1) + assert_not_operator(h2, :>, h2) + end + + def test_cmp_samekeys + h1 = {a:1} + h2 = {a:2} + + assert_operator(h1, :<=, h1) + assert_not_operator(h1, :<=, h2) + assert_not_operator(h2, :<=, h1) + assert_operator(h2, :<=, h2) + + assert_operator(h1, :>=, h1) + assert_not_operator(h1, :>=, h2) + assert_not_operator(h2, :>=, h1) + assert_operator(h2, :>=, h2) + + assert_not_operator(h1, :<, h1) + assert_not_operator(h1, :<, h2) + assert_not_operator(h2, :<, h1) + assert_not_operator(h2, :<, h2) + + assert_not_operator(h1, :>, h1) + assert_not_operator(h1, :>, h2) + assert_not_operator(h2, :>, h1) + assert_not_operator(h2, :>, h2) + end + + def test_to_proc + h = { + 1 => 10, + 2 => 20, + 3 => 30, + } + + assert_equal([10, 20, 30], [1, 2, 3].map(&h)) + end + + def test_transform_keys + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_keys {|k| :"#{k}!" } + assert_equal({a: 1, b: 2, c: 3}, x) + assert_equal({a!: 1, b!: 2, c!: 3}, y) + + enum = x.transform_keys + assert_equal(x.size, enum.size) + assert_instance_of(Enumerator, enum) + + y = x.transform_keys.with_index {|k, i| "#{k}.#{i}" } + assert_equal(%w(a.0 b.1 c.2), y.keys) + end + + def test_transform_keys_bang + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_keys! {|k| :"#{k}!" } + assert_equal({a!: 1, b!: 2, c!: 3}, x) + assert_same(x, y) + + enum = x.transform_keys! + assert_equal(x.size, enum.size) + assert_instance_of(Enumerator, enum) + + 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[true => :a, false => :b] + x.transform_keys! {|k| !k } + assert_equal([false, :a, true, :b], x.flatten) + end + + def test_transform_values + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_values {|v| v ** 2 } + assert_equal([1, 4, 9], y.values_at(:a, :b, :c)) + assert_not_same(x, y) + + 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_bang + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_values! {|v| v ** 2 } + assert_equal([1, 4, 9], y.values_at(:a, :b, :c)) + assert_same(x, y) + + 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)) + 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 + class TestSubHash < TestHash class SubHash < Hash def reject(*) diff --git a/test/ruby/test_ifunless.rb b/test/ruby/test_ifunless.rb index e144ff8efd..d533e155bc 100644 --- a/test/ruby/test_ifunless.rb +++ b/test/ruby/test_ifunless.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestIfunless < Test::Unit::TestCase diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index 3dbf365a7c..1b485760e3 100644 --- a/test/ruby/test_integer.rb +++ b/test/ruby/test_integer.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestInteger < Test::Unit::TestCase @@ -97,6 +98,22 @@ class TestInteger < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-32be"))} assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-32le"))} assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("iso-2022-jp"))} + + assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")} + + obj = Struct.new(:s).new(%w[42 not-an-integer]) + def obj.to_str; s.shift; end + assert_equal(42, Integer(obj, 10)) + + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class Float + undef to_int + def to_int; raise "conversion failed"; end + end + assert_equal (1 << 100), Integer((1 << 100).to_f) + assert_equal 1, Integer(1.0) + end; end def test_int_p @@ -104,42 +121,8 @@ class TestInteger < Test::Unit::TestCase assert_predicate(1, :integer?) end - def test_odd_p_even_p - Fixnum.class_eval do - alias odd_bak odd? - alias even_bak even? - remove_method :odd?, :even? - end - - assert_predicate(1, :odd?) - assert_not_predicate(2, :odd?) - assert_not_predicate(1, :even?) - assert_predicate(2, :even?) - - ensure - Fixnum.class_eval do - alias odd? odd_bak - alias even? even_bak - remove_method :odd_bak, :even_bak - end - end - def test_succ assert_equal(2, 1.send(:succ)) - - Fixnum.class_eval do - alias succ_bak succ - remove_method :succ - end - - assert_equal(2, 1.succ) - assert_equal(4294967297, 4294967296.succ) - - ensure - Fixnum.class_eval do - alias succ succ_bak - remove_method :succ_bak - end end def test_chr @@ -184,61 +167,238 @@ class TestInteger < Test::Unit::TestCase end end + def assert_int_equal(expected, result, mesg = nil) + assert_kind_of(Integer, result, mesg) + assert_equal(expected, result, mesg) + end + + def assert_float_equal(expected, result, mesg = nil) + assert_kind_of(Float, result, mesg) + assert_equal(expected, result, mesg) + end + def test_round - assert_equal(11111, 11111.round) - assert_equal(Fixnum, 11111.round.class) - assert_equal(11111, 11111.round(0)) - assert_equal(Fixnum, 11111.round(0).class) + assert_int_equal(11111, 11111.round) + assert_int_equal(11111, 11111.round(0)) + + assert_int_equal(11111, 11111.round(1)) + assert_int_equal(11111, 11111.round(2)) + + assert_int_equal(11110, 11111.round(-1)) + assert_int_equal(11100, 11111.round(-2)) + assert_int_equal(+200, +249.round(-2)) + assert_int_equal(+300, +250.round(-2)) + assert_int_equal(-200, -249.round(-2)) + assert_int_equal(+200, +249.round(-2, half: :even)) + assert_int_equal(+200, +250.round(-2, half: :even)) + assert_int_equal(+300, +349.round(-2, half: :even)) + assert_int_equal(+400, +350.round(-2, half: :even)) + assert_int_equal(+200, +249.round(-2, half: :up)) + assert_int_equal(+300, +250.round(-2, half: :up)) + assert_int_equal(+300, +349.round(-2, half: :up)) + assert_int_equal(+400, +350.round(-2, half: :up)) + assert_int_equal(+200, +249.round(-2, half: :down)) + assert_int_equal(+200, +250.round(-2, half: :down)) + assert_int_equal(+300, +349.round(-2, half: :down)) + assert_int_equal(+300, +350.round(-2, half: :down)) + assert_int_equal(-300, -250.round(-2)) + assert_int_equal(-200, -249.round(-2, half: :even)) + assert_int_equal(-200, -250.round(-2, half: :even)) + assert_int_equal(-300, -349.round(-2, half: :even)) + assert_int_equal(-400, -350.round(-2, half: :even)) + assert_int_equal(-200, -249.round(-2, half: :up)) + assert_int_equal(-300, -250.round(-2, half: :up)) + assert_int_equal(-300, -349.round(-2, half: :up)) + assert_int_equal(-400, -350.round(-2, half: :up)) + assert_int_equal(-200, -249.round(-2, half: :down)) + assert_int_equal(-200, -250.round(-2, half: :down)) + assert_int_equal(-300, -349.round(-2, half: :down)) + assert_int_equal(-300, -350.round(-2, half: :down)) + assert_int_equal(+30 * 10**70, (+25 * 10**70).round(-71)) + assert_int_equal(-30 * 10**70, (-25 * 10**70).round(-71)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71)) + assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71)) + assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71)) + assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71)) + assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71)) + assert_int_equal(+20 * 10**70, (+25 * 10**70).round(-71, half: :even)) + assert_int_equal(-20 * 10**70, (-25 * 10**70).round(-71, half: :even)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71, half: :even)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71, half: :even)) + assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71, half: :even)) + assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71, half: :even)) + assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71, half: :even)) + assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71, half: :even)) + assert_int_equal(+30 * 10**70, (+25 * 10**70).round(-71, half: :up)) + assert_int_equal(-30 * 10**70, (-25 * 10**70).round(-71, half: :up)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71, half: :up)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71, half: :up)) + assert_int_equal(+40 * 10**70, (+35 * 10**70).round(-71, half: :up)) + assert_int_equal(-40 * 10**70, (-35 * 10**70).round(-71, half: :up)) + assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71, half: :up)) + assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71, half: :up)) + assert_int_equal(+20 * 10**70, (+25 * 10**70).round(-71, half: :down)) + assert_int_equal(-20 * 10**70, (-25 * 10**70).round(-71, half: :down)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).round(-71, half: :down)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).round(-71, half: :down)) + assert_int_equal(+30 * 10**70, (+35 * 10**70).round(-71, half: :down)) + assert_int_equal(-30 * 10**70, (-35 * 10**70).round(-71, half: :down)) + assert_int_equal(+30 * 10**70, (+35 * 10**70 - 1).round(-71, half: :down)) + assert_int_equal(-30 * 10**70, (-35 * 10**70 + 1).round(-71, half: :down)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1110, 1111_1111_1111_1111_1111_1111_1111_1111.round(-1)) + assert_int_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.round(1)) + assert_int_equal(10**400, (10**400).round(1)) + end - assert_equal(11111.0, 11111.round(1)) - assert_equal(Float, 11111.round(1).class) - assert_equal(11111.0, 11111.round(2)) - assert_equal(Float, 11111.round(2).class) + def test_floor + assert_int_equal(11111, 11111.floor) + assert_int_equal(11111, 11111.floor(0)) + + assert_int_equal(11111, 11111.floor(1)) + assert_int_equal(11111, 11111.floor(2)) + + assert_int_equal(11110, 11110.floor(-1)) + assert_int_equal(11110, 11119.floor(-1)) + assert_int_equal(11100, 11100.floor(-2)) + assert_int_equal(11100, 11199.floor(-2)) + assert_int_equal(0, 11111.floor(-5)) + assert_int_equal(+200, +299.floor(-2)) + assert_int_equal(+300, +300.floor(-2)) + assert_int_equal(-300, -299.floor(-2)) + assert_int_equal(-300, -300.floor(-2)) + assert_int_equal(+20 * 10**70, (+25 * 10**70).floor(-71)) + assert_int_equal(-30 * 10**70, (-25 * 10**70).floor(-71)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).floor(-71)) + assert_int_equal(-30 * 10**70, (-25 * 10**70 + 1).floor(-71)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1110, 1111_1111_1111_1111_1111_1111_1111_1111.floor(-1)) + assert_int_equal(-1111_1111_1111_1111_1111_1111_1111_1120, (-1111_1111_1111_1111_1111_1111_1111_1111).floor(-1)) + + 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)) + end - assert_equal(11110, 11111.round(-1)) - assert_equal(Fixnum, 11111.round(-1).class) - assert_equal(11100, 11111.round(-2)) - assert_equal(Fixnum, 11111.round(-2).class) + def test_ceil + assert_int_equal(11111, 11111.ceil) + assert_int_equal(11111, 11111.ceil(0)) + + assert_int_equal(11111, 11111.ceil(1)) + assert_int_equal(11111, 11111.ceil(2)) + + assert_int_equal(11110, 11110.ceil(-1)) + assert_int_equal(11120, 11119.ceil(-1)) + assert_int_equal(11200, 11101.ceil(-2)) + assert_int_equal(11200, 11200.ceil(-2)) + assert_int_equal(100000, 11111.ceil(-5)) + assert_int_equal(300, 299.ceil(-2)) + assert_int_equal(300, 300.ceil(-2)) + assert_int_equal(-200, -299.ceil(-2)) + assert_int_equal(-300, -300.ceil(-2)) + assert_int_equal(+30 * 10**70, (+25 * 10**70).ceil(-71)) + assert_int_equal(-20 * 10**70, (-25 * 10**70).ceil(-71)) + assert_int_equal(+30 * 10**70, (+25 * 10**70 - 1).ceil(-71)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).ceil(-71)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1120, 1111_1111_1111_1111_1111_1111_1111_1111.ceil(-1)) + assert_int_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).ceil(-1)) + + 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)) + end - assert_equal(1111_1111_1111_1111_1111_1111_1111_1110, 1111_1111_1111_1111_1111_1111_1111_1111.round(-1)) - assert_equal(Bignum, 1111_1111_1111_1111_1111_1111_1111_1111.round(-1).class) - assert_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1)) - assert_equal(Bignum, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1).class) + def test_truncate + assert_int_equal(11111, 11111.truncate) + assert_int_equal(11111, 11111.truncate(0)) + + assert_int_equal(11111, 11111.truncate(1)) + assert_int_equal(11111, 11111.truncate(2)) + + assert_int_equal(11110, 11110.truncate(-1)) + assert_int_equal(11110, 11119.truncate(-1)) + assert_int_equal(11100, 11100.truncate(-2)) + assert_int_equal(11100, 11199.truncate(-2)) + assert_int_equal(0, 11111.truncate(-5)) + assert_int_equal(+200, +299.truncate(-2)) + assert_int_equal(+300, +300.truncate(-2)) + assert_int_equal(-200, -299.truncate(-2)) + assert_int_equal(-300, -300.truncate(-2)) + assert_int_equal(+20 * 10**70, (+25 * 10**70).truncate(-71)) + assert_int_equal(-20 * 10**70, (-25 * 10**70).truncate(-71)) + assert_int_equal(+20 * 10**70, (+25 * 10**70 - 1).truncate(-71)) + assert_int_equal(-20 * 10**70, (-25 * 10**70 + 1).truncate(-71)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1110, 1111_1111_1111_1111_1111_1111_1111_1111.truncate(-1)) + assert_int_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).truncate(-1)) + + assert_int_equal(1111_1111_1111_1111_1111_1111_1111_1111, 1111_1111_1111_1111_1111_1111_1111_1111.truncate(1)) + assert_int_equal(10**400, (10**400).truncate(1)) end - def test_bitwise_and_with_integer_mimic_object - def (obj = Object.new).to_int - 10 + MimicInteger = Struct.new(:to_int) + module CoercionToInt + def coerce(other) + [other, to_int] end - assert_raise(TypeError, '[ruby-core:39491]') { 3 & obj } + end - def obj.coerce(other) - [other, 10] - end + def test_bitwise_and_with_integer_mimic_object + obj = MimicInteger.new(10) + assert_raise(TypeError, '[ruby-core:39491]') { 3 & obj } + obj.extend(CoercionToInt) assert_equal(3 & 10, 3 & obj) end def test_bitwise_or_with_integer_mimic_object - def (obj = Object.new).to_int - 10 - end + obj = MimicInteger.new(10) assert_raise(TypeError, '[ruby-core:39491]') { 3 | obj } - - def obj.coerce(other) - [other, 10] - end + obj.extend(CoercionToInt) assert_equal(3 | 10, 3 | obj) end def test_bitwise_xor_with_integer_mimic_object - def (obj = Object.new).to_int - 10 - end + obj = MimicInteger.new(10) assert_raise(TypeError, '[ruby-core:39491]') { 3 ^ obj } + obj.extend(CoercionToInt) + assert_equal(3 ^ 10, 3 ^ obj) + end - def obj.coerce(other) - [other, 10] + module CoercionToSelf + def coerce(other) + [self.class.new(other), self] end + end + + def test_bitwise_and_with_integer_coercion + obj = Struct.new(:value) do + include(CoercionToSelf) + def &(other) + self.value & other.value + end + end.new(10) + assert_equal(3 & 10, 3 & obj) + end + + def test_bitwise_or_with_integer_coercion + obj = Struct.new(:value) do + include(CoercionToSelf) + def |(other) + self.value | other.value + end + end.new(10) + assert_equal(3 | 10, 3 | obj) + end + + def test_bitwise_xor_with_integer_coercion + obj = Struct.new(:value) do + include(CoercionToSelf) + def ^(other) + self.value ^ other.value + end + end.new(10) assert_equal(3 ^ 10, 3 ^ obj) end @@ -277,4 +437,92 @@ class TestInteger < Test::Unit::TestCase assert_equal(i+1, (n+1).bit_length, "#{n+1}.bit_length") } end + + def test_digits + assert_equal([0], 0.digits) + assert_equal([1], 1.digits) + 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)) + end + + def test_digits_for_negative_numbers + assert_raise(Math::DomainError) { -1.digits } + assert_raise(Math::DomainError) { -1234567890.digits } + assert_raise(Math::DomainError) { -1234567890.digits(100) } + assert_raise(Math::DomainError) { -1234567890.digits(13) } + end + + def test_digits_for_invalid_base_numbers + assert_raise(ArgumentError) { 10.digits(-1) } + assert_raise(ArgumentError) { 10.digits(0) } + assert_raise(ArgumentError) { 10.digits(1) } + end + + def test_digits_for_non_integral_base_numbers + assert_equal([1], 1.digits(10r)) + assert_equal([1], 1.digits(10.0)) + assert_raise(RangeError) { 10.digits(10+1i) } + end + + def test_digits_for_non_numeric_base_argument + assert_raise(TypeError) { 10.digits("10") } + assert_raise(TypeError) { 10.digits("a") } + + class << (o = Object.new) + def to_int + 10 + end + end + assert_equal([0, 1], 10.digits(o)) + end + + def test_square_root + assert_raise(TypeError) {Integer.sqrt("x")} + assert_raise(Math::DomainError) {Integer.sqrt(-1)} + assert_equal(0, Integer.sqrt(0)) + (1...4).each {|i| assert_equal(1, Integer.sqrt(i))} + (4...9).each {|i| assert_equal(2, Integer.sqrt(i))} + (9...16).each {|i| assert_equal(3, Integer.sqrt(i))} + (1..40).each do |i| + mesg = "10**#{i}" + s = Integer.sqrt(n = 10**i) + if i.even? + assert_equal(10**(i/2), Integer.sqrt(n), mesg) + else + assert_include((s**2)...(s+1)**2, n, mesg) + end + end + 50.step(400, 10) do |i| + exact = 10**(i/2) + x = 10**i + assert_equal(exact, Integer.sqrt(x), "10**#{i}") + assert_equal(exact, Integer.sqrt(x+1), "10**#{i}+1") + assert_equal(exact-1, Integer.sqrt(x-1), "10**#{i}-1") + end + + bug13440 = '[ruby-core:80696] [Bug #13440]' + failures = [] + 0.step(to: 50, by: 0.05) do |i| + n = (10**i).to_i + root = Integer.sqrt(n) + failures << n unless root*root <= n && (root+1)*(root+1) > n + end + assert_empty(failures, bug13440) + end + + def test_fdiv + assert_equal(1.0, 1.fdiv(1)) + assert_equal(0.5, 1.fdiv(2)) + end + + def test_obj_fdiv + o = Object.new + def o.coerce(x); [x, 0.5]; end + assert_equal(2.0, 1.fdiv(o)) + o = Object.new + def o.coerce(x); [self, x]; end + def o.fdiv(x); 1; end + assert_equal(1.0, 1.fdiv(o)) + end end diff --git a/test/ruby/test_integer_comb.rb b/test/ruby/test_integer_comb.rb index e00e7588fd..1ad13dd31b 100644 --- a/test/ruby/test_integer_comb.rb +++ b/test/ruby/test_integer_comb.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestIntegerComb < Test::Unit::TestCase @@ -109,28 +110,6 @@ class TestIntegerComb < Test::Unit::TestCase #VS.concat VS.find_all {|v| Fixnum === v }.map {|v| 0x4000000000000000.coerce(v)[0] } #VS.sort! {|a, b| a.abs <=> b.abs } - min = -1 - min *= 2 while min.class == Fixnum - FIXNUM_MIN = min/2 - max = 1 - max *= 2 while (max-1).class == Fixnum - FIXNUM_MAX = max/2-1 - - def test_fixnum_range - assert_instance_of(Bignum, FIXNUM_MIN-1) - assert_instance_of(Fixnum, FIXNUM_MIN) - assert_instance_of(Fixnum, FIXNUM_MAX) - assert_instance_of(Bignum, FIXNUM_MAX+1) - end - - def check_class(n) - if FIXNUM_MIN <= n && n <= FIXNUM_MAX - assert_instance_of(Fixnum, n) - else - assert_instance_of(Bignum, n) - end - end - def test_aref VS.each {|a| 100.times {|i| @@ -141,7 +120,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|b| c = nil assert_nothing_raised("(#{a})[#{b}]") { c = a[b] } - check_class(c) + assert_kind_of(Integer, c) if b < 0 assert_equal(0, c, "(#{a})[#{b}]") else @@ -155,7 +134,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a + b - check_class(c) + assert_kind_of(Integer, c) assert_equal(b + a, c, "#{a} + #{b}") assert_equal(a, c - b, "(#{a} + #{b}) - #{b}") assert_equal(a-~b-1, c, "#{a} + #{b}") # Hacker's Delight @@ -170,7 +149,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a - b - check_class(c) + assert_kind_of(Integer, c) assert_equal(a, c + b, "(#{a} - #{b}) + #{b}") assert_equal(-b, c - a, "(#{a} - #{b}) - #{a}") assert_equal(a+~b+1, c, "#{a} - #{b}") # Hacker's Delight @@ -185,7 +164,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a * b - check_class(c) + assert_kind_of(Integer, c) assert_equal(b * a, c, "#{a} * #{b}") assert_equal(b.send(:*, a), c, "#{a} * #{b}") assert_equal(b, c / a, "(#{a} * #{b}) / #{a}") if a != 0 @@ -203,8 +182,8 @@ class TestIntegerComb < Test::Unit::TestCase assert_raise(ZeroDivisionError) { a.divmod(b) } else q, r = a.divmod(b) - check_class(q) - check_class(r) + assert_kind_of(Integer, q) + assert_kind_of(Integer, r) assert_equal(a, b*q+r) assert_operator(r.abs, :<, b.abs) if 0 < b @@ -228,7 +207,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| small_values.each {|b| c = a ** b - check_class(c) + assert_kind_of(Integer, c) d = 1 b.times { d *= a } assert_equal(d, c, "(#{a}) ** #{b}") @@ -244,7 +223,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_not VS.each {|a| b = ~a - check_class(b) + assert_kind_of(Integer, b) assert_equal(-1 ^ a, b, "~#{a}") assert_equal(-a-1, b, "~#{a}") # Hacker's Delight assert_equal(0, a & b, "#{a} & ~#{a}") @@ -256,7 +235,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a | b - check_class(c) + assert_kind_of(Integer, c) assert_equal(b | a, c, "#{a} | #{b}") assert_equal(a + b - (a&b), c, "#{a} | #{b}") assert_equal((a & ~b) + b, c, "#{a} | #{b}") # Hacker's Delight @@ -269,7 +248,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a & b - check_class(c) + assert_kind_of(Integer, c) assert_equal(b & a, c, "#{a} & #{b}") assert_equal(a + b - (a|b), c, "#{a} & #{b}") assert_equal((~a | b) - ~a, c, "#{a} & #{b}") # Hacker's Delight @@ -282,7 +261,7 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| VS.each {|b| c = a ^ b - check_class(c) + assert_kind_of(Integer, c) assert_equal(b ^ a, c, "#{a} ^ #{b}") assert_equal((a|b)-(a&b), c, "#{a} ^ #{b}") # Hacker's Delight assert_equal(b, c ^ a, "(#{a} ^ #{b}) ^ #{a}") @@ -295,12 +274,12 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| small_values.each {|b| c = a << b - check_class(c) + assert_kind_of(Integer, c) if 0 <= b assert_equal(a, c >> b, "(#{a} << #{b}) >> #{b}") assert_equal(a * 2**b, c, "#{a} << #{b}") end - 0.upto(c.size*8+10) {|nth| + 0.upto(c.bit_length+10) {|nth| assert_equal(a[nth-b], c[nth], "(#{a} << #{b})[#{nth}]") } } @@ -312,12 +291,12 @@ class TestIntegerComb < Test::Unit::TestCase VS.each {|a| small_values.each {|b| c = a >> b - check_class(c) + assert_kind_of(Integer, c) if b <= 0 assert_equal(a, c << b, "(#{a} >> #{b}) << #{b}") assert_equal(a * 2**(-b), c, "#{a} >> #{b}") end - 0.upto(c.size*8+10) {|nth| + 0.upto(c.bit_length+10) {|nth| assert_equal(a[nth+b], c[nth], "(#{a} >> #{b})[#{nth}]") } } @@ -327,7 +306,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_succ VS.each {|a| b = a.succ - check_class(b) + assert_kind_of(Integer, b) assert_equal(a+1, b, "(#{a}).succ") assert_equal(a, b.pred, "(#{a}).succ.pred") assert_equal(a, b-1, "(#{a}).succ - 1") @@ -337,7 +316,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_pred VS.each {|a| b = a.pred - check_class(b) + assert_kind_of(Integer, b) assert_equal(a-1, b, "(#{a}).pred") assert_equal(a, b.succ, "(#{a}).pred.succ") assert_equal(a, b + 1, "(#{a}).pred + 1") @@ -347,7 +326,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_unary_plus VS.each {|a| b = +a - check_class(b) + assert_kind_of(Integer, b) assert_equal(a, b, "+(#{a})") } end @@ -355,7 +334,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_unary_minus VS.each {|a| b = -a - check_class(b) + assert_kind_of(Integer, b) assert_equal(0-a, b, "-(#{a})") assert_equal(~a+1, b, "-(#{a})") assert_equal(0, a+b, "#{a}+(-(#{a}))") @@ -387,7 +366,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_abs VS.each {|a| b = a.abs - check_class(b) + assert_kind_of(Integer, b) if a < 0 assert_equal(-a, b, "(#{a}).abs") else @@ -399,7 +378,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_ceil VS.each {|a| b = a.ceil - check_class(b) + assert_kind_of(Integer, b) assert_equal(a, b, "(#{a}).ceil") } end @@ -407,7 +386,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_floor VS.each {|a| b = a.floor - check_class(b) + assert_kind_of(Integer, b) assert_equal(a, b, "(#{a}).floor") } end @@ -415,7 +394,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_round VS.each {|a| b = a.round - check_class(b) + assert_kind_of(Integer, b) assert_equal(a, b, "(#{a}).round") } end @@ -423,7 +402,7 @@ class TestIntegerComb < Test::Unit::TestCase def test_truncate VS.each {|a| b = a.truncate - check_class(b) + assert_kind_of(Integer, b) assert_equal(a, b, "(#{a}).truncate") } end @@ -435,7 +414,7 @@ class TestIntegerComb < Test::Unit::TestCase assert_raise(ZeroDivisionError) { a.divmod(b) } else r = a.remainder(b) - check_class(r) + assert_kind_of(Integer, r) if a < 0 assert_operator(-b.abs, :<, r, "#{a}.remainder(#{b})") assert_operator(0, :>=, r, "#{a}.remainder(#{b})") @@ -460,7 +439,7 @@ class TestIntegerComb < Test::Unit::TestCase else assert_equal(false, z, "(#{a}).zero?") assert_equal(a, n, "(#{a}).nonzero?") - check_class(n) + assert_kind_of(Integer, n) end assert(z ^ n, "(#{a}).zero? ^ (#{a}).nonzero?") } @@ -478,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 03a7faa2dc..178e3c3d7e 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1,14 +1,15 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' require 'tmpdir' require "fcntl" require 'io/nonblock' +require 'pathname' require 'socket' require 'stringio' require 'timeout' require 'tempfile' require 'weakref' -require_relative 'envutil' class TestIO < Test::Unit::TestCase module Feature @@ -145,7 +146,19 @@ class TestIO < Test::Unit::TestCase end def test_gets_rs - # default_rs + rs = ":" + pipe(proc do |w| + w.print "aaa:bbb" + w.close + end, proc do |r| + assert_equal "aaa:", r.gets(rs) + assert_equal "bbb", r.gets(rs) + assert_nil r.gets(rs) + r.close + end) + end + + def test_gets_default_rs pipe(proc do |w| w.print "aaa\nbbb\n" w.close @@ -155,8 +168,9 @@ class TestIO < Test::Unit::TestCase assert_nil r.gets r.close end) + end - # nil + def test_gets_rs_nil pipe(proc do |w| w.print "a\n\nb\n\n" w.close @@ -165,8 +179,9 @@ class TestIO < Test::Unit::TestCase assert_nil r.gets("") r.close end) + end - # "\377" + def test_gets_rs_377 pipe(proc do |w| w.print "\377xyz" w.close @@ -175,8 +190,9 @@ class TestIO < Test::Unit::TestCase assert_equal("\377", r.gets("\377"), "[ruby-dev:24460]") r.close end) + end - # "" + def test_gets_paragraph pipe(proc do |w| w.print "a\n\nb\n\n" w.close @@ -188,6 +204,68 @@ class TestIO < Test::Unit::TestCase end) end + def test_gets_chomp_rs + rs = ":" + pipe(proc do |w| + w.print "aaa:bbb" + w.close + end, proc do |r| + assert_equal "aaa", r.gets(rs, chomp: true) + assert_equal "bbb", r.gets(rs, chomp: true) + assert_nil r.gets(rs, chomp: true) + r.close + end) + end + + def test_gets_chomp_default_rs + pipe(proc do |w| + w.print "aaa\r\nbbb\nccc" + w.close + end, proc do |r| + assert_equal "aaa", r.gets(chomp: true) + assert_equal "bbb", r.gets(chomp: true) + assert_equal "ccc", r.gets(chomp: true) + 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 + pipe(proc do |w| + 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_nil r.gets("") + r.close + end) + end + + def test_gets_chomp_paragraph + pipe(proc do |w| + w.print "a\n\nb\n\n" + w.close + end, proc do |r| + assert_equal "a", r.gets("", chomp: true) + assert_equal "b", r.gets("", chomp: true) + assert_nil r.gets("", chomp: true) + r.close + end) + end + def test_gets_limit_extra_arg pipe(proc do |w| w << "0123456789\n0123456789" @@ -301,6 +379,16 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_append + with_srccontent("foobar") {|src, content| + File.open('dst', 'ab') do |dst| + ret = IO.copy_stream(src, dst) + assert_equal(content.bytesize, ret) + assert_equal(content, File.read("dst")) + end + } + end + def test_copy_stream_smaller with_srccontent {|src, content| @@ -467,6 +555,19 @@ class TestIO < Test::Unit::TestCase end if have_nonblock? + def test_copy_stream_no_busy_wait + 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 + end + end + end + def test_copy_stream_pipe_nonblock mkcdtmpdir { with_read_pipe("abc") {|r1| @@ -874,6 +975,32 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_strio_to_tempfile + bug11015 = '[ruby-core:68676] [Bug #11015]' + # StringIO to Tempfile + src = StringIO.new("abcd") + dst = Tempfile.new("baz") + ret = IO.copy_stream(src, dst) + assert_equal(4, ret) + pos = dst.pos + dst.rewind + assert_equal("abcd", dst.read) + assert_equal(4, pos, bug11015) + ensure + dst.close! + end + + def test_copy_stream_pathname_to_pathname + bug11199 = '[ruby-dev:49008] [Bug #11199]' + mkcdtmpdir { + File.open("src", "w") {|f| f << "ok" } + src = Pathname.new("src") + dst = Pathname.new("dst") + IO.copy_stream(src, dst) + assert_equal("ok", IO.read("dst"), bug11199) + } + end + def test_copy_stream_write_in_binmode bug8767 = '[ruby-core:56518] [Bug #8767]' mkcdtmpdir { @@ -1063,6 +1190,18 @@ class TestIO < Test::Unit::TestCase } end + def test_copy_stream_to_duplex_io + result = IO.pipe {|a,w| + Thread.start {w.puts "yes"; w.close} + IO.popen([EnvUtil.rubybin, '-pe$_="#$.:#$_"'], "r+") {|b| + IO.copy_stream(a, b) + b.close_write + b.read + } + } + assert_equal("1:yes\n", result) + end + def ruby(*args) args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin @@ -1100,6 +1239,63 @@ class TestIO < Test::Unit::TestCase end) end + def test_write_with_multiple_arguments + pipe(proc do |w| + w.write("foo", "bar") + w.close + end, proc do |r| + assert_equal("foobar", r.read) + end) + end + + def test_write_with_multiple_arguments_and_buffer + mkcdtmpdir do + line = "x"*9+"\n" + file = "test.out" + open(file, "wb") do |w| + w.write(line) + assert_equal(11, w.write(line, "\n")) + end + open(file, "rb") do |r| + assert_equal([line, line, "\n"], r.readlines) + end + + line = "x"*99+"\n" + open(file, "wb") do |w| + w.write(line*81) # 8100 bytes + assert_equal(100, w.write("a"*99, "\n")) + end + open(file, "rb") do |r| + 81.times {assert_equal(line, r.gets)} + assert_equal("a"*99+"\n", r.gets) + end + end + end + + def test_write_with_many_arguments + [1023, 1024].each do |n| + pipe(proc do |w| + w.write(*(["a"] * n)) + w.close + end, proc do |r| + assert_equal("a" * n, r.read) + end) + end + end + + def test_write_with_multiple_nonstring_arguments + assert_in_out_err([], "STDOUT.write(:foo, :bar)", ["foobar"]) + end + + def test_write_buffered_with_multiple_arguments + out, err, (_, status) = EnvUtil.invoke_ruby(["-e", "sleep 0.1;puts 'foo'"], "", true, true) do |_, o, e, i| + [o.read, e.read, Process.waitpid2(i)] + end + assert_predicate(status, :success?) + assert_equal("foo\n", out) + assert_empty(err) + end + def test_write_non_writable with_pipe do |r, w| assert_raise(IOError) do @@ -1110,40 +1306,32 @@ class TestIO < Test::Unit::TestCase def test_dup ruby do |f| - f2 = f.dup - f.puts "foo" - f2.puts "bar" - f.close_write - f2.close_write - assert_equal("foo\nbar\n", f.read) - assert_equal("", f2.read) + begin + f2 = f.dup + f.puts "foo" + f2.puts "bar" + f.close_write + f2.close_write + assert_equal("foo\nbar\n", f.read) + assert_equal("", f2.read) + ensure + f2.close + end end end def test_dup_many - ruby('-e', <<-'End') {|f| - if defined?(Process::RLIMIT_NOFILE) - lim = Process.getrlimit(Process::RLIMIT_NOFILE)[0] - Process.setrlimit(Process::RLIMIT_NOFILE, [lim, 1024].min) - end - ok = 0 + opts = {} + opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE) + assert_separately([], <<-'End', opts) a = [] - begin + assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do loop {a << IO.pipe} - rescue Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM - ok += 1 end - print "no" if ok != 1 - begin + assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do loop {a << [a[-1][0].dup, a[-1][1].dup]} - rescue Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM - ok += 1 end - print "no" if ok != 2 - print "ok" End - assert_equal("ok", f.read) - } end def test_inspect @@ -1208,7 +1396,7 @@ class TestIO < Test::Unit::TestCase t.value assert_equal("", s) end - end + end if /cygwin/ !~ RUBY_PLATFORM def test_read pipe(proc do |w| @@ -1261,10 +1449,9 @@ class TestIO < Test::Unit::TestCase t.value assert_equal("xxx", s) end - end + end if /cygwin/ !~ RUBY_PLATFORM def test_write_nonblock - skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM pipe(proc do |w| w.write_nonblock(1) w.close @@ -1274,7 +1461,6 @@ class TestIO < Test::Unit::TestCase end def test_read_nonblock_with_not_empty_buffer - skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| w.write "foob" w.close @@ -1284,7 +1470,6 @@ class TestIO < Test::Unit::TestCase end def test_write_nonblock_simple_no_exceptions - skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM pipe(proc do |w| w.write_nonblock('1', exception: false) w.close @@ -1294,8 +1479,6 @@ class TestIO < Test::Unit::TestCase end def test_read_nonblock_error - return if !have_nonblock? - skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| begin r.read_nonblock 4096 @@ -1311,11 +1494,9 @@ class TestIO < Test::Unit::TestCase assert_kind_of(IO::WaitReadable, $!) end } - end + end if have_nonblock? def test_read_nonblock_no_exceptions - return if !have_nonblock? - skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, exception: false) w.puts "HI!" @@ -1323,11 +1504,9 @@ class TestIO < Test::Unit::TestCase w.close assert_equal nil, r.read_nonblock(4096, exception: false) } - end + end if have_nonblock? def test_read_nonblock_with_buffer_no_exceptions - return if !have_nonblock? - skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| assert_equal :wait_readable, r.read_nonblock(4096, "", exception: false) w.puts "HI!" @@ -1338,11 +1517,9 @@ class TestIO < Test::Unit::TestCase w.close assert_equal nil, r.read_nonblock(4096, "", exception: false) } - end + end if have_nonblock? def test_write_nonblock_error - return if !have_nonblock? - skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| begin loop { @@ -1352,11 +1529,9 @@ class TestIO < Test::Unit::TestCase assert_kind_of(IO::WaitWritable, $!) end } - end + end if have_nonblock? def test_write_nonblock_no_exceptions - return if !have_nonblock? - skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe {|r, w| loop { ret = w.write_nonblock("a"*100000, exception: false) @@ -1366,7 +1541,7 @@ class TestIO < Test::Unit::TestCase end } } - end + end if have_nonblock? def test_gets pipe(proc do |w| @@ -1383,6 +1558,9 @@ class TestIO < Test::Unit::TestCase f.close_read f.write "foobarbaz" assert_raise(IOError) { f.read } + assert_nothing_raised(IOError) {f.close_read} + assert_nothing_raised(IOError) {f.close} + assert_nothing_raised(IOError) {f.close_read} end end @@ -1390,9 +1568,23 @@ class TestIO < Test::Unit::TestCase with_pipe do |r, w| r.close_read assert_raise(Errno::EPIPE) { w.write "foobarbaz" } + assert_nothing_raised(IOError) {r.close_read} + assert_nothing_raised(IOError) {r.close} + assert_nothing_raised(IOError) {r.close_read} end end + def test_write_epipe_nosync + assert_separately([], <<-"end;") + r, w = IO.pipe + r.close + w.sync = false + assert_raise(Errno::EPIPE) { + loop { w.write "a" } + } + end; + end + def test_close_read_non_readable with_pipe do |r, w| assert_raise(IOError) do @@ -1406,6 +1598,9 @@ class TestIO < Test::Unit::TestCase f.write "foobarbaz" f.close_write assert_equal("foobarbaz", f.read) + assert_nothing_raised(IOError) {f.close_write} + assert_nothing_raised(IOError) {f.close} + assert_nothing_raised(IOError) {f.close_write} end end @@ -1431,18 +1626,22 @@ class TestIO < Test::Unit::TestCase end def test_pid - r, w = IO.pipe - assert_equal(nil, r.pid) - assert_equal(nil, w.pid) - - pipe = IO.popen(EnvUtil.rubybin, "r+") - pid1 = pipe.pid - pipe.puts "p $$" - pipe.close_write - pid2 = pipe.read.chomp.to_i - assert_equal(pid2, pid1) - assert_equal(pid2, pipe.pid) - pipe.close + IO.pipe {|r, w| + assert_equal(nil, r.pid) + assert_equal(nil, w.pid) + } + + begin + pipe = IO.popen(EnvUtil.rubybin, "r+") + pid1 = pipe.pid + pipe.puts "p $$" + pipe.close_write + pid2 = pipe.read.chomp.to_i + assert_equal(pid2, pid1) + assert_equal(pid2, pipe.pid) + ensure + pipe.close + end assert_raise(IOError) { pipe.pid } end @@ -1477,23 +1676,21 @@ class TestIO < Test::Unit::TestCase def test_set_lineno make_tempfile {|t| - ruby("-e", <<-SRC, t.path) do |f| + assert_separately(["-", t.path], <<-SRC) open(ARGV[0]) do |f| - p $. - f.gets; p $. - f.gets; p $. - f.lineno = 1000; p $. - f.gets; p $. - f.gets; p $. - f.rewind; p $. - f.gets; p $. - f.gets; p $. - f.gets; p $. - f.gets; p $. + assert_equal(0, $.) + f.gets; assert_equal(1, $.) + f.gets; assert_equal(2, $.) + f.lineno = 1000; assert_equal(2, $.) + f.gets; assert_equal(1001, $.) + f.gets; assert_equal(1001, $.) + f.rewind; assert_equal(1001, $.) + f.gets; assert_equal(1, $.) + f.gets; assert_equal(2, $.) + f.gets; assert_equal(3, $.) + f.gets; assert_equal(3, $.) end SRC - assert_equal("0,1,2,2,1001,1001,1001,1,2,3,3", f.read.chomp.gsub("\n", ",")) - end pipe(proc do |w| w.puts "foo" @@ -1633,7 +1830,6 @@ class TestIO < Test::Unit::TestCase end def test_close_on_exec - skip "IO\#close_on_exec is not implemented." unless have_close_on_exec? ruby do |f| assert_equal(true, f.close_on_exec?) f.close_on_exec = false @@ -1661,7 +1857,7 @@ class TestIO < Test::Unit::TestCase w.close_on_exec = false assert_equal(false, w.close_on_exec?) end - end + end if have_close_on_exec? def test_pos make_tempfile {|t| @@ -1680,7 +1876,7 @@ class TestIO < Test::Unit::TestCase end def test_pos_with_getc - bug6179 = '[ruby-core:43497]' + _bug6179 = '[ruby-core:43497]' make_tempfile {|t| ["", "t", "b"].each do |mode| open(t.path, "w#{mode}") do |f| @@ -1703,6 +1899,25 @@ class TestIO < Test::Unit::TestCase } end + def can_seek_data(f) + if /linux/ =~ RUBY_PLATFORM + require "-test-/file" + # lseek(2) + case Bug::File::Fs.fsname(f.path) + when "btrfs" + return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,1]) >= 0 + when "ocfs" + return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,2]) >= 0 + when "xfs" + return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,5]) >= 0 + when "ext4" + return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,8]) >= 0 + when "tmpfs" + return true if (Etc.uname[:release].split('.').map(&:to_i) <=> [3,8]) >= 0 + end + end + false + end def test_seek make_tempfile {|t| @@ -1729,16 +1944,28 @@ class TestIO < Test::Unit::TestCase if defined?(IO::SEEK_DATA) open(t.path) { |f| + break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, IO::SEEK_DATA) assert_equal("foo\nbar\nbaz\n", f.read) } + open(t.path, 'r+') { |f| + break unless can_seek_data(f) + f.seek(100*1024, IO::SEEK_SET) + f.print("zot\n") + f.seek(50*1024, IO::SEEK_DATA) + assert_operator(f.pos, :>=, 50*1024) + assert_match(/\A\0*zot\n\z/, f.read) + } end if defined?(IO::SEEK_HOLE) - open(t.path) { |f|2 + open(t.path) { |f| + break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, IO::SEEK_HOLE) + assert_operator(f.pos, :>, 20) + f.seek(100*1024, IO::SEEK_HOLE) assert_equal("", f.read) } end @@ -1765,16 +1992,28 @@ class TestIO < Test::Unit::TestCase if defined?(IO::SEEK_DATA) open(t.path) { |f| + break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, :DATA) assert_equal("foo\nbar\nbaz\n", f.read) } + open(t.path, 'r+') { |f| + break unless can_seek_data(f) + f.seek(100*1024, :SET) + f.print("zot\n") + f.seek(50*1024, :DATA) + assert_operator(f.pos, :>=, 50*1024) + assert_match(/\A\0*zot\n\z/, f.read) + } end if defined?(IO::SEEK_HOLE) open(t.path) { |f| + break unless can_seek_data(f) assert_equal("foo\n", f.gets) f.seek(0, :HOLE) + assert_operator(f.pos, :>, 20) + f.seek(100*1024, :HOLE) assert_equal("", f.read) } end @@ -1836,6 +2075,10 @@ class TestIO < Test::Unit::TestCase assert_raise(ArgumentError) do open(t.path, "rr") { } end + + assert_raise(ArgumentError) do + open(t.path, "rbt") { } + end } end @@ -1916,7 +2159,7 @@ class TestIO < Test::Unit::TestCase assert_raise(Errno::EBADF, feature2250) {t.close} end ensure - t.unlink + t.close! end def test_autoclose_false_closed_by_finalizer @@ -1932,7 +2175,7 @@ class TestIO < Test::Unit::TestCase assert_nothing_raised(Errno::EBADF, feature2250) {t.close} end ensure - t.unlink + t.close! end def test_open_redirect @@ -1954,6 +2197,22 @@ class TestIO < Test::Unit::TestCase end end + def test_read_command + assert_equal("foo\n", IO.read("|echo foo")) + assert_warn(/invoke external command/) do + File.read("|#{EnvUtil.rubybin} -e puts") + end + assert_warn(/invoke external command/) 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 + end + def test_reopen make_tempfile {|t| open(__FILE__) do |f| @@ -2102,11 +2361,35 @@ End } end + bug11320 = '[ruby-core:69780] [Bug #11320]' + ["UTF-8", "EUC-JP", "Shift_JIS"].each do |enc| + define_method("test_reopen_nonascii(#{enc})") do + mkcdtmpdir do + fname = "\u{30eb 30d3 30fc}".encode(enc) + File.write(fname, '') + assert_file.exist?(fname) + stdin = $stdin.dup + begin + assert_nothing_raised(Errno::ENOENT, "#{bug11320}: #{enc}") { + $stdin.reopen(fname, 'r') + } + ensure + $stdin.reopen(stdin) + stdin.close + end + end + 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 } @@ -2218,6 +2501,36 @@ End end) end + def test_puts_parallel + skip "not portable" + pipe(proc do |w| + threads = [] + 100.times do + threads << Thread.new { w.puts "hey" } + end + threads.each(&:join) + w.close + end, proc do |r| + assert_equal("hey\n" * 100, r.read) + end) + end + + def test_puts_old_write + capture = String.new + def capture.write(str) + self << str + end + + capture.clear + assert_warning(/[.#]write is outdated/) do + stdout, $stdout = $stdout, capture + puts "hey" + ensure + $stdout = stdout + end + assert_equal("hey\n", capture) + end + def test_display pipe(proc do |w| "foo".display(w) @@ -2233,6 +2546,14 @@ End assert_raise(TypeError) { $> = Object.new } assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo)) + + assert_separately(%w[-Eutf-8], <<-"end;") # do + 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 + $\u{6a19 6e96 51fa 529b} = x.new + end + end; end def test_initialize @@ -2267,7 +2588,16 @@ End end def test_new_with_block - assert_in_out_err([], "r, w = IO.pipe; IO.new(r) {}", [], /^.+$/) + assert_in_out_err([], "r, w = IO.pipe; r.autoclose=false; IO.new(r.fileno) {}.close", [], /^.+$/) + n = "IO\u{5165 51fa 529b}" + c = eval("class #{n} < IO; self; end") + IO.pipe do |r, w| + assert_warning(/#{n}/) { + r.autoclose=false + io = c.new(r.fileno) {} + io.close + } + end end def test_readline2 @@ -2302,8 +2632,6 @@ End def test_nofollow # O_NOFOLLOW is not standard. - return if /freebsd|linux/ !~ RUBY_PLATFORM - return unless defined? File::NOFOLLOW mkcdtmpdir { open("file", "w") {|f| f << "content" } begin @@ -2318,7 +2646,7 @@ End File.foreach("slnk", :open_args=>[File::RDONLY|File::NOFOLLOW]) {} } } - end + end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW def test_tainted make_tempfile {|t| @@ -2333,13 +2661,21 @@ End } end + def test_DATA_binmode + assert_separately([], <<-SRC) +assert_not_predicate(DATA, :binmode?) +__END__ + SRC + end + def test_threaded_flush bug3585 = '[ruby-core:31348]' - src = %q{\ + src = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; t = Thread.new { sleep 3 } Thread.new {sleep 1; t.kill; p 'hi!'} t.join - }.gsub(/^\s+/, '') + end; 10.times.map do Thread.start do assert_in_out_err([], src) {|stdout, stderr| @@ -2352,21 +2688,27 @@ End def test_flush_in_finalizer1 require 'tempfile' bug3910 = '[ruby-dev:42341]' - Tempfile.open("bug3910") {|t| + tmp = Tempfile.open("bug3910") {|t| path = t.path t.close fds = [] assert_nothing_raised(TypeError, bug3910) do 500.times { f = File.open(path, "w") + f.instance_variable_set(:@test_flush_in_finalizer1, true) fds << f.fileno f.print "hoge" } end - t.unlink + t } ensure - GC.start + ObjectSpace.each_object(File) {|f| + if f.instance_variables.include?(:@test_flush_in_finalizer1) + f.close + end + } + tmp.close! end def test_flush_in_finalizer2 @@ -2375,14 +2717,23 @@ End Tempfile.open("bug3910") {|t| path = t.path t.close - 1.times do - io = open(path,"w") - io.print "hoge" - end - assert_nothing_raised(TypeError, bug3910) do - GC.start + begin + 1.times do + io = open(path,"w") + io.instance_variable_set(:@test_flush_in_finalizer2, true) + io.print "hoge" + end + assert_nothing_raised(TypeError, bug3910) do + GC.start + end + ensure + ObjectSpace.each_object(File) {|f| + if f.instance_variables.include?(:@test_flush_in_finalizer2) + f.close + end + } end - t.unlink + t.close! } end @@ -2408,13 +2759,56 @@ End } end + def os_and_fs(path) + uname = Etc.uname + os = "#{uname[:sysname]} #{uname[:release]}" + + fs = nil + if uname[:sysname] == 'Linux' + # [ruby-dev:45703] Old Linux's fadvise() doesn't work on tmpfs. + mount = `mount` + mountpoints = [] + mount.scan(/ on (\S+) type (\S+) /) { + mountpoints << [$1, $2] + } + mountpoints.sort_by {|mountpoint, fstype| mountpoint.length }.reverse_each {|mountpoint, fstype| + if path == mountpoint + fs = fstype + break + end + mountpoint += "/" if %r{/\z} !~ mountpoint + if path.start_with?(mountpoint) + fs = fstype + break + end + } + end + + if fs + "#{fs} on #{os}" + else + os + end + end + def test_advise make_tempfile {|tf| assert_raise(ArgumentError, "no arguments") { tf.advise } %w{normal random sequential willneed dontneed noreuse}.map(&:to_sym).each do |adv| [[0,0], [0, 20], [400, 2]].each do |offset, len| open(tf.path) do |t| - assert_equal(t.advise(adv, offset, len), nil) + ret = assert_nothing_raised(lambda { os_and_fs(tf.path) }) { + begin + t.advise(adv, offset, len) + rescue Errno::EINVAL => e + if /linux/ =~ RUBY_PLATFORM && (Etc.uname[:release].split('.').map(&:to_i) <=> [3,6]) < 0 + next # [ruby-core:65355] tmpfs is not supported + else + raise e + end + end + } + assert_nil(ret) assert_raise(ArgumentError, "superfluous arguments") do t.advise(adv, offset, len, offset) end @@ -2441,10 +2835,10 @@ End def test_invalid_advise feature4204 = '[ruby-dev:42887]' make_tempfile {|tf| - %w{Normal rand glark will_need zzzzzzzzzzzz \u2609}.map(&:to_sym).each do |adv| + %W{Normal rand glark will_need zzzzzzzzzzzz \u2609}.map(&:to_sym).each do |adv| [[0,0], [0, 20], [400, 2]].each do |offset, len| open(tf.path) do |t| - assert_raise(NotImplementedError, feature4204) { t.advise(adv, offset, len) } + assert_raise_with_message(NotImplementedError, /#{Regexp.quote(adv.inspect)}/, feature4204) { t.advise(adv, offset, len) } end end end @@ -2452,9 +2846,6 @@ End end def test_fcntl_lock_linux - return if /x86_64-linux/ !~ RUBY_PLATFORM # A binary form of struct flock depend on platform - return if [nil].pack("p").bytesize != 8 # Return if x32 platform. - pad=0 Tempfile.create(self.class.name) do |f| r, w = IO.pipe @@ -2483,11 +2874,10 @@ End Process.kill :TERM, pid Process.waitpid2(pid) end - end + end if /x86_64-linux/ =~ RUBY_PLATFORM and # A binary form of struct flock depend on platform + [nil].pack("p").bytesize == 8 # unless x32 platform. def test_fcntl_lock_freebsd - return if /freebsd/ !~ RUBY_PLATFORM # A binary form of struct flock depend on platform - start = 12 len = 34 sysid = 0 @@ -2518,7 +2908,7 @@ End Process.kill :TERM, pid Process.waitpid2(pid) end - end + end if /freebsd/ =~ RUBY_PLATFORM # A binary form of struct flock depend on platform def test_fcntl_dupfd Tempfile.create(self.class.name) do |f| @@ -2532,7 +2922,6 @@ End end def test_cross_thread_close_fd - skip "cross thread close causes hung-up if pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM with_pipe do |r,w| read_thread = Thread.new do begin @@ -2555,15 +2944,41 @@ End $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 + def test_single_exception_on_close + a = [] + t = [] + 10.times do + r, w = IO.pipe + a << [r, w] + t << Thread.new do + while r.gets + end rescue IOError + Thread.current.pending_interrupt? + end + end + a.each do |r, w| + w.write(-"\n") + w.close + r.close + end + t.each do |th| + assert_equal false, th.value, '[ruby-core:81581] [Bug #13632]' + end + end + def test_open_mode feature4742 = "[ruby-core:36338]" bug6055 = '[ruby-dev:45268]' @@ -2618,6 +3033,10 @@ End end end + def test_s_binread_does_not_leak_with_invalid_offset + assert_raise(Errno::EINVAL) { IO.binread(__FILE__, 0, -1) } + end + def test_s_binwrite mkcdtmpdir do path = "test_s_binwrite" @@ -2703,11 +3122,8 @@ End end def test_ioctl_linux - return if /linux/ !~ RUBY_PLATFORM # Alpha, mips, sparc and ppc have an another ioctl request number scheme. # So, hardcoded 0x80045200 may fail. - return if /^i.?86|^x86_64/ !~ RUBY_PLATFORM - assert_nothing_raised do File.open('/dev/urandom'){|f1| entropy_count = "" @@ -2724,21 +3140,24 @@ End } end assert_equal(File.size(__FILE__), buf.unpack('i!')[0]) - end + end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM def test_ioctl_linux2 - return if /linux/ !~ RUBY_PLATFORM - return if /^i.?86|^x86_64/ !~ RUBY_PLATFORM - - return unless system('tty', '-s') # stdin is not a terminal - File.open('/dev/tty') { |f| + return unless STDIN.tty? # stdin is not a terminal + begin + f = File.open('/dev/tty') + rescue Errno::ENOENT, Errno::ENXIO => e + skip e.message + else tiocgwinsz=0x5413 winsize="" assert_nothing_raised { f.ioctl(tiocgwinsz, winsize) } - } - end + ensure + f.close if f + end + end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM def test_setpos mkcdtmpdir { @@ -2799,7 +3218,6 @@ End def test_frozen_autoclose with_pipe do |r,w| - fd = r.fileno assert_equal(true, r.freeze.autoclose?) end end @@ -2821,33 +3239,46 @@ End end def test_readpartial_locktmp - skip "nonblocking mode is not supported for pipe on this platform" if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM bug6099 = '[ruby-dev:45297]' buf = " " * 100 data = "a" * 100 + th = nil with_pipe do |r,w| - r.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) + r.nonblock = true th = Thread.new {r.readpartial(100, buf)} + Thread.pass until th.stop? - buf.replace("") + + assert_equal 100, buf.bytesize + + begin + 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) + assert_predicate(th, :alive?) w.write(data) Thread.pass while th.alive? th.join end assert_equal(data, buf, bug6099) - rescue RuntimeError # can't modify string; temporarily locked end def test_advise_pipe # we don't know if other platforms have a real posix_fadvise() - return if /linux/ !~ RUBY_PLATFORM 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) } - assert_raise(Errno::ESPIPE, Errno::EINVAL) { w.advise(:willneed) } + assert_raise(Errno::ESPIPE, Errno::EINVAL) { + r.advise(:willneed) or skip "fadvise(2) is not implemented" + } + assert_raise(Errno::ESPIPE, Errno::EINVAL) { + w.advise(:willneed) or skip "fadvise(2) is not implemented" + } end - end + end if /linux/ =~ RUBY_PLATFORM def assert_buffer_not_raise_shared_string_error bug6764 = '[ruby-core:46586]' @@ -2915,14 +3346,13 @@ End assert_normal_exit %q{ require "tempfile" - # try to raise RLIM_NOFILE to >FD_SETSIZE - # Unfortunately, ruby export FD_SETSIZE. then we assume it's 1024. + # Unfortunately, ruby doesn't export FD_SETSIZE. then we assume it's 1024. fd_setsize = 1024 + # try to raise RLIM_NOFILE to >FD_SETSIZE begin - Process.setrlimit(Process::RLIMIT_NOFILE, fd_setsize+10) - rescue =>e - # Process::RLIMIT_NOFILE couldn't be raised. skip the test + Process.setrlimit(Process::RLIMIT_NOFILE, fd_setsize+20) + rescue Errno::EPERM exit 0 end @@ -2932,8 +3362,8 @@ End } IO.select(tempfiles) - }, bug8080, timeout: 30 - end + }, bug8080, timeout: 100 + end if defined?(Process::RLIMIT_NOFILE) def test_read_32bit_boundary bug8431 = '[ruby-core:55098] [Bug #8431]' @@ -2994,29 +3424,37 @@ End bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" - r, = IO.pipe - t = Thread.new { 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 } - ensure - t.kill - end + IO.pipe {|r,| + 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 } + t.join + } + end if /cygwin/ !~ RUBY_PLATFORM def test_readpartial_unlocktmp_ensure bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" - r, = IO.pipe - t = Thread.new { 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 } - ensure - t.kill - end + IO.pipe {|r, w| + 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 } + t.join + } + end if /cygwin/ !~ RUBY_PLATFORM def test_readpartial_bad_args IO.pipe do |r, w| @@ -3033,15 +3471,19 @@ End bug8669 = '[ruby-core:56121] [Bug #8669]' str = "" - r, = IO.pipe - t = Thread.new { 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 } - ensure - t.kill - end + IO.pipe {|r, w| + 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 } + t.join + } + end if /cygwin/ !~ RUBY_PLATFORM def test_exception_at_close bug10153 = '[ruby-core:64463] [Bug #10153] exception in close at the end of block' @@ -3051,4 +3493,283 @@ End end end end + + def test_close_twice + open(__FILE__) {|f| + assert_equal(nil, f.close) + assert_equal(nil, f.close) + } + end + + def test_close_uninitialized + io = IO.allocate + assert_raise(IOError) { io.close } + end + + def test_open_fifo_does_not_block_other_threads + mkcdtmpdir { + File.mkfifo("fifo") + assert_separately([], <<-'EOS') + t1 = Thread.new { + open("fifo", "r") {|r| + r.read + } + } + t2 = Thread.new { + open("fifo", "w") {|w| + w.write "foo" + } + } + t1_value, _ = assert_join_threads([t1, t2]) + assert_equal("foo", t1_value) + EOS + } + end if /mswin|mingw|bccwin|cygwin/ !~ RUBY_PLATFORM + + def test_open_flag + make_tempfile do |t| + assert_raise(Errno::EEXIST){ open(t.path, File::WRONLY|File::CREAT, flags: File::EXCL){} } + assert_raise(Errno::EEXIST){ open(t.path, 'w', flags: File::EXCL){} } + assert_raise(Errno::EEXIST){ open(t.path, mode: 'w', flags: File::EXCL){} } + end + end + + def test_open_flag_binary + make_tempfile do |t| + open(t.path, File::RDONLY, flags: File::BINARY) do |f| + assert_equal true, f.binmode? + end + open(t.path, 'r', flags: File::BINARY) do |f| + assert_equal true, f.binmode? + end + open(t.path, mode: 'r', flags: File::BINARY) do |f| + assert_equal true, f.binmode? + end + end + end if File::BINARY != 0 + + def test_race_gets_and_close + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + bug13076 = '[ruby-core:78845] [Bug #13076]' + begin; + 10.times do |i| + a = [] + t = [] + 10.times do + r,w = IO.pipe + a << [r,w] + t << Thread.new do + begin + while r.gets + end + rescue IOError + end + end + end + a.each do |r,w| + w.puts "hoge" + w.close + r.close + end + assert_nothing_raised(IOError, bug13076) { + t.each(&:join) + } + end + end; + end + + def test_race_closed_stream + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug13158 = '[ruby-core:79262] [Bug #13158]' + closed = nil + q = Queue.new + IO.pipe do |r, w| + thread = Thread.new do + begin + q << true + assert_raise_with_message(IOError, /stream closed/) do + while r.gets + end + end + ensure + closed = r.closed? + end + end + q.pop + sleep 0.01 until thread.stop? + r.close + thread.join + assert_equal(true, closed, bug13158 + ': stream should be closed') + end + end; + end + + if RUBY_ENGINE == "ruby" # implementation details + def test_foreach_rs_conversion + make_tempfile {|t| + a = [] + rs = Struct.new(:count).new(0) + def rs.to_str; self.count += 1; "\n"; end + IO.foreach(t.path, rs) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + assert_equal(1, rs.count) + } + end + + def test_foreach_rs_invalid + make_tempfile {|t| + rs = Object.new + def rs.to_str; raise "invalid rs"; end + assert_raise(RuntimeError) do + IO.foreach(t.path, rs, mode:"w") {} + end + assert_equal(["foo\n", "bar\n", "baz\n"], IO.foreach(t.path).to_a) + } + end + + def test_foreach_limit_conversion + make_tempfile {|t| + a = [] + lim = Struct.new(:count).new(0) + def lim.to_int; self.count += 1; -1; end + IO.foreach(t.path, lim) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + assert_equal(1, lim.count) + } + end + + def test_foreach_limit_invalid + make_tempfile {|t| + lim = Object.new + def lim.to_int; raise "invalid limit"; end + assert_raise(RuntimeError) do + IO.foreach(t.path, lim, mode:"w") {} + end + assert_equal(["foo\n", "bar\n", "baz\n"], IO.foreach(t.path).to_a) + } + end + + def test_readlines_rs_invalid + make_tempfile {|t| + rs = Object.new + def rs.to_str; raise "invalid rs"; end + assert_raise(RuntimeError) do + IO.readlines(t.path, rs, mode:"w") + end + assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) + } + end + + def test_readlines_limit_invalid + make_tempfile {|t| + lim = Object.new + def lim.to_int; raise "invalid limit"; end + assert_raise(RuntimeError) do + IO.readlines(t.path, lim, mode:"w") + end + assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) + } + end + + def test_closed_stream_in_rescue + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + 10.times do + assert_nothing_raised(RuntimeError, /frozen IOError/) do + IO.pipe do |r, w| + th = Thread.start {r.close} + r.gets + rescue IOError + # swallow pending exceptions + begin + sleep 0.001 + rescue IOError + retry + end + ensure + th.kill.join + 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 + 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) + + 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_select_exceptfds + if Etc.uname[:sysname] == 'SunOS' && Etc.uname[:release] == '5.11' + skip "Solaris 11 fails this" + 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) + 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_select_leak + assert_no_memory_leak([], <<-"end;", <<-"end;", rss: true, timeout: 240) + r, w = IO.pipe + rset = [r] + wset = [w] + Thread.new { IO.select(rset, wset, nil, 0) }.join + end; + 20_000.times do + th = Thread.new { IO.select(rset, wset) } + Thread.pass until th.stop? + th.kill + th.join + GC.start + end + end; + end end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 95081322d4..9ff5307fc3 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -1,9 +1,9 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' require 'tmpdir' require 'tempfile' require 'timeout' -require_relative 'envutil' class TestIO_M17N < Test::Unit::TestCase ENCS = [ @@ -313,6 +313,17 @@ EOT } end + def test_ignored_encoding_option + enc = "\u{30a8 30f3 30b3 30fc 30c7 30a3 30f3 30b0}" + pattern = /#{enc}/ + assert_warning(pattern) { + open(IO::NULL, external_encoding: "us-ascii", encoding: enc) {} + } + assert_warning(pattern) { + open(IO::NULL, internal_encoding: "us-ascii", encoding: enc) {} + } + end + def test_io_new_enc with_tmpdir { generate_file("tmp", "\xa1") @@ -459,7 +470,7 @@ EOT w.close end, proc do |r| - timeout(1) { + Timeout.timeout(1) { assert_equal("before \xa2\xa2".encode("utf-8", "euc-jp"), r.gets(rs)) } @@ -1214,7 +1225,6 @@ EOT end def test_stdin_external_encoding_with_reopen - skip "passing non-stdio fds is not supported" if /mswin|mingw/ =~ RUBY_PLATFORM with_tmpdir { open("tst", "w+") {|f| pid = spawn(EnvUtil.rubybin, '-e', <<-'End', 10=>f) @@ -1230,7 +1240,7 @@ EOT assert_equal("\u3042".force_encoding("ascii-8bit"), result) } } - end + end unless /mswin|mingw/ =~ RUBY_PLATFORM # passing non-stdio fds is not supported def test_popen_r_enc IO.popen("#{EnvUtil.rubybin} -e 'putc 255'", "r:ascii-8bit") {|f| @@ -1598,6 +1608,44 @@ EOT } end + + def test_binmode_decode_universal_newline + with_tmpdir { + generate_file("t.txt", "a\n") + assert_raise(ArgumentError) { + open("t.txt", "rb", newline: :universal) {} + } + } + end + + def test_default_mode_decode_universal_newline_gets + with_tmpdir { + generate_file("t.crlf", "a\r\nb\r\nc\r\n") + open("t.crlf", "r", newline: :universal) {|f| + assert_equal("a\n", f.gets) + assert_equal("b\n", f.gets) + assert_equal("c\n", f.gets) + assert_equal(nil, f.gets) + } + + generate_file("t.cr", "a\rb\rc\r") + open("t.cr", "r", newline: :universal) {|f| + assert_equal("a\n", f.gets) + assert_equal("b\n", f.gets) + assert_equal("c\n", f.gets) + assert_equal(nil, f.gets) + } + + generate_file("t.lf", "a\nb\nc\n") + open("t.lf", "r", newline: :universal) {|f| + assert_equal("a\n", f.gets) + assert_equal("b\n", f.gets) + assert_equal("c\n", f.gets) + assert_equal(nil, f.gets) + } + } + end + def test_read_newline_conversion_with_encoding_conversion with_tmpdir { generate_file("t.utf8.crlf", "a\r\nb\r\n") @@ -1702,8 +1750,7 @@ EOT args.each {|arg| f.print arg } } content = File.read("t", :mode=>"rb:ascii-8bit") - assert_equal(expected.dup.force_encoding("ascii-8bit"), - content.force_encoding("ascii-8bit")) + assert_equal(expected.b, content.b) } end @@ -1883,7 +1930,7 @@ EOT with_tmpdir { src = "\u3042\r\n" generate_file("t.txt", src) - srcbin = src.dup.force_encoding("ascii-8bit") + srcbin = src.b open("t.txt", "rt:utf-8:euc-jp") {|f| f.binmode result = f.read @@ -2035,14 +2082,14 @@ EOT def test_strip_bom with_tmpdir { - text = "\uFEFFa" - stripped = "a" + text = "\uFEFF\u0100a" + stripped = "\u0100a" %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name| path = '%s-bom.txt' % name 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"), + assert_equal(content[1..-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) @@ -2052,10 +2099,10 @@ EOT bug3407 = '[ruby-core:30641]' path = 'UTF-8-bom.txt' result = File.read(path, encoding: 'BOM|UTF-8') - assert_equal("a", result.force_encoding("ascii-8bit"), bug3407) + assert_equal(stripped.b, result.force_encoding("ascii-8bit"), bug3407) bug8323 = '[ruby-core:54563] [Bug #8323]' - expected = "a\xff".force_encoding("utf-8") + expected = (stripped.b + "\xff").force_encoding("utf-8") open(path, 'ab') {|f| f.write("\xff")} result = File.read(path, encoding: 'BOM|UTF-8') assert_not_predicate(result, :valid_encoding?, bug8323) @@ -2086,6 +2133,55 @@ EOT end; end + def test_bom_non_utf + enc = nil + + assert_warn(/BOM/) { + open(__FILE__, "r:bom|us-ascii") {|f| enc = f.external_encoding} + } + assert_equal(Encoding::US_ASCII, enc) + + enc = nil + assert_warn(/BOM/) { + open(__FILE__, "r", encoding: "bom|us-ascii") {|f| enc = f.external_encoding} + } + assert_equal(Encoding::US_ASCII, enc) + + enc = nil + assert_warn(/BOM/) { + open(IO::NULL, "w:bom|us-ascii") {|f| enc = f.external_encoding} + } + assert_equal(Encoding::US_ASCII, enc) + + enc = nil + assert_warn(/BOM/) { + open(IO::NULL, "w", encoding: "bom|us-ascii") {|f| enc = f.external_encoding} + } + assert_equal(Encoding::US_ASCII, enc) + + tlhInganHol = "\u{f8e4 f8d9 f8d7 f8dc f8d0 f8db} \u{f8d6 f8dd f8d9}" + assert_warn(/#{tlhInganHol}/) { + EnvUtil.with_default_internal(nil) { + open(IO::NULL, "w:bom|#{tlhInganHol}") {|f| enc = f.external_encoding} + } + } + 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" @@ -2156,32 +2252,34 @@ EOT def test_textmode_paragraph_nonasciicompat bug3534 = ['[ruby-dev:41803]', '[Bug #3534]'] - r, w = IO.pipe - [Encoding::UTF_32BE, Encoding::UTF_32LE, - Encoding::UTF_16BE, Encoding::UTF_16LE, - Encoding::UTF_8].each do |e| - r.set_encoding(Encoding::US_ASCII, e) - wthr = Thread.new{ w.print(bug3534[0], "\n\n\n\n", bug3534[1], "\n") } - assert_equal((bug3534[0]+"\n\n").encode(e), r.gets(""), bug3534[0]) - assert_equal((bug3534[1]+"\n").encode(e), r.gets(), bug3534[1]) - wthr.join - end + IO.pipe {|r, w| + [Encoding::UTF_32BE, Encoding::UTF_32LE, + Encoding::UTF_16BE, Encoding::UTF_16LE, + Encoding::UTF_8].each do |e| + r.set_encoding(Encoding::US_ASCII, e) + wthr = Thread.new{ w.print(bug3534[0], "\n\n\n\n", bug3534[1], "\n") } + assert_equal((bug3534[0]+"\n\n").encode(e), r.gets(""), bug3534[0]) + assert_equal((bug3534[1]+"\n").encode(e), r.gets(), bug3534[1]) + wthr.join + end + } end def test_binmode_paragraph_nonasciicompat bug3534 = ['[ruby-dev:41803]', '[Bug #3534]'] - r, w = IO.pipe - r.binmode - w.binmode - [Encoding::UTF_32BE, Encoding::UTF_32LE, - Encoding::UTF_16BE, Encoding::UTF_16LE, - Encoding::UTF_8].each do |e| - r.set_encoding(Encoding::US_ASCII, e) - wthr = Thread.new{ w.print(bug3534[0], "\n\n\n\n", bug3534[1], "\n") } - assert_equal((bug3534[0]+"\n\n").encode(e), r.gets(""), bug3534[0]) - assert_equal((bug3534[1]+"\n").encode(e), r.gets(), bug3534[1]) - wthr.join - end + IO.pipe {|r, w| + r.binmode + w.binmode + [Encoding::UTF_32BE, Encoding::UTF_32LE, + Encoding::UTF_16BE, Encoding::UTF_16LE, + Encoding::UTF_8].each do |e| + r.set_encoding(Encoding::US_ASCII, e) + wthr = Thread.new{ w.print(bug3534[0], "\n\n\n\n", bug3534[1], "\n") } + assert_equal((bug3534[0]+"\n\n").encode(e), r.gets(""), bug3534[0]) + assert_equal((bug3534[1]+"\n").encode(e), r.gets(), bug3534[1]) + wthr.join + end + } end def test_puts_widechar @@ -2191,7 +2289,7 @@ EOT w.binmode w.puts(0x010a.chr(Encoding::UTF_32BE)) w.puts(0x010a.chr(Encoding::UTF_16BE)) - w.puts(0x0a010000.chr(Encoding::UTF_32LE)) + w.puts(0x0a01.chr(Encoding::UTF_32LE)) w.puts(0x0a01.chr(Encoding::UTF_16LE)) w.close end, @@ -2199,7 +2297,7 @@ EOT r.binmode assert_equal("\x00\x00\x01\x0a\n", r.read(5), bug) assert_equal("\x01\x0a\n", r.read(3), bug) - assert_equal("\x00\x00\x01\x0a\n", r.read(5), bug) + assert_equal("\x01\x0a\x00\x00\n", r.read(5), bug) assert_equal("\x01\x0a\n", r.read(3), bug) assert_equal("", r.read, bug) r.close @@ -2550,6 +2648,22 @@ EOT } end if /mswin|mingw/ =~ RUBY_PLATFORM + def test_read_with_buf_broken_ascii_only + a, b = IO.pipe + a.binmode + b.binmode + b.write("\xE2\x9C\x93") + b.close + + buf = "".force_encoding("binary") + assert buf.ascii_only?, "should have been ascii_only?" + a.read(1, buf) + assert !buf.ascii_only?, "should not have been ascii_only?" + ensure + a.close rescue nil + b.close rescue nil + end + def test_each_codepoint_need_more bug11444 = '[ruby-core:70379] [Bug #11444]' tests = [ @@ -2577,7 +2691,7 @@ EOT begin assert_in_out_err(args, "", out, err, "#{bug11444}: #{test} in #{mode} mode", - timeout: 1) + timeout: 10) rescue Exception => e failure << e end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 7fc6dba53e..ed88c9b43d 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -1,5 +1,5 @@ require 'test/unit' -require_relative 'envutil' +require 'tempfile' class TestISeq < Test::Unit::TestCase ISeq = RubyVM::InstructionSequence @@ -9,9 +9,19 @@ class TestISeq < Test::Unit::TestCase assert_normal_exit('p RubyVM::InstructionSequence.compile("1", "mac", "", 0).to_a', bug5894) end + def compile(src, line = nil, opt = nil) + EnvUtil.suppress_warning do + ISeq.new(src, __FILE__, __FILE__, line, opt) + end + end + def lines src - body = RubyVM::InstructionSequence.new(src).to_a[13] - lines = body.find_all{|e| e.kind_of? Fixnum} + body = compile(src).to_a[13] + body.find_all{|e| e.kind_of? Integer} + end + + def test_allocate + assert_raise(TypeError) {ISeq.allocate} end def test_to_a_lines @@ -47,13 +57,13 @@ class TestISeq < Test::Unit::TestCase end def test_unsupport_type - ary = RubyVM::InstructionSequence.compile("p").to_a + ary = compile("p").to_a ary[9] = :foobar - assert_raise_with_message(TypeError, /:foobar/) {RubyVM::InstructionSequence.load(ary)} + assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)} end if defined?(RubyVM::InstructionSequence.load) def test_loaded_cdhash_mark - iseq = RubyVM::InstructionSequence.compile(<<-'end;', __FILE__, __FILE__, __LINE__+1) + iseq = compile(<<-'end;', __LINE__+1) def bug(kw) case kw when "false" then false @@ -73,26 +83,27 @@ class TestISeq < Test::Unit::TestCase end if defined?(RubyVM::InstructionSequence.load) def test_disasm_encoding - src = "\u{3042} = 1; \u{3042}" - enc, Encoding.default_internal = Encoding.default_internal, src.encoding - assert_equal(src.encoding, RubyVM::InstructionSequence.compile(src).disasm.encoding) + src = "\u{3042} = 1; \u{3042}; \u{3043}" + asm = compile(src).disasm + assert_equal(src.encoding, asm.encoding) + assert_predicate(asm, :valid_encoding?) src.encode!(Encoding::Shift_JIS) - assert_equal(true, RubyVM::InstructionSequence.compile(src).disasm.ascii_only?) - ensure - Encoding.default_internal = enc + asm = compile(src).disasm + assert_equal(src.encoding, asm.encoding) + assert_predicate(asm, :valid_encoding?) end LINE_BEFORE_METHOD = __LINE__ def method_test_line_trace - a = 1 + _a = 1 - b = 2 + _b = 2 end def test_line_trace - iseq = ISeq.compile \ + iseq = compile \ %q{ a = 1 b = 2 c = 3 @@ -145,10 +156,279 @@ class TestISeq < Test::Unit::TestCase assert_same a, b end + def test_disable_opt + src = "a['foo'] = a['bar']; 'a'.freeze" + body= compile(src, __LINE__, false).to_a[13] + body.each{|insn| + next unless Array === insn + op = insn.first + assert(!op.to_s.match(/^opt_/), "#{op}") + } + end + def test_invalid_source bug11159 = '[ruby-core:69219] [Bug #11159]' - assert_raise(TypeError, bug11159) {ISeq.compile(nil)} - assert_raise(TypeError, bug11159) {ISeq.compile(:foo)} - assert_raise(TypeError, bug11159) {ISeq.compile(1)} + assert_raise(TypeError, bug11159) {compile(nil)} + assert_raise(TypeError, bug11159) {compile(:foo)} + assert_raise(TypeError, bug11159) {compile(1)} + end + + def test_frozen_string_literal_compile_option + $f = 'f' + line = __LINE__ + 2 + code = <<-'EOS' + ['foo', 'foo', "#{$f}foo", "#{'foo'}"] + EOS + 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?) + end + + # Safe call chain is not optimized when Coverage is running. + # So we can test it only when Coverage is not running. + def test_safe_call_chain + src = "a&.a&.a&.a&.a&.a" + body = compile(src, __LINE__, {peephole_optimization: true}).to_a[13] + labels = body.select {|op, arg| op == :branchnil}.map {|op, arg| arg} + assert_equal(1, labels.uniq.size) + end if (!defined?(Coverage) || !Coverage.running?) + + def test_parent_iseq_mark + assert_separately([], <<-'end;', timeout: 20) + ->{ + ->{ + ->{ + eval <<-EOS + class Segfault + define_method :segfault do + x = nil + GC.disable + 1000.times do |n| + n.times do + x = (foo rescue $!).local_variables + end + GC.start + end + x + end + end + EOS + }.call + }.call + }.call + at_exit { assert_equal([:n, :x], Segfault.new.segfault.sort) } + end; + end + + def test_syntax_error_message + feature11951 = '[Feature #11951]' + + src, line = <<-'end;', __LINE__+1 + def x@;end + def y@;end + end; + e1 = e2 = nil + m1 = EnvUtil.verbose_warning do + e1 = assert_raise(SyntaxError) do + eval(src, nil, __FILE__, line) + end + end + m2 = EnvUtil.verbose_warning do + e2 = assert_raise(SyntaxError) do + ISeq.new(src, __FILE__, __FILE__, line) + 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__]) + end + + def test_compile_file_error + Tempfile.create(%w"test_iseq .rb") do |f| + f.puts "end" + f.close + path = f.path + assert_in_out_err(%W[- #{path}], "#{<<-"begin;"}\n#{<<-"end;"}", /keyword_end/, [], success: true) + begin; + path = ARGV[0] + begin + RubyVM::InstructionSequence.compile_file(path) + rescue SyntaxError => e + puts e.message + end + end; + end + end + + def test_translate_by_object + assert_separately([], <<-"end;") + class Object + def translate + end + end + assert_equal(0, eval("0")) + end; + end + + def test_inspect + %W[foo \u{30d1 30b9}].each do |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 + end + end + + def sample_iseq + ISeq.compile <<-EOS.gsub(/^.*?: /, "") + 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 ary[9] + 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 test_to_binary_with_objects + # conceptually backport from r62856. + # ISeq binary dump doesn't consider alignment in 2.5 and older + skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM + code = "[]"+100.times.map{|i|"<</#{i}/"}.join + iseq = RubyVM::InstructionSequence.compile(code) + bin = assert_nothing_raised do + iseq.to_binary + rescue RuntimeError => e + skip e.message if /compile with coverage/ =~ e.message + raise + end + iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) + assert_equal(iseq2.to_a, iseq.to_a) + end + + def test_to_binary_tracepoint + # conceptually backport from r62856. + # ISeq binary dump doesn't consider alignment in 2.5 and older + skip "does not work on other than x86" unless /x(?:86|64)|i\d86/ =~ RUBY_PLATFORM + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + iseq = RubyVM::InstructionSequence.compile("x = 1\n y = 2", filename) + # conceptually partial backport from r63103, r65567. + # All ISeq#to_binary should rescue to skip when running with coverage. + # current trunk (2.6-) has assert_iseq_to_binary. + begin + iseq_bin = iseq.to_binary + rescue RuntimeError => e + skip e.message if /compile with coverage/ =~ e.message + raise + end + ary = [] + TracePoint.new(:line){|tp| + next unless tp.path == filename + ary << [tp.path, tp.lineno] + }.enable{ + ISeq.load_from_binary(iseq_bin).eval + } + assert_equal [[filename, 1], [filename, 2]], ary, '[Bug #14702]' end end diff --git a/test/ruby/test_iterator.rb b/test/ruby/test_iterator.rb index 34652db2bb..9bfa947607 100644 --- a/test/ruby/test_iterator.rb +++ b/test/ruby/test_iterator.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class Array @@ -73,7 +74,7 @@ class TestIterator < Test::Unit::TestCase def test_break done = true loop{ - break + break if true done = false # should not reach here } assert(done) @@ -83,7 +84,7 @@ class TestIterator < Test::Unit::TestCase loop { break if done done = true - next + next if true bad = true # should not reach here } assert(!bad) @@ -93,7 +94,7 @@ class TestIterator < Test::Unit::TestCase loop { break if done done = true - redo + redo if true bad = true # should not reach here } assert(!bad) @@ -108,7 +109,7 @@ class TestIterator < Test::Unit::TestCase def test_append_method_to_built_in_class x = [[1,2],[3,4],[5,6]] - assert_equal(x.iter_test1{|x|x}, x.iter_test2{|x|x}) + assert_equal(x.iter_test1{|e|e}, x.iter_test2{|e|e}) end class IterTest @@ -278,6 +279,9 @@ class TestIterator < Test::Unit::TestCase def proc_call(&b) b.call end + def proc_call2(b) + b.call + end def proc_yield() yield end @@ -299,6 +303,7 @@ class TestIterator < Test::Unit::TestCase def test_ljump assert_raise(LocalJumpError) {get_block{break}.call} + assert_raise(LocalJumpError) {proc_call2(get_block{break}){}} # cannot use assert_nothing_raised due to passing block. begin diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 13bac278f2..8a45016c13 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestKeywordArguments < Test::Unit::TestCase def f1(str: "foo", num: 424242) @@ -311,11 +311,16 @@ class TestKeywordArguments < Test::Unit::TestCase feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument' o = Object.new assert_nothing_raised(SyntaxError, feature7701) do - eval("def o.foo(a:) a; end") + eval("def o.foo(a:) a; end", nil, "xyzzy") eval("def o.bar(a:,**b) [a, b]; end") end assert_raise_with_message(ArgumentError, /missing keyword/, feature7701) {o.foo} assert_raise_with_message(ArgumentError, /unknown keyword/, feature7701) {o.foo(a:0, b:1)} + begin + o.foo(a: 0, b: 1) + rescue => e + assert_equal('xyzzy', e.backtrace_locations[0].path) + end assert_equal(42, o.foo(a: 42), feature7701) assert_equal([[:keyreq, :a]], o.method(:foo).parameters, feature7701) @@ -363,22 +368,33 @@ class TestKeywordArguments < Test::Unit::TestCase def test_block_required_keyword feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument' b = assert_nothing_raised(SyntaxError, feature7701) do - break eval("proc {|a:| a}") + break eval("proc {|a:| a}", nil, 'xyzzy', __LINE__) end assert_raise_with_message(ArgumentError, /missing keyword/, feature7701) {b.call} - assert_raise_with_message(ArgumentError, /unknown keyword/, feature7701) {b.call(a:0, b:1)} + e = assert_raise_with_message(ArgumentError, /unknown keyword/, feature7701) {b.call(a:0, b:1)} + assert_equal('xyzzy', e.backtrace_locations[0].path) + assert_equal(42, b.call(a: 42), feature7701) assert_equal([[:keyreq, :a]], b.parameters, feature7701) bug8139 = '[ruby-core:53608] [Bug #8139] required keyword argument with rest hash' b = assert_nothing_raised(SyntaxError, feature7701) do - break eval("proc {|a:, **b| [a, b]}") + break eval("proc {|a:, **bl| [a, bl]}", nil, __FILE__, __LINE__) end assert_equal([42, {}], b.call(a: 42), feature7701) assert_equal([42, {c: feature7701}], b.call(a: 42, c: feature7701), feature7701) - assert_equal([[:keyreq, :a], [:keyrest, :b]], b.parameters, feature7701) + assert_equal([[:keyreq, :a], [:keyrest, :bl]], b.parameters, feature7701) assert_raise_with_message(ArgumentError, /missing keyword/, bug8139) {b.call(c: bug8139)} assert_raise_with_message(ArgumentError, /missing keyword/, bug8139) {b.call} + + b = assert_nothing_raised(SyntaxError, feature7701) do + break eval("proc {|m, a:| [m, a]}", nil, 'xyzzy', __LINE__) + end + assert_raise_with_message(ArgumentError, /missing keyword/) {b.call} + assert_equal([:ok, 42], b.call(:ok, a: 42)) + e = assert_raise_with_message(ArgumentError, /unknown keyword/) {b.call(42, a:0, b:1)} + assert_equal('xyzzy', e.backtrace_locations[0].path) + assert_equal([[:opt, :m], [:keyreq, :a]], b.parameters) end def test_super_with_keyword @@ -440,7 +456,7 @@ class TestKeywordArguments < Test::Unit::TestCase end end assert_equal([{}, {}], a.new.foo({})) - assert_equal([{}, {:bar=>"x"}], a.new.foo({}, bar: "x")) + assert_equal([{}, {:bar=>"x"}], a.new.foo({}, bar: "x"), bug8040) end def test_precedence_of_keyword_arguments_with_post_argument @@ -487,6 +503,29 @@ class TestKeywordArguments < Test::Unit::TestCase assert_equal([1, 9], m1(1, o, &->(a, k: 0) {break [a, k]}), bug10016) end + def test_splat_hash + m = Object.new + def m.f() :ok; end + def m.f2(a = nil) a; end + o = {a: 1} + assert_raise_with_message(ArgumentError, /unknown keyword: a/) { + 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]') + + o = {a: 42} + assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]') + + assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]') + + o = {} + a = [:ok] + assert_equal(:ok, m.f2(*a, **o), '[ruby-core:83638] [Bug #10856]') + end + def test_gced_object_in_stack bug8964 = '[ruby-dev:47729] [Bug #8964]' assert_normal_exit %q{ @@ -506,6 +545,16 @@ class TestKeywordArguments < Test::Unit::TestCase }, bug8964 end + def test_dynamic_symbol_keyword + bug10266 = '[ruby-dev:48564] [Bug #10266]' + assert_separately(['-', bug10266], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bug = ARGV.shift + "hoge".to_sym + assert_nothing_raised(bug) {eval("def a(hoge:); end")} + end; + end + def test_unknown_keyword_with_block bug10413 = '[ruby-core:65837] [Bug #10413]' class << (o = Object.new) @@ -520,4 +569,117 @@ class TestKeywordArguments < Test::Unit::TestCase 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) { + [].sample(random: nil, "invalid-argument": nil) + } + end + + def test_super_with_anon_restkeywords + bug10659 = '[ruby-core:67157] [Bug #10659]' + + foo = Class.new do + def foo(**h) + h + end + end + + class << (obj = foo.new) + def foo(bar: "bar", **) + super + end + end + + assert_nothing_raised(TypeError, bug10659) { + assert_equal({:bar => "bar"}, obj.foo, bug10659) + } + end + + def m(a) yield a end + + def test_nonsymbol_key + result = m(["a" => 10]) { |a = nil, **b| [a, b] } + assert_equal([{"a" => 10}, {}], result) + end + + def method_for_test_to_hash_call_during_setup_complex_parameters k1:, k2:, **rest_kw + [k1, k2, rest_kw] + end + + def test_to_hash_call_during_setup_complex_parameters + sym = "sym_#{Time.now}".to_sym + h = method_for_test_to_hash_call_during_setup_complex_parameters k1: "foo", k2: "bar", sym => "baz" + assert_equal ["foo", "bar", {sym => "baz"}], h, '[Bug #11027]' + end + + class AttrSetTest + attr_accessor :foo + alias set_foo :foo= + end + + def test_attr_set_method_cache + obj = AttrSetTest.new + h = {a: 1, b: 2} + 2.times{ + obj.foo = 1 + assert_equal(1, obj.foo) + obj.set_foo 2 + assert_equal(2, obj.foo) + obj.set_foo(x: 1, y: 2) + assert_equal({x: 1, y: 2}, obj.foo) + obj.set_foo(x: 1, y: 2, **h) + assert_equal({x: 1, y: 2, **h}, obj.foo) + } + end + + def test_kwrest_overwritten + bug13015 = '[ruby-core:78536] [Bug #13015]' + + klass = EnvUtil.labeled_class("Parent") do + def initialize(d:) + end + end + + klass = EnvUtil.labeled_class("Child", klass) do + def initialize(d:, **h) + h = [2, 3] + super + end + end + + assert_raise_with_message(TypeError, /expected Hash/, bug13015) do + klass.new(d: 4) + end + end + + def test_non_keyword_hash_subclass + bug12884 = '[ruby-core:77813] [Bug #12884]' + klass = EnvUtil.labeled_class("Child", Hash) + obj = Object.new + def obj.t(params = klass.new, d: nil); params; end + x = klass.new + x["foo"] = "bar" + result = obj.t(x) + assert_equal(x, result) + assert_kind_of(klass, result, bug12884) + end + + def test_arity_error_message + obj = Object.new + def obj.t(x:) end + assert_raise_with_message(ArgumentError, /required keyword: x\)/) do + obj.t(42) + end + obj = Object.new + def obj.t(x:, y:, z: nil) end + assert_raise_with_message(ArgumentError, /required keywords: x, y\)/) do + obj.t(42) + end + end + + def test_splat_empty_hash_with_block_passing + assert_valid_syntax("bug15087(**{}, &nil)") + end end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 24efa71bc1..3ac2e4cb98 100644 --- a/test/ruby/test_lambda.rb +++ b/test/ruby/test_lambda.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestLambdaParameters < Test::Unit::TestCase @@ -25,8 +26,15 @@ class TestLambdaParameters < Test::Unit::TestCase def test_lambda_as_iterator a = 0 2.times(&->(_){ a += 1 }) - assert_equal(a, 2) + assert_equal(2, a) assert_raise(ArgumentError) {1.times(&->(){ a += 1 })} + bug9605 = '[ruby-core:61468] [Bug #9605]' + assert_nothing_raised(ArgumentError, bug9605) {1.times(&->(n){ a += 1 })} + assert_equal(3, a, bug9605) + assert_nothing_raised(ArgumentError, bug9605) { + a = %w(Hi there how are you).each_with_index.detect(&->(w, i) {w.length == 3}) + } + assert_equal(["how", 2], a, bug9605) end def test_call_rest_args @@ -55,9 +63,48 @@ class TestLambdaParameters < Test::Unit::TestCase assert_equal(nil, ->(&b){ b }.call) foo { puts "bogus block " } assert_equal(1, ->(&b){ b.call }.call { 1 }) - b = nil - assert_equal(1, ->(&b){ b.call }.call { 1 }) - assert_nil(b) + _b = nil + assert_equal(1, ->(&_b){ _b.call }.call { 1 }) + assert_nil(_b) + end + + def test_call_block_from_lambda + bug9605 = '[ruby-core:61470] [Bug #9605]' + plus = ->(x,y) {x+y} + assert_raise(ArgumentError, bug9605) {proc(&plus).call [1,2]} + end + + def test_instance_exec + bug12568 = '[ruby-core:76300] [Bug #12568]' + assert_nothing_raised(ArgumentError, bug12568) do + instance_exec([1,2,3], &->(a=[]){ a }) + end + end + + def test_instance_eval_return + bug13090 = '[ruby-core:78917] [Bug #13090] cannot return in lambdas' + x = :ng + assert_nothing_raised(LocalJumpError) do + x = instance_eval(&->(_){return :ok}) + end + ensure + assert_equal(:ok, x, bug13090) + end + + def test_instance_exec_return + bug13090 = '[ruby-core:78917] [Bug #13090] cannot return in lambdas' + x = :ng + assert_nothing_raised(LocalJumpError) do + x = instance_exec(&->(){return :ok}) + end + ensure + assert_equal(:ok, x, bug13090) + end + + def test_arity_error + assert_raise(ArgumentError, '[Bug #12705]') do + [1, 2].tap(&lambda {|a, b|}) + end end def foo @@ -91,7 +138,7 @@ class TestLambdaParameters < Test::Unit::TestCase end def return_in_current(val) - 1.tap &->(*) {return 0} + 1.tap(&->(*) {return 0}) val end @@ -100,7 +147,7 @@ class TestLambdaParameters < Test::Unit::TestCase end def return_in_callee(val) - yield_block &->(*) {return 0} + yield_block(&->(*) {return 0}) val end diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 760db95f7b..03371c912a 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestLazyEnumerator < Test::Unit::TestCase class Step @@ -14,7 +14,14 @@ class TestLazyEnumerator < Test::Unit::TestCase def each(*args) @args = args - @enum.each {|i| @current = i; yield i} + @enum.each do |v| + @current = v + if v.is_a? Enumerable + yield *v + else + yield v + end + end end end @@ -25,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 @@ -100,6 +107,15 @@ class TestLazyEnumerator < Test::Unit::TestCase assert_equal(1, a.current) end + def test_map_packed_nested + bug = '[ruby-core:81638] [Bug#13648]' + + a = Step.new([[1, 2]]) + expected = [[[1, 2]]] + assert_equal(expected, a.map {|*args| args}.map {|*args| args}.to_a) + assert_equal(expected, a.lazy.map {|*args| args}.map {|*args| args}.to_a, bug) + end + def test_flat_map a = Step.new(1..3) assert_equal(2, a.flat_map {|x| [x * 2]}.first) @@ -198,6 +214,34 @@ class TestLazyEnumerator < Test::Unit::TestCase e.lazy.grep(proc {|x| x == [2, "2"]}, &:join).force) end + def test_grep_v + a = Step.new('a'..'f') + assert_equal('b', a.grep_v(/a/).first) + assert_equal('f', a.current) + assert_equal('a', a.lazy.grep_v(/c/).first) + assert_equal('a', a.current) + assert_equal(%w[b c d f], a.grep_v(proc {|x| /[aeiou]/ =~ x})) + assert_equal(%w[b c d f], a.lazy.grep_v(proc {|x| /[aeiou]/ =~ x}).to_a) + end + + def test_grep_v_with_block + a = Step.new('a'..'f') + assert_equal('B', a.grep_v(/a/) {|i| i.upcase}.first) + assert_equal('B', a.lazy.grep_v(/a/) {|i| i.upcase}.first) + end + + def test_grep_v_multiple_values + e = Enumerator.new { |yielder| + 3.times { |i| + yielder.yield(i, i.to_s) + } + } + assert_equal([[0, "0"], [1, "1"]], e.grep_v(proc {|x| x == [2, "2"]})) + assert_equal([[0, "0"], [1, "1"]], e.lazy.grep_v(proc {|x| x == [2, "2"]}).force) + assert_equal(["00", "11"], + e.lazy.grep_v(proc {|x| x == [2, "2"]}, &:join).force) + end + def test_zip a = Step.new(1..3) assert_equal([1, "a"], a.zip("a".."c").first) @@ -247,6 +291,11 @@ class TestLazyEnumerator < Test::Unit::TestCase assert_equal(nil, a.current) end + def test_take_bad_arg + a = Step.new(1..10) + assert_raise(ArgumentError) { a.lazy.take(-1) } + end + def test_take_recycle bug6428 = '[ruby-dev:45634]' a = Step.new(1..10) @@ -313,11 +362,11 @@ class TestLazyEnumerator < Test::Unit::TestCase def test_take_rewound bug7696 = '[ruby-core:51470]' e=(1..42).lazy.take(2) - assert_equal 1, e.next - assert_equal 2, e.next + assert_equal 1, e.next, bug7696 + assert_equal 2, e.next, bug7696 e.rewind - assert_equal 1, e.next - assert_equal 2, e.next + assert_equal 1, e.next, bug7696 + assert_equal 2, e.next, bug7696 end def test_take_while @@ -452,6 +501,15 @@ EOS assert_equal Float::INFINITY, loop.lazy.cycle.size assert_equal nil, lazy.select{}.cycle(4).size assert_equal nil, lazy.select{}.cycle.size + + class << (obj = Object.new) + def each; end + def size; 0; end + include Enumerable + end + lazy = obj.lazy + assert_equal 0, lazy.cycle.size + assert_raise(TypeError) {lazy.cycle("").size} end def test_map_zip @@ -470,6 +528,7 @@ EOS bug7507 = '[ruby-core:51510]' { slice_before: //, + slice_after: //, with_index: nil, cycle: nil, each_with_object: 42, @@ -480,6 +539,19 @@ EOS assert_equal Enumerator::Lazy, [].lazy.send(method, *arg).class, bug7507 end assert_equal Enumerator::Lazy, [].lazy.chunk{}.class, bug7507 + assert_equal Enumerator::Lazy, [].lazy.slice_when{}.class, bug7507 + end + + def test_each_cons_limit + n = 1 << 120 + assert_equal([1, 2], (1..n).lazy.each_cons(2).first) + assert_equal([[1, 2], [2, 3]], (1..n).lazy.each_cons(2).first(2)) + end + + def test_each_slice_limit + n = 1 << 120 + assert_equal([1, 2], (1..n).lazy.each_slice(2).first) + assert_equal([[1, 2], [3, 4]], (1..n).lazy.each_slice(2).first(2)) end def test_no_warnings @@ -490,4 +562,20 @@ EOS assert_warning("") {le.drop(1).force} assert_warning("") {le.drop_while{false}.force} end + + def test_symbol_chain + assert_equal(["1", "3"], [1, 2, 3].lazy.reject(&:even?).map(&:to_s).force) + assert_raise(NoMethodError) do + [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 end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index 6e6831968d..cf1a2babd7 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -1,6 +1,6 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestRubyLiteral < Test::Unit::TestCase @@ -14,20 +14,20 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal ':sym', :sym.inspect assert_instance_of Symbol, :sym assert_equal '1234', 1234.inspect - assert_instance_of Fixnum, 1234 + assert_instance_of Integer, 1234 assert_equal '1234', 1_2_3_4.inspect - assert_instance_of Fixnum, 1_2_3_4 + assert_instance_of Integer, 1_2_3_4 assert_equal '18', 0x12.inspect - assert_instance_of Fixnum, 0x12 + assert_instance_of Integer, 0x12 assert_raise(SyntaxError) { eval("0x") } assert_equal '15', 0o17.inspect - assert_instance_of Fixnum, 0o17 + assert_instance_of Integer, 0o17 assert_raise(SyntaxError) { eval("0o") } assert_equal '5', 0b101.inspect - assert_instance_of Fixnum, 0b101 + assert_instance_of Integer, 0b101 assert_raise(SyntaxError) { eval("0b") } assert_equal '123456789012345678901234567890', 123456789012345678901234567890.inspect - assert_instance_of Bignum, 123456789012345678901234567890 + assert_instance_of Integer, 123456789012345678901234567890 assert_instance_of Float, 1.3 assert_equal '2', eval("0x00+2").inspect end @@ -90,6 +90,9 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal "\u201c", eval(%[?\\\u{201c}]), bug6069 assert_equal "\u201c".encode("euc-jp"), eval(%[?\\\u{201c}].encode("euc-jp")), bug6069 assert_equal "\u201c".encode("iso-8859-13"), eval(%[?\\\u{201c}].encode("iso-8859-13")), bug6069 + + assert_equal "ab", eval("?a 'b'") + assert_equal "a\nb", eval("<<A 'b'\na\nA") end def test_dstring @@ -116,12 +119,78 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal :a3c, :"a#{1+2}c" end + def test_dsymbol_redefined_intern + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + class String + alias _intern intern + def intern + "<#{upcase}>" + end + end + mesg = "literal symbol should not be affected by method redefinition" + str = "foo" + assert_equal(:foo, :"#{str}", mesg) + end; + end + def test_xstring assert_equal "foo\n", `echo foo` s = 'foo' assert_equal "foo\n", `echo #{s}` end + def test_frozen_string + all_assertions do |a| + a.for("false with indicator") do + str = eval("# -*- frozen-string-literal: false -*-\n""'foo'") + assert_not_predicate(str, :frozen?) + end + a.for("true with indicator") do + str = eval("# -*- frozen-string-literal: true -*-\n""'foo'") + assert_predicate(str, :frozen?) + end + a.for("false without indicator") do + str = eval("# frozen-string-literal: false\n""'foo'") + assert_not_predicate(str, :frozen?) + end + a.for("true without indicator") do + str = eval("# frozen-string-literal: true\n""'foo'") + assert_predicate(str, :frozen?) + end + a.for("false with preceding garbage") do + str = eval("# x frozen-string-literal: false\n""'foo'") + assert_not_predicate(str, :frozen?) + end + a.for("true with preceding garbage") do + str = eval("# x frozen-string-literal: true\n""'foo'") + assert_not_predicate(str, :frozen?) + end + a.for("false with succeeding garbage") do + str = eval("# frozen-string-literal: false x\n""'foo'") + assert_not_predicate(str, :frozen?) + end + a.for("true with succeeding garbage") do + str = eval("# frozen-string-literal: true x\n""'foo'") + assert_not_predicate(str, :frozen?) + end + end + end + + if defined?(RubyVM::InstructionSequence.compile_option) and + RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal) + def test_debug_frozen_string + src = 'n = 1; "foo#{n ? "-#{n}" : ""}"'; f = "test.rb"; n = 1 + 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" + } + end + end + def test_regexp assert_instance_of Regexp, // assert_match(//, 'a') @@ -354,6 +423,35 @@ class TestRubyLiteral < Test::Unit::TestCase end; end + def test_hash_duplicated_key + 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" => 100, "b" => 200, "a" => 300, "a" => 400} + 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')) + end + + def test_hash_frozen_key_id + key = "a".freeze + h = {key => 100} + assert_equal(100, h['a']) + assert_same(key, *h.keys) + end + + def test_hash_key_tampering + key = "a" + h = {key => 100} + key.upcase! + assert_equal(100, h['a']) + end + def test_range assert_instance_of Range, (1..2) assert_equal(1..2, 1..2) @@ -390,7 +488,7 @@ class TestRubyLiteral < Test::Unit::TestCase end def test__LINE__ - assert_instance_of Fixnum, __LINE__ + assert_instance_of Integer, __LINE__ assert_equal __LINE__, __LINE__ end @@ -456,6 +554,7 @@ class TestRubyLiteral < Test::Unit::TestCase } } } + assert_equal(100.0, 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100e100) end def test_symbol_list diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index e933475997..9c7df4cb03 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -1,6 +1,6 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestM17N < Test::Unit::TestCase def assert_encoding(encname, actual, message=nil) @@ -9,7 +9,7 @@ class TestM17N < Test::Unit::TestCase module AESU def ua(str) str.dup.force_encoding("US-ASCII") end - def a(str) str.dup.force_encoding("ASCII-8BIT") end + def a(str) str.b end def e(str) str.dup.force_encoding("EUC-JP") end def s(str) str.dup.force_encoding("Windows-31J") end def u(str) str.dup.force_encoding("UTF-8") end @@ -261,6 +261,21 @@ class TestM17N < Test::Unit::TestCase end end + def test_utf_without_bom_asciionly + bug10598 = '[ruby-core:66835] [Bug #10598]' + encs = [Encoding::UTF_16, Encoding::UTF_32].find_all {|enc| + "abcd".force_encoding(enc).ascii_only? + } + 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 @@ -271,7 +286,7 @@ class TestM17N < Test::Unit::TestCase o = Object.new [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE].each do |e| o.instance_eval "undef inspect;def inspect;'abc'.encode('#{e}');end" - assert_raise(Encoding::CompatibilityError) { [o].inspect } + assert_equal '[abc]', [o].inspect end ensure Encoding.default_internal = orig_int @@ -295,13 +310,18 @@ class TestM17N < Test::Unit::TestCase def o.inspect "abc".encode(Encoding.default_external) end - assert_raise(Encoding::CompatibilityError) { [o].inspect } + assert_equal '[abc]', [o].inspect Encoding.default_external = Encoding::US_ASCII def o.inspect "\u3042" end - assert_raise(Encoding::CompatibilityError) { [o].inspect } + assert_equal '[\u3042]', [o].inspect + + def o.inspect + "\x82\xa0".force_encoding(Encoding::Windows_31J) + end + assert_equal '[\x{82A0}]', [o].inspect ensure Encoding.default_internal = orig_int Encoding.default_external = orig_ext @@ -452,7 +472,7 @@ class TestM17N < Test::Unit::TestCase def test_regexp_ascii_none r = /a/n - assert_warning(%r{regexp match /.../n against to}) { + assert_warning(%r{historical binary regexp match /\.\.\./n against}) { assert_regexp_generic_ascii(r) } @@ -461,13 +481,13 @@ class TestM17N < Test::Unit::TestCase assert_equal(0, r =~ s("a")) assert_equal(0, r =~ u("a")) assert_equal(nil, r =~ a("\xc2\xa1")) - assert_warning(%r{regexp match /.../n against to EUC-JP string}) { + assert_warning(%r{historical binary regexp match /\.\.\./n against EUC-JP string}) { assert_equal(nil, r =~ e("\xc2\xa1")) } - assert_warning(%r{regexp match /.../n against to Windows-31J string}) { + assert_warning(%r{historical binary regexp match /\.\.\./n against Windows-31J string}) { assert_equal(nil, r =~ s("\xc2\xa1")) } - assert_warning(%r{regexp match /.../n against to UTF-8 string}) { + assert_warning(%r{historical binary regexp match /\.\.\./n against UTF-8 string}) { assert_equal(nil, r =~ u("\xc2\xa1")) } @@ -480,6 +500,9 @@ class TestM17N < Test::Unit::TestCase assert_regexp_fixed_ascii8bit(eval(a(%{/\xc2\xa1/n}))) assert_regexp_fixed_ascii8bit(eval(a(%q{/\xc2\xa1/}))) + s = '\xc2\xa1' + assert_regexp_fixed_ascii8bit(/#{s}/) + assert_raise(SyntaxError) { eval("/\xa1\xa1/n".force_encoding("euc-jp")) } [/\xc2\xa1/n, eval(a(%{/\xc2\xa1/})), eval(a(%{/\xc2\xa1/n}))].each {|r| @@ -709,7 +732,7 @@ class TestM17N < Test::Unit::TestCase def test_union_1_regexp assert_regexp_generic_ascii(Regexp.union(//)) - assert_warning(%r{regexp match /.../n against to}) { + assert_warning(%r{historical binary regexp match /.../n against}) { assert_regexp_generic_ascii(Regexp.union(//n)) } assert_regexp_fixed_eucjp(Regexp.union(//e)) @@ -752,7 +775,7 @@ class TestM17N < Test::Unit::TestCase end def test_dynamic_ascii_regexp - assert_warning(%r{regexp match /.../n against to}) { + assert_warning(%r{historical binary regexp match /.../n against}) { assert_regexp_generic_ascii(/#{ }/n) } assert_regexp_fixed_ascii8bit(/#{ }\xc2\xa1/n) @@ -1115,7 +1138,7 @@ class TestM17N < Test::Unit::TestCase def test_dup_scan s1 = e("\xa4\xa2")*100 - s2 = s1.dup.force_encoding("ascii-8bit") + s2 = s1.b s2.scan(/\A./n) {|f| assert_equal(Encoding::ASCII_8BIT, f.encoding) } @@ -1123,7 +1146,7 @@ class TestM17N < Test::Unit::TestCase def test_dup_aref s1 = e("\xa4\xa2")*100 - s2 = s1.dup.force_encoding("ascii-8bit") + s2 = s1.b assert_equal(Encoding::ASCII_8BIT, s2[10..-1].encoding) end @@ -1478,6 +1501,31 @@ class TestM17N < Test::Unit::TestCase s = u("\xE3\x81\x82\xE3\x81\x84") s.setbyte(-4, 0x84) assert_equal(u("\xE3\x81\x84\xE3\x81\x84"), s) + + x = "x" * 100 + t = nil + failure = proc {"#{i}: #{encdump(t)}"} + + s = "\u{3042 3044}" + s.bytesize.times {|i| + t = s + x + t.setbyte(i, t.getbyte(i)+1) + assert_predicate(t, :valid_encoding?, failure) + assert_not_predicate(t, :ascii_only?, failure) + t = s + x + t.setbyte(i, 0x20) + assert_not_predicate(t, :valid_encoding?, failure) + } + + s = "\u{41 42 43}" + s.bytesize.times {|i| + t = s + x + t.setbyte(i, 0x20) + assert_predicate(t, :valid_encoding?, failure) + assert_predicate(t, :ascii_only?, failure) + t.setbyte(i, 0xe3) + assert_not_predicate(t, :valid_encoding?, failure) + } end def test_compatible @@ -1582,8 +1630,9 @@ class TestM17N < Test::Unit::TestCase assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub{u("\x81")} } assert_equal(e("\xA4\xA2\xA2\xAE"), e("\xA4\xA2\xA4").scrub{e("\xA2\xAE")}) - assert_equal(u("\x81"), u("a\x81").scrub {|c| break c}) + assert_equal(u("\x81"), u("a\x81c").scrub {|c| break c}) assert_raise(ArgumentError) {u("a\x81").scrub {|c| c}} + assert_raise(ArgumentError) {u("a").scrub("?") {|c| c}} end def test_scrub_widechar @@ -1599,6 +1648,18 @@ class TestM17N < Test::Unit::TestCase assert_equal("\uFFFD".encode("UTF-32LE"), "\xff".force_encoding(Encoding::UTF_32LE). scrub) + c = nil + assert_equal("?\u3042".encode(Encoding::UTF_16LE), + "\x00\xD8\x42\x30".force_encoding(Encoding::UTF_16LE). + scrub {|e| c = e; "?".encode(Encoding::UTF_16LE)}) + assert_equal("\x00\xD8".force_encoding(Encoding::UTF_16LE), c) + assert_raise(ArgumentError) {"\uFFFD\u3042".encode("UTF-16BE").scrub("") {}} + end + + def test_scrub_dummy_encoding + s = "\u{3042}".encode("iso-2022-jp") + assert_equal(s, s.scrub) + assert_equal(s, s.force_encoding("iso-2022-jp").scrub("?")) end def test_scrub_bang @@ -1614,6 +1675,24 @@ class TestM17N < Test::Unit::TestCase assert_equal("\uFFFD\uFFFD\uFFFD", str) end + def test_escaped_metachar + bug10670 = '[ruby-core:67193] [Bug #10670]' + + escape_plain = /\A[\x5B]*\z/.freeze + + assert_match(escape_plain, 0x5b.chr(::Encoding::UTF_8), bug10670) + assert_match(escape_plain, 0x5b.chr, bug10670) + end + + def test_inspect_with_default_internal + bug11787 = '[ruby-dev:49415] [Bug #11787]' + + s = EnvUtil.with_default_internal(::Encoding::EUC_JP) do + [e("\xB4\xC1\xBB\xFA")].inspect + end + assert_equal(e("[\"\xB4\xC1\xBB\xFA\"]"), s, bug11787) + end + def test_greek_capital_gap bug12204 = '[ruby-core:74478] [Bug #12204] GREEK CAPITAL RHO and SIGMA' assert_equal("\u03A3", "\u03A1".succ, bug12204) diff --git a/test/ruby/test_m17n_comb.rb b/test/ruby/test_m17n_comb.rb index 55bfe39553..99c162a92f 100644 --- a/test/ruby/test_m17n_comb.rb +++ b/test/ruby/test_m17n_comb.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: false require 'test/unit' +require 'etc' require_relative 'allpairs' class TestM17NComb < Test::Unit::TestCase @@ -658,7 +660,9 @@ class TestM17NComb < Test::Unit::TestCase combination(STRINGS, STRINGS) {|s1, s2| if !s1.ascii_only? && !s2.ascii_only? && !Encoding.compatible?(s1,s2) if s1.bytesize > s2.bytesize - assert_raise(Encoding::CompatibilityError) { s1.chomp(s2) } + assert_raise(Encoding::CompatibilityError, "#{encdump(s1)}.chomp(#{encdump(s2)})") do + s1.chomp(s2) + end end next end @@ -671,6 +675,17 @@ class TestM17NComb < Test::Unit::TestCase } end + def test_str_smart_chomp + bug10893 = '[ruby-core:68258] [Bug #10893]' + encodings = Encoding.list.select {|enc| !enc.dummy?} + combination(encodings, encodings) do |e1, e2| + expected = "abc".encode(e1) + combination(["abc\n", "abc\r\n"], ["", "\n"]) do |str, rs| + assert_equal(expected, str.encode(e1).chomp(rs.encode(e2)), bug10893) + end + end + end + def test_str_chop STRINGS.each {|s| s = s.dup @@ -729,29 +744,43 @@ class TestM17NComb < Test::Unit::TestCase } end - def test_str_crypt - begin - # glibc 2.16 or later denies salt contained other than [0-9A-Za-z./] #7312 - glibcpath = `ldd #{RbConfig.ruby}`[/\S+\/libc.so\S+/] - glibcver = `#{glibcpath}`[/\AGNU C Library.*version ([0-9.]+)/, 1].split('.').map(&:to_i) - strict_crypt = (glibcver <=> [2, 16]) > -1 - rescue - 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 + end + def test_str_crypt combination(STRINGS, STRINGS) {|str, salt| - if strict_crypt - next unless salt.ascii_only? && /\A[0-9a-zA-Z.\/]+\z/ =~ salt - end - if b(salt).length < 2 - assert_raise(ArgumentError) { str.crypt(salt) } - next - end - t = str.crypt(salt) - assert_equal(b(str).crypt(b(salt)), t, "#{encdump(str)}.crypt(#{encdump(salt)})") - assert_encoding('ASCII-8BIT', t.encoding) + # skip input other than [0-9A-Za-z./] to confirm strict behavior + next unless salt.ascii_only? && /\A[0-9a-zA-Z.\/]+\z/ =~ salt + + confirm_crypt_result(str, salt) } end + if !strict_crypt + def test_str_crypt_nonstrict + combination(STRINGS, STRINGS) {|str, salt| + # only test input other than [0-9A-Za-z./] to confirm non-strict behavior + next if salt.ascii_only? && /\A[0-9a-zA-Z.\/]+\z/ =~ salt + + confirm_crypt_result(str, salt) + } + end + end + + private def confirm_crypt_result(str, salt) + if b(salt).length < 2 + assert_raise(ArgumentError) { str.crypt(salt) } + return + end + t = str.crypt(salt) + assert_equal(b(str).crypt(b(salt)), t, "#{encdump(str)}.crypt(#{encdump(salt)})") + assert_encoding('ASCII-8BIT', t.encoding) + end + def test_str_delete combination(STRINGS, STRINGS) {|s1, s2| if s1.empty? @@ -779,7 +808,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_downcase STRINGS.each {|s| if !s.valid_encoding? - assert_raise(ArgumentError) { s.downcase } + assert_raise(ArgumentError, "Offending string: #{s.inspect}, encoding: #{s.encoding}") { s.downcase } next end t = s.downcase @@ -1042,25 +1071,26 @@ class TestM17NComb < Test::Unit::TestCase def test_str_scan combination(STRINGS, STRINGS) {|s1, s2| + desc = proc {"#{s1.dump}.scan(#{s2.dump})"} if !s2.valid_encoding? - assert_raise(RegexpError) { s1.scan(s2) } + assert_raise(RegexpError, desc) { s1.scan(s2) } next end if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding if s1.valid_encoding? - assert_raise(Encoding::CompatibilityError) { s1.scan(s2) } + assert_raise(Encoding::CompatibilityError, desc) { s1.scan(s2) } else - assert_match(/invalid byte sequence/, assert_raise(ArgumentError) { s1.scan(s2) }.message) + assert_raise_with_message(ArgumentError, /invalid byte sequence/, desc) { s1.scan(s2) } end next end if !s1.valid_encoding? - assert_raise(ArgumentError) { s1.scan(s2) } + assert_raise(ArgumentError, desc) { s1.scan(s2) } next end r = enccall(s1, :scan, s2) r.each {|t| - assert_equal(s2, t) + assert_equal(s2, t, desc) } } end @@ -1546,8 +1576,8 @@ class TestM17NComb < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError, desc) { s1.start_with?(s2) } next end - s1 = s1.dup.force_encoding("ASCII-8BIT") - s2 = s2.dup.force_encoding("ASCII-8BIT") + s1 = s1.b + s2 = s2.b if s1.length < s2.length assert_equal(false, enccall(s1, :start_with?, s2), desc) next @@ -1606,4 +1636,9 @@ class TestM17NComb < Test::Unit::TestCase } end + def test_bug11486 + bug11486 = '[Bug #11486]' + assert_nil ("\u3042"*19+"\r"*19+"\u3042"*20+"\r"*20).encode(Encoding::EUC_JP).gsub!(/xxx/i, ""), bug11486 + assert_match Regexp.new("ABC\uff41".encode(Encoding::EUC_JP), Regexp::IGNORECASE), "abc\uFF21".encode(Encoding::EUC_JP), bug11486 + end end diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index 8e0bca46ba..0565a1c04f 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -1,6 +1,6 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require_relative 'envutil' require_relative 'marshaltestlib' class TestMarshal < Test::Unit::TestCase @@ -103,17 +103,19 @@ class TestMarshal < Test::Unit::TestCase def test_pipe o1 = C.new("a" * 10000) - o2 = IO.pipe do |r, w| - Thread.new {Marshal.dump(o1, w)} - Marshal.load(r) + IO.pipe do |r, w| + th = Thread.new {Marshal.dump(o1, w)} + o2 = Marshal.load(r) + th.join + assert_equal(o1.str, o2.str) end - assert_equal(o1.str, o2.str) - o2 = IO.pipe do |r, w| - Thread.new {Marshal.dump(o1, w, 2)} - Marshal.load(r) + IO.pipe do |r, w| + th = Thread.new {Marshal.dump(o1, w, 2)} + o2 = Marshal.load(r) + th.join + assert_equal(o1.str, o2.str) end - assert_equal(o1.str, o2.str) assert_raise(TypeError) { Marshal.dump("foo", Object.new) } assert_raise(TypeError) { Marshal.load(Object.new) } @@ -247,6 +249,10 @@ class TestMarshal < Test::Unit::TestCase assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug2548) end + def test_symlink + assert_include(Marshal.dump([:a, :a]), ';') + end + def test_symlink_in_ivar bug10991 = '[ruby-core:68587] [Bug #10991]' sym = Marshal.load("\x04\x08" + @@ -562,7 +568,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 @@ -608,6 +614,107 @@ class TestMarshal < Test::Unit::TestCase end end + def test_packed_string + packed = ["foo"].pack("p") + bare = "".force_encoding(Encoding::ASCII_8BIT) << packed + 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 + callcc {|c| @cc = c } + nil + end + def marshal_load(v) + end + end + + def test_continuation + require "continuation" + c = Bug9523.new + assert_raise_with_message(RuntimeError, /Marshal\.dump reentered at marshal_dump/) do + Marshal.dump(c) + GC.start + 1000.times {"x"*1000} + GC.start + c.cc.call + end + end + + def test_undumpable_message + c = Module.new {break module_eval("class IO\u{26a1} < IO;self;end")} + assert_raise_with_message(TypeError, /IO\u{26a1}/) { + Marshal.dump(c.new(0, autoclose: false)) + } + end + + def test_undumpable_data + c = Module.new {break module_eval("class T\u{23F0 23F3}<Time;undef _dump;self;end")} + assert_raise_with_message(TypeError, /T\u{23F0 23F3}/) { + Marshal.dump(c.new) + } + end + + def test_unloadable_data + c = eval("class Unloadable\u{23F0 23F3}<Time;;self;end") + c.class_eval { + alias _dump_data _dump + undef _dump + } + d = Marshal.dump(c.new) + assert_raise_with_message(TypeError, /Unloadable\u{23F0 23F3}/) { + Marshal.load(d) + } + end + + def test_unloadable_userdef + c = eval("class Userdef\u{23F0 23F3}<Time;self;end") + class << c + undef _load + end + d = Marshal.dump(c.new) + assert_raise_with_message(TypeError, /Userdef\u{23F0 23F3}/) { + Marshal.load(d) + } + end + + def test_unloadable_usrmarshal + c = eval("class UsrMarshal\u{23F0 23F3}<Time;self;end") + c.class_eval { + alias marshal_dump _dump + } + d = Marshal.dump(c.new) + assert_raise_with_message(TypeError, /UsrMarshal\u{23F0 23F3}/) { + Marshal.load(d) + } + end + + 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) + 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) + assert_empty(err) + assert_predicate(status, :success?) + assert_equal(expected, out) + end + def test_marshal_honor_post_proc_value_for_link str = 'x' # for link obj = [str, str] @@ -615,10 +722,12 @@ class TestMarshal < Test::Unit::TestCase end def test_marshal_load_extended_class_crash - crash = "\x04\be:\x0F\x00omparableo:\vObject\x00" - - opt = %w[--disable=gems] - assert_ruby_status(opt, "Marshal.load(#{crash.dump})") + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + assert_raise_with_message(ArgumentError, /undefined/) do + Marshal.load("\x04\be:\x0F\x00omparableo:\vObject\x00") + end + end; end def test_marshal_load_r_prepare_reference_crash @@ -631,4 +740,75 @@ class TestMarshal < Test::Unit::TestCase end RUBY end + + MethodMissingWithoutRespondTo = Struct.new(:wrapped_object) do + undef respond_to? + def method_missing(*args, &block) + wrapped_object.public_send(*args, &block) + end + def respond_to_missing?(name, private = false) + wrapped_object.respond_to?(name, false) + end + end + + def test_method_missing_without_respond_to + bug12353 = "[ruby-core:75377] [Bug #12353]: try method_missing if" \ + " respond_to? is undefined" + obj = MethodMissingWithoutRespondTo.new("foo") + dump = assert_nothing_raised(NoMethodError, bug12353) do + Marshal.dump(obj) + end + assert_equal(obj, Marshal.load(dump)) + end + + class Bug12974 + def marshal_dump + dup + end + end + + def test_marshal_dump_recursion + assert_raise_with_message(RuntimeError, /same class instance/) do + 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 + self.foo.baz = :problem + {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 end diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb index 74b3940c1d..f226287442 100644 --- a/test/ruby/test_math.rb +++ b/test/ruby/test_math.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class TestMath < Test::Unit::TestCase def assert_infinity(a, *rest) rest = ["not infinity: #{a.inspect}"] if rest.empty? - assert_not_predicate(a, :finite?, *rest) + assert_predicate(a, :infinite?, *rest) end def assert_nan(a, *rest) @@ -11,20 +12,32 @@ class TestMath < Test::Unit::TestCase assert_predicate(a, :nan?, *rest) end - def check(a, b) + def assert_float(a, b) err = [Float::EPSILON * 4, [a.abs, b.abs].max * Float::EPSILON * 256].max assert_in_delta(a, b, err) end + alias check assert_float + + def assert_float_and_int(exp_ary, act_ary) + flo_exp, int_exp, flo_act, int_act = *exp_ary, *act_ary + assert_float(flo_exp, flo_act) + assert_equal(int_exp, int_act) + end def test_atan2 check(+0.0, Math.atan2(+0.0, +0.0)) check(-0.0, Math.atan2(-0.0, +0.0)) check(+Math::PI, Math.atan2(+0.0, -0.0)) check(-Math::PI, Math.atan2(-0.0, -0.0)) - assert_raise(Math::DomainError) { Math.atan2(Float::INFINITY, Float::INFINITY) } - assert_raise(Math::DomainError) { Math.atan2(Float::INFINITY, -Float::INFINITY) } - assert_raise(Math::DomainError) { Math.atan2(-Float::INFINITY, Float::INFINITY) } - assert_raise(Math::DomainError) { Math.atan2(-Float::INFINITY, -Float::INFINITY) } + + inf = Float::INFINITY + expected = 3.0 * Math::PI / 4.0 + assert_nothing_raised { check(+expected, Math.atan2(+inf, -inf)) } + assert_nothing_raised { check(-expected, Math.atan2(-inf, -inf)) } + expected = Math::PI / 4.0 + assert_nothing_raised { check(+expected, Math.atan2(+inf, +inf)) } + assert_nothing_raised { check(-expected, Math.atan2(-inf, +inf)) } + check(0, Math.atan2(0, 1)) check(Math::PI / 4, Math.atan2(1, 1)) check(Math::PI / 2, Math.atan2(1, 0)) @@ -36,6 +49,7 @@ class TestMath < Test::Unit::TestCase check(0.0, Math.cos(2 * Math::PI / 4)) check(-1.0, Math.cos(4 * Math::PI / 4)) check(0.0, Math.cos(6 * Math::PI / 4)) + check(0.5403023058681398, Math.cos(1)) end def test_sin @@ -97,6 +111,8 @@ class TestMath < Test::Unit::TestCase check(Math.sinh(0) / Math.cosh(0), Math.tanh(0)) check(Math.sinh(1) / Math.cosh(1), Math.tanh(1)) check(Math.sinh(2) / Math.cosh(2), Math.tanh(2)) + check(+1.0, Math.tanh(+1000.0)) + check(-1.0, Math.tanh(-1000.0)) end def test_acosh @@ -137,11 +153,14 @@ class TestMath < Test::Unit::TestCase check(1, Math.log(10, 10)) check(2, Math.log(100, 10)) check(Math.log(2.0 ** 64), Math.log(1 << 64)) - assert_equal(1.0/0, Math.log(1.0/0)) + check(Math.log(2) * 1024.0, Math.log(2 ** 1024)) + 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(TypeError) { Math.log(1,nil) } + assert_raise(Math::DomainError, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) } + assert_nothing_raised { assert_nan(Math.log(0.0, 0.0)) } end def test_log2 @@ -149,7 +168,8 @@ class TestMath < Test::Unit::TestCase check(1, Math.log2(2)) check(2, Math.log2(4)) check(Math.log2(2.0 ** 64), Math.log2(1 << 64)) - assert_equal(1.0/0, Math.log2(1.0/0)) + check(1024.0, Math.log2(2 ** 1024)) + 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) } @@ -160,7 +180,8 @@ class TestMath < Test::Unit::TestCase check(1, Math.log10(10)) check(2, Math.log10(100)) check(Math.log10(2.0 ** 64), Math.log10(1 << 64)) - assert_equal(1.0/0, Math.log10(1.0/0)) + check(Math.log10(2) * 1024, Math.log10(2 ** 1024)) + 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) } @@ -170,22 +191,25 @@ class TestMath < Test::Unit::TestCase check(0, Math.sqrt(0)) check(1, Math.sqrt(1)) check(2, Math.sqrt(4)) - assert_equal(1.0/0, Math.sqrt(1.0/0)) + 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) } end + def test_cbrt + check(1, Math.cbrt(1)) + check(-2, Math.cbrt(-8)) + check(3, Math.cbrt(27)) + check(-0.1, Math.cbrt(-0.001)) + assert_nothing_raised { assert_infinity(Math.cbrt(1.0/0)) } + end + def test_frexp - check(0.0, Math.frexp(0.0).first) - assert_equal(0, Math.frexp(0).last) - check(0.5, Math.frexp(0.5).first) - assert_equal(0, Math.frexp(0.5).last) - check(0.5, Math.frexp(1.0).first) - assert_equal(1, Math.frexp(1.0).last) - check(0.5, Math.frexp(2.0).first) - assert_equal(2, Math.frexp(2.0).last) - check(0.75, Math.frexp(3.0).first) - assert_equal(2, Math.frexp(3.0).last) + assert_float_and_int([0.0, 0], Math.frexp(0.0)) + assert_float_and_int([0.5, 0], Math.frexp(0.5)) + 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)) end def test_ldexp @@ -222,6 +246,8 @@ class TestMath < Test::Unit::TestCase check(2, Math.gamma(3)) check(15 * sqrt_pi / 8, Math.gamma(3.5)) check(6, Math.gamma(4)) + check(1.1240007277776077e+21, Math.gamma(23)) + check(2.5852016738885062e+22, Math.gamma(24)) # no SEGV [ruby-core:25257] 31.upto(65) do |i| @@ -231,54 +257,86 @@ class TestMath < Test::Unit::TestCase end assert_raise(Math::DomainError) { Math.gamma(-Float::INFINITY) } + x = Math.gamma(-0.0) + mesg = "Math.gamma(-0.0) should be -INF" + assert_infinity(x, mesg) + assert_predicate(x, :negative?, mesg) end def test_lgamma sqrt_pi = Math.sqrt(Math::PI) + assert_float_and_int([Math.log(4 * sqrt_pi / 3), 1], Math.lgamma(-1.5)) + assert_float_and_int([Math.log(2 * sqrt_pi), -1], Math.lgamma(-0.5)) + assert_float_and_int([Math.log(sqrt_pi), 1], Math.lgamma(0.5)) + assert_float_and_int([0, 1], Math.lgamma(1)) + assert_float_and_int([Math.log(sqrt_pi / 2), 1], Math.lgamma(1.5)) + assert_float_and_int([0, 1], Math.lgamma(2)) + assert_float_and_int([Math.log(3 * sqrt_pi / 4), 1], Math.lgamma(2.5)) + assert_float_and_int([Math.log(2), 1], Math.lgamma(3)) + 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)) - g, s = Math.lgamma(-1.5) - check(Math.log(4 * sqrt_pi / 3), g) - assert_equal(s, 1) - - g, s = Math.lgamma(-0.5) - check(Math.log(2 * sqrt_pi), g) - assert_equal(s, -1) - - g, s = Math.lgamma(0.5) - check(Math.log(sqrt_pi), g) - assert_equal(s, 1) - - assert_equal([0, 1], Math.lgamma(1)) + assert_raise(Math::DomainError) { 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) + end - g, s = Math.lgamma(1.5) - check(Math.log(sqrt_pi / 2), g) - assert_equal(s, 1) + def test_fixnum_to_f + check(12.0, Math.sqrt(144)) + end - assert_equal([0, 1], Math.lgamma(2)) + def test_override_integer_to_f + Integer.class_eval do + alias _to_f to_f + def to_f + (self + 1)._to_f + end + end - g, s = Math.lgamma(2.5) - check(Math.log(3 * sqrt_pi / 4), g) - assert_equal(s, 1) + check(Math.cos((0 + 1)._to_f), Math.cos(0)) + check(Math.exp((0 + 1)._to_f), Math.exp(0)) + check(Math.log((0 + 1)._to_f), Math.log(0)) + ensure + Integer.class_eval { undef to_f; alias to_f _to_f; undef _to_f } + end - g, s = Math.lgamma(3) - check(Math.log(2), g) - assert_equal(s, 1) + def test_bignum_to_f + check((1 << 65).to_f, Math.sqrt(1 << 130)) + end - g, s = Math.lgamma(3.5) - check(Math.log(15 * sqrt_pi / 8), g) - assert_equal(s, 1) + def test_override_bignum_to_f + Integer.class_eval do + alias _to_f to_f + def to_f + (self << 1)._to_f + end + end - g, s = Math.lgamma(4) - check(Math.log(6), g) - assert_equal(s, 1) + check(Math.cos((1 << 64 << 1)._to_f), Math.cos(1 << 64)) + check(Math.log((1 << 64 << 1)._to_f), Math.log(1 << 64)) + ensure + Integer.class_eval { undef to_f; alias to_f _to_f; undef _to_f } + end - assert_raise(Math::DomainError) { Math.lgamma(-Float::INFINITY) } + def test_rational_to_f + check((2 ** 31).fdiv(3 ** 20), Math.sqrt((2 ** 62)/(3 ** 40).to_r)) end - def test_cbrt - check(1, Math.cbrt(1)) - check(-2, Math.cbrt(-8)) - check(3, Math.cbrt(27)) - check(-0.1, Math.cbrt(-0.001)) + def test_override_rational_to_f + Rational.class_eval do + alias _to_f to_f + def to_f + (self + 1)._to_f + end + end + + check(Math.cos((0r + 1)._to_f), Math.cos(0r)) + check(Math.exp((0r + 1)._to_f), Math.exp(0r)) + check(Math.log((0r + 1)._to_f), Math.log(0r)) + ensure + Rational.class_eval { undef to_f; alias to_f _to_f; undef _to_f } end end diff --git a/test/ruby/test_metaclass.rb b/test/ruby/test_metaclass.rb index 6386a02dfa..8c1990a78c 100644 --- a/test/ruby/test_metaclass.rb +++ b/test/ruby/test_metaclass.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestMetaclass < Test::Unit::TestCase diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index f478e11486..77273dade5 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1,6 +1,6 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestMethod < Test::Unit::TestCase def setup @@ -125,6 +125,11 @@ class TestMethod < Test::Unit::TestCase assert_nil(eval("class TestCallee; __callee__; end")) end + def test_orphan_callee + c = Class.new{def foo; proc{__callee__}; end; alias alias_foo foo} + assert_equal(:alias_foo, c.new.alias_foo.call, '[Bug #11046]') + end + def test_method_in_define_method_block bug4606 = '[ruby-core:35386]' c = Class.new do @@ -192,6 +197,7 @@ 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_owner @@ -247,6 +253,13 @@ class TestMethod < Test::Unit::TestCase m = o.method(:bar).unbind assert_raise(TypeError) { m.bind(Object.new) } + cx = EnvUtil.labeled_class("X\u{1f431}") + assert_raise_with_message(TypeError, /X\u{1f431}/) do + o.method(cx) + end + end + + def test_bind_module_instance_method feature4254 = '[ruby-core:34267]' m = M.instance_method(:meth) assert_equal(:meth, m.bind(Object.new).call, feature4254) @@ -272,21 +285,44 @@ class TestMethod < Test::Unit::TestCase assert_raise(TypeError) do Class.new.class_eval { define_method(:bar, o.method(:bar)) } end + cx = EnvUtil.labeled_class("X\u{1f431}") + assert_raise_with_message(TypeError, /X\u{1F431}/) do + Class.new {define_method(cx) {}} + end end - def test_define_singleton_method + def test_define_method_no_proc o = Object.new def o.foo(c) c.class_eval { define_method(:foo) } end c = Class.new - o.foo(c) { :foo } - assert_equal(:foo, c.new.foo) + assert_raise(ArgumentError) {o.foo(c)} + bug11283 = '[ruby-core:69655] [Bug #11283]' + assert_raise(ArgumentError, bug11283) {o.foo(c) {:foo}} + end + + def test_define_singleton_method o = Object.new o.instance_eval { define_singleton_method(:foo) { :foo } } assert_equal(:foo, o.foo) + end + + def test_define_singleton_method_no_proc + o = Object.new + assert_raise(ArgumentError) { + o.instance_eval { define_singleton_method(:bar) } + } + + bug11283 = '[ruby-core:69655] [Bug #11283]' + def o.define(n) + define_singleton_method(n) + end + assert_raise(ArgumentError, bug11283) {o.define(:bar) {:bar}} + end + def test_define_method_invalid_arg assert_raise(TypeError) do Class.new.class_eval { define_method(:foo, Object.new) } end @@ -382,11 +418,7 @@ class TestMethod < Test::Unit::TestCase end } c2 = Class.new(c1) { define_method(:m) { Proc.new { super() } } } - # c2.new.m.call should return :m1, but currently it raise NoMethodError. - # see [Bug #4881] and [Bug #3136] - assert_raise(NoMethodError) { - c2.new.m.call - } + assert_equal(:m1, c2.new.m.call, 'see [Bug #4881] and [Bug #3136]') end def test_clone @@ -423,6 +455,9 @@ class TestMethod < Test::Unit::TestCase c3.class_eval { alias bar foo } m3 = c3.new.method(:bar) assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)>", m3.inspect, bug7806) + + m.taint + assert_predicate(m.inspect, :tainted?, "inspect result should be infected") end def test_callee_top_level @@ -564,6 +599,11 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters) end + def test_hidden_parameters + instance_eval("def m((_)"+",(_)"*256+");end") + assert_empty(method(:m).parameters.map{|_,n|n}.compact) + end + def test_public_method_with_zsuper_method c = Class.new c.class_eval do @@ -653,6 +693,15 @@ class TestMethod < Test::Unit::TestCase EOC end + def test_unbound_method_proc_coerce + # '&' coercion of an UnboundMethod raises TypeError + assert_raise(TypeError) do + Class.new do + define_method('foo', &Object.instance_method(:to_s)) + end + end + end + def test___dir__ assert_instance_of String, __dir__ assert_equal(File.dirname(File.realpath(__FILE__)), __dir__) @@ -727,23 +776,6 @@ class TestMethod < Test::Unit::TestCase }, '[Bug #7825]' end - def test_unlinked_method_entry_in_method_object_bug - bug8100 = '[ruby-core:53640] [Bug #8100]' - begin - assert_normal_exit %q{ - loop do - def x - "hello" * 1000 - end - method(:x).call - end - }, bug8100, timeout: 2 - rescue Timeout::Error => e - else - end - assert_raise(Timeout::Error, bug8100) {raise e if e} - end - def test_singleton_method feature8391 = '[ruby-core:54914] [Feature #8391]' c1 = Class.new @@ -755,4 +787,263 @@ class TestMethod < Test::Unit::TestCase m = assert_nothing_raised(NameError, feature8391) {break o.singleton_method(:bar)} 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) + curried = m.curry + assert_equal(6, curried.(1).(2).(3), Feature9783) + + curried = m.curry(3) + assert_equal(6, curried.(1).(2).(3), Feature9783) + + assert_raise_with_message(ArgumentError, /wrong number/) {m.curry(2)} + end + + def test_curry_method + c = Class.new { + def three_args(a,b,c) a + b + c end + } + assert_curry_three_args(c.new.method(:three_args)) + end + + def test_curry_from_proc + c = Class.new { + define_method(:three_args) {|x,y,z| x + y + z} + } + assert_curry_three_args(c.new.method(:three_args)) + end + + def assert_curry_var_args(m) + curried = m.curry(3) + assert_equal([1, 2, 3], curried.(1).(2).(3), Feature9783) + + curried = m.curry(2) + assert_equal([1, 2], curried.(1).(2), Feature9783) + + curried = m.curry(0) + assert_equal([1], curried.(1), Feature9783) + end + + def test_curry_var_args + c = Class.new { + def var_args(*args) args end + } + assert_curry_var_args(c.new.method(:var_args)) + end + + def test_curry_from_proc_var_args + c = Class.new { + define_method(:var_args) {|*args| args} + } + assert_curry_var_args(c.new.method(:var_args)) + end + + Feature9781 = '[ruby-core:62202] [Feature #9781]' + + def test_super_method + o = Derived.new + m = o.method(:foo).super_method + assert_equal(Base, m.owner, Feature9781) + assert_same(o, m.receiver, Feature9781) + assert_equal(:foo, m.name, Feature9781) + m = assert_nothing_raised(NameError, Feature9781) {break m.super_method} + assert_nil(m, Feature9781) + end + + def test_super_method_unbound + m = Derived.instance_method(:foo) + m = m.super_method + assert_equal(Base.instance_method(:foo), m, Feature9781) + m = assert_nothing_raised(NameError, Feature9781) {break m.super_method} + assert_nil(m, Feature9781) + + bug11419 = '[ruby-core:70254]' + m = Object.instance_method(:tap) + m = assert_nothing_raised(NameError, bug11419) {break m.super_method} + assert_nil(m, bug11419) + end + + def test_super_method_module + m1 = Module.new {def foo; end} + c1 = Class.new(Derived) {include m1; def foo; end} + m = c1.instance_method(:foo) + assert_equal(c1, m.owner, Feature9781) + m = m.super_method + assert_equal(m1, m.owner, Feature9781) + m = m.super_method + assert_equal(Derived, m.owner, Feature9781) + m = m.super_method + assert_equal(Base, m.owner, Feature9781) + m2 = Module.new {def foo; end} + o = c1.new.extend(m2) + m = o.method(:foo) + assert_equal(m2, m.owner, Feature9781) + m = m.super_method + assert_equal(c1, m.owner, Feature9781) + assert_same(o, m.receiver, Feature9781) + + c1 = Class.new {def foo; end} + c2 = Class.new(c1) {include m1; include m2} + m = c2.instance_method(:foo) + assert_equal(m2, m.owner) + m = m.super_method + assert_equal(m1, m.owner) + m = m.super_method + assert_equal(c1, m.owner) + assert_nil(m.super_method) + end + + def test_super_method_removed + c1 = Class.new {private def foo; end} + c2 = Class.new(c1) {public :foo} + c3 = Class.new(c2) {def foo; end} + c1.class_eval {undef foo} + m = c3.instance_method(:foo) + m = assert_nothing_raised(NameError, Feature9781) {break m.super_method} + assert_nil(m, Feature9781) + end + + def test_prepended_public_zsuper + mod = EnvUtil.labeled_module("Mod") {private def foo; :ok end} + mods = [mod] + 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 + m = obj.method(:foo) + assert_equal(mods, mods.map {m.owner.tap {m = m.super_method}}) + assert_nil(m) + end + + def test_super_method_with_prepended_module + bug = '[ruby-core:81666] [Bug #13656] should be the method of the parent' + c1 = EnvUtil.labeled_class("C1") {def m; end} + c2 = EnvUtil.labeled_class("C2", c1) {def m; end} + c2.prepend(EnvUtil.labeled_module("M")) + m1 = c1.instance_method(:m) + m2 = c2.instance_method(:m).super_method + assert_equal(m1, m2, bug) + assert_equal(c1, m2.owner, bug) + 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 rest_parameter(*rest) + rest + end + + def test_splat_long_array + n = 10_000_000 + assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]' + end + + class C + D = "Const_D" + def foo + a = b = c = a = b = c = 12345 + end + end + + def test_to_proc_binding + bug11012 = '[ruby-core:68673] [Bug #11012]' + + b = C.new.method(:foo).to_proc.binding + assert_equal([], b.local_variables, bug11012) + assert_equal("Const_D", b.eval("D"), bug11012) # Check CREF + + assert_raise(NameError, bug11012){ b.local_variable_get(:foo) } + assert_equal(123, b.local_variable_set(:foo, 123), bug11012) + assert_equal(123, b.local_variable_get(:foo), bug11012) + assert_equal(456, b.local_variable_set(:bar, 456), bug11012) + assert_equal(123, b.local_variable_get(:foo), bug11012) + assert_equal(456, b.local_variable_get(:bar), bug11012) + assert_equal([:bar, :foo], b.local_variables.sort, bug11012) + end + + class MethodInMethodClass + def m1 + def m2 + end + + self.class.send(:define_method, :m3){} # [Bug #11754] + end + private + end + + def test_method_in_method_visibility_should_be_public + assert_equal([:m1].sort, MethodInMethodClass.public_instance_methods(false).sort) + assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort) + + MethodInMethodClass.new.m1 + assert_equal([:m1, :m2, :m3].sort, MethodInMethodClass.public_instance_methods(false).sort) + assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort) + end + + def test_define_method_with_symbol + assert_normal_exit %q{ + define_method(:foo, &:to_s) + define_method(:bar, :to_s.to_proc) + }, '[Bug #11850]' + c = Class.new{ + define_method(:foo, &:to_s) + define_method(:bar, :to_s.to_proc) + } + obj = c.new + assert_equal('1', obj.foo(1)) + assert_equal('1', obj.bar(1)) + end + + def test_argument_error_location + body = <<-'END_OF_BODY' + eval <<-'EOS' + $line_lambda = __LINE__; $f = lambda do + _x = 1 + end + $line_method = __LINE__; def foo + _x = 1 + end + begin + $f.call(1) + rescue ArgumentError => e + assert_equal "(eval):#{$line_lambda.to_s}:in `block in <main>'", e.backtrace.first + end + begin + foo(1) + rescue ArgumentError => e + assert_equal "(eval):#{$line_method}:in `foo'", e.backtrace.first + end + EOS + END_OF_BODY + + assert_separately [], body + # without trace insn + assert_separately [], "RubyVM::InstructionSequence.compile_option = {trace_instruction: false}\n" + body + end + + def test_eqq + assert_operator(0.method(:<), :===, 5) + assert_not_operator(0.method(:<), :===, -5) + end end diff --git a/test/ruby/test_mixed_unicode_escapes.rb b/test/ruby/test_mixed_unicode_escapes.rb index 982b57e286..f0b4cc691f 100644 --- a/test/ruby/test_mixed_unicode_escapes.rb +++ b/test/ruby/test_mixed_unicode_escapes.rb @@ -1,5 +1,6 @@ # -*- coding: cp932 -*- -# This test is in a differnt file than TestUnicodeEscapes +# frozen_string_literal: false +# This test is in a different file than TestUnicodeEscapes # So that we can have a different coding comment above require 'test/unit' @@ -12,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_module.rb b/test/ruby/test_module.rb index 9910e261d5..076ea0901f 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -1,6 +1,6 @@ +# frozen_string_literal: false require 'test/unit' require 'pp' -require_relative 'envutil' $m0 = Module.nesting @@ -75,6 +75,14 @@ class TestModule < Test::Unit::TestCase include Mixin def user end + + def user2 + end + protected :user2 + + def user3 + end + private :user3 end module Other @@ -250,7 +258,7 @@ class TestModule < Test::Unit::TestCase "\u3042", "Name?", ].each do |name, msg| - expected = "wrong constant name %s" % quote(name) + 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 yield name @@ -302,8 +310,8 @@ class TestModule < Test::Unit::TestCase classes = [] klass = Class.new { define_singleton_method(:const_missing) { |name| - classes << name - klass + classes << name + klass } } klass.const_get("Foo::Bar::Baz") @@ -432,6 +440,10 @@ class TestModule < Test::Unit::TestCase EOS end + def test_include_with_no_args + assert_raise(ArgumentError) { Module.new { include } } + end + def test_included_modules assert_equal([], Mixin.included_modules) assert_equal([Mixin], User.included_modules) @@ -443,8 +455,8 @@ class TestModule < Test::Unit::TestCase end def test_instance_methods - assert_equal([:user], User.instance_methods(false)) - assert_equal([:user, :mixin].sort, User.instance_methods(true).sort) + assert_equal([:user, :user2], User.instance_methods(false).sort) + assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort) assert_equal([:mixin], Mixin.instance_methods) assert_equal([:mixin], Mixin.instance_methods(true)) assert_equal([:cClass], (class << CClass; self; end).instance_methods(false)) @@ -459,12 +471,17 @@ class TestModule < Test::Unit::TestCase end def test_method_defined? - assert_method_not_defined?(User, :wombat) - assert_method_defined?(User, :user) - assert_method_defined?(User, :mixin) - assert_method_not_defined?(User, :wombat) - assert_method_defined?(User, :user) - assert_method_defined?(User, :mixin) + 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 !User.method_defined?("wombat") + assert User.method_defined?("mixin") + assert User.method_defined?("user") + assert User.method_defined?("user2") + assert !User.method_defined?("user3") end def module_exec_aux @@ -506,7 +523,7 @@ class TestModule < Test::Unit::TestCase end def test_name - assert_equal("Fixnum", Fixnum.name) + assert_equal("Integer", Integer.name) assert_equal("TestModule::Mixin", Mixin.name) assert_equal("TestModule::User", User.name) end @@ -519,9 +536,9 @@ class TestModule < Test::Unit::TestCase assert_nil(n.name) assert_equal([:N], m.constants) m.module_eval("module O end") - assert_equal([:N, :O], m.constants) + assert_equal([:N, :O], m.constants.sort) m.module_eval("class C; end") - assert_equal([:N, :O, :C], m.constants) + assert_equal([:C, :N, :O], m.constants.sort) assert_nil(m::N.name) assert_match(/\A#<Module:.*>::O\z/, m::O.name) assert_match(/\A#<Module:.*>::C\z/, m::C.name) @@ -590,6 +607,8 @@ class TestModule < Test::Unit::TestCase const_set(:X, 123) end assert_equal(false, klass.class_eval { Module.constants }.include?(:X)) + + assert_equal(false, Complex.constants(false).include?(:compatible)) end module M1 @@ -612,13 +631,22 @@ class TestModule < Test::Unit::TestCase end def test_freeze - m = Module.new + m = Module.new do + def self.baz; end + 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(FrozenError) do + m.__send__ :private, :bar + end + assert_raise(FrozenError) do + m.private_class_method :baz + end end def test_attr_obsoleted_flag @@ -678,9 +706,16 @@ class TestModule < Test::Unit::TestCase def test_const_set_invalid_name c1 = Class.new - assert_raise(NameError) { c1.const_set(:foo, :foo) } - assert_raise(NameError) { c1.const_set("bar", :foo) } - assert_raise(TypeError) { c1.const_set(1, :foo) } + assert_raise_with_message(NameError, /foo/) { c1.const_set(:foo, :foo) } + assert_raise_with_message(NameError, /bar/) { c1.const_set("bar", :foo) } + assert_raise_with_message(TypeError, /1/) { c1.const_set(1, :foo) } + assert_nothing_raised(NameError) { c1.const_set("X\u{3042}", :foo) } + assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-16be"), :foo) } + assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-16le"), :foo) } + assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32be"), :foo) } + assert_raise(NameError) { c1.const_set("X\u{3042}".encode("utf-32le"), :foo) } + cx = EnvUtil.labeled_class("X\u{3042}") + assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) } end def test_const_get_invalid_name @@ -818,6 +853,9 @@ class TestModule < Test::Unit::TestCase c.class_eval('@@foo = :foo') c.class_eval { remove_class_variable(:@@foo) } assert_equal(false, c.class_variable_defined?(:@@foo)) + assert_raise(NameError) do + c.class_eval { remove_class_variable(:@var) } + end end def test_export_method @@ -828,7 +866,7 @@ class TestModule < Test::Unit::TestCase end def test_attr - assert_in_out_err([], <<-INPUT, %w(:ok nil), /warning: private attribute\?$/) + assert_in_out_err([], <<-INPUT, %w(nil)) $VERBOSE = true c = Class.new c.instance_eval do @@ -836,7 +874,6 @@ class TestModule < Test::Unit::TestCase attr_reader :foo end o = c.new - o.foo rescue p(:ok) p(o.instance_eval { foo }) INPUT @@ -909,25 +946,34 @@ class TestModule < Test::Unit::TestCase assert_include(c.constants(false), :Foo, bug9413) end - def test_frozen_class + 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 + 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 - o = Object.new + def test_frozen_singleton_class + klass = Class.new + o = klass.new c = class << o; self; end c.freeze - assert_raise(RuntimeError) do + assert_raise_with_message(FrozenError, /frozen/) do c.instance_eval { undef_method(:foo) } end + klass.class_eval do + def self.foo + end + end end def test_method_defined @@ -945,13 +991,28 @@ class TestModule < Test::Unit::TestCase 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(true, c.public_method_defined?("foo")) + assert_equal(false, c.public_method_defined?("bar")) + assert_equal(false, c.public_method_defined?("baz")) + 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(false, c.protected_method_defined?("foo")) + assert_equal(true, c.protected_method_defined?("bar")) + assert_equal(false, c.protected_method_defined?("baz")) + 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(false, c.private_method_defined?("foo")) + assert_equal(false, c.private_method_defined?("bar")) + assert_equal(true, c.private_method_defined?("baz")) end def test_top_public_private @@ -1070,6 +1131,8 @@ class TestModule < Test::Unit::TestCase assert_equal("C\u{df}", c.name, '[ruby-core:24600]') c = eval("class C\u{df}; self; end") assert_equal("TestModule::C\u{df}", c.name, '[ruby-core:24600]') + c = Module.new.module_eval("class X\u{df} < Module; self; end") + assert_match(/::X\u{df}:/, c.new.to_s) end def test_method_added @@ -1093,13 +1156,13 @@ class TestModule < Test::Unit::TestCase assert_equal [:f], memo.shift, '[ruby-core:25536]' assert_equal mod.instance_method(:f), memo.shift assert_equal :g, memo.shift - assert_equal [:f, :g], memo.shift + assert_equal [:f, :g].sort, memo.shift.sort assert_equal mod.instance_method(:f), memo.shift assert_equal :a, memo.shift - assert_equal [:f, :g, :a], memo.shift + assert_equal [:f, :g, :a].sort, memo.shift.sort assert_equal mod.instance_method(:a), memo.shift assert_equal :a=, memo.shift - assert_equal [:f, :g, :a, :a=], memo.shift + assert_equal [:f, :g, :a, :a=].sort, memo.shift.sort assert_equal mod.instance_method(:a=), memo.shift end @@ -1222,6 +1285,20 @@ class TestModule < Test::Unit::TestCase undef foo end end + + stderr = EnvUtil.verbose_warning do + Module.new do + def foo; end + mod = self + c = Class.new do + include mod + end + c.new.foo + def foo; end + end + end + assert_match(/: warning: method redefined; discarding old foo/, stderr) + assert_match(/: warning: previous definition of foo/, stderr) end def test_protected_singleton_method @@ -1272,21 +1349,58 @@ class TestModule < Test::Unit::TestCase c = Class.new do attr_writer :foo end - assert_raise(ArgumentError) { c.new.send :foo= } + 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 } + 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 @@ -1339,8 +1453,20 @@ class TestModule < Test::Unit::TestCase assert_equal("foo", c::FOO) end + def test_deprecate_constant + 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} + bug12382 = '[ruby-core:75505] [Bug #12382]' + assert_warn(/deprecated/, bug12382) {c.class_eval "FOO"} + end + def test_constants_with_private_constant assert_not_include(::TestModule.constants, :PrivateClass) + assert_not_include(::TestModule.constants(true), :PrivateClass) + assert_not_include(::TestModule.constants(false), :PrivateClass) end def test_toplevel_private_constant @@ -1486,6 +1612,12 @@ class TestModule < Test::Unit::TestCase end end + def test_prepend_CMP + bug11878 = '[ruby-core:72493] [Bug #11878]' + assert_equal(-1, C1 <=> M2) + assert_equal(+1, M2 <=> C1, bug11878) + end + def test_prepend_inheritance bug6654 = '[ruby-core:45914]' a = labeled_module("a") @@ -1609,12 +1741,46 @@ class TestModule < Test::Unit::TestCase to_f / other end end - Fixnum.send(:prepend, M) + Integer.send(:prepend, M) assert_equal(0.5, 1 / 2, "#{bug7983}") } assert_equal(0, 1 / 2) end + def test_redefine_optmethod_after_prepend + bug11826 = '[ruby-core:72188] [Bug #11826]' + assert_separately [], %{ + module M + end + class Integer + prepend M + def /(other) + quo(other) + end + end + assert_equal(1 / 2r, 1 / 2, "#{bug11826}") + }, ignore_stderr: true + assert_equal(0, 1 / 2) + end + + def test_override_optmethod_after_prepend + bug11836 = '[ruby-core:72226] [Bug #11836]' + assert_separately [], %{ + module M + end + class Integer + prepend M + end + module M + def /(other) + quo(other) + end + end + assert_equal(1 / 2r, 1 / 2, "#{bug11836}") + }, ignore_stderr: true + assert_equal(0, 1 / 2) + end + def test_prepend_visibility bug8005 = '[ruby-core:53106] [Bug #8005]' c = Class.new do @@ -1629,7 +1795,7 @@ class TestModule < Test::Unit::TestCase def test_prepend_visibility_inherited bug8238 = '[ruby-core:54105] [Bug #8238]' - assert_separately [], <<-"end;", timeout: 3 + assert_separately [], <<-"end;", timeout: 20 class A def foo() A; end private :foo @@ -1708,6 +1874,27 @@ class TestModule < Test::Unit::TestCase assert_equal('hello!', foo.new.hello, bug9236) end + def test_prepend_each_classes + m = labeled_module("M") + c1 = labeled_class("C1") {prepend m} + c2 = labeled_class("C2", c1) {prepend m} + assert_equal([m, c2, m, c1], c2.ancestors[0, 4], "should be able to prepend each classes") + end + + def test_prepend_no_duplication + m = labeled_module("M") + c = labeled_class("C") {prepend m; prepend m} + assert_equal([m, c], c.ancestors[0, 2], "should never duplicate") + end + + def test_prepend_in_superclass + m = labeled_module("M") + c1 = labeled_class("C1") + c2 = labeled_class("C2", c1) {prepend m} + c1.class_eval {prepend m} + assert_equal([m, c2, m, c1], c2.ancestors[0, 4], "should accesisble prepended module in superclass") + end + def test_prepend_call_super assert_separately([], <<-'end;') #do bug10847 = '[ruby-core:68093] [Bug #10847]' @@ -1719,6 +1906,29 @@ class TestModule < Test::Unit::TestCase end; end + def test_prepend_module_with_no_args + 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) @@ -1726,8 +1936,8 @@ class TestModule < Test::Unit::TestCase m2.send(:include, m) m2.class_variable_set(:@@bar, 2) assert_equal([:@@foo], m.class_variables) - assert_equal([:@@bar, :@@foo], m2.class_variables) - assert_equal([:@@bar, :@@foo], m2.class_variables(true)) + assert_equal([:@@bar, :@@foo], m2.class_variables.sort) + assert_equal([:@@bar, :@@foo], m2.class_variables(true).sort) assert_equal([:@@bar], m2.class_variables(false)) end @@ -1785,6 +1995,10 @@ class TestModule < Test::Unit::TestCase assert_equal(['public', 'protected'], list) end + def test_extend_module_with_no_args + assert_raise(ArgumentError) { Module.new { extend } } + end + def test_invalid_attr %W[ foo? @@ -1823,6 +2037,11 @@ class TestModule < Test::Unit::TestCase assert_warning '' do assert_equal(42, a.ivar) end + + name = "@\u{5909 6570}" + assert_warning(/instance variable #{name} not initialized/) do + assert_nil(a.instance_eval(name)) + end end def test_uninitialized_attr @@ -1864,6 +2083,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) @@ -1891,6 +2126,25 @@ class TestModule < Test::Unit::TestCase end end + def test_frozen_visibility + bug11532 = '[ruby-core:70828] [Bug #11532]' + + c = Class.new {const_set(:A, 1)}.freeze + 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(FrozenError, /frozen class/, bug11532) { + c.class_eval {public_constant :A} + } + + c = Class.new {const_set(:A, 1)}.freeze + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { + c.class_eval {deprecate_constant :A} + } + end + def test_singleton_class_ancestors feature8035 = '[ruby-core:53171]' obj = Object.new @@ -1907,13 +2161,13 @@ 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} + assert_raise(NoMethodError) {Object.remove_const} + Module.new.public_class_method(:remove_const) + assert_raise(NoMethodError, bug8284) {Object.remove_const} end - def test_include_module_with_constants_invalidates_method_cache - assert_in_out_err([], <<-RUBY, %w(123 456), []) + def test_include_module_with_constants_does_not_invalidate_method_cache + assert_in_out_err([], <<-RUBY, %w(123 456 true), []) A = 123 class Foo @@ -1927,8 +2181,13 @@ class TestModule < Test::Unit::TestCase end puts Foo.a + starting = RubyVM.stat[:global_method_state] + Foo.send(:include, M) + + ending = RubyVM.stat[:global_method_state] puts Foo.a + puts starting == ending RUBY end @@ -1987,11 +2246,68 @@ class TestModule < Test::Unit::TestCase A.prepend InspectIsShallow - expect = "#<Method: A(Object)#inspect(shallow_inspect)>" + expect = "#<Method: A(ShallowInspect)#inspect(shallow_inspect)>" assert_equal expect, A.new.method(:inspect).inspect, "#{bug_10282}" RUBY end + def test_define_method_with_unbound_method + # Passing an UnboundMethod to define_method succeeds if it is from an ancestor + assert_nothing_raised do + cls = Class.new(String) do + define_method('foo', String.instance_method(:to_s)) + end + + obj = cls.new('bar') + assert_equal('bar', obj.foo) + end + + # Passing an UnboundMethod to define_method fails if it is not from an ancestor + assert_raise(TypeError) do + Class.new do + define_method('foo', String.instance_method(:to_s)) + end + end + end + + def test_redefinition_mismatch + m = Module.new + m.module_eval "A = 1" + assert_raise_with_message(TypeError, /is not a module/) { + m.module_eval "module A; end" + } + n = "M\u{1f5ff}" + m.module_eval "#{n} = 42" + assert_raise_with_message(TypeError, "#{n} is not a module") { + m.module_eval "module #{n}; end" + } + + assert_separately([], <<-"end;") + Etc = (class C\u{1f5ff}; self; end).new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + require 'etc' + } + 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 + private def assert_top_method_is_private(method) diff --git a/test/ruby/test_not.rb b/test/ruby/test_not.rb index 486075bf83..721f868a5a 100644 --- a/test/ruby/test_not.rb +++ b/test/ruby/test_not.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestIfunless < Test::Unit::TestCase diff --git a/test/ruby/test_notimp.rb b/test/ruby/test_notimp.rb index 9721723b29..ddebb657bf 100644 --- a/test/ruby/test_notimp.rb +++ b/test/ruby/test_notimp.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' require 'tmpdir' @@ -35,7 +36,7 @@ class TestNotImplement < Test::Unit::TestCase proc {`ps -l #{pid}`} end assert_nothing_raised(Timeout::Error, ps) do - Timeout.timeout(5) { + Timeout.timeout(EnvUtil.apply_timeout_scale(5)) { pid = fork {} Process.wait pid pid = nil diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 98fabc4e13..6efc40320a 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -1,14 +1,11 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestNumeric < Test::Unit::TestCase - class DummyNumeric < Numeric - end - def test_coerce a, b = 1.coerce(2) - assert_equal(Fixnum, a.class) - assert_equal(Fixnum, b.class) + assert_kind_of(Integer, a) + assert_kind_of(Integer, b) a, b = 1.coerce(2.0) assert_equal(Float, a.class) @@ -16,98 +13,107 @@ class TestNumeric < Test::Unit::TestCase assert_raise(TypeError) { -Numeric.new } - EnvUtil.with_default_external(Encoding::UTF_8) do - assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"} - assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"} - assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"} - assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"} - end - EnvUtil.with_default_external(Encoding::US_ASCII) do - 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, /:"\\u3042"/) {1^:"\u{3042}"} - end + assert_raise_with_message(TypeError, /can't be coerced into /) {1+:foo} + assert_raise_with_message(TypeError, /can't be coerced into /) {1&:foo} + assert_raise_with_message(TypeError, /can't be coerced into /) {1|:foo} + assert_raise_with_message(TypeError, /can't be coerced into /) {1^:foo} + + assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {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, /:"\\u3042"/) {1|:"\u{3042}"} + assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"} bug10711 = '[ruby-core:67405] [Bug #10711]' - exp = "1.2 can't be coerced into Fixnum" + exp = "1.2 can't be coerced into Integer" assert_raise_with_message(TypeError, exp, bug10711) { 1 & 1.2 } end def test_dummynumeric - a = DummyNumeric.new - - DummyNumeric.class_eval do + a = Class.new(Numeric) do def coerce(x); nil; end - end + end.new assert_raise(TypeError) { -a } assert_nil(1 <=> a) assert_raise(ArgumentError) { 1 <= a } - DummyNumeric.class_eval do - remove_method :coerce + a = Class.new(Numeric) do def coerce(x); 1.coerce(x); end - end + end.new assert_equal(2, 1 + a) assert_equal(0, 1 <=> a) assert_operator(1, :<=, a) - DummyNumeric.class_eval do - remove_method :coerce + a = Class.new(Numeric) do def coerce(x); [x, 1]; end - end + end.new assert_equal(-1, -a) - ensure - DummyNumeric.class_eval do - remove_method :coerce - end + bug7688 = '[ruby-core:51389] [Bug #7688]' + a = Class.new(Numeric) do + def coerce(x); raise StandardError, "my error"; end + end.new + assert_raise_with_message(StandardError, "my error") { 1 + a } + assert_raise_with_message(StandardError, "my error") { 1 < a } + + a = Class.new(Numeric) do + def coerce(x); :bad_return_value; end + end.new + assert_raise_with_message(TypeError, "coerce must return [x, y]") { 1 + a } + assert_raise_with_message(TypeError, "coerce must return [x, y]") { 1 < a } + end + + def test_singleton_method + a = Numeric.new + assert_raise_with_message(TypeError, /foo/) { def a.foo; end } + assert_raise_with_message(TypeError, /\u3042/) { eval("def a.\u3042; end") } + end + + def test_dup + a = Numeric.new + assert_same a, a.dup end - def test_numeric + def test_clone a = Numeric.new - assert_raise(TypeError) { def a.foo; end } - assert_raise(TypeError) { eval("def a.\u3042; end") } - assert_raise(TypeError) { a.dup } + assert_same a, a.clone + assert_raise(ArgumentError) {a.clone(freeze: false)} + + c = EnvUtil.labeled_class("\u{1f4a9}", Numeric) + assert_raise_with_message(ArgumentError, /\u{1f4a9}/) do + c.new.clone(freeze: false) + end end def test_quo - assert_raise(TypeError) {DummyNumeric.new.quo(1)} + a = Numeric.new + assert_raise(TypeError) {a.quo(1)} end def test_quo_ruby_core_41575 - x = DummyNumeric.new rat = 84.quo(1) - DummyNumeric.class_eval do + x = Class.new(Numeric) do define_method(:to_r) { rat } - end + end.new assert_equal(2.quo(1), x.quo(42), '[ruby-core:41575]') - ensure - DummyNumeric.class_eval do - remove_method :to_r - end end def test_divmod =begin - DummyNumeric.class_eval do + x = Class.new(Numeric) do def /(x); 42.0; end def %(x); :mod; end - end + end.new - assert_equal(42, DummyNumeric.new.div(1)) - assert_equal(:mod, DummyNumeric.new.modulo(1)) - assert_equal([42, :mod], DummyNumeric.new.divmod(1)) + assert_equal(42, x.div(1)) + assert_equal(:mod, x.modulo(1)) + assert_equal([42, :mod], x.divmod(1)) =end assert_kind_of(Integer, 11.divmod(3.5).first, '[ruby-dev:34006]') - -=begin - ensure - DummyNumeric.class_eval do - remove_method :/, :% - end -=end end def test_real_p @@ -119,51 +125,70 @@ class TestNumeric < Test::Unit::TestCase end def test_abs - a = DummyNumeric.new - DummyNumeric.class_eval do + a = Class.new(Numeric) do def -@; :ok; end def <(x); true; end - end + end.new assert_equal(:ok, a.abs) - DummyNumeric.class_eval do - remove_method :< + a = Class.new(Numeric) do def <(x); false; end - end + end.new assert_equal(a, a.abs) - - ensure - DummyNumeric.class_eval do - remove_method :-@, :< - end end def test_zero_p - DummyNumeric.class_eval do + a = Class.new(Numeric) do def ==(x); true; end - end + end.new - assert_predicate(DummyNumeric.new, :zero?) + assert_predicate(a, :zero?) + end - ensure - DummyNumeric.class_eval do - remove_method :== - end + def test_nonzero_p + a = Class.new(Numeric) do + def zero?; true; end + end.new + assert_nil(a.nonzero?) + + a = Class.new(Numeric) do + def zero?; false; end + end.new + assert_equal(a, a.nonzero?) + end + + def test_positive_p + a = Class.new(Numeric) do + def >(x); true; end + end.new + assert_predicate(a, :positive?) + + a = Class.new(Numeric) do + def >(x); false; end + end.new + assert_not_predicate(a, :positive?) + end + + def test_negative_p + a = Class.new(Numeric) do + def <(x); true; end + end.new + assert_predicate(a, :negative?) + + a = Class.new(Numeric) do + def <(x); false; end + end.new + assert_not_predicate(a, :negative?) end def test_to_int - DummyNumeric.class_eval do + a = Class.new(Numeric) do def to_i; :ok; end - end - - assert_equal(:ok, DummyNumeric.new.to_int) + end.new - ensure - DummyNumeric.class_eval do - remove_method :to_i - end + assert_equal(:ok, a.to_int) end def test_cmp @@ -173,42 +198,32 @@ class TestNumeric < Test::Unit::TestCase end def test_floor_ceil_round_truncate - DummyNumeric.class_eval do + a = Class.new(Numeric) do def to_f; 1.5; end - end + end.new - a = DummyNumeric.new assert_equal(1, a.floor) assert_equal(2, a.ceil) assert_equal(2, a.round) assert_equal(1, a.truncate) - DummyNumeric.class_eval do - remove_method :to_f + a = Class.new(Numeric) do def to_f; 1.4; end - end + end.new - a = DummyNumeric.new assert_equal(1, a.floor) assert_equal(2, a.ceil) assert_equal(1, a.round) assert_equal(1, a.truncate) - DummyNumeric.class_eval do - remove_method :to_f + a = Class.new(Numeric) do def to_f; -1.5; end - end + end.new - a = DummyNumeric.new assert_equal(-2, a.floor) assert_equal(-1, a.ceil) assert_equal(-2, a.round) assert_equal(-1, a.truncate) - - ensure - DummyNumeric.class_eval do - remove_method :to_f - end end def assert_step(expected, (from, *args), inf: false) @@ -241,8 +256,7 @@ class TestNumeric < Test::Unit::TestCase end def test_step - i, bignum = 32, 1 << 30 - bignum <<= (i <<= 1) - 32 until bignum.is_a?(Bignum) + bignum = RbConfig::LIMITS['FIXNUM_MAX'] + 1 assert_raise(ArgumentError) { 1.step(10, 1, 0) { } } assert_raise(ArgumentError) { 1.step(10, 1, 0).size } assert_raise(ArgumentError) { 1.step(10, 0) { } } @@ -333,4 +347,64 @@ class TestNumeric < Test::Unit::TestCase assert_not_operator(1, :eql?, 1.0) assert_not_operator(1, :eql?, 2) end + + def test_coerced_remainder + assert_separately([], <<-'end;') + x = Class.new do + def coerce(a) [self, a]; end + def %(a) self; end + end.new + assert_raise(ArgumentError) {1.remainder(x)} + end; + end + + def test_comparison_comparable + bug12864 = '[ruby-core:77713] [Bug #12864]' + + myinteger = Class.new do + include Comparable + + def initialize(i) + @i = i.to_i + end + attr_reader :i + + def <=>(other) + @i <=> (other.is_a?(self.class) ? other.i : other) + end + end + + all_assertions(bug12864) do |a| + [5, 2**62, 2**61].each do |i| + a.for("%#x"%i) do + m = myinteger.new(i) + assert_equal(i, m) + assert_equal(m, i) + end + 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]') + end + end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 596bddf1e5..c25dcf9c37 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -1,6 +1,6 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestObject < Test::Unit::TestCase def setup @@ -12,16 +12,71 @@ class TestObject < Test::Unit::TestCase $VERBOSE = @verbose end + def test_itself + feature6373 = '[ruby-core:44704] [Feature #6373]' + object = Object.new + assert_same(object, object.itself, feature6373) + end + + def test_yield_self + feature = '[ruby-core:46320] [Feature #6721]' + object = Object.new + assert_same(self, object.yield_self {self}, feature) + assert_same(object, object.yield_self {|x| break x}, feature) + enum = object.yield_self + assert_instance_of(Enumerator, enum) + assert_equal(1, enum.size) + end + def test_dup - assert_raise(TypeError) { 1.dup } - assert_raise(TypeError) { true.dup } - assert_raise(TypeError) { nil.dup } + assert_equal 1, 1.dup + assert_equal true, true.dup + assert_equal nil, nil.dup + assert_equal false, false.dup + x = :x; assert_equal x, x.dup + x = "bug13145".intern; assert_equal x, x.dup + x = 1 << 64; assert_equal x, x.dup + x = 1.72723e-77; assert_equal x, x.dup assert_raise(TypeError) do Object.new.instance_eval { initialize_copy(1) } end end + def test_clone + a = Object.new + def a.b; 2 end + + a.freeze + c = a.clone + assert_equal(true, c.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(2, d.b) + assert_equal(3, d.e) + + assert_equal 1, 1.clone + assert_equal true, true.clone + assert_equal nil, nil.clone + assert_equal false, false.clone + x = :x; assert_equal x, x.dup + x = "bug13145".intern; assert_equal x, x.dup + x = 1 << 64; assert_equal x, x.clone + x = 1.72723e-77; assert_equal x, x.clone + assert_raise(ArgumentError) {1.clone(freeze: false)} + assert_raise(ArgumentError) {true.clone(freeze: false)} + assert_raise(ArgumentError) {nil.clone(freeze: false)} + assert_raise(ArgumentError) {false.clone(freeze: false)} + x = EnvUtil.labeled_class("\u{1f4a9}").new + assert_raise_with_message(ArgumentError, /\u{1f4a9}/) do + Object.new.clone(freeze: x) + end + end + def test_init_dupclone cls = Class.new do def initialize_clone(orig); throw :initialize_clone; end @@ -29,8 +84,8 @@ class TestObject < Test::Unit::TestCase end obj = cls.new - assert_throws(:initialize_clone) {obj.clone} - assert_throws(:initialize_dup) {obj.dup} + assert_throw(:initialize_clone) {obj.clone} + assert_throw(:initialize_dup) {obj.dup} end def test_instance_of @@ -44,12 +99,12 @@ class TestObject < Test::Unit::TestCase def test_taint_frozen_obj o = Object.new o.freeze - assert_raise(RuntimeError) { o.taint } + assert_raise(FrozenError) { o.taint } o = Object.new o.taint o.freeze - assert_raise(RuntimeError) { o.untaint } + assert_raise(FrozenError) { o.untaint } end def test_freeze_immediate @@ -57,6 +112,20 @@ class TestObject < Test::Unit::TestCase 1.freeze assert_equal(true, 1.frozen?) assert_equal(true, 2.frozen?) + assert_equal(true, true.frozen?) + assert_equal(true, false.frozen?) + assert_equal(true, nil.frozen?) + end + + def test_frozen_error_message + name = "C\u{30c6 30b9 30c8}" + klass = EnvUtil.labeled_class(name) { + attr_accessor :foo + } + obj = klass.new.freeze + assert_raise_with_message(FrozenError, /#{name}/) { + obj.foo = 1 + } end def test_nil_to_f @@ -213,28 +282,46 @@ class TestObject < Test::Unit::TestCase end def test_remove_instance_variable - o = Object.new - o.instance_eval { @foo = :foo } - o.remove_instance_variable(:@foo) - assert_equal(false, o.instance_variable_defined?(:@foo)) + { 'T_OBJECT' => Object.new, + 'T_CLASS,T_MODULE' => Class.new(Object), + 'generic ivar' => '', + }.each do |desc, o| + e = assert_raise(NameError, "#{desc} iv removal raises before set") do + o.remove_instance_variable(:@foo) + end + assert_equal([o, :@foo], [e.receiver, e.name]) + o.instance_eval { @foo = :foo } + assert_equal(:foo, o.remove_instance_variable(:@foo), + "#{desc} iv removal returns original value") + assert_not_send([o, :instance_variable_defined?, :@foo], + "#{desc} iv removed successfully") + e = assert_raise(NameError, "#{desc} iv removal raises after removal") do + o.remove_instance_variable(:@foo) + end + assert_equal([o, :@foo], [e.receiver, e.name]) + end end - def test_convert_type + def test_convert_string o = Object.new def o.to_s; 1; end assert_raise(TypeError) { String(o) } def o.to_s; "o"; end assert_equal("o", String(o)) + def o.to_str; "O"; end + assert_equal("O", String(o)) def o.respond_to?(*) false; end assert_raise(TypeError) { String(o) } end - def test_check_convert_type + def test_convert_array o = Object.new def o.to_a; 1; end assert_raise(TypeError) { Array(o) } def o.to_a; [1]; end assert_equal([1], Array(o)) + def o.to_ary; [2]; end + assert_equal([2], Array(o)) def o.respond_to?(*) false; end assert_equal([o], Array(o)) end @@ -289,12 +376,12 @@ class TestObject < Test::Unit::TestCase end def test_redefine_method_which_may_case_serious_problem - assert_in_out_err([], <<-INPUT, [], /warning: redefining `object_id' may cause serious problems$/) + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `object_id' may cause serious problems$") $VERBOSE = false def (Object.new).object_id; end INPUT - assert_in_out_err([], <<-INPUT, [], /warning: redefining `__send__' may cause serious problems$/) + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining `__send__' may cause serious problems$") $VERBOSE = false def (Object.new).__send__; end INPUT @@ -312,7 +399,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 @@ -337,7 +424,7 @@ class TestObject < Test::Unit::TestCase assert_raise(NoMethodError, bug2202) {o2.meth2} %w(object_id __send__ initialize).each do |m| - assert_in_out_err([], <<-INPUT, %w(:ok), /warning: removing `#{m}' may cause serious problems$/) + assert_in_out_err([], <<-INPUT, %w(:ok), %r"warning: removing `#{m}' may cause serious problems$") $VERBOSE = false begin Class.new.instance_eval { remove_method(:#{m}) } @@ -346,6 +433,19 @@ class TestObject < Test::Unit::TestCase end INPUT end + + m = "\u{30e1 30bd 30c3 30c9}" + c = Class.new + assert_raise_with_message(NameError, /#{m}/) do + c.class_eval {remove_method m} + end + c = Class.new { + define_method(m) {} + remove_method(m) + } + assert_raise_with_message(NameError, /#{m}/) do + c.class_eval {remove_method m} + end end def test_method_missing @@ -571,7 +671,7 @@ class TestObject < Test::Unit::TestCase end begin nil.public_send(o) { x = :ng } - rescue + rescue TypeError end assert_equal(:ok, x) end @@ -701,6 +801,16 @@ class TestObject < Test::Unit::TestCase end EOS assert_match(/\bToS\u{3042}:/, x) + + name = "X".freeze + x = Object.new.taint + class<<x;self;end.class_eval {define_method(:to_s) {name}} + assert_same(name, x.to_s) + assert_not_predicate(name, :tainted?) + assert_raise(FrozenError) {name.taint} + assert_equal("X", [x].join("")) + assert_not_predicate(name, :tainted?) + assert_not_predicate(eval('"X".freeze'), :tainted?) end def test_inspect @@ -739,10 +849,12 @@ class TestObject < Test::Unit::TestCase def initialize @\u{3044} = 42 end - new.inspect + new end EOS - assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x) + assert_match(/\bInspect\u{3042}:.* @\u{3044}=42\b/, x.inspect) + x.instance_variable_set("@\u{3046}".encode(Encoding::EUC_JP), 6) + assert_match(/@\u{3046}=6\b/, x.inspect) end def test_singleton_class @@ -769,18 +881,16 @@ class TestObject < Test::Unit::TestCase def test_redef_method_missing bug5473 = '[ruby-core:40287]' ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code| - out, err, status = EnvUtil.invoke_ruby([], <<-SRC, true, true) + exc = code[/\A[A-Z]\w+/] || 'RuntimeError' + assert_separately([], <<-SRC) class ::Object def method_missing(m, *a, &b) raise #{code} end end - p((1.foo rescue $!)) + assert_raise_with_message(#{exc}, "bug5473", #{bug5473.dump}) {1.foo} SRC - assert_send([status, :success?], bug5473) - assert_equal("", err, bug5473) - assert_equal((eval("raise #{code}") rescue $!.inspect), out.chomp, bug5473) end end @@ -789,7 +899,7 @@ 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)}} + assert_raise(FrozenError, "frozen") {c.instance_eval {initialize_copy(b)}} d = a.dup.trust [a, b, c, d] end @@ -810,18 +920,29 @@ class TestObject < Test::Unit::TestCase end def test_type_error_message - issue = "Bug #7539" + _issue = "Bug #7539" assert_raise_with_message(TypeError, "can't convert Array into Integer") {Integer([42])} assert_raise_with_message(TypeError, 'no implicit conversion of Array into Integer') {[].first([42])} + assert_raise_with_message(TypeError, "can't convert Array into Rational") {Rational([42])} end def test_copied_ivar_memory_leak bug10191 = '[ruby-core:64700] [Bug #10191]' - assert_no_memory_leak([], <<-"end;", <<-"end;", bug10191, rss: true, timeout: 60, limit: 2.5) + assert_no_memory_leak([], <<-"end;", <<-"end;", bug10191, timeout: 60, limit: 1.8) def (a = Object.new).set; @v = nil; end num = 500_000 end; num.times {a.clone.set} end; end + + def test_clone_object_should_not_be_old + assert_normal_exit <<-EOS, '[Bug #13775]' + b = proc { } + 10.times do |i| + b.clone + GC.start + end + EOS + end end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index 6eb7c8b205..c352b75b70 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestObjectSpace < Test::Unit::TestCase def self.deftest_id2ref(obj) @@ -78,9 +78,84 @@ End end assert_in_out_err([], code[""], ["finalized"]) assert_in_out_err([], code["private "], ["finalized"]) + c = EnvUtil.labeled_class("C\u{3042}").new + o = Object.new + assert_raise_with_message(ArgumentError, /C\u{3042}/) { + ObjectSpace.define_finalizer(o, c) + } + end + + def test_finalizer_with_super + assert_in_out_err(["-e", <<-END], "", %w(:ok), []) + class A + def foo + end + end + + class B < A + def foo + 1.times { super } + end + end + + class C + module M + end + + FINALIZER = proc do + M.module_eval(__FILE__, "", __LINE__) do + end + end + + def define_finalizer + ObjectSpace.define_finalizer(self, FINALIZER) + end + end + + class D + def foo + B.new.foo + end + end + + C::M.singleton_class.send :define_method, :module_eval do |src, id, line| + end + + GC.stress = true + 10.times do + C.new.define_finalizer + D.new.foo + end + + p :ok + END end def test_each_object + klass = Class.new + new_obj = klass.new + + found = [] + count = ObjectSpace.each_object(klass) do |obj| + found << obj + end + assert_equal(1, count) + assert_equal(1, found.size) + assert_same(new_obj, found[0]) + end + + def test_each_object_enumerator + klass = Class.new + new_obj = klass.new + + found = [] + counter = ObjectSpace.each_object(klass) + assert_equal(1, counter.each {|obj| found << obj}) + assert_equal(1, found.size) + assert_same(new_obj, found[0]) + end + + def test_each_object_no_gabage assert_separately([], <<-End) GC.disable eval('begin; 1.times{}; rescue; ensure; end') @@ -105,4 +180,27 @@ End p Thread.current[:__recursive_key__] end; end + + def test_each_object_singleton_class + assert_separately([], <<-End) + class C + class << self + $c = self + end + end + + exist = false + ObjectSpace.each_object(Class){|o| + exist = true if $c == o + } + assert(exist, 'Bug #11360') + End + + klass = Class.new + instance = klass.new + sclass = instance.singleton_class + meta = klass.singleton_class + assert_kind_of(meta, sclass) + assert_include(ObjectSpace.each_object(meta).to_a, sclass) + end end diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 6e22891ddd..2a4cc19699 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -1,98 +1,124 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' +require 'objspace' class TestRubyOptimization < Test::Unit::TestCase - - BIGNUM_POS_MIN_32 = 1073741824 # 2 ** 30 - if BIGNUM_POS_MIN_32.kind_of?(Fixnum) - FIXNUM_MAX = 4611686018427387903 # 2 ** 62 - 1 - else - FIXNUM_MAX = 1073741823 # 2 ** 30 - 1 - end - - BIGNUM_NEG_MAX_32 = -1073741825 # -2 ** 30 - 1 - if BIGNUM_NEG_MAX_32.kind_of?(Fixnum) - FIXNUM_MIN = -4611686018427387904 # -2 ** 62 - else - FIXNUM_MIN = -1073741824 # -2 ** 30 - end - - def redefine_method(klass, method) - (@redefine_method_seq ||= 0) - seq = (@redefine_method_seq += 1) - eval(<<-End, ::TOPLEVEL_BINDING) + def assert_redefine_method(klass, method, code, msg = nil) + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; class #{klass} - alias redefine_method_orig_#{seq} #{method} undef #{method} def #{method}(*args) args[0] end end - End - begin - return yield - ensure - eval(<<-End, ::TOPLEVEL_BINDING) - class #{klass} - undef #{method} - alias #{method} redefine_method_orig_#{seq} - end - End - end + #{code} + end; end - def test_fixnum_plus - a, b = 1, 2 - assert_equal 3, a + b - assert_instance_of Fixnum, FIXNUM_MAX - assert_instance_of Bignum, FIXNUM_MAX + 1 + def disasm(name) + RubyVM::InstructionSequence.of(method(name)).disasm + end + def test_fixnum_plus assert_equal 21, 10 + 11 - assert_equal 11, redefine_method('Fixnum', '+') { 10 + 11 } - assert_equal 21, 10 + 11 + assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11') end def test_fixnum_minus assert_equal 5, 8 - 3 - assert_instance_of Fixnum, FIXNUM_MIN - assert_instance_of Bignum, FIXNUM_MIN - 1 - - assert_equal 5, 8 - 3 - assert_equal 3, redefine_method('Fixnum', '-') { 8 - 3 } - assert_equal 5, 8 - 3 + assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3') end def test_fixnum_mul assert_equal 15, 3 * 5 + assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5') end def test_fixnum_div assert_equal 3, 15 / 5 - assert_equal 6.66, redefine_method('Float', '/') { 4.2 / 6.66 }, "bug 9238" + assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5') end def test_fixnum_mod assert_equal 1, 8 % 7 + 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_equal 2.0, redefine_method('Float', '+') { 2.0 + 2.0 } + assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + end + + def test_float_minus assert_equal 4.0, 2.0 + 2.0 + assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + end + + def test_float_mul + assert_equal 29.25, 4.5 * 6.5 + assert_redefine_method('Float', '*', 'assert_equal 6.5, 4.5 * 6.5') + end + + def test_float_div + assert_in_delta 0.63063063063063063, 4.2 / 6.66 + 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_nil redefine_method('String', 'length') { "string".length } - assert_equal 6, "string".length + assert_redefine_method('String', 'length', 'assert_nil "string".length') + end + + def test_string_size + assert_equal 6, "string".size + assert_redefine_method('String', 'size', 'assert_nil "string".size') end def test_string_empty? assert_equal true, "".empty? assert_equal false, "string".empty? - assert_nil redefine_method('String', 'empty?') { "string".empty? } - assert_equal true, "".empty? - assert_equal false, "string".empty? + assert_redefine_method('String', 'empty?', 'assert_nil "string".empty?') end def test_string_plus @@ -100,8 +126,7 @@ class TestRubyOptimization < Test::Unit::TestCase assert_equal "x", "x" + "" assert_equal "x", "" + "x" assert_equal "ab", "a" + "b" - assert_equal 'b', redefine_method('String', '+') { "a" + "b" } - assert_equal "ab", "a" + "b" + assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"') end def test_string_succ @@ -111,34 +136,110 @@ class TestRubyOptimization < Test::Unit::TestCase def test_string_format assert_equal '2', '%d' % 2 + assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2') + end + + def test_string_freeze + assert_equal "foo", "foo".freeze + assert_equal "foo".freeze.object_id, "foo".freeze.object_id + assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze') + end + + def test_string_uminus + assert_same "foo".freeze, -"foo" + assert_redefine_method('String', '-@', 'assert_nil(-"foo")') + end + + def test_string_freeze_saves_memory + n = 16384 + data = '.'.freeze + r, w = IO.pipe + w.write data + + s = r.readpartial(n, '') + assert_operator ObjectSpace.memsize_of(s), :>=, n, + 'IO buffer NOT resized prematurely because will likely be reused' + + s.freeze + assert_equal ObjectSpace.memsize_of(data), ObjectSpace.memsize_of(s), + 'buffer resized on freeze since it cannot be written to again' + ensure + r.close if r + w.close if w + end + + def test_string_eq_neq + %w(== !=).each do |m| + assert_redefine_method('String', m, <<-end) + assert_equal :b, ("a" #{m} "b").to_sym + b = 'b' + assert_equal :b, ("a" #{m} b).to_sym + assert_equal :b, (b #{m} "b").to_sym + end + end + end + + def test_string_ltlt + assert_equal "", "" << "" + assert_equal "x", "x" << "" + assert_equal "x", "" << "x" + assert_equal "ab", "a" << "b" + assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"') end def test_array_plus assert_equal [1,2], [1]+[2] + assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') end def test_array_minus assert_equal [2], [1,2] - [1] + assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]') end def test_array_length assert_equal 0, [].length assert_equal 3, [1,2,3].length + assert_redefine_method('Array', 'length', 'assert_nil([].length); assert_nil([1,2,3].length)') end def test_array_empty? assert_equal true, [].empty? assert_equal false, [1,2,3].empty? + assert_redefine_method('Array', 'empty?', 'assert_nil([].empty?); assert_nil([1,2,3].empty?)') end def test_hash_length assert_equal 0, {}.length assert_equal 1, {1=>1}.length + assert_redefine_method('Hash', 'length', 'assert_nil({}.length); assert_nil({1=>1}.length)') end def test_hash_empty? assert_equal true, {}.empty? assert_equal false, {1=>1}.empty? + assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)') + end + + def test_hash_aref_with + h = { "foo" => 1 } + assert_equal 1, h["foo"] + assert_redefine_method('Hash', '[]', "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + h = { "foo" => 1 } + assert_equal "foo", h["foo"] + end; + end + + def test_hash_aset_with + h = {} + assert_equal 1, h["foo"] = 1 + assert_redefine_method('Hash', '[]=', "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + h = {} + assert_equal 1, h["foo"] = 1, "assignment always returns value set" + assert_nil h["foo"] + end; end class MyObj @@ -157,67 +258,248 @@ class TestRubyOptimization < Test::Unit::TestCase assert_equal true, MyObj.new == nil end + def self.tailcall(klass, src, file = nil, path = nil, line = nil, tailcall: true) + unless file + loc, = caller_locations(1, 1) + file = loc.path + line ||= loc.lineno + end + RubyVM::InstructionSequence.new("proc {|_|_.class_eval {#{src}}}", + file, (path || file), line, + tailcall_optimization: tailcall, + trace_instruction: false) + .eval[klass] + end + + def tailcall(*args) + self.class.tailcall(singleton_class, *args) + end + def test_tailcall bug4082 = '[ruby-core:33289]' - option = { - tailcall_optimization: true, - trace_instruction: false, - } - iseq = RubyVM::InstructionSequence.new(<<-EOF, "Bug#4082", bug4082, nil, option).eval - class #{self.class}::Tailcall - def fact_helper(n, res) - if n == 1 - res - else - fact_helper(n - 1, n * res) - end - end - def fact(n) - fact_helper(n, 1) + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def fact_helper(n, res) + if n == 1 + res + else + fact_helper(n - 1, n * res) end end - EOF - assert_equal(9131, Tailcall.new.fact(3000).to_s.size, bug4082) + def fact(n) + fact_helper(n, 1) + end + end; + assert_equal(9131, fact(3000).to_s.size, message(bug4082) {disasm(:fact_helper)}) end def test_tailcall_with_block bug6901 = '[ruby-dev:46065]' - option = { - tailcall_optimization: true, - trace_instruction: false, + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def identity(val) + val + end + + def delay + -> { + identity(yield) + } + end + end; + assert_equal(123, delay { 123 }.call, message(bug6901) {disasm(:delay)}) + end + + def just_yield + yield + end + + def test_tailcall_inhibited_by_block + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def yield_result + just_yield {:ok} + end + end; + assert_equal(:ok, yield_result, message {disasm(:yield_result)}) + end + + def do_raise + raise "should be rescued" + end + + def errinfo + $! + end + + def test_tailcall_inhibited_by_rescue + bug12082 = '[ruby-core:73871] [Bug #12082]' + + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def to_be_rescued + return do_raise + 1 + 2 + rescue + errinfo + end + end; + result = assert_nothing_raised(RuntimeError, message(bug12082) {disasm(:to_be_rescued)}) { + to_be_rescued } - iseq = RubyVM::InstructionSequence.new(<<-EOF, "Bug#6901", bug6901, nil, option).eval - def identity(val) - val + assert_instance_of(RuntimeError, result, bug12082) + assert_equal("should be rescued", result.message, bug12082) end - def delay - -> { - identity(yield) + def test_tailcall_symbol_block_arg + bug12565 = '[ruby-core:46065]' + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def apply_one_and_two(&block) + yield(1, 2) + end + + def add_one_and_two + apply_one_and_two(&:+) + end + end; + assert_equal(3, add_one_and_two, + message(bug12565) {disasm(:add_one_and_two)}) + end + + def test_tailcall_interrupted_by_sigint + bug12576 = 'ruby-core:76327' + script = "#{<<-"begin;"}\n#{<<~'end;'}" + begin; + RubyVM::InstructionSequence.compile_option = { + :tailcall_optimization => true, + :trace_instruction => false + } + + eval "#{<<~"begin;"}\n#{<<~'end;1'}" + begin; + def foo + foo + end + puts("start") + STDOUT.flush + foo + end;1 + end; + status, _err = EnvUtil.invoke_ruby([], "", true, true, {}) { + |in_p, out_p, err_p, pid| + in_p.write(script) + in_p.close + out_p.gets + sig = :INT + begin + Process.kill(sig, pid) + Timeout.timeout(1) do + *, stat = Process.wait2(pid) + [stat, err_p.read] + end + rescue Timeout::Error + if sig == :INT + sig = :KILL + retry + else + raise + end + end } + assert_not_equal("SEGV", Signal.signame(status.termsig || 0), bug12576) + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_tailcall_condition_block + bug = '[ruby-core:78015] [Bug #12905]' + + src = "#{<<-"begin;"}\n#{<<~"end;"}" + begin; + def run(current, final) + if current < final + run(current+1, final) + else + nil + end + end + end; + + obj = Object.new + 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) + level *= 2 + mesg = message {"#{bug}: #{$!.backtrace_locations.size} / #{level} stack levels"} + assert_nothing_raised(SystemStackError, mesg) { + obj.run(1, level) + } + end + + class Bug10557 + def [](_) + block_given? + end + + def []=(_, _) + block_given? + end end - EOF - assert_equal(123, delay { 123 }.call, bug6901) + + def test_block_given_aset_aref + bug10557 = '[ruby-core:66595]' + assert_equal(true, Bug10557.new.[](nil){}, bug10557) + assert_equal(true, Bug10557.new.[](0){}, bug10557) + assert_equal(true, Bug10557.new.[](false){}, bug10557) + assert_equal(true, Bug10557.new.[](''){}, bug10557) + assert_equal(true, Bug10557.new.[]=(nil, 1){}, bug10557) + assert_equal(true, Bug10557.new.[]=(0, 1){}, bug10557) + assert_equal(true, Bug10557.new.[]=(false, 1){}, bug10557) + assert_equal(true, Bug10557.new.[]=('', 1){}, bug10557) + end + + def test_string_freeze_block + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + class String + undef freeze + def freeze + block_given? + end + end + assert_equal(true, "block".freeze {}) + assert_equal(false, "block".freeze) + end; end def test_opt_case_dispatch - code = <<-EOF + code = "#{<<-"begin;"}\n#{<<~"end;"}" + begin; case foo when "foo" then :foo + when true then true + when false then false when :sym then :sym when 6 then :fix + when nil then nil when 0.1 then :float when 0xffffffffffffffff then :big else :nomatch end - EOF + end; check = { 'foo' => :foo, + true => true, + false => false, :sym => :sym, 6 => :fix, + nil => nil, 0.1 => :float, 0xffffffffffffffff => :big, } @@ -229,7 +511,8 @@ class TestRubyOptimization < Test::Unit::TestCase assert_equal :nomatch, eval("foo = :blah\n#{code}") check.each do |foo, _| klass = foo.class.to_s - assert_separately([], <<-"end;") # do + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; class #{klass} undef === def ===(*args) @@ -243,6 +526,13 @@ class TestRubyOptimization < Test::Unit::TestCase end end + def test_eqq + [ nil, true, false, 0.1, :sym, 'str', 0xffffffffffffffff ].each do |v| + k = v.class.to_s + assert_redefine_method(k, '===', "assert_equal(#{v.inspect} === 0, 0)") + end + end + def test_opt_case_dispatch_inf inf = 1.0/0.0 result = case inf @@ -253,4 +543,245 @@ class TestRubyOptimization < Test::Unit::TestCase end assert_nil result, '[ruby-dev:49423] [Bug #11804]' end + + def test_nil_safe_conditional_assign + bug11816 = '[ruby-core:74993] [Bug #11816]' + assert_ruby_status([], 'nil&.foo &&= false', bug11816) + end + + def test_peephole_string_literal_range + code = "#{<<~"begin;"}\n#{<<~"end;"}" + begin; + case ver + when "2.0.0".."2.3.2" then :foo + 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) + end + + def test_branch_condition_backquote + bug = '[ruby-core:80740] [Bug #13444] redefined backquote should be called' + class << self + def `(s) + @q = s + @r + end + end + + @q = nil + @r = nil + assert_equal("bar", ("bar" unless `foo`), bug) + assert_equal("foo", @q, bug) + + @q = nil + @r = true + assert_equal("bar", ("bar" if `foo`), bug) + assert_equal("foo", @q, bug) + + @q = nil + @r = "z" + assert_equal("bar", ("bar" if `foo#{@r}`)) + assert_equal("fooz", @q, bug) + end + + def test_branch_condition_def + bug = '[ruby-core:80740] [Bug #13444] method should be defined' + c = Class.new do + raise "bug" unless def t;:ok;end + end + assert_nothing_raised(NoMethodError, bug) do + assert_equal(:ok, c.new.t) + end + end + + def test_branch_condition_defs + bug = '[ruby-core:80740] [Bug #13444] singleton method should be defined' + raise "bug" unless def self.t;:ok;end + assert_nothing_raised(NameError, bug) do + assert_equal(:ok, t) + end + end + + def test_retry_label_in_unreachable_chunk + bug = '[ruby-core:81272] [Bug #13578]' + assert_valid_syntax("#{<<-"begin;"}\n#{<<-"end;"}", bug) + begin; + def t; if false; case 42; when s {}; end; end; end + end; + end + + def bptest_yield &b + yield + end + + def bptest_yield_pass &b + bptest_yield(&b) + end + + def bptest_bp_value &b + b + end + + def bptest_bp_pass_bp_value &b + bptest_bp_value(&b) + end + + def bptest_binding &b + binding + end + + def bptest_set &b + b = Proc.new{2} + end + + def test_block_parameter + assert_equal(1, bptest_yield{1}) + assert_equal(1, bptest_yield_pass{1}) + assert_equal(1, send(:bptest_yield){1}) + + assert_equal(Proc, bptest_bp_value{}.class) + assert_equal nil, bptest_bp_value + assert_equal(Proc, bptest_bp_pass_bp_value{}.class) + assert_equal nil, bptest_bp_pass_bp_value + + assert_equal Proc, bptest_binding{}.local_variable_get(:b).class + + assert_equal 2, bptest_set{1}.call + end + + 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) + 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} + END + end + + def test_peephole_optimization_without_trace + assert_separately [], <<-END + RubyVM::InstructionSequence.compile_option = {trace_instruction: false} + eval "def foo; 1.times{|(a), &b| nil && a}; end" + END + end + + def test_clear_unreachable_keyword_args + assert_separately [], <<-END, timeout: 15 + 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')) + 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_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 end diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb index 4b089f7322..aec418913e 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -1,4 +1,5 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' class TestPack < Test::Unit::TestCase @@ -78,6 +79,14 @@ class TestPack < Test::Unit::TestCase assert_equal("\x01\x02\x03\x04", [0x01020304].pack("L"+mod)) assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("q"+mod)) assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("Q"+mod)) + psize = [nil].pack('p').bytesize + if psize == 4 + assert_equal("\x01\x02\x03\x04", [0x01020304].pack("j"+mod)) + assert_equal("\x01\x02\x03\x04", [0x01020304].pack("J"+mod)) + elsif psize == 8 + assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("j"+mod)) + assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("J"+mod)) + end assert_match(/\A\x00*\x01\x02\z/, [0x0102].pack("s!"+mod)) assert_match(/\A\x00*\x01\x02\z/, [0x0102].pack("S!"+mod)) assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("i"+mod)) @@ -86,7 +95,14 @@ class TestPack < Test::Unit::TestCase assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I!"+mod)) assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("l!"+mod)) assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("L!"+mod)) - %w[s S l L q Q s! S! i I i! I! l! L!].each {|fmt| + if psize == 4 + assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("j!"+mod)) + assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("J!"+mod)) + elsif psize == 8 + assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("j!"+mod)) + assert_match(/\A\x00*\x01\x02\x03\x04\x05\x06\x07\x08\z/, [0x0102030405060708].pack("J!"+mod)) + end + %w[s S l L q Q j J s! S! i I i! I! l! L! j! J!].each {|fmt| fmt += mod nuls = [0].pack(fmt) v = 0 @@ -111,6 +127,14 @@ class TestPack < Test::Unit::TestCase assert_equal("\x04\x03\x02\x01", [0x01020304].pack("L"+mod)) assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("q"+mod)) assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("Q"+mod)) + psize = [nil].pack('p').bytesize + if psize == 4 + assert_equal("\x04\x03\x02\x01", [0x01020304].pack("j"+mod)) + assert_equal("\x04\x03\x02\x01", [0x01020304].pack("J"+mod)) + elsif psize == 8 + assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("j"+mod)) + assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("J"+mod)) + end assert_match(/\A\x02\x01\x00*\z/, [0x0102].pack("s!"+mod)) assert_match(/\A\x02\x01\x00*\z/, [0x0102].pack("S!"+mod)) assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("i"+mod)) @@ -119,7 +143,14 @@ class TestPack < Test::Unit::TestCase assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I!"+mod)) assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("l!"+mod)) assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("L!"+mod)) - %w[s S l L q Q s! S! i I i! I! l! L!].each {|fmt| + if psize == 4 + assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("j!"+mod)) + assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("J!"+mod)) + elsif psize == 8 + assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("j!"+mod)) + assert_match(/\A\x08\x07\x06\x05\x04\x03\x02\x01\x00*\z/, [0x0102030405060708].pack("J!"+mod)) + end + %w[s S l L q Q j J s! S! i I i! I! l! L! j! J!].each {|fmt| fmt += mod nuls = [0].pack(fmt) v = 0 @@ -181,7 +212,7 @@ class TestPack < Test::Unit::TestCase assert_equal a[0], a.pack("p").unpack("p")[0] assert_equal a, a.pack("p").freeze.unpack("p*") assert_raise(ArgumentError) { (a.pack("p") + "").unpack("p*") } - assert_raise(ArgumentError) { (a.pack("p") << "d").unpack("p*") } + assert_equal a, (a.pack("p") << "d").unpack("p*") end def test_format_string_modified @@ -417,6 +448,43 @@ class TestPack < Test::Unit::TestCase assert_operator(8, :<=, [1].pack("Q!").bytesize) end + def test_pack_unpack_jJ + # Note: we assume that the size of intptr_t and uintptr_t equals to the size + # of real pointer. + psize = [nil].pack("p").bytesize + if psize == 4 + s1 = [67305985, -50462977].pack("j*") + s2 = [67305985, 4244504319].pack("J*") + assert_equal(s1, s2) + assert_equal([67305985, -50462977], s2.unpack("j*")) + assert_equal([67305985, 4244504319], s1.unpack("J*")) + + s1 = [67305985, -50462977].pack("j!*") + s2 = [67305985, 4244504319].pack("J!*") + assert_equal([67305985, -50462977], s1.unpack("j!*")) + assert_equal([67305985, 4244504319], s2.unpack("J!*")) + + assert_equal(4, [1].pack("j").bytesize) + assert_equal(4, [1].pack("J").bytesize) + elsif psize == 8 + s1 = [578437695752307201, -506097522914230529].pack("j*") + s2 = [578437695752307201, 17940646550795321087].pack("J*") + assert_equal(s1, s2) + assert_equal([578437695752307201, -506097522914230529], s2.unpack("j*")) + assert_equal([578437695752307201, 17940646550795321087], s1.unpack("J*")) + + s1 = [578437695752307201, -506097522914230529].pack("j!*") + s2 = [578437695752307201, 17940646550795321087].pack("J!*") + assert_equal([578437695752307201, -506097522914230529], s2.unpack("j!*")) + assert_equal([578437695752307201, 17940646550795321087], s1.unpack("J!*")) + + assert_equal(8, [1].pack("j").bytesize) + assert_equal(8, [1].pack("J").bytesize) + else + assert false, "we don't know such platform now." + end + end + def test_pack_unpack_nN assert_equal("\000\000\000\001\377\377\177\377\200\000\377\377", [0,1,-1,32767,-32768,65535].pack("n*")) assert_equal("\000\000\000\000\000\000\000\001\377\377\377\377", [0,1,-1].pack("N*")) @@ -480,6 +548,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 @@ -526,6 +597,11 @@ EXPECTED assert_equal(["a"*46], "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n".unpack("u")) assert_equal(["abcdefghi"], "&86)C9&5F\n#9VAI\n".unpack("u")) + assert_equal(["abcdef"], "#86)C\n#9&5F\n".unpack("u")) + assert_equal(["abcdef"], "#86)CX\n#9&5FX\n".unpack("u")) # X is a (dummy) checksum. + assert_equal(["abcdef"], "#86)C\r\n#9&5F\r\n".unpack("u")) + assert_equal(["abcdef"], "#86)CX\r\n#9&5FX\r\n".unpack("u")) # X is a (dummy) checksum. + assert_equal(["\x00"], "\"\n".unpack("u")) assert_equal(["\x00"], "! \r \n".unpack("u")) end @@ -613,6 +689,11 @@ EXPECTED assert_equal(["pre=hoge"], "pre=hoge".unpack("M")) assert_equal(["pre==31after"], "pre==31after".unpack("M")) assert_equal(["pre===31after"], "pre===31after".unpack("M")) + + bug = '[ruby-core:83055] [Bug #13949]' + s = "abcdef".unpack1("M") + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_predicate(s, :ascii_only?, bug) end def test_pack_unpack_P2 @@ -709,4 +790,90 @@ EXPECTED $VERBOSE = verbose end + def test_invalid_warning + assert_warning(/unknown pack directive ',' in ','/) { + [].pack(",") + } + assert_warning(/\A[ -~]+\Z/) { + [].pack("\x7f") + } + assert_warning(/\A(.* in '\u{3042}'\n)+\z/) { + [].pack("\u{3042}") + } + end + + def test_pack_resize + assert_separately([], <<-'end;') + ary = [] + obj = Class.new { + define_method(:to_str) { + ary.clear() + ary = nil + GC.start + "TALOS" + } + }.new + + ary.push(obj) + ary.push(".") + + assert_raise_with_message(ArgumentError, /too few/) {ary.pack("AA")} + end; + end + + def test_pack_with_buffer + buf = String.new(capacity: 100) + + assert_raise_with_message(FrozenError, /frozen/) { + [0xDEAD_BEEF].pack('N', buffer: 'foo'.freeze) + } + assert_raise_with_message(TypeError, /must be String/) { + [0xDEAD_BEEF].pack('N', buffer: Object.new) + } + + addr = [buf].pack('p') + + [0xDEAD_BEEF].pack('N', buffer: buf) + assert_equal "\xDE\xAD\xBE\xEF", buf + + [0xBABE_F00D].pack('@4N', buffer: buf) + assert_equal "\xDE\xAD\xBE\xEF\xBA\xBE\xF0\x0D", buf + assert_equal addr, [buf].pack('p') + + [0xBAAD_FACE].pack('@10N', buffer: buf) + assert_equal "\xDE\xAD\xBE\xEF\xBA\xBE\xF0\x0D\0\0\xBA\xAD\xFA\xCE", buf + + assert_equal addr, [buf].pack('p') + end + + def test_unpack_with_block + ret = []; "ABCD".unpack("CCCC") {|v| ret << v } + assert_equal [65, 66, 67, 68], ret + ret = []; "A".unpack("B*") {|v| ret << v.dup } + assert_equal ["01000001"], ret + end + + def test_unpack1 + assert_equal 65, "A".unpack1("C") + assert_equal 68, "ABCD".unpack1("x3C") + assert_equal 0x3042, "\u{3042 3044 3046}".unpack1("U*") + assert_equal "hogefuga", "aG9nZWZ1Z2E=".unpack1("m") + assert_equal "01000001", "A".unpack1("B*") + end + + def test_pack_infection + tainted_array_string = ["123456"] + tainted_array_string.first.taint + ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm', 'P', 'p'].each do |f| + assert_predicate(tainted_array_string.pack(f), :tainted?) + end + end + + def test_unpack_infection + tainted_string = "123456" + tainted_string.taint + ['a', 'A', 'Z', 'B', 'b', 'H', 'h', 'u', 'M', 'm'].each do |f| + assert_predicate(tainted_string.unpack(f).first, :tainted?) + end + end end diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 9ef311b934..b725634a38 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1,4 +1,5 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' require 'stringio' @@ -173,6 +174,7 @@ class TestParse < Test::Unit::TestCase end c = Class.new + c.freeze assert_nothing_raised(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 if false @@ -182,7 +184,6 @@ class TestParse < Test::Unit::TestCase END end - c = Class.new assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 $1 &= 1 @@ -206,9 +207,9 @@ class TestParse < Test::Unit::TestCase END end - assert_raise(SyntaxError) do + assert_nothing_raised(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 - class Foo Bar; end + class Foo 1; end END end end @@ -218,7 +219,6 @@ class TestParse < Test::Unit::TestCase def o.>(x); x; end def o./(x); x; end - a = nil assert_nothing_raised do o.instance_eval <<-END, __FILE__, __LINE__+1 undef >, / @@ -363,7 +363,7 @@ class TestParse < Test::Unit::TestCase def test_dstr_disallowed_variable bug8375 = '[ruby-core:54885] [Bug #8375]' - %w[@ @1 @@. @@ @@1 @@. $ $%].each do |src| + %w[@ @1 @. @@ @@1 @@. $ $%].each do |src| src = '#'+src+' ' str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do break eval('"'+src+'"') @@ -376,6 +376,25 @@ 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") + invalid.each do |name| + assert_syntax_error(name, "`#{name}' is not allowed as #{type} variable name") + end + end + + def test_disallowed_instance_variable + assert_disallowed_variable("an instance", *%w[@ @1 @.]) + end + + def test_disallowed_class_variable + assert_disallowed_variable("a class", *%w[@@ @@1 @@.]) + end + + def test_disallowed_gloal_variable + assert_disallowed_variable("a global", *%w[$ $%]) + end + def test_arg2 o = Object.new @@ -465,21 +484,41 @@ class TestParse < Test::Unit::TestCase end def test_string - assert_raise(SyntaxError) do - eval '"\xg1"' - end + mesg = 'from the backslash through the invalid char' - assert_raise(SyntaxError) do - eval '"\u{1234"' - end + e = assert_syntax_error('"\xg1"', /hex escape/) + assert_equal(' ^', e.message.lines.last, mesg) - assert_raise(SyntaxError) do - eval '"\M1"' - end + e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape') + assert_equal(' ^', e.message.lines.last, mesg) - assert_raise(SyntaxError) do - eval '"\C1"' - end + e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape') + assert_equal(' ^', e.message.lines.last, mesg) + + e = assert_syntax_error('"\u{xxxx', 'Unicode escape') + assert_pattern_list([ + /.*: invalid Unicode escape\n.*\n/, + / \^/, + /\n/, + /.*: unterminated Unicode escape\n.*\n/, + / \^/, + /\n/, + /.*: unterminated string.*\n.*\n/, + / \^/, + ], e.message) + + e = assert_syntax_error('"\M1"', /escape character syntax/) + assert_equal(' ^~~', e.message.lines.last, mesg) + + e = assert_syntax_error('"\C1"', /escape character syntax/) + assert_equal(' ^~~', 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?"')) @@ -493,6 +532,8 @@ class TestParse < Test::Unit::TestCase 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_equal("\u{1234}", eval("?\u{1234}")) assert_equal("\u{1234}", eval('?\u{1234}')) end @@ -516,8 +557,9 @@ class TestParse < Test::Unit::TestCase assert_nothing_raised(SyntaxError, bug) do assert_equal(sym, eval(':"foo\u{0}bar"')) end - assert_raise(SyntaxError) do - eval ':"foo\u{}bar"' + assert_nothing_raised(SyntaxError) do + assert_equal(:foobar, eval(':"foo\u{}bar"')) + assert_equal(:foobar, eval(':"foo\u{ }bar"')) end end @@ -651,10 +693,12 @@ x = __ENCODING__ def test_invalid_instance_variable assert_raise(SyntaxError) { eval('@#') } + assert_raise(SyntaxError) { eval('@') } end def test_invalid_class_variable assert_raise(SyntaxError) { eval('@@1') } + assert_raise(SyntaxError) { eval('@@') } end def test_invalid_char @@ -664,11 +708,13 @@ x = __ENCODING__ assert_in_out_err(%W"-e \x01x", "", [], invalid_char, bug10117) assert_syntax_error("\x01x", invalid_char, bug10117) assert_equal(nil, eval("\x04x")) + assert_equal 1, x end def test_literal_concat x = "baz" assert_equal("foobarbaz", eval('"foo" "bar#{x}"')) + assert_equal("baz", x) end def test_unassignable @@ -700,6 +746,12 @@ x = __ENCODING__ end END end + assert_raise(SyntaxError) do + eval "#{<<~"begin;"}\n#{<<~'end;'}", nil, __FILE__, __LINE__+1 + begin; + x, true + end; + end end def test_block_dup @@ -741,6 +793,7 @@ x = __ENCODING__ 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") @@ -750,6 +803,7 @@ x = __ENCODING__ assert_nil eval("true; nil") assert_nil eval("false; nil") assert_nil eval("defined?(1); nil") + assert_equal 1, x assert_raise(SyntaxError) do eval %q(1; next; 2) @@ -760,7 +814,7 @@ x = __ENCODING__ end def test_assign_in_conditional - assert_raise(SyntaxError) do + assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 (x, y = 1, 2) ? 1 : 2 END @@ -808,6 +862,7 @@ x = __ENCODING__ eval <<-END, nil, __FILE__, __LINE__+1 :"foo#{"x"}baz" ? 1 : 2 END + assert_equal "bar", x end end @@ -819,26 +874,6 @@ x = __ENCODING__ end end - def test_intern - assert_equal(':""', ''.intern.inspect) - assert_equal(':$foo', '$foo'.intern.inspect) - assert_equal(':"!foo"', '!foo'.intern.inspect) - assert_equal(':"foo=="', "foo==".intern.inspect) - end - - def test_all_symbols - x = Symbol.all_symbols - assert_kind_of(Array, x) - assert_empty(x.reject {|s| s.is_a?(Symbol) }) - end - - def test_is_class_id - c = Class.new - assert_raise(NameError) do - c.instance_eval { remove_class_variable(:@var) } - end - end - def test_method_block_location bug5614 = '[ruby-core:40936]' expected = nil @@ -852,4 +887,227 @@ x = __ENCODING__ actual = e.backtrace.first[/\A#{Regexp.quote(__FILE__)}:(\d+):/o, 1].to_i 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}|}")} + end + + def test_unused_variable + o = Object.new + 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")} + end + + def test_named_capture_conflict + a = 1 + assert_warning('') {eval("a = 1; /(?<a>)/ =~ ''")} + a = "\u{3042}" + assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")} + end + + def test_rescue_in_command_assignment + bug = '[ruby-core:75621] [Bug #12402]' + all_assertions(bug) do |a| + a.for("lhs = arg") do + v = bug + v = raise(bug) rescue "ok" + assert_equal("ok", v) + end + a.for("lhs op_asgn arg") do + v = 0 + v += raise(bug) rescue 1 + assert_equal(1, v) + end + a.for("lhs[] op_asgn arg") do + v = [0] + v[0] += raise(bug) rescue 1 + assert_equal([1], v) + end + a.for("lhs.m op_asgn arg") do + k = Struct.new(:m) + v = k.new(0) + v.m += raise(bug) rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs::m op_asgn arg") do + k = Struct.new(:m) + v = k.new(0) + v::m += raise(bug) rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs.C op_asgn arg") do + k = Struct.new(:C) + v = k.new(0) + v.C += raise(bug) rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs::C op_asgn arg") do + v = Class.new + v::C ||= raise(bug) rescue 1 + assert_equal(1, v::C) + end + a.for("lhs = command") do + v = bug + v = raise bug rescue "ok" + assert_equal("ok", v) + end + a.for("lhs op_asgn command") do + v = 0 + v += raise bug rescue 1 + assert_equal(1, v) + end + a.for("lhs[] op_asgn command") do + v = [0] + v[0] += raise bug rescue 1 + assert_equal([1], v) + end + a.for("lhs.m op_asgn command") do + k = Struct.new(:m) + v = k.new(0) + v.m += raise bug rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs::m op_asgn command") do + k = Struct.new(:m) + v = k.new(0) + v::m += raise bug rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs.C op_asgn command") do + k = Struct.new(:C) + v = k.new(0) + v.C += raise bug rescue 1 + assert_equal(k.new(1), v) + end + a.for("lhs::C op_asgn command") do + v = Class.new + v::C ||= raise bug rescue 1 + assert_equal(1, v::C) + end + end + end + + def test_yyerror_at_eol + assert_syntax_error(" 0b", /\^/) + assert_syntax_error(" 0b\n", /\^/) + end + + def test_error_def_in_argument + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + assert_syntax_error("def f r:def d; def f 0end", /unexpected/) + end; + + assert_syntax_error("def\nf(000)end", /^ \^~~/) + assert_syntax_error("def\nf(&)end", /^ \^/) + end + + def test_method_location_in_rescue + bug = '[ruby-core:79388] [Bug #13181]' + obj, line = Object.new, __LINE__+1 + def obj.location + # + raise + rescue + caller_locations(1, 1)[0] + end + + assert_equal(line, obj.location.lineno, bug) + end + + def test_negative_line_number + bug = '[ruby-core:80920] [Bug #13523]' + obj = Object.new + obj.instance_eval("def t(e = false);raise if e; __LINE__;end", "test", -100) + assert_equal(-100, obj.t, bug) + assert_equal(-100, obj.method(:t).source_location[1], bug) + e = assert_raise(RuntimeError) {obj.t(true)} + assert_equal(-100, e.backtrace_locations.first.lineno, bug) + end + + def test_file_in_indented_heredoc + name = '[ruby-core:80987] [Bug #13540]' # long enough to be shared + assert_equal(name+"\n", eval("#{<<-"begin;"}\n#{<<-'end;'}", nil, name)) + begin; + <<~HEREDOC + #{__FILE__} + HEREDOC + end; + end + + def test_unexpected_token_error + assert_raise(SyntaxError) do + eval('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') + end + 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 + end + + def test_truncated_source_line + e = assert_raise_with_message(SyntaxError, /unexpected tIDENTIFIER/) do + eval("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789") + end + 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) + end + + def test_lparenarg + o = Struct.new(:x).new + def o.i(x) + self.x = x + end + o.instance_eval {i (-1.3).abs} + assert_equal(1.3, o.x) + o.instance_eval {i = 0; i (-1.3).abs} + assert_equal(1.3, o.x) + end + + def test_serial_comparison + assert_warning(/comparison '<' after/) do + $VERBOSE = true + x = 1 + eval("if false; 0 < x < 2; end") + end + end + + def test_eof_in_def + assert_raise(SyntaxError) { eval("def m\n\0""end") } + assert_raise(SyntaxError) { eval("def m\n\C-d""end") } + assert_raise(SyntaxError) { eval("def m\n\C-z""end") } + end + + def test_void_value_in_command_rhs + w = "void value expression" + ex = assert_syntax_error("x = return 1", w) + assert_equal(1, ex.message.scan(w).size, "same #{w.inspect} warning should be just once") + end + +=begin + def test_past_scope_variable + assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}} + end +=end end diff --git a/test/ruby/test_path.rb b/test/ruby/test_path.rb index 2db7be0b76..6af4fb6ac0 100644 --- a/test/ruby/test_path.rb +++ b/test/ruby/test_path.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestPath < Test::Unit::TestCase @@ -230,6 +231,9 @@ class TestPath < Test::Unit::TestCase assert_equal('', File.extname('a')) ext = '.rb' assert_equal(ext, File.extname('a.rb')) + assert_equal(ext, File.extname('.a.rb')) + assert_equal(ext, File.extname('a/b/d/test.rb')) + 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 = '' diff --git a/test/ruby/test_pipe.rb b/test/ruby/test_pipe.rb index bcea91bebb..efca8f28c1 100644 --- a/test/ruby/test_pipe.rb +++ b/test/ruby/test_pipe.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require_relative 'ut_eof' diff --git a/test/ruby/test_primitive.rb b/test/ruby/test_primitive.rb index 9d451e2ae0..19af44ad32 100644 --- a/test/ruby/test_primitive.rb +++ b/test/ruby/test_primitive.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestRubyPrimitive < Test::Unit::TestCase @@ -81,7 +82,7 @@ class TestRubyPrimitive < Test::Unit::TestCase end i = 0 while i < 3 - r = A3::B3::C # cache + r = r = A3::B3::C # cache class A3::B3 remove_const :C end diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 06f137fdd1..df9bb5f549 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestProc < Test::Unit::TestCase def setup @@ -205,7 +205,14 @@ class TestProc < Test::Unit::TestCase b = b.binding assert_instance_of(Binding, b, '[ruby-core:25589]') bug10432 = '[ruby-core:65919] [Bug #10432]' - assert_same(self, b.eval("self"), bug10432) + assert_same(self, b.receiver, bug10432) + assert_not_send [b, :local_variable_defined?, :value] + assert_raise(NameError) { + b.local_variable_get(:value) + } + assert_equal 42, b.local_variable_set(:value, 42) + assert_send [b, :local_variable_defined?, :value] + assert_equal 42, b.local_variable_get(:value) end def test_block_given_method @@ -239,48 +246,66 @@ class TestProc < Test::Unit::TestCase assert_equal([false, false], m.call, "#{bug8341} nested without block") end - def test_curry + def test_curry_proc b = proc {|x, y, z| (x||0) + (y||0) + (z||0) } assert_equal(6, b.curry[1][2][3]) assert_equal(6, b.curry[1, 2][3, 4]) assert_equal(6, b.curry(5)[1][2][3][4][5]) assert_equal(6, b.curry(5)[1, 2][3, 4][5]) assert_equal(1, b.curry(1)[1]) + end + def test_curry_proc_splat b = proc {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) } assert_equal(6, b.curry[1][2][3]) assert_equal(10, b.curry[1, 2][3, 4]) assert_equal(15, b.curry(5)[1][2][3][4][5]) assert_equal(15, b.curry(5)[1, 2][3, 4][5]) assert_equal(1, b.curry(1)[1]) + end + def test_curry_lambda b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) } assert_equal(6, b.curry[1][2][3]) assert_raise(ArgumentError) { b.curry[1, 2][3, 4] } assert_raise(ArgumentError) { b.curry(5) } assert_raise(ArgumentError) { b.curry(1) } + end + def test_curry_lambda_splat b = lambda {|x, y, z, *w| (x||0) + (y||0) + (z||0) + w.inject(0, &:+) } assert_equal(6, b.curry[1][2][3]) assert_equal(10, b.curry[1, 2][3, 4]) assert_equal(15, b.curry(5)[1][2][3][4][5]) assert_equal(15, b.curry(5)[1, 2][3, 4][5]) assert_raise(ArgumentError) { b.curry(1) } + end + def test_curry_no_arguments b = proc { :foo } assert_equal(:foo, b.curry[]) + end + def test_curry_given_blocks b = lambda {|x, y, &blk| blk.call(x + y) }.curry b = b.call(2) { raise } b = b.call(3) {|x| x + 4 } assert_equal(9, b) + end + def test_lambda? l = proc {} 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, Proc.new(&l).lambda?) l = lambda {} assert_equal(true, l.lambda?) assert_equal(true, l.curry.lambda?, '[ruby-core:24127]') + assert_equal(true, proc(&l).lambda?) + assert_equal(true, lambda(&l).lambda?) + assert_equal(true, Proc.new(&l).lambda?) end def test_curry_ski_fib @@ -315,16 +340,11 @@ class TestProc < Test::Unit::TestCase assert_equal(fib, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]) end - def test_curry_from_knownbug + def test_curry_passed_block a = lambda {|x, y, &b| b } b = a.curry[1] - assert_equal(:ok, - if b.call(2){} == nil - :ng - else - :ok - end, 'moved from btest/knownbug, [ruby-core:15551]') + assert_not_nil(b.call(2){}, '[ruby-core:15551]: passed block to curried block') end def test_curry_instance_exec @@ -410,6 +430,7 @@ class TestProc < Test::Unit::TestCase t = Thread.new { sleep } assert_raise(ThreadError) { t.instance_eval { initialize { } } } t.kill + t.join end def test_to_proc @@ -425,7 +446,7 @@ class TestProc < Test::Unit::TestCase assert_equal(:noreason, exc.reason) end - def test_binding2 + def test_curry_binding assert_raise(ArgumentError) { proc {}.curry.binding } end @@ -572,7 +593,7 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3], pr.call([1,2,3,4,5,6]) end - def test_proc_args_opt_signle + def test_proc_args_opt_single bug7621 = '[ruby-dev:46801]' pr = proc {|a=:a| a @@ -1099,6 +1120,9 @@ 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 pm0() end @@ -1141,7 +1165,7 @@ class TestProc < Test::Unit::TestCase assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:pmk6).to_proc.parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).to_proc.parameters) - assert_equal([], "".method(:upcase).to_proc.parameters) + assert_equal([], "".method(:empty?).to_proc.parameters) assert_equal([[:rest]], "".method(:gsub).to_proc.parameters) assert_equal([[:rest]], proc {}.curry.parameters) end @@ -1153,6 +1177,8 @@ class TestProc < Test::Unit::TestCase 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 @@line_of_source_location_test = __LINE__ + 1 @@ -1215,7 +1241,10 @@ class TestProc < Test::Unit::TestCase def test_curry_with_trace # bug3751 = '[ruby-core:31871]' set_trace_func(proc {}) - test_curry + methods.grep(/\Atest_curry/) do |test| + next if test == __method__ + __send__(test) + end ensure set_trace_func(nil) end @@ -1253,6 +1282,28 @@ class TestProc < Test::Unit::TestCase binding end + 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} + assert_equal(%i[a b x y], a.sort) + end + + def test_local_variables_nested + b = tap {break binding} + assert_equal(%i[b], b.local_variables, '[ruby-dev:48351] [Bug #10001]') + end + + def local_variables_of(bind) + this_should_not_be_in_bind = this_should_not_be_in_bind = 2 + bind.local_variables + end + + def test_local_variables_in_other_context + feature8773 = '[Feature #8773]' + assert_equal([:feature8773], local_variables_of(binding), feature8773) + end + def test_local_variable_get b = get_binding assert_equal(0, b.local_variable_get(:a)) @@ -1276,9 +1327,53 @@ class TestProc < Test::Unit::TestCase assert_equal(20, b.eval("b")) end + def test_local_variable_set_wb + assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 15) + b = binding + n = 20_000 + + n.times do |i| + v = rand(2_000) + name = "n#{v}" + value = Object.new + b.local_variable_set name, value + end + end; + end + def test_local_variable_defined? b = get_binding assert_equal(true, b.local_variable_defined?(:a)) assert_equal(false, b.local_variable_defined?(:b)) end + + def test_binding_receiver + feature8779 = '[ruby-dev:47613] [Feature #8779]' + + assert_same(self, binding.receiver, feature8779) + + obj = Object.new + def obj.b; binding; end + assert_same(obj, obj.b.receiver, feature8779) + end + + def test_proc_mark + assert_normal_exit(<<-'EOS') + def f + Enumerator.new{ + 100000.times {|i| + yield + s = "#{i}" + } + } + end + + def g + x = proc{} + f(&x) + end + e = g + e.each {} + EOS + end end diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 0a97ba76ec..ba7b0f1177 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -1,7 +1,9 @@ +# coding: utf-8 +# frozen_string_literal: false require 'test/unit' require 'tempfile' require 'timeout' -require_relative 'envutil' +require 'io/wait' require 'rbconfig' class TestProcess < Test::Unit::TestCase @@ -65,8 +67,11 @@ class TestProcess < Test::Unit::TestCase return unless rlimit_exist? with_tmpchdir { write_file 's', <<-"End" - # if limit=0, this test freeze pn OpenBSD - limit = /openbsd/ =~ RUBY_PLATFORM ? 1 : 0 + # Too small RLIMIT_NOFILE, such as zero, causes problems. + # [OpenBSD] Setting to zero freezes this test. + # [GNU/Linux] EINVAL on poll(). EINVAL on ruby's internal poll() ruby with "[ASYNC BUG] thread_timer: select". + pipes = IO.pipe + limit = pipes.map {|io| io.fileno }.min result = 1 begin Process.setrlimit(Process::RLIMIT_NOFILE, limit) @@ -116,11 +121,15 @@ class TestProcess < Test::Unit::TestCase } assert_raise(ArgumentError) { Process.getrlimit(:FOO) } assert_raise(ArgumentError) { Process.getrlimit("FOO") } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") } end def test_rlimit_value return unless rlimit_exist? + assert_raise(ArgumentError) { Process.setrlimit(:FOO, 0) } assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit(:CORE, "\u{30eb 30d3 30fc}") } with_tmpchdir do s = run_in_child(<<-'End') cur, max = Process.getrlimit(:NOFILE) @@ -131,7 +140,7 @@ class TestProcess < Test::Unit::TestCase exit false end End - assert_not_equal(0, s.exitstatus) + assert_not_predicate(s, :success?) s = run_in_child(<<-'End') cur, max = Process.getrlimit(:NOFILE) Process.setrlimit(:NOFILE, [max-10, cur].min) @@ -141,7 +150,7 @@ class TestProcess < Test::Unit::TestCase exit false end End - assert_not_equal(0, s.exitstatus) + assert_not_predicate(s, :success?) end end @@ -172,7 +181,11 @@ class TestProcess < Test::Unit::TestCase io.close assert_raise(ArgumentError) { system(*TRUECOMMAND, :pgroup=>-1) } - assert_raise(Errno::EPERM) { Process.wait spawn(*TRUECOMMAND, :pgroup=>2) } + IO.popen([RUBY, '-egets'], 'w') do |f| + assert_raise(Errno::EPERM) { + Process.wait spawn(*TRUECOMMAND, :pgroup=>f.pid) + } + end io1 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true]) io2 = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>io1.pid]) @@ -229,6 +242,22 @@ class TestProcess < Test::Unit::TestCase :rlimit_core=>n, :rlimit_cpu=>3600]) {|io| assert_equal("[#{n}, #{n}]\n[3600, 3600]", io.read.chomp) } + + 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 + end; + + assert_raise(ArgumentError, /rlimit_cpu/) { + system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) + } end MANDATORY_ENVS = %w[RUBYLIB] @@ -307,6 +336,16 @@ class TestProcess < Test::Unit::TestCase end end + def test_execopt_env_path + bug8004 = '[ruby-core:53103] [Bug #8004]' + Dir.mktmpdir do |d| + open("#{d}/tmp_script.cmd", "w") {|f| f.puts ": ;"; f.chmod(0755)} + assert_not_nil(pid = Process.spawn({"PATH" => d}, "tmp_script.cmd"), bug8004) + wpid, st = Process.waitpid2(pid) + assert_equal([pid, true], [wpid, st.success?], bug8004) + end + end + def _test_execopts_env_popen(cmd) message = cmd.inspect IO.popen({"FOO"=>"BAR"}, cmd) {|io| @@ -397,9 +436,13 @@ class TestProcess < Test::Unit::TestCase IO.popen([*PWD, :chdir => d]) {|io| assert_equal(d, io.read.chomp) } - assert_raise(Errno::ENOENT) { + assert_raise_with_message(Errno::ENOENT, %r"d/notexist") { Process.wait Process.spawn(*PWD, :chdir => "d/notexist") } + n = "d/\u{1F37A}" + assert_raise_with_message(Errno::ENOENT, /#{n}/) { + Process.wait Process.spawn(*PWD, :chdir => n) + } } end @@ -416,13 +459,32 @@ class TestProcess < Test::Unit::TestCase def test_execopts_open_chdir_m17n_path with_tmpchdir {|d| Dir.mkdir "テスト" - system(*PWD, :chdir => "テスト", :out => "open_chdir_テスト") + (pwd = PWD.dup).insert(1, '-EUTF-8:UTF-8') + system(*pwd, :chdir => "テスト", :out => "open_chdir_テスト") assert_file.exist?("open_chdir_テスト") assert_file.not_exist?("テスト/open_chdir_テスト") - assert_equal("#{d}/テスト", File.read("open_chdir_テスト").chomp.encode(__ENCODING__)) + assert_equal("#{d}/テスト", File.read("open_chdir_テスト", encoding: "UTF-8").chomp) } end if windows? || Encoding.find('locale') == Encoding::UTF_8 + def test_execopts_open_failure + with_tmpchdir {|d| + assert_raise_with_message(Errno::ENOENT, %r"d/notexist") { + Process.wait Process.spawn(*PWD, :in => "d/notexist") + } + assert_raise_with_message(Errno::ENOENT, %r"d/notexist") { + Process.wait Process.spawn(*PWD, :out => "d/notexist") + } + n = "d/\u{1F37A}" + assert_raise_with_message(Errno::ENOENT, /#{n}/) { + Process.wait Process.spawn(*PWD, :in => n) + } + assert_raise_with_message(Errno::ENOENT, /#{n}/) { + Process.wait Process.spawn(*PWD, :out => n) + } + } + end + UMASK = [RUBY, '-e', 'printf "%04o\n", File.umask'] def test_execopts_umask @@ -464,7 +526,7 @@ class TestProcess < Test::Unit::TestCase SORT = [RUBY, '-e', "puts ARGF.readlines.sort"] CAT = [RUBY, '-e', "IO.copy_stream STDIN, STDOUT"] - def test_execopts_redirect + def test_execopts_redirect_fd with_tmpchdir {|d| Process.wait Process.spawn(*ECHO["a"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]) assert_equal("a", File.read("out").chomp) @@ -536,76 +598,169 @@ class TestProcess < Test::Unit::TestCase assert_equal("bb\naa\n", File.read("out")) system(*SORT, STDIN=>["out"], STDOUT=>"out2") assert_equal("aa\nbb\n", File.read("out2")) + } + end - with_pipe {|r1, w1| - with_pipe {|r2, w2| - opts = {STDIN=>r1, STDOUT=>w2} - opts.merge(w1=>:close, r2=>:close) unless windows? - pid = spawn(*SORT, opts) - r1.close - w2.close - w1.puts "c" - w1.puts "a" - w1.puts "b" - w1.close - assert_equal("a\nb\nc\n", r2.read) - r2.close - Process.wait(pid) - } + def test_execopts_redirect_open_order_normal + minfd = 3 + maxfd = 20 + with_tmpchdir {|d| + opts = {} + minfd.upto(maxfd) {|fd| opts[fd] = ["out#{fd}", "w"] } + system RUBY, "-e", "#{minfd}.upto(#{maxfd}) {|fd| IO.new(fd).print fd.to_s }", opts + minfd.upto(maxfd) {|fd| assert_equal(fd.to_s, File.read("out#{fd}")) } + } + end unless windows? # passing non-stdio fds is not supported on Windows + + def test_execopts_redirect_open_order_reverse + minfd = 3 + maxfd = 20 + with_tmpchdir {|d| + opts = {} + maxfd.downto(minfd) {|fd| opts[fd] = ["out#{fd}", "w"] } + system RUBY, "-e", "#{minfd}.upto(#{maxfd}) {|fd| IO.new(fd).print fd.to_s }", opts + minfd.upto(maxfd) {|fd| assert_equal(fd.to_s, File.read("out#{fd}")) } + } + end unless windows? # passing non-stdio fds is not supported on Windows + + def test_execopts_redirect_open_fifo + with_tmpchdir {|d| + begin + File.mkfifo("fifo") + rescue NotImplementedError + return + end + assert(FileTest.pipe?("fifo"), "should be pipe") + t1 = Thread.new { + system(*ECHO["output to fifo"], :out=>"fifo") + } + t2 = Thread.new { + IO.popen([*CAT, :in=>"fifo"]) {|f| f.read } } + _, v2 = assert_join_threads([t1, t2]) + assert_equal("output to fifo\n", v2) + } + end unless windows? # does not support fifo - unless windows? - # passing non-stdio fds is not supported on Windows - with_pipes(5) {|pipes| - ios = pipes.flatten - h = {} - ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] } - h2 = h.invert - _rios = pipes.map {|r, w| r } - wios = pipes.map {|r, w| w } - child_wfds = wios.map {|w| h2[w].fileno } - pid = spawn(RUBY, "-e", - "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h) - pipes.each {|r, w| - assert_equal("#{h2[w].fileno}\n", r.gets) - } - Process.wait pid; - } + def test_execopts_redirect_open_fifo_interrupt_raise + with_tmpchdir {|d| + begin + File.mkfifo("fifo") + rescue NotImplementedError + return + end + IO.popen([RUBY, '-e', <<-'EOS']) {|io| + class E < StandardError; end + trap(:USR1) { raise E } + begin + puts "start" + STDOUT.flush + system("cat", :in => "fifo") + rescue E + puts "ok" + end + EOS + assert_equal("start\n", io.gets) + sleep 0.5 + Process.kill(:USR1, io.pid) + assert_equal("ok\n", io.read) + } + } + end unless windows? # does not support fifo - with_pipes(5) {|pipes| - ios = pipes.flatten - h = {} - ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] } - h2 = h.invert - _rios = pipes.map {|r, w| r } - wios = pipes.map {|r, w| w } - child_wfds = wios.map {|w| h2[w].fileno } - pid = spawn(RUBY, "-e", - "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h) - pipes.each {|r, w| - assert_equal("#{h2[w].fileno}\n", r.gets) - } - Process.wait pid - } + def test_execopts_redirect_open_fifo_interrupt_print + with_tmpchdir {|d| + begin + File.mkfifo("fifo") + rescue NotImplementedError + return + end + IO.popen([RUBY, '-e', <<-'EOS']) {|io| + trap(:USR1) { print "trap\n" } + system("cat", :in => "fifo") + EOS + sleep 1 + Process.kill(:USR1, io.pid) + sleep 1 + File.write("fifo", "ok\n") + assert_equal("trap\nok\n", io.read) + } + } + end unless windows? # does not support fifo + + def test_execopts_redirect_pipe + with_pipe {|r1, w1| + with_pipe {|r2, w2| + opts = {STDIN=>r1, STDOUT=>w2} + opts.merge(w1=>:close, r2=>:close) unless windows? + pid = spawn(*SORT, opts) + r1.close + w2.close + w1.puts "c" + w1.puts "a" + w1.puts "b" + w1.close + assert_equal("a\nb\nc\n", r2.read) + r2.close + Process.wait(pid) + } + } - closed_fd = nil - with_pipes(5) {|pipes| - io = pipes.last.last - closed_fd = io.fileno + unless windows? + # passing non-stdio fds is not supported on Windows + with_pipes(5) {|pipes| + ios = pipes.flatten + h = {} + ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] } + h2 = h.invert + _rios = pipes.map {|r, w| r } + wios = pipes.map {|r, w| w } + child_wfds = wios.map {|w| h2[w].fileno } + pid = spawn(RUBY, "-e", + "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h) + pipes.each {|r, w| + assert_equal("#{h2[w].fileno}\n", r.gets) } - assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) } - - with_pipe {|r, w| - if w.respond_to?(:"close_on_exec=") - w.close_on_exec = true - pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w) - w.close - assert_equal("a", r.read) - Process.wait pid - end + Process.wait pid; + } + + with_pipes(5) {|pipes| + ios = pipes.flatten + h = {} + ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] } + h2 = h.invert + _rios = pipes.map {|r, w| r } + wios = pipes.map {|r, w| w } + child_wfds = wios.map {|w| h2[w].fileno } + pid = spawn(RUBY, "-e", + "[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h) + pipes.each {|r, w| + assert_equal("#{h2[w].fileno}\n", r.gets) } - end + Process.wait pid + } + + closed_fd = nil + with_pipes(5) {|pipes| + io = pipes.last.last + closed_fd = io.fileno + } + assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) } + + with_pipe {|r, w| + if w.respond_to?(:"close_on_exec=") + w.close_on_exec = true + pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w) + w.close + assert_equal("a", r.read) + Process.wait pid + end + } + end + end + def test_execopts_redirect_symbol + with_tmpchdir {|d| system(*ECHO["funya"], :out=>"out") assert_equal("funya\n", File.read("out")) system(RUBY, '-e', 'STDOUT.reopen(STDERR); puts "henya"', :err=>"out") @@ -626,6 +781,17 @@ class TestProcess < Test::Unit::TestCase } end + def test_execopts_redirect_to_out_and_err + with_tmpchdir {|d| + ret = system(RUBY, "-e", 'STDERR.print "e"; STDOUT.print "o"', [:out, :err] => "foo") + assert_equal(true, ret) + assert_equal("eo", File.read("foo")) + ret = system(RUBY, "-e", 'STDERR.print "E"; STDOUT.print "O"', [:err, :out] => "bar") + assert_equal(true, ret) + assert_equal("EO", File.read("bar")) + } + end + def test_execopts_redirect_dup2_child with_tmpchdir {|d| Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", @@ -670,6 +836,11 @@ class TestProcess < Test::Unit::TestCase IO.popen("#{RUBY} -e 'puts :foo'") {|io| assert_equal("foo\n", io.read) } assert_raise(Errno::ENOENT) { IO.popen(["echo bar"]) {} } # assuming "echo bar" command not exist. IO.popen(ECHO["baz"]) {|io| assert_equal("baz\n", io.read) } + } + end + + def test_execopts_popen_stdio + with_tmpchdir {|d| assert_raise(ArgumentError) { IO.popen([*ECHO["qux"], STDOUT=>STDOUT]) {|io| } } @@ -679,7 +850,12 @@ class TestProcess < Test::Unit::TestCase assert_raise(ArgumentError) { IO.popen([*ECHO["fuga"], STDOUT=>"out"]) {|io| } } - skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + } + end + + def test_execopts_popen_extra_fd + skip "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| assert_equal("b\n", io.read) @@ -735,11 +911,14 @@ class TestProcess < Test::Unit::TestCase } with_pipe {|r, w| io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')"]) - w.close - errmsg = io.read - assert_equal("", r.read) - assert_not_equal("", errmsg) - Process.wait + begin + w.close + errmsg = io.read + assert_equal("", r.read) + assert_not_equal("", errmsg) + ensure + io.close + end } with_pipe {|r, w| errmsg = `#{RUBY} -e "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts(123)"` @@ -787,29 +966,38 @@ class TestProcess < Test::Unit::TestCase } with_pipe {|r, w| io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')", :close_others=>true]) - w.close - errmsg = io.read - assert_equal("", r.read) - assert_not_equal("", errmsg) - Process.wait + begin + w.close + errmsg = io.read + assert_equal("", r.read) + assert_not_equal("", errmsg) + ensure + io.close + end } with_pipe {|r, w| w.close_on_exec = false io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>false]) - w.close - errmsg = io.read - assert_equal("mo\n", r.read) - assert_equal("", errmsg) - Process.wait + begin + w.close + errmsg = io.read + assert_equal("mo\n", r.read) + assert_equal("", errmsg) + ensure + io.close + end } with_pipe {|r, w| w.close_on_exec = false io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('mo')", :close_others=>nil]) - w.close - errmsg = io.read - assert_equal("mo\n", r.read) - assert_equal("", errmsg) - Process.wait + begin + w.close + errmsg = io.read + assert_equal("mo\n", r.read) + assert_equal("", errmsg) + ensure + io.close + end } } @@ -828,7 +1016,7 @@ class TestProcess < Test::Unit::TestCase rescue NotImplementedError skip "IO#close_on_exec= is not supported" end - end + end unless windows? # passing non-stdio fds is not supported on Windows def test_execopts_redirect_tempfile bug6269 = '[ruby-core:44181]' @@ -1133,8 +1321,7 @@ class TestProcess < Test::Unit::TestCase Process.wait spawn([RUBY, "poiu"], "-e", "exit 4") assert_equal(4, $?.exitstatus) - assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]).read) - Process.wait + assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read }) write_file("s", <<-"End") exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5") @@ -1186,6 +1373,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") @@ -1223,25 +1418,12 @@ class TestProcess < Test::Unit::TestCase return unless Signal.list.include?("QUIT") with_tmpchdir do - write_file("foo", "puts;STDOUT.flush;sleep 30") - pid = nil - IO.pipe do |r, w| - pid = spawn(RUBY, "foo", out: w) - w.close - Thread.new { r.read(1); Process.kill(:SIGQUIT, pid) } - Process.wait(pid) - end - t = Time.now - s = $? + s = assert_in_out_err([], "Signal.trap(:QUIT,'DEFAULT'); Process.kill(:SIGQUIT, $$);sleep 30", //, //, rlimit_core: 0) assert_equal([false, true, false, nil], [s.exited?, s.signaled?, s.stopped?, s.success?], "[s.exited?, s.signaled?, s.stopped?, s.success?]") - assert_send( - [["#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>", - "#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig }) (core dumped)>"], - :include?, - s.inspect]) - EnvUtil.diagnostic_reports("QUIT", RUBY, pid, t) + assert_equal("#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>", + s.inspect.sub(/ \(core dumped\)(?=>\z)/, '')) end end @@ -1338,8 +1520,14 @@ 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) + 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 @@ -1401,7 +1589,10 @@ class TestProcess < Test::Unit::TestCase pid = nil IO.pipe do |r, w| pid = fork { r.read(1); exit } - Thread.start { raise } + Thread.start { + Thread.current.report_on_exception = false + raise + } w.puts end Process.wait pid @@ -1458,6 +1649,9 @@ class TestProcess < Test::Unit::TestCase end def test_aspawn_too_long_path + if /solaris/i =~ RUBY_PLATFORM && !defined?(Process::RLIMIT_NPROC) + skip "Too exhaustive test on platforms without Process::RLIMIT_NPROC such as Solaris 10" + end bug4315 = '[ruby-core:34833] #7904 [ruby-core:52628] #11613' assert_fail_too_long_path(%w"echo |", bug4315) end @@ -1497,7 +1691,7 @@ class TestProcess < Test::Unit::TestCase with_tmpchdir do assert_nothing_raised('[ruby-dev:12261]') do - timeout(3) do + Timeout.timeout(3) do pid = spawn('yes | ls') Process.waitpid pid end @@ -1589,6 +1783,35 @@ class TestProcess < Test::Unit::TestCase } end + def test_popen_exit + bug11510 = '[ruby-core:70671] [Bug #11510]' + pid = nil + opt = {timeout: 10, stdout_filter: ->(s) {pid = s}} + if windows? + opt[:new_pgroup] = true + else + opt[:pgroup] = true + end + assert_ruby_status(["-", RUBY], <<-'end;', bug11510, **opt) + RUBY = ARGV[0] + th = Thread.start { + Thread.current.abort_on_exception = true + IO.popen([RUBY, "-esleep 15", err: [:child, :out]]) {|f| + STDOUT.puts f.pid + STDOUT.flush + sleep(2) + } + } + sleep(0.001) until th.stop? + end; + assert_match(/\A\d+\Z/, pid) + ensure + if pid + pid = pid.to_i + [:TERM, :KILL].each {|sig| Process.kill(sig, pid) rescue break} + end + end + def test_execopts_new_pgroup return unless windows? @@ -1741,6 +1964,27 @@ EOS end end if windows? + def test_exec_nonascii + bug12841 = '[ruby-dev:49838] [Bug #12841]' + + [ + "\u{7d05 7389}", + "zuf\u{00E4}llige_\u{017E}lu\u{0165}ou\u{010D}k\u{00FD}_\u{10D2 10D0 10DB 10D4 10DD 10E0 10D4 10D1}_\u{0440 0430 0437 043B 043E 0433 0430}_\u{548C 65B0 52A0 5761 4EE5 53CA 4E1C}", + "c\u{1EE7}a", + ].each do |arg| + begin + arg = arg.encode(Encoding.find("locale")) + rescue + else + assert_in_out_err([], "#{<<-"begin;"}\n#{<<-"end;"}", [arg], [], bug12841) + begin; + arg = "#{arg.b}".force_encoding("#{arg.encoding.name}") + exec(ENV["COMSPEC"]||"cmd.exe", "/c", "echo", arg) + end; + end + end + end if windows? + def test_clock_gettime t1 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) t2 = Time.now; t2 = t2.tv_sec * 1000000000 + t2.tv_nsec @@ -1845,6 +2089,8 @@ EOS def test_clock_getres r = Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond) + rescue Errno::EINVAL + else assert_kind_of(Integer, r) assert_raise(Errno::EINVAL) { Process.clock_getres(:foo) } end @@ -1928,4 +2174,203 @@ EOS assert_kind_of(Float, t, "Process.clock_getres(:#{n})") end + def test_deadlock_by_signal_at_forking + assert_separately(["-", RUBY], <<-INPUT, timeout: 80) + ruby = ARGV.shift + GC.start # reduce garbage + GC.disable # avoid triggering CoW after forks + trap(:QUIT) {} + parent = $$ + 100.times do |i| + pid = fork {Process.kill(:QUIT, parent)} + IO.popen(ruby, 'r+'){} + Process.wait(pid) + $stdout.puts + $stdout.flush + end + INPUT + end if defined?(fork) + + def test_process_detach + pid = fork {} + th = Process.detach(pid) + assert_equal pid, th.pid + status = th.value + assert status.success?, status.inspect + end if defined?(fork) + + def test_kill_at_spawn_failure + bug11166 = '[ruby-core:69304] [Bug #11166]' + th = nil + x = with_tmpchdir {|d| + prog = "#{d}/notexist" + th = Thread.start {system(prog);sleep} + th.kill + th.join(0.1) + } + assert_equal(th, x, bug11166) + end if defined?(fork) + + def test_exec_fd_3_redirect + # ensure we can redirect anything to fd=3 in a child process. + # fd=3 is a commonly reserved FD for the timer thread pipe in the + # parent, but fd=3 is the first FD used by the sd_listen_fds function + # for systemd + assert_separately(['-', RUBY], <<-INPUT, timeout: 60) + ruby = ARGV.shift + begin + a = IO.pipe + b = IO.pipe + pid = fork do + exec ruby, '-e', 'print IO.for_fd(3).read(1)', 3 => a[0], 1 => b[1] + end + b[1].close + a[0].close + a[1].write('.') + assert_equal ".", b[0].read(1) + ensure + Process.wait(pid) if pid + a.each(&:close) if a + b.each(&:close) if b + end + INPUT + end if defined?(fork) + + def test_exec_close_reserved_fd + cmd = ".#{File::ALT_SEPARATOR || File::SEPARATOR}bug11353" + with_tmpchdir { + (3..6).each do |i| + ret = run_in_child(<<-INPUT) + begin + $VERBOSE = nil + Process.exec('#{cmd}', 'dummy', #{i} => :close) + rescue SystemCallError + end + INPUT + assert_equal(0, ret) + end + } + end + + def test_signals_work_after_exec_fail + r, w = IO.pipe + pid = status = nil + Timeout.timeout(30) do + pid = fork do + r.close + begin + trap(:USR1) { w.syswrite("USR1\n"); exit 0 } + exec "/path/to/non/existent/#$$/#{rand}.ex" + rescue SystemCallError + w.syswrite("exec failed\n") + end + sleep + exit 1 + end + w.close + assert_equal "exec failed\n", r.gets + Process.kill(:USR1, pid) + assert_equal "USR1\n", r.gets + assert_nil r.gets + _, status = Process.waitpid2(pid) + end + assert_predicate status, :success? + rescue Timeout::Error + begin + Process.kill(:KILL, pid) + rescue Errno::ESRCH + end + raise + ensure + w.close if w + r.close if r + end if defined?(fork) + + def test_threading_works_after_exec_fail + r, w = IO.pipe + pid = status = nil + Timeout.timeout(90) do + pid = fork do + r.close + begin + exec "/path/to/non/existent/#$$/#{rand}.ex" + rescue SystemCallError + w.syswrite("exec failed\n") + end + q = Queue.new + run = true + 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 + 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) + assert_operator vals[0], :>, vals[1], vals.inspect + _, status = Process.waitpid2(pid) + end + assert_predicate status, :success? + rescue Timeout::Error + begin + Process.kill(:KILL, pid) + rescue Errno::ESRCH + end + raise + ensure + w.close if w + r.close if r + end if defined?(fork) + + def test_many_args + bug11418 = '[ruby-core:70251] [Bug #11418]' + assert_in_out_err([], <<-"end;", ["x"]*256, [], bug11418, timeout: 60) + bin = "#{EnvUtil.rubybin}" + args = Array.new(256) {"x"} + GC.stress = true + system(bin, "--disable=gems", "-w", "-e", "puts ARGV", *args) + end; + end + + def test_to_hash_on_arguments + all_assertions do |a| + %w[Array String].each do |type| + a.for(type) do + assert_separately(['-', EnvUtil.rubybin], <<~"END;") + class #{type} + def to_hash + raise "[Bug-12355]: #{type}#to_hash is called" + end + end + ex = ARGV[0] + assert_equal(true, system([ex, ex], "-e", "")) + END; + end + end + end + end + + def test_forked_child_handles_signal + skip "fork not supported" unless Process.respond_to?(:fork) + assert_normal_exit(<<-"end;", '[ruby-core:82883] [Bug #13916]') + require 'timeout' + pid = fork { sleep } + Process.kill(:TERM, pid) + assert_equal pid, Timeout.timeout(30) { Process.wait(pid) } + end; + end + + if Process.respond_to?(:initgroups) + def test_initgroups + assert_raise(ArgumentError) do + Process.initgroups("\0", 0) + end + end + end + + def test_last_status + Process.wait spawn(RUBY, "-e", "exit 13") + assert_same(Process.last_status, $?) + end end diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index 7b68bc38d0..882d33fb40 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestRand < Test::Unit::TestCase def assert_random_int(ws, m, init = 0) @@ -210,7 +210,8 @@ class TestRand < Test::Unit::TestCase assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0..-1) } assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0.0...0.0) } assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0.0...-0.1) } - assert_raise(ArgumentError, bug3027 = '[ruby-core:29075]') { r.rand(nil) } + bug3027 = '[ruby-core:29075]' + assert_raise(ArgumentError, bug3027) { r.rand(nil) } end def test_random_seed @@ -420,7 +421,7 @@ END (1..10).to_a.shuffle raise 'default seed is not set' if srand == 0 end - p2, st = Process.waitpid2(pid) + _, st = Process.waitpid2(pid) assert_predicate(st, :success?, "#{st.inspect}") rescue NotImplementedError, ArgumentError end @@ -428,9 +429,20 @@ END def assert_fork_status(n, mesg, &block) IO.pipe do |r, w| (1..n).map do - p1 = fork {w.puts(block.call.to_s)} - _, st = Process.waitpid2(p1) - assert_send([st, :success?], mesg) + st = desc = nil + IO.pipe do |re, we| + p1 = fork { + re.close + STDERR.reopen(we) + w.puts(block.call.to_s) + } + we.close + err = Thread.start {re.read} + _, st = Process.waitpid2(p1) + desc = FailDesc[st, mesg, err.value] + end + assert(!st.signaled?, desc) + assert(st.success?, mesg) r.gets.strip end end @@ -452,6 +464,10 @@ END assert_fork_status(1, bug5661) {stable.rand(4)} r1, r2 = *assert_fork_status(2, bug5661) {stable.rand} assert_equal(r1, r2, bug5661) + + assert_fork_status(1, '[ruby-core:82100] [Bug #13753]') do + Random::DEFAULT.rand(4) + end rescue NotImplementedError end @@ -491,7 +507,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 @@ -500,7 +516,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 @@ -524,4 +540,48 @@ END [1, 2].sample(1, random: gen) assert_equal(2, gen.limit, bug7935) end + + def test_random_ulong_limited_no_rand + c = Class.new do + undef rand + def bytes(n) + "\0"*n + end + end + gen = c.new.extend(Random::Formatter) + assert_equal(1, [1, 2].sample(random: gen)) + end + + def test_default_seed + assert_separately([], <<-End) + seed = Random::DEFAULT::seed + rand1 = Random::DEFAULT::rand + rand2 = Random.new(seed).rand + assert_equal(rand1, rand2) + + srand seed + rand3 = rand + assert_equal(rand1, rand3) + End + end + + def test_urandom + [0, 1, 100].each do |size| + v = Random.urandom(size) + assert_kind_of(String, v) + assert_equal(size, v.bytesize) + end + end + + def test_new_seed + size = 0 + n = 8 + n.times do + v = Random.new_seed + assert_kind_of(Integer, v) + size += v.size + end + # probability of failure <= 1/256**8 + assert_operator(size.fdiv(n), :>, 15) + end end diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index 6afa0ea565..da883767bc 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -1,10 +1,30 @@ +# frozen_string_literal: false require 'test/unit' require 'delegate' require 'timeout' require 'bigdecimal' -require_relative 'envutil' class TestRange < Test::Unit::TestCase + def test_new + assert_equal((0..2), Range.new(0, 2)) + assert_equal((0..2), Range.new(0, 2, false)) + assert_equal((0...2), Range.new(0, 2, true)) + + assert_raise(ArgumentError) { (1.."3") } + + obj = Object.new + def obj.<=>(other) + raise RuntimeError, "cmp" + end + assert_raise_with_message(RuntimeError, "cmp") { (obj..3) } + end + + def test_frozen_initialize + r = Range.allocate + r.freeze + assert_raise(FrozenError){r.__send__(:initialize, 1, 2)} + end + def test_range_string # XXX: Is this really the test of Range? assert_equal([], ("a" ... "a").to_a) @@ -60,6 +80,9 @@ class TestRange < Test::Unit::TestCase 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)) end def test_max @@ -77,6 +100,9 @@ class TestRange < Test::Unit::TestCase assert_equal(0, (0..0).max) assert_equal(nil, (0...0).max) + + assert_equal([10,9,8], (0..10).max(3)) + assert_equal([9,8,7], (0...10).max(3)) end def test_initialize_twice @@ -92,6 +118,16 @@ class TestRange < Test::Unit::TestCase assert_nothing_raised { r.instance_eval { initialize 5, 6} } end + def test_marshal + r = 1..2 + 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)} + end + def test_bad_value assert_raise(ArgumentError) { (1 .. :a) } end @@ -126,7 +162,10 @@ class TestRange < Test::Unit::TestCase end def test_hash - assert_kind_of(Fixnum, (0..1).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_kind_of(String, (0..1).hash.to_s) end def test_step @@ -246,6 +285,7 @@ class TestRange < Test::Unit::TestCase def test_begin_end assert_equal(0, (0..1).begin) assert_equal(1, (0..1).end) + assert_equal(1, (0...1).end) end def test_first_last @@ -260,7 +300,10 @@ class TestRange < Test::Unit::TestCase assert_equal([0, 1, 2], (0...10).first(3)) assert_equal([7, 8, 9], (0...10).last(3)) assert_equal(0, (0...10).first) + assert_equal(10, (0...10).last) assert_equal("a", ("a"..."c").first) + assert_equal("c", ("a"..."c").last) + assert_equal(0, (2...0).last) end def test_to_s @@ -296,6 +339,30 @@ class TestRange < Test::Unit::TestCase } end + def test_eqq_non_linear + bug12003 = '[ruby-core:72908] [Bug #12003]' + c = Class.new { + attr_reader :value + + def initialize(value) + @value = value + end + + def succ + self.class.new(@value.succ) + end + + def ==(other) + @value == other.value + end + + def <=>(other) + @value <=> other.value + end + } + assert_operator(c.new(0)..c.new(10), :===, c.new(5), bug12003) + end + def test_include assert_include("a".."z", "c") assert_not_include("a".."z", "5") @@ -321,12 +388,15 @@ class TestRange < Test::Unit::TestCase assert_raise(TypeError) { [][o] } class << o; attr_accessor :end end o.end = 0 - assert_raise(NoMethodError) { [][o] } + assert_raise(TypeError) { [][o] } def o.exclude_end=(v) @exclude_end = v end def o.exclude_end?() @exclude_end end o.exclude_end = false assert_nil([0][o]) assert_raise(RangeError) { [0][o] = 1 } + class << o + private :begin, :end + end o.begin = 10 o.end = 10 assert_nil([0][o]) @@ -391,6 +461,10 @@ class TestRange < Test::Unit::TestCase assert_raise(TypeError) do (1..42).bsearch{ "not ok" } end + c = eval("class C\u{309a 26a1 26c4 1f300};self;end") + assert_raise_with_message(TypeError, /C\u{309a 26a1 26c4 1f300}/) do + (1..42).bsearch {c.new} + end assert_equal (1..42).bsearch{}, (1..42).bsearch{false} end @@ -475,20 +549,22 @@ class TestRange < Test::Unit::TestCase assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x }) end - def check_bsearch_values(range, search) + def check_bsearch_values(range, search, a) from, to = range.begin, range.end cmp = range.exclude_end? ? :< : :<= + r = nil - # (0) trivial test - r = Range.new(to, from, range.exclude_end?).bsearch do |x| - fail "#{to}, #{from}, #{range.exclude_end?}, #{x}" - end - assert_equal nil, r + a.for "(0) trivial test" do + r = Range.new(to, from, range.exclude_end?).bsearch do |x| + fail "#{to}, #{from}, #{range.exclude_end?}, #{x}" + end + assert_nil r - r = (to...to).bsearch do - fail + r = (to...to).bsearch do + fail + end + assert_nil r end - assert_equal nil, r # prepare for others yielded = [] @@ -497,46 +573,53 @@ class TestRange < Test::Unit::TestCase val >= search end - # (1) log test - max = case from - when Float then 65 - when Integer then Math.log(to-from+(range.exclude_end? ? 0 : 1), 2).to_i + 1 - end - assert_operator yielded.size, :<=, max - - # (2) coverage test - expect = if search < from - from - elsif search.send(cmp, to) - search - else - nil - end - assert_equal expect, r - - # (3) uniqueness test - assert_equal nil, yielded.uniq! - - # (4) end of range test - case - when range.exclude_end? - assert_not_include yielded, to - assert_not_equal r, to - when search >= to - assert_include yielded, to - assert_equal search == to ? to : nil, r + a.for "(1) log test" do + max = case from + when Float then 65 + when Integer then Math.log(to-from+(range.exclude_end? ? 0 : 1), 2).to_i + 1 + end + assert_operator yielded.size, :<=, max end - # start of range test - if search <= from - assert_include yielded, from - assert_equal from, r + a.for "(2) coverage test" do + expect = case + when search < from + from + when search.send(cmp, to) + search + else + nil + end + assert_equal expect, r end - # (5) out of range test - yielded.each do |val| - assert_operator from, :<=, val - assert_send [val, cmp, to] + a.for "(3) uniqueness test" do + assert_nil yielded.uniq! + end + + a.for "(4) end of range test" do + case + when range.exclude_end? + assert_not_include yielded, to + assert_not_equal r, to + when search >= to + assert_include yielded, to + assert_equal search == to ? to : nil, r + end + end + + a.for "(5) start of range test" do + if search <= from + assert_include yielded, from + assert_equal from, r + end + end + + a.for "(6) out of range test" do + yielded.each do |val| + assert_operator from, :<=, val + assert_send [val, cmp, to] + end end end @@ -544,10 +627,12 @@ class TestRange < Test::Unit::TestCase ints = [-1 << 100, -123456789, -42, -1, 0, 1, 42, 123456789, 1 << 100] floats = [-Float::INFINITY, -Float::MAX, -42.0, -4.2, -Float::EPSILON, -Float::MIN, 0.0, Float::MIN, Float::EPSILON, Math::PI, 4.2, 42.0, Float::MAX, Float::INFINITY] - [ints, floats].each do |values| - values.combination(2).to_a.product(values).each do |(from, to), search| - check_bsearch_values(from..to, search) - check_bsearch_values(from...to, search) + all_assertions do |a| + [ints, floats].each do |values| + values.combination(2).to_a.product(values).each do |(from, to), search| + check_bsearch_values(from..to, search, a) + check_bsearch_values(from...to, search, a) + end end end end @@ -567,17 +652,6 @@ class TestRange < Test::Unit::TestCase assert_raise(TypeError) { ("a".."z").bsearch {} } end - def test_bsearch_with_mathn - assert_separately ['-r', 'mathn'], %q{ - msg = '[ruby-core:25740]' - answer = (1..(1 << 100)).bsearch{|x| - assert_predicate(x, :integer?, msg) - x >= 42 - } - assert_equal(42, answer, msg) - } - end - def test_each_no_blockarg a = "a" def a.upto(x, e, &b) diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index 67f895abc6..2152486742 100644 --- a/test/ruby/test_rational.rb +++ b/test/ruby/test_rational.rb @@ -1,41 +1,29 @@ +# frozen_string_literal: false require 'test/unit' class RationalSub < Rational; end class Rational_Test < Test::Unit::TestCase - def setup - @complex = defined?(Complex) - if @complex - @keiju = Complex.instance_variables.include?(:@RCS_ID) - end - seps = [File::SEPARATOR, File::ALT_SEPARATOR].compact.map{|x| Regexp.escape(x)}.join("|") - @unify = $".grep(/(?:^|#{seps})mathn(?:\.(?:rb|so))?/).size != 0 - end - def test_ratsub c = RationalSub.__send__(:convert, 1) assert_kind_of(Numeric, c) - if @unify - assert_instance_of(Fixnum, c) - else - assert_instance_of(RationalSub, c) + assert_instance_of(RationalSub, c) - c2 = c + 1 - assert_instance_of(RationalSub, c2) - c2 = c - 1 - assert_instance_of(RationalSub, c2) + c2 = c + 1 + assert_instance_of(RationalSub, c2) + c2 = c - 1 + assert_instance_of(RationalSub, c2) - c3 = c - c2 - assert_instance_of(RationalSub, c3) + c3 = c - c2 + assert_instance_of(RationalSub, c3) - s = Marshal.dump(c) - c5 = Marshal.load(s) - assert_equal(c, c5) - assert_instance_of(RationalSub, c5) - end + s = Marshal.dump(c) + c5 = Marshal.load(s) + assert_equal(c, c5) + assert_instance_of(RationalSub, c5) c1 = Rational(1) assert_equal(c1.hash, c.hash, '[ruby-dev:38850]') @@ -47,18 +35,16 @@ class Rational_Test < Test::Unit::TestCase c2 = Rational(0) c3 = Rational(1) - assert_equal(true, c.eql?(c2)) - assert_equal(false, c.eql?(c3)) + assert_operator(c, :eql?, c2) + assert_not_operator(c, :eql?, c3) - if @unify - assert_equal(true, c.eql?(0)) - else - assert_equal(false, c.eql?(0)) - end + assert_not_operator(c, :eql?, 0) end def test_hash - assert_instance_of(Fixnum, 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 @@ -75,10 +61,7 @@ class Rational_Test < Test::Unit::TestCase def test_freeze c = Rational(1) - c.freeze - unless @unify - assert_equal(true, c.frozen?) - end + assert_predicate(c, :frozen?) assert_instance_of(String, c.to_s) end @@ -111,16 +94,14 @@ class Rational_Test < Test::Unit::TestCase c = Rational(Rational(1,2),Rational(1,2)) assert_equal(Rational(1), c) - if @complex && !@keiju - c = Rational(Complex(1,2),2) - assert_equal(Complex(Rational(1,2),1), c) + c = Rational(Complex(1,2),2) + assert_equal(Complex(Rational(1,2),1), c) - c = Rational(2,Complex(1,2)) - assert_equal(Complex(Rational(2,5),Rational(-4,5)), c) + c = Rational(2,Complex(1,2)) + assert_equal(Complex(Rational(2,5),Rational(-4,5)), c) - c = Rational(Complex(1,2),Complex(1,2)) - assert_equal(Rational(1), c) - end + c = Rational(Complex(1,2),Complex(1,2)) + assert_equal(Rational(1), c) assert_equal(Rational(3),Rational(3)) assert_equal(Rational(1),Rational(3,3)) @@ -129,8 +110,15 @@ 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, 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_raise(TypeError){Rational(Object.new)} assert_raise(ArgumentError){Rational()} assert_raise(ArgumentError){Rational(1,2,3)} @@ -178,57 +166,15 @@ class Rational_Test < Test::Unit::TestCase def test_attr2 c = Rational(1) - if @unify -=begin - assert_equal(true, c.finite?) - assert_equal(false, c.infinite?) - assert_equal(false, c.nan?) - assert_equal(true, c.integer?) - assert_equal(false, c.float?) - assert_equal(true, c.rational?) -=end - assert_equal(true, c.real?) -=begin - assert_equal(false, c.complex?) - assert_equal(true, c.exact?) - assert_equal(false, c.inexact?) -=end - else -=begin - assert_equal(true, c.finite?) - assert_equal(false, c.infinite?) - assert_equal(false, c.nan?) - assert_equal(false, c.integer?) - assert_equal(false, c.float?) - assert_equal(true, c.rational?) -=end - assert_equal(true, c.real?) -=begin - assert_equal(false, c.complex?) - assert_equal(true, c.exact?) - assert_equal(false, c.inexact?) -=end - end + assert_not_predicate(c, :integer?) + assert_predicate(c, :real?) -=begin - assert_equal(true, Rational(0).positive?) - assert_equal(true, Rational(1).positive?) - assert_equal(false, Rational(-1).positive?) - assert_equal(false, Rational(0).negative?) - assert_equal(false, Rational(1).negative?) - assert_equal(true, Rational(-1).negative?) - - assert_equal(0, Rational(0).sign) - assert_equal(1, Rational(2).sign) - assert_equal(-1, Rational(-2).sign) -=end - - assert_equal(true, Rational(0).zero?) - assert_equal(true, Rational(0,1).zero?) - assert_equal(false, Rational(1,1).zero?) - - assert_equal(nil, Rational(0).nonzero?) - assert_equal(nil, Rational(0,1).nonzero?) + assert_predicate(Rational(0), :zero?) + assert_predicate(Rational(0,1), :zero?) + assert_not_predicate(Rational(1,1), :zero?) + + assert_nil(Rational(0).nonzero?) + assert_nil(Rational(0,1).nonzero?) assert_equal(Rational(1,1), Rational(1,1).nonzero?) end @@ -248,12 +194,6 @@ class Rational_Test < Test::Unit::TestCase assert_equal(Rational(1,1), -Rational(-1,1)) assert_equal(Rational(1,1), -Rational(1,-1)) assert_equal(Rational(-1,1), -Rational(-1,-1)) - -=begin - assert_equal(0, Rational(0).negate) - assert_equal(-2, Rational(2).negate) - assert_equal(2, Rational(-2).negate) -=end end def test_add @@ -341,15 +281,13 @@ class Rational_Test < Test::Unit::TestCase assert_equal(-2, (-c).div(c2)) assert_equal(1, (-c).div(-c2)) - unless @unify - c = Rational(11) - c2 = Rational(3) + c = Rational(11) + c2 = Rational(3) - assert_equal(3, c.div(c2)) - assert_equal(-4, c.div(-c2)) - assert_equal(-4, (-c).div(c2)) - assert_equal(3, (-c).div(-c2)) - end + assert_equal(3, c.div(c2)) + assert_equal(-4, c.div(-c2)) + assert_equal(-4, (-c).div(c2)) + assert_equal(3, (-c).div(-c2)) end def test_modulo @@ -376,15 +314,13 @@ class Rational_Test < Test::Unit::TestCase assert_equal(Rational(99,100), (-c).modulo(c2)) assert_equal(Rational(-101,100), (-c).modulo(-c2)) - unless @unify - c = Rational(11) - c2 = Rational(3) + c = Rational(11) + c2 = Rational(3) - assert_equal(2, c.modulo(c2)) - assert_equal(-1, c.modulo(-c2)) - assert_equal(1, (-c).modulo(c2)) - assert_equal(-2, (-c).modulo(-c2)) - end + assert_equal(2, c.modulo(c2)) + assert_equal(-1, c.modulo(-c2)) + assert_equal(1, (-c).modulo(c2)) + assert_equal(-2, (-c).modulo(-c2)) end def test_divmod @@ -411,54 +347,15 @@ class Rational_Test < Test::Unit::TestCase assert_equal([-2, Rational(99,100)], (-c).divmod(c2)) assert_equal([1, Rational(-101,100)], (-c).divmod(-c2)) - unless @unify - c = Rational(11) - c2 = Rational(3) + c = Rational(11) + c2 = Rational(3) - assert_equal([3,2], c.divmod(c2)) - assert_equal([-4,-1], c.divmod(-c2)) - assert_equal([-4,1], (-c).divmod(c2)) - assert_equal([3,-2], (-c).divmod(-c2)) - end + assert_equal([3,2], c.divmod(c2)) + assert_equal([-4,-1], c.divmod(-c2)) + assert_equal([-4,1], (-c).divmod(c2)) + assert_equal([3,-2], (-c).divmod(-c2)) end -=begin - def test_quot - c = Rational(1,2) - c2 = Rational(2,3) - - assert_eql(0, c.quot(c2)) - assert_eql(0, c.quot(2)) - assert_eql(0, c.quot(2.0)) - - c = Rational(301,100) - c2 = Rational(7,5) - - assert_equal(2, c.quot(c2)) - assert_equal(-2, c.quot(-c2)) - assert_equal(-2, (-c).quot(c2)) - assert_equal(2, (-c).quot(-c2)) - - c = Rational(301,100) - c2 = Rational(2) - - assert_equal(1, c.quot(c2)) - assert_equal(-1, c.quot(-c2)) - assert_equal(-1, (-c).quot(c2)) - assert_equal(1, (-c).quot(-c2)) - - unless @unify - c = Rational(11) - c2 = Rational(3) - - assert_equal(3, c.quot(c2)) - assert_equal(-3, c.quot(-c2)) - assert_equal(-3, (-c).quot(c2)) - assert_equal(3, (-c).quot(-c2)) - end - end -=end - def test_remainder c = Rational(1,2) c2 = Rational(2,3) @@ -483,54 +380,15 @@ class Rational_Test < Test::Unit::TestCase assert_equal(Rational(-101,100), (-c).remainder(c2)) assert_equal(Rational(-101,100), (-c).remainder(-c2)) - unless @unify - c = Rational(11) - c2 = Rational(3) + c = Rational(11) + c2 = Rational(3) - assert_equal(2, c.remainder(c2)) - assert_equal(2, c.remainder(-c2)) - assert_equal(-2, (-c).remainder(c2)) - assert_equal(-2, (-c).remainder(-c2)) - end + assert_equal(2, c.remainder(c2)) + assert_equal(2, c.remainder(-c2)) + assert_equal(-2, (-c).remainder(c2)) + assert_equal(-2, (-c).remainder(-c2)) end -=begin - def test_quotrem - c = Rational(1,2) - c2 = Rational(2,3) - - assert_eql([0, Rational(1,2)], c.quotrem(c2)) - assert_eql([0, Rational(1,2)], c.quotrem(2)) - assert_eql([0, 0.5], c.quotrem(2.0)) - - c = Rational(301,100) - c2 = Rational(7,5) - - assert_equal([2, Rational(21,100)], c.quotrem(c2)) - assert_equal([-2, Rational(21,100)], c.quotrem(-c2)) - assert_equal([-2, Rational(-21,100)], (-c).quotrem(c2)) - assert_equal([2, Rational(-21,100)], (-c).quotrem(-c2)) - - c = Rational(301,100) - c2 = Rational(2) - - assert_equal([1, Rational(101,100)], c.quotrem(c2)) - assert_equal([-1, Rational(101,100)], c.quotrem(-c2)) - assert_equal([-1, Rational(-101,100)], (-c).quotrem(c2)) - assert_equal([1, Rational(-101,100)], (-c).quotrem(-c2)) - - unless @unify - c = Rational(11) - c2 = Rational(3) - - assert_equal([3,2], c.quotrem(c2)) - assert_equal([-3,2], c.quotrem(-c2)) - assert_equal([-3,-2], (-c).quotrem(c2)) - assert_equal([3,-2], (-c).quotrem(-c2)) - end - end -=end - def test_quo c = Rational(1,2) c2 = Rational(2,3) @@ -578,50 +436,38 @@ class Rational_Test < Test::Unit::TestCase # p ** p x = 2 ** Rational(2) assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) x = Rational(2) ** 2 assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) x = Rational(2) ** Rational(2) assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) # -p ** p x = (-2) ** Rational(2) assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) x = Rational(-2) ** 2 assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) x = Rational(-2) ** Rational(2) assert_equal(Rational(4), x) - unless @unify - assert_instance_of(Rational, x) - end + assert_instance_of(Rational, x) assert_equal(4, x.numerator) assert_equal(1, x.denominator) @@ -663,9 +509,7 @@ class Rational_Test < Test::Unit::TestCase assert_equal(1, x.numerator) assert_equal(4, x.denominator) - unless @unify # maybe bug mathn - assert_raise(ZeroDivisionError){0 ** -1} - end + assert_raise(ZeroDivisionError){0 ** -1} end def test_cmp @@ -698,23 +542,23 @@ class Rational_Test < Test::Unit::TestCase assert_equal(-1, Rational(b-1) <=> Rational(b)) assert_equal(+1, Rational(b) <=> Rational(b-1)) - assert_equal(false, Rational(0) < Rational(0)) - assert_equal(true, Rational(0) <= Rational(0)) - assert_equal(true, Rational(0) >= Rational(0)) - assert_equal(false, Rational(0) > Rational(0)) + assert_not_operator(Rational(0), :<, Rational(0)) + assert_operator(Rational(0), :<=, Rational(0)) + assert_operator(Rational(0), :>=, Rational(0)) + assert_not_operator(Rational(0), :>, Rational(0)) - assert_equal(nil, Rational(0) <=> nil) - assert_equal(nil, Rational(0) <=> 'foo') + assert_nil(Rational(0) <=> nil) + assert_nil(Rational(0) <=> 'foo') end def test_eqeq assert_equal(Rational(1,1), Rational(1)) assert_equal(Rational(-1,1), Rational(-1)) - assert_equal(false, Rational(2,1) == Rational(1)) - assert_equal(true, Rational(2,1) != Rational(1)) - assert_equal(false, Rational(1) == nil) - assert_equal(false, Rational(1) == '') + assert_not_operator(Rational(2,1), :==, Rational(1)) + assert_operator(Rational(2,1), :!=, Rational(1)) + assert_not_operator(Rational(1), :==, nil) + assert_not_operator(Rational(1), :==, '') end def test_coerce @@ -728,7 +572,7 @@ class Rational_Test < Test::Unit::TestCase end class ObjectX - def + (x) Rational(1) end + def +(x) Rational(1) end alias - + alias * + alias / + @@ -747,42 +591,32 @@ class Rational_Test < Test::Unit::TestCase end end - def test_unify - if @unify - assert_instance_of(Fixnum, Rational(1,2) + Rational(1,2)) - assert_instance_of(Fixnum, Rational(1,2) - Rational(1,2)) - assert_instance_of(Fixnum, Rational(1,2) * 2) - assert_instance_of(Fixnum, Rational(1,2) / Rational(1,2)) - assert_instance_of(Fixnum, Rational(1,2).div(Rational(1,2))) - assert_instance_of(Fixnum, Rational(1,2).quo(Rational(1,2))) - assert_instance_of(Fixnum, Rational(1,2) ** -2) - end - end - def test_math assert_equal(Rational(1,2), Rational(1,2).abs) assert_equal(Rational(1,2), Rational(-1,2).abs) - if @complex && !@keiju - assert_equal(Rational(1,2), Rational(1,2).magnitude) - assert_equal(Rational(1,2), Rational(-1,2).magnitude) - end + assert_equal(Rational(1,2), Rational(1,2).magnitude) + assert_equal(Rational(1,2), Rational(-1,2).magnitude) assert_equal(1, Rational(1,2).numerator) assert_equal(2, Rational(1,2).denominator) end def test_trunc - [[Rational(13, 5), [ 2, 3, 2, 3]], # 2.6 - [Rational(5, 2), [ 2, 3, 2, 3]], # 2.5 - [Rational(12, 5), [ 2, 3, 2, 2]], # 2.4 - [Rational(-12,5), [-3, -2, -2, -2]], # -2.4 - [Rational(-5, 2), [-3, -2, -2, -3]], # -2.5 - [Rational(-13, 5), [-3, -2, -2, -3]], # -2.6 + [[Rational(13, 5), [ 2, 3, 2, 3, 3, 3, 3]], # 2.6 + [Rational(5, 2), [ 2, 3, 2, 3, 2, 3, 2]], # 2.5 + [Rational(12, 5), [ 2, 3, 2, 2, 2, 2, 2]], # 2.4 + [Rational(-12,5), [-3, -2, -2, -2, -2, -2, -2]], # -2.4 + [Rational(-5, 2), [-3, -2, -2, -3, -2, -3, -2]], # -2.5 + [Rational(-13, 5), [-3, -2, -2, -3, -3, -3, -3]], # -2.6 ].each do |i, a| - assert_equal(a[0], i.floor) - assert_equal(a[1], i.ceil) - assert_equal(a[2], i.truncate) - assert_equal(a[3], i.round) + s = proc {i.inspect} + assert_equal(a[0], i.floor, s) + assert_equal(a[1], i.ceil, s) + assert_equal(a[2], i.truncate, s) + assert_equal(a[3], i.round, s) + assert_equal(a[4], i.round(half: :even), s) + assert_equal(a[5], i.round(half: :up), s) + assert_equal(a[6], i.round(half: :down), s) end end @@ -792,13 +626,8 @@ class Rational_Test < Test::Unit::TestCase assert_instance_of(String, c.to_s) assert_equal('1/2', c.to_s) - if @unify - assert_equal('0', Rational(0,2).to_s) - assert_equal('0', Rational(0,-2).to_s) - else - assert_equal('0/1', Rational(0,2).to_s) - assert_equal('0/1', Rational(0,-2).to_s) - end + assert_equal('0/1', Rational(0,2).to_s) + assert_equal('0/1', Rational(0,-2).to_s) assert_equal('1/2', Rational(1,2).to_s) assert_equal('-1/2', Rational(-1,2).to_s) assert_equal('1/2', Rational(-1,-2).to_s) @@ -815,21 +644,22 @@ class Rational_Test < Test::Unit::TestCase def test_marshal c = Rational(1,2) - c.instance_eval{@ivar = 9} s = Marshal.dump(c) c2 = Marshal.load(s) assert_equal(c, c2) - assert_equal(9, c2.instance_variable_get(:@ivar)) assert_instance_of(Rational, c2) + assert_raise(TypeError){ + Marshal.load("\x04\bU:\rRational[\ai\x060") + } + assert_raise(ZeroDivisionError){ Marshal.load("\x04\bU:\rRational[\ai\x06i\x05") } bug3656 = '[ruby-core:31622]' c = Rational(1,2) - c.freeze assert_predicate(c, :frozen?) result = c.marshal_load([2,3]) rescue :fail assert_equal(:fail, result, bug3656) @@ -841,132 +671,113 @@ class Rational_Test < Test::Unit::TestCase assert_nothing_raised(bug6625) do assert_equal(Rational(1, 2), Marshal.load(dump), bug6625) end + dump = "\x04\x08o:\x0dRational\x07:\x11@denominatori\x07:\x0f@numerator0" + assert_raise(TypeError) do + Marshal.load(dump) + end end - def test_parse - assert_equal(Rational(5), '5'.to_r) - assert_equal(Rational(-5), '-5'.to_r) - assert_equal(Rational(5,3), '5/3'.to_r) - assert_equal(Rational(-5,3), '-5/3'.to_r) -# assert_equal(Rational(5,-3), '5/-3'.to_r) -# assert_equal(Rational(-5,-3), '-5/-3'.to_r) - - assert_equal(Rational(5), '5.0'.to_r) - assert_equal(Rational(-5), '-5.0'.to_r) - assert_equal(Rational(5,3), '5.0/3'.to_r) - assert_equal(Rational(-5,3), '-5.0/3'.to_r) -# assert_equal(Rational(5,-3), '5.0/-3'.to_r) -# assert_equal(Rational(-5,-3), '-5.0/-3'.to_r) - - assert_equal(Rational(5), '5e0'.to_r) - assert_equal(Rational(-5), '-5e0'.to_r) - assert_equal(Rational(5,3), '5e0/3'.to_r) - assert_equal(Rational(-5,3), '-5e0/3'.to_r) -# assert_equal(Rational(5,-3), '5e0/-3'.to_r) -# assert_equal(Rational(-5,-3), '-5e0/-3'.to_r) - - assert_equal(Rational(5e1), '5e1'.to_r) - assert_equal(Rational(-5e2), '-5e2'.to_r) - assert_equal(Rational(5e3,3), '5e003/3'.to_r) - assert_equal(Rational(-5e4,3), '-5e004/3'.to_r) -# assert_equal(Rational(5e1,-3), '5e1/-3'.to_r) -# assert_equal(Rational(-5e2,-3), '-5e2/-3'.to_r) - - assert_equal(Rational(33,100), '.33'.to_r) - assert_equal(Rational(33,100), '0.33'.to_r) - assert_equal(Rational(-33,100), '-.33'.to_r) - assert_equal(Rational(-33,100), '-0.33'.to_r) - assert_equal(Rational(-33,100), '-0.3_3'.to_r) - - assert_equal(Rational(1,2), '5e-1'.to_r) - assert_equal(Rational(50), '5e+1'.to_r) - assert_equal(Rational(1,2), '5.0e-1'.to_r) - assert_equal(Rational(50), '5.0e+1'.to_r) - assert_equal(Rational(50), '5e1'.to_r) - assert_equal(Rational(50), '5E1'.to_r) - assert_equal(Rational(500), '5e2'.to_r) - assert_equal(Rational(5000), '5e3'.to_r) - assert_equal(Rational(500000000000), '5e1_1'.to_r) - - assert_equal(Rational(5), Rational('5')) - assert_equal(Rational(-5), Rational('-5')) - assert_equal(Rational(5,3), Rational('5/3')) - assert_equal(Rational(-5,3), Rational('-5/3')) -# assert_equal(Rational(5,-3), Rational('5/-3')) -# assert_equal(Rational(-5,-3), Rational('-5/-3')) - - assert_equal(Rational(5), Rational('5.0')) - assert_equal(Rational(-5), Rational('-5.0')) - assert_equal(Rational(5,3), Rational('5.0/3')) - assert_equal(Rational(-5,3), Rational('-5.0/3')) -# assert_equal(Rational(5,-3), Rational('5.0/-3')) -# assert_equal(Rational(-5,-3), Rational('-5.0/-3')) - - assert_equal(Rational(5), Rational('5e0')) - assert_equal(Rational(-5), Rational('-5e0')) - assert_equal(Rational(5,3), Rational('5e0/3')) - assert_equal(Rational(-5,3), Rational('-5e0/3')) -# assert_equal(Rational(5,-3), Rational('5e0/-3')) -# assert_equal(Rational(-5,-3), Rational('-5e0/-3')) - - assert_equal(Rational(5e1), Rational('5e1')) - assert_equal(Rational(-5e2), Rational('-5e2')) - assert_equal(Rational(5e3,3), Rational('5e003/3')) - assert_equal(Rational(-5e4,3), Rational('-5e004/3')) -# assert_equal(Rational(5e1,-3), Rational('5e1/-3')) -# assert_equal(Rational(-5e2,-3), Rational('-5e2/-3')) - - assert_equal(Rational(33,100), Rational('.33')) - assert_equal(Rational(33,100), Rational('0.33')) - assert_equal(Rational(-33,100), Rational('-.33')) - assert_equal(Rational(-33,100), Rational('-0.33')) - assert_equal(Rational(-33,100), Rational('-0.3_3')) - - assert_equal(Rational(1,2), Rational('5e-1')) - assert_equal(Rational(50), Rational('5e+1')) - assert_equal(Rational(1,2), Rational('5.0e-1')) - assert_equal(Rational(50), Rational('5.0e+1')) - assert_equal(Rational(50), Rational('5e1')) - assert_equal(Rational(50), Rational('5E1')) - assert_equal(Rational(500), Rational('5e2')) - assert_equal(Rational(5000), Rational('5e3')) - assert_equal(Rational(500000000000), Rational('5e1_1')) - - assert_equal(Rational(0), ''.to_r) - assert_equal(Rational(0), ' '.to_r) - assert_equal(Rational(5), "\f\n\r\t\v5\0".to_r) - assert_equal(Rational(0), '_'.to_r) - assert_equal(Rational(0), '_5'.to_r) - assert_equal(Rational(5), '5_'.to_r) - assert_equal(Rational(5), '5x'.to_r) - assert_equal(Rational(5), '5/_3'.to_r) - assert_equal(Rational(5,3), '5/3_'.to_r) - assert_equal(Rational(5,3), '5/3.3'.to_r) - assert_equal(Rational(5,3), '5/3x'.to_r) - assert_raise(ArgumentError){ Rational('')} - assert_raise(ArgumentError){ Rational('_')} - assert_raise(ArgumentError){ Rational("\f\n\r\t\v5\0")} - assert_raise(ArgumentError){ Rational('_5')} - assert_raise(ArgumentError){ Rational('5_')} - assert_raise(ArgumentError){ Rational('5x')} - assert_raise(ArgumentError){ Rational('5/_3')} - assert_raise(ArgumentError){ Rational('5/3_')} - assert_raise(ArgumentError){ Rational('5/3.3')} - assert_raise(ArgumentError){ Rational('5/3x')} + def assert_valid_rational(n, d, r) + x = Rational(n, d) + assert_equal(x, r.to_r, "#{r.dump}.to_r") + assert_equal(x, Rational(r), "Rational(#{r.dump})") + end + + def assert_invalid_rational(n, d, r) + x = Rational(n, d) + assert_equal(x, r.to_r, "#{r.dump}.to_r") + assert_raise(ArgumentError, "Rational(#{r.dump})") {Rational(r)} end -=begin - def test_reciprocal - assert_equal(Rational(1,9), Rational(9,1).reciprocal) - assert_equal(Rational(9,1), Rational(1,9).reciprocal) - assert_equal(Rational(-1,9), Rational(-9,1).reciprocal) - assert_equal(Rational(-9,1), Rational(-1,9).reciprocal) - assert_equal(Rational(1,9), Rational(9,1).inverse) - assert_equal(Rational(9,1), Rational(1,9).inverse) - assert_equal(Rational(-1,9), Rational(-9,1).inverse) - assert_equal(Rational(-9,1), Rational(-1,9).inverse) + def test_parse + ok = method(:assert_valid_rational) + ng = method(:assert_invalid_rational) + + ok[ 5, 1, '5'] + ok[-5, 1, '-5'] + ok[ 5, 3, '5/3'] + ok[-5, 3, '-5/3'] + ok[ 5, 3, '5_5/33'] + ok[ 5,33, '5/3_3'] + ng[ 5, 1, '5__5/33'] + ng[ 5, 3, '5/3__3'] + + ok[ 5, 1, '5.0'] + ok[-5, 1, '-5.0'] + ok[ 5, 3, '5.0/3'] + ok[-5, 3, '-5.0/3'] + ok[ 501,100, '5.0_1'] + ok[ 501,300, '5.0_1/3'] + ok[ 5,33, '5.0/3_3'] + ng[ 5, 1, '5.0__1/3'] + ng[ 5, 3, '5.0/3__3'] + + ok[ 5, 1, '5e0'] + ok[-5, 1, '-5e0'] + ok[ 5, 3, '5e0/3'] + ok[-5, 3, '-5e0/3'] + ok[550, 1, '5_5e1'] + ng[ 5, 1, '5_e1'] + + ok[ 5e1, 1, '5e1'] + ok[-5e2, 1, '-5e2'] + ok[ 5e3, 3, '5e003/3'] + ok[-5e4, 3, '-5e004/3'] + ok[ 5e3, 1, '5e0_3'] + ok[ 5e1,33, '5e1/3_3'] + ng[ 5e0, 1, '5e0__3/3'] + ng[ 5e1, 3, '5e1/3__3'] + + ok[ 33, 100, '.33'] + ok[ 33, 100, '0.33'] + ok[-33, 100, '-.33'] + ok[-33, 100, '-0.33'] + ok[-33, 100, '-0.3_3'] + ng[ -3, 10, '-0.3__3'] + + ok[ 1, 2, '5e-1'] + ok[50, 1, '5e+1'] + ok[ 1, 2, '5.0e-1'] + ok[50, 1, '5.0e+1'] + ok[50, 1, '5e1'] + ok[50, 1, '5E1'] + ok[500, 1, '5e2'] + ok[5000, 1, '5e3'] + ok[500000000000, 1, '5e1_1'] + ng[ 5, 1, '5e'] + ng[ 5, 1, '5e_'] + ng[ 5, 1, '5e_1'] + ng[50, 1, '5e1_'] + + ok[ 50, 33, '5/3.3'] + ok[ 5, 3, '5/3e0'] + ok[ 5, 30, '5/3e1'] + ng[ 5, 3, '5/3._3'] + ng[ 50, 33, '5/3.3_'] + ok[500,333, '5/3.3_3'] + ng[ 5, 3, '5/3e'] + ng[ 5, 3, '5/3_e'] + ng[ 5, 3, '5/3e_'] + ng[ 5, 3, '5/3e_1'] + ng[ 5, 30, '5/3e1_'] + ok[ 5, 300000000000, '5/3e1_1'] + + ng[0, 1, ''] + ng[0, 1, ' '] + ng[5, 1, "\f\n\r\t\v5\0"] + ng[0, 1, '_'] + ng[0, 1, '_5'] + ng[5, 1, '5_'] + ng[5, 1, '5x'] + ng[5, 1, '5/_3'] + ng[5, 3, '5/3_'] + ng[5, 3, '5/3x'] + end + + def test_parse_zero_denominator + assert_raise(ZeroDivisionError) {"1/0".to_r} + assert_raise(ZeroDivisionError) {Rational("1/0")} end -=end def test_to_i assert_equal(1, Rational(3,2).to_i) @@ -979,15 +790,8 @@ class Rational_Test < Test::Unit::TestCase end def test_to_c - if @complex && !@keiju - if @unify - assert_equal(Rational(3,2), Rational(3,2).to_c) - assert_equal(Rational(3,2), Complex(Rational(3,2))) - else - assert_equal(Complex(Rational(3,2)), Rational(3,2).to_c) - assert_equal(Complex(Rational(3,2)), Complex(Rational(3,2))) - end - end + assert_equal(Complex(Rational(3,2)), Rational(3,2).to_c) + assert_equal(Complex(Rational(3,2)), Complex(Rational(3,2))) end def test_to_r @@ -1007,13 +811,7 @@ class Rational_Test < Test::Unit::TestCase c = Rational(1,2).to_r assert_equal([1,2], [c.numerator, c.denominator]) - if @complex - if @keiju - assert_raise(NoMethodError){Complex(1,2).to_r} - else - assert_raise(RangeError){Complex(1,2).to_r} - end - end + assert_raise(RangeError){Complex(1,2).to_r} if (0.0/0).nan? assert_raise(FloatDomainError){(0.0/0).to_r} @@ -1063,12 +861,7 @@ class Rational_Test < Test::Unit::TestCase assert_equal(r.rationalize(Rational(1,10)), Rational(-1,3)) assert_equal(r.rationalize(Rational(-1,10)), Rational(-1,3)) - if @complex - if @keiju - else - assert_raise(RangeError){Complex(1,2).rationalize} - end - end + assert_raise(RangeError){Complex(1,2).rationalize} if (0.0/0).nan? assert_raise(FloatDomainError){(0.0/0).rationalize} @@ -1095,9 +888,19 @@ class Rational_Test < Test::Unit::TestCase assert_equal(1152921470247108503, 1073741789.lcm(1073741827)) end + def test_gcd_no_memory_leak + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-"end;"}", limit: 1.2, rss: true) + x = (1<<121) + 1 + y = (1<<99) + 1 + 1000.times{x.gcd(y)} + begin; + 100.times {1000.times{x.gcd(y)}} + end; + end + def test_supp - assert_equal(true, 1.real?) - assert_equal(true, 1.1.real?) + assert_predicate(1, :real?) + assert_predicate(1.1, :real?) assert_equal(1, 1.numerator) assert_equal(9, 9.numerator) @@ -1109,13 +912,6 @@ class Rational_Test < Test::Unit::TestCase assert_equal(1.0, 1.0.denominator) assert_equal(1.0, 9.0.denominator) -=begin - assert_equal(Rational(1,9), 9.reciprocal) - assert_in_delta(0.1111, 9.0.reciprocal, 0.001) - assert_equal(Rational(1,9), 9.inverse) - assert_in_delta(0.1111, 9.0.inverse, 0.001) -=end - assert_equal(Rational(1,2), 1.quo(2)) assert_equal(Rational(5000000000), 10000000000.quo(2)) assert_equal(0.5, 1.0.quo(2)) @@ -1127,6 +923,13 @@ class Rational_Test < Test::Unit::TestCase assert_equal(5000000000.0, 10000000000.fdiv(2)) assert_equal(0.5, 1.0.fdiv(2)) assert_equal(0.25, Rational(1,2).fdiv(2)) + + a = 0xa42fcabf_c51ce400_00001000_00000000_00000000_00000000_00000000_00000000 + b = 1<<1074 + assert_equal(Rational(a, b).to_f, a.fdiv(b)) + a = 3 + b = 0x20_0000_0000_0001 + assert_equal(Rational(a, b).to_f, a.fdiv(b)) end def test_ruby19 @@ -1135,12 +938,9 @@ class Rational_Test < Test::Unit::TestCase end def test_fixed_bug - if @unify - assert_instance_of(Fixnum, Rational(1,2) ** 0) # mathn's bug - end - n = Float::MAX.to_i * 2 - assert_equal(1.0, Rational(n + 2, n + 1).to_f, '[ruby-dev:33852]') + x = EnvUtil.suppress_warning {Rational(n + 2, n + 1).to_f} + assert_equal(1.0, x, '[ruby-dev:33852]') end def test_power_of_1_and_minus_1 @@ -1149,7 +949,7 @@ class Rational_Test < Test::Unit::TestCase one = Rational( 1, 1) assert_eql one, one ** -big , bug5715 assert_eql one, (-one) ** -big , bug5715 - assert_eql -one, (-one) ** -(big+1) , bug5715 + assert_eql (-one), (-one) ** -(big+1) , bug5715 assert_equal Complex, ((-one) ** Rational(1,3)).class end @@ -1163,7 +963,34 @@ class Rational_Test < Test::Unit::TestCase assert_raise(ZeroDivisionError, bug5713) { Rational(0, 1) ** Rational(-2,3) } end + def test_power_overflow + bug = '[ruby-core:79686] [Bug #13242]: Infinity due to overflow' + x = EnvUtil.suppress_warning {4r**40000000} + assert_predicate x, :infinite?, bug + x = EnvUtil.suppress_warning {(1/4r)**40000000} + assert_equal 0, x, bug + end + + def test_positive_p + assert_predicate(1/2r, :positive?) + assert_not_predicate(-1/2r, :positive?) + end + + def test_negative_p + assert_predicate(-1/2r, :negative?) + assert_not_predicate(1/2r, :negative?) + end + def test_known_bug end + def test_finite_p + assert_predicate(1/2r, :finite?) + assert_predicate(-1/2r, :finite?) + end + + def test_infinite_p + assert_nil((1/2r).infinite?) + assert_nil((-1/2r).infinite?) + end end diff --git a/test/ruby/test_rational2.rb b/test/ruby/test_rational2.rb index 3b6a985bc6..4e96bf621c 100644 --- a/test/ruby/test_rational2.rb +++ b/test/ruby/test_rational2.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class Rational_Test2 < Test::Unit::TestCase diff --git a/test/ruby/test_readpartial.rb b/test/ruby/test_readpartial.rb index b969c38692..bc22556cd4 100644 --- a/test/ruby/test_readpartial.rb +++ b/test/ruby/test_readpartial.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' require 'fcntl' @@ -50,8 +51,8 @@ class TestReadPartial < Test::Unit::TestCase w << 'abc' assert_equal('ab', r.readpartial(2)) assert_equal('c', r.readpartial(2)) - assert_raise(TimeoutError) { - timeout(0.1) { r.readpartial(2) } + assert_raise(Timeout::Error) { + Timeout.timeout(0.1) { r.readpartial(2) } } } end @@ -64,8 +65,8 @@ class TestReadPartial < Test::Unit::TestCase assert_equal("de", r.readpartial(2)) assert_equal("f\n", r.readpartial(4096)) assert_equal("ghi\n", r.readpartial(4096)) - assert_raise(TimeoutError) { - timeout(0.1) { r.readpartial(2) } + assert_raise(Timeout::Error) { + Timeout.timeout(0.1) { r.readpartial(2) } } } end diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index b116dbbf62..7725820038 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -1,14 +1,11 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' - -# to supress warnings for future calls of Module#refine -EnvUtil.suppress_warning do - Module.new { - refine(Object) {} - } -end class TestRefinement < Test::Unit::TestCase + module Sandbox + BINDING = binding + end + class Foo def x return "Foo#x" @@ -73,10 +70,14 @@ class TestRefinement < Test::Unit::TestCase end end - eval <<-EOF, TOPLEVEL_BINDING + class FooExtClient using TestRefinement::FooExt - class TestRefinement::FooExtClient + begin + def self.map_x_on(foo) + [foo].map(&:x)[0] + end + def self.invoke_x_on(foo) return foo.x end @@ -100,14 +101,18 @@ class TestRefinement < Test::Unit::TestCase def self.invoke_call_x_on(foo) return foo.call_x end + + def self.return_proc(&block) + block + end end - EOF + end - eval <<-EOF, TOPLEVEL_BINDING + class TestRefinement::FooExtClient2 using TestRefinement::FooExt using TestRefinement::FooExt2 - class TestRefinement::FooExtClient2 + begin def self.invoke_y_on(foo) return foo.y end @@ -116,7 +121,7 @@ class TestRefinement < Test::Unit::TestCase return foo.a end end - EOF + end def test_override foo = Foo.new @@ -170,10 +175,10 @@ class TestRefinement < Test::Unit::TestCase end end - def test_send_should_not_use_refinements + def test_send_should_use_refinements foo = Foo.new assert_raise(NoMethodError) { foo.send(:z) } - assert_raise(NoMethodError) { FooExtClient.send_z_on(foo) } + assert_equal("FooExt#z", FooExtClient.send_z_on(foo)) assert_raise(NoMethodError) { foo.send(:z) } assert_equal(true, RespondTo::Sub.new.respond_to?(:foo)) @@ -231,20 +236,20 @@ class TestRefinement < Test::Unit::TestCase assert_equal("Foo#x", foo.x) end - module FixnumSlashExt - refine Fixnum do + module IntegerSlashExt + refine Integer do def /(other) quo(other) end end end def test_override_builtin_method assert_equal(0, 1 / 2) - assert_equal(Rational(1, 2), eval_using(FixnumSlashExt, "1 / 2")) + assert_equal(Rational(1, 2), eval_using(IntegerSlashExt, "1 / 2")) assert_equal(0, 1 / 2) end - module FixnumPlusExt - refine Fixnum do + module IntegerPlusExt + refine Integer do def self.method_added(*args); end def +(other) "overridden" end end @@ -252,14 +257,14 @@ class TestRefinement < Test::Unit::TestCase def test_override_builtin_method_with_method_added assert_equal(3, 1 + 2) - assert_equal("overridden", eval_using(FixnumPlusExt, "1 + 2")) + assert_equal("overridden", eval_using(IntegerPlusExt, "1 + 2")) assert_equal(3, 1 + 2) end def test_return_value_of_refine mod = nil result = nil - m = Module.new { + Module.new { result = refine(Object) { mod = self } @@ -268,10 +273,10 @@ class TestRefinement < Test::Unit::TestCase end module RefineSameClass - REFINEMENT1 = refine(Fixnum) { + REFINEMENT1 = refine(Integer) { def foo; return "foo" end } - REFINEMENT2 = refine(Fixnum) { + REFINEMENT2 = refine(Integer) { def bar; return "bar" end } REFINEMENT3 = refine(String) { @@ -286,15 +291,15 @@ class TestRefinement < Test::Unit::TestCase assert_not_equal(RefineSameClass::REFINEMENT1, RefineSameClass::REFINEMENT3) end - module FixnumFooExt - refine Fixnum do + module IntegerFooExt + refine Integer do def foo; "foo"; end end end def test_respond_to_should_not_use_refinements assert_equal(false, 1.respond_to?(:foo)) - assert_equal(false, eval_using(FixnumFooExt, "1.respond_to?(:foo)")) + assert_equal(false, eval_using(IntegerFooExt, "1.respond_to?(:foo)")) end module StringCmpExt @@ -348,19 +353,66 @@ class TestRefinement < Test::Unit::TestCase assert_equal([:c, :m1, :m2], x) end - def test_refine_module - m1 = Module.new - assert_raise(TypeError) do - Module.new { - refine m1 do + module RefineModule + module M + def foo + "M#foo" + end + + def bar + "M#bar" + end + + def baz + "M#baz" + end + end + + class C + include M + + def baz + "#{super} C#baz" + end + end + + module M2 + refine M do def foo - :m2 + "M@M2#foo" end + + def bar + "#{super} M@M2#bar" end - } + + def baz + "#{super} M@M2#baz" + end + end + end + + using M2 + + def self.call_foo + C.new.foo + end + + def self.call_bar + C.new.bar + end + + def self.call_baz + C.new.baz end end + def test_refine_module + assert_equal("M@M2#foo", RefineModule.call_foo) + assert_equal("M#bar M@M2#bar", RefineModule.call_bar) + assert_equal("M#baz C#baz", RefineModule.call_baz) + end + def test_refine_neither_class_nor_module assert_raise(TypeError) do Module.new { @@ -385,7 +437,7 @@ class TestRefinement < Test::Unit::TestCase def test_refine_in_class assert_raise(NoMethodError) do Class.new { - refine Fixnum do + refine Integer do def foo "c" end @@ -419,7 +471,7 @@ class TestRefinement < Test::Unit::TestCase def test_main_using_is_private assert_raise(NoMethodError) do - eval("self.using Module.new", TOPLEVEL_BINDING) + eval("self.using Module.new", Sandbox::BINDING) end end @@ -433,9 +485,8 @@ class TestRefinement < Test::Unit::TestCase end def test_module_using_class - c = Class.new assert_raise(TypeError) do - eval("using TestRefinement::UsingClass", TOPLEVEL_BINDING) + eval("using TestRefinement::UsingClass", Sandbox::BINDING) end end @@ -450,13 +501,13 @@ class TestRefinement < Test::Unit::TestCase module Inspect module M - Fixnum = refine(Fixnum) {} + Integer = refine(Integer) {} end end def test_inspect - assert_equal("#<refinement:Fixnum@TestRefinement::Inspect::M>", - Inspect::M::Fixnum.inspect) + assert_equal("#<refinement:Integer@TestRefinement::Inspect::M>", + Inspect::M::Integer.inspect) end def test_using_method_cache @@ -507,8 +558,10 @@ class TestRefinement < Test::Unit::TestCase end class C - def foo - "redefined" + EnvUtil.suppress_warning do + def foo + "redefined" + end end end end @@ -596,7 +649,7 @@ class TestRefinement < Test::Unit::TestCase def test_using_in_module assert_raise(RuntimeError) do - eval(<<-EOF, TOPLEVEL_BINDING) + eval(<<-EOF, Sandbox::BINDING) $main = self module M end @@ -609,14 +662,16 @@ class TestRefinement < Test::Unit::TestCase def test_using_in_method assert_raise(RuntimeError) do - eval(<<-EOF, TOPLEVEL_BINDING) + eval(<<-EOF, Sandbox::BINDING) $main = self module M end - def call_using_in_method - $main.send(:using, M) + class C + def call_using_in_method + $main.send(:using, M) + end end - call_using_in_method + C.new.call_using_in_method EOF end end @@ -657,7 +712,7 @@ class TestRefinement < Test::Unit::TestCase end end - eval <<-EOF, TOPLEVEL_BINDING + eval <<-EOF, Sandbox::BINDING using TestRefinement::IncludeIntoRefinement::M module TestRefinement::IncludeIntoRefinement::User @@ -720,7 +775,7 @@ class TestRefinement < Test::Unit::TestCase end end - eval <<-EOF, TOPLEVEL_BINDING + eval <<-EOF, Sandbox::BINDING using TestRefinement::PrependIntoRefinement::M module TestRefinement::PrependIntoRefinement::User @@ -747,6 +802,7 @@ class TestRefinement < Test::Unit::TestCase PrependIntoRefinement::User.invoke_baz_on(x)) end + PrependAfterRefine_CODE = <<-EOC module PrependAfterRefine class C def foo @@ -780,6 +836,21 @@ class TestRefinement < Test::Unit::TestCase prepend Mixin end end + EOC + eval PrependAfterRefine_CODE + + def test_prepend_after_refine_wb_miss + if /\A(arm|mips)/ =~ RUBY_PLATFORM + skip "too slow cpu" + end + assert_normal_exit %Q{ + GC.stress = true + 10.times{ + #{PrependAfterRefine_CODE} + undef PrependAfterRefine + } + } + end def test_prepend_after_refine x = eval_using(PrependAfterRefine::M, @@ -866,7 +937,7 @@ class TestRefinement < Test::Unit::TestCase def test_module_using_invalid_self assert_raise(RuntimeError) do - eval <<-EOF, TOPLEVEL_BINDING + eval <<-EOF, Sandbox::BINDING module TestRefinement::TestModuleUsingInvalidSelf Module.new.send(:using, TestRefinement::FooExt) end @@ -945,7 +1016,7 @@ class TestRefinement < Test::Unit::TestCase end def test_eval_with_binding_scoping - assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "HELLO WORLD"], []) + assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "dlrow olleh"], []) module M refine String do def upcase @@ -955,8 +1026,9 @@ class TestRefinement < Test::Unit::TestCase end puts "hello world".upcase - puts eval(%{using M; "hello world".upcase}, TOPLEVEL_BINDING) - puts eval(%{"hello world".upcase}, TOPLEVEL_BINDING) + b = binding + puts eval(%{using M; "hello world".upcase}, b) + puts eval(%{"hello world".upcase}, b) INPUT end @@ -1219,6 +1291,24 @@ class TestRefinement < Test::Unit::TestCase end; end + def test_singleton_method_should_not_use_refinements + assert_separately([], <<-"end;") + bug10744 = '[ruby-core:67603] [Bug #10744]' + + class C + end + + module RefinementBug + refine C.singleton_class do + def foo + end + end + end + + assert_raise(NameError, bug10744) { C.singleton_method(:foo) } + end; + end + def test_refined_method_defined assert_separately([], <<-"end;") bug10753 = '[ruby-core:67656] [Bug #10753]' @@ -1371,6 +1461,38 @@ class TestRefinement < Test::Unit::TestCase :foo, bug10826) end + def test_undef_original_method + assert_in_out_err([], <<-INPUT, ["NoMethodError"], []) + module NoPlus + refine String do + undef + + end + end + + using NoPlus + "a" + "b" rescue p($!.class) + INPUT + end + + def test_undef_prepended_method + bug13096 = '[ruby-core:78944] [Bug #13096]' + klass = EnvUtil.labeled_class("X") do + def foo; end + end + klass.prepend(Module.new) + ext = EnvUtil.labeled_module("Ext") do + refine klass do + def foo + end + end + end + assert_nothing_raised(NameError, bug13096) do + klass.class_eval do + undef :foo + end + end + end + def test_call_refined_method_in_duplicate_module bug10885 = '[ruby-dev:48878]' assert_in_out_err([], <<-INPUT, [], [], bug10885) @@ -1426,9 +1548,567 @@ class TestRefinement < Test::Unit::TestCase } end + def test_alias_refined_method2 + bug11182 = '[ruby-core:69360]' + assert_in_out_err([], <<-INPUT, ["C"], [], bug11182) + class C + def foo + puts "C" + end + end + + module M + refine C do + def foo + puts "Refined C" + end + end + end + + class D < C + alias bar foo + end + + using M + D.new.bar + INPUT + end + + def test_reopen_refinement_module + assert_separately([], <<-"end;") + $VERBOSE = nil + class C + end + + module R + refine C do + def m + :foo + end + end + end + + using R + assert_equal(:foo, C.new.m) + + module R + refine C do + def m + :bar + end + end + end + + assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]") + end; + end + + module MixedUsing1 + class C + def foo + :orig_foo + end + end + + module R1 + refine C do + def foo + [:R1, super] + end + end + end + + module_function + + def foo + [:foo, C.new.foo] + end + + using R1 + + def bar + [:bar, C.new.foo] + end + end + + module MixedUsing2 + class C + def foo + :orig_foo + end + end + + module R1 + refine C do + def foo + [:R1_foo, super] + end + end + end + + module R2 + refine C do + def bar + [:R2_bar, C.new.foo] + end + + using R1 + + def baz + [:R2_baz, C.new.foo] + end + end + end + + using R2 + module_function + def f1; C.new.bar; end + def f2; C.new.baz; end + end + + def test_mixed_using + assert_equal([:foo, :orig_foo], MixedUsing1.foo) + assert_equal([:bar, [:R1, :orig_foo]], MixedUsing1.bar) + + assert_equal([:R2_bar, :orig_foo], MixedUsing2.f1) + assert_equal([:R2_baz, [:R1_foo, :orig_foo]], MixedUsing2.f2) + end + + module MethodMissing + class Foo + end + + module Bar + refine Foo do + def method_missing(mid, *args) + "method_missing refined" + end + end + end + + using Bar + + def self.call_undefined_method + Foo.new.foo + end + end + + def test_method_missing + assert_raise(NoMethodError) do + MethodMissing.call_undefined_method + end + end + + module VisibleRefinements + module RefA + refine Object do + def in_ref_a + end + end + end + + module RefB + refine Object do + def in_ref_b + end + end + end + + module RefC + using RefA + + refine Object do + def in_ref_c + end + end + end + + module Foo + using RefB + USED_MODS = Module.used_modules + end + + module Bar + using RefC + USED_MODS = Module.used_modules + end + + module Combined + using RefA + using RefB + USED_MODS = Module.used_modules + end + end + + def test_used_modules + ref = VisibleRefinements + assert_equal [], Module.used_modules + assert_equal [ref::RefB], ref::Foo::USED_MODS + assert_equal [ref::RefC], ref::Bar::USED_MODS + assert_equal [ref::RefB, ref::RefA], ref::Combined::USED_MODS + end + + def test_warn_setconst_in_refinmenet + bug10103 = '[ruby-core:64143] [Bug #10103]' + warnings = [ + "-:3: warning: not defined at the refinement, but at the outer class/module", + "-:4: warning: not defined at the refinement, but at the outer class/module" + ] + assert_in_out_err([], <<-INPUT, [], warnings, bug10103) + module M + refine String do + FOO = 123 + @@foo = 456 + end + end + INPUT + end + + def test_symbol_proc + assert_equal("FooExt#x", FooExtClient.map_x_on(Foo.new)) + assert_equal("Foo#x", FooExtClient.return_proc(&:x).(Foo.new)) + end + + def test_symbol_proc_with_block + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug = '[ruby-core:80219] [Bug #13325]' + begin; + module M + refine Class.new do + end + end + class C + def call(a, x, &b) + b.call(a, &x) + end + end + o = C.new + r = nil + x = ->(z){r = z} + assert_equal(42, o.call(42, x, &:tap)) + assert_equal(42, r) + using M + r = nil + assert_equal(42, o.call(42, x, &:tap), bug) + assert_equal(42, r, bug) + end; + end + + module AliasInSubclass + class C + def foo + :original + end + end + + class D < C + alias bar foo + end + + module M + refine D do + def bar + :refined + end + end + end + end + + def test_refine_alias_in_subclass + assert_equal(:refined, + eval_using(AliasInSubclass::M, "AliasInSubclass::D.new.bar")) + end + + def test_refine_with_prepend + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug = '[ruby-core:78073] [Bug #12920]' + Integer.prepend(Module.new) + Module.new do + refine Integer do + define_method(:+) {} + end + end + assert_kind_of(Time, Time.now, bug) + end; + end + + def test_public_in_refine + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug12729 = '[ruby-core:77161] [Bug #12729]' + + class Cow + private + def moo() "Moo"; end + end + + module PublicCows + refine(Cow) { + public :moo + } + end + + using PublicCows + assert_equal("Moo", Cow.new.moo, bug12729) + end; + end + + module SuperToModule + class Parent + end + + class Child < Parent + end + + module FooBar + refine Parent do + def to_s + "Parent" + end + end + + refine Child do + def to_s + super + " -> Child" + end + end + end + + using FooBar + def Child.test + new.to_s + end + end + + def test_super_to_module + bug = '[ruby-core:79588] [Bug #13227]' + assert_equal("Parent -> Child", SuperToModule::Child.test, bug) + end + + def test_include_refinement + bug = '[ruby-core:79632] [Bug #13236] cannot include refinement module' + r = nil + m = Module.new do + r = refine(String) {def test;:ok end} + end + assert_raise_with_message(ArgumentError, /refinement/, bug) do + m.module_eval {include r} + end + assert_raise_with_message(ArgumentError, /refinement/, bug) do + m.module_eval {prepend r} + end + end + + class ParentDefiningPrivateMethod + private + def some_inherited_method + end + end + + module MixinDefiningPrivateMethod + private + def some_included_method + end + end + + class SomeChildClassToRefine < ParentDefiningPrivateMethod + include MixinDefiningPrivateMethod + + private + def some_method + end + end + + def test_refine_inherited_method_with_visibility_changes + Module.new do + refine(SomeChildClassToRefine) do + def some_inherited_method; end + def some_included_method; end + def some_method; end + end + end + + obj = SomeChildClassToRefine.new + + assert_raise_with_message(NoMethodError, /private/) do + obj.some_inherited_method + end + + assert_raise_with_message(NoMethodError, /private/) do + obj.some_included_method + end + + assert_raise_with_message(NoMethodError, /private/) do + obj.some_method + end + end + + def test_refined_method_alias_warning + c = Class.new do + def t; :t end + def f; :f end + end + Module.new do + refine(c) do + alias foo t + end + end + assert_warning('', '[ruby-core:82385] [Bug #13817] refined method is not redefined') do + c.class_eval do + alias foo f + end + end + end + + def test_using_wrong_argument + bug = '[ruby-dev:50270] [Bug #13956]' + pattern = /expected Module/ + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = ""#{bug.dump} + pattern = /#{pattern}/ + begin; + assert_raise_with_message(TypeError, pattern, bug) { + using(1) do end + } + end; + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = ""#{bug.dump} + pattern = /#{pattern}/ + begin; + assert_raise_with_message(TypeError, pattern, bug) { + Module.new {using(1) {}} + } + end; + end + + class ToString + c = self + using Module.new {refine(c) {def to_s; "ok"; end}} + def string + "#{self}" + end + end + + def test_tostring + assert_equal("ok", ToString.new.string) + assert_predicate(ToString.new.taint.string, :tainted?) + end + + class ToSymbol + c = self + using Module.new {refine(c) {def intern; "<#{upcase}>"; end}} + def symbol + :"#{@string}" + end + def initialize(string) + @string = string + end + end + + def test_dsym_literal + assert_equal(:foo, ToSymbol.new("foo").symbol) + 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]') + end + private def eval_using(mod, s) - eval("using #{mod}; #{s}", TOPLEVEL_BINDING) + eval("using #{mod}; #{s}", Sandbox::BINDING) end end diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 8223a143b3..fe271dc3d7 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1,6 +1,6 @@ # coding: US-ASCII +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestRegexp < Test::Unit::TestCase def setup @@ -74,6 +74,12 @@ class TestRegexp < Test::Unit::TestCase end end + def test_to_s_extended_subexp + re = /#\g#{"\n"}/x + re = /#{re}/ + assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s} + end + def test_union assert_equal :ok, begin Regexp.union( @@ -111,8 +117,10 @@ class TestRegexp < Test::Unit::TestCase assert_equal('#<MatchData "& y" foo:"amp" foo:"y">', /&(?<foo>.*?); (?<foo>y)/.match("aaa & yyy").inspect) - /(?<id>[A-Za-z_]+)/ =~ "!abc" - assert_equal("abc", Regexp.last_match(:id)) + /(?<_id>[A-Za-z_]+)/ =~ "!abc" + assert_not_nil(Regexp.last_match) + assert_equal("abc", Regexp.last_match(1)) + assert_equal("abc", Regexp.last_match(:_id)) /a/ =~ "b" # doesn't match. assert_equal(nil, Regexp.last_match) @@ -142,10 +150,16 @@ class TestRegexp < Test::Unit::TestCase assert_equal("a[b]c", "abc".sub(/(?<x>[bc])/, "[\\k<x>]")) assert_equal("o", "foo"[/(?<bar>o)/, "bar"]) + assert_equal("o", "foo"[/(?<@bar>o)/, "@bar"]) + assert_equal("o", "foo"[/(?<@bar>.)\g<@bar>\k<@bar>/, "@bar"]) 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 @@ -173,8 +187,23 @@ class TestRegexp < Test::Unit::TestCase assert_raise(IndexError, bug9903) {m[key.dup.force_encoding(Encoding::Shift_JIS)]} end + def test_match_data_named_captures + assert_equal({'a' => '1', 'b' => '2', 'c' => nil}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('12').named_captures) + assert_equal({'a' => '1', 'b' => '2', 'c' => '3'}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('123').named_captures) + assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures) + + assert_equal({'a' => 'x'}, /(?<a>x)|(?<a>y)/.match('x').named_captures) + assert_equal({'a' => 'y'}, /(?<a>x)|(?<a>y)/.match('y').named_captures) + + assert_equal({'a' => '1', 'b' => '2'}, /^(.)(?<a>.)(?<b>.)/.match('012').named_captures) + assert_equal({'a' => '2'}, /^(?<a>.)(?<a>.)/.match('12').named_captures) + + assert_equal({}, /^(.)/.match('123').named_captures) + end + def test_assign_named_capture assert_equal("a", eval('/(?<foo>.)/ =~ "a"; foo')) + assert_equal(nil, eval('/(?<@foo>.)/ =~ "a"; defined?(@foo)')) assert_equal("a", eval('foo = 1; /(?<foo>.)/ =~ "a"; foo')) assert_equal("a", eval('1.times {|foo| /(?<foo>.)/ =~ "a"; break foo }')) assert_nothing_raised { eval('/(?<Foo>.)/ =~ "a"') } @@ -186,6 +215,15 @@ class TestRegexp < Test::Unit::TestCase assert_not_include(local_variables, :nil, "[ruby-dev:32675]") end + def test_assign_named_capture_trace + bug = '[ruby-core:79940] [Bug #13287]' + assert_normal_exit("#{<<-"begin;"}\n#{<<-"end;"}", bug) + begin; + / (?<foo>.*)/ =~ "bar" && + true + end; + end + def test_match_regexp r = /./ m = r.match("a") @@ -355,15 +393,46 @@ class TestRegexp < Test::Unit::TestCase def test_match_aref m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal("foobarbaz", m[0]) assert_equal("foo", m[1]) + assert_equal("foo", m[-4]) + assert_nil(m[-1]) + assert_nil(m[-11]) + assert_nil(m[-11, 1]) + assert_nil(m[-11..1]) + assert_nil(m[5]) + assert_nil(m[9]) assert_equal(["foo", "bar", "baz"], m[1..3]) + assert_equal(["foo", "bar", "baz"], m[1, 3]) + assert_equal([], m[3..1]) + assert_equal([], m[3, 0]) + assert_equal(nil, m[3, -1]) + assert_equal(nil, m[9, 1]) + assert_equal(["baz"], m[3, 1]) + assert_equal(["baz", nil], m[3, 5]) assert_nil(m[5]) assert_raise(IndexError) { m[:foo] } + assert_raise(TypeError) { m[nil] } end def test_match_values_at + idx = Object.new + def idx.to_int; 2; end m = /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal(["foo", "bar", "baz"], m.values_at(1, 2, 3)) + assert_equal(["foo", "bar", "baz"], m.values_at(1..3)) + assert_equal(["foo", "bar", "baz", nil, nil], m.values_at(1..5)) + assert_equal([], m.values_at(3..1)) + assert_equal([nil, nil, nil, nil, nil], m.values_at(5..9)) + assert_equal(["bar"], m.values_at(idx)) + assert_raise(RangeError){ m.values_at(-11..1) } + assert_raise(TypeError){ m.values_at(nil) } + + m = /(?<a>\d+) *(?<op>[+\-*\/]) *(?<b>\d+)/.match("1 + 2") + assert_equal(["1", "2", "+"], m.values_at(:a, 'b', :op)) + assert_equal(["+"], m.values_at(idx)) + assert_raise(TypeError){ m.values_at(nil) } + assert_raise(IndexError){ m.values_at(:foo) } end def test_match_string @@ -389,6 +458,9 @@ class TestRegexp < Test::Unit::TestCase assert_equal(arg_encoding_none, Regexp.new("", nil, "N").options) assert_raise(RegexpError) { Regexp.new(")(") } + assert_raise(RegexpError) { Regexp.new('[\\40000000000') } + assert_raise(RegexpError) { Regexp.new('[\\600000000000.') } + assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") } end def test_unescape @@ -476,6 +548,30 @@ class TestRegexp < Test::Unit::TestCase $_ = nil; assert_nil(~/./) end + def test_match_p + /backref/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal(false, //.match?(nil)) + assert_equal(true, //.match?("")) + assert_equal(true, /.../.match?(:abc)) + assert_raise(TypeError) { /.../.match?(Object.new) } + assert_equal(true, /b/.match?('abc')) + assert_equal(true, /b/.match?('abc', 1)) + assert_equal(true, /../.match?('abc', 1)) + assert_equal(true, /../.match?('abc', -2)) + assert_equal(false, /../.match?("abc", -4)) + assert_equal(false, /../.match?("abc", 4)) + assert_equal(true, /../.match?("\u3042xx", 1)) + assert_equal(false, /../.match?("\u3042x", 1)) + assert_equal(true, /\z/.match?("")) + assert_equal(true, /\z/.match?("abc")) + assert_equal(true, /R.../.match?("Ruby")) + assert_equal(false, /R.../.match?("Ruby", 1)) + assert_equal(false, /P.../.match?("Ruby")) + assert_equal('backref', $&) + end + def test_eqq assert_equal(false, /../ === nil) end @@ -490,6 +586,10 @@ class TestRegexp < Test::Unit::TestCase assert_equal("\\v", Regexp.quote("\v")) assert_equal("\u3042\\t", Regexp.quote("\u3042\t")) assert_equal("\\t\xff", Regexp.quote("\t" + [0xff].pack("C"))) + + bug13034 = '[ruby-core:78646] [Bug #13034]' + str = "\x00".force_encoding("UTF-16BE") + assert_equal(str, Regexp.quote(str), bug13034) end def test_try_convert @@ -544,6 +644,16 @@ class TestRegexp < Test::Unit::TestCase 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/) { $= } + end + def test_match_setter /foo/ =~ "foo" m = $~ @@ -552,6 +662,33 @@ class TestRegexp < Test::Unit::TestCase assert_equal("foo", $&) end + def test_match_without_regexp + # create a MatchData for each assertion because the internal state may change + test = proc {|&blk| "abc".sub("a", ""); blk.call($~) } + + bug10877 = '[ruby-core:68209] [Bug #10877]' + 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]} } + end + test.call {|m| assert_equal(/a/, m.regexp) } + test.call {|m| assert_equal("abc", m.string) } + test.call {|m| assert_equal(1, m.size) } + test.call {|m| assert_equal(0, m.begin(0)) } + test.call {|m| assert_equal(1, m.end(0)) } + test.call {|m| assert_equal([0, 1], m.offset(0)) } + test.call {|m| assert_equal([], m.captures) } + test.call {|m| assert_equal([], m.names) } + test.call {|m| assert_equal({}, m.named_captures) } + test.call {|m| assert_equal(/a/.match("abc"), m) } + test.call {|m| assert_equal(/a/.match("abc").hash, m.hash) } + test.call {|m| assert_equal("bc", m.post_match) } + test.call {|m| assert_equal("", m.pre_match) } + test.call {|m| assert_equal(["a", nil], m.values_at(0, 1)) } + end + def test_last_match /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal("foobarbaz", Regexp.last_match(0)) @@ -579,20 +716,7 @@ class TestRegexp < Test::Unit::TestCase assert_equal(3, "foobarbaz\u3042".rindex(/b../n, 5)) end - def test_taint - m = Thread.new do - "foo"[/foo/] - $SAFE = 3 - /foo/.match("foo") - end.value - assert_predicate(m, :tainted?) - assert_nothing_raised('[ruby-core:26137]') { - m = proc {$SAFE = 3; %r"#{ }"o}.call - } - assert_predicate(m, :tainted?) - end - - def check(re, ss, fs = [], msg = nil) + def assert_regexp(re, ss, fs = [], msg = nil) re = Regexp.new(re) unless re.is_a?(Regexp) ss = [ss] unless ss.is_a?(Array) ss.each do |e, s| @@ -604,10 +728,12 @@ class TestRegexp < Test::Unit::TestCase fs = [fs] unless fs.is_a?(Array) fs.each {|s| assert_no_match(re, s, msg) } end + alias check assert_regexp - def failcheck(re) + def assert_fail(re) assert_raise(RegexpError) { %r"#{ re }" } end + alias failcheck assert_fail def test_parse check(/\*\+\?\{\}\|\(\)\<\>\`\'/, "*+?{}|()<>`'") @@ -814,6 +940,29 @@ class TestRegexp < Test::Unit::TestCase assert_no_match(/[[:ascii:]]/, "\x80\xFF") end + def test_cclass_R + assert_match /\A\R\z/, "\r" + assert_match /\A\R\z/, "\n" + assert_match /\A\R\z/, "\r\n" + 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_warning('') {/\X/ =~ "\u{a0}"} + end + def test_backward assert_equal(3, "foobar".rindex(/b.r/i)) assert_equal(nil, "foovar".rindex(/b.r/i)) @@ -839,6 +988,7 @@ class TestRegexp < Test::Unit::TestCase assert_raise(TypeError) { Regexp.allocate.names } assert_raise(TypeError) { Regexp.allocate.named_captures } + assert_raise(TypeError) { MatchData.allocate.hash } assert_raise(TypeError) { MatchData.allocate.regexp } assert_raise(TypeError) { MatchData.allocate.names } assert_raise(TypeError) { MatchData.allocate.size } @@ -918,15 +1068,22 @@ class TestRegexp < Test::Unit::TestCase assert_no_match(/^\p{age=1.1}$/u, "\u2754") end + MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end") + def test_matchdata a = "haystack".match(/hay/) b = "haystack".match(/hay/) assert_equal(a, b, '[ruby-core:24748]') h = {a => 42} assert_equal(42, h[b], '[ruby-core:24748]') + assert_match(/#<TestRegexp::MatchData_\u{3042}:/, MatchData_A.allocate.inspect) + + h = /^(?<@time>\d+): (?<body>.*)/.match("123456: hoge fuga") + assert_equal("123456", h["@time"]) + assert_equal("hoge fuga", h["body"]) end - def test_regexp_poped + def test_regexp_popped assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } end @@ -977,7 +1134,7 @@ class TestRegexp < Test::Unit::TestCase def test_error_message_on_failed_conversion bug7539 = '[ruby-core:50733]' assert_equal false, /x/=== 42 - assert_raise_with_message(TypeError, 'no implicit conversion of Fixnum into String', bug7539) { + assert_raise_with_message(TypeError, 'no implicit conversion of Integer into String', bug7539) { Regexp.quote(42) } end @@ -988,6 +1145,9 @@ class TestRegexp < Test::Unit::TestCase conds = {"xy"=>true, "yx"=>true, "xx"=>false, "yy"=>false} assert_match_each(/\A((x)|(y))(?(2)y|x)\z/, conds, bug8583) assert_match_each(/\A((?<x>x)|(?<y>y))(?(<x>)y|x)\z/, conds, bug8583) + + bug12418 = '[ruby-core:75694] [Bug #12418]' + assert_raise(RegexpError, bug12418){ Regexp.new('(0?0|(?(5)||)|(?(5)||))?') } end def test_options_in_look_behind @@ -1009,8 +1169,9 @@ class TestRegexp < Test::Unit::TestCase assert_equal(/0/, pr1.call(0)) assert_equal(/0/, pr1.call(1)) assert_equal(/0/, pr1.call(2)) + end - # recursive + def test_once_recursive pr2 = proc{|i| if i > 0 /#{pr2.call(i-1).to_s}#{i}/ @@ -1019,9 +1180,10 @@ class TestRegexp < Test::Unit::TestCase end } assert_equal(/(?-mix:(?-mix:(?-mix:)1)2)3/, pr2.call(3)) + end - # multi-thread - m = Mutex.new + def test_once_multithread + m = Thread::Mutex.new pr3 = proc{|i| /#{m.unlock; sleep 0.5; i}/o } @@ -1031,8 +1193,9 @@ class TestRegexp < Test::Unit::TestCase th2 = Thread.new{m.lock; ary << pr3.call(n+=1)} th1.join; th2.join assert_equal([/1/, /1/], ary) + end - # escape + def test_once_escape pr4 = proc{|i| catch(:xyzzy){ /#{throw :xyzzy, i}/o =~ "" @@ -1056,6 +1219,36 @@ class TestRegexp < Test::Unit::TestCase RUBY end + def test_invalid_free_at_parse_depth_limit_over + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + begin + require '-test-/regexp' + rescue LoadError + else + bug = '[ruby-core:79624] [Bug #13234]' + Bug::Regexp.parse_depth_limit = 10 + src = "[" * 100 + 3.times do + assert_raise_with_message(RegexpError, /parse depth limit over/, bug) do + Regexp.new(src) + end + end + end + 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) + end + # This assertion is for porting x2() tests in testpy.py of Onigmo. def assert_match_at(re, str, positions, msg = nil) re = Regexp.new(re) unless re.is_a?(Regexp) diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index 562f0c13ea..28cf686a26 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require_relative 'envutil' require 'tmpdir' class TestRequire < Test::Unit::TestCase @@ -18,22 +18,24 @@ class TestRequire < Test::Unit::TestCase t.puts "dummy" t.close - assert_separately([], <<-INPUT) + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; $:.replace([IO::NULL]) assert_raise(LoadError) do require \"#{ t.path }\" end - INPUT + end; } end def test_require_too_long_filename - assert_separately(["RUBYOPT"=>nil], <<-INPUT) + assert_separately(["RUBYOPT"=>nil], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; $:.replace([IO::NULL]) assert_raise(LoadError) do require '#{ "foo/" * 10000 }foo' end - INPUT + end; begin assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e| @@ -60,6 +62,12 @@ 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 @@ -67,6 +75,12 @@ 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 @@ -74,6 +88,12 @@ 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) @@ -85,9 +105,18 @@ class TestRequire < Test::Unit::TestCase end end - def assert_require_nonascii_path(encoding, bug) + 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) Dir.mktmpdir {|tmp| - dir = "\u3042" * 5 begin require_path = File.join(tmp, dir, 'foo.rb').encode(encoding) rescue @@ -98,6 +127,17 @@ class TestRequire < Test::Unit::TestCase begin load_path = $:.dup features = $".dup + yield require_path + ensure + $:.replace(load_path) + $".replace(features) + end + } + end + + def assert_require_nonascii_path(encoding, bug) + prepare_require_path("\u3042" * 5, encoding) {|require_path| + begin # leave paths for require encoding objects bug = "#{bug} require #{encoding} path" require_path = "#{require_path}" @@ -107,13 +147,35 @@ class TestRequire < Test::Unit::TestCase assert_equal(self.class.ospath_encoding(require_path), $:.last.encoding, '[Bug #8753]') assert(!require(require_path), bug) } - ensure - $:.replace(load_path) - $".replace(features) end } 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 @@ -160,13 +222,20 @@ class TestRequire < Test::Unit::TestCase end def test_require_with_unc - ruby = File.expand_path(EnvUtil.rubybin).sub(/\A(\w):/, '//127.0.0.1/\1$/') - skip "local drive #$1: is not shared" unless File.exist?(ruby) - pid = nil - assert_nothing_raised {pid = spawn(ruby, "-rabbrev", "-e0")} - ret, status = Process.wait2(pid) - assert_equal(pid, ret) - assert_predicate(status, :success?) + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + t.puts "puts __FILE__" + 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) + args = ['--disable-gems', "-I#{File.dirname(path)}"] + assert_in_out_err(args, "#{<<~"END;"}", [path], []) + begin + require '#{File.basename(path)}' + rescue Errno::EPERM + end + END; + } end if /mswin|mingw/ =~ RUBY_PLATFORM def test_require_twice @@ -182,6 +251,26 @@ class TestRequire < Test::Unit::TestCase end end + def assert_syntax_error_backtrace + Dir.mktmpdir do |tmp| + req = File.join(tmp, "test.rb") + File.write(req, "'\n") + e = assert_raise_with_message(SyntaxError, /unterminated/) { + yield req + } + assert_not_nil(bt = e.backtrace) + assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}) + end + end + + def test_require_syntax_error + assert_syntax_error_backtrace {|req| require req} + end + + def test_load_syntax_error + assert_syntax_error_backtrace {|req| load req} + end + def test_define_class begin require "socket" @@ -229,7 +318,7 @@ class TestRequire < Test::Unit::TestCase assert_separately([], <<-INPUT) module Zlib; end class Zlib::Error; end - assert_raise(NameError) do + assert_raise(TypeError) do require 'zlib' end INPUT @@ -293,7 +382,8 @@ class TestRequire < Test::Unit::TestCase } end - def test_load2 # [ruby-core:25039] + def test_load_scope + bug1982 = '[ruby-core:25039] [Bug #1982]' Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| t.puts "Hello = 'hello'" t.puts "class Foo" @@ -301,7 +391,7 @@ class TestRequire < Test::Unit::TestCase t.puts "end" t.close - assert_in_out_err([], <<-INPUT, %w("hello"), []) + assert_in_out_err([], <<-INPUT, %w("hello"), [], bug1982) load(#{ t.path.dump }, true) INPUT } @@ -355,13 +445,6 @@ class TestRequire < Test::Unit::TestCase 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 @@ -403,7 +486,7 @@ class TestRequire < Test::Unit::TestCase File.symlink("../a/tst.rb", "b/tst.rb") result = IO.popen([EnvUtil.rubybin, "b/tst.rb"], &:read) assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]") - rescue NotImplementedError + rescue NotImplementedError, Errno::EACCES skip "File.symlink is not implemented" end } @@ -424,7 +507,8 @@ class TestRequire < Test::Unit::TestCase verbose = $VERBOSE Tempfile.create(%w"bug5754 .rb") {|tmp| path = tmp.path - tmp.print %{\ + tmp.print "#{<<~"begin;"}\n#{<<~"end;"}" + begin; th = Thread.current t = th[:t] scratch = th[:scratch] @@ -436,7 +520,7 @@ class TestRequire < Test::Unit::TestCase else scratch << :post end - } + end; tmp.close class << (output = "") @@ -516,7 +600,8 @@ class TestRequire < Test::Unit::TestCase open(File.join("b", "bar.rb"), "w") {|f| f.puts "p :ok" } - assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; $:.replace([IO::NULL]) $: << "." Dir.chdir("a") @@ -525,7 +610,7 @@ class TestRequire < Test::Unit::TestCase p :ng unless require "bar" Dir.chdir("..") p :ng if require "b/bar" - INPUT + end; } } end @@ -535,7 +620,8 @@ class TestRequire < Test::Unit::TestCase Dir.mktmpdir {|tmp| Dir.chdir(tmp) { open("foo.rb", "w") {} - assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; $:.replace([IO::NULL]) a = Object.new def a.to_str @@ -545,7 +631,7 @@ class TestRequire < Test::Unit::TestCase require "foo" last_path = $:.pop p :ok if last_path == a && last_path.class == Object - INPUT + end; } } end @@ -557,14 +643,15 @@ class TestRequire < Test::Unit::TestCase open("foo.rb", "w") {} Dir.mkdir("a") open(File.join("a", "bar.rb"), "w") {} - assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7158) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; $:.replace([IO::NULL]) $: << '~' ENV['HOME'] = "#{tmp}" require "foo" ENV['HOME'] = "#{tmp}/a" p :ok if require "bar" - INPUT + end; } } end @@ -574,7 +661,8 @@ class TestRequire < Test::Unit::TestCase Dir.mktmpdir {|tmp| Dir.chdir(tmp) { open("foo.rb", "w") {} - assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) + assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; $:.replace([IO::NULL]) a = Object.new def a.to_path @@ -584,13 +672,14 @@ class TestRequire < Test::Unit::TestCase begin require "foo" p [:ng, $LOAD_PATH, ENV['RUBYLIB']] - rescue LoadError + rescue LoadError => e + raise unless e.path == "foo" end def a.to_path "#{tmp}" end p :ok if require "foo" - INPUT + end; } } end @@ -600,7 +689,8 @@ class TestRequire < Test::Unit::TestCase Dir.mktmpdir {|tmp| Dir.chdir(tmp) { open("foo.rb", "w") {} - assert_in_out_err(["RUBYOPT"=>nil], <<-INPUT, %w(:ok), [], bug7158) + assert_in_out_err([{"RUBYOPT"=>nil}, '--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; $:.replace([IO::NULL]) a = Object.new def a.to_str @@ -610,13 +700,14 @@ class TestRequire < Test::Unit::TestCase begin require "foo" p [:ng, $LOAD_PATH, ENV['RUBYLIB']] - rescue LoadError + rescue LoadError => e + raise unless e.path == "foo" end def a.to_str "#{tmp}" end p :ok if require "foo" - INPUT + end; } } end @@ -628,7 +719,8 @@ class TestRequire < Test::Unit::TestCase open("foo.rb", "w") {} Dir.mkdir("a") open(File.join("a", "bar.rb"), "w") {} - assert_in_out_err([], <<-INPUT, %w(:ok), [], bug7383) + assert_in_out_err(['--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383) + begin; $:.replace([IO::NULL]) $:.#{add} "#{tmp}" $:.#{add} "#{tmp}/a" @@ -637,10 +729,14 @@ class TestRequire < Test::Unit::TestCase # Expanded load path cache should be rebuilt. begin require "bar" - rescue LoadError - p :ok + rescue LoadError => e + if e.path == "bar" + p :ok + else + raise + end end - INPUT + end; } } end @@ -658,10 +754,11 @@ class TestRequire < Test::Unit::TestCase Dir.mktmpdir {|tmp| Dir.chdir(tmp) { open("bar.rb", "w") {|f| f.puts 'TOPLEVEL_BINDING.eval("lib = 2")' } - assert_in_out_err(%w[-r./bar.rb], <<-INPUT, %w([:lib] 2), [], bug7536) + assert_in_out_err(%w[-r./bar.rb], "#{<<~"begin;"}\n#{<<~"end;"}", %w([:lib] 2), [], bug7536) + begin; puts TOPLEVEL_BINDING.eval("local_variables").inspect puts TOPLEVEL_BINDING.eval("lib").inspect - INPUT + end; } } end @@ -670,35 +767,132 @@ class TestRequire < Test::Unit::TestCase bug7530 = '[ruby-core:50645]' Tempfile.create(%w'bug-7530- .rb') {|script| script.close - assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], <<-INPUT, %w(:ok), [], bug7530) + assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7530, timeout: 60) + begin; PATH = ARGV.shift - THREADS = 2 + THREADS = 4 ITERATIONS_PER_THREAD = 1000 THREADS.times.map { Thread.new do ITERATIONS_PER_THREAD.times do require PATH - $".pop + $".delete_if {|p| Regexp.new(PATH) =~ p} end end }.each(&:join) p :ok - INPUT + end; } end - def test_loading_fifo_threading + def test_loading_fifo_threading_raise + Tempfile.create(%w'fifo .rb') {|f| + f.close + File.unlink(f.path) + File.mkfifo(f.path) + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3) + begin; + th = Thread.current + Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)} + assert_raise(IOError) do + load(ARGV[0]) + end + end; + } + end if File.respond_to?(:mkfifo) + + def test_loading_fifo_threading_success + Tempfile.create(%w'fifo .rb') {|f| + f.close + File.unlink(f.path) + File.mkfifo(f.path) + + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3) + begin; + path = ARGV[0] + th = Thread.current + $ok = false + Thread.start { + begin + sleep(0.001) + end until th.stop? + open(path, File::WRONLY | File::NONBLOCK) {|fifo_w| + fifo_w.print "$ok = true\n__END__\n" # ensure finishing + } + } + + load(path) + assert($ok) + end; + } + end if File.respond_to?(:mkfifo) + + def test_loading_fifo_fd_leak Tempfile.create(%w'fifo .rb') {|f| f.close File.unlink(f.path) File.mkfifo(f.path) - assert_separately(["-", f.path], <<-END, timeout: 3) - th = Thread.current - Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)} - assert_raise(IOError) {load(ARGV[0])} - END + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 3) + begin; + Process.setrlimit(Process::RLIMIT_NOFILE, 50) + th = Thread.current + 100.times do |i| + Thread.start {begin sleep(0.001) end until th.stop?; th.raise(IOError)} + assert_raise(IOError, "\#{i} time") do + begin + tap {tap {tap {load(ARGV[0])}}} + rescue LoadError + GC.start + retry + end + end + end + end; } - rescue Errno::ENOENT - end unless /mswin|mingw/ =~ RUBY_PLATFORM + end if File.respond_to?(:mkfifo) and defined?(Process::RLIMIT_NOFILE) + + def test_throw_while_loading + Tempfile.create(%w'bug-11404 .rb') do |f| + f.puts 'sleep' + f.close + + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + path = ARGV[0] + class Error < RuntimeError + def exception(*) + begin + throw :blah + rescue UncaughtThrowError + end + self + end + end + + assert_throw(:blah) do + x = Thread.current + Thread.start { + sleep 0.00001 + x.raise Error.new + } + load path + end + end; + end + end + + def test_symlink_load_path + Dir.mktmpdir {|tmp| + Dir.mkdir(File.join(tmp, "real")) + begin + File.symlink "real", File.join(tmp, "symlink") + rescue NotImplementedError, Errno::EACCES + skip "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") + } + end end diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index b6be5011cd..54213c4698 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -4,8 +4,6 @@ require 'test/unit' require 'tmpdir' require 'tempfile' -require_relative 'envutil' - class TestRubyOptions < Test::Unit::TestCase def write_file(filename, content) File.open(filename, "w") {|f| @@ -50,7 +48,7 @@ class TestRubyOptions < Test::Unit::TestCase end assert_in_out_err(%w(-p -l -a -e) + ['p [$-p, $-l, $-a]'], - "foo\nbar\nbaz\n") do |r, e| + "foo\nbar\nbaz") do |r, e| assert_equal( [ '[true, true, true]', 'foo', '[true, true, true]', 'bar', @@ -59,6 +57,7 @@ class TestRubyOptions < Test::Unit::TestCase end end + def test_warning save_rubyopt = ENV['RUBYOPT'] ENV['RUBYOPT'] = nil @@ -66,6 +65,7 @@ class TestRubyOptions < Test::Unit::TestCase 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(-W -e) + ['p $-W'], "", %w(2), []) + assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), []) ensure ENV['RUBYOPT'] = save_rubyopt end @@ -85,10 +85,21 @@ class TestRubyOptions < Test::Unit::TestCase "", %w(true), []) end + q = Regexp.method(:quote) + VERSION_PATTERN = + case RUBY_ENGINE + when 'jruby' + /^jruby #{q[RUBY_ENGINE_VERSION]} \(#{q[RUBY_VERSION]}\).*? \[#{ + q[RbConfig::CONFIG["host_os"]]}-#{q[RbConfig::CONFIG["host_cpu"]]}\]$/ + else + /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \[#{q[RUBY_PLATFORM]}\]$/ + end + private_constant :VERSION_PATTERN + def test_verbose assert_in_out_err(["-vve", ""]) do |r, e| - assert_match(/^ruby #{RUBY_VERSION}(?:[p ]|dev).*? \[#{RUBY_PLATFORM}\]$/, r.join) - assert_equal RUBY_DESCRIPTION, r.join.chomp + assert_match(VERSION_PATTERN, r[0]) + assert_equal(RUBY_DESCRIPTION, r[0]) assert_equal([], e) end @@ -120,6 +131,8 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%w(--disable foobarbazqux -e) + [""], "", [], /unknown argument for --disable: `foobarbazqux'/) 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"], []) end def test_kanji @@ -141,8 +154,8 @@ class TestRubyOptions < Test::Unit::TestCase def test_version assert_in_out_err(%w(--version)) do |r, e| - assert_match(/^ruby #{RUBY_VERSION}(?:[p ]|dev).*? \[#{RUBY_PLATFORM}\]$/, r.join) - assert_equal RUBY_DESCRIPTION, r.join.chomp + assert_match(VERSION_PATTERN, r[0]) + assert_equal(RUBY_DESCRIPTION, r[0]) assert_equal([], e) end end @@ -172,6 +185,10 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err(%w(-0141 -e) + ["print gets"], "foo\nbar\0baz", %w(foo ba), []) assert_in_out_err(%w(-0e) + ["print gets"], "foo\nbar\0baz", %W(foo bar\0), []) + + assert_in_out_err(%w(-00 -e) + ["p gets, gets"], "foo\nbar\n\nbaz\nzot\n\n\n", %w("foo\nbar\n\n" "baz\nzot\n\n"), []) + + assert_in_out_err(%w(-00 -e) + ["p gets, gets"], "foo\nbar\n\n\n\nbaz\n", %w("foo\nbar\n\n" "baz\n"), []) end def test_autosplit @@ -193,13 +210,13 @@ class TestRubyOptions < Test::Unit::TestCase def test_yydebug assert_in_out_err(["-ye", ""]) do |r, e| - assert_equal([], r) - assert_not_equal([], e) + assert_not_equal([], r) + assert_equal([], e) end assert_in_out_err(%w(--yydebug -e) + [""]) do |r, e| - assert_equal([], r) - assert_not_equal([], e) + assert_not_equal([], r) + assert_equal([], e) end end @@ -209,10 +226,12 @@ 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/ =~ RUBY_PLATFORM && + if /mswin|mingw|aix/ =~ RUBY_PLATFORM && (str = "\u3042".force_encoding(Encoding.find("locale"))).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, + # which allows \u3042. out, err = [str], [] else out, err = [], /invalid multibyte char/ @@ -258,6 +277,10 @@ class TestRubyOptions < Test::Unit::TestCase assert_equal([], e) end + ENV['RUBYOPT'] = '-w' + 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"]) ensure if rubyopt_orig ENV['RUBYOPT'] = rubyopt_orig @@ -277,13 +300,15 @@ class TestRubyOptions < Test::Unit::TestCase @verbose = $VERBOSE $VERBOSE = nil - ENV['PATH'] = File.dirname(t.path) - - assert_in_out_err(%w(-S) + [File.basename(t.path)], "", %w(1), []) + path, name = File.split(t.path) - ENV['RUBYPATH'] = File.dirname(t.path) + ENV['PATH'] = (path_orig && RbConfig::CONFIG['LIBPATHENV'] == 'PATH') ? + [path, path_orig].join(File::PATH_SEPARATOR) : path + assert_in_out_err(%w(-S) + [name], "", %w(1), []) + ENV['PATH'] = path_orig - assert_in_out_err(%w(-S) + [File.basename(t.path)], "", %w(1), []) + ENV['RUBYPATH'] = path + assert_in_out_err(%w(-S) + [name], "", %w(1), []) } ensure @@ -307,16 +332,36 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err([], "#! /test_r_u_b_y_test_r_u_b_y_options_foobarbazqux -foo -bar\r\np 1\r\n", [], /: no Ruby script found in input/) - assert_in_out_err([{'RUBYOPT' => nil}], "#!ruby -KU -Eutf-8\r\np \"\u3042\"\r\n") do |r, e| - assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8)) - assert_equal([], e) - end + warning = /mswin|mingw/ =~ RUBY_PLATFORM ? [] : /shebang line ending with \\r/ + assert_in_out_err([{'RUBYOPT' => nil}], "#!ruby -KU -Eutf-8\r\np \"\u3042\"\r\n", + ["\"\u3042\""], warning, + encoding: Encoding::UTF_8) bug4118 = '[ruby-dev:42680]' assert_in_out_err(%w[], "#!/bin/sh\n""#!shebang\n""#!ruby\n""puts __LINE__\n", %w[4], [], bug4118) assert_in_out_err(%w[-x], "#!/bin/sh\n""#!shebang\n""#!ruby\n""puts __LINE__\n", %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 + Tempfile.create(%w"pflag .rb") do |script| + code = "#!ruby -p" + script.puts(code) + script.close + assert_in_out_err([script.path, script.path], '', [code]) + end + Tempfile.create(%w"sflag .rb") do |script| + script.puts("#!ruby -s") + script.puts("p $abc") + script.close + assert_in_out_err([script.path, "-abc=foo"], '', ['"foo"']) + end end def test_sflag @@ -372,45 +417,78 @@ class TestRubyOptions < Test::Unit::TestCase end def test_indentation_check - Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| - t.puts "begin" - t.puts " end" - t.flush - err = ["#{t.path}:2: warning: mismatched indentations at 'end' with 'begin' at 1"] - assert_in_out_err(["-w", t.path], "", [], err) - assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err) + all_assertions do |a| + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) do |t| + [ + "begin", "if false", "for _ in []", "while false", + "def foo", "class X", "module M", + ["-> do", "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"] + t.rewind + t.truncate(0) + t.puts src + t.flush + assert_in_out_err(["-w", t.path], "", [], err) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err) + end - t.rewind - t.puts "# -*- warn-indent: false -*-" - t.puts "begin" - t.puts " end" - t.flush - assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]') + a.for("false directive with #{b}") do + t.rewind + t.truncate(0) + t.puts "# -*- warn-indent: false -*-" + t.puts src + t.flush + assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]') + end - err = ["#{t.path}:4: warning: mismatched indentations at 'end' with 'begin' at 3"] - t.rewind - t.puts "# -*- warn-indent: false -*-" - t.puts "# -*- warn-indent: true -*-" - t.puts "begin" - t.puts " end" - t.flush - assert_in_out_err(["-w", t.path], "", [], err, '[ruby-core:25442]') + a.for("false and true directives with #{b}") do + err = ["#{t.path}:4: warning: mismatched indentations at '#{e}' with '#{k}' at 3"] + t.rewind + t.truncate(0) + t.puts "# -*- warn-indent: false -*-" + t.puts "# -*- warn-indent: true -*-" + t.puts src + t.flush + assert_in_out_err(["-w", t.path], "", [], err, '[ruby-core:25442]') + end - err = ["#{t.path}:4: warning: mismatched indentations at 'end' with 'begin' at 2"] - t.rewind - t.puts "# -*- warn-indent: true -*-" - t.puts "begin" - t.puts "# -*- warn-indent: false -*-" - t.puts " end" - t.flush - assert_in_out_err(["-w", t.path], "", [], [], '[ruby-core:25442]') - } + a.for("false directives after #{b}") do + t.rewind + t.truncate(0) + t.puts "# -*- warn-indent: true -*-" + t.puts src[0] + t.puts "# -*- warn-indent: false -*-" + t.puts src[1] + t.flush + 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"] + t.rewind + t.truncate(0) + t.print "\u{feff}" + t.puts src + t.flush + assert_in_out_err(["-w", t.path], "", [], err) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err) + end + end + end + end end def test_notfound notexist = "./notexist.rb" - rubybin = EnvUtil.rubybin.dup - rubybin.gsub!(%r(/), '\\') if /mswin|mingw/ =~ RUBY_PLATFORM + dir, *rubybin = RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME', 'EXEEXT') + rubybin = "#{dir}/#{rubybin.join('')}" + rubybin.tr!('/', '\\') if /mswin|mingw/ =~ RUBY_PLATFORM rubybin = Regexp.quote(rubybin) pat = Regexp.quote(notexist) bug1573 = '[ruby-core:23717]' @@ -437,10 +515,14 @@ class TestRubyOptions < Test::Unit::TestCase } if File.respond_to? :symlink n2 = File.join(d, 't2') - File.symlink(n1, n2) - IO.popen([ruby, n2]) {|f| - assert_equal(n2, f.read) - } + begin + File.symlink(n1, n2) + rescue Errno::EACCES + else + IO.popen([ruby, n2]) {|f| + assert_equal(n2, f.read) + } + end end Dir.chdir(d) { n3 = '-e' @@ -458,8 +540,13 @@ class TestRubyOptions < Test::Unit::TestCase } end + 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 + end + def test_set_program_name - skip "platform dependent feature" if /linux|freebsd|netbsd|openbsd|darwin/ !~ RUBY_PLATFORM + skip "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") @@ -468,7 +555,7 @@ class TestRubyOptions < Test::Unit::TestCase ps = nil 10.times do sleep 0.1 - ps = `ps -p #{pid} -o command` + ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps end assert_match(/hello world/, ps) @@ -478,7 +565,14 @@ class TestRubyOptions < Test::Unit::TestCase end def test_setproctitle - skip "platform dependent feature" if /linux|freebsd|netbsd|openbsd|darwin/ !~ RUBY_PLATFORM + skip "platform dependent feature" unless defined?(PSCMD) and PSCMD + + assert_separately([], "#{<<-"{#"}\n#{<<-'};'}") + {# + assert_raise(ArgumentError) do + Process.setproctitle("hello\0") + end + }; with_tmpchdir do write_file("test-script", "$_0 = $0.dup; Process.setproctitle('hello world'); $0 == $_0 or Process.setproctitle('$0 changed!'); sleep 60") @@ -487,7 +581,7 @@ class TestRubyOptions < Test::Unit::TestCase ps = nil 10.times do sleep 0.1 - ps = `ps -p #{pid} -o command` + ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps end assert_match(/hello world/, ps) @@ -499,74 +593,86 @@ class TestRubyOptions < Test::Unit::TestCase module SEGVTest opts = {} if /mswin|mingw/ =~ RUBY_PLATFORM - additional = '[\s\w\.\']*' + additional = /[\s\w\.\']*/ else opts[:rlimit_core] = 0 - additional = "" + additional = nil end ExecOptions = opts.freeze - ExpectedStderr = - %r(\A - -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n - #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n - (?:--\s(?:.+\n)*\n)? - --\sControl\sframe\sinformation\s-+\n - (?:c:.*\n)* - (?: - --\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n - -e:1:in\s\`<main>\'\n - -e:1:in\s\`kill\'\n - )? - \n - (?: - --\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n - (?:(?:.*\s)?\[0x\h+\]\n)*\n - )? - (?m:.*) - \[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 - (?:#{additional}) - \z - )x + ExpectedStderrList = [ + %r( + -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n + )x, + %r( + #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n + )x, + %r( + (?:--\s(?:.+\n)*\n)? + --\sControl\sframe\sinformation\s-+\n + (?:c:.*\n)* + )x, + %r( + (?: + --\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n + -e:1:in\s\`<main>\'\n + -e:1:in\s\`kill\'\n + )? + )x, + %r( + (?: + --\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n + (?:(?:.*\s)?\[0x\h+\]\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 + )? + )x, + ] + ExpectedStderrList << additional if additional + end + + def assert_segv(args, message=nil) + test_stdin = "" + opt = SEGVTest::ExecOptions.dup + list = SEGVTest::ExpectedStderrList + + assert_in_out_err(args, test_stdin, //, list, encoding: "ASCII-8BIT", **opt) end def test_segv_test - opts = SEGVTest::ExecOptions.dup - expected_stderr = SEGVTest::ExpectedStderr - - assert_in_out_err(["--disable-gems", "-e", "Process.kill :SEGV, $$"], "", [], expected_stderr, nil, opts) + assert_segv(["--disable-gems", "-e", "Process.kill :SEGV, $$"]) end def test_segv_loaded_features - opts = SEGVTest::ExecOptions.dup - bug7402 = '[ruby-core:49573]' - status = assert_in_out_err(['-e', 'class Bogus; def to_str; exit true; end; end', - '-e', '$".clear', - '-e', '$".unshift Bogus.new', - '-e', '(p $"; abort) unless $".size == 1', - '-e', 'Process.kill :SEGV, $$'], - "", [], /#<Bogus:/, - nil, - opts) + + status = assert_segv(['-e', 'END {Process.kill :SEGV, $$}', + '-e', 'class Bogus; def to_str; exit true; end; end', + '-e', '$".clear', + '-e', '$".unshift Bogus.new', + '-e', '(p $"; abort) unless $".size == 1', + ]) assert_not_predicate(status, :success?, "segv but success #{bug7402}") end def test_segv_setproctitle - opts = SEGVTest::ExecOptions.dup - expected_stderr = SEGVTest::ExpectedStderr - bug7597 = '[ruby-dev:46786]' Tempfile.create(["test_ruby_test_bug7597", ".rb"]) {|t| t.write "f" * 100 t.flush - assert_in_out_err(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], - "", [], expected_stderr, bug7597, opts) + assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], bug7597) } end @@ -665,6 +771,92 @@ class TestRubyOptions < Test::Unit::TestCase end end + case RUBY_PLATFORM + when /mswin|mingw/ + def test_command_line_glob_nonascii + bug10555 = '[ruby-dev:48752] [Bug #10555]' + name = "\u{3042}.txt" + expected = name.encode("locale") rescue "?.txt" + with_tmpchdir do |dir| + open(name, "w") {} + assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [], + bug10555, encoding: "locale") + end + end + + def test_command_line_progname_nonascii + bug10555 = '[ruby-dev:48752] [Bug #10555]' + name = expected = nil + unless (0x80..0x10000).any? {|c| + name = c.chr(Encoding::UTF_8) + expected = name.encode("locale") rescue nil + } + skip "can't make locale name" + end + name << ".rb" + expected << ".rb" + with_tmpchdir do |dir| + open(name, "w") {|f| f.puts "puts File.basename($0)"} + assert_in_out_err([name], "", [expected], [], + bug10555, encoding: "locale") + end + end + + def test_command_line_glob_with_dir + bug10941 = '[ruby-core:68430] [Bug #10941]' + with_tmpchdir do |dir| + Dir.mkdir('test') + assert_in_out_err(["-e", "", "test/*"], "", [], [], bug10941) + end + end + + Ougai = %W[\u{68ee}O\u{5916}.txt \u{68ee 9d0e 5916}.txt \u{68ee 9dd7 5916}.txt] + def test_command_line_glob_noncodepage + 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: "?")} + assert_in_out_err(["-e", "puts ARGV", "*.txt"], "", ougai) + end + end + + def assert_e_script_encoding(str, args = []) + cmds = [ + EnvUtil::LANG_ENVS.inject({}) {|h, k| h[k] = ENV[k]; h}, + *args, + '-e', "s = '#{str}'", + '-e', 'puts s.encoding.name', + '-e', 'puts s.dump', + ] + assert_in_out_err(cmds, "", [str.encoding.name, str.dump], [], + "#{str.encoding}:#{str.dump} #{args.inspect}") + end + + # tested codepages: 437 850 852 855 932 65001 + # Since the codepage is shared all processes per conhost.exe, do + # not chcp, or parallel test may break. + def test_locale_codepage + locale = Encoding.find("locale") + list = %W"\u{c7} \u{452} \u{3066 3059 3068}" + list.each do |s| + assert_e_script_encoding(s, %w[-U]) + end + list.each do |s| + s = s.encode(locale) rescue next + assert_e_script_encoding(s) + assert_e_script_encoding(s, %W[-E#{locale.name}]) + end + end + when /cygwin/ + def test_command_line_non_ascii + assert_separately([{"LC_ALL"=>"ja_JP.SJIS"}, "-", "\u{3042}".encode("SJIS")], <<-"end;") + bug12184 = '[ruby-dev:49519] [Bug #12184]' + a = ARGV[0] + assert_equal([Encoding::SJIS, 130, 160], [a.encoding, *a.bytes], bug12184) + end; + end + end + def test_script_is_directory feature2408 = '[ruby-core:26925]' assert_in_out_err(%w[.], "", [], /Is a directory -- \./, feature2408) @@ -679,4 +871,126 @@ class TestRubyOptions < Test::Unit::TestCase bug7157 = '[ruby-core:47967]' assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157) end + + def assert_norun_with_rflag(*opt) + bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option" + stderr = [] + Tempfile.create(%w"bug10435- .rb") do |script| + dir, base = File.split(script.path) + script.puts "abort ':run'" + script.close + opts = ['-C', dir, '-r', "./#{base}", *opt] + _, e = assert_in_out_err([*opts, '-ep'], "", //) + stderr.concat(e) if e + stderr << "---" + _, e = assert_in_out_err([*opts, base], "", //) + stderr.concat(e) if e + end + assert_not_include(stderr, ":run", bug10435) + end + + def test_dump_syntax_with_rflag + assert_norun_with_rflag('-c') + assert_norun_with_rflag('--dump=syntax') + end + + def test_dump_yydebug_with_rflag + assert_norun_with_rflag('-y') + assert_norun_with_rflag('--dump=yydebug') + end + + def test_dump_parsetree_with_rflag + assert_norun_with_rflag('--dump=parsetree') + assert_norun_with_rflag('--dump=parsetree', '-e', '#frozen-string-literal: true') + end + + def test_dump_insns_with_rflag + assert_norun_with_rflag('--dump=insns') + end + + def test_frozen_string_literal + all_assertions do |a| + [["disable", "false"], ["enable", "true"]].each do |opt, exp| + %W[frozen_string_literal frozen-string-literal].each do |arg| + key = "#{opt}=#{arg}" + a.for(key) do + assert_in_out_err(["--disable=gems", "--#{key}"], 'p("foo".frozen?)', [exp]) + end + end + end + %W"disable enable".product(%W[false true]) do |opt, exp| + a.for("#{opt}=>#{exp}") do + assert_in_out_err(["-w", "--disable=gems", "--#{opt}=frozen-string-literal"], <<-"end;", [exp]) + #-*- frozen-string-literal: #{exp} -*- + p("foo".frozen?) + end; + end + end + end + end + + def test_frozen_string_literal_debug + with_debug_pat = /created at/ + wo_debug_pat = /can\'t modify frozen String \(FrozenError\)\n\z/ + frozen = [ + ["--enable-frozen-string-literal", true], + ["--disable-frozen-string-literal", false], + [nil, false], + ] + debugs = [ + ["--debug-frozen-string-literal", true], + ["--debug=frozen-string-literal", true], + ["--debug", true], + [nil, false], + ] + opts = ["--disable=gems"] + 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) + 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 + if __FILE__.encoding == __dir__.encoding + p true + else + puts "__FILE__: \#{__FILE__.encoding}, __dir__: \#{__dir__.encoding}" + end + END + end + 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_argv_tainted + assert_separately(%w[- arg], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_predicate(ARGV[0], :tainted?, '[ruby-dev:50596] [Bug #14941]') + end; + end end diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb index 613cfe2161..7673d8dfbe 100644 --- a/test/ruby/test_rubyvm.rb +++ b/test/ruby/test_rubyvm.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class TestRubyVM < Test::Unit::TestCase def test_stat assert_kind_of Hash, RubyVM.stat - assert_kind_of Fixnum, RubyVM.stat[:global_method_state] + assert_kind_of Integer, RubyVM.stat[:global_method_state] RubyVM.stat(stat = {}) assert_not_empty stat @@ -12,5 +13,6 @@ class TestRubyVM < Test::Unit::TestCase 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 end diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 2e9caa3e8c..3085c0902a 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestSetTraceFunc < Test::Unit::TestCase def setup @@ -35,9 +35,9 @@ class TestSetTraceFunc < Test::Unit::TestCase events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) - assert_equal(["c-call", 4, :+, Fixnum], + assert_equal(["c-call", 4, :+, Integer], events.shift) - assert_equal(["c-return", 4, :+, Fixnum], + assert_equal(["c-return", 4, :+, Integer], events.shift) assert_equal(["line", 5, __method__, self.class], events.shift) @@ -73,9 +73,9 @@ class TestSetTraceFunc < Test::Unit::TestCase events.shift) assert_equal(["line", 5, :add, self.class], events.shift) - assert_equal(["c-call", 5, :+, Fixnum], + assert_equal(["c-call", 5, :+, Integer], events.shift) - assert_equal(["c-return", 5, :+, Fixnum], + assert_equal(["c-return", 5, :+, Integer], events.shift) assert_equal(["return", 6, :add, self.class], events.shift) @@ -239,8 +239,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], @@ -285,14 +283,12 @@ class TestSetTraceFunc < Test::Unit::TestCase [["c-return", 1, :set_trace_func, Kernel], ["line", 4, __method__, self.class], - ["c-call", 4, :any?, Enumerable], - ["c-call", 4, :each, Array], + ["c-call", 4, :any?, Array], ["line", 4, __method__, self.class], - ["c-return", 4, :each, Array], - ["c-return", 4, :any?, Enumerable], + ["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 @@ -355,8 +351,8 @@ class TestSetTraceFunc < Test::Unit::TestCase ["c-return", 8, :new, Class], ["call", 4, :foo, ThreadTraceInnerClass], ["line", 5, :foo, ThreadTraceInnerClass], - ["c-call", 5, :+, Fixnum], - ["c-return", 5, :+, Fixnum], + ["c-call", 5, :+, Integer], + ["c-return", 5, :+, Integer], ["return", 6, :foo, ThreadTraceInnerClass], ["line", 9, __method__, self.class], ["c-call", 9, :set_trace_func, Thread]].each do |e| @@ -467,7 +463,7 @@ class TestSetTraceFunc < Test::Unit::TestCase EOF self.class.class_eval{remove_const(:XYZZY)} ensure - trace.disable if trace && trace.enabled? + trace.disable if trace&.enabled? end answer_events = [ @@ -566,7 +562,7 @@ class TestSetTraceFunc < Test::Unit::TestCase ms = [events1, answer_events].map{|evs| evs.map{|e| - "#{e[0]} - #{e[2]}:#{e[1]} id; #{e[4]}" + "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" } } @@ -575,6 +571,7 @@ class TestSetTraceFunc < Test::Unit::TestCase "#{a} <-> #{b}" end }.compact.join("\n") + answer_events.zip(events1){|answer, event| assert_equal answer, event, mesg } @@ -640,16 +637,19 @@ 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{ + trace.enable{|*a| + args = a foo } foo assert_equal([:foo], ary) + assert_equal([], args) trace = TracePoint.new{} begin @@ -664,17 +664,20 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_tracepoint_disable ary = [] + args = nil trace = TracePoint.trace(:call){|tp| next if !target_thread? ary << tp.method_id } foo - trace.disable{ + trace.disable{|*a| + args = a foo } foo trace.disable assert_equal([:foo, :foo], ary) + assert_equal([], args) trace = TracePoint.new{} trace.enable{ @@ -769,10 +772,10 @@ class TestSetTraceFunc < Test::Unit::TestCase # pp events # expected_events = [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], - [:c_call, :times, Integer, Fixnum, nil], + [:c_call, :times, Integer, Integer, nil], [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3], - [:c_return, :times, Integer, Fixnum, 1], + [:c_return, :times, Integer, Integer, 1], [:call, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4], @@ -852,6 +855,21 @@ 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{ @@ -979,6 +997,27 @@ class TestSetTraceFunc < Test::Unit::TestCase end end + def test_trace_point_binding_after_break + bug10689 = '[ruby-dev:48797]' + assert_in_out_err([], <<-INPUT, [], [], bug10689) + class Bug + include Enumerable + + def each + [0].each do + yield + end + end + end + + TracePoint.trace(:c_return) do |tp| + tp.binding + end + + Bug.new.all? { false } + INPUT + end + def test_tracepoint_b_return_with_next n = 0 TracePoint.new(:b_return){ @@ -1021,7 +1060,7 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_isolated_raise_in_trace bug9088 = '[ruby-dev:47793] [Bug #9088]' - assert_ruby_status([], <<-END, bug9088) + assert_in_out_err([], <<-END, [], [], bug9088) set_trace_func proc {raise rescue nil} 1.times {break} END @@ -1030,6 +1069,7 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_a_call events = [] TracePoint.new(:a_call){|tp| + next if !target_thread? events << tp.event }.enable{ 1.times{ @@ -1051,6 +1091,7 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_a_return events = [] TracePoint.new(:a_return){|tp| + next if !target_thread? events << tp.event }.enable{ 1.times{ @@ -1074,6 +1115,7 @@ class TestSetTraceFunc < Test::Unit::TestCase events = [] assert !defined?(MISSING_CONSTANT_59398) TracePoint.new(:c_call, :c_return, :call, :return){|tp| + next if !target_thread? next unless tp.defined_class == Module # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name # but this only happens when running the full test suite @@ -1104,6 +1146,7 @@ class TestSetTraceFunc < Test::Unit::TestCase events = [] aliased = AliasedRubyMethod.new TracePoint.new(:call, :return){|tp| + next if !target_thread? events << [tp.event, tp.method_id] }.enable{ aliased.bar @@ -1122,14 +1165,15 @@ class TestSetTraceFunc < Test::Unit::TestCase events = [] aliased = AliasedCMethod.new TracePoint.new(:call, :return, :c_call, :c_return){|tp| + next if !target_thread? events << [tp.event, tp.method_id] }.enable{ aliased.size } assert_equal([ [:call, :size], - [:c_call, :original_size], - [:c_return, :original_size], + [:c_call, :size], + [:c_return, :size], [:return, :size] ], events, "should use alias method name for tracing c methods") end @@ -1139,6 +1183,7 @@ class TestSetTraceFunc < Test::Unit::TestCase events = [] assert !respond_to?(:missing_method_59398) TracePoint.new(:c_call, :c_return, :call, :return){|tp| + next if !target_thread? next unless tp.defined_class == BasicObject # rake/ext/module.rb aliases :const_missing and Ruby uses the aliased name # but this only happens when running the full test suite @@ -1255,16 +1300,49 @@ class TestSetTraceFunc < Test::Unit::TestCase end def test_recursive - assert_ruby_status [], %q{ - stack = [] + assert_in_out_err([], %q{\ TracePoint.new(:c_call){|tp| - p 2 - stack << tp.method_id + p tp.method_id }.enable{ p 1 } - raise if stack != [:p, :hash, :inspect] - }, '[Bug #9940]' + }, %w[:p :to_s 1], [], '[Bug #9940]') + end + + def method_prefix event + case event + when :call, :return + :n + when :c_call, :c_return + :c + when :b_call, :b_return + :b + end + end + + def method_label tp + "#{method_prefix(tp.event)}##{tp.method_id}" + end + + def assert_consistent_call_return message='', check_events: nil + check_events ||= %i(a_call a_return) + call_stack = [] + + TracePoint.new(*check_events){|tp| + next unless target_thread? + + case tp.event.to_s + when /call/ + call_stack << method_label(tp) + when /return/ + frame = call_stack.pop + assert_equal(frame, method_label(tp)) + end + }.enable do + yield + end + + assert_equal true, call_stack.empty? end def method_test_rescue_should_not_cause_b_return @@ -1284,133 +1362,40 @@ class TestSetTraceFunc < Test::Unit::TestCase end def test_rescue_and_ensure_should_not_cause_b_return - curr_thread = Thread.current - trace = TracePoint.new(:b_call, :b_return){ - next if curr_thread != Thread.current - flunk("Should not reach here because there is no block.") - } - - begin - trace.enable + assert_consistent_call_return '[Bug #9957]' do method_test_rescue_should_not_cause_b_return begin method_test_ensure_should_not_cause_b_return rescue # ignore end - ensure - trace.disable end end define_method(:method_test_argument_error_on_bmethod){|correct_key: 1|} def test_argument_error_on_bmethod - events = [] - curr_thread = Thread.current - TracePoint.new(:call, :return){|tp| - next if curr_thread != Thread.current - events << [tp.event, tp.method_id] - }.enable do + assert_consistent_call_return '[Bug #9959]' do begin method_test_argument_error_on_bmethod(wrong_key: 2) - rescue => e + rescue # ignore end end - - assert_equal [], events # should be empty. - end - - def method_prefix event - case event - when :call, :return - :n - when :c_call, :c_return - :c - when :b_call, :b_return - :b - end - end - - def method_label tp - "#{method_prefix(tp.event)}##{tp.method_id}" - end - - def assert_consistent_call_return message='', check_events: nil - check_events ||= %i(a_call a_return) - call_events = [] - return_events = [] - - TracePoint.new(*check_events){|tp| - next unless target_thread? - - case tp.event.to_s - when /call/ - call_events << method_label(tp) - when /return/ - return_events << method_label(tp) - end - }.enable do - yield - end - - assert_equal false, call_events.empty? - assert_equal false, return_events.empty? - assert_equal call_events, return_events.reverse, message end def test_rb_rescue - events = [] - curr_thread = Thread.current - TracePoint.new(:a_call, :a_return){|tp| - next if curr_thread != Thread.current - events << [tp.event, tp.method_id] - }.enable do + assert_consistent_call_return '[Bug #9961]' do begin -Numeric.new - rescue => e + rescue # ignore end end - - assert_equal [ - [:b_call, :test_rb_rescue], - [:c_call, :new], - [:c_call, :initialize], - [:c_return, :initialize], - [:c_return, :new], - [:c_call, :-@], - [:c_call, :coerce], - [:c_call, :new], - [:c_call, :initialize], - [:c_return, :initialize], - [:c_return, :new], - [:c_call, :exception], - [:c_return, :exception], - [:c_call, :backtrace], - [:c_return, :backtrace], - [:c_return, :coerce], # don't miss it! - [:c_call, :to_s], - [:c_return, :to_s], - [:c_call, :to_s], - [:c_return, :to_s], - [:c_call, :new], - [:c_call, :initialize], - [:c_return, :initialize], - [:c_return, :new], - [:c_call, :exception], - [:c_return, :exception], - [:c_call, :backtrace], - [:c_return, :backtrace], - [:c_return, :-@], - [:c_call, :===], - [:c_return, :===], - [:b_return, :test_rb_rescue]], events end def test_b_call_with_redo - assert_consistent_call_return do + assert_consistent_call_return '[Bug #9964]' do i = 0 1.times{ break if (i+=1) > 10 @@ -1433,6 +1418,25 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal [__LINE__ - 3, __LINE__ - 2], lines, 'Bug #10449' end + def test_elsif_line_event + bug10763 = '[ruby-core:67720] [Bug #10763]' + lines = [] + line = nil + + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno if line + }.enable{ + line = __LINE__ + if !line + 1 + elsif line + 2 + end + } + assert_equal [line+1, line+3, line+4], lines, bug10763 + end + class Bug10724 def initialize loop{return} @@ -1444,12 +1448,414 @@ class TestSetTraceFunc < Test::Unit::TestCase evs = [] TracePoint.new(:call, :return){|tp| - return if Thread.current != target_th + next unless target_thread? evs << tp.event }.enable{ - a = Bug10724.new + Bug10724.new } assert_equal([:call, :return], evs) end + + require 'fiber' + def test_fiber_switch + # test for resume/yield + evs = [] + TracePoint.new(:fiber_switch){|tp| + next unless target_thread? + evs << tp.event + }.enable{ + f = Fiber.new{ + Fiber.yield + Fiber.yield + Fiber.yield + } + f.resume + f.resume + f.resume + f.resume + begin + f.resume + rescue FiberError + end + } + assert_equal 8, evs.size + evs.each{|ev| + assert_equal ev, :fiber_switch + } + + # test for transfer + evs = [] + TracePoint.new(:fiber_switch){|tp| + next unless target_thread? + evs << tp.event + }.enable{ + f1 = f2 = nil + f1 = Fiber.new{ + f2.transfer + f2.transfer + Fiber.yield :ok + } + f2 = Fiber.new{ + f1.transfer + f1.transfer + } + assert_equal :ok, f1.resume + } + assert_equal 6, evs.size + evs.each{|ev| + assert_equal ev, :fiber_switch + } + end + + def test_tracepoint_callee_id + events = [] + capture_events = Proc.new{|tp| + next unless target_thread? + events << [tp.event, tp.method_id, tp.callee_id] + } + + o = Class.new{ + def m + raise + end + alias alias_m m + }.new + TracePoint.new(:raise, :call, :return, &capture_events).enable{ + o.alias_m rescue nil + } + assert_equal [[:call, :m, :alias_m], [:raise, :m, :alias_m], [:return, :m, :alias_m]], events + events.clear + + o = Class.new{ + alias alias_raise raise + def m + alias_raise + end + }.new + TracePoint.new(:c_return, &capture_events).enable{ + o.m rescue nil + } + assert_equal [:c_return, :raise, :alias_raise], events[0] + events.clear + + o = Class.new(String){ + include Enumerable + alias each each_char + }.new('foo') + TracePoint.new(:c_return, &capture_events).enable{ + o.find{true} + } + assert_equal [:c_return, :each_char, :each], events[0] + events.clear + + o = Class.new{ + define_method(:m){} + alias alias_m m + }.new + TracePoint.new(:call, :return, &capture_events).enable{ + o.alias_m + } + assert_equal [[:call, :m, :alias_m], [:return, :m, :alias_m]], events + events.clear + + o = Class.new{ + def m + tap{return} + end + alias alias_m m + }.new + TracePoint.new(:return, &capture_events).enable{ + o.alias_m + } + assert_equal [[:return, :m, :alias_m]], events + events.clear + + o = Class.new{ + define_method(:m){raise} + alias alias_m m + }.new + TracePoint.new(:b_return, :return, &capture_events).enable{ + o.alias_m rescue nil + } + assert_equal [[:b_return, :m, :alias_m], [:return, :m, :alias_m]], events[0..1] + events.clear + + o = Class.new{ + define_method(:m){tap{return}} + alias alias_m m + }.new + TracePoint.new(:b_return, &capture_events).enable{ + o.alias_m + } + assert_equal [[:b_return, :m, :alias_m], [:b_return, :m, :alias_m]], events[0..1] + events.clear + + o = Class.new{ + alias alias_tap tap + define_method(:m){alias_tap{return}} + }.new + TracePoint.new(:c_return, &capture_events).enable{ + o.m + } + assert_equal [[:c_return, :tap, :alias_tap]], events + events.clear + + c = Class.new{ + alias initialize itself + } + TracePoint.new(:c_call, &capture_events).enable{ + c.new + } + assert_equal [:c_call, :itself, :initialize], events[1] + events.clear + + o = Class.new{ + alias alias_itself itself + }.new + TracePoint.new(:c_call, :c_return, &capture_events).enable{ + o.alias_itself + } + assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events + events.clear + end + + # tests for `return_value` with non-local exit [Bug #13369] + + def tp_return_value mid + ary = [] + TracePoint.new(:return, :b_return){|tp| next if !target_thread?; ary << [tp.event, tp.method_id, tp.return_value]}.enable{ + send mid + } + ary.pop # last b_return event is not required. + ary + end + + def f_raise + raise + rescue + return :f_raise_return + end + + def f_iter1 + yield + return :f_iter1_return + end + + def f_iter2 + yield + return :f_iter2_return + end + + def f_return_in_iter + f_iter1 do + f_iter2 do + return :f_return_in_iter_return + end + end + 2 + end + + def f_break_in_iter + f_iter1 do + f_iter2 do + break :f_break_in_iter_break + end + :f_iter1_block_value + end + :f_break_in_iter_return + end + + def test_return_value_with_rescue + assert_equal [[:return, :f_raise, :f_raise_return]], + tp_return_value(:f_raise), + '[Bug #13369]' + + assert_equal [[:b_return, :f_return_in_iter, nil], + [:return, :f_iter2, nil], + [:b_return, :f_return_in_iter, nil], + [:return, :f_iter1, nil], + [:return, :f_return_in_iter, :f_return_in_iter_return]], + tp_return_value(:f_return_in_iter), + '[Bug #13369]' + + assert_equal [[:b_return, :f_break_in_iter, :f_break_in_iter_break], + [:return, :f_iter2, nil], + [:b_return, :f_break_in_iter, :f_iter1_block_value], + [:return, :f_iter1, :f_iter1_return], + [:return, :f_break_in_iter, :f_break_in_iter_return]], + tp_return_value(:f_break_in_iter), + '[Bug #13369]' + end + + define_method(:f_last_defined) do + :f_last_defined + end + + define_method(:f_return_defined) do + return :f_return_defined + end + + define_method(:f_break_defined) do + return :f_break_defined + end + + define_method(:f_raise_defined) do + raise + rescue + return :f_raise_defined + end + + define_method(:f_break_in_rescue_defined) do + raise + rescue + break :f_break_in_rescue_defined + end + + def test_return_value_with_rescue_and_defined_methods + assert_equal [[:b_return, :f_last_defined, :f_last_defined], + [:return, :f_last_defined, :f_last_defined]], + tp_return_value(:f_last_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_return_defined, nil], # current limitation + [:return, :f_return_defined, :f_return_defined]], + tp_return_value(:f_return_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_break_defined, nil], + [:return, :f_break_defined, :f_break_defined]], + tp_return_value(:f_break_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_raise_defined, nil], + [: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], + [:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]], + tp_return_value(:f_break_in_rescue_defined), + '[Bug #13369]' + end + + def f_iter + yield + end + + def f_break_in_rescue + f_iter do + begin + raise + rescue + break :b + end + end + :f_break_in_rescue_return_value + end + + def test_break_with_rescue + assert_equal [[:b_return, :f_break_in_rescue, :b], + [:return, :f_iter, nil], + [:return, :f_break_in_rescue, :f_break_in_rescue_return_value]], + tp_return_value(:f_break_in_rescue), + '[Bug #13369]' + end + + def test_trace_point_raising_exception_in_bmethod_call + bug13705 = '[ruby-dev:50162]' + assert_normal_exit %q{ + define_method(:m) {} + + tp = TracePoint.new(:call) do + next unless target_thread? + raise '' + end + + tap do + tap do + begin + tp.enable + m + rescue + end + end + end + }, bug13705 + end + + 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 = 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 = 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' + + 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 ["c-return", base_line + 31], events[0] + assert_equal ["line", base_line + 32], events[1] + assert_equal ["line", base_line + 33], events[2] + assert_equal ["call", base_line + -6], events[3] + assert_equal ["return", base_line + -4], events[4] + assert_equal ["line", base_line + 34], events[5] + assert_equal ["line", base_line + 35], events[6] + assert_equal ["c-call", base_line + 35], events[7] # Thread.current + assert_equal ["c-return", base_line + 35], events[8] # Thread.current + assert_equal ["c-call", base_line + 35], events[9] # Thread#set_trace_func + assert_equal nil, events[10] + end end diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index d3e8f864c9..2d98b0b564 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' require 'tempfile' -require_relative 'envutil' class TestSignal < Test::Unit::TestCase def test_signal @@ -73,12 +73,14 @@ class TestSignal < Test::Unit::TestCase def test_invalid_signal_name assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.kill("\u{30eb 30d3 30fc}", $$) } end if Process.respond_to?(:kill) def test_signal_exception assert_raise(ArgumentError) { SignalException.new } assert_raise(ArgumentError) { SignalException.new(-1) } assert_raise(ArgumentError) { SignalException.new(:XXXXXXXXXX) } + 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) @@ -161,11 +163,28 @@ 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") } ensure Signal.trap(:INT, oldtrap) if oldtrap end end if Process.respond_to?(:kill) + %w"KILL STOP".each do |sig| + if Signal.list.key?(sig) + define_method("test_trap_uncatchable_#{sig}") do + assert_raise(Errno::EINVAL, "SIG#{sig} is not allowed to be caught") { Signal.trap(sig) {} } + end + end + end + + def test_sigexit + assert_in_out_err([], 'Signal.trap(:EXIT) {print "OK"}', ["OK"]) + assert_in_out_err([], 'Signal.trap("EXIT") {print "OK"}', ["OK"]) + assert_in_out_err([], 'Signal.trap(:SIGEXIT) {print "OK"}', ["OK"]) + assert_in_out_err([], 'Signal.trap("SIGEXIT") {print "OK"}', ["OK"]) + assert_in_out_err([], 'Signal.trap(0) {print "OK"}', ["OK"]) + end + def test_kill_immediately_before_termination Signal.list[sig = "USR1"] or sig = "INT" assert_in_out_err(["-e", <<-"end;"], "", %w"foo") @@ -174,30 +193,12 @@ class TestSignal < Test::Unit::TestCase end; end if Process.respond_to?(:kill) - def test_signal_requiring - t = Tempfile.new(%w"require_ensure_test .rb") - t.puts "sleep" - t.close - error = IO.popen([EnvUtil.rubybin, "-e", <<EOS, t.path, :err => File::NULL]) do |child| -trap(:INT, "DEFAULT") -th = Thread.new do - begin - require ARGV[0] - ensure - err = $! ? [$!, $!.backtrace] : $! - Marshal.dump(err, STDOUT) - STDOUT.flush - end -end -Thread.pass while th.running? -Process.kill(:INT, $$) -th.join -EOS - Marshal.load(child) - end - t.close! - assert_nil(error) - end if Process.respond_to?(:kill) + def test_trap_system_default + assert_separately([], <<-End) + trap(:QUIT, "SYSTEM_DEFAULT") + assert_equal("SYSTEM_DEFAULT", trap(:QUIT, "DEFAULT")) + End + end if Signal.list.key?('QUIT') def test_reserved_signal assert_raise(ArgumentError) { @@ -218,6 +219,15 @@ EOS end def test_signame + Signal.list.each do |name, num| + assert_equal(num, Signal.list[Signal.signame(num)], name) + end + assert_nil(Signal.signame(-1)) + signums = Signal.list.invert + assert_nil(Signal.signame((1..1000).find {|num| !signums[num]})) + end + + def test_signame_delivered 10.times do IO.popen([EnvUtil.rubybin, "-e", <<EOS, :err => File::NULL]) do |child| Signal.trap("INT") do |signo| @@ -291,4 +301,24 @@ EOS assert_ruby_status(['-e', 'Process.kill(:CONT, $$)']) end end if Process.respond_to?(:kill) + + def test_signal_list_dedupe_keys + a = Signal.list.keys.map(&:object_id).sort + b = Signal.list.keys.map(&:object_id).sort + assert_equal a, b + end + + def test_self_stop + assert_ruby_status([], <<-'end;') + begin + fork{ + sleep 1 + Process.kill(:CONT, Process.ppid) + } + Process.kill(:STOP, Process.pid) + rescue NotImplementedError + # ok + end + end; + end end diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb index fdf08aafa1..61002b8b18 100644 --- a/test/ruby/test_sleep.rb +++ b/test/ruby/test_sleep.rb @@ -1,19 +1,14 @@ +# frozen_string_literal: false require 'test/unit' +require 'etc' class TestSleep < Test::Unit::TestCase def test_sleep_5sec GC.disable - start = Time.now + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) sleep 5 - slept = Time.now-start - bottom = - case RUBY_PLATFORM - when /linux/ - 4.98 if /Linux ([\d.]+)/ =~ `uname -sr` && ($1.split('.')<=>%w/2 6 18/)<1 - when /mswin|mingw/ - 4.98 - end - bottom ||= 5.0 + slept = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start + bottom = 5.0 assert_operator(slept, :>=, bottom) assert_operator(slept, :<=, 6.0, "[ruby-core:18015]: longer than expected") ensure diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb index 9fc3fd059f..a07ac7908b 100644 --- a/test/ruby/test_sprintf.rb +++ b/test/ruby/test_sprintf.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestSprintf < Test::Unit::TestCase @@ -83,6 +84,18 @@ class TestSprintf < Test::Unit::TestCase assert_equal("NaN", sprintf("%-f", nan)) assert_equal("+NaN", sprintf("%+f", nan)) + assert_equal("NaN", sprintf("%3f", nan)) + assert_equal("NaN", sprintf("%-3f", nan)) + assert_equal("+NaN", sprintf("%+3f", nan)) + + assert_equal(" NaN", sprintf("% 3f", nan)) + assert_equal(" NaN", sprintf("%- 3f", nan)) + assert_equal("+NaN", sprintf("%+ 3f", nan)) + + assert_equal(" NaN", sprintf("% 03f", nan)) + assert_equal(" NaN", sprintf("%- 03f", nan)) + assert_equal("+NaN", sprintf("%+ 03f", nan)) + assert_equal(" NaN", sprintf("%8f", nan)) assert_equal("NaN ", sprintf("%-8f", nan)) assert_equal(" +NaN", sprintf("%+8f", nan)) @@ -106,6 +119,26 @@ class TestSprintf < Test::Unit::TestCase assert_equal("Inf", sprintf("%-f", inf)) assert_equal("+Inf", sprintf("%+f", inf)) + assert_equal(" Inf", sprintf("% f", inf)) + assert_equal(" Inf", sprintf("%- f", inf)) + assert_equal("+Inf", sprintf("%+ f", inf)) + + assert_equal(" Inf", sprintf("% 0f", inf)) + assert_equal(" Inf", sprintf("%- 0f", inf)) + assert_equal("+Inf", sprintf("%+ 0f", inf)) + + assert_equal("Inf", sprintf("%3f", inf)) + assert_equal("Inf", sprintf("%-3f", inf)) + assert_equal("+Inf", sprintf("%+3f", inf)) + + assert_equal(" Inf", sprintf("% 3f", inf)) + assert_equal(" Inf", sprintf("%- 3f", inf)) + assert_equal("+Inf", sprintf("%+ 3f", inf)) + + assert_equal(" Inf", sprintf("% 03f", inf)) + assert_equal(" Inf", sprintf("%- 03f", inf)) + assert_equal("+Inf", sprintf("%+ 03f", inf)) + assert_equal(" Inf", sprintf("%8f", inf)) assert_equal("Inf ", sprintf("%-8f", inf)) assert_equal(" +Inf", sprintf("%+8f", inf)) @@ -126,6 +159,26 @@ class TestSprintf < Test::Unit::TestCase assert_equal("-Inf", sprintf("%-f", -inf)) assert_equal("-Inf", sprintf("%+f", -inf)) + assert_equal("-Inf", sprintf("% f", -inf)) + assert_equal("-Inf", sprintf("%- f", -inf)) + assert_equal("-Inf", sprintf("%+ f", -inf)) + + assert_equal("-Inf", sprintf("% 0f", -inf)) + assert_equal("-Inf", sprintf("%- 0f", -inf)) + assert_equal("-Inf", sprintf("%+ 0f", -inf)) + + assert_equal("-Inf", sprintf("%4f", -inf)) + assert_equal("-Inf", sprintf("%-4f", -inf)) + assert_equal("-Inf", sprintf("%+4f", -inf)) + + assert_equal("-Inf", sprintf("% 4f", -inf)) + assert_equal("-Inf", sprintf("%- 4f", -inf)) + assert_equal("-Inf", sprintf("%+ 4f", -inf)) + + assert_equal("-Inf", sprintf("% 04f", -inf)) + assert_equal("-Inf", sprintf("%- 04f", -inf)) + assert_equal("-Inf", sprintf("%+ 04f", -inf)) + assert_equal(" -Inf", sprintf("%8f", -inf)) assert_equal("-Inf ", sprintf("%-8f", -inf)) assert_equal(" -Inf", sprintf("%+8f", -inf)) @@ -148,6 +201,43 @@ class TestSprintf < Test::Unit::TestCase assert_equal(" Inf", sprintf("% e", inf), '[ruby-dev:34002]') end + def test_bignum + assert_match(/\A10{120}\.0+\z/, sprintf("%f", 100**60)) + assert_match(/\A10{180}\.0+\z/, sprintf("%f", 1000**60)) + end + + def test_rational + assert_match(/\A0\.10+\z/, sprintf("%.60f", 0.1r)) + assert_match(/\A0\.010+\z/, sprintf("%.60f", 0.01r)) + assert_match(/\A0\.0010+\z/, sprintf("%.60f", 0.001r)) + assert_match(/\A0\.3+\z/, sprintf("%.60f", 1/3r)) + assert_match(/\A1\.20+\z/, sprintf("%.60f", 1.2r)) + + ["", *"0".."9"].each do |len| + ["", *".0"..".9"].each do |prec| + ['', '+', '-', ' ', '0', '+0', '-0', ' 0', '+ ', '- ', '+ 0', '- 0'].each do |flags| + fmt = "%#{flags}#{len}#{prec}f" + [0, 0.1, 0.01, 0.001, 1.001, 100.0, 100.001, 10000000000.0, 0.00000000001, 1/3r, 2/3r, 1.2r, 10r].each do |num| + assert_equal(sprintf(fmt, num.to_f), sprintf(fmt, num.to_r), "sprintf(#{fmt.inspect}, #{num.inspect}.to_r)") + assert_equal(sprintf(fmt, -num.to_f), sprintf(fmt, -num.to_r), "sprintf(#{fmt.inspect}, #{(-num).inspect}.to_r)") if num > 0 + end + end + end + end + + bug11766 = '[ruby-core:71806] [Bug #11766]' + assert_equal("x"*10+" 1.0", sprintf("x"*10+"%8.1f", 1r), bug11766) + end + + def test_rational_precision + assert_match(/\A0\.\d{600}\z/, sprintf("%.600f", 600**~60)) + end + + def test_hash + options = {:capture=>/\d+/} + assert_equal("with options {:capture=>/\\d+/}", sprintf("with options %p" % options)) + end + def test_invalid # Star precision before star width: assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%.**d", 5, 10, 1)} @@ -244,6 +334,19 @@ class TestSprintf < Test::Unit::TestCase assert_equal(" 0x1.000p+0", sprintf("%20.3a", 1), bug3979) end + def test_float_prec + assert_equal("5.00", sprintf("%.2f",5.005)) + assert_equal("5.02", sprintf("%.2f",5.015)) + assert_equal("5.02", sprintf("%.2f",5.025)) + assert_equal("5.04", sprintf("%.2f",5.035)) + bug12889 = '[ruby-core:77864] [Bug #12889]' + assert_equal("1234567892", sprintf("%.0f", 1234567891.99999)) + assert_equal("1234567892", sprintf("%.0f", 1234567892.49999)) + assert_equal("1234567892", sprintf("%.0f", 1234567892.50000)) + assert_equal("1234567894", sprintf("%.0f", 1234567893.50000)) + assert_equal("1234567892", sprintf("%.0f", 1234567892.00000), bug12889) + end + BSIZ = 120 def test_skip @@ -309,12 +412,28 @@ class TestSprintf < Test::Unit::TestCase def test_star assert_equal("-1 ", sprintf("%*d", -3, -1)) + assert_raise_with_message(ArgumentError, /width too big/) { + sprintf("%*999999999999999999999999999999999999999999999999999999999999$d", 1) + } + assert_raise_with_message(ArgumentError, /prec too big/) { + sprintf("%.*999999999999999999999999999999999999999999999999999999999999$d", 1) + } end def test_escape assert_equal("%" * BSIZ, sprintf("%%" * BSIZ)) end + def test_percent_sign_at_end + assert_raise_with_message(ArgumentError, "incomplete format specifier; use %% (double %) instead") do + sprintf("%") + end + + assert_raise_with_message(ArgumentError, "incomplete format specifier; use %% (double %) instead") do + sprintf("abc%") + end + end + def test_rb_sprintf assert_match(/^#<TestSprintf::T012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789:0x[0-9a-f]+>$/, T012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.new.inspect) @@ -333,7 +452,10 @@ class TestSprintf < Test::Unit::TestCase assert_raise_with_message(ArgumentError, "named<key2> after numbered") {sprintf("%1$<key2>s", :key => "value")} assert_raise_with_message(ArgumentError, "named<key2> after unnumbered(2)") {sprintf("%s%s%<key2>s", "foo", "bar", :key => "value")} assert_raise_with_message(ArgumentError, "named<key2> after <key>") {sprintf("%<key><key2>s", :key => "value")} - assert_raise_with_message(KeyError, "key<key> not found") {sprintf("%<key>s", {})} + h = {} + e = assert_raise_with_message(KeyError, "key<key> not found") {sprintf("%<key>s", h)} + assert_same(h, e.receiver) + assert_equal(:key, e.key) end def test_named_untyped_enc @@ -378,4 +500,36 @@ class TestSprintf < Test::Unit::TestCase assert_equal(enc, e.message.encoding) end end + + def test_named_default + h = Hash.new('world') + assert_equal("hello world", "hello %{location}" % h) + assert_equal("hello world", "hello %<location>s" % h) + end + + def test_named_with_nil + h = { key: nil, key2: "key2_val" } + assert_equal("key is , key2 is key2_val", "key is %{key}, key2 is %{key2}" % h) + end + + def test_width_underflow + bug = 'https://github.com/mruby/mruby/issues/3347' + assert_equal("!", sprintf("%*c", 0, ?!.ord), bug) + end + + def test_negative_width_overflow + assert_raise_with_message(ArgumentError, /too big/) do + sprintf("%*s", RbConfig::LIMITS["INT_MIN"], "") + 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 + end end diff --git a/test/ruby/test_sprintf_comb.rb b/test/ruby/test_sprintf_comb.rb index c58ddf4f15..4113113030 100644 --- a/test/ruby/test_sprintf_comb.rb +++ b/test/ruby/test_sprintf_comb.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require_relative 'allpairs' diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 4942621392..9574ed31c9 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1,9 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' - -# use of $= is deprecated after 1.7.1 -def pre_1_7_1 -end class TestString < Test::Unit::TestCase ENUMERATOR_WANTARRAY = RUBY_VERSION >= "3.0.0" @@ -16,12 +12,94 @@ class TestString < Test::Unit::TestCase super end - def S(str) - @cls.new(str) + def S(*args) + @cls.new(*args) end def test_s_new - assert_equal("RUBY", S("RUBY")) + assert_equal("", S()) + assert_equal(Encoding::ASCII_8BIT, S().encoding) + + assert_equal("", S("")) + assert_equal(__ENCODING__, S("").encoding) + + src = "RUBY" + assert_equal(src, S(src)) + assert_equal(__ENCODING__, S(src).encoding) + + src.force_encoding("euc-jp") + assert_equal(src, S(src)) + assert_equal(Encoding::EUC_JP, S(src).encoding) + + + assert_equal("", S(encoding: "euc-jp")) + assert_equal(Encoding::EUC_JP, S(encoding: "euc-jp").encoding) + + assert_equal("", S("", encoding: "euc-jp")) + assert_equal(Encoding::EUC_JP, S("", encoding: "euc-jp").encoding) + + src = "RUBY" + assert_equal(src, S(src, encoding: "euc-jp")) + assert_equal(Encoding::EUC_JP, S(src, encoding: "euc-jp").encoding) + + src.force_encoding("euc-jp") + assert_equal(src, S(src, encoding: "utf-8")) + assert_equal(Encoding::UTF_8, S(src, encoding: "utf-8").encoding) + + assert_equal("", S(capacity: 1000)) + assert_equal(Encoding::ASCII_8BIT, S(capacity: 1000).encoding) + + assert_equal("", S(capacity: 1000, encoding: "euc-jp")) + assert_equal(Encoding::EUC_JP, S(capacity: 1000, encoding: "euc-jp").encoding) + + assert_equal("", S("", capacity: 1000)) + assert_equal(__ENCODING__, S("", capacity: 1000).encoding) + + assert_equal("", S("", capacity: 1000, encoding: "euc-jp")) + assert_equal(Encoding::EUC_JP, S("", capacity: 1000, encoding: "euc-jp").encoding) + end + + def test_initialize + str = S("").freeze + assert_equal("", str.__send__(:initialize)) + 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 + String.new(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize) + assert_equal("mystring", str[0, 8]) + end + + def test_initialize_nonstring + assert_raise(TypeError) { + S(1) + } + assert_raise(TypeError) { + S(1, capacity: 1000) + } + end + + def test_initialize_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {('x'*100000).__send__(:initialize, '')} +1_000.times(&code) +PREP +100_000.times(&code) +CODE end def test_AREF # '[]' @@ -127,20 +205,6 @@ class TestString < Test::Unit::TestCase s[S("Foo")] = S("Bar") assert_equal(S("BarBar"), s) - pre_1_7_1 do - s = S("FooBar") - s[S("Foo")] = S("xyz") - assert_equal(S("xyzBar"), s) - - $= = true - s = S("FooBar") - s[S("FOO")] = S("Bar") - assert_equal(S("BarBar"), s) - s[S("FOO")] = S("xyz") - assert_equal(S("BarBar"), s) - $= = false - end - s = S("a string") s[0..s.size] = S("another string") assert_equal(S("another string"), s) @@ -152,6 +216,8 @@ class TestString < Test::Unit::TestCase assert_equal("fobar", s) assert_raise(ArgumentError) { "foo"[1, 2, 3] = "" } + + assert_raise(IndexError) {"foo"[RbConfig::LIMITS["LONG_MIN"]] = "l"} end def test_CMP # '<=>' @@ -161,12 +227,6 @@ class TestString < Test::Unit::TestCase assert_equal(-1, S("ABCDEF") <=> S("abcdef")) - pre_1_7_1 do - $= = true - assert_equal(0, S("ABCDEF") <=> S("abcdef")) - $= = false - end - assert_nil("foo" <=> Object.new) o = Object.new @@ -190,13 +250,6 @@ class TestString < Test::Unit::TestCase assert_not_equal(:foo, S("foo")) assert_equal(S("abcdef"), S("abcdef")) - pre_1_7_1 do - $= = true - assert_equal(S("CAT"), S('cat')) - assert_equal(S("CaT"), S('cAt')) - $= = false - end - assert_not_equal(S("CAT"), S('cat')) assert_not_equal(S("CaT"), S('cAt')) @@ -236,12 +289,6 @@ class TestString < Test::Unit::TestCase assert_equal(10, S("FeeFieFoo-Fum") =~ /Fum$/) assert_equal(nil, S("FeeFieFoo-Fum") =~ /FUM$/) - pre_1_7_1 do - $= = true - assert_equal(10, S("FeeFieFoo-Fum") =~ /FUM$/) - $= = false - end - o = Object.new def o.=~(x); x + "bar"; end assert_equal("foobar", S("foo") =~ o) @@ -282,10 +329,10 @@ class TestString < Test::Unit::TestCase def casetest(a, b, rev=false) msg = proc {"#{a} should#{' not' if rev} match #{b}"} case a - when b - assert(!rev, msg) - else - assert(rev, msg) + when b + assert(!rev, msg) + else + assert(rev, msg) end end @@ -293,13 +340,6 @@ class TestString < Test::Unit::TestCase # assert_equal(true, S("foo") === :foo) casetest(S("abcdef"), S("abcdef")) - pre_1_7_1 do - $= = true - casetest(S("CAT"), S('cat')) - casetest(S("CaT"), S('cAt')) - $= = false - end - casetest(S("CAT"), S('cat'), true) # Reverse the test - we don't want to casetest(S("CaT"), S('cAt'), true) # find these in the case. end @@ -357,6 +397,56 @@ class TestString < Test::Unit::TestCase $/ = save assert_equal(S("a").hash, S("a\u0101").chomp(S("\u0101")).hash, '[ruby-core:22414]') + + s = S("hello") + assert_equal("hel", s.chomp('lo')) + assert_equal("hello", s) + + s = S("hello") + assert_equal("hello", s.chomp('he')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b}", s.chomp("\u{3061 306f}")) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b 3061 306f}", s.chomp('lo')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal("hello", s.chomp("\u{3061 306f}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal("\xe3\x81\x82", s.chomp("\x82")) + assert_equal("\xe3\x81\x82", s) + + s = S("\x95\x5c").force_encoding("Shift_JIS") + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.chomp("\x5c")) + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s) + + # clear coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.chomp("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.chomp(klass.new)) + assert_equal("abba", s) + + # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified + s = "foo\n" + assert_equal("foo", s.chomp("\n")) + s = "foo\r\n" + assert_equal("foo", s.chomp("\n")) + s = "foo\r" + assert_equal("foo", s.chomp("\n")) + ensure + $/ = save end def test_chomp! @@ -413,6 +503,67 @@ class TestString < Test::Unit::TestCase assert_equal("foo\r", s) assert_equal(S("a").hash, S("a\u0101").chomp!(S("\u0101")).hash, '[ruby-core:22414]') + + s = S("").freeze + assert_raise_with_message(FrozenError, /frozen/) {s.chomp!} + + s = S("ax") + o = Struct.new(:s).new(s) + def o.to_str + s.freeze + "x" + end + assert_raise_with_message(FrozenError, /frozen/) {s.chomp!(o)} + + s = S("hello") + assert_equal("hel", s.chomp!('lo')) + assert_equal("hel", s) + + s = S("hello") + assert_equal(nil, s.chomp!('he')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b}", s.chomp!("\u{3061 306f}")) + assert_equal("\u{3053 3093 306b}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal(nil, s.chomp!('lo')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal(nil, s.chomp!("\u{3061 306f}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal(nil, s.chomp!("\x82")) + assert_equal("\xe3\x81\x82", s) + + s = S("\x95\x5c").force_encoding("Shift_JIS") + assert_equal(nil, s.chomp!("\x5c")) + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s) + + # clear coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.chomp!("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.chomp!(klass.new)) + assert_equal("abb", s) + + # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified + s = "foo\n" + assert_equal("foo", s.chomp!("\n")) + s = "foo\r\n" + assert_equal("foo", s.chomp!("\n")) + s = "foo\r" + assert_equal("foo", s.chomp!("\n")) + ensure + $/ = save end def test_chop @@ -465,13 +616,14 @@ class TestString < Test::Unit::TestCase end end - null = File.exist?("/dev/null") ? "/dev/null" : "NUL" # maybe DOSISH - assert_equal("", File.read(null).clone, '[ruby-dev:32819] reported by Kazuhiro NISHIYAMA') + assert_equal("", File.read(IO::NULL).clone, '[ruby-dev:32819] reported by Kazuhiro NISHIYAMA') end def test_concat assert_equal(S("world!"), S("world").concat(33)) assert_equal(S("world!"), S("world").concat(S('!'))) + b = S("sn") + assert_equal(S("snsnsn"), b.concat(b, b)) bug7090 = '[ruby-core:47751]' result = S("").force_encoding(Encoding::UTF_16LE) @@ -479,6 +631,12 @@ class TestString < Test::Unit::TestCase expected = S("\u0300".encode(Encoding::UTF_16LE)) assert_equal(expected, result, bug7090) assert_raise(TypeError) { 'foo' << :foo } + assert_raise(FrozenError) { 'foo'.freeze.concat('bar') } + end + + def test_concat_literals + s="." * 50 + assert_equal(Encoding::UTF_8, "#{s}x".encoding) end def test_count @@ -505,6 +663,19 @@ class TestString < Test::Unit::TestCase 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"))) + 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| + 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 } + end; end def test_delete @@ -516,7 +687,7 @@ class TestString < Test::Unit::TestCase 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".tr("\u3041", "a").ascii_only?) + assert_equal(false, "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")) @@ -582,6 +753,79 @@ class TestString < Test::Unit::TestCase def test_dump a= S("Test") << 1 << 2 << 3 << 9 << 13 << 10 assert_equal(S('"Test\\x01\\x02\\x03\\t\\r\\n"'), a.dump) + b= S("\u{7F}") + assert_equal(S('"\\x7F"'), b.dump) + b= S("\u{AB}") + assert_equal(S('"\\u00AB"'), b.dump) + b= S("\u{ABC}") + assert_equal(S('"\\u0ABC"'), b.dump) + b= S("\uABCD") + assert_equal(S('"\\uABCD"'), b.dump) + b= S("\u{ABCDE}") + assert_equal(S('"\\u{ABCDE}"'), b.dump) + b= S("\u{10ABCD}") + 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), + '"a\x00b\x00c\x00".force_encoding("UTF-16LE")'.undump) + + assert_equal('\#', '"\\\\#"'.undump) + assert_equal('\#{', '"\\\\\#{"'.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 } end def test_dup @@ -610,14 +854,15 @@ class TestString < Test::Unit::TestCase res=[] S("hello\n\n\nworld").lines(S('')).each {|x| res << x} - assert_equal(S("hello\n\n\n"), res[0]) - assert_equal(S("world"), res[1]) + assert_equal(S("hello\n\n"), res[0]) + assert_equal(S("world"), res[1]) $/ = "!" res=[] S("hello!world").lines.each {|x| res << x} assert_equal(S("hello!"), res[0]) assert_equal(S("world"), res[1]) + ensure $/ = save end @@ -642,13 +887,20 @@ class TestString < Test::Unit::TestCase assert_equal [65, 66, 67], s.bytes {} } else - assert_warning(/deprecated/) { + 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 end @@ -679,13 +931,20 @@ class TestString < Test::Unit::TestCase assert_equal [0x3042, 0x3044, 0x3046], s.codepoints {} } else - assert_warning(/deprecated/) { + 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 end @@ -710,7 +969,8 @@ class TestString < Test::Unit::TestCase assert_equal ["A", "B", "C"], s.chars {} } else - assert_warning(/deprecated/) { + 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]) @@ -720,6 +980,74 @@ class TestString < Test::Unit::TestCase end end + def test_each_grapheme_cluster + [ + "\u{0D 0A}", + "\u{20 200d}", + "\u{600 600}", + "\u{600 20}", + "\u{261d 1F3FB}", + "\u{1f600}", + "\u{20 308}", + "\u{1F477 1F3FF 200D 2640 FE0F}", + "\u{1F468 200D 1F393}", + "\u{1F46F 200D 2642 FE0F}", + "\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 308}", ["\u000A", "\u0308"]], + ["\u{d 308}", ["\u000D", "\u0308"]], + ["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 + + s = ("x"+"\u{10ABCD}"*250000) + assert_empty(s.each_grapheme_cluster {s.clear}) + end + + def test_grapheme_clusters + [ + "\u{20 200d}", + "\u{600 600}", + "\u{600 20}", + "\u{261d 1F3FB}", + "\u{1f600}", + "\u{20 308}", + "\u{1F477 1F3FF 200D 2640 FE0F}", + "\u{1F468 200D 1F393}", + "\u{1F46F 200D 2642 FE0F}", + "\u{1f469 200d 2764 fe0f 200d 1f469}", + ].each do |g| + 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]) + } + end + end + def test_each_line save = $/ $/ = "\n" @@ -730,8 +1058,13 @@ class TestString < Test::Unit::TestCase res=[] S("hello\n\n\nworld").each_line(S('')) {|x| res << x} - assert_equal(S("hello\n\n\n"), res[0]) - assert_equal(S("world"), res[1]) + assert_equal(S("hello\n\n"), res[0]) + assert_equal(S("world"), res[1]) + + res=[] + S("hello\r\n\r\nworld").each_line(S('')) {|x| res << x} + assert_equal(S("hello\r\n\r\n"), res[0]) + assert_equal(S("world"), res[1]) $/ = "!" @@ -760,6 +1093,58 @@ class TestString < Test::Unit::TestCase assert_nothing_raised(bug7646) do "\n\u0100".each_line("\n") {} end + ensure + $/ = save + end + + def test_each_line_chomp + res = [] + S("hello\nworld").each_line("\n", chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + 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]) + + 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]) + + res = [] + S("hello!world").each_line(S('!'), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world"), res[1]) + + res = [] + S("a").each_line(S('ab'), chomp: true).each {|x| res << x} + assert_equal(1, res.size) + assert_equal(S("a"), res[0]) + + s = nil + "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 + assert_equal "hello\nworld", S("hello\nworld").each_line(nil, chomp: true).next + + res = [] + S("").each_line(chomp: true) {|x| res << x} + assert_equal([], res) + + res = [] + S("\n").each_line(chomp: true) {|x| res << x} + assert_equal([S("")], res) + + 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 @@ -772,7 +1157,7 @@ class TestString < Test::Unit::TestCase assert_equal ["hello\n", "world"], s.lines {} } else - assert_warning(/deprecated/) { + 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]) @@ -808,6 +1193,7 @@ class TestString < Test::Unit::TestCase S("hello").gsub(/./) { |s| s[0].to_s + S(' ')}) assert_equal(S("HELL-o"), 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 @@ -831,6 +1217,11 @@ class TestString < Test::Unit::TestCase c.force_encoding Encoding::US_ASCII assert_equal Encoding::UTF_8, a.gsub(/world/, c).encoding + + assert_equal S("a\u{e9}apos<"), S("a\u{e9}'<").gsub("'", "apos") + + bug9849 = '[ruby-core:62669] [Bug #9849]' + assert_equal S("\u{3042 3042 3042}!foo!"), S("\u{3042 3042 3042}/foo/").gsub("/", "!"), bug9849 end def test_gsub! @@ -890,18 +1281,6 @@ class TestString < Test::Unit::TestCase assert_not_equal(S("sub-setter").hash, S("discover").hash, bug9172) end - def test_hash_random - str = 'abc' - a = [str.hash.to_s] - 3.times { - assert_in_out_err(["-e", "print #{str.dump}.hash"], "") do |r, e| - a += r - assert_equal([], e) - end - } - assert_not_equal([str.hash.to_s], a.uniq) - end - def test_hex assert_equal(255, S("0xff").hex) assert_equal(-255, S("-0xff").hex) @@ -959,6 +1338,14 @@ class TestString < Test::Unit::TestCase assert_nil($~) end + def test_insert + assert_equal("Xabcd", S("abcd").insert(0, 'X')) + assert_equal("abcXd", S("abcd").insert(3, 'X')) + assert_equal("abcdX", S("abcd").insert(4, 'X')) + assert_equal("abXcd", S("abcd").insert(-3, 'X')) + assert_equal("abcdX", S("abcd").insert(-1, 'X')) + end + def test_intern assert_equal(:koala, S("koala").intern) assert_not_equal(:koala, S("Koala").intern) @@ -990,6 +1377,9 @@ class TestString < Test::Unit::TestCase assert_equal(S("AAAAA000"), S("ZZZZ999").next) assert_equal(S("*+"), S("**").next) + + assert_equal(S("!"), S(" ").next) + assert_equal(S(""), S("").next) end def test_next! @@ -1026,6 +1416,10 @@ class TestString < Test::Unit::TestCase a = S("**") assert_equal(S("*+"), a.next!) assert_equal(S("*+"), a) + + a = S(" ") + assert_equal(S("!"), a.next!) + assert_equal(S("!"), a) end def test_oct @@ -1060,10 +1454,10 @@ class TestString < Test::Unit::TestCase assert_equal(s2, s) fs = "".freeze - assert_raise(RuntimeError) { fs.replace("a") } - assert_raise(RuntimeError) { fs.replace(fs) } + 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 @@ -1113,6 +1507,9 @@ class TestString < Test::Unit::TestCase assert_nil("foo".rindex(//, -100)) assert_nil($~) + + assert_equal(3, "foo".rindex(//)) + assert_equal([3, 3], $~.offset(0)) end def test_rjust @@ -1145,6 +1542,18 @@ class TestString < Test::Unit::TestCase res = [] a.scan(/./) { |w| res << w } assert_predicate(res[0], :tainted?, '[ruby-core:33338] #4087') + + /h/ =~ a + a.scan(/x/) + assert_nil($~) + + /h/ =~ a + 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_size @@ -1286,19 +1695,11 @@ class TestString < Test::Unit::TestCase assert_equal(S("Bar"), a.slice!(S("Bar"))) assert_equal(S("Foo"), a) - pre_1_7_1 do - a=S("FooBar") - assert_nil(a.slice!(S("xyzzy"))) - assert_equal(S("FooBar"), a) - assert_nil(a.slice!(S("plugh"))) - assert_equal(S("FooBar"), a) - end - assert_raise(ArgumentError) { "foo".slice! } end def test_split - assert_nil($;) + fs, $; = $;, nil assert_equal([S("a"), S("b"), S("c")], S(" a b\t c ").split) assert_equal([S("a"), S("b"), S("c")], S(" a b\t c ").split(S(" "))) @@ -1322,14 +1723,37 @@ class TestString < Test::Unit::TestCase assert_equal([], "".split(//, 1)) assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + ensure + $; = fs + end + + def test_fs + assert_raise_with_message(TypeError, /\$;/) { + $; = [] + } + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug = '[ruby-core:79582] $; must not be GCed' + begin; + $; = " " + $a = nil + alias $; $a + alias $-F $a + GC.start + assert_equal([], "".split, bug) + end; + end + + def test_split_encoding bug6206 = '[ruby-dev:45441]' Encoding.list.each do |enc| next unless enc.ascii_compatible? s = S("a:".force_encoding(enc)) assert_equal([enc]*2, s.split(":", 2).map(&:encoding), bug6206) end + end + def test_split_wchar bug8642 = '[ruby-core:56036] [Bug #8642]' [ Encoding::UTF_16BE, Encoding::UTF_16LE, @@ -1342,6 +1766,25 @@ class TestString < Test::Unit::TestCase end end + def test_split_invalid_sequence + bug10886 = '[ruby-core:68229] [Bug #10886]' + broken = S("\xa1".force_encoding("utf-8")) + assert_raise(ArgumentError, bug10886) { + S("a,b").split(broken) + } + end + + def test_split_invalid_argument + assert_raise(TypeError) { + S("a,b").split(BasicObject.new) + } + end + + def test_split_dupped + s = "abc" + s.split("b", 1).map(&:upcase!) + assert_equal("abc", s) + end def test_squeeze assert_equal(S("abc"), S("aaabbbbccc").squeeze) assert_equal(S("aa bb cc"), S("aa bb cc").squeeze(S(" "))) @@ -1374,6 +1817,11 @@ class TestString < Test::Unit::TestCase 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("hel", $&) + assert_equal(false, "hello".start_with?(/el/)) + assert_nil($&) end def test_strip @@ -1410,6 +1858,7 @@ class TestString < Test::Unit::TestCase assert_equal(S("HELL-o"), S("hello").sub(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 }) + assert_equal(S("h<e>llo"), S("hello").sub('e', S('<\0>'))) assert_equal(S("a\\aba"), S("ababa").sub(/b/, '\\')) assert_equal(S("ab\\aba"), S("ababa").sub(/(b)/, '\1\\')) @@ -1459,6 +1908,16 @@ class TestString < Test::Unit::TestCase o = Object.new def o.to_s; self; end assert_match(/^foo#<Object:0x.*>baz$/, "foobarbaz".sub("bar") { o }) + + assert_equal(S("Abc"), S("abc").sub("a", "A")) + m = nil + assert_equal(S("Abc"), S("abc").sub("a") {m = $~; "A"}) + assert_equal(S("a"), m[0]) + 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>') + } end def test_sub! @@ -1487,6 +1946,12 @@ class TestString < Test::Unit::TestCase 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 @@ -1509,6 +1974,14 @@ class TestString < Test::Unit::TestCase assert_equal("2000aaa", "1999zzz".succ) assert_equal("AAAA0000", "ZZZ9999".succ) assert_equal("**+", "***".succ) + + assert_equal("!", " ".succ) + assert_equal("", "".succ) + + bug = '[ruby-core:83062] [Bug #13952]' + s = "\xff".b + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.succ, :ascii_only?, bug) end def test_succ! @@ -1550,6 +2023,14 @@ class TestString < Test::Unit::TestCase assert_equal(S("No.10"), a.succ!) assert_equal(S("No.10"), a) + a = S(" ") + assert_equal(S("!"), a.succ!) + assert_equal(S("!"), a) + + a = S("") + assert_equal(S(""), a.succ!) + assert_equal(S(""), a) + assert_equal("aaaaaaaaaaaa", "zzzzzzzzzzz".succ!) assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", "zzzzzzzzzzzzzzzzzzzzzzz".succ!) end @@ -1561,6 +2042,8 @@ class TestString < Test::Unit::TestCase assert_equal(16, n.sum(17)) n[0] = 2.chr assert_not_equal(15, n.sum) + assert_equal(17, n.sum(0)) + assert_equal(17, n.sum(-1)) end def check_sum(str, bits=16) @@ -1575,7 +2058,7 @@ class TestString < Test::Unit::TestCase assert_equal(294, "abc".sum) check_sum("abc") check_sum("\x80") - 0.upto(70) {|bits| + -3.upto(70) {|bits| check_sum("xyz", bits) } end @@ -1690,8 +2173,13 @@ class TestString < Test::Unit::TestCase assert_equal(false, "\u3041\u3042".tr("\u3041", "a").ascii_only?) bug6156 = '[ruby-core:43335]' + bug13950 = '[ruby-core:83056] [Bug #13950]' str, range, star = %w[b a-z *].map{|s|s.encode("utf-16le")} - assert_equal(star, str.tr(range, star), bug6156) + result = str.tr(range, star) + assert_equal(star, result, bug6156) + assert_not_predicate(str, :ascii_only?) + assert_not_predicate(star, :ascii_only?) + assert_not_predicate(result, :ascii_only?, bug13950) end def test_tr! @@ -1879,7 +2367,7 @@ class TestString < Test::Unit::TestCase end def test_frozen_check - assert_raise(RuntimeError) { + assert_raise(FrozenError) { s = "" s.sub!(/\A/) { s.freeze; "zzz" } } @@ -1971,6 +2459,50 @@ class TestString < Test::Unit::TestCase assert_raise(ArgumentError) { "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, :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('backref', $&) + end + + def test_match_p_string + /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, :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('backref', $&) + end + def test_clear s = "foo" * 100 s.clear @@ -2008,7 +2540,12 @@ class TestString < Test::Unit::TestCase end assert_equal(["\u30E6\u30FC\u30B6", "@", "\u30C9\u30E1.\u30A4\u30F3"], - "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".partition(/[@.]/)) + "\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3".partition(/[@.]/)) + + bug = '[ruby-core:82911]' + hello = "hello" + hello.partition("hi").map(&:upcase!) + assert_equal("hello", hello, bug) end def test_rpartition @@ -2028,10 +2565,20 @@ class TestString < Test::Unit::TestCase bug8138 = '[ruby-dev:47183]' assert_equal(["\u30E6\u30FC\u30B6@\u30C9\u30E1", ".", "\u30A4\u30F3"], "\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) end def test_setter assert_raise(TypeError) { $/ = 1 } + name = "\u{5206 884c}" + assert_separately([], <<-"end;") # do + alias $#{name} $/ + assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } + end; end def test_to_id @@ -2086,7 +2633,31 @@ class TestString < Test::Unit::TestCase =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_nil("foo".casecmp(:foo)) + assert_nil("foo".casecmp(Object.new)) + + o = Object.new + def o.to_str; "fOO"; end + assert_equal(0, "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_nil("foo".casecmp?(:foo)) + assert_nil("foo".casecmp?(Object.new)) + + o = Object.new + def o.to_str; "fOO"; end + assert_equal(true, "FoO".casecmp?(o)) end def test_upcase2 @@ -2098,10 +2669,270 @@ class TestString < Test::Unit::TestCase end def test_rstrip + assert_equal(" hello", " hello ".rstrip) assert_equal("\u3042", "\u3042 ".rstrip) assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip } end + def test_rstrip_bang + s1 = S(" hello ") + assert_equal(" hello", s1.rstrip!) + assert_equal(" hello", s1) + + s2 = S("\u3042 ") + assert_equal("\u3042", s2.rstrip!) + assert_equal("\u3042", s2) + + s3 = S(" \u3042") + assert_equal(nil, s3.rstrip!) + assert_equal(" \u3042", s3) + + s4 = S("\u3042") + assert_equal(nil, s4.rstrip!) + assert_equal("\u3042", s4) + + assert_raise(Encoding::CompatibilityError) { "\u3042".encode("ISO-2022-JP").rstrip! } + end + + def test_lstrip + assert_equal("hello ", " hello ".lstrip) + assert_equal("\u3042", " \u3042".lstrip) + end + + def test_lstrip_bang + s1 = S(" hello ") + assert_equal("hello ", s1.lstrip!) + assert_equal("hello ", s1) + + s2 = S("\u3042 ") + assert_equal(nil, s2.lstrip!) + assert_equal("\u3042 ", s2) + + s3 = S(" \u3042") + assert_equal("\u3042", s3.lstrip!) + assert_equal("\u3042", s3) + + s4 = S("\u3042") + assert_equal(nil, s4.lstrip!) + assert_equal("\u3042", s4) + 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/) } + + s = S("hello") + assert_equal("lo", s.delete_prefix('hel')) + assert_equal("hello", s) + + s = S("hello") + assert_equal("hello", s.delete_prefix('lo')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{306b 3061 306f}", s.delete_prefix("\u{3053 3093}")) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b 3061 306f}", s.delete_prefix('hel')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal("hello", s.delete_prefix("\u{3053 3093}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal("\xe3\x81\x82", s.delete_prefix("\xe3")) + assert_equal("\xe3\x81\x82", s) + + s = S("\x95\x5c").force_encoding("Shift_JIS") + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s.delete_prefix("\x95")) + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s) + + # clear coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("bba", s.delete_prefix(klass.new)) + assert_equal("abba", s) + 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/) } + + s = S("hello") + assert_equal("lo", s.delete_prefix!('hel')) + assert_equal("lo", s) + + s = S("hello") + assert_equal(nil, s.delete_prefix!('lo')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{306b 3061 306f}", s.delete_prefix!("\u{3053 3093}")) + assert_equal("\u{306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal(nil, s.delete_prefix!('hel')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal(nil, s.delete_prefix!("\u{3053 3093}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal(nil, s.delete_prefix!("\xe3")) + assert_equal("\xe3\x81\x82", s) + + # clear coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix!("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("bba", s.delete_prefix!(klass.new)) + assert_equal("bba", s) + + s = S("ax").freeze + assert_raise_with_message(FrozenError, /frozen/) {s.delete_prefix!("a")} + + s = S("ax") + o = Struct.new(:s).new(s) + def o.to_str + s.freeze + "a" + end + 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/) } + + s = S("hello") + assert_equal("hel", s.delete_suffix('lo')) + assert_equal("hello", s) + + s = S("hello") + assert_equal("hello", s.delete_suffix('he')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b}", s.delete_suffix("\u{3061 306f}")) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b 3061 306f}", s.delete_suffix('lo')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal("hello", s.delete_suffix("\u{3061 306f}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal("\xe3\x81\x82", s.delete_suffix("\x82")) + assert_equal("\xe3\x81\x82", s) + + # clear coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_suffix("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.delete_suffix(klass.new)) + assert_equal("abba", s) + + # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified, + # but delete_suffix does not + s = "foo\n" + assert_equal("foo", s.delete_suffix("\n")) + s = "foo\r\n" + assert_equal("foo\r", s.delete_suffix("\n")) + s = "foo\r" + assert_equal("foo\r", s.delete_suffix("\n")) + 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/) } + + s = S("hello").freeze + assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!('lo')} + + s = S("ax") + o = Struct.new(:s).new(s) + def o.to_str + s.freeze + "x" + end + assert_raise_with_message(FrozenError, /frozen/) {s.delete_suffix!(o)} + + s = S("hello") + assert_equal("hel", s.delete_suffix!('lo')) + assert_equal("hel", s) + + s = S("hello") + assert_equal(nil, s.delete_suffix!('he')) + assert_equal("hello", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal("\u{3053 3093 306b}", s.delete_suffix!("\u{3061 306f}")) + assert_equal("\u{3053 3093 306b}", s) + + s = S("\u{3053 3093 306b 3061 306f}") + assert_equal(nil, s.delete_suffix!('lo')) + assert_equal("\u{3053 3093 306b 3061 306f}", s) + + s = S("hello") + assert_equal(nil, s.delete_suffix!("\u{3061 306f}")) + assert_equal("hello", s) + + # skip if argument is a broken string + s = S("\xe3\x81\x82") + assert_equal(nil, s.delete_suffix!("\x82")) + assert_equal("\xe3\x81\x82", s) + + s = S("\x95\x5c").force_encoding("Shift_JIS") + assert_equal(nil, s.delete_suffix!("\x5c")) + assert_equal("\x95\x5c".force_encoding("Shift_JIS"), s) + + # clear coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_suffix!("\u{3053 3093}"), :ascii_only?) + + # argument should be converted to String + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.delete_suffix!(klass.new)) + assert_equal("abb", s) + + # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified, + # but delete_suffix does not + s = "foo\n" + assert_equal("foo", s.delete_suffix!("\n")) + s = "foo\r\n" + assert_equal("foo\r", s.delete_suffix!("\n")) + s = "foo\r" + assert_equal(nil, s.delete_suffix!("\n")) + end + =begin def test_symbol_table_overflow assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/) @@ -2110,6 +2941,23 @@ class TestString < Test::Unit::TestCase 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(//, '') h = {} @@ -2151,7 +2999,9 @@ class TestString < Test::Unit::TestCase end def test_prepend - assert_equal(S("hello world!"), "world!".prepend("hello ")) + assert_equal(S("hello world!"), "!".prepend("hello ", "world")) + b = S("ue") + assert_equal(S("ueueue"), b.prepend(b, b)) foo = Object.new def foo.to_str @@ -2225,15 +3075,70 @@ class TestString < Test::Unit::TestCase RUBY end + class Bug9581 < String + def =~ re; :foo end + end + + def test_regexp_match_subclass + s = Bug9581.new("abc") + r = /abc/ + assert_equal(:foo, s =~ r) + assert_equal(:foo, s.send(:=~, r)) + assert_equal(:foo, s.send(:=~, /abc/)) + assert_equal(:foo, s =~ /abc/, "should not use optimized instruction") + end + def test_LSHIFT_neary_long_max return unless @cls == String - assert_ruby_status([], <<-'end;', '[ruby-core:61886] [Bug #9709]', timeout: 60) + assert_ruby_status([], <<-'end;', '[ruby-core:61886] [Bug #9709]', timeout: 20) begin a = "a" * 0x4000_0000 a << "a" * 0x1_0000 rescue NoMemoryError end end; + end if [0].pack("l!").bytesize < [nil].pack("p").bytesize + # enable only when string size range is smaller than memory space + + def test_uplus_minus + str = "foo" + assert_not_predicate(str, :frozen?) + assert_not_predicate(+str, :frozen?) + assert_predicate(-str, :frozen?) + + assert_same(str, +str) + assert_not_same(str, -str) + + str = "bar".freeze + assert_predicate(str, :frozen?) + assert_not_predicate(+str, :frozen?) + assert_predicate(-str, :frozen?) + + assert_not_same(str, +str) + assert_same(str, -str) + + bar = %w(b a r).join('') + assert_same(str, -bar, "uminus deduplicates [Feature #13077]") + end + + def test_ord + assert_equal(97, "a".ord) + assert_equal(97, "abc".ord) + assert_equal(0x3042, "\u3042\u3043".ord) + assert_raise(ArgumentError) { "".ord } + end + + def test_chr + assert_equal("a", "abcde".chr) + assert_equal("a", "a".chr) + assert_equal("\u3042", "\u3042\u3043".chr) + assert_equal('', ''.chr) + end + + def test_substr_code_range + data = "\xff" + "a"*200 + assert_not_predicate(data, :valid_encoding?) + assert_predicate(data[100..-1], :valid_encoding?) end end diff --git a/test/ruby/test_stringchar.rb b/test/ruby/test_stringchar.rb index 7f7342c9ab..e13beef69c 100644 --- a/test/ruby/test_stringchar.rb +++ b/test/ruby/test_stringchar.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestStringchar < Test::Unit::TestCase diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index e23b5b021c..af68346442 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -1,7 +1,7 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' require 'timeout' -require_relative 'envutil' module TestStruct def test_struct @@ -92,9 +92,38 @@ module TestStruct assert_equal([:utime, :stime, :cutime, :cstime], Process.times.members) end + def test_struct_new_with_empty_hash + assert_equal({:a=>1}, Struct.new(:a, {}).new({:a=>1}).a) + 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_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 + + @Struct.instance_eval do + remove_const(:KeywordInitTrue) + remove_const(:KeywordInitFalse) + end + end + def test_initialize klass = @Struct.new(:a) assert_raise(ArgumentError) { klass.new(1, 2) } + klass = @Struct.new(:total) do + def initialize(a, b) + super(a+b) + end + end + assert_equal 3, klass.new(1,2).total end def test_each @@ -141,7 +170,7 @@ module TestStruct assert_equal("#<struct :@a=3>", o.inspect) methods = klass.instance_methods(false) - assert_equal([:@a, :"@a="].inspect, methods.inspect, '[Bug #8756]') + assert_equal([:@a, :"@a="].sort.inspect, methods.sort.inspect, '[Bug #8756]') assert_include(methods, :@a) assert_include(methods, :"@a=") end @@ -156,8 +185,10 @@ module TestStruct klass = @Struct.new(:a) o = klass.new(1) assert_equal(1, o[0]) - assert_raise(IndexError) { o[-2] } - assert_raise(IndexError) { o[1] } + assert_raise_with_message(IndexError, /offset -2\b/) {o[-2]} + assert_raise_with_message(IndexError, /offset 1\b/) {o[1]} + assert_raise_with_message(NameError, /foo/) {o["foo"]} + assert_raise_with_message(NameError, /foo/) {o[:foo]} end def test_aset @@ -165,8 +196,10 @@ module TestStruct o = klass.new(1) o[0] = 2 assert_equal(2, o[:a]) - assert_raise(IndexError) { o[-2] = 3 } - assert_raise(IndexError) { o[1] = 3 } + assert_raise_with_message(IndexError, /offset -2\b/) {o[-2] = 3} + assert_raise_with_message(IndexError, /offset 1\b/) {o[1] = 3} + assert_raise_with_message(NameError, /foo/) {o["foo"] = 3} + assert_raise_with_message(NameError, /foo/) {o[:foo] = 3} end def test_values_at @@ -183,6 +216,48 @@ module TestStruct assert_raise(ArgumentError) { o.select(1) } end + def test_big_struct + klass1 = @Struct.new(*('a'..'z').map(&:to_sym)) + o = klass1.new + assert_nil o.z + assert_equal(:foo, o.z = :foo) + assert_equal(:foo, o.z) + assert_equal(:foo, o[25]) + end + + def test_overridden_aset + bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by []= method' + + struct = Class.new(Struct.new(*(:a..:z), :result)) do + def []=(*args) + raise args.inspect + end + end + + obj = struct.new + assert_nothing_raised(RuntimeError, bug10601) do + obj.result = 42 + end + assert_equal(42, obj.result, bug10601) + end + + def test_overridden_aref + bug10601 = '[ruby-core:66846] [Bug #10601]: should not be affected by [] method' + + struct = Class.new(Struct.new(*(:a..:z), :result)) do + def [](*args) + raise args.inspect + end + end + + obj = struct.new + obj.result = 42 + result = assert_nothing_raised(RuntimeError, bug10601) do + break obj.result + end + assert_equal(42, result, bug10601) + end + def test_equal klass1 = @Struct.new(:a) klass2 = @Struct.new(:a, :b) @@ -196,7 +271,7 @@ module TestStruct def test_hash klass = @Struct.new(:a) o = klass.new(1) - assert_kind_of(Fixnum, o.hash) + assert_kind_of(Integer, o.hash) end def test_eql @@ -289,6 +364,8 @@ module TestStruct x = Object.new o = klass.new("test", x) assert_same(x, o.b?) + o.send("b?=", 42) + assert_equal(42, o.b?) end def test_bang_mark_in_member @@ -296,6 +373,8 @@ module TestStruct x = Object.new o = klass.new("test", x) assert_same(x, o.b!) + o.send("b!=", 42) + assert_equal(42, o.b!) end def test_setter_method_returns_value @@ -304,6 +383,20 @@ module TestStruct assert_equal "[Bug #9353]", x.send(:a=, "[Bug #9353]") end + def test_dig + klass = @Struct.new(:a) + o = klass.new(klass.new({b: [1, 2, 3]})) + assert_equal(1, o.dig(:a, :a, :b, 0)) + assert_nil(o.dig(:b, 0)) + end + + def test_new_dupilicate + bug12291 = '[ruby-core:74971] [Bug #12291]' + assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) { + @Struct.new(:a, :a) + } + end + class TopStruct < Test::Unit::TestCase include TestStruct diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index 6278e4b88a..cf7580ab00 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestSuper < Test::Unit::TestCase class Base @@ -194,7 +194,7 @@ class TestSuper < Test::Unit::TestCase end end overlaid.call(str = "123") - overlaid.call(ary = [1,2,3]) + overlaid.call([1,2,3]) str.reverse end @@ -229,11 +229,8 @@ class TestSuper < Test::Unit::TestCase A.send(:include, Override) end - # [Bug #3351] def test_double_include - assert_equal([:Base, :Override], DoubleInclude::B.new.foo) - # should be changed as follows? - # assert_equal([:Base, :Override, :Override], DoubleInclude::B.new.foo) + assert_equal([:Base, :Override, :Override], DoubleInclude::B.new.foo, "[Bug #3351]") end module DoubleInclude2 @@ -318,7 +315,6 @@ class TestSuper < Test::Unit::TestCase } sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { def foo - x = Object.new lambda { super() } end } @@ -408,6 +404,13 @@ class TestSuper < Test::Unit::TestCase assert_equal([1, 2, 3, false, 5], y.foo(1, 2, 3, false, 5)) end + def test_missing_super + o = Class.new {def foo; super; end}.new + e = assert_raise(NoMethodError) {o.foo} + assert_same(o, e.receiver) + assert_equal(:foo, e.name) + end + def test_missing_super_in_method_module bug9315 = '[ruby-core:59358] [Bug #9315]' a = Module.new do @@ -482,8 +485,8 @@ class TestSuper < Test::Unit::TestCase bug9740 = '[ruby-core:62017] [Bug #9740]' b.module_eval do - define_method(:foo) do |result| - um.bind(self).call(result) + define_method(:foo) do |res| + um.bind(self).call(res) end end @@ -509,4 +512,52 @@ class TestSuper < Test::Unit::TestCase end assert_equal("A", b.new.foo, bug10263) end + + def test_super_with_block + a = Class.new do + def foo + yield + end + end + + b = Class.new(a) do + def foo + super{ + "b" + } + end + end + + assert_equal "b", b.new.foo{"c"} + end + + def test_public_zsuper_with_prepend + bug12876 = '[ruby-core:77784] [Bug #12876]' + m = EnvUtil.labeled_module("M") + c = EnvUtil.labeled_class("C") {prepend m; public :initialize} + o = assert_nothing_raised(Timeout::Error, bug12876) { + Timeout.timeout(3) {c.new} + } + assert_instance_of(c, o) + m.module_eval {def initialize; raise "exception in M"; end} + assert_raise_with_message(RuntimeError, "exception in M") { + c.new + } + 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 end diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb index 7f261b68bb..36cbf4a710 100644 --- a/test/ruby/test_symbol.rb +++ b/test/ruby/test_symbol.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestSymbol < Test::Unit::TestCase @@ -12,6 +13,19 @@ class TestSymbol < Test::Unit::TestCase assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(n))} end + def test_intern + assert_equal(':""', ''.intern.inspect) + assert_equal(':$foo', '$foo'.intern.inspect) + assert_equal(':"!foo"', '!foo'.intern.inspect) + assert_equal(':"foo=="', "foo==".intern.inspect) + end + + def test_all_symbols + x = Symbol.all_symbols + assert_kind_of(Array, x) + assert_empty(x.reject {|s| s.is_a?(Symbol) }) + end + def test_inspect_invalid # 2) Symbol#inspect sometimes returns invalid symbol representations: assert_eval_inspected(:"!") @@ -106,6 +120,93 @@ class TestSymbol < Test::Unit::TestCase end end + def test_to_proc_yield + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 5.0) + begin; + GC.stress = true + true.tap(&:itself) + end; + end + + def test_to_proc_new_proc + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 5.0) + begin; + GC.stress = true + 2.times {Proc.new(&:itself)} + end; + end + + def test_to_proc_no_method + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 5.0) + begin; + bug11566 = '[ruby-core:70980] [Bug #11566]' + assert_raise(NoMethodError, bug11566) {Proc.new(&:foo).(1)} + assert_raise(NoMethodError, bug11566) {:foo.to_proc.(1)} + end; + end + + def test_to_proc_arg + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 5.0) + begin; + def (obj = Object.new).proc(&b) b; end + assert_same(:itself.to_proc, obj.proc(&:itself)) + end; + end + + def test_to_proc_call_with_symbol_proc + first = 1 + bug11594 = "[ruby-core:71088] [Bug #11594] corrupted the first local variable" + # symbol which does not have a Proc + ->(&blk) {}.call(&:test_to_proc_call_with_symbol_proc) + assert_equal(1, first, bug11594) + end + + private def return_from_proc + Proc.new { return 1 }.tap(&:call) + end + + def test_return_from_symbol_proc + bug12462 = '[ruby-core:75856] [Bug #12462]' + assert_equal(1, return_from_proc, bug12462) + end + + def test_to_proc_for_hash_each + bug11830 = '[ruby-core:72205] [Bug #11830]' + assert_normal_exit("#{<<-"begin;"}\n#{<<-'end;'}", bug11830) + begin; + {}.each(&:destroy) + end; + end + + def test_to_proc_iseq + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}", timeout: 5) + begin; + bug11845 = '[ruby-core:72381] [Bug #11845]' + assert_nil(:class.to_proc.source_location, bug11845) + assert_equal([[: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) + end; + end + + def test_to_proc_binding + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}", timeout: 5) + begin; + bug12137 = '[ruby-core:74100] [Bug #12137]' + assert_raise(ArgumentError, bug12137) { + :succ.to_proc.binding + } + end; + end + + def test_to_proc_instance_exec + bug = '[ruby-core:78839] [Bug #13074] should evaluate on the argument' + assert_equal(2, BasicObject.new.instance_exec(1, &:succ), bug) + assert_equal(3, BasicObject.new.instance_exec(1, 2, &:+), bug) + end + def test_call o = Object.new def o.foo(x, y); x + y; end @@ -141,6 +242,35 @@ class TestSymbol < Test::Unit::TestCase assert_equal([false, false], m2.call(self, m2), "#{bug8531} nested without block") end + def test_block_curry_proc + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + b = proc { true }.curry + assert(b.call, "without block") + assert(b.call { |o| o.to_s }, "with block") + assert(b.call(&:to_s), "with sym block") + end; + end + + def test_block_curry_lambda + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + b = lambda { true }.curry + assert(b.call, "without block") + assert(b.call { |o| o.to_s }, "with block") + assert(b.call(&:to_s), "with sym block") + end; + end + + def test_block_method_to_proc + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + b = method(:tap).to_proc + assert(b.call { |o| o.to_s }, "with block") + assert(b.call(&:to_s), "with sym block") + end; + end + def test_succ assert_equal(:fop, :foo.succ) end @@ -156,7 +286,19 @@ class TestSymbol < Test::Unit::TestCase assert_equal(0, :FoO.casecmp(:fOO)) assert_equal(1, :FoO.casecmp(:BaR)) assert_equal(-1, :baR.casecmp(:FoO)) + assert_nil(:foo.casecmp("foo")) + assert_nil(:foo.casecmp(Object.new)) + 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_nil(:foo.casecmp?("foo")) + assert_nil(:foo.casecmp?(Object.new)) end def test_length @@ -176,7 +318,73 @@ class TestSymbol < Test::Unit::TestCase assert_equal(:fOo, :FoO.swapcase) end - def test_symbol_poped + def test_MATCH # '=~' + assert_equal(10, :"FeeFieFoo-Fum" =~ /Fum$/) + assert_equal(nil, "FeeFieFoo-Fum" =~ /FUM$/) + + o = Object.new + def o.=~(x); x + "bar"; end + assert_equal("foobar", :"foo" =~ o) + + assert_raise(TypeError) { :"foo" =~ "foo" } + end + + def test_match_method + assert_equal("bar", :"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")) + x = nil + :"foo".match(o, "bar", "baz") {|y| x = y } + assert_equal("foobarbaz", x) + + assert_raise(ArgumentError) { :"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, :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, ("\u3042" + '\x').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('backref', $&) + end + + def test_match_p_string + /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, :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, ("\u3042" + '\x').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('backref', $&) + end + + def test_symbol_popped assert_nothing_raised { eval('a = 1; :"#{ a }"; 1') } end @@ -192,13 +400,42 @@ class TestSymbol < Test::Unit::TestCase assert_equal(Encoding::US_ASCII, "$-A".force_encoding("iso-8859-15").intern.encoding) assert_equal(Encoding::US_ASCII, "foobar~!".force_encoding("iso-8859-15").intern.encoding) assert_equal(Encoding::UTF_8, "\u{2192}".intern.encoding) - assert_raise(EncodingError) {"\xb0a".force_encoding("utf-8").intern} + assert_raise_with_message(EncodingError, /\\xb0/i) {"\xb0a".force_encoding("utf-8").intern} end def test_singleton_method assert_raise(TypeError) { a = :foo; def a.foo; end } end + SymbolsForEval = [ + :foo, + "dynsym_#{Random.rand(10000)}_#{Time.now}".to_sym + ] + + def test_instance_eval + bug11086 = '[ruby-core:68961] [Bug #11086]' + SymbolsForEval.each do |sym| + assert_nothing_raised(TypeError, sym, bug11086) { + sym.instance_eval {} + } + assert_raise(TypeError, sym, bug11086) { + sym.instance_eval {def foo; end} + } + end + end + + def test_instance_exec + bug11086 = '[ruby-core:68961] [Bug #11086]' + SymbolsForEval.each do |sym| + assert_nothing_raised(TypeError, sym, bug11086) { + sym.instance_exec {} + } + assert_raise(TypeError, sym, bug11086) { + sym.instance_exec {def foo; end} + } + end + end + def test_frozen_symbol assert_equal(true, :foo.frozen?) assert_equal(true, :each.frozen?) @@ -206,4 +443,88 @@ class TestSymbol < Test::Unit::TestCase assert_equal(true, "foo#{Time.now.to_i}".to_sym.frozen?) assert_equal(true, :foo.to_sym.frozen?) end + + def test_symbol_gc_1 + assert_normal_exit('".".intern;GC.start(immediate_sweep:false);eval %[GC.start;".".intern]', + '', + child_env: '--disable-gems') + assert_normal_exit('".".intern;GC.start(immediate_sweep:false);eval %[GC.start;:"."]', + '', + child_env: '--disable-gems') + assert_normal_exit('".".intern;GC.start(immediate_sweep:false);eval %[GC.start;%i"."]', + '', + child_env: '--disable-gems') + assert_normal_exit('tap{".".intern};GC.start(immediate_sweep:false);' + + 'eval %[syms=Symbol.all_symbols;GC.start;syms.each(&:to_sym)]', + '', + child_env: '--disable-gems') + end + + def test_dynamic_attrset_id + bug10259 = '[ruby-dev:48559] [Bug #10259]' + class << (obj = Object.new) + attr_writer :unagi + end + assert_nothing_raised(NoMethodError, bug10259) {obj.send("unagi=".intern, 1)} + end + + def test_symbol_fstr_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) + begin; + 200_000.times { |i| (i + 200_000).to_s.to_sym } + end; + end + + def test_hash_redefinition + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + bug11035 = '[ruby-core:68767] [Bug #11035]' + class Symbol + def hash + raise + end + end + + h = {} + assert_nothing_raised(RuntimeError, bug11035) { + h[:foo] = 1 + } + assert_nothing_raised(RuntimeError, bug11035) { + h['bar'.to_sym] = 2 + } + 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, + '[ruby-core:80430] [Bug #13376]' + + sym = "dynsym_#{Random.rand(10000)}_#{Time.now}" + assert_not_equal sym.to_sym.hash, + `#{ruby} -e 'puts #{sym.inspect}.to_sym.hash'`.to_i + end + + def test_eq_can_be_redefined + assert_in_out_err([], <<-RUBY, ["foo"], []) + class Symbol + remove_method :== + def ==(obj) + "foo" + end + end + + puts :a == :a + RUBY + end end diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index cf38b5974c..f8d28a4f8e 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1,11 +1,19 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestSyntax < Test::Unit::TestCase + using Module.new { + refine(Object) do + def `(s) #` + s + end + end + } + def assert_syntax_files(test) srcdir = File.expand_path("../../..", __FILE__) srcdir = File.join(srcdir, test) - assert_separately(%W[--disable-gem -r#{__dir__}/envutil - #{srcdir}], + assert_separately(%W[--disable-gem - #{srcdir}], __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY) dir = ARGV.shift for script in Dir["#{dir}/**/*.rb"].sort @@ -37,7 +45,8 @@ class TestSyntax < Test::Unit::TestCase make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") assert_raise(ArgumentError, enc.name) {load(f.path)} end - f.close! + ensure + f.close! if f end def test_script_lines @@ -53,7 +62,8 @@ class TestSyntax < Test::Unit::TestCase assert_equal([enc, enc], debug_lines[f.path].map(&:encoding), bug4361) end end - f.close! + ensure + f.close! if f end def test_newline_in_block_parameters @@ -83,6 +93,20 @@ class TestSyntax < Test::Unit::TestCase assert_valid_syntax("tap (proc do end)", __FILE__, bug9726) end + def test_normal_argument + assert_valid_syntax('def foo(x) end') + assert_syntax_error('def foo(X) end', /constant/) + assert_syntax_error('def foo(@x) end', /instance variable/) + assert_syntax_error('def foo(@@x) end', /class variable/) + end + + def test_optional_argument + assert_valid_syntax('def foo(x=nil) end') + assert_syntax_error('def foo(X=nil) end', /constant/) + assert_syntax_error('def foo(@x=nil) end', /instance variable/) + assert_syntax_error('def foo(@@x=nil) end', /class variable/) + end + def test_keyword_rest bug5989 = '[ruby-core:42455]' assert_valid_syntax("def kwrest_test(**a) a; end", __FILE__, bug5989) @@ -101,32 +125,185 @@ class TestSyntax < Test::Unit::TestCase assert_nothing_raised(ArgumentError, bug7922) {o.bug7922(foo: 42)} end + class KW2 + def kw(k1: 1, k2: 2) [k1, k2] end + end + def test_keyword_splat assert_valid_syntax("foo(**h)", __FILE__) - o = Object.new - def o.kw(k1: 1, k2: 2) [k1, k2] end + o = KW2.new h = {k1: 11, k2: 12} assert_equal([11, 12], o.kw(**h)) - assert_equal([11, 22], o.kw(k2: 22, **h)) - assert_equal([11, 12], o.kw(**h, **{k2: 22})) - assert_equal([11, 22], o.kw(**{k2: 22}, **h)) + assert_equal([11, 12], o.kw(k2: 22, **h)) + assert_equal([11, 22], o.kw(**h, **{k2: 22})) + assert_equal([11, 12], o.kw(**{k2: 22}, **h)) + end + + def test_keyword_duplicated_splat + bug10315 = '[ruby-core:65368] [Bug #10315]' + + o = KW2.new + assert_equal([23, 2], o.kw(**{k1: 22}, **{k1: 23}), bug10315) + h = {k3: 31} assert_raise(ArgumentError) {o.kw(**h)} h = {"k1"=>11, k2: 12} assert_raise(TypeError) {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 + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), k: a.add(2))")} + assert_equal(2, r) + assert_equal([1, 2], a, bug10315) + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f({k: a.add(1), k: a.add(2)})")} + assert_equal(2, r) + assert_equal([1, 2], a, bug10315) + end + + def test_keyword_empty_splat + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + bug10719 = '[ruby-core:67446] [Bug #10719]' + assert_valid_syntax("foo(a: 1, **{})", bug10719) + end; + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + 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) + + 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") + end + + o = Object.new + assert_warn(/circular argument reference - var/) 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") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var: def bar(var) var; end) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("proc {|var: 1| var}") + end + end + + 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('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/) + 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) + + o = Object.new + assert_warn(/circular argument reference - var/) do + o.instance_eval("def foo(var = bar(var)) var end") + end + + o = Object.new + assert_warn(/circular argument reference - var/) do + o.instance_eval("def foo(var = bar {var}) var end") + end + + o = Object.new + assert_warn(/circular argument reference - var/) do + o.instance_eval("def foo(var = (def bar;end; 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") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var = bar {|var| var}) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var = def bar(var) var; end) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("proc {|var = 1| var}") + end + end + def test_warn_grouped_expression bug5214 = '[ruby-core:39050]' assert_warning("", bug5214) do - assert_valid_syntax("foo \\\n(\n true)", "test") {$VERBOSE = true} + assert_valid_syntax("foo \\\n(\n true)", "test", verbose: true) end end def test_warn_unreachable assert_warning("test:3: warning: statement not reached\n") do code = "loop do\n" "break\n" "foo\n" "end" - assert_valid_syntax(code, "test") {$VERBOSE = true} + assert_valid_syntax(code, "test", verbose: true) end end @@ -145,8 +322,14 @@ WARN [:/, "regexp literal"], [:%, "string literal"], ].each do |op, syn| - assert_warning(warning % [op, syn]) do - assert_valid_syntax("puts 1 #{op}0", "test") {$VERBOSE = true} + all_assertions do |a| + ["puts 1 #{op}0", "puts :a #{op}0", "m = 1; puts m #{op}0"].each do |src| + a.for(src) do + assert_warning(warning % [op, syn], src) do + assert_valid_syntax(src, "test", verbose: true) + end + end + end end end end @@ -176,20 +359,68 @@ WARN assert_not_label(:foo, 'class Foo < not_label:foo; end', bug6347) end + def test_no_label_with_percent + assert_syntax_error('{%"a": 1}', /unexpected ':'/) + assert_syntax_error("{%'a': 1}", /unexpected ':'/) + assert_syntax_error('{%Q"a": 1}', /unexpected ':'/) + assert_syntax_error("{%Q'a': 1}", /unexpected ':'/) + assert_syntax_error('{%q"a": 1}', /unexpected ':'/) + assert_syntax_error("{%q'a': 1}", /unexpected ':'/) + end + + def test_block_after_cond + bug10653 = '[ruby-dev:48790] [Bug #10653]' + assert_valid_syntax("false ? raise {} : tap {}", bug10653) + assert_valid_syntax("false ? raise do end : tap do end", bug10653) + end + + def test_paren_after_label + bug11456 = '[ruby-dev:49221] [Bug #11456]' + assert_valid_syntax("{foo: (1 rescue 0)}", bug11456) + assert_valid_syntax("{foo: /=/}", bug11456) + end + + def test_percent_string_after_label + bug11812 = '[ruby-core:72084]' + assert_valid_syntax('{label:%w(*)}', bug11812) + assert_valid_syntax('{label: %w(*)}', bug11812) + end + + def test_heredoc_after_label + bug11849 = '[ruby-core:72396] [Bug #11849]' + assert_valid_syntax("{label:<<DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label:<<-DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label:<<~DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<-DOC\n""DOC\n""}", bug11849) + assert_valid_syntax("{label: <<~DOC\n""DOC\n""}", bug11849) + end + + def test_cmdarg_kwarg_lvar_clashing_method + bug12073 = '[ruby-core:73816] [Bug#12073]' + a = a = 1 + assert_valid_syntax("a b: 1") + assert_valid_syntax("a = 1; a b: 1", bug12073) + end + def test_duplicated_arg assert_syntax_error("def foo(a, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _) end") end def test_duplicated_rest assert_syntax_error("def foo(a, *a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, *_) end") end def test_duplicated_opt assert_syntax_error("def foo(a, a=1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _=1) end") 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") end def test_duplicated_rest_opt @@ -202,30 +433,37 @@ WARN def test_duplicated_opt_post assert_syntax_error("def foo(a=1, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, _) end") end def test_duplicated_kw assert_syntax_error("def foo(a, a: 1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _: 1) end") 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} 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") 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") end def test_duplicated_rest_kwrest assert_syntax_error("def foo(*a, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, **_) end") 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") end def test_duplicated_when @@ -240,7 +478,7 @@ WARN } } assert_warning(/#{w}/){#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ - a = 1 + a = a = 1 eval %q{ case 1 when 1, 1 @@ -251,6 +489,18 @@ WARN } end + def test_invalid_break + assert_syntax_error("def m; break; end", /Invalid break/) + assert_syntax_error('/#{break}/', /Invalid break/) + assert_syntax_error('/#{break}/o', /Invalid break/) + end + + def test_invalid_next + assert_syntax_error("def m; next; end", /Invalid next/) + assert_syntax_error('/#{next}/', /Invalid next/) + assert_syntax_error('/#{next}/o', /Invalid next/) + end + def test_lambda_with_space feature6390 = '[ruby-dev:45605]' assert_valid_syntax("-> (x, y) {}", __FILE__, feature6390) @@ -301,6 +551,151 @@ e" assert_equal(expected, actual, "#{Bug7559}: ") end + def assert_dedented_heredoc(expect, result, mesg = "") + all_assertions(mesg) do |a| + %w[eos "eos" 'eos' `eos`].each do |eos| + a.for(eos) do + assert_equal(eval("<<-#{eos}\n#{expect}eos\n"), + eval("<<~#{eos}\n#{result}eos\n")) + end + end + end + end + + def test_dedented_heredoc_without_indentation + result = " y\n" \ + "z\n" + expect = result + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_indentation + result = " a\n" \ + " b\n" + expect = " a\n" \ + "b\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_blank_less_indented_line + # the blank line has two leading spaces + result = " a\n" \ + " \n" \ + " b\n" + expect = "a\n" \ + "\n" \ + "b\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_blank_less_indented_line_escaped + result = " a\n" \ + "\\ \\ \n" \ + " b\n" + expect = result + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_blank_more_indented_line + # the blank line has six leading spaces + result = " a\n" \ + " \n" \ + " b\n" + expect = "a\n" \ + " \n" \ + "b\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_blank_more_indented_line_escaped + result = " a\n" \ + "\\ \\ \\ \\ \\ \\ \n" \ + " b\n" + expect = result + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_empty_line + result = " This would contain specially formatted text.\n" \ + "\n" \ + " That might span many lines\n" + expect = 'This would contain specially formatted text.'"\n" \ + ''"\n" \ + 'That might span many lines'"\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_interpolated_expression + result = ' #{1}a'"\n" \ + " zy\n" + expect = ' #{1}a'"\n" \ + "zy\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_interpolated_string + w = w = "" + result = " \#{mesg} a\n" \ + " zy\n" + expect = '#{mesg} a'"\n" \ + ' zy'"\n" + assert_dedented_heredoc(expect, result) + end + + def test_dedented_heredoc_with_newline + bug11989 = '[ruby-core:72855] [Bug #11989] after escaped newline should not be dedented' + result = ' x\n'" y\n" \ + " z\n" + expect = 'x\n'" y\n" \ + "z\n" + assert_dedented_heredoc(expect, result, bug11989) + end + + def test_dedented_heredoc_with_concatenation + bug11990 = '[ruby-core:72857] [Bug #11990] concatenated string should not be dedented' + %w[eos "eos" 'eos'].each do |eos| + assert_equal("x\n y", + eval("<<~#{eos} ' y'\n x\neos\n"), + "#{bug11990} with #{eos}") + end + %w[eos "eos" 'eos' `eos`].each do |eos| + _, expect = eval("[<<~#{eos}, ' x']\n"" y\n""eos\n") + assert_equal(' x', expect, bug11990) + end + end + + def test_dedented_heredoc_expr_at_beginning + result = " a\n" \ + '#{1}'"\n" + expected = " a\n" \ + '#{1}'"\n" + 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; + end + def test_lineno_after_heredoc bug7559 = '[ruby-dev:46737]' expected, _, actual = __LINE__, <<eom, __LINE__ @@ -312,6 +707,39 @@ eom assert_equal(expected, actual, bug7559) end + def test_dedented_heredoc_invalid_identifer + assert_syntax_error('<<~ "#{}"', /unexpected <</) + end + + def test_heredoc_mixed_encoding + assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d\u1234 + TEXT + HEREDOC + assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d + \u1234 + TEXT + HEREDOC + assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234\xe9\x9d + TEXT + HEREDOC + assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234 + \xe9\x9d + TEXT + HEREDOC + end + def test_lineno_operation_brace_block expected = __LINE__ + 1 actual = caller_lineno\ @@ -401,6 +829,12 @@ 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") + end + end + def test__END___cr assert_syntax_error("__END__\r<<<<<\n", /unexpected <</) end @@ -412,6 +846,15 @@ eom end end + def test_unexpected_fraction + msg = /unexpected fraction/ + assert_syntax_error("0x0.0", msg) + assert_syntax_error("0b0.0", msg) + assert_syntax_error("0d0.0", msg) + assert_syntax_error("0o0.0", msg) + assert_syntax_error("0.0.0", msg) + end + def test_error_message_encoding bug10114 = '[ruby-core:64228] [Bug #10114]' code = "# -*- coding: utf-8 -*-\n" "def n \"\u{2208}\"; end" @@ -433,6 +876,325 @@ eom end end + def test_invalid_symbol_space + assert_syntax_error(": foo", /unexpected ':'/) + assert_syntax_error(": #\n foo", /unexpected ':'/) + assert_syntax_error(":#\n foo", /unexpected ':'/) + end + + def test_fluent_dot + assert_valid_syntax("a\n.foo") + assert_valid_syntax("a\n&.foo") + end + + def test_no_warning_logop_literal + assert_warning("") do + eval("true||raise;nil") + end + assert_warning("") do + eval("false&&raise;nil") + end + assert_warning("") do + eval("''||raise;nil") + end + end + + def test_warning_literal_in_condition + assert_warn(/literal in condition/) do + eval('1 if ""') + end + assert_warn(/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 + eval('1 if :foo') + end + assert_warning(/literal in condition/) do + eval('1 if :"#{"foo".upcase}"') + end + + assert_warn('') do + eval('1 if !""') + end + assert_warn('') do + eval('1 if !//') + end + assert_warn('') do + eval('1 if !(true..false)') + end + assert_warning('') do + eval('1 if !1') + end + assert_warning('') do + eval('1 if !:foo') + end + assert_warning('') do + eval('1 if !:"#{"foo".upcase}"') + end + end + + def test_alias_symbol + bug8851 = '[ruby-dev:47681] [Bug #8851]' + formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] + all_assertions(bug8851) do |all| + formats.product(formats) do |form1, form2| + all.for(code = "alias #{form1 % 'a'} #{form2 % 'p'}") do + assert_valid_syntax(code) + end + end + end + end + + def test_undef_symbol + bug8851 = '[ruby-dev:47681] [Bug #8851]' + formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] + all_assertions(bug8851) do |all| + formats.product(formats) do |form1, form2| + all.for(code = "undef #{form1 % 'a'}, #{form2 % 'p'}") do + assert_valid_syntax(code) + end + end + end + end + + def test_parenthesised_statement_argument + assert_syntax_error("foo(bar rescue nil)", /unexpected modifier_rescue/) + assert_valid_syntax("foo (bar rescue nil)") + end + + def test_cmdarg_in_paren + bug11873 = '[ruby-core:72482] [Bug #11873]' + assert_valid_syntax %q{a b{c d}, :e do end}, bug11873 + assert_valid_syntax %q{a b(c d), :e do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, :e do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), :e do end}, bug11873 + assert_valid_syntax %q{a b{c d}, 1 do end}, bug11873 + assert_valid_syntax %q{a b(c d), 1 do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, 1 do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), 1 do end}, bug11873 + assert_valid_syntax %q{a b{c d}, "x" do end}, bug11873 + assert_valid_syntax %q{a b(c d), "x" do end}, bug11873 + assert_valid_syntax %q{a b{c(d)}, "x" do end}, bug11873 + assert_valid_syntax %q{a b(c(d)), "x" do end}, bug11873 + end + + def test_block_after_cmdarg_in_paren + bug11873 = '[ruby-core:72482] [Bug #11873]' + def bug11873.p(*);end; + + assert_raise(LocalJumpError, bug11873) do + bug11873.instance_eval do + p p{p p;p(p)}, tap do + raise SyntaxError, "should not be passed to tap" + end + end + end + + assert_raise(LocalJumpError, bug11873) do + bug11873.instance_eval do + p p{p(p);p p}, tap do + raise SyntaxError, "should not be passed to tap" + end + end + end + end + + def test_do_block_in_hash_brace + bug13073 = '[ruby-core:78837] [Bug #13073]' + assert_valid_syntax 'p :foo, {a: proc do end, b: proc do end}', bug13073 + assert_valid_syntax 'p :foo, {:a => proc do end, b: proc do end}', bug13073 + assert_valid_syntax 'p :foo, {"a": proc do end, b: proc do end}', bug13073 + assert_valid_syntax 'p :foo, {** proc do end, b: proc do end}', bug13073 + assert_valid_syntax 'p :foo, {proc do end => proc do end, b: proc do end}', bug13073 + end + + def test_do_after_local_variable + obj = Object.new + def obj.m; yield; end + result = assert_nothing_raised(SyntaxError) do + obj.instance_eval("m = 1; m do :ok end") + end + assert_equal(:ok, result) + end + + def test_brace_after_local_variable + obj = Object.new + def obj.m; yield; end + result = assert_nothing_raised(SyntaxError) do + obj.instance_eval("m = 1; m {:ok}") + end + assert_equal(:ok, result) + end + + def test_brace_after_literal_argument + bug = '[ruby-core:81037] [Bug #13547]' + error = /unexpected '{'/ + assert_syntax_error('m "x" {}', error) + assert_syntax_error('m 1 {}', error, bug) + assert_syntax_error('m 1.0 {}', error, bug) + assert_syntax_error('m :m {}', error, bug) + assert_syntax_error('m :"#{m}" {}', error, bug) + assert_syntax_error('m ?x {}', error, bug) + assert_syntax_error('m %[] {}', error, bug) + assert_syntax_error('m 0..1 {}', error, bug) + assert_syntax_error('m [] {}', error, bug) + end + + def test_return_toplevel + feature4840 = '[ruby-core:36785] [Feature #4840]' + line = __LINE__+2 + code = "#{<<~"begin;"}#{<<~'end;'}" + begin; + return; raise + begin return; rescue SystemExit; exit false; end + begin return; ensure puts "ensured"; end #=> ensured + begin ensure return; end + begin raise; ensure; return; end + begin raise; rescue; return; end + return false; raise + return 1; raise + "#{return}" + raise((return; "should not raise")) + 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 + end; + .split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]} + failed = proc do |n, s| + RubyVM::InstructionSequence.compile(s, __FILE__, nil, n).disasm + end + Tempfile.create(%w"test_return_ .rb") do |lib| + lib.close + args = %W[-W0 -r#{lib.path}] + 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 + 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_syntax_error_in_rescue + bug12613 = '[ruby-core:76531] [Bug #12613]' + assert_syntax_error("#{<<-"begin;"}\n#{<<-"end;"}", /Invalid retry/, bug12613) + begin; + while true + begin + p + rescue + retry + else + retry + end + break + end + end; + 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 + eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + tap do + result << :begin + raise "An exception occurred!" + ensure + result << :ensure + end + end; + end + assert_equal([:begin, :ensure], result) + end + + def test_rescue_do_end_rescued + result = [] + assert_nothing_raised(RuntimeError) do + eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + tap do + result << :begin + raise "An exception occurred!" + rescue + result << :rescue + else + result << :else + ensure + result << :ensure + end + end; + end + assert_equal([:begin, :rescue, :ensure], result) + end + + def test_rescue_do_end_no_raise + result = [] + assert_nothing_raised(RuntimeError) do + eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + tap do + result << :begin + rescue + result << :rescue + else + result << :else + ensure + result << :ensure + end + end; + end + assert_equal([:begin, :else, :ensure], result) + end + + def test_rescue_do_end_ensure_result + result = eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + proc do + :begin + ensure + :ensure + end.call + end; + assert_equal(:begin, 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 + 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 db19a3c2cd..60037ab044 100644 --- a/test/ruby/test_system.rb +++ b/test/ruby/test_system.rb @@ -1,6 +1,6 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' -require_relative 'envutil' class TestSystem < Test::Unit::TestCase def test_system @@ -86,7 +86,7 @@ class TestSystem < Test::Unit::TestCase File.unlink tmpfilename testname = '[ruby-core:44505]' - assert_match /Windows/, `ver`, testname + assert_match(/Windows/, `ver`, testname) assert_equal 0, $?.to_i, testname end } diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 6568b8dfbc..135d17237e 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1,7 +1,6 @@ # -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require 'thread' -require_relative 'envutil' class TestThread < Test::Unit::TestCase class Thread < ::Thread @@ -27,6 +26,26 @@ class TestThread < Test::Unit::TestCase end 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) + ensure + th.join + end + + def test_inspect_with_fiber + inspect1 = inspect2 = nil + + Thread.new{ + inspect1 = Thread.current.inspect + Fiber.new{ + inspect2 = Thread.current.inspect + }.resume + }.join + + assert_equal inspect1, inspect2, '[Bug #13689]' + end + def test_main_thread_variable_in_enumerator assert_equal Thread.main, Thread.current @@ -83,41 +102,43 @@ 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 def test_mutex_synchronize - m = Mutex.new + m = Thread::Mutex.new r = 0 - max = 10 - (1..max).map{ + num_threads = 10 + loop=100 + (1..num_threads).map{ Thread.new{ - i=0 - while i<max*max - i+=1 + loop.times{ m.synchronize{ - r += 1 + tmp = r + # empty and waste loop for making thread preemption + 100.times { + } + r = tmp + 1 } - end + } } }.each{|e| e.join } - assert_equal(max * max * max, r) + assert_equal(num_threads*loop, r) end def test_mutex_synchronize_yields_no_block_params bug8097 = '[ruby-core:53424] [Bug #8097]' - assert_empty(Mutex.new.synchronize {|*params| break params}, bug8097) + assert_empty(Thread::Mutex.new.synchronize {|*params| break params}, bug8097) end def test_local_barrier dir = File.dirname(__FILE__) lbtest = File.join(dir, "lbtest.rb") $:.unshift File.join(File.dirname(dir), 'ruby') - require 'envutil' $:.shift 3.times { `#{EnvUtil.rubybin} #{lbtest}` @@ -127,17 +148,21 @@ class TestThread < Test::Unit::TestCase def test_priority c1 = c2 = 0 - t1 = Thread.new { loop { c1 += 1 } } + run = true + t1 = Thread.new { c1 += 1 while run } t1.priority = 3 - t2 = Thread.new { loop { c2 += 1 } } + t2 = Thread.new { c2 += 1 while run } t2.priority = -3 assert_equal(3, t1.priority) assert_equal(-3, t2.priority) sleep 0.5 5.times do + assert_not_predicate(t1, :stop?) + assert_not_predicate(t2, :stop?) break if c1 > c2 sleep 0.1 end + run = false t1.kill t2.kill assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed @@ -163,6 +188,14 @@ class TestThread < Test::Unit::TestCase t2.kill if t2 end + def test_new_symbol_proc + bug = '[ruby-core:80147] [Bug #13313]' + assert_ruby_status([], "#{<<-"begin;"}\n#{<<-'end;'}", bug) + begin; + exit("1" == Thread.start(1, &:to_s).value) + end; + end + def test_join t = Thread.new { sleep } assert_nil(t.join(0.05)) @@ -284,7 +317,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 @@ -296,7 +332,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 @@ -319,7 +358,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 @@ -331,8 +374,84 @@ class TestThread < Test::Unit::TestCase INPUT end + def test_report_on_exception + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + q1 = Thread::Queue.new + q2 = Thread::Queue.new + + 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.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 but from the global flag") + + 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_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_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) + raise "report 3" + } + q2.push(Thread.report_on_exception = true) + assert_equal(false, q1.pop) + Thread.pass while th.alive? + } + + 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_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 + q2.pop + raise "report 5" + } + assert_raise_with_message(RuntimeError, "report 5") { + q2.push(true) + Thread.pass while th.alive? + } + } + end; + 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 @@ -380,14 +499,14 @@ class TestThread < Test::Unit::TestCase ok = false t = Thread.new do EnvUtil.suppress_warning do - $SAFE = 3 + $SAFE = 1 end ok = true sleep end Thread.pass until ok assert_equal(0, Thread.current.safe_level) - assert_equal(3, t.safe_level) + assert_equal(1, t.safe_level) ensure t.kill if t @@ -407,20 +526,62 @@ class TestThread < Test::Unit::TestCase assert_equal(false, t.key?(:qux)) assert_equal(false, t.key?("qux")) - assert_equal([:foo, :bar, :baz], t.keys) + assert_equal([:foo, :bar, :baz].sort, t.keys.sort) + + ensure + t.kill if t + end + + def test_thread_local_fetch + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + x = nil + assert_equal("foo", t.fetch(:foo, 0)) + assert_equal("foo", t.fetch(:foo) {x = true}) + assert_nil(x) + assert_equal("foo", t.fetch("foo", 0)) + assert_equal("foo", t.fetch("foo") {x = true}) + assert_nil(x) + + x = nil + assert_equal(0, t.fetch(:qux, 0)) + assert_equal(1, t.fetch(:qux) {x = 1}) + assert_equal(1, x) + assert_equal(2, t.fetch("qux", 2)) + assert_equal(3, t.fetch("qux") {x = 3}) + assert_equal(3, x) + + e = assert_raise(KeyError) {t.fetch(:qux)} + assert_equal(:qux, e.key) + assert_equal(t, e.receiver) ensure t.kill if t 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 + bug10667 = '[ruby-core:67185] [Bug #10667]' + t = Thread.new {}.join + key_str = "foo#{rand}" + key_sym = key_str.to_sym + t.thread_variable_set(key_str, "bar") + assert_equal("bar", t.thread_variable_get(key_str), "#{bug10667}: string key") + assert_equal("bar", t.thread_variable_get(key_sym), "#{bug10667}: symbol key") end def test_select_wait @@ -434,7 +595,7 @@ class TestThread < Test::Unit::TestCase end def test_mutex_deadlock - m = Mutex.new + m = Thread::Mutex.new m.synchronize do assert_raise(ThreadError) do m.synchronize do @@ -445,7 +606,7 @@ class TestThread < Test::Unit::TestCase end def test_mutex_interrupt - m = Mutex.new + m = Thread::Mutex.new m.lock t = Thread.new do m.lock @@ -457,18 +618,18 @@ class TestThread < Test::Unit::TestCase end def test_mutex_illegal_unlock - m = Mutex.new + 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 - m1 = Mutex.new - m2 = Mutex.new + m1 = Thread::Mutex.new + m2 = Thread::Mutex.new m1.lock m2.lock m1.unlock @@ -476,7 +637,7 @@ class TestThread < Test::Unit::TestCase assert_equal(false, m1.locked?) assert_equal(false, m2.locked?) - m3 = Mutex.new + m3 = Thread::Mutex.new m1.lock m2.lock m3.lock @@ -489,7 +650,7 @@ class TestThread < Test::Unit::TestCase end def test_mutex_trylock - m = Mutex.new + m = Thread::Mutex.new assert_equal(true, m.try_lock) assert_equal(false, m.try_lock, '[ruby-core:20943]') @@ -523,22 +684,22 @@ class TestThread < Test::Unit::TestCase end def test_no_valid_cfp - skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#intialize' if defined?(WIN32OLE) + skip '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) - assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join) + assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083) + assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083) end def make_handle_interrupt_test_thread1 flag r = [] - ready_p = false - done = false + ready_q = Queue.new + done_q = Queue.new th = Thread.new{ begin Thread.handle_interrupt(RuntimeError => flag){ begin - ready_p = true - sleep 0.01 until done + ready_q << true + done_q.pop rescue r << :c1 end @@ -547,10 +708,10 @@ class TestThread < Test::Unit::TestCase r << :c2 end } - Thread.pass until ready_p + ready_q.pop th.raise begin - done = true + done_q << true th.join rescue r << :c3 @@ -611,24 +772,22 @@ class TestThread < Test::Unit::TestCase r=:ng 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.current.raise RuntimeError - r=:ok + Thread.pass until r == :wait + Thread.current.raise RuntimeError, "will raise in sleep" + r = :ok sleep ensure - th_s.raise e + th_s.raise e, "raise from ensure", $@ end } } - sleep 1 - r=:ng - th.raise RuntimeError - th.join - rescue e - end + } + assert_raise(e) {r = :wait; sleep 0.2} + th.join assert_equal(:ok,r) end @@ -637,6 +796,7 @@ class TestThread < Test::Unit::TestCase th_waiting = true t = Thread.new { + Thread.current.report_on_exception = false Thread.handle_interrupt(RuntimeError => :on_blocking) { nil while th_waiting # async interrupt should be raised _before_ writing puts arguments @@ -657,6 +817,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 @@ -674,7 +835,7 @@ class TestThread < Test::Unit::TestCase end def test_handle_interrupted? - q = Queue.new + q = Thread::Queue.new Thread.handle_interrupt(RuntimeError => :never){ done = false th = Thread.new{ @@ -705,7 +866,7 @@ class TestThread < Test::Unit::TestCase end def test_thread_timer_and_ensure - assert_normal_exit(<<_eom, 'r36492', timeout: 3) + assert_normal_exit(<<_eom, 'r36492', timeout: 10) flag = false t = Thread.new do begin @@ -725,9 +886,24 @@ _eom end def test_uninitialized - c = Class.new(Thread) - c.class_eval { def initialize; end } + c = Class.new(Thread) {def initialize; end} assert_raise(ThreadError) { c.new.start } + + bug11959 = '[ruby-core:72732] [Bug #11959]' + + c = Class.new(Thread) {def initialize; exit; end} + assert_raise(ThreadError, bug11959) { c.new } + + c = Class.new(Thread) {def initialize; raise; end} + assert_raise(ThreadError, bug11959) { c.new } + + c = Class.new(Thread) { + def initialize + pending = pending_interrupt? + super {pending} + end + } + assert_equal(false, c.new.value, bug11959) end def test_backtrace @@ -742,24 +918,24 @@ _eom def test_thread_timer_and_interrupt bug5757 = '[ruby-dev:44985]' - t0 = Time.now.to_f pid = nil cmd = 'Signal.trap(:INT, "DEFAULT"); r,=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; r.read' opt = {} opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM - s, _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| out_p.gets pid = cpid + t0 = Time.now.to_f Process.kill(:SIGINT, pid) Process.wait(pid) - [$?, err_p.read] + t1 = Time.now.to_f + [$?, t1 - t0, err_p.read] end - t1 = Time.now.to_f assert_equal(pid, s.pid, bug5757) assert_equal([false, true, false, Signal.list["INT"]], [s.exited?, s.signaled?, s.stopped?, s.termsig], "[s.exited?, s.signaled?, s.stopped?, s.termsig]") - assert_in_delta(t1 - t0, 1, 1, bug5757) + assert_include(0..2, t, bug5757) end def test_thread_join_in_trap @@ -798,22 +974,27 @@ _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 - assert_in_out_err([], <<-INPUT, %w(false), []) + assert_in_out_err([], <<-'INPUT', ["false false aborting"], []) +q = Thread::Queue.new Thread.new(Thread.current) {|mth| begin - Thead.pass until mth.stop? + q.push nil + mth.run + Thread.pass until mth.stop? + p :mth_stopped # don't run if killed by rb_thread_terminate_all ensure - p mth.alive? + puts "#{mth.alive?} #{mth.status} #{Thread.current.status}" end } +q.pop INPUT end @@ -841,35 +1022,34 @@ Thread.new(Thread.current) {|mth| 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 - mutex = Mutex.new + mutex = Thread::Mutex.new assert_equal(mutex.owned?, false) mutex.synchronize { @@ -881,16 +1061,15 @@ Thread.new(Thread.current) {|mth| def test_mutex_owned2 begin - mutex = Mutex.new + mutex = Thread::Mutex.new th = Thread.new { # lock forever mutex.lock sleep } - Thread.pass until th.status == "sleep" - # acquired another thread. - assert_equal(mutex.locked?, true) + # acquired by another thread. + Thread.pass until mutex.locked? assert_equal(mutex.owned?, false) ensure th.kill if th @@ -899,7 +1078,7 @@ Thread.new(Thread.current) {|mth| def test_mutex_unlock_on_trap assert_in_out_err([], <<-INPUT, %w(locked unlocked false), []) - m = Mutex.new + m = Thread::Mutex.new trapped = false Signal.trap("INT") { |signo| @@ -964,7 +1143,7 @@ Thread.new(Thread.current) {|mth| def test_blocking_mutex_unlocked_on_fork bug8433 = '[ruby-core:55102] [Bug #8433]' - mutex = Mutex.new + mutex = Thread::Mutex.new flag = false mutex.lock @@ -998,9 +1177,9 @@ Thread.new(Thread.current) {|mth| end Process.wait2(f.pid) end - unless th.join(3) + unless th.join(EnvUtil.apply_timeout_scale(30)) Process.kill(:QUIT, f.pid) - Process.kill(:KILL, f.pid) unless th.join(1) + Process.kill(:KILL, f.pid) unless th.join(EnvUtil.apply_timeout_scale(1)) end _, status = th.value output = f.read @@ -1008,4 +1187,131 @@ Thread.new(Thread.current) {|mth| assert_not_predicate(status, :signaled?, FailDesc[status, bug9751, output]) assert_predicate(status, :success?, bug9751) end if Process.respond_to?(:fork) + + def test_fork_while_locked + m = 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_subclass_no_initialize + t = Module.new do + break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") + end + t.class_eval do + def initialize + end + end + assert_raise_with_message(ThreadError, /C\u{30b9 30ec 30c3 30c9}/) do + t.new {} + end + end + + def test_thread_name + t = Thread.start {sleep} + sleep 0.001 until t.stop? + assert_nil t.name + s = t.inspect + t.name = 'foo' + assert_equal 'foo', t.name + t.name = nil + assert_nil t.name + assert_equal s, t.inspect + ensure + t.kill + t.join + end + + def test_thread_invalid_name + bug11756 = '[ruby-core:71774] [Bug #11756]' + t = Thread.start {} + assert_raise(ArgumentError, bug11756) {t.name = "foo\0bar"} + assert_raise(ArgumentError, bug11756) {t.name = "foo".encode(Encoding::UTF_32BE)} + ensure + t.kill + t.join + end + + def test_thread_invalid_object + bug11756 = '[ruby-core:71774] [Bug #11756]' + t = Thread.start {} + assert_raise(TypeError, bug11756) {t.name = []} + ensure + t.kill + t.join + 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_interrupt_for_killed_thread + assert_normal_exit(<<-_end, '[Bug #8996]', timeout: 5, timeout_error: nil) + Thread.report_on_exception = false + trap(:TERM){exit} + while true + t = Thread.new{sleep 0} + t.raise Interrupt + end + _end + end + + def test_signal_at_join + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "can't trap a signal from another process on Windows" + # opt = {new_pgroup: true} + end + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}") + {# + 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 end diff --git a/test/ruby/test_threadgroup.rb b/test/ruby/test_threadgroup.rb index e29c477247..ec95bd6419 100644 --- a/test/ruby/test_threadgroup.rb +++ b/test/ruby/test_threadgroup.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: false require 'test/unit' -require 'thread' -require_relative 'envutil' class TestThreadGroup < Test::Unit::TestCase def test_thread_init thgrp = ThreadGroup.new - Thread.new{ + th = Thread.new{ thgrp.add(Thread.current) - assert_equal(thgrp, Thread.new{sleep 1}.group) - }.join + Thread.new{sleep 1} + }.value + assert_equal(thgrp, th.group) + ensure + th.join end def test_frozen_thgroup diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index d016812e3a..5ddcc9dcfe 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -1,9 +1,8 @@ +# frozen_string_literal: false require 'test/unit' -require 'rational' require 'delegate' require 'timeout' require 'delegate' -require_relative 'envutil' class TestTime < Test::Unit::TestCase def setup @@ -47,6 +46,7 @@ class TestTime < Test::Unit::TestCase tm = [2001,2,28,23,59,30] 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") } end def test_time_add() @@ -172,6 +172,7 @@ class TestTime < Test::Unit::TestCase assert_equal(100000, Time.at(0.0001).nsec) 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_equal(10, Time.at(0.00000001).nsec) assert_equal(1, Time.at(0.000000001).nsec) @@ -235,6 +236,17 @@ class TestTime < Test::Unit::TestCase assert_equal(1, Time.at(0, 0.001).nsec) end + def test_at_with_unit + assert_equal(123456789, Time.at(0, 123456789, :nanosecond).nsec) + assert_equal(123456789, Time.at(0, 123456789, :nsec).nsec) + assert_equal(123456000, Time.at(0, 123456, :microsecond).nsec) + assert_equal(123456000, Time.at(0, 123456, :usec).nsec) + assert_equal(123000000, Time.at(0, 123, :millisecond).nsec) + assert_raise(ArgumentError){ Time.at(0, 1, 2) } + assert_raise(ArgumentError){ Time.at(0, 1, :invalid) } + assert_raise(ArgumentError){ Time.at(0, 1, nil) } + end + def test_at_rational assert_equal(1, Time.at(Rational(1,1) / 1000000000).nsec) assert_equal(1, Time.at(1167609600 + Rational(1,1) / 1000000000).nsec) @@ -307,7 +319,9 @@ class TestTime < Test::Unit::TestCase in_timezone('JST-9') do t = Time.local(2013, 2, 24) assert_equal('JST', Time.local(2013, 2, 24).zone) - assert_equal('JST', Marshal.load(Marshal.dump(t)).zone) + t = Marshal.load(Marshal.dump(t)) + assert_equal('JST', t.zone) + assert_equal('JST', (t+1).zone, '[ruby-core:81892] [Bug #13710]') end end @@ -410,13 +424,18 @@ class TestTime < Test::Unit::TestCase end def test_time_interval - m = Mutex.new.lock + m = Thread::Mutex.new.lock assert_nothing_raised { Timeout.timeout(10) { m.sleep(0) } } assert_raise(ArgumentError) { m.sleep(-1) } + assert_raise(TypeError) { m.sleep("") } + assert_raise(TypeError) { sleep("") } + obj = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {m.sleep(obj)} + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {sleep(obj)} end def test_to_f @@ -424,6 +443,12 @@ class TestTime < Test::Unit::TestCase assert_equal(946684800.0, t2000.to_f) end + def test_to_f_accuracy + # https://bugs.ruby-lang.org/issues/10135#note-1 + f = 1381089302.195 + assert_equal(f, Time.at(f).to_f, "[ruby-core:64373] [Bug #10135] note-1") + end + def test_cmp t2000 = get_t2000 assert_equal(-1, t2000 <=> Time.gm(2001)) @@ -485,6 +510,7 @@ class TestTime < Test::Unit::TestCase t3 = t1.getlocal("-02:00") assert_equal(t1, t3) assert_equal(-7200, t3.utc_offset) + assert_equal([1999, 12, 31, 22, 0, 0], [t3.year, t3.mon, t3.mday, t3.hour, t3.min, t3.sec]) t1.localtime assert_equal(t1, t2) assert_equal(t1.gmt?, t2.gmt?) @@ -517,8 +543,19 @@ class TestTime < Test::Unit::TestCase assert_equal(Time.at(946684800).getlocal.to_s, Time.at(946684800).to_s) end + def assert_zone_encoding(time) + zone = time.zone + assert_predicate(zone, :valid_encoding?) + if zone.ascii_only? + assert_equal(Encoding::US_ASCII, zone.encoding) + else + enc = Encoding.default_internal || Encoding.find('locale') + assert_equal(enc, zone.encoding) + end + end + def test_zone - assert_equal(Encoding.find('locale'), Time.now.zone.encoding) + assert_zone_encoding Time.now end def test_plus_minus_succ @@ -567,7 +604,7 @@ class TestTime < Test::Unit::TestCase assert_equal(1, t2000.yday) assert_equal(false, t2000.isdst) assert_equal("UTC", t2000.zone) - assert_equal(Encoding.find("locale"), t2000.zone.encoding) + assert_zone_encoding(t2000) assert_equal(0, t2000.gmt_offset) assert_not_predicate(t2000, :sunday?) assert_not_predicate(t2000, :monday?) @@ -589,7 +626,7 @@ class TestTime < Test::Unit::TestCase assert_equal(t.yday, Time.at(946684800).yday) assert_equal(t.isdst, Time.at(946684800).isdst) assert_equal(t.zone, Time.at(946684800).zone) - assert_equal(Encoding.find("locale"), Time.at(946684800).zone.encoding) + assert_zone_encoding(Time.at(946684800)) assert_equal(t.gmt_offset, Time.at(946684800).gmt_offset) assert_equal(t.sunday?, Time.at(946684800).sunday?) assert_equal(t.monday?, Time.at(946684800).monday?) @@ -627,6 +664,8 @@ class TestTime < Test::Unit::TestCase assert_equal("UTC", t2000.strftime("%Z")) assert_equal("%", t2000.strftime("%%")) assert_equal("0", t2000.strftime("%-S")) + 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_equal("foo\0bar\x0000\x0000\x0000", t2000.strftime("foo\0bar\0%H\0%M\0%S")) @@ -654,6 +693,12 @@ class TestTime < Test::Unit::TestCase t = Time.at(946684800, 123456.789) assert_equal("946684800", t.strftime("%s")) assert_equal("946684800", t.utc.strftime("%s")) + + t = Time.at(10000000000000000000000) + assert_equal("<<10000000000000000000000>>", t.strftime("<<%s>>")) + assert_equal("<<010000000000000000000000>>", t.strftime("<<%24s>>")) + assert_equal("<<010000000000000000000000>>", t.strftime("<<%024s>>")) + assert_equal("<< 10000000000000000000000>>", t.strftime("<<%_24s>>")) end def test_strftime_zone @@ -704,6 +749,13 @@ class TestTime < Test::Unit::TestCase assert_equal(" 2", t.strftime("%l")) assert_equal("02", t.strftime("%0l")) assert_equal(" 2", t.strftime("%_l")) + assert_equal("MON", t.strftime("%^a")) + assert_equal("OCT", t.strftime("%^b")) + + t = get_t2000 + assert_equal("UTC", t.strftime("%^Z")) + assert_equal("utc", t.strftime("%#Z")) + assert_equal("SAT JAN 1 00:00:00 2000", t.strftime("%^c")) end def test_strftime_invalid_flags @@ -723,6 +775,12 @@ class TestTime < Test::Unit::TestCase t = Time.utc(-1,1,4) assert_equal("-0001", t.strftime("%Y")) assert_equal("-0001", t.strftime("%G")) + + t = Time.utc(10000000000000000000000,1,1) + assert_equal("<<10000000000000000000000>>", t.strftime("<<%Y>>")) + assert_equal("<<010000000000000000000000>>", t.strftime("<<%24Y>>")) + assert_equal("<<010000000000000000000000>>", t.strftime("<<%024Y>>")) + assert_equal("<< 10000000000000000000000>>", t.strftime("<<%_24Y>>")) end def test_strftime_weeknum @@ -786,8 +844,7 @@ class TestTime < Test::Unit::TestCase end def test_strftime_too_wide - bug4457 = '[ruby-dev:43285]' - assert_raise(Errno::ERANGE, bug4457) {Time.now.strftime('%8192z')} + assert_equal(8192, Time.now.strftime('%8192z').size) end def test_strfimte_zoneoffset @@ -999,6 +1056,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 @@ -1017,4 +1097,70 @@ class TestTime < Test::Unit::TestCase } end + def test_getlocal_nil + now = Time.now + now2 = nil + now3 = nil + assert_nothing_raised { + now2 = now.getlocal + now3 = now.getlocal(nil) + } + assert_equal now2, now3 + assert_equal now2.zone, now3.zone + end + + def test_strftime_yearday_on_last_day_of_year + t = Time.utc(2015, 12, 31, 0, 0, 0) + assert_equal("365", t.strftime("%j")) + t = Time.utc(2016, 12, 31, 0, 0, 0) + assert_equal("366", t.strftime("%j")) + + t = Time.utc(2015, 12, 30, 20, 0, 0).getlocal("+05:00") + assert_equal("365", t.strftime("%j")) + t = Time.utc(2016, 12, 30, 20, 0, 0).getlocal("+05:00") + assert_equal("366", t.strftime("%j")) + + t = Time.utc(2016, 1, 1, 1, 0, 0).getlocal("-05:00") + assert_equal("365", t.strftime("%j")) + t = Time.utc(2017, 1, 1, 1, 0, 0).getlocal("-05:00") + 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 + def to_s; "Inexact"; end + define_method(:to_int) {bad} + define_method(:to_r) {bad} + end.new + assert_raise_with_message(TypeError, /Inexact/) {Time.at(x)} + end + + 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 + 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 + assert_equal expect, ObjectSpace.memsize_of(t) + rescue LoadError => e + skip "failed to load objspace: #{e.message}" + end end diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb index 533ceb3d8e..bfe9b4eef3 100644 --- a/test/ruby/test_time_tz.rb +++ b/test/ruby/test_time_tz.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestTimeTZ < Test::Unit::TestCase @@ -83,6 +84,16 @@ 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 + } def time_to_s(t) t.to_s @@ -96,6 +107,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) + } + skip "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]) @@ -125,12 +148,19 @@ class TestTimeTZ < Test::Unit::TestCase 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]) @@ -148,31 +178,40 @@ class TestTimeTZ < Test::Unit::TestCase def test_europe_brussels with_tz(tz="Europe/Brussels") { assert_time_constructor(tz, "1916-04-30 23:59:59 +0100", :local, [1916,4,30,23,59,59]) - assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1], "[ruby-core:30672]") + assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1], "[ruby-core:30672] [Bug #3411]") assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,0,59,59]) assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1,1,0,0]) assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,1,59,59]) } end + def test_europe_berlin + with_tz(tz="Europe/Berlin") { + assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [2011,10,30,2,0,0], "[ruby-core:67345] [Bug #10698]") + assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [0,0,2,30,10,2011,nil,nil,false,nil]) + assert_time_constructor(tz, "2011-10-30 02:00:00 +0200", :local, [0,0,2,30,10,2011,nil,nil,true,nil]) + } + end + def test_europe_lisbon - with_tz(tz="Europe/Lisbon") { + with_tz("Europe/Lisbon") { assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone) } end if has_lisbon_tz - def test_europe_moscow - with_tz(tz="Europe/Moscow") { - assert_time_constructor(tz, "1992-03-29 00:00:00 +0400", :local, [1992,3,28,23,0,0]) - assert_time_constructor(tz, "1992-03-29 00:59:59 +0400", :local, [1992,3,28,23,59,59]) - } - end - 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 @@ -259,12 +298,13 @@ class TestTimeTZ < Test::Unit::TestCase assert_equal(format_gmtoff(gmtoff), t.strftime("%z")) assert_equal(format_gmtoff(gmtoff, true), t.strftime("%:z")) assert_equal(format_gmtoff2(gmtoff), t.strftime("%::z")) + assert_equal(Encoding::US_ASCII, t.zone.encoding) } } } group_by(sample) {|tz, _, _, _| tz }.each {|tz, a| - a.each_with_index {|(_, u, l, gmtoff), i| + a.each_with_index {|(_, _, l, gmtoff), i| expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)]) monotonic_to_past = i == 0 || (a[i-1][2] <=> l) < 0 monotonic_to_future = i == a.length-1 || (l <=> a[i+1][2]) < 0 @@ -326,10 +366,23 @@ America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isd 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 16:30:00 1981 UTC = Fri Jan 1 00:30: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 @@ -346,15 +399,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 -Europe/Moscow Sat Jan 18 23:59:59 1992 UTC = Sun Jan 19 01:59:59 1992 MSK isdst=0 gmtoff=7200 -Europe/Moscow Sun Jan 19 00:00:00 1992 UTC = Sun Jan 19 03:00:00 1992 MSK isdst=0 gmtoff=10800 -Europe/Moscow Sat Mar 28 19:59:59 1992 UTC = Sat Mar 28 22:59:59 1992 MSK isdst=0 gmtoff=10800 -Europe/Moscow Sat Mar 28 20:00:00 1992 UTC = Sun Mar 29 00:00:00 1992 MSD isdst=1 gmtoff=14400 -Europe/Moscow Sat Sep 26 18:59:59 1992 UTC = Sat Sep 26 22:59:59 1992 MSD isdst=1 gmtoff=14400 -Europe/Moscow Sat Sep 26 19:00:00 1992 UTC = Sat Sep 26 22:00:00 1992 MSK isdst=0 gmtoff=10800 +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 @@ -393,7 +449,7 @@ End ] } } - assert_includes(results, [true, true, true, true, true]) + assert_include(results, [true, true, true, true, true]) } end @@ -402,5 +458,6 @@ 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 end diff --git a/test/ruby/test_trace.rb b/test/ruby/test_trace.rb index 775c458fb1..77be94e9be 100644 --- a/test/ruby/test_trace.rb +++ b/test/ruby/test_trace.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestTrace < Test::Unit::TestCase diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index d18953dc70..44d238ffd2 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -1,8 +1,9 @@ # encoding: ASCII-8BIT # make sure this runs in binary mode +# frozen_string_literal: false # some of the comments are in UTF-8 require 'test/unit' -require_relative 'envutil' + class TestTranscode < Test::Unit::TestCase def test_errors assert_raise(Encoding::ConverterNotFoundError) { 'abc'.encode('foo', 'bar') } @@ -12,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 @@ -362,7 +363,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u00BF", "\xBF", 'windows-1255') # ¿ check_both_ways("\u05B0", "\xC0", 'windows-1255') # ְ check_both_ways("\u05B9", "\xC9", 'windows-1255') # ֹ - assert_raise(Encoding::UndefinedConversionError) { "\xCA".encode("utf-8", 'windows-1255') } + check_both_ways("\u05BA", "\xCA", 'windows-1255') # ֺ check_both_ways("\u05BB", "\xCB", 'windows-1255') # ֻ check_both_ways("\u05BF", "\xCF", 'windows-1255') # ֿ check_both_ways("\u05C0", "\xD0", 'windows-1255') # ׀ @@ -483,7 +484,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u2580", "\xDF", 'IBM775') # ▀ check_both_ways("\u00D3", "\xE0", 'IBM775') # Ó check_both_ways("\u2019", "\xEF", 'IBM775') # ’ - check_both_ways("\u00AD", "\xF0", 'IBM775') # osft hyphen + check_both_ways("\u00AD", "\xF0", 'IBM775') # soft hyphen check_both_ways("\u00A0", "\xFF", 'IBM775') # non-breaking space end @@ -502,7 +503,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u2580", "\xDF", 'IBM852') # ▀ check_both_ways("\u00D3", "\xE0", 'IBM852') # Ó check_both_ways("\u00B4", "\xEF", 'IBM852') # ´ - check_both_ways("\u00AD", "\xF0", 'IBM852') # osft hyphen + check_both_ways("\u00AD", "\xF0", 'IBM852') # soft hyphen check_both_ways("\u00A0", "\xFF", 'IBM852') # non-breaking space end @@ -521,7 +522,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u2580", "\xDF", 'IBM855') # ▀ check_both_ways("\u042F", "\xE0", 'IBM855') # Я check_both_ways("\u2116", "\xEF", 'IBM855') # № - check_both_ways("\u00AD", "\xF0", 'IBM855') # osft hyphen + check_both_ways("\u00AD", "\xF0", 'IBM855') # soft hyphen check_both_ways("\u00A0", "\xFF", 'IBM855') # non-breaking space end @@ -997,6 +998,92 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u00A0", "\xFF", 'CP855') # non-breaking space end + def test_ill_formed_utf_8_replace + fffd1 = "\uFFFD".encode 'UTF-16BE' + fffd2 = "\uFFFD\uFFFD".encode 'UTF-16BE' + fffd3 = "\uFFFD\uFFFD\uFFFD".encode 'UTF-16BE' + fffd4 = "\uFFFD\uFFFD\uFFFD\uFFFD".encode 'UTF-16BE' + fffd5 = "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD".encode 'UTF-16BE' + fffd6 = "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD".encode 'UTF-16BE' + + assert_equal fffd1, "\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xC3".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xDF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xE0".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xE0\xA0".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xE0\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xE1".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xEC".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xE1\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xEC\xBF".encode("utf-16be", "utf-8", invalid: :replace) + + assert_equal fffd2, "\xC0\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xC0\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xC1\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xC1\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xE0\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xE0\x9F".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xE0\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xE0\x9F\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xED\xA0".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xED\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xED\xA0\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xED\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF0\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF0\x8F".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF0\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF0\x8F\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF0\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF0\x8F\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF4\x90".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF4\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF4\x90\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF4\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF4\x90\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF4\xBF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF5\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF7\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF5\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF7\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF5\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF7\xBF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xF8".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xFB".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xF8\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xFB\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xF8\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xFB\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xF8\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xFB\xBF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xF8\x80\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xFB\xBF\xBF\xBF\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xFC".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xFD".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xFC\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xFD\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xFC\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xFD\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xFC\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xFD\xBF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xFC\x80\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xFD\xBF\xBF\xBF\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd6, "\xFC\x80\x80\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd6, "\xFD\xBF\xBF\xBF\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xFE".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd1, "\xFF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xFE\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd2, "\xFF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xFE\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd3, "\xFF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xFE\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd4, "\xFF\xBF\xBF\xBF".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xFE\x80\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd5, "\xFF\xBF\xBF\xBF\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd6, "\xFE\x80\x80\x80\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + assert_equal fffd6, "\xFF\xBF\xBF\xBF\x80\x80".encode("utf-16be", "utf-8", invalid: :replace) + end + def check_utf_16_both_ways(utf8, raw) copy = raw.dup 0.step(copy.length-1, 2) { |i| copy[i+1], copy[i] = copy[i], copy[i+1] } @@ -1212,6 +1299,9 @@ class TestTranscode < Test::Unit::TestCase def test_invalid_replace_string assert_equal("a<x>A", "a\x80A".encode("us-ascii", "euc-jp", :invalid=>:replace, :replace=>"<x>")) assert_equal("a<x>A", "a\x80A".encode("us-ascii", "euc-jis-2004", :invalid=>:replace, :replace=>"<x>")) + s = "abcd\u{c1}" + r = s.b.encode("UTF-8", "UTF-8", invalid: :replace, replace: "\u{fffd}") + assert_equal(s, r) end def test_undef_replace @@ -2019,6 +2109,13 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u4e17", "\x81\x40", 'Big5-UAO') # 丗 end + def test_EBCDIC + check_both_ways("abcdeABCDE", "\x81\x82\x83\x84\x85\xC1\xC2\xC3\xC4\xC5", 'IBM037') + check_both_ways("aijrszAIJRSZ09", "\x81\x89\x91\x99\xA2\xA9\xC1\xC9\xD1\xD9\xE2\xE9\xF0\xF9", 'IBM037') + check_both_ways("Matz", "\xD4\x81\xA3\xA9", 'IBM037') + check_both_ways("D\u00FCrst", "\xC4\xDC\x99\xA2\xA3", 'IBM037') # Dürst + end + def test_nothing_changed a = "James".force_encoding("US-ASCII") b = a.encode("Shift_JIS") @@ -2083,17 +2180,19 @@ class TestTranscode < Test::Unit::TestCase def test_valid_dummy_encoding bug9314 = '[ruby-core:59354] [Bug #9314]' - assert_separately(%W[- -- #{bug9314}], <<-'end;') - bug = ARGV.shift - result = assert_nothing_raised(TypeError, bug) {break "test".encode(Encoding::UTF_16)} - assert_equal("\xFE\xFF\x00t\x00e\x00s\x00t", result.b, bug) - result = assert_nothing_raised(TypeError, bug) {break "test".encode(Encoding::UTF_32)} - assert_equal("\x00\x00\xFE\xFF\x00\x00\x00t\x00\x00\x00e\x00\x00\x00s\x00\x00\x00t", result.b, bug) + assert_separately(%W[- -- #{bug9314}], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bug = ARGV.shift + result = assert_nothing_raised(TypeError, bug) {break "test".encode(Encoding::UTF_16)} + assert_equal("\xFE\xFF\x00t\x00e\x00s\x00t", result.b, bug) + result = assert_nothing_raised(TypeError, bug) {break "test".encode(Encoding::UTF_32)} + assert_equal("\x00\x00\xFE\xFF\x00\x00\x00t\x00\x00\x00e\x00\x00\x00s\x00\x00\x00t", result.b, bug) end; end def test_loading_race - assert_separately([], <<-'end;') #do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; bug11277 = '[ruby-dev:49106] [Bug #11277]' num = 2 th = (0...num).map do |i| @@ -2110,6 +2209,17 @@ class TestTranscode < Test::Unit::TestCase end; end + def test_scrub_encode_with_coderange + bug = '[ruby-core:82674] [Bug #13874]' + s = "\xe5".b + u = Encoding::UTF_8 + assert_equal("?", s.encode(u, u, invalid: :replace, replace: "?"), + "should replace invalid byte") + assert_predicate(s, :valid_encoding?, "any char is valid in binary") + assert_equal("?", s.encode(u, u, invalid: :replace, replace: "?"), + "#{bug} coderange should not have side effects") + end + def test_universal_newline bug11324 = '[ruby-core:69841] [Bug #11324]' usascii = Encoding::US_ASCII diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb index e1c98076c0..6d513a238f 100644 --- a/test/ruby/test_undef.rb +++ b/test/ruby/test_undef.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestUndef < Test::Unit::TestCase diff --git a/test/ruby/test_unicode_escape.rb b/test/ruby/test_unicode_escape.rb index 2561b49486..5913bb0130 100644 --- a/test/ruby/test_unicode_escape.rb +++ b/test/ruby/test_unicode_escape.rb @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestUnicodeEscape < Test::Unit::TestCase def test_basic @@ -256,16 +256,17 @@ EOS assert_raise(SyntaxError) { eval %q("\ughij") } # bad hex digits assert_raise(SyntaxError) { eval %q("\u{ghij}") } # bad hex digits - assert_raise(SyntaxError) { eval %q("\u{123 456 }")} # extra space - assert_raise(SyntaxError) { eval %q("\u{ 123 456}")} # extra space - assert_raise(SyntaxError) { eval %q("\u{123 456}")} # extra space + assert_raise_with_message(SyntaxError, /invalid/) { + eval %q("\u{123.}") # bad char + } -# The utf-8 encoding object currently does not object to codepoints -# in the surrogate blocks, so these do not raise an error. -# assert_raise(SyntaxError) { "\uD800" } # surrogate block -# assert_raise(SyntaxError) { "\uDCBA" } # surrogate block -# assert_raise(SyntaxError) { "\uDFFF" } # surrogate block -# assert_raise(SyntaxError) { "\uD847\uDD9A" } # surrogate pair + # assert_raise(SyntaxError) { eval %q("\u{123 456 }")} # extra space + # assert_raise(SyntaxError) { eval %q("\u{ 123 456}")} # extra space + # assert_raise(SyntaxError) { eval %q("\u{123 456}")} # extra space + assert_raise(SyntaxError) { eval %q("\uD800") } # surrogate block + assert_raise(SyntaxError) { eval %q("\uDCBA") } # surrogate block + assert_raise(SyntaxError) { eval %q("\uDFFF") } # surrogate block + assert_raise(SyntaxError) { eval %q("\uD847\uDD9A") } # surrogate pair end end diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index b902288fdd..a9b1fd50ff 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestVariable < Test::Unit::TestCase class Gods @@ -29,12 +29,14 @@ class TestVariable < Test::Unit::TestCase @@rule = "Cronus" # modifies @@rule in Gods include Olympians def ruler4 - @@rule + EnvUtil.suppress_warning { + @@rule + } end end def test_variable - assert_instance_of(Fixnum, $$) + assert_instance_of(Integer, $$) # read-only variable assert_raise(NameError) do @@ -65,6 +67,7 @@ class TestVariable < Test::Unit::TestCase def test_local_variables lvar = 1 assert_instance_of(Symbol, local_variables[0], "[ruby-dev:34008]") + lvar end def test_local_variables2 @@ -72,6 +75,7 @@ class TestVariable < Test::Unit::TestCase proc do |y| assert_equal([:x, :y], local_variables.sort) end.call + x end def test_local_variables3 @@ -81,29 +85,79 @@ class TestVariable < Test::Unit::TestCase assert_equal([:x, :y, :z], local_variables.sort) end end.call + x end def test_shadowing_local_variables bug9486 = '[ruby-core:60501] [Bug #9486]' - x = tap {|x| break local_variables} - assert_equal([:x, :bug9486, :x], x) + assert_equal([:x, :bug9486], tap {|x| break local_variables}, bug9486) end def test_shadowing_block_local_variables bug9486 = '[ruby-core:60501] [Bug #9486]' - x = tap {|;x| break local_variables} - assert_equal([:x, :bug9486, :x], x) + assert_equal([:x, :bug9486], tap {|;x| x = x; break local_variables}, bug9486) + end + + def test_global_variables + gv = global_variables + assert_empty(gv.grep(/\A(?!\$)/)) + assert_nil($~) + assert_not_include(gv, :$1) + /(\w)(\d)?(.)(.)(.)(.)(.)(.)(.)(.)(\d)?(.)/ =~ "globalglobalglobal" + assert_not_nil($~) + gv = global_variables - gv + assert_include(gv, :$1) + assert_not_include(gv, :$2) + assert_not_include(gv, :$11) + assert_include(gv, :$12) end def test_global_variable_0 assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, []) end - def test_global_variable_poped - assert_nothing_raised { eval("$foo; 1") } + def test_global_variable_popped + assert_nothing_raised { + EnvUtil.suppress_warning { + eval("$foo; 1") + } + } + end + + def test_constant_popped + assert_nothing_raised { + EnvUtil.suppress_warning { + eval("TestVariable::Gods; 1") + } + } + end + + 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}" + + assert_raise_with_message(FrozenError, msg) do + v.instance_variable_set(:@foo, :bar) + end + + assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)} + assert_not_send([v, :instance_variable_defined?, :@foo]) + + assert_raise_with_message(FrozenError, msg) do + v.remove_instance_variable(:@foo) + end + end + 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) + assert_equal(%i(v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11), v, bug11674) end - def test_constant_poped - assert_nothing_raised { eval("TestVariable::Gods; 1") } + private + def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:) + local_variables end end diff --git a/test/ruby/test_vm_dump.rb b/test/ruby/test_vm_dump.rb new file mode 100644 index 0000000000..68f0fa7f27 --- /dev/null +++ b/test/ruby/test_vm_dump.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +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, "", [], /^\[IMPORTANT\]/) + end + + def test_darwin_invalid_call + assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle::Function.new(Fiddle::Pointer.new(1), [], Fiddle::TYPE_VOID).call']) + end + + def test_darwin_segv_in_syscall + assert_darwin_vm_dump_works('-e1.times{Process.kill :SEGV,$$}') + end + + def test_darwin_invalid_access + assert_darwin_vm_dump_works(['-rfiddle', '-eFiddle.dlunwrap(100).class']) + end +end diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index bcaab894a7..cde186c5f3 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestWeakMap < Test::Unit::TestCase def setup @@ -19,13 +19,13 @@ class TestWeakMap < Test::Unit::TestCase assert_raise(ArgumentError) {@wm[true] = x} assert_raise(ArgumentError) {@wm[false] = x} assert_raise(ArgumentError) {@wm[nil] = x} - assert_raise(RuntimeError) {@wm[42] = x} - assert_raise(RuntimeError) {@wm[:foo] = 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(RuntimeError) {@wm[x] = 42} - assert_raise(RuntimeError) {@wm[x] = :foo} + assert_raise(ArgumentError) {@wm[x] = 42} + assert_raise(ArgumentError) {@wm[x] = :foo} end def test_include? @@ -39,6 +39,7 @@ class TestWeakMap < Test::Unit::TestCase x = nil end GC.start + skip # TODO: failure introduced from r60440 assert_not_send([@wm, m, k]) end alias test_member? test_include? diff --git a/test/ruby/test_whileuntil.rb b/test/ruby/test_whileuntil.rb index ca853af944..121c44817d 100644 --- a/test/ruby/test_whileuntil.rb +++ b/test/ruby/test_whileuntil.rb @@ -1,6 +1,6 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' -require_relative 'envutil' class TestWhileuntil < Test::Unit::TestCase def test_while @@ -61,7 +61,7 @@ class TestWhileuntil < Test::Unit::TestCase tmp = open(tmpfilename, "r") while line = tmp.gets() - break if 3 + break if $. == 3 assert_no_match(/vt100/, line) assert_no_match(/Amiga/, line) assert_no_match(/paper/, line) diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb index 143ee55a9f..9b2b2f37e0 100644 --- a/test/ruby/test_yield.rb +++ b/test/ruby/test_yield.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'stringio' @@ -66,7 +67,7 @@ class TestRubyYield < Test::Unit::TestCase end def test_with_enum - obj = Object + obj = Object.new def obj.each yield(*[]) end @@ -340,11 +341,12 @@ class TestRubyYieldGen < Test::Unit::TestCase t = t.subst('vars') { " [#{vars.join(",")}]" } emu_values = emu(t, vars, islambda) s = t.to_s + o = Object.new #print "#{s}\t\t" #STDOUT.flush eval_values = disable_stderr { begin - eval(s, nil, 'generated_code_in_check_nofork') + o.instance_eval(s, 'generated_code_in_check_nofork') rescue ArgumentError ArgumentError end @@ -354,23 +356,29 @@ class TestRubyYieldGen < Test::Unit::TestCase assert_equal(emu_values, eval_values, s) end + def assert_all_sentences(syntax, *args) + syntax = Sentence.expand_syntax(syntax) + all_assertions do |a| + Sentence.each(syntax, *args) {|t| + a.for(t) {yield t} + } + end + end + def test_yield - syntax = Sentence.expand_syntax(Syntax) - Sentence.each(syntax, :test_proc, 4) {|t| + assert_all_sentences(Syntax, :test_proc, 4) {|t| check_nofork(t) } end def test_yield_lambda - syntax = Sentence.expand_syntax(Syntax) - Sentence.each(syntax, :test_lambda, 4) {|t| + assert_all_sentences(Syntax, :test_lambda, 4) {|t| check_nofork(t, true) } end def test_yield_enum - syntax = Sentence.expand_syntax(Syntax) - Sentence.each(syntax, :test_enum, 4) {|t| + assert_all_sentences(Syntax, :test_enum, 4) {|t| code = t.to_s r1, r2 = disable_stderr { eval(code, nil, 'generated_code_in_test_yield_enum') @@ -390,4 +398,28 @@ class TestRubyYieldGen < Test::Unit::TestCase end assert_equal [m, nil], y.s(m){|a,b|[a,b]} end + + def test_block_cached_argc + # [Bug #11451] + assert_separately([], <<-"end;") + class Yielder + def each + yield :x, :y, :z + end + end + class Getter1 + include Enumerable + def each(&block) + Yielder.new.each(&block) + end + end + class Getter2 + include Enumerable + def each + Yielder.new.each { |a, b, c, d| yield(a) } + end + end + Getter1.new.map{Getter2.new.each{|x|}} + end; + end end diff --git a/test/ruby/ut_eof.rb b/test/ruby/ut_eof.rb index 83325f2efc..fcd7a63988 100644 --- a/test/ruby/ut_eof.rb +++ b/test/ruby/ut_eof.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' module TestEOF |
