diff options
Diffstat (limited to 'test/ruby')
146 files changed, 47059 insertions, 6651 deletions
diff --git a/test/ruby/allpairs.rb b/test/ruby/allpairs.rb index 6cb2729b19..e5893e252a 100644 --- a/test/ruby/allpairs.rb +++ b/test/ruby/allpairs.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false module AllPairs module_function @@ -66,7 +67,6 @@ module AllPairs def each_index(*vs) n = vs.length max_v = vs.max - prime = make_prime(max_v) h = {} make_large_block(max_v, n) {|row| row = vs.zip(row).map {|v, i| i % v } 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..bde47017a2 --- /dev/null +++ b/test/ruby/enc/test_case_comprehensive.rb @@ -0,0 +1,307 @@ +# 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? + if code>="\u1C90" and code<="\u1CBF" # exception for Georgian: use lowercase for titlecase + titlecase[code] = hex2utf8(data[13]) unless data[13].empty? + else + titlecase[code] = hex2utf8 data[14] unless data[14].empty? + end + end + read_data_file('CaseFolding') do |code, data| + casefold[code] = hex2utf8(data[2]) if data[1] =~ /^[CF]$/ + 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..aa20531783 --- /dev/null +++ b/test/ruby/enc/test_case_mapping.rb @@ -0,0 +1,221 @@ +# 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 test_georgian_canary + message = "Reexamine implementation of Georgian in String#capitalize" + assert_equal false, "\u1CBB".match?(/\p{assigned}/), message + assert_equal false, "\u1CBC".match?(/\p{assigned}/), message + end + + def test_georgian_unassigned + message = "Unassigned codepoints should not be converted" + assert_equal "\u1CBB", "\u1CBB".capitalize, message + assert_equal "\u1CBC", "\u1CBC".capitalize, message + end + + def test_georgian_capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u1C91\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u1C90\u10D1\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u1C91\u10D2".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u1C92".capitalize + assert_equal "\u10D0\u10D1\u10D2", "\u10D0\u10D1\u10D2".capitalize + end + + def 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 90144cffff..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 @@ -42,7 +43,7 @@ module Emoji def test_encoding_name %w(UTF8-DoCoMo SJIS-DoCoMo).each do |n| - assert Encoding.name_list.include?(n), "encoding not found: #{n}" + assert_include Encoding.name_list, n, "encoding not found: #{n}" end end @@ -126,7 +127,7 @@ module Emoji SJIS-KDDI ISO-2022-JP-KDDI stateless-ISO-2022-JP-KDDI).each do |n| - assert Encoding.name_list.include?(n), "encoding not found: #{n}" + assert_include Encoding.name_list, n, "encoding not found: #{n}" end end @@ -250,7 +251,7 @@ module Emoji def test_encoding_name %w(UTF8-SoftBank SJIS-SoftBank).each do |n| - assert Encoding.name_list.include?(n), "encoding not found: #{n}" + assert_include Encoding.name_list, n, "encoding not found: #{n}" end end diff --git a/test/ruby/enc/test_emoji_breaks.rb b/test/ruby/enc/test_emoji_breaks.rb new file mode 100644 index 0000000000..37a29640d8 --- /dev/null +++ b/test/ruby/enc/test_emoji_breaks.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class BreakTest + attr_reader :string, :comment, :filename, :line_number, :type, :shortname + + def initialize (filename, line_number, data, comment='') + @filename = filename + @line_number = line_number + @comment = comment.gsub(/\s+/, ' ').strip + if filename=='emoji-test' + codes, @type = data.split(/\s*;\s*/) + @shortname = '' + else + codes, @type, @shortname = data.split(/\s*;\s*/) + end + @type = @type.gsub(/\s+/, ' ').strip + @shortname = @shortname.gsub(/\s+/, ' ').strip + @string = codes.split(/\s+/) + .map do |ch| + c = ch.to_i(16) + # eliminate cases with surrogates + # raise ArgumentError if 0xD800 <= c and c <= 0xDFFF + c.chr('UTF-8') + end.join + end +end + +class TestEmojiBreaks < Test::Unit::TestCase + EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-variation-sequences emoji-zwj-sequences] + EMOJI_VERSION = RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] + EMOJI_DATA_PATH = File.expand_path("../../../enc/unicode/data/emoji/#{EMOJI_VERSION}", __dir__) + + def self.expand_filename(basename) + File.expand_path("#{EMOJI_DATA_PATH}/#{basename}.txt", __dir__) + end + + def self.data_files_available? + EMOJI_DATA_FILES.all? do |f| + File.exist?(expand_filename(f)) + end + end + + def test_data_files_available + unless TestEmojiBreaks.data_files_available? + skip "Emoji data files not available in #{EMOJI_DATA_PATH}." + end + end +end + +TestEmojiBreaks.data_files_available? and class TestEmojiBreaks + def read_data + tests = [] + EMOJI_DATA_FILES.each do |filename| + version_mismatch = true + file_tests = [] + IO.foreach(TestEmojiBreaks.expand_filename(filename), encoding: Encoding::UTF_8) do |line| + line.chomp! + raise "File Name Mismatch" if $.==1 and not line=="# #{filename}.txt" + version_mismatch = false if line=="# Version: #{EMOJI_VERSION}" + next if /\A(#|\z)/.match? line + file_tests << BreakTest.new(filename, $., *line.split('#')) rescue 'whatever' + end + raise "File Version Mismatch" if version_mismatch + tests += file_tests + end + tests + end + + def all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def test_single_emoji + all_tests.each do |test| + expected = [test.string] + actual = test.string.each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + def test_embedded_emoji + all_tests.each do |test| + expected = ["\t", test.string, "\t"] + actual = "\t#{test.string}\t".each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + # test some pseodorandom combinations of emoji + def test_mixed_emoji + srand 0 + length = all_tests.length + step = 503 # use a prime number + all_tests.each do |test1| + start = rand step + start.step(by: step, to: length-1) do |t2| + test2 = all_tests[t2] + # exclude skin tones, because they glue to previous grapheme clusters + next if (0x1F3FB..0x1F3FF).include? test2.string.ord + expected = [test1.string, test2.string] + actual = (test1.string+test2.string).each_grapheme_cluster.to_a + assert_equal expected, actual, + "file1: #{test1.filename}, line1 #{test1.line_number}, " + + "file2: #{test2.filename}, line2 #{test2.line_number},\n" + + "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" + + "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}" + end + end + end +end diff --git a/test/ruby/enc/test_euc_jp.rb b/test/ruby/enc/test_euc_jp.rb index 1ccc55ccb9..4aec69e4db 100644 --- a/test/ruby/enc/test_euc_jp.rb +++ b/test/ruby/enc/test_euc_jp.rb @@ -1,11 +1,12 @@ # vim: set fileencoding=euc-jp +# frozen_string_literal: false require "test/unit" class TestEUC_JP < Test::Unit::TestCase def test_mbc_case_fold assert_match(/()(a)\1\2/i, "aA") - assert_no_match(/()(a)\1\2/i, "aA") + assert_match(/()(a)\1\2/i, "aA") end def test_property diff --git a/test/ruby/enc/test_euc_kr.rb b/test/ruby/enc/test_euc_kr.rb index 087bc795f7..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 @@ -25,4 +26,12 @@ class TestEucKr < Test::Unit::TestCase def test_left_adjust_char_head assert_equal(s("\xa1\xa1"), s("\xa1\xa1\xa1\xa1").chop) end + + def test_euro_sign + assert_equal("\u{20ac}", s("\xa2\xe6").encode("utf-8")) + end + + def test_registered_mark + assert_equal("\u{00ae}", s("\xa2\xe7").encode("utf-8")) + end end 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 f81cb7801c..059992d167 100644 --- a/test/ruby/enc/test_shift_jis.rb +++ b/test/ruby/enc/test_shift_jis.rb @@ -1,11 +1,12 @@ # vim: set fileencoding=shift_jis +# frozen_string_literal: false require "test/unit" class TestShiftJIS < Test::Unit::TestCase def test_mbc_case_fold assert_match(/()(a)\1\2/i, "aA") - assert_no_match(/()(a)\1\2/i, "a`A") + assert_match(/()(a)\1\2/i, "a`A") end def test_property @@ -22,6 +23,6 @@ class TestShiftJIS < Test::Unit::TestCase s = "" s << 0x82a9 assert_equal("", s) - assert_raise(ArgumentError) { s << 0x82 } + assert_raise(RangeError) { s << 0x82 } end end diff --git a/test/ruby/enc/test_utf16.rb b/test/ruby/enc/test_utf16.rb index 90a8314067..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 @@ -49,65 +50,77 @@ class TestUTF16 < Test::Unit::TestCase #{encdump expected} expected but not equal to #{encdump actual}. EOT - assert_block(full_message) { expected == actual } + assert_equal(expected, actual, full_message) end # 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 @@ -122,7 +135,7 @@ EOT def test_sym_eq s = "aa".force_encoding("utf-16le") - assert(s.intern != :aa, "#{encdump s}.intern != :aa") + assert_not_equal(:aa, s.intern, "#{encdump s}.intern != :aa") end def test_compatible @@ -253,10 +266,10 @@ EOT def test_succ s = "\xff\xff".force_encoding("utf-16be") - assert(s.succ.valid_encoding?, "#{encdump s}.succ.valid_encoding?") + assert_predicate(s.succ, :valid_encoding?, "#{encdump s}.succ.valid_encoding?") s = "\xdb\xff\xdf\xff".force_encoding("utf-16be") - assert(s.succ.valid_encoding?, "#{encdump s}.succ.valid_encoding?") + assert_predicate(s.succ, :valid_encoding?, "#{encdump s}.succ.valid_encoding?") end def test_regexp_union @@ -366,7 +379,7 @@ EOT def test_regexp_escape s = "\0*".force_encoding("UTF-16BE") r = Regexp.new(Regexp.escape(s)) - assert(r =~ s, "#{encdump(r)} =~ #{encdump(s)}") + assert_match(r, s, "#{encdump(r)} =~ #{encdump(s)}") end def test_casecmp2 diff --git a/test/ruby/enc/test_utf32.rb b/test/ruby/enc/test_utf32.rb index 3d4a458512..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 @@ -15,7 +16,7 @@ class TestUTF32 < Test::Unit::TestCase #{encdump expected} expected but not equal to #{encdump actual}. EOT - assert_block(full_message) { expected == actual } + assert_equal(expected, actual, full_message) end def test_substr @@ -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 new file mode 100644 index 0000000000..f264cba759 --- /dev/null +++ b/test/ruby/enc/test_windows_1252.rb @@ -0,0 +1,26 @@ +# encoding:windows-1252 +# frozen_string_literal: false + +require "test/unit" + +class TestWindows1252 < Test::Unit::TestCase + def test_stset + assert_match(/^(\xdf)\1$/i, "\xdf\xdf") + assert_match(/^(\xdf)\1$/i, "ssss") + # assert_match(/^(\xdf)\1$/i, "\xdfss") # this must be bug... + assert_match(/^[\xdfz]+$/i, "sszzsszz") + assert_match(/^SS$/i, "\xdf") + assert_match(/^Ss$/i, "\xdf") + end + + def test_windows_1252 + [0x8a, 0x8c, 0x8e, *0xc0..0xd6, *0xd8..0xde, 0x9f].zip([0x9a, 0x9c, 0x9e, *0xe0..0xf6, *0xf8..0xfe, 0xff]).each do |c1, c2| + c1 = c1.chr("windows-1252") + c2 = c2.chr("windows-1252") + assert_match(/^(#{ c1 })\1$/i, c2 + c1) + assert_match(/^(#{ c2 })\1$/i, c1 + c2) + assert_match(/^[#{ c1 }]+$/i, c2 + c1) + assert_match(/^[#{ c2 }]+$/i, c1 + c2) + end + end +end 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 978bf770bf..0000000000 --- a/test/ruby/envutil.rb +++ /dev/null @@ -1,190 +0,0 @@ -require "open3" -require "timeout" - -module EnvUtil - def rubybin - unless ENV["RUBYOPT"] - - end - 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, 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 - opt = opt.dup - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = err_c if capture_stderr - if enc = opt.delete(:encoding) - out_p.set_encoding(enc) if out_p - err_p.set_encoding(enc) 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 - if block_given? - return yield in_p, out_p, err_p - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr - in_p.write stdin_data.to_str - in_p.close - timeout = opt.fetch(:timeout, 10) - if (!capture_stdout || th_stdout.join(timeout)) && (!capture_stderr || th_stderr.join(timeout)) - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr - else - raise Timeout::Error - end - out_p.close if capture_stdout - err_p.close if capture_stderr - Process.wait pid - status = $? - return stdout, stderr, status - end - ensure - [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.kill; 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 - ensure - stderr, $stderr, $VERBOSE = $stderr, stderr, verbose - return stderr - end - module_function :verbose_warning - - def under_gc_stress - stress, GC.stress = GC.stress, true - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress -end - -module Test - module Unit - module Assertions - public - def assert_normal_exit(testsrc, message = '', opt = {}) - stdout, stderr, status = EnvUtil.invoke_ruby(%W'-W0', testsrc, true, true, opt) - pid = status.pid - faildesc = proc do - signo = status.termsig - signame = Signal.list.invert[signo] - sigdesc = "signal #{signo}" - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc << " (core dumped)" - end - full_message = '' - if !message.empty? - full_message << message << "\n" - end - if msg.empty? - full_message << "pid #{pid} killed by #{sigdesc}" - else - msg << "\n" if /\n\z/ !~ msg - full_message << "pid #{pid} killed by #{sigdesc}\n#{msg.gsub(/^/, '| ')}" - end - full_message - end - assert_block(faildesc) { !status.signaled? } - 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 block_given? - yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }) - else - if test_stdout.is_a?(Regexp) - assert_match(test_stdout, stdout, message) - else - assert_equal(test_stdout, stdout.lines.map {|l| l.chomp }, message) - end - if test_stderr.is_a?(Regexp) - assert_match(test_stderr, stderr, message) - else - assert_equal(test_stderr, stderr.lines.map {|l| l.chomp }, message) - end - status - end - end - - def assert_ruby_status(args, test_stdin="", message=nil, opt={}) - stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, false, false, opt) - m = message ? "#{message} (#{status.inspect})" : "ruby exit status is not success: #{status.inspect}" - assert(status.success?, m) - 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) - end -end diff --git a/test/ruby/lbtest.rb b/test/ruby/lbtest.rb index df7872dc76..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 @@ -35,14 +35,15 @@ lb = LocalBarrier.new(n) (n - 1).times do |i| Thread.start do - sleep((rand(n) + 1) / 10.0) - puts "#{i}: done" + sleep((rand(n) + 1) / 100.0) + print "#{i}: done\n" lb.sync - puts "#{i}: cont" + print "#{i}: cont\n" end end lb.sync -puts "#{n-1}: cone" +print "#{n-1}: cont\n" +# lb.join # [ruby-dev:30653] -puts "exit." +print "exit.\n" diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb index 39471aac64..5c48a8d853 100644 --- a/test/ruby/marshaltestlib.rb +++ b/test/ruby/marshaltestlib.rb @@ -1,6 +1,7 @@ # coding: utf-8 +# frozen_string_literal: false module MarshalTestLib - # include this module to a Test::Unit::TestCase and definde encode(o) and + # include this module to a Test::Unit::TestCase and define encode(o) and # decode(s) methods. e.g. # # def encode(o) @@ -40,6 +41,14 @@ module MarshalTestLib end end + def marshal_equal_with_ancestry(o1, msg = nil) + marshal_equal(o1, msg) do |o| + ancestry = o.singleton_class.ancestors + ancestry[ancestry.index(o.singleton_class)] = :singleton_class + ancestry + end + end + class MyObject; def initialize(v) @v = v end; attr_reader :v; end def test_object o1 = Object.new @@ -54,24 +63,26 @@ module MarshalTestLib def test_object_extend o1 = Object.new o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) end def test_object_subclass_extend o1 = MyObject.new(2) o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors + marshal_equal_with_ancestry(o1) + end + + def test_object_prepend + bug8041 = '[ruby-core:53202] [Bug #8041]' + + o1 = MyObject.new(42) + o1.singleton_class.class_eval {prepend Mod1} + assert_nothing_raised(ArgumentError, bug8041) { + marshal_equal_with_ancestry(o1, bug8041) } end @@ -99,7 +110,9 @@ module MarshalTestLib class MyException < Exception; def initialize(v, *args) super(*args); @v = v; end; attr_reader :v; end def test_exception marshal_equal(Exception.new('foo')) {|o| o.message} - marshal_equal(assert_raise(NoMethodError) {no_such_method()}) {|o| o.message} + obj = Object.new + e = assert_raise(NoMethodError) {obj.no_such_method()} + marshal_equal(e) {|o| o.message} end def test_exception_subclass @@ -141,25 +154,17 @@ module MarshalTestLib def test_hash_extend o1 = Hash.new o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) end def test_hash_subclass_extend o1 = MyHash.new(2) o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) end def test_bignum @@ -178,22 +183,6 @@ module MarshalTestLib marshal_equal(0x3fff_ffff) end - def test_fixnum_ivar - o1 = 1 - o1.instance_eval { @iv = 2 } - marshal_equal(o1) {|o| o.instance_eval { @iv }} - ensure - 1.instance_eval { remove_instance_variable("@iv") } - end - - def test_fixnum_ivar_self - o1 = 1 - o1.instance_eval { @iv = 1 } - marshal_equal(o1) {|o| o.instance_eval { @iv }} - ensure - 1.instance_eval { remove_instance_variable("@iv") } - end - def test_float marshal_equal(-1.0) marshal_equal(0.0) @@ -207,30 +196,6 @@ module MarshalTestLib marshal_equal(NegativeZero) {|o| 1.0/o} end - def test_float_ivar - o1 = 1.23 - o1.instance_eval { @iv = 1 } - marshal_equal(o1) {|o| o.instance_eval { @iv }} - end - - def test_float_ivar_self - o1 = 5.5 - o1.instance_eval { @iv = o1 } - marshal_equal(o1) {|o| o.instance_eval { @iv }} - end - - def test_float_extend - o1 = 0.0/0.0 - o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } - o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } - end - class MyRange < Range; def initialize(v, *args) super(*args); @v = v; end end def test_range marshal_equal(1..2) @@ -277,7 +242,7 @@ module MarshalTestLib str = MyString.new(10, "b") str.instance_eval { @v = str } marshal_equal(str) { |o| - assert_equal(o.__id__, o.instance_eval { @v }.__id__) + assert_same(o, o.instance_eval { @v }) o.instance_eval { @v } } end @@ -287,36 +252,17 @@ module MarshalTestLib o.extend(Mod1) str = MyString.new(o, "c") marshal_equal(str) { |v| - assert(v.instance_eval { @v }.kind_of?(Mod1)) + assert_kind_of(Mod1, v.instance_eval { @v }) } end MyStruct = Struct.new("MyStruct", :a, :b) - if RUBY_VERSION < "1.8.0" - # Struct#== is not defined in ruby/1.6 - class MyStruct - def ==(rhs) - return true if __id__ == rhs.__id__ - return false unless rhs.is_a?(::Struct) - return false if self.class != rhs.class - members.each do |member| - return false if self.__send__(member) != rhs.__send__(member) - end - return true - end - end - end class MySubStruct < MyStruct; def initialize(v, *args) super(*args); @v = v; end end def test_struct marshal_equal(MyStruct.new(1,2)) end def test_struct_subclass - if RUBY_VERSION < "1.8.0" - # Substruct instance cannot be dumped in ruby/1.6 - # ::Marshal.dump(MySubStruct.new(10, 1, 2)) #=> uninitialized struct - return false - end marshal_equal(MySubStruct.new(10,1,2)) end @@ -329,13 +275,9 @@ module MarshalTestLib def test_struct_subclass_extend o1 = MyStruct.new o1.extend(Mod1) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) o1.extend(Mod2) - marshal_equal(o1) { |o| - (class << self; self; end).ancestors - } + marshal_equal_with_ancestry(o1) end def test_symbol @@ -426,6 +368,11 @@ module MarshalTestLib o = Object.new def o.m() end assert_raise(TypeError) { marshaltest(o) } + + bug8043 = '[ruby-core:53206] [Bug #8043]' + class << o; prepend Mod1; end + assert_raise(TypeError, bug8043) {marshaltest(o)} + o = Object.new c = class << o @v = 1 @@ -444,7 +391,7 @@ module MarshalTestLib o = Object.new o.extend Mod1 o.extend Mod2 - marshal_equal(o) {|obj| class << obj; ancestors end} + marshal_equal_with_ancestry(o) o = Object.new o.extend Module.new assert_raise(TypeError) { marshaltest(o) } @@ -457,7 +404,7 @@ module MarshalTestLib o = "" o.extend Mod1 o.extend Mod2 - marshal_equal(o) {|obj| class << obj; ancestors end} + marshal_equal_with_ancestry(o) o = "" o.extend Module.new assert_raise(TypeError) { marshaltest(o) } @@ -485,20 +432,6 @@ module MarshalTestLib end MyStruct2 = Struct.new(:a, :b) - if RUBY_VERSION < "1.8.0" - # Struct#== is not defined in ruby/1.6 - class MyStruct2 - def ==(rhs) - return true if __id__ == rhs.__id__ - return false unless rhs.is_a?(::Struct) - return false if self.class != rhs.class - members.each do |member| - return false if self.__send__(member) != rhs.__send__(member) - end - return true - end - end - end def test_struct_toplevel o = MyStruct2.new(1,2) marshal_equal(o) 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 6320121bce..e81636fa43 100644 --- a/test/ruby/test_alias.rb +++ b/test/ruby/test_alias.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestAlias < Test::Unit::TestCase @@ -52,16 +53,6 @@ class TestAlias < Test::Unit::TestCase end end - def test_JVN_83768862 - d = lambda { - $SAFE = 4 - dclass = Class.new(C) - dclass.send(:alias_method, :mm, :m) - dclass.new - }.call - assert_raise(SecurityError) { d.mm } - end - def test_nonexistmethod assert_raise(NameError){ Class.new{ @@ -104,4 +95,142 @@ class TestAlias < Test::Unit::TestCase end assert_equal(:ok, d.new.bar) end + + module SuperInAliasedModuleMethod + module M + def foo + super << :M + end + + alias bar foo + end + + class Base + def foo + [:Base] + end + end + + class Derived < Base + include M + end + end + + # [ruby-dev:46028] + def test_super_in_aliased_module_method # fails in 1.8 + assert_equal([:Base, :M], SuperInAliasedModuleMethod::Derived.new.bar) + end + + def test_alias_wb_miss + assert_normal_exit "#{<<-"begin;"}\n#{<<-'end;'}" + begin; + require 'stringio' + GC.verify_internal_consistency + GC.start + class StringIO + alias_method :read_nonblock, :sysread + end + GC.verify_internal_consistency + end; + end + + def test_cyclic_zsuper + bug9475 = '[ruby-core:60431] [Bug #9475]' + + a = Module.new do + def foo + "A" + end + end + + b = Class.new do + include a + attr_reader :b + + def foo + @b ||= 0 + raise SystemStackError if (@b += 1) > 1 + # "foo from B" + super + "B" + end + end + + c = Class.new(b) do + alias orig_foo foo + + def foo + # "foo from C" + orig_foo + "C" + end + end + + b.class_eval do + alias orig_foo foo + attr_reader :b2 + + def foo + @b2 ||= 0 + raise SystemStackError if (@b2 += 1) > 1 + # "foo from B (again)" + orig_foo + "B2" + end + end + + assert_nothing_raised(SystemStackError, bug9475) do + assert_equal("ABC", c.new.foo, bug9475) + end + end + + def test_alias_in_module + bug9663 = '[ruby-core:61635] [Bug #9663]' + + assert_separately(['-', bug9663], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + bug = ARGV[0] + + m = Module.new do + alias orig_to_s to_s + end + + o = Object.new.extend(m) + 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 ffd5096789..a76bdccf45 100644 --- a/test/ruby/test_argf.rb +++ b/test/ruby/test_argf.rb @@ -1,51 +1,53 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' require 'tmpdir' require 'tempfile' -require_relative 'envutil' +require 'fileutils' class TestArgf < Test::Unit::TestCase def setup - @t1 = Tempfile.new("argf-foo") + @tmpdir = Dir.mktmpdir + @tmp_count = 0 + @t1 = make_tempfile0("argf-foo") @t1.binmode @t1.puts "1" @t1.puts "2" @t1.close - @t2 = Tempfile.new("argf-bar") + @t2 = make_tempfile0("argf-bar") @t2.binmode @t2.puts "3" @t2.puts "4" @t2.close - @t3 = Tempfile.new("argf-baz") + @t3 = make_tempfile0("argf-baz") @t3.binmode @t3.puts "5" @t3.puts "6" @t3.close - @tmps = [@t1, @t2, @t3] end def teardown - @tmps.each {|t| - bak = t.path + ".bak" - File.unlink bak if File.file? bak - t.close(true) - } + FileUtils.rmtree(@tmpdir) + end + + def make_tempfile0(basename) + @tmp_count += 1 + open("#{@tmpdir}/#{basename}-#{@tmp_count}", "w") end - def make_tempfile - t = Tempfile.new("argf-qux") + def make_tempfile(basename = "argf-qux") + t = make_tempfile0(basename) t.puts "foo" t.puts "bar" t.puts "baz" t.close - @tmps << t t end - def ruby(*args) + def ruby(*args, external_encoding: Encoding::UTF_8) args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin - f = IO.popen([ruby] + args, 'r+') + f = IO.popen([ruby] + args, 'r+', external_encoding: external_encoding) yield(f) ensure f.close unless !f || f.closed? @@ -55,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| @@ -69,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)) @@ -151,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' @@ -165,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)) @@ -175,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 @@ -188,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)) @@ -200,33 +235,56 @@ 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 - if no_safe_rename - assert_equal([], e) - assert_equal([], r) - assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path)) - else - assert_match(/Can't rename .* to .*: .*. skipping file/, e.first) #' - assert_equal([], r) - assert_equal("foo\nbar\nbaz\n", File.read(t.path)) - end + 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_nonascii + ext = Encoding.default_external or + skip "no default external encoding" + t = nil + ["\u{3042}", "\u{e9}"].any? do |n| + t = make_tempfile(n.encode(ext)) + rescue Encoding::UndefinedConversionError end + t or skip "no name to test" + assert_in_out_err(["-i.bak", "-", t.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + }; + assert_equal("foo.new\n""bar.new\n""baz.new\n", File.read(t.path)) + assert_equal("foo\n""bar\n""baz\n", File.read(t.path + ".bak")) end def test_inplace_no_backup t = make_tempfile - 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 @@ -240,67 +298,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 - t = make_tempfile - - 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 - t = make_tempfile - - 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) @@ -308,7 +424,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) @@ -320,11 +437,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 @@ -336,11 +454,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" @@ -355,28 +474,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) @@ -388,12 +508,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) @@ -407,27 +528,28 @@ 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) end end - t1 = Tempfile.new("argf-foo") + t1 = open("#{@tmpdir}/argf-hoge", "w") t1.binmode t1.puts "foo" t1.close - t2 = Tempfile.new("argf-bar") + t2 = open("#{@tmpdir}/argf-moge", "w") t2.binmode t2.puts "bar" t2.close @@ -443,54 +565,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', "#{<<~"{#"}\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 + 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") @@ -500,66 +639,83 @@ class TestArgf < Test::Unit::TestCase end end + def test_readpartial_eof_twice + 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 @@ -570,32 +726,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) @@ -609,12 +768,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) @@ -628,12 +788,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) @@ -647,43 +808,117 @@ class TestArgf < Test::Unit::TestCase end def test_binmode + bug5268 = '[ruby-core:39234]' + open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"} ruby('-e', "ARGF.binmode; STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f| f.binmode - assert_equal("1\n2\n3\n4\n5\n6\n", f.read) + assert_equal("1\n2\n3\n4\n5\r\n6\r\n", f.read, bug5268) end end + def test_textmode + bug5268 = '[ruby-core:39234]' + open(@t3.path, "wb") {|f| f.write "5\r\n6\r\n"} + ruby('-e', "STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f| + f.binmode + assert_equal("1\n2\n3\n4\n5\n6\n", f.read, bug5268) + end + 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', "#{<<~"{#"}\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', "#{<<~"{#"}\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', "#{<<~"{#"}\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 + + def test_skip_in_each_char + [[@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', "#{<<~"{#"}\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 + + def test_skip_in_each_codepoint + [[@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', "#{<<~"{#"}\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', "#{<<~"{#"}\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 @@ -694,4 +929,149 @@ class TestArgf < Test::Unit::TestCase assert_equal([@t1.path, @t2.path, @t3.path].inspect, f.gets.chomp) end end + + def test_readlines_limit_0 + bug4024 = '[ruby-dev:42538]' + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_raise(ArgumentError, bug4024) do + argf.readlines(0) + end + ensure + argf.close + end + end + + def test_each_line_limit_0 + bug4024 = '[ruby-dev:42538]' + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_raise(ArgumentError, bug4024) do + argf.each_line(0).next + end + ensure + argf.close + end + end + + def test_unreadable + bug4274 = '[ruby-core:34446]' + paths = (1..2).map do + t = Tempfile.new("bug4274-") + path = t.path + t.close! + path + end + argf = ARGF.class.new(*paths) + paths.each do |path| + assert_raise_with_message(Errno::ENOENT, /- #{Regexp.quote(path)}\z/) {argf.gets} + end + assert_nil(argf.gets, bug4274) + end + + def test_readlines_twice + bug5952 = '[ruby-dev:45160]' + assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952) + end + + def test_lines + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + $stderr = $stdout + s = [] + ARGF.lines {|l| s << l } + p s + }; + assert_match(/deprecated/, f.gets) + assert_equal("[\"1\\n\", \"2\\n\", \"3\\n\", \"4\\n\", \"5\\n\", \"6\\n\"]\n", f.read) + end + end + + def test_bytes + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + $stderr = $stdout + print Marshal.dump(ARGF.bytes.to_a) + }; + assert_match(/deprecated/, f.gets) + assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read)) + end + end + + def test_chars + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + $stderr = $stdout + print [Marshal.dump(ARGF.chars.to_a)].pack('m') + }; + assert_match(/deprecated/, f.gets) + assert_equal(["1", "\n", "2", "\n", "3", "\n", "4", "\n", "5", "\n", "6", "\n"], Marshal.load(f.read.unpack('m').first)) + end + end + + def test_codepoints + 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_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb new file mode 100644 index 0000000000..143906f4e2 --- /dev/null +++ b/test/ruby/test_arithmetic_sequence.rb @@ -0,0 +1,464 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestArithmeticSequence < Test::Unit::TestCase + def test_new + assert_raise(NoMethodError) { Enumerator::ArithmeticSequence.new } + end + + def test_allocate + assert_raise(TypeError) { Enumerator::ArithmeticSequence.allocate } + end + + def test_begin + assert_equal(1, 1.step.begin) + assert_equal(1, 1.step(10).begin) + assert_equal(1, 1.step(to: 10).begin) + assert_equal(1, 1.step(nil).begin) + assert_equal(1, 1.step(to: nil).begin) + assert_equal(1, 1.step(by: 2).begin) + assert_equal(1, 1.step(by: -1).begin) + assert_equal(1, 1.step(by: nil).begin) + assert_equal(1, 1.step(10, 2).begin) + assert_equal(1, 1.step(10, by: 2).begin) + assert_equal(1, 1.step(to: 10, by: 2).begin) + assert_equal(10, 10.step(to: 1, by: -1).begin) + assert_equal(10, 10.step(to: 1, by: -2).begin) + assert_equal(10, 10.step(to: -1, by: -2).begin) + assert_equal(10.0, 10.0.step(to: -1.0, by: -2.0).begin) + end + + def test_end + assert_equal(nil, 1.step.end) + assert_equal(10, 1.step(10).end) + assert_equal(10, 1.step(to: 10).end) + assert_equal(nil, 1.step(nil).end) + assert_equal(nil, 1.step(to: nil).end) + assert_equal(nil, 1.step(by: 2).end) + assert_equal(nil, 1.step(by: -1).end) + assert_equal(nil, 1.step(by: nil).end) + assert_equal(10, 1.step(10, 2).end) + assert_equal(10, 1.step(10, by: 2).end) + assert_equal(10, 1.step(to: 10, by: 2).end) + assert_equal(1, 10.step(to: 1, by: -1).end) + assert_equal(1, 10.step(to: 1, by: -2).end) + assert_equal(-1, 10.step(to: -1, by: -2).end) + assert_equal(-1.0, 10.0.step(to: -1.0, by: -2.0).end) + end + + def test_exclude_end_p + assert_equal(false, 1.step.exclude_end?) + assert_equal(false, 1.step(10).exclude_end?) + assert_equal(false, 1.step(to: 10).exclude_end?) + assert_equal(false, 1.step(nil).exclude_end?) + assert_equal(false, 1.step(to: nil).exclude_end?) + assert_equal(false, 1.step(by: 2).exclude_end?) + assert_equal(false, 1.step(by: -1).exclude_end?) + assert_equal(false, 1.step(by: nil).exclude_end?) + assert_equal(false, 1.step(10, 2).exclude_end?) + assert_equal(false, 1.step(10, by: 2).exclude_end?) + assert_equal(false, 1.step(to: 10, by: 2).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -1).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -2).exclude_end?) + assert_equal(false, 10.step(to: -1, by: -2).exclude_end?) + end + + def test_step + assert_equal(1, 1.step.step) + assert_equal(1, 1.step(10).step) + assert_equal(1, 1.step(to: 10).step) + assert_equal(1, 1.step(nil).step) + assert_equal(1, 1.step(to: nil).step) + assert_equal(2, 1.step(by: 2).step) + assert_equal(-1, 1.step(by: -1).step) + assert_equal(1, 1.step(by: nil).step) + assert_equal(2, 1.step(10, 2).step) + assert_equal(2, 1.step(10, by: 2).step) + assert_equal(2, 1.step(to: 10, by: 2).step) + assert_equal(-1, 10.step(to: 1, by: -1).step) + assert_equal(-2, 10.step(to: 1, by: -2).step) + assert_equal(-2, 10.step(to: -1, by: -2).step) + assert_equal(-2.0, 10.0.step(to: -1.0, by: -2.0).step) + end + + def test_eq + seq = 1.step + assert_equal(seq, seq) + assert_equal(seq, 1.step) + assert_equal(seq, 1.step(nil)) + end + + def test_eqq + seq = 1.step + assert_operator(seq, :===, seq) + assert_operator(seq, :===, 1.step) + assert_operator(seq, :===, 1.step(nil)) + end + + def test_eql_p + seq = 1.step + assert_operator(seq, :eql?, seq) + assert_operator(seq, :eql?, 1.step) + assert_operator(seq, :eql?, 1.step(nil)) + end + + def test_hash + seq = 1.step + assert_equal(seq.hash, seq.hash) + assert_equal(seq.hash, 1.step.hash) + assert_equal(seq.hash, 1.step(nil).hash) + assert_kind_of(String, seq.hash.to_s) + end + + def test_first + seq = 1.step + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 2, 3], seq.first(3)) + + seq = 1.step(by: 2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = 10.step(by: -2) + assert_equal(10, seq.first) + assert_equal([10], seq.first(1)) + assert_equal([10, 8, 6], seq.first(3)) + + seq = 1.step(by: 4) + assert_equal([1, 5, 9], seq.first(3)) + + seq = 1.step(10, by: 4) + assert_equal([1, 5, 9], seq.first(5)) + + seq = 1.step(0) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 1.step(10, by: -1) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 1.step(10, by: 0) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 1, 1], seq.first(3)) + + seq = 10.0.step(-1.0, by: -2.0) + assert_equal(10.0, seq.first) + assert_equal([10.0], seq.first(1)) + assert_equal([10.0, 8.0, 6.0], seq.first(3)) + end + + def test_first_bug15518 + bug15518 = '[Bug #15518]' + seq = (1 .. 10.0).step(1) + five_float_classes = Array.new(5) { Float } + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1r) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + end + + def test_last + seq = 1.step(10) + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = 1.step(10, 2) + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([5, 7, 9], seq.last(3)) + + seq = 10.step(1, -2) + assert_equal(2, seq.last) + assert_equal([2], seq.last(1)) + assert_equal([6, 4, 2], seq.last(3)) + + seq = 10.step(-1, -2) + assert_equal(0, seq.last) + + seq = 1.step(10, 4) + assert_equal([1, 5, 9], seq.last(5)) + + seq = 10.step(1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = 1.step(10, -1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = (1..10).step + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = (1...10).step + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([7, 8, 9], seq.last(3)) + + seq = 10.0.step(-3.0, by: -2.0) + assert_equal(-2.0, seq.last) + assert_equal([-2.0], seq.last(1)) + assert_equal([2.0, 0.0, -2.0], seq.last(3)) + end + + def test_last_with_float + res = (1..3).step(2).last(2.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(5.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + end + + def test_last_with_rational + res = (1..3).step(2).last(2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(10/2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + end + + def test_to_a + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1.step(10).to_a) + assert_equal([1, 3, 5, 7, 9], 1.step(10, 2).to_a) + assert_equal([1, 3, 5, 7, 9], (1..10).step(2).to_a) + assert_equal([10, 8, 6, 4, 2], 10.step(1, by: -2).to_a) + assert_equal([10, 8, 6, 4, 2], (10..1).step(-2).to_a) + assert_equal([10.0, 8.0, 6.0, 4.0, 2.0], (10.0..1.0).step(-2.0).to_a) + end + + def test_to_a_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_num_equal_type([1/10r, 1/5r, 3/10r, 2/5r, 1/2r], seq.to_a, + '[ruby-core:90648] [Bug #15444]') + end + + def test_last_bug17218 + seq = (1.0997r .. 1.1r).step(0.0001r) + assert_equal([1.0997r, 1.0998r, 1.0999r, 1.1r], seq.to_a, '[ruby-core:100312] [Bug #17218]') + end + + def test_slice + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [7, 9]], seq.each_slice(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [4, 2]], seq.each_slice(3).to_a) + end + + def test_cons + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [3, 5, 7], [5, 7, 9]], seq.each_cons(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [8, 6, 4], [6, 4, 2]], seq.each_cons(3).to_a) + end + + def test_with_index + seq = 1.step(6, 2) + assert_equal([[1, 0], [3, 1], [5, 2]], seq.with_index.to_a) + assert_equal([[1, 10], [3, 11], [5, 12]], seq.with_index(10).to_a) + + seq = 10.step(5, -2) + assert_equal([[10, 0], [8, 1], [6, 2]], seq.with_index.to_a) + assert_equal([[10, 10], [8, 11], [6, 12]], seq.with_index(10).to_a) + end + + def test_with_object + obj = [0, 1] + seq = 1.step(10, 2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([25, 945], ret) + + obj = [0, 1] + seq = 10.step(1, -2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([30, 3840], ret) + end + + def test_next + seq = 1.step(10, 2) + [1, 3, 5, 7, 9].each do |i| + assert_equal(i, seq.next) + end + + seq = 10.step(1, -2) + [10, 8, 6, 4, 2].each do |i| + assert_equal(i, seq.next) + end + + seq = ((1/10r)..(1/2r)).step(0) + assert_equal(1/10r, seq.next) + end + + def test_next_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_equal(1/10r, seq.next, '[ruby-core:90648] [Bug #15444]') + end + + def test_next_rewind + seq = 1.step(6, 2) + assert_equal(1, seq.next) + assert_equal(3, seq.next) + seq.rewind + assert_equal(1, seq.next) + assert_equal(3, seq.next) + assert_equal(5, seq.next) + assert_raise(StopIteration) { seq.next } + + seq = 10.step(5, -2) + assert_equal(10, seq.next) + assert_equal(8, seq.next) + seq.rewind + assert_equal(10, seq.next) + assert_equal(8, seq.next) + assert_equal(6, seq.next) + assert_raise(StopIteration) { seq.next } + end + + def test_next_after_stopiteration + seq = 1.step(2, 2) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + seq.rewind + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + end + + def test_stop_result + seq = 1.step(2, 2) + res = seq.each {} + assert_equal(1, seq.next) + exc = assert_raise(StopIteration) { seq.next } + assert_equal(res, exc.result) + end + + def test_peek + seq = 1.step(2, 2) + assert_equal(1, seq.peek) + assert_equal(1, seq.peek) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + + seq = 10.step(9, -2) + assert_equal(10, seq.peek) + assert_equal(10, seq.peek) + assert_equal(10, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + end + + def test_next_values + seq = 1.step(2, 2) + assert_equal([1], seq.next_values) + end + + def test_peek_values + seq = 1.step(2, 2) + assert_equal([1], seq.peek_values) + end + + def test_num_step_inspect + assert_equal('(1.step)', 1.step.inspect) + assert_equal('(1.step(10))', 1.step(10).inspect) + assert_equal('(1.step(10, 2))', 1.step(10, 2).inspect) + assert_equal('(1.step(10, by: 2))', 1.step(10, by: 2).inspect) + assert_equal('(1.step(by: 2))', 1.step(by: 2).inspect) + end + + def test_range_step_inspect + assert_equal('((1..).step)', (1..).step.inspect) + assert_equal('((1..10).step)', (1..10).step.inspect) + assert_equal('((1..10).step(2))', (1..10).step(2).inspect) + end + + def test_num_step_size + assert_equal(10, 1.step(10).size) + assert_equal(5, 1.step(10, 2).size) + assert_equal(4, 1.step(10, 3).size) + assert_equal(1, 1.step(10, 10).size) + assert_equal(0, 1.step(0).size) + assert_equal(Float::INFINITY, 1.step.size) + + assert_equal(10, 10.step(1, -1).size) + assert_equal(5, 10.step(1, -2).size) + assert_equal(4, 10.step(1, -3).size) + assert_equal(1, 10.step(1, -10).size) + assert_equal(0, 1.step(2, -1).size) + assert_equal(Float::INFINITY, 1.step(by: -1).size) + end + + def test_range_step_size + assert_equal(10, (1..10).step.size) + assert_equal(9, (1...10).step.size) + assert_equal(5, (1..10).step(2).size) + assert_equal(5, (1...10).step(2).size) + assert_equal(4, (1...9).step(2).size) + assert_equal(Float::INFINITY, (1..).step.size) + + assert_equal(10, (10..1).step(-1).size) + assert_equal(9, (10...1).step(-1).size) + assert_equal(5, (10..1).step(-2).size) + assert_equal(5, (10...1).step(-2).size) + assert_equal(4, (10...2).step(-2).size) + assert_equal(Float::INFINITY, (1..).step(-1).size) + end + + def assert_num_equal_type(ary1, ary2, message=nil) + assert_equal(ary1.length, ary2.length, message) + ary1.zip(ary2) do |e1, e2| + assert_equal(e1.class, e2.class, message) + if e1.is_a? Complex + assert_equal(e1.real, e2.real, message) + assert_equal(e1.imag, e2.imag, message) + else + assert_equal(e1, e2, message) + end + end + end + + def test_complex + assert_num_equal_type([1, 1+1i, 1+2i], (1..).step(1i).take(3)) + assert_num_equal_type([1, 1+1.0i, 1+2.0i], (1..).step(1.0i).take(3)) + assert_num_equal_type([0.0, 0.0+1.0i, 0.0+2.0i], (0.0..).step(1.0i).take(3)) + assert_num_equal_type([0.0+0.0i, 0.0+1.0i, 0.0+2.0i], (0.0i..).step(1.0i).take(3)) + end + + def test_sum + assert_equal([1, 3, 5, 7, 9].sum, (1..10).step(2).sum) + assert_equal([1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0].sum, (1.0..10.0).step(1.5).sum) + assert_equal([1/2r, 1r, 3/2r, 2, 5/2r, 3, 7/2r, 4].sum, ((1/2r)...(9/2r)).step(1/2r).sum) + end +end diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb new file mode 100644 index 0000000000..b98248f603 --- /dev/null +++ b/test/ruby/test_arity.rb @@ -0,0 +1,70 @@ +# 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 \(.*\b(\d+)\b.* (\d\S*?)\)/) do + case method_proc + when nil + yield + when Symbol + method(method_proc).call(*args) + else + method_proc.call(*args) + end + end + [$1, $2] + end + + def a + end + + def b(a, b, c, d=1, e=2, f, g, h, i, &block) + end + + def c(a, b, c, d=1, e=2, *rest) + end + + def d(a, b: 42) + end + + def e(a, b:42, **c) + end + + def f(a, b, c=1, *rest, d: 3) + end + + def test_method_err_mess + assert_equal %w[1 0], err_mess(:a, 1) + assert_equal %w[10 7..9], err_mess(:b, 10) + assert_equal %w[2 3+], err_mess(:c, 2) + assert_equal %w[2 1], err_mess(:d, 2) + assert_equal %w[0 1], err_mess(:d, 0) + assert_equal %w[2 1], err_mess(:e, 2) + assert_equal %w[0 1], err_mess(:e, 0) + assert_equal %w[1 2+], err_mess(:f, 1) + end + + def test_proc_err_mess + assert_equal %w[0 1..2], err_mess(->(b, c=42){}, 0) + assert_equal %w[1 2+], err_mess(->(a, b, c=42, *d){}, 1) + assert_equal %w[3 4+], err_mess(->(a, b, *c, d, e){}, 3) + assert_equal %w[3 1..2], err_mess(->(b, c=42){}, 3) + assert_equal %w[1 0], err_mess(->(&block){}, 1) + # Double checking: + p = Proc.new{|b, c=42| :ok} + assert_equal :ok, p.call(1, 2, 3) + assert_equal :ok, p.call + end + + def test_message_change_issue_6085 + assert_equal %w[3 1..2], err_mess{ SignalException.new(1, "", nil) } + assert_equal %w[1 0], err_mess{ Hash.new(1){} } + assert_equal %w[3 1..2], err_mess{ Module.send :define_method, 1, 2, 3 } + assert_equal %w[1 2], err_mess{ "".sub!(//) } + assert_equal %w[0 1..2], err_mess{ "".sub!{} } + assert_equal %w[0 1+], err_mess{ exec } + assert_equal %w[0 1+], err_mess{ Struct.new } + end +end diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index e8edcc25e3..8d48b86ea9 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -1,5 +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 @@ -12,6 +15,17 @@ class TestArray < Test::Unit::TestCase $VERBOSE = @verbose end + def test_percent_i + assert_equal([:foo, :bar], %i[foo bar]) + assert_equal([:"\"foo"], %i["foo]) + end + + def test_percent_I + x = 10 + assert_equal([:foo, :b10], %I[foo b#{x}]) + assert_equal([:"\"foo10"], %I["foo#{x}]) + end + def test_0_literal assert_equal([1, 2, 3, 4], [1, 2] + [3, 4]) assert_equal([1, 2, 1, 2], [1, 2] * 2) @@ -27,15 +41,17 @@ class TestArray < Test::Unit::TestCase assert_equal(2, x[2]) assert_equal([1, 2, 3], x[1..3]) assert_equal([1, 2, 3], x[1,3]) + assert_equal([3, 4, 5], x[3..]) x[0, 2] = 10 - assert(x[0] == 10 && x[1] == 2) + assert_equal([10, 2, 3, 4, 5], x) x[0, 0] = -1 - assert(x[0] == -1 && x[1] == 10) + assert_equal([-1, 10, 2, 3, 4, 5], x) x[-1, 1] = 20 - assert(x[-1] == 20 && x.pop == 20) + assert_equal(20, x[-1]) + assert_equal(20, x.pop) end def test_array_andor_0 @@ -85,7 +101,7 @@ class TestArray < Test::Unit::TestCase end def test_misc_0 - assert(defined? "a".chomp) + assert(defined? "a".chomp, '"a".chomp is not defined') assert_equal(["a", "b", "c"], "abc".scan(/./)) assert_equal([["1a"], ["2b"], ["3c"]], "1a2b3c".scan(/(\d.)/)) # non-greedy match @@ -130,14 +146,17 @@ class TestArray < Test::Unit::TestCase assert_equal(1, x.first) assert_equal([1], x.first(1)) assert_equal([1, 2, 3], x.first(3)) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)} assert_equal(5, x.last) assert_equal([5], x.last(1)) assert_equal([3, 4, 5], x.last(3)) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.last(1, 2)} assert_equal(1, x.shift) assert_equal([2, 3, 4], x.shift(3)) assert_equal([5], x) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.first(1, 2)} assert_equal([2, 3, 4, 5], x.unshift(2, 3, 4)) assert_equal([1, 2, 3, 4, 5], x.unshift(1)) @@ -146,6 +165,7 @@ class TestArray < Test::Unit::TestCase assert_equal(5, x.pop) assert_equal([3, 4], x.pop(2)) assert_equal([1, 2], x) + assert_raise_with_message(ArgumentError, /0\.\.1/) {x.pop(1, 2)} assert_equal([1, 2, 3, 4], x.push(3, 4)) assert_equal([1, 2, 3, 4, 5], x.push(5)) @@ -155,6 +175,7 @@ class TestArray < Test::Unit::TestCase def test_find_all_0 assert_respond_to([], :find_all) assert_respond_to([], :select) # Alias + assert_respond_to([], :filter) # Alias assert_equal([], [].find_all{ |obj| obj == "foo"}) x = ["foo", "bar", "baz", "baz", 1, 2, 3, 3, 4] @@ -183,6 +204,8 @@ class TestArray < Test::Unit::TestCase assert_equal([0, 1, 2, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(3...4){|i| i+10}) assert_equal([0, 1, 12, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(2..-2){|i| i+10}) assert_equal([0, 1, 12, 13, 4, 5], [0, 1, 2, 3, 4, 5].fill(2...-2){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3..){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill(3...){|i| i+10}) end # From rubicon @@ -209,6 +232,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) @@ -232,19 +262,45 @@ class TestArray < Test::Unit::TestCase def test_MINUS # '-' assert_equal(@cls[], @cls[1] - @cls[1]) assert_equal(@cls[1], @cls[1, 2, 3, 4, 5] - @cls[2, 3, 4, 5]) - # Ruby 1.8 feature change - #assert_equal(@cls[1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5]) assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5]) a = @cls[] 1000.times { a << 1 } assert_equal(1000, a.length) - #assert_equal(@cls[1], a - @cls[2]) assert_equal(@cls[1] * 1000, a - @cls[2]) - #assert_equal(@cls[1], @cls[1, 2, 1] - @cls[2]) assert_equal(@cls[1, 1], @cls[1, 2, 1] - @cls[2]) assert_equal(@cls[1, 2, 3], @cls[1, 2, 3] - @cls[4, 5, 6]) end + def test_MINUS_big_array # '-' + assert_equal(@cls[1]*64, @cls[1, 2, 3, 4, 5]*64 - @cls[2, 3, 4, 5]*64) + 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] * 1000, a - @cls[2]) + end + + def test_difference + assert_equal(@cls[], @cls[1].difference(@cls[1])) + assert_equal(@cls[1], @cls[1, 2, 3, 4, 5].difference(@cls[2, 3, 4, 5])) + assert_equal(@cls[1, 1], @cls[1, 2, 1].difference(@cls[2])) + assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5].difference(@cls[2, 3, 4, 5])) + assert_equal(@cls[], @cls[1, 2, 3, 4].difference(@cls[1], @cls[2], @cls[3], @cls[4])) + a = [1] + assert_equal(@cls[1], a.difference(@cls[2], @cls[2])) + assert_equal(@cls[], a.difference(@cls[1])) + assert_equal(@cls[1], a) + end + + def test_difference_big_array + assert_equal(@cls[1]*64, (@cls[1, 2, 3, 4, 5] * 64).difference(@cls[2, 3, 4] * 64, @cls[3, 5] * 64)) + assert_equal(@cls[1, 1, 1, 1]*64, (@cls[1, 2, 1, 3, 1, 4, 1, 5] * 64).difference(@cls[2, 3, 4, 5] * 64)) + a = @cls[1] * 1000 + assert_equal(@cls[1] * 1000, a.difference(@cls[2], @cls[2])) + assert_equal(@cls[], a.difference(@cls[1])) + assert_equal(@cls[1] * 1000, a) + end + def test_LSHIFT # '<<' a = @cls[] a << 1 @@ -270,17 +326,17 @@ class TestArray < Test::Unit::TestCase end def test_EQUAL # '==' - assert(@cls[] == @cls[]) - assert(@cls[1] == @cls[1]) - assert(@cls[1, 1, 2, 2] == @cls[1, 1, 2, 2]) - assert(@cls[1.0, 1.0, 2.0, 2.0] == @cls[1, 1, 2, 2]) + assert_operator(@cls[], :==, @cls[]) + assert_operator(@cls[1], :==, @cls[1]) + assert_operator(@cls[1, 1, 2, 2], :==, @cls[1, 1, 2, 2]) + assert_operator(@cls[1.0, 1.0, 2.0, 2.0], :==, @cls[1, 1, 2, 2]) end def test_VERY_EQUAL # '===' - assert(@cls[] === @cls[]) - assert(@cls[1] === @cls[1]) - assert(@cls[1, 1, 2, 2] === @cls[1, 1, 2, 2]) - assert(@cls[1.0, 1.0, 2.0, 2.0] === @cls[1, 1, 2, 2]) + assert_operator(@cls[], :===, @cls[]) + assert_operator(@cls[1], :===, @cls[1]) + assert_operator(@cls[1, 1, 2, 2], :===, @cls[1, 1, 2, 2]) + assert_operator(@cls[1.0, 1.0, 2.0, 2.0], :===, @cls[1, 1, 2, 2]) end def test_AREF # '[]' @@ -318,12 +374,11 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[99], a[-2..-2]) assert_equal(@cls[10, 11, 12], a[9..11]) + assert_equal(@cls[98, 99, 100], a[97..]) assert_equal(@cls[10, 11, 12], a[-91..-89]) + assert_equal(@cls[98, 99, 100], a[-3..]) assert_nil(a[10, -3]) - # Ruby 1.8 feature change: - # Array#[size..x] returns [] instead of nil. - #assert_nil(a[10..7]) assert_equal [], a[10..7] assert_raise(TypeError) {a['cat']} @@ -379,33 +434,33 @@ class TestArray < Test::Unit::TestCase assert_equal(b, a[10..19] = b) assert_equal(@cls[*(0..9).to_a] + b + @cls[*(20..99).to_a], a) - # Ruby 1.8 feature change: - # assigning nil does not remove elements. -=begin a = @cls[*(0..99).to_a] assert_equal(nil, a[0,1] = nil) - assert_equal(@cls[*(1..99).to_a], a) + assert_equal(@cls[nil] + @cls[*(1..99).to_a], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[10,10] = nil) - assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a) + assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[-1, 1] = nil) - assert_equal(@cls[*(0..98).to_a], a) + assert_equal(@cls[*(0..98).to_a] + @cls[nil], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[-10, 10] = nil) - assert_equal(@cls[*(0..89).to_a], a) + assert_equal(@cls[*(0..89).to_a] + @cls[nil], a) a = @cls[*(0..99).to_a] assert_equal(nil, a[0,1000] = nil) - assert_equal(@cls[] , a) + assert_equal(@cls[nil] , a) a = @cls[*(0..99).to_a] assert_equal(nil, a[10..19] = nil) - assert_equal(@cls[*(0..9).to_a] + @cls[*(20..99).to_a], a) -=end + assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[10..] = nil) + assert_equal(@cls[*(0..9).to_a] + @cls[nil], a) a = @cls[1, 2, 3] a[1, 0] = a @@ -414,6 +469,29 @@ class TestArray < Test::Unit::TestCase a = @cls[1, 2, 3] a[-1, 0] = a assert_equal([1, 2, 1, 2, 3, 3], a) + + a = @cls[] + a[5,0] = [5] + assert_equal([nil, nil, nil, nil, nil, 5], a) + + a = @cls[1] + a[1,0] = [2] + assert_equal([1, 2], a) + + a = @cls[1] + a[1,1] = [2] + 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 @@ -452,42 +530,39 @@ class TestArray < Test::Unit::TestCase def test_clone for taint in [ false, true ] - for untrust in [ false, true ] - for frozen in [ false, true ] - a = @cls[*(0..99).to_a] - a.taint if taint - a.untrust if untrust - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert(a.__id__ != b.__id__) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.untrusted?, b.untrusted?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @cls[*(0..99).to_a] + a.taint if taint + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_equal(a.__id__, b.__id__) + assert_equal(a.frozen?, b.frozen?) + assert_equal(a.tainted?, b.tainted?) end end end 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 }) - # Ruby 1.9 feature change: - # Enumerable#collect without block returns an Enumerator. - #assert_equal([1, 2, 3], @cls[1, 2, 3].collect) assert_kind_of Enumerator, @cls[1, 2, 3].collect + + assert_raise(ArgumentError) { + assert_equal([[1, 2, 3]], [[1, 2, 3]].collect(&->(a, b, c) {[a, b, c]})) + } end # also update map! 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 } ) @@ -533,7 +608,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]])) @@ -541,8 +618,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 @@ -552,6 +633,29 @@ class TestArray < Test::Unit::TestCase assert_equal(3, a.count {|x| x % 2 == 1 }) assert_equal(2, a.count(1) {|x| x % 2 == 1 }) assert_raise(ArgumentError) { a.count(0, 1) } + + bug8654 = '[ruby-core:56072]' + assert_in_out_err [], <<-EOS, ["0"], [], bug8654 + a1 = [] + a2 = Array.new(100) { |i| i } + a2.count do |i| + p i + a2.replace(a1) if i == 0 + end + EOS + + assert_in_out_err [], <<-EOS, ["[]", "0"], [], bug8654 + ARY = Array.new(100) { |i| i } + class Integer + alias old_equal == + def == other + ARY.replace([]) if self.equal?(0) + p ARY + self.equal?(other) + end + end + p ARY.count(42) + EOS end def test_delete @@ -574,6 +678,14 @@ class TestArray < Test::Unit::TestCase a = @cls[*('cab'..'cat').to_a] assert_equal(99, a.delete('cup') { 99 } ) assert_equal(@cls[*('cab'..'cat').to_a], a) + + o = Object.new + def o.==(other); true; end + o2 = Object.new + def o2.==(other); true; end + a = @cls[1, o, o2, 2] + assert_equal(o2, a.delete(42)) + assert_equal([1, 2], a) end def test_delete_at @@ -607,6 +719,11 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(a, a.delete_if { |i| i > 3 }) assert_equal(@cls[1, 2, 3], a) + + bug2545 = '[ruby-core:27366]' + a = @cls[ 5, 6, 7, 8, 9, 10 ] + assert_equal(9, a.delete_if {|i| break i if i > 8; i < 7}) + assert_equal(@cls[7, 8, 9, 10], a, bug2545) end def test_dup @@ -618,7 +735,7 @@ class TestArray < Test::Unit::TestCase b = a.dup assert_equal(a, b) - assert(a.__id__ != b.__id__) + assert_not_equal(a.__id__, b.__id__) assert_equal(false, b.frozen?) assert_equal(a.tainted?, b.tainted?) end @@ -637,7 +754,7 @@ class TestArray < Test::Unit::TestCase a = @cls[] i = 0 a.each { |e| - assert_equal(a[i], e) + assert(false, "Never get here") i += 1 } assert_equal(0, i) @@ -657,7 +774,7 @@ class TestArray < Test::Unit::TestCase a = @cls[] i = 0 a.each_index { |ind| - assert_equal(i, ind) + assert(false, "Never get here") i += 1 } assert_equal(0, i) @@ -666,15 +783,15 @@ class TestArray < Test::Unit::TestCase end def test_empty? - assert(@cls[].empty?) - assert(!@cls[1].empty?) + assert_empty(@cls[]) + assert_not_empty(@cls[1]) end def test_eql? - assert(@cls[].eql?(@cls[])) - assert(@cls[1].eql?(@cls[1])) - assert(@cls[1, 1, 2, 2].eql?(@cls[1, 1, 2, 2])) - assert(!@cls[1.0, 1.0, 2.0, 2.0].eql?(@cls[1, 1, 2, 2])) + assert_send([@cls[], :eql?, @cls[]]) + assert_send([@cls[1], :eql?, @cls[1]]) + assert_send([@cls[1, 1, 2, 2], :eql?, @cls[1, 1, 2, 2]]) + assert_not_send([@cls[1.0, 1.0, 2.0, 2.0], :eql?, @cls[1, 1, 2, 2]]) end def test_fill @@ -711,25 +828,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 - a6.untrust a7 = a6.flatten assert_equal(true, a7.tainted?) - assert_equal(true, a7.untrusted?) + 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 ] @@ -742,16 +886,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 @@ -764,8 +934,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] @@ -782,7 +974,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] @@ -799,7 +991,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] @@ -816,7 +1008,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] @@ -833,7 +1025,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] @@ -853,18 +1045,20 @@ class TestArray < Test::Unit::TestCase a1 = @cls[ 'cat', 'dog' ] a2 = @cls[ 'cat', 'dog' ] a3 = @cls[ 'dog', 'cat' ] - assert(a1.hash == a2.hash) - assert(a1.hash != a3.hash) + assert_equal(a1.hash, a2.hash) + assert_not_equal(a1.hash, a3.hash) + bug9231 = '[ruby-core:58993] [Bug #9231]' + assert_not_equal(false.hash, @cls[].hash, bug9231) end def test_include? a = @cls[ 'cat', 99, /a/, @cls[ 1, 2, 3] ] - assert(a.include?('cat')) - assert(a.include?(99)) - assert(a.include?(/a/)) - assert(a.include?([1,2,3])) - assert(!a.include?('ca')) - assert(!a.include?([1,2])) + assert_include(a, 'cat') + assert_include(a, 99) + assert_include(a, /a/) + assert_include(a, [1,2,3]) + assert_not_include(a, 'ca') + assert_not_include(a, [1,2]) end def test_index @@ -890,29 +1084,52 @@ class TestArray < Test::Unit::TestCase a = @cls[] assert_equal("", a.join) assert_equal("", a.join(',')) + assert_equal(Encoding::US_ASCII, a.join.encoding) $, = "" a = @cls[1, 2] assert_equal("12", a.join) + assert_equal("12", a.join(nil)) assert_equal("1,2", a.join(',')) $, = "" a = @cls[1, 2, 3] assert_equal("123", a.join) + assert_equal("123", a.join(nil)) assert_equal("1,2,3", a.join(',')) $, = ":" a = @cls[1, 2, 3] assert_equal("1:2:3", a.join) + assert_equal("1:2:3", a.join(nil)) assert_equal("1,2,3", a.join(',')) $, = "" a = @cls[1, 2, 3] a.taint - a.untrust s = a.join assert_equal(true, s.tainted?) - assert_equal(true, s.untrusted?) + + bug5902 = '[ruby-core:42161]' + sep = ":".taint + + s = @cls[].join(sep) + assert_equal(false, s.tainted?, bug5902) + s = @cls[1].join(sep) + assert_equal(false, s.tainted?, bug5902) + s = @cls[1, 2].join(sep) + assert_equal(true, s.tainted?, bug5902) + + e = ''.force_encoding('EUC-JP') + u = ''.force_encoding('UTF-8') + assert_equal(Encoding::US_ASCII, [[]].join.encoding) + assert_equal(Encoding::US_ASCII, [1, [u]].join.encoding) + assert_equal(Encoding::UTF_8, [u, [e]].join.encoding) + assert_equal(Encoding::UTF_8, [u, [1]].join.encoding) + assert_equal(Encoding::UTF_8, [Struct.new(:to_str).new(u)].join.encoding) + bug5379 = '[ruby-core:39776]' + assert_equal(Encoding::US_ASCII, [[], u, nil].join.encoding, bug5379) + assert_equal(Encoding::UTF_8, [[], "\u3042", nil].join.encoding, bug5379) ensure $, = nil end @@ -934,8 +1151,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 } ) @@ -1040,13 +1257,18 @@ 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)) assert_equal(@cls[1, 2, 3, 4, 5, nil], a.push(nil)) - # Ruby 1.8 feature: - # Array#push accepts any number of arguments. - #assert_raise(ArgumentError, "a.push()") { a.push() } a.push assert_equal @cls[1, 2, 3, 4, 5, nil], a a.push 6, 7 @@ -1079,6 +1301,70 @@ class TestArray < Test::Unit::TestCase a = @cls[ 1, 2, 3, 4, 5 ] assert_equal(a, a.reject! { |i| i > 3 }) assert_equal(@cls[1, 2, 3], a) + + bug2545 = '[ruby-core:27366]' + a = @cls[ 5, 6, 7, 8, 9, 10 ] + assert_equal(9, a.reject! {|i| break i if i > 8; i < 7}) + assert_equal(@cls[7, 8, 9, 10], a, bug2545) + 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 @@ -1091,10 +1377,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 @@ -1108,9 +1394,6 @@ class TestArray < Test::Unit::TestCase a = @cls[*%w( dog cat bee ant )] assert_equal(@cls[*%w(ant bee cat dog)], a.reverse!) assert_equal(@cls[*%w(ant bee cat dog)], a) - # Ruby 1.8 feature change: - # Array#reverse always returns self. - #assert_nil(@cls[].reverse!) assert_equal @cls[], @cls[].reverse! end @@ -1126,6 +1409,7 @@ class TestArray < Test::Unit::TestCase a = @cls[] i = 0 a.reverse_each { |e| + i += 1 assert(false, "Never get here") } assert_equal(0, i) @@ -1189,14 +1473,14 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[99], a.slice(-2..-2)) assert_equal(@cls[10, 11, 12], a.slice(9..11)) + assert_equal(@cls[98, 99, 100], a.slice(97..)) + assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) assert_equal(@cls[10, 11, 12], a.slice(-91..-89)) assert_nil(a.slice(-101..-1)) + assert_nil(a.slice(-101..)) assert_nil(a.slice(10, -3)) - # Ruby 1.8 feature change: - # Array#slice[size..x] always returns []. - #assert_nil(a.slice(10..7)) assert_equal @cls[], a.slice(10..7) end @@ -1237,6 +1521,8 @@ class TestArray < Test::Unit::TestCase assert_equal(nil, a.slice!(-6,2)) assert_equal(@cls[1, 2, 3, 4, 5], a) + assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + assert_raise(ArgumentError) { @cls[1].slice! } assert_raise(ArgumentError) { @cls[1].slice!(0, 0, 0) } end @@ -1281,7 +1567,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 @@ -1309,6 +1595,32 @@ class TestArray < Test::Unit::TestCase end end + def test_sort_bang_with_freeze + ary = [] + o1 = Object.new + o1.singleton_class.class_eval { + define_method(:<=>) {|v| + ary.freeze + 1 + } + } + o2 = o1.clone + ary << o1 << o2 + orig = ary.dup + 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__ @@ -1336,7 +1648,7 @@ class TestArray < Test::Unit::TestCase def o.to_ary foo_bar() end - assert_match(/foo_bar/, assert_raise(NoMethodError) {a.concat(o)}.message) + assert_raise_with_message(NoMethodError, /foo_bar/) {a.concat(o)} end def test_to_s @@ -1359,6 +1671,95 @@ class TestArray < Test::Unit::TestCase $, = nil end + StubToH = [ + [:key, :value], + Object.new.tap do |kvp| + def kvp.to_ary + [:obtained, :via_to_ary] + end + end, + ] + + def test_to_h + array = StubToH + assert_equal({key: :value, obtained: :via_to_ary}, array.to_h) + + e = assert_raise(TypeError) { + [[: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_to_h_block + array = StubToH + assert_equal({"key" => "value", "obtained" => "via_to_ary"}, + array.to_h {|k, v| [k.to_s, v.to_s]}) + + assert_equal({first_one: :ok, not_ok: :ng}, + [[:first_one, :ok], :not_ok].to_h {|k, v| [k, v || :ng]}) + + e = assert_raise(TypeError) { + [[:first_one, :ok], :not_ok].to_h {|k, v| v ? [k, v] : k} + } + assert_equal "wrong element type Symbol at 1 (expected array)", e.message + array = [1] + k = eval("class C\u{1f5ff}; self; end").new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) {array.to_h {k}} + e = assert_raise(ArgumentError) { + [[:first_one, :ok], [1, 2], [:not_ok]].to_h {|kv| kv} + } + assert_equal "wrong array length at 2 (expected 2, was 1)", e.message + end + + def test_min + assert_equal(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 @@ -1395,6 +1796,18 @@ class TestArray < Test::Unit::TestCase assert_equal(d, c) assert_equal(@cls[1, 2, 3], @cls[1, 2, 3].uniq) + + a = %w(a a) + b = a.uniq + assert_equal(%w(a a), a) + assert(a.none?(&:frozen?)) + assert_equal(%w(a), b) + assert(b.none?(&:frozen?)) + + bug9340 = "[ruby-core:59457]" + ary = [bug9340, bug9340.dup, bug9340.dup] + assert_equal 1, ary.uniq.size + assert_same bug9340, ary.uniq[0] end def test_uniq_with_block @@ -1415,6 +1828,13 @@ class TestArray < Test::Unit::TestCase assert_equal([1,3], a) assert_equal([1], b) assert_not_same(a, b) + + a = %w(a a) + b = a.uniq {|v| v } + assert_equal(%w(a a), a) + assert(a.none?(&:frozen?)) + assert_equal(%w(a), b) + assert(b.none?(&:frozen?)) end def test_uniq! @@ -1454,7 +1874,20 @@ 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"} ] + a.sort_by!{|e| e[:c]} + a.uniq! {|e| e[:c]} + end + + a = %w(a a) + b = a.uniq + assert_equal(%w(a a), a) + assert(a.none?(&:frozen?)) + assert_equal(%w(a), b) + assert(b.none?(&:frozen?)) end def test_uniq_bang_with_block @@ -1476,6 +1909,21 @@ class TestArray < Test::Unit::TestCase b = a.uniq! {|v| v.even? } assert_equal([1,2], a) assert_equal(nil, b) + + a = %w(a a) + b = a.uniq! {|v| v } + assert_equal(%w(a), b) + assert_same(a, b) + assert b.none?(&:frozen?) + end + + def test_uniq_bang_with_freeze + ary = [1,2] + orig = ary.dup + assert_raise(FrozenError, "frozen during comparison") { + ary.uniq! {|v| ary.freeze; 1} + } + assert_equal(orig, ary, "must not be modified once frozen") end def test_unshift @@ -1486,6 +1934,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[]) @@ -1495,15 +1954,111 @@ class TestArray < Test::Unit::TestCase assert_equal(@cls[1,2], @cls[1] | @cls[2]) assert_equal(@cls[1,2], @cls[1, 1] | @cls[2, 2]) assert_equal(@cls[1,2], @cls[1, 2] | @cls[1, 2]) + + a = %w(a b c) + b = %w(a b c d e) + c = a | b + assert_equal(c, b) + assert_not_same(c, b) + assert_equal(%w(a b c), a) + assert_equal(%w(a b c d e), b) + assert(a.none?(&:frozen?)) + assert(b.none?(&:frozen?)) + assert(c.none?(&:frozen?)) + end + + def test_OR_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]|[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_union + assert_equal(@cls[], @cls[].union(@cls[])) + assert_equal(@cls[1], @cls[1].union(@cls[])) + assert_equal(@cls[1], @cls[].union(@cls[1])) + assert_equal(@cls[1], @cls[].union(@cls[], @cls[1])) + assert_equal(@cls[1], @cls[1].union(@cls[1])) + assert_equal(@cls[1], @cls[1].union(@cls[1], @cls[1], @cls[1])) + + assert_equal(@cls[1,2], @cls[1].union(@cls[2])) + assert_equal(@cls[1,2], @cls[1, 1].union(@cls[2, 2])) + assert_equal(@cls[1,2], @cls[1, 2].union(@cls[1, 2])) + assert_equal(@cls[1,2], @cls[1, 1].union(@cls[1, 1], @cls[1, 2], @cls[2, 1], @cls[2, 2, 2])) + + a = %w(a b c) + b = %w(a b c d e) + c = a.union(b) + assert_equal(c, b) + assert_not_same(c, b) + assert_equal(%w(a b c), a) + assert_equal(%w(a b c d e), b) + assert(a.none?(&:frozen?)) + assert(b.none?(&:frozen?)) + assert(c.none?(&:frozen?)) + end + + def test_union_big_array + assert_equal(@cls[1,2], (@cls[1]*64).union(@cls[2]*64)) + assert_equal(@cls[1,2,3], (@cls[1, 2]*64).union(@cls[1, 2]*64, @cls[3]*60)) + + a = (1..64).to_a + b = (1..128).to_a + c = a | b + assert_equal(c, b) + assert_not_same(c, b) + assert_equal((1..64).to_a, a) + assert_equal((1..128).to_a, b) end def test_combination - 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 @@ -1528,10 +2083,23 @@ class TestArray < Test::Unit::TestCase acc = [1,2].product(*[o]*10) assert_equal([1,2].product([3,4], [3,4], [3,4], [3,4], [3,4], [3,4], [3,4], [3,4], [3,4], [3,4]), acc) + + a = [] + [1, 2].product([0, 1, 2, 3, 4][1, 4]) {|x| a << x } + a.all? {|x| assert_not_include(x, 0)} 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]], @@ -1549,10 +2117,31 @@ class TestArray < Test::Unit::TestCase a.permutation {|x| b << x; a.replace(@cls[9, 8, 7, 6]) } assert_equal(@cls[9, 8, 7, 6], a) assert_equal(@cls[1, 2, 3, 4].permutation.to_a, b) + + bug3708 = '[ruby-dev:42067]' + 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]], @@ -1571,10 +2160,30 @@ class TestArray < Test::Unit::TestCase a.repeated_permutation(4) {|x| b << x; a.replace(@cls[9, 8, 7, 6]) } assert_equal(@cls[9, 8, 7, 6], a) assert_equal(@cls[1, 2, 3, 4].repeated_permutation(4).to_a, b) + + a = @cls[0, 1, 2, 3, 4][1, 4].repeated_permutation(2) + 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]], @@ -1587,8 +2196,8 @@ class TestArray < Test::Unit::TestCase [2,2,2,2],[2,2,2,3],[2,2,3,3],[2,3,3,3],[3,3,3,3]], a.repeated_combination(4).to_a.sort) assert_equal(@cls[], a.repeated_combination(-1).to_a) - assert_equal("abcde".each_char.to_a.repeated_combination(5).map{|a|a.sort}.sort, - "edcba".each_char.to_a.repeated_combination(5).map{|a|a.sort}.sort) + assert_equal("abcde".each_char.to_a.repeated_combination(5).map{|e|e.sort}.sort, + "edcba".each_char.to_a.repeated_combination(5).map{|e|e.sort}.sort) assert_equal(@cls[].repeated_combination(0).to_a, @cls[[]]) assert_equal(@cls[].repeated_combination(1).to_a, @cls[]) @@ -1597,6 +2206,18 @@ class TestArray < Test::Unit::TestCase a.repeated_combination(4) {|x| b << x; a.replace(@cls[9, 8, 7, 6]) } assert_equal(@cls[9, 8, 7, 6], a) assert_equal(@cls[1, 2, 3, 4].repeated_combination(4).to_a, b) + + a = @cls[0, 1, 2, 3, 4][1, 4].repeated_combination(2) + 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 @@ -1619,19 +2240,6 @@ class TestArray < Test::Unit::TestCase assert_equal([3,4,5,0], [1,2,3,4,5,0].drop_while {|i| i < 3 }) end - def test_modify_check - a = [] - a.freeze - assert_raise(RuntimeError) { a.shift } - a = [1, 2] - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - a.shift - end.value - end - end - LONGP = [127, 63, 31, 15, 7].map {|x| 2**x-1 }.find do |x| begin [].first(x) @@ -1668,13 +2276,14 @@ class TestArray < Test::Unit::TestCase assert_raise(IndexError) { [0][LONGP] = 2 } assert_raise(IndexError) { [0][(LONGP + 1) / 2 - 1] = 2 } assert_raise(IndexError) { [0][LONGP..-1] = 2 } + assert_raise(IndexError) { [0][LONGP..] = 2 } a = [0] a[2] = 4 assert_equal([0, nil, 4], a) assert_raise(ArgumentError) { [0][0, 0, 0] = 0 } assert_raise(ArgumentError) { [0].freeze[0, 0, 0] = 0 } assert_raise(TypeError) { [0][:foo] = 0 } - assert_raise(RuntimeError) { [0].freeze[:foo] = 0 } + assert_raise(FrozenError) { [0].freeze[:foo] = 0 } end def test_first2 @@ -1682,6 +2291,11 @@ class TestArray < Test::Unit::TestCase assert_raise(ArgumentError) { [0].first(-1) } end + def test_last2 + assert_equal([0], [0].last(2)) + assert_raise(ArgumentError) { [0].last(-1) } + end + def test_shift2 assert_equal(0, ([0] * 16).shift) # check @@ -1689,15 +2303,18 @@ 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 assert_raise(ArgumentError) { [][0, 0, 0] } + assert_raise(TypeError) { [][(1..10).step(2)] } end def test_fetch @@ -1750,9 +2367,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 @@ -1778,7 +2397,9 @@ class TestArray < Test::Unit::TestCase def test_values_at2 a = [0, 1, 2, 3, 4, 5] assert_equal([1, 2, 3], a.values_at(1..3)) - assert_equal([], a.values_at(7..8)) + assert_equal([nil, nil], a.values_at(7..8)) + bug6203 = '[ruby-core:43678]' + assert_equal([4, 5, nil, nil], a.values_at(4..7), bug6203) assert_equal([nil], a.values_at(2**31-1)) end @@ -1790,7 +2411,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 ] @@ -1800,6 +2420,55 @@ 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_filter + assert_equal([0, 2], [0, 1, 2, 3].filter {|x| x % 2 == 0 }) + end + + # alias for select! + def test_filter! + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(nil, a.filter! { true }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { false }) + assert_equal(@cls[], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { |i| i > 3 }) + assert_equal(@cls[4, 5], a) end def test_delete2 @@ -1812,6 +2481,22 @@ class TestArray < Test::Unit::TestCase assert_equal([1, 3], [0, 1, 2, 3].reject {|x| x % 2 == 0 }) end + def test_reject_with_callcc + need_continuation + bug9727 = '[ruby-dev:48101] [Bug #9727]' + cont = nil + a = [*1..10].reject do |i| + callcc {|c| cont = c} if !cont and i == 10 + false + end + if a.size < 1000 + a.unshift(:x) + cont.call + end + assert_equal(1000, a.size, bug9727) + assert_equal([:x, *1..10], a.uniq, bug9727) + end + def test_zip assert_equal([[1, :a, "a"], [2, :b, "b"], [3, nil, "c"]], [1, 2, 3].zip([:a, :b], ["a", "b", "c", "d"])) @@ -1821,13 +2506,22 @@ class TestArray < Test::Unit::TestCase ary = Object.new def ary.to_a; [1, 2]; end - assert_raise(NoMethodError){ %w(a b).zip(ary) } + assert_raise(TypeError) {%w(a b).zip(ary)} def ary.each; [3, 4].each{|e|yield e}; end assert_equal([['a', 3], ['b', 4]], %w(a b).zip(ary)) def ary.to_ary; [5, 6]; end assert_equal([['a', 5], ['b', 6]], %w(a b).zip(ary)) end + def test_zip_bug + bug8153 = "ruby-core:53650" + r = 1..1 + def r.respond_to?(*) + super + end + assert_equal [[42, 1]], [42].zip(r), bug8153 + end + def test_transpose assert_equal([[1, :a], [2, :b], [3, :c]], [[1, 2, 3], [:a, :b, :c]].transpose) @@ -1854,11 +2548,18 @@ class TestArray < Test::Unit::TestCase assert_not_equal([0, 1, 2], [0, 1, 3]) end - def test_hash2 - a = [] - a << a - assert_equal([[a]].hash, a.hash) - assert_not_equal([a, a].hash, a.hash) # Implementation dependent + A = Array.new(3, &:to_s) + B = A.dup + + def test_equal_resize + o = Object.new + def o.==(o) + A.clear + B.clear + true + end + A[1] = o + assert_equal(A, B) end def test_flatten_error @@ -1870,22 +2571,78 @@ 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 100.times do assert_equal([0, 1, 2], [2, 1, 0].shuffle.sort) end + + gen = Random.new(0) + assert_raise(ArgumentError) {[1, 2, 3].shuffle(1, random: gen)} + srand(0) + 100.times do + assert_equal([0, 1, 2].shuffle, [0, 1, 2].shuffle(random: gen)) + end + + assert_raise_with_message(ArgumentError, /unknown keyword/) do + [0, 1, 2].shuffle(xawqij: "a") + end + assert_raise_with_message(ArgumentError, /unknown keyword/) do + [0, 1, 2].shuffle!(xawqij: "a") + end + end + + def test_shuffle_random + gen = proc do + 10000000 + end + class << gen + alias rand call + end + assert_raise(RangeError) { + [*0..2].shuffle(random: gen) + } + + ary = (0...10000).to_a + gen = proc do + ary.replace([]) + 0.5 + end + class << gen + alias rand call + end + assert_raise(RuntimeError) {ary.shuffle!(random: gen)} + + zero = Object.new + def zero.to_int + 0 + end + gen_to_int = proc do |max| + zero + end + class << gen_to_int + alias rand call + 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 100.times do - assert([0, 1, 2].include?([2, 1, 0].sample)) + assert_include([0, 1, 2], [2, 1, 0].sample) samples = [2, 1, 0].sample(2) samples.each{|sample| - assert([0, 1, 2].include?(sample)) + assert_include([0, 1, 2], sample) } end @@ -1894,7 +2651,7 @@ class TestArray < Test::Unit::TestCase (0..20).each do |n| 100.times do b = a.sample(n) - assert_equal([n, 18].min, b.uniq.size) + assert_equal([n, 18].min, b.size) assert_equal(a, (a | b).sort) assert_equal(b.sort, (a & b).sort) end @@ -1907,6 +2664,81 @@ class TestArray < Test::Unit::TestCase end assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)} + + gen = Random.new(0) + srand(0) + a = (1..18).to_a + (0..20).each do |n| + 100.times do |i| + assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}") + end + end + + assert_raise_with_message(ArgumentError, /unknown keyword/) do + [0, 1, 2].sample(xawqij: "a") + end + end + + def test_sample_random + ary = (0...10000).to_a + assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)} + gen0 = proc do |max| + max/2 + end + class << gen0 + alias rand call + end + gen1 = proc do |max| + ary.replace([]) + max/2 + end + class << gen1 + alias rand call + end + assert_equal(5000, ary.sample(random: gen0)) + assert_nil(ary.sample(random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000], ary.sample(1, random: gen0)) + assert_equal([], ary.sample(1, random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000, 4999], ary.sample(2, random: gen0)) + assert_equal([], ary.sample(2, random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000, 4999, 5001], ary.sample(3, random: gen0)) + assert_equal([], ary.sample(3, random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000, 4999, 5001, 4998], ary.sample(4, random: gen0)) + assert_equal([], ary.sample(4, random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000, 4999, 5001, 4998, 5002, 4997, 5003, 4996, 5004, 4995], ary.sample(10, random: gen0)) + assert_equal([], ary.sample(10, random: gen1)) + assert_equal([], ary) + ary = (0...10000).to_a + assert_equal([5000, 0, 5001, 2, 5002, 4, 5003, 6, 5004, 8, 5005], ary.sample(11, random: gen0)) + ary.sample(11, random: gen1) # implementation detail, may change in the future + assert_equal([], ary) + + half = Object.new + def half.to_int + 5000 + end + gen_to_int = proc do |max| + half + end + class << gen_to_int + alias rand call + 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 @@ -1923,6 +2755,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 @@ -1937,9 +2772,23 @@ class TestArray < Test::Unit::TestCase end def test_combination2 - assert_nothing_raised do - (0..100).to_a.combination(50) { break } - end + assert_equal(:called, (0..100).to_a.combination(50) { break :called }, "[ruby-core:29240] ... must be yielded even if 100C50 > signed integer") + end + + def test_combination_clear + bug9939 = '[ruby-core:63149] [Bug #9939]' + assert_nothing_raised(bug9939) { + a = [*0..100] + a.combination(3) {|*,x| a.clear} + } + + 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 @@ -1957,17 +2806,15 @@ 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 a = @cls[1, 2, 3] a.taint - a.untrust s = a.inspect assert_equal(true, s.tainted?) - assert_equal(true, s.untrusted?) end def test_initialize2 @@ -1982,6 +2829,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 @@ -2058,9 +2910,241 @@ class TestArray < Test::Unit::TestCase assert_equal([], a.rotate!(13)) assert_equal([], a.rotate!(-13)) a = [].freeze - e = assert_raise(RuntimeError) {a.rotate!} - assert_match(/can't modify frozen array/, e.message) + assert_raise_with_message(FrozenError, /can\'t modify frozen/) {a.rotate!} a = [1,2,3] assert_raise(ArgumentError) { a.rotate!(1, 1) } end + + def test_bsearch_typechecks_return_values + 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 + + def test_bsearch_with_no_block + enum = [1, 2, 42, 100, 666].bsearch + assert_nil enum.size + assert_equal 42, enum.each{|x| x >= 33 } + end + + def test_bsearch_in_find_minimum_mode + a = [0, 4, 7, 10, 12] + assert_equal(4, a.bsearch {|x| x >= 4 }) + assert_equal(7, a.bsearch {|x| x >= 6 }) + assert_equal(0, a.bsearch {|x| x >= -1 }) + assert_equal(nil, a.bsearch {|x| x >= 100 }) + end + + def test_bsearch_in_find_any_mode + a = [0, 4, 7, 10, 12] + assert_include([4, 7], a.bsearch {|x| 1 - x / 4 }) + assert_equal(nil, a.bsearch {|x| 4 - x / 2 }) + assert_equal(nil, a.bsearch {|x| 1 }) + assert_equal(nil, a.bsearch {|x| -1 }) + + assert_include([4, 7], a.bsearch {|x| (1 - x / 4) * (2**100) }) + assert_equal(nil, a.bsearch {|x| 1 * (2**100) }) + assert_equal(nil, a.bsearch {|x| (-1) * (2**100) }) + + 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 + \K(?:\1\S+\s\(\2\)\n)*/x) do + "...(snip #{$&.count("\n")} lines)...\n" + end + end + begin + assert_normal_exit(<<-EOS, '[Bug #9718]', timeout: 5, stdout_filter: reduce) + queue = [] + 50.times do + 10_000.times do + queue << lambda{} + end + GC.start(full_mark: false, immediate_sweep: true) + GC.verify_internal_consistency + queue.shift.call + end + EOS + rescue Timeout::Error => e + skip e.message + end + end + + sizeof_long = [0].pack("l!").size + sizeof_voidp = [""].pack("p").size + if sizeof_long < sizeof_voidp + ARY_MAX = (1<<(8*sizeof_long-1)) / sizeof_voidp - 1 + Bug11235 = '[ruby-dev:49043] [Bug #11235]' + + def test_push_over_ary_max + 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], "#{<<~"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], "#{<<~"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..c1f8c46890 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) { + o.instance_eval {self.foo += 1} + } + assert_raise(NoMethodError, bug11096) { + o.instance_eval {self.foo &&= 1} + } + + assert_raise(NoMethodError, bug11096) { + o.instance_eval {self[0] += 1} + } + assert_raise(NoMethodError, bug11096) { + 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 @@ -424,11 +480,10 @@ class TestAssignment < Test::Unit::TestCase assert_equal 1, a assert_equal [2, 3], b - # not supported yet - #a, *b, c = 1, 2, 3, 4 - #assert_equal 1, a - #assert_equal [2,3], b - #assert_equal 4, c + a, *b, c = 1, 2, 3, 4 + assert_equal 1, a + assert_equal [2,3], b + assert_equal 4, c a = 1, 2 assert_equal [1, 2], a @@ -496,6 +551,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 +752,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_ast.rb b/test/ruby/test_ast.rb new file mode 100644 index 0000000000..3ba67e69a2 --- /dev/null +++ b/test/ruby/test_ast.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' + +class RubyVM + module AbstractSyntaxTree + class Node + class CodePosition + include Comparable + attr_reader :lineno, :column + def initialize(lineno, column) + @lineno = lineno + @column = column + end + + def <=>(other) + case + when lineno < other.lineno + -1 + when lineno == other.lineno + column <=> other.column + when lineno > other.lineno + 1 + end + end + end + + def beg_pos + CodePosition.new(first_lineno, first_column) + end + + def end_pos + CodePosition.new(last_lineno, last_column) + end + + alias to_s inspect + end + end +end + +class TestAst < Test::Unit::TestCase + class Helper + attr_reader :errors + + def initialize(path) + @path = path + @errors = [] + @debug = false + end + + def validate_range + @errors = [] + validate_range0(ast) + + @errors.empty? + end + + def validate_not_cared + @errors = [] + validate_not_cared0(ast) + + @errors.empty? + end + + def ast + return @ast if defined?(@ast) + @ast = RubyVM::AbstractSyntaxTree.parse_file(@path) + end + + private + + def validate_range0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + return true if children.empty? + # These NODE_D* has NODE_ARRAY as nd_next->nd_next whose last locations + # we can not update when item is appended. + return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type + + min = children.map(&:beg_pos).min + max = children.map(&:end_pos).max + + unless beg_pos <= min + @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node } + end + + unless max <= end_pos + @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node } + end + + p "#{node} => #{children}" if @debug + + children.each do |child| + p child if @debug + validate_range0(child) + end + end + + def validate_not_cared0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0 + @errors << { type: :first_column, node: node } if beg_pos.column == -1 + @errors << { type: :last_lineno, node: node } if end_pos.lineno == 0 + @errors << { type: :last_column, node: node } if end_pos.column == -1 + + children.each {|c| validate_not_cared0(c) } + end + end + + SRCDIR = File.expand_path("../../..", __FILE__) + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_ranges:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_range + + assert_equal([], helper.errors) + end + end + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_not_cared:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_not_cared + + assert_equal([], helper.errors) + end + end + + def test_allocate + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate} + end + + def test_parse_argument_error + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)} + end + + def test_column_with_long_heredoc_identifier + term = "A"*257 + ast = RubyVM::AbstractSyntaxTree.parse("<<-#{term}\n""ddddddd\n#{term}\n") + node = ast.children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + end + + def test_column_of_heredoc + node = RubyVM::AbstractSyntaxTree.parse("<<-SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(6, node.last_column) + + node = RubyVM::AbstractSyntaxTree.parse("<<SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(5, node.last_column) + end + + def test_parse_raises_syntax_error + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse("end") + end + end + + def test_parse_file_raises_syntax_error + Tempfile.create(%w"test_ast .rb") do |f| + f.puts "end" + f.close + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse_file(f.path) + end + end + end + + def test_of + proc = Proc.new { 1 + 2 } + method = self.method(__method__) + + node_proc = RubyVM::AbstractSyntaxTree.of(proc) + node_method = RubyVM::AbstractSyntaxTree.of(method) + + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method) + assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") } + + Tempfile.create(%w"test_of .rb") do |tmp| + tmp.print "#{<<-"begin;"}\n#{<<-'end;'}" + begin; + SCRIPT_LINES__ = {} + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x})) + end; + tmp.close + assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + load ARGV[0] + assert_empty(SCRIPT_LINES__) + end; + end + end + + def test_scope_local_variables + node = RubyVM::AbstractSyntaxTree.parse("x = 0") + lv, _, body = *node.children + assert_equal([:x], lv) + assert_equal(:LASGN, body.type) + end + + def test_call + node = RubyVM::AbstractSyntaxTree.parse("nil.foo") + _, _, body = *node.children + assert_equal(:CALL, body.type) + recv, mid, args = body.children + assert_equal(:NIL, recv.type) + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_fcall + node = RubyVM::AbstractSyntaxTree.parse("foo()") + _, _, body = *node.children + assert_equal(:FCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_vcall + node = RubyVM::AbstractSyntaxTree.parse("foo") + _, _, body = *node.children + assert_equal(:VCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_defn + node = RubyVM::AbstractSyntaxTree.parse("def a; end") + _, _, body = *node.children + assert_equal(:DEFN, body.type) + mid, defn = body.children + assert_equal(:a, mid) + assert_equal(:SCOPE, defn.type) + end + + def test_defs + node = RubyVM::AbstractSyntaxTree.parse("def a.b; end") + _, _, body = *node.children + assert_equal(:DEFS, body.type) + recv, mid, defn = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:b, mid) + assert_equal(:SCOPE, defn.type) + end + + def test_dstr + node = RubyVM::AbstractSyntaxTree.parse('"foo#{1}bar"') + _, _, body = *node.children + assert_equal(:DSTR, body.type) + head, body = body.children + assert_equal("foo", head) + assert_equal(:EVSTR, body.type) + body, = body.children + assert_equal(:LIT, body.type) + assert_equal([1], body.children) + end + + def test_op_asgn2 + node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo") + _, _, body = *node.children + assert_equal(:OP_ASGN2, body.type) + recv, _, mid, op, value = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:field, mid) + assert_equal(:+, op) + assert_equal(:VCALL, value.type) + end +end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index c2039086cf..1d2d1af00c 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -1,12 +1,365 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' +require 'tempfile' 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 + + def test_non_realpath_in_loadpath + require 'tmpdir' + tmpdir = Dir.mktmpdir('autoload') + tmpdirs = [tmpdir] + tmpdirs.unshift(tmpdir + '/foo') + Dir.mkdir(tmpdirs[0]) + tmpfiles = [tmpdir + '/foo.rb', tmpdir + '/foo/bar.rb'] + open(tmpfiles[0] , 'w') do |f| + f.puts <<-INPUT +$:.unshift(File.expand_path('..', __FILE__)+'/./foo') +module Foo + autoload :Bar, 'bar' +end +p Foo::Bar + INPUT + end + open(tmpfiles[1], 'w') do |f| + f.puts 'class Foo::Bar; end' + end + assert_in_out_err([tmpfiles[0]], "", ["Foo::Bar"], []) + ensure + File.unlink(*tmpfiles) rescue nil if tmpfiles + tmpdirs.each {|dir| Dir.rmdir(dir)} + end + + def test_autoload_p + bug4565 = '[ruby-core:35679]' + + require 'tmpdir' + Dir.mktmpdir('autoload') {|tmpdir| + tmpfile = tmpdir + '/foo.rb' + tmpfile2 = tmpdir + '/bar.rb' + a = Module.new do + autoload :X, tmpfile + autoload :Y, tmpfile2 + end + b = Module.new do + include a + end + assert_equal(true, a.const_defined?(:X)) + assert_equal(true, b.const_defined?(:X)) + assert_equal(tmpfile, a.autoload?(:X), bug4565) + assert_equal(tmpfile, b.autoload?(:X), bug4565) + assert_equal(true, a.const_defined?("Y")) + assert_equal(true, b.const_defined?("Y")) + assert_equal(tmpfile2, a.autoload?("Y")) + assert_equal(tmpfile2, b.autoload?("Y")) + } + end + + def test_autoload_with_unqualified_file_name # [ruby-core:69206] + lp = $LOAD_PATH.dup + lf = $LOADED_FEATURES.dup + + Dir.mktmpdir('autoload') { |tmpdir| + $LOAD_PATH << tmpdir + + Dir.chdir(tmpdir) do + eval <<-END + class ::Object + module A + autoload :C, 'b' + end + end + END + + File.open('b.rb', 'w') {|file| file.puts 'module A; class C; end; end'} + assert_kind_of Class, ::A::C + end + } + ensure + $LOAD_PATH.replace lp + $LOADED_FEATURES.replace lf + Object.send(:remove_const, :A) if Object.const_defined?(:A) + end + + def test_require_explicit + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class Object; AutoloadTest = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + assert(require file.path) + assert_equal(1, ::AutoloadTest) + end + ensure + remove_autoload_constant + end + } + end + + def test_threaded_accessing_constant + # 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 + } + } + end + + def test_threaded_accessing_inner_constant + # 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 + } + } + end + + def test_nameerror_when_autoload_did_not_define_the_constant + Tempfile.create(['autoload', '.rb']) {|file| + file.puts '' + file.close + add_autoload(file.path) + begin + assert_raise(NameError) do + AutoloadTest + end + ensure + remove_autoload_constant + end + } + end + + def test_override_autoload + Tempfile.create(['autoload', '.rb']) {|file| + file.puts '' + file.close + add_autoload(file.path) + begin + eval %q(class AutoloadTest; end) + assert_equal(Class, AutoloadTest.class) + ensure + remove_autoload_constant + end + } + end + + def test_override_while_autoloading + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; sleep 0.5; end' + file.close + add_autoload(file.path) + begin + # while autoloading... + t = Thread.new { AutoloadTest } + sleep 0.1 + # override it + EnvUtil.suppress_warning { + eval %q(AutoloadTest = 1) + } + t.join + assert_equal(1, AutoloadTest) + ensure + remove_autoload_constant + end + } + 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 test_autoload_same_file + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/b.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + module Foo; end + module Bar; end + end; + 3.times do # timing-dependent, needs a few times to hit [Bug #14742] + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'b' + autoload :Bar, 'b' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + bug = '[ruby-core:86935] [Bug #14742]' + assert_instance_of Module, t1.value, bug + assert_instance_of Module, t2.value, bug + end; + end + end + end + + def test_no_leak + assert_no_memory_leak([], '', <<~'end;', 'many autoloads', timeout: 30) + 200000.times do |i| + m = Module.new + m.instance_eval do + autoload :Foo, 'x' + autoload :Bar, i.to_s + end + end + end; + end + + def add_autoload(path) + (@autoload_paths ||= []) << path + ::Object.class_eval {autoload(:AutoloadTest, path)} + end + + def remove_autoload_constant + $".replace($" - @autoload_paths) + ::Object.class_eval {remove_const(:AutoloadTest)} + end end diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb new file mode 100644 index 0000000000..0730b5d1c5 --- /dev/null +++ b/test/ruby/test_backtrace.rb @@ -0,0 +1,332 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' + +class TestBacktrace < Test::Unit::TestCase + def test_exception + bt = Fiber.new{ + begin + raise + rescue => e + e.backtrace + end + }.resume + assert_equal(1, bt.size) + assert_match(/.+:\d+:.+/, bt[0]) + end + + def helper_test_exception_backtrace_locations + raise + end + + def test_exception_backtrace_locations + backtrace, backtrace_locations = Fiber.new{ + begin + raise + rescue => e + [e.backtrace, e.backtrace_locations] + end + }.resume + assert_equal(backtrace, backtrace_locations.map{|e| e.to_s}) + + backtrace, backtrace_locations = Fiber.new{ + begin + begin + helper_test_exception_backtrace_locations + rescue + raise + end + rescue => e + [e.backtrace, e.backtrace_locations] + end + }.resume + assert_equal(backtrace, backtrace_locations.map{|e| e.to_s}) + end + + def call_helper_test_exception_backtrace_locations + helper_test_exception_backtrace_locations(:bad_argument) + end + + def test_argument_error_backtrace_locations + backtrace, backtrace_locations = Fiber.new{ + begin + helper_test_exception_backtrace_locations(1) + rescue ArgumentError => e + [e.backtrace, e.backtrace_locations] + end + }.resume + assert_equal(backtrace, backtrace_locations.map{|e| e.to_s}) + + backtrace, backtrace_locations = Fiber.new{ + begin + call_helper_test_exception_backtrace_locations + rescue ArgumentError => e + [e.backtrace, e.backtrace_locations] + end + }.resume + assert_equal(backtrace, backtrace_locations.map{|e| e.to_s}) + end + + def test_caller_lev + cs = [] + Fiber.new{ + Proc.new{ + cs << caller(0) + cs << caller(1) + cs << caller(2) + cs << caller(3) + cs << caller(4) + cs << caller(5) + }.call + }.resume + 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]) + + # + max = 7 + rec = lambda{|n| + if n > 0 + 1.times{ + rec[n-1] + } + else + (max*3).times{|i| + total_size = caller(0).size + c = caller(i) + if c + assert_equal(total_size - i, caller(i).size, "[ruby-dev:45673]") + end + } + end + } + Fiber.new{ + rec[max] + }.resume + end + + def test_caller_lev_and_n + m = 10 + rec = lambda{|n| + if n < 0 + (m*6).times{|lev| + (m*6).times{|i| + t = caller(0).size + r = caller(lev, i) + r = r.size if r.respond_to? :size + + # 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, i, r].inspect) + else + if t - lev > i + assert_equal(i, r, [t, lev, i, r].inspect) + else + assert_equal(t - lev, r, [t, lev, i, r].inspect) + end + end + } + } + else + rec[n-1] + end + } + rec[m] + end + + def test_caller_with_nil_length + assert_equal caller(0), caller(0, nil) + end + + def test_caller_locations + cs = caller(0); locs = caller_locations(0).map{|loc| + loc.to_s + } + assert_equal(cs, locs) + end + + def test_caller_locations_with_range + cs = caller(0,2); locs = caller_locations(0..1).map { |loc| + loc.to_s + } + assert_equal(cs, locs) + end + + def test_caller_locations_to_s_inspect + cs = caller(0); locs = caller_locations(0) + cs.zip(locs){|str, loc| + assert_equal(str, loc.to_s) + assert_equal(str.inspect, loc.inspect) + } + 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 + else + q.pop + end + end + + def test_thread_backtrace + begin + q = Thread::Queue.new + th = Thread.new{ + th_rec q + } + sleep 0.5 + th_backtrace = th.backtrace + th_locations = th.backtrace_locations + + assert_equal(10, th_backtrace.count{|e| e =~ /th_rec/}) + assert_equal(th_backtrace, th_locations.map{|e| e.to_s}) + assert_equal(th_backtrace, th.backtrace(0)) + assert_equal(th_locations.map{|e| e.to_s}, + th.backtrace_locations(0).map{|e| e.to_s}) + th_backtrace.size.times{|n| + assert_equal(n, th.backtrace(0, n).size) + assert_equal(n, th.backtrace_locations(0, n).size) + } + n = th_backtrace.size + assert_equal(n, th.backtrace(0, n + 1).size) + 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 = Thread::Queue.new + th = Thread.new{ + th_rec q + } + sleep 0.5 + bt = th.backtrace(0,2) + locs = th.backtrace_locations(0..1).map { |loc| + loc.to_s + } + assert_equal(bt, locs) + ensure + q << true + th.join + end + end + + def test_core_backtrace_alias + obj = BasicObject.new + e = assert_raise(NameError) do + class << obj + alias foo bar + end + end + assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label) + end + + def test_core_backtrace_undef + obj = BasicObject.new + e = assert_raise(NameError) do + class << obj + undef foo + end + end + assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label) + end + + def test_core_backtrace_hash_merge + e = assert_raise(TypeError) do + {**nil} + end + assert_not_match(/\Acore#/, e.backtrace_locations[0].base_label) + end + + def test_notty_backtrace + err = ["-:1:in `<main>': unhandled exception"] + assert_in_out_err([], "raise", [], err) + + err = ["-:2:in `foo': foo! (RuntimeError)", + "\tfrom -:4:in `<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + foo + end; + + err = ["-:7:in `rescue in bar': bar! (RuntimeError)", + "\tfrom -:4:in `bar'", + "\tfrom -:9:in `<main>'", + "-:2:in `foo': foo! (RuntimeError)", + "\tfrom -:5:in `bar'", + "\tfrom -:9:in `<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + def bar + foo + rescue + raise "bar!" + end + bar + end; + end +end diff --git a/test/ruby/test_basicinstructions.rb b/test/ruby/test_basicinstructions.rb index ff14e4a7a6..ab32ee54e2 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 @@ -85,7 +86,9 @@ class TestBasicInstructions < Test::Unit::TestCase s = "OK" prev = nil 3.times do - assert_equal prev.object_id, (prev ||= /#{s}/o).object_id if prev + re = /#{s}/o + assert_same prev, re if prev + prev = re end end @@ -114,7 +117,6 @@ class TestBasicInstructions < Test::Unit::TestCase assert_equal({1=>2}, {1=>2}) assert_equal({1=>2, 3=>4}, {1=>2, 3=>4}) assert_equal({1=>2, 3=>4}, {3=>4, 1=>2}) - # assert_equal({1=>2, 3=>4}, {1,2, 3,4}) # 1.9 doesn't support assert_equal({"key"=>"val"}, {"key"=>"val"}) end @@ -208,9 +210,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 @@ -500,6 +502,7 @@ class TestBasicInstructions < Test::Unit::TestCase class OP attr_reader :x + attr_accessor :foo def x=(x) @x = x :Bug1996 @@ -600,6 +603,19 @@ class TestBasicInstructions < Test::Unit::TestCase assert_equal 4, x[0] end + def test_send_opassign + return if defined?(RUBY_ENGINE) and RUBY_ENGINE != "ruby" + + bug7773 = '[ruby-core:51821]' + 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 :Bug1996, x.send(:x=, :case_when_setter_returns_other_value), bug7773 + assert_equal :case_when_setter_returns_other_value, x.x, bug7773 + end + def test_backref /re/ =~ 'not match' assert_nil $~ @@ -632,7 +648,7 @@ class TestBasicInstructions < Test::Unit::TestCase assert_equal 'i', $~[9] assert_equal 'x', $` assert_equal 'abcdefghi', $& - assert_equal 'y', $' + assert_equal "y", $' assert_equal 'i', $+ assert_equal 'a', $1 assert_equal 'b', $2 @@ -662,19 +678,45 @@ class TestBasicInstructions < Test::Unit::TestCase end def test_array_splat + feature1125 = '[ruby-core:21901]' + a = [] assert_equal [], [*a] assert_equal [1], [1, *a] + assert_not_same(a, [*a], feature1125) a = [2] assert_equal [2], [*a] assert_equal [1, 2], [1, *a] + assert_not_same(a, [*a], feature1125) a = [2, 3] assert_equal [2, 3], [*a] assert_equal [1, 2, 3], [1, *a] + assert_not_same(a, [*a], feature1125) a = nil 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 0540f5df1c..eb8394864f 100644 --- a/test/ruby/test_beginendblock.rb +++ b/test/ruby/test_beginendblock.rb @@ -1,127 +1,179 @@ +# 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 e4 e3 e2 e4-2 e4-1 e1-1 e4-1-1), result.split) - - input = Tempfile.new(self.class.name) - inputpath = input.path - input.close - 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.open - 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 - assert_raise(SyntaxError) do + assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do eval("def foo; BEGIN {}; end") end - assert_raise(SyntaxError) do + assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do eval('eval("def foo; BEGIN {}; end")') end end def test_begininclass - assert_raise(SyntaxError) do + assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do eval("class TestBeginEndBlock; BEGIN {}; end") end end def test_endblockwarn - ruby = EnvUtil.rubybin - # Use Tempfile to create temporary file path. - launcher = Tempfile.new(self.class.name) - errout = Tempfile.new(self.class.name) - - 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)) - # expecting Tempfile to unlink launcher and errout file. + 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 - } - 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]' + 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 - def test_should_propagate_exit_code + def test_propagate_exit_code ruby = EnvUtil.rubybin assert_equal false, system(ruby, '-e', 'at_exit{exit 2}') assert_equal 2, $?.exitstatus assert_nil $?.termsig end - def test_should_propagate_signaled - ruby = EnvUtil.rubybin - out = IO.popen( - [ruby, - '-e', 'STDERR.reopen(STDOUT)', - '-e', 'at_exit{Process.kill(:INT, $$); sleep 5 }']) {|f| - timeout(10) { - f.read - } - } - assert_match(/Interrupt$/, out) + def test_propagate_signaled + 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 + 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 + bug5218 = '[ruby-core:43173][Bug #5218]' + cmd = [ + "raise 'X' rescue nil", + "nil", + "exit(42)", + ] + %w[at_exit END].each do |ex| + out, err, status = EnvUtil.invoke_ruby(cmd.map {|s|["-e", "#{ex} {#{s}}"]}.flatten, "", true, true) + assert_equal(["", "", 42], [out, err, status.exitstatus], "#{bug5218}: #{ex}") + end + end + + def test_callcc_at_exit + bug9110 = '[ruby-core:58329][Bug #9110]' + 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 5cff48c4d7..58d63a7c29 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,20 +56,37 @@ 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) assert_equal($x, fact(40)) - assert($x < $x+2) - assert($x > $x-2) + assert_operator($x, :<, $x+2) + assert_operator($x, :>, $x-2) assert_equal(815915283247897734345611269596115894272000000000, $x) assert_not_equal(815915283247897734345611269596115894272000000001, $x) assert_equal(815915283247897734345611269596115894272000000001, $x+1) 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) @@ -106,19 +156,10 @@ class TestBignum < Test::Unit::TestCase assert_equal("nd075ib45k86g" ,18446744073709551616.to_s(31), "[ruby-core:10686]") assert_equal("1777777777777777777777" ,18446744073709551615.to_s(8)) assert_equal("-1777777777777777777777" ,-18446744073709551615.to_s(8)) + assert_match(/\A10{99}1\z/, (10**100+1).to_s) + assert_match(/\A10{900}9{100}\z/, (10**1000+(10**100-1)).to_s) end - - T_ZERO = (2**32).coerce(0).first - T_ONE = (2**32).coerce(1).first - T_MONE = (2**32).coerce(-1).first - T31 = 2**31 # 2147483648 - T31P = T31 - 1 # 2147483647 - T32 = 2**32 # 4294967296 - T32P = T32 - 1 # 4294967295 - T64 = 2**64 # 18446744073709551616 - T64P = T64 - 1 # 18446744073709551615 - def test_big_2comp assert_equal("-4294967296", (~T32P).to_s) assert_equal("..f00000000", "%x" % -T32) @@ -180,21 +221,24 @@ class TestBignum < Test::Unit::TestCase end def test_cmp - assert(T31P > 1) - assert(T31P < 2147483648.0) - assert(T31P < T64P) - assert(T64P > T31P) + assert_operator(T31P, :>, 1) + assert_operator(T31P, :<, 2147483648.0) + assert_operator(T31P, :<, T64P) + assert_operator(T64P, :>, T31P) assert_raise(ArgumentError) { T31P < "foo" } + assert_operator(T64, :<, (1.0/0.0)) + assert_not_operator(T64, :>, (1.0/0.0)) end def test_eq - assert(T31P != 1) - assert(T31P == 2147483647.0) - assert(T31P != "foo") + assert_not_equal(T31P, 1) + assert_equal(T31P, 2147483647.0) + assert_not_equal(T31P, "foo") + assert_not_equal(2**77889, (1.0/0.0), '[ruby-core:31603]') end def test_eql - assert(T31P.eql?(T31P)) + assert_send([T31P, :eql?, T31P]) end def test_convert @@ -239,18 +283,149 @@ 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 + def test_mul_large_numbers + a = %w[ + 32580286268570032115047167942578356789222410206194227403993117616454027392 + 62501901985861926098797067562795526004375784403965882943322008991129440928 + 33855888840298794008677656280486901895499985197580043127115026675632969396 + 55040226415022070581995493731570435346323030715226718346725312551631168110 + 83966158581772380474470605428802018934282425947323171408377505151988776271 + 85865548747366001752375899635539662017095652855537225416899242508164949615 + 96848508410008685252121247181772953744297349638273854170932226446528911938 + 03430429031094465344063914822790537339912760237589085026016396616506014081 + 53557719631183538265614091691713138728177917059624255801026099255450058876 + 97412698978242128457751836011774504753020608663272925708049430557191193188 + 23212591809241860763625985763438355314593186083254640117460724730431447842 + 15432124830037389073162094304199742919767272162759192882136828372588787906 + 96027938532441670018954643423581446981760344524184231299785949158765352788 + 38452309862972527623669323263424418781899966895996672291193305401609553502 + 63893514163147729201340204483973131948541009975283778189609285614445485714 + 63843850089417416331356938086609682943037801440660232801570877143192251897 + 63026816485314923378023904237699794122181407920355722922555234540701118607 + 37971417665315821995516986204709574657462370947443531049033704997194647442 + 13711787319587466437795542850136751816475182349380345341647976135081955799 + 56787050815348701001765730577514591032367920292271016649813170789854524395 + 72571698998841196411826453893352760318867994518757872432266374568779920489 + 55597104558927387008506485038236352630863481679853742412042588244086070827 + 43705456833283086410967648483312972903432798923897357373793064381177468258 + 69131640408147806442422254638590386673344704147156793990832671592488742473 + 31524606724894164324227362735271650556732855509929890983919463699819116427 + ].join.to_i + b = %w[ + 31519454770031243652776765515030872050264386564379909299874378289835540661 + 99756262835346828114038365624177182230027040172583473561802565238817167503 + 85144159132462819032164726177606533272071955542237648482852154879445654746 + 25061253606344846225905712926863168413666058602449408307586532461776530803 + 56810626880722653177544008166119272373179841889454920521993413902672848145 + 77974951972342194855267960390195830413354782136431833731467699250684103370 + 98571305167189174270854698169136844578685346745340041520068176478277580590 + 43810457765638903028049263788987034217272442328962400931269515791911786205 + 15357047519615932249418012945178659435259428163356223753159488306813844040 + 93609959555018799309373542926110109744437994067754004273450659607204900586 + 28878103661124568217617766580438460505513654179249613168352070584906185237 + 34829991855182473813233425492094534396541544295119674419522772382981982574 + 64708442087451070125274285088681225122475041996116377707892328889948526913 + 82239084041628877737628853240361038273348062246951097300286513836140601495 + 63604611754185656404194406869925540477185577643853560887894081047256701731 + 66884554460428760857958761948461476977864005799494946578017758268987123749 + 85937011490156431231903167442071541493304390639100774497107347884381581049 + 85451663323551635322518839895028929788021096587229364219084708576998525298 + 39594168681411529110089531428721005176467479027585291807482375043729783455 + 35827667428080449919778142400266842990117940984804919512360370451936835708 + 76338722049621773169385978521438867493162717866679193103745711403152099047 + 27294943901673885707639094215339506973982546487889199083181789561917985023 + 82368442718514694400160954955539704757794969665555505203532944598698824542 + 00599461848630034847211204029842422678421808487300084850702007663003230882 + 16645745324467830796203354080471008809087072562876681588151822072260738003 + ].join.to_i + c = %w[ + 10269128594368631269792194698469828812223242061960065022209211719149714886 + 03494742299892841188636314745174778237781513956755034582435818316155459882 + 71422025990633195596790290038198841087091600598192959108790192789550336119 + 13849937951116346796903163312950010689963716629093190601532313463306463573 + 64436438673379454947908896258675634478867189655764364639888427350090856831 + 84369949421175534994092429682748078316130135651006102162888937624830856951 + 64818150356583421988135211585954838926347035741143424980258821170351244310 + 33072045488402539147707418016613224788469923473310249137422855065567940804 + 75231970365923936034328561426062696074717204901606475826224235014948198414 + 19979210494282212322919438926816203585575357874850252052656098969732107129 + 30639419804565653489687198910271702181183420960744232756057631336661646896 + 48734093497394719644969417287962767186599484579769717220518657324467736902 + 16947995288312851432262922140679347615046098863974141226499783975470926697 + 95970415188661518504275964397022973192968233221707696639386238428211541334 + 69925631385166494600401675904803418143232703594169525858261988389529181035 + 06048776134746377586210180203524132714354779486439559392942733781343640971 + 02430607931736785273011780813863748280091795277451796799961887248262211653 + 38966967509803488282644299584920109534552889962877144862747797551711984992 + 00726518175235286668236031649728858774545087668286506201943248842967749907 + 05345423019480534625965140632428736051632750698608916592720742728646191514 + 86268964807395494825321744802493138032936406889713953832376411900451422777 + 06372983421062172556566901346288286168790235741528630664513209619789835729 + 36999522461733403414326366959273556098219489572448083984779946889707480205 + 42459898495081687425132939473146331452400120169525968892769310016015870148 + 66821361032541586130017904207971120217385522074967066199941112154460026348 + 07223950375610474071278649031647998546085807777970592429037128484222394216 + 33776560239741740193444702279919018283324070210090106960567819910943036248 + 16660475627526085805165023447934326510232828674828006752369603151390527384 + 16810180735871644266726954590262010744712519045524839388305761859432443670 + 05188791334908140831469790180096209292338569623252372975043915954675335333 + 66614002146554533771788633057869340167604765688639181655208751680821446276 + 75871494160208888666798836473728725968253820774671626436794492530356258709 + 62318715778035246655925307167306434486713879511272648637608703497794724929 + 54912261106702913491290913962825303534484477936036071463820553314826894581 + 36951927032835690160443252405644718368516656317176848748544135126122940034 + 68454782581240953957381976073459570718038035358630417744490242611126043987 + 89191812971310096496208294948623403471433467614886863238916702384858514703 + 24327715474804343531844042107910755966152655912676456945146277848606406879 + 49724219295823540160221752189725460676360350860849986313532861445465771187 + 86822806696323658053947125253562001971534265078959827450518368635828010637 + 91977444206363529864361796188661941906329947840521598310396004328950804758 + 79728679236044038853668859284513594307352133390781441610395116807369310560 + 35193762565748328526426224069629084264376146174383444988110993194030351064 + 29660536743256949099972314033972121470913480844652490838985461134989129492 + 75577567064571716731774820127381261057956083604361635892088585967074514802 + 51958582645785905276289980534832170529946494815794770854644518463332458915 + 77572397432680871220602513555535017751714443325264019171753694163676670792 + 04353584782364068773777058727187323211012094819929720407636607815292764459 + 21851731257845562153822058534043916834839514338448582518847879059020959697 + 90538105704766415685100946308842788321400392381169436435078204622400475281 + ].join.to_i + assert_equal(c, a*b, '[ruby-core:48552]') + end + def test_divrem assert_equal(0, T32 / T64) end + def test_divide + bug5490 = '[ruby-core:40429]' + assert_raise(ZeroDivisionError, bug5490) {T1024./(0)} + assert_equal(Float::INFINITY, T1024./(0.0), bug5490) + end + def test_div assert_equal(T32.to_f, T32 / 1.0) assert_raise(TypeError) { T32 / "foo" } assert_equal(0x20000000, 0x40000001.div(2.0), "[ruby-dev:34553]") + bug5490 = '[ruby-core:40429]' + assert_raise(ZeroDivisionError, bug5490) {T1024.div(0)} + assert_raise(ZeroDivisionError, bug5490) {T1024.div(0.0)} end def test_idiv @@ -273,6 +448,8 @@ class TestBignum < Test::Unit::TestCase end def test_quo + assert_kind_of(Float, T32.quo(1.0)) + assert_equal(T32.to_f, T32.quo(1)) assert_equal(T32.to_f, T32.quo(1.0)) assert_equal(T32.to_f, T32.quo(T_ONE)) @@ -280,11 +457,11 @@ class TestBignum < Test::Unit::TestCase assert_raise(TypeError) { T32.quo("foo") } assert_equal(1024**1024, (1024**1024).quo(1)) - assert_equal(1024**1024, (1024**1024).quo(1.0)) + assert_equal(Float::INFINITY, (1024**1024).quo(1.0)) assert_equal(1024**1024*2, (1024**1024*2).quo(1)) inf = 1 / 0.0; nan = inf / inf - assert((1024**1024*2).quo(nan).nan?) + assert_send([(1024**1024*2).quo(nan), :nan?]) end def test_pow @@ -296,6 +473,9 @@ class TestBignum < Test::Unit::TestCase ### rational changes the behavior of Bignum#** #assert_raise(TypeError) { T32**"foo" } assert_raise(TypeError, ArgumentError) { T32**"foo" } + + feature3429 = '[ruby-core:30735]' + assert_kind_of(Integer, (2 ** 7830457), feature3429) end def test_and @@ -309,6 +489,7 @@ class TestBignum < Test::Unit::TestCase assert_equal(T32 + T31, T32 | T31) assert_equal(-T31, (-T32) | (-T31)) assert_equal(T64 + T32, T32 | T64) + assert_equal(FIXNUM_MAX, T_ZERO | FIXNUM_MAX) end def test_xor @@ -318,34 +499,83 @@ class TestBignum < Test::Unit::TestCase assert_equal(T64 + T32, T32 ^ T64) end + class DummyNumeric < Numeric + def to_int + 1 + end + end + + def test_and_with_float + assert_raise(TypeError) { T1024 & 1.5 } + end + + def test_and_with_rational + assert_raise(TypeError, "#1792") { T1024 & Rational(3, 2) } + end + + def test_and_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { T1024 & DummyNumeric.new } + end + + def test_or_with_float + assert_raise(TypeError) { T1024 | 1.5 } + end + + def test_or_with_rational + assert_raise(TypeError, "#1792") { T1024 | Rational(3, 2) } + end + + def test_or_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { T1024 | DummyNumeric.new } + end + + def test_xor_with_float + assert_raise(TypeError) { T1024 ^ 1.5 } + end + + def test_xor_with_rational + assert_raise(TypeError, "#1792") { T1024 ^ Rational(3, 2) } + end + + def test_xor_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { T1024 ^ DummyNumeric.new } + 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 + big = 2**300 + assert_equal(2**65538 / (2**65537), 2**65538 >> big.coerce(65537).first) 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 @@ -355,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 @@ -362,44 +594,81 @@ class TestBignum < Test::Unit::TestCase end def test_size - assert(T31P.size.is_a?(Integer)) + assert_kind_of(Integer, T31P.size) 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 interrupt + def test_interrupt_during_to_s + if defined?(Integer::GMP_VERSION) + return # GMP doesn't support interrupt during an operation. + end time = Time.now start_flag = false end_flag = false + num = (65536 ** 65536) + q = Queue.new thread = Thread.new do - start_flag = true - yield - end_flag = true + assert_raise(RuntimeError) { + q << true + num.to_s + end_flag = true + } end - sleep 1 + q.pop # sync thread.raise - thread.join rescue nil - start_flag && !end_flag && Time.now - time < 10 + thread.join + time = Time.now - time + skip "too fast cpu" if end_flag + assert_operator(time, :<, 10) end - def test_interrupt - assert(interrupt { (65536 ** 65536).to_s }) + def test_interrupt_during_bigdivrem + if defined?(Integer::GMP_VERSION) + return # GMP doesn't support interrupt during an operation. + end + return unless Process.respond_to?(:kill) + begin + trace = [] + oldtrap = Signal.trap(:INT) {|sig| trace << :int } + a = 456 ** 100 + b = 123 ** 100 + c = nil + 100.times do |n| + a **= 3 + b **= 3 + trace.clear + th = Thread.new do + sleep 0.1; Process.kill :INT, $$ + sleep 0.1; Process.kill :INT, $$ + end + c = a / b + trace << :end + th.join + if trace == [:int, :int, :end] + assert_equal(a / b, c) + return + end + end + skip "cannot create suitable test case" + ensure + Signal.trap(:INT, oldtrap) if oldtrap + end end def test_too_big_to_s - if (big = 2**31-1).is_a?(Fixnum) + if (big = 2**31-1).fixnum? return end - e = assert_raise(RangeError) {(1 << big).to_s} - assert_match(/too big to convert/, e.message) + assert_raise_with_message(RangeError, /too big to convert/) {(1 << big).to_s} end def test_fix_fdiv @@ -419,7 +688,7 @@ class TestBignum < Test::Unit::TestCase def test_float_fdiv b = 1E+300.to_i assert_equal(b, (b ** 2).fdiv(b)) - assert(@big.fdiv(0.0 / 0.0).nan?) + assert_send([@big.fdiv(0.0 / 0.0), :nan?]) assert_in_delta(1E+300, (10**500).fdiv(1E+200), 1E+285) end @@ -427,5 +696,104 @@ 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 + # this test assumes 32bit/64bit platform + assert_raise(TypeError) { a = 1 << 64; def a.foo; end } + end + + def test_frozen + assert_equal(true, (2**100).frozen?) + end + + def test_bitwise_and_with_integer_mimic_object + def (obj = Object.new).to_int + 10 + end + assert_raise(TypeError, '[ruby-core:39491]') { T1024 & obj } + + def obj.coerce(other) + [other, 10] + end + assert_equal(T1024 & 10, T1024 & obj) + end + + def test_bitwise_or_with_integer_mimic_object + def (obj = Object.new).to_int + 10 + end + assert_raise(TypeError, '[ruby-core:39491]') { T1024 | obj } + + def obj.coerce(other) + [other, 10] + end + assert_equal(T1024 | 10, T1024 | obj) + end + + def test_bitwise_xor_with_integer_mimic_object + def (obj = Object.new).to_int + 10 + end + assert_raise(TypeError, '[ruby-core:39491]') { T1024 ^ obj } + + def obj.coerce(other) + [other, 10] + 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 8f861d96a1..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 @@ -16,4 +17,86 @@ class TestCall < Test::Unit::TestCase assert_equal([1, 2, 3, 4], aaa(1, 2, 3, 4)) assert_equal([1, 2, 3, 4], aaa(1, *[2, 3, 4])) end + + def test_callinfo + bug9622 = '[ruby-core:61422] [Bug #9622]' + o = Class.new do + def foo(*args) + bar(:foo, *args) + end + def bar(name) + name + end + end.new + e = assert_raise(ArgumentError) {o.foo(100)} + 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 98498dada6..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,66 @@ 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 + + def test_optimization + case 1 + when 0.9, 1.1 + assert(false) + when 1.0 + assert(true) + else + assert(false) + end + case 536870912 + when 536870911.9, 536870912.1 + assert(false) + when 536870912.0 + assert(true) + else + assert(false) + end + end + + def test_method_missing + flag = false + + case 1 + when Class.new(BasicObject) { def method_missing(*) true end }.new + flag = true + end + + assert(flag) + end + + def test_nomethoderror + assert_raise(NoMethodError) { + case 1 + when Class.new(BasicObject) { }.new + 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 c81f0752d4..2ab1e7f266 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 @@ -105,6 +105,32 @@ class TestClass < Test::Unit::TestCase end end + def test_extend_object + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:extend_object).bind(c).call(Object.new) + end + end + + def test_append_features + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:append_features).bind(c).call(Module.new) + end + end + + def test_prepend_features + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:prepend_features).bind(c).call(Module.new) + end + end + + def test_module_specific_methods + assert_empty(Class.private_instance_methods(true) & + [:module_function, :extend_object, :append_features, :prepend_features]) + end + def test_method_redefinition feature2155 = '[ruby-dev:39400]' @@ -118,23 +144,21 @@ class TestClass < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end alias bar foo def foo; end end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end alias bar foo alias bar foo end end - assert_equal("", stderr) line = __LINE__+4 stderr = EnvUtil.verbose_warning do @@ -146,22 +170,20 @@ class TestClass < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do define_method(:foo) do end alias bar foo alias bar foo end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end undef foo end end - assert_equal("", stderr) end def test_check_inheritable @@ -172,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 @@ -187,6 +212,8 @@ class TestClass < Test::Unit::TestCase def test_singleton_class assert_raise(TypeError) { 1.extend(Module.new) } + assert_raise(TypeError) { 1.0.extend(Module.new) } + assert_raise(TypeError) { (2.0**1000).extend(Module.new) } assert_raise(TypeError) { :foo.extend(Module.new) } assert_in_out_err([], <<-INPUT, %w(:foo :foo true true), []) @@ -203,6 +230,13 @@ class TestClass < Test::Unit::TestCase def test_uninitialized assert_raise(TypeError) { Class.allocate.new } assert_raise(TypeError) { Class.allocate.superclass } + bug6863 = '[ruby-core:47148]' + assert_raise(TypeError, bug6863) { Class.new(Class.allocate) } + + allocator = Class.instance_method(:allocate) + assert_raise_with_message(TypeError, /prohibited/) { + allocator.bind(Rational).call + } end def test_nonascii_name @@ -212,13 +246,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 @@ -236,4 +285,396 @@ class TestClass < Test::Unit::TestCase copy.send(:include, mod) assert_equal("mod#foo", copy.new.foo) end + + def test_nested_class_removal + assert_normal_exit('File.__send__(:remove_const, :Stat); at_exit{File.stat(".")}; GC.start') + end + + class PrivateClass + end + private_constant :PrivateClass + + def test_redefine_private_class + assert_raise(NameError) do + eval("class ::TestClass::PrivateClass; end") + end + eval <<-END + class ::TestClass + class PrivateClass + def foo; 42; end + end + end + END + assert_equal(42, PrivateClass.new.foo) + end + + StrClone = String.clone + Class.new(StrClone) + + def test_cloned_class + bug5274 = StrClone.new("[ruby-dev:44460]") + assert_equal(bug5274, Marshal.load(Marshal.dump(bug5274))) + end + + def test_cannot_reinitialize_class_with_initialize_copy # [ruby-core:50869] + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["Object"], []) + begin; + class Class + def initialize_copy(*); super; end + end + + class A; end + class B; end + + A.send(:initialize_copy, Class.new(B)) rescue nil + + p A.superclass + end; + end + + module M + C = 1 + + def self.m + C + end + end + + def test_constant_access_from_method_in_cloned_module # [ruby-core:47834] + m = M.dup + assert_equal 1, m::C + assert_equal 1, m.m + end + + def test_invalid_superclass + assert_raise(TypeError) do + eval <<-'end;' + class C < nil + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < false + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < true + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < 0 + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < "" + 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 + bug5283 = '[ruby-dev:44477]' + added = [] + c = Class.new + c.singleton_class.class_eval do + define_method(:singleton_method_added) {|mid| added << [self, mid]} + def foo; :foo; end + end + added.clear + d = c.clone + assert_empty(added.grep(->(k) {c == k[0]}), bug5283) + assert_equal(:foo, d.foo) + end + + def test_clone_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_singleton_class_of_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_method_exists_on_singleton_class_of_singleton_class + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class.define_method(:s2_method) { :s2 } + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_equal(:s2, o.singleton_class.s2_method) + assert_equal(:s2, clone.singleton_class.s2_method) + assert_equal([:s2_method], o.singleton_class.singleton_class.instance_methods(false)) + assert_equal([:s2_method], clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_singleton_class_p + feature7609 = '[ruby-core:51087] [Feature #7609]' + assert_predicate(self.singleton_class, :singleton_class?, feature7609) + 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..321feb07c7 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 @@ -25,4 +26,39 @@ class TestClone < Test::Unit::TestCase assert_equal([M003, M002, M001], M003.ancestors) end + + def test_user_flags + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1, 2, 3].clone + assert_equal [], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1,2,3,4,5,6,7][1..-2].clone + x.push(1,1,1,1,1) + assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Hash + undef initialize_copy + def initialize_copy(*); end + end + h = {} + h.default_proc = proc { raise } + h = h.clone + assert_equal nil, h[:not_exist], '[Bug #14847]' + EOS + end end diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb index 00ce6b485a..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,8 +76,40 @@ 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 + bug7870 = '[ruby-core:52305] [Bug #7870]' + assert_nothing_raised(SystemStackError, bug7870) { + assert_nil(Time.new <=> "") + } + end + + def test_no_cmp + bug9003 = '[ruby-core:57736] [Bug #9003]' + assert_nothing_raised(SystemStackError, bug9003) { + @o <=> @o.dup + } end end diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb index f6d65de6de..0161ce8ffe 100644 --- a/test/ruby/test_complex.rb +++ b/test/ruby/test_complex.rb @@ -1,16 +1,14 @@ +# 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 + 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]') + assert_equal(5.quo(2), Complex(2.5, 0).rationalize(0), '[ruby-core:40667]') end def test_compsub @@ -18,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]') @@ -47,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 @@ -85,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 @@ -127,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()} @@ -203,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?) @@ -261,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 @@ -299,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 @@ -316,10 +265,41 @@ 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_add_with_redefining_int_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :+ + def +(other); 42; end + end + a = Complex(1, 2) + Complex(0, 1) + puts a == Complex(42, 42) + end; + end + + def test_add_with_redefining_float_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :+ + def +(other); 42.0; end + end + a = Complex(1.0, 2.0) + Complex(0, 1) + puts a == Complex(42.0, 42.0) + end; + end + + def test_add_with_redefining_rational_plus + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :+ + def +(other); 355/113r; end + end + a = Complex(1r, 2r) + Complex(0, 1) + puts a == Complex(355/113r, 355/113r) + end; end def test_sub @@ -331,10 +311,41 @@ 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_sub_with_redefining_int_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :- + def -(other); 42; end + end + a = Complex(1, 2) - Complex(0, 1) + puts a == Complex(42, 42) + end; + end + + def test_sub_with_redefining_float_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :- + def -(other); 42.0; end + end + a = Complex(1.0, 2.0) - Complex(0, 1) + puts a == Complex(42.0, 42.0) + end; + end + + def test_sub_with_redefining_rational_minus + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :- + def -(other); 355/113r; end + end + a = Complex(1r, 2r) - Complex(0, 1) + puts a == Complex(355/113r, 355/113r) + end; end def test_mul @@ -346,24 +357,58 @@ 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)) + + assert_equal(Complex(-0.0, -0.0), Complex(-0.0, 0) * Complex(0, 0)) + end + + def test_mul_with_redefining_int_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Integer + remove_method :* + def *(other); 42; end + end + a = Complex(2, 0) * Complex(1, 2) + puts a == Complex(0, 84) + end; + end + + def test_mul_with_redefining_float_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Float + remove_method :* + def *(other); 42.0; end + end + a = Complex(2.0, 0.0) * Complex(1, 2) + puts a == Complex(0.0, 84.0) + end; + end + + + def test_mul_with_redefining_rational_mult + assert_in_out_err([], <<-'end;', ['true'], []) + class Rational + remove_method :* + def *(other); 355/113r; end + end + a = Complex(2r, 0r) * Complex(1, 2) + puts a == Complex(0r, 2*355/113r) + end; end def test_div 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) @@ -375,30 +420,25 @@ 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)) + + c = Complex(1) + r = c / c + assert_instance_of(Complex, r) + assert_equal(1, r) + assert_predicate(r.real, :integer?) + assert_predicate(r.imag, :integer?) 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) @@ -410,17 +450,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 @@ -454,13 +488,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) @@ -469,21 +498,21 @@ 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) + + c = Complex(0.0, -888888888888888.0)**8888 + assert_not_predicate(c.real, :nan?) + assert_not_predicate(c.imag, :nan?) end def test_cmp @@ -493,19 +522,19 @@ class Complex_Test < Test::Unit::TestCase end def test_eqeq - assert(Complex(1,0) == Complex(1)) - assert(Complex(-1,0) == Complex(-1)) + assert_equal(Complex(1), Complex(1,0)) + assert_equal(Complex(-1), Complex(-1,0)) - assert_equal(false, Complex(2,1) == Complex(1)) - assert_equal(true, Complex(2,1) != Complex(1)) - assert_equal(false, Complex(1) == nil) - assert_equal(false, Complex(1) == '') + assert_not_equal(Complex(1), Complex(2,1)) + assert_operator(Complex(2,1), :!=, Complex(1)) + assert_not_equal(nil, Complex(1)) + assert_not_equal('', Complex(1)) nan = 0.0 / 0 if nan.nan? && nan != nan - assert_equal(false, Complex(nan, 0) == Complex(nan, 0)) - assert_equal(false, Complex(0, nan) == Complex(0, nan)) - assert_equal(false, Complex(nan, nan) == Complex(nan, nan)) + assert_not_equal(Complex(nan, 0), Complex(nan, 0)) + assert_not_equal(Complex(0, nan), Complex(0, nan)) + assert_not_equal(Complex(nan, nan), Complex(nan, nan)) end end @@ -515,17 +544,25 @@ 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 + alias - + + alias * + + alias / + + alias quo + + alias ** + + def coerce(x) [x, Complex(1)] 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 + def test_coerce2 + x = ObjectX.new + %w(+ - * / quo **).each do |op| + assert_kind_of(Numeric, Complex(1).__send__(op, x)) end end @@ -550,8 +587,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) @@ -579,23 +614,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 @@ -617,21 +650,32 @@ 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) + + bug3656 = '[ruby-core:31622]' + c = Complex(1,2) + assert_predicate(c, :frozen?) + result = c.marshal_load([2,3]) rescue :fail + assert_equal(:fail, result, bug3656) + assert_equal(Complex(1,2), c) + end - s = Marshal.dump(c) - c2 = Marshal.load(s) - assert_equal(c, c2) - assert_instance_of(Complex, c2) + def test_marshal_compatibility + bug6625 = '[ruby-core:45775]' + dump = "\x04\x08o:\x0cComplex\x07:\x0a@reali\x06:\x0b@imagei\x07" + assert_nothing_raised(bug6625) do + assert_equal(Complex(1, 2), Marshal.load(dump), bug6625) end end @@ -667,6 +711,15 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(0.0,3.0), '3.0i'.to_c) assert_equal(Complex(0.0,-3.0), '-3.0i'.to_c) + assert_equal(Complex(5.1), '5.1'.to_c) + assert_equal(Complex(-5.2), '-5.2'.to_c) + assert_equal(Complex(5.3,3.4), '5.3+3.4i'.to_c) + assert_equal(Complex(-5.5,3.6), '-5.5+3.6i'.to_c) + assert_equal(Complex(5.3,-3.4), '5.3-3.4i'.to_c) + assert_equal(Complex(-5.5,-3.6), '-5.5-3.6i'.to_c) + assert_equal(Complex(0.0,3.1), '3.1i'.to_c) + assert_equal(Complex(0.0,-3.2), '-3.2i'.to_c) + assert_equal(Complex(5.0), '5e0'.to_c) assert_equal(Complex(-5.0), '-5e0'.to_c) assert_equal(Complex(5.0,3.0), '5e0+3e0i'.to_c) @@ -676,6 +729,15 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(0.0,3.0), '3e0i'.to_c) assert_equal(Complex(0.0,-3.0), '-3e0i'.to_c) + assert_equal(Complex(5e1), '5e1'.to_c) + assert_equal(Complex(-5e2), '-5e2'.to_c) + assert_equal(Complex(5e3,3e4), '5e003+3e4i'.to_c) + assert_equal(Complex(-5e5,3e6), '-5e5+3e006i'.to_c) + assert_equal(Complex(5e3,-3e4), '5e003-3e4i'.to_c) + assert_equal(Complex(-5e5,-3e6), '-5e5-3e006i'.to_c) + assert_equal(Complex(0.0,3e1), '3e1i'.to_c) + assert_equal(Complex(0.0,-3e2), '-3e2i'.to_c) + assert_equal(Complex(0.33), '.33'.to_c) assert_equal(Complex(0.33), '0.33'.to_c) assert_equal(Complex(-0.33), '-.33'.to_c) @@ -718,6 +780,15 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(0.0,3.0), Complex('3.0i')) assert_equal(Complex(0.0,-3.0), Complex('-3.0i')) + assert_equal(Complex(5.1), Complex('5.1')) + assert_equal(Complex(-5.2), Complex('-5.2')) + assert_equal(Complex(5.3,3.4), Complex('5.3+3.4i')) + assert_equal(Complex(-5.5,3.6), Complex('-5.5+3.6i')) + assert_equal(Complex(5.3,-3.4), Complex('5.3-3.4i')) + assert_equal(Complex(-5.5,-3.6), Complex('-5.5-3.6i')) + assert_equal(Complex(0.0,3.1), Complex('3.1i')) + assert_equal(Complex(0.0,-3.2), Complex('-3.2i')) + assert_equal(Complex(5.0), Complex('5e0')) assert_equal(Complex(-5.0), Complex('-5e0')) assert_equal(Complex(5.0,3.0), Complex('5e0+3e0i')) @@ -727,6 +798,15 @@ class Complex_Test < Test::Unit::TestCase assert_equal(Complex(0.0,3.0), Complex('3e0i')) assert_equal(Complex(0.0,-3.0), Complex('-3e0i')) + assert_equal(Complex(5e1), Complex('5e1')) + assert_equal(Complex(-5e2), Complex('-5e2')) + assert_equal(Complex(5e3,3e4), Complex('5e003+3e4i')) + assert_equal(Complex(-5e5,3e6), Complex('-5e5+3e006i')) + assert_equal(Complex(5e3,-3e4), Complex('5e003-3e4i')) + assert_equal(Complex(-5e5,-3e6), Complex('-5e5-3e006i')) + assert_equal(Complex(0.0,3e1), Complex('3e1i')) + assert_equal(Complex(0.0,-3e2), Complex('-3e2i')) + assert_equal(Complex(0.33), Complex('.33')) assert_equal(Complex(0.33), Complex('0.33')) assert_equal(Complex(-0.33), Complex('-.33')) @@ -760,57 +840,81 @@ 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_Complex_without_exception + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex('5x', exception: false)) + } + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(nil, exception: false)) + } + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(Object.new, exception: false)) + } + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(1, nil, exception: false)) + } + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(1, Object.new, exception: false)) + } + + o = Object.new + def o.to_c; raise; end + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(o, exception: false)) + } + assert_nothing_raised(ArgumentError){ + assert_equal(nil, Complex(1, o, exception: false)) + } + 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 @@ -828,12 +932,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 @@ -849,10 +951,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]) @@ -865,9 +965,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) @@ -897,9 +1033,9 @@ class Complex_Test < Test::Unit::TestCase if (0.0/0).nan? nan = 0.0/0 - assert(nan.arg.equal?(nan)) - assert(nan.angle.equal?(nan)) - assert(nan.phase.equal?(nan)) + assert_same(nan, nan.arg) + assert_same(nan, nan.angle) + assert_same(nan, nan.phase) end assert_equal(Math::PI, -1.arg) @@ -936,134 +1072,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|\/)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 @@ -1073,13 +1088,40 @@ 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) + assert_raise(ArgumentError){ Complex('--8i')} end def test_known_bug end + def test_canonicalize_internal + obj = Class.new(Numeric) do + attr_accessor :real + alias real? real + end.new + obj.real = true + c = Complex.rect(obj, 1); + obj.real = false + c = c.conj + assert_equal(obj, c.real) + assert_equal(-1, c.imag) + end + + def test_canonicalize_polar + obj = Class.new(Numeric) do + def initialize + @x = 2 + end + def real? + (@x -= 1) > 0 + end + end.new + assert_raise(TypeError) do + Complex.polar(1, obj) + end + end end diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb index 4e960c3e36..594fc3f45a 100644 --- a/test/ruby/test_complex2.rb +++ b/test/ruby/test_complex2.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class Complex_Test2 < Test::Unit::TestCase def test_kumi - return unless defined?(Rational) + skip unless defined?(Rational) assert_equal(Complex(1, 0), +Complex(1, 0)) assert_equal(Complex(-1, 0), -Complex(1, 0)) diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb index 47c535fca0..0360f5ee42 100644 --- a/test/ruby/test_complexrational.rb +++ b/test/ruby/test_complexrational.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: false require 'test/unit' class ComplexRational_Test < Test::Unit::TestCase def test_rat_srat - return unless defined?(Rational) + skip unless defined?(Rational) c = SimpleRat(1,3) cc = Rational(3,2) @@ -74,8 +75,8 @@ class ComplexRational_Test < Test::Unit::TestCase assert_equal(0, Rational(2,3) <=> SimpleRat(2,3)) assert_equal(0, SimpleRat(2,3) <=> Rational(2,3)) - assert(Rational(2,3) == SimpleRat(2,3)) - assert(SimpleRat(2,3) == Rational(2,3)) + assert_equal(Rational(2,3), SimpleRat(2,3)) + assert_equal(SimpleRat(2,3), Rational(2,3)) assert_equal(SimpleRat, (c + 0).class) assert_equal(SimpleRat, (c - 0).class) @@ -88,7 +89,7 @@ class ComplexRational_Test < Test::Unit::TestCase end def test_comp_srat - return unless defined?(Rational) + skip unless defined?(Rational) c = Complex(SimpleRat(2,3),SimpleRat(1,2)) cc = Complex(Rational(3,2),Rational(2,1)) @@ -168,10 +169,10 @@ class ComplexRational_Test < Test::Unit::TestCase assert_equal([Float,Float], (cc ** c).instance_eval{[real.class, imag.class]}) - assert(Complex(SimpleRat(2,3),SimpleRat(3,2)) == - Complex(Rational(2,3),Rational(3,2))) - assert(Complex(Rational(2,3),Rational(3,2)) == - Complex(SimpleRat(2,3),SimpleRat(3,2))) + assert_equal(Complex(SimpleRat(2,3),SimpleRat(3,2)), + Complex(Rational(2,3),Rational(3,2))) + assert_equal(Complex(Rational(2,3),Rational(3,2)), + Complex(SimpleRat(2,3),SimpleRat(3,2))) assert_equal([SimpleRat,SimpleRat], (c + 0).instance_eval{[real.class, imag.class]}) @@ -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 3708a5a0ca..8784e0e988 100644 --- a/test/ruby/test_const.rb +++ b/test/ruby/test_const.rb @@ -1,3 +1,5 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' class TestConst < Test::Unit::TestCase @@ -35,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) @@ -45,4 +47,26 @@ class TestConst < Test::Unit::TestCase assert defined?(TEST4) assert_equal 8, TEST4 end + + def test_redefinition + c = Class.new + 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 +350000.times { FOO = :BAR } +PRE + 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 c719db8c35..8c62d20840 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 } @@ -77,5 +79,67 @@ class TestContinuation < Test::Unit::TestCase }, '[ruby-dev:34802]' end -end + def tracing_with_set_trace_func + orig_thread = Thread.current + cont = nil + func = lambda do |*args| + if orig_thread == Thread.current + if cont + @memo += 1 + c = cont + cont = nil + begin + c.call(nil) + rescue RuntimeError + set_trace_func(nil) + end + end + end + end + cont = callcc { |cc| cc } + if cont + set_trace_func(func) + else + set_trace_func(nil) + end + end + + def _test_tracing_with_set_trace_func + @memo = 0 + tracing_with_set_trace_func + tracing_with_set_trace_func + tracing_with_set_trace_func + assert_equal 0, @memo + end + + def tracing_with_thread_set_trace_func + cont = nil + func = lambda do |*args| + if cont + @memo += 1 + c = cont + cont = nil + begin + c.call(nil) + rescue RuntimeError + Thread.current.set_trace_func(nil) + end + end + end + cont = callcc { |cc| cc } + if cont + Thread.current.set_trace_func(func) + else + Thread.current.set_trace_func(nil) + end + end + + def test_tracing_with_thread_set_trace_func + @memo = 0 + tracing_with_thread_set_trace_func + tracing_with_thread_set_trace_func + tracing_with_thread_set_trace_func + assert_equal 3, @memo + end +end diff --git a/test/ruby/test_default_gems.rb b/test/ruby/test_default_gems.rb new file mode 100644 index 0000000000..837f7571ea --- /dev/null +++ b/test/ruby/test_default_gems.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: false +require 'rubygems' + +class TestDefaultGems < Test::Unit::TestCase + + def test_validate_gemspec + srcdir = File.expand_path('../../..', __FILE__) + Dir.glob("#{srcdir}/{lib,ext}/**/*.gemspec").map do |src| + assert_nothing_raised do + raise("invalid spec in #{src}") unless Gem::Specification.load(src) + end + end + end + +end diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index 07485bd2cc..e1571d5714 100644 --- a/test/ruby/test_defined.rb +++ b/test/ruby/test_defined.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestDefined < Test::Unit::TestCase @@ -22,40 +23,80 @@ class TestDefined < Test::Unit::TestCase return !defined?(yield) end - def test_defined + def test_defined_global_variable $x = nil assert(defined?($x)) # global variable assert_equal('global-variable', defined?($x))# returns description + end + def test_defined_local_variable assert_nil(defined?(foo)) # undefined foo=5 assert(defined?(foo)) # local variable + end + def test_defined_constant assert(defined?(Array)) # constant assert(defined?(::Array)) # toplevel constant assert(defined?(File::Constants)) # nested constant + end + + def test_defined_public_method assert(defined?(Object.new)) # method assert(defined?(Object::new)) # method + end + + def test_defined_private_method assert(!defined?(Object.print)) # private method + end + + def test_defined_operator assert(defined?(1 == 2)) # operator expression + end + def test_defined_protected_method f = Foo.new assert_nil(defined?(f.foo)) # protected method f.bar(f) { |v| assert(v) } + f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") } + end + + def test_defined_undefined_method + f = Foo.new assert_nil(defined?(f.quux)) # undefined method + end + + def test_defined_undefined_argument + f = Foo.new assert_nil(defined?(f.baz(x))) # undefined argument x = 0 assert(defined?(f.baz(x))) assert_nil(defined?(f.quux(x))) assert(defined?(print(x))) assert_nil(defined?(quux(x))) + end + + def test_defined_attrasgn + f = Foo.new assert(defined?(f.attr = 1)) f.attrasgn_test { |v| assert(v) } + end + + def test_defined_undef + x = Object.new + def x.foo; end + assert(defined?(x.foo)) + x.instance_eval {undef :foo} + assert(!defined?(x.foo), "undefed method should not be defined?") + end + def test_defined_yield assert(defined_test) # not iterator assert(!defined_test{}) # called as iterator + end + def test_defined_matchdata /a/ =~ '' assert_equal nil, defined?($&) assert_equal nil, defined?($`) @@ -66,23 +107,198 @@ class TestDefined < Test::Unit::TestCase /a/ =~ 'a' assert_equal 'global-variable', defined?($&) assert_equal 'global-variable', defined?($`) - assert_equal 'global-variable', defined?($') + assert_equal 'global-variable', defined?($') # ' assert_equal nil, defined?($+) assert_equal nil, defined?($1) assert_equal nil, defined?($2) /(a)/ =~ 'a' assert_equal 'global-variable', defined?($&) assert_equal 'global-variable', defined?($`) - assert_equal 'global-variable', defined?($') + assert_equal 'global-variable', defined?($') # ' assert_equal 'global-variable', defined?($+) assert_equal 'global-variable', defined?($1) assert_equal nil, defined?($2) /(a)b/ =~ 'ab' assert_equal 'global-variable', defined?($&) assert_equal 'global-variable', defined?($`) - assert_equal 'global-variable', defined?($') + assert_equal 'global-variable', defined?($') # ' assert_equal 'global-variable', defined?($+) assert_equal 'global-variable', defined?($1) assert_equal nil, defined?($2) end + + def test_defined_literal + assert_equal("nil", defined?(nil)) + assert_equal("true", defined?(true)) + assert_equal("false", defined?(false)) + assert_equal("expression", defined?(1)) + end + + def test_defined_empty_paren_expr + bug8224 = '[ruby-core:54024] [Bug #8224]' + (1..3).each do |level| + expr = "("*level+")"*level + assert_equal("nil", eval("defined? #{expr}"), "#{bug8224} defined? #{expr}") + assert_equal("nil", eval("defined?(#{expr})"), "#{bug8224} defined?(#{expr})") + 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) + assert_same(defined?(Foo), defined?(Array), feature7035) + end + + class TestAutoloadedSuperclass + autoload :A, "a" + end + + class TestAutoloadedSubclass < TestAutoloadedSuperclass + def a? + defined?(A) + end + end + + def test_autoloaded_subclass + bug = "[ruby-core:35509]" + + x = TestAutoloadedSuperclass.new + class << x + def a?; defined?(A); end + end + assert_equal("constant", x.a?, bug) + + assert_equal("constant", TestAutoloadedSubclass.new.a?, bug) + end + + class TestAutoloadedNoload + autoload :A, "a" + def a? + defined?(A) + end + def b? + defined?(A::B) + end + end + + def test_autoloaded_noload + loaded = $".dup + $".clear + loadpath = $:.dup + $:.clear + x = TestAutoloadedNoload.new + assert_equal("constant", x.a?) + assert_nil(x.b?) + assert_equal([], $") + ensure + $".replace(loaded) + $:.replace(loadpath) + end + + def test_exception + bug5786 = '[ruby-dev:45021]' + assert_nil(defined?(raise("[Bug#5786]")::A), bug5786) + end + + def test_define_method + bug6644 = '[ruby-core:45831]' + a = Class.new do + def self.def_f!; + singleton_class.send(:define_method, :f) { defined? super } + end + end + aa = Class.new(a) + a.def_f! + assert_nil(a.f) + assert_nil(aa.f) + aa.def_f! + assert_equal("super", aa.f, bug6644) + assert_nil(a.f, bug6644) + end + + def test_super_in_included_method + c0 = Class.new do + def m + end + end + m1 = Module.new do + def m + defined?(super) + end + end + c = Class.new(c0) do include m1 + def m + super + end + end + assert_equal("super", c.new.m) + end + + def test_super_in_block + bug8367 = '[ruby-core:54769] [Bug #8367]' + c = Class.new do + def x; end + end + + m = Module.new do + def b; yield; end + def x; b {return defined?(super)}; end + end + + o = c.new + o.extend(m) + assert_equal("super", o.x, bug8367) + end + + def test_super_toplevel + assert_separately([], "assert_nil(defined?(super))") + end + + class ExampleRespondToMissing + attr_reader :called + + def initialize + @called = false + end + + def respond_to_missing? *args + @called = true + false + end + + 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 + bug_11211 = '[Bug #11211]' + obj = ExampleRespondToMissing.new + assert_equal("method", defined?(obj.existing_method), bug_11211) + 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 236fd991db..5196c08f81 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' require 'fileutils' -require 'pathname' class TestDir < Test::Unit::TestCase def setup @verbose = $VERBOSE $VERBOSE = nil - @root = Pathname.new(Dir.mktmpdir('__test_dir__')).realpath.to_s + @root = File.realpath(Dir.mktmpdir('__test_dir__')) @nodir = File.join(@root, "dummy") - for i in ?a..?z + @dirs = [] + for i in "a".."z" if i.ord % 2 == 0 FileUtils.touch(File.join(@root, i)) else FileUtils.mkdir(File.join(@root, i)) + @dirs << File.join(i, "") end end end @@ -43,15 +45,6 @@ class TestDir < Test::Unit::TestCase end end - def test_JVN_13947696 - b = lambda { - d = Dir.open('.') - $SAFE = 4 - d.close - } - assert_raise(SecurityError) { b.call } - end - def test_nodir assert_raise(Errno::ENOENT) { Dir.open(@nodir) } end @@ -90,12 +83,6 @@ class TestDir < Test::Unit::TestCase d.rewind b = (0..5).map { d.read } assert_equal(a, b) - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - d.rewind - end.join - end ensure d.close end @@ -142,19 +129,23 @@ 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, - Dir.glob(@root + "\0\0\0" + File.join(@root, "*")).sort) + assert_warning(/nul-separated patterns/) do + assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }.sort, + Dir.glob(@root + "\0\0\0" + File.join(@root, "*")).sort) + end - 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) }, @@ -163,16 +154,149 @@ 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) - d = "\u{3042}\u{3044}".encode("utf-16le") - assert_raise(Encoding::CompatibilityError) {Dir.glob(d)} - m = Class.new {define_method(:to_path) {d}} - assert_raise(Encoding::CompatibilityError) {Dir.glob(m.new)} + 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 + bug6977 = '[ruby-core:47418]' + bug8006 = '[ruby-core:53108] [Bug #8006]' + Dir.chdir(@root) do + assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006) + + FileUtils.mkdir_p("a/b/c/d/e/f") + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/d/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/b/c/d/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/?/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/**/d/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/**/d/e/f"), bug6977) + + bug8283 = '[ruby-core:54387] [Bug #8283]' + dirs = ["a/.x", "a/b/.y"] + FileUtils.mkdir_p(dirs) + dirs.map {|dir| open("#{dir}/z", "w") {}} + assert_equal([], Dir.glob("a/**/z").sort, bug8283) + assert_equal(["a/.x/z"], Dir.glob("a/**/.x/z"), bug8283) + assert_equal(["a/.x/z"], Dir.glob("a/.x/**/z"), bug8283) + assert_equal(["a/b/.y/z"], Dir.glob("a/**/.y/z"), bug8283) + end + end + + def test_glob_recursive_directory + Dir.chdir(@root) do + ['d', 'e'].each do |path| + FileUtils.mkdir_p("c/#{path}/a/b/c") + FileUtils.touch("c/#{path}/a/a.file") + FileUtils.touch("c/#{path}/a/b/b.file") + FileUtils.touch("c/#{path}/a/b/c/c.file") + end + bug15540 = '[ruby-core:91110] [Bug #15540]' + assert_equal(["c/d/a/", "c/d/a/b/", "c/d/a/b/c/", "c/e/a/", "c/e/a/b/", "c/e/a/b/c/"], + Dir.glob('c/{d,e}/a/**/'), bug15540) + end + end + + def test_glob_starts_with_brace + Dir.chdir(@root) do + bug15649 = '[ruby-core:91728] [Bug #15649]' + assert_equal(["#{@root}/a", "#{@root}/b"], + Dir.glob("{#{@root}/a,#{@root}/b}"), bug15649) + end + end + + 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), "")} + Dir.mkdir(File.join(@root, "a/dir")) + dirs = @dirs + %w[a/dir/] + dirs.sort! + assert_equal(files, Dir.glob("*/*.c", base: @root).sort) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".").sort}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a").sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "").sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil).sort}) + assert_equal(@dirs, Dir.glob("*/", base: @root).sort) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".").sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a").sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "").sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil).sort}) + assert_equal(dirs, Dir.glob("**/*/", base: @root).sort) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".").sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a").sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "").sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil).sort}) + end + + def test_glob_base_dir + files = %w[a/foo.c c/bar.c] + files.each {|n| File.write(File.join(@root, n), "")} + Dir.mkdir(File.join(@root, "a/dir")) + dirs = @dirs + %w[a/dir/] + dirs.sort! + assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}.sort) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d)}}) + assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d).sort}}) + assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d).sort}}) + 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.open(@root) {|dir| dir.children}, true) + assert_entries(Dir.children(@root), true) + assert_raise(ArgumentError) {Dir.children(@root+"\0")} + end + + def test_each_child + assert_entries(Dir.open(@root) {|dir| dir.each_child.to_a}, true) + assert_entries(Dir.each_child(@root).to_a, true) + assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a} end def test_dir_enc @@ -195,20 +319,158 @@ class TestDir < Test::Unit::TestCase end end + def test_unknown_keywords + bug8060 = '[ruby-dev:47152] [Bug #8060]' + assert_raise_with_message(ArgumentError, /unknown keyword/, bug8060) do + Dir.open(@root, xawqij: "a") {} + end + end + 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 + bug8597 = '[ruby-core:55764] [Bug #8597]' + 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") + + ENV["HOME"] = @nodir + assert_nothing_raised(ArgumentError) do + assert_equal(@nodir, Dir.home) + end + assert_nothing_raised(ArgumentError) do + assert_equal(@nodir, Dir.home("")) + end + if user = ENV["USER"] + tilde = windows? ? "~" : "~#{user}" + assert_nothing_raised(ArgumentError) do + assert_equal(File.expand_path(tilde), Dir.home(user)) + end + end + %W[no:such:user \u{7559 5b88}:\u{756a}].each do |user| + assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)} + end + ensure + ENV["HOME"] = env_home + ENV["LOGDIR"] = env_logdir + end + + def test_symlinks_not_resolved + Dir.mktmpdir do |dirname| + Dir.chdir(dirname) do + begin + File.symlink('some-dir', 'dir-symlink') + rescue NotImplementedError, Errno::EACCES + return + end + + Dir.mkdir('some-dir') + File.write('some-dir/foo', 'some content') + + assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*'].sort + assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*'].sort + 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 81accb7f93..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,74 +12,108 @@ class TestDir_M17N < Test::Unit::TestCase } end + 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}") + File.open(filename, "w") {} + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", opts) + assert_include(ents, filename) + EOS + + return if /cygwin/ =~ RUBY_PLATFORM + assert_separately(%w[-EASCII-8BIT], <<-EOS, :chdir=>dir) + filename = #{code}.chr('UTF-8').force_encoding("ASCII-8BIT") + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", opts) + expected_filename = #{code}.chr('UTF-8').encode(Encoding.find("filesystem")) rescue expected_filename = "?" + expected_filename = expected_filename.force_encoding("ASCII-8BIT") + if /mswin|mingw/ =~ RUBY_PLATFORM + case + when ents.include?(filename) + when ents.include?(expected_filename) + filename = expected_filename + else + ents = Dir.entries(".", {:encoding => Encoding.find("filesystem")}) + filename = expected_filename + end + end + assert_include(ents, filename) + EOS + } + end + ## UTF-8 default_external, no default_internal def test_filename_extutf8 with_tmpdir {|d| - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) + assert_include(ents, filename) EOS } end def test_filename_extutf8_invalid - skip "ruby on windows doesn't support invalid utf-8 path" if /mswin|mingw/ =~ RUBY_PLATFORM + 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_ruby_status(%w[-EASCII-8BIT], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) filename = "\xff".force_encoding("ASCII-8BIT") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%FF")) + filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) filename = "\xff".force_encoding("UTF-8") # invalid byte sequence as UTF-8 File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%FF")) + filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") + assert_include(ents, filename) EOS } - end + end unless /mswin|mingw/ =~ RUBY_PLATFORM def test_filename_as_bytes_extutf8 with_tmpdir {|d| - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) filename = "\xc2\xa1".force_encoding("utf-8") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) - if /mswin|mingw/ =~ RUBY_PLATFORM + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) + if /mswin|mingw|darwin/ =~ RUBY_PLATFORM filename = "\x8f\xa2\xc2".force_encoding("euc-jp") else filename = "\xc2\xa1".force_encoding("euc-jp") end - begin + assert_nothing_raised(Errno::ENOENT) do open(filename) {} - exit true - rescue Errno::ENOENT - exit false end EOS - skip "no meaning test on windows" if /mswin|mingw/ =~ RUBY_PLATFORM - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) - filename1 = "\xc2\xa1".force_encoding("utf-8") - filename2 = "\xc2\xa1".force_encoding("euc-jp") - filename3 = filename1.encode("euc-jp") - filename4 = filename2.encode("utf-8") - s1 = File.stat(filename1) rescue nil - s2 = File.stat(filename2) rescue nil - s3 = File.stat(filename3) rescue nil - s4 = File.stat(filename4) rescue nil - exit((s1 && s2 && !s3 && !s4) ? true : false) - EOS + # no meaning test on windows + unless /mswin|mingw|darwin/ =~ RUBY_PLATFORM + assert_separately(%W[-EUTF-8], <<-'EOS', :chdir=>d) + filename1 = "\xc2\xa1".force_encoding("utf-8") + filename2 = "\xc2\xa1".force_encoding("euc-jp") + filename3 = filename1.encode("euc-jp") + filename4 = filename2.encode("utf-8") + assert_file.stat(filename1) + assert_file.stat(filename2) + assert_file.not_exist?(filename3) + assert_file.not_exist?(filename4) + EOS + end } end @@ -86,26 +121,23 @@ class TestDir_M17N < Test::Unit::TestCase def test_filename_extutf8_inteucjp_representable with_tmpdir {|d| - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) filename = "\u3042" File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EUTF-8:EUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EUTF-8:EUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") - begin + assert_nothing_raised(Errno::ENOENT) do open(filename) {} - exit true - rescue Errno::ENOENT - exit false end EOS } @@ -113,30 +145,31 @@ class TestDir_M17N < Test::Unit::TestCase def test_filename_extutf8_inteucjp_unrepresentable with_tmpdir {|d| - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP filename2 = "\u3042" # HIRAGANA LETTER A which is representable in EUC-JP File.open(filename1, "w") {} File.open(filename2, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename1) && ents.include?(filename2) + assert_include(ents, filename1) + assert_include(ents, filename2) EOS - assert_ruby_status(%w[-EUTF-8:EUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP filename2 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename1) && ents.include?(filename2) + assert_include(ents, filename1) + assert_include(ents, filename2) EOS - assert_ruby_status(%w[-EUTF-8:EUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) filename1 = "\u2661" # WHITE HEART SUIT which is not representable in EUC-JP filename2 = "\u3042" # HIRAGANA LETTER A which is representable in EUC-JP filename3 = "\xA4\xA2".force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP - s1 = File.stat(filename1) rescue nil - s2 = File.stat(filename2) rescue nil - s3 = File.stat(filename3) rescue nil - exit((s1 && s2 && s3) ? true : false) + assert_file.stat(filename1) + assert_file.stat(filename2) + assert_file.stat(filename3) EOS } end @@ -144,73 +177,273 @@ class TestDir_M17N < Test::Unit::TestCase ## others def test_filename_bytes_euc_jp + return if /cygwin/ =~ RUBY_PLATFORM with_tmpdir {|d| - assert_ruby_status(%w[-EEUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) ents.each {|e| e.force_encoding("ASCII-8BIT") } - exit ents.include?(filename.force_encoding("ASCII-8BIT")) || - ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%A4%A2".force_encoding("ASCII-8BIT"))) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8") + end + assert_include(ents, filename.force_encoding("ASCII-8BIT")) EOS } end def test_filename_euc_jp + return if /cygwin/ =~ RUBY_PLATFORM with_tmpdir {|d| - assert_ruby_status(%w[-EEUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%A4%A2".force_encoding("euc-jp"))) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8").force_encoding("euc-jp") + end + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EASCII-8BIT], <<-'EOS', nil, :chdir=>d) - filename = "\xA4\xA2" + assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".force_encoding('ASCII-8BIT') + win_expected_filename = filename.encode(Encoding.find("filesystem"), "euc-jp") rescue "?" opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || - ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%A4%A2".force_encoding("ASCII-8BIT"))) || - ((RUBY_PLATFORM =~ /mswin|mingw/) != nil && ents.include?("\x82\xA0".force_encoding("ASCII-8BIT"))) + unless ents.include?(filename) + case RUBY_PLATFORM + when /darwin/ + filename = filename.encode("utf-8", "euc-jp").b + when /mswin|mingw/ + if ents.include?(win_expected_filename.b) + ents = Dir.entries(".", {:encoding => Encoding.find("filesystem")}) + filename = win_expected_filename + end + end + end + assert_include(ents, filename) EOS } end - def test_filename_utf8_raw_name - with_tmpdir {|d| - assert_ruby_status(%w[-EUTF-8], <<-'EOS', nil, :chdir=>d) - filename = "\u3042".force_encoding("utf-8") - File.open(filename, "w") {} - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) - exit ents.include?(filename) - EOS - assert_ruby_status(%w[-EASCII-8BIT], <<-'EOS', nil, :chdir=>d) - filename = "\u3042".force_encoding("ASCII-8BIT") - opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM - ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /mswin|mingw/) != nil && ents.include?("\x82\xA0".force_encoding("ASCII-8BIT"))) - EOS - } + def test_filename_utf8_raw_jp_name + assert_raw_file_name(0x3042, "UTF-8") + end + + def test_filename_utf8_raw_windows_1251_name + assert_raw_file_name(0x0424, "UTF-8") + end + + def test_filename_utf8_raw_windows_1252_name + 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_ruby_status(%w[-EEUC-JP], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) filename = "\xA4\xA2".force_encoding("euc-jp") File.open(filename, "w") {} opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%A4%A2".force_encoding("euc-jp"))) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8", "euc-jp").force_encoding("euc-jp") + end + assert_include(ents, filename) EOS - assert_ruby_status(%w[-EEUC-JP:UTF-8], <<-'EOS', nil, :chdir=>d) + assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d) filename = "\u3042" opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM ents = Dir.entries(".", opts) - exit ents.include?(filename) || ((RUBY_PLATFORM =~ /darwin/) != nil && ents.include?("%A4%A2")) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.force_encoding("euc-jp") + end + assert_include(ents, filename) EOS } end -end + def test_error_nonascii + bug6071 = '[ruby-dev:45279]' + paths = ["\u{3042}".encode("sjis"), "\u{ff}".encode("iso-8859-1")] + encs = with_tmpdir { + paths.map {|path| + Dir.open(path) rescue $!.message.encoding + } + } + assert_equal(paths.map(&:encoding), encs, bug6071) + end + + def test_inspect_nonascii + bug6072 = '[ruby-dev:45280]' + paths = ["\u{3042}".encode("sjis"), "\u{ff}".encode("iso-8859-1")] + encs = with_tmpdir { + paths.map {|path| + Dir.mkdir(path) + Dir.open(path) {|d| d.inspect.encoding} + } + } + assert_equal(paths.map(&:encoding), encs, bug6072) + end + + def test_glob_incompatible + d = "\u{3042}\u{3044}".encode("utf-16le") + assert_raise(Encoding::CompatibilityError) {Dir.glob(d)} + m = Class.new {define_method(:to_path) {d}} + assert_raise(Encoding::CompatibilityError) {Dir.glob(m.new)} + end + + def test_glob_compose + bug7267 = '[ruby-core:48745] [Bug #7267]' + + pp = Object.new.extend(Test::Unit::Assertions) + def pp.mu_pp(str) #:nodoc: + str.dump + end + + with_tmpdir {|d| + orig = %W"d\u{e9}tente x\u{304c 304e 3050 3052 3054}" + orig.each {|n| open(n, "w") {}} + orig.each do |o| + n = Dir.glob("#{o[0..0]}*")[0] + pp.assert_equal(o, n, bug7267) + end + } + end + + 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 + + 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 => enc} + orig.map! {|o| o.encode("filesystem") rescue o.tr("^a-z", "?")} + else + orig.each {|o| o.force_encoding(enc) } + end + ents = Dir.entries(".", opts).reject {|n| /\A\./ =~ n} + ents.sort! + 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 00682f69cd..6f098db454 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -1,14 +1,11 @@ +# frozen_string_literal: false require 'test/unit' class TestEncodingConverter < Test::Unit::TestCase def check_ec(edst, esrc, eres, dst, src, ec, off, len, opts=nil) res = ec.primitive_convert(src, dst, off, len, opts) - 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) @@ -22,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 @@ -43,9 +40,9 @@ class TestEncodingConverter < Test::Unit::TestCase def test_asciicompat_encoding_iso2022jp acenc = Encoding::Converter.asciicompat_encoding("ISO-2022-JP") - str = "\e$B~~\(B".force_encoding("iso-2022-jp") + str = "\e$B~~\e(B".force_encoding("iso-2022-jp") str2 = str.encode(acenc) - str3 = str.encode("ISO-2022-JP") + str3 = str2.encode("ISO-2022-JP") assert_equal(str, str3) end @@ -85,8 +82,8 @@ class TestEncodingConverter < Test::Unit::TestCase } encoding_list = Encoding.list.map {|e| e.name } - assert(!encoding_list.include?(name1)) - assert(!encoding_list.include?(name2)) + assert_not_include(encoding_list, name1) + assert_not_include(encoding_list, name2) end def test_newline_converter_with_ascii_incompatible @@ -146,7 +143,7 @@ class TestEncodingConverter < Test::Unit::TestCase def test_nil_source_buffer ec = Encoding::Converter.new("UTF-8", "EUC-JP") - ret = ec.primitive_convert(nil, dst="", nil, 10) + ret = ec.primitive_convert(nil, "", nil, 10) assert_equal(:finished, ret) end @@ -449,6 +446,16 @@ class TestEncodingConverter < Test::Unit::TestCase assert_econv("abc\rdef", :finished, 50, ec, "abc\ndef", "") end + def test_no_universal_newline1 + ec = Encoding::Converter.new("UTF-8", "EUC-JP", universal_newline: false) + assert_econv("abc\r\ndef", :finished, 50, ec, "abc\r\ndef", "") + end + + def test_no_universal_newline2 + ec = Encoding::Converter.new("", "", universal_newline: false) + assert_econv("abc\r\ndef", :finished, 50, ec, "abc\r\ndef", "") + end + def test_after_output ec = Encoding::Converter.new("UTF-8", "EUC-JP") a = ["", "abc\u{3042}def", ec, nil, 100, :after_output=>true] @@ -464,44 +471,44 @@ class TestEncodingConverter < Test::Unit::TestCase def test_errinfo_invalid_euc_jp ec = Encoding::Converter.new("EUC-JP", "Shift_JIS") - ec.primitive_convert(src="\xff", dst="", nil, 10) + ec.primitive_convert("\xff", "", nil, 10) assert_errinfo(:invalid_byte_sequence, "EUC-JP", "Shift_JIS", "\xFF", "", ec) end def test_errinfo_invalid_euc_jp2 ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert(src="\xff", dst="", nil, 10) + ec.primitive_convert("\xff", "", nil, 10) assert_errinfo(:invalid_byte_sequence, "EUC-JP", "UTF-8", "\xFF", "", ec) end def test_errinfo_undefined_hiragana ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert(src="\xa4\xa2", dst="", nil, 10) + ec.primitive_convert("\xa4\xa2", "", nil, 10) assert_errinfo(:undefined_conversion, "UTF-8", "ISO-8859-1", "\xE3\x81\x82", "", ec) end def test_errinfo_invalid_partial_character ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert(src="\xa4", dst="", nil, 10) + ec.primitive_convert("\xa4", "", nil, 10) assert_errinfo(:incomplete_input, "EUC-JP", "UTF-8", "\xA4", "", ec) end def test_errinfo_valid_partial_character ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") - ec.primitive_convert(src="\xa4", dst="", nil, 10, :partial_input=>true) + ec.primitive_convert("\xa4", "", nil, 10, :partial_input=>true) assert_errinfo(:source_buffer_empty, nil, nil, nil, nil, ec) end def test_errinfo_invalid_utf16be ec = Encoding::Converter.new("UTF-16BE", "UTF-8") - ec.primitive_convert(src="\xd8\x00\x00@", dst="", nil, 10) + ec.primitive_convert(src="\xd8\x00\x00@", "", nil, 10) assert_errinfo(:invalid_byte_sequence, "UTF-16BE", "UTF-8", "\xD8\x00", "\x00", ec) assert_equal("@", src) end def test_errinfo_invalid_utf16le ec = Encoding::Converter.new("UTF-16LE", "UTF-8") - ec.primitive_convert(src="\x00\xd8@\x00", dst="", nil, 10) + ec.primitive_convert(src="\x00\xd8@\x00", "", nil, 10) assert_errinfo(:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "@\x00", ec) assert_equal("", src) end @@ -588,7 +595,7 @@ class TestEncodingConverter < Test::Unit::TestCase def test_putback2 ec = Encoding::Converter.new("utf-16le", "euc-jp") - ret = ec.primitive_convert(src="\x00\xd8\x21\x00", dst="", nil, nil) + ret = ec.primitive_convert("\x00\xd8\x21\x00", "", nil, nil) assert_equal(:invalid_byte_sequence, ret) assert_equal("\x00".force_encoding("utf-16le"), ec.putback(1)) assert_equal("\x21".force_encoding("utf-16le"), ec.putback(1)) @@ -673,6 +680,7 @@ class TestEncodingConverter < Test::Unit::TestCase ec = Encoding::Converter.new("utf-8", "euc-jp") assert_raise(Encoding::InvalidByteSequenceError) { ec.convert("a\x80") } assert_raise(Encoding::UndefinedConversionError) { ec.convert("\ufffd") } + assert_predicate(ec.convert("abc".taint), :tainted?) ret = ec.primitive_convert(nil, "", nil, nil) assert_equal(:finished, ret) assert_raise(ArgumentError) { ec.convert("a") } @@ -694,20 +702,20 @@ class TestEncodingConverter < Test::Unit::TestCase def test_last_error1 ec = Encoding::Converter.new("sjis", "euc-jp") assert_equal(nil, ec.last_error) - assert_equal(:incomplete_input, ec.primitive_convert(src="fo\x81", dst="", nil, nil)) + assert_equal(:incomplete_input, ec.primitive_convert("fo\x81", "", nil, nil)) assert_kind_of(Encoding::InvalidByteSequenceError, ec.last_error) end def test_last_error2 ec = Encoding::Converter.new("sjis", "euc-jp") - assert_equal("fo", ec.convert(src="fo\x81")) + assert_equal("fo", ec.convert("fo\x81")) assert_raise(Encoding::InvalidByteSequenceError) { ec.finish } assert_kind_of(Encoding::InvalidByteSequenceError, ec.last_error) end def test_us_ascii ec = Encoding::Converter.new("UTF-8", "US-ASCII") - ec.primitive_convert(src="\u{3042}", dst="") + ec.primitive_convert("\u{3042}", "") err = ec.last_error assert_kind_of(Encoding::UndefinedConversionError, err) assert_equal("\u{3042}", err.error_char) @@ -715,7 +723,7 @@ class TestEncodingConverter < Test::Unit::TestCase def test_88591 ec = Encoding::Converter.new("UTF-8", "ISO-8859-1") - ec.primitive_convert(src="\u{3042}", dst="") + ec.primitive_convert("\u{3042}", "") err = ec.last_error assert_kind_of(Encoding::UndefinedConversionError, err) assert_equal("\u{3042}", err.error_char) @@ -892,4 +900,25 @@ class TestEncodingConverter < Test::Unit::TestCase "".encode("euc-jp", :undef => :replace, :replace => broken) } end + + def test_newline_option + 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 + Encoding.list.grep(->(enc) {/\AISO-8859-\d+\z/i =~ enc.name}) do |enc| + assert_separately(%W[--disable=gems -d - #{enc.name}], <<-EOS, ignore_stderr: true) + Encoding.default_external = ext = ARGV[0] + Encoding.default_internal = int ='utf-8' + assert_nothing_raised do + Encoding::Converter.new(ext, int) + end + EOS + end + end end diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 46b86ccabf..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 @@ -13,6 +13,7 @@ class TestEncoding < Test::Unit::TestCase assert_equal(e, Encoding.find(e.name.upcase)) assert_equal(e, Encoding.find(e.name.capitalize)) assert_equal(e, Encoding.find(e.name.downcase)) + assert_equal(e, Encoding.find(e)) end end @@ -21,7 +22,7 @@ class TestEncoding < Test::Unit::TestCase aliases.each do |a, en| e = Encoding.find(a) assert_equal(e.name, en) - assert(e.names.include?(a)) + assert_include(e.names, a) end end @@ -33,6 +34,9 @@ class TestEncoding < Test::Unit::TestCase assert_raise(TypeError) { e.dup } assert_raise(TypeError) { e.clone } assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id) + assert_not_predicate(e, :tainted?) + Marshal.load(Marshal.dump(e).taint) + assert_not_predicate(e, :tainted?, '[ruby-core:71793] [Bug #11760]') end end @@ -41,7 +45,7 @@ class TestEncoding < Test::Unit::TestCase assert_nothing_raised{Encoding.find("locale")} assert_nothing_raised{Encoding.find("filesystem")} - if /(?:ms|dar)win/ !~ RUBY_PLATFORM + if /(?:ms|dar)win|mingw/ !~ RUBY_PLATFORM # Unix's filesystem encoding is default_external assert_ruby_status(%w[-EUTF-8:EUC-JP], <<-'EOS') exit Encoding.find("filesystem") == Encoding::UTF_8 @@ -49,6 +53,9 @@ class TestEncoding < Test::Unit::TestCase exit Encoding.find("filesystem") == Encoding::EUC_JP EOS end + + bug5150 = '[ruby-dev:44327]' + assert_raise(TypeError, bug5150) {Encoding.find(1)} end def test_replicate @@ -81,8 +88,8 @@ class TestEncoding < Test::Unit::TestCase def test_aliases assert_instance_of(Hash, Encoding.aliases) Encoding.aliases.each do |k, v| - assert(Encoding.name_list.include?(k)) - assert(Encoding.name_list.include?(v)) + assert_include(Encoding.name_list, k) + assert_include(Encoding.name_list, v) assert_instance_of(String, k) assert_instance_of(String, v) end @@ -95,4 +102,27 @@ class TestEncoding < Test::Unit::TestCase str2 = Marshal.load(Marshal.dump(str2)) assert_equal(str, str2, '[ruby-dev:38596]') end + + def test_compatible_p + ua = "abc".force_encoding(Encoding::UTF_8) + assert_equal(Encoding::UTF_8, Encoding.compatible?(ua, :abc)) + assert_equal(nil, Encoding.compatible?(ua, 1)) + 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]' + begin; + assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) { + eval("/regexp/sQ") + } + end; + end end diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb index c500cee4c3..568fa0ea8d 100644 --- a/test/ruby/test_enum.rb +++ b/test/ruby/test_enum.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: false require 'test/unit' -require 'continuation' +EnvUtil.suppress_warning {require 'continuation'} +require 'stringio' class TestEnumerable < Test::Unit::TestCase def setup @@ -12,6 +14,16 @@ class TestEnumerable < Test::Unit::TestCase yield 3 yield 1 yield 2 + self + end + end + @empty = Object.new + class << @empty + attr_reader :block + include Enumerable + def each(&block) + @block = block + self end end @verbose = $VERBOSE @@ -22,11 +34,33 @@ class TestEnumerable < Test::Unit::TestCase $VERBOSE = @verbose end + 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 assert_equal([1, 2, 1, 2], @obj.grep(1..2)) a = [] @obj.grep(2) {|x| a << x } assert_equal([2, 2], a) + + bug5801 = '[ruby-dev:45041]' + @empty.grep(//) + 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 @@ -52,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 @@ -64,44 +100,233 @@ 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 + + StubToH = Object.new.tap do |obj| + def obj.each(*args) + yield(*args) + yield [:key, :value] + yield :other_key, :other_value + kvp = Object.new + def kvp.to_ary + [:obtained, :via_to_ary] + end + yield kvp + end + obj.extend Enumerable + obj.freeze + end + + def test_to_h + obj = StubToH + + assert_equal({ + :hello => :world, + :key => :value, + :other_key => :other_value, + :obtained => :via_to_ary, + }, obj.to_h(:hello, :world)) + + e = assert_raise(TypeError) { + obj.to_h(:not_an_array) + } + assert_equal "wrong element type Symbol (expected array)", e.message + + e = assert_raise(ArgumentError) { + obj.to_h([1]) + } + assert_equal "element has wrong array length (expected 2, was 1)", e.message + end + + def test_to_h_block + obj = StubToH + + assert_equal({ + "hello" => "world", + "key" => "value", + "other_key" => "other_value", + "obtained" => "via_to_ary", + }, obj.to_h(:hello, :world) {|k, v| [k.to_s, v.to_s]}) + + e = assert_raise(TypeError) { + obj.to_h {:not_an_array} + } + assert_equal "wrong element type Symbol (expected array)", e.message + + e = assert_raise(ArgumentError) { + obj.to_h {[1]} + } + assert_equal "element has wrong array length (expected 2, was 1)", e.message + end + def test_inject assert_equal(12, @obj.inject {|z, x| z * x }) assert_equal(48, @obj.inject {|z, x| z * 2 + x }) assert_equal(12, @obj.inject(:*)) assert_equal(24, @obj.inject(2) {|z, x| z * x }) assert_equal(24, @obj.inject(2, :*) {|z, x| z * x }) + assert_equal(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 @@ -109,6 +334,25 @@ 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_all_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.all?([:b, 2]) {|x| x == 4 } + EOS end def test_any @@ -116,46 +360,126 @@ 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_any_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 23].any?(1) {|x| x == 1 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).any?(34) {|x| x == 2 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.any?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.any?([:b, 2]) {|x| x == 4 } + EOS end def test_one assert(@obj.one? {|x| x == 3 }) assert(!(@obj.one? {|x| x == 1 })) assert(!(@obj.one? {|x| x == 4 })) + assert(@obj.one?(3..4)) + assert(!(@obj.one?(1..2))) + assert(!(@obj.one?(4..5))) assert(%w{ant bear cat}.one? {|word| word.length == 4}) assert(!(%w{ant bear cat}.one? {|word| word.length > 4})) assert(!(%w{ant bear cat}.one? {|word| word.length < 4})) + assert(%w{ant bear cat}.one?(/b/)) + assert(!(%w{ant bear cat}.one?(/t/))) assert(!([ nil, true, 99 ].one?)) assert([ nil, true, false ].one?) + assert(![].one?) + assert(!@empty.one?) + assert([ nil, true, 99 ].one?(Integer)) + end + + def test_one_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.one?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.one?([:b, 2]) {|x| x == 4 } + EOS end def test_none assert(@obj.none? {|x| x == 4 }) assert(!(@obj.none? {|x| x == 1 })) assert(!(@obj.none? {|x| x == 3 })) + assert(@obj.none?(4..5)) + assert(!(@obj.none?(1..3))) assert(%w{ant bear cat}.none? {|word| word.length == 5}) assert(!(%w{ant bear cat}.none? {|word| word.length >= 4})) + assert(%w{ant bear cat}.none?(/d/)) + assert(!(%w{ant bear cat}.none?(/b/))) assert([].none?) assert([nil].none?) assert([nil,false].none?) + assert(![nil,false,true].none?) + assert(@empty.none?) + end + + def test_none_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.none?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.none?([:b, 2]) {|x| x == 4 } + EOS end def test_min assert_equal(1, @obj.min) assert_equal(3, @obj.min {|a,b| b <=> a }) - 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 @@ -167,24 +491,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 }) @@ -232,25 +567,77 @@ 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) ary = Object.new def ary.to_a; [1, 2]; end - assert_raise(NoMethodError){ %w(a b).zip(ary) } + assert_raise(TypeError) {%w(a b).zip(ary)} def ary.each; [3, 4].each{|e|yield e}; end 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 @@ -259,6 +646,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} + block = @empty.block + assert_nothing_raised(bug5801) {100.times {block.call}} end def test_drop @@ -267,10 +661,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 @@ -305,12 +708,41 @@ 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 assert_equal([2,1,3,2,1], @obj.reverse_each.to_a) end + def test_reverse_each_memory_corruption + bug16354 = '[ruby-dev:50867]' + assert_normal_exit %q{ + size = 1000 + (0...size).reverse_each do |i| + i.inspect + ObjectSpace.each_object(Array) do |a| + a.clear if a.length == size + end + end + }, bug16354 + end + def test_chunk e = [].chunk {|elt| true } assert_equal([], e.to_a) @@ -318,22 +750,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]], @@ -351,6 +767,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 @@ -363,25 +785,362 @@ 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 + + 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 - hs = [{}] - e = [:foo].slice_before(hs[0]) {|elt, h| - hs << h + 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) + 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 + + def test_transient_heap_sort_by + klass = Class.new do + include Comparable + attr_reader :i + def initialize e + @i = e + end + def <=> other + GC.start + i <=> other.i + end + end + assert_equal [1, 2, 3, 4, 5], (1..5).sort_by{|e| klass.new e} + end end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 0ee33ca6f1..efcfef580f 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestEnumerator < Test::Unit::TestCase @@ -9,10 +10,13 @@ class TestEnumerator < Test::Unit::TestCase a.each {|x| yield x } end end + @sized = @obj.clone + def @sized.size + 42 + end end def enum_test obj - i = 0 obj.map{|e| e }.sort @@ -21,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 @@ -43,7 +47,15 @@ class TestEnumerator < Test::Unit::TestCase } end - def test_nested_itaration + 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 yield [:ok2, :x].each.next @@ -57,9 +69,22 @@ class TestEnumerator < Test::Unit::TestCase def test_initialize assert_equal([1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).to_a) - assert_equal([1, 2, 3], Enumerator.new(@obj, :foo, 1, 2, 3).to_a) + _, err = capture_io do + assert_equal([1, 2, 3], Enumerator.new(@obj, :foo, 1, 2, 3).to_a) + end + assert_match 'Enumerator.new without a block is deprecated', err assert_equal([1, 2, 3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.take(3)) assert_raise(ArgumentError) { Enumerator.new } + + enum = @obj.to_enum + assert_raise(NoMethodError) { enum.each {} } + enum.freeze + assert_raise(FrozenError) { + capture_io do + # warning: Enumerator.new without a block is deprecated; use Object#to_enum + enum.__send__(:initialize, @obj, :foo) + end + } end def test_initialize_copy @@ -78,6 +103,7 @@ class TestEnumerator < Test::Unit::TestCase 1.times do foo = [1,2,3].to_enum GC.start + foo end GC.start end @@ -97,6 +123,36 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([[1,5],[2,6],[3,7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a) end + def test_with_index_large_offset + bug8010 = '[ruby-dev:47131] [Bug #8010]' + s = 1 << (8*1.size-2) + assert_equal([[1,s],[2,s+1],[3,s+2]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_a, bug8010) + s <<= 1 + assert_equal([[1,s],[2,s+1],[3,s+2]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_a, bug8010) + end + + def test_with_index_nonnum_offset + bug8010 = '[ruby-dev:47131] [Bug #8010]' + s = Object.new + def s.to_int; 1 end + assert_equal([[1,1],[2,2],[3,3]], @obj.to_enum(:foo, 1, 2, 3).with_index(s).to_a, bug8010) + end + + def test_with_index_string_offset + bug8010 = '[ruby-dev:47131] [Bug #8010]' + assert_raise(TypeError, bug8010){ @obj.to_enum(:foo, 1, 2, 3).with_index('1').to_a } + end + + def test_with_index_dangling_memo + bug9178 = '[ruby-core:58692] [Bug #9178]' + assert_separately([], <<-"end;") + bug = "#{bug9178}" + e = [1].to_enum(:chunk).with_index {|c,i| i == 5} + assert_kind_of(Enumerator, e) + assert_equal([false, [1]], e.to_a[0], bug) + end; + end + def test_with_object obj = [0, 1] ret = (1..10).each.with_object(obj) {|i, memo| @@ -238,6 +294,21 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([1,2], e.next_values) end + def test_each_arg + o = Object.new + def o.each(ary) + ary << 1 + yield + end + ary = [] + e = o.to_enum { 1 } + assert_equal(1, e.size) + e_arg = e.each(ary) + assert_equal(nil, e_arg.size) + e_arg.next + assert_equal([1], ary) + end + def test_feed o = Object.new def o.each(ary) @@ -336,7 +407,13 @@ class TestEnumerator < Test::Unit::TestCase e = (0..10).each_cons(2) assert_equal("#<Enumerator: 0..10:each_cons(2)>", e.inspect) - e = Enumerator.new {|y| x = y.yield; 10 } + e = (0..10).each_with_object({}) + assert_equal("#<Enumerator: 0..10:each_with_object({})>", e.inspect) + + e = (0..10).each_with_object(a: 1) + assert_equal("#<Enumerator: 0..10:each_with_object(a: 1)>", e.inspect) + + e = Enumerator.new {|y| y.yield; 10 } assert_match(/\A#<Enumerator: .*:each>/, e.inspect) a = [] @@ -346,6 +423,20 @@ class TestEnumerator < Test::Unit::TestCase e.inspect) end + def test_inspect_verbose + bug6214 = '[ruby-dev:45449]' + assert_warning("", bug6214) { "".bytes.inspect } + assert_warning("", bug6214) { [].lazy.inspect } + end + + def test_inspect_encoding + c = Class.new{define_method("\u{3042}"){}} + e = c.new.enum_for("\u{3042}") + s = assert_nothing_raised(Encoding::CompatibilityError) {break e.inspect} + assert_equal(Encoding::UTF_8, s.encoding) + assert_match(/\A#<Enumerator: .*:\u{3042}>\z/, s) + end + def test_generator # note: Enumerator::Generator is a class just for internal g = Enumerator::Generator.new {|y| y << 1 << 2 << 3; :foo } @@ -356,6 +447,30 @@ class TestEnumerator < Test::Unit::TestCase a = [] assert_equal(:foo, g2.each {|x| a << x }) assert_equal([1, 2, 3], a) + + g.freeze + 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 + g = Enumerator::Generator.new {|y, x| y << 1 << 2 << 3; x } + a = [] + assert_equal(:bar, g.each(:bar) {|x| a << x }) + assert_equal([1, 2, 3], a) end def test_yielder @@ -370,8 +485,310 @@ class TestEnumerator < Test::Unit::TestCase assert_equal([1], y.yield(1)) assert_equal([1, 2], y.yield(2)) assert_equal([1, 2, 3], y.yield(3)) + assert_equal([1, 2, 3, 4], y.yield(4, 5)) + + a = [] + y = Enumerator::Yielder.new {|*x| a.concat(x) } + assert_equal([1], y.yield(1)) + assert_equal([1, 2, 3], y.yield(2, 3)) assert_raise(LocalJumpError) { Enumerator::Yielder.new } end -end + def test_size + assert_equal nil, Enumerator.new{}.size + assert_equal 42, Enumerator.new(->{42}){}.size + obj = Object.new + def obj.call; 42; end + assert_equal 42, Enumerator.new(obj){}.size + assert_equal 42, Enumerator.new(42){}.size + assert_equal 1 << 70, Enumerator.new(1 << 70){}.size + assert_equal Float::INFINITY, Enumerator.new(Float::INFINITY){}.size + assert_equal nil, Enumerator.new(nil){}.size + assert_raise(TypeError) { Enumerator.new("42"){} } + + assert_equal nil, @obj.to_enum(:foo, 0, 1).size + assert_equal 2, @obj.to_enum(:foo, 0, 1){ 2 }.size + end + + def test_size_for_enum_created_by_enumerators + enum = to_enum{ 42 } + assert_equal 42, enum.with_index.size + assert_equal 42, enum.with_object(:foo).size + end + + def test_size_for_enum_created_from_array + arr = %w[hello world] + %i[each each_with_index reverse_each sort_by! sort_by map map! + keep_if reject! reject select! select filter! filter delete_if].each do |method| + assert_equal arr.size, arr.send(method).size + end + end + + def test_size_for_enum_created_from_enumerable + %i[find_all reject map flat_map partition group_by sort_by min_by max_by + minmax_by each_with_index reverse_each each_entry].each do |method| + assert_equal nil, @obj.send(method).size + assert_equal 42, @sized.send(method).size + end + assert_equal nil, @obj.each_with_object(nil).size + assert_equal 42, @sized.each_with_object(nil).size + end + + def test_size_for_enum_created_from_hash + h = {a: 1, b: 2, c: 3} + methods = %i[delete_if reject reject! select select! filter filter! keep_if each each_key each_pair] + enums = methods.map {|method| h.send(method)} + s = enums.group_by(&:size) + assert_equal([3], s.keys, ->{s.reject!{|k| k==3}.inspect}) + h[:d] = 4 + s = enums.group_by(&:size) + assert_equal([4], s.keys, ->{s.reject!{|k| k==4}.inspect}) + end + + def test_size_for_enum_created_from_env + %i[each_pair reject! delete_if select select! filter filter! keep_if].each do |method| + assert_equal ENV.size, ENV.send(method).size + end + end + + def test_size_for_enum_created_from_struct + s = Struct.new(:foo, :bar, :baz).new(1, 2) + %i[each each_pair select].each do |method| + assert_equal 3, s.send(method).size + end + end + + def check_consistency_for_combinatorics(method) + [ [], [:a, :b, :c, :d, :e] ].product([-2, 0, 2, 5, 6]) do |array, arg| + assert_equal array.send(method, arg).to_a.size, array.send(method, arg).size, + "inconsistent size for #{array}.#{method}(#{arg})" + end + end + + def test_size_for_array_combinatorics + check_consistency_for_combinatorics(:permutation) + assert_equal 24, [0, 1, 2, 4].permutation.size + assert_equal 2933197128679486453788761052665610240000000, + (1..42).to_a.permutation(30).size # 1.upto(42).inject(:*) / 1.upto(12).inject(:*) + + check_consistency_for_combinatorics(:combination) + assert_equal 28258808871162574166368460400, + (1..100).to_a.combination(42).size + # 1.upto(100).inject(:*) / 1.upto(42).inject(:*) / 1.upto(58).inject(:*) + + check_consistency_for_combinatorics(:repeated_permutation) + assert_equal 291733167875766667063796853374976, + (1..42).to_a.repeated_permutation(20).size # 42 ** 20 + + check_consistency_for_combinatorics(:repeated_combination) + assert_equal 28258808871162574166368460400, + (1..59).to_a.repeated_combination(42).size + # 1.upto(100).inject(:*) / 1.upto(42).inject(:*) / 1.upto(58).inject(:*) + end + + def test_size_for_cycle + 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 + assert_equal Float::INFINITY, loop.size + assert_equal 42, 42.times.size + end + + def test_size_for_each_slice + assert_equal nil, @obj.each_slice(3).size + assert_equal 6, @sized.each_slice(7).size + assert_equal 5, @sized.each_slice(10).size + assert_equal 1, @sized.each_slice(70).size + assert_raise(ArgumentError){ @obj.each_slice(0).size } + end + + def test_size_for_each_cons + assert_equal nil, @obj.each_cons(3).size + assert_equal 33, @sized.each_cons(10).size + assert_equal 0, @sized.each_cons(70).size + assert_raise(ArgumentError){ @obj.each_cons(0).size } + end + + def test_size_for_step + assert_equal 42, 5.step(46).size + assert_equal 4, 1.step(10, 3).size + assert_equal 3, 1.step(9, 3).size + assert_equal 0, 1.step(-11).size + assert_equal 0, 1.step(-11, 2).size + assert_equal 7, 1.step(-11, -2).size + assert_equal 7, 1.step(-11.1, -2).size + assert_equal 0, 42.step(Float::INFINITY, -2).size + assert_equal 1, 42.step(55, Float::INFINITY).size + assert_equal 1, 42.step(Float::INFINITY, Float::INFINITY).size + assert_equal 14, 0.1.step(4.2, 0.3).size + assert_equal Float::INFINITY, 42.step(Float::INFINITY, 2).size + + assert_equal 10, (1..10).step.size + assert_equal 4, (1..10).step(3).size + assert_equal 3, (1...10).step(3).size + assert_equal Float::INFINITY, (42..Float::INFINITY).step(2).size + assert_equal 0, (1..10).step(-2).size + end + + def test_size_for_downup_to + assert_equal 0, 1.upto(-100).size + assert_equal 102, 1.downto(-100).size + assert_equal Float::INFINITY, 42.upto(Float::INFINITY).size + end + + def test_size_for_string + assert_equal 5, 'hello'.each_byte.size + assert_equal 5, 'hello'.each_char.size + assert_equal 5, 'hello'.each_codepoint.size + end + + def test_peek_for_enumerator_objects + e = 2.times + assert_equal(0, e.peek) + e.next + assert_equal(1, e.peek) + 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 + + def test_enum_chain_and_plus + r = 1..5 + + e1 = r.chain() + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = r.chain([6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = r.chain([6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + # `a + b + c` should not return `Enumerator::Chain.new(a, b, c)` + # because it is expected that `(a + b).each` be called. + e4 = e2.dup + class << e4 + attr_reader :each_is_called + def each + super + @each_is_called = true + end + end + e5 = e4 + 9.step + assert_kind_of(Enumerator::Chain, e5) + assert_equal(Float::INFINITY, e5.size) + ary = [] + e5.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + assert_equal(true, e4.each_is_called) + end + + def test_chained_enums + a = (1..5).each + + e0 = Enumerator::Chain.new() + assert_kind_of(Enumerator::Chain, e0) + assert_equal(0, e0.size) + ary = [] + e0.each { |x| ary << x } + assert_equal([], ary) + + e1 = Enumerator::Chain.new(a) + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = Enumerator::Chain.new(a, [6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = Enumerator::Chain.new(a, [6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + e4 = Enumerator::Chain.new(a, Enumerator.new { |y| y << 6 << 7 << 8 }) + assert_kind_of(Enumerator::Chain, e4) + assert_equal(nil, e4.size) + ary = [] + e4.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e5 = Enumerator::Chain.new(e1, e2) + assert_kind_of(Enumerator::Chain, e5) + assert_equal(13, e5.size) + ary = [] + e5.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8], ary) + + rewound = [] + e1.define_singleton_method(:rewind) { rewound << object_id } + e2.define_singleton_method(:rewind) { rewound << object_id } + e5.rewind + assert_equal(rewound, [e2.object_id, e1.object_id]) + + rewound = [] + a = [1] + e6 = Enumerator::Chain.new(a) + a.define_singleton_method(:rewind) { rewound << object_id } + e6.rewind + assert_equal(rewound, []) + + assert_equal( + '#<Enumerator::Chain: [' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>' + + ']>, ' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>, ' + + '[6, 7, 8]' + + ']>' + + ']>', + e5.inspect + ) + end +end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index 060c50e68e..e9fb2524f6 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -1,8 +1,24 @@ +# frozen_string_literal: false require 'test/unit' 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 @@ -30,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 @@ -37,7 +54,7 @@ class TestEnv < Test::Unit::TestCase end assert_raise(TypeError) { - tmp = ENV[1] + ENV[1] } assert_raise(TypeError) { ENV[1] = 'foo' @@ -87,56 +104,63 @@ 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 def test_fetch ENV["test"] = "foo" assert_equal("foo", ENV.fetch("test")) ENV.delete("test") - assert_raise(KeyError) { ENV.fetch("test") } + feature8649 = '[ruby-core:56062] [Feature #8649]' + 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_raise(SecurityError) do - Thread.new do - $SAFE = 4 - ENV["test"] = "foo" - end.join - end 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" } - if /netbsd/ =~ RUBY_PLATFORM + assert_invalid_env {|v| ENV[v] = "test"} + assert_invalid_env {|v| ENV["test"] = v} + + begin + # setenv(3) allowed the name includes '=', + # but POSIX.1-2001 says it should fail with EINVAL. + # see also http://togetter.com/li/22380 ENV["foo=bar"] = "test" assert_equal("test", ENV["foo=bar"]) assert_equal("test", ENV["foo"]) - else - assert_raise(Errno::EINVAL) { ENV["foo=bar"] = "test" } + rescue Errno::EINVAL end + ENV[PATH_ENV] = "/tmp/".taint assert_equal("/tmp/", ENV[PATH_ENV]) end def test_keys - a = nil - assert_block { a = ENV.keys } + a = ENV.keys assert_kind_of(Array, a) a.each {|k| assert_kind_of(String, k) } end @@ -146,8 +170,7 @@ class TestEnv < Test::Unit::TestCase end def test_values - a = nil - assert_block { a = ENV.values } + a = ENV.values assert_kind_of(Array, a) a.each {|k| assert_kind_of(String, k) } end @@ -172,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" @@ -179,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 @@ -190,6 +219,22 @@ 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_filter_bang + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + assert_equal(h1, h2) + + assert_nil(ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }) + end + + def test_keep_if h1 = {} ENV.each_pair {|k, v| h1[k] = v } ENV["test"] = "foo" @@ -197,6 +242,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 @@ -219,6 +266,32 @@ class TestEnv < Test::Unit::TestCase end end + def test_filter + ENV["test"] = "foo" + h = ENV.filter {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } + assert_equal(1, h.size) + k = h.keys.first + v = h.values.first + if IGNORE_CASE + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end + + def test_slice + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + assert_equal({}, ENV.slice()) + assert_equal({}, ENV.slice("")) + assert_equal({}, ENV.slice("unknown")) + assert_equal({"foo"=>"bar", "baz"=>"qux"}, ENV.slice("foo", "baz")) + end + def test_clear ENV.clear assert_equal(0, ENV.size) @@ -267,16 +340,16 @@ class TestEnv < Test::Unit::TestCase def test_empty_p ENV.clear - assert(ENV.empty?) + assert_predicate(ENV, :empty?) ENV["test"] = "foo" - assert(!ENV.empty?) + assert_not_predicate(ENV, :empty?) end def test_has_key - assert(!ENV.has_key?("test")) + assert_not_send([ENV, :has_key?, "test"]) ENV["test"] = "foo" - assert(ENV.has_key?("test")) - assert_raise(ArgumentError) { ENV.has_key?("foo\0bar") } + assert_send([ENV, :has_key?, "test"]) + assert_invalid_env {|v| ENV.has_key?(v)} end def test_assoc @@ -290,14 +363,16 @@ 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 ENV.clear - assert(!ENV.has_value?("foo")) + assert_not_send([ENV, :has_value?, "foo"]) ENV["test"] = "foo" - assert(ENV.has_value?("foo")) + assert_send([ENV, :has_value?, "foo"]) end def test_rassoc @@ -322,6 +397,12 @@ class TestEnv < Test::Unit::TestCase assert_equal(h, ENV.to_hash) end + def test_to_h + assert_equal(ENV.to_hash, ENV.to_h) + assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h, + ENV.to_h {|k, v| ["$#{k}", v.size]}) + end + def test_reject h1 = {} ENV.each_pair {|k, v| h1[k] = v } @@ -359,6 +440,8 @@ class TestEnv < Test::Unit::TestCase ENV["foo"] = "xxx" ENV.replace({"foo"=>"bar", "baz"=>"qux"}) check(ENV.to_hash.to_a, [%w(foo bar), %w(baz qux)]) + ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"}) + check(ENV.to_hash.to_a, [%w(Foo Bar), %w(Baz Qux)]) end def test_update @@ -374,4 +457,129 @@ class TestEnv < Test::Unit::TestCase ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| v1 ? k + "_" + v1 + "_" + v2 : v2 } check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) end + + def test_huge_value + huge_value = "bar" * 40960 + ENV["foo"] = "bar" + if /mswin/ =~ RUBY_PLATFORM + assert_raise(Errno::EINVAL) { ENV["foo"] = huge_value } + assert_equal("bar", ENV["foo"]) + else + assert_nothing_raised { ENV["foo"] = huge_value } + assert_equal(huge_value, ENV["foo"]) + end + end + + if /mswin|mingw/ =~ RUBY_PLATFORM + def windows_version + @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i + end + + def test_win32_blocksize + keys = [] + len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1} + val = "bar" * 1000 + key = nil + while (len -= val.size + (key="foo#{len}").size + 2) > 0 + keys << key + ENV[key] = val + end + if windows_version < 6 + 1.upto(12) {|i| + assert_raise(Errno::EINVAL) { ENV[key] = val } + } + else + 1.upto(12) {|i| + assert_nothing_raised(Errno::EINVAL) { ENV[key] = val } + } + end + ensure + keys.each {|k| ENV.delete(k)} + end + end + + def test_frozen + ENV[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]' + assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9977, limit: 2.0) + ENV.clear + k = 'FOO' + v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500) + doit = proc {ENV[k] = v} + 500.times(&doit) + end; + end + + def test_memory_leak_select + bug9978 = '[ruby-dev:48325] [Bug #9978]' + assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9978, limit: 2.0) + ENV.clear + k = 'FOO' + (ENV[k] = 'bar'*5000 rescue 'bar'*1500) + doit = proc {ENV.select {break}} + 500.times(&doit) + end; + end + + def test_memory_crash_select + assert_normal_exit(<<-'end;') + 1000.times {ENV["FOO#{i}"] = 'bar'} + ENV.select {ENV.clear} + end; + end + + def test_memory_leak_shift + bug9983 = '[ruby-dev:48332] [Bug #9983]' + assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9983, limit: 2.0) + ENV.clear + k = 'FOO' + v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500) + doit = proc {ENV[k] = v; ENV.shift} + 500.times(&doit) + end; + end + + if Encoding.find("locale") == Encoding::UTF_8 + def test_utf8 + text = "testing \u{e5 e1 e2 e4 e3 101 3042}" + test = ENV["test"] + ENV["test"] = text + assert_equal text, ENV["test"] + ensure + ENV["test"] = test + end + end + end end diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb index a6900e075e..9cb69ddc37 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,72 +127,87 @@ class TestEval < Test::Unit::TestCase } end - def forall_TYPE(mid) - objects = [Object.new, [], nil, true, false, 77, :sym] # TODO: check + 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 - send mid, obj + obj.instance_variable_set :@ivar, 12 unless obj.frozen? + yield obj end end def test_instance_eval_string_basic - forall_TYPE :instance_eval_string_basic_i - end - - def instance_eval_string_basic_i(o) - assert_equal nil, o.instance_eval("nil") - assert_equal true, o.instance_eval("true") - assert_equal false, o.instance_eval("false") - assert_equal o, o.instance_eval("self") - assert_equal 1, o.instance_eval("1") - assert_equal :sym, o.instance_eval(":sym") - - assert_equal 11, o.instance_eval("11") - assert_equal 12, o.instance_eval("@ivar") - assert_equal 13, o.instance_eval("@@cvar") - assert_equal 14, o.instance_eval("$gvar__eval") - assert_equal 15, o.instance_eval("Const") - assert_equal 16, o.instance_eval("7 + 9") - assert_equal 17, o.instance_eval("17.to_i") - assert_equal "18", o.instance_eval(%q("18")) - assert_equal "19", o.instance_eval(%q("1#{9}")) - - 1.times { - assert_equal 12, o.instance_eval("@ivar") - assert_equal 13, o.instance_eval("@@cvar") - assert_equal 14, o.instance_eval("$gvar__eval") - assert_equal 15, o.instance_eval("Const") - } + forall_TYPE do |o| + assert_equal nil, o.instance_eval("nil") + assert_equal true, o.instance_eval("true") + assert_equal false, o.instance_eval("false") + assert_equal o, o.instance_eval("self") + assert_equal 1, o.instance_eval("1") + assert_equal :sym, o.instance_eval(":sym") + + assert_equal 11, o.instance_eval("11") + 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") + assert_equal 16, o.instance_eval("7 + 9") + assert_equal 17, o.instance_eval("17.to_i") + assert_equal "18", o.instance_eval(%q("18")) + assert_equal "19", o.instance_eval(%q("1#{9}")) + + 1.times { + 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") + } + end end def test_instance_eval_block_basic - forall_TYPE :instance_eval_block_basic_i - end - - def instance_eval_block_basic_i(o) - assert_equal nil, o.instance_eval { nil } - assert_equal true, o.instance_eval { true } - assert_equal false, o.instance_eval { false } - assert_equal o, o.instance_eval { self } - assert_equal 1, o.instance_eval { 1 } - assert_equal :sym, o.instance_eval { :sym } - - assert_equal 11, o.instance_eval { 11 } - assert_equal 12, o.instance_eval { @ivar } - assert_equal 13, o.instance_eval { @@cvar } - assert_equal 14, o.instance_eval { $gvar__eval } - assert_equal 15, o.instance_eval { Const } - assert_equal 16, o.instance_eval { 7 + 9 } - assert_equal 17, o.instance_eval { 17.to_i } - assert_equal "18", o.instance_eval { "18" } - assert_equal "19", o.instance_eval { "1#{9}" } + forall_TYPE do |o| + assert_equal nil, o.instance_eval { nil } + assert_equal true, o.instance_eval { true } + assert_equal false, o.instance_eval { false } + assert_equal o, o.instance_eval { self } + assert_equal 1, o.instance_eval { 1 } + assert_equal :sym, o.instance_eval { :sym } + + assert_equal 11, o.instance_eval { 11 } + 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 } + assert_equal 16, o.instance_eval { 7 + 9 } + assert_equal 17, o.instance_eval { 17.to_i } + assert_equal "18", o.instance_eval { "18" } + assert_equal "19", o.instance_eval { "1#{9}" } + + 1.times { + 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 } + } + end + end - 1.times { - assert_equal 12, o.instance_eval { @ivar } - assert_equal 13, o.instance_eval { @@cvar } - assert_equal 14, o.instance_eval { $gvar__eval } - assert_equal 15, o.instance_eval { Const } - } + 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 @@ -204,29 +219,61 @@ class TestEval < Test::Unit::TestCase end end + def test_instance_eval_method + bug2788 = '[ruby-core:28324]' + [Object.new, [], nil, true, false].each do |o| + assert_nothing_raised(TypeError, "#{bug2788} (#{o.inspect})") do + o.instance_eval { + def defd_using_instance_eval() :ok end + } + end + assert_equal(:ok, o.defd_using_instance_eval) + class << o + remove_method :defd_using_instance_eval + end + end + end + + def test_instance_eval_on_argf_singleton_class + bug8188 = '[ruby-core:53839] [Bug #8188]' + assert_warning('', bug8188) do + ARGF.singleton_class.instance_eval{} + end + end + + class Foo + Bar = 2 + end + + def test_instance_eval_const + bar = nil + assert_nothing_raised(NameError) do + bar = Foo.new.instance_eval("Bar") + end + assert_equal(2, bar) + end + # # 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 def test_eval_orig assert_nil(eval("")) - $bad=false - eval 'while false; $bad = true; print "foo\n" end' - assert(!$bad) + bad=false + 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)' @@ -238,53 +285,40 @@ 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 - assert_equal("local1", eval("local1", $x)) # normal local var - assert_equal("local2", eval("local2", $x)) # nested local var - $bad = true + x = make_test_binding + assert_equal("local1", eval("local1", x)) # normal local var + assert_equal("local2", eval("local2", x)) # nested local var + bad = true begin p eval("local1") rescue NameError # must raise error - $bad = false + bad = false end - assert(!$bad) + assert(!bad) # !! use class_eval to avoid nested definition - self.class.class_eval %q( + x = self.class.class_eval %q( module EvTest EVTEST1 = 25 evtest2 = 125 - $x = binding + evtest2 = evtest2 + binding end ) - assert_equal(25, eval("EVTEST1", $x)) # constant in module - assert_equal(125, eval("evtest2", $x)) # local var in module - $bad = true + assert_equal(25, eval("EVTEST1", x)) # constant in module + assert_equal(125, eval("evtest2", x)) # local var in module + bad = true begin eval("EVTEST1") rescue NameError # must raise error - $bad = false - end - assert(!$bad) - - if false - # Ruby 2.0 doesn't see Proc as Binding - x = proc{} - eval "i4 = 1", x - assert_equal(1, eval("i4", x)) - x = proc{proc{}}.call - eval "i4 = 22", x - assert_equal(22, eval("i4", x)) - $x = [] - x = proc{proc{}}.call - eval "(0..9).each{|i5| $x[i5] = proc{i5*2}}", x - assert_equal(8, $x[4].call) + bad = false end + assert(!bad) x = binding eval "i = 1", x @@ -292,10 +326,10 @@ class TestEval < Test::Unit::TestCase x = proc{binding}.call eval "i = 22", x assert_equal(22, eval("i", x)) - $x = [] + t = [] x = proc{binding}.call - eval "(0..9).each{|i5| $x[i5] = proc{i5*2}}", x - assert_equal(8, $x[4].call) + eval "(0..9).each{|i5| t[i5] = proc{i5*2}}", x + assert_equal(8, t[4].call) x = proc{binding}.call eval "for i6 in 1..1; j6=i6; end", x assert(eval("defined? i6", x)) @@ -305,34 +339,13 @@ 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")) assert_equal(eval("foo22"), eval("foo22", p)) assert_equal(55, eval("foo22")) }.call - - if false - # Ruby 2.0 doesn't see Proc as Binding - p1 = proc{i7 = 0; proc{i7}}.call - assert_equal(0, p1.call) - eval "i7=5", p1 - assert_equal(5, p1.call) - assert(!defined?(i7)) - end - - if false - # Ruby 2.0 doesn't see Proc as Binding - p1 = proc{i7 = 0; proc{i7}}.call - i7 = nil - assert_equal(0, p1.call) - eval "i7=1", p1 - assert_equal(1, p1.call) - eval "i7=5", p1 - assert_equal(5, p1.call) - assert_nil(i7) - end end def test_nil_instance_eval_cvar @@ -352,10 +365,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 @@ -384,6 +397,24 @@ class TestEval < Test::Unit::TestCase assert_equal("ok", x) end + def test_define_method_toplevel + feature6609 = '[ruby-core:45715]' + main = eval("self", TOPLEVEL_BINDING) + assert_nothing_raised(NoMethodError, feature6609) do + main.instance_eval do + define_method("feature6609_block") {feature6609} + end + end + assert_equal(feature6609, feature6609_block) + + assert_nothing_raised(NoMethodError, feature6609) do + main.instance_eval do + define_method("feature6609_method", Object.instance_method(:feature6609_block)) + end + end + assert_equal(feature6609, feature6609_method) + end + def test_eval_using_integer_as_binding assert_raise(TypeError) { eval("", 1) } end @@ -392,16 +423,6 @@ class TestEval < Test::Unit::TestCase assert_raise(RuntimeError) { eval("raise ''") } end - def test_eval_using_untainted_binding_under_safe4 - assert_raise(SecurityError) do - Thread.new do - b = binding - $SAFE = 4 - eval("", b) - end.join - end - end - def test_eval_with_toplevel_binding # [ruby-dev:37142] ruby("-e", "x = 0; eval('p x', TOPLEVEL_BINDING)") do |f| f.close_write @@ -415,4 +436,93 @@ class TestEval < Test::Unit::TestCase assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-32be"))} assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-32le"))} end + + def test_instance_eval_method_proc + bug3860 = Class.new do + def initialize(a); + @a=a + end + def get(*args) + @a + end + end + foo = bug3860.new 1 + foo_pr = foo.method(:get).to_proc + result = foo.instance_eval(&foo_pr) + assert_equal(1, result, 'Bug #3786, Bug #3860, [ruby-core:32501]') + end + + def test_file_encoding + fname = "\u{3042}".encode("euc-jp") + assert_equal(fname, eval("__FILE__", nil, fname, 1)) + end + + def test_eval_location_fstring + o = Object.new + o.instance_eval "def foo() end", "generated code" + o.instance_eval "def bar() end", "generated code" + + a, b = o.method(:foo).source_location[0], + o.method(:bar).source_location[0] + + assert_same a, b + end + + def test_eval_location_binding + assert_warning(/__FILE__ in eval/) do + assert_equal(__FILE__, eval("__FILE__", binding)) + end + 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 + binding + end + GC.stress = true + b = nil + tap do + b = m {} + end + 0.times.to_a + b.eval('yield') + }, '[Bug #10368]' + end + + def test_gced_eval_location + Dir.mktmpdir do |d| + File.write("#{d}/2.rb", "") + File.write("#{d}/1.rb", "require_relative '2'\n""__FILE__\n") + file = "1.rb" + path = File.expand_path(file, d) + assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d))) + assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d))) + end + end + + def orphan_proc + proc {eval("return :ng")} + end + + 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 18ecb404be..de249c9b7c 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -1,42 +1,46 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' +require 'tempfile' class TestException < Test::Unit::TestCase - def test_exception + def test_exception_rescued begin raise "this must be handled" assert(false) rescue assert(true) end + end - $bad = true + def test_exception_retry + bad = true begin raise "this must be handled no.2" rescue - if $bad - $bad = false + if bad + bad = false retry - assert(false) end + assert(!bad) end assert(true) + end - # exception in rescue clause - $string = "this must be handled no.3" - e = assert_raise(RuntimeError) do + def test_exception_in_rescue + string = "this must be handled no.3" + assert_raise_with_message(RuntimeError, string) do begin raise "exception in rescue clause" rescue - raise $string + raise string end assert(false) end - assert_equal($string, e.message) + end - # exception in ensure clause - $string = "exception in ensure clause" - e = assert_raise(RuntimeError) do + def test_exception_in_ensure + string = "exception in ensure clause" + assert_raise_with_message(RuntimeError, string) do begin raise "this must be handled no.4" ensure @@ -46,55 +50,148 @@ class TestException < Test::Unit::TestCase end assert(false) end - assert_equal($string, e.message) + end - $bad = true + def test_exception_ensure + bad = true begin begin raise "this must be handled no.5" ensure - $bad = false + bad = false end rescue end - assert(!$bad) + assert(!bad) + end - $bad = true + def test_exception_ensure_2 # just duplication? + bad = true begin begin raise "this must be handled no.6" ensure - $bad = false + bad = false end rescue end - assert(!$bad) + assert(!bad) + end + + def test_errinfo_in_debug + bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do + def to_s + require '\0' + rescue LoadError + self.class.to_s + end + end - $bad = true + err = EnvUtil.verbose_warning do + assert_raise(bug9568) do + $DEBUG, debug = true, $DEBUG + begin + raise bug9568 + ensure + $DEBUG = debug + end + end + end + 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 begin break ensure - $bad = false + bad = false end end - assert(!$bad) + assert(!bad) + end + + def test_catch_no_throw + assert_equal(:foo, catch {:foo}) + end + + def test_catch_throw + 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 - assert(catch(:foo) { - loop do - loop do - throw :foo, true - break - end - break - assert(false) # should no reach here - end - false - }) + def test_catch_throw_in_require + bug7185 = '[ruby-dev:46234]' + Tempfile.create(["dep", ".rb"]) {|t| + t.puts("throw :extdep, 42") + t.close + 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 + def test_else_no_exception begin assert(true) rescue @@ -102,7 +199,9 @@ class TestException < Test::Unit::TestCase else assert(true) end + end + def test_else_raised begin assert(true) raise @@ -112,7 +211,9 @@ class TestException < Test::Unit::TestCase else assert(false) end + end + def test_else_nested_no_exception begin assert(true) begin @@ -128,7 +229,9 @@ class TestException < Test::Unit::TestCase else assert(true) end + end + def test_else_nested_rescued begin assert(true) begin @@ -146,7 +249,9 @@ class TestException < Test::Unit::TestCase else assert(true) end + end + def test_else_nested_unrescued begin assert(true) begin @@ -164,7 +269,9 @@ class TestException < Test::Unit::TestCase else assert(false) end + end + def test_else_nested_rescued_reraise begin assert(true) begin @@ -192,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), []) @@ -224,28 +351,11 @@ class TestException < Test::Unit::TestCase INPUT end - def test_safe4 - cmd = proc{raise SystemExit} - safe0_p = proc{|*args| args} - - test_proc = proc { - $SAFE = 4 - begin - cmd.call - rescue SystemExit => e - safe0_p["SystemExit: #{e.inspect}"] - raise e - rescue Exception => e - safe0_p["Exception (NOT SystemExit): #{e.inspect}"] - raise e - end - } - assert_raise(SystemExit, '[ruby-dev:38760]') {test_proc.call} - end - def test_thread_signal_location - stdout, stderr, status = EnvUtil.invoke_ruby("-d", <<-RUBY, false, true) + skip + _, 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 @@ -278,6 +388,17 @@ end.join assert_equal(e.inspect, e.new.inspect) end + def test_to_s + e = StandardError.new("foo") + assert_equal("foo", e.to_s) + + def (s = Object.new).to_s + "bar" + end + e = StandardError.new(s) + assert_equal("bar", e.to_s) + end + def test_set_backtrace e = Exception.new @@ -296,20 +417,984 @@ end.join exit rescue SystemExit => e end - assert(e.success?) + assert_send([e, :success?], "success by default") + + begin + exit(true) + rescue SystemExit => e + end + assert_send([e, :success?], "true means success") + + begin + exit(false) + rescue SystemExit => e + end + assert_not_send([e, :success?], "false means failure") begin abort rescue SystemExit => e end - assert(!e.success?) + assert_not_send([e, :success?], "abort means failure") end def test_nomethoderror bug3237 = '[ruby-core:29948]' str = "\u2600" id = :"\u2604" - e = assert_raise(NoMethodError) {str.__send__(id)} - assert_equal("undefined method `#{id}' for #{str.inspect}:String", e.message, bug3237) + msg = "undefined method `#{id}' for \"#{str}\":String" + assert_raise_with_message(NoMethodError, msg, bug3237) do + str.__send__(id) + end + end + + def test_errno + assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding) + end + + def test_too_many_args_in_eval + bug5720 = '[ruby-core:41520]' + arg_string = (0...140000).to_a.join(", ") + assert_raise(SystemStackError, bug5720) {eval "raise(#{arg_string})"} + end + + def test_systemexit_new + e0 = SystemExit.new + assert_equal(0, e0.status) + assert_equal("SystemExit", e0.message) + ei = SystemExit.new(3) + assert_equal(3, ei.status) + assert_equal("SystemExit", ei.message) + es = SystemExit.new("msg") + assert_equal(0, es.status) + assert_equal("msg", es.message) + eis = SystemExit.new(7, "msg") + assert_equal(7, eis.status) + assert_equal("msg", eis.message) + + bug5728 = '[ruby-dev:44951]' + et = SystemExit.new(true) + assert_equal(true, et.success?, bug5728) + assert_equal("SystemExit", et.message, bug5728) + ef = SystemExit.new(false) + assert_equal(false, ef.success?, bug5728) + assert_equal("SystemExit", ef.message, bug5728) + ets = SystemExit.new(true, "msg") + assert_equal(true, ets.success?, bug5728) + assert_equal("msg", ets.message, bug5728) + efs = SystemExit.new(false, "msg") + assert_equal(false, efs.success?, bug5728) + assert_equal("msg", efs.message, bug5728) + end + + def test_exception_in_name_error_to_str + bug5575 = '[ruby-core:41612]' + Tempfile.create(["test_exception_in_name_error_to_str", ".rb"]) do |t| + t.puts <<-EOC + begin + BasicObject.new.inspect + rescue + $!.inspect + end + EOC + t.close + assert_nothing_raised(NameError, bug5575) do + load(t.path) + end + end + end + + def test_equal + bug5865 = '[ruby-core:41979]' + assert_equal(RuntimeError.new("a"), RuntimeError.new("a"), bug5865) + assert_not_equal(RuntimeError.new("a"), StandardError.new("a"), bug5865) + end + + def test_exception_in_exception_equal + bug5865 = '[ruby-core:41979]' + Tempfile.create(["test_exception_in_exception_equal", ".rb"]) do |t| + t.puts <<-EOC + o = Object.new + def o.exception(arg) + end + _ = RuntimeError.new("a") == o + EOC + t.close + assert_nothing_raised(ArgumentError, bug5865) do + load(t.path) + end + end + end + + Bug4438 = '[ruby-core:35364]' + + def test_rescue_single_argument + assert_raise(TypeError, Bug4438) do + begin + raise + rescue 1 + end + end + end + + def test_rescue_splat_argument + assert_raise(TypeError, Bug4438) do + begin + raise + rescue *Array(1) + end + end + end + + def test_to_s_taintness_propagation + for exc in [Exception, NameError] + m = "abcdefg" + e = exc.new(m) + e.taint + s = e.to_s + assert_equal(false, m.tainted?, + "#{exc}#to_s should not propagate taintness") + assert_equal(false, s.tainted?, + "#{exc}#to_s should not propagate taintness") + end + + o = Object.new + def o.to_str + "foo" + end + o.taint + e = NameError.new(o) + s = e.to_s + assert_equal(false, s.tainted?) + end + + def m + m(&->{return 0}) + 42 + end + + def test_stackoverflow + 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(%w[--disable-gem], <<-SRC) + assert_raise(SystemStackError, #{bug9109.dump}) { + h = {a: ->{h[:a].call}} + h[:a].call + } + SRC + rescue SystemStackError + end + + def test_machine_stackoverflow_by_define_method + bug9454 = '[ruby-core:60113] [Bug #9454]' + assert_separately(%w[--disable-gem], <<-SRC) + assert_raise(SystemStackError, #{bug9454.dump}) { + define_method(:foo) {self.foo} + self.foo + } + SRC + 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 + e = assert_raise(StandardError) { + begin + raise msg + rescue => e + cause = e.cause + raise StandardError + end + } + assert_nil(cause, msg) + cause = e.cause + assert_instance_of(RuntimeError, cause, msg) + assert_equal(msg, cause.message, msg) + end + + def test_cause_reraised + msg = "[Feature #8257]" + e = assert_raise(RuntimeError) { + begin + raise msg + rescue => e + raise e + end + } + 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 + errs = [ + /-: unexpected return\n/, + /.*undefined local variable or method `n'.*\n/, + ] + assert_in_out_err([], <<-'end;', [], errs) + END{n}; END{return} + end; + end + + def test_raise_with_cause + msg = "[Feature #8257]" + cause = ArgumentError.new("foobar") + e = assert_raise(RuntimeError) {raise msg, cause: cause} + assert_same(cause, e.cause) + 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: RuntimeError.new("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_cause_exception_in_cause_message + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}") do |outs, errs, status| + begin; + exc = Class.new(StandardError) do + def initialize(obj, cnt) + super(obj) + @errcnt = cnt + end + def to_s + return super if @errcnt <= 0 + @errcnt -= 1 + raise "xxx" + end + end.new("ok", 10) + raise "[Bug #17033]", cause: exc + end; + assert_equal(1, errs.count {|m| m.include?("[Bug #17033]")}, proc {errs.pretty_inspect}) + end + 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_new_default + error = NameError.new + assert_equal("NameError", error.message) + end + + def test_name_error_new_message + error = NameError.new("Message") + assert_equal("Message", error.message) + end + + def test_name_error_new_name + error = NameError.new("Message") + assert_nil(error.name) + + error = NameError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_name_error_new_receiver + receiver = Object.new + + error = NameError.new + assert_raise(ArgumentError) {error.receiver} + assert_equal("NameError", error.message) + + error = NameError.new(receiver: receiver) + assert_equal(["NameError", receiver], + [error.message, error.receiver]) + + error = NameError.new("Message", :foo, receiver: receiver) + assert_equal(["Message", receiver, :foo], + [error.message, error.receiver, error.name]) + end + + def test_nomethod_error_new_default + error = NoMethodError.new + assert_equal("NoMethodError", error.message) + end + + def test_nomethod_error_new_message + error = NoMethodError.new("Message") + assert_equal("Message", error.message) + end + + def test_nomethod_error_new_name + error = NoMethodError.new("Message") + assert_nil(error.name) + + error = NoMethodError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_nomethod_error_new_name_args + error = NoMethodError.new("Message", :foo) + assert_nil(error.args) + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_equal([:foo, [1, 2]], [error.name, error.args]) + end + + def test_nomethod_error_new_name_args_priv + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_not_predicate(error, :private_call?) + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_equal([:foo, [1, 2], true], + [error.name, error.args, error.private_call?]) + end + + def test_nomethod_error_new_receiver + receiver = Object.new + + error = NoMethodError.new + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new(receiver: receiver) + assert_equal(receiver, error.receiver) + + error = NoMethodError.new("Message") + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", receiver: receiver) + assert_equal(["Message", receiver], + [error.message, error.receiver]) + + error = NoMethodError.new("Message", :foo) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, receiver: receiver) + assert_equal(["Message", :foo, receiver], + [error.message, error.name, error.receiver]) + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver) + assert_equal(["Message", :foo, [1, 2], receiver], + [error.message, error.name, error.args, error.receiver]) + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver) + assert_equal([:foo, [1, 2], true, receiver], + [error.name, error.args, error.private_call?, error.receiver]) + end + + def test_name_error_info_const + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval("Object") + } + assert_equal(:Object, e.name) + + e = assert_raise(NameError) { + BasicObject::X + } + assert_same(BasicObject, e.receiver) + assert_equal(:X, e.name) + end + + def test_name_error_info_method + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval {foo} + } + assert_equal(:foo, e.name) + assert_same(obj, e.receiver) + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + + def test_name_error_info_local_variables + obj = PrettyObject.new + def obj.test(a, b=nil, *c, &d) + e = a + 1.times {|f| g = foo; g} + e + 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]) + def (obj = Object.new).w(n) warn("test warning", uplevel: n) end + warning = capture_warning_warn {obj.w(0)} + assert_equal("#{__FILE__}:#{__LINE__-2}: warning: test warning\n", warning[0]) + warning = capture_warning_warn {obj.w(1)} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", uplevel: -1)} + assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/) + 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_backtrace_in_eval + bug = '[ruby-core:84434] [Bug #14229]' + assert_in_out_err(['-e', 'eval("raise")'], "", [], /^\(eval\):1:/, bug) + end + + def test_full_message + message = RuntimeError.new("testerror").full_message + assert_operator(message, :end_with?, "\n") + + test_method = "def foo; raise 'testerror'; end" + + out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true) + assert_predicate(status1, :success?) + assert_empty(err1, "expected nothing wrote to $stdout by #full_message") + + _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) + assert_equal(err2, out1) + + e = RuntimeError.new("a\n") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_operator(message, :end_with?, "\n") + message = message.gsub(/\e\[[\d;]*m/, '') + assert_not_operator(message, :end_with?, "\n\n") + e = RuntimeError.new("a\n\nb\n\nc") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_all?(message.lines) do |m| + /\e\[\d[;\d]*m[^\e]*\n/ !~ m + end + + e = RuntimeError.new("testerror") + message = e.full_message(highlight: false) + assert_not_match(/\e/, message) + + bt = ["test:100", "test:99", "test:98", "test:1"] + e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt} + + bottom = "test:100: testerror (RuntimeError)\n" + top = "test:1\n" + remark = "Traceback (most recent call last):" + + message = e.full_message(highlight: false, order: :top) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + + message = e.full_message(highlight: false, order: :bottom) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, remark) + assert_operator(message, :end_with?, bottom) + + assert_raise_with_message(ArgumentError, /:top or :bottom/) { + e.full_message(highlight: false, order: :middle) + } + + message = e.full_message(highlight: true) + assert_match(/\e/, message) + + message = e.full_message + if Exception.to_tty? + assert_match(/\e/, message) + message = message.gsub(/\e\[[\d;]*m/, '') + assert_operator(message, :start_with?, remark) + assert_operator(message, :end_with?, bottom) + else + assert_not_match(/\e/, message) + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + end + end + + def test_exception_in_message + code = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class Bug14566 < StandardError + def message; raise self.class; end + end + raise Bug14566 + end; + assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 2) + end + + def test_non_exception_cause + assert_raise_with_message(TypeError, /exception/) do + raise "foo", cause: 1 + end; + end + + def test_circular_cause_handle + assert_raise_with_message(ArgumentError, /circular cause/) do + begin + raise "error 1" + rescue => e1 + raise "error 2" rescue raise e1, cause: $! + end + end; + end + + def test_super_in_method_missing + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class Object + def method_missing(name, *args, &block) + super + end + end + + bug14670 = '[ruby-dev:50522] [Bug #14670]' + assert_raise_with_message(NoMethodError, /`foo'/, bug14670) do + Object.new.foo + end + end; end end diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index edfe55a1d3..e6b89ffa45 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -1,11 +1,11 @@ +# frozen_string_literal: false require 'test/unit' require 'fiber' -require 'continuation' -require_relative './envutil' +EnvUtil.suppress_warning {require 'continuation'} +require 'tmpdir' class TestFiber < Test::Unit::TestCase def test_normal - f = Fiber.current assert_equal(:ok2, Fiber.new{|e| assert_equal(:ok1, e) @@ -34,50 +34,55 @@ class TestFiber < Test::Unit::TestCase end def test_many_fibers - max = 10000 + max = 10_000 assert_equal(max, max.times{ Fiber.new{} }) + GC.start # force collect created fibers assert_equal(max, max.times{|i| Fiber.new{ }.resume } ) + GC.start # force collect created fibers end def test_many_fibers_with_threads - max = 1000 - @cnt = 0 - (1..100).map{|ti| - Thread.new{ - max.times{|i| - Fiber.new{ - @cnt += 1 - }.resume + assert_normal_exit <<-SRC, timeout: 60 + max = 1000 + @cnt = 0 + (1..100).map{|ti| + Thread.new{ + max.times{|i| + Fiber.new{ + @cnt += 1 + }.resume + } } + }.each{|t| + t.join } - }.each{|t| - t.join - } - assert_equal(:ok, :ok) + SRC end def test_error 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 f.resume } assert_raise(RuntimeError){ - f = Fiber.new{ + Fiber.new{ @c = callcc{|c| @c = c} }.resume @c.call # cross fiber callcc @@ -115,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 @@ -188,6 +193,221 @@ class TestFiber < Test::Unit::TestCase f2 = Fiber.new{ f1.resume } f1.transfer }, '[ruby-dev:40833]' + assert_normal_exit %q{ + require 'fiber' + Fiber.new{}.resume + 1.times{Fiber.current.transfer} + } end -end + def test_resume_root_fiber + Thread.new do + assert_raise(FiberError) do + Fiber.current.resume + end + end.join + end + + def test_gc_root_fiber + bug4612 = '[ruby-core:35891]' + + assert_normal_exit %q{ + require 'fiber' + GC.stress = true + Thread.start{ Fiber.current; nil }.join + GC.start + }, 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, bug5083) + assert_instance_of(Class, Fiber.new(&Class.new.method(:undef_method)).resume(:to_s), bug5083) + end + + def test_prohibit_resume_transferred_fiber + assert_raise(FiberError){ + root_fiber = Fiber.current + f = Fiber.new{ + root_fiber.transfer + } + f.transfer + f.resume + } + assert_raise(FiberError){ + g=nil + f=Fiber.new{ + g.resume + g.resume + } + g=Fiber.new{ + f.resume + f.resume + } + f.transfer + } + end + + def test_fork_from_fiber + skip 'fork not supported' unless Process.respond_to?(:fork) + pid = nil + bug5700 = '[ruby-core:41456]' + assert_nothing_raised(bug5700) do + Fiber.new do + pid = fork do + xpid = nil + Fiber.new { + xpid = fork do + # enough to trigger GC on old root fiber + count = 10000 + count = 1000 if /openbsd/i =~ RUBY_PLATFORM + count.times do + Fiber.new {}.transfer + Fiber.new { Fiber.yield } + end + exit!(0) + end + }.transfer + _, status = Process.waitpid2(xpid) + exit!(status.success?) + end + end.resume + end + pid, status = Process.waitpid2(pid) + assert_equal(0, status.exitstatus, bug5700) + assert_equal(false, status.signaled?, bug5700) + end + + def test_exit_in_fiber + bug5993 = '[ruby-dev:45218]' + assert_nothing_raised(bug5993) do + Thread.new{ Fiber.new{ Thread.exit }.resume; raise "unreachable" }.join + end + end + + def test_fatal_in_fiber + assert_in_out_err(["-r-test-/fatal/rb_fatal", "-e", <<-EOS], "", [], /ok/) + Fiber.new{ + rb_fatal "ok" + }.resume + puts :ng # unreachable. + EOS + end + + def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true + env = {} + env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size + env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size + out, _ = Dir.mktmpdir("test_fiber") {|tmpdir| + EnvUtil.invoke_ruby([env, '-e', script], '', true, true, chdir: tmpdir, timeout: 30) + } + use_length ? out.length : out + end + + def test_stack_size + h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false)) + h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false)) + h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 10, 1024 * 1024 * 10, false)) + + assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size]) + + # check VM machine stack size + script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume' + size_default = invoke_rec script, nil, nil + assert_operator(size_default, :>, 0) + size_0 = invoke_rec script, 0, nil + assert_operator(size_default, :>, size_0) + size_large = invoke_rec script, 1024 * 1024 * 10, nil + assert_operator(size_default, :<, size_large) + + return if /mswin|mingw/ =~ RUBY_PLATFORM + + # check machine stack size + # Note that machine stack size may not change size (depend on OSs) + script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume' + vm_stack_size = 1024 * 1024 + size_default = invoke_rec script, vm_stack_size, nil + size_0 = invoke_rec script, vm_stack_size, 0 + assert_operator(size_default, :>=, size_0) + size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10 + assert_operator(size_default, :<=, size_large) + end + + def test_separate_lastmatch + bug7678 = '[ruby-core:51331]' + /a/ =~ "a" + m1 = $~ + m2 = nil + Fiber.new do + /b/ =~ "b" + m2 = $~ + end.resume + assert_equal("b", m2[0]) + assert_equal(m1, $~, bug7678) + end + + def test_separate_lastline + bug7678 = '[ruby-core:51331]' + $_ = s1 = "outer" + s2 = nil + Fiber.new do + s2 = "inner" + end.resume + assert_equal("inner", s2) + assert_equal(s1, $_, bug7678) + 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_create_fiber_in_new_thread + ret = Thread.new{ + Thread.new{ + Fiber.new{Fiber.yield :ok}.resume + }.value + }.value + assert_equal :ok, ret, '[Bug #14642]' + end + + def test_machine_stack_gc + assert_normal_exit <<-RUBY, '[Bug #14561]', timeout: 10 + enum = Enumerator.new { |y| y << 1 } + thread = Thread.new { enum.peek } + thread.join + sleep 5 # pause until thread cache wait time runs out. Native thread exits. + GC.start + RUBY + end +end diff --git a/test/ruby/test_file.rb b/test/ruby/test_file.rb index ba9549fda5..3deab76e93 100644 --- a/test/ruby/test_file.rb +++ b/test/ruby/test_file.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' +require "-test-/file" require_relative 'ut_eof' class TestFile < Test::Unit::TestCase @@ -28,114 +30,200 @@ class TestFile < Test::Unit::TestCase include TestEOF def open_file(content) - f = Tempfile.new("test-eof") - f << content - f.rewind - yield f + Tempfile.create("test-eof") {|f| + f << content + f.rewind + yield f + } end alias open_file_rw open_file include TestEOF::Seek + def test_empty_file_bom + bug6487 = '[ruby-core:45203]' + Tempfile.create(__method__.to_s) {|f| + assert_file.exist?(f.path) + assert_nothing_raised(bug6487) {File.read(f.path, mode: 'r:utf-8')} + assert_nothing_raised(bug6487) {File.read(f.path, mode: 'r:bom|utf-8')} + } + end + + def assert_bom(bytes, name) + bug6487 = '[ruby-core:45203]' + + Tempfile.create(name.to_s) {|f| + f.sync = true + expected = "" + result = nil + bytes[0...-1].each do |x| + f.write x + f.write ' ' + f.pos -= 1 + expected << x + assert_nothing_raised(bug6487) {result = File.read(f.path, mode: 'rb:bom|utf-8')} + assert_equal("#{expected} ".force_encoding("utf-8"), result) + end + f.write bytes[-1] + assert_nothing_raised(bug6487) {result = File.read(f.path, mode: 'rb:bom|utf-8')} + assert_equal '', result, "valid bom" + } + end + + def test_bom_8 + assert_bom(["\xEF", "\xBB", "\xBF"], __method__) + end + + def test_bom_16be + assert_bom(["\xFE", "\xFF"], __method__) + end + + def test_bom_16le + assert_bom(["\xFF", "\xFE"], __method__) + end + + def test_bom_32be + assert_bom(["\0", "\0", "\xFE", "\xFF"], __method__) + end + + def test_bom_32le + assert_bom(["\xFF", "\xFE\0\0"], __method__) + end + def test_truncate_wbuf - f = Tempfile.new("test-truncate") - f.print "abc" - f.truncate(0) - f.print "def" - f.flush - assert_equal("\0\0\0def", File.read(f.path), "[ruby-dev:24191]") - f.close + Tempfile.create("test-truncate") {|f| + f.print "abc" + f.truncate(0) + f.print "def" + f.flush + assert_equal("\0\0\0def", File.read(f.path), "[ruby-dev:24191]") + } end def test_truncate_rbuf - f = Tempfile.new("test-truncate") - f.puts "abc" - f.puts "def" - f.close - f.open - assert_equal("abc\n", f.gets) - f.truncate(3) - assert_equal(nil, f.gets, "[ruby-dev:24197]") + Tempfile.create("test-truncate") {|f| + f.puts "abc" + f.puts "def" + f.rewind + assert_equal("abc\n", f.gets) + f.truncate(3) + assert_equal(nil, f.gets, "[ruby-dev:24197]") + } end def test_truncate_beyond_eof - f = Tempfile.new("test-truncate") - f.print "abc" - f.truncate 10 - assert_equal("\0" * 7, f.read(100), "[ruby-dev:24532]") + Tempfile.create("test-truncate") {|f| + f.print "abc" + f.truncate 10 + assert_equal("\0" * 7, f.read(100), "[ruby-dev:24532]") + } + end + + def test_truncate_size + Tempfile.create("test-truncate") do |f| + q1 = Thread::Queue.new + q2 = Thread::Queue.new + + th = Thread.new do + data = '' + 64.times do |i| + data << i.to_s + f.rewind + f.print data + f.truncate(data.bytesize) + q1.push data.bytesize + q2.pop + end + q1.push nil + end + + while size = q1.pop + assert_equal size, File.size(f.path) + assert_equal size, f.size + q2.push true + end + th.join + end end def test_read_all_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - assert_equal("a", f.read, "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + assert_equal("a", f.read, "mode = <#{mode}>") + } end end def test_gets_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - assert_equal("a", f.gets("a"), "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + assert_equal("a", f.gets("a"), "mode = <#{mode}>") + } end end def test_gets_para_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "\na" - f.rewind - assert_equal("a", f.gets(""), "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "\na" + f.rewind + assert_equal("a", f.gets(""), "mode = <#{mode}>") + } end end def test_each_char_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - result = [] - f.each_char {|b| result << b } - assert_equal([?a], result, "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + result = [] + f.each_char {|b| result << b } + assert_equal([?a], result, "mode = <#{mode}>") + } end end def test_each_byte_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - result = [] - f.each_byte {|b| result << b.chr } - assert_equal([?a], result, "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + result = [] + f.each_byte {|b| result << b.chr } + assert_equal([?a], result, "mode = <#{mode}>") + } end end def test_getc_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - assert_equal(?a, f.getc, "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + assert_equal(?a, f.getc, "mode = <#{mode}>") + } end end def test_getbyte_extended_file [nil, {:textmode=>true}, {:binmode=>true}].each do |mode| - f = Tempfile.new("test-extended-file", mode) - assert_nil(f.getc) - f.print "a" - f.rewind - assert_equal(?a, f.getbyte.chr, "mode = <#{mode}>") + Tempfile.create("test-extended-file", mode) {|f| + assert_nil(f.getc) + f.print "a" + f.rewind + assert_equal(?a, f.getbyte.chr, "mode = <#{mode}>") + } end end @@ -161,7 +249,7 @@ class TestFile < Test::Unit::TestCase def test_realpath Dir.mktmpdir('rubytest-realpath') {|tmpdir| realdir = File.realpath(tmpdir) - tst = realdir.sub(/#{Regexp.escape(File::SEPARATOR)}/, '\0\0\0') + tst = realdir + (File::SEPARATOR*3 + ".") assert_equal(realdir, File.realpath(tst)) assert_equal(realdir, File.realpath(".", tst)) if File::ALT_SEPARATOR @@ -171,14 +259,264 @@ 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) - tst = realdir.sub(/#{Regexp.escape(File::SEPARATOR)}/, '\0\0\0') + tst = realdir + (File::SEPARATOR*3 + ".") assert_equal(realdir, File.realdirpath(tst)) assert_equal(realdir, File.realdirpath(".", tst)) assert_equal(File.join(realdir, "foo"), File.realdirpath("foo", tst)) } + begin + result = File.realdirpath("bar", "//:/foo") + rescue SystemCallError + else + if result.start_with?("//") + assert_equal("//:/foo/bar", result) + end + 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], []) + require "tempfile" + t = Time.at(-1) + begin + Tempfile.create('test_utime_with_minus_time_segv') {|f| + File.utime(t, t, f) + } + rescue + end + puts '#{bug5596}' + EOS + end + + def test_utime + bug6385 = '[ruby-core:44776]' + + mod_time_contents = Time.at 1306527039 + + file = Tempfile.new("utime") + file.close + path = file.path + + File.utime(File.atime(path), mod_time_contents, path) + stats = File.stat(path) + + file.open + file_mtime = file.mtime + file.close(true) + + assert_equal(mod_time_contents, file_mtime, bug6385) + 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| + file = File.join(tmpdir, "\u3042") + File.open(file, 'w'){} + assert_equal(File.chmod(0666, file), 1, bug5671) + end + end + + def test_file_open_permissions + Dir.mktmpdir(__method__.to_s) do |tmpdir| + tmp = File.join(tmpdir, 'x') + File.open(tmp, :mode => IO::RDWR | IO::CREAT | IO::BINARY, + :encoding => Encoding::ASCII_8BIT) do |x| + + assert_predicate(x, :autoclose?) + assert_equal Encoding::ASCII_8BIT, x.external_encoding + x.write 'hello' + + x.seek 0, IO::SEEK_SET + + assert_equal 'hello', x.read + + end + end + end + + def test_file_open_double_mode + assert_raise_with_message(ArgumentError, 'mode specified twice') { + File.open("a", 'w', :mode => 'rw+') + } + 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') + File.open(tmp, 'wb', :encoding => Encoding::EUC_JP) do |x| + assert_equal Encoding::EUC_JP, x.external_encoding + end + end + end + + def test_untainted_path + bug5374 = '[ruby-core:39745]' + cwd = ("./"*40+".".taint).dup.untaint + in_safe = proc {|safe| $SAFE = safe; File.stat(cwd)} + assert_not_send([cwd, :tainted?]) + (0..1).each do |level| + assert_nothing_raised(SecurityError, bug5374) {in_safe[level]} + end + ensure + $SAFE = 0 + end + + if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM + def test_long_unc + feature3399 = '[ruby-core:30623]' + path = File.expand_path(__FILE__) + path.sub!(%r'\A//', 'UNC/') + assert_nothing_raised(Errno::ENOENT, feature3399) do + File.stat("//?/#{path}") + end + end end + def test_open_nul + Dir.mktmpdir(__method__.to_s) do |tmpdir| + path = File.join(tmpdir, "foo") + assert_raise(ArgumentError) do + open(path + "\0bar", "w") {} + end + 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::EISDIR + skip 'O_TMPFILE not supported (EISDIR)' + 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 35d6bcff14..7d4eeb6d59 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -1,8 +1,15 @@ +# frozen_string_literal: false require "test/unit" require "fileutils" require "tmpdir" +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") assert_raise(Encoding::CompatibilityError) {yield d} @@ -13,63 +20,193 @@ class TestFileExhaustive < Test::Unit::TestCase def setup @dir = Dir.mktmpdir("rubytest-file") 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) - assert(n.is_a?(Integer), n.inspect + " is not Fixnum.") + assert_kind_of(Integer, n) end def assert_integer_or_nil(n) - assert(n.is_a?(Integer) || n.equal?(nil), n.inspect + " is neither Fixnum nor nil.") + msg = ->{"#{n.inspect} is neither Integer nor nil."} + if n + assert_kind_of(Integer, n, msg) + else + assert_nil(n, msg) + end 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) @@ -86,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) @@ -98,236 +235,414 @@ 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 + assert_nothing_raised { File.stat(DRIVE + "/") } + assert_nothing_raised { File.stat(DRIVE + "/.") } + assert_nothing_raised { File.stat(DRIVE + "/..") } + assert_raise(Errno::ENOENT) { File.stat(DRIVE + "/...") } + # want to test the root of empty drive, but there is no method to test it... + end if DRIVE + + def test_stat_dotted_prefix + Dir.mktmpdir do |dir| + prefix = File.join(dir, "...a") + Dir.mkdir(prefix) + assert_file.exist?(prefix) + + assert_nothing_raised { File.stat(prefix) } + + Dir.chdir(dir) do + assert_nothing_raised { File.stat(File.basename(prefix)) } + end + end + 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.directory?(@dir+"/..."))) - assert(!(File.directory?(@file))) - assert(!(File.directory?(@nofile))) + assert_file.directory?(@dir) + assert_file.not_directory?(@dir+"/...") + assert_file.not_directory?(regular_file) + assert_file.not_directory?(utf8_file) + assert_file.not_directory?(nofile) end - def test_pipe_p ## xxx - assert(!(File.pipe?(@dir))) - assert(!(File.pipe?(@file))) - assert(!(File.pipe?(@nofile))) + def test_pipe_p + assert_file.not_pipe?(@dir) + 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.symlink?(@dir))) - assert(!(File.symlink?(@file))) - assert(File.symlink?(@symlinkfile)) if @symlinkfile - assert(!(File.symlink?(@hardlinkfile))) if @hardlinkfile - assert(!(File.symlink?(@nofile))) + assert_file.not_symlink?(@dir) + 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 - assert(!(File.socket?(@dir))) - assert(!(File.socket?(@file))) - assert(!(File.socket?(@nofile))) + def test_socket_p + assert_file.not_socket?(@dir) + 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 - assert(!(File.blockdev?(@dir))) - assert(!(File.blockdev?(@file))) - assert(!(File.blockdev?(@nofile))) + def test_blockdev_p + assert_file.not_blockdev?(@dir) + 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 - assert(!(File.chardev?(@dir))) - assert(!(File.chardev?(@file))) - assert(!(File.chardev?(@nofile))) + def test_chardev_p + assert_file.not_chardev?(@dir) + 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.exist?(@nofile))) + assert_file.exist?(@dir) + 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.readable?(@file))) - File.chmod(0600, @file) - assert(File.readable?(@file)) - assert(!(File.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.readable_real?(@file))) - File.chmod(0600, @file) - assert(File.readable_real?(@file)) - assert(!(File.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.world_readable?(@file))) - File.chmod(0600, @file) - assert(!(File.world_readable?(@file))) - assert(!(File.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.writable?(@file))) - File.chmod(0600, @file) - assert(File.writable?(@file)) - assert(!(File.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.writable_real?(@file))) - File.chmod(0600, @file) - assert(File.writable_real?(@file)) - assert(!(File.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.world_writable?(@file))) - File.chmod(0600, @file) - assert(!(File.world_writable?(@file))) - assert(!(File.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.executable?(@file))) - assert(!(File.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.executable_real?(@file))) - assert(!(File.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.file?(@dir))) - assert(File.file?(@file)) - assert(!(File.file?(@nofile))) + assert_file.not_file?(@dir) + 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.zero?(@file))) - assert(File.zero?(@zerofile)) - assert(!(File.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.size?(@zerofile))) - assert(!(File.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 + 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 io_open(file_name) + # avoid File.open since we do not want #to_path + io = IO.for_fd(IO.sysopen(file_name)) + yield io + ensure + io&.close + end + + def test_suid + assert_file.not_setuid?(regular_file) + assert_file.not_setuid?(utf8_file) + if suidfile + assert_file.setuid?(suidfile) + io_open(suidfile) { |io| assert_file.setuid?(io) } + end + end + + def test_sgid + assert_file.not_setgid?(regular_file) + assert_file.not_setgid?(utf8_file) + if sgidfile + assert_file.setgid?(sgidfile) + io_open(sgidfile) { |io| assert_file.setgid?(io) } + end + end + + def test_sticky + assert_file.not_sticky?(regular_file) + assert_file.not_sticky?(utf8_file) + if stickyfile + assert_file.sticky?(stickyfile) + io_open(stickyfile) { |io| assert_file.sticky?(io) } + end 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_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_suid_sgid_sticky ## xxx - assert(!(File.setuid?(@file))) - assert(!(File.setgid?(@file))) - assert(!(File.sticky?(@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_identical_p - assert(File.identical?(@file, @file)) - assert(!(File.identical?(@file, @zerofile))) - assert(!(File.identical?(@file, @nofile))) - assert(!(File.identical?(@nofile, @file))) + 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 @@ -336,114 +651,633 @@ 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_hardlink - return unless @hardlinkfile - assert_equal("file", File.ftype(@hardlinkfile)) - assert_raise(Errno::EEXIST) { File.link(@file, @file) } + 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_symlink2 - return unless @symlinkfile - assert_equal(@file, File.readlink(@symlinkfile)) - assert_raise(Errno::EINVAL) { File.readlink(@file) } - assert_raise(Errno::ENOENT) { File.readlink(@nofile) } + 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(regular_file, regular_file) } + assert_raise(Errno::EEXIST) { File.link(utf8_file, utf8_file) } + end + + def test_readlink + 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) + end rescue NotImplementedError end + def test_readlink_long_path + return unless symlinkfile + bug9157 = '[ruby-core:58592] [Bug #9157]' + assert_separately(["-", symlinkfile, bug9157], "#{<<~begin}#{<<~"end;"}") + begin + symlinkfile, bug9157 = *ARGV + 100.step(1000, 100) do |n| + File.unlink(symlinkfile) + link = "foo"*n + begin + File.symlink(link, symlinkfile) + rescue Errno::ENAMETOOLONG + break + end + assert_equal(link, File.readlink(symlinkfile), bug9157) + end + 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.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))) + end + + if NTFS + def test_expand_path_ntfs + [regular_file, utf8_file].each do |file| + assert_equal(file, File.expand_path(file + " ")) + assert_equal(file, File.expand_path(file + ".")) + 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 - assert_kind_of(String, File.expand_path("~")) - unless /mingw|mswin/ =~ RUBY_PLATFORM - assert_raise(ArgumentError) { File.expand_path("~foo_bar_baz_unknown_user_wahaha") } - assert_raise(ArgumentError) { File.expand_path("~foo_bar_baz_unknown_user_wahaha", "/") } + end + + case RUBY_PLATFORM + when /darwin/ + def test_expand_path_hfs + ["\u{feff}", *"\u{2000}"..."\u{2100}"].each do |c| + file = regular_file + c + full_path = File.expand_path(file) + 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 + end + + if DRIVE + def test_expand_path_absolute + assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar")) + assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar")) + assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo')) end + else + def test_expand_path_absolute + assert_equal("/foo", File.expand_path('/foo')) + end + end + + def test_expand_path_memsize + bug9934 = '[ruby-core:63114] [Bug #9934]' + require "objspace" + path = File.expand_path("/foo") + assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934) + path = File.expand_path("/a"*25) + assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934) + end + + def test_expand_path_encoding + drive = (DRIVE ? 'C:' : '') + if Encoding.find("filesystem") == Encoding::CP1251 + a = "#{drive}/\u3042\u3044\u3046\u3048\u304a".encode("cp932") + else + a = "#{drive}/\u043f\u0440\u0438\u0432\u0435\u0442".encode("cp1251") + end + assert_equal(a, File.expand_path(a)) + 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| + assert_equal(expected.force_encoding(cp), File.expand_path(a.dup.force_encoding(cp)), cp) + end + + path = "\u3042\u3044\u3046\u3048\u304a".encode("EUC-JP") + assert_equal("#{Dir.pwd}/#{path}".encode("CP932"), File.expand_path(path).encode("CP932")) + + path = "\u3042\u3044\u3046\u3048\u304a".encode("CP51932") + assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path)) assert_incompatible_encoding {|d| File.expand_path(d)} end + def test_expand_path_encoding_filesystem + home = ENV["HOME"] + ENV["HOME"] = "#{DRIVE}/UserHome" + + path = "~".encode("US-ASCII") + dir = "C:/".encode("IBM437") + fs = Encoding.find("filesystem") + + assert_equal fs, File.expand_path(path).encoding + assert_equal fs, File.expand_path(path, dir).encoding + ensure + ENV["HOME"] = home + end + + UnknownUserHome = "~foo_bar_baz_unknown_user_wahaha".freeze + + def test_expand_path_home + assert_kind_of(String, File.expand_path("~")) if ENV["HOME"] + assert_raise(ArgumentError) { File.expand_path(UnknownUserHome) } + assert_raise(ArgumentError) { File.expand_path(UnknownUserHome, "/") } + begin + bug3630 = '[ruby-core:31537]' + home = ENV["HOME"] + home_drive = ENV["HOMEDRIVE"] + home_path = ENV["HOMEPATH"] + user_profile = ENV["USERPROFILE"] + ENV["HOME"] = nil + ENV["HOMEDRIVE"] = nil + ENV["HOMEPATH"] = nil + ENV["USERPROFILE"] = nil + ENV["HOME"] = "~" + assert_raise(ArgumentError, bug3630) { File.expand_path("~") } + ENV["HOME"] = "." + assert_raise(ArgumentError, bug3630) { File.expand_path("~") } + ensure + ENV["HOME"] = home + ENV["HOMEDRIVE"] = home_drive + ENV["HOMEPATH"] = home_path + ENV["USERPROFILE"] = user_profile + end + end + + def test_expand_path_home_dir_string + home = ENV["HOME"] + new_home = "#{DRIVE}/UserHome" + ENV["HOME"] = new_home + bug8034 = "[ruby-core:53168]" + + assert_equal File.join(new_home, "foo"), File.expand_path("foo", "~"), bug8034 + assert_equal File.join(new_home, "bar", "foo"), File.expand_path("foo", "~/bar"), bug8034 + + 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 + + if /mswin|mingw/ =~ RUBY_PLATFORM + def test_expand_path_home_memory_leak_in_path + assert_no_memory_leak_at_expand_path_home('', 'in path') + end + + def test_expand_path_home_memory_leak_in_base + assert_no_memory_leak_at_expand_path_home('".",', 'in base') + end + + def assert_no_memory_leak_at_expand_path_home(arg, message) + prep = 'ENV["HOME"] = "foo"*100' + assert_no_memory_leak([], prep, <<-TRY, "memory leaked at non-absolute home #{message}") + 10000.times do + begin + File.expand_path(#{arg}"~/a") + rescue ArgumentError => e + next + ensure + abort("ArgumentError (non-absolute home) expected") unless e + end + end + GC.start + TRY + end + end + + + 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") + end if DRIVE + + def test_expand_path_resolve_empty_string_current_directory + assert_equal(Dir.pwd, File.expand_path("")) + end + + def test_expand_path_resolve_dot_current_directory + assert_equal(Dir.pwd, File.expand_path(".")) + end + + def test_expand_path_resolve_file_name_relative_current_directory + assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo")) + end + + def test_ignore_nil_dir_string + assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo", nil)) + end + + def test_expand_path_resolve_file_name_and_dir_string_relative + assert_equal(File.join(Dir.pwd, "bar", "foo"), + File.expand_path("foo", "bar")) + end + + def test_expand_path_cleanup_dots_file_name + bug = "[ruby-talk:18512]" + + assert_equal(File.join(Dir.pwd, ".a"), File.expand_path(".a"), bug) + assert_equal(File.join(Dir.pwd, "..a"), File.expand_path("..a"), bug) + + if DRIVE + # cleanup dots only on Windows + assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a."), bug) + assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a.."), bug) + else + assert_equal(File.join(Dir.pwd, "a."), File.expand_path("a."), bug) + assert_equal(File.join(Dir.pwd, "a.."), File.expand_path("a.."), bug) + end + end + + def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_a_complete_path + 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}")) + end + + def test_expand_path_ignores_supplied_dir_if_path_contains_a_drive_letter + 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/")) + end + + def test_expand_path_removes_trailing_spaces_from_absolute_path + 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 + assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar")) + end if DRIVE + + def test_expand_path_converts_a_pathname_which_starts_with_a_slash_and_unc_pathname + assert_equal("//foo", File.expand_path('//foo', "//bar")) + assert_equal("//bar/foo", File.expand_path('/foo', "//bar")) + assert_equal("//foo", File.expand_path('//foo', "/bar")) + end if DRIVE + + def test_expand_path_converts_a_dot_with_unc_dir + assert_equal("//", File.expand_path('.', "//")) + end + + def test_expand_path_preserves_unc_path_root + assert_equal("//", File.expand_path("//")) + assert_equal("//", File.expand_path("//.")) + assert_equal("//", File.expand_path("//..")) + end + + def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_host_share + assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar")) + end if DRIVE + + def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_a_current_drive + assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo')) + end + + def test_expand_path_returns_tainted_strings_or_not + assert_equal(true, File.expand_path('foo').tainted?) + assert_equal(true, File.expand_path('foo'.taint).tainted?) + assert_equal(true, File.expand_path('/foo'.taint).tainted?) + assert_equal(true, File.expand_path('foo', 'bar').tainted?) + assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?) + assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?) + assert_equal(true, File.expand_path('~').tainted?) if ENV["HOME"] + + if DRIVE + assert_equal(true, File.expand_path('/foo').tainted?) + assert_equal(false, File.expand_path('//foo').tainted?) + assert_equal(true, File.expand_path('C:/foo'.taint).tainted?) + assert_equal(false, File.expand_path('C:/foo').tainted?) + assert_equal(true, File.expand_path('foo', '/bar').tainted?) + assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?) + assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?) + assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?) + assert_equal(false, File.expand_path('C:/foo/../bar').tainted?) + assert_equal(false, File.expand_path('foo', '//bar').tainted?) + else + assert_equal(false, File.expand_path('/foo').tainted?) + assert_equal(false, File.expand_path('foo', '/bar').tainted?) + end + end + + def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base + old_home = ENV["HOME"] + home = ENV["HOME"] = "#{DRIVE}/UserHome" + assert_equal(home, File.expand_path("~")) + assert_equal(home, File.expand_path("~", "C:/FooBar")) + assert_equal(File.join(home, "a"), File.expand_path("~/a", "C:/FooBar")) + ensure + ENV["HOME"] = old_home + end + + def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_unc_home + old_home = ENV["HOME"] + unc_home = ENV["HOME"] = "//UserHome" + assert_equal(unc_home, File.expand_path("~")) + ensure + ENV["HOME"] = old_home + end if DRIVE + + def test_expand_path_does_not_modify_a_home_string_argument + old_home = ENV["HOME"] + home = ENV["HOME"] = "#{DRIVE}/UserHome" + str = "~/a" + assert_equal("#{home}/a", File.expand_path(str)) + assert_equal("~/a", str) + ensure + ENV["HOME"] = old_home + end + + def test_expand_path_raises_argument_error_for_any_supplied_username + bug = '[ruby-core:39597]' + assert_raise(ArgumentError, bug) { File.expand_path("~anything") } + end if DRIVE + + def test_expand_path_for_existent_username + user = ENV['USER'] + skip "ENV['USER'] is not set" unless user + assert_equal(ENV['HOME'], File.expand_path("~#{user}")) + end unless DRIVE + + def test_expand_path_error_for_nonexistent_username + user = "\u{3086 3046 3066 3044}:\u{307F 3084 304A 3046}" + assert_raise_with_message(ArgumentError, /#{user}/) {File.expand_path("~#{user}")} + end unless DRIVE + + def test_expand_path_error_for_non_absolute_home + old_home = ENV["HOME"] + ENV["HOME"] = "./UserHome" + assert_raise_with_message(ArgumentError, /non-absolute home/) {File.expand_path("~")} + ensure + ENV["HOME"] = old_home + end + + def test_expand_path_raises_a_type_error_if_not_passed_a_string_type + assert_raise(TypeError) { File.expand_path(1) } + assert_raise(TypeError) { File.expand_path(nil) } + assert_raise(TypeError) { File.expand_path(true) } + end + + def test_expand_path_expands_dot_dir + assert_equal("#{DRIVE}/dir", File.expand_path("#{DRIVE}/./dir")) + end + + def test_expand_path_does_not_expand_wildcards + assert_equal("#{DRIVE}/*", File.expand_path("./*", "#{DRIVE}/")) + assert_equal("#{Dir.pwd}/*", File.expand_path("./*", Dir.pwd)) + assert_equal("#{DRIVE}/?", File.expand_path("./?", "#{DRIVE}/")) + assert_equal("#{Dir.pwd}/?", File.expand_path("./?", Dir.pwd)) + end if DRIVE + + def test_expand_path_does_not_modify_the_string_argument + str = "./a/b/../c" + assert_equal("#{Dir.pwd}/a/c", File.expand_path(str, Dir.pwd)) + assert_equal("./a/b/../c", str) + end + + def test_expand_path_returns_a_string_when_passed_a_string_subclass + sub = Class.new(String) + str = sub.new "./a/b/../c" + path = File.expand_path(str, Dir.pwd) + assert_equal("#{Dir.pwd}/a/c", path) + assert_instance_of(String, path) + end + + def test_expand_path_accepts_objects_that_have_a_to_path_method + klass = Class.new { def to_path; "a/b/c"; end } + obj = klass.new + assert_equal("#{Dir.pwd}/a/b/c", File.expand_path(obj)) + end + + def test_expand_path_with_drive_letter + bug10858 = '[ruby-core:68130] [Bug #10858]' + assert_match(%r'/bar/foo\z'i, File.expand_path('z:foo', 'bar'), bug10858) + 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", ".*")) + end + + if NTFS + def test_basename_strip + [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 end + else + def test_basename_strip + [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 + end + + if File::ALT_SEPARATOR == '\\' + def test_basename_backslash + a = "foo/\225\\\\" + [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected| + assert_equal(expected.force_encoding(cp), File.basename(a.dup.force_encoding(cp)), cp) + end + end + end + def test_basename_encoding assert_incompatible_encoding {|d| File.basename(d)} assert_incompatible_encoding {|d| File.basename(d, ".*")} assert_raise(Encoding::CompatibilityError) {File.basename("foo.ext", ".*".encode("utf-16le"))} + + s = "foo\x93_a".force_encoding("cp932") + assert_equal(s, File.basename(s, "_a")) + + s = "\u4032.\u3024" + assert_equal(s, File.basename(s, ".\x95\\".force_encoding("cp932"))) 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("")) + end + + def test_dirname_encoding assert_incompatible_encoding {|d| File.dirname(d)} end + if File::ALT_SEPARATOR == '\\' + def test_dirname_backslash + a = "\225\\\\foo" + [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected| + assert_equal(expected.force_encoding(cp), File.dirname(a.dup.force_encoding(cp)), cp) + end + end + 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| @@ -465,58 +1299,164 @@ 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 s = "foo" + File::SEPARATOR + "bar" + File::SEPARATOR + "baz" assert_equal(s, File.join("foo", "bar", "baz")) assert_equal(s, File.join(["foo", "bar", "baz"])) + o = Object.new def o.to_path; "foo"; end assert_equal(s, File.join(o, "bar", "baz")) assert_equal(s, File.join("foo" + File::SEPARATOR, "bar", File::SEPARATOR + "baz")) end + def test_join_alt_separator + if File::ALT_SEPARATOR == '\\' + a = "\225\\" + b = "foo" + [%W"cp437 \225\\foo", %W"cp932 \225\\/foo"].each do |cp, expected| + assert_equal(expected.force_encoding(cp), File.join(a.dup.force_encoding(cp), b.dup.force_encoding(cp)), cp) + end + end + end + + def test_join_ascii_incompatible + bug7168 = '[ruby-core:48012]' + names = %w"a b".map {|s| s.encode(Encoding::UTF_16LE)} + assert_raise(Encoding::CompatibilityError, bug7168) {File.join(*names)} + assert_raise(Encoding::CompatibilityError, bug7168) {File.join(names)} + + a = Object.new + b = names[1] + names = [a, "b"] + a.singleton_class.class_eval do + define_method(:to_path) do + names[1] = b + "a" + end + end + 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)) @@ -543,28 +1483,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) @@ -578,11 +1521,7 @@ class TestFileExhaustive < Test::Unit::TestCase assert_integer_or_nil(fs1.rdev_minor) assert_integer(fs1.ino) assert_integer(fs1.mode) - unless /emx|mswin|mingw/ =~ RUBY_PLATFORM - # on Windows, nlink is always 1. but this behavior will be changed - # in the future. - assert_equal(@hardlinkfile ? 2 : 1, fs1.nlink) - end + assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) assert_integer(fs1.uid) assert_integer(fs1.gid) assert_equal(3, fs1.size) @@ -593,179 +1532,197 @@ 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 + # test for special files such as pagefile.sys on Windows + assert_nothing_raised do + Dir::glob("C:/*.sys") {|f| File::Stat.new(f) } + end + end if DRIVE + def test_path_check assert_nothing_raised { ENV["PATH"] } end - def test_find_file - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - load(@file) - end.join - end - 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 2aee65c211..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 @@ -87,14 +95,21 @@ class TestFixnum < Test::Unit::TestCase next if b == 0 q, r = a.divmod(b) assert_equal(a, b*q+r) - assert(r.abs < b.abs) - assert(0 < b ? (0 <= r && r < b) : (b < r && r <= 0)) + assert_operator(r.abs, :<, b.abs) + if 0 < b + assert_operator(r, :>=, 0) + assert_operator(r, :<, b) + else + assert_operator(r, :>, b) + assert_operator(r, :<=, 0) + end assert_equal(q, a/b) assert_equal(q, a.div(b)) assert_equal(r, a%b) assert_equal(r, a.modulo(b)) } } + assert_raise(FloatDomainError) { 2.divmod(Float::NAN) } end def test_not @@ -157,6 +172,7 @@ class TestFixnum < Test::Unit::TestCase assert_equal(0, 1 / (2**32)) assert_equal(0, 1.div(2**32)) + assert_kind_of(Float, 1.quo(2.0)) assert_equal(0.5, 1.quo(2.0)) assert_equal(0.5, 1 / 2.0) assert_equal(0, 1.div(2.0)) @@ -194,39 +210,143 @@ class TestFixnum < Test::Unit::TestCase end def test_cmp - assert(1 != nil) + assert_operator(1, :!=, nil) 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) - assert(1.send(:>, 0)) - assert(!(1.send(:>, 1))) - assert(!(1.send(:>, 2))) - assert(!(1.send(:>, 4294967296))) - assert(1.send(:>, 0.0)) - assert_raise(ArgumentError) { 1.send(:>, nil) } - - assert(1.send(:>=, 0)) - assert(1.send(:>=, 1)) - assert(!(1.send(:>=, 2))) - assert(!(1.send(:>=, 4294967296))) - assert(1.send(:>=, 0.0)) - assert_raise(ArgumentError) { 1.send(:>=, nil) } - - assert(!(1.send(:<, 0))) - assert(!(1.send(:<, 1))) - assert(1.send(:<, 2)) - assert(1.send(:<, 4294967296)) - assert(!(1.send(:<, 0.0))) - assert_raise(ArgumentError) { 1.send(:<, nil) } - - assert(!(1.send(:<=, 0))) - assert(1.send(:<=, 1)) - assert(1.send(:<=, 2)) - assert(1.send(:<=, 4294967296)) - assert(!(1.send(:<=, 0.0))) - assert_raise(ArgumentError) { 1.send(:<=, nil) } + assert_operator(1, :>, 0) + assert_not_operator(1, :>, 1) + assert_not_operator(1, :>, 2) + assert_not_operator(1, :>, 4294967296) + assert_operator(1, :>, 0.0) + assert_raise(ArgumentError) { 1 > nil } + + assert_operator(1, :>=, 0) + assert_operator(1, :>=, 1) + assert_not_operator(1, :>=, 2) + assert_not_operator(1, :>=, 4294967296) + assert_operator(1, :>=, 0.0) + assert_raise(ArgumentError) { 1 >= nil } + + assert_not_operator(1, :<, 0) + assert_not_operator(1, :<, 1) + assert_operator(1, :<, 2) + assert_operator(1, :<, 4294967296) + assert_not_operator(1, :<, 0.0) + assert_raise(ArgumentError) { 1 < nil } + + assert_not_operator(1, :<=, 0) + assert_operator(1, :<=, 1) + assert_operator(1, :<=, 2) + assert_operator(1, :<=, 4294967296) + assert_not_operator(1, :<=, 0.0) + assert_raise(ArgumentError) { 1 <= nil } + end + + class DummyNumeric < Numeric + def to_int + 1 + end + end + + def test_and_with_float + assert_raise(TypeError) { 1 & 1.5 } + end + + def test_and_with_rational + assert_raise(TypeError, "#1792") { 1 & Rational(3, 2) } + end + + def test_and_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { 1 & DummyNumeric.new } + end + + def test_or_with_float + assert_raise(TypeError) { 1 | 1.5 } + end + + def test_or_with_rational + assert_raise(TypeError, "#1792") { 1 | Rational(3, 2) } + end + + def test_or_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { 1 | DummyNumeric.new } + end + + def test_xor_with_float + assert_raise(TypeError) { 1 ^ 1.5 } + end + + def test_xor_with_rational + assert_raise(TypeError, "#1792") { 1 ^ Rational(3, 2) } + end + + def test_xor_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { 1 ^ DummyNumeric.new } + end + + def test_singleton_method + assert_raise(TypeError) { a = 1; def a.foo; end } + end + + def test_frozen + assert_equal(true, 1.frozen?) + end + + def assert_eql(a, b, mess) + assert a.eql?(b), "expected #{a} & #{b} to be eql? #{mess}" + end + + def test_power_of_1_and_minus_1 + bug5715 = '[ruby-core:41498]' + big = 1 << 66 + assert_eql 1, 1 ** -big , bug5715 + assert_eql 1, (-1) ** -big , bug5715 + assert_eql (-1), (-1) ** -(big+1), bug5715 + end + + def test_power_of_0 + bug5713 = '[ruby-core:41494]' + big = 1 << 66 + 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 new file mode 100644 index 0000000000..8ea6416bdb --- /dev/null +++ b/test/ruby/test_flip.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFlip < Test::Unit::TestCase + def setup + @verbose_bak, $VERBOSE = $VERBOSE, nil + end + + def teardown + $VERBOSE = @verbose_bak + end + + def test_flip_flop + eval <<-END + 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 + end + + def test_hidden_key + bug6899 = '[ruby-core:47253]' + foo = "foor" + bar = "bar" + assert_nothing_raised(NotImplementedError, bug6899) do + 2000.times {eval %[(foo..bar) ? 1 : 2]} + end + foo = bar + end + + def test_shared_eval + bug7671 = '[ruby-core:51296]' + vs = (1..9).to_a + eval("vs.select {|n| if n==2..n==16 then 1 end}") + v = eval("vs.select {|n| if n==3..n==6 then 1 end}") + assert_equal([*3..6], v, bug7671) + end + + def test_shared_thread + ff = eval("proc {|n| true if n==3..n==5}") + v = 1..9 + a = true + th = Thread.new { + v.select {|i| + Thread.pass while a + ff[i].tap {a = true} + } + } + v1 = v.select {|i| + Thread.pass until a + ff[i].tap {a = false} + } + v2 = th.value + expected = [3, 4, 5] + mesg = 'flip-flop should be separated per threads' + 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 03d9c94766..0b2e4df05b 100644 --- a/test/ruby/test_float.rb +++ b/test/ruby/test_float.rb @@ -1,6 +1,9 @@ +# frozen_string_literal: false require 'test/unit' class TestFloat < Test::Unit::TestCase + include EnvUtil + def test_float assert_equal(2, 2.6.floor) assert_equal(-3, (-2.6).floor) @@ -10,18 +13,20 @@ class TestFloat < Test::Unit::TestCase assert_equal(-2, (-2.6).truncate) assert_equal(3, 2.6.round) assert_equal(-2, (-2.4).truncate) - assert((13.4 % 1 - 0.4).abs < 0.0001) + 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) extend Test::Unit::Assertions - assert(x != y) - assert_equal(false, (x < y)) - assert_equal(false, (x > y)) - assert_equal(false, (x <= y)) - assert_equal(false, (x >= y)) + assert_operator(x, :!=, y) + assert_not_operator(x, :<, y) + assert_not_operator(x, :>, y) + assert_not_operator(x, :<=, y) + assert_not_operator(x, :>=, y) end def test_nan nan = Float::NAN @@ -54,27 +59,76 @@ class TestFloat < Test::Unit::TestCase assert_equal(a == b, b == a) end + def test_cmp_int + 100.times {|i| + int0 = 1 << i + [int0, -int0].each {|int| + flt = int.to_f + bigger = int + 1 + smaller = int - 1 + assert_operator(flt, :==, int) + assert_operator(flt, :>, smaller) + assert_operator(flt, :>=, smaller) + assert_operator(flt, :<, bigger) + assert_operator(flt, :<=, bigger) + assert_equal(0, flt <=> int) + assert_equal(-1, flt <=> bigger) + assert_equal(1, flt <=> smaller) + assert_operator(int, :==, flt) + assert_operator(bigger, :>, flt) + assert_operator(bigger, :>=, flt) + assert_operator(smaller, :<, flt) + assert_operator(smaller, :<=, flt) + assert_equal(0, int <=> flt) + assert_equal(-1, smaller <=> flt) + assert_equal(1, bigger <=> flt) + [ + [int, flt + 0.5, bigger], + [smaller, flt - 0.5, int] + ].each {|smaller2, flt2, bigger2| + next if flt2 == flt2.round + assert_operator(flt2, :!=, smaller2) + assert_operator(flt2, :!=, bigger2) + assert_operator(flt2, :>, smaller2) + assert_operator(flt2, :>=, smaller2) + assert_operator(flt2, :<, bigger2) + assert_operator(flt2, :<=, bigger2) + assert_equal(-1, flt2 <=> bigger2) + assert_equal(1, flt2 <=> smaller2) + assert_operator(smaller2, :!=, flt2) + assert_operator(bigger2, :!=, flt2) + assert_operator(bigger2, :>, flt2) + assert_operator(bigger2, :>=, flt2) + assert_operator(smaller2, :<, flt2) + assert_operator(smaller2, :<=, flt2) + assert_equal(-1, smaller2 <=> flt2) + assert_equal(1, bigger2 <=> flt2) + } + } + } + end + def test_strtod a = Float("0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("0.0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("+0.0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("-0.0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("0.0000000000000000001") - assert(a != 0.0) + assert_not_equal(0.0, a) a = Float("+0.0000000000000000001") - assert(a != 0.0) + assert_not_equal(0.0, a) a = Float("-0.0000000000000000001") - assert(a != 0.0) + assert_not_equal(0.0, a) a = Float(".0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("+.0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) a = Float("-.0") - assert(a.abs < Float::EPSILON) + assert_in_delta(a, 0, Float::EPSILON) assert_raise(ArgumentError){Float("0.")} assert_raise(ArgumentError){Float("+0.")} assert_raise(ArgumentError){Float("-0.")} @@ -85,20 +139,56 @@ class TestFloat < Test::Unit::TestCase assert_raise(ArgumentError){Float("-.")} assert_raise(ArgumentError){Float("1e")} assert_raise(ArgumentError){Float("1__1")} + assert_raise(ArgumentError){Float("1.")} + assert_raise(ArgumentError){Float("1.e+00")} + assert_raise(ArgumentError){Float("0x1.p+0")} # add expected behaviour here. assert_equal(10, Float("1_0")) assert_equal([ 0.0].pack('G'), [Float(" 0x0p+0").to_f].pack('G')) assert_equal([-0.0].pack('G'), [Float("-0x0p+0").to_f].pack('G')) assert_equal(255.0, Float("0Xff")) - assert_equal(255.5, Float("0Xff.8")) - assert_equal(1.0, Float("0X1.P+0")) assert_equal(1024.0, Float("0x1p10")) assert_equal(1024.0, Float("0x1p+10")) assert_equal(0.0009765625, Float("0x1p-10")) assert_equal(2.6881171418161356e+43, Float("0x1.3494a9b171bf5p+144")) assert_equal(-3.720075976020836e-44, Float("-0x1.a8c1f14e2af5dp-145")) - + assert_equal(31.0*2**1019, Float("0x0."+("0"*268)+"1fp2099")) + assert_equal(31.0*2**1019, Float("0x0."+("0"*600)+"1fp3427")) + assert_equal(-31.0*2**1019, Float("-0x0."+("0"*268)+"1fp2099")) + assert_equal(-31.0*2**1019, Float("-0x0."+("0"*600)+"1fp3427")) + suppress_warning do + assert_equal(31.0*2**-1027, Float("0x1f"+("0"*268)+".0p-2099")) + assert_equal(31.0*2**-1027, Float("0x1f"+("0"*600)+".0p-3427")) + assert_equal(-31.0*2**-1027, Float("-0x1f"+("0"*268)+".0p-2099")) + assert_equal(-31.0*2**-1027, Float("-0x1f"+("0"*600)+".0p-3427")) + end + + assert_equal(1.0e10, Float("1.0_"+"00000"*Float::DIG+"e10")) + + z = "0" * (Float::DIG * 4 + 10) + all_assertions_foreach("long invalid string", "1.0", "1.0e", "1.0e-", "1.0e+") do |n| + assert_raise(ArgumentError, n += z + "A") {Float(n)} + assert_raise(ArgumentError, n += z + ".0") {Float(n)} + end + + x = nil + 2000.times do + x = Float("0x"+"0"*30) + break unless x == 0.0 + end + assert_equal(0.0, x, ->{"%a" % x}) + x = nil + 2000.times do + begin + x = Float("0x1."+"0"*270) + rescue ArgumentError => e + raise unless /"0x1\.0{270}"/ =~ e.message + else + break + end + end + assert_nil(x, ->{"%a" % x}) end def test_divmod @@ -106,6 +196,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 @@ -113,6 +205,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 @@ -127,6 +222,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 @@ -153,6 +250,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 @@ -160,6 +259,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 @@ -167,6 +268,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 @@ -174,6 +276,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 @@ -184,6 +287,24 @@ class TestFloat < Test::Unit::TestCase assert_raise(TypeError) { 2.0.send(:%, nil) } end + def test_modulo3 + bug6048 = '[ruby-core:42726]' + 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, 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 assert_equal([1.0, 0.0], 2.0.divmod(2)) assert_equal([1.0, 0.0], 2.0.divmod((2**32).coerce(2).first)) @@ -207,15 +328,15 @@ class TestFloat < Test::Unit::TestCase def test_eql inf = Float::INFINITY nan = Float::NAN - assert(1.0.eql?(1.0)) - assert(inf.eql?(inf)) - assert(!(nan.eql?(nan))) - assert(!(1.0.eql?(nil))) + assert_operator(1.0, :eql?, 1.0) + assert_operator(inf, :eql?, inf) + assert_not_operator(nan, :eql?, nan) + assert_not_operator(1.0, :eql?, nil) - assert(1.0 == 1) - assert(1.0 != 2**32) - assert(1.0 != nan) - assert(1.0 != nil) + assert_equal(1.0, 1) + assert_not_equal(1.0, 2**32) + assert_not_equal(1.0, nan) + assert_not_equal(1.0, nil) end def test_cmp @@ -239,6 +360,20 @@ class TestFloat < Test::Unit::TestCase assert_equal(-1, (Float::MAX.to_i*2) <=> inf) assert_equal(1, (-Float::MAX.to_i*2) <=> -inf) + bug3609 = '[ruby-core:31470]' + def (pinf = Object.new).infinite?; +1 end + def (ninf = Object.new).infinite?; -1 end + def (fin = Object.new).infinite?; nil end + nonum = Object.new + assert_equal(0, inf <=> pinf, bug3609) + assert_equal(1, inf <=> fin, bug3609) + assert_equal(1, inf <=> ninf, bug3609) + assert_nil(inf <=> nonum, bug3609) + assert_equal(-1, -inf <=> pinf, bug3609) + assert_equal(-1, -inf <=> fin, bug3609) + assert_equal(0, -inf <=> ninf, bug3609) + assert_nil(-inf <=> nonum, bug3609) + assert_raise(ArgumentError) { 1.0 > nil } assert_raise(ArgumentError) { 1.0 >= nil } assert_raise(ArgumentError) { 1.0 < nil } @@ -246,8 +381,32 @@ class TestFloat < Test::Unit::TestCase end def test_zero_p - assert(0.0.zero?) - assert(!(1.0.zero?)) + assert_predicate(0.0, :zero?) + 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 @@ -259,9 +418,9 @@ class TestFloat < Test::Unit::TestCase def test_finite_p inf = Float::INFINITY - assert(!(inf.finite?)) - assert(!((-inf).finite?)) - assert(1.0.finite?) + assert_not_predicate(inf, :finite?) + assert_not_predicate(-inf, :finite?) + assert_predicate(1.0, :finite?) end def test_floor_ceil_round_truncate @@ -290,11 +449,119 @@ class TestFloat < Test::Unit::TestCase assert_raise(FloatDomainError) { inf.ceil } assert_raise(FloatDomainError) { inf.round } assert_raise(FloatDomainError) { inf.truncate } + end + def test_round_with_precision 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(-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)) + assert_equal(1.0e-300, 1.1e-300.round(300)) + assert_equal(-1.0e-300, -1.1e-300.round(300)) + + bug5227 = '[ruby-core:39093]' + assert_equal(42.0, 42.0.round(308), bug5227) + assert_equal(1.0e307, 1.0e307.round(2), bug5227) + + assert_raise(TypeError) {1.0.round("4")} + 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 = [ @@ -411,33 +678,187 @@ class TestFloat < Test::Unit::TestCase def test_round VS.each {|f| + msg = "round(#{f})" i = f.round if f < 0 - assert_operator(i, :<, 0) + assert_operator(i, :<, 0, msg) else - assert_operator(i, :>, 0) + assert_operator(i, :>, 0, msg) end d = f - i - assert_operator(-0.5, :<=, d) - assert_operator(d, :<=, 0.5) + assert_operator(-0.5, :<=, d, msg) + assert_operator(d, :<=, 0.5, msg) + } + 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_equal(1, Float(([1] * 10000).join).infinite?) - assert(!Float(([1] * 10000).join("_")).infinite?) # is it really OK? + 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") } - assert_equal(1, Float("1e10_00").infinite?) + assert_equal(15.9375, Float('0xf.fp0')) + assert_raise(ArgumentError) { Float('0x') } + assert_equal(15, Float('0xf')) + assert_equal(15, Float('0xfp0')) + assert_raise(ArgumentError) { Float('0xfp') } + assert_raise(ArgumentError) { Float('0xf.') } + assert_raise(ArgumentError) { Float('0xf.p') } + assert_raise(ArgumentError) { Float('0xf.p0') } + assert_raise(ArgumentError) { Float('0xf.f') } + assert_raise(ArgumentError) { Float('0xf.fp') } + assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) + assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?) assert_raise(TypeError) { Float(nil) } + assert_raise(TypeError) { Float(:test) } o = Object.new def o.to_f; inf = Float::INFINITY; inf/inf; end - assert(Float(o).nan?) + assert_predicate(Float(o), :nan?) + end + + def test_invalid_str + bug4310 = '[ruby-core:34820]' + assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}} + end + + def test_Float_with_exception_keyword + assert_raise(ArgumentError) { + Float(".", exception: true) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Float(".", exception: false)) + } + assert_raise(RangeError) { + Float(1i, exception: true) + } + assert_nothing_raised(RangeError) { + assert_equal(nil, Float(1i, exception: false)) + } + assert_raise(TypeError) { + Float(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(:test, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + o = Object.new + def o.to_f; 3.14; end + assert_equal(3.14, Float(o, exception: false)) + } + assert_nothing_raised(RuntimeError) { + o = Object.new + def o.to_f; raise; end + assert_equal(nil, Float(o, exception: false)) + } end def test_num2dbl - assert_raise(TypeError) do + assert_raise(ArgumentError, "comparison of String with 0 failed") do 1.0.step(2.0, "0.5") {} end assert_raise(TypeError) do @@ -450,4 +871,112 @@ class TestFloat < Test::Unit::TestCase sleep(0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1) end end + + def test_step + 1000.times do + a = rand + b = a+rand*1000 + s = (b - a) / 10 + assert_equal(11, (a..b).step(s).to_a.length) + end + + (1.0..12.7).step(1.3).each do |n| + assert_operator(n, :<=, 12.7) + end + + assert_equal([5.0, 4.0, 3.0, 2.0], 5.0.step(1.5, -1).to_a) + end + + def test_step2 + assert_equal([0.0], 0.0.step(1.0, Float::INFINITY).to_a) + end + + def test_step_excl + 1000.times do + a = rand + b = a+rand*1000 + s = (b - a) / 10 + assert_equal(10, (a...b).step(s).to_a.length) + end + + assert_equal([1.0, 2.9, 4.8, 6.699999999999999], (1.0...6.8).step(1.9).to_a) + + e = 1+1E-12 + (1.0 ... e).step(1E-16) do |n| + assert_operator(n, :<=, e) + end + end + + def test_singleton_method + # flonum on 64bit platform + assert_raise(TypeError) { a = 1.0; def a.foo; end } + # always not flonum + assert_raise(TypeError) { a = Float::INFINITY; def a.foo; end } + end + + def test_long_string + assert_separately([], <<-'end;') + assert_in_epsilon(10.0, ("1."+"1"*300000).to_f*9) + 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) + assert_operator(+0.0, :eql?, -0.0) + 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 e5f5ba6a4f..16f1076e48 100644 --- a/test/ruby/test_fnmatch.rb +++ b/test/ruby/test_fnmatch.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestFnmatch < Test::Unit::TestCase @@ -9,98 +10,160 @@ class TestFnmatch < Test::Unit::TestCase assert_equal(t.include?(i.chr), !File.fnmatch("[!#{s}]", i.chr, File::FNM_DOTMATCH)) end end + def test_fnmatch - assert(File.fnmatch('\[1\]' , '[1]'), "[ruby-dev:22819]") - assert(File.fnmatch('*?', 'a'), "[ruby-dev:22815]") - assert(File.fnmatch('*/', 'a/')) - assert(File.fnmatch('\[1\]' , '[1]', File::FNM_PATHNAME)) - assert(File.fnmatch('*?', 'a', File::FNM_PATHNAME)) - assert(File.fnmatch('*/', 'a/', File::FNM_PATHNAME)) + assert_file.for("[ruby-dev:22819]").fnmatch('\[1\]' , '[1]') + assert_file.for("[ruby-dev:22815]").fnmatch('*?', 'a') + assert_file.fnmatch('*/', 'a/') + assert_file.fnmatch('\[1\]' , '[1]', File::FNM_PATHNAME) + assert_file.fnmatch('*?', 'a', File::FNM_PATHNAME) + assert_file.fnmatch('*/', 'a/', File::FNM_PATHNAME) + end + + def test_text # text - assert(File.fnmatch('cat', 'cat')) - assert(!File.fnmatch('cat', 'category')) - assert(!File.fnmatch('cat', 'wildcat')) + assert_file.fnmatch('cat', 'cat') + assert_file.not_fnmatch('cat', 'category') + assert_file.not_fnmatch('cat', 'wildcat') + end + + def test_any_one # '?' matches any one character - assert(File.fnmatch('?at', 'cat')) - assert(File.fnmatch('c?t', 'cat')) - assert(File.fnmatch('ca?', 'cat')) - assert(File.fnmatch('?a?', 'cat')) - assert(!File.fnmatch('c??t', 'cat')) - assert(!File.fnmatch('??at', 'cat')) - assert(!File.fnmatch('ca??', 'cat')) + assert_file.fnmatch('?at', 'cat') + assert_file.fnmatch('c?t', 'cat') + assert_file.fnmatch('ca?', 'cat') + assert_file.fnmatch('?a?', 'cat') + assert_file.not_fnmatch('c??t', 'cat') + assert_file.not_fnmatch('??at', 'cat') + assert_file.not_fnmatch('ca??', 'cat') + end + + def test_any_chars # '*' matches any number (including 0) of any characters - assert(File.fnmatch('c*', 'cats')) - assert(File.fnmatch('c*ts', 'cats')) - assert(File.fnmatch('*ts', 'cats')) - assert(File.fnmatch('*c*a*t*s*', 'cats')) - assert(!File.fnmatch('c*t', 'cats')) - assert(!File.fnmatch('*abc', 'abcabz')) - assert(File.fnmatch('*abz', 'abcabz')) - assert(!File.fnmatch('a*abc', 'abc')) - assert(File.fnmatch('a*bc', 'abc')) - assert(!File.fnmatch('a*bc', 'abcd')) + assert_file.fnmatch('c*', 'cats') + assert_file.fnmatch('c*ts', 'cats') + assert_file.fnmatch('*ts', 'cats') + assert_file.fnmatch('*c*a*t*s*', 'cats') + assert_file.not_fnmatch('c*t', 'cats') + assert_file.not_fnmatch('*abc', 'abcabz') + assert_file.fnmatch('*abz', 'abcabz') + assert_file.not_fnmatch('a*abc', 'abc') + assert_file.fnmatch('a*bc', 'abc') + assert_file.not_fnmatch('a*bc', 'abcd') + end + + def test_char_class # [seq] : matches any character listed between bracket # [!seq] or [^seq] : matches any character except those listed between bracket bracket_test("bd-gikl-mosv-x", "bdefgiklmosvwx") + end + + def test_escape # escaping character - assert(File.fnmatch('\?', '?')) - assert(!File.fnmatch('\?', '\?')) - assert(!File.fnmatch('\?', 'a')) - assert(!File.fnmatch('\?', '\a')) - assert(File.fnmatch('\*', '*')) - assert(!File.fnmatch('\*', '\*')) - assert(!File.fnmatch('\*', 'cats')) - assert(!File.fnmatch('\*', '\cats')) - assert(File.fnmatch('\a', 'a')) - assert(!File.fnmatch('\a', '\a')) - assert(File.fnmatch('[a\-c]', 'a')) - assert(File.fnmatch('[a\-c]', '-')) - assert(File.fnmatch('[a\-c]', 'c')) - assert(!File.fnmatch('[a\-c]', 'b')) - assert(!File.fnmatch('[a\-c]', '\\')) + assert_file.fnmatch('\?', '?') + assert_file.not_fnmatch('\?', '\?') + assert_file.not_fnmatch('\?', 'a') + assert_file.not_fnmatch('\?', '\a') + assert_file.fnmatch('\*', '*') + assert_file.not_fnmatch('\*', '\*') + assert_file.not_fnmatch('\*', 'cats') + assert_file.not_fnmatch('\*', '\cats') + assert_file.fnmatch('\a', 'a') + assert_file.not_fnmatch('\a', '\a') + assert_file.fnmatch('[a\-c]', 'a') + assert_file.fnmatch('[a\-c]', '-') + assert_file.fnmatch('[a\-c]', 'c') + assert_file.not_fnmatch('[a\-c]', 'b') + assert_file.not_fnmatch('[a\-c]', '\\') + end + + def test_fnm_escape # escaping character loses its meaning if FNM_NOESCAPE is set - assert(!File.fnmatch('\?', '?', File::FNM_NOESCAPE)) - assert(File.fnmatch('\?', '\?', File::FNM_NOESCAPE)) - assert(!File.fnmatch('\?', 'a', File::FNM_NOESCAPE)) - assert(File.fnmatch('\?', '\a', File::FNM_NOESCAPE)) - assert(!File.fnmatch('\*', '*', File::FNM_NOESCAPE)) - assert(File.fnmatch('\*', '\*', File::FNM_NOESCAPE)) - assert(!File.fnmatch('\*', 'cats', File::FNM_NOESCAPE)) - assert(File.fnmatch('\*', '\cats', File::FNM_NOESCAPE)) - assert(!File.fnmatch('\a', 'a', File::FNM_NOESCAPE)) - assert(File.fnmatch('\a', '\a', File::FNM_NOESCAPE)) - assert(File.fnmatch('[a\-c]', 'a', File::FNM_NOESCAPE)) - assert(!File.fnmatch('[a\-c]', '-', File::FNM_NOESCAPE)) - assert(File.fnmatch('[a\-c]', 'c', File::FNM_NOESCAPE)) - assert(File.fnmatch('[a\-c]', 'b', File::FNM_NOESCAPE)) # '\\' < 'b' < 'c' - assert(File.fnmatch('[a\-c]', '\\', File::FNM_NOESCAPE)) + assert_file.not_fnmatch('\?', '?', File::FNM_NOESCAPE) + assert_file.fnmatch('\?', '\?', File::FNM_NOESCAPE) + assert_file.not_fnmatch('\?', 'a', File::FNM_NOESCAPE) + assert_file.fnmatch('\?', '\a', File::FNM_NOESCAPE) + assert_file.not_fnmatch('\*', '*', File::FNM_NOESCAPE) + assert_file.fnmatch('\*', '\*', File::FNM_NOESCAPE) + assert_file.not_fnmatch('\*', 'cats', File::FNM_NOESCAPE) + assert_file.fnmatch('\*', '\cats', File::FNM_NOESCAPE) + assert_file.not_fnmatch('\a', 'a', File::FNM_NOESCAPE) + assert_file.fnmatch('\a', '\a', File::FNM_NOESCAPE) + assert_file.fnmatch('[a\-c]', 'a', File::FNM_NOESCAPE) + assert_file.not_fnmatch('[a\-c]', '-', File::FNM_NOESCAPE) + assert_file.fnmatch('[a\-c]', 'c', File::FNM_NOESCAPE) + assert_file.fnmatch('[a\-c]', 'b', File::FNM_NOESCAPE) # '\\' < 'b' < 'c' + assert_file.fnmatch('[a\-c]', '\\', File::FNM_NOESCAPE) + end + + def test_fnm_casefold # case is ignored if FNM_CASEFOLD is set - assert(!File.fnmatch('cat', 'CAT')) - assert(File.fnmatch('cat', 'CAT', File::FNM_CASEFOLD)) - assert(!File.fnmatch('[a-z]', 'D')) - assert(File.fnmatch('[a-z]', 'D', File::FNM_CASEFOLD)) - assert(!File.fnmatch('[abc]', 'B')) - assert(File.fnmatch('[abc]', 'B', File::FNM_CASEFOLD)) + assert_file.not_fnmatch('cat', 'CAT') + assert_file.fnmatch('cat', 'CAT', File::FNM_CASEFOLD) + assert_file.not_fnmatch('[a-z]', 'D') + assert_file.fnmatch('[a-z]', 'D', File::FNM_CASEFOLD) + assert_file.not_fnmatch('[abc]', 'B') + assert_file.fnmatch('[abc]', 'B', File::FNM_CASEFOLD) + end + + def test_fnm_pathname # wildcard doesn't match '/' if FNM_PATHNAME is set - assert(File.fnmatch('foo?boo', 'foo/boo')) - assert(File.fnmatch('foo*', 'foo/boo')) - assert(!File.fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME)) - assert(!File.fnmatch('foo*', 'foo/boo', File::FNM_PATHNAME)) + assert_file.fnmatch('foo?boo', 'foo/boo') + assert_file.fnmatch('foo*', 'foo/boo') + assert_file.not_fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME) + assert_file.not_fnmatch('foo*', 'foo/boo', File::FNM_PATHNAME) + end + + def test_fnm_dotmatch # wildcard matches leading period if FNM_DOTMATCH is set - assert(!File.fnmatch('*', '.profile')) - assert(File.fnmatch('*', '.profile', File::FNM_DOTMATCH)) - assert(File.fnmatch('.*', '.profile')) - assert(File.fnmatch('*', 'dave/.profile')) - assert(File.fnmatch('*/*', 'dave/.profile')) - assert(!File.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME)) - assert(File.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH)) + assert_file.not_fnmatch('*', '.profile') + assert_file.fnmatch('*', '.profile', File::FNM_DOTMATCH) + assert_file.fnmatch('.*', '.profile') + assert_file.fnmatch('*', 'dave/.profile') + assert_file.fnmatch('*/*', 'dave/.profile') + assert_file.not_fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME) + assert_file.fnmatch('*/*', 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) + end + + def test_recursive # recursive matching - assert(File.fnmatch('**/foo', 'a/b/c/foo', File::FNM_PATHNAME)) - assert(File.fnmatch('**/foo', '/foo', File::FNM_PATHNAME)) - assert(!File.fnmatch('**/foo', 'a/.b/c/foo', File::FNM_PATHNAME)) - assert(File.fnmatch('**/foo', 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH)) - assert(File.fnmatch('**/foo', '/root/foo', File::FNM_PATHNAME)) - assert(File.fnmatch('**/foo', 'c:/root/foo', File::FNM_PATHNAME)) + assert_file.fnmatch('**/foo', 'a/b/c/foo', File::FNM_PATHNAME) + assert_file.fnmatch('**/foo', '/foo', File::FNM_PATHNAME) + assert_file.not_fnmatch('**/foo', 'a/.b/c/foo', File::FNM_PATHNAME) + assert_file.fnmatch('**/foo', 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH) + assert_file.fnmatch('**/foo', '/root/foo', File::FNM_PATHNAME) + assert_file.fnmatch('**/foo', 'c:/root/foo', File::FNM_PATHNAME) + end + + def test_extglob + feature5422 = '[ruby-core:40037]' + assert_file.for(feature5422).not_fnmatch?( "{.g,t}*", ".gem") + assert_file.for(feature5422).fnmatch?("{.g,t}*", ".gem", File::FNM_EXTGLOB) + end + + def test_unmatched_encoding + bug7911 = '[ruby-dev:47069] [Bug #7911]' + path = "\u{3042}" + pattern_ascii = 'a'.encode('US-ASCII') + pattern_eucjp = path.encode('EUC-JP') + assert_nothing_raised(ArgumentError, bug7911) do + assert_file.not_fnmatch(pattern_ascii, path) + assert_file.not_fnmatch(pattern_eucjp, path) + assert_file.not_fnmatch(pattern_ascii, path, File::FNM_CASEFOLD) + assert_file.not_fnmatch(pattern_eucjp, path, File::FNM_CASEFOLD) + assert_file.fnmatch("{*,#{pattern_ascii}}", path, File::FNM_EXTGLOB) + assert_file.fnmatch("{*,#{pattern_eucjp}}", path, File::FNM_EXTGLOB) + end end + def test_unicode + 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 1bd3df4c1a..7a6309b6a3 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestGc < Test::Unit::TestCase @@ -12,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 { @@ -33,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) @@ -46,9 +51,399 @@ class TestGc < Test::Unit::TestCase GC.enable end + def test_start_full_mark + return unless use_rgengc? + + GC.start(full_mark: false) + assert_nil GC.latest_gc_info(:major_by) + + GC.start(full_mark: true) + assert_not_nil GC.latest_gc_info(:major_by) + end + + def test_start_immediate_sweep + GC.start(immediate_sweep: false) + assert_equal false, GC.latest_gc_info(:immediate_sweep) + + GC.start(immediate_sweep: true) + assert_equal true, GC.latest_gc_info(:immediate_sweep) + end + def test_count c = GC.count GC.start assert_operator(c, :<, GC.count) end + + def test_stat + res = GC.stat + assert_equal(false, res.empty?) + assert_kind_of(Integer, res[:count]) + + arg = Hash.new + res = GC.stat(arg) + assert_equal(arg, res) + assert_equal(false, res.empty?) + assert_kind_of(Integer, res[:count]) + + stat, count = {}, {} + GC.start + GC.stat(stat) + ObjectSpace.count_objects(count) + assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots]) + assert_equal(count[:FREE], stat[:heap_free_slots]) + + # measure again without GC.start + 1000.times{ "a" + "b" } + GC.stat(stat) + ObjectSpace.count_objects(count) + 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 + stat = GC.stat + assert_equal stat[:count], GC.stat(:count) + 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 + 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 :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 :force, GC.latest_gc_info[:major_by] + ensure + GC.stress = false + end + + def test_latest_gc_info_argument + info = {} + GC.latest_gc_info(info) + + assert_not_empty info + assert_equal info[:gc_by], GC.latest_gc_info(:gc_by) + 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 + assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]") + GC.stress = true + 10.times do + obj = Object.new + def obj.foo() end + def obj.bar() raise "obj.foo is called, but this is obj.bar" end + obj.foo + end + EOS + end + + def test_singleton_method_added + assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:44436]") + class BasicObject + undef singleton_method_added + def singleton_method_added(mid) + raise + end + end + b = proc {} + class << b; end + b.clone rescue nil + GC.start + EOS + end + + def test_gc_parameter + env = { + "RUBY_GC_MALLOC_LIMIT" => "60000000", + "RUBY_GC_HEAP_INIT_SLOTS" => "100000" + } + assert_normal_exit("exit", "[ruby-core:39777]", :child_env => env) + + env = { + "RUBYOPT" => "", + "RUBY_GC_HEAP_INIT_SLOTS" => "100000" + } + assert_in_out_err([env, "-e", "exit"], "", [], [], "[ruby-core:39795]") + assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[ruby-core:39795]") + assert_in_out_err([env, "-W1", "-e", "exit"], "", [], [], "[ruby-core:39795]") + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_INIT_SLOTS=100000/, "[ruby-core:39795]") + + env = { + "RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0", + "RUBY_GC_HEAP_GROWTH_MAX_SLOTS" => "10000" + } + assert_normal_exit("exit", "", :child_env => env) + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "") + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]") + + env = { + "RUBY_GC_HEAP_INIT_SLOTS" => "100000", + "RUBY_GC_HEAP_FREE_SLOTS" => "10000", + "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.9", + } + 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'], //, "") if use_rgengc? + + # check obsolete + assert_in_out_err([{'RUBY_FREE_MIN' => '100'}, '-w', '-eexit'], '', [], + /RUBY_FREE_MIN is obsolete. Use RUBY_GC_HEAP_FREE_SLOTS instead/) + assert_in_out_err([{'RUBY_HEAP_MIN_SLOTS' => '100'}, '-w', '-eexit'], '', [], + /RUBY_HEAP_MIN_SLOTS is obsolete. Use RUBY_GC_HEAP_INIT_SLOTS instead/) + + env = { + "RUBY_GC_MALLOC_LIMIT" => "60000000", + "RUBY_GC_MALLOC_LIMIT_MAX" => "160000000", + "RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR" => "2.0" + } + assert_normal_exit("exit", "", :child_env => env) + assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT=6000000/, "") + 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/, "") + + 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 + GC::Profiler.enable + assert_equal(true, GC::Profiler.enabled?) + GC::Profiler.disable + assert_equal(false, GC::Profiler.enabled?) + ensure + GC::Profiler.disable + end + + def test_profiler_clear + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30 + GC::Profiler.enable + + GC.start + assert_equal(1, GC::Profiler.raw_data.size) + GC::Profiler.clear + assert_equal(0, GC::Profiler.raw_data.size) + + 200.times{ GC.start } + assert_equal(200, GC::Profiler.raw_data.size) + GC::Profiler.clear + assert_equal(0, GC::Profiler.raw_data.size) + eom + end + + def test_profiler_total_time + GC::Profiler.enable + GC::Profiler.clear + + GC.start + assert_operator(GC::Profiler.total_time, :>=, 0) + ensure + GC::Profiler.disable + end + + def test_finalizing_main_thread + assert_in_out_err(%w[--disable-gems], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]") + ObjectSpace.define_finalizer(Thread.main) { p 'finalize' } + EOS + end + + def test_expand_heap + assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom' + GC.start + 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_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_pages] + 1 + eom + end + + def test_gc_internals + 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: 60) + raise_proc = proc do |id| + GC.start + end + 1000.times do + ObjectSpace.define_finalizer(Object.new, raise_proc) + end + end; + end + end + + def test_exception_in_finalizer + bug9168 = '[ruby-core:58652] [Bug #9168]' + assert_normal_exit(<<-'end;', bug9168, encoding: Encoding::ASCII_8BIT) + raise_proc = proc {raise} + 10000.times do + ObjectSpace.define_finalizer(Object.new, raise_proc) + Thread.handle_interrupt(RuntimeError => :immediate) {break} + Thread.handle_interrupt(RuntimeError => :on_blocking) {break} + Thread.handle_interrupt(RuntimeError => :never) {break} + end + 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_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 782edc9a0c..e1b6e7257e 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -1,11 +1,13 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require 'continuation' +EnvUtil.suppress_warning {require 'continuation'} class TestHash < Test::Unit::TestCase def test_hash - x = {1=>2, 2=>4, 3=>6} - y = {1=>2, 2=>4, 3=>6} # y = {1, 2, 2, 4, 3, 6} # 1.9 doesn't support + x = @cls[1=>2, 2=>4, 3=>6] + y = @cls[1=>2, 2=>4, 3=>6] # y = {1, 2, 2, 4, 3, 6} # 1.9 doesn't support assert_equal(2, x[1]) @@ -19,8 +21,8 @@ class TestHash < Test::Unit::TestCase end) assert_equal(3, x.length) - assert(x.has_key?(1)) - assert(x.has_value?(4)) + assert_send([x, :has_key?, 1]) + assert_send([x, :has_value?, 4]) assert_equal([4,6], x.values_at(2,3)) assert_equal({1=>2, 2=>4, 3=>6}, x) @@ -77,7 +79,7 @@ class TestHash < Test::Unit::TestCase # From rubicon def setup - @cls = Hash + @cls ||= Hash @h = @cls[ 1 => 'one', 2 => 'two', 3 => 'three', self => 'self', true => 'true', nil => 'nil', @@ -91,6 +93,36 @@ class TestHash < Test::Unit::TestCase $VERBOSE = @verbose end + def test_bad_initialize_copy + h = Class.new(Hash) { + def initialize_copy(h) + super(Object.new) + end + }.new + assert_raise(TypeError) { h.dup } + end + + def test_clear_initialize_copy + h = @cls[1=>2] + h.instance_eval {initialize_copy({})} + assert_empty(h) + end + + def test_self_initialize_copy + h = @cls[1=>2] + h.instance_eval {initialize_copy(h)} + assert_equal(2, h[1]) + end + + def test_dup_will_rehash + set1 = @cls[] + set2 = @cls[set1 => true] + + set1[set1] = true + + assert_equal set2, set2.dup + end + def test_s_AREF h = @cls["a" => 100, "b" => 200] assert_equal(100, h['a']) @@ -101,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 @@ -113,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 # '[]' @@ -183,22 +266,88 @@ 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_ASET_fstring_non_literal_key + underscore = "_" + non_literal_strings = Proc.new{ ["abc#{underscore}def", "abc" * 5, "abc" + "def", "" << "ghi" << "jkl"] } + + a, b = {}, {} + non_literal_strings.call.each do |string| + assert_equal 1, a[string] = 1 + end + + non_literal_strings.call.each do |string| + assert_equal 1, b[string] = 1 + end + + [a.keys, b.keys].transpose.each do |key_a, key_b| + assert_same key_a, key_b + end + end + + def test_hash_aset_fstring_identity + h = {}.compare_by_identity + h['abc'] = 1 + 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} + assert_same a.keys[0], b.keys[0] + assert_same "ABC".freeze, a.keys[0] + var = +'ABC' + c = { var => :t } + assert_same "ABC".freeze, c.keys[0] + end + + def test_tainted_string_key + str = 'str'.taint + h = {} + h[str] = nil + key = h.keys.first + assert_predicate str, :tainted? + assert_not_predicate str, :frozen? + assert_predicate key, :tainted? + assert_predicate key, :frozen? + end + def test_EQUAL # '==' h1 = @cls[ "a" => 1, "c" => 2 ] h2 = @cls[ "a" => 1, "c" => 2, 7 => 35 ] h3 = @cls[ "a" => 1, "c" => 2, 7 => 35 ] h4 = @cls[ ] - assert(h1 == h1) - assert(h2 == h2) - assert(h3 == h3) - assert(h4 == h4) - assert(!(h1 == h2)) - assert(h2 == h3) - assert(!(h3 == h4)) + assert_equal(h1, h1) + assert_equal(h2, h2) + assert_equal(h3, h3) + assert_equal(h4, h4) + assert_not_equal(h1, h2) + assert_equal(h2, h3) + assert_not_equal(h3, h4) end def test_clear - assert(@h.size > 0) + assert_operator(@h.size, :>, 0) @h.clear assert_equal(0, @h.size) assert_nil(@h[1]) @@ -206,20 +355,16 @@ class TestHash < Test::Unit::TestCase def test_clone for taint in [ false, true ] - for untrust in [ false, true ] - for frozen in [ false, true ] - a = @h.clone - a.taint if taint - a.untrust if untrust - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert(a.__id__ != b.__id__) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.untrusted?, b.untrusted?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = @h.clone + a.taint if taint + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) + assert_equal(a.tainted?, b.tainted?) end end end @@ -291,31 +436,46 @@ class TestHash < Test::Unit::TestCase end def test_keep_if - h = {1=>2,3=>4,5=>6} + h = @cls[1=>2,3=>4,5=>6] assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 }) - h = {1=>2,3=>4,5=>6} + h = @cls[1=>2,3=>4,5=>6] 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 untrust in [ false, true ] - for frozen in [ false, true ] - a = @h.dup - a.taint if taint - a.freeze if frozen - b = a.dup - - assert_equal(a, b) - assert(a.__id__ != b.__id__) - assert_equal(false, b.frozen?) - assert_equal(a.tainted?, b.tainted?) - assert_equal(a.untrusted?, b.untrusted?) - end + for frozen in [ false, true ] + a = @h.dup + a.taint if taint + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(false, b.frozen?) + assert_equal(a.tainted?, b.tainted?) end end end + def test_dup_equality + h = @cls['k' => 'v'] + assert_equal(h, h.dup) + h1 = @cls[h => 1] + assert_equal(h1, h1.dup) + h[1] = 2 + assert_equal(h1, h1.dup) + end + def test_each count = 0 @cls[].each { |k, v| count + 1 } @@ -326,6 +486,11 @@ class TestHash < Test::Unit::TestCase assert_equal(v, h.delete(k)) end assert_equal(@cls[], h) + + h = @cls[] + h[1] = 1 + h[2] = 2 + assert_equal([[1,1],[2,2]], h.each.to_a) end def test_each_key @@ -368,13 +533,11 @@ class TestHash < Test::Unit::TestCase end def test_empty? - assert(@cls[].empty?) - assert(!@h.empty?) + assert_empty(@cls[]) + assert_not_empty(@h) end def test_fetch - assert_raise(KeyError) { @cls[].fetch(1) } - assert_raise(KeyError) { @h.fetch('gumby') } assert_equal('gumbygumby', @h.fetch('gumby') {|k| k * 2 }) assert_equal('pokey', @h.fetch('gumby', 'pokey')) @@ -383,28 +546,38 @@ class TestHash < Test::Unit::TestCase assert_equal('nil', @h.fetch(nil)) end + def test_fetch_error + assert_raise(KeyError) { @cls[].fetch(1) } + assert_raise(KeyError) { @h.fetch('gumby') } + 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? - assert(!@cls[].key?(1)) - assert(!@cls[].key?(nil)) - assert(@h.key?(nil)) - assert(@h.key?(1)) - assert(!@h.key?('gumby')) + assert_not_send([@cls[], :key?, 1]) + assert_not_send([@cls[], :key?, nil]) + assert_send([@h, :key?, nil]) + assert_send([@h, :key?, 1]) + assert_not_send([@h, :key?, 'gumby']) end def test_value? - assert(!@cls[].value?(1)) - assert(!@cls[].value?(nil)) - assert(@h.value?('one')) - assert(@h.value?(nil)) - assert(!@h.value?('gumby')) + assert_not_send([@cls[], :value?, 1]) + assert_not_send([@cls[], :value?, nil]) + assert_send([@h, :value?, 'one']) + assert_send([@h, :value?, nil]) + assert_not_send([@h, :value?, 'gumby']) end def test_include? - assert(!@cls[].include?(1)) - assert(!@cls[].include?(nil)) - assert(@h.include?(nil)) - assert(@h.include?(1)) - assert(!@h.include?('gumby')) + assert_not_send([@cls[], :include?, 1]) + assert_not_send([@cls[], :include?, nil]) + assert_send([@h, :include?, nil]) + assert_send([@h, :include?, 1]) + assert_not_send([@h, :include?, 'gumby']) end def test_key @@ -418,11 +591,11 @@ class TestHash < Test::Unit::TestCase def test_values_at res = @h.values_at('dog', 'cat', 'horse') - assert(res.length == 3) + assert_equal(3, res.length) assert_equal([nil, nil, nil], res) res = @h.values_at - assert(res.length == 0) + assert_equal(0, res.length) res = @h.values_at(3, 2, 1, nil) assert_equal 4, res.length @@ -433,6 +606,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 @@ -441,21 +631,21 @@ class TestHash < Test::Unit::TestCase assert_equal(nil, h['nil']) h.each do |k, v| - assert(@h.key?(v)) # not true in general, but works here + assert_send([@h, :key?, v]) # not true in general, but works here end h = @cls[ 'a' => 1, 'b' => 2, 'c' => 1].invert assert_equal(2, h.length) - assert(h[1] == 'a' || h[1] == 'c') + assert_include(%w[a c], h[1]) assert_equal('b', h[2]) end def test_key? - assert(!@cls[].key?(1)) - assert(!@cls[].key?(nil)) - assert(@h.key?(nil)) - assert(@h.key?(1)) - assert(!@h.key?('gumby')) + assert_not_send([@cls[], :key?, 1]) + assert_not_send([@cls[], :key?, nil]) + assert_send([@h, :key?, nil]) + assert_send([@h, :key?, 1]) + assert_not_send([@h, :key?, 'gumby']) end def test_keys @@ -474,11 +664,11 @@ class TestHash < Test::Unit::TestCase end def test_member? - assert(!@cls[].member?(1)) - assert(!@cls[].member?(nil)) - assert(@h.member?(nil)) - assert(@h.member?(1)) - assert(!@h.member?('gumby')) + assert_not_send([@cls[], :member?, 1]) + assert_not_send([@cls[], :member?, nil]) + assert_send([@h, :member?, nil]) + assert_send([@h, :member?, 1]) + assert_not_send([@h, :member?, 'gumby']) end def test_rehash @@ -493,6 +683,8 @@ class TestHash < Test::Unit::TestCase end def test_reject + assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].reject {|k, v| k + v < 7 }) + base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ] h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ] h2 = @cls[ 2 => false, 'cat' => 99 ] @@ -509,6 +701,15 @@ class TestHash < Test::Unit::TestCase assert_equal(h3, h.reject {|k,v| v }) assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h.taint + h = EnvUtil.suppress_warning {h.reject {false}} + assert_instance_of(Hash, h) + assert_not_predicate(h, :tainted?) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) end def test_reject! @@ -544,12 +745,30 @@ class TestHash < Test::Unit::TestCase assert_nil(h[2]) end + def test_replace_bug9230 + h = @cls[] + h.replace(@cls[]) + assert_empty h + + h = @cls[] + h.replace(@cls[].compare_by_identity) + assert_predicate(h, :compare_by_identity?) + end + + def test_replace_bug15358 + h1 = {} + h2 = {a:1,b:2,c:3,d:4,e:5} + h2.replace(h1) + GC.start + assert(true) + end + def test_shift h = @h.dup @h.length.times { k, v = h.shift - assert(@h.key?(k)) + assert_send([@h, :key?, k]) assert_equal(@h[k], v) } @@ -616,15 +835,59 @@ class TestHash < Test::Unit::TestCase h = @cls[ 1=>2, 3=>4, 5=>6 ] h.taint - h.untrust a = h.to_a assert_equal(true, a.tainted?) - assert_equal(true, a.untrusted?) end def test_to_hash h = @h.to_hash assert_equal(@h, h) + assert_instance_of(@cls, h) + end + + def test_to_h + h = @h.to_h + assert_equal(@h, h) + 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_to_h_block + h = @h.to_h {|k, v| [k.to_s, v.to_s]} + assert_equal({ + "1"=>"one", "2"=>"two", "3"=>"three", to_s=>"self", + "true"=>"true", ""=>"nil", "nil"=>"" + }, + h) + assert_instance_of(Hash, h) + end + + def test_nil_to_h + h = nil.to_h + assert_equal({}, h) + assert_nil(h.default) + assert_nil(h.default_proc) end def test_to_s @@ -656,11 +919,11 @@ class TestHash < Test::Unit::TestCase end def test_value2? - assert(!@cls[].value?(1)) - assert(!@cls[].value?(nil)) - assert(@h.value?(nil)) - assert(@h.value?('one')) - assert(!@h.value?('gumby')) + assert_not_send([@cls[], :value?, 1]) + assert_not_send([@cls[], :value?, nil]) + assert_send([@h, :value?, nil]) + assert_send([@h, :value?, 'one']) + assert_not_send([@h, :value?, 'gumby']) end def test_values @@ -673,36 +936,29 @@ class TestHash < Test::Unit::TestCase assert_equal([], expected - vals) end - def test_security_check - h = {} - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - h[1] = 1 - end.join - end - end - - def test_intialize_wrong_arguments + def test_initialize_wrong_arguments assert_raise(ArgumentError) do Hash.new(0) { } end end def test_create - assert_equal({1=>2, 3=>4}, Hash[[[1,2],[3,4]]]) + assert_equal({1=>2, 3=>4}, @cls[[[1,2],[3,4]]]) assert_raise(ArgumentError) { Hash[0, 1, 2] } - assert_equal({1=>2, 3=>4}, Hash[1,2,3,4]) + 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]) o = Object.new def o.to_hash() {1=>2} end - assert_equal({1=>2}, Hash[o], "[ruby-dev:34555]") + assert_equal({1=>2}, @cls[o], "[ruby-dev:34555]") end def test_rehash2 - h = {1 => 2, 3 => 4} + h = @cls[1 => 2, 3 => 4] assert_equal(h.dup, h.rehash) assert_raise(RuntimeError) { h.each { h.rehash } } - assert_equal({}, {}.rehash) + assert_equal({}, @cls[].rehash) end def test_fetch2 @@ -710,57 +966,141 @@ class TestHash < Test::Unit::TestCase end def test_default_proc - h = Hash.new {|hh, k| hh + k + "baz" } + h = @cls.new {|hh, k| hh + k + "baz" } assert_equal("foobarbaz", h.default_proc.call("foo", "bar")) - h = {} + assert_nil(h.default_proc = nil) + assert_nil(h.default_proc) + h.default_proc = ->(_,_){ true } + assert_equal(true, h[:nope]) + h = @cls[] assert_nil(h.default_proc) end def test_shift2 - h = Hash.new {|hh, k| :foo } + h = @cls.new {|hh, k| :foo } h[1] = 2 assert_equal([1, 2], h.shift) assert_equal(:foo, h.shift) assert_equal(:foo, h.shift) - h = Hash.new(:foo) + h = @cls.new(:foo) h[1] = 2 assert_equal([1, 2], h.shift) assert_equal(:foo, h.shift) assert_equal(:foo, h.shift) - h = {1=>2} + h =@cls[1=>2] h.each { assert_equal([1, 2], h.shift) } end + def test_shift_none + h = @cls.new {|hh, k| "foo"} + def h.default(k = nil) + super.upcase + end + assert_equal("FOO", h.shift) + end + def test_reject_bang2 - assert_equal({1=>2}, {1=>2,3=>4}.reject! {|k, v| k + v == 7 }) - assert_nil({1=>2,3=>4}.reject! {|k, v| k == 5 }) - assert_nil({}.reject! { }) + assert_equal({1=>2}, @cls[1=>2,3=>4].reject! {|k, v| k + v == 7 }) + assert_nil(@cls[1=>2,3=>4].reject! {|k, v| k == 5 }) + assert_nil(@cls[].reject! { }) end def test_select - assert_equal({3=>4,5=>6}, {1=>2,3=>4,5=>6}.select {|k, v| k + v >= 7 }) + assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].select {|k, v| k + v >= 7 }) + + base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ '2' => false, 'cat' => 99 ] + h2 = @cls[ 1 => 'one', true => 'true' ] + h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ] + + h = base.dup + assert_equal(h, h.select { true }) + assert_equal(@cls[], h.select { false }) + + h = base.dup + assert_equal(h1, h.select {|k,v| k.instance_of?(String) }) + + assert_equal(h2, h.select {|k,v| v.instance_of?(String) }) + + assert_equal(h3, h.select {|k,v| v }) + assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h.taint + h = h.select {true} + assert_instance_of(Hash, h) + assert_not_predicate(h, :tainted?) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) end def test_select! - h = {1=>2,3=>4,5=>6} + h = @cls[1=>2,3=>4,5=>6] assert_equal(h, h.select! {|k, v| k + v >= 7 }) assert_equal({3=>4,5=>6}, h) - h = {1=>2,3=>4,5=>6} + h = @cls[1=>2,3=>4,5=>6] 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_filter + assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].filter {|k, v| k + v >= 7 }) + + base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ '2' => false, 'cat' => 99 ] + h2 = @cls[ 1 => 'one', true => 'true' ] + h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ] + + h = base.dup + assert_equal(h, h.filter { true }) + assert_equal(@cls[], h.filter { false }) + + h = base.dup + assert_equal(h1, h.filter {|k,v| k.instance_of?(String) }) + + assert_equal(h2, h.filter {|k,v| v.instance_of?(String) }) + + assert_equal(h3, h.filter {|k,v| v }) + assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h.taint + h = h.filter {true} + assert_instance_of(Hash, h) + assert_not_predicate(h, :tainted?) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) + end + + def test_filter! + h = @cls[1=>2,3=>4,5=>6] + assert_equal(h, h.filter! {|k, v| k + v >= 7 }) + assert_equal({3=>4,5=>6}, h) + h = @cls[1=>2,3=>4,5=>6] + assert_equal(nil, h.filter!{true}) + end + def test_clear2 - assert_equal({}, {1=>2,3=>4,5=>6}.clear) - h = {1=>2,3=>4,5=>6} + assert_equal({}, @cls[1=>2,3=>4,5=>6].clear) + h = @cls[1=>2,3=>4,5=>6] h.each { h.clear } assert_equal({}, h) end def test_replace2 - h1 = Hash.new { :foo } - h2 = {} + h1 = @cls.new { :foo } + h2 = @cls.new h2.replace h1 assert_equal(:foo, h2[0]) @@ -768,73 +1108,127 @@ 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 - assert_equal(0, {}.size) + assert_equal(0, @cls[].size) end def test_equal2 - assert({} != 0) + assert_not_equal(0, @cls[]) o = Object.new - def o.to_hash; {}; end + o.instance_variable_set(:@cls, @cls) + def o.to_hash; @cls[]; end def o.==(x); true; end - assert({} == o) + assert_equal({}, o) def o.==(x); false; end - assert({} != o) + assert_not_equal({}, o) - h1 = {1=>2}; h2 = {3=>4} - assert(h1 != h2) - h1 = {1=>2}; h2 = {1=>4} - assert(h1 != h2) + h1 = @cls[1=>2]; h2 = @cls[3=>4] + assert_not_equal(h1, h2) + h1 = @cls[1=>2]; h2 = @cls[1=>4] + assert_not_equal(h1, h2) end def test_eql - assert(!({}.eql?(0))) + assert_not_send([@cls[], :eql?, 0]) o = Object.new - def o.to_hash; {}; end + o.instance_variable_set(:@cls, @cls) + def o.to_hash; @cls[]; end def o.eql?(x); true; end - assert({}.eql?(o)) + assert_send([@cls[], :eql?, o]) def o.eql?(x); false; end - assert(!({}.eql?(o))) + assert_not_send([@cls[], :eql?, o]) end def test_hash2 - assert_kind_of(Integer, {}.hash) + assert_kind_of(Integer, @cls[].hash) + h = @cls[1=>2] + h.shift + assert_equal({}.hash, h.hash, '[ruby-core:38650]') + bug9231 = '[ruby-core:58993] [Bug #9231]' + assert_not_equal(0, @cls[].hash, bug9231) end def test_update2 - h1 = {1=>2, 3=>4} + h1 = @cls[1=>2, 3=>4] h2 = {1=>3, 5=>7} h1.update(h2) {|k, v1, v2| k + v1 + v2 } assert_equal({1=>6, 3=>4, 5=>7}, h1) end + def test_update3 + h1 = @cls[1=>2, 3=>4] + h1.update() + assert_equal({1=>2, 3=>4}, h1) + h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + h1.update(h2, h3) + assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1) + end + + def test_update4 + h1 = @cls[1=>2, 3=>4] + h1.update(){|k, v1, v2| k + v1 + v2 } + assert_equal({1=>2, 3=>4}, h1) + h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + h1.update(h2, h3){|k, v1, v2| k + v1 + v2 } + assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1) + end + def test_merge - h1 = {1=>2, 3=>4} + h1 = @cls[1=>2, 3=>4] h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + assert_equal({1=>2, 3=>4}, h1.merge()) assert_equal({1=>3, 3=>4, 5=>7}, h1.merge(h2)) assert_equal({1=>6, 3=>4, 5=>7}, h1.merge(h2) {|k, v1, v2| k + v1 + v2 }) + assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3)) + assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3) {|k, v1, v2| k + v1 + v2 }) end def test_assoc - assert_equal([3,4], {1=>2, 3=>4, 5=>6}.assoc(3)) - assert_nil({1=>2, 3=>4, 5=>6}.assoc(4)) + assert_equal([3,4], @cls[1=>2, 3=>4, 5=>6].assoc(3)) + assert_nil(@cls[1=>2, 3=>4, 5=>6].assoc(4)) + assert_equal([1.0,1], @cls[1.0=>1].assoc(1)) + end + + def test_assoc_compare_by_identity + h = @cls[] + h.compare_by_identity + h["a"] = 1 + h["a".dup] = 2 + assert_equal(["a",1], h.assoc("a")) end def test_rassoc - assert_equal([3,4], {1=>2, 3=>4, 5=>6}.rassoc(4)) + assert_equal([3,4], @cls[1=>2, 3=>4, 5=>6].rassoc(4)) assert_nil({1=>2, 3=>4, 5=>6}.rassoc(3)) end def test_flatten - assert_equal([[1], [2]], {[1] => [2]}.flatten) + assert_equal([[1], [2]], @cls[[1] => [2]].flatten) + + a = @cls[1=> "one", 2 => [2,"two"], 3 => [3, ["three"]]] + assert_equal([1, "one", 2, [2, "two"], 3, [3, ["three"]]], a.flatten) + assert_equal([[1, "one"], [2, [2, "two"]], [3, [3, ["three"]]]], a.flatten(0)) + assert_equal([1, "one", 2, [2, "two"], 3, [3, ["three"]]], a.flatten(1)) + 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(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 - h = {1=>2} + h = @cls[1=>2] c = nil f = false h.each { callcc {|c2| c = c2 } } @@ -844,7 +1238,7 @@ class TestHash < Test::Unit::TestCase end assert_raise(RuntimeError) { h.each { h.rehash } } - h = {1=>2} + h = @cls[1=>2] c = nil assert_raise(RuntimeError) do h.each { callcc {|c2| c = c2 } } @@ -853,15 +1247,95 @@ class TestHash < Test::Unit::TestCase end end + def test_callcc_iter_level + bug9105 = '[ruby-dev:47803] [Bug #9105]' + h = @cls[1=>2, 3=>4] + c = nil + f = false + h.each {callcc {|c2| c = c2}} + unless f + f = true + c.call + end + assert_nothing_raised(RuntimeError, bug9105) do + h.each {|i, j| + h.delete(i); + assert_not_equal(false, i, bug9105) + } + end + end + + def test_callcc_escape + bug9105 = '[ruby-dev:47803] [Bug #9105]' + assert_nothing_raised(RuntimeError, bug9105) do + h=@cls[] + cnt=0 + c = callcc {|cc|cc} + h[cnt] = true + h.each{|i| + cnt+=1 + c.call if cnt == 1 + } + end + end + + def test_callcc_reenter + bug9105 = '[ruby-dev:47803] [Bug #9105]' + assert_nothing_raised(RuntimeError, bug9105) do + h = @cls[1=>2,3=>4] + c = nil + f = false + h.each { |i| + callcc {|c2| c = c2 } unless c + h.delete(1) if f + } + unless f + f = true + c.call + end + end + end + + def test_threaded_iter_level + bug9105 = '[ruby-dev:47807] [Bug #9105]' + h = @cls[1=>2] + 2.times.map { + f = false + th = Thread.start {h.each {f = true; sleep}} + Thread.pass until f + Thread.pass until th.stop? + th + }.each {|th| th.run; th.join} + assert_nothing_raised(RuntimeError, bug9105) do + h[5] = 6 + end + assert_equal(6, h[5], bug9105) + end + def test_compare_by_identity a = "foo" - assert(!{}.compare_by_identity?) - h = { a => "bar" } - assert(!h.compare_by_identity?) + assert_not_predicate(@cls[], :compare_by_identity?) + h = @cls[a => "bar"] + assert_not_predicate(h, :compare_by_identity?) h.compare_by_identity - assert(h.compare_by_identity?) + assert_predicate(h, :compare_by_identity?) #assert_equal("bar", h[a]) assert_nil(h["foo"]) + + bug8703 = '[ruby-core:56256] [Bug #8703] copied identhash' + h.clear + assert_predicate(h.dup, :compare_by_identity?, bug8703) + end + + def test_same_key + bug9646 = '[ruby-dev:48047] [Bug #9646] Infinite loop at Hash#each' + h = @cls[a=[], 1] + a << 1 + h[[]] = 2 + a.clear + cnt = 0 + r = h.each{ break nil if (cnt+=1) > 100 } + assert_not_nil(r,bug9646) end class ObjWithHash @@ -877,31 +1351,444 @@ class TestHash < Test::Unit::TestCase end def test_hash_hash - assert_equal({0=>2,11=>1}.hash, {11=>1,0=>2}.hash) + assert_equal({0=>2,11=>1}.hash, @cls[11=>1,0=>2].hash) o1 = ObjWithHash.new(0,1) o2 = ObjWithHash.new(11,1) - assert_equal({o1=>1,o2=>2}.hash, {o2=>2,o1=>1}.hash) + assert_equal({o1=>1,o2=>2}.hash, @cls[o2=>2,o1=>1].hash) end def test_hash_bignum_hash x = 2<<(32-3)-1 - assert_equal({x=>1}.hash, {x=>1}.hash) + assert_equal({x=>1}.hash, @cls[x=>1].hash) x = 2<<(64-3)-1 - assert_equal({x=>1}.hash, {x=>1}.hash) + assert_equal({x=>1}.hash, @cls[x=>1].hash) o = Object.new - def o.hash; 2<<100; end - assert_equal({x=>1}.hash, {x=>1}.hash) + def o.hash; 2 << 100; end + assert_equal({o=>1}.hash, @cls[o=>1].hash) end - def test_hash_poped - assert_nothing_raised { eval("a = 1; {a => a}; a") } + def test_hash_popped + assert_nothing_raised { eval("a = 1; @cls[a => a]; a") } end def test_recursive_key - h = {} + h = @cls[] assert_nothing_raised { h[h] = :foo } h.rehash assert_equal(:foo, h[h]) end + + def test_inverse_hash + feature4262 = '[ruby-core:34334]' + [@cls[1=>2], @cls[123=>"abc"]].each do |h| + assert_not_equal(h.hash, h.invert.hash, feature4262) + end + end + + def test_recursive_hash_value_struct + bug9151 = '[ruby-core:58567] [Bug #9151]' + + s = Struct.new(:x) {def hash; [x,""].hash; end} + a = s.new + b = s.new + a.x = b + b.x = a + assert_nothing_raised(SystemStackError, bug9151) {a.hash} + assert_nothing_raised(SystemStackError, bug9151) {b.hash} + + h = @cls[] + h[[a,"hello"]] = 1 + assert_equal(1, h.size) + h[[b,"world"]] = 2 + assert_equal(2, h.size) + + obj = Object.new + h = @cls[a => obj] + assert_same(obj, h[b]) + end + + def test_recursive_hash_value_array + h = @cls[] + h[[[1]]] = 1 + assert_equal(1, h.size) + h[[[2]]] = 1 + assert_equal(2, h.size) + + a = [] + a << a + + h = @cls[] + h[[a, 1]] = 1 + assert_equal(1, h.size) + h[[a, 2]] = 2 + assert_equal(2, h.size) + h[[a, a]] = 3 + assert_equal(3, h.size) + + obj = Object.new + h = @cls[a => obj] + assert_same(obj, h[[[a]]]) + end + + def test_recursive_hash_value_array_hash + h = @cls[] + rec = [h] + h[:x] = rec + + obj = Object.new + h2 = {rec => obj} + [h, {x: rec}].each do |k| + k = [k] + assert_same(obj, h2[k], ->{k.inspect}) + end + end + + def test_recursive_hash_value_hash_array + h = @cls[] + rec = [h] + h[:x] = rec + + obj = Object.new + h2 = {h => obj} + [rec, [h]].each do |k| + k = {x: k} + assert_same(obj, h2[k], ->{k.inspect}) + end + end + + def test_exception_in_rehash_memory_leak + return unless @cls == Hash + + bug9187 = '[ruby-core:58728] [Bug #9187]' + + prepare = <<-EOS + class Foo + def initialize + @raise = false + end + + def hash + raise if @raise + @raise = true + return 0 + end + end + h = {Foo.new => true} + EOS + + code = <<-EOS + 10_0000.times do + h.rehash rescue nil + end + GC.start + EOS + + assert_no_memory_leak([], prepare, code, bug9187) + end + + def test_wrapper + bug9381 = '[ruby-core:59638] [Bug #9381]' + + wrapper = Class.new do + def initialize(obj) + @obj = obj + end + + def hash + @obj.hash + end + + def eql?(other) + @obj.eql?(other) + end + end + + 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 + end + 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 + + def test_reserved_hash_val + s = Struct.new(:hash) + h = {} + keys = [*0..8] + keys.each {|i| h[s.new(i)]=true} + msg = proc {h.inspect} + assert_equal(keys, h.keys.map(&:hash), msg) + end + + class TestSubHash < TestHash + class SubHash < Hash + def reject(*) + super + end + end + + def setup + @cls = SubHash + super + end + end + + def test_ar2st + # insert + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + h[obj] = true + assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9, test=>true}', h.inspect + + # delete + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + def obj.eql? other + other.class == Object + end + obj2 = Object.new + def obj2.hash + 0 + end + + h[obj2] = true + h.delete obj + assert_equal '{0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9}', h.inspect + + # lookup + obj = Object.new + obj.instance_variable_set(:@h, h = {}) + def obj.hash + 10.times{|i| @h[i] = i} + 0 + end + def obj.inspect + 'test' + end + def obj.eql? other + other.class == Object + end + obj2 = Object.new + def obj2.hash + 0 + end + + h[obj2] = true + assert_equal true, h[obj] + end end diff --git a/test/ruby/test_ifunless.rb b/test/ruby/test_ifunless.rb index bffc794512..f68e5154a2 100644 --- a/test/ruby/test_ifunless.rb +++ b/test/ruby/test_ifunless.rb @@ -1,14 +1,15 @@ +# frozen_string_literal: false require 'test/unit' -class TestIfunless < Test::Unit::TestCase +class TestIfUnless < Test::Unit::TestCase def test_if_unless - $x = 'test'; - assert(if $x == $x then true else false end) - $bad = false - unless $x == $x - $bad = true + x = 'test'; + assert(if x == x then true else false end) + bad = false + unless x == x + bad = true end - assert(!$bad) - assert(unless $x != $x then true else false end) + assert(!bad) + assert(unless x != x then true else false end) end end diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index 7f8212ebf0..0f289d8ed9 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 @@ -23,6 +24,32 @@ class TestInteger < Test::Unit::TestCase rescue nil end, "[ruby-dev:32084] [ruby-dev:34547]") + + x = EnvUtil.suppress_warning {2 ** -0x4000000000000000} + assert_in_delta(0.0, (x / 2), Float::EPSILON) + + <<~EXPRS.each_line.with_index(__LINE__+1) do |expr, line| + crash01: 111r+11**-11111161111111 + crash02: 1118111111111**-1111111111111111**1+1==11111 + crash03: -1111111**-1111*11 - -1111111** -111111111 + crash04: 1118111111111** -1111111111111111**1+11111111111**1 ===111 + crash05: 11** -111155555555555555 -55 !=5-555 + crash07: 1 + 111111111**-1111811111 + crash08: 18111111111**-1111111111111111**1 + 1111111111**-1111**1 + crash10: -7 - -1111111** -1111**11 + crash12: 1118111111111** -1111111111111111**1 + 1111 - -1111111** -1111*111111111119 + crash13: 1.0i - -1111111** -111111111 + crash14: 11111**111111111**111111 * -11111111111111111111**-111111111111 + crash15: ~1**1111 + -~1**~1**111 + crash17: 11** -1111111**1111 /11i + crash18: 5555i**-5155 - -9111111**-1111**11 + crash19: 111111*-11111111111111111111**-1111111111111111 + crash20: 1111**111-11**-11111**11 + crash21: 11**-10111111119-1i -1r + EXPRS + name, expr = expr.split(':', 2) + assert_ruby_status(%w"-W0", expr, name) + end end def test_lshift @@ -35,9 +62,9 @@ class TestInteger < Test::Unit::TestCase def test_rshift # assert_equal(bdsize(0x40000001), (1 >> -0x40000001).size) - assert((1 >> 0x80000000).zero?) - assert((1 >> 0xffffffff).zero?) - assert((1 >> 0x100000000).zero?) + assert_predicate((1 >> 0x80000000), :zero?) + assert_predicate((1 >> 0xffffffff), :zero?) + assert_predicate((1 >> 0x100000000), :zero?) # assert_equal((1 << 0x40000000), (1 >> -0x40000000)) # assert_equal((1 << 0x40000001), (1 >> -0x40000001)) end @@ -90,49 +117,98 @@ class TestInteger < Test::Unit::TestCase assert_equal(2 ** 50, Integer(2.0 ** 50)) assert_raise(TypeError) { Integer(nil) } - end - def test_int_p - assert(!(1.0.integer?)) - assert(1.integer?) - end + bug14552 = '[ruby-core:85813]' + obj = Object.new + def obj.to_int; "str"; end + assert_raise(TypeError, bug14552) { Integer(obj) } + def obj.to_i; 42; end + assert_equal(42, Integer(obj), bug14552) - def test_odd_p_even_p - Fixnum.class_eval do - alias odd_bak odd? - alias even_bak even? - remove_method :odd?, :even? - end + obj = Object.new + def obj.to_i; "str"; end + assert_raise(TypeError) { Integer(obj) } - assert(1.odd?) - assert(!(2.odd?)) - assert(!(1.even?)) - assert(2.even?) + bug6192 = '[ruby-core:43566]' + assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16be"))} + assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16le"))} + 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"))} - ensure - Fixnum.class_eval do - alias odd? odd_bak - alias even? even_bak - remove_method :odd_bak, :even_bak - end + 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_succ - assert_equal(2, 1.send(:succ)) + def test_Integer_with_exception_keyword + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer("1z", exception: false)) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer(Object.new, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; 42.5; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(-Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::NAN, exception: false)) + } - Fixnum.class_eval do - alias succ_bak succ - remove_method :succ - end + assert_raise(ArgumentError) { + Integer("1z", exception: true) + } + assert_raise(TypeError) { + Integer(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Integer(nil, exception: false)) + } - assert_equal(2, 1.succ) - assert_equal(4294967297, 4294967296.succ) + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Integer;def method_missing(*);"";end;end + assert_equal(0, Integer("0", 2)) + end; + end - ensure - Fixnum.class_eval do - alias succ succ_bak - remove_method :succ_bak - end + def test_int_p + assert_not_predicate(1.0, :integer?) + assert_predicate(1, :integer?) + end + + def test_succ + assert_equal(2, 1.send(:succ)) end def test_chr @@ -177,25 +253,365 @@ 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_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) - - 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) - - 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) + 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 + + 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 + + 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 + + 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 + + MimicInteger = Struct.new(:to_int) + module CoercionToInt + def coerce(other) + [other, to_int] + end + 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 + 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_xor_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 + + 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 + + def test_bit_length + assert_equal(13, (-2**12-1).bit_length) + assert_equal(12, (-2**12).bit_length) + assert_equal(12, (-2**12+1).bit_length) + assert_equal(9, -0x101.bit_length) + assert_equal(8, -0x100.bit_length) + assert_equal(8, -0xff.bit_length) + assert_equal(1, -2.bit_length) + assert_equal(0, -1.bit_length) + assert_equal(0, 0.bit_length) + assert_equal(1, 1.bit_length) + assert_equal(8, 0xff.bit_length) + assert_equal(9, 0x100.bit_length) + assert_equal(9, 0x101.bit_length) + assert_equal(12, (2**12-1).bit_length) + assert_equal(13, (2**12).bit_length) + assert_equal(13, (2**12+1).bit_length) + + assert_equal(10001, (-2**10000-1).bit_length) + assert_equal(10000, (-2**10000).bit_length) + assert_equal(10000, (-2**10000+1).bit_length) + assert_equal(10000, (2**10000-1).bit_length) + assert_equal(10001, (2**10000).bit_length) + assert_equal(10001, (2**10000+1).bit_length) + + 2.upto(1000) {|i| + n = 2**i + assert_equal(i+1, (-n-1).bit_length, "(#{-n-1}).bit_length") + assert_equal(i, (-n).bit_length, "(#{-n}).bit_length") + assert_equal(i, (-n+1).bit_length, "(#{-n+1}).bit_length") + assert_equal(i, (n-1).bit_length, "#{n-1}.bit_length") + assert_equal(i+1, (n).bit_length, "#{n}.bit_length") + 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) + + x = 0xffff_ffff_ffff_ffff + assert_equal(x, Integer.sqrt(x ** 2), "[ruby-core:95453]") + 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 c057deb36f..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 @@ -106,28 +107,8 @@ class TestIntegerComb < Test::Unit::TestCase ] #VS.map! {|v| 0x4000000000000000.coerce(v)[0] } - - 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 + #VS.concat VS.find_all {|v| Fixnum === v }.map {|v| 0x4000000000000000.coerce(v)[0] } + #VS.sort! {|a, b| a.abs <=> b.abs } def test_aref VS.each {|a| @@ -139,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 @@ -153,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 @@ -168,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 @@ -183,8 +164,9 @@ 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 assert_equal(a.abs * b.abs, (a * b).abs, "(#{a} * #{b}).abs") assert_equal((a-100)*(b-100)+(a-100)*100+(b-100)*100+10000, c, "#{a} * #{b}") @@ -200,11 +182,17 @@ 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(r.abs < b.abs) - assert(0 < b ? (0 <= r && r < b) : (b < r && r <= 0)) + assert_operator(r.abs, :<, b.abs) + if 0 < b + assert_operator(r, :>=, 0) + assert_operator(r, :<, b) + else + assert_operator(r, :>, b) + assert_operator(r, :<=, 0) + end assert_equal(q, a/b) assert_equal(q, a.div(b)) assert_equal(r, a%b) @@ -219,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}") @@ -235,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}") @@ -247,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 @@ -260,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 @@ -273,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}") @@ -286,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}]") } } @@ -303,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}]") } } @@ -318,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") @@ -328,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") @@ -338,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 @@ -346,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}))") @@ -378,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 @@ -390,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 @@ -398,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 @@ -406,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 @@ -414,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 @@ -426,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})") @@ -451,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?") } @@ -469,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 41d3640105..92fba2c04e 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -1,35 +1,116 @@ +# 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_relative 'envutil' +require 'weakref' class TestIO < Test::Unit::TestCase - def have_close_on_exec? - begin + module Feature + def have_close_on_exec? $stdin.close_on_exec? true rescue NotImplementedError false end + + def have_nonblock? + IO.method_defined?("nonblock=") + end end - def have_nonblock? - IO.method_defined?("nonblock=") + include Feature + extend Feature + + def pipe(wp, rp) + re, we = nil, nil + r, w = IO.pipe + rt = Thread.new do + begin + rp.call(r) + rescue Exception + r.close + re = $! + end + end + wt = Thread.new do + begin + wp.call(w) + rescue Exception + w.close + we = $! + end + end + flunk("timeout") unless wt.join(10) && rt.join(10) + ensure + w&.close + r&.close + (wt.kill; wt.join) if wt + (rt.kill; rt.join) if rt + raise we if we + raise re if re + end + + def with_pipe + r, w = IO.pipe + begin + yield r, w + ensure + r.close + w.close + end + end + + def with_read_pipe(content) + pipe(proc do |w| + w << content + w.close + end, proc do |r| + yield r + end) + end + + def mkcdtmpdir + Dir.mktmpdir {|d| + Dir.chdir(d) { + yield + } + } + end + + def trapping_usr2 + @usr2_rcvd = 0 + r, w = IO.pipe + trap(:USR2) do + w.write([@usr2_rcvd += 1].pack('L')) + end + yield r + ensure + trap(:USR2, "DEFAULT") + w&.close + r&.close end def test_pipe r, w = IO.pipe assert_instance_of(IO, r) assert_instance_of(IO, w) - w.print "abc" - w.close - assert_equal("abc", r.read) - r.close + [ + Thread.start{ + w.print "abc" + w.close + }, + Thread.start{ + assert_equal("abc", r.read) + r.close + } + ].each{|thr| thr.join} end def test_pipe_block @@ -38,16 +119,22 @@ class TestIO < Test::Unit::TestCase x = [r,w] assert_instance_of(IO, r) assert_instance_of(IO, w) - w.print "abc" - w.close - assert_equal("abc", r.read) - assert(!r.closed?) - assert(w.closed?) + [ + Thread.start do + w.print "abc" + w.close + end, + Thread.start do + assert_equal("abc", r.read) + end + ].each{|thr| thr.join} + assert_not_predicate(r, :closed?) + assert_predicate(w, :closed?) :foooo } assert_equal(:foooo, ret) - assert(x[0].closed?) - assert(x[1].closed?) + assert_predicate(x[0], :closed?) + assert_predicate(x[1], :closed?) end def test_pipe_block_close @@ -58,107 +145,218 @@ class TestIO < Test::Unit::TestCase r.close if (i&1) == 0 w.close if (i&2) == 0 } - assert(x[0].closed?) - assert(x[1].closed?) + assert_predicate(x[0], :closed?) + assert_predicate(x[1], :closed?) } end def test_gets_rs - # default_rs - r, w = IO.pipe - w.print "aaa\nbbb\n" - w.close - assert_equal "aaa\n", r.gets - assert_equal "bbb\n", r.gets - assert_nil r.gets - r.close - - # nil - r, w = IO.pipe - w.print "a\n\nb\n\n" - w.close - assert_equal "a\n\nb\n\n", r.gets(nil) - assert_nil r.gets("") - r.close - - # "\377" - r, w = IO.pipe('ascii-8bit') - w.print "\377xyz" - w.close - r.binmode - assert_equal("\377", r.gets("\377"), "[ruby-dev:24460]") - r.close - - # "" - r, w = IO.pipe - w.print "a\n\nb\n\n" - w.close - assert_equal "a\n\n", r.gets(""), "[ruby-core:03771]" - assert_equal "b\n\n", r.gets("") - assert_nil r.gets("") - r.close + 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 + end, proc do |r| + assert_equal "aaa\n", r.gets + assert_equal "bbb\n", r.gets + assert_nil r.gets + r.close + end) + end + + def test_gets_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\n", r.gets(nil) + assert_nil r.gets("") + r.close + end) + end + + def test_gets_rs_377 + pipe(proc do |w| + w.print "\377xyz" + w.close + end, proc do |r| + r.binmode + 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 + end, proc do |r| + assert_equal "a\n\n", r.gets(""), "[ruby-core:03771]" + assert_equal "b\n\n", r.gets("") + assert_nil r.gets("") + r.close + 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 - with_pipe {|r, w| - r, w = IO.pipe + pipe(proc do |w| w << "0123456789\n0123456789" w.close + end, proc do |r| assert_equal("0123456789\n0", r.gets(nil, 12)) assert_raise(TypeError) { r.gets(3,nil) } - } + end) end # This test cause SEGV. def test_ungetc - r, w = IO.pipe - w.close - s = "a" * 1000 - assert_raise(IOError, "[ruby-dev:31650]") { 200.times { r.ungetc s } } - ensure - r.close + pipe(proc do |w| + w.close + end, proc do |r| + s = "a" * 1000 + assert_raise(IOError, "[ruby-dev:31650]") { 200.times { r.ungetc s } } + end) end def test_ungetbyte - t = make_tempfile - t.open - t.binmode - t.ungetbyte(0x41) - assert_equal(-1, t.pos) - assert_equal(0x41, t.getbyte) - t.rewind - assert_equal(0, t.pos) - t.ungetbyte("qux") - assert_equal(-3, t.pos) - assert_equal("quxfoo\n", t.gets) - assert_equal(4, t.pos) - t.set_encoding("utf-8") - t.ungetbyte(0x89) - t.ungetbyte(0x8e) - t.ungetbyte("\xe7") - t.ungetbyte("\xe7\xb4\x85") - assert_equal(-2, t.pos) - assert_equal("\u7d05\u7389bar\n", t.gets) + make_tempfile {|t| + t.open + t.binmode + t.ungetbyte(0x41) + assert_equal(-1, t.pos) + assert_equal(0x41, t.getbyte) + t.rewind + assert_equal(0, t.pos) + t.ungetbyte("qux") + assert_equal(-3, t.pos) + assert_equal("quxfoo\n", t.gets) + assert_equal(4, t.pos) + t.set_encoding("utf-8") + t.ungetbyte(0x89) + t.ungetbyte(0x8e) + t.ungetbyte("\xe7") + t.ungetbyte("\xe7\xb4\x85") + assert_equal(-2, t.pos) + assert_equal("\u7d05\u7389bar\n", t.gets) + } end def test_each_byte - r, w = IO.pipe - w << "abc def" - w.close - r.each_byte {|byte| break if byte == 32 } - assert_equal("def", r.read, "[ruby-dev:31659]") - ensure - r.close + pipe(proc do |w| + w << "abc def" + w.close + end, proc do |r| + r.each_byte {|byte| break if byte == 32 } + assert_equal("def", r.read, "[ruby-dev:31659]") + end) + end + + def test_each_byte_with_seek + make_tempfile {|t| + bug5119 = '[ruby-core:38609]' + i = 0 + open(t.path) do |f| + f.each_byte {i = f.pos} + end + assert_equal(12, i, bug5119) + } end def test_each_codepoint - t = make_tempfile - bug2959 = '[ruby-core:28650]' - a = "" - File.open(t, 'rt') {|f| - f.each_codepoint {|c| a << c} + make_tempfile {|t| + bug2959 = '[ruby-core:28650]' + a = "" + File.open(t, 'rt') {|f| + f.each_codepoint {|c| a << c} + } + assert_equal("foo\nbar\nbaz\n", a, bug2959) + } + end + + def test_codepoints + make_tempfile {|t| + bug2959 = '[ruby-core:28650]' + a = "" + File.open(t, 'rt') {|f| + assert_warn(/deprecated/) { + f.codepoints {|c| a << c} + } + } + assert_equal("foo\nbar\nbaz\n", a, bug2959) } - assert_equal("foo\nbar\nbaz\n", a, bug2959) end def test_rubydev33072 @@ -170,165 +368,216 @@ class TestIO < Test::Unit::TestCase end end - def with_pipe - r, w = IO.pipe - begin - yield r, w - ensure - r.close unless r.closed? - w.close unless w.closed? - end + def with_srccontent(content = "baz") + src = "src" + mkcdtmpdir { + File.open(src, "w") {|f| f << content } + yield src, content + } end - def with_read_pipe(content) - r, w = IO.pipe - w << content - w.close - begin - yield r - ensure - r.close - end + def test_copy_stream_small + with_srccontent("foobar") {|src, content| + ret = IO.copy_stream(src, "dst") + assert_equal(content.bytesize, ret) + assert_equal(content, File.read("dst")) + } end - def mkcdtmpdir - Dir.mktmpdir {|d| - Dir.chdir(d) { - yield - } + 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 - mkcdtmpdir { - - content = "foobar" - File.open("src", "w") {|f| f << content } - ret = IO.copy_stream("src", "dst") - assert_equal(content.bytesize, ret) - assert_equal(content, File.read("dst")) + def test_copy_stream_smaller + with_srccontent {|src, content| # overwrite by smaller file. - content = "baz" - File.open("src", "w") {|f| f << content } - ret = IO.copy_stream("src", "dst") + dst = "dst" + File.open(dst, "w") {|f| f << "foobar"} + + ret = IO.copy_stream(src, dst) assert_equal(content.bytesize, ret) - assert_equal(content, File.read("dst")) + assert_equal(content, File.read(dst)) - ret = IO.copy_stream("src", "dst", 2) + ret = IO.copy_stream(src, dst, 2) assert_equal(2, ret) - assert_equal(content[0,2], File.read("dst")) + assert_equal(content[0,2], File.read(dst)) - ret = IO.copy_stream("src", "dst", 0) + ret = IO.copy_stream(src, dst, 0) assert_equal(0, ret) - assert_equal("", File.read("dst")) + assert_equal("", File.read(dst)) - ret = IO.copy_stream("src", "dst", nil, 1) + ret = IO.copy_stream(src, dst, nil, 1) assert_equal(content.bytesize-1, ret) - assert_equal(content[1..-1], File.read("dst")) + assert_equal(content[1..-1], File.read(dst)) + } + end + def test_copy_stream_noent + with_srccontent {|src, content| assert_raise(Errno::ENOENT) { IO.copy_stream("nodir/foo", "dst") } assert_raise(Errno::ENOENT) { - IO.copy_stream("src", "nodir/bar") + IO.copy_stream(src, "nodir/bar") } + } + end - with_pipe {|r, w| - ret = IO.copy_stream("src", w) + def test_copy_stream_pipe + with_srccontent {|src, content| + pipe(proc do |w| + ret = IO.copy_stream(src, w) assert_equal(content.bytesize, ret) w.close + end, proc do |r| assert_equal(content, r.read) - } + end) + } + end + def test_copy_stream_write_pipe + with_srccontent {|src, content| with_pipe {|r, w| w.close - assert_raise(IOError) { IO.copy_stream("src", w) } + assert_raise(IOError) { IO.copy_stream(src, w) } } + } + end + + def with_pipecontent + mkcdtmpdir { + yield "abc" + } + end - pipe_content = "abc" + def test_copy_stream_pipe_to_file + with_pipecontent {|pipe_content| + dst = "dst" with_read_pipe(pipe_content) {|r| - ret = IO.copy_stream(r, "dst") + ret = IO.copy_stream(r, dst) assert_equal(pipe_content.bytesize, ret) - assert_equal(pipe_content, File.read("dst")) + assert_equal(pipe_content, File.read(dst)) } + } + end - with_read_pipe("abc") {|r1| + def test_copy_stream_read_pipe + with_pipecontent {|pipe_content| + with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| + pipe(proc do |w2| w2.sync = false w2 << "def" ret = IO.copy_stream(r1, w2) assert_equal(2, ret) w2.close + end, proc do |r2| assert_equal("defbc", r2.read) - } + end) } - with_read_pipe("abc") {|r1| + with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| + pipe(proc do |w2| w2.sync = false w2 << "def" ret = IO.copy_stream(r1, w2, 1) assert_equal(1, ret) w2.close + end, proc do |r2| assert_equal("defb", r2.read) - } + end) } - with_read_pipe("abc") {|r1| + with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| + pipe(proc do |w2| ret = IO.copy_stream(r1, w2) assert_equal(2, ret) w2.close + end, proc do |r2| assert_equal("bc", r2.read) - } + end) } - with_read_pipe("abc") {|r1| + with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| + pipe(proc do |w2| ret = IO.copy_stream(r1, w2, 1) assert_equal(1, ret) w2.close + end, proc do |r2| assert_equal("b", r2.read) - } + end) } - with_read_pipe("abc") {|r1| + with_read_pipe(pipe_content) {|r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| + pipe(proc do |w2| ret = IO.copy_stream(r1, w2, 0) assert_equal(0, ret) w2.close + end, proc do |r2| assert_equal("", r2.read) - } + end) } - with_pipe {|r1, w1| + pipe(proc do |w1| w1 << "abc" + w1 << "def" + w1.close + end, proc do |r1| assert_equal("a", r1.getc) - with_pipe {|r2, w2| - w1 << "def" - w1.close + pipe(proc do |w2| ret = IO.copy_stream(r1, w2) assert_equal(5, ret) w2.close + end, proc do |r2| assert_equal("bcdef", r2.read) - } - } + end) + end) + } + end - with_pipe {|r, w| - ret = IO.copy_stream("src", w, 1, 1) + def test_copy_stream_file_to_pipe + with_srccontent {|src, content| + pipe(proc do |w| + ret = IO.copy_stream(src, w, 1, 1) assert_equal(1, ret) w.close + end, proc do |r| assert_equal(content[1,1], r.read) - } + end) + } + end + + if have_nonblock? + def test_copy_stream_no_busy_wait + skip "MJIT has busy wait on GC. This sometimes fails with --jit." if RubyVM::MJIT.enabled? + skip "multiple threads already active" if Thread.list.size > 1 + + msg = 'r58534 [ruby-core:80969] [Backport #13533]' + IO.pipe do |r,w| + r.nonblock = true + assert_cpu_usage_low(msg) do + th = Thread.new { IO.copy_stream(r, IO::NULL) } + sleep 0.1 + w.close + th.join + end + end + end - if have_nonblock? + def test_copy_stream_pipe_nonblock + mkcdtmpdir { with_read_pipe("abc") {|r1| assert_equal("a", r1.getc) with_pipe {|r2, w2| @@ -336,7 +585,6 @@ class TestIO < Test::Unit::TestCase w2.nonblock = true rescue Errno::EBADF skip "nonblocking IO for pipe is not implemented" - break end s = w2.syswrite("a" * 100000) t = Thread.new { sleep 0.1; r2.read } @@ -346,25 +594,51 @@ class TestIO < Test::Unit::TestCase assert_equal("a" * s + "bc", t.value) } } - end + } + end + end + + def with_bigcontent + yield "abc" * 123456 + end - bigcontent = "abc" * 123456 - File.open("bigsrc", "w") {|f| f << bigcontent } - ret = IO.copy_stream("bigsrc", "bigdst") + def with_bigsrc + mkcdtmpdir { + with_bigcontent {|bigcontent| + bigsrc = "bigsrc" + File.open("bigsrc", "w") {|f| f << bigcontent } + yield bigsrc, bigcontent + } + } + end + + def test_copy_stream_bigcontent + with_bigsrc {|bigsrc, bigcontent| + ret = IO.copy_stream(bigsrc, "bigdst") assert_equal(bigcontent.bytesize, ret) assert_equal(bigcontent, File.read("bigdst")) + } + end - File.unlink("bigdst") - ret = IO.copy_stream("bigsrc", "bigdst", nil, 100) + def test_copy_stream_bigcontent_chop + with_bigsrc {|bigsrc, bigcontent| + ret = IO.copy_stream(bigsrc, "bigdst", nil, 100) assert_equal(bigcontent.bytesize-100, ret) assert_equal(bigcontent[100..-1], File.read("bigdst")) + } + end - File.unlink("bigdst") - ret = IO.copy_stream("bigsrc", "bigdst", 30000, 100) + def test_copy_stream_bigcontent_mid + with_bigsrc {|bigsrc, bigcontent| + ret = IO.copy_stream(bigsrc, "bigdst", 30000, 100) assert_equal(30000, ret) assert_equal(bigcontent[100, 30000], File.read("bigdst")) + } + end - File.open("bigsrc") {|f| + def test_copy_stream_bigcontent_fpos + with_bigsrc {|bigsrc, bigcontent| + File.open(bigsrc) {|f| begin assert_equal(0, f.pos) ret = IO.copy_stream(f, "bigdst", nil, 10) @@ -376,19 +650,38 @@ class TestIO < Test::Unit::TestCase assert_equal(bigcontent[30, 40], File.read("bigdst")) assert_equal(0, f.pos) rescue NotImplementedError - #skip "pread(2) is not implemtented." + #skip "pread(2) is not implemented." end } + } + end + def test_copy_stream_closed_pipe + with_srccontent {|src,| with_pipe {|r, w| w.close - assert_raise(IOError) { IO.copy_stream("src", w) } + assert_raise(IOError) { IO.copy_stream(src, w) } } + } + end - megacontent = "abc" * 1234567 - File.open("megasrc", "w") {|f| f << megacontent } + def with_megacontent + yield "abc" * 1234567 + end - if have_nonblock? + def with_megasrc + mkcdtmpdir { + with_megacontent {|megacontent| + megasrc = "megasrc" + File.open(megasrc, "w") {|f| f << megacontent } + yield megasrc, megacontent + } + } + end + + if have_nonblock? + def test_copy_stream_megacontent_nonblock + with_megacontent {|megacontent| with_pipe {|r1, w1| with_pipe {|r2, w2| begin @@ -399,33 +692,48 @@ class TestIO < Test::Unit::TestCase end t1 = Thread.new { w1 << megacontent; w1.close } t2 = Thread.new { r2.read } - ret = IO.copy_stream(r1, w2) - assert_equal(megacontent.bytesize, ret) - w2.close - t1.join - assert_equal(megacontent, t2.value) + t3 = Thread.new { + ret = IO.copy_stream(r1, w2) + assert_equal(megacontent.bytesize, ret) + w2.close + } + _, t2_value, _ = assert_join_threads([t1, t2, t3]) + assert_equal(megacontent, t2_value) } } - end + } + end + end + def test_copy_stream_megacontent_pipe_to_file + with_megasrc {|megasrc, megacontent| with_pipe {|r1, w1| with_pipe {|r2, w2| t1 = Thread.new { w1 << megacontent; w1.close } t2 = Thread.new { r2.read } - ret = IO.copy_stream(r1, w2) - assert_equal(megacontent.bytesize, ret) - w2.close - t1.join - assert_equal(megacontent, t2.value) + t3 = Thread.new { + ret = IO.copy_stream(r1, w2) + assert_equal(megacontent.bytesize, ret) + w2.close + } + _, t2_value, _ = assert_join_threads([t1, t2, t3]) + assert_equal(megacontent, t2_value) } } + } + end + def test_copy_stream_megacontent_file_to_pipe + with_megasrc {|megasrc, megacontent| with_pipe {|r, w| - t = Thread.new { r.read } - ret = IO.copy_stream("megasrc", w) - assert_equal(megacontent.bytesize, ret) - w.close - assert_equal(megacontent, t.value) + t1 = Thread.new { r.read } + t2 = Thread.new { + ret = IO.copy_stream(megasrc, w) + assert_equal(megacontent.bytesize, ret) + w.close + } + t1_value, _ = assert_join_threads([t1, t2]) + assert_equal(megacontent, t1_value) } } end @@ -433,15 +741,16 @@ class TestIO < Test::Unit::TestCase def test_copy_stream_rbuf mkcdtmpdir { begin - with_pipe {|r, w| + pipe(proc do |w| File.open("foo", "w") {|f| f << "abcd" } File.open("foo") {|f| f.read(1) assert_equal(3, IO.copy_stream(f, w, 10, 1)) } w.close + end, proc do |r| assert_equal("bcd", r.read) - } + end) rescue NotImplementedError skip "pread(2) is not implemtented." end @@ -458,88 +767,145 @@ class TestIO < Test::Unit::TestCase end end - def test_copy_stream_socket - return unless defined? UNIXSocket - mkcdtmpdir { - - content = "foobar" - File.open("src", "w") {|f| f << content } - + def test_copy_stream_socket1 + with_srccontent("foobar") {|src, content| with_socketpair {|s1, s2| - ret = IO.copy_stream("src", s1) + ret = IO.copy_stream(src, s1) assert_equal(content.bytesize, ret) s1.close assert_equal(content, s2.read) } + } + end if defined? UNIXSocket - bigcontent = "abc" * 123456 - File.open("bigsrc", "w") {|f| f << bigcontent } - + def test_copy_stream_socket2 + with_bigsrc {|bigsrc, bigcontent| with_socketpair {|s1, s2| - t = Thread.new { s2.read } - ret = IO.copy_stream("bigsrc", s1) - assert_equal(bigcontent.bytesize, ret) - s1.close - result = t.value + t1 = Thread.new { s2.read } + t2 = Thread.new { + ret = IO.copy_stream(bigsrc, s1) + assert_equal(bigcontent.bytesize, ret) + s1.close + } + result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent, result) } + } + end if defined? UNIXSocket + def test_copy_stream_socket3 + with_bigsrc {|bigsrc, bigcontent| with_socketpair {|s1, s2| - t = Thread.new { s2.read } - ret = IO.copy_stream("bigsrc", s1, 10000) - assert_equal(10000, ret) - s1.close - result = t.value + t1 = Thread.new { s2.read } + t2 = Thread.new { + ret = IO.copy_stream(bigsrc, s1, 10000) + assert_equal(10000, ret) + s1.close + } + result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[0,10000], result) } + } + end if defined? UNIXSocket - File.open("bigsrc") {|f| + def test_copy_stream_socket4 + with_bigsrc {|bigsrc, bigcontent| + File.open(bigsrc) {|f| assert_equal(0, f.pos) with_socketpair {|s1, s2| - t = Thread.new { s2.read } - ret = IO.copy_stream(f, s1, nil, 100) - assert_equal(bigcontent.bytesize-100, ret) - assert_equal(0, f.pos) - s1.close - result = t.value + t1 = Thread.new { s2.read } + t2 = Thread.new { + ret = IO.copy_stream(f, s1, nil, 100) + assert_equal(bigcontent.bytesize-100, ret) + assert_equal(0, f.pos) + s1.close + } + result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[100..-1], result) } } + } + end if defined? UNIXSocket - File.open("bigsrc") {|f| + def test_copy_stream_socket5 + with_bigsrc {|bigsrc, bigcontent| + File.open(bigsrc) {|f| assert_equal(bigcontent[0,100], f.read(100)) assert_equal(100, f.pos) with_socketpair {|s1, s2| - t = Thread.new { s2.read } - ret = IO.copy_stream(f, s1) - assert_equal(bigcontent.bytesize-100, ret) - assert_equal(bigcontent.length, f.pos) - s1.close - result = t.value + t1 = Thread.new { s2.read } + t2 = Thread.new { + ret = IO.copy_stream(f, s1) + assert_equal(bigcontent.bytesize-100, ret) + assert_equal(bigcontent.length, f.pos) + s1.close + } + result, _ = assert_join_threads([t1, t2]) assert_equal(bigcontent[100..-1], result) } } + } + end if defined? UNIXSocket + def test_copy_stream_socket6 + mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } - if have_nonblock? - with_socketpair {|s1, s2| - begin - s1.nonblock = true - rescue Errno::EBADF - skip "nonblocking IO for pipe is not implemented" - end - t = Thread.new { s2.read } + with_socketpair {|s1, s2| + begin + s1.nonblock = true + rescue Errno::EBADF + skip "nonblocking IO for pipe is not implemented" + end + t1 = Thread.new { s2.read } + t2 = Thread.new { ret = IO.copy_stream("megasrc", s1) assert_equal(megacontent.bytesize, ret) s1.close - result = t.value - assert_equal(megacontent, result) } - end + result, _ = assert_join_threads([t1, t2]) + assert_equal(megacontent, result) + } } - end + end if defined? UNIXSocket + + def test_copy_stream_socket7 + GC.start + mkcdtmpdir { + megacontent = "abc" * 1234567 + File.open("megasrc", "w") {|f| f << megacontent } + + with_socketpair {|s1, s2| + begin + s1.nonblock = true + rescue Errno::EBADF + skip "nonblocking IO for pipe is not implemented" + end + trapping_usr2 do |rd| + nr = 30 + begin + pid = fork do + s1.close + IO.select([s2]) + Process.kill(:USR2, Process.ppid) + buf = String.new(capacity: 16384) + nil while s2.read(16384, buf) + end + s2.close + nr.times do + assert_equal megacontent.bytesize, IO.copy_stream("megasrc", s1) + end + assert_equal(1, rd.read(4).unpack1('L')) + ensure + s1.close + _, status = Process.waitpid2(pid) if pid + end + assert_predicate(status, :success?) + end + } + } + end if defined? UNIXSocket and IO.method_defined?("nonblock=") def test_copy_stream_strio src = StringIO.new("abcd") @@ -617,6 +983,73 @@ 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 { + EnvUtil.with_default_internal(Encoding::UTF_8) do + # StringIO to object with to_path + bytes = "\xDE\xAD\xBE\xEF".force_encoding(Encoding::ASCII_8BIT) + src = StringIO.new(bytes) + dst = Object.new + def dst.to_path + "qux" + end + assert_nothing_raised(bug8767) { + IO.copy_stream(src, dst) + } + assert_equal(bytes, File.binread("qux"), bug8767) + assert_equal(4, src.pos, bug8767) + end + } + end + + def test_copy_stream_read_in_binmode + bug8767 = '[ruby-core:56518] [Bug #8767]' + mkcdtmpdir { + EnvUtil.with_default_internal(Encoding::UTF_8) do + # StringIO to object with to_path + bytes = "\xDE\xAD\xBE\xEF".force_encoding(Encoding::ASCII_8BIT) + File.binwrite("qux", bytes) + dst = StringIO.new + src = Object.new + def src.to_path + "qux" + end + assert_nothing_raised(bug8767) { + IO.copy_stream(src, dst) + } + assert_equal(bytes, dst.string.b, bug8767) + assert_equal(4, dst.pos, bug8767) + end + } + end + class Rot13IO def initialize(io) @io = io @@ -686,28 +1119,30 @@ class TestIO < Test::Unit::TestCase w.write "zz" src = StringIO.new("abcd") IO.copy_stream(src, w) - t = Thread.new { + t1 = Thread.new { w.close } - assert_equal("zzabcd", r.read) - t.join + t2 = Thread.new { r.read } + _, result = assert_join_threads([t1, t2]) + assert_equal("zzabcd", result) } end def test_copy_stream_strio_rbuf - with_pipe {|r, w| + pipe(proc do |w| w << "abcd" w.close + end, proc do |r| assert_equal("a", r.read(1)) sio = StringIO.new IO.copy_stream(r, sio) assert_equal("bcd", sio.string) - } + end) end def test_copy_stream_src_wbuf mkcdtmpdir { - with_pipe {|r, w| + pipe(proc do |w| File.open("foe", "w+") {|f| f.write "abcd\n" f.rewind @@ -716,17 +1151,41 @@ class TestIO < Test::Unit::TestCase } assert_equal("xycd\n", File.read("foe")) w.close + end, proc do |r| assert_equal("cd\n", r.read) r.close - } + end) } end + class Bug5237 + attr_reader :count + def initialize + @count = 0 + end + + def read(bytes, buffer) + @count += 1 + buffer.replace "this is a test" + nil + end + end + + def test_copy_stream_broken_src_read_eof + src = Bug5237.new + dst = StringIO.new + assert_equal 0, src.count + th = Thread.new { IO.copy_stream(src, dst) } + flunk("timeout") unless th.join(10) + assert_equal 1, src.count + end + def test_copy_stream_dst_rbuf mkcdtmpdir { - with_pipe {|r, w| + pipe(proc do |w| w << "xyz" w.close + end, proc do |r| File.open("fom", "w+b") {|f| f.write "abcd\n" f.rewind @@ -735,40 +1194,40 @@ class TestIO < Test::Unit::TestCase IO.copy_stream(r, f) } assert_equal("abxyz", File.read("fom")) - } + end) } end - def safe_4 - t = Thread.new do - $SAFE = 4 - yield - end - unless t.join(10) - t.kill - flunk("timeout in safe_4") - end - end - - def pipe(wp, rp) - r, w = IO.pipe - rt = Thread.new { rp.call(r) } - wt = Thread.new { wp.call(w) } - flunk("timeout") unless rt.join(10) && wt.join(10) - ensure - r.close unless !r || r.closed? - w.close unless !w || w.closed? - (rt.kill; rt.join) if rt - (wt.kill; wt.join) if wt + def test_copy_stream_to_duplex_io + result = IO.pipe {|a,w| + th = Thread.start {w.puts "yes"; w.close} + IO.popen([EnvUtil.rubybin, '-pe$_="#$.:#$_"'], "r+") {|b| + IO.copy_stream(a, b) + b.close_write + assert_join_threads([th]) + b.read + } + } + assert_equal("1:yes\n", result) end def ruby(*args) args = ['-e', '$>.write($<.read)'] if args.empty? ruby = EnvUtil.rubybin - f = IO.popen([ruby] + args, 'r+') + opts = {} + if defined?(Process::RLIMIT_NPROC) + lim = Process.getrlimit(Process::RLIMIT_NPROC)[1] + opts[:rlimit_nproc] = [lim, 2048].min + end + f = IO.popen([ruby] + args, 'r+', opts) + pid = f.pid yield(f) ensure f.close unless !f || f.closed? + begin + Process.wait(pid) + rescue Errno::ECHILD, Errno::ESRCH + end end def test_try_convert @@ -779,7 +1238,7 @@ class TestIO < Test::Unit::TestCase def test_ungetc2 f = false pipe(proc do |w| - 0 until f + Thread.pass until f w.write("1" * 10000) w.close end, proc do |r| @@ -789,6 +1248,70 @@ 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_no_args + IO.pipe do |r, w| + assert_equal 0, w.write, '[ruby-core:86285] [Bug #14338]' + assert_equal :wait_readable, r.read_nonblock(1, exception: false) + end + end + def test_write_non_writable with_pipe do |r, w| assert_raise(IOError) do @@ -799,44 +1322,39 @@ 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| - 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 with_pipe do |r, w| assert_match(/^#<IO:fd \d+>$/, r.inspect) - assert_raise(SecurityError) do - safe_4 { r.inspect } - end + r.freeze + assert_match(/^#<IO:fd \d+>$/, r.inspect) end end @@ -856,7 +1374,7 @@ class TestIO < Test::Unit::TestCase with_pipe do |r, w| s = "" t = Thread.new { r.readpartial(5, s) } - 0 until s.size == 5 + Thread.pass until t.stop? assert_raise(RuntimeError) { s.clear } w.write "foobarbaz" w.close @@ -875,6 +1393,27 @@ class TestIO < Test::Unit::TestCase } end + def test_readpartial_with_not_empty_buffer + pipe(proc do |w| + w.write "foob" + w.close + end, proc do |r| + r.readpartial(5, s = "01234567") + assert_equal("foob", s) + end) + end + + def test_readpartial_buffer_error + with_pipe do |r, w| + s = "" + t = Thread.new { r.readpartial(5, s) } + Thread.pass until t.stop? + t.kill + t.value + assert_equal("", s) + end + end if /cygwin/ !~ RUBY_PLATFORM + def test_read pipe(proc do |w| w.write "foobarbaz" @@ -891,7 +1430,7 @@ class TestIO < Test::Unit::TestCase with_pipe do |r, w| s = "" t = Thread.new { r.read(5, s) } - 0 until s.size == 5 + Thread.pass until t.stop? assert_raise(RuntimeError) { s.clear } w.write "foobarbaz" w.close @@ -899,8 +1438,36 @@ class TestIO < Test::Unit::TestCase end end + def test_read_with_not_empty_buffer + pipe(proc do |w| + w.write "foob" + w.close + end, proc do |r| + r.read(nil, s = "01234567") + assert_equal("foob", s) + end) + end + + def test_read_buffer_error + with_pipe do |r, w| + s = "" + t = Thread.new { r.read(5, s) } + Thread.pass until t.stop? + t.kill + t.value + assert_equal("", s) + end + with_pipe do |r, w| + s = "xxx" + t = Thread.new {r.read(2, s)} + Thread.pass until t.stop? + t.kill + t.value + assert_equal("xxx", s) + 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 @@ -909,9 +1476,25 @@ class TestIO < Test::Unit::TestCase end) end + def test_read_nonblock_with_not_empty_buffer + with_pipe {|r, w| + w.write "foob" + w.close + r.read_nonblock(5, s = "01234567") + assert_equal("foob", s) + } + end + + def test_write_nonblock_simple_no_exceptions + pipe(proc do |w| + w.write_nonblock('1', exception: false) + w.close + end, proc do |r| + assert_equal("1", r.read) + end) + 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 @@ -919,11 +1502,40 @@ class TestIO < Test::Unit::TestCase assert_kind_of(IO::WaitReadable, $!) end } - end + + with_pipe {|r, w| + begin + r.read_nonblock 4096, "" + rescue Errno::EWOULDBLOCK + assert_kind_of(IO::WaitReadable, $!) + end + } + end if have_nonblock? + + def test_read_nonblock_no_exceptions + with_pipe {|r, w| + assert_equal :wait_readable, r.read_nonblock(4096, exception: false) + w.puts "HI!" + assert_equal "HI!\n", r.read_nonblock(4096, exception: false) + w.close + assert_equal nil, r.read_nonblock(4096, exception: false) + } + end if have_nonblock? + + def test_read_nonblock_with_buffer_no_exceptions + with_pipe {|r, w| + assert_equal :wait_readable, r.read_nonblock(4096, "", exception: false) + w.puts "HI!" + buf = "buf" + value = r.read_nonblock(4096, buf, exception: false) + assert_equal value, "HI!\n" + assert_same(buf, value) + w.close + assert_equal nil, r.read_nonblock(4096, "", exception: false) + } + 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 { @@ -933,7 +1545,19 @@ class TestIO < Test::Unit::TestCase assert_kind_of(IO::WaitWritable, $!) end } - end + end if have_nonblock? + + def test_write_nonblock_no_exceptions + with_pipe {|r, w| + loop { + ret = w.write_nonblock("a"*100000, exception: false) + if ret.is_a?(Symbol) + assert_equal :wait_writable, ret + break + end + } + } + end if have_nonblock? def test_gets pipe(proc do |w| @@ -941,7 +1565,7 @@ class TestIO < Test::Unit::TestCase w.close end, proc do |r| assert_equal("", r.gets(0)) - assert_equal("foobarbaz", s = r.gets(9)) + assert_equal("foobarbaz", r.gets(9)) end) end @@ -950,6 +1574,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 @@ -957,15 +1584,21 @@ 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_close_read_security_error - with_pipe do |r, w| - assert_raise(SecurityError) do - safe_4 { 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 @@ -981,84 +1614,113 @@ 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 - def test_close_write_security_error + def test_close_write_non_readable with_pipe do |r, w| - assert_raise(SecurityError) do - safe_4 { r.close_write } + assert_raise(IOError) do + r.close_write end end end - def test_close_write_non_readable - with_pipe do |r, w| - assert_raise(IOError) do - r.close_write + def test_close_read_write_separately + bug = '[ruby-list:49598]' + (1..10).each do |i| + assert_nothing_raised(IOError, "#{bug} trying ##{i}") do + IO.popen(EnvUtil.rubybin, "r+") {|f| + th = Thread.new {f.close_write} + f.close_read + th.join + } end end 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 + def test_pid_after_close_read + pid1 = pid2 = nil + IO.popen("exit ;", "r+") do |io| + pid1 = io.pid + io.close_read + pid2 = io.pid + end + assert_not_nil(pid1) + assert_equal(pid1, pid2) + end + def make_tempfile - t = Tempfile.new("foo") + t = Tempfile.new("test_io") t.binmode t.puts "foo" t.puts "bar" t.puts "baz" t.close - t + if block_given? + begin + yield t + ensure + t.close(true) + end + else + t + end end def test_set_lineno - t = make_tempfile - - ruby("-e", <<-SRC, t.path) do |f| - 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 $. - end - SRC - assert_equal("0,1,2,2,1001,1001,1001,1,2,3,3", f.read.chomp.gsub("\n", ",")) - end + make_tempfile {|t| + assert_separately(["-", t.path], <<-SRC) + open(ARGV[0]) do |f| + 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 - pipe(proc do |w| - w.puts "foo" - w.puts "bar" - w.puts "baz" - w.close - end, proc do |r| - r.gets; assert_equal(1, $.) - r.gets; assert_equal(2, $.) - r.lineno = 1000; assert_equal(2, $.) - r.gets; assert_equal(1001, $.) - r.gets; assert_equal(1001, $.) - end) + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + r.gets; assert_equal(1, $.) + r.gets; assert_equal(2, $.) + r.lineno = 1000; assert_equal(2, $.) + r.gets; assert_equal(1001, $.) + r.gets; assert_equal(1001, $.) + end) + } end def test_readline @@ -1090,21 +1752,28 @@ class TestIO < Test::Unit::TestCase end def test_lines + verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| - e = r.lines + e = nil + assert_warn(/deprecated/) { + e = r.lines + } assert_equal("foo\n", e.next) assert_equal("bar\n", e.next) assert_equal("baz\n", e.next) assert_raise(StopIteration) { e.next } end) + ensure + $VERBOSE = verbose end def test_bytes + verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.binmode w.puts "foo" @@ -1112,27 +1781,38 @@ class TestIO < Test::Unit::TestCase w.puts "baz" w.close end, proc do |r| - e = r.bytes + e = nil + assert_warn(/deprecated/) { + e = r.bytes + } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c.ord, e.next) end assert_raise(StopIteration) { e.next } end) + ensure + $VERBOSE = verbose end def test_chars + verbose, $VERBOSE = $VERBOSE, nil pipe(proc do |w| w.puts "foo" w.puts "bar" w.puts "baz" w.close end, proc do |r| - e = r.chars + e = nil + assert_warn(/deprecated/) { + e = r.chars + } (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| assert_equal(c, e.next) end assert_raise(StopIteration) { e.next } end) + ensure + $VERBOSE = verbose end def test_readbyte @@ -1166,8 +1846,9 @@ 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 assert_equal(false, f.close_on_exec?) f.close_on_exec = true assert_equal(true, f.close_on_exec?) @@ -1176,125 +1857,281 @@ class TestIO < Test::Unit::TestCase end with_pipe do |r, w| + assert_equal(true, r.close_on_exec?) + r.close_on_exec = false assert_equal(false, r.close_on_exec?) r.close_on_exec = true assert_equal(true, r.close_on_exec?) r.close_on_exec = false assert_equal(false, r.close_on_exec?) + assert_equal(true, w.close_on_exec?) + w.close_on_exec = false assert_equal(false, w.close_on_exec?) w.close_on_exec = true assert_equal(true, w.close_on_exec?) w.close_on_exec = false assert_equal(false, w.close_on_exec?) end + end if have_close_on_exec? + + def test_pos + make_tempfile {|t| + open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| + f.write "Hello" + assert_equal(5, f.pos) + end + open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| + f.sync = true + f.read + f.write "Hello" + assert_equal(5, f.pos) + end + } end - def test_close_security_error - with_pipe do |r, w| - assert_raise(SecurityError) do - safe_4 { r.close } + def test_pos_with_getc + _bug6179 = '[ruby-core:43497]' + make_tempfile {|t| + ["", "t", "b"].each do |mode| + open(t.path, "w#{mode}") do |f| + f.write "0123456789\n" + end + + open(t.path, "r#{mode}") do |f| + assert_equal 0, f.pos, "mode=r#{mode}" + assert_equal '0', f.getc, "mode=r#{mode}" + assert_equal 1, f.pos, "mode=r#{mode}" + assert_equal '1', f.getc, "mode=r#{mode}" + assert_equal 2, f.pos, "mode=r#{mode}" + assert_equal '2', f.getc, "mode=r#{mode}" + assert_equal 3, f.pos, "mode=r#{mode}" + assert_equal '3', f.getc, "mode=r#{mode}" + assert_equal 4, f.pos, "mode=r#{mode}" + assert_equal '4', f.getc, "mode=r#{mode}" + end + end + } + 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_pos - t = make_tempfile + def test_seek + make_tempfile {|t| + open(t.path) { |f| + f.seek(9) + assert_equal("az\n", f.read) + } - open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| - f.write "Hello" - assert_equal(5, f.pos) - end - open(t.path, IO::RDWR|IO::CREAT|IO::TRUNC, 0600) do |f| - f.sync = true - f.read - f.write "Hello" - assert_equal(5, f.pos) - end + open(t.path) { |f| + f.seek(9, IO::SEEK_SET) + assert_equal("az\n", f.read) + } + + open(t.path) { |f| + f.seek(-4, IO::SEEK_END) + assert_equal("baz\n", f.read) + } + + open(t.path) { |f| + assert_equal("foo\n", f.gets) + f.seek(2, IO::SEEK_CUR) + assert_equal("r\nbaz\n", f.read) + } + + 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| + 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 + } end - def test_sysseek - t = make_tempfile + def test_seek_symwhence + make_tempfile {|t| + open(t.path) { |f| + f.seek(9, :SET) + assert_equal("az\n", f.read) + } - open(t.path) do |f| - f.sysseek(-4, IO::SEEK_END) - assert_equal("baz\n", f.read) - end + open(t.path) { |f| + f.seek(-4, :END) + assert_equal("baz\n", f.read) + } - open(t.path) do |f| - a = [f.getc, f.getc, f.getc] - a.reverse_each {|c| f.ungetc c } - assert_raise(IOError) { f.sysseek(1) } - end + open(t.path) { |f| + assert_equal("foo\n", f.gets) + f.seek(2, :CUR) + assert_equal("r\nbaz\n", f.read) + } + + 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 + } end - def test_syswrite - t = make_tempfile + def test_sysseek + make_tempfile {|t| + open(t.path) do |f| + f.sysseek(-4, IO::SEEK_END) + assert_equal("baz\n", f.read) + end - open(t.path, "w") do |f| - o = Object.new - def o.to_s; "FOO\n"; end - f.syswrite(o) - end - assert_equal("FOO\n", File.read(t.path)) + open(t.path) do |f| + a = [f.getc, f.getc, f.getc] + a.reverse_each {|c| f.ungetc c } + assert_raise(IOError) { f.sysseek(1) } + end + } + end + + def test_syswrite + make_tempfile {|t| + open(t.path, "w") do |f| + o = Object.new + def o.to_s; "FOO\n"; end + f.syswrite(o) + end + assert_equal("FOO\n", File.read(t.path)) + } end def test_sysread - t = make_tempfile + make_tempfile {|t| + open(t.path) do |f| + a = [f.getc, f.getc, f.getc] + a.reverse_each {|c| f.ungetc c } + assert_raise(IOError) { f.sysread(1) } + end + } + end - open(t.path) do |f| - a = [f.getc, f.getc, f.getc] - a.reverse_each {|c| f.ungetc c } - assert_raise(IOError) { f.sysread(1) } - end + def test_sysread_with_not_empty_buffer + pipe(proc do |w| + w.write "foob" + w.close + end, proc do |r| + r.sysread( 5, s = "01234567" ) + assert_equal( "foob", s ) + end) end def test_flag - t = make_tempfile + make_tempfile {|t| + assert_raise(ArgumentError) do + open(t.path, "z") { } + end - assert_raise(ArgumentError) do - open(t.path, "z") { } - end + assert_raise(ArgumentError) do + open(t.path, "rr") { } + end - assert_raise(ArgumentError) do - open(t.path, "rr") { } - end + assert_raise(ArgumentError) do + open(t.path, "rbt") { } + end + } end def test_sysopen - t = make_tempfile + make_tempfile {|t| + fd = IO.sysopen(t.path) + assert_kind_of(Integer, fd) + f = IO.for_fd(fd) + assert_equal("foo\nbar\nbaz\n", f.read) + f.close - fd = IO.sysopen(t.path) - assert_kind_of(Integer, fd) - f = IO.for_fd(fd) - assert_equal("foo\nbar\nbaz\n", f.read) - f.close + fd = IO.sysopen(t.path, "w", 0666) + assert_kind_of(Integer, fd) + if defined?(Fcntl::F_GETFL) + f = IO.for_fd(fd) + else + f = IO.for_fd(fd, 0666) + end + f.write("FOO\n") + f.close - fd = IO.sysopen(t.path, "w", 0666) - assert_kind_of(Integer, fd) - if defined?(Fcntl::F_GETFL) + fd = IO.sysopen(t.path, "r") + assert_kind_of(Integer, fd) f = IO.for_fd(fd) - else - f = IO.for_fd(fd, 0666) - end - f.write("FOO\n") - f.close - - fd = IO.sysopen(t.path, "r") - assert_kind_of(Integer, fd) - f = IO.for_fd(fd) - assert_equal("FOO\n", f.read) - f.close + assert_equal("FOO\n", f.read) + f.close + } end - def try_fdopen(fd, autoclose = true, level = 100) + def try_fdopen(fd, autoclose = true, level = 50) if level > 0 - try_fdopen(fd, autoclose, level - 1) - GC.start - level + begin + 1.times {return try_fdopen(fd, autoclose, level - 1)} + ensure + GC.start + end else - IO.for_fd(fd, autoclose: autoclose) - nil + WeakRef.new(IO.for_fd(fd, autoclose: autoclose)) end end @@ -1302,32 +2139,62 @@ class TestIO < Test::Unit::TestCase feature2250 = '[ruby-core:26222]' pre = 'ft2250' - Tempfile.new(pre) do |t| + Dir.mktmpdir {|d| + t = open("#{d}/#{pre}", "w") f = IO.for_fd(t.fileno) assert_equal(true, f.autoclose?) f.autoclose = false assert_equal(false, f.autoclose?) f.close - assert_nothing_raised(Errno::EBADF) {t.close} + assert_nothing_raised(Errno::EBADF, feature2250) {t.close} - t.open + t = open("#{d}/#{pre}", "w") f = IO.for_fd(t.fileno, autoclose: false) assert_equal(false, f.autoclose?) f.autoclose = true assert_equal(true, f.autoclose?) f.close - assert_raise(Errno::EBADF) {t.close} - end + assert_raise(Errno::EBADF, feature2250) {t.close} + } + end - Tempfile.new(pre) do |t| - try_fdopen(t.fileno) - assert_raise(Errno::EBADF) {t.close} + def test_autoclose_true_closed_by_finalizer + # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1465760 + # http://ci.rvm.jp/results/trunk-mjit@silicon-docker/1469765 + skip 'this randomly fails with MJIT' if RubyVM::MJIT.enabled? + + feature2250 = '[ruby-core:26222]' + pre = 'ft2250' + t = Tempfile.new(pre) + w = try_fdopen(t.fileno) + begin + w.close + begin + t.close + rescue Errno::EBADF + end + skip "expect IO object was GC'ed but not recycled yet" + rescue WeakRef::RefError + assert_raise(Errno::EBADF, feature2250) {t.close} end + ensure + t&.close! + end - Tempfile.new(pre) do |t| - try_fdopen(f.fileno, false) - assert_nothing_raised(Errno::EBADF) {t.close} + def test_autoclose_false_closed_by_finalizer + feature2250 = '[ruby-core:26222]' + pre = 'ft2250' + t = Tempfile.new(pre) + w = try_fdopen(t.fileno, false) + begin + w.close + t.close + skip "expect IO object was GC'ed but not recycled yet" + rescue WeakRef::RefError + assert_nothing_raised(Errno::EBADF, feature2250) {t.close} end + ensure + t.close! end def test_open_redirect @@ -1349,100 +2216,249 @@ class TestIO < Test::Unit::TestCase end end - def test_reopen - t = make_tempfile - - with_pipe do |r, w| - assert_raise(SecurityError) do - safe_4 { r.reopen(t.path) } - end + def test_read_command + assert_equal("foo\n", IO.read("|echo foo")) + assert_raise(Errno::ENOENT, Errno::EINVAL) do + File.read("|#{EnvUtil.rubybin} -e puts") end - - open(__FILE__) do |f| - f.gets - assert_nothing_raised { - f.reopen(t.path) - assert_equal("foo\n", f.gets) - } + assert_raise(Errno::ENOENT, Errno::EINVAL) do + File.binread("|#{EnvUtil.rubybin} -e puts") end - - open(__FILE__) do |f| - f.gets - f2 = open(t.path) - f2.gets - assert_nothing_raised { - f.reopen(f2) - assert_equal("bar\n", f.gets, '[ruby-core:24240]') - } + assert_raise(Errno::ENOENT, Errno::EINVAL) do + Class.new(IO).read("|#{EnvUtil.rubybin} -e puts") end - - open(__FILE__) do |f| - f2 = open(t.path) - f.reopen(f2) - assert_equal("foo\n", f.gets) - assert_equal("bar\n", f.gets) - f.reopen(f2) - assert_equal("baz\n", f.gets, '[ruby-dev:39479]') + 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| + f.gets + assert_nothing_raised { + f.reopen(t.path) + assert_equal("foo\n", f.gets) + } + end + + open(__FILE__) do |f| + f.gets + f2 = open(t.path) + begin + f2.gets + assert_nothing_raised { + f.reopen(f2) + assert_equal("bar\n", f.gets, '[ruby-core:24240]') + } + ensure + f2.close + end + end + + open(__FILE__) do |f| + f2 = open(t.path) + begin + f.reopen(f2) + assert_equal("foo\n", f.gets) + assert_equal("bar\n", f.gets) + f.reopen(f2) + assert_equal("baz\n", f.gets, '[ruby-dev:39479]') + ensure + f2.close + end + end + } + end + def test_reopen_inherit mkcdtmpdir { - system(EnvUtil.rubybin, '-e', <<"End") + system(EnvUtil.rubybin, '-e', <<-"End") f = open("out", "w") STDOUT.reopen(f) STDERR.reopen(f) system(#{EnvUtil.rubybin.dump}, '-e', 'STDOUT.print "out"') system(#{EnvUtil.rubybin.dump}, '-e', 'STDERR.print "err"') -End + End assert_equal("outerr", File.read("out")) } end - 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) + def test_reopen_stdio + mkcdtmpdir { + fname = 'bug11319' + File.write(fname, 'hello') + system(EnvUtil.rubybin, '-e', "STDOUT.reopen('#{fname}', 'w+')") + assert_equal('', File.read(fname)) + } + end + + def test_reopen_mode + feature7067 = '[ruby-core:47694]' + make_tempfile {|t| + open(__FILE__) do |f| + assert_nothing_raised { + f.reopen(t.path, "r") + assert_equal("foo\n", f.gets) + } + end + + open(__FILE__) do |f| + assert_nothing_raised(feature7067) { + f.reopen(t.path, File::RDONLY) + assert_equal("foo\n", f.gets) + } + end + } + end + def test_reopen_opt + feature7103 = '[ruby-core:47806]' + make_tempfile {|t| + open(__FILE__) do |f| + assert_nothing_raised(feature7103) { + f.reopen(t.path, "r", binmode: true) + } + assert_equal("foo\n", f.gets) + end + + open(__FILE__) do |f| + assert_nothing_raised(feature7103) { + f.reopen(t.path, autoclose: false) + } + assert_equal("foo\n", f.gets) + end + } + end + + def make_tempfile_for_encoding t = make_tempfile + open(t.path, "rb+:utf-8") {|f| f.puts "\u7d05\u7389bar\n"} + if block_given? + yield t + else + t + end + ensure + t&.close(true) if block_given? + end - a = [] - IO.foreach(t.path) {|x| a << x } - assert_equal(["foo\n", "bar\n", "baz\n"], a) + def test_reopen_encoding + make_tempfile_for_encoding {|t| + open(__FILE__) {|f| + f.reopen(t.path, "r:utf-8") + s = f.gets + assert_equal(Encoding::UTF_8, s.encoding) + assert_equal("\u7d05\u7389bar\n", s) + } - a = [] - IO.foreach(t.path, {:mode => "r" }) {|x| a << x } - assert_equal(["foo\n", "bar\n", "baz\n"], a) + open(__FILE__) {|f| + f.reopen(t.path, "r:UTF-8:EUC-JP") + s = f.gets + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xB9\xC8\xB6\xCCbar\n".force_encoding(Encoding::EUC_JP), s) + } + } + end - a = [] - IO.foreach(t.path, {:open_args => [] }) {|x| a << x } - assert_equal(["foo\n", "bar\n", "baz\n"], a) + def test_reopen_opt_encoding + feature7103 = '[ruby-core:47806]' + make_tempfile_for_encoding {|t| + open(__FILE__) {|f| + assert_nothing_raised(feature7103) {f.reopen(t.path, encoding: "ASCII-8BIT")} + s = f.gets + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xe7\xb4\x85\xe7\x8e\x89bar\n", s) + } + + open(__FILE__) {|f| + assert_nothing_raised(feature7103) {f.reopen(t.path, encoding: "UTF-8:EUC-JP")} + s = f.gets + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xB9\xC8\xB6\xCCbar\n".force_encoding(Encoding::EUC_JP), s) + } + } + 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(t.path, {:open_args => ["r"] }) {|x| a << x } + 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(t.path, "b") {|x| a << x } - assert_equal(["foo\nb", "ar\nb", "az\n"], a) + IO.foreach("|" + EnvUtil.rubybin + " -e 'puts :zot'", :open_args => ["r"]) {|x| a << x } + assert_equal(["zot\n"], a) - a = [] - IO.foreach(t.path, 3) {|x| a << x } - assert_equal(["foo", "\n", "bar", "\n", "baz", "\n"], a) + make_tempfile {|t| + a = [] + IO.foreach(t.path) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) - a = [] - IO.foreach(t.path, "b", 3) {|x| a << x } - assert_equal(["foo", "\nb", "ar\n", "b", "az\n"], a) + a = [] + IO.foreach(t.path, {:mode => "r" }) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + + a = [] + IO.foreach(t.path, {:open_args => [] }) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + + a = [] + IO.foreach(t.path, {:open_args => ["r"] }) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + a = [] + IO.foreach(t.path, "b") {|x| a << x } + assert_equal(["foo\nb", "ar\nb", "az\n"], a) + + a = [] + IO.foreach(t.path, 3) {|x| a << x } + assert_equal(["foo", "\n", "bar", "\n", "baz", "\n"], a) + + a = [] + IO.foreach(t.path, "b", 3) {|x| a << x } + assert_equal(["foo", "\nb", "ar\n", "b", "az\n"], a) + + bug = '[ruby-dev:31525]' + assert_raise(ArgumentError, bug) {IO.foreach} + + a = nil + assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a} + assert_equal(["foo\n", "bar\n", "baz\n"], a, bug) + + bug6054 = '[ruby-dev:45267]' + assert_raise_with_message(IOError, /not opened for reading/, bug6054) do + IO.foreach(t.path, mode:"w").next + end + } end def test_s_readlines - t = make_tempfile - - assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) - assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b")) - assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2)) - assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2)) + make_tempfile {|t| + assert_equal(["foo\n", "bar\n", "baz\n"], IO.readlines(t.path)) + assert_equal(["foo\nb", "ar\nb", "az\n"], IO.readlines(t.path, "b")) + assert_equal(["fo", "o\n", "ba", "r\n", "ba", "z\n"], IO.readlines(t.path, 2)) + assert_equal(["fo", "o\n", "b", "ar", "\nb", "az", "\n"], IO.readlines(t.path, "b", 2)) + } end def test_printf @@ -1455,9 +2471,11 @@ End end def test_print - t = make_tempfile - - assert_in_out_err(["-", t.path], "print while $<.gets", %w(foo bar baz), []) + make_tempfile {|t| + assert_in_out_err(["-", t.path], + "print while $<.gets", + %w(foo bar baz), []) + } end def test_print_separators @@ -1502,6 +2520,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) @@ -1517,35 +2565,58 @@ End assert_raise(TypeError) { $> = Object.new } assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo)) + + assert_separately(%w[-Eutf-8], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + alias $\u{6a19 6e96 51fa 529b} $stdout + x = eval("class X\u{307b 3052}; self; end".encode("euc-jp")) + assert_raise_with_message(TypeError, /\\$\u{6a19 6e96 51fa 529b} must.*, X\u{307b 3052} given/) do + $\u{6a19 6e96 51fa 529b} = x.new + end + end; end def test_initialize return unless defined?(Fcntl::F_GETFL) - t = make_tempfile - - fd = IO.sysopen(t.path, "w") - assert_kind_of(Integer, fd) - %w[r r+ w+ a+].each do |mode| - assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)} - end - f = IO.new(fd, "w") - f.write("FOO\n") - f.close + make_tempfile {|t| + fd = IO.sysopen(t.path, "w") + assert_kind_of(Integer, fd) + %w[r r+ w+ a+].each do |mode| + assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)} + end + f = IO.new(fd, "w") + f.write("FOO\n") + f.close - assert_equal("FOO\n", File.read(t.path)) + assert_equal("FOO\n", File.read(t.path)) + } end def test_reinitialize - t = make_tempfile - f = open(t.path) - assert_raise(RuntimeError) do - f.instance_eval { initialize } - end + make_tempfile {|t| + f = open(t.path) + begin + assert_raise(RuntimeError) do + f.instance_eval { initialize } + end + ensure + f.close + 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 @@ -1567,11 +2638,11 @@ End end def test_s_read - t = make_tempfile - - assert_equal("foo\nbar\nbaz\n", File.read(t.path)) - assert_equal("foo\nba", File.read(t.path, 6)) - assert_equal("bar\n", File.read(t.path, 4, 4)) + make_tempfile {|t| + assert_equal("foo\nbar\nbaz\n", File.read(t.path)) + assert_equal("foo\nba", File.read(t.path, 6)) + assert_equal("bar\n", File.read(t.path, 4, 4)) + } end def test_uninitialized @@ -1580,8 +2651,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 @@ -1596,17 +2665,1194 @@ End File.foreach("slnk", :open_args=>[File::RDONLY|File::NOFOLLOW]) {} } } - end + end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW def test_tainted - t = make_tempfile - assert(File.read(t.path, 4).tainted?, '[ruby-dev:38826]') - assert(File.open(t.path) {|f| f.read(4)}.tainted?, '[ruby-dev:38826]') + make_tempfile {|t| + assert_predicate(File.read(t.path, 4), :tainted?, '[ruby-dev:38826]') + assert_predicate(File.open(t.path) {|f| f.read(4)}, :tainted?, '[ruby-dev:38826]') + } end def test_binmode_after_closed - t = make_tempfile + make_tempfile {|t| + assert_raise(IOError) {t.binmode} + } + 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 = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + t = Thread.new { sleep 3 } + Thread.new {sleep 1; t.kill; p 'hi!'} + t.join + end; + 10.times.map do + Thread.start do + assert_in_out_err([], src, timeout: 20) {|stdout, stderr| + assert_no_match(/hi.*hi/, stderr.join, bug3585) + } + end + end.each {|th| th.join} + end + + def test_flush_in_finalizer1 + bug3910 = '[ruby-dev:42341]' + 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 + } + ensure + 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 + bug3910 = '[ruby-dev:42341]' + Tempfile.open("bug3910") {|t| + path = t.path + t.close + 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.close! + } + end + + def test_readlines_limit_0 + bug4024 = '[ruby-dev:42538]' + make_tempfile {|t| + open(t.path, "r") do |io| + assert_raise(ArgumentError, bug4024) do + io.readlines(0) + end + end + } + end + + def test_each_line_limit_0 + bug4024 = '[ruby-dev:42538]' + make_tempfile {|t| + open(t.path, "r") do |io| + assert_raise(ArgumentError, bug4024) do + io.each_line(0).next + end + 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| + 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 + assert_raise(TypeError, "wrong type for first argument") do + t.advise(adv.to_s, offset, len) + end + assert_raise(TypeError, "wrong type for last argument") do + t.advise(adv, offset, Array(len)) + end + assert_raise(RangeError, "last argument too big") do + t.advise(adv, offset, 9999e99) + end + end + assert_raise(IOError, "closed file") do + make_tempfile {|tf2| + tf2.advise(adv.to_sym, offset, len) + } + end + end + end + } + 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| + [[0,0], [0, 20], [400, 2]].each do |offset, len| + open(tf.path) do |t| + assert_raise_with_message(NotImplementedError, /#{Regexp.quote(adv.inspect)}/, feature4204) { t.advise(adv, offset, len) } + end + end + end + } + end + + def test_fcntl_lock_linux + pad = 0 + Tempfile.create(self.class.name) do |f| + r, w = IO.pipe + pid = fork do + r.close + lock = [Fcntl::F_WRLCK, IO::SEEK_SET, pad, 12, 34, 0].pack("s!s!i!L!L!i!") + f.fcntl Fcntl::F_SETLKW, lock + w.syswrite "." + sleep + end + w.close + assert_equal ".", r.read(1) + r.close + pad = 0 + getlock = [Fcntl::F_WRLCK, 0, pad, 0, 0, 0].pack("s!s!i!L!L!i!") + f.fcntl Fcntl::F_GETLK, getlock + + ptype, whence, pad, start, len, lockpid = getlock.unpack("s!s!i!L!L!i!") + + assert_equal(ptype, Fcntl::F_WRLCK) + assert_equal(whence, IO::SEEK_SET) + assert_equal(start, 12) + assert_equal(len, 34) + assert_equal(pid, lockpid) + + Process.kill :TERM, pid + Process.waitpid2(pid) + 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 + start = 12 + len = 34 + sysid = 0 + Tempfile.create(self.class.name) do |f| + r, w = IO.pipe + pid = fork do + r.close + lock = [start, len, 0, Fcntl::F_WRLCK, IO::SEEK_SET, sysid].pack("qqis!s!i!") + f.fcntl Fcntl::F_SETLKW, lock + w.syswrite "." + sleep + end + w.close + assert_equal ".", r.read(1) + r.close + + getlock = [0, 0, 0, Fcntl::F_WRLCK, 0, 0].pack("qqis!s!i!") + f.fcntl Fcntl::F_GETLK, getlock + + start, len, lockpid, ptype, whence, sysid = getlock.unpack("qqis!s!i!") + + assert_equal(ptype, Fcntl::F_WRLCK) + assert_equal(whence, IO::SEEK_SET) + assert_equal(start, 12) + assert_equal(len, 34) + assert_equal(pid, lockpid) + + Process.kill :TERM, pid + Process.waitpid2(pid) + 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| + fd = f.fcntl(Fcntl::F_DUPFD, 63) + begin + assert_operator(fd, :>=, 63) + ensure + IO.for_fd(fd).close + end + end + end + + def test_cross_thread_close_fd + with_pipe do |r,w| + read_thread = Thread.new do + begin + r.read(1) + rescue => e + e + end + end + + sleep(0.1) until read_thread.stop? + r.close + read_thread.join + assert_kind_of(IOError, read_thread.value) + end + end + + def test_cross_thread_close_stdio + assert_separately([], <<-'end;') + IO.pipe do |r,w| + $stdin.reopen(r) + r.close + read_thread = Thread.new do + begin + $stdin.read(1) + rescue IOError => e + e + end + end + sleep(0.1) until read_thread.stop? + $stdin.close + 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]' + + mkcdtmpdir do + assert_not_nil(f = File.open('symbolic', 'w')) + f.close + assert_not_nil(f = File.open('numeric', File::WRONLY|File::TRUNC|File::CREAT)) + f.close + assert_not_nil(f = File.open('hash-symbolic', :mode => 'w')) + f.close + assert_not_nil(f = File.open('hash-numeric', :mode => File::WRONLY|File::TRUNC|File::CREAT), feature4742) + f.close + assert_nothing_raised(bug6055) {f = File.open('hash-symbolic', binmode: true)} + f.close + end + end + + def test_s_write + mkcdtmpdir do + path = "test_s_write" + File.write(path, "foo\nbar\nbaz") + assert_equal("foo\nbar\nbaz", File.read(path)) + File.write(path, "FOO", 0) + assert_equal("FOO\nbar\nbaz", File.read(path)) + File.write(path, "BAR") + assert_equal("BAR", File.read(path)) + File.write(path, "\u{3042}", mode: "w", encoding: "EUC-JP") + assert_equal("\u{3042}".encode("EUC-JP"), File.read(path, encoding: "EUC-JP")) + File.delete path + assert_equal(6, File.write(path, 'string', 2)) + File.delete path + assert_raise(Errno::EINVAL) { File.write('nonexisting','string', -2) } + assert_equal(6, File.write(path, 'string')) + assert_equal(3, File.write(path, 'sub', 1)) + assert_equal("ssubng", File.read(path)) + File.delete path + assert_equal(3, File.write(path, "foo", encoding: "UTF-8")) + File.delete path + assert_equal(3, File.write(path, "foo", 0, encoding: "UTF-8")) + assert_equal("foo", File.read(path)) + assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) + assert_equal("ffo", File.read(path)) + File.delete path + assert_equal(1, File.write(path, "f", 1, encoding: "UTF-8")) + assert_equal("\00f", File.read(path)) + assert_equal(1, File.write(path, "f", 0, encoding: "UTF-8")) + assert_equal("ff", File.read(path)) + assert_raise(TypeError) { + File.write(path, "foo", Object.new => Object.new) + } + 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" + File.binwrite(path, "foo\nbar\nbaz") + assert_equal("foo\nbar\nbaz", File.read(path)) + File.binwrite(path, "FOO", 0) + assert_equal("FOO\nbar\nbaz", File.read(path)) + File.binwrite(path, "BAR") + assert_equal("BAR", File.read(path)) + File.binwrite(path, "\u{3042}") + assert_equal("\u{3042}".force_encoding("ASCII-8BIT"), File.binread(path)) + File.delete path + assert_equal(6, File.binwrite(path, 'string', 2)) + File.delete path + assert_equal(6, File.binwrite(path, 'string')) + assert_equal(3, File.binwrite(path, 'sub', 1)) + assert_equal("ssubng", File.binread(path)) + assert_equal(6, File.size(path)) + assert_raise(Errno::EINVAL) { File.binwrite('nonexisting', 'string', -2) } + assert_nothing_raised(TypeError) { File.binwrite(path, "string", mode: "w", encoding: "EUC-JP") } + end + end + + def test_race_between_read + Tempfile.create("test") {|file| + begin + path = file.path + file.close + write_file = File.open(path, "wt") + read_file = File.open(path, "rt") + + threads = [] + 10.times do |i| + threads << Thread.new {write_file.print(i)} + threads << Thread.new {read_file.read} + end + assert_join_threads(threads) + assert(true, "[ruby-core:37197]") + ensure + read_file.close + write_file.close + end + } + end + + def test_warn + assert_warning "warning\n" do + warn "warning" + end + + assert_warning '' do + warn + end + + assert_warning "[Feature #5029]\n[ruby-core:38070]\n" do + warn "[Feature #5029]", "[ruby-core:38070]" + end + end + + def test_cloexec + return unless defined? Fcntl::FD_CLOEXEC + open(__FILE__) {|f| + assert_predicate(f, :close_on_exec?) + g = f.dup + begin + assert_predicate(g, :close_on_exec?) + f.reopen(g) + assert_predicate(f, :close_on_exec?) + ensure + g.close + end + g = IO.new(f.fcntl(Fcntl::F_DUPFD)) + begin + assert_predicate(g, :close_on_exec?) + ensure + g.close + end + } + IO.pipe {|r,w| + assert_predicate(r, :close_on_exec?) + assert_predicate(w, :close_on_exec?) + } + end + + def test_ioctl_linux + # Alpha, mips, sparc and ppc have an another ioctl request number scheme. + # So, hardcoded 0x80045200 may fail. + assert_nothing_raised do + File.open('/dev/urandom'){|f1| + entropy_count = "" + # RNDGETENTCNT(0x80045200) mean "get entropy count". + f1.ioctl(0x80045200, entropy_count) + } + end + + buf = '' + assert_nothing_raised do + fionread = 0x541B + File.open(__FILE__){|f1| + f1.ioctl(fionread, buf) + } + end + assert_equal(File.size(__FILE__), buf.unpack('i!')[0]) + end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM + + def test_ioctl_linux2 + 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) + } + ensure + f&.close + end + end if /^(?:i.?86|x86_64)-linux/ =~ RUBY_PLATFORM + + def test_setpos + mkcdtmpdir { + File.open("tmp.txt", "wb") {|f| + f.puts "a" + f.puts "bc" + f.puts "def" + } + pos1 = pos2 = pos3 = nil + File.open("tmp.txt", "rb") {|f| + assert_equal("a\n", f.gets) + pos1 = f.pos + assert_equal("bc\n", f.gets) + pos2 = f.pos + assert_equal("def\n", f.gets) + pos3 = f.pos + assert_equal(nil, f.gets) + } + File.open("tmp.txt", "rb") {|f| + f.pos = pos1 + assert_equal("bc\n", f.gets) + assert_equal("def\n", f.gets) + assert_equal(nil, f.gets) + } + File.open("tmp.txt", "rb") {|f| + f.pos = pos2 + assert_equal("def\n", f.gets) + assert_equal(nil, f.gets) + } + File.open("tmp.txt", "rb") {|f| + f.pos = pos3 + assert_equal(nil, f.gets) + } + File.open("tmp.txt", "rb") {|f| + f.pos = File.size("tmp.txt") + s = "not empty string " + assert_equal("", f.read(0,s)) + } + } + end + + def test_std_fileno + assert_equal(0, STDIN.fileno) + assert_equal(1, STDOUT.fileno) + assert_equal(2, STDERR.fileno) + assert_equal(0, $stdin.fileno) + assert_equal(1, $stdout.fileno) + assert_equal(2, $stderr.fileno) + end + + def test_frozen_fileno + bug9865 = '[ruby-dev:48241] [Bug #9865]' + with_pipe do |r,w| + fd = r.fileno + assert_equal(fd, r.freeze.fileno, bug9865) + end + end + + def test_frozen_autoclose + with_pipe do |r,w| + assert_equal(true, r.freeze.autoclose?) + end + end + + def test_sysread_locktmp + bug6099 = '[ruby-dev:45297]' + buf = " " * 100 + data = "a" * 100 + with_pipe do |r,w| + th = Thread.new {r.sysread(100, buf)} + Thread.pass until th.stop? + buf.replace("") + assert_empty(buf, bug6099) + w.write(data) + Thread.pass while th.alive? + th.join + end + assert_equal(data, buf, bug6099) + end + + def test_readpartial_locktmp + bug6099 = '[ruby-dev:45297]' + buf = " " * 100 + data = "a" * 100 + th = nil + with_pipe do |r,w| + r.nonblock = true + th = Thread.new {r.readpartial(100, buf)} + + Thread.pass until th.stop? + + assert_equal 100, buf.bytesize + + msg = /can't modify string; temporarily locked/ + assert_raise_with_message(RuntimeError, msg) do + buf.replace("") + end + assert_predicate(th, :alive?) + w.write(data) + th.join + end + assert_equal(data, buf, bug6099) + end + + def test_advise_pipe + # we don't know if other platforms have a real posix_fadvise() + with_pipe do |r,w| + # Linux 2.6.15 and earlier returned EINVAL instead of ESPIPE + assert_raise(Errno::ESPIPE, Errno::EINVAL) { + r.advise(:willneed) or skip "fadvise(2) is not implemented" + } + assert_raise(Errno::ESPIPE, Errno::EINVAL) { + w.advise(:willneed) or skip "fadvise(2) is not implemented" + } + end + end if /linux/ =~ RUBY_PLATFORM + + def assert_buffer_not_raise_shared_string_error + bug6764 = '[ruby-core:46586]' + bug9847 = '[ruby-core:62643] [Bug #9847]' + size = 28 + data = [*"a".."z", *"A".."Z"].shuffle.join("") + t = Tempfile.new("test_io") + t.write(data) t.close - assert_raise(IOError) {t.binmode} + w = [] + assert_nothing_raised(RuntimeError, bug6764) do + buf = '' + File.open(t.path, "r") do |r| + while yield(r, size, buf) + w << buf.dup + end + end + end + assert_equal(data, w.join(""), bug9847) + ensure + t.close! + end + + def test_read_buffer_not_raise_shared_string_error + assert_buffer_not_raise_shared_string_error do |r, size, buf| + r.read(size, buf) + end + end + + def test_sysread_buffer_not_raise_shared_string_error + assert_buffer_not_raise_shared_string_error do |r, size, buf| + begin + r.sysread(size, buf) + rescue EOFError + nil + end + end + end + + def test_readpartial_buffer_not_raise_shared_string_error + assert_buffer_not_raise_shared_string_error do |r, size, buf| + begin + r.readpartial(size, buf) + rescue EOFError + nil + end + end + end + + def test_puts_recursive_ary + bug5986 = '[ruby-core:42444]' + c = Class.new { + def to_ary + [self] + end + } + s = StringIO.new + s.puts(c.new) + assert_equal("[...]\n", s.string, bug5986) + end + + def test_io_select_with_many_files + bug8080 = '[ruby-core:53349]' + + assert_normal_exit %q{ + require "tempfile" + + # 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+20) + rescue Errno::EPERM + exit 0 + end + + tempfiles = [] + (0..fd_setsize+1).map {|i| + tempfiles << Tempfile.open("test_io_select_with_many_files") + } + + IO.select(tempfiles) + }, bug8080, timeout: 50 + end if defined?(Process::RLIMIT_NOFILE) + + def test_read_32bit_boundary + bug8431 = '[ruby-core:55098] [Bug #8431]' + make_tempfile {|t| + assert_separately(["-", bug8431, t.path], <<-"end;") + msg = ARGV.shift + f = open(ARGV[0], "rb") + f.seek(0xffff_ffff) + assert_nil(f.read(1), msg) + end; + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_write_32bit_boundary + bug8431 = '[ruby-core:55098] [Bug #8431]' + make_tempfile {|t| + def t.close(unlink_now = false) + # TODO: Tempfile should deal with this delay on Windows? + # NOTE: re-opening with O_TEMPORARY does not work. + path = self.path + ret = super + if unlink_now + begin + File.unlink(path) + rescue Errno::ENOENT + rescue Errno::EACCES + sleep(2) + retry + end + end + ret + end + + begin + assert_separately(["-", bug8431, t.path], <<-"end;", timeout: 30) + msg = ARGV.shift + f = open(ARGV[0], "wb") + f.seek(0xffff_ffff) + begin + # this will consume very long time or fail by ENOSPC on a + # filesystem which sparse file is not supported + f.write('1') + pos = f.tell + rescue Errno::ENOSPC + skip "non-sparse file system" + rescue SystemCallError + else + assert_equal(0x1_0000_0000, pos, msg) + end + end; + rescue Timeout::Error + skip "Timeout because of slow file writing" + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_unlocktmp_ensure + bug8669 = '[ruby-core:56121] [Bug #8669]' + + str = "" + 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 = "" + 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| + w.write '.' + buf = String.new + assert_raise(ArgumentError) { r.readpartial(1, buf, exception: false) } + assert_raise(TypeError) { r.readpartial(1, exception: false) } + assert_equal [[r],[],[]], IO.select([r], nil, nil, 1) + assert_equal '.', r.readpartial(1) + end + end + + def test_sysread_unlocktmp_ensure + bug8669 = '[ruby-core:56121] [Bug #8669]' + + str = "" + 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' + assert_raise(Errno::EBADF, bug10153) do + IO.pipe do |r, w| + assert_nothing_raised {IO.open(w.fileno) {}} + 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_exclusive_mode + make_tempfile do |t| + assert_raise(Errno::EEXIST){ open(t.path, 'wx'){} } + assert_raise(ArgumentError){ open(t.path, 'rx'){} } + assert_raise(ArgumentError){ open(t.path, 'ax'){} } + end + end + + def test_race_gets_and_close + opt = { signal: :ABRT, timeout: 200 } + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}", opt) + 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 + t.each do |th| + assert_same(th, th.join(2), bug13076) + end + 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 + skip "multiple threads already active" if Thread.list.size > 1 + res = {} + ObjectSpace.count_objects(res) # creates strings on first call + [ 'foo'.b, '*' * 24 ].each do |buf| + with_pipe do |r, w| + GC.disable + begin + before = ObjectSpace.count_objects(res)[:T_STRING] + n = w.write(buf) + s = w.syswrite(buf) + after = ObjectSpace.count_objects(res)[:T_STRING] + ensure + GC.enable + end + assert_equal before, after, + "no strings left over after write [ruby-core:78898] [Bug #13085]: #{ before } strings before write -> #{ after } strings after write" + assert_not_predicate buf, :frozen?, 'no inadvertent freeze' + assert_equal buf.bytesize, n, 'IO#write wrote expected size' + assert_equal s, n, 'IO#syswrite wrote expected size' + end + end + end + + 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_recycled_fd_close + dot = -'.' + IO.pipe do |sig_rd, sig_wr| + noex = Thread.new do # everything right and never see exceptions :) + until sig_rd.wait_readable(0) + IO.pipe do |r, w| + th = Thread.new { r.read(1) } + w.write(dot) + + assert_same th, th.join(15), '"good" reader timeout' + assert_equal(dot, th.value) + end + end + sig_rd.read(4) + end + 1000.times do |i| # stupid things and make exceptions: + IO.pipe do |r,w| + th = Thread.new do + begin + while r.gets + end + rescue IOError => e + e + end + end + Thread.pass until th.stop? + + r.close + assert_same th, th.join(30), '"bad" reader timeout' + assert_match(/stream closed/, th.value.message) + end + end + sig_wr.write 'done' + assert_same noex, noex.join(20), '"good" writer timeout' + assert_equal 'done', noex.value ,'r63216' + end + end + + def test_select_leak + # avoid malloc arena explosion from glibc and jemalloc: + env = { + 'MALLOC_ARENA_MAX' => '1', + 'MALLOC_ARENA_TEST' => '1', + 'MALLOC_CONF' => 'narenas:1', + } + assert_no_memory_leak([env], <<-"end;", <<-"end;", rss: true, timeout: 60) + r, w = IO.pipe + rset = [r] + wset = [w] + exc = StandardError.new(-"select used to leak on exception") + exc.set_backtrace([]) + Thread.new { IO.select(rset, wset, nil, 0) }.join + end; + th = Thread.new do + Thread.handle_interrupt(StandardError => :on_blocking) do + begin + IO.select(rset, wset) + rescue + retry + end while true + end + end + 50_000.times do + Thread.pass until th.stop? + th.raise(exc) + end + th.kill + th.join + end; + end + + def test_external_encoding_index + IO.pipe {|r, w| + assert_raise(TypeError) {Marshal.dump(r)} + assert_raise(TypeError) {Marshal.dump(w)} + } end end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 21b7941cbe..416a24ba6b 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -1,7 +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 = [ @@ -19,6 +21,35 @@ class TestIO_M17N < Test::Unit::TestCase } end + def pipe(*args, wp, rp) + re, we = nil, nil + r, w = IO.pipe(*args) + rt = Thread.new do + begin + rp.call(r) + rescue Exception + r.close + re = $! + end + end + wt = Thread.new do + begin + wp.call(w) + rescue Exception + w.close + we = $! + end + end + flunk("timeout") unless wt.join(10) && rt.join(10) + ensure + w.close unless !w || w.closed? + r.close unless !r || r.closed? + (wt.kill; wt.join) if wt + (rt.kill; rt.join) if rt + raise we if we + raise re if re + end + def with_pipe(*args) r, w = IO.pipe(*args) begin @@ -42,7 +73,7 @@ class TestIO_M17N < Test::Unit::TestCase #{encdump expected} expected but not equal to #{encdump actual}. EOT - assert_block(full_message) { expected == actual } + assert_equal(expected, actual, full_message) end def test_open_r @@ -75,6 +106,42 @@ EOT } end + def test_open_r_ascii8bit + with_tmpdir { + generate_file('tmp', "") + EnvUtil.with_default_external(Encoding::ASCII_8BIT) do + EnvUtil.with_default_internal(Encoding::UTF_8) do + open("tmp", "r") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + open("tmp", "r:ascii-8bit") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + open("tmp", "r:ascii-8bit:utf-16") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + end + EnvUtil.with_default_internal(nil) do + open("tmp", "r") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + open("tmp", "r:ascii-8bit") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + open("tmp", "r:ascii-8bit:utf-16") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + end + end + } + end + def test_open_r_enc_in_opt with_tmpdir { generate_file('tmp', "") @@ -85,7 +152,27 @@ EOT } end - def test_open_r_enc_in_opt2 + def test_open_r_encname_in_opt + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r", encoding: Encoding::EUC_JP) {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_r_ext_enc_in_opt + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r", external_encoding: Encoding::EUC_JP) {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_r_ext_encname_in_opt with_tmpdir { generate_file('tmp', "") open("tmp", "r", external_encoding: "euc-jp") {|f| @@ -98,6 +185,16 @@ EOT def test_open_r_enc_enc with_tmpdir { generate_file('tmp', "") + open("tmp", "r", external_encoding: Encoding::EUC_JP, internal_encoding: Encoding::UTF_8) {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_r_encname_encname + with_tmpdir { + generate_file('tmp', "") open("tmp", "r:euc-jp:utf-8") {|f| assert_equal(Encoding::EUC_JP, f.external_encoding) assert_equal(Encoding::UTF_8, f.internal_encoding) @@ -105,7 +202,7 @@ EOT } end - def test_open_r_enc_enc_in_opt + def test_open_r_encname_encname_in_opt with_tmpdir { generate_file('tmp', "") open("tmp", "r", encoding: "euc-jp:utf-8") {|f| @@ -115,7 +212,17 @@ EOT } end - def test_open_r_enc_enc_in_opt2 + def test_open_r_enc_enc_in_opt + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r", external_encoding: Encoding::EUC_JP, internal_encoding: Encoding::UTF_8) {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_r_externalencname_internalencname_in_opt with_tmpdir { generate_file('tmp', "") open("tmp", "r", external_encoding: "euc-jp", internal_encoding: "utf-8") {|f| @@ -206,13 +313,24 @@ 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") fd = IO.sysopen("tmp") f = IO.new(fd, "r:sjis") begin - assert_equal(Encoding::Shift_JIS, f.read.encoding) + assert_equal(Encoding::Windows_31J, f.read.encoding) ensure f.close end @@ -220,54 +338,68 @@ EOT end def test_s_pipe_invalid - with_pipe("utf-8", "euc-jp", :invalid=>:replace) {|r, w| - w << "\x80" - w.close - assert_equal("?", r.read) - } + pipe("utf-8", "euc-jp", { :invalid=>:replace }, + proc do |w| + w << "\x80" + w.close + end, + proc do |r| + assert_equal("?", r.read) + end) end def test_s_pipe_undef - with_pipe("utf-8:euc-jp", :undef=>:replace) {|r, w| - w << "\ufffd" - w.close - assert_equal("?", r.read) - } + pipe("utf-8:euc-jp", { :undef=>:replace }, + proc do |w| + w << "\ufffd" + w.close + end, + proc do |r| + assert_equal("?", r.read) + end) end def test_s_pipe_undef_replace_string - with_pipe("utf-8:euc-jp", :undef=>:replace, :replace=>"X") {|r, w| - w << "\ufffd" - w.close - assert_equal("X", r.read) - } + pipe("utf-8:euc-jp", { :undef=>:replace, :replace=>"X" }, + proc do |w| + w << "\ufffd" + w.close + end, + proc do |r| + assert_equal("X", r.read) + end) end def test_dup - with_pipe("utf-8:euc-jp") {|r, w| - w << "\u3042" - w.close - r2 = r.dup - begin - assert_equal("\xA4\xA2".force_encoding("euc-jp"), r2.read) - ensure - r2.close - end - - } + pipe("utf-8:euc-jp", + proc do |w| + w << "\u3042" + w.close + end, + proc do |r| + r2 = r.dup + begin + assert_equal("\xA4\xA2".force_encoding("euc-jp"), r2.read) + ensure + r2.close + end + end) end def test_dup_undef - with_pipe("utf-8:euc-jp", :undef=>:replace) {|r, w| - w << "\uFFFD" - w.close - r2 = r.dup - begin - assert_equal("?", r2.read) - ensure - r2.close - end - } + pipe("utf-8:euc-jp", { :undef=>:replace }, + proc do |w| + w << "\uFFFD" + w.close + end, + proc do |r| + r2 = r.dup + begin + assert_equal("?", r2.read) + ensure + r2.close + end + end) end def test_stdin @@ -331,47 +463,53 @@ EOT end def test_pipe_terminator_conversion - with_pipe("euc-jp:utf-8") {|r, w| - w.write "before \xa2\xa2 after" - rs = "\xA2\xA2".encode("utf-8", "euc-jp") - w.close - timeout(1) { - assert_equal("before \xa2\xa2".encode("utf-8", "euc-jp"), - r.gets(rs)) - } - } + rs = "\xA2\xA2".encode("utf-8", "euc-jp") + pipe("euc-jp:utf-8", + proc do |w| + w.write "before \xa2\xa2 after" + w.close + end, + proc do |r| + Timeout.timeout(1) { + assert_equal("before \xa2\xa2".encode("utf-8", "euc-jp"), + r.gets(rs)) + } + end) end def test_pipe_conversion - with_pipe("euc-jp:utf-8") {|r, w| - w.write "\xa1\xa1" - assert_equal("\xa1\xa1".encode("utf-8", "euc-jp"), r.getc) - } + pipe("euc-jp:utf-8", + proc do |w| + w.write "\xa1\xa1" + end, + proc do |r| + assert_equal("\xa1\xa1".encode("utf-8", "euc-jp"), r.getc) + end) end def test_pipe_convert_partial_read - with_pipe("euc-jp:utf-8") {|r, w| - begin - t = Thread.new { - w.write "\xa1" - sleep 0.1 - w.write "\xa1" - } - assert_equal("\xa1\xa1".encode("utf-8", "euc-jp"), r.getc) - ensure - t.join if t - end - } + pipe("euc-jp:utf-8", + proc do |w| + w.write "\xa1" + sleep 0.1 + w.write "\xa1" + end, + proc do |r| + assert_equal("\xa1\xa1".encode("utf-8", "euc-jp"), r.getc) + end) end def test_getc_invalid - with_pipe("euc-jp:utf-8") {|r, w| - w << "\xa1xyz" - w.close - err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } - assert_equal("\xA1".force_encoding("ascii-8bit"), err.error_bytes) - assert_equal("xyz", r.read(10)) - } + pipe("euc-jp:utf-8", + proc do |w| + w << "\xa1xyz" + w.close + end, + proc do |r| + err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } + assert_equal("\xA1".force_encoding("ascii-8bit"), err.error_bytes) + assert_equal("xyz", r.read(10)) + end) end def test_getc_stateful_conversion @@ -389,14 +527,13 @@ EOT with_tmpdir { src = "\u3042" generate_file('tmp', src) - defext = Encoding.default_external - Encoding.default_external = Encoding::UTF_8 - open("tmp", "rt") {|f| - s = f.getc - assert_equal(true, s.valid_encoding?) - assert_equal("\u3042", s) - } - Encoding.default_external = defext + EnvUtil.with_default_external(Encoding::UTF_8) do + open("tmp", "rt") {|f| + s = f.getc + assert_equal(true, s.valid_encoding?) + assert_equal("\u3042", s) + } + end } end @@ -404,17 +541,40 @@ EOT with_tmpdir { src = "\xE3\x81" generate_file('tmp', src) - defext = Encoding.default_external - Encoding.default_external = Encoding::UTF_8 - open("tmp", "rt") {|f| - s = f.getc - assert_equal(false, s.valid_encoding?) - assert_equal("\xE3".force_encoding("UTF-8"), s) - s = f.getc - assert_equal(false, s.valid_encoding?) - assert_equal("\x81".force_encoding("UTF-8"), s) + EnvUtil.with_default_external(Encoding::UTF_8) do + open("tmp", "rt") {|f| + s = f.getc + assert_equal(false, s.valid_encoding?) + assert_equal("\xE3".force_encoding("UTF-8"), s) + s = f.getc + assert_equal(false, s.valid_encoding?) + assert_equal("\x81".force_encoding("UTF-8"), s) + } + end + } + end + + def test_ungetc_int + with_tmpdir { + generate_file('tmp', "A") + s = open("tmp", "r:GB18030") {|f| + f.ungetc(0x8431A439) + f.read + } + assert_equal(Encoding::GB18030, s.encoding) + assert_str_equal(0x8431A439.chr("GB18030")+"A", s) + } + end + + def test_ungetc_str + with_tmpdir { + generate_file('tmp', "A") + s = open("tmp", "r:GB18030") {|f| + f.ungetc(0x8431A439.chr("GB18030")) + f.read } - Encoding.default_external = defext + assert_equal(Encoding::GB18030, s.encoding) + assert_str_equal(0x8431A439.chr("GB18030")+"A", s) } end @@ -573,185 +733,224 @@ EOT utf8 = "\u6666" eucjp = "\xb3\xa2".force_encoding("EUC-JP") - with_pipe {|r,w| - assert_equal(Encoding.default_external, r.external_encoding) - assert_equal(nil, r.internal_encoding) + pipe(proc do |w| w << utf8 w.close + end, proc do |r| + assert_equal(Encoding.default_external, r.external_encoding) + assert_equal(nil, r.internal_encoding) s = r.read assert_equal(Encoding.default_external, s.encoding) assert_str_equal(utf8.dup.force_encoding(Encoding.default_external), s) - } - - with_pipe("EUC-JP") {|r,w| - assert_equal(Encoding::EUC_JP, r.external_encoding) - assert_equal(nil, r.internal_encoding) - w << eucjp - w.close - assert_equal(eucjp, r.read) - } - - with_pipe("UTF-8") {|r,w| - w << "a" * 1023 + "\u3042" + "a" * 1022 - w.close - assert_equal(true, r.read.valid_encoding?) - } - - with_pipe("UTF-8:EUC-JP") {|r,w| - assert_equal(Encoding::UTF_8, r.external_encoding) - assert_equal(Encoding::EUC_JP, r.internal_encoding) - w << utf8 - w.close - assert_equal(eucjp, r.read) - } - - e = assert_raise(ArgumentError) {with_pipe("UTF-8", "UTF-8".encode("UTF-32BE")) {}} - assert_match(/invalid name encoding/, e.message) - e = assert_raise(ArgumentError) {with_pipe("UTF-8".encode("UTF-32BE")) {}} - assert_match(/invalid name encoding/, e.message) + end) + + pipe("EUC-JP", + proc do |w| + w << eucjp + w.close + end, + proc do |r| + assert_equal(Encoding::EUC_JP, r.external_encoding) + assert_equal(nil, r.internal_encoding) + assert_equal(eucjp, r.read) + end) + + pipe("UTF-8", + proc do |w| + w << "a" * 1023 + "\u3042" + "a" * 1022 + w.close + end, + proc do |r| + assert_equal(true, r.read.valid_encoding?) + end) + + pipe("UTF-8:EUC-JP", + proc do |w| + w << utf8 + w.close + end, + proc do |r| + assert_equal(Encoding::UTF_8, r.external_encoding) + assert_equal(Encoding::EUC_JP, r.internal_encoding) + assert_equal(eucjp, r.read) + end) + + assert_raise_with_message(ArgumentError, /invalid name encoding/) do + with_pipe("UTF-8", "UTF-8".encode("UTF-32BE")) {} + end + assert_raise_with_message(ArgumentError, /invalid name encoding/) do + with_pipe("UTF-8".encode("UTF-32BE")) {} + end ENCS.each {|enc| - with_pipe(enc) {|r, w| - w << "\xc2\xa1" - w.close - s = r.getc - assert_equal(enc, s.encoding) - } + pipe(enc, + proc do |w| + w << "\xc2\xa1" + w.close + end, + proc do |r| + s = r.getc + assert_equal(enc, s.encoding) + end) } ENCS.each {|enc| next if enc == Encoding::ASCII_8BIT next if enc == Encoding::UTF_8 - with_pipe("#{enc}:UTF-8") {|r, w| - w << "\xc2\xa1" - w.close - s = r.read - assert_equal(Encoding::UTF_8, s.encoding) - assert_equal(s.encode("UTF-8"), s) - } + pipe("#{enc}:UTF-8", + proc do |w| + w << "\xc2\xa1" + w.close + end, + proc do |r| + s = r.read + assert_equal(Encoding::UTF_8, s.encoding) + assert_equal(s.encode("UTF-8"), s) + end) } end def test_marshal - with_pipe("EUC-JP") {|r, w| - data = 56225 - Marshal.dump(data, w) - w.close - result = nil - assert_nothing_raised("[ruby-dev:33264]") { result = Marshal.load(r) } - assert_equal(data, result) - } + data = 56225 + pipe("EUC-JP", + proc do |w| + Marshal.dump(data, w) + w.close + end, + proc do |r| + result = nil + assert_nothing_raised("[ruby-dev:33264]") { result = Marshal.load(r) } + assert_equal(data, result) + end) end def test_gets_nil - with_pipe("UTF-8:EUC-JP") {|r, w| - w << "\u{3042}" - w.close - result = r.gets(nil) - assert_equal("\u{3042}".encode("euc-jp"), result) - } + pipe("UTF-8:EUC-JP", + proc do |w| + w << "\u{3042}" + w.close + end, + proc do |r| + result = r.gets(nil) + assert_equal("\u{3042}".encode("euc-jp"), result) + end) end def test_gets_limit - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.gets(1)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.gets(2)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4".force_encoding("euc-jp"), r.gets(3)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4".force_encoding("euc-jp"), r.gets(4)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6".force_encoding("euc-jp"), r.gets(5)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6".force_encoding("euc-jp"), r.gets(6)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(7)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(8)) - } - with_pipe("euc-jp") {|r, w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close - assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(9)) - } + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.gets(1)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.gets(2)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4".force_encoding("euc-jp"), r.gets(3)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4".force_encoding("euc-jp"), r.gets(4)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6".force_encoding("euc-jp"), r.gets(5)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6".force_encoding("euc-jp"), r.gets(6)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(7)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(8)) }) + pipe("euc-jp", + proc {|w| w << "\xa4\xa2\xa4\xa4\xa4\xa6\n\xa4\xa8\xa4\xaa"; w.close }, + proc {|r| assert_equal("\xa4\xa2\xa4\xa4\xa4\xa6\n".force_encoding("euc-jp"), r.gets(9)) }) end def test_gets_invalid - with_pipe("utf-8:euc-jp") {|r, w| - before = "\u{3042}\u{3044}" - invalid = "\x80".force_encoding("utf-8") - after = "\u{3046}\u{3048}" - w << before + invalid + after - w.close - err = assert_raise(Encoding::InvalidByteSequenceError) { r.gets } - assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) - assert_equal(after.encode("euc-jp"), r.gets) - } + before = "\u{3042}\u{3044}" + invalid = "\x80".force_encoding("utf-8") + after = "\u{3046}\u{3048}" + pipe("utf-8:euc-jp", + proc do |w| + w << before + invalid + after + w.close + end, + proc do |r| + err = assert_raise(Encoding::InvalidByteSequenceError) { r.gets } + assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) + assert_equal(after.encode("euc-jp"), r.gets) + end) end def test_getc_invalid2 - with_pipe("utf-8:euc-jp") {|r, w| - before1 = "\u{3042}" - before2 = "\u{3044}" - invalid = "\x80".force_encoding("utf-8") - after1 = "\u{3046}" - after2 = "\u{3048}" - w << before1 + before2 + invalid + after1 + after2 - w.close - assert_equal(before1.encode("euc-jp"), r.getc) - assert_equal(before2.encode("euc-jp"), r.getc) - err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } - assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) - assert_equal(after1.encode("euc-jp"), r.getc) - assert_equal(after2.encode("euc-jp"), r.getc) - } + before1 = "\u{3042}" + before2 = "\u{3044}" + invalid = "\x80".force_encoding("utf-8") + after1 = "\u{3046}" + after2 = "\u{3048}" + pipe("utf-8:euc-jp", + proc do |w| + w << before1 + before2 + invalid + after1 + after2 + w.close + end, + proc do |r| + assert_equal(before1.encode("euc-jp"), r.getc) + assert_equal(before2.encode("euc-jp"), r.getc) + err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } + assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) + assert_equal(after1.encode("euc-jp"), r.getc) + assert_equal(after2.encode("euc-jp"), r.getc) + end) end def test_getc_invalid3 - with_pipe("utf-16le:euc-jp", binmode: true) {|r, w| - before1 = "\x42\x30".force_encoding("utf-16le") - before2 = "\x44\x30".force_encoding("utf-16le") - invalid = "\x00\xd8".force_encoding("utf-16le") - after1 = "\x46\x30".force_encoding("utf-16le") - after2 = "\x48\x30".force_encoding("utf-16le") - w << before1 + before2 + invalid + after1 + after2 - w.close - assert_equal(before1.encode("euc-jp"), r.getc) - assert_equal(before2.encode("euc-jp"), r.getc) - err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } - assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) - assert_equal(after1.encode("euc-jp"), r.getc) - assert_equal(after2.encode("euc-jp"), r.getc) - } + before1 = "\x42\x30".force_encoding("utf-16le") + before2 = "\x44\x30".force_encoding("utf-16le") + invalid = "\x00\xd8".force_encoding("utf-16le") + after1 = "\x46\x30".force_encoding("utf-16le") + after2 = "\x48\x30".force_encoding("utf-16le") + pipe("utf-16le:euc-jp", { :binmode => true }, + proc do |w| + w << before1 + before2 + invalid + after1 + after2 + w.close + end, + proc do |r| + assert_equal(before1.encode("euc-jp"), r.getc) + assert_equal(before2.encode("euc-jp"), r.getc) + err = assert_raise(Encoding::InvalidByteSequenceError) { r.getc } + assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) + assert_equal(after1.encode("euc-jp"), r.getc) + assert_equal(after2.encode("euc-jp"), r.getc) + end) end def test_read_all - with_pipe("utf-8:euc-jp") {|r, w| - str = "\u3042\u3044" - w << str - w.close - assert_equal(str.encode("euc-jp"), r.read) - } + str = "\u3042\u3044" + pipe("utf-8:euc-jp", + proc do |w| + w << str + w.close + end, + proc do |r| + assert_equal(str.encode("euc-jp"), r.read) + end) end def test_read_all_invalid - with_pipe("utf-8:euc-jp") {|r, w| - before = "\u{3042}\u{3044}" - invalid = "\x80".force_encoding("utf-8") - after = "\u{3046}\u{3048}" - w << before + invalid + after - w.close - err = assert_raise(Encoding::InvalidByteSequenceError) { r.read } - assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) - assert_equal(after.encode("euc-jp"), r.read) - } + before = "\u{3042}\u{3044}" + invalid = "\x80".force_encoding("utf-8") + after = "\u{3046}\u{3048}" + pipe("utf-8:euc-jp", + proc do |w| + w << before + invalid + after + w.close + end, + proc do |r| + err = assert_raise(Encoding::InvalidByteSequenceError) { r.read } + assert_equal(invalid.force_encoding("ascii-8bit"), err.error_bytes) + assert_equal(after.encode("euc-jp"), r.read) + end) end def test_file_foreach @@ -764,84 +963,131 @@ EOT end def test_set_encoding - with_pipe("utf-8:euc-jp") {|r, w| - s = "\u3042".force_encoding("ascii-8bit") - s << "\x82\xa0".force_encoding("ascii-8bit") - w << s - w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - r.set_encoding("shift_jis:euc-jp") - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - } + pipe("utf-8:euc-jp", + proc do |w| + s = "\u3042".force_encoding("ascii-8bit") + s << "\x82\xa0".force_encoding("ascii-8bit") + w << s + w.close + end, + proc do |r| + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + r.set_encoding("shift_jis:euc-jp") + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + end) end def test_set_encoding2 - with_pipe("utf-8:euc-jp") {|r, w| - s = "\u3042".force_encoding("ascii-8bit") - s << "\x82\xa0".force_encoding("ascii-8bit") - w << s - w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - r.set_encoding("shift_jis", "euc-jp") - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - } + pipe("utf-8:euc-jp", + proc do |w| + s = "\u3042".force_encoding("ascii-8bit") + s << "\x82\xa0".force_encoding("ascii-8bit") + w << s + w.close + end, + proc do |r| + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + r.set_encoding("shift_jis", "euc-jp") + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + end) end def test_set_encoding_nil - with_pipe("utf-8:euc-jp") {|r, w| - s = "\u3042".force_encoding("ascii-8bit") - s << "\x82\xa0".force_encoding("ascii-8bit") - w << s - w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - r.set_encoding(nil) - assert_equal("\x82\xa0".force_encoding(Encoding.default_external), r.read) - } + pipe("utf-8:euc-jp", + proc do |w| + s = "\u3042".force_encoding("ascii-8bit") + s << "\x82\xa0".force_encoding("ascii-8bit") + w << s + w.close + end, + proc do |r| + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + r.set_encoding(nil) + assert_equal("\x82\xa0".force_encoding(Encoding.default_external), r.read) + end) end def test_set_encoding_enc - with_pipe("utf-8:euc-jp") {|r, w| - s = "\u3042".force_encoding("ascii-8bit") - s << "\x82\xa0".force_encoding("ascii-8bit") - w << s - w.close - assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) - r.set_encoding(Encoding::Shift_JIS) - assert_equal("\x82\xa0".force_encoding(Encoding::Shift_JIS), r.getc) - } + pipe("utf-8:euc-jp", + proc do |w| + s = "\u3042".force_encoding("ascii-8bit") + s << "\x82\xa0".force_encoding("ascii-8bit") + w << s + w.close + end, + proc do |r| + assert_equal("\xa4\xa2".force_encoding("euc-jp"), r.getc) + r.set_encoding(Encoding::Shift_JIS) + assert_equal("\x82\xa0".force_encoding(Encoding::Shift_JIS), r.getc) + end) end def test_set_encoding_invalid - with_pipe {|r, w| - w << "\x80" - w.close - r.set_encoding("utf-8:euc-jp", :invalid=>:replace) - assert_equal("?", r.read) - } + pipe(proc do |w| + w << "\x80" + w.close + end, + proc do |r| + r.set_encoding("utf-8:euc-jp", :invalid=>:replace) + assert_equal("?", r.read) + end) + end + + def test_set_encoding_identical + #bug5568 = '[ruby-core:40727]' + bug6324 = '[ruby-core:44455]' + open(__FILE__, "r") do |f| + assert_warning('', bug6324) { + f.set_encoding("eucjp:euc-jp") + } + assert_warning('', bug6324) { + f.set_encoding("eucjp", "euc-jp") + } + assert_warning('', bug6324) { + f.set_encoding(Encoding::EUC_JP, "euc-jp") + } + assert_warning('', bug6324) { + f.set_encoding("eucjp", Encoding::EUC_JP) + } + assert_warning('', bug6324) { + f.set_encoding(Encoding::EUC_JP, Encoding::EUC_JP) + } + nonstr = Object.new + def nonstr.to_str; "eucjp"; end + assert_warning('', bug6324) { + f.set_encoding(nonstr, nonstr) + } + end end def test_set_encoding_undef - with_pipe {|r, w| - w << "\ufffd" - w.close - r.set_encoding("utf-8", "euc-jp", :undef=>:replace) - assert_equal("?", r.read) - } + pipe(proc do |w| + w << "\ufffd" + w.close + end, + proc do |r| + r.set_encoding("utf-8", "euc-jp", :undef=>:replace) + assert_equal("?", r.read) + end) end def test_set_encoding_undef_replace - with_pipe {|r, w| - w << "\ufffd" - w.close - r.set_encoding("utf-8", "euc-jp", :undef=>:replace, :replace=>"ZZZ") - assert_equal("ZZZ", r.read) - } - with_pipe {|r, w| - w << "\ufffd" - w.close - r.set_encoding("utf-8:euc-jp", :undef=>:replace, :replace=>"ZZZ") - assert_equal("ZZZ", r.read) - } + pipe(proc do |w| + w << "\ufffd" + w.close + end, + proc do |r| + r.set_encoding("utf-8", "euc-jp", :undef=>:replace, :replace=>"ZZZ") + assert_equal("ZZZ", r.read) + end) + pipe(proc do |w| + w << "\ufffd" + w.close + end, + proc do |r| + r.set_encoding("utf-8:euc-jp", :undef=>:replace, :replace=>"ZZZ") + assert_equal("ZZZ", r.read) + end) end def test_set_encoding_binmode @@ -872,63 +1118,113 @@ EOT f.set_encoding("iso-2022-jp") } } + assert_nothing_raised { + open(__FILE__, "r", binmode: true) {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + f.set_encoding("iso-2022-jp") + } + } + assert_raise(ArgumentError) { + open(__FILE__, "rb", binmode: true) {|f| + f.set_encoding("iso-2022-jp") + } + } + assert_raise(ArgumentError) { + open(__FILE__, "rb", binmode: false) {|f| + f.set_encoding("iso-2022-jp") + } + } end - def test_write_conversion_fixenc - with_pipe {|r, w| - w.set_encoding("iso-2022-jp:utf-8") - t = Thread.new { r.read.force_encoding("ascii-8bit") } - w << "\u3042" - w << "\u3044" - w.close - assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), t.value) + def test_set_encoding_unsupported + bug5567 = '[ruby-core:40726]' + IO.pipe do |r, w| + assert_nothing_raised(bug5567) do + assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx")} + assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")} + assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")} + end + end + end + + def test_textmode_twice + assert_raise(ArgumentError) { + open(__FILE__, "rt", textmode: true) {|f| + f.set_encoding("iso-2022-jp") + } + } + assert_raise(ArgumentError) { + open(__FILE__, "rt", textmode: false) {|f| + f.set_encoding("iso-2022-jp") + } } end + def test_write_conversion_fixenc + pipe(proc do |w| + w.set_encoding("iso-2022-jp:utf-8") + w << "\u3042" + w << "\u3044" + w.close + end, + proc do |r| + assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), + r.read.force_encoding("ascii-8bit")) + end) + end + def test_write_conversion_anyenc_stateful - with_pipe {|r, w| - w.set_encoding("iso-2022-jp") - t = Thread.new { r.read.force_encoding("ascii-8bit") } - w << "\u3042" - w << "\x82\xa2".force_encoding("sjis") - w.close - assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), t.value) - } + pipe(proc do |w| + w.set_encoding("iso-2022-jp") + w << "\u3042" + w << "\x82\xa2".force_encoding("sjis") + w.close + end, + proc do |r| + assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), + r.read.force_encoding("ascii-8bit")) + end) end def test_write_conversion_anyenc_stateless - with_pipe {|r, w| - w.set_encoding("euc-jp") - t = Thread.new { r.read.force_encoding("ascii-8bit") } - w << "\u3042" - w << "\x82\xa2".force_encoding("sjis") - w.close - assert_equal("\xa4\xa2\xa4\xa4".force_encoding("ascii-8bit"), t.value) - } + pipe(proc do |w| + w.set_encoding("euc-jp") + w << "\u3042" + w << "\x82\xa2".force_encoding("sjis") + w.close + end, + proc do |r| + assert_equal("\xa4\xa2\xa4\xa4".force_encoding("ascii-8bit"), + r.read.force_encoding("ascii-8bit")) + end) end def test_write_conversion_anyenc_stateful_nosync - with_pipe {|r, w| - w.sync = false - w.set_encoding("iso-2022-jp") - t = Thread.new { r.read.force_encoding("ascii-8bit") } - w << "\u3042" - w << "\x82\xa2".force_encoding("sjis") - w.close - assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), t.value) - } + pipe(proc do |w| + w.sync = false + w.set_encoding("iso-2022-jp") + w << "\u3042" + w << "\x82\xa2".force_encoding("sjis") + w.close + end, + proc do |r| + assert_equal("\e$B$\"$$\e(B".force_encoding("ascii-8bit"), + r.read.force_encoding("ascii-8bit")) + end) end def test_read_stateful - with_pipe("euc-jp:iso-2022-jp") {|r, w| - w << "\xA4\xA2" - w.close - assert_equal("\e$B$\"\e(B".force_encoding("iso-2022-jp"), r.read) - } + pipe("euc-jp:iso-2022-jp", + proc do |w| + w << "\xA4\xA2" + w.close + end, + proc do |r| + assert_equal("\e$B$\"\e(B".force_encoding("iso-2022-jp"), r.read) + end) 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) @@ -944,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| @@ -1026,6 +1322,16 @@ EOT } end + def test_open_pipe_r_enc2 + open("|#{EnvUtil.rubybin} -e 'putc \"\\u3042\"'", "r:UTF-8") {|f| + assert_equal(Encoding::UTF_8, f.external_encoding) + assert_equal(nil, f.internal_encoding) + s = f.read + assert_equal(Encoding::UTF_8, s.encoding) + assert_equal("\u3042", s) + } + end + def test_s_foreach_enc with_tmpdir { generate_file("t", "\xff") @@ -1147,7 +1453,12 @@ EOT end def test_both_textmode_binmode - assert_raise(ArgumentError) { open("not-exist", "r", :textmode=>true, :binmode=>true) } + bug5918 = '[ruby-core:42199]' + assert_raise(ArgumentError, bug5918) { open("not-exist", "r", :textmode=>true, :binmode=>true) } + assert_raise(ArgumentError, bug5918) { open("not-exist", "rt", :binmode=>true) } + assert_raise(ArgumentError, bug5918) { open("not-exist", "rt", :binmode=>false) } + assert_raise(ArgumentError, bug5918) { open("not-exist", "rb", :textmode=>true) } + assert_raise(ArgumentError, bug5918) { open("not-exist", "rb", :textmode=>false) } end def test_textmode_decode_universal_newline_read @@ -1158,6 +1469,9 @@ EOT open("t.crlf", "rt:euc-jp:utf-8") {|f| assert_equal("a\nb\nc\n", f.read) } open("t.crlf", "rt") {|f| assert_equal("a\nb\nc\n", f.read) } open("t.crlf", "r", :textmode=>true) {|f| assert_equal("a\nb\nc\n", f.read) } + open("t.crlf", "r", textmode: true, universal_newline: false) {|f| + assert_equal("a\r\nb\r\nc\r\n", f.read) + } generate_file("t.cr", "a\rb\rc\r") assert_equal("a\nb\nc\n", File.read("t.cr", mode:"rt:euc-jp:utf-8")) @@ -1294,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") @@ -1398,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 @@ -1414,7 +1765,7 @@ EOT u16 = "\x85\x35\0\r\x00\xa2\0\r\0\n\0\n".force_encoding("utf-16be") i = "\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r\n\n".force_encoding("iso-2022-jp") n = system_newline - un = n.encode("utf-16be").force_encoding("ascii-8bit") + n.encode("utf-16be").force_encoding("ascii-8bit") assert_write("a\rb\r#{n}c#{n}", "wt", a) assert_write("\xc2\xa2", "wt", e) @@ -1579,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 @@ -1731,19 +2082,105 @@ EOT def test_strip_bom with_tmpdir { - text = "\uFEFFa" + 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) + assert_equal(stripped, result) end bug3407 = '[ruby-core:30641]' - result = File.read('UTF-8-bom.txt', encoding: 'BOM|UTF-8') - assert_equal("a", result.force_encoding("ascii-8bit"), bug3407) + path = 'UTF-8-bom.txt' + result = File.read(path, encoding: 'BOM|UTF-8') + assert_equal(stripped.b, result.force_encoding("ascii-8bit"), bug3407) + + bug8323 = '[ruby-core:54563] [Bug #8323]' + 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) + assert_equal(expected, result, bug8323) + result = File.read(path, encoding: 'BOM|UTF-8:UTF-8') + assert_not_predicate(result, :valid_encoding?, bug8323) + assert_equal(expected, result, bug8323) + + path = 'ascii.txt' + generate_file(path, stripped) + result = File.read(path, encoding: 'BOM|UTF-8') + assert_equal(stripped, result, bug8323) + result = File.read(path, encoding: 'BOM|UTF-8:UTF-8') + assert_equal(stripped, result, bug8323) + } + end + + def test_bom_too_long_utfname + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_warn(/Unsupported encoding/) { + open(IO::NULL, "r:bom|utf-" + "x" * 10000) {} + } + end; + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_warn(/Unsupported encoding/) { + open(IO::NULL, encoding: "bom|utf-" + "x" * 10000) {} + } + 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 @@ -1774,64 +2211,496 @@ EOT open("ff", "w") {|f| } open("ff", "rt") {|f| f.ungetc "a" - assert(!f.eof?, "[ruby-dev:40506] (3)") + assert_not_predicate(f, :eof?, "[ruby-dev:40506] (3)") } } end def test_cbuf_select - with_pipe("US-ASCII:UTF-8", :universal_newline => true) do |r, w| - w << "\r\n" - r.ungetc(r.getc) - assert_equal([[r],[],[]], IO.select([r], nil, nil, 1)) - end + pipe("US-ASCII:UTF-8", { :universal_newline => true }, + proc do |w| + w << "\r\n" + end, + proc do |r| + r.ungetc(r.getc) + assert_equal([[r],[],[]], IO.select([r], nil, nil, 1)) + end) end def test_textmode_paragraphmode - with_pipe("US-ASCII:UTF-8", :universal_newline => true) do |r, w| - w << "a\n\n\nc".gsub(/\n/, "\r\n") - w.close - assert_equal("a\n\n", r.gets("")) - assert_equal("c", r.gets(""), "[ruby-core:23723] (18)") - end + pipe("US-ASCII:UTF-8", { :universal_newline => true }, + proc do |w| + w << "a\n\n\nc".gsub(/\n/, "\r\n") + w.close + end, + proc do |r| + assert_equal("a\n\n", r.gets("")) + assert_equal("c", r.gets(""), "[ruby-core:23723] (18)") + end) end def test_textmode_paragraph_binaryread - with_pipe("US-ASCII:UTF-8", :universal_newline => true) do |r, w| - w << "a\n\n\ncdefgh".gsub(/\n/, "\r\n") - w.close - assert_equal("a\n\n", r.gets("")) - assert_equal("c", r.getc) - assert_equal("defgh", r.readpartial(10)) - end + pipe("US-ASCII:UTF-8", { :universal_newline => true }, + proc do |w| + w << "a\n\n\ncdefgh".gsub(/\n/, "\r\n") + w.close + end, + proc do |r| + assert_equal("a\n\n", r.gets("")) + assert_equal("c", r.getc) + assert_equal("defgh", r.readpartial(10)) + end) end 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) - 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]) - 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) - 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]) + 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 + bug = '[ruby-dev:42212]' + pipe(Encoding::ASCII_8BIT, + proc do |w| + w.binmode + w.puts(0x010a.chr(Encoding::UTF_32BE)) + w.puts(0x010a.chr(Encoding::UTF_16BE)) + w.puts(0x0a01.chr(Encoding::UTF_32LE)) + w.puts(0x0a01.chr(Encoding::UTF_16LE)) + w.close + end, + proc do |r| + r.binmode + assert_equal("\x00\x00\x01\x0a\n", r.read(5), bug) + assert_equal("\x01\x0a\n", r.read(3), 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 + end) + end + + def test_getc_ascii_only + bug4557 = '[ruby-core:35630]' + c = with_tmpdir { + open("a", "wb") {|f| f.puts "a"} + open("a", "rt") {|f| f.getc} + } + assert_predicate(c, :ascii_only?, bug4557) + end + + def test_getc_conversion + bug8516 = '[ruby-core:55444] [Bug #8516]' + c = with_tmpdir { + open("a", "wb") {|f| f.putc "\xe1"} + open("a", "r:iso-8859-1:utf-8") {|f| f.getc} + } + assert_not_predicate(c, :ascii_only?, bug8516) + assert_equal(1, c.size, bug8516) + end + + def test_default_mode_on_dosish + with_tmpdir { + open("a", "w") {|f| f.write "\n"} + assert_equal("\r\n", IO.binread("a")) + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_default_mode_on_unix + with_tmpdir { + open("a", "w") {|f| f.write "\n"} + assert_equal("\n", IO.binread("a")) + } + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_text_mode + with_tmpdir { + open("a", "wb") {|f| f.write "\r\n"} + assert_equal("\n", open("a", "rt"){|f| f.read}) + } + end + + def test_binary_mode + with_tmpdir { + open("a", "wb") {|f| f.write "\r\n"} + assert_equal("\r\n", open("a", "rb"){|f| f.read}) + } + end + + def test_default_stdout_stderr_mode + with_pipe do |in_r, in_w| + with_pipe do |out_r, out_w| + pid = Process.spawn({}, EnvUtil.rubybin, in: in_r, out: out_w, err: out_w) + in_r.close + out_w.close + in_w.write <<-EOS + STDOUT.puts "abc" + STDOUT.flush + STDERR.puts "def" + STDERR.flush + EOS + in_w.close + Process.wait pid + assert_equal "abc\r\ndef\r\n", out_r.binmode.read + out_r.close + end + end + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_cr_decorator_on_stdout + with_pipe do |in_r, in_w| + with_pipe do |out_r, out_w| + pid = Process.spawn({}, EnvUtil.rubybin, in: in_r, out: out_w) + in_r.close + out_w.close + in_w.write <<-EOS + STDOUT.set_encoding('locale', nil, newline: :cr) + STDOUT.puts "abc" + STDOUT.flush + EOS + in_w.close + Process.wait pid + assert_equal "abc\r", out_r.binmode.read + out_r.close + end end end -end + def test_lf_decorator_on_stdout + with_pipe do |in_r, in_w| + with_pipe do |out_r, out_w| + pid = Process.spawn({}, EnvUtil.rubybin, in: in_r, out: out_w) + in_r.close + out_w.close + in_w.write <<-EOS + STDOUT.set_encoding('locale', nil, newline: :lf) + STDOUT.puts "abc" + STDOUT.flush + EOS + in_w.close + Process.wait pid + assert_equal "abc\n", out_r.binmode.read + out_r.close + end + end + end + + def test_crlf_decorator_on_stdout + with_pipe do |in_r, in_w| + with_pipe do |out_r, out_w| + pid = Process.spawn({}, EnvUtil.rubybin, in: in_r, out: out_w) + in_r.close + out_w.close + in_w.write <<-EOS + STDOUT.set_encoding('locale', nil, newline: :crlf) + STDOUT.puts "abc" + STDOUT.flush + EOS + in_w.close + Process.wait pid + assert_equal "abc\r\n", out_r.binmode.read + out_r.close + end + end + end + + def test_binmode_with_pipe + with_pipe do |r, w| + src = "a\r\nb\r\nc\r\n" + w.binmode.write src + w.close + + assert_equal("a", r.getc) + assert_equal("\n", r.getc) + r.binmode + assert_equal("b", r.getc) + assert_equal("\r", r.getc) + assert_equal("\n", r.getc) + assert_equal("c", r.getc) + assert_equal("\r", r.getc) + assert_equal("\n", r.getc) + assert_equal(nil, r.getc) + r.close + end + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_stdin_binmode + with_pipe do |in_r, in_w| + with_pipe do |out_r, out_w| + pid = Process.spawn({}, EnvUtil.rubybin, '-e', <<-'End', in: in_r, out: out_w) + STDOUT.binmode + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDIN.binmode + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + STDOUT.write STDIN.getc + End + in_r.close + out_w.close + src = "a\r\nb\r\nc\r\n" + in_w.binmode.write src + in_w.close + Process.wait pid + assert_equal "a\nb\r\nc\r\n", out_r.binmode.read + out_r.close + end + end + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_length + with_tmpdir { + str = "a\nb" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal(str, f.read(3)) + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_length_binmode + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + # read with length should be binary mode + assert_equal("a\r\n", f.read(3)) # binary + assert_equal("b\nc\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_gets_and_read_with_binmode + with_tmpdir { + str = "a\r\nb\r\nc\r\n\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("a\n", f.gets) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\r\n", f.read(3)) # binary + assert_equal("\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_getc_and_read_with_binmode + with_tmpdir { + str = "a\r\nb\r\nc\n\n\r\n\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\n\n\n\n", f.read) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_binmode_and_gets + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + open("tmp", "wb") { |f| f.write str } + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c\n", f.gets) # text + assert_equal("\n", f.gets) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_with_binmode_and_getc + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n" + open("tmp", "wb") { |f| f.write str } + open("tmp", "r") do |f| + assert_equal("a", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("b\r\n", f.read(3)) # binary + assert_equal("c", f.getc) # text + assert_equal("\n", f.getc) # text + assert_equal("\n", f.getc) # text + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_write_with_binmode + with_tmpdir { + str = "a\r\n" + generate_file("tmp", str) + open("tmp", "r+") do |f| + assert_equal("a\r\n", f.read(3)) # binary + f.write("b\n\n"); # text + f.rewind + assert_equal("a\nb\n\n", f.read) # text + f.rewind + assert_equal("a\r\nb\r\n\r\n", f.binmode.read) # binary + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_seek_with_setting_binmode + with_tmpdir { + str = "a\r\nb\r\nc\r\n\r\n\n\n\n\n\n\n\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("a\n", f.gets) # text + assert_equal("b\r\n", f.read(3)) # binary + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_error_nonascii + bug6071 = '[ruby-dev:45279]' + paths = ["\u{3042}".encode("sjis"), "\u{ff}".encode("iso-8859-1")] + encs = with_tmpdir { + paths.map {|path| + open(path) rescue $!.message.encoding + } + } + assert_equal(paths.map(&:encoding), encs, bug6071) + end + + def test_inspect_nonascii + bug6072 = '[ruby-dev:45280]' + paths = ["\u{3042}".encode("sjis"), "\u{ff}".encode("iso-8859-1")] + encs = with_tmpdir { + paths.map {|path| + open(path, "wb") {|f| f.inspect.encoding} + } + } + assert_equal(paths.map(&:encoding), encs, bug6072) + end + + def test_pos_dont_move_cursor_position + bug6179 = '[ruby-core:43497]' + with_tmpdir { + str = "line one\r\nline two\r\nline three\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + assert_equal("line one\n", f.readline) + assert_equal(10, f.pos, bug6179) + assert_equal("line two\n", f.readline, bug6179) + assert_equal(20, f.pos, bug6179) + assert_equal("line three\n", f.readline, bug6179) + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_pos_with_buffer_end_cr + bug6401 = '[ruby-core:44874]' + with_tmpdir { + # Read buffer size is 8191. This generates '\r' at 8191. + lines = ["X" * 8187, "X"] + generate_file("tmp", lines.join("\r\n") + "\r\n") + + open("tmp", "r") do |f| + lines.each do |line| + f.pos + assert_equal(line, f.readline.chomp, bug6401) + end + end + } + end if /mswin|mingw/ =~ RUBY_PLATFORM + + def test_read_crlf_and_eof + bug6271 = '[ruby-core:44189]' + with_tmpdir { + str = "a\r\nb\r\nc\r\n" + generate_file("tmp", str) + open("tmp", "r") do |f| + i = 0 + until f.eof? + assert_equal(str[i], f.read(1), bug6271) + i += 1 + end + assert_equal(str.size, i, bug6271) + end + } + 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 = [ + ["incomplete multibyte", "\u{1f376}".b[0,3], [], ["invalid byte sequence in UTF-8"]], + ["multibyte at boundary", "x"*8190+"\u{1f376}", ["1f376"], []], + ] + failure = [] + ["bin", "text"].product(tests) do |mode, (test, data, out, err)| + code = <<-"end;" + c = nil + begin + open(ARGV[0], "r#{mode[0]}:utf-8") do |f| + f.each_codepoint{|i| c = i} + end + rescue ArgumentError => e + STDERR.puts e.message + else + printf "%x", c + end + end; + Tempfile.create("codepoint") do |f| + args = ['-e', code, f.path] + f.print data + f.close + begin + assert_in_out_err(args, "", out, err, + "#{bug11444}: #{test} in #{mode} mode", + timeout: 10) + rescue Exception => e + failure << e + end + end + end + unless failure.empty? + flunk failure.join("\n---\n") + end + end +end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb new file mode 100644 index 0000000000..9de241c485 --- /dev/null +++ b/test/ruby/test_iseq.rb @@ -0,0 +1,539 @@ +require 'test/unit' +require 'tempfile' + +return + +class TestISeq < Test::Unit::TestCase + ISeq = RubyVM::InstructionSequence + + def test_no_linenum + bug5894 = '[ruby-dev:45130]' + 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 = 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 + src = <<-EOS + p __LINE__ # 1 + p __LINE__ # 2 + # 3 + p __LINE__ # 4 + EOS + assert_equal [1, 2, 4], lines(src) + + src = <<-EOS + # 1 + p __LINE__ # 2 + # 3 + p __LINE__ # 4 + # 5 + EOS + assert_equal [2, 4], lines(src) + + src = <<-EOS + 1 # should be optimized out + 2 # should be optimized out + p __LINE__ # 3 + p __LINE__ # 4 + 5 # should be optimized out + 6 # should be optimized out + p __LINE__ # 7 + 8 # should be optimized out + 9 + EOS + assert_equal [3, 4, 7, 9], lines(src) + end + + def test_unsupport_type + ary = compile("p").to_a + ary[9] = :foobar + assert_raise_with_message(TypeError, /:foobar/) {ISeq.load(ary)} + end if defined?(RubyVM::InstructionSequence.load) + + def test_loaded_cdhash_mark + iseq = compile(<<-'end;', __LINE__+1) + def bug(kw) + case kw + when "false" then false + when "true" then true + when "nil" then nil + else raise("unhandled argument: #{kw.inspect}") + end + end + end; + assert_separately([], <<-"end;") + iseq = #{iseq.to_a.inspect} + RubyVM::InstructionSequence.load(iseq).eval + assert_equal(false, bug("false")) + GC.start + assert_equal(false, bug("false")) + end; + end if defined?(RubyVM::InstructionSequence.load) + + def test_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) + asm = compile(src).disasm + assert_equal(src.encoding, asm.encoding) + assert_predicate(asm, :valid_encoding?) + + obj = Object.new + name = "\u{2603 26a1}" + obj.instance_eval("def #{name}; tap {}; end") + assert_include(RubyVM::InstructionSequence.of(obj.method(name)).disasm, name) + end + + LINE_BEFORE_METHOD = __LINE__ + def method_test_line_trace + + _a = 1 + + _b = 2 + + end + + def test_line_trace + iseq = compile \ + %q{ a = 1 + b = 2 + c = 3 + # d = 4 + e = 5 + # f = 6 + g = 7 + + } + assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all) + iseq.line_trace_specify(1, true) # line 2 + iseq.line_trace_specify(3, true) # line 5 + + result = [] + TracePoint.new(:specified_line){|tp| + result << tp.lineno + }.enable{ + iseq.eval + } + assert_equal([2, 5], result) + + iseq = ISeq.of(self.class.instance_method(:method_test_line_trace)) + assert_equal([LINE_BEFORE_METHOD + 3, LINE_BEFORE_METHOD + 5], iseq.line_trace_all) + end if false # TODO: now, it is only for C APIs. + + LINE_OF_HERE = __LINE__ + def test_location + iseq = ISeq.of(method(:test_location)) + + assert_equal(__FILE__, iseq.path) + assert_match(/#{__FILE__}/, iseq.absolute_path) + assert_equal("test_location", iseq.label) + assert_equal("test_location", iseq.base_label) + assert_equal(LINE_OF_HERE+1, iseq.first_lineno) + + line = __LINE__ + iseq = ISeq.of(Proc.new{}) + assert_equal(__FILE__, iseq.path) + assert_match(/#{__FILE__}/, iseq.absolute_path) + assert_equal("test_location", iseq.base_label) + assert_equal("block in test_location", iseq.label) + assert_equal(line+1, iseq.first_lineno) + end + + def test_label_fstring + c = Class.new{ def foobar() end } + + a, b = eval("# encoding: us-ascii\n'foobar'.freeze"), + ISeq.of(c.instance_method(:foobar)).label + 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) {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;"}", /unexpected 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 strip_lineno(source) + source.gsub(/^.*?: /, "") + end + + def sample_iseq + ISeq.compile(strip_lineno(<<-EOS)) + 1: class C + 2: def foo + 3: begin + 4: rescue + 5: p :rescue + 6: ensure + 7: p :ensure + 8: end + 9: end + 10: def bar + 11: 1.times{ + 12: 2.times{ + 13: } + 14: } + 15: end + 16: end + 17: class D < C + 18: end + EOS + end + + def test_each_child + iseq = sample_iseq + + collect_iseq = lambda{|iseq| + iseqs = [] + iseq.each_child{|child_iseq| + iseqs << collect_iseq.call(child_iseq) + } + ["#{iseq.label}@#{iseq.first_lineno}", *iseqs.sort_by{|k, *| k}] + } + + expected = ["<compiled>@1", + ["<class:C>@1", + ["bar@10", ["block in bar@11", + ["block (2 levels) in bar@12"]]], + ["foo@2", ["ensure in foo@2"], + ["rescue in foo@4"]]], + ["<class:D>@17"]] + + assert_equal expected, collect_iseq.call(iseq) + end + + def test_trace_points + collect_iseq = lambda{|iseq| + iseqs = [] + iseq.each_child{|child_iseq| + iseqs << collect_iseq.call(child_iseq) + } + [["#{iseq.label}@#{iseq.first_lineno}", iseq.trace_points], *iseqs.sort_by{|k, *| k}] + } + assert_equal [["<compiled>@1", [[1, :line], + [17, :line]]], + [["<class:C>@1", [[1, :class], + [2, :line], + [10, :line], + [16, :end]]], + [["bar@10", [[10, :call], + [11, :line], + [15, :return]]], + [["block in bar@11", [[11, :b_call], + [12, :line], + [14, :b_return]]], + [["block (2 levels) in bar@12", [[12, :b_call], + [13, :b_return]]]]]], + [["foo@2", [[2, :call], + [4, :line], + [7, :line], + [9, :return]]], + [["ensure in foo@2", [[7, :line]]]], + [["rescue in foo@4", [[5, :line]]]]]], + [["<class:D>@17", [[17, :class], + [18, :end]]]]], collect_iseq.call(sample_iseq) + end + + def test_empty_iseq_lineno + iseq = ISeq.compile(<<-EOS) + # 1 + # 2 + def foo # line 3 empty method + end # line 4 + 1.time do # line 5 empty block + end # line 6 + class C # line 7 empty class + end + EOS + + iseq.each_child{|ci| + ary = ci.to_a + type = ary[9] + name = ary[5] + line = ary[13].first + case 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 hexdump(bin) + bin.unpack1("H*").gsub(/.{1,32}/) {|s| + "#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n" + } + end + + def assert_iseq_to_binary(code, mesg = nil) + iseq = RubyVM::InstructionSequence.compile(code) + bin = assert_nothing_raised(mesg) do + iseq.to_binary + rescue RuntimeError => e + skip e.message if /compile with coverage/ =~ e.message + raise + end + 10.times do + bin2 = iseq.to_binary + assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)}) + end + iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) + a1 = iseq.to_a + a2 = iseq2.to_a + assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble}) + iseq2 + end + + def test_to_binary_with_objects + assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join) + assert_iseq_to_binary("@x ||= (1..2)") + end + + def test_to_binary_line_info + assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval + begin; + class P + def p; end + def q; end + E = "" + N = "#{E}" + attr_reader :i + end + end; + end + + def collect_from_binary_tracepoint_lines(tracepoint_type, filename) + iseq = RubyVM::InstructionSequence.compile(strip_lineno(<<-RUBY), filename) + class A + class B + 2.times { + def self.foo + a = 'good day' + raise + rescue + 'dear reader' + end + } + end + B.foo + end + RUBY + + iseq_bin = iseq.to_binary + lines = [] + TracePoint.new(tracepoint_type){|tp| + next unless tp.path == filename + lines << tp.lineno + }.enable{ + ISeq.load_from_binary(iseq_bin).eval + } + + lines + end + + def test_to_binary_line_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:line, filename) + + assert_equal [1, 2, 3, 4, 4, 12, 5, 6, 8], lines, '[Bug #14702]' + end + + def test_to_binary_class_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:class, filename) + + assert_equal [1, 2], lines, '[Bug #14702]' + end + + def test_to_binary_end_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:end, filename) + + assert_equal [11, 13], lines, '[Bug #14702]' + end + + def test_to_binary_return_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:return, filename) + + assert_equal [9], lines, '[Bug #14702]' + end + + def test_to_binary_b_call_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:b_call, filename) + + assert_equal [3, 3], lines, '[Bug #14702]' + end + + def test_to_binary_b_return_tracepoint + filename = "#{File.basename(__FILE__)}_#{__LINE__}" + lines = collect_from_binary_tracepoint_lines(:b_return, filename) + + assert_equal [10, 10], lines, '[Bug #14702]' + end + + def test_iseq_of + [proc{}, + method(:test_iseq_of), + RubyVM::InstructionSequence.compile("p 1", __FILE__)].each{|src| + iseq = RubyVM::InstructionSequence.of(src) + assert_equal __FILE__, iseq.path + } + end + + def test_iseq_of_twice_for_same_code + [proc{}, + method(:test_iseq_of_twice_for_same_code), + RubyVM::InstructionSequence.compile("p 1")].each{|src| + iseq1 = RubyVM::InstructionSequence.of(src) + iseq2 = RubyVM::InstructionSequence.of(src) + + # ISeq objects should be same for same src + assert_equal iseq1.object_id, iseq2.object_id + } + end +end diff --git a/test/ruby/test_iterator.rb b/test/ruby/test_iterator.rb index 362becc0e0..ebfb60354a 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 @@ -25,14 +26,14 @@ class TestIterator < Test::Unit::TestCase end def test_array - $x = [1, 2, 3, 4] - $y = [] + x = [1, 2, 3, 4] + y = [] # iterator over array - for i in $x - $y.push i + for i in x + y.push i end - assert_equal($x, $y) + assert_equal(x, y) end def tt @@ -73,42 +74,52 @@ class TestIterator < Test::Unit::TestCase def test_break done = true loop{ - break + break if true done = false # should not reach here } assert(done) done = false - $bad = false + bad = false loop { break if done done = true - next - $bad = true # should not reach here + next if true + bad = true # should not reach here } - assert(!$bad) + assert(!bad) done = false - $bad = false + bad = false loop { break if done done = true - redo - $bad = true # should not reach here + redo if true + bad = true # should not reach here } - assert(!$bad) + assert(!bad) - $x = [] + x = [] for i in 1 .. 7 - $x.push i + x.push i end - assert_equal(7, $x.size) - assert_equal([1, 2, 3, 4, 5, 6, 7], $x) + assert_equal(7, x.size) + assert_equal([1, 2, 3, 4, 5, 6, 7], x) + end + + def test_array_for_masgn + a = [Struct.new(:to_ary).new([1,2])] + x = [] + a.each {|i,j|x << [i,j]} + assert_equal([[1,2]], x) + x = [] + for i,j in a; x << [i,j]; end + assert_equal([[1,2]], x) end def test_append_method_to_built_in_class - $x = [[1,2],[3,4],[5,6]] - assert_equal($x.iter_test1{|x|x}, $x.iter_test2{|x|x}) + x = [[1,2],[3,4],[5,6]] + assert_equal(x.iter_test1{|e|e}, x.iter_test2{|e|e}) end class IterTest @@ -278,6 +289,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 +313,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_jit.rb b/test/ruby/test_jit.rb new file mode 100644 index 0000000000..fb63d4087c --- /dev/null +++ b/test/ruby/test_jit.rb @@ -0,0 +1,1047 @@ +# frozen_string_literal: true +require 'test/unit' +require 'tmpdir' +require_relative '../lib/jit_support' + +# Test for --jit option +class TestJIT < Test::Unit::TestCase + include JITSupport + + IGNORABLE_PATTERNS = [ + /\ASuccessful MJIT finish\n\z/, + ] + MAX_CACHE_PATTERNS = [ + /\AJIT compaction \([^)]+\): .+\n\z/, + /\ANo units can be unloaded -- .+\n\z/, + ] + + # trace_* insns are not compiled for now... + TEST_PENDING_INSNS = RubyVM::INSTRUCTION_NAMES.select { |n| n.start_with?('trace_') }.map(&:to_sym) + [ + # not supported yet + :getblockparamproxy, + :defineclass, + :opt_call_c_function, + + # joke + :bitblt, + :answer, + + # TODO: write tests for them + :reput, + :tracecoverage, + ] + + def self.untested_insns + @untested_insns ||= (RubyVM::INSTRUCTION_NAMES.map(&:to_sym) - TEST_PENDING_INSNS) + end + + def setup + unless JITSupport.supported? + skip 'JIT seems not supported on this platform' + end + + # ruby -w -Itest/lib test/ruby/test_jit.rb + if $VERBOSE && !defined?(@@at_exit_hooked) + at_exit do + unless TestJIT.untested_insns.empty? + warn "untested insns are found!: #{TestJIT.untested_insns.join(' ')}" + end + end + @@at_exit_hooked = true + end + end + + def test_compile_insn_nop + assert_compile_once('nil rescue true', result_inspect: 'nil', insns: %i[nop]) + end + + def test_compile_insn_local + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[setlocal_WC_0 getlocal_WC_0]) + begin; + foo = 1 + foo + end; + + insns = %i[setlocal getlocal setlocal_WC_0 getlocal_WC_0 setlocal_WC_1 getlocal_WC_1] + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", success_count: 3, stdout: '168', insns: insns) + begin; + def foo + a = 0 + [1, 2].each do |i| + a += i + [3, 4].each do |j| + a *= j + end + end + a + end + + print foo + end; + end + + def test_compile_insn_blockparam + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2, insns: %i[getblockparam setblockparam]) + begin; + def foo(&b) + a = b + b = 2 + a.call + 2 + end + + print foo { 1 } + end; + end + + def test_compile_insn_getblockparamproxy + skip "support this in mjit_compile" + end + + def test_compile_insn_getspecial + assert_compile_once('$1', result_inspect: 'nil', insns: %i[getspecial]) + end + + def test_compile_insn_setspecial + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[setspecial]) + begin; + true if nil.nil?..nil.nil? + end; + ensure + $VERBOSE = verbose_bak + end + + def test_compile_insn_instancevariable + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getinstancevariable setinstancevariable]) + begin; + @foo = 1 + @foo + end; + + # optimized getinstancevariable call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '33', success_count: 1, min_calls: 2) + begin; + class A + def initialize + @a = 1 + @b = 2 + end + + def three + @a + @b + end + end + + a = A.new + print(a.three) # set ic + print(a.three) # inlined ic + end; + end + + def test_compile_insn_classvariable + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 1, insns: %i[getclassvariable setclassvariable]) + begin; + class Foo + def self.foo + @@foo = 1 + @@foo + end + end + + print Foo.foo + end; + end + + def test_compile_insn_constant + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getconstant setconstant]) + begin; + FOO = 1 + FOO + end; + end + + def test_compile_insn_global + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[getglobal setglobal]) + begin; + $foo = 1 + $foo + end; + end + + def test_compile_insn_putnil + assert_compile_once('nil', result_inspect: 'nil', insns: %i[putnil]) + end + + def test_compile_insn_putself + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 1, insns: %i[putself]) + begin; + proc { print "hello" }.call + end; + end + + def test_compile_insn_putobject + assert_compile_once('0', result_inspect: '0', insns: %i[putobject_INT2FIX_0_]) + assert_compile_once('1', result_inspect: '1', insns: %i[putobject_INT2FIX_1_]) + assert_compile_once('2', result_inspect: '2', insns: %i[putobject]) + end + + def test_compile_insn_putspecialobject_putiseq + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hellohello', success_count: 2, insns: %i[putspecialobject putiseq]) + begin; + print 2.times.map { + def method_definition + 'hello' + end + method_definition + }.join + end; + end + + def test_compile_insn_putstring_concatstrings_tostring + assert_compile_once('"a#{}b" + "c"', result_inspect: '"abc"', insns: %i[putstring concatstrings tostring]) + end + + def test_compile_insn_freezestring + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~'end;'}", stdout: 'true', success_count: 1, insns: %i[freezestring]) + begin; + # frozen_string_literal: true + print proc { "#{true}".frozen? }.call + end; + end + + def test_compile_insn_toregexp + assert_compile_once('/#{true}/ =~ "true"', result_inspect: '0', insns: %i[toregexp]) + end + + def test_compile_insn_newarray + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '[1, 2, 3]', insns: %i[newarray]) + begin; + a, b, c = 1, 2, 3 + [a, b, c] + end; + end + + def test_compile_insn_intern_duparray + assert_compile_once('[:"#{0}"] + [1,2,3]', result_inspect: '[:"0", 1, 2, 3]', insns: %i[intern duparray]) + end + + def test_compile_insn_expandarray + assert_compile_once('y = [ true, false, nil ]; x, = y; x', result_inspect: 'true', insns: %i[expandarray]) + end + + def test_compile_insn_concatarray + assert_compile_once('["t", "r", *x = "u", "e"].join', result_inspect: '"true"', insns: %i[concatarray]) + end + + def test_compile_insn_splatarray + assert_compile_once('[*(1..2)]', result_inspect: '[1, 2]', insns: %i[splatarray]) + end + + def test_compile_insn_newhash + assert_compile_once('a = 1; { a: a }', result_inspect: '{:a=>1}', insns: %i[newhash]) + end + + def test_compile_insn_duphash + assert_compile_once('{ a: 1 }', result_inspect: '{:a=>1}', insns: %i[duphash]) + end + + def test_compile_insn_newrange + assert_compile_once('a = 1; 0..a', result_inspect: '0..1', insns: %i[newrange]) + end + + def test_compile_insn_pop + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[pop]) + begin; + a = false + b = 1 + a || b + end; + end + + def test_compile_insn_dup + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '3', insns: %i[dup]) + begin; + a = 1 + a&.+(2) + end; + end + + def test_compile_insn_dupn + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[dupn]) + begin; + klass = Class.new + klass::X ||= true + end; + end + + def test_compile_insn_swap_topn + assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn]) + end + + def test_compile_insn_reverse + assert_compile_once('q, (w, e), r = 1, [2, 3], 4; [q, w, e, r]', result_inspect: '[1, 2, 3, 4]', insns: %i[reverse]) + end + + def test_compile_insn_reput + skip "write test" + end + + def test_compile_insn_setn + assert_compile_once('[nil][0] = 1', result_inspect: '1', insns: %i[setn]) + end + + def test_compile_insn_adjuststack + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: 'true', insns: %i[adjuststack]) + begin; + x = [true] + x[0] ||= nil + x[0] + end; + end + + def test_compile_insn_defined + assert_compile_once('defined?(a)', result_inspect: 'nil', insns: %i[defined]) + end + + def test_compile_insn_checkkeyword + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'true', success_count: 1, insns: %i[checkkeyword]) + begin; + def test(x: rand) + x + end + print test(x: true) + end; + end + + def test_compile_insn_tracecoverage + skip "write test" + end + + def test_compile_insn_defineclass + skip "support this in mjit_compile (low priority)" + end + + def test_compile_insn_send + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2, insns: %i[send]) + begin; + print proc { yield_self { 1 } }.call + end; + end + + def test_compile_insn_opt_str_freeze + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"foo"', insns: %i[opt_str_freeze]) + begin; + 'foo'.freeze + end; + end + + def test_compile_insn_opt_str_uminus + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"bar"', insns: %i[opt_str_uminus]) + begin; + -'bar' + end; + end + + def test_compile_insn_opt_newarray_max + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '2', insns: %i[opt_newarray_max]) + begin; + a = 1 + b = 2 + [a, b].max + end; + end + + def test_compile_insn_opt_newarray_min + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '1', insns: %i[opt_newarray_min]) + begin; + a = 1 + b = 2 + [a, b].min + end; + end + + def test_compile_insn_opt_send_without_block + assert_compile_once('print', result_inspect: 'nil', insns: %i[opt_send_without_block]) + end + + def test_compile_insn_invokesuper + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 4, insns: %i[invokesuper]) + begin; + mod = Module.new { + def test + super + 2 + end + } + klass = Class.new { + prepend mod + def test + 1 + end + } + print klass.new.test + end; + end + + def test_compile_insn_invokeblock_leave + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '2', success_count: 2, insns: %i[invokeblock leave]) + begin; + def foo + yield + end + print foo { 2 } + end; + end + + def test_compile_insn_throw + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '4', success_count: 2, insns: %i[throw]) + begin; + def test + proc do + if 1+1 == 1 + return 3 + else + return 4 + end + 5 + end.call + end + print test + end; + end + + def test_compile_insn_jump_branchif + assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: 'nil', insns: %i[jump branchif]) + begin; + a = false + 1 + 1 while a + end; + end + + def test_compile_insn_branchunless + assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '1', insns: %i[branchunless]) + begin; + a = true + if a + 1 + else + 2 + end + end; + end + + def test_compile_insn_branchnil + assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '3', insns: %i[branchnil]) + begin; + a = 2 + a&.+(1) + end; + end + + def test_compile_insn_checktype + assert_compile_once("#{<<~"begin;"}\n#{<<~'end;'}", result_inspect: '"42"', insns: %i[checktype]) + begin; + a = '2' + "4#{a}" + end; + end + + def test_compile_insn_inlinecache + assert_compile_once('Struct', result_inspect: 'Struct', insns: %i[opt_getinlinecache opt_setinlinecache]) + end + + def test_compile_insn_once + assert_compile_once('/#{true}/o =~ "true" && $~.to_a', result_inspect: '["true"]', insns: %i[once]) + end + + def test_compile_insn_checkmatch_opt_case_dispatch + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '"world"', insns: %i[checkmatch opt_case_dispatch]) + begin; + case 'hello' + when 'hello' + 'world' + end + end; + end + + def test_compile_insn_opt_calc + assert_compile_once('4 + 2 - ((2 * 3 / 2) % 2)', result_inspect: '5', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod]) + assert_compile_once('4.0 + 2.0 - ((2.0 * 3.0 / 2.0) % 2.0)', result_inspect: '5.0', insns: %i[opt_plus opt_minus opt_mult opt_div opt_mod]) + assert_compile_once('4 + 2', result_inspect: '6') + end + + def test_compile_insn_opt_cmp + assert_compile_once('(1 == 1) && (1 != 2)', result_inspect: 'true', insns: %i[opt_eq opt_neq]) + end + + def test_compile_insn_opt_rel + assert_compile_once('1 < 2 && 1 <= 1 && 2 > 1 && 1 >= 1', result_inspect: 'true', insns: %i[opt_lt opt_le opt_gt opt_ge]) + end + + def test_compile_insn_opt_ltlt + assert_compile_once('[1] << 2', result_inspect: '[1, 2]', insns: %i[opt_ltlt]) + end + + def test_compile_insn_opt_and + assert_compile_once('1 & 3', result_inspect: '1', insns: %i[opt_and]) + end + + def test_compile_insn_opt_or + assert_compile_once('1 | 3', result_inspect: '3', insns: %i[opt_or]) + end + + def test_compile_insn_opt_aref + # optimized call (optimized JIT) -> send call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '21', success_count: 2, min_calls: 1, insns: %i[opt_aref]) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call({ 1 => 2 }) + print block.call(obj) + end; + + # send call -> optimized call (send JIT) -> optimized call + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '122', success_count: 1, min_calls: 2) + begin; + obj = Object.new + def obj.[](h) + h + end + + block = proc { |h| h[1] } + print block.call(obj) + print block.call({ 1 => 2 }) + print block.call({ 1 => 2 }) + end; + end + + def test_compile_insn_opt_aref_with + assert_compile_once("{ '1' => 2 }['1']", result_inspect: '2', insns: %i[opt_aref_with]) + end + + def test_compile_insn_opt_aset + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '5', insns: %i[opt_aset opt_aset_with]) + begin; + hash = { '1' => 2 } + (hash['2'] = 2) + (hash[1.to_s] = 3) + end; + end + + def test_compile_insn_opt_length_size + assert_compile_once("#{<<~"begin;"}\n#{<<~"end;"}", result_inspect: '4', insns: %i[opt_length opt_size]) + begin; + array = [1, 2] + array.length + array.size + end; + end + + def test_compile_insn_opt_empty_p + assert_compile_once('[].empty?', result_inspect: 'true', insns: %i[opt_empty_p]) + end + + def test_compile_insn_opt_succ + assert_compile_once('1.succ', result_inspect: '2', insns: %i[opt_succ]) + end + + def test_compile_insn_opt_not + assert_compile_once('!!true', result_inspect: 'true', insns: %i[opt_not]) + end + + def test_compile_insn_opt_regexpmatch1 + assert_compile_once("/true/ =~ 'true'", result_inspect: '0', insns: %i[opt_regexpmatch1]) + end + + def test_compile_insn_opt_regexpmatch2 + assert_compile_once("'true' =~ /true/", result_inspect: '0', insns: %i[opt_regexpmatch2]) + end + + def test_compile_insn_opt_call_c_function + skip "support this in opt_call_c_function (low priority)" + end + + def test_jit_output + out, err = eval_with_jit('5.times { puts "MJIT" }', verbose: 1, min_calls: 5) + assert_equal("MJIT\n" * 5, out) + assert_match(/^#{JIT_SUCCESS_PREFIX}: block in <main>@-e:1 -> .+_ruby_mjit_p\d+u\d+\.c$/, err) + assert_match(/^Successful MJIT finish$/, err) + end + + def test_nothing_to_unload_with_jit_wait + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 11, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS) + begin; + def a1() a2() end + def a2() a3() end + def a3() a4() end + def a4() a5() end + def a5() a6() end + def a6() a7() end + def a7() a8() end + def a8() a9() end + def a9() a10() end + def a10() a11() end + def a11() print('hello') end + a1 + end; + end + + def test_unload_units_on_fiber + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: 'hello', success_count: 12, max_cache: 10, ignorable_patterns: MAX_CACHE_PATTERNS) + begin; + def a1() a2(false); a2(true) end + def a2(a) a3(a) end + def a3(a) a4(a) end + def a4(a) a5(a) end + def a5(a) a6(a) end + def a6(a) a7(a) end + def a7(a) a8(a) end + def a8(a) a9(a) end + def a9(a) a10(a) end + def a10(a) + if a + Fiber.new { a11 }.resume + end + end + def a11() print('hello') end + a1 + end; + end + + def test_unload_units_and_compaction + Dir.mktmpdir("jit_test_unload_units_") do |dir| + # MIN_CACHE_SIZE is 10 + out, err = eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~'end;'}", verbose: 1, min_calls: 1, max_cache: 10) + begin; + i = 0 + while i < 11 + eval(<<-EOS) + def mjit#{i} + print #{i} + end + mjit#{i} + EOS + i += 1 + end + + if defined?(fork) + # test the child does not try to delete files which are deleted by parent, + # and test possible deadlock on fork during MJIT unload and JIT compaction on child + Process.waitpid(Process.fork {}) + end + end; + + debug_info = %Q[stdout:\n"""\n#{out}\n"""\n\nstderr:\n"""\n#{err}"""\n] + assert_equal('012345678910', out, debug_info) + compactions, errs = err.lines.partition do |l| + l.match?(/\AJIT compaction \(\d+\.\dms\): Compacted \d+ methods ->/) + end + 10.times do |i| + assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit#{i}@\(eval\):/, errs[i], debug_info) + end + assert_equal("Too many JIT code -- 1 units unloaded\n", errs[10], debug_info) + assert_match(/\A#{JIT_SUCCESS_PREFIX}: mjit10@\(eval\):/, errs[11], debug_info) + + # On --jit-wait, when the number of JIT-ed code reaches --jit-max-cache, + # it should trigger compaction. + unless RUBY_PLATFORM.match?(/mswin|mingw/) # compaction is not supported on Windows yet + assert_equal(3, compactions.size, debug_info) + end + + if appveyor_mswin? + # "Permission Denied" error is preventing to remove so file on AppVeyor. + warn 'skipped to test directory emptiness in TestJIT#test_unload_units on AppVeyor mswin' + else + # verify .o files are deleted on unload_units + assert_send([Dir, :empty?, dir], debug_info) + end + end + end + + def test_local_stack_on_exception + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '3', success_count: 2) + begin; + def b + raise + rescue + 2 + end + + def a + # Calling #b should be vm_exec, not direct mjit_exec. + # Otherwise `1` on local variable would be purged. + 1 + b + end + + print a + end; + end + + def test_local_stack_with_sp_motion_by_blockargs + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 2) + begin; + def b(base) + 1 + end + + # This method is simple enough to have false in catch_except_p. + # So local_stack_p would be true in JIT compiler. + def a + m = method(:b) + + # ci->flag has VM_CALL_ARGS_BLOCKARG and cfp->sp is moved in vm_caller_setup_arg_block. + # So, for this send insn, JIT-ed code should use cfp->sp instead of local variables for stack. + Module.module_eval(&m) + end + + print a + end; + end + + def test_catching_deep_exception + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '1', success_count: 4) + begin; + def catch_true(paths, prefixes) # catch_except_p: TRUE + prefixes.each do |prefix| # catch_except_p: TRUE + paths.each do |path| # catch_except_p: FALSE + return path + end + end + end + + def wrapper(paths, prefixes) + catch_true(paths, prefixes) + end + + print wrapper(['1'], ['2']) + end; + end + + def test_inlined_undefined_ivar + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "bbb", success_count: 2, min_calls: 3) + begin; + class Foo + def initialize + @a = :a + end + + def bar + if @b.nil? + @b = :b + end + end + end + + print(Foo.new.bar) + print(Foo.new.bar) + print(Foo.new.bar) + end; + end + + def test_inlined_setivar_frozen + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "FrozenError\n", success_count: 1, min_calls: 3) + begin; + class A + def a + @a = 1 + end + end + + a = A.new + a.a + a.a + a.a + a.freeze + begin + a.a + rescue FrozenError => e + p e.class + end + end; + end + + def test_attr_reader + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "4nil\nnil\n6", success_count: 2, min_calls: 2) + begin; + class A + attr_reader :a, :b + + def initialize + @a = 2 + end + + def test + a + end + + def undefined + b + end + end + + a = A.new + print(a.test * a.test) + p(a.undefined) + p(a.undefined) + + # redefinition + class A + def test + 3 + end + end + + print(2 * a.test) + end; + + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "true", success_count: 1, min_calls: 2) + begin; + class Hoge + attr_reader :foo + + def initialize + @foo = [] + @bar = nil + end + end + + class Fuga < Hoge + def initialize + @bar = nil + @foo = [] + end + end + + def test(recv) + recv.foo.empty? + end + + hoge = Hoge.new + fuga = Fuga.new + + test(hoge) # VM: cc set index=1 + test(hoge) # JIT: compile with index=1 + test(fuga) # JIT -> VM: cc set index=2 + print test(hoge) # JIT: should use index=1, not index=2 in cc + end; + end + + def test_jump_to_precompiled_branch + assert_eval_with_jit("#{<<~'begin;'}\n#{<<~'end;'}", stdout: ".0", success_count: 1, min_calls: 1) + begin; + def test(foo) + ".#{foo unless foo == 1}" if true + end + print test(0) + end; + end + + def test_clean_so + if appveyor_mswin? + skip 'Removing so file is failing on AppVeyor mswin due to Permission Denied.' + end + Dir.mktmpdir("jit_test_clean_so_") do |dir| + code = "x = 0; 10.times {|i|x+=i}" + eval_with_jit({"TMPDIR"=>dir}, code) + assert_send([Dir, :empty?, dir]) + eval_with_jit({"TMPDIR"=>dir}, code, save_temps: true) + assert_not_send([Dir, :empty?, dir]) + end + end + + def test_clean_objects_on_exec + if /mswin|mingw/ =~ RUBY_PLATFORM + # TODO: check call stack and close handle of code which is not on stack, and remove objects on best-effort basis + skip 'Removing so file being used does not work on Windows' + end + Dir.mktmpdir("jit_test_clean_objects_on_exec_") do |dir| + eval_with_jit({"TMPDIR"=>dir}, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1) + begin; + def a; end; a + exec "true" + end; + error_message = "Undeleted files:\n #{Dir.glob("#{dir}/*").join("\n ")}\n" + assert_send([Dir, :empty?, dir], error_message) + end + end + + def test_lambda_longjmp + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: '5', success_count: 1) + begin; + fib = lambda do |x| + return x if x == 0 || x == 1 + fib.call(x-1) + fib.call(x-2) + end + print fib.call(5) + end; + end + + def test_stack_pointer_with_assignment + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1) + begin; + 2.times do + a, _ = nil + p a + end + end; + end + + def test_program_counter_with_regexpmatch + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "aa", success_count: 1) + begin; + 2.times do + break if /a/ =~ "ab" && !$~[0] + print $~[0] + end + end; + end + + def test_pushed_values_with_opt_aset_with + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "{}{}", success_count: 1) + begin; + 2.times do + print(Thread.current["a"] = {}) + end + end; + end + + def test_pushed_values_with_opt_aref_with + assert_eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", stdout: "nil\nnil\n", success_count: 1) + begin; + 2.times do + p(Thread.current["a"]) + end + end; + end + + def test_caller_locations_without_catch_table + out, _ = eval_with_jit("#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 1) + begin; + def b # 2 + caller_locations.first # 3 + end # 4 + # 5 + def a # 6 + print # <-- don't leave PC here # 7 + b # 8 + end + puts a + puts a + end; + lines = out.lines + assert_equal("-e:8:in `a'\n", lines[0]) + assert_equal("-e:8:in `a'\n", lines[1]) + end + + def test_fork_with_mjit_worker_thread + Dir.mktmpdir("jit_test_fork_with_mjit_worker_thread_") do |dir| + # min_calls: 2 to skip fork block + out, err = eval_with_jit({ "TMPDIR" => dir }, "#{<<~"begin;"}\n#{<<~"end;"}", min_calls: 2, verbose: 1) + begin; + def before_fork; end + def after_fork; end + + before_fork; before_fork # the child should not delete this .o file + pid = Process.fork do # this child should not delete shared .pch file + after_fork; after_fork # this child does not share JIT-ed after_fork with parent + end + after_fork; after_fork # this parent does not share JIT-ed after_fork with child + + Process.waitpid(pid) + end; + success_count = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size + debug_info = "stdout:\n```\n#{out}\n```\n\nstderr:\n```\n#{err}```\n" + assert_equal(3, success_count, debug_info) + + # assert no remove error + assert_equal("Successful MJIT finish\n" * 2, err.gsub(/^#{JIT_SUCCESS_PREFIX}:[^\n]+\n/, ''), debug_info) + + # ensure objects are deleted + assert_send([Dir, :empty?, dir], debug_info) + end + end if defined?(fork) + + private + + def appveyor_mswin? + ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/) + end + + # The shortest way to test one proc + def assert_compile_once(script, result_inspect:, insns: [], uplevel: 1) + if script.match?(/\A\n.+\n\z/m) + script = script.gsub(/^/, ' ') + else + script = " #{script} " + end + assert_eval_with_jit("p proc {#{script}}.call", stdout: "#{result_inspect}\n", success_count: 1, insns: insns, uplevel: uplevel + 1) + end + + # Shorthand for normal test cases + def assert_eval_with_jit(script, stdout: nil, success_count:, min_calls: 1, max_cache: 1000, insns: [], uplevel: 1, ignorable_patterns: []) + out, err = eval_with_jit(script, verbose: 1, min_calls: min_calls, max_cache: max_cache) + actual = err.scan(/^#{JIT_SUCCESS_PREFIX}:/).size + # Add --jit-verbose=2 logs for cl.exe because compiler's error message is suppressed + # for cl.exe with --jit-verbose=1. See `start_process` in mjit_worker.c. + if RUBY_PLATFORM.match?(/mswin/) && success_count != actual + out2, err2 = eval_with_jit(script, verbose: 2, min_calls: min_calls, max_cache: max_cache) + end + + # Make sure that the script has insns expected to be tested + used_insns = method_insns(script) + insns.each do |insn| + unless used_insns.include?(insn) + $stderr.puts + warn "'#{insn}' insn is not included in the script. Actual insns are: #{used_insns.join(' ')}\n", uplevel: uplevel+2 + end + TestJIT.untested_insns.delete(insn) + end + + assert_equal( + success_count, actual, + "Expected #{success_count} times of JIT success, but succeeded #{actual} times.\n\n"\ + "script:\n#{code_block(script)}\nstderr:\n#{code_block(err)}#{( + "\nstdout(verbose=2 retry):\n#{code_block(out2)}\nstderr(verbose=2 retry):\n#{code_block(err2)}" if out2 || err2 + )}", + ) + if stdout + assert_equal(stdout, out, "Expected stdout #{out.inspect} to match #{stdout.inspect} with script:\n#{code_block(script)}") + end + err_lines = err.lines.reject! do |l| + l.chomp.empty? || l.match?(/\A#{JIT_SUCCESS_PREFIX}/) || (IGNORABLE_PATTERNS + ignorable_patterns).any? { |pat| pat.match?(l) } + end + unless err_lines.empty? + warn err_lines.join(''), uplevel: uplevel + end + end + + # Collect block's insns or defined method's insns, which are expected to be JIT-ed. + # Note that this intentionally excludes insns in script's toplevel because they are not JIT-ed. + def method_insns(script) + insns = [] + RubyVM::InstructionSequence.compile(script).to_a.last.each do |(insn, *args)| + case insn + when :putiseq, :send + insns += collect_insns(args.last) + when :defineclass + insns += collect_insns(args[1]) + end + end + insns.uniq + end + + # Recursively collect insns in iseq_array + def collect_insns(iseq_array) + return [] if iseq_array.nil? + + insns = iseq_array.last.select { |x| x.is_a?(Array) }.map(&:first) + iseq_array.last.each do |(insn, *args)| + case insn + when :putiseq, :send + insns += collect_insns(args.last) + end + end + insns + end +end diff --git a/test/ruby/test_key_error.rb b/test/ruby/test_key_error.rb new file mode 100644 index 0000000000..fe1d5bb5ab --- /dev/null +++ b/test/ruby/test_key_error.rb @@ -0,0 +1,42 @@ +require 'test/unit' + +class TestKeyError < Test::Unit::TestCase + def test_default + error = KeyError.new + assert_equal("KeyError", error.message) + end + + def test_message + error = KeyError.new("Message") + assert_equal("Message", error.message) + end + + def test_receiver + receiver = Object.new + error = KeyError.new(receiver: receiver) + assert_equal(receiver, error.receiver) + error = KeyError.new + assert_raise(ArgumentError) {error.receiver} + end + + def test_key + error = KeyError.new(key: :key) + assert_equal(:key, error.key) + error = KeyError.new + assert_raise(ArgumentError) {error.key} + end + + def test_receiver_and_key + receiver = Object.new + error = KeyError.new(receiver: receiver, key: :key) + assert_equal([receiver, :key], + [error.receiver, error.key]) + end + + def test_all + receiver = Object.new + error = KeyError.new("Message", receiver: receiver, key: :key) + assert_equal(["Message", receiver, :key], + [error.message, error.receiver, error.key]) + end +end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb new file mode 100644 index 0000000000..8e2da53bdf --- /dev/null +++ b/test/ruby/test_keyword.rb @@ -0,0 +1,748 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestKeywordArguments < Test::Unit::TestCase + def f1(str: "foo", num: 424242) + [str, num] + end + + def test_f1 + assert_equal(["foo", 424242], f1) + assert_equal(["bar", 424242], f1(str: "bar")) + assert_equal(["foo", 111111], f1(num: 111111)) + assert_equal(["bar", 111111], f1(str: "bar", num: 111111)) + assert_raise(ArgumentError) { f1(str: "bar", check: true) } + assert_raise(ArgumentError) { f1("string") } + end + + + def f2(x, str: "foo", num: 424242) + [x, str, num] + end + + def test_f2 + assert_equal([:xyz, "foo", 424242], f2(:xyz)) + assert_equal([{"bar"=>42}, "foo", 424242], f2("bar"=>42)) + end + + + def f3(str: "foo", num: 424242, **h) + [str, num, h] + end + + def test_f3 + assert_equal(["foo", 424242, {}], f3) + assert_equal(["bar", 424242, {}], f3(str: "bar")) + assert_equal(["foo", 111111, {}], f3(num: 111111)) + assert_equal(["bar", 111111, {}], f3(str: "bar", num: 111111)) + assert_equal(["bar", 424242, {:check=>true}], f3(str: "bar", check: true)) + assert_raise(ArgumentError) { f3("string") } + end + + + define_method(:f4) {|str: "foo", num: 424242| [str, num] } + + def test_f4 + assert_equal(["foo", 424242], f4) + assert_equal(["bar", 424242], f4(str: "bar")) + assert_equal(["foo", 111111], f4(num: 111111)) + assert_equal(["bar", 111111], f4(str: "bar", num: 111111)) + assert_raise(ArgumentError) { f4(str: "bar", check: true) } + assert_raise(ArgumentError) { f4("string") } + end + + + define_method(:f5) {|str: "foo", num: 424242, **h| [str, num, h] } + + def test_f5 + assert_equal(["foo", 424242, {}], f5) + assert_equal(["bar", 424242, {}], f5(str: "bar")) + assert_equal(["foo", 111111, {}], f5(num: 111111)) + assert_equal(["bar", 111111, {}], f5(str: "bar", num: 111111)) + assert_equal(["bar", 424242, {:check=>true}], f5(str: "bar", check: true)) + assert_raise(ArgumentError) { f5("string") } + end + + + def f6(str: "foo", num: 424242, **h, &blk) + [str, num, h, blk] + end + + def test_f6 # [ruby-core:40518] + assert_equal(["foo", 424242, {}, nil], f6) + assert_equal(["bar", 424242, {}, nil], f6(str: "bar")) + assert_equal(["foo", 111111, {}, nil], f6(num: 111111)) + assert_equal(["bar", 111111, {}, nil], f6(str: "bar", num: 111111)) + assert_equal(["bar", 424242, {:check=>true}, nil], f6(str: "bar", check: true)) + a = f6 {|x| x + 42 } + assert_equal(["foo", 424242, {}], a[0, 3]) + assert_equal(43, a.last.call(1)) + end + + def f7(*r, str: "foo", num: 424242, **h) + [r, str, num, h] + end + + def test_f7 # [ruby-core:41772] + assert_equal([[], "foo", 424242, {}], f7) + assert_equal([[], "bar", 424242, {}], f7(str: "bar")) + assert_equal([[], "foo", 111111, {}], f7(num: 111111)) + assert_equal([[], "bar", 111111, {}], f7(str: "bar", num: 111111)) + assert_equal([[1], "foo", 424242, {}], f7(1)) + assert_equal([[1, 2], "foo", 424242, {}], f7(1, 2)) + assert_equal([[1, 2, 3], "foo", 424242, {}], f7(1, 2, 3)) + assert_equal([[1], "bar", 424242, {}], f7(1, str: "bar")) + assert_equal([[1, 2], "bar", 424242, {}], f7(1, 2, str: "bar")) + assert_equal([[1, 2, 3], "bar", 424242, {}], f7(1, 2, 3, str: "bar")) + end + + define_method(:f8) { |opt = :ion, *rest, key: :word| + [opt, rest, key] + } + + def test_f8 + assert_equal([:ion, [], :word], f8) + assert_equal([1, [], :word], f8(1)) + assert_equal([1, [2], :word], f8(1, 2)) + end + + def f9(r, o=42, *args, p, k: :key, **kw, &b) + [r, o, args, p, k, kw, b] + end + + def test_f9 + assert_equal([1, 42, [], 2, :key, {}, nil], f9(1, 2)) + assert_equal([1, 2, [], 3, :key, {}, nil], f9(1, 2, 3)) + assert_equal([1, 2, [3], 4, :key, {}, nil], f9(1, 2, 3, 4)) + assert_equal([1, 2, [3, 4], 5, :key, {str: "bar"}, nil], f9(1, 2, 3, 4, 5, str: "bar")) + end + + def f10(a: 1, **) + a + end + + def test_f10 + assert_equal(42, f10(a: 42)) + assert_equal(1, f10(b: 42)) + end + + def test_method_parameters + assert_equal([[:key, :str], [:key, :num]], method(:f1).parameters); + assert_equal([[:req, :x], [:key, :str], [:key, :num]], method(:f2).parameters); + assert_equal([[:key, :str], [:key, :num], [:keyrest, :h]], method(:f3).parameters); + assert_equal([[:key, :str], [:key, :num]], method(:f4).parameters); + assert_equal([[:key, :str], [:key, :num], [:keyrest, :h]], method(:f5).parameters); + assert_equal([[:key, :str], [:key, :num], [:keyrest, :h], [:block, :blk]], method(:f6).parameters); + assert_equal([[:rest, :r], [:key, :str], [:key, :num], [:keyrest, :h]], method(:f7).parameters); + assert_equal([[:opt, :opt], [:rest, :rest], [:key, :key]], method(:f8).parameters) # [Bug #7540] [ruby-core:50735] + assert_equal([[:req, :r], [:opt, :o], [:rest, :args], [:req, :p], [:key, :k], + [:keyrest, :kw], [:block, :b]], method(:f9).parameters) + end + + def test_lambda + f = ->(str: "foo", num: 424242) { [str, num] } + assert_equal(["foo", 424242], f[]) + assert_equal(["bar", 424242], f[str: "bar"]) + assert_equal(["foo", 111111], f[num: 111111]) + assert_equal(["bar", 111111], f[str: "bar", num: 111111]) + end + + + def p1 + Proc.new do |str: "foo", num: 424242| + [str, num] + end + end + + def test_p1 + assert_equal(["foo", 424242], p1[]) + assert_equal(["bar", 424242], p1[str: "bar"]) + assert_equal(["foo", 111111], p1[num: 111111]) + assert_equal(["bar", 111111], p1[str: "bar", num: 111111]) + assert_raise(ArgumentError) { p1[str: "bar", check: true] } + assert_equal(["foo", 424242], p1["string"] ) + end + + + def p2 + Proc.new do |x, str: "foo", num: 424242| + [x, str, num] + end + end + + def test_p2 + assert_equal([nil, "foo", 424242], p2[]) + assert_equal([:xyz, "foo", 424242], p2[:xyz]) + end + + + def p3 + Proc.new do |str: "foo", num: 424242, **h| + [str, num, h] + end + end + + def test_p3 + assert_equal(["foo", 424242, {}], p3[]) + assert_equal(["bar", 424242, {}], p3[str: "bar"]) + assert_equal(["foo", 111111, {}], p3[num: 111111]) + assert_equal(["bar", 111111, {}], p3[str: "bar", num: 111111]) + assert_equal(["bar", 424242, {:check=>true}], p3[str: "bar", check: true]) + assert_equal(["foo", 424242, {}], p3["string"]) + end + + + def p4 + Proc.new do |str: "foo", num: 424242, **h, &blk| + [str, num, h, blk] + end + end + + def test_p4 + assert_equal(["foo", 424242, {}, nil], p4[]) + assert_equal(["bar", 424242, {}, nil], p4[str: "bar"]) + assert_equal(["foo", 111111, {}, nil], p4[num: 111111]) + assert_equal(["bar", 111111, {}, nil], p4[str: "bar", num: 111111]) + assert_equal(["bar", 424242, {:check=>true}, nil], p4[str: "bar", check: true]) + a = p4.call {|x| x + 42 } + assert_equal(["foo", 424242, {}], a[0, 3]) + assert_equal(43, a.last.call(1)) + end + + + def p5 + Proc.new do |*r, str: "foo", num: 424242, **h| + [r, str, num, h] + end + end + + def test_p5 + assert_equal([[], "foo", 424242, {}], p5[]) + assert_equal([[], "bar", 424242, {}], p5[str: "bar"]) + assert_equal([[], "foo", 111111, {}], p5[num: 111111]) + assert_equal([[], "bar", 111111, {}], p5[str: "bar", num: 111111]) + assert_equal([[1], "foo", 424242, {}], p5[1]) + assert_equal([[1, 2], "foo", 424242, {}], p5[1, 2]) + assert_equal([[1, 2, 3], "foo", 424242, {}], p5[1, 2, 3]) + assert_equal([[1], "bar", 424242, {}], p5[1, str: "bar"]) + assert_equal([[1, 2], "bar", 424242, {}], p5[1, 2, str: "bar"]) + assert_equal([[1, 2, 3], "bar", 424242, {}], p5[1, 2, 3, str: "bar"]) + end + + + def p6 + Proc.new do |o1, o2=42, *args, p, k: :key, **kw, &b| + [o1, o2, args, p, k, kw, b] + end + end + + def test_p6 + assert_equal([nil, 42, [], nil, :key, {}, nil], p6[]) + assert_equal([1, 42, [], 2, :key, {}, nil], p6[1, 2]) + assert_equal([1, 2, [], 3, :key, {}, nil], p6[1, 2, 3]) + assert_equal([1, 2, [3], 4, :key, {}, nil], p6[1, 2, 3, 4]) + assert_equal([1, 2, [3, 4], 5, :key, {str: "bar"}, nil], p6[1, 2, 3, 4, 5, str: "bar"]) + end + + def test_proc_parameters + assert_equal([[:key, :str], [:key, :num]], p1.parameters); + assert_equal([[:opt, :x], [:key, :str], [:key, :num]], p2.parameters); + assert_equal([[:key, :str], [:key, :num], [:keyrest, :h]], p3.parameters); + assert_equal([[:key, :str], [:key, :num], [:keyrest, :h], [:block, :blk]], p4.parameters); + assert_equal([[:rest, :r], [:key, :str], [:key, :num], [:keyrest, :h]], p5.parameters); + assert_equal([[:opt, :o1], [:opt, :o2], [:rest, :args], [:opt, :p], [:key, :k], + [:keyrest, :kw], [:block, :b]], p6.parameters) + end + + def m1(*args) + yield(*args) + end + + def test_block + blk = Proc.new {|str: "foo", num: 424242| [str, num] } + assert_equal(["foo", 424242], m1(&blk)) + assert_equal(["bar", 424242], m1(str: "bar", &blk)) + assert_equal(["foo", 111111], m1(num: 111111, &blk)) + assert_equal(["bar", 111111], m1(str: "bar", num: 111111, &blk)) + end + + def rest_keyrest(*args, **opt) + return *args, opt + end + + def test_rest_keyrest + bug7665 = '[ruby-core:51278]' + bug8463 = '[ruby-core:55203] [Bug #8463]' + expect = [*%w[foo bar], {zzz: 42}] + assert_equal(expect, rest_keyrest(*expect), bug7665) + pr = proc {|*args, **opt| next *args, opt} + assert_equal(expect, pr.call(*expect), bug7665) + assert_equal(expect, pr.call(expect), bug8463) + pr = proc {|a, *b, **opt| next a, *b, opt} + assert_equal(expect, pr.call(expect), bug8463) + pr = proc {|a, **opt| next a, opt} + assert_equal(expect.values_at(0, -1), pr.call(expect), bug8463) + end + + def test_bare_kwrest + # valid syntax, but its semantics is undefined + assert_valid_syntax("def bug7662(**) end") + assert_valid_syntax("def bug7662(*, **) end") + assert_valid_syntax("def bug7662(a, **) end") + end + + def test_without_paren + bug7942 = '[ruby-core:52820] [Bug #7942]' + assert_valid_syntax("def bug7942 a: 1; end") + assert_valid_syntax("def bug7942 a: 1, **; end") + + o = Object.new + eval("def o.bug7942 a: 1; a; end", nil, __FILE__, __LINE__) + assert_equal(1, o.bug7942(), bug7942) + assert_equal(42, o.bug7942(a: 42), bug7942) + + o = Object.new + eval("def o.bug7942 a: 1, **; a; end", nil, __FILE__, __LINE__) + assert_equal(1, o.bug7942(), bug7942) + assert_equal(42, o.bug7942(a: 42), bug7942) + end + + def test_required_keyword + 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", 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) + + bug8139 = '[ruby-core:53608] [Bug #8139] required keyword argument with rest hash' + assert_equal([42, {}], o.bar(a: 42), feature7701) + assert_equal([42, {c: feature7701}], o.bar(a: 42, c: feature7701), feature7701) + assert_equal([[:keyreq, :a], [:keyrest, :b]], o.method(:bar).parameters, feature7701) + assert_raise_with_message(ArgumentError, /missing keyword/, bug8139) {o.bar(c: bug8139)} + assert_raise_with_message(ArgumentError, /missing keyword/, bug8139) {o.bar} + end + + def test_required_keyword_with_newline + bug9669 = '[ruby-core:61658] [Bug #9669]' + assert_nothing_raised(SyntaxError, bug9669) do + eval(<<-'end;', nil, __FILE__, __LINE__) + def bug9669.foo a: + return a + end + end; + end + assert_equal(42, bug9669.foo(a: 42)) + o = nil + assert_nothing_raised(SyntaxError, bug9669) do + eval(<<-'end;', nil, __FILE__, __LINE__) + o = { + a: + 1 + } + end; + end + assert_equal({a: 1}, o, bug9669) + end + + def test_required_keyword_with_reserved + bug10279 = '[ruby-core:65211] [Bug #10279]' + h = nil + assert_nothing_raised(SyntaxError, bug10279) do + break eval(<<-'end;', nil, __FILE__, __LINE__) + h = {a: if true then 42 end} + end; + end + assert_equal({a: 42}, h, bug10279) + end + + 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}", nil, 'xyzzy', __LINE__) + end + assert_raise_with_message(ArgumentError, /missing keyword/, feature7701) {b.call} + 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:, **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, :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 + bug8236 = '[ruby-core:54094] [Bug #8236]' + base = Class.new do + def foo(*args) + args + end + end + a = Class.new(base) do + def foo(arg, bar: 'x') + super + end + end + b = Class.new(base) do + def foo(*args, bar: 'x') + super + end + end + assert_equal([42, {:bar=>"x"}], a.new.foo(42), bug8236) + assert_equal([42, {:bar=>"x"}], b.new.foo(42), bug8236) + end + + def test_zsuper_only_named_kwrest + bug8416 = '[ruby-core:55033] [Bug #8416]' + base = Class.new do + def foo(**h) + h + end + end + a = Class.new(base) do + def foo(**h) + super + end + end + assert_equal({:bar=>"x"}, a.new.foo(bar: "x"), bug8416) + end + + def test_zsuper_only_anonymous_kwrest + bug8416 = '[ruby-core:55033] [Bug #8416]' + base = Class.new do + def foo(**h) + h + end + end + a = Class.new(base) do + def foo(**) + super + end + end + assert_equal({:bar=>"x"}, a.new.foo(bar: "x"), bug8416) + end + + def test_precedence_of_keyword_arguments + bug8040 = '[ruby-core:53199] [Bug #8040]' + a = Class.new do + def foo(x, **h) + [x, h] + end + end + assert_equal([{}, {}], a.new.foo({})) + assert_equal([{}, {:bar=>"x"}], a.new.foo({}, bar: "x"), bug8040) + end + + def test_precedence_of_keyword_arguments_with_post_argument + bug8993 = '[ruby-core:57706] [Bug #8993]' + a = Class.new do + def foo(a, b, c=1, *d, e, f:2, **g) + [a, b, c, d, e, f, g] + end + end + assert_equal([1, 2, 1, [], {:f=>5}, 2, {}], a.new.foo(1, 2, f:5), bug8993) + end + + def test_splat_keyword_nondestructive + bug9776 = '[ruby-core:62161] [Bug #9776]' + + h = {a: 1} + assert_equal({a:1, b:2}, {**h, b:2}) + assert_equal({a:1}, h, bug9776) + + pr = proc {|**opt| next opt} + assert_equal({a: 1}, pr.call(**h)) + assert_equal({a: 1, b: 2}, pr.call(**h, b: 2)) + assert_equal({a: 1}, h, bug9776) + end + + def test_splat_hash_conversion + bug9898 = '[ruby-core:62921] [Bug #9898]' + + o = Object.new + def o.to_hash() { a: 1 } end + assert_equal({a: 1}, m1(**o) {|x| break x}, bug9898) + o2 = Object.new + def o2.to_hash() { b: 2 } end + assert_equal({a: 1, b: 2}, m1(**o, **o2) {|x| break x}, bug9898) + end + + def test_implicit_hash_conversion + bug10016 = '[ruby-core:63593] [Bug #10016]' + + o = Object.new + def o.to_hash() { k: 9 } end + assert_equal([1, 42, [], o, :key, {}, nil], f9(1, o)) + assert_equal([1, 9], m1(1, o) {|a, k: 0| break [a, k]}, bug10016) + assert_equal([1, 9], m1(1, o, &->(a, k: 0) {break [a, k]}), bug10016) + end + + def test_splat_hash + m = Object.new + def m.f() :ok; end + def m.f1(a) a; end + def m.f2(a = nil) a; end + def m.f3(**a) a; end + def m.f4(*a) a; end + o = {a: 1} + assert_raise_with_message(ArgumentError, /unknown keyword: a/) { + 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_warning('', 'splat to kwrest') do + assert_equal({a: 42}, m.f3(**o)) + end + assert_warning('', 'splat to rest') do + assert_equal([{a: 42}], m.f4(**o)) + end + + assert_warning('') do + assert_equal({a: 42}, m.f2("a".to_sym => 42), '[ruby-core:82291] [Bug #13793]') + end + + 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{ + def m(a: []) + end + GC.stress = true + tap { m } + GC.start + tap { m } + }, bug8964 + assert_normal_exit %q{ + prc = Proc.new {|a: []|} + GC.stress = true + tap { prc.call } + GC.start + tap { prc.call } + }, 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) + def bar(k2: 'v2') + end + + def foo + bar(k1: 1) + end + end + assert_raise_with_message(ArgumentError, /unknown keyword: k1/, bug10413) { + o.foo {raise "unreachable"} + } + end + + def test_unknown_keyword + bug13004 = '[ruby-dev:49893] [Bug #13004]' + assert_raise_with_message(ArgumentError, /unknown keyword: invalid-argument/, bug13004) { + [].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 many_kwargs(a0: '', a1: '', a2: '', a3: '', a4: '', a5: '', a6: '', a7: '', + b0: '', b1: '', b2: '', b3: '', b4: '', b5: '', b6: '', b7: '', + c0: '', c1: '', c2: '', c3: '', c4: '', c5: '', c6: '', c7: '', + d0: '', d1: '', d2: '', d3: '', d4: '', d5: '', d6: '', d7: '', + e0: '') + [a0, a1, a2, a3, a4, a5, a6, a7, + b0, b1, b2, b3, b4, b5, b6, b7, + c0, c1, c2, c3, c4, c5, c6, c7, + d0, d1, d2, d3, d4, d5, d6, d7, + e0] + end + + def test_many_kwargs + i = 0 + assert_equal(:ok, many_kwargs(a0: :ok)[i], "#{i}: a0"); i+=1 + assert_equal(:ok, many_kwargs(a1: :ok)[i], "#{i}: a1"); i+=1 + assert_equal(:ok, many_kwargs(a2: :ok)[i], "#{i}: a2"); i+=1 + assert_equal(:ok, many_kwargs(a3: :ok)[i], "#{i}: a3"); i+=1 + assert_equal(:ok, many_kwargs(a4: :ok)[i], "#{i}: a4"); i+=1 + assert_equal(:ok, many_kwargs(a5: :ok)[i], "#{i}: a5"); i+=1 + assert_equal(:ok, many_kwargs(a6: :ok)[i], "#{i}: a6"); i+=1 + assert_equal(:ok, many_kwargs(a7: :ok)[i], "#{i}: a7"); i+=1 + + assert_equal(:ok, many_kwargs(b0: :ok)[i], "#{i}: b0"); i+=1 + assert_equal(:ok, many_kwargs(b1: :ok)[i], "#{i}: b1"); i+=1 + assert_equal(:ok, many_kwargs(b2: :ok)[i], "#{i}: b2"); i+=1 + assert_equal(:ok, many_kwargs(b3: :ok)[i], "#{i}: b3"); i+=1 + assert_equal(:ok, many_kwargs(b4: :ok)[i], "#{i}: b4"); i+=1 + assert_equal(:ok, many_kwargs(b5: :ok)[i], "#{i}: b5"); i+=1 + assert_equal(:ok, many_kwargs(b6: :ok)[i], "#{i}: b6"); i+=1 + assert_equal(:ok, many_kwargs(b7: :ok)[i], "#{i}: b7"); i+=1 + + assert_equal(:ok, many_kwargs(c0: :ok)[i], "#{i}: c0"); i+=1 + assert_equal(:ok, many_kwargs(c1: :ok)[i], "#{i}: c1"); i+=1 + assert_equal(:ok, many_kwargs(c2: :ok)[i], "#{i}: c2"); i+=1 + assert_equal(:ok, many_kwargs(c3: :ok)[i], "#{i}: c3"); i+=1 + assert_equal(:ok, many_kwargs(c4: :ok)[i], "#{i}: c4"); i+=1 + assert_equal(:ok, many_kwargs(c5: :ok)[i], "#{i}: c5"); i+=1 + assert_equal(:ok, many_kwargs(c6: :ok)[i], "#{i}: c6"); i+=1 + assert_equal(:ok, many_kwargs(c7: :ok)[i], "#{i}: c7"); i+=1 + + assert_equal(:ok, many_kwargs(d0: :ok)[i], "#{i}: d0"); i+=1 + assert_equal(:ok, many_kwargs(d1: :ok)[i], "#{i}: d1"); i+=1 + assert_equal(:ok, many_kwargs(d2: :ok)[i], "#{i}: d2"); i+=1 + assert_equal(:ok, many_kwargs(d3: :ok)[i], "#{i}: d3"); i+=1 + assert_equal(:ok, many_kwargs(d4: :ok)[i], "#{i}: d4"); i+=1 + assert_equal(:ok, many_kwargs(d5: :ok)[i], "#{i}: d5"); i+=1 + assert_equal(:ok, many_kwargs(d6: :ok)[i], "#{i}: d6"); i+=1 + assert_equal(:ok, many_kwargs(d7: :ok)[i], "#{i}: d7"); i+=1 + + assert_equal(:ok, many_kwargs(e0: :ok)[i], "#{i}: e0"); i+=1 + end + + def test_splat_empty_hash_with_block_passing + assert_valid_syntax("bug15087(**{}, &nil)") + end +end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb index 241042d2c7..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 @@ -22,13 +23,18 @@ class TestLambdaParameters < Test::Unit::TestCase assert_raise(ArgumentError) { ->(a,b){ }.call(1,2,3) } end -end - -__END__ 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 @@ -57,12 +63,121 @@ __END__ 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 assert_equal(nil, ->(&b){ b }.call) end + + def test_in_basic_object + bug5966 = '[ruby-core:42349]' + called = false + BasicObject.new.instance_eval {->() {called = true}.()} + assert_equal(true, called, bug5966) + end + + def test_location_on_error + bug6151 = '[ruby-core:43314]' + called = 0 + line, f = __LINE__, lambda do + called += 1 + true + end + e = assert_raise(ArgumentError) do + f.call(42) + end + assert_send([e.backtrace.first, :start_with?, "#{__FILE__}:#{line}:"], bug6151) + assert_equal(0, called) + e = assert_raise(ArgumentError) do + 42.times(&f) + end + assert_send([e.backtrace.first, :start_with?, "#{__FILE__}:#{line}:"], bug6151) + assert_equal(0, called) + end + + def return_in_current(val) + 1.tap(&->(*) {return 0}) + val + end + + def yield_block + yield + end + + def return_in_callee(val) + yield_block(&->(*) {return 0}) + val + end + + def test_return + feature8693 = '[ruby-core:56193] [Feature #8693]' + assert_equal(42, return_in_current(42), feature8693) + assert_equal(42, return_in_callee(42), feature8693) + end + + def test_do_lambda_source_location + exp_lineno = __LINE__ + 3 + lmd = ->(x, + y, + z) do + # + end + file, lineno = lmd.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + end + + def test_brace_lambda_source_location + exp_lineno = __LINE__ + 3 + lmd = ->(x, + y, + z) { + # + } + file, lineno = lmd.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp_lineno, lineno, "must be at the beginning of the block") + end end diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb new file mode 100644 index 0000000000..03371c912a --- /dev/null +++ b/test/ruby/test_lazy_enumerator.rb @@ -0,0 +1,581 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestLazyEnumerator < Test::Unit::TestCase + class Step + include Enumerable + attr_reader :current, :args + + def initialize(enum) + @enum = enum + @current = nil + @args = nil + end + + def each(*args) + @args = args + @enum.each do |v| + @current = v + if v.is_a? Enumerable + yield *v + else + yield v + end + end + end + end + + def test_initialize + assert_equal([1, 2, 3], [1, 2, 3].lazy.to_a) + assert_equal([1, 2, 3], Enumerator::Lazy.new([1, 2, 3]){|y, v| y << v}.to_a) + assert_raise(ArgumentError) { Enumerator::Lazy.new([1, 2, 3]) } + + a = [1, 2, 3].lazy + a.freeze + assert_raise(FrozenError) { + a.__send__ :initialize, [4, 5], &->(y, *v) { y << yield(*v) } + } + end + + def test_each_args + a = Step.new(1..3) + assert_equal(1, a.lazy.each(4).first) + assert_equal([4], a.args) + end + + def test_each_line + name = lineno = nil + File.open(__FILE__) do |f| + f.each("").map do |paragraph| + paragraph[/\A\s*(.*)/, 1] + end.find do |line| + if name = line[/^class\s+(\S+)/, 1] + lineno = f.lineno + true + end + end + end + assert_equal(self.class.name, name) + assert_operator(lineno, :>, 2) + + name = lineno = nil + File.open(__FILE__) do |f| + f.lazy.each("").map do |paragraph| + paragraph[/\A\s*(.*)/, 1] + end.find do |line| + if name = line[/^class\s+(\S+)/, 1] + lineno = f.lineno + true + end + end + end + assert_equal(self.class.name, name) + assert_equal(2, lineno) + end + + def test_select + a = Step.new(1..6) + assert_equal(4, a.select {|x| x > 3}.first) + assert_equal(6, a.current) + assert_equal(4, a.lazy.select {|x| x > 3}.first) + assert_equal(4, a.current) + + a = Step.new(['word', nil, 1]) + assert_raise(TypeError) {a.select {|x| "x"+x}.first} + assert_equal(nil, a.current) + assert_equal("word", a.lazy.select {|x| "x"+x}.first) + assert_equal("word", a.current) + end + + def test_select_multiple_values + e = Enumerator.new { |yielder| + for i in 1..5 + yielder.yield(i, i.to_s) + end + } + assert_equal([[2, "2"], [4, "4"]], + e.select {|x| x[0] % 2 == 0}) + assert_equal([[2, "2"], [4, "4"]], + e.lazy.select {|x| x[0] % 2 == 0}.force) + end + + def test_map + a = Step.new(1..3) + assert_equal(2, a.map {|x| x * 2}.first) + assert_equal(3, a.current) + assert_equal(2, a.lazy.map {|x| x * 2}.first) + 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) + assert_equal(3, a.current) + assert_equal(2, a.lazy.flat_map {|x| [x * 2]}.first) + assert_equal(1, a.current) + end + + def test_flat_map_nested + a = Step.new(1..3) + assert_equal([1, "a"], + a.flat_map {|x| ("a".."c").map {|y| [x, y]}}.first) + assert_equal(3, a.current) + assert_equal([1, "a"], + a.lazy.flat_map {|x| ("a".."c").lazy.map {|y| [x, y]}}.first) + assert_equal(1, a.current) + end + + def test_flat_map_to_ary + to_ary = Class.new { + def initialize(value) + @value = value + end + + def to_ary + [:to_ary, @value] + end + } + assert_equal([:to_ary, 1, :to_ary, 2, :to_ary, 3], + [1, 2, 3].flat_map {|x| to_ary.new(x)}) + assert_equal([:to_ary, 1, :to_ary, 2, :to_ary, 3], + [1, 2, 3].lazy.flat_map {|x| to_ary.new(x)}.force) + end + + def test_flat_map_non_array + assert_equal(["1", "2", "3"], [1, 2, 3].flat_map {|x| x.to_s}) + assert_equal(["1", "2", "3"], [1, 2, 3].lazy.flat_map {|x| x.to_s}.force) + end + + def test_flat_map_hash + assert_equal([{?a=>97}, {?b=>98}, {?c=>99}], [?a, ?b, ?c].flat_map {|x| {x=>x.ord}}) + assert_equal([{?a=>97}, {?b=>98}, {?c=>99}], [?a, ?b, ?c].lazy.flat_map {|x| {x=>x.ord}}.force) + end + + def test_reject + a = Step.new(1..6) + assert_equal(4, a.reject {|x| x < 4}.first) + assert_equal(6, a.current) + assert_equal(4, a.lazy.reject {|x| x < 4}.first) + assert_equal(4, a.current) + + a = Step.new(['word', nil, 1]) + assert_equal(nil, a.reject {|x| x}.first) + assert_equal(1, a.current) + assert_equal(nil, a.lazy.reject {|x| x}.first) + assert_equal(nil, a.current) + end + + def test_reject_multiple_values + e = Enumerator.new { |yielder| + for i in 1..5 + yielder.yield(i, i.to_s) + end + } + assert_equal([[2, "2"], [4, "4"]], + e.reject {|x| x[0] % 2 != 0}) + assert_equal([[2, "2"], [4, "4"]], + e.lazy.reject {|x| x[0] % 2 != 0}.force) + end + + def test_grep + a = Step.new('a'..'f') + assert_equal('c', a.grep(/c/).first) + assert_equal('f', a.current) + assert_equal('c', a.lazy.grep(/c/).first) + assert_equal('c', a.current) + assert_equal(%w[a e], a.grep(proc {|x| /[aeiou]/ =~ x})) + assert_equal(%w[a e], a.lazy.grep(proc {|x| /[aeiou]/ =~ x}).to_a) + end + + def test_grep_with_block + a = Step.new('a'..'f') + assert_equal('C', a.grep(/c/) {|i| i.upcase}.first) + assert_equal('C', a.lazy.grep(/c/) {|i| i.upcase}.first) + end + + def test_grep_multiple_values + e = Enumerator.new { |yielder| + 3.times { |i| + yielder.yield(i, i.to_s) + } + } + assert_equal([[2, "2"]], e.grep(proc {|x| x == [2, "2"]})) + assert_equal([[2, "2"]], e.lazy.grep(proc {|x| x == [2, "2"]}).force) + assert_equal(["22"], + 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) + assert_equal(3, a.current) + assert_equal([1, "a"], a.lazy.zip("a".."c").first) + assert_equal(1, a.current) + end + + def test_zip_short_arg + a = Step.new(1..5) + assert_equal([5, nil], a.zip("a".."c").last) + assert_equal([5, nil], a.lazy.zip("a".."c").force.last) + end + + def test_zip_without_arg + a = Step.new(1..3) + assert_equal([1], a.zip.first) + assert_equal(3, a.current) + assert_equal([1], a.lazy.zip.first) + assert_equal(1, a.current) + end + + def test_zip_bad_arg + a = Step.new(1..3) + assert_raise(TypeError){ a.lazy.zip(42) } + end + + def test_zip_with_block + # zip should be eager when a block is given + a = Step.new(1..3) + ary = [] + assert_equal(nil, a.lazy.zip("a".."c") {|x, y| ary << [x, y]}) + assert_equal(a.zip("a".."c"), ary) + assert_equal(3, a.current) + end + + def test_take + a = Step.new(1..10) + assert_equal(1, a.take(5).first) + assert_equal(5, a.current) + assert_equal(1, a.lazy.take(5).first) + assert_equal(1, a.current) + assert_equal((1..5).to_a, a.lazy.take(5).force) + assert_equal(5, a.current) + a = Step.new(1..10) + assert_equal([], a.lazy.take(0).force) + 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) + take5 = a.lazy.take(5) + assert_equal((1..5).to_a, take5.force, bug6428) + assert_equal((1..5).to_a, take5.force, bug6428) + end + + def test_take_nested + bug7696 = '[ruby-core:51470]' + a = Step.new(1..10) + take5 = a.lazy.take(5) + assert_equal([*(1..5)]*5, take5.flat_map{take5}.force, bug7696) + end + + def test_drop_while_nested + bug7696 = '[ruby-core:51470]' + a = Step.new(1..10) + drop5 = a.lazy.drop_while{|x| x < 6} + assert_equal([*(6..10)]*5, drop5.flat_map{drop5}.force, bug7696) + end + + def test_drop_nested + bug7696 = '[ruby-core:51470]' + a = Step.new(1..10) + drop5 = a.lazy.drop(5) + assert_equal([*(6..10)]*5, drop5.flat_map{drop5}.force, bug7696) + end + + def test_zip_nested + bug7696 = '[ruby-core:51470]' + enum = ('a'..'z').each + enum.next + zip = (1..3).lazy.zip(enum, enum) + assert_equal([[1, 'a', 'a'], [2, 'b', 'b'], [3, 'c', 'c']]*3, zip.flat_map{zip}.force, bug7696) + end + + def test_zip_lazy_on_args + zip = Step.new(1..2).lazy.zip(42..Float::INFINITY) + assert_equal [[1, 42], [2, 43]], zip.force + end + + def test_zip_efficient_on_array_args + ary = [42, :foo] + %i[to_enum enum_for lazy each].each do |forbid| + ary.define_singleton_method(forbid){ fail "#{forbid} was called"} + end + zip = Step.new(1..2).lazy.zip(ary) + assert_equal [[1, 42], [2, :foo]], zip.force + end + + def test_zip_nonsingle + bug8735 = '[ruby-core:56383] [Bug #8735]' + + obj = Object.new + def obj.each + yield + yield 1, 2 + end + + assert_equal(obj.to_enum.zip(obj.to_enum), obj.to_enum.lazy.zip(obj.to_enum).force, bug8735) + end + + def test_take_rewound + bug7696 = '[ruby-core:51470]' + e=(1..42).lazy.take(2) + assert_equal 1, e.next, bug7696 + assert_equal 2, e.next, bug7696 + e.rewind + assert_equal 1, e.next, bug7696 + assert_equal 2, e.next, bug7696 + end + + def test_take_while + a = Step.new(1..10) + assert_equal(1, a.take_while {|i| i < 5}.first) + assert_equal(5, a.current) + assert_equal(1, a.lazy.take_while {|i| i < 5}.first) + assert_equal(1, a.current) + assert_equal((1..4).to_a, a.lazy.take_while {|i| i < 5}.to_a) + end + + def test_drop + a = Step.new(1..10) + assert_equal(6, a.drop(5).first) + assert_equal(10, a.current) + assert_equal(6, a.lazy.drop(5).first) + assert_equal(6, a.current) + assert_equal((6..10).to_a, a.lazy.drop(5).to_a) + end + + def test_drop_while + a = Step.new(1..10) + assert_equal(5, a.drop_while {|i| i % 5 > 0}.first) + assert_equal(10, a.current) + assert_equal(5, a.lazy.drop_while {|i| i % 5 > 0}.first) + assert_equal(5, a.current) + assert_equal((5..10).to_a, a.lazy.drop_while {|i| i % 5 > 0}.to_a) + end + + def test_drop_and_take + assert_equal([4, 5], (1..Float::INFINITY).lazy.drop(3).take(2).to_a) + end + + def test_cycle + a = Step.new(1..3) + assert_equal("1", a.cycle(2).map(&:to_s).first) + assert_equal(3, a.current) + assert_equal("1", a.lazy.cycle(2).map(&:to_s).first) + assert_equal(1, a.current) + end + + def test_cycle_with_block + # cycle should be eager when a block is given + a = Step.new(1..3) + ary = [] + assert_equal(nil, a.lazy.cycle(2) {|i| ary << i}) + assert_equal(a.cycle(2).to_a, ary) + assert_equal(3, a.current) + end + + def test_cycle_chain + a = 1..3 + assert_equal([1,2,3,1,2,3,1,2,3,1], a.lazy.cycle.take(10).force) + assert_equal([2,2,2,2,2,2,2,2,2,2], a.lazy.cycle.select {|x| x == 2}.take(10).force) + assert_equal([2,2,2,2,2,2,2,2,2,2], a.lazy.select {|x| x == 2}.cycle.take(10).force) + end + + def test_force + assert_equal([1, 2, 3], (1..Float::INFINITY).lazy.take(3).force) + end + + def test_inspect + assert_equal("#<Enumerator::Lazy: 1..10>", (1..10).lazy.inspect) + assert_equal('#<Enumerator::Lazy: #<Enumerator: "foo":each_char>>', + "foo".each_char.lazy.inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>", + (1..10).lazy.map {}.inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:take(0)>", + (1..10).lazy.take(0).inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:take(3)>", + (1..10).lazy.take(3).inspect) + assert_equal('#<Enumerator::Lazy: #<Enumerator::Lazy: "a".."c">:grep(/b/)>', + ("a".."c").lazy.grep(/b/).inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle(3)>", + (1..10).lazy.cycle(3).inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle>", + (1..10).lazy.cycle.inspect) + assert_equal("#<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:cycle(3)>", + (1..10).lazy.cycle(3).inspect) + l = (1..10).lazy.map {}.collect {}.flat_map {}.collect_concat {}.select {}.find_all {}.reject {}.grep(1).zip(?a..?c).take(10).take_while {}.drop(3).drop_while {}.cycle(3) + assert_equal(<<EOS.chomp, l.inspect) +#<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..10>:map>:collect>:flat_map>:collect_concat>:select>:find_all>:reject>:grep(1)>:zip("a".."c")>:take(10)>:take_while>:drop(3)>:drop_while>:cycle(3)> +EOS + end + + def test_lazy_to_enum + lazy = [1, 2, 3].lazy + def lazy.foo(*args) + yield args + yield args + end + enum = lazy.to_enum(:foo, :hello, :world) + assert_equal Enumerator::Lazy, enum.class + assert_equal nil, enum.size + assert_equal [[:hello, :world], [:hello, :world]], enum.to_a + + assert_equal [1, 2, 3], lazy.to_enum.to_a + end + + def test_size + lazy = [1, 2, 3].lazy + assert_equal 3, lazy.size + assert_equal 42, Enumerator::Lazy.new([],->{42}){}.size + assert_equal 42, Enumerator::Lazy.new([],42){}.size + assert_equal 42, Enumerator::Lazy.new([],42){}.lazy.size + assert_equal 42, lazy.to_enum{ 42 }.size + + %i[map collect].each do |m| + assert_equal 3, lazy.send(m){}.size + end + assert_equal 3, lazy.zip([4]).size + %i[flat_map collect_concat select find_all reject take_while drop_while].each do |m| + assert_equal nil, lazy.send(m){}.size + end + assert_equal nil, lazy.grep(//).size + + assert_equal 2, lazy.take(2).size + assert_equal 3, lazy.take(4).size + assert_equal 4, loop.lazy.take(4).size + assert_equal nil, lazy.select{}.take(4).size + + assert_equal 1, lazy.drop(2).size + assert_equal 0, lazy.drop(4).size + assert_equal Float::INFINITY, loop.lazy.drop(4).size + assert_equal nil, lazy.select{}.drop(4).size + + assert_equal 0, lazy.cycle(0).size + assert_equal 6, lazy.cycle(2).size + assert_equal 3 << 80, 4.times.inject(lazy){|enum| enum.cycle(1 << 20)}.size + assert_equal Float::INFINITY, lazy.cycle.size + assert_equal Float::INFINITY, loop.lazy.cycle(4).size + 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 + bug7507 = '[ruby-core:50545]' + assert_ruby_status(["-e", "GC.stress = true", "-e", "(1..10).lazy.map{}.zip(){}"], "", bug7507) + assert_ruby_status(["-e", "GC.stress = true", "-e", "(1..10).lazy.map{}.zip().to_a"], "", bug7507) + end + + def test_require_block + %i[select reject drop_while take_while map flat_map].each do |method| + assert_raise(ArgumentError){ [].lazy.send(method) } + end + end + + def test_laziness_conservation + bug7507 = '[ruby-core:51510]' + { + slice_before: //, + slice_after: //, + with_index: nil, + cycle: nil, + each_with_object: 42, + each_slice: 42, + each_entry: nil, + each_cons: 42, + }.each do |method, arg| + 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 + le = (1..3).lazy + assert_warning("") {le.zip([4,5,6]).force} + assert_warning("") {le.zip(4..6).force} + assert_warning("") {le.take(1).force} + 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 71fcf49eff..ff40474bf7 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -1,3 +1,5 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' class TestRubyLiteral < Test::Unit::TestCase @@ -12,21 +14,22 @@ 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 def test_self @@ -53,15 +56,43 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal "3", "\x33" assert_equal "\n", "\n" bug2500 = '[ruby-core:27228]' + bug5262 = '[ruby-core:39222]' %w[c C- M-].each do |pre| ["u", %w[u{ }]].each do |open, close| - str = "\"\\#{pre}\\#{open}5555#{close}\"" - assert_raise(SyntaxError, "#{bug2500} eval(#{str})") {eval(str)} + ["?", ['"', '"']].each do |qopen, qclose| + str = "#{qopen}\\#{pre}\\#{open}5555#{close}#{qclose}" + assert_raise(SyntaxError, "#{bug2500} eval(#{str})") {eval(str)} + + str = "#{qopen}\\#{pre}\\#{open}\u201c#{close}#{qclose}" + assert_raise(SyntaxError, "#{bug5262} eval(#{str})") {eval(str)} + + str = "#{qopen}\\#{pre}\\#{open}\u201c#{close}#{qclose}".encode("euc-jp") + assert_raise(SyntaxError, "#{bug5262} eval(#{str})") {eval(str)} + + str = "#{qopen}\\#{pre}\\#{open}\u201c#{close}#{qclose}".encode("iso-8859-13") + assert_raise(SyntaxError, "#{bug5262} eval(#{str})") {eval(str)} + + str = "#{qopen}\\#{pre}\\#{open}\xe2\x7f#{close}#{qclose}".force_encoding("utf-8") + assert_raise(SyntaxError, "#{bug5262} eval(#{str})") {eval(str)} + end end end + bug6069 = '[ruby-dev:45278]' assert_equal "\x13", "\c\x33" assert_equal "\x13", "\C-\x33" assert_equal "\xB3", "\M-\x33" + assert_equal "\u201c", eval(%["\\\u{201c}"]), bug5262 + assert_equal "\u201c".encode("euc-jp"), eval(%["\\\u{201c}"].encode("euc-jp")), bug5262 + assert_equal "\u201c".encode("iso-8859-13"), eval(%["\\\u{201c}"].encode("iso-8859-13")), bug5262 + 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 "\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 @@ -75,16 +106,108 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal('FooBar', b, 'r3842') end + def test_dstring_encoding + bug11519 = '[ruby-core:70703] [Bug #11519]' + ['"foo#{}"', '"#{}foo"', '"#{}"'].each do |code| + a = eval("#-*- coding: utf-8 -*-\n#{code}") + assert_equal(Encoding::UTF_8, a.encoding, + proc{"#{bug11519}: #{code}.encoding"}) + end + end + def test_dsymbol 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 + + def test_frozen_string_in_array_literal + list = eval("# frozen-string-literal: true\n""['foo', 'bar']") + assert_equal 2, list.length + list.each { |str| assert_predicate str, :frozen? } + end + + if defined?(RubyVM::InstructionSequence.compile_option) and + RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal) + def test_debug_frozen_string + src = '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 + + def test_debug_frozen_string_in_array_literal + src = '["foo"]'; f = "test.rb"; n = 1 + opt = {frozen_string_literal: true, debug_frozen_string_literal: true} + ary = RubyVM::InstructionSequence.compile(src, f, f, n, opt).eval + assert_equal("foo", ary.first) + assert_predicate(ary.first, :frozen?) + assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { + ary.first << "x" + } unless ENV['RUBY_ISEQ_DUMP_DEBUG'] + end + end + def test_regexp assert_instance_of Regexp, // assert_match(//, 'a') @@ -110,6 +233,8 @@ class TestRubyLiteral < Test::Unit::TestCase def test_dregexp assert_instance_of Regexp, /re#{'ge'}xp/ assert_equal(/regexp/, /re#{'ge'}xp/) + bug3903 = '[ruby-core:32682]' + assert_raise(SyntaxError, bug3903) {eval('/[#{"\x80"}]/')} end def test_array @@ -158,6 +283,210 @@ class TestRubyLiteral < Test::Unit::TestCase assert_equal "literal", h["string"] end + def test_hash_literal_frozen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def frozen_hash_literal_arg + {0=>1,1=>4,2=>17} + end + + ObjectSpace.each_object(Hash) do |a| + if a.class == Hash and !a.default_proc and a.size == 3 && + a[0] == 1 && a[1] == 4 && a[2] == 17 + # should not be found. + raise + end + end + assert_not_include frozen_hash_literal_arg, 3 + end; + end + + def test_big_array_and_hash_literal + assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).map{'x'}.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] + assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("[#{(1..1_000_000).to_a.join(", ")}]").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] + assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("{#{(1..1_000_000).map{|n| "#{n} => x"}.join(', ')}}").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] + assert_normal_exit %q{GC.disable=true; x = nil; raise if eval("{#{(1..1_000_000).map{|n| "#{n} => #{n}"}.join(', ')}}").size != 1_000_000}, "", timeout: 300, child_env: %[--disable-gems] + end + + def test_big_hash_literal + bug7466 = '[ruby-dev:46658]' + h = { + 0xFE042 => 0xE5CD, + 0xFE043 => 0xE5CD, + 0xFE045 => 0xEA94, + 0xFE046 => 0xE4E3, + 0xFE047 => 0xE4E2, + 0xFE048 => 0xEA96, + 0xFE049 => 0x3013, + 0xFE04A => 0xEB36, + 0xFE04B => 0xEB37, + 0xFE04C => 0xEB38, + 0xFE04D => 0xEB49, + 0xFE04E => 0xEB82, + 0xFE04F => 0xE4D2, + 0xFE050 => 0xEB35, + 0xFE051 => 0xEAB9, + 0xFE052 => 0xEABA, + 0xFE053 => 0xE4D4, + 0xFE054 => 0xE4CD, + 0xFE055 => 0xEABB, + 0xFE056 => 0xEABC, + 0xFE057 => 0xEB32, + 0xFE058 => 0xEB33, + 0xFE059 => 0xEB34, + 0xFE05A => 0xEB39, + 0xFE05B => 0xEB5A, + 0xFE190 => 0xE5A4, + 0xFE191 => 0xE5A5, + 0xFE192 => 0xEAD0, + 0xFE193 => 0xEAD1, + 0xFE194 => 0xEB47, + 0xFE195 => 0xE509, + 0xFE196 => 0xEAA0, + 0xFE197 => 0xE50B, + 0xFE198 => 0xEAA1, + 0xFE199 => 0xEAA2, + 0xFE19A => 0x3013, + 0xFE19B => 0xE4FC, + 0xFE19C => 0xE4FA, + 0xFE19D => 0xE4FC, + 0xFE19E => 0xE4FA, + 0xFE19F => 0xE501, + 0xFE1A0 => 0x3013, + 0xFE1A1 => 0xE5DD, + 0xFE1A2 => 0xEADB, + 0xFE1A3 => 0xEAE9, + 0xFE1A4 => 0xEB13, + 0xFE1A5 => 0xEB14, + 0xFE1A6 => 0xEB15, + 0xFE1A7 => 0xEB16, + 0xFE1A8 => 0xEB17, + 0xFE1A9 => 0xEB18, + 0xFE1AA => 0xEB19, + 0xFE1AB => 0xEB1A, + 0xFE1AC => 0xEB44, + 0xFE1AD => 0xEB45, + 0xFE1AE => 0xE4CB, + 0xFE1AF => 0xE5BF, + 0xFE1B0 => 0xE50E, + 0xFE1B1 => 0xE4EC, + 0xFE1B2 => 0xE4EF, + 0xFE1B3 => 0xE4F8, + 0xFE1B4 => 0x3013, + 0xFE1B5 => 0x3013, + 0xFE1B6 => 0xEB1C, + 0xFE1B9 => 0xEB7E, + 0xFE1D3 => 0xEB22, + 0xFE7DC => 0xE4D8, + 0xFE1D4 => 0xEB23, + 0xFE1D5 => 0xEB24, + 0xFE1D6 => 0xEB25, + 0xFE1CC => 0xEB1F, + 0xFE1CD => 0xEB20, + 0xFE1CE => 0xE4D9, + 0xFE1CF => 0xE48F, + 0xFE1C5 => 0xE5C7, + 0xFE1C6 => 0xEAEC, + 0xFE1CB => 0xEB1E, + 0xFE1DA => 0xE4DD, + 0xFE1E1 => 0xEB57, + 0xFE1E2 => 0xEB58, + 0xFE1E3 => 0xE492, + 0xFE1C9 => 0xEB1D, + 0xFE1D9 => 0xE4D3, + 0xFE1DC => 0xE5D4, + 0xFE1BA => 0xE4E0, + 0xFE1BB => 0xEB76, + 0xFE1C8 => 0xE4E0, + 0xFE1DD => 0xE5DB, + 0xFE1BC => 0xE4DC, + 0xFE1D8 => 0xE4DF, + 0xFE1BD => 0xE49A, + 0xFE1C7 => 0xEB1B, + 0xFE1C2 => 0xE5C2, + 0xFE1C0 => 0xE5C0, + 0xFE1B8 => 0xE4DB, + 0xFE1C3 => 0xE470, + 0xFE1BE => 0xE4D8, + 0xFE1C4 => 0xE4D9, + 0xFE1B7 => 0xE4E1, + 0xFE1BF => 0xE4DE, + 0xFE1C1 => 0xE5C1, + 0xFE1CA => 0x3013, + 0xFE1D0 => 0xE4E1, + 0xFE1D1 => 0xEB21, + 0xFE1D2 => 0xE4D7, + 0xFE1D7 => 0xE4DA, + 0xFE1DB => 0xE4EE, + 0xFE1DE => 0xEB3F, + 0xFE1DF => 0xEB46, + 0xFE1E0 => 0xEB48, + 0xFE336 => 0xE4FB, + 0xFE320 => 0xE472, + 0xFE321 => 0xEB67, + 0xFE322 => 0xEACA, + 0xFE323 => 0xEAC0, + 0xFE324 => 0xE5AE, + 0xFE325 => 0xEACB, + 0xFE326 => 0xEAC9, + 0xFE327 => 0xE5C4, + 0xFE328 => 0xEAC1, + 0xFE329 => 0xE4E7, + 0xFE32A => 0xE4E7, + 0xFE32B => 0xEACD, + 0xFE32C => 0xEACF, + 0xFE32D => 0xEACE, + 0xFE32E => 0xEAC7, + 0xFE32F => 0xEAC8, + 0xFE330 => 0xE471, + 0xFE331 => "[Bug #7466]", + } + k = h.keys + assert_equal([129, 0xFE331], [k.size, k.last], bug7466) + + code = [ + "h = {", + (1..128).map {|i| "#{i} => 0,"}, + (129..140).map {|i| "#{i} => [],"}, + "}", + ].join + assert_separately([], <<-"end;") + GC.stress = true + #{code} + GC.stress = false + assert_equal(140, h.size) + 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) @@ -194,7 +523,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 @@ -222,15 +551,12 @@ class TestRubyLiteral < Test::Unit::TestCase } } bug2407 = '[ruby-dev:39798]' - head.each {|h| - if /^0/ =~ h - begin - eval("#{h}_") - rescue SyntaxError => e - assert_match(/numeric literal without digits\Z/, e.message, bug2407) - end + head.grep_v(/^0/) do |s| + head.grep(/^0/) do |h| + h = "#{s}#{h}_" + assert_syntax_error(h, /numeric literal without digits\Z/, "#{bug2407}: #{h.inspect}") end - } + end end def test_float @@ -260,6 +586,17 @@ class TestRubyLiteral < Test::Unit::TestCase } } } + assert_equal(100.0, 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100e100) end + def test_symbol_list + assert_equal([:foo, :bar], %i[foo bar]) + assert_equal([:"\"foo"], %i["foo]) + + x = 10 + assert_equal([:foo, :b10], %I[foo b#{x}]) + assert_equal([:"\"foo10"], %I["foo#{x}]) + + assert_ruby_status(["--disable-gems", "--dump=parsetree"], "%I[foo bar]") + end end diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index 07cda751d1..75daf61376 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -1,5 +1,6 @@ +# coding: US-ASCII +# frozen_string_literal: false require 'test/unit' -require 'stringio' class TestM17N < Test::Unit::TestCase def assert_encoding(encname, actual, message=nil) @@ -8,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 @@ -23,19 +24,8 @@ class TestM17N < Test::Unit::TestCase assert_equal(a(bytes), a(actual), message) end - def assert_warning(pat, mesg=nil) - begin - org_stderr = $stderr - $stderr = StringIO.new(warn = '') - yield - ensure - $stderr = org_stderr - end - assert_match(pat, warn, mesg) - end - def assert_regexp_generic_encoding(r) - assert(!r.fixed_encoding?) + assert_not_predicate(r, :fixed_encoding?) %w[ASCII-8BIT EUC-JP Windows-31J UTF-8].each {|ename| # "\xc2\xa1" is a valid sequence for ASCII-8BIT, EUC-JP, Windows-31J and UTF-8. assert_nothing_raised { r =~ "\xc2\xa1".force_encoding(ename) } @@ -43,7 +33,7 @@ class TestM17N < Test::Unit::TestCase end def assert_regexp_fixed_encoding(r) - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) %w[ASCII-8BIT EUC-JP Windows-31J UTF-8].each {|ename| enc = Encoding.find(ename) if enc == r.encoding @@ -117,7 +107,7 @@ class TestM17N < Test::Unit::TestCase elsif !s2.ascii_only? assert_equal(s2.encoding, t.encoding) else - assert([s1.encoding, s2.encoding].include?(t.encoding)) + assert_include([s1.encoding, s2.encoding], t.encoding) end end @@ -205,31 +195,137 @@ class TestM17N < Test::Unit::TestCase end def test_string_inspect_encoding - orig_int = Encoding.default_internal + EnvUtil.suppress_warning do + begin + orig_int = Encoding.default_internal + orig_ext = Encoding.default_external + Encoding.default_internal = nil + [Encoding::UTF_8, Encoding::EUC_JP, Encoding::Windows_31J, Encoding::GB18030]. + each do |e| + Encoding.default_external = e + str = "\x81\x30\x81\x30".force_encoding('GB18030') + assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect) + str = e("\xa1\x8f\xa1\xa1") + expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP") + assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect) + str = s("\x81@") + assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect) + str = "\u3042\u{10FFFD}" + assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect) + end + Encoding.default_external = Encoding::UTF_8 + [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE, + Encoding::UTF8_SOFTBANK].each do |e| + str = "abc".encode(e) + assert_equal('"abc"', str.inspect) + end + ensure + Encoding.default_internal = orig_int + Encoding.default_external = orig_ext + end + end + end + + STR_WITHOUT_BOM = "\u3042".freeze + STR_WITH_BOM = "\uFEFF\u3042".freeze + bug8940 = '[ruby-core:59757] [Bug #8940]' + bug9415 = '[ruby-dev:47895] [Bug #9415]' + %w/UTF-16 UTF-32/.each do |enc| + %w/BE LE/.each do |endian| + bom = "\uFEFF".encode("#{enc}#{endian}").force_encoding(enc) + + define_method("test_utf_16_32_inspect(#{enc}#{endian})") do + s = STR_WITHOUT_BOM.encode(enc + endian) + # When a UTF-16/32 string doesn't have a BOM, + # inspect as a dummy encoding string. + assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, + s.dup.force_encoding(enc).inspect) + assert_normal_exit("#{bom.b.dump}.force_encoding('#{enc}').inspect", bug8940) + end + + define_method("test_utf_16_32_codepoints(#{enc}#{endian})") do + assert_equal([0xFEFF], bom.codepoints, bug9415) + end + + define_method("test_utf_16_32_ord(#{enc}#{endian})") do + assert_equal(0xFEFF, bom.ord, bug9415) + end + + define_method("test_utf_16_32_inspect(#{enc}#{endian}-BOM)") do + s = STR_WITH_BOM.encode(enc + endian) + # When a UTF-16/32 string has a BOM, + # inspect as a particular encoding string. + assert_equal(s.inspect, + s.dup.force_encoding(enc).inspect) + end + 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 + orig_int = Encoding.default_internal + orig_ext = Encoding.default_external + Encoding.default_internal = nil + Encoding.default_external = Encoding::UTF_8 + 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_equal '[abc]', [o].inspect + end + ensure + Encoding.default_internal = orig_int + Encoding.default_external = orig_ext + end + end + end + + def test_object_inspect_external + orig_v, $VERBOSE = $VERBOSE, false + orig_int, Encoding.default_internal = Encoding.default_internal, nil orig_ext = Encoding.default_external - Encoding.default_internal = nil - [Encoding::UTF_8, Encoding::EUC_JP, Encoding::Windows_31J, Encoding::GB18030]. - each do |e| - Encoding.default_external = e - str = "\x81\x30\x81\x30".force_encoding('GB18030') - assert_equal(Encoding::GB18030 == e ? %{"#{str}"} : '"\x{81308130}"', str.inspect) - str = e("\xa1\x8f\xa1\xa1") - expected = "\"\\xA1\x8F\xA1\xA1\"".force_encoding("EUC-JP") - assert_equal(Encoding::EUC_JP == e ? expected : "\"\\xA1\\x{8FA1A1}\"", str.inspect) - str = s("\x81@") - assert_equal(Encoding::Windows_31J == e ? %{"#{str}"} : '"\x{8140}"', str.inspect) - str = "\u3042\u{10FFFD}" - assert_equal(Encoding::UTF_8 == e ? %{"#{str}"} : '"\u3042\u{10FFFD}"', str.inspect) + o = Object.new + + Encoding.default_external = Encoding::UTF_16BE + def o.inspect + "abc" end - Encoding.default_external = Encoding::UTF_8 - [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE, - Encoding::UTF8_SOFTBANK].each do |e| - str = "abc".encode(e) - assert_equal('"abc"', str.inspect) + assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect } + + def o.inspect + "abc".encode(Encoding.default_external) + end + assert_equal '[abc]', [o].inspect + + Encoding.default_external = Encoding::US_ASCII + def o.inspect + "\u3042" end + 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 + $VERBOSE = orig_v end def test_str_dump @@ -269,7 +365,10 @@ class TestM17N < Test::Unit::TestCase "\u3042".encode("UTF-16LE"), "\u3042".encode("UTF-16BE"), ].each do |str| - assert_equal(str, eval(str.dump), "[ruby-dev:33142]") + dump = str.dump + assert_equal(str, eval(dump), "[ruby-dev:33142]") + assert_equal(str, dump.undump) + assert_equal(str, eval("# frozen-string-literal: true\n#{dump}"), '[Bug #14687]') end end @@ -295,15 +394,15 @@ class TestM17N < Test::Unit::TestCase ].each {|pat2| s = [pat2.gsub(/ /, "")].pack("B*").force_encoding("utf-8") if pat2 <= bits_0x10ffff - assert(s.valid_encoding?, "#{pat2}") + assert_predicate(s, :valid_encoding?, "#{pat2}") else - assert(!s.valid_encoding?, "#{pat2}") + assert_not_predicate(s, :valid_encoding?, "#{pat2}") end } if / / =~ pat0 pat3 = pat1.gsub(/X/, "0") s = [pat3.gsub(/ /, "")].pack("B*").force_encoding("utf-8") - assert(!s.valid_encoding?, "#{pat3}") + assert_not_predicate(s, :valid_encoding?, "#{pat3}") end } } @@ -323,12 +422,12 @@ class TestM17N < Test::Unit::TestCase pat0.gsub(/x/, '1'), ].each {|pat1| s = [pat1.gsub(/ /, "")].pack("B*").force_encoding("utf-8") - assert(!s.valid_encoding?, "#{pat1}") + assert_not_predicate(s, :valid_encoding?, "#{pat1}") } } pats.values_at(0,3).each {|pat| s = [pat.gsub(/ /, "")].pack("B*").force_encoding("utf-8") - assert(s.valid_encoding?, "#{pat}") + assert_predicate(s, :valid_encoding?, "#{pat}") } end @@ -376,7 +475,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) } @@ -385,13 +484,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")) } @@ -404,6 +503,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| @@ -527,30 +629,38 @@ class TestM17N < Test::Unit::TestCase assert_nothing_raised { r = Regexp.new(s) } - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) r = eval('/\p{Hiragana}/'.force_encoding("euc-jp")) - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) r = /\p{Hiragana}/e - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) + assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) + + r = /\p{AsciI}/e + assert_predicate(r, :fixed_encoding?) + assert_match(r, "a".force_encoding("euc-jp")) + + r = /\p{hiraganA}/e + assert_predicate(r, :fixed_encoding?) assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) r = eval('/\u{3042}\p{Hiragana}/'.force_encoding("euc-jp")) - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) assert_equal(Encoding::UTF_8, r.encoding) r = eval('/\p{Hiragana}\u{3042}/'.force_encoding("euc-jp")) - assert(r.fixed_encoding?) + assert_predicate(r, :fixed_encoding?) assert_equal(Encoding::UTF_8, r.encoding) end def test_regexp_embed_preprocess r1 = /\xa4\xa2/e r2 = /#{r1}/ - assert(r2.source.include?(r1.source)) + assert_include(r2.source, r1.source) end def test_begin_end_offset @@ -595,10 +705,10 @@ class TestM17N < Test::Unit::TestCase def test_union_0 r = Regexp.union assert_regexp_generic_ascii(r) - assert(r !~ a("")) - assert(r !~ e("")) - assert(r !~ s("")) - assert(r !~ u("")) + assert_not_match(r, a("")) + assert_not_match(r, e("")) + assert_not_match(r, s("")) + assert_not_match(r, u("")) end def test_union_1_asciionly_string @@ -625,7 +735,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)) @@ -668,7 +778,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) @@ -779,15 +889,15 @@ class TestM17N < Test::Unit::TestCase end def test_sprintf_p - enc = "".inspect.encoding - asc = Encoding::US_ASCII Encoding.list.each do |e| format = "%p".force_encoding(e) ['', 'a', "\xC2\xA1", "\x00"].each do |s| s.force_encoding(e) - assert_strenc(s.inspect, e.ascii_compatible? && enc == asc ? e : enc, format % s) + enc = (''.force_encoding(e) + s.inspect).encoding + assert_strenc(s.inspect, enc, format % s) end s = "\xC2\xA1".force_encoding(e) + enc = ('' + s.inspect).encoding assert_strenc('%10s' % s.inspect, enc, "%10p" % s) end end @@ -821,9 +931,9 @@ class TestM17N < Test::Unit::TestCase end def test_str_lt - assert(a("a") < a("\xa1")) - assert(a("a") < s("\xa1")) - assert(s("a") < a("\xa1")) + assert_operator(a("a"), :<, a("\xa1")) + assert_operator(a("a"), :<, s("\xa1")) + assert_operator(s("a"), :<, a("\xa1")) end def test_str_multiply @@ -898,9 +1008,22 @@ class TestM17N < Test::Unit::TestCase assert_equal("\u{439}", "a\u{439}bcdefghijklmnop"[1, 1][0, 1], bug2379) end + def test_str_aref_force_encoding + bug5836 = '[ruby-core:41896]' + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + s = "abc".force_encoding(enc) + assert_equal("", s[3, 1], bug5836) + end + end + def test_aset s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") assert_raise(Encoding::CompatibilityError){s["\xb0\xa3"] = "foo"} + + a = ua("a") + a[/a/] = u("") + assert_equal Encoding::US_ASCII, a.encoding end def test_str_center @@ -926,6 +1049,7 @@ class TestM17N < Test::Unit::TestCase assert_equal("X\u3042\u3044X", "A\u3042\u3044\u3046".tr("^\u3042\u3044", "X")) assert_equal("\u3042\u3046" * 100, ("\u3042\u3044" * 100).tr("\u3044", "\u3046")) + assert_equal("Y", "\u3042".tr("^X", "Y")) end def test_tr_s @@ -939,6 +1063,11 @@ class TestM17N < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError){s.count(a("\xa3\xb0"))} end + def test_count_sjis_trailing_byte + bug10078 = '[ruby-dev:48442] [Bug #10078]' + assert_equal(0, s("\x98\x61").count("a"), bug10078) + end + def test_delete assert_equal(1, e("\xa1\xa2").delete("z").length) s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") @@ -953,6 +1082,10 @@ class TestM17N < Test::Unit::TestCase assert_equal(false, e("\xa1\xa2\xa3\xa4").include?(e("\xa3"))) s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") assert_equal(false, s.include?(e("\xb0\xa3"))) + bug11488 = '[ruby-core:70592] [Bug #11488]' + each_encoding("abcdef", "def") do |str, substr| + assert_equal(true, str.include?(substr), bug11488) + end end def test_index @@ -962,6 +1095,10 @@ class TestM17N < Test::Unit::TestCase assert_nil(e("\xa1\xa2\xa3\xa4").rindex(e("\xa3"))) s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") assert_raise(Encoding::CompatibilityError){s.rindex(a("\xb1\xa3"))} + bug11488 = '[ruby-core:70592] [Bug #11488]' + each_encoding("abcdef", "def") do |str, substr| + assert_equal(3, str.index(substr), bug11488) + end end def test_next @@ -982,15 +1119,15 @@ class TestM17N < Test::Unit::TestCase s = "\x80".force_encoding("ASCII-8BIT") r = Regexp.new("\x80".force_encoding("ASCII-8BIT")) s2 = s.sub(r, "") - assert(s2.empty?) - assert(s2.ascii_only?) + assert_empty(s2) + assert_predicate(s2, :ascii_only?) end def test_sub3 repl = "\x81".force_encoding("sjis") assert_equal(false, repl.valid_encoding?) s = "a@".sub(/a/, repl) - assert(s.valid_encoding?) + assert_predicate(s, :valid_encoding?) end def test_insert @@ -1004,7 +1141,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) } @@ -1012,7 +1149,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 @@ -1029,7 +1166,12 @@ class TestM17N < Test::Unit::TestCase end def test_reverse - assert_equal(u("\xf0jihgfedcba"), u("abcdefghij\xf0").reverse) + bug11387 = '[ruby-dev:49189] [Bug #11387]' + s1 = u("abcdefghij\xf0") + s2 = s1.reverse + assert_not_predicate(s1, :valid_encoding?, bug11387) + assert_equal(u("\xf0jihgfedcba"), s2) + assert_not_predicate(s2, :valid_encoding?, bug11387) end def test_reverse_bang @@ -1054,7 +1196,6 @@ class TestM17N < Test::Unit::TestCase assert_equal(false, s.ascii_only?, "[ruby-core:14566] reported by Sam Ruby") s = "abc".force_encoding(Encoding::ASCII_8BIT) - t = s.gsub(/b/, "\xa1\xa1".force_encoding("euc-jp")) assert_equal(Encoding::ASCII_8BIT, s.encoding) assert_raise(Encoding::CompatibilityError) { @@ -1066,14 +1207,20 @@ class TestM17N < Test::Unit::TestCase s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") assert_equal(e("\xa3\xb0z\xa3\xb2\xa3\xb3\xa3\xb4"), s.gsub(/\xa3\xb1/e, "z")) - assert_equal(Encoding::EUC_JP, (a("").gsub(//) { e("") }.encoding)) - assert_equal(Encoding::EUC_JP, (a("a").gsub(/a/) { e("") }.encoding)) + assert_equal(Encoding::ASCII_8BIT, (a("").gsub(//) { e("") }.encoding)) + assert_equal(Encoding::ASCII_8BIT, (a("a").gsub(/a/) { e("") }.encoding)) end def test_end_with s1 = s("\x81\x40") s2 = "@" assert_equal(false, s1.end_with?(s2), "#{encdump s1}.end_with?(#{encdump s2})") + each_encoding("\u3042\u3044", "\u3044") do |_s1, _s2| + assert_equal(true, _s1.end_with?(_s2), "#{encdump _s1}.end_with?(#{encdump _s2})") + end + each_encoding("\u3042a\u3044", "a\u3044") do |_s1, _s2| + assert_equal(true, _s1.end_with?(_s2), "#{encdump _s1}.end_with?(#{encdump _s2})") + end end def test_each_line @@ -1093,6 +1240,13 @@ class TestM17N < Test::Unit::TestCase assert_equal(a, s.each_char.to_a, "[ruby-dev:33211] #{encdump s}.each_char.to_a") end + def test_str_concat + assert_equal(1, "".concat(0xA2).size) + assert_equal(Encoding::ASCII_8BIT, "".force_encoding("US-ASCII").concat(0xA2).encoding) + assert_equal("A\x84\x31\xA4\x39".force_encoding("GB18030"), + "A".force_encoding("GB18030") << 0x8431A439) + end + def test_regexp_match assert_equal([0,0], //.match("\xa1\xa1".force_encoding("euc-jp"),-1).offset(0)) assert_equal(0, // =~ :a) @@ -1102,6 +1256,13 @@ class TestM17N < Test::Unit::TestCase assert_equal(e("\xa1\xa2\xa1\xa3").split(//), [e("\xa1\xa2"), e("\xa1\xa3")], '[ruby-dev:32452]') + + each_encoding("abc,def", ",", "abc", "def") do |str, sep, *expected| + assert_equal(expected, str.split(sep, -1)) + end + each_encoding("abc\0def", "\0", "abc", "def") do |str, sep, *expected| + assert_equal(expected, str.split(sep, -1)) + end end def test_nonascii_method_name @@ -1127,7 +1288,7 @@ class TestM17N < Test::Unit::TestCase def test_symbol_op ops = %w" - .. ... + - +(binary) -(binary) * / % ** +@ -@ | ^ & ! <=> > >= < <= == + .. ... + - * / % ** +@ -@ | ^ & ! <=> > >= < <= == === != =~ !~ ~ ! [] []= << >> :: ` " ops.each do |op| @@ -1139,6 +1300,19 @@ class TestM17N < Test::Unit::TestCase 0.upto(255) {|b| assert_equal([b].pack("C"), b.chr) } + assert_equal("\x84\x31\xA4\x39".force_encoding("GB18030"), 0x8431A439.chr("GB18030")) + e = assert_raise(RangeError) { + 2206368128.chr(Encoding::UTF_8) + } + assert_not_match(/-\d+ out of char range/, e.message) + + assert_raise(RangeError){ 0x80.chr("US-ASCII") } + assert_raise(RangeError){ 0x80.chr("SHIFT_JIS") } + assert_raise(RangeError){ 0xE0.chr("SHIFT_JIS") } + assert_raise(RangeError){ 0x100.chr("SHIFT_JIS") } + assert_raise(RangeError){ 0xA0.chr("EUC-JP") } + assert_raise(RangeError){ 0x100.chr("EUC-JP") } + assert_raise(RangeError){ 0xA1A0.chr("EUC-JP") } end def test_marshal @@ -1150,8 +1324,8 @@ class TestM17N < Test::Unit::TestCase def test_env locale_encoding = Encoding.find("locale") ENV.each {|k, v| - assert_equal(locale_encoding, k.encoding) - assert_equal(locale_encoding, v.encoding) + assert_equal(locale_encoding, k.encoding, k) + assert_equal(locale_encoding, v.encoding, v) } end @@ -1293,6 +1467,24 @@ class TestM17N < Test::Unit::TestCase s = "\xa1\xa1\x8f".force_encoding("euc-jp") assert_equal(false, s.valid_encoding?) assert_equal(true, s.reverse.valid_encoding?) + + bug4018 = '[ruby-core:33027]' + s = "\xa1\xa1".force_encoding("euc-jp") + assert_equal(true, s.valid_encoding?) + s << "\x8f".force_encoding("euc-jp") + assert_equal(false, s.valid_encoding?, bug4018) + s = "aa".force_encoding("utf-16be") + assert_equal(true, s.valid_encoding?) + s << "\xff".force_encoding("utf-16be") + assert_equal(false, s.valid_encoding?, bug4018) + + bug6190 = '[ruby-core:43557]' + s = "\xe9" + s = s.encode("utf-8", "utf-8") + assert_equal(false, s.valid_encoding?, bug6190) + s = "\xe9" + s.encode!("utf-8", "utf-8") + assert_equal(false, s.valid_encoding?, bug6190) end def test_getbyte @@ -1312,6 +1504,42 @@ 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_setbyte_range + s = u("\xE3\x81\x82\xE3\x81\x84") + assert_nothing_raised { s.setbyte(0, -1) } + assert_nothing_raised { s.setbyte(0, 0x00) } + assert_nothing_raised { s.setbyte(0, 0x7F) } + assert_nothing_raised { s.setbyte(0, 0x80) } + assert_nothing_raised { s.setbyte(0, 0xff) } + assert_nothing_raised { s.setbyte(0, 0x100) } + assert_nothing_raised { s.setbyte(0, 0x4f7574206f6620636861722072616e6765) } end def test_compatible @@ -1325,15 +1553,162 @@ class TestM17N < Test::Unit::TestCase end def test_force_encoding - assert(("".center(1, "\x80".force_encoding("utf-8")); true), - "moved from btest/knownbug, [ruby-dev:33807]") + assert_equal(u("\x80"), "".center(1, u("\x80")), + "moved from btest/knownbug, [ruby-dev:33807]") a = "".force_encoding("ascii-8bit") << 0xC3 << 0xB6 assert_equal(1, a.force_encoding("utf-8").size, '[ruby-core:22437]') b = "".force_encoding("ascii-8bit") << 0xC3.chr << 0xB6.chr assert_equal(1, b.force_encoding("utf-8").size, '[ruby-core:22437]') + + assert_raise(TypeError){ ''.force_encoding(nil) } end def test_combchar_codepoint assert_equal([0x30BB, 0x309A], "\u30BB\u309A".codepoints.to_a) + assert_equal([0x30BB, 0x309A], "\u30BB\u309A".codepoints.to_a) + end + + def each_encoding(*strings) + Encoding.list.each do |enc| + next if enc.dummy? + strs = strings.map {|s| s.encode(enc)} rescue next + yield(*strs) + end + end + + def test_str_b + s = "\u3042" + assert_equal(a("\xE3\x81\x82"), s.b) + assert_equal(Encoding::ASCII_8BIT, s.b.encoding) + s.taint + assert_predicate(s.b, :tainted?) + s = "abc".b + assert_predicate(s.b, :ascii_only?) + end + + def test_scrub_valid_string + str = "foo" + assert_equal(str, str.scrub) + assert_not_same(str, str.scrub) + assert_predicate(str.dup.taint.scrub, :tainted?) + str = "\u3042\u3044" + assert_equal(str, str.scrub) + assert_not_same(str, str.scrub) + assert_predicate(str.dup.taint.scrub, :tainted?) + str.force_encoding(Encoding::ISO_2022_JP) # dummy encoding + assert_equal(str, str.scrub) + assert_not_same(str, str.scrub) + assert_nothing_raised(ArgumentError) {str.scrub(nil)} + assert_predicate(str.dup.taint.scrub, :tainted?) + end + + def test_scrub_replace_default + assert_equal("\uFFFD\uFFFD\uFFFD", u("\x80\x80\x80").scrub) + assert_equal("\uFFFDA", u("\xF4\x80\x80A").scrub) + assert_predicate(u("\x80\x80\x80").taint.scrub, :tainted?) + assert_predicate(u("\xF4\x80\x80A").taint.scrub, :tainted?) + + # examples in Unicode 6.1.0 D93b + assert_equal("\x41\uFFFD\uFFFD\x41\uFFFD\x41", + u("\x41\xC0\xAF\x41\xF4\x80\x80\x41").scrub) + assert_equal("\x41\uFFFD\uFFFD\uFFFD\x41", + u("\x41\xE0\x9F\x80\x41").scrub) + assert_equal("\u0061\uFFFD\uFFFD\uFFFD\u0062\uFFFD\u0063\uFFFD\uFFFD\u0064", + u("\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64").scrub) + assert_equal("abcdefghijklmnopqrstuvwxyz\u0061\uFFFD\uFFFD\uFFFD\u0062\uFFFD\u0063\uFFFD\uFFFD\u0064", + u("abcdefghijklmnopqrstuvwxyz\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64").scrub) + end + + def test_scrub_replace_argument + assert_equal("foo", u("foo").scrub("\u3013")) + assert_predicate(u("foo").taint.scrub("\u3013"), :tainted?) + assert_not_predicate(u("foo").scrub("\u3013".taint), :tainted?) + assert_equal("\u3042\u3044", u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013")) + assert_predicate(u("\xE3\x81\x82\xE3\x81\x84").taint.scrub("\u3013"), :tainted?) + assert_not_predicate(u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013".taint), :tainted?) + assert_equal("\u3042\u3013", u("\xE3\x81\x82\xE3\x81").scrub("\u3013")) + assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub("\u3013"), :tainted?) + assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub("\u3013".taint), :tainted?) + assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub(e("\xA4\xA2")) } + assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub(1) } + assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub(u("\x81")) } + assert_equal(e("\xA4\xA2\xA2\xAE"), e("\xA4\xA2\xA4").scrub(e("\xA2\xAE"))) + end + + def test_scrub_replace_block + assert_equal("\u3042<e381>", u("\xE3\x81\x82\xE3\x81").scrub{|x|'<'+x.unpack('H*')[0]+'>'}) + assert_predicate(u("\xE3\x81\x82\xE3\x81").taint.scrub{|x|'<'+x.unpack('H*')[0]+'>'}, :tainted?) + assert_predicate(u("\xE3\x81\x82\xE3\x81").scrub{|x|('<'+x.unpack('H*')[0]+'>').taint}, :tainted?) + assert_raise(Encoding::CompatibilityError){ u("\xE3\x81\x82\xE3\x81").scrub{e("\xA4\xA2")} } + assert_raise(TypeError){ u("\xE3\x81\x82\xE3\x81").scrub{1} } + assert_raise(ArgumentError){ u("\xE3\x81\x82\xE3\x81\x82\xE3\x81").scrub{u("\x81")} } + assert_equal(e("\xA4\xA2\xA2\xAE"), e("\xA4\xA2\xA4").scrub{e("\xA2\xAE")}) + + 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 + assert_equal("\uFFFD\u3042".encode("UTF-16BE"), + "\xD8\x00\x30\x42".force_encoding(Encoding::UTF_16BE). + scrub) + assert_equal("\uFFFD\u3042".encode("UTF-16LE"), + "\x00\xD8\x42\x30".force_encoding(Encoding::UTF_16LE). + scrub) + assert_equal("\uFFFD".encode("UTF-32BE"), + "\xff".force_encoding(Encoding::UTF_32BE). + scrub) + 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 + str = "\u3042\u3044" + assert_same(str, str.scrub!) + str.force_encoding(Encoding::ISO_2022_JP) # dummy encoding + assert_same(str, str.scrub!) + assert_nothing_raised(ArgumentError) {str.scrub!(nil)} + + str = u("\x80\x80\x80") + str.scrub! + assert_same(str, str.scrub!) + 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) end end diff --git a/test/ruby/test_m17n_comb.rb b/test/ruby/test_m17n_comb.rb index cf80377172..99c162a92f 100644 --- a/test/ruby/test_m17n_comb.rb +++ b/test/ruby/test_m17n_comb.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: false require 'test/unit' -require 'stringio' +require 'etc' require_relative 'allpairs' class TestM17NComb < Test::Unit::TestCase @@ -8,10 +9,11 @@ class TestM17NComb < Test::Unit::TestCase end module AESU - def a(str) str.dup.force_encoding("ASCII-8BIT") end - def e(str) str.dup.force_encoding("EUC-JP") end - def s(str) str.dup.force_encoding("Shift_JIS") end - def u(str) str.dup.force_encoding("UTF-8") end + def a(str) str.dup.force_encoding(Encoding::US_ASCII) end + def b(str) str.b end + def e(str) str.dup.force_encoding(Encoding::EUC_JP) end + def s(str) str.dup.force_encoding(Encoding::SJIS) end + def u(str) str.dup.force_encoding(Encoding::UTF_8) end end include AESU extend AESU @@ -20,72 +22,16 @@ class TestM17NComb < Test::Unit::TestCase assert_instance_of(String, actual, message) enc = Encoding.find(enc) if String === enc assert_equal(enc, actual.encoding, message) - assert_equal(a(bytes), a(actual), message) - end - - def assert_warning(pat, mesg=nil) - begin - org_stderr = $stderr - $stderr = StringIO.new(warn = '') - yield - ensure - $stderr = org_stderr - end - assert_match(pat, warn, mesg) - end - - def assert_regexp_generic_encoding(r) - assert(!r.fixed_encoding?) - %w[ASCII-8BIT EUC-JP Shift_JIS UTF-8].each {|ename| - # "\xc2\xa1" is a valid sequence for ASCII-8BIT, EUC-JP, Shift_JIS and UTF-8. - assert_nothing_raised { r =~ "\xc2\xa1".force_encoding(ename) } - } - end - - def assert_regexp_fixed_encoding(r) - assert(r.fixed_encoding?) - %w[ASCII-8BIT EUC-JP Shift_JIS UTF-8].each {|ename| - enc = Encoding.find(ename) - if enc == r.encoding - assert_nothing_raised { r =~ "\xc2\xa1".force_encoding(enc) } - else - assert_raise(ArgumentError) { r =~ "\xc2\xa1".force_encoding(enc) } - end - } - end - - def assert_regexp_generic_ascii(r) - assert_encoding("ASCII-8BIT", r.encoding) - assert_regexp_generic_encoding(r) - end - - def assert_regexp_fixed_ascii8bit(r) - assert_encoding("ASCII-8BIT", r.encoding) - assert_regexp_fixed_encoding(r) - end - - def assert_regexp_fixed_eucjp(r) - assert_encoding("EUC-JP", r.encoding) - assert_regexp_fixed_encoding(r) - end - - def assert_regexp_fixed_sjis(r) - assert_encoding("Shift_JIS", r.encoding) - assert_regexp_fixed_encoding(r) - end - - def assert_regexp_fixed_utf8(r) - assert_encoding("UTF-8", r.encoding) - assert_regexp_fixed_encoding(r) + assert_equal(b(bytes), b(actual), message) end STRINGS = [ - a(""), e(""), s(""), u(""), - a("a"), e("a"), s("a"), u("a"), - a("."), e("."), s("."), u("."), + b(""), e(""), s(""), u(""), + b("a"), e("a"), s("a"), u("a"), + b("."), e("."), s("."), u("."), # single character - a("\x80"), a("\xff"), + b("\x80"), b("\xff"), e("\xa1\xa1"), e("\xfe\xfe"), e("\x8e\xa1"), e("\x8e\xfe"), e("\x8f\xa1\xa1"), e("\x8f\xfe\xfe"), @@ -94,7 +40,7 @@ class TestM17NComb < Test::Unit::TestCase u("\xc2\x80"), u("\xf4\x8f\xbf\xbf"), # same byte sequence - a("\xc2\xa1"), e("\xc2\xa1"), s("\xc2\xa1"), u("\xc2\xa1"), + b("\xc2\xa1"), e("\xc2\xa1"), s("\xc2\xa1"), u("\xc2\xa1"), s("\x81A"), # mutibyte character which contains "A" s("\x81a"), # mutibyte character which contains "a" @@ -106,11 +52,13 @@ class TestM17NComb < Test::Unit::TestCase # for transitivity test u("\xe0\xa0\xa1"), e("\xe0\xa0\xa1"), s("\xe0\xa0\xa1"), # [ruby-dev:32693] - e("\xa1\xa1"), a("\xa1\xa1"), s("\xa1\xa1"), # [ruby-dev:36484] + e("\xa1\xa1"), b("\xa1\xa1"), s("\xa1\xa1"), # [ruby-dev:36484] + ] - #"aa".force_encoding("utf-16be"), - #"aaaa".force_encoding("utf-32be"), - #"aaa".force_encoding("utf-32be"), + WSTRINGS = [ + "aa".force_encoding("utf-16be"), + "aaaa".force_encoding("utf-32be"), + "aaa".force_encoding("utf-32be"), ] def combination(*args, &b) @@ -141,7 +89,7 @@ class TestM17NComb < Test::Unit::TestCase r end - def enccall(recv, meth, *args, &block) + def encdumpcall(recv, meth, *args, &block) desc = '' if String === recv desc << encdump(recv) @@ -164,12 +112,18 @@ class TestM17NComb < Test::Unit::TestCase if block desc << ' {}' end + desc + end + + def assert_enccall(recv, meth, *args, &block) + desc = encdumpcall(recv, meth, *args, &block) result = nil assert_nothing_raised(desc) { result = recv.send(meth, *args, &block) } result end + alias enccall assert_enccall def assert_str_enc_propagation(t, s1, s2) if !s1.ascii_only? @@ -177,7 +131,7 @@ class TestM17NComb < Test::Unit::TestCase elsif !s2.ascii_only? assert_equal(s2.encoding, t.encoding) else - assert([s1.encoding, s2.encoding].include?(t.encoding)) + assert_include([s1.encoding, s2.encoding], t.encoding) end end @@ -254,7 +208,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_new STRINGS.each {|s| t = String.new(s) - assert_strenc(a(s), s.encoding, t) + assert_strenc(b(s), s.encoding, t) } end @@ -264,8 +218,8 @@ class TestM17NComb < Test::Unit::TestCase assert_raise(Encoding::CompatibilityError) { s1 + s2 } else t = enccall(s1, :+, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert_equal(a(s1) + a(s2), a(t)) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_equal(b(s1) + b(s2), b(t)) assert_str_enc_propagation(t, s1, s2) end } @@ -275,34 +229,34 @@ class TestM17NComb < Test::Unit::TestCase STRINGS.each {|s| [0,1,2].each {|n| t = s * n - assert(t.valid_encoding?) if s.valid_encoding? - assert_strenc(a(s) * n, s.encoding, t) + assert_predicate(t, :valid_encoding?) if s.valid_encoding? + assert_strenc(b(s) * n, s.encoding, t) } } end def test_sprintf_s STRINGS.each {|s| - assert_strenc(a(s), s.encoding, "%s".force_encoding(s.encoding) % s) + assert_strenc(b(s), s.encoding, "%s".force_encoding(s.encoding) % s) if !s.empty? # xxx - t = enccall(a("%s"), :%, s) - assert_strenc(a(s), s.encoding, t) + t = enccall(b("%s"), :%, s) + assert_strenc(b(s), (b('')+s).encoding, t) end } end def test_str_eq_reflexive STRINGS.each {|s| - assert(s == s, "#{encdump s} == #{encdump s}") + assert_equal(s, s, "#{encdump s} == #{encdump s}") } end def test_str_eq_symmetric combination(STRINGS, STRINGS) {|s1, s2| if s1 == s2 - assert(s2 == s1, "#{encdump s2} == #{encdump s1}") + assert_equal(s2, s1, "#{encdump s2} == #{encdump s1}") else - assert(!(s2 == s1), "!(#{encdump s2} == #{encdump s1})") + assert_not_equal(s2, s1, "!(#{encdump s2} == #{encdump s1})") end } end @@ -310,7 +264,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_eq_transitive combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| if s1 == s2 && s2 == s3 - assert(s1 == s3, "transitive: #{encdump s1} == #{encdump s2} == #{encdump s3}") + assert_equal(s1, s3, "transitive: #{encdump s1} == #{encdump s2} == #{encdump s3}") end } end @@ -318,18 +272,18 @@ class TestM17NComb < Test::Unit::TestCase def test_str_eq combination(STRINGS, STRINGS) {|s1, s2| desc_eq = "#{encdump s1} == #{encdump s2}" - if a(s1) == a(s2) and + if b(s1) == b(s2) and (s1.ascii_only? && s2.ascii_only? or s1.encoding == s2.encoding) then - assert(s1 == s2, desc_eq) - assert(!(s1 != s2)) + assert_operator(s1, :==, s2, desc_eq) + assert_not_operator(s1, :!=, s2) assert_equal(0, s1 <=> s2) - assert(s1.eql?(s2), desc_eq) + assert_operator(s1, :eql?, s2, desc_eq) else - assert(!(s1 == s2), "!(#{desc_eq})") - assert(s1 != s2) + assert_not_operator(s1, :==, s2, "!(#{desc_eq})") + assert_operator(s1, :!=, s2) assert_not_equal(0, s1 <=> s2) - assert(!s1.eql?(s2)) + assert_not_operator(s1, :eql?, s2) end } end @@ -339,8 +293,8 @@ class TestM17NComb < Test::Unit::TestCase s = s1.dup if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding s << s2 - assert(s.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert_equal(a(s), a(s1) + a(s2)) + assert_predicate(s, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_equal(b(s), b(s1) + b(s2)) assert_str_enc_propagation(s, s1, s2) else assert_raise(Encoding::CompatibilityError) { s << s2 } @@ -353,7 +307,7 @@ class TestM17NComb < Test::Unit::TestCase t = ''.force_encoding(s.encoding) 0.upto(s.length-1) {|i| u = s[i] - assert(u.valid_encoding?) if s.valid_encoding? + assert_predicate(u, :valid_encoding?) if s.valid_encoding? t << u } assert_equal(t, s) @@ -365,7 +319,7 @@ class TestM17NComb < Test::Unit::TestCase t = ''.force_encoding(s.encoding) 0.upto(s.length-1) {|i| u = s[i,1] - assert(u.valid_encoding?) if s.valid_encoding? + assert_predicate(u, :valid_encoding?) if s.valid_encoding? t << u } assert_equal(t, s) @@ -375,7 +329,7 @@ class TestM17NComb < Test::Unit::TestCase t = ''.force_encoding(s.encoding) 0.step(s.length-1, 2) {|i| u = s[i,2] - assert(u.valid_encoding?) if s.valid_encoding? + assert_predicate(u, :valid_encoding?) if s.valid_encoding? t << u } assert_equal(t, s) @@ -387,9 +341,9 @@ class TestM17NComb < Test::Unit::TestCase if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding t = enccall(s1, :[], s2) if t != nil - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? assert_equal(s2, t) - assert_match(/#{Regexp.escape(a(s2))}/, a(s1)) + assert_match(/#{Regexp.escape(b(s2))}/, b(s1)) if s1.valid_encoding? assert_match(/#{Regexp.escape(s2)}/, s1) end @@ -415,7 +369,7 @@ class TestM17NComb < Test::Unit::TestCase assert_nil(t, desc) next end - assert(t.valid_encoding?) if s.valid_encoding? + assert_predicate(t, :valid_encoding?) if s.valid_encoding? if last < 0 last += s.length end @@ -446,7 +400,7 @@ class TestM17NComb < Test::Unit::TestCase if last < 0 last += s.length end - assert(t.valid_encoding?) if s.valid_encoding? + assert_predicate(t, :valid_encoding?) if s.valid_encoding? t2 = '' first.upto(last-1) {|i| c = s[i] @@ -465,8 +419,8 @@ class TestM17NComb < Test::Unit::TestCase assert_raise(IndexError) { t[i] = s2 } else t[i] = s2 - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s2))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s2)]) if s1.valid_encoding? && s2.valid_encoding? if i == s1.length && s2.empty? assert_nil(t[i]) @@ -493,9 +447,9 @@ class TestM17NComb < Test::Unit::TestCase if i < -s1.length || s1.length < i assert_raise(IndexError) { t[i,len] = s2 } else - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? t[i,len] = s2 - assert(a(t).index(a(s2))) + assert_send([b(t), :index, b(s2)]) if s1.valid_encoding? && s2.valid_encoding? if i == s1.length && s2.empty? assert_nil(t[i]) @@ -539,7 +493,7 @@ class TestM17NComb < Test::Unit::TestCase if !t[s2] else enccall(t, :[]=, s2, s3) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? && s3.valid_encoding? + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? && s3.valid_encoding? end end } @@ -553,8 +507,8 @@ class TestM17NComb < Test::Unit::TestCase assert_raise(RangeError) { t[first..last] = s2 } else enccall(t, :[]=, first..last, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s2))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s2)]) if s1.valid_encoding? && s2.valid_encoding? if first < 0 assert_equal(s2, t[s1.length+first, s2.length]) @@ -580,8 +534,8 @@ class TestM17NComb < Test::Unit::TestCase assert_raise(RangeError) { t[first...last] = s2 } else enccall(t, :[]=, first...last, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s2))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s2)]) if s1.valid_encoding? && s2.valid_encoding? if first < 0 assert_equal(s2, t[s1.length+first, s2.length]) @@ -616,16 +570,16 @@ class TestM17NComb < Test::Unit::TestCase begin t1 = s.capitalize rescue ArgumentError - assert(!s.valid_encoding?) + assert_not_predicate(s, :valid_encoding?) next end - assert(t1.valid_encoding?) if s.valid_encoding? - assert(t1.casecmp(s)) + assert_predicate(t1, :valid_encoding?) if s.valid_encoding? + assert_operator(t1, :casecmp, s) t2 = s.dup t2.capitalize! assert_equal(t1, t2) r = s.downcase - r = enccall(r, :sub, /\A[a-z]/) {|ch| a(ch).upcase } + r = enccall(r, :sub, /\A[a-z]/) {|ch| b(ch).upcase } assert_equal(r, t1) } end @@ -633,20 +587,16 @@ class TestM17NComb < Test::Unit::TestCase def test_str_casecmp combination(STRINGS, STRINGS) {|s1, s2| #puts "#{encdump(s1)}.casecmp(#{encdump(s2)})" - begin - r = s1.casecmp(s2) - rescue ArgumentError - assert(!s1.valid_encoding? || !s2.valid_encoding?) - next - end - #assert_equal(s1.upcase <=> s2.upcase, r) + next unless s1.valid_encoding? && s2.valid_encoding? && Encoding.compatible?(s1, s2) + r = s1.casecmp(s2) + assert_equal(s1.upcase <=> s2.upcase, r) } end def test_str_center combination(STRINGS, [0,1,2,3,10]) {|s1, width| t = s1.center(width) - assert(a(t).index(a(s1))) + assert_send([b(t), :index, b(s1)]) } combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| if s2.empty? @@ -658,8 +608,8 @@ class TestM17NComb < Test::Unit::TestCase next end t = enccall(s1, :center, width, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s1))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s1)]) assert_str_enc_propagation(t, s1, s2) if (t != s1) } end @@ -667,7 +617,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_ljust combination(STRINGS, [0,1,2,3,10]) {|s1, width| t = s1.ljust(width) - assert(a(t).index(a(s1))) + assert_send([b(t), :index, b(s1)]) } combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| if s2.empty? @@ -679,8 +629,8 @@ class TestM17NComb < Test::Unit::TestCase next end t = enccall(s1, :ljust, width, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s1))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s1)]) assert_str_enc_propagation(t, s1, s2) if (t != s1) } end @@ -688,7 +638,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_rjust combination(STRINGS, [0,1,2,3,10]) {|s1, width| t = s1.rjust(width) - assert(a(t).index(a(s1))) + assert_send([b(t), :index, b(s1)]) } combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| if s2.empty? @@ -700,8 +650,8 @@ class TestM17NComb < Test::Unit::TestCase next end t = enccall(s1, :rjust, width, s2) - assert(t.valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? - assert(a(t).index(a(s1))) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_send([b(t), :index, b(s1)]) assert_str_enc_propagation(t, s1, s2) if (t != s1) } end @@ -710,12 +660,14 @@ 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 t = enccall(s1, :chomp, s2) - assert(t.valid_encoding?, "#{encdump(s1)}.chomp(#{encdump(s2)})") if s1.valid_encoding? && s2.valid_encoding? + assert_predicate(t, :valid_encoding?, "#{encdump(s1)}.chomp(#{encdump(s2)})") if s1.valid_encoding? && s2.valid_encoding? assert_equal(s1.encoding, t.encoding) t2 = s1.dup t2.chomp!(s2) @@ -723,14 +675,25 @@ 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 desc = "#{encdump s}.chop" t = nil assert_nothing_raised(desc) { t = s.chop } - assert(t.valid_encoding?) if s.valid_encoding? - assert(a(s).index(a(t))) + assert_predicate(t, :valid_encoding?) if s.valid_encoding? + assert_send([b(s), :index, b(t)]) t2 = s.dup t2.chop! assert_equal(t, t2) @@ -741,8 +704,8 @@ class TestM17NComb < Test::Unit::TestCase STRINGS.each {|s| t = s.dup t.clear - assert(t.valid_encoding?) - assert(t.empty?) + assert_predicate(t, :valid_encoding?) + assert_empty(t) } end @@ -751,7 +714,7 @@ class TestM17NComb < Test::Unit::TestCase t = s.clone assert_equal(s, t) assert_equal(s.encoding, t.encoding) - assert_equal(a(s), a(t)) + assert_equal(b(s), b(t)) } end @@ -760,38 +723,64 @@ class TestM17NComb < Test::Unit::TestCase t = s.dup assert_equal(s, t) assert_equal(s.encoding, t.encoding) - assert_equal(a(s), a(t)) + assert_equal(b(s), b(t)) } end def test_str_count combination(STRINGS, STRINGS) {|s1, s2| + desc = proc {encdumpcall(s1, :count, s2)} if !s1.valid_encoding? || !s2.valid_encoding? - assert_raise(ArgumentError, Encoding::CompatibilityError) { s1.count(s2) } + assert_raise(ArgumentError, Encoding::CompatibilityError, desc) { s1.count(s2) } next end if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding - assert_raise(Encoding::CompatibilityError) { s1.count(s2) } + assert_raise(Encoding::CompatibilityError, desc) { s1.count(s2) } next end n = enccall(s1, :count, s2) - n0 = a(s1).count(a(s2)) + n0 = b(s1).count(b(s2)) assert_operator(n, :<=, n0) } 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 a(salt).length < 2 - assert_raise(ArgumentError) { str.crypt(salt) } - next - end - t = str.crypt(salt) - assert_equal(a(str).crypt(a(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? @@ -807,7 +796,7 @@ class TestM17NComb < Test::Unit::TestCase next end t = enccall(s1, :delete, s2) - assert(t.valid_encoding?) + assert_predicate(t, :valid_encoding?) assert_equal(t.encoding, s1.encoding) assert_operator(t.length, :<=, s1.length) t2 = s1.dup @@ -819,13 +808,13 @@ 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 - assert(t.valid_encoding?) + assert_predicate(t, :valid_encoding?) assert_equal(t.encoding, s.encoding) - assert(t.casecmp(s)) + assert_operator(t, :casecmp, s) t2 = s.dup t2.downcase! assert_equal(t, t2) @@ -835,26 +824,21 @@ class TestM17NComb < Test::Unit::TestCase def test_str_dump STRINGS.each {|s| t = s.dump - assert(t.valid_encoding?) - assert(t.ascii_only?) + assert_predicate(t, :valid_encoding?) + assert_predicate(t, :ascii_only?) u = eval(t) - assert_equal(a(s), a(u)) + assert_equal(b(s), b(u)) } end def test_str_each_line combination(STRINGS, STRINGS) {|s1, s2| - if !s1.valid_encoding? || !s2.valid_encoding? - assert_raise(ArgumentError, Encoding::CompatibilityError) { s1.each_line(s2) {} } - next - end if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding assert_raise(Encoding::CompatibilityError) { s1.each_line(s2) {} } next end lines = [] enccall(s1, :each_line, s2) {|line| - assert(line.valid_encoding?) assert_equal(s1.encoding, line.encoding) lines << line } @@ -871,7 +855,7 @@ class TestM17NComb < Test::Unit::TestCase s.each_byte {|b| bytes << b } - a(s).split(//).each_with_index {|ch, i| + b(s).split(//).each_with_index {|ch, i| assert_equal(ch.ord, bytes[i]) } } @@ -880,9 +864,9 @@ class TestM17NComb < Test::Unit::TestCase def test_str_empty? STRINGS.each {|s| if s.length == 0 - assert(s.empty?) + assert_empty(s) else - assert(!s.empty?) + assert_not_empty(s) end } end @@ -890,7 +874,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_hex STRINGS.each {|s| t = s.hex - t2 = a(s)[/\A[0-9a-fA-Fx]*/].hex + t2 = b(s)[/\A[0-9a-fA-Fx]*/].hex assert_equal(t2, t) } end @@ -905,12 +889,12 @@ class TestM17NComb < Test::Unit::TestCase end t = enccall(s1, :include?, s2) if t - assert(a(s1).include?(a(s2))) - assert(s1.index(s2)) - assert(s1.rindex(s2)) + assert_include(b(s1), b(s2)) + assert_send([s1, :index, s2]) + assert_send([s1, :rindex, s2]) else - assert(!s1.index(s2)) - assert(!s1.rindex(s2), "!#{encdump(s1)}.rindex(#{encdump(s2)})") + assert_not_send([s1, :index, s2]) + assert_not_send([s1, :rindex, s2], "!#{encdump(s1)}.rindex(#{encdump(s2)})") end if s2.empty? assert_equal(true, t) @@ -986,7 +970,7 @@ class TestM17NComb < Test::Unit::TestCase end if t #puts "#{encdump s1}.rindex(#{encdump s2}, #{pos}) => #{t}" - assert(a(s1).index(a(s2))) + assert_send([b(s1), :index, b(s2)]) pos2 = pos pos2 += s1.length if pos < 0 re = /\A(.{0,#{pos2}})#{Regexp.escape(s2)}/m @@ -1031,14 +1015,14 @@ class TestM17NComb < Test::Unit::TestCase t1.insert(nth, s2) slen = s2.length assert_equal(t1[nth-slen+1,slen], s2, "t=#{encdump s1}; t.insert(#{nth},#{encdump s2}); t") - rescue Encoding::CompatibilityError, IndexError => e + rescue Encoding::CompatibilityError, IndexError end } end def test_str_intern STRINGS.each {|s| - if /\0/ =~ a(s) + if /\0/ =~ b(s) assert_raise(ArgumentError) { s.intern } elsif s.valid_encoding? sym = s.intern @@ -1059,7 +1043,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_oct STRINGS.each {|s| t = s.oct - t2 = a(s)[/\A[0-9a-fA-FxX]*/].oct + t2 = b(s)[/\A[0-9a-fA-FxX]*/].oct assert_equal(t2, t) } end @@ -1087,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_raise(ArgumentError, /invalid byte sequence/) { s1.scan(s2) } + 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 @@ -1144,8 +1129,8 @@ class TestM17NComb < Test::Unit::TestCase "#{encdump s}.slice!#{encdumpargs args}.encoding") end if [s, *args].all? {|o| !(String === o) || o.valid_encoding? } - assert(r.valid_encoding?) - assert(t.valid_encoding?) + assert_predicate(r, :valid_encoding?) + assert_predicate(t, :valid_encoding?) assert_equal(s.length, r.length + t.length) end } @@ -1167,13 +1152,13 @@ class TestM17NComb < Test::Unit::TestCase end t = enccall(s1, :split, s2) t.each {|r| - assert(a(s1).include?(a(r))) + assert_include(b(s1), b(r)) assert_equal(s1.encoding, r.encoding) } - assert(a(s1).include?(t.map {|u| a(u) }.join(a(s2)))) + assert_include(b(s1), t.map {|u| b(u) }.join(b(s2))) if s1.valid_encoding? && s2.valid_encoding? t.each {|r| - assert(r.valid_encoding?) + assert_predicate(r, :valid_encoding?) } end } @@ -1224,7 +1209,7 @@ class TestM17NComb < Test::Unit::TestCase def test_str_sum STRINGS.each {|s| - assert_equal(a(s).sum, s.sum) + assert_equal(b(s).sum, s.sum) } end @@ -1235,8 +1220,8 @@ class TestM17NComb < Test::Unit::TestCase next end t1 = s.swapcase - assert(t1.valid_encoding?) if s.valid_encoding? - assert(t1.casecmp(s)) + assert_predicate(t1, :valid_encoding?) if s.valid_encoding? + assert_operator(t1, :casecmp, s) t2 = s.dup t2.swapcase! assert_equal(t1, t2) @@ -1297,6 +1282,20 @@ class TestM17NComb < Test::Unit::TestCase } end + def test_tr_sjis + expected = "\x83}\x83~\x83\x80\x83\x81\x83\x82".force_encoding(Encoding::SJIS) + source = "\xCF\xD0\xD1\xD2\xD3".force_encoding(Encoding::SJIS) + from = "\xCF-\xD3".force_encoding(Encoding::SJIS) + to = "\x83}-\x83\x82".force_encoding(Encoding::SJIS) + assert_equal(expected, source.tr(from, to)) + + expected = "\x84}\x84~\x84\x80\x84\x81\x84\x82".force_encoding(Encoding::SJIS) + source = "\x84M\x84N\x84O\x84P\x84Q".force_encoding(Encoding::SJIS) + from = "\x84@-\x84`".force_encoding(Encoding::SJIS) + to = "\x84p-\x84\x91".force_encoding(Encoding::SJIS) + assert_equal(expected, source.tr(from, to)) + end + def test_tr_s combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| desc = "#{encdump s1}.tr_s(#{encdump s2}, #{encdump s3})" @@ -1335,8 +1334,8 @@ class TestM17NComb < Test::Unit::TestCase next end t1 = s.upcase - assert(t1.valid_encoding?) - assert(t1.casecmp(s)) + assert_predicate(t1, :valid_encoding?) + assert_operator(t1, :casecmp, s) t2 = s.dup t2.upcase! assert_equal(t1, t2) @@ -1358,11 +1357,24 @@ class TestM17NComb < Test::Unit::TestCase #puts encdump(s) t = s.succ if s.valid_encoding? - assert(t.valid_encoding?, "#{encdump s}.succ.valid_encoding?") + assert_predicate(t, :valid_encoding?, "#{encdump s}.succ.valid_encoding?") end s = t } } + + Encoding.list.each do |enc| + next if enc.dummy? + {"A"=>"B", "A1"=>"A2", "A9"=>"B0", "9"=>"10", "Z"=>"AA"}.each do |orig, expected| + s = orig.encode(enc) + assert_strenc(expected.encode(enc), enc, s.succ, proc {"#{orig.dump}.encode(#{enc}).succ"}) + end + end + end + + def test_str_succ2 + assert_equal(a("\x01\x00"), a("\x7f").succ) + assert_equal(b("\x01\x00"), b("\xff").succ) end def test_str_hash @@ -1564,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 @@ -1624,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 d78183192c..f6d84d181a 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' require_relative 'marshaltestlib' @@ -40,6 +41,11 @@ class TestMarshal < Test::Unit::TestCase obj = (x.to_f + y.to_f / z.to_f) * Math.exp(w.to_f / (x.to_f + y.to_f / z.to_f)) assert_equal obj, Marshal.load(Marshal.dump(obj)) } + + bug3659 = '[ruby-dev:41936]' + [1.0, 10.0, 100.0, 110.0].each {|x| + assert_equal(x, Marshal.load(Marshal.dump(x)), bug3659) + } end StrClone = String.clone @@ -57,8 +63,8 @@ class TestMarshal < Test::Unit::TestCase def test_struct_invalid_members TestMarshal.const_set :StructInvalidMembers, Struct.new(:a) - Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo") assert_raise(TypeError, "[ruby-dev:31759]") { + Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo") TestMarshal::StructInvalidMembers.members } end @@ -79,10 +85,9 @@ class TestMarshal < Test::Unit::TestCase def test_too_long_string data = Marshal.dump(C.new("a".force_encoding("ascii-8bit"))) data[-2, 1] = "\003\377\377\377" - e = assert_raise(ArgumentError, "[ruby-dev:32054]") { + assert_raise_with_message(ArgumentError, "marshal data too short", "[ruby-dev:32054]") { Marshal.load(data) } - assert_equal("marshal data too short", e.message) end @@ -98,17 +103,19 @@ class TestMarshal < Test::Unit::TestCase def test_pipe o1 = C.new("a" * 10000) - r, w = IO.pipe - t = Thread.new { Marshal.load(r) } - Marshal.dump(o1, w) - o2 = t.value - assert_equal(o1.str, o2.str) + 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 - r, w = IO.pipe - t = Thread.new { Marshal.load(r) } - Marshal.dump(o1, w, 2) - o2 = t.value - assert_equal(o1.str, o2.str) + 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_raise(TypeError) { Marshal.dump("foo", Object.new) } assert_raise(TypeError) { Marshal.load(Object.new) } @@ -182,83 +189,58 @@ class TestMarshal < Test::Unit::TestCase end end - def test_taint_and_untrust + def test_taint x = Object.new x.taint - x.untrust s = Marshal.dump(x) assert_equal(true, s.tainted?) - assert_equal(true, s.untrusted?) y = Marshal.load(s) assert_equal(true, y.tainted?) - assert_equal(true, y.untrusted?) end - def test_taint_and_untrust_each_object + def test_taint_each_object x = Object.new obj = [[x]] # clean object causes crean stream assert_equal(false, obj.tainted?) - assert_equal(false, obj.untrusted?) assert_equal(false, obj.first.tainted?) - assert_equal(false, obj.first.untrusted?) assert_equal(false, obj.first.first.tainted?) - assert_equal(false, obj.first.first.untrusted?) s = Marshal.dump(obj) assert_equal(false, s.tainted?) - assert_equal(false, s.untrusted?) - # tainted/untrusted object causes tainted/untrusted stream + # tainted object causes tainted stream x.taint - x.untrust assert_equal(false, obj.tainted?) - assert_equal(false, obj.untrusted?) assert_equal(false, obj.first.tainted?) - assert_equal(false, obj.first.untrusted?) assert_equal(true, obj.first.first.tainted?) - assert_equal(true, obj.first.first.untrusted?) t = Marshal.dump(obj) assert_equal(true, t.tainted?) - assert_equal(true, t.untrusted?) # clean stream causes clean objects assert_equal(false, s.tainted?) - assert_equal(false, s.untrusted?) y = Marshal.load(s) assert_equal(false, y.tainted?) - assert_equal(false, y.untrusted?) assert_equal(false, y.first.tainted?) - assert_equal(false, y.first.untrusted?) assert_equal(false, y.first.first.tainted?) - assert_equal(false, y.first.first.untrusted?) - # tainted/untrusted stream causes tainted/untrusted objects + # tainted stream causes tainted objects assert_equal(true, t.tainted?) - assert_equal(true, t.untrusted?) y = Marshal.load(t) assert_equal(true, y.tainted?) - assert_equal(true, y.untrusted?) assert_equal(true, y.first.tainted?) - assert_equal(true, y.first.untrusted?) assert_equal(true, y.first.first.tainted?) - assert_equal(true, y.first.first.untrusted?) # same tests by different senario s.taint - s.untrust assert_equal(true, s.tainted?) - assert_equal(true, s.untrusted?) y = Marshal.load(s) assert_equal(true, y.tainted?) - assert_equal(true, y.untrusted?) assert_equal(true, y.first.tainted?) - assert_equal(true, y.first.untrusted?) assert_equal(true, y.first.first.tainted?) - assert_equal(true, y.first.first.untrusted?) end - def test_symbol + def test_symbol2 [:ruby, :"\u{7d05}\u{7389}"].each do |sym| assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]') end @@ -267,6 +249,21 @@ 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" + + "I" ":\x0bKernel" + + ("\x06" + + ("I" ":\x07@a" + + ("\x06" ":\x07@b" "e;\x0""o:\x0bObject""\x0")) + + "0")) + assert_equal(:Kernel, sym, bug10991) + end + ClassUTF8 = eval("class R\u{e9}sum\u{e9}; self; end") iso_8859_1 = Encoding::ISO_8859_1 @@ -316,7 +313,7 @@ class TestMarshal < Test::Unit::TestCase end end - def test_regexp + def test_regexp2 assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000")) assert_equal(/u/, Marshal.load("\004\b/\a\\u\000")) assert_equal(/u/, Marshal.load("\004\bI/\a\\u\000\006:\016@encoding\"\vEUC-JP")) @@ -328,10 +325,11 @@ class TestMarshal < Test::Unit::TestCase assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109) assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do - re = Tempfile.open("marshal_regexp") do |f| + re = Tempfile.create("marshal_regexp") do |f| f.binmode.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII") - f.close - Marshal.load(f.open.binmode) + f.rewind + re2 = Marshal.load(f) + re2 end assert_equal(//, re) end @@ -426,7 +424,7 @@ class TestMarshal < Test::Unit::TestCase m = Marshal.dump(o) } o2 = Marshal.load(m) - assert_equal(STDIN, o.stdin) + assert_equal(STDIN, o2.stdin) end def test_marshal_string_encoding @@ -449,5 +447,380 @@ class TestMarshal < Test::Unit::TestCase o2 = Marshal.load(m) assert_equal(o1, o2) end - + + def test_marshal_symbol_ascii8bit + bug6209 = '[ruby-core:43762]' + o1 = "\xff".force_encoding("ASCII-8BIT").intern + m = Marshal.dump(o1) + o2 = nil + assert_nothing_raised(EncodingError, bug6209) {o2 = Marshal.load(m)} + assert_equal(o1, o2, bug6209) + end + + class PrivateClass + def initialize(foo) + @foo = foo + end + attr_reader :foo + end + private_constant :PrivateClass + + def test_marshal_private_class + o1 = PrivateClass.new("test") + o2 = Marshal.load(Marshal.dump(o1)) + assert_equal(o1.class, o2.class) + assert_equal(o1.foo, o2.foo) + end + + def test_marshal_complex + assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x05")} + assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x06i\x00")} + assert_equal(Complex(1, 2), Marshal.load("\x04\bU:\fComplex[\ai\x06i\a")) + assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\bi\x00i\x00i\x00")} + end + + def test_marshal_rational + assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x05")} + assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x06i\x00")} + assert_equal(Rational(1, 2), Marshal.load("\x04\bU:\rRational[\ai\x06i\a")) + assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\bi\x00i\x00i\x00")} + end + + def test_marshal_flonum_reference + bug7348 = '[ruby-core:49323]' + e = [] + ary = [ [2.0, e], [e] ] + assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug7348) + end + + class TestClass + end + + module TestModule + end + + def test_marshal_load_should_not_taint_classes + bug7325 = '[ruby-core:49198]' + for c in [TestClass, TestModule] + assert_not_predicate(c, :tainted?) + c2 = Marshal.load(Marshal.dump(c).taint) + assert_same(c, c2) + assert_not_predicate(c, :tainted?, bug7325) + end + end + + class Bug7627 < Struct.new(:bar) + attr_accessor :foo + + def marshal_dump; 'dump'; end # fake dump data + def marshal_load(*); end # do nothing + end + + def test_marshal_dump_struct_ivar + bug7627 = '[ruby-core:51163]' + obj = Bug7627.new + obj.foo = '[Bug #7627]' + + dump = Marshal.dump(obj) + loaded = Marshal.load(dump) + + assert_equal(obj, loaded, bug7627) + assert_nil(loaded.foo, bug7627) + end + + class LoadData + attr_reader :data + def initialize(data) + @data = data + end + alias marshal_dump data + alias marshal_load initialize + end + + class Bug8276 < LoadData + def initialize(*) + super + freeze + end + alias marshal_load initialize + end + + class FrozenData < LoadData + def marshal_load(data) + super + data.instance_variables.each do |iv| + instance_variable_set(iv, data.instance_variable_get(iv)) + end + freeze + end + end + + def test_marshal_dump_excess_encoding + bug8276 = '[ruby-core:54334] [Bug #8276]' + t = Bug8276.new(bug8276) + s = Marshal.dump(t) + assert_nothing_raised(RuntimeError, bug8276) {s = Marshal.load(s)} + assert_equal(t.data, s.data, bug8276) + end + + def test_marshal_dump_ivar + s = "data with ivar" + s.instance_variable_set(:@t, 42) + t = Bug8276.new(s) + s = Marshal.dump(t) + assert_raise(FrozenError) {Marshal.load(s)} + end + + def test_marshal_load_ivar + s = "data with ivar" + s.instance_variable_set(:@t, 42) + hook = ->(v) { + if LoadData === v + assert_send([v, :instance_variable_defined?, :@t], v.class.name) + assert_equal(42, v.instance_variable_get(:@t), v.class.name) + end + v + } + [LoadData, FrozenData].each do |klass| + t = klass.new(s) + d = Marshal.dump(t) + v = assert_nothing_raised(RuntimeError) {break Marshal.load(d, hook)} + assert_send([v, :instance_variable_defined?, :@t], klass.name) + assert_equal(42, v.instance_variable_get(:@t), klass.name) + end + end + + def test_class_ivar + assert_raise(TypeError) {Marshal.load("\x04\x08Ic\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")} + assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")} + assert_not_operator(TestClass, :instance_variable_defined?, :@bug) + end + + def test_module_ivar + assert_raise(TypeError) {Marshal.load("\x04\x08Im\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")} + assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")} + assert_not_operator(TestModule, :instance_variable_defined?, :@bug) + end + + class TestForRespondToFalse + def respond_to?(a) + false + end + end + + def test_marshal_respond_to_arity + assert_nothing_raised(ArgumentError, '[Bug #7722]') do + Marshal.dump(TestForRespondToFalse.new) + 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] + assert_equal(['X', 'X'], Marshal.load(Marshal.dump(obj), ->(v) { v == str ? v.upcase : v })) + end + + def test_marshal_load_extended_class_crash + 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 + crash = "\x04\bI/\x05\x00\x06:\x06E{\x06@\x05T" + + opt = %w[--disable=gems] + assert_separately(opt, <<-RUBY) + assert_raise_with_message(ArgumentError, /bad link/) do + Marshal.load(#{crash.dump}) + 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 + if self.foo.baz + self.foo.remove_instance_variable(:@baz) + else + self.foo.baz = :problem + end + {foo: self.foo} + end + + def marshal_load(data) + self.foo = data[:foo] + end + end + end + + def test_marshal_dump_adding_instance_variable + obj = Bug15968.new + assert_raise_with_message(RuntimeError, /instance variable added/) do + Marshal.dump(obj) + end + end + + def test_marshal_dump_removing_instance_variable + obj = Bug15968.new + obj.baz = :Bug15968 + assert_raise_with_message(RuntimeError, /instance variable removed/) do + Marshal.dump(obj) + end + end end diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb index 64b72ce5c0..5cc12bcfeb 100644 --- a/test/ruby/test_math.rb +++ b/test/ruby/test_math.rb @@ -1,27 +1,43 @@ +# 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(!a.finite?, *rest) + assert_predicate(a, :infinite?, *rest) end def assert_nan(a, *rest) rest = ["not nan: #{a.inspect}"] if rest.empty? - assert(a.nan?, *rest) + 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 - assert_raise(Math::DomainError) { Math.atan2(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) } + 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)) + + 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)) @@ -33,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 @@ -46,9 +63,9 @@ class TestMath < Test::Unit::TestCase def test_tan check(0.0, Math.tan(0 * Math::PI / 4)) check(1.0, Math.tan(1 * Math::PI / 4)) - assert(Math.tan(2 * Math::PI / 4).abs > 1024) + assert_operator(Math.tan(2 * Math::PI / 4).abs, :>, 1024) check(0.0, Math.tan(4 * Math::PI / 4)) - assert(Math.tan(6 * Math::PI / 4).abs > 1024) + assert_operator(Math.tan(6 * Math::PI / 4).abs, :>, 1024) end def test_acos @@ -94,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 @@ -133,18 +152,24 @@ class TestMath < Test::Unit::TestCase check(0, Math.log(1, 10)) check(1, Math.log(10, 10)) check(2, Math.log(100, 10)) - assert_equal(1.0/0, Math.log(1.0/0)) + check(Math.log(2.0 ** 64), Math.log(1 << 64)) + 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 check(0, Math.log2(1)) check(1, Math.log2(2)) check(2, Math.log2(4)) - assert_equal(1.0/0, Math.log2(1.0/0)) + check(Math.log2(2.0 ** 64), Math.log2(1 << 64)) + 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) } @@ -154,7 +179,9 @@ class TestMath < Test::Unit::TestCase check(0, Math.log10(1)) check(1, Math.log10(10)) check(2, Math.log10(100)) - assert_equal(1.0/0, Math.log10(1.0/0)) + check(Math.log10(2.0 ** 64), Math.log10(1 << 64)) + 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) } @@ -164,22 +191,26 @@ 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)) } + assert_operator(Math.cbrt(1.0 - Float::EPSILON), :<=, 1.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 @@ -216,6 +247,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| @@ -225,54 +258,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 d135577208..3bf25928c9 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1,5 +1,6 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestMethod < Test::Unit::TestCase def setup @@ -21,7 +22,14 @@ class TestMethod < Test::Unit::TestCase def mo5(a, *b, c) end def mo6(a, *b, c, &d) end def mo7(a, b = nil, *c, d, &e) end - def ma1((a), &b) end + def ma1((a), &b) nil && a end + def mk1(**) end + def mk2(**o) nil && o end + def mk3(a, **o) nil && o end + def mk4(a = nil, **o) nil && o end + def mk5(a, b = nil, **o) nil && o end + def mk6(a, b = nil, c, **o) nil && o end + def mk7(a, b = nil, *c, d, **o) nil && o end class Base def foo() :base end @@ -31,12 +39,16 @@ class TestMethod < Test::Unit::TestCase end class T def initialize; end + def initialize_copy(*) super end + def initialize_clone(*) super end + def initialize_dup(*) super end + def respond_to_missing?(*) super end def normal_method; end end module M def func; end module_function :func - def meth; end + def meth; :meth end end def mv1() end @@ -63,6 +75,13 @@ class TestMethod < Test::Unit::TestCase assert_equal(-2, method(:mo4).arity) assert_equal(-3, method(:mo5).arity) assert_equal(-3, method(:mo6).arity) + assert_equal(-1, method(:mk1).arity) + assert_equal(-1, method(:mk2).arity) + assert_equal(-2, method(:mk3).arity) + assert_equal(-1, method(:mk4).arity) + assert_equal(-2, method(:mk5).arity) + assert_equal(-3, method(:mk6).arity) + assert_equal(-3, method(:mk7).arity) end def test_arity_special @@ -90,6 +109,51 @@ class TestMethod < Test::Unit::TestCase assert_equal(:m, Class.new {define_method(:m) {__method__}}.new.m) assert_equal(:m, Class.new {define_method(:m) {tap{return __method__}}}.new.m) assert_nil(eval("class TestCallee; __method__; end")) + + assert_equal(:test_callee, __callee__) + [ + ["method", Class.new {def m; __callee__; end},], + ["block", Class.new {def m; tap{return __callee__}; end},], + ["define_method", Class.new {define_method(:m) {__callee__}}], + ["define_method block", Class.new {define_method(:m) {tap{return __callee__}}}], + ].each do |mesg, c| + c.class_eval {alias m2 m} + o = c.new + assert_equal(:m, o.m, mesg) + assert_equal(:m2, o.m2, mesg) + end + 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 + [:m1, :m2].each do |m| + define_method(m) do + __method__ + end + end + end + assert_equal(:m1, c.new.m1, bug4606) + assert_equal(:m2, c.new.m2, bug4606) + end + + def test_method_in_block_in_define_method_block + bug4606 = '[ruby-core:35386]' + c = Class.new do + [:m1, :m2].each do |m| + define_method(m) do + tap { return __method__ } + end + end + end + assert_equal(:m1, c.new.m1, bug4606) + assert_equal(:m2, c.new.m2, bug4606) end def test_body @@ -97,6 +161,7 @@ class TestMethod < Test::Unit::TestCase def o.foo; end assert_nothing_raised { RubyVM::InstructionSequence.disasm(o.method(:foo)) } assert_nothing_raised { RubyVM::InstructionSequence.disasm("x".method(:upcase)) } + assert_nothing_raised { RubyVM::InstructionSequence.disasm(method(:to_s).to_proc) } end def test_new @@ -131,6 +196,28 @@ class TestMethod < Test::Unit::TestCase o = Object.new 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 + c = Class.new do + def foo; end + end + assert_equal(c, c.instance_method(:foo).owner) + c2 = Class.new(c) + assert_equal(c, c2.instance_method(:foo).owner) + end + + def test_owner_missing + c = Class.new do + def respond_to_missing?(name, bool) + name == :foo + end + end + c2 = Class.new(c) + assert_equal(c, c.new.method(:foo).owner) + assert_equal(c2, c2.new.method(:foo).owner) end def test_receiver_name_owner @@ -142,6 +229,12 @@ class TestMethod < Test::Unit::TestCase assert_equal(class << o; self; end, m.owner) assert_equal(:foo, m.unbind.name) assert_equal(class << o; self; end, m.unbind.owner) + class << o + alias bar foo + end + m = o.method(:bar) + assert_equal(:bar, m.name) + assert_equal(:foo, m.original_name) end def test_instance_method @@ -159,6 +252,17 @@ class TestMethod < Test::Unit::TestCase def o.bar; end 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) end def test_define_method @@ -181,22 +285,140 @@ 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_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 + + assert_raise(TypeError) do + Module.new.module_eval {define_method(:foo, Base.instance_method(:foo))} + end + end + + def test_define_singleton_method_with_extended_method + bug8686 = "[ruby-core:56174]" + + m = Module.new do + extend self + + def a + "a" + end + end + + assert_nothing_raised(bug8686) do + m.define_singleton_method(:a, m.method(:a)) + end + end + + def test_define_method_transplating + feature4254 = '[ruby-core:34267]' + m = Module.new {define_method(:meth, M.instance_method(:meth))} + assert_equal(:meth, Object.new.extend(m).meth, feature4254) + c = Class.new {define_method(:meth, M.instance_method(:meth))} + assert_equal(:meth, c.new.meth, feature4254) + end + + def test_define_method_visibility + c = Class.new do + public + define_method(:foo) {:foo} + protected + define_method(:bar) {:bar} + private + define_method(:baz) {:baz} + end + + 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)) + + assert_equal(false, c.private_method_defined?(:foo)) + assert_equal(false, c.private_method_defined?(:bar)) + assert_equal(true, c.private_method_defined?(:baz)) + + m = Module.new do + module_function + define_method(:foo) {:foo} + end + assert_equal(true, m.respond_to?(:foo)) + assert_equal(false, m.public_method_defined?(:foo)) + assert_equal(false, m.protected_method_defined?(:foo)) + assert_equal(true, m.private_method_defined?(:foo)) + end + + def test_define_method_in_private_scope + bug9005 = '[ruby-core:57747] [Bug #9005]' + c = Class.new + class << c + public :define_method + end + TOPLEVEL_BINDING.eval("proc{|c|c.define_method(:x) {|x|throw x}}").call(c) + o = c.new + assert_throw(bug9005) {o.x(bug9005)} + end + + def test_singleton_define_method_in_private_scope + bug9141 = '[ruby-core:58497] [Bug #9141]' + o = Object.new + class << o + public :define_singleton_method + end + TOPLEVEL_BINDING.eval("proc{|o|o.define_singleton_method(:x) {|x|throw x}}").call(o) + assert_throw(bug9141) do + o.x(bug9141) + end + end + + def test_super_in_proc_from_define_method + c1 = Class.new { + def m + :m1 + end + } + c2 = Class.new(c1) { define_method(:m) { Proc.new { super() } } } + assert_equal(:m1, c2.new.m.call, 'see [Bug #4881] and [Bug #3136]') end def test_clone @@ -208,15 +430,6 @@ class TestMethod < Test::Unit::TestCase assert_equal(:bar, m.clone.bar) end - def test_call - o = Object.new - def o.foo; p 1; end - def o.bar(x); x; end - m = o.method(:foo) - m.taint - assert_raise(SecurityError) { m.call } - end - def test_inspect o = Object.new def o.foo; end @@ -236,6 +449,23 @@ class TestMethod < Test::Unit::TestCase c2.class_eval { private :foo } m2 = c2.new.method(:foo) assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo>", m2.inspect) + + bug7806 = '[ruby-core:52048] [Bug #7806]' + c3 = Class.new(c) + c3.class_eval { alias bar foo } + m3 = c3.new.method(:bar) + assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)>", m3.inspect, bug7806) + + m.taint + assert_predicate(m.inspect, :tainted?, "inspect result should be infected") + + bug15608 = '[ruby-core:91570] [Bug #15608]' + c4 = Class.new(c) + c4.class_eval { alias bar foo } + o = c4.new + o.singleton_class + m4 = o.method(:bar) + assert_equal("#<Method: #{c4.inspect}(#{c.inspect})#bar(foo)>", m4.inspect, bug15608) end def test_callee_top_level @@ -261,10 +491,16 @@ class TestMethod < Test::Unit::TestCase end def test_default_accessibility - assert T.public_instance_methods.include?(:normal_method), 'normal methods are public by default' - assert !T.public_instance_methods.include?(:initialize), '#initialize is private' - assert !M.public_instance_methods.include?(:func), 'module methods are private by default' - assert M.public_instance_methods.include?(:meth), 'normal methods are public by default' + tmethods = T.public_instance_methods + assert_include tmethods, :normal_method, 'normal methods are public by default' + assert_not_include tmethods, :initialize, '#initialize is private' + assert_not_include tmethods, :initialize_copy, '#initialize_copy is private' + assert_not_include tmethods, :initialize_clone, '#initialize_clone is private' + assert_not_include tmethods, :initialize_dup, '#initialize_dup is private' + assert_not_include tmethods, :respond_to_missing?, '#respond_to_missing? is private' + mmethods = M.public_instance_methods + assert_not_include mmethods, :func, 'module methods are private by default' + assert_include mmethods, :meth, 'normal methods are public by default' end define_method(:pm0) {||} @@ -277,7 +513,14 @@ class TestMethod < Test::Unit::TestCase define_method(:pmo5) {|a, *b, c|} define_method(:pmo6) {|a, *b, c, &d|} define_method(:pmo7) {|a, b = nil, *c, d, &e|} - define_method(:pma1) {|(a), &b|} + define_method(:pma1) {|(a), &b| nil && a} + define_method(:pmk1) {|**|} + define_method(:pmk2) {|**o|} + define_method(:pmk3) {|a, **o|} + define_method(:pmk4) {|a = nil, **o|} + define_method(:pmk5) {|a, b = nil, **o|} + define_method(:pmk6) {|a, b = nil, c, **o|} + define_method(:pmk7) {|a, b = nil, *c, d, **o|} def test_bound_parameters assert_equal([], method(:m0).parameters) @@ -291,6 +534,13 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters) assert_equal([[:req], [:block, :b]], method(:ma1).parameters) + assert_equal([[:keyrest]], method(:mk1).parameters) + assert_equal([[:keyrest, :o]], method(:mk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:mk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:mk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters) end def test_unbound_parameters @@ -305,6 +555,13 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters) + assert_equal([[:keyrest]], self.class.instance_method(:mk1).parameters) + assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:mk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:mk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters) end def test_bmethod_bound_parameters @@ -319,6 +576,13 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], method(:pma1).parameters) + assert_equal([[:keyrest]], method(:pmk1).parameters) + assert_equal([[:keyrest, :o]], method(:pmk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:pmk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:pmk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:pmk7).parameters) end def test_bmethod_unbound_parameters @@ -333,6 +597,19 @@ class TestMethod < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:pmo6).parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:pmo7).parameters) assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) + assert_equal([[:req], [:block, :b]], self.class.instance_method(:pma1).parameters) + assert_equal([[:keyrest]], self.class.instance_method(:pmk1).parameters) + assert_equal([[:keyrest, :o]], self.class.instance_method(:pmk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:pmk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:pmk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:pmk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:pmk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:pmk7).parameters) + 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 @@ -411,4 +688,514 @@ class TestMethod < Test::Unit::TestCase assert_nothing_raised { v.instance_eval { mv2 } } assert_nothing_raised { v.instance_eval { mv3 } } end + + def test_bound_method_entry + bug6171 = '[ruby-core:43383]' + assert_ruby_status([], <<-EOC, bug6171) + class Bug6171 + def initialize(target) + define_singleton_method(:reverse, target.method(:reverse).to_proc) + end + end + 100.times {p = Bug6171.new('test'); 1000.times {p.reverse}} + 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__) + bug8436 = '[ruby-core:55123] [Bug #8436]' + assert_equal(__dir__, eval("__dir__", binding), bug8436) + bug8662 = '[ruby-core:56099] [Bug #8662]' + assert_equal("arbitrary", eval("__dir__", binding, "arbitrary/file.rb"), bug8662) + assert_equal("arbitrary", Object.new.instance_eval("__dir__", "arbitrary/file.rb"), bug8662) + end + + def test_alias_owner + bug7613 = '[ruby-core:51105]' + bug7993 = '[Bug #7993]' + c = Class.new { + def foo + end + prepend Module.new + attr_reader :zot + } + x = c.new + class << x + alias bar foo + end + assert_equal(c, c.instance_method(:foo).owner) + assert_equal(c, x.method(:foo).owner) + assert_equal(x.singleton_class, x.method(:bar).owner) + assert_not_equal(x.method(:foo), x.method(:bar), bug7613) + assert_equal(c, x.method(:zot).owner, bug7993) + assert_equal(c, c.instance_method(:zot).owner, bug7993) + end + + def test_included + m = Module.new { + def foo + end + } + c = Class.new { + def foo + end + include m + } + assert_equal(c, c.instance_method(:foo).owner) + end + + def test_prepended + bug7836 = '[ruby-core:52160] [Bug #7836]' + bug7988 = '[ruby-core:53038] [Bug #7988]' + m = Module.new { + def foo + end + } + c = Class.new { + def foo + end + prepend m + } + assert_raise(NameError, bug7988) {Module.new{prepend m}.instance_method(:bar)} + true || c || bug7836 + end + + def test_gced_bmethod + assert_normal_exit %q{ + require 'irb' + IRB::Irb.module_eval do + define_method(:eval_input) do + IRB::Irb.module_eval { alias_method :eval_input, :to_s } + GC.start + Kernel + end + end + IRB.start + }, '[Bug #7825]' + end + + def test_singleton_method + feature8391 = '[ruby-core:54914] [Feature #8391]' + c1 = Class.new + c1.class_eval { def foo; :foo; end } + o = c1.new + def o.bar; :bar; end + assert_nothing_raised(NameError) {o.method(:foo)} + assert_raise(NameError, feature8391) {o.singleton_method(:foo)} + 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 test_super_method_alias + c0 = Class.new do + def m1 + [:C0_m1] + end + def m2 + [:C0_m2] + end + end + + c1 = Class.new(c0) do + def m1 + [:C1_m1] + super + end + alias m2 m1 + end + + c2 = Class.new(c1) do + def m2 + [:C2_m2] + super + end + end + o1 = c2.new + assert_equal([:C2_m2, :C1_m1, :C0_m1], o1.m2) + + m = o1.method(:m2) + assert_equal([:C2_m2, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C0_m1], m.call) + + assert_nil(m.super_method) + end + + def test_super_method_alias_to_prepended_module + m = Module.new do + def m1 + [:P_m1] + super + end + + def m2 + [:P_m2] + super + end + end + + c0 = Class.new do + def m1 + [:C0_m1] + end + end + + c1 = Class.new(c0) do + def m1 + [:C1_m1] + super + end + prepend m + alias m2 m1 + end + + o1 = c1.new + assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], o1.m2) + + m = o1.method(:m2) + assert_equal([:P_m2, :P_m1, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:P_m1, :C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C1_m1, :C0_m1], m.call) + + m = m.super_method + assert_equal([:C0_m1], m.call) + + assert_nil(m.super_method) + end + + # Bug 17780 + def test_super_method_module_alias + m = Module.new do + def foo + end + alias :f :foo + end + + method = m.instance_method(:f) + super_method = method.super_method + assert_nil(super_method) + end + + def 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 + + def test_compose_with_method + c = Class.new { + def f(x) x * 2 end + def g(x) x + 1 end + } + f = c.new.method(:f) + g = c.new.method(:g) + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_proc + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_callable + c = Class.new { + def f(x) x * 2 end + } + c2 = Class.new { + def call(x) x + 1 end + } + f = c.new.method(:f) + g = c2.new + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + end + + def test_compose_with_noncallable + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + + assert_raise(NoMethodError) { + (f << 5).call(2) + } + assert_raise(NoMethodError) { + (f >> 5).call(2) + } + 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 0e4737dea6..ef78475424 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 @@ -64,20 +64,6 @@ class TestModule < Test::Unit::TestCase # Support stuff - def remove_pp_mixins(list) - list.reject {|c| c == PP::ObjectMixin } - end - - def remove_json_mixins(list) - list.reject {|c| c.to_s.start_with?("JSON") } - end - - def remove_rake_mixins(list) - list. - reject {|c| c.to_s == "RakeFileUtils" }. - reject {|c| c.to_s.start_with?("FileUtils") } - end - module Mixin MIXIN = 1 def mixin @@ -89,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 @@ -141,7 +135,7 @@ class TestModule < Test::Unit::TestCase :bClass3 end end - + class CClass < BClass def self.cClass end @@ -165,60 +159,61 @@ class TestModule < Test::Unit::TestCase end def test_GE # '>=' - assert(Mixin >= User) - assert(Mixin >= Mixin) - assert(!(User >= Mixin)) + assert_operator(Mixin, :>=, User) + assert_operator(Mixin, :>=, Mixin) + assert_not_operator(User, :>=, Mixin) - assert(Object >= String) - assert(String >= String) - assert(!(String >= Object)) + assert_operator(Object, :>=, String) + assert_operator(String, :>=, String) + assert_not_operator(String, :>=, Object) end def test_GT # '>' - assert(Mixin > User) - assert(!(Mixin > Mixin)) - assert(!(User > Mixin)) + assert_operator(Mixin, :>, User) + assert_not_operator(Mixin, :>, Mixin) + assert_not_operator(User, :>, Mixin) - assert(Object > String) - assert(!(String > String)) - assert(!(String > Object)) + assert_operator(Object, :>, String) + assert_not_operator(String, :>, String) + assert_not_operator(String, :>, Object) end def test_LE # '<=' - assert(User <= Mixin) - assert(Mixin <= Mixin) - assert(!(Mixin <= User)) + assert_operator(User, :<=, Mixin) + assert_operator(Mixin, :<=, Mixin) + assert_not_operator(Mixin, :<=, User) - assert(String <= Object) - assert(String <= String) - assert(!(Object <= String)) + assert_operator(String, :<=, Object) + assert_operator(String, :<=, String) + assert_not_operator(Object, :<=, String) end def test_LT # '<' - assert(User < Mixin) - assert(!(Mixin < Mixin)) - assert(!(Mixin < User)) + assert_operator(User, :<, Mixin) + assert_not_operator(Mixin, :<, Mixin) + assert_not_operator(Mixin, :<, User) - assert(String < Object) - assert(!(String < String)) - assert(!(Object < String)) + assert_operator(String, :<, Object) + assert_not_operator(String, :<, String) + assert_not_operator(Object, :<, String) end def test_VERY_EQUAL # '===' - assert(Object === self) - assert(Test::Unit::TestCase === self) - assert(TestModule === self) - assert(!(String === self)) + assert_operator(Object, :===, self) + assert_operator(Test::Unit::TestCase, :===, self) + assert_operator(TestModule, :===, self) + assert_not_operator(String, :===, self) end def test_ancestors assert_equal([User, Mixin], User.ancestors) assert_equal([Mixin], Mixin.ancestors) - assert_equal([Object, Kernel, BasicObject], - remove_rake_mixins(remove_json_mixins(remove_pp_mixins(Object.ancestors)))) - assert_equal([String, Comparable, Object, Kernel, BasicObject], - remove_rake_mixins(remove_json_mixins(remove_pp_mixins(String.ancestors)))) + ancestors = Object.ancestors + mixins = ancestors - [Object, Kernel, BasicObject] + mixins << JSON::Ext::Generator::GeneratorMethods::String if defined?(JSON::Ext::Generator::GeneratorMethods::String) + assert_equal([Object, Kernel, BasicObject], ancestors - mixins) + assert_equal([String, Comparable, Object, Kernel, BasicObject], String.ancestors - mixins) end CLASS_EVAL = 2 @@ -227,7 +222,7 @@ class TestModule < Test::Unit::TestCase def test_class_eval Other.class_eval("CLASS_EVAL = 1") assert_equal(1, Other::CLASS_EVAL) - assert(Other.constants.include?(:CLASS_EVAL)) + assert_include(Other.constants, :CLASS_EVAL) assert_equal(2, Other.class_eval { CLASS_EVAL }) Other.class_eval("@@class_eval = 'a'") @@ -247,24 +242,144 @@ class TestModule < Test::Unit::TestCase end def test_const_defined? - assert(Math.const_defined?(:PI)) - assert(Math.const_defined?("PI")) - assert(!Math.const_defined?(:IP)) - assert(!Math.const_defined?("IP")) + assert_operator(Math, :const_defined?, :PI) + assert_operator(Math, :const_defined?, "PI") + assert_not_operator(Math, :const_defined?, :IP) + assert_not_operator(Math, :const_defined?, "IP") + end + + def each_bad_constants(m, &b) + [ + "#<Class:0x7b8b718b>", + ":Object", + "", + ":", + ["String::", "[Bug #7573]"], + "\u3042", + "Name?", + ].each do |name, msg| + expected = "wrong constant name %s" % name + msg = "#{msg}#{': ' if msg}wrong constant name #{name.dump}" + assert_raise_with_message(NameError, expected, "#{msg} to #{m}") do + yield name + end + end + end + + def test_bad_constants_get + each_bad_constants("get") {|name| + Object.const_get name + } + end + + def test_bad_constants_defined + each_bad_constants("defined?") {|name| + Object.const_defined? name + } + end + + def test_leading_colons + assert_equal Object, AClass.const_get('::Object') end def test_const_get assert_equal(Math::PI, Math.const_get("PI")) assert_equal(Math::PI, Math.const_get(:PI)) + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "PI"; end + def n.count; @count; end + assert_equal(Math::PI, Math.const_get(n)) + assert_equal(1, n.count) + end + + def test_nested_get + assert_equal Other, Object.const_get([self.class, 'Other'].join('::')) + assert_equal User::USER, self.class.const_get([User, 'USER'].join('::')) + assert_raise(NameError) { + Object.const_get([self.class.name, 'String'].join('::')) + } + end + + def test_nested_get_symbol + const = [self.class, Other].join('::').to_sym + assert_raise(NameError) {Object.const_get(const)} + + const = [User, 'USER'].join('::').to_sym + assert_raise(NameError) {self.class.const_get(const)} + end + + def test_nested_get_const_missing + classes = [] + klass = Class.new { + define_singleton_method(:const_missing) { |name| + classes << name + klass + } + } + klass.const_get("Foo::Bar::Baz") + assert_equal [:Foo, :Bar, :Baz], classes + end + + def test_nested_get_bad_class + assert_raise(TypeError) do + self.class.const_get([User, 'USER', 'Foo'].join('::')) + end + end + + def test_nested_defined + assert_send([Object, :const_defined?, [self.class.name, 'Other'].join('::')]) + assert_send([self.class, :const_defined?, 'User::USER']) + assert_not_send([self.class, :const_defined?, 'User::Foo']) + assert_not_send([Object, :const_defined?, [self.class.name, 'String'].join('::')]) + end + + def test_nested_defined_symbol + const = [self.class, Other].join('::').to_sym + assert_raise(NameError) {Object.const_defined?(const)} + + const = [User, 'USER'].join('::').to_sym + assert_raise(NameError) {self.class.const_defined?(const)} + end + + def test_nested_defined_inheritance + assert_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::')]) + assert_send([self.class, :const_defined?, 'User::MIXIN']) + assert_send([Object, :const_defined?, 'File::SEEK_SET']) + + # const_defined? with `false` + assert_not_send([Object, :const_defined?, [self.class.name, 'User', 'MIXIN'].join('::'), false]) + assert_not_send([self.class, :const_defined?, 'User::MIXIN', false]) + assert_not_send([Object, :const_defined?, 'File::SEEK_SET', false]) + end + + def test_nested_defined_bad_class + assert_raise(TypeError) do + self.class.const_defined?('User::USER::Foo') + end end def test_const_set - assert(!Other.const_defined?(:KOALA)) + assert_not_operator(Other, :const_defined?, :KOALA) Other.const_set(:KOALA, 99) - assert(Other.const_defined?(:KOALA)) + assert_operator(Other, :const_defined?, :KOALA) assert_equal(99, Other::KOALA) Other.const_set("WOMBAT", "Hi") assert_equal("Hi", Other::WOMBAT) + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "HOGE"; end + def n.count; @count; end + def n.count=(v); @count=v; end + assert_not_operator(Other, :const_defined?, :HOGE) + Other.const_set(n, 999) + assert_equal(1, n.count) + n.count = 0 + assert_equal(999, Other.const_get(n)) + assert_equal(1, n.count) + n.count = 0 + assert_equal(true, Other.const_defined?(n)) + assert_equal(1, n.count) end def test_constants @@ -272,38 +387,138 @@ class TestModule < Test::Unit::TestCase assert_equal([:MIXIN, :USER], User.constants.sort) end + def test_self_initialize_copy + bug9535 = '[ruby-dev:47989] [Bug #9535]' + m = Module.new do + def foo + :ok + end + initialize_copy(self) + end + assert_equal(:ok, Object.new.extend(m).foo, bug9535) + end + + def test_initialize_copy_empty + bug9813 = '[ruby-dev:48182] [Bug #9813]' + m = Module.new do + def x + end + const_set(:X, 1) + @x = 2 + end + assert_equal([:x], m.instance_methods) + assert_equal([:@x], m.instance_variables) + assert_equal([:X], m.constants) + m.module_eval do + initialize_copy(Module.new) + end + assert_empty(m.instance_methods, bug9813) + assert_empty(m.instance_variables, bug9813) + assert_empty(m.constants, bug9813) + end + + def test_dup + bug6454 = '[ruby-core:45132]' + + a = Module.new + Other.const_set :BUG6454, a + b = a.dup + Other.const_set :BUG6454_dup, b + + assert_equal "TestModule::Other::BUG6454_dup", b.inspect, bug6454 + end + + def test_dup_anonymous + bug6454 = '[ruby-core:45132]' + + a = Module.new + original = a.inspect + + b = a.dup + + assert_not_equal original, b.inspect, bug6454 + end + + def test_public_include + assert_nothing_raised('#8846') do + Module.new.include(Module.new { def foo; end }).instance_methods == [:foo] + end + end + + def test_include_toplevel + assert_separately([], <<-EOS) + Mod = Module.new {def foo; :include_foo end} + TOPLEVEL_BINDING.eval('include Mod') + + assert_equal(:include_foo, TOPLEVEL_BINDING.eval('foo')) + assert_equal([Object, Mod], Object.ancestors.slice(0, 2)) + 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) - assert_equal([Kernel], - remove_rake_mixins(remove_json_mixins(remove_pp_mixins(Object.included_modules)))) - assert_equal([Comparable, Kernel], - remove_rake_mixins(remove_json_mixins(remove_pp_mixins(String.included_modules)))) + + mixins = Object.included_modules - [Kernel] + mixins << JSON::Ext::Generator::GeneratorMethods::String if defined?(JSON::Ext::Generator::GeneratorMethods::String) + assert_equal([Kernel], Object.included_modules - mixins) + assert_equal([Comparable, Kernel], String.included_modules - mixins) 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)) assert_equal([], (class << BClass; self; end).instance_methods(false)) assert_equal([:cm2], (class << AClass; self; end).instance_methods(false)) - # Ruby 1.8 feature change: - # #instance_methods includes protected methods. - #assert_equal([:aClass], AClass.instance_methods(false)) assert_equal([:aClass, :aClass2], AClass.instance_methods(false).sort) assert_equal([:aClass, :aClass2], (AClass.instance_methods(true) - Object.instance_methods(true)).sort) end def test_method_defined? - assert_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) + [User, Class.new{include User}, Class.new{prepend User}].each do |klass| + [[], [true]].each do |args| + assert !klass.method_defined?(:wombat, *args) + assert klass.method_defined?(:mixin, *args) + assert klass.method_defined?(:user, *args) + assert klass.method_defined?(:user2, *args) + assert !klass.method_defined?(:user3, *args) + + assert !klass.method_defined?("wombat", *args) + assert klass.method_defined?("mixin", *args) + assert klass.method_defined?("user", *args) + assert klass.method_defined?("user2", *args) + assert !klass.method_defined?("user3", *args) + end + end + end + + def test_method_defined_without_include_super + assert User.method_defined?(:user, false) + assert !User.method_defined?(:mixin, false) + assert Mixin.method_defined?(:mixin, false) + + User.const_set(:FOO, c = Class.new) + + c.prepend(User) + assert !c.method_defined?(:user, false) + c.define_method(:user){} + assert c.method_defined?(:user, false) + + assert !c.method_defined?(:mixin, false) + c.define_method(:mixin){} + assert c.method_defined?(:mixin, false) + + assert !c.method_defined?(:userx, false) + c.define_method(:userx){} + assert c.method_defined?(:userx, false) end def module_exec_aux @@ -339,17 +554,38 @@ class TestModule < Test::Unit::TestCase def test_module_eval User.module_eval("MODULE_EVAL = 1") assert_equal(1, User::MODULE_EVAL) - assert(User.constants.include?(:MODULE_EVAL)) + assert_include(User.constants, :MODULE_EVAL) User.instance_eval("remove_const(:MODULE_EVAL)") - assert(!User.constants.include?(:MODULE_EVAL)) + assert_not_include(User.constants, :MODULE_EVAL) 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 + def test_classpath + m = Module.new + n = Module.new + m.const_set(:N, n) + assert_nil(m.name) + assert_nil(n.name) + assert_equal([:N], m.constants) + m.module_eval("module O end") + assert_equal([:N, :O], m.constants.sort) + m.module_eval("class C; end") + assert_equal([:C, :N, :O], m.constants.sort) + assert_nil(m::N.name) + assert_match(/\A#<Module:.*>::O\z/, m::O.name) + assert_match(/\A#<Module:.*>::C\z/, m::C.name) + self.class.const_set(:M, m) + prefix = self.class.name + "::M::" + assert_equal(prefix+"N", m.const_get(:N).name) + assert_equal(prefix+"O", m.const_get(:O).name) + assert_equal(prefix+"C", m.const_get(:C).name) + end + def test_private_class_method assert_raise(ExpectedException) { AClass.cm1 } assert_raise(ExpectedException) { AClass.cm3 } @@ -403,6 +639,13 @@ class TestModule < Test::Unit::TestCase p Module.constants - ary, Module.constants(true), Module.constants(false) INPUT assert_in_out_err([], src, %w([:M] [:WALTER] []), []) + + klass = Class.new do + 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 @@ -425,13 +668,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 @@ -451,7 +703,7 @@ class TestModule < Test::Unit::TestCase assert_equal(false, o.respond_to?(:bar=)) end - def test_const_get2 + def test_const_get_evaled c1 = Class.new c2 = Class.new(c1) @@ -466,6 +718,9 @@ class TestModule < Test::Unit::TestCase assert_raise(NameError) { c2::Bar } assert_raise(NameError) { c2.const_get(:Bar) } assert_raise(NameError) { c2.const_get(:Bar, false) } + assert_raise(NameError) { c2.const_get("Bar", false) } + assert_raise(NameError) { c2.const_get("BaR11", false) } + assert_raise(NameError) { Object.const_get("BaR11", false) } c1.instance_eval do def const_missing(x) @@ -477,18 +732,104 @@ class TestModule < Test::Unit::TestCase assert_equal(:Bar, c2::Bar) assert_equal(:Bar, c2.const_get(:Bar)) assert_equal(:Bar, c2.const_get(:Bar, false)) + assert_equal(:Bar, c2.const_get("Bar")) + assert_equal(:Bar, c2.const_get("Bar", false)) + + v = c2.const_get("Bar11", false) + assert_equal("Bar11".to_sym, v) assert_raise(NameError) { c1.const_get(:foo) } end - def test_const_set2 + def test_const_set_invalid_name + c1 = Class.new + 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 c1 = Class.new - assert_raise(NameError) { c1.const_set(:foo, :foo) } + assert_raise(NameError) { c1.const_get(:foo) } + bug5084 = '[ruby-dev:44200]' + assert_raise(TypeError, bug5084) { c1.const_get(1) } + bug7574 = '[ruby-dev:46749]' + assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { + Object.const_get("String\0") + } end - def test_const_get3 + def test_const_defined_invalid_name c1 = Class.new assert_raise(NameError) { c1.const_defined?(:foo) } + bug5084 = '[ruby-dev:44200]' + assert_raise(TypeError, bug5084) { c1.const_defined?(1) } + bug7574 = '[ruby-dev:46749]' + assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { + Object.const_defined?("String\0") + } + end + + def test_const_get_no_inherited + bug3422 = '[ruby-core:30719]' + assert_in_out_err([], <<-INPUT, %w[1 NameError A], [], bug3422) + BasicObject::A = 1 + puts [true, false].map {|inh| + begin + Object.const_get(:A, inh) + rescue NameError => e + [e.class, e.name] + end + } + INPUT + end + + def test_const_get_inherited + bug3423 = '[ruby-core:30720]' + assert_in_out_err([], <<-INPUT, %w[NameError A NameError A], [], bug3423) + module Foo; A = 1; end + class Object; include Foo; end + class Bar; include Foo; end + + puts [Object, Bar].map {|klass| + begin + klass.const_get(:A, false) + rescue NameError => e + [e.class, e.name] + end + } + INPUT + end + + def test_const_in_module + bug3423 = '[ruby-core:37698]' + assert_in_out_err([], <<-INPUT, %w[ok], [], bug3423) + module LangModuleSpecInObject + module LangModuleTop + end + end + include LangModuleSpecInObject + module LangModuleTop + end + puts "ok" if LangModuleSpecInObject::LangModuleTop == LangModuleTop + INPUT + + bug5264 = '[ruby-core:39227]' + assert_in_out_err([], <<-'INPUT', [], [], bug5264) + class A + class X; end + end + class B < A + module X; end + end + INPUT end def test_class_variable_get @@ -496,14 +837,35 @@ class TestModule < Test::Unit::TestCase c.class_eval('@@foo = :foo') assert_equal(:foo, c.class_variable_get(:@@foo)) assert_raise(NameError) { c.class_variable_get(:@@bar) } # c.f. instance_variable_get + assert_raise(NameError) { c.class_variable_get(:'@@') } + assert_raise(NameError) { c.class_variable_get('@@') } assert_raise(NameError) { c.class_variable_get(:foo) } + assert_raise(NameError) { c.class_variable_get("bar") } + assert_raise(TypeError) { c.class_variable_get(1) } + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@@foo"; end + def n.count; @count; end + assert_equal(:foo, c.class_variable_get(n)) + assert_equal(1, n.count) end def test_class_variable_set c = Class.new c.class_variable_set(:@@foo, :foo) assert_equal(:foo, c.class_eval('@@foo')) + assert_raise(NameError) { c.class_variable_set(:'@@', 1) } + assert_raise(NameError) { c.class_variable_set('@@', 1) } assert_raise(NameError) { c.class_variable_set(:foo, 1) } + assert_raise(NameError) { c.class_variable_set("bar", 1) } + assert_raise(TypeError) { c.class_variable_set(1, 1) } + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@@foo"; end + def n.count; @count; end + c.class_variable_set(n, :bar) + assert_equal(:bar, c.class_eval('@@foo')) + assert_equal(1, n.count) end def test_class_variable_defined @@ -511,7 +873,16 @@ class TestModule < Test::Unit::TestCase c.class_eval('@@foo = :foo') assert_equal(true, c.class_variable_defined?(:@@foo)) assert_equal(false, c.class_variable_defined?(:@@bar)) + assert_raise(NameError) { c.class_variable_defined?(:'@@') } + assert_raise(NameError) { c.class_variable_defined?('@@') } assert_raise(NameError) { c.class_variable_defined?(:foo) } + assert_raise(NameError) { c.class_variable_defined?("bar") } + assert_raise(TypeError) { c.class_variable_defined?(1) } + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@@foo"; end + def n.count; @count; end + assert_equal(true, c.class_variable_defined?(n)) + assert_equal(1, n.count) end def test_remove_class_variable @@ -519,6 +890,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 @@ -529,7 +903,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 @@ -537,7 +911,6 @@ class TestModule < Test::Unit::TestCase attr_reader :foo end o = c.new - o.foo rescue p(:ok) p(o.instance_eval { foo }) INPUT @@ -548,13 +921,6 @@ class TestModule < Test::Unit::TestCase end def test_undef - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - Class.instance_eval { undef_method(:foo) } - end.join - end - c = Class.new assert_raise(NameError) do c.instance_eval { undef_method(:foo) } @@ -571,7 +937,7 @@ class TestModule < Test::Unit::TestCase end %w(object_id __send__ initialize).each do |n| - assert_in_out_err([], <<-INPUT, [], /warning: undefining `#{n}' may cause serious problems$/) + assert_in_out_err([], <<-INPUT, [], %r"warning: undefining `#{n}' may cause serious problems$") $VERBOSE = false Class.new.instance_eval { undef_method(:#{n}) } INPUT @@ -604,30 +970,52 @@ class TestModule < Test::Unit::TestCase m.instance_eval { remove_const(:Foo) } end - def test_frozen_class + class Bug9413 + class << self + Foo = :foo + end + end + + def test_singleton_constants + bug9413 = '[ruby-core:59763] [Bug #9413]' + c = Bug9413.singleton_class + assert_include(c.constants(true), :Foo, bug9413) + assert_include(c.constants(false), :Foo, bug9413) + end + + 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 - c = Class.new - c.class_eval do + cl = Class.new + def_methods = proc do def foo; end def bar; end def baz; end @@ -635,30 +1023,46 @@ class TestModule < Test::Unit::TestCase protected :bar private :baz end - - 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)) - - 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_change_visibility_under_safe4 - c = Class.new - c.class_eval do - def foo; end - end - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - c.class_eval { private :foo } - end.join + cl.class_eval(&def_methods) + sc = Class.new(cl) + mod = Module.new(&def_methods) + only_prepend = Class.new{prepend(mod)} + empty_prepend = cl.clone + empty_prepend.prepend(Module.new) + overlap_prepend = cl.clone + overlap_prepend.prepend(mod) + + [[], [true], [false]].each do |args| + [cl, sc, only_prepend, empty_prepend, overlap_prepend].each do |c| + always_false = [sc, only_prepend].include?(c) && args == [false] + + assert_equal(always_false ? false : true, c.public_method_defined?(:foo, *args)) + assert_equal(always_false ? false : false, c.public_method_defined?(:bar, *args)) + assert_equal(always_false ? false : false, c.public_method_defined?(:baz, *args)) + + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : true, c.public_method_defined?("foo", *args)) + assert_equal(always_false ? false : false, c.public_method_defined?("bar", *args)) + assert_equal(always_false ? false : false, c.public_method_defined?("baz", *args)) + + assert_equal(always_false ? false : false, c.protected_method_defined?(:foo, *args)) + assert_equal(always_false ? false : true, c.protected_method_defined?(:bar, *args)) + assert_equal(always_false ? false : false, c.protected_method_defined?(:baz, *args)) + + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : false, c.protected_method_defined?("foo", *args)) + assert_equal(always_false ? false : true, c.protected_method_defined?("bar", *args)) + assert_equal(always_false ? false : false, c.protected_method_defined?("baz", *args)) + + assert_equal(always_false ? false : false, c.private_method_defined?(:foo, *args)) + assert_equal(always_false ? false : false, c.private_method_defined?(:bar, *args)) + assert_equal(always_false ? false : true, c.private_method_defined?(:baz, *args)) + + # Test if string arguments are converted to symbols + assert_equal(always_false ? false : false, c.private_method_defined?("foo", *args)) + assert_equal(always_false ? false : false, c.private_method_defined?("bar", *args)) + assert_equal(always_false ? false : true, c.private_method_defined?("baz", *args)) + end end end @@ -758,24 +1162,6 @@ class TestModule < Test::Unit::TestCase assert_equal(false, m.include?(m)) end - def test_include_under_safe4 - m = Module.new - c1 = Class.new - assert_raise(SecurityError) do - lambda { - $SAFE = 4 - c1.instance_eval { include(m) } - }.call - end - assert_nothing_raised do - lambda { - $SAFE = 4 - c2 = Class.new - c2.instance_eval { include(m) } - }.call - end - end - def test_send a = AClass.new assert_equal(:aClass, a.__send__(:aClass)) @@ -796,6 +1182,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 @@ -819,16 +1207,74 @@ 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 + def test_method_undefined + added = [] + undefed = [] + removed = [] + mod = Module.new do + mod = self + def f + end + (class << self ; self ; end).class_eval do + define_method :method_added do |sym| + added << sym + end + define_method :method_undefined do |sym| + undefed << sym + end + define_method :method_removed do |sym| + removed << sym + end + end + end + assert_method_defined?(mod, :f) + mod.module_eval do + undef :f + end + assert_equal [], added + assert_equal [:f], undefed + assert_equal [], removed + end + + def test_method_removed + added = [] + undefed = [] + removed = [] + mod = Module.new do + mod = self + def f + end + (class << self ; self ; end).class_eval do + define_method :method_added do |sym| + added << sym + end + define_method :method_undefined do |sym| + undefed << sym + end + define_method :method_removed do |sym| + removed << sym + end + end + end + assert_method_defined?(mod, :f) + mod.module_eval do + remove_method :f + end + assert_equal [], added + assert_equal [], undefed + assert_equal [:f], removed + end + def test_method_redefinition feature2155 = '[ruby-dev:39400]' @@ -842,23 +1288,21 @@ class TestModule < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Module.new do def foo; end alias bar foo def foo; end end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Module.new do def foo; end alias bar foo alias bar foo end end - assert_equal("", stderr) line = __LINE__+4 stderr = EnvUtil.verbose_warning do @@ -870,31 +1314,42 @@ class TestModule < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Module.new do define_method(:foo) do end alias bar foo - alias barf oo + alias bar foo end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning('', '[ruby-dev:39397]') do Module.new do module_function def foo; end module_function :foo end end - assert_equal("", stderr, '[ruby-dev:39397]') - stderr = EnvUtil.verbose_warning do + assert_warning '' do Module.new do def foo; end undef foo end end - assert_equal("", stderr) + + 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 @@ -929,7 +1384,6 @@ class TestModule < Test::Unit::TestCase def test_attr_inherited_visibility bug3406 = '[ruby-core:30638]' - skip(bug3406) c = Class.new do class << self private @@ -940,4 +1394,1007 @@ class TestModule < Test::Unit::TestCase assert_nothing_raised(bug3406) {c.x = 1} assert_equal(1, c.x, bug3406) end + + def test_attr_writer_with_no_arguments + bug8540 = "[ruby-core:55543]" + c = Class.new do + attr_writer :foo + end + assert_raise(ArgumentError, bug8540) { c.new.send :foo= } + end + + def test_private_constant_in_class + c = Class.new + c.const_set(:FOO, "foo") + assert_equal("foo", c::FOO) + c.private_constant(: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 + 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 + c = Class.new + c.const_set(:FOO, "foo") + c.const_set(:BAR, "bar") + assert_equal("foo", c::FOO) + assert_equal("bar", c::BAR) + c.private_constant(:FOO, :BAR) + assert_raise(NameError) { c::FOO } + assert_raise(NameError) { c::BAR } + assert_equal("foo", c.class_eval("FOO")) + assert_equal("bar", c.class_eval("BAR")) + end + + def test_private_constant_with_no_args + assert_in_out_err([], <<-RUBY, [], ["-:3: warning: private_constant with no argument is just ignored"]) + $-w = true + class X + private_constant + end + RUBY + end + + def test_private_constant_const_missing + c = Class.new + c.const_set(:FOO, "foo") + c.private_constant(:FOO) + class << c + attr_reader :const_missing_arg + def const_missing(name) + @const_missing_arg = name + name == :FOO ? const_get(:FOO) : super + end + end + assert_equal("foo", c::FOO) + assert_equal(:FOO, c.const_missing_arg) + end + + class PrivateClass + end + private_constant :PrivateClass + + def test_define_module_under_private_constant + assert_raise(NameError) do + eval %q{class TestModule::PrivateClass; end} + end + assert_raise(NameError) do + eval %q{module TestModule::PrivateClass::TestModule; end} + end + eval %q{class PrivateClass; end} + eval %q{module PrivateClass::TestModule; end} + assert_instance_of(Module, PrivateClass::TestModule) + PrivateClass.class_eval { remove_const(:TestModule) } + end + + def test_public_constant + c = Class.new + c.const_set(:FOO, "foo") + assert_equal("foo", c::FOO) + c.private_constant(:FOO) + assert_raise(NameError) { c::FOO } + assert_equal("foo", c.class_eval("FOO")) + c.public_constant(:FOO) + 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 + + NIL = nil + FALSE = false + deprecate_constant(:NIL, :FALSE) + + def test_deprecate_nil_constant + w = EnvUtil.verbose_warning {2.times {FALSE}} + assert_equal(1, w.scan("::FALSE").size, w) + w = EnvUtil.verbose_warning {2.times {NIL}} + assert_equal(1, w.scan("::NIL").size, w) + 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 + src = <<-INPUT + class Object + private_constant :Object + end + p Object + begin + p ::Object + rescue + p :ok + end + INPUT + assert_in_out_err([], src, %w(Object :ok), []) + end + + def test_private_constants_clear_inlinecache + bug5702 = '[ruby-dev:44929]' + src = <<-INPUT + class A + C = :Const + def self.get_C + A::C + end + # fill cache + A.get_C + private_constant :C, :D rescue nil + begin + A.get_C + rescue NameError + puts "A.get_C" + end + end + INPUT + assert_in_out_err([], src, %w(A.get_C), [], bug5702) + end + + def test_constant_lookup_in_method_defined_by_class_eval + src = <<-INPUT + class A + B = 42 + end + + A.class_eval do + def self.f + B + end + + def f + B + end + end + + begin + A.f + rescue NameError + puts "A.f" + end + begin + A.new.f + rescue NameError + puts "A.new.f" + end + INPUT + assert_in_out_err([], src, %w(A.f A.new.f), []) + end + + def test_constant_lookup_in_toplevel_class_eval + src = <<-INPUT + module X + A = 123 + end + begin + X.class_eval { A } + rescue NameError => e + puts e + end + INPUT + assert_in_out_err([], src, ["uninitialized constant A"], []) + end + + def test_constant_lookup_in_module_in_class_eval + src = <<-INPUT + class A + B = 42 + end + + A.class_eval do + module C + begin + B + rescue NameError + puts "NameError" + end + end + end + INPUT + assert_in_out_err([], src, ["NameError"], []) + end + + module M0 + def m1; [:M0] end + end + module M1 + def m1; [:M1, *super] end + end + module M2 + def m1; [:M2, *super] end + end + M3 = Module.new do + def m1; [:M3, *super] end + end + module M4 + def m1; [:M4, *super] end + end + class C + def m1; end + end + class C0 < C + include M0 + prepend M1 + def m1; [:C0, *super] end + end + class C1 < C0 + prepend M2, M3 + include M4 + def m1; [:C1, *super] end + end + + def test_prepend + obj = C0.new + expected = [:M1,:C0,:M0] + assert_equal(expected, obj.m1) + obj = C1.new + expected = [:M2,:M3,:C1,:M4,:M1,:C0,:M0] + assert_equal(expected, obj.m1) + end + + def test_public_prepend + assert_nothing_raised('#8846') do + Class.new.prepend(Module.new) + 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") + b = labeled_module("b") {include a} + c = labeled_class("c") {prepend b} + assert_operator(c, :<, b, bug6654) + assert_operator(c, :<, a, bug6654) + bug8357 = '[ruby-core:54736] [Bug #8357]' + b = labeled_module("b") {prepend a} + c = labeled_class("c") {include b} + assert_operator(c, :<, b, bug8357) + assert_operator(c, :<, a, bug8357) + bug8357 = '[ruby-core:54742] [Bug #8357]' + assert_kind_of(b, c.new, bug8357) + end + + def test_prepend_instance_methods + bug6655 = '[ruby-core:45915]' + assert_equal(Object.instance_methods, Class.new {prepend Module.new}.instance_methods, bug6655) + end + + def test_prepend_singleton_methods + o = Object.new + o.singleton_class.class_eval {prepend Module.new} + assert_equal([], o.singleton_methods) + end + + def test_prepend_remove_method + c = Class.new do + prepend Module.new {def foo; end} + end + assert_raise(NameError) do + c.class_eval do + remove_method(:foo) + end + end + c.class_eval do + def foo; end + end + removed = nil + c.singleton_class.class_eval do + define_method(:method_removed) {|id| removed = id} + end + assert_nothing_raised(NoMethodError, NameError, '[Bug #7843]') do + c.class_eval do + remove_method(:foo) + end + end + assert_equal(:foo, removed) + end + + def test_prepend_class_ancestors + bug6658 = '[ruby-core:45919]' + m = labeled_module("m") + c = labeled_class("c") {prepend m} + assert_equal([m, c], c.ancestors[0, 2], bug6658) + + bug6662 = '[ruby-dev:45868]' + c2 = labeled_class("c2", c) + anc = c2.ancestors + assert_equal([c2, m, c, Object], anc[0..anc.index(Object)], bug6662) + end + + def test_prepend_module_ancestors + bug6659 = '[ruby-dev:45861]' + m0 = labeled_module("m0") {def x; [:m0, *super] end} + m1 = labeled_module("m1") {def x; [:m1, *super] end; prepend m0} + m2 = labeled_module("m2") {def x; [:m2, *super] end; prepend m1} + c0 = labeled_class("c0") {def x; [:c0] end} + c1 = labeled_class("c1") {def x; [:c1] end; prepend m2} + c2 = labeled_class("c2", c0) {def x; [:c2, *super] end; include m2} + + assert_equal([m0, m1], m1.ancestors, bug6659) + + bug6662 = '[ruby-dev:45868]' + assert_equal([m0, m1, m2], m2.ancestors, bug6662) + assert_equal([m0, m1, m2, c1], c1.ancestors[0, 4], bug6662) + assert_equal([:m0, :m1, :m2, :c1], c1.new.x) + assert_equal([c2, m0, m1, m2, c0], c2.ancestors[0, 5], bug6662) + assert_equal([:c2, :m0, :m1, :m2, :c0], c2.new.x) + + m3 = labeled_module("m3") {include m1; prepend m1} + assert_equal([m3, m0, m1], m3.ancestors) + m3 = labeled_module("m3") {prepend m1; include m1} + assert_equal([m0, m1, m3], m3.ancestors) + m3 = labeled_module("m3") {prepend m1; prepend m1} + assert_equal([m0, m1, m3], m3.ancestors) + m3 = labeled_module("m3") {include m1; include m1} + assert_equal([m3, m0, m1], m3.ancestors) + end + + def labeled_module(name, &block) + EnvUtil.labeled_module(name, &block) + end + + def labeled_class(name, superclass = Object, &block) + EnvUtil.labeled_class(name, superclass, &block) + end + + def test_prepend_instance_methods_false + bug6660 = '[ruby-dev:45863]' + assert_equal([:m1], Class.new{ prepend Module.new; def m1; end }.instance_methods(false), bug6660) + assert_equal([:m1], Class.new(Class.new{def m2;end}){ prepend Module.new; def m1; end }.instance_methods(false), bug6660) + end + + def test_cyclic_prepend + bug7841 = '[ruby-core:52205] [Bug #7841]' + m1 = Module.new + m2 = Module.new + m1.instance_eval { prepend(m2) } + assert_raise(ArgumentError, bug7841) do + m2.instance_eval { prepend(m1) } + end + end + + def test_prepend_optmethod + bug7983 = '[ruby-dev:47124] [Bug #7983]' + assert_separately [], %{ + module M + def /(other) + to_f / other + end + end + 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 + prepend Module.new {} + def foo() end + protected :foo + end + a = c.new + assert_respond_to a, [:foo, true], bug8005 + assert_nothing_raised(NoMethodError, bug8005) {a.send :foo} + end + + def test_prepend_visibility_inherited + bug8238 = '[ruby-core:54105] [Bug #8238]' + assert_separately [], <<-"end;", timeout: 20 + class A + def foo() A; end + private :foo + end + class B < A + public :foo + prepend Module.new + end + assert_equal(A, B.new.foo, "#{bug8238}") + end; + end + + def test_prepend_included_modules + bug8025 = '[ruby-core:53158] [Bug #8025]' + mixin = labeled_module("mixin") + c = labeled_module("c") {prepend mixin} + im = c.included_modules + assert_not_include(im, c, bug8025) + assert_include(im, mixin, bug8025) + c1 = labeled_class("c1") {prepend mixin} + c2 = labeled_class("c2", c1) + im = c2.included_modules + assert_not_include(im, c1, bug8025) + assert_not_include(im, c2, bug8025) + assert_include(im, mixin, bug8025) + end + + def test_prepend_super_in_alias + bug7842 = '[Bug #7842]' + + p = labeled_module("P") do + def m; "P"+super; end + end + a = labeled_class("A") do + def m; "A"; end + end + b = labeled_class("B", a) do + def m; "B"+super; end + alias m2 m + prepend p + alias m3 m + end + assert_equal("BA", b.new.m2, bug7842) + assert_equal("PBA", b.new.m3, bug7842) + end + + def test_include_super_in_alias + bug9236 = '[Bug #9236]' + + fun = labeled_module("Fun") do + def hello + orig_hello + end + end + + m1 = labeled_module("M1") do + def hello + 'hello!' + end + end + + m2 = labeled_module("M2") do + def hello + super + end + end + + foo = labeled_class("Foo") do + include m1 + include m2 + + alias orig_hello hello + include fun + end + + 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]' + module M; end + Float.prepend M + assert_nothing_raised(SystemStackError, bug10847) do + 0.3.numerator + end + 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) + m2 = Module.new + m2.send(:include, m) + m2.class_variable_set(:@@bar, 2) + assert_equal([:@@foo], m.class_variables) + 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 + + Bug6891 = '[ruby-core:47241]' + + def test_extend_module_with_protected_method + list = [] + + x = Class.new { + @list = list + + extend Module.new { + protected + + def inherited(klass) + @list << "protected" + super(klass) + end + } + + extend Module.new { + def inherited(klass) + @list << "public" + super(klass) + end + } + } + + assert_nothing_raised(NoMethodError, Bug6891) {Class.new(x)} + assert_equal(['public', 'protected'], list) + end + + def test_extend_module_with_protected_bmethod + list = [] + + x = Class.new { + extend Module.new { + protected + + define_method(:inherited) do |klass| + list << "protected" + super(klass) + end + } + + extend Module.new { + define_method(:inherited) do |klass| + list << "public" + super(klass) + end + } + } + + assert_nothing_raised(NoMethodError, Bug6891) {Class.new(x)} + 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? + @foo + @@foo + $foo + \u3042$ + ].each do |name| + assert_raise_with_message(NameError, /#{Regexp.quote(quote(name))}/) do + Module.new { attr_accessor name.to_sym } + end + end + end + + private def quote(name) + encoding = Encoding.default_internal || Encoding.default_external + (name.encoding == encoding || name.ascii_only?) ? name : name.inspect + end + + class AttrTest + class << self + attr_accessor :cattr + end + attr_accessor :iattr + def ivar + @ivar + end + end + + def test_uninitialized_instance_variable + a = AttrTest.new + assert_warning(/instance variable @ivar not initialized/) do + assert_nil(a.ivar) + end + a.instance_variable_set(:@ivar, 42) + 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 + a = AttrTest.new + assert_warning '' do + assert_nil(a.iattr) + end + a.iattr = 42 + assert_warning '' do + assert_equal(42, a.iattr) + end + end + + def test_uninitialized_attr_class + assert_warning '' do + assert_nil(AttrTest.cattr) + end + AttrTest.cattr = 42 + assert_warning '' do + assert_equal(42, AttrTest.cattr) + end + end + + def test_uninitialized_attr_non_object + a = Class.new(Array) do + attr_accessor :iattr + end.new + assert_warning '' do + assert_nil(a.iattr) + end + a.iattr = 42 + assert_warning '' do + assert_equal(42, a.iattr) + end + end + + def test_remove_const + m = Module.new + 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) + assert_top_method_is_private(:private) + assert_top_method_is_private(:define_method) + end + + module PrivateConstantReopen + PRIVATE_CONSTANT = true + private_constant :PRIVATE_CONSTANT + end + + def test_private_constant_reopen + assert_raise(NameError) do + eval <<-EOS, TOPLEVEL_BINDING + module TestModule::PrivateConstantReopen::PRIVATE_CONSTANT + end + EOS + end + assert_raise(NameError) do + eval <<-EOS, TOPLEVEL_BINDING + class TestModule::PrivateConstantReopen::PRIVATE_CONSTANT + end + EOS + 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 + assert_equal [obj.singleton_class, Object], obj.singleton_class.ancestors.first(2), feature8035 + + mod = Module.new + obj.extend mod + assert_equal [obj.singleton_class, mod, Object], obj.singleton_class.ancestors.first(3) + + obj = Object.new + obj.singleton_class.send :prepend, mod + assert_equal [mod, obj.singleton_class, Object], obj.singleton_class.ancestors.first(3) + end + + def test_visibility_by_public_class_method + bug8284 = '[ruby-core:54404] [Bug #8284]' + 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_does_not_invalidate_method_cache + assert_in_out_err([], <<-RUBY, %w(123 456 true), []) + A = 123 + + class Foo + def self.a + A + end + end + + module M + A = 456 + end + + puts Foo.a + starting = RubyVM.stat[:global_method_state] + + Foo.send(:include, M) + + ending = RubyVM.stat[:global_method_state] + puts Foo.a + puts starting == ending + RUBY + end + + def test_return_value_of_define_method + retvals = [] + Class.new.class_eval do + retvals << define_method(:foo){} + retvals << define_method(:bar, instance_method(:foo)) + end + assert_equal :foo, retvals[0] + assert_equal :bar, retvals[1] + end + + def test_return_value_of_define_singleton_method + retvals = [] + Class.new do + retvals << define_singleton_method(:foo){} + retvals << define_singleton_method(:bar, method(:foo)) + end + assert_equal :foo, retvals[0] + assert_equal :bar, retvals[1] + end + + def test_prepend_gc + assert_separately [], %{ + module Foo + end + class Object + prepend Foo + end + GC.start # make created T_ICLASS old (or remembered shady) + class Object # add methods into T_ICLASS (need WB if it is old) + def foo; end + attr_reader :bar + end + 1_000_000.times{''} # cause GC + } + end + + def test_inspect_segfault + bug_10282 = '[ruby-core:65214] [Bug #10282]' + assert_separately [], <<-RUBY + module ShallowInspect + def shallow_inspect + "foo" + end + end + + module InspectIsShallow + include ShallowInspect + alias_method :inspect, :shallow_inspect + end + + class A + end + + A.prepend InspectIsShallow + + 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) + assert_separately [], %{ + methods = singleton_class.private_instance_methods(false) + assert_include(methods, :#{method}, ":#{method} should be private") + + assert_raise_with_message(NoMethodError, "private method `#{method}' called for main:Object") { + self.#{method} + } + } + end end diff --git a/test/ruby/test_not.rb b/test/ruby/test_not.rb new file mode 100644 index 0000000000..12e4c4b696 --- /dev/null +++ b/test/ruby/test_not.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestNot < Test::Unit::TestCase + def test_not_with_grouped_expression + assert_equal(false, (not (true))) + assert_equal(true, (not (false))) + end + + def test_not_with_empty_grouped_expression + assert_equal(true, (not ())) + end +end diff --git a/test/ruby/test_notimp.rb b/test/ruby/test_notimp.rb index dfe51683c9..daa5a82d7b 100644 --- a/test/ruby/test_notimp.rb +++ b/test/ruby/test_notimp.rb @@ -1,32 +1,53 @@ +# frozen_string_literal: false require 'test/unit' +require 'timeout' require 'tmpdir' class TestNotImplement < Test::Unit::TestCase def test_respond_to_fork - assert_includes(Process.methods, :fork) + assert_include(Process.methods, :fork) if /linux/ =~ RUBY_PLATFORM assert_equal(true, Process.respond_to?(:fork)) end end def test_respond_to_lchmod - assert_includes(File.methods, :lchmod) - if /linux/ =~ RUBY_PLATFORM - assert_equal(false, File.respond_to?(:lchmod)) - end - if /freebsd/ =~ RUBY_PLATFORM + assert_include(File.methods, :lchmod) + case RUBY_PLATFORM + when /freebsd/, /linux-musl/ assert_equal(true, File.respond_to?(:lchmod)) + when /linux/ + assert_equal(false, File.respond_to?(:lchmod)) end end def test_call_fork - if Process.respond_to?(:fork) - assert_nothing_raised { + GC.start + pid = nil + ps = + case RUBY_PLATFORM + when /linux/ # assume Linux Distribution uses procps + proc {`ps -eLf #{pid}`} + when /freebsd/ + proc {`ps -lH #{pid}`} + when /darwin/ + proc {`ps -lM #{pid}`} + else + proc {`ps -l #{pid}`} + end + assert_nothing_raised(Timeout::Error, ps) do + Timeout.timeout(EnvUtil.apply_timeout_scale(5)) { pid = fork {} Process.wait pid + pid = nil } end - end + ensure + if pid + Process.kill(:KILL, pid) + Process.wait pid + end + end if Process.respond_to?(:fork) def test_call_lchmod if File.respond_to?(:lchmod) @@ -36,9 +57,14 @@ class TestNotImplement < Test::Unit::TestCase File.open(f, "w") {} File.symlink f, g newmode = 0444 - File.lchmod newmode, "#{d}/g" - snew = File.lstat(g) - assert_equal(newmode, snew.mode & 0777) + begin + File.lchmod newmode, "#{d}/g" + rescue Errno::EOPNOTSUPP + skip $! + else + snew = File.lstat(g) + assert_equal(newmode, snew.mode & 0777) + end } end end diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index f4fbea4ce9..e48c3448e8 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -1,135 +1,194 @@ +# frozen_string_literal: false require 'test/unit' 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) assert_equal(Float, b.class) assert_raise(TypeError) { -Numeric.new } + + 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 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 + 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(1 <= a) + assert_operator(1, :<=, a) - DummyNumeric.class_eval do + 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_numeric + def test_dup 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.dup + end + + def test_clone + a = Numeric.new + 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(ArgumentError) {DummyNumeric.new.quo(1)} + a = Numeric.new + assert_raise(TypeError) {a.quo(1)} + end + + def test_quo_ruby_core_41575 + rat = 84.quo(1) + x = Class.new(Numeric) do + define_method(:to_r) { rat } + end.new + assert_equal(2.quo(1), x.quo(42), '[ruby-core:41575]') 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 - assert(Numeric.new.real?) + assert_predicate(Numeric.new, :real?) end def test_integer_p - assert(!Numeric.new.integer?) + assert_not_predicate(Numeric.new, :integer?) 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 + 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(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 @@ -139,69 +198,145 @@ 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 + 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 + 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 test_step - a = [] - 1.step(10) {|x| a << x } - assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + def assert_step(expected, (from, *args), inf: false) + enum = from.step(*args) + size = enum.size + xsize = expected.size - a = [] - 1.step(10, 2) {|x| a << x } - assert_equal([1, 3, 5, 7, 9], a) + if inf + assert_send [size, :infinite?], "step size: +infinity" + assert_send [size, :>, 0], "step size: +infinity" - assert_raise(ArgumentError) { 1.step(10, 1, 0) { } } - assert_raise(ArgumentError) { 1.step(10, 0) { } } + a = [] + from.step(*args) { |x| a << x; break if a.size == xsize } + assert_equal expected, a, "step" - a = [] - 10.step(1, -2) {|x| a << x } - assert_equal([10, 8, 6, 4, 2], a) + a = [] + enum.each { |x| a << x; break if a.size == xsize } + assert_equal expected, a, "step enumerator" + else + assert_equal expected.size, size, "step size" - a = [] - 1.0.step(10.0, 2.0) {|x| a << x } - assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a) + a = [] + from.step(*args) { |x| a << x } + assert_equal expected, a, "step" - a = [] - 1.step(10, 2**32) {|x| a << x } - assert_equal([1], a) + a = [] + enum.each { |x| a << x } + assert_equal expected, a, "step enumerator" + end + end - a = [] - 10.step(1, -(2**32)) {|x| a << x } - assert_equal([10], a) + def test_step + 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) { } } + assert_raise(ArgumentError) { 1.step(10, "1") { } } + assert_raise(ArgumentError) { 1.step(10, "1").size } + assert_raise(TypeError) { 1.step(10, nil) { } } + assert_nothing_raised { 1.step(10, 0).size } + assert_nothing_raised { 1.step(10, nil).size } + assert_nothing_raised { 1.step(by: 0, to: nil) } + assert_nothing_raised { 1.step(by: 0, to: nil).size } + assert_nothing_raised { 1.step(by: 0) } + assert_nothing_raised { 1.step(by: 0).size } + assert_nothing_raised { 1.step(by: nil) } + assert_nothing_raised { 1.step(by: nil).size } + + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: nil)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: 10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: -1)) + + bug9811 = '[ruby-dev:48177] [Bug #9811]' + assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} } + assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size } + assert_raise(ArgumentError, bug9811) { 1.step(10, to: 11) {} } + assert_raise(ArgumentError, bug9811) { 1.step(10, to: 11).size } + assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11) {} } + assert_raise(ArgumentError, bug9811) { 1.step(10, 1, by: 11).size } + + assert_equal(bignum*2+1, (-bignum).step(bignum, 1).size) + assert_equal(bignum*2, (-bignum).step(bignum-1, 1).size) + + assert_equal(10+1, (0.0).step(10.0, 1.0).size) + + i, bigflo = 1, bignum.to_f + i <<= 1 until (bigflo - i).to_i < bignum + bigflo -= i >> 1 + assert_equal(bigflo.to_i, (0.0).step(bigflo-1.0, 1.0).size) + + assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 10] + assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, to: 10] + assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, to: 10, by: nil] + assert_step [1, 3, 5, 7, 9], [1, 10, 2] + assert_step [1, 3, 5, 7, 9], [1, to: 10, by: 2] + + assert_step [10, 8, 6, 4, 2], [10, 1, -2] + assert_step [10, 8, 6, 4, 2], [10, to: 1, by: -2] + assert_step [1.0, 3.0, 5.0, 7.0, 9.0], [1.0, 10.0, 2.0] + assert_step [1.0, 3.0, 5.0, 7.0, 9.0], [1.0, to: 10.0, by: 2.0] + assert_step [1], [1, 10, bignum] + assert_step [1], [1, to: 10, by: bignum] + + assert_step [], [2, 1, 3] + assert_step [], [-2, -1, -3] + assert_step [3, 3, 3, 3], [3, by: 0], inf: true + assert_step [3, 3, 3, 3], [3, by: 0, to: 42], inf: true + assert_step [10], [10, 1, -bignum] + + assert_step [], [1, 0, Float::INFINITY] + assert_step [], [0, 1, -Float::INFINITY] + assert_step [10], [10, to: 1, by: -bignum] + + assert_step [10, 11, 12, 13], [10], inf: true + assert_step [10, 9, 8, 7], [10, by: -1], inf: true + assert_step [10, 9, 8, 7], [10, by: -1, to: nil], inf: true + + assert_step [42, 42, 42, 42], [42, by: 0, to: -Float::INFINITY], inf: true + assert_step [42, 42, 42, 42], [42, by: 0, to: 42.5], inf: true + assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: 0.0], inf: true + assert_step [4.2, 4.2, 4.2, 4.2], [4.2, by: -0.0], inf: true + assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 44], inf: true + assert_step [42.0, 42.0, 42.0, 42.0], [42, by: 0.0, to: 0], inf: true + assert_step [42.0, 42.0, 42.0, 42.0], [42, by: -0.0, to: 44], inf: true + + assert_step [bignum]*4, [bignum, by: 0], inf: true + assert_step [bignum]*4, [bignum, by: 0.0], inf: true + assert_step [bignum]*4, [bignum, by: 0, to: bignum+1], inf: true + assert_step [bignum]*4, [bignum, by: 0, to: 0], inf: true end def test_num2long @@ -211,12 +346,72 @@ class TestNumeric < Test::Unit::TestCase assert_raise(TypeError) { 1 & 9223372036854777856.0 } o = Object.new def o.to_int; 1; end - assert_equal(1, 1 & o) + assert_raise(TypeError) { assert_equal(1, 1 & o) } end def test_eql - assert(1 == 1.0) - assert(!(1.eql?(1.0))) - assert(!(1.eql?(2))) + assert_equal(1, 1.0) + 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 63c1d32bef..28e07162d7 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -1,5 +1,6 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestObject < Test::Unit::TestCase def setup @@ -11,16 +12,82 @@ 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 + def initialize_dup(orig); throw :initialize_dup; end + end + + obj = cls.new + assert_throw(:initialize_clone) {obj.clone} + assert_throw(:initialize_dup) {obj.dup} + end + def test_instance_of assert_raise(TypeError) { 1.instance_of?(1) } end @@ -32,29 +99,33 @@ 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 } - end - - def test_freeze_under_safe_4 - o = Object.new - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - o.freeze - end.join - end + assert_raise(FrozenError) { o.untaint } end def test_freeze_immediate - assert_equal(false, 1.frozen?) + assert_equal(true, 1.frozen?) 1.freeze assert_equal(true, 1.frozen?) - assert_equal(false, 2.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 @@ -147,19 +218,49 @@ class TestObject < Test::Unit::TestCase assert_equal([:foo2], (o2.public_methods(false) - o0.public_methods(false)).sort) end + def test_methods_prepend + bug8044 = '[ruby-core:53207] [Bug #8044]' + o = Object.new + def o.foo; end + assert_equal([:foo], o.methods(false)) + class << o; prepend Module.new; end + assert_equal([:foo], o.methods(false), bug8044) + end + def test_instance_variable_get o = Object.new o.instance_eval { @foo = :foo } assert_equal(:foo, o.instance_variable_get(:@foo)) assert_equal(nil, o.instance_variable_get(:@bar)) + assert_raise(NameError) { o.instance_variable_get('@') } + assert_raise(NameError) { o.instance_variable_get(:'@') } assert_raise(NameError) { o.instance_variable_get(:foo) } + assert_raise(NameError) { o.instance_variable_get("bar") } + assert_raise(TypeError) { o.instance_variable_get(1) } + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end + def n.count; @count; end + assert_equal(:foo, o.instance_variable_get(n)) + assert_equal(1, n.count) end def test_instance_variable_set o = Object.new o.instance_variable_set(:@foo, :foo) assert_equal(:foo, o.instance_eval { @foo }) + assert_raise(NameError) { o.instance_variable_set(:'@', 1) } + assert_raise(NameError) { o.instance_variable_set('@', 1) } assert_raise(NameError) { o.instance_variable_set(:foo, 1) } + assert_raise(NameError) { o.instance_variable_set("bar", 1) } + assert_raise(TypeError) { o.instance_variable_set(1, 1) } + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end + def n.count; @count; end + o.instance_variable_set(n, :bar) + assert_equal(:bar, o.instance_eval { @foo }) + assert_equal(1, n.count) end def test_instance_variable_defined @@ -167,32 +268,85 @@ class TestObject < Test::Unit::TestCase o.instance_eval { @foo = :foo } assert_equal(true, o.instance_variable_defined?(:@foo)) assert_equal(false, o.instance_variable_defined?(:@bar)) + assert_raise(NameError) { o.instance_variable_defined?(:'@') } + assert_raise(NameError) { o.instance_variable_defined?('@') } assert_raise(NameError) { o.instance_variable_defined?(:foo) } + assert_raise(NameError) { o.instance_variable_defined?("bar") } + assert_raise(TypeError) { o.instance_variable_defined?(1) } + + n = Object.new + def n.to_str; @count = defined?(@count) ? @count + 1 : 1; "@foo"; end + def n.count; @count; end + assert_equal(true, o.instance_variable_defined?(n)) + assert_equal(1, n.count) end def test_remove_instance_variable - o = Object.new - o.instance_eval { @foo = :foo } - o.instance_eval { 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 + + def test_convert_hash + assert_equal({}, Hash(nil)) + assert_equal({}, Hash([])) + assert_equal({key: :value}, Hash(key: :value)) + assert_raise(TypeError) { Hash([1,2]) } + assert_raise(TypeError) { Hash(Object.new) } + o = Object.new + def o.to_hash; {a: 1, b: 2}; end + assert_equal({a: 1, b: 2}, Hash(o)) + def o.to_hash; 9; end + assert_raise(TypeError) { Hash(o) } end def test_to_integer o = Object.new def o.to_i; nil; end assert_raise(TypeError) { Integer(o) } + def o.to_i; 42; end + assert_equal(42, Integer(o)) + def o.respond_to?(*) false; end + assert_raise(TypeError) { Integer(o) } end class MyInteger @@ -211,17 +365,6 @@ class TestObject < Test::Unit::TestCase assert_equal(1+3+5+7+9, n) end - def test_add_method_under_safe4 - o = Object.new - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - def o.foo - end - end.join - end - end - def test_redefine_method_under_verbose assert_in_out_err([], <<-INPUT, %w(2), /warning: method redefined; discarding old foo$/) $VERBOSE = true @@ -233,35 +376,30 @@ 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 + + bug10421 = '[ruby-dev:48691] [Bug #10421]' + assert_in_out_err([], <<-INPUT, ["1"], [], bug10421) + $VERBOSE = false + class C < BasicObject + def object_id; 1; end + end + puts C.new.object_id + INPUT end def test_remove_method - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - Object.instance_eval { remove_method(:foo) } - end.join - end - - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - Class.instance_eval { remove_method(:foo) } - end.join - end - c = Class.new c.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do c.instance_eval { remove_method(:foo) } end @@ -286,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}) } @@ -295,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 @@ -334,16 +485,29 @@ class TestObject < Test::Unit::TestCase assert_nothing_raised(bug2494) {[b].flatten} end + def test_respond_to_missing_string + c = Class.new do + def respond_to_missing?(id, priv) + !(id !~ /\Agadzoks\d+\z/) ^ priv + end + end + foo = c.new + assert_equal(false, foo.respond_to?("gadzooks16")) + assert_equal(true, foo.respond_to?("gadzooks17", true)) + assert_equal(true, foo.respond_to?("gadzoks16")) + assert_equal(false, foo.respond_to?("gadzoks17", true)) + end + def test_respond_to_missing c = Class.new do - def respond_to_missing?(id, priv=false) + def respond_to_missing?(id, priv) if id == :foobar true else false end end - def method_missing(id,*args) + def method_missing(id, *args) if id == :foobar return [:foo, *args] else @@ -356,8 +520,8 @@ class TestObject < Test::Unit::TestCase assert_equal([:foo], foo.foobar); assert_equal([:foo, 1], foo.foobar(1)); assert_equal([:foo, 1, 2, 3, 4, 5], foo.foobar(1, 2, 3, 4, 5)); - assert(foo.respond_to?(:foobar)) - assert_equal(false, foo.respond_to?(:foobarbaz)) + assert_respond_to(foo, :foobar) + assert_not_respond_to(foo, :foobarbaz) assert_raise(NoMethodError) do foo.foobarbaz end @@ -383,10 +547,163 @@ class TestObject < Test::Unit::TestCase end end + def test_implicit_respond_to + bug5158 = '[ruby-core:38799]' + + p = Object.new + + called = [] + p.singleton_class.class_eval do + define_method(:to_ary) do + called << [:to_ary, bug5158] + end + end + [[p]].flatten + assert_equal([[:to_ary, bug5158]], called, bug5158) + + called = [] + p.singleton_class.class_eval do + define_method(:respond_to?) do |*a| + called << [:respond_to?, *a] + false + end + end + [[p]].flatten + assert_equal([[:respond_to?, :to_ary, true]], called, bug5158) + end + + def test_implicit_respond_to_arity_1 + p = Object.new + + called = [] + p.singleton_class.class_eval do + define_method(:respond_to?) do |a| + called << [:respond_to?, a] + false + end + end + [[p]].flatten + assert_equal([[:respond_to?, :to_ary]], called, '[bug:6000]') + end + + def test_implicit_respond_to_arity_3 + p = Object.new + + called = [] + p.singleton_class.class_eval do + define_method(:respond_to?) do |a, b, c| + called << [:respond_to?, a, b, c] + false + end + end + + msg = 'respond_to? must accept 1 or 2 arguments (requires 3)' + assert_raise_with_message(ArgumentError, msg, '[bug:6000]') do + [[p]].flatten + end + end + + def test_method_missing_passed_block + bug5731 = '[ruby-dev:44961]' + + c = Class.new do + def method_missing(meth, *args) yield(meth, *args) end + end + a = c.new + result = nil + assert_nothing_raised(LocalJumpError, bug5731) do + a.foo {|x| result = x} + end + assert_equal(:foo, result, bug5731) + result = nil + e = a.enum_for(:foo) + assert_nothing_raised(LocalJumpError, bug5731) do + e.each {|x| result = x} + end + assert_equal(:foo, result, bug5731) + + c = Class.new do + def respond_to_missing?(id, priv) + true + end + def method_missing(id, *args, &block) + return block.call(:foo, *args) + end + end + foo = c.new + + result = nil + assert_nothing_raised(LocalJumpError, bug5731) do + foo.foobar {|x| result = x} + end + assert_equal(:foo, result, bug5731) + result = nil + assert_nothing_raised(LocalJumpError, bug5731) do + foo.enum_for(:foobar).each {|x| result = x} + end + assert_equal(:foo, result, bug5731) + + result = nil + foobar = foo.method(:foobar) + foobar.call {|x| result = x} + assert_equal(:foo, result, bug5731) + + result = nil + foobar = foo.method(:foobar) + foobar.enum_for(:call).each {|x| result = x} + assert_equal(:foo, result, bug5731) + end + def test_send_with_no_arguments assert_raise(ArgumentError) { 1.send } end + def test_send_with_block + x = :ng + 1.send(:times) { x = :ok } + assert_equal(:ok, x) + + x = :ok + o = Object.new + def o.inspect + yield if block_given? + super + end + begin + nil.public_send(o) { x = :ng } + rescue TypeError + end + assert_equal(:ok, x) + end + + def test_public_send + c = Class.new do + def pub + :ok + end + + def invoke(m) + public_send(m) + end + + protected + def prot + :ng + end + + private + def priv + :ng + end + end.new + assert_equal(:ok, c.public_send(:pub)) + assert_raise(NoMethodError) {c.public_send(:priv)} + assert_raise(NoMethodError) {c.public_send(:prot)} + assert_raise(NoMethodError) {c.invoke(:priv)} + bug7499 = '[ruby-core:50489]' + assert_raise(NoMethodError, bug7499) {c.invoke(:prot)} + end + def test_no_superclass_method bug2312 = '[ruby-dev:39581]' @@ -449,117 +766,95 @@ class TestObject < Test::Unit::TestCase end def test_untrusted - obj = lambda { - $SAFE = 4 - x = Object.new - x.instance_eval { @foo = 1 } - x - }.call - assert_equal(true, obj.untrusted?) - assert_equal(true, obj.tainted?) - - x = Object.new - assert_equal(false, x.untrusted?) - assert_raise(SecurityError) do - lambda { - $SAFE = 4 - x.instance_eval { @foo = 1 } - }.call + verbose = $VERBOSE + $VERBOSE = false + begin + obj = Object.new + assert_equal(false, obj.untrusted?) + assert_equal(false, obj.tainted?) + obj.untrust + assert_equal(true, obj.untrusted?) + assert_equal(true, obj.tainted?) + obj.trust + assert_equal(false, obj.untrusted?) + assert_equal(false, obj.tainted?) + obj.taint + assert_equal(true, obj.untrusted?) + assert_equal(true, obj.tainted?) + obj.untaint + assert_equal(false, obj.untrusted?) + assert_equal(false, obj.tainted?) + ensure + $VERBOSE = verbose end + end + def test_to_s x = Object.new x.taint - assert_raise(SecurityError) do - lambda { - $SAFE = 4 - x.instance_eval { @foo = 1 } - }.call - end + s = x.to_s + assert_equal(true, s.tainted?) - x.untrust - assert_equal(true, x.untrusted?) - assert_nothing_raised do - lambda { - $SAFE = 4 - x.instance_eval { @foo = 1 } - }.call - end + x = eval(<<-EOS) + class ToS\u{3042} + new.to_s + end + EOS + assert_match(/\bToS\u{3042}:/, x) - x.trust - assert_equal(false, x.untrusted?) - assert_raise(SecurityError) do - lambda { - $SAFE = 4 - x.instance_eval { @foo = 1 } - }.call - end + 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 - a = Object.new - a.untrust - assert_equal(true, a.untrusted?) - b = a.dup - assert_equal(true, b.untrusted?) - c = a.clone - assert_equal(true, c.untrusted?) + def test_inspect + x = Object.new + assert_match(/\A#<Object:0x\h+>\z/, x.inspect) - a = Object.new - b = lambda { - $SAFE = 4 - a.dup - }.call - assert_equal(true, b.untrusted?) + x.instance_variable_set(:@ivar, :value) + assert_match(/\A#<Object:0x\h+ @ivar=:value>\z/, x.inspect) - a = Object.new - b = lambda { - $SAFE = 4 - a.clone - }.call - assert_equal(true, b.untrusted?) - end - - def test_to_s x = Object.new - x.taint - x.untrust - s = x.to_s - assert_equal(true, s.untrusted?) - assert_equal(true, s.tainted?) - end + x.instance_variable_set(:@recur, x) + assert_match(/\A#<Object:0x\h+ @recur=#<Object:0x\h+ \.\.\.>>\z/, x.inspect) - def test_exec_recursive - Thread.current[:__recursive_key__] = nil - a = [[]] - a.inspect + x = Object.new + x.instance_variable_set(:@foo, "value") + x.instance_variable_set(:@bar, 42) + assert_match(/\A#<Object:0x\h+ (?:@foo="value", @bar=42|@bar=42, @foo="value")>\z/, x.inspect) - assert_nothing_raised do - -> do - $SAFE = 4 - begin - a.hash - rescue ArgumentError - end - end.call + # #inspect does not call #to_s anymore + feature6130 = '[ruby-core:43238]' + x = Object.new + def x.to_s + "to_s" end + assert_match(/\A#<Object:0x\h+>\z/, x.inspect, feature6130) - -> do - assert_nothing_raised do - $SAFE = 4 - a.inspect + x = eval(<<-EOS) + class Inspect\u{3042} + new.inspect end - end.call + EOS + assert_match(/\bInspect\u{3042}:/, x) - -> do - o = Object.new - def o.to_ary(x); end - def o.==(x); $SAFE = 4; false; end - a = [[o]] - b = [] - b << b - - assert_nothing_raised do - b == a + x = eval(<<-EOS) + class Inspect\u{3042} + def initialize + @\u{3044} = 42 + end + new end - end.call + EOS + 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 @@ -582,4 +877,73 @@ class TestObject < Test::Unit::TestCase :foo.singleton_class end end + + def test_redef_method_missing + bug5473 = '[ruby-core:40287]' + ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code| + exc = code[/\A[A-Z]\w+/] || 'RuntimeError' + assert_separately([], <<-SRC) + $VERBOSE = nil + class ::Object + def method_missing(m, *a, &b) + raise #{code} + end + end + + assert_raise_with_message(#{exc}, "bug5473", #{bug5473.dump}) {1.foo} + SRC + end + end + + def assert_not_initialize_copy + a = yield + b = yield + assert_nothing_raised("copy") {a.instance_eval {initialize_copy(b)}} + c = a.dup.freeze + assert_raise(FrozenError, "frozen") {c.instance_eval {initialize_copy(b)}} + d = a.dup.trust + [a, b, c, d] + end + + def test_bad_initialize_copy + assert_not_initialize_copy {Object.new} + assert_not_initialize_copy {[].to_enum} + assert_not_initialize_copy {Enumerator::Generator.new {}} + assert_not_initialize_copy {Enumerator::Yielder.new {}} + assert_not_initialize_copy {File.stat(__FILE__)} + assert_not_initialize_copy {open(__FILE__)}.each(&:close) + assert_not_initialize_copy {ARGF.class.new} + assert_not_initialize_copy {Random.new} + assert_not_initialize_copy {//} + assert_not_initialize_copy {/.*/.match("foo")} + st = Struct.new(:foo) + assert_not_initialize_copy {st.new} + end + + def test_type_error_message + _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, 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 24731a7a50..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) @@ -39,13 +39,13 @@ End h = {} ObjectSpace.count_objects(h) assert_kind_of(Hash, h) - assert(h.keys.all? {|x| x.is_a?(Symbol) || x.is_a?(Integer) }) - assert(h.values.all? {|x| x.is_a?(Integer) }) + assert_empty(h.keys.delete_if {|x| x.is_a?(Symbol) || x.is_a?(Integer) }) + assert_empty(h.values.delete_if {|x| x.is_a?(Integer) }) h = ObjectSpace.count_objects assert_kind_of(Hash, h) - assert(h.keys.all? {|x| x.is_a?(Symbol) || x.is_a?(Integer) }) - assert(h.values.all? {|x| x.is_a?(Integer) }) + assert_empty(h.keys.delete_if {|x| x.is_a?(Symbol) || x.is_a?(Integer) }) + assert_empty(h.values.delete_if {|x| x.is_a?(Integer) }) assert_raise(TypeError) { ObjectSpace.count_objects(1) } @@ -64,5 +64,143 @@ End !b END assert_raise(ArgumentError) { ObjectSpace.define_finalizer([], Object.new) } + + code = proc do |priv| + <<-"CODE" + fin = Object.new + class << fin + #{priv}def call(id) + puts "finalized" + end + end + ObjectSpace.define_finalizer([], fin) + CODE + 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') + arys = [] + ObjectSpace.each_object(Array){|ary| + arys << ary + } + GC.enable + arys.each{|ary| + begin + assert_equal(String, ary.inspect.class) # should not cause SEGV + rescue RuntimeError + # rescue "can't modify frozen File" error. + end + } + End + end + + def test_each_object_recursive_key + assert_normal_exit(<<-'end;', '[ruby-core:66742] [Bug #10579]') + h = {["foo"]=>nil} + 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 8e8311e6ef..bc3eacce52 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -1,88 +1,124 @@ +# frozen_string_literal: false require 'test/unit' +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_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_redefine_method('String', 'empty?', 'assert_nil "string".empty?') end def test_string_plus @@ -90,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 @@ -101,24 +136,120 @@ 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_fixnum_and + assert_equal 1, 1&3 + assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3') + end + + def test_fixnum_or + assert_equal 3, 1|3 + assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1') end def test_array_plus assert_equal [1,2], [1]+[2] + assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') 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 @@ -137,4 +268,560 @@ 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]' + + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + 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) + 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]' + + 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 + } + assert_instance_of(RuntimeError, result, bug12082) + assert_equal("should be rescued", result.message, bug12082) + end + + 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 + + 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 = "#{<<-"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 + end; + check = { + 'foo' => :foo, + true => true, + false => false, + :sym => :sym, + 6 => :fix, + nil => nil, + 0.1 => :float, + 0xffffffffffffffff => :big, + } + iseq = RubyVM::InstructionSequence.compile(code) + assert_match %r{\bopt_case_dispatch\b}, iseq.disasm + check.each do |foo, expect| + assert_equal expect, eval("foo = #{foo.inspect}\n#{code}") + end + assert_equal :nomatch, eval("foo = :blah\n#{code}") + check.each do |foo, _| + klass = foo.class.to_s + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class #{klass} + undef === + def ===(*args) + false + end + end + foo = #{foo.inspect} + ret = #{code} + assert_equal :nomatch, ret, foo.inspect + end; + 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 + when 1 then 1 + when 0 then 0 + else + inf.to_i rescue nil + 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; + [ true, false ].each do |opt| + iseq = RubyVM::InstructionSequence.compile(code, + frozen_string_literal: opt) + insn = iseq.disasm + assert_match %r{putobject\s+#{Regexp.quote('"1.8.0"..."1.8.8"')}}, insn + assert_match %r{putobject\s+#{Regexp.quote('"2.0.0".."2.3.2"')}}, insn + assert_no_match(/putstring/, insn) + assert_no_match(/newrange/, insn) + end + end + + def test_peephole_dstr + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + exp = -'a' + z = 'a' + [exp, -"#{z}"] + end; + [ false, true ].each do |fsl| + iseq = RubyVM::InstructionSequence.compile(code, + frozen_string_literal: fsl) + assert_same(*iseq.eval, + "[ruby-core:85542] [Bug #14475] fsl: #{fsl}") + end + end + + def test_branch_condition_backquote + 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 1, 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: 30 + 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_blockparam_in_rescue + obj = Object.new + def obj.foo(&b) + raise + rescue + b.call + end + result = nil + assert_equal(42, obj.foo {result = 42}) + assert_equal(42, result) + end + + def test_unconditional_branch_to_leave_block + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + tap {true || tap {}} + end; + end + + def test_jump_elimination_with_optimized_out_block + x = Object.new + def x.bug(obj) + if obj || obj + obj = obj + else + raise "[ruby-core:87830] [Bug #14897]" + end + obj + end + assert_equal(:ok, x.bug(:ok)) + end + + def test_jump_elimination_with_optimized_out_block_2 + x = Object.new + def x.bug + a = "aaa" + ok = :NG + if a == "bbb" || a == "ccc" then + a = a + else + ok = :ok + end + ok + end + assert_equal(:ok, x.bug) + end + + def test_peephole_jump_after_newarray + i = 0 + %w(1) || 2 while (i += 1) < 100 + assert_equal(100, i) + end + + def test_optimized_empty_ensure + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 1) + 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 a4a6308299..658208d9df 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -1,3 +1,5 @@ +# coding: US-ASCII +# frozen_string_literal: false require 'test/unit' class TestPack < Test::Unit::TestCase @@ -70,73 +72,118 @@ class TestPack < Test::Unit::TestCase assert_equal [1,1,1], "\000\000\000\001\000\000\000\001\000\000\000\001".unpack('N*') end + def _integer_big_endian(mod='') + assert_equal("\x01\x02", [0x0102].pack("s"+mod)) + assert_equal("\x01\x02", [0x0102].pack("S"+mod)) + assert_equal("\x01\x02\x03\x04", [0x01020304].pack("l"+mod)) + 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)) + assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I"+mod)) + assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("i!"+mod)) + 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)) + 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 + s = "".force_encoding("ascii-8bit") + nuls.bytesize.times {|i| + j = i + 40 + v = v * 256 + j + s << [j].pack("C") + } + assert_equal(s, [v].pack(fmt), "[#{v}].pack(#{fmt.dump})") + assert_equal([v], s.unpack(fmt), "#{s.dump}.unpack(#{fmt.dump})") + s2 = s+s + fmt2 = fmt+"*" + assert_equal([v,v], s2.unpack(fmt2), "#{s2.dump}.unpack(#{fmt2.dump})") + } + end + + def _integer_little_endian(mod='') + assert_equal("\x02\x01", [0x0102].pack("s"+mod)) + assert_equal("\x02\x01", [0x0102].pack("S"+mod)) + assert_equal("\x04\x03\x02\x01", [0x01020304].pack("l"+mod)) + 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)) + assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I"+mod)) + assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("i!"+mod)) + 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)) + 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 + s = "".force_encoding("ascii-8bit") + nuls.bytesize.times {|i| + j = i+40 + v = v * 256 + j + s << [j].pack("C") + } + s.reverse! + assert_equal(s, [v].pack(fmt), "[#{v}].pack(#{fmt.dump})") + assert_equal([v], s.unpack(fmt), "#{s.dump}.unpack(#{fmt.dump})") + s2 = s+s + fmt2 = fmt+"*" + assert_equal([v,v], s2.unpack(fmt2), "#{s2.dump}.unpack(#{fmt2.dump})") + } + end + def test_integer_endian s = [1].pack("s") - assert_includes(["\0\1", "\1\0"], s) + assert_include(["\0\1", "\1\0"], s) if s == "\0\1" - # big endian - assert_equal("\x01\x02", [0x0102].pack("s")) - assert_equal("\x01\x02", [0x0102].pack("S")) - assert_equal("\x01\x02\x03\x04", [0x01020304].pack("l")) - assert_equal("\x01\x02\x03\x04", [0x01020304].pack("L")) - assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("q")) - assert_equal("\x01\x02\x03\x04\x05\x06\x07\x08", [0x0102030405060708].pack("Q")) - assert_match(/\A\x00*\x01\x02\z/, [0x0102].pack("s!")) - assert_match(/\A\x00*\x01\x02\z/, [0x0102].pack("S!")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("i")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("i!")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("I!")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("l!")) - assert_match(/\A\x00*\x01\x02\x03\x04\z/, [0x01020304].pack("L!")) - %w[s S l L q Q s! S! i I i! I! l! L!].each {|fmt| - nuls = [0].pack(fmt) - v = 0 - s = "".force_encoding("ascii-8bit") - nuls.bytesize.times {|i| - j = i + 40 - v = v * 256 + j - s << [j].pack("C") - } - assert_equal(s, [v].pack(fmt), "[#{v}].pack(#{fmt.dump})") - assert_equal([v], s.unpack(fmt), "#{s.dump}.unpack(#{fmt.dump})") - s2 = s+s - fmt2 = fmt+"*" - assert_equal([v,v], s2.unpack(fmt2), "#{s2.dump}.unpack(#{fmt2.dump})") - } + _integer_big_endian() else - # little endian - assert_equal("\x02\x01", [0x0102].pack("s")) - assert_equal("\x02\x01", [0x0102].pack("S")) - assert_equal("\x04\x03\x02\x01", [0x01020304].pack("l")) - assert_equal("\x04\x03\x02\x01", [0x01020304].pack("L")) - assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("q")) - assert_equal("\x08\x07\x06\x05\x04\x03\x02\x01", [0x0102030405060708].pack("Q")) - assert_match(/\A\x02\x01\x00*\z/, [0x0102].pack("s!")) - assert_match(/\A\x02\x01\x00*\z/, [0x0102].pack("S!")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("i")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("i!")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("I!")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("l!")) - assert_match(/\A\x04\x03\x02\x01\x00*\z/, [0x01020304].pack("L!")) - %w[s S l L q Q s! S! i I i! I! l! L!].each {|fmt| - nuls = [0].pack(fmt) - v = 0 - s = "".force_encoding("ascii-8bit") - nuls.bytesize.times {|i| - j = i+40 - v = v * 256 + j - s << [j].pack("C") - } - s.reverse! - assert_equal(s, [v].pack(fmt), "[#{v}].pack(#{fmt.dump})") - assert_equal([v], s.unpack(fmt), "#{s.dump}.unpack(#{fmt.dump})") - s2 = s+s - fmt2 = fmt+"*" - assert_equal([v,v], s2.unpack(fmt2), "#{s2.dump}.unpack(#{fmt2.dump})") - } + _integer_little_endian() end + assert_equal("\x01\x02\x02\x01", [0x0102,0x0102].pack("s>s<")) + assert_equal([0x0102,0x0102], "\x01\x02\x02\x01".unpack("s>s<")) + end + + def test_integer_endian_explicit + _integer_big_endian('>') + _integer_little_endian('<') end def test_pack_U @@ -165,6 +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_equal a, (a.pack("p") << "d").unpack("p*") end def test_format_string_modified @@ -265,6 +313,9 @@ class TestPack < Test::Unit::TestCase assert_equal(["1"], "\x80".unpack("B1")) assert_equal(["10"], "\x80".unpack("B2")) assert_equal(["100"], "\x80".unpack("B3")) + + assert_equal(Encoding::US_ASCII, "\xff\x00".unpack("b*")[0].encoding) + assert_equal(Encoding::US_ASCII, "\xff\x00".unpack("B*")[0].encoding) end def test_pack_unpack_hH @@ -305,6 +356,9 @@ class TestPack < Test::Unit::TestCase assert_equal(["10e"], "\x10\xef".unpack("H3")) assert_equal(["10ef"], "\x10\xef".unpack("H4")) assert_equal(["10ef"], "\x10\xef".unpack("H5")) + + assert_equal(Encoding::US_ASCII, "\x10\xef".unpack("h*")[0].encoding) + assert_equal(Encoding::US_ASCII, "\x10\xef".unpack("H*")[0].encoding) end def test_pack_unpack_cC @@ -374,6 +428,7 @@ class TestPack < Test::Unit::TestCase assert_operator(4, :<=, [1].pack("L!").bytesize) end + require 'rbconfig' def test_pack_unpack_qQ s1 = [578437695752307201, -506097522914230529].pack("q*") s2 = [578437695752307201, 17940646550795321087].pack("Q*") @@ -381,8 +436,55 @@ class TestPack < Test::Unit::TestCase assert_equal([578437695752307201, -506097522914230529], s2.unpack("q*")) assert_equal([578437695752307201, 17940646550795321087], s1.unpack("Q*")) + # Note: q! and Q! should not work on platform which has no long long type. + # Is there a such platform now? + # @shyouhei: Yes. gcc -ansi is one of such platform. + s1 = [578437695752307201, -506097522914230529].pack("q!*") + s2 = [578437695752307201, 17940646550795321087].pack("Q!*") + assert_equal([578437695752307201, -506097522914230529], s2.unpack("q!*")) + assert_equal([578437695752307201, 17940646550795321087], s1.unpack("Q!*")) + assert_equal(8, [1].pack("q").bytesize) assert_equal(8, [1].pack("Q").bytesize) + assert_operator(8, :<=, [1].pack("q!").bytesize) + assert_operator(8, :<=, [1].pack("Q!").bytesize) + end if RbConfig::CONFIG['HAVE_LONG_LONG'] + + def test_pack_unpack_jJ + # Note: we assume that the size of intptr_t and uintptr_t equals to the size + # 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 @@ -414,7 +516,7 @@ class TestPack < Test::Unit::TestCase %w(f d e E g G).each do |f| v = [x].pack(f).unpack(f) if x.nan? - assert(v.first.nan?) + assert_predicate(v.first, :nan?) else assert_equal([x], v) end @@ -448,6 +550,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 @@ -481,6 +586,10 @@ class TestPack < Test::Unit::TestCase assert_equal("M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n", ["a"*46].pack("u0")) assert_equal("M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n", ["a"*46].pack("u1")) assert_equal("M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n", ["a"*46].pack("u2")) + assert_equal(<<EXPECTED, ["a"*80].pack("u68")) +_86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A +186%A86%A86%A86%A86%A86$` +EXPECTED assert_equal([""], "".unpack("u")) assert_equal(["a"], "!80``\n".unpack("u")) @@ -490,6 +599,11 @@ class TestPack < Test::Unit::TestCase 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 @@ -510,6 +624,18 @@ class TestPack < Test::Unit::TestCase assert_equal(["\377"], "/w==\n".unpack("m")) assert_equal(["\377\377"], "//8=\n".unpack("m")) assert_equal(["\377\377\377"], "////\n".unpack("m")) + assert_equal([""], "A\n".unpack("m")) + assert_equal(["\0"], "AA\n".unpack("m")) + assert_equal(["\0"], "AA=\n".unpack("m")) + assert_equal(["\0\0"], "AAA\n".unpack("m")) + + bug10019 = '[ruby-core:63604] [Bug #10019]' + size = ((4096-4)/4*3+1) + assert_separately(%W[- #{size} #{bug10019}], <<-'end;') + size = ARGV.shift.to_i + bug = ARGV.shift + assert_equal(size, ["a"*size].pack("m#{size+2}").unpack("m")[0].size, bug) + end; end def test_pack_unpack_m0 @@ -553,8 +679,23 @@ class TestPack < Test::Unit::TestCase assert_equal(["a"*1023], (("a"*73+"=\n")*14+"a=\n").unpack("M")) assert_equal(["\x0a"], "=0a=\n".unpack("M")) assert_equal(["\x0a"], "=0A=\n".unpack("M")) - assert_equal([""], "=0Z=\n".unpack("M")) + assert_equal(["=0Z=\n"], "=0Z=\n".unpack("M")) assert_equal([""], "=\r\n".unpack("M")) + assert_equal(["\xC6\xF7"], "=C6=F7".unpack('M*')) + + assert_equal(["pre123after"], "pre=31=32=33after".unpack("M")) + assert_equal(["preafter"], "pre=\nafter".unpack("M")) + assert_equal(["preafter"], "pre=\r\nafter".unpack("M")) + assert_equal(["pre="], "pre=".unpack("M")) + assert_equal(["pre=\r"], "pre=\r".unpack("M")) + 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 @@ -595,16 +736,6 @@ class TestPack < Test::Unit::TestCase assert_equal([0x100000000], "\220\200\200\200\000".unpack("w"), [0x100000000]) end - def test_modify_under_safe4 - s = "foo" - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - s.clear - end.join - end - end - def test_length_too_big assert_raise(RangeError) { [].pack("C100000000000000000000") } end @@ -615,4 +746,143 @@ class TestPack < Test::Unit::TestCase assert_equal([1,nil], str.unpack("#{fmt}2")) } end + + def test_short_with_block + bug4059 = '[ruby-core:33193]' + result = :ok + assert_nil("".unpack("i") {|x| result = x}, bug4059) + assert_equal(:ok, result) + end + + def test_pack_garbage + verbose = $VERBOSE + $VERBOSE = false + + assert_silent do + assert_equal "\000", [0].pack("*U") + end + + $VERBOSE = true + + _, err = capture_io do + assert_equal "\000", [0].pack("*U") + end + + assert_match %r%unknown pack directive '\*' in '\*U'$%, err + ensure + $VERBOSE = verbose + end + + def test_unpack_garbage + verbose = $VERBOSE + $VERBOSE = false + + assert_silent do + assert_equal [0], "\000".unpack("*U") + end + + $VERBOSE = true + + _, err = capture_io do + assert_equal [0], "\000".unpack("*U") + end + + assert_match %r%unknown unpack directive '\*' in '\*U'$%, err + ensure + $VERBOSE = verbose + end + + def test_invalid_warning + assert_warning(/unknown pack directive ',' in ','/) { + [].pack(",") + } + assert_warning(/\A[ -~]+\Z/) { + [].pack("\x7f") + } + assert_warning(/\A(.* in '\u{3042}'\n)+\z/) { + [].pack("\u{3042}") + } + + assert_warning(/\A.* in '.*U'\Z/) { + assert_equal "\000", [0].pack("\0U") + } + assert_warning(/\A.* in '.*U'\Z/) { + "\000".unpack("\0U") + } + end + + def test_pack_resize + 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 14990be12c..24ca62a3f6 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -1,3 +1,5 @@ +# coding: US-ASCII +# frozen_string_literal: false require 'test/unit' require 'stringio' @@ -12,18 +14,17 @@ class TestParse < Test::Unit::TestCase end def test_else_without_rescue - x = eval <<-END + assert_syntax_error(<<-END, %r":#{__LINE__+2}: else without rescue"o, [__FILE__, __LINE__+1]) begin else 42 end END - assert_equal(42, x) end def test_alias_backref assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 alias $foo $1 END end @@ -36,7 +37,7 @@ class TestParse < Test::Unit::TestCase a = false b = c = d = true assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a &&= t.foo 42 b &&= t.foo 42 c &&= t.foo nil @@ -51,7 +52,7 @@ class TestParse < Test::Unit::TestCase a = [nil, nil, true, true] assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a[0] ||= t.foo 42 a[1] &&= t.foo 42 a[2] ||= t.foo 42 @@ -67,7 +68,7 @@ class TestParse < Test::Unit::TestCase o.foo = o.Foo = o::baz = nil o.bar = o.Bar = o::qux = 1 assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 o.foo ||= t.foo 42 o.bar &&= t.foo 42 o.Foo ||= t.foo 42 @@ -81,7 +82,7 @@ class TestParse < Test::Unit::TestCase assert_equal([42, 42], [o::baz, o::qux]) assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 $1 ||= t.foo 42 END end @@ -90,7 +91,7 @@ class TestParse < Test::Unit::TestCase a = b = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = t.bar "foo" do "bar" end.gsub "ob", "OB" @@ -104,7 +105,7 @@ class TestParse < Test::Unit::TestCase a = nil assert_nothing_raised do - t.instance_eval <<-END + t.instance_eval <<-END, __FILE__, __LINE__+1 a = bar "foo" do "bar" end END end @@ -112,7 +113,7 @@ class TestParse < Test::Unit::TestCase a = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = t::bar "foo" do "bar" end END end @@ -136,7 +137,7 @@ class TestParse < Test::Unit::TestCase end assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 c::foo, c::bar = 1, 2 c.Foo, c.Bar = 1, 2 c::FOO, c::BAR = 1, 2 @@ -149,7 +150,7 @@ class TestParse < Test::Unit::TestCase def test_dynamic_constant_assignment assert_raise(SyntaxError) do - Object.new.instance_eval <<-END + Object.new.instance_eval <<-END, __FILE__, __LINE__+1 def foo self::FOO, self::BAR = 1, 2 ::FOO, ::BAR = 1, 2 @@ -158,13 +159,13 @@ class TestParse < Test::Unit::TestCase end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 $1, $2 = 1, 2 END end assert_raise(SyntaxError) do - Object.new.instance_eval <<-END + Object.new.instance_eval <<-END, __FILE__, __LINE__+1 def foo ::FOO = 1 end @@ -172,16 +173,18 @@ class TestParse < Test::Unit::TestCase end c = Class.new - assert_raise(SyntaxError) do - eval <<-END + c.freeze + assert_nothing_raised(SyntaxError) do + eval <<-END, nil, __FILE__, __LINE__+1 + if false c::FOO &= 1 ::FOO &= 1 + end END end - c = Class.new assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 $1 &= 1 END end @@ -189,13 +192,13 @@ class TestParse < Test::Unit::TestCase def test_class_module assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 class foo; end END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo class Foo; end module Bar; end @@ -203,9 +206,9 @@ class TestParse < Test::Unit::TestCase END end - assert_raise(SyntaxError) do - eval <<-END - class Foo Bar; end + assert_nothing_raised(SyntaxError) do + eval <<-END, nil, __FILE__, __LINE__+1 + class Foo 1; end END end end @@ -215,9 +218,8 @@ 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 + o.instance_eval <<-END, __FILE__, __LINE__+1 undef >, / END end @@ -231,7 +233,7 @@ class TestParse < Test::Unit::TestCase o.foo = o.Foo = o::baz = nil o.bar = o.Bar = o::qux = 1 assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 o.foo ||= 42 o.bar &&= 42 o.Foo ||= 42 @@ -246,7 +248,7 @@ class TestParse < Test::Unit::TestCase a = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = -2.0 ** 2 END end @@ -259,7 +261,7 @@ class TestParse < Test::Unit::TestCase a = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 o.foo 1 do|; a| a = 42 end END end @@ -268,25 +270,25 @@ class TestParse < Test::Unit::TestCase def test_bad_arg assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo(FOO); end END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo(@foo); end END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo($foo); end END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo(@@foo); end END end @@ -295,7 +297,7 @@ class TestParse < Test::Unit::TestCase def o.foo(*r); yield(*r); end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 o.foo 1 {|; @a| @a = 42 } END end @@ -304,7 +306,7 @@ class TestParse < Test::Unit::TestCase def test_do_lambda a = b = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = -> do b = 42 end @@ -320,7 +322,7 @@ class TestParse < Test::Unit::TestCase a = b = nil assert_nothing_raised do - o.instance_eval <<-END + o.instance_eval <<-END, __FILE__, __LINE__+1 a = foo 1 do 42 end.to_s b = foo 1 do 42 end::to_s END @@ -332,7 +334,7 @@ class TestParse < Test::Unit::TestCase def test_call_method a = b = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = proc {|x| x + "bar" }.("foo") b = proc {|x| x + "bar" }::("foo") END @@ -358,15 +360,45 @@ class TestParse < Test::Unit::TestCase assert_equal("foo 1 bar", "foo #$1 bar") end + def test_dstr_disallowed_variable + bug8375 = '[ruby-core:54885] [Bug #8375]' + %w[@ @1 @. @@ @@1 @@. $ $%].each do |src| + src = '#'+src+' ' + str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do + break eval('"'+src+'"') + end + assert_equal(src, str, bug8375) + end + end + def test_dsym 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 assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end END end @@ -377,7 +409,7 @@ class TestParse < Test::Unit::TestCase assert_raise(ArgumentError) { o.foo() } assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,z,&b); b.call(a*1000+z*100); end END end @@ -386,7 +418,7 @@ class TestParse < Test::Unit::TestCase assert_raise(ArgumentError) { o.foo() } assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end END end @@ -398,19 +430,19 @@ class TestParse < Test::Unit::TestCase def test_duplicate_argument assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 1.times {|&b?| } END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 1.times {|a, a|} END end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo(a, a); end END end @@ -418,27 +450,51 @@ class TestParse < Test::Unit::TestCase def test_define_singleton_error assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def ("foo").foo; end END end end + def test_op_asgn1_with_block + t = Object.new + a = [] + blk = proc {|x| a << x } + def t.[](_) + yield(:aref) + nil + end + def t.[]=(_, _) + yield(:aset) + end + def t.dummy(_) + end + eval <<-END, nil, __FILE__, __LINE__+1 + t[42, &blk] ||= 42 + END + assert_equal([:aref, :aset], a) + a.clear + eval <<-END, nil, __FILE__, __LINE__+1 + t[42, &blk] ||= t.dummy 42 # command_asgn test + END + assert_equal([:aref, :aset], a) + end + def test_backquote t = Object.new assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def t.`(x); "foo" + x + "bar"; end END end a = b = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 a = t.` "zzz" 1.times {|;z| t.` ("zzz") } END - t.instance_eval <<-END + t.instance_eval <<-END, __FILE__, __LINE__+1 b = `zzz` END end @@ -451,21 +507,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?"')) @@ -479,6 +555,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 @@ -502,14 +580,15 @@ 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 def test_parse_string assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 / END end @@ -524,13 +603,14 @@ class TestParse < Test::Unit::TestCase ) end - assert_raise(SyntaxError) do - eval %q( + assert_nothing_raised(SyntaxError) do + x = eval %q( <<FOO #$ FOO ) end + assert_equal "\#$\n", x assert_raise(SyntaxError) do eval %Q( @@ -550,14 +630,15 @@ FOO ) end - assert_raise(SyntaxError) do - eval %q( + assert_nothing_raised(SyntaxError) do + x = eval %q( <<FOO #$ foo FOO ) end + assert_equal "\#$\nfoo\n", x assert_nothing_raised do eval "x = <<""FOO\r\n1\r\nFOO" @@ -568,7 +649,7 @@ FOO def test_magic_comment x = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 # coding = utf-8 x = __ENCODING__ END @@ -576,7 +657,7 @@ x = __ENCODING__ assert_equal(Encoding.find("UTF-8"), x) assert_raise(ArgumentError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 # coding = foobarbazquxquux_dummy_enconding x = __ENCODING__ END @@ -595,7 +676,7 @@ x = __ENCODING__ def test_dot_in_next_line x = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 x = 1 .to_s END @@ -611,7 +692,7 @@ x = __ENCODING__ def test_embedded_rd assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 =begin END end @@ -635,21 +716,28 @@ 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 + bug10117 = '[ruby-core:64243] [Bug #10117]' + invalid_char = /Invalid char `\\x01'/ x = 1 - assert_equal(1, eval("\x01x")) + 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 @@ -675,17 +763,23 @@ x = __ENCODING__ eval %q(__ENCODING__ = 1) end assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 def foo FOO = 1 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 assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 foo(&proc{}) {} END end @@ -693,7 +787,7 @@ x = __ENCODING__ def test_set_backref assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 $& = 1 END end @@ -706,7 +800,7 @@ x = __ENCODING__ end r = nil assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 o[&proc{|x| r = x }] = 1 END end @@ -722,6 +816,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") @@ -731,30 +826,25 @@ 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) end - o = Object.new - assert_nothing_raised do - eval <<-END - x = def o.foo; end - END - end - assert_equal($stderr.string.lines.to_a.size, 14) + assert_equal(13, $stderr.string.lines.to_a.size) $stderr = stderr end def test_assign_in_conditional - assert_raise(SyntaxError) do - eval <<-END + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 (x, y = 1, 2) ? 1 : 2 END end assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 if @x = true 1 else @@ -766,63 +856,357 @@ x = __ENCODING__ def test_literal_in_conditional assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 "foo" ? 1 : 2 END end assert_nothing_raised do x = "bar" - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 /foo#{x}baz/ ? 1 : 2 END end assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 (true..false) ? 1 : 2 END end assert_nothing_raised do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 ("foo".."bar") ? 1 : 2 END end assert_nothing_raised do x = "bar" - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 :"foo#{"x"}baz" ? 1 : 2 END + assert_equal "bar", x end end def test_no_blockarg assert_raise(SyntaxError) do - eval <<-END + eval <<-END, nil, __FILE__, __LINE__+1 yield(&:+) END 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) + def test_method_block_location + bug5614 = '[ruby-core:40936]' + expected = nil + e = assert_raise(NoMethodError) do + 1.times do + expected = __LINE__+1 + end.print do + # + end + end + actual = e.backtrace.first[/\A#{Regexp.quote(__FILE__)}:(\d+):/o, 1].to_i + assert_equal(expected, actual, bug5614) + end + + def test_no_shadowing_variable_warning + assert_no_warning(/shadowing outer local variable/) {eval("a=1; tap {|a|}")} end - def test_all_symbols - x = Symbol.all_symbols - assert_kind_of(Array, x) - assert(x.all? {|s| s.is_a?(Symbol) }) + 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_is_class_id - c = Class.new - assert_raise(NameError) do - c.instance_eval { remove_class_variable(:@var) } + 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_location_of_invalid_token + assert_raise_with_message(SyntaxError, /^ \^~~\z/) do + eval('class xxx end') + end + end + + def test_whitespace_warning + assert_raise_with_message(SyntaxError, /backslash/) do + eval("\\foo") + end + assert_raise_with_message(SyntaxError, /escaped space/) do + eval("\\ ") + end + assert_raise_with_message(SyntaxError, /escaped horizontal tab/) do + eval("\\\t") + end + assert_raise_with_message(SyntaxError, /escaped form feed/) do + eval("\\\f") + end + assert_raise_with_message(SyntaxError, /escaped carriage return/) do + assert_warn(/middle of line/) {eval("\\\r")} + end + assert_raise_with_message(SyntaxError, /escaped vertical tab/) do + eval("\\\v") + end + end + + def test_command_def_cmdarg + assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}") + begin; + m def x(); end + 1.tap do end + end; + end + + NONASCII_CONSTANTS = [ + *%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]}, + "\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}", + *%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]}, + ] + + def assert_nonascii_const + assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n| + m = Module.new + assert_not_operator(m, :const_defined?, n) + assert_raise_with_message(NameError, /uninitialized/) do + m.const_get(n) + end + assert_nil(eval("defined?(m::#{n})")) + + v = yield m, n + + assert_operator(m, :const_defined?, n) + assert_equal("constant", eval("defined?(m::#{n})")) + assert_same(v, m.const_get(n)) + + m.__send__(:remove_const, n) + assert_not_operator(m, :const_defined?, n) + assert_nil(eval("defined?(m::#{n})")) + end + end + + def test_nonascii_const_set + assert_nonascii_const do |m, n| + m.const_set(n, 42) + end + end + + def test_nonascii_constant + assert_nonascii_const do |m, n| + m.module_eval("class #{n}; self; end") + end + end + + def test_cdmarg_after_command_args_and_tlbrace_arg + assert_valid_syntax('let () { m(a) do; end }') + end + + def test_void_value_in_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 31c1885371..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 = '' @@ -239,4 +243,21 @@ class TestPath < Test::Unit::TestCase assert_equal('', File.extname('.x')) assert_equal('', File.extname('..x')) end + + def test_ascii_incompatible_path + s = "\u{221e}\u{2603}" + assert_raise(Encoding::CompatibilityError) {open(s.encode("utf-16be"))} + assert_raise(Encoding::CompatibilityError) {open(s.encode("utf-16le"))} + assert_raise(Encoding::CompatibilityError) {open(s.encode("utf-32be"))} + assert_raise(Encoding::CompatibilityError) {open(s.encode("utf-32le"))} + end + + def test_join + bug5483 = '[ruby-core:40338]' + path = %w[a b] + Encoding.list.each do |e| + next unless e.ascii_compatible? + assert_equal(e, File.join(*path.map {|s| s.force_encoding(e)}).encoding, bug5483) + end + end end diff --git a/test/ruby/test_pipe.rb b/test/ruby/test_pipe.rb index 34f231ad8c..9fa42fd375 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' @@ -13,4 +14,36 @@ class TestPipe < Test::Unit::TestCase r.close end end + class WithConversion < self + def open_file(content) + r, w = IO.pipe + w << content + w.close + r.set_encoding("us-ascii:utf-8") + begin + yield r + ensure + r.close + end + end + end + + def test_stdout_epipe + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + io = STDOUT + begin + save = io.dup + IO.popen("echo", "w", out: IO::NULL) do |f| + io.reopen(f) + Process.wait(f.pid) + assert_raise(Errno::EPIPE) do + io.print "foo\n" + end + end + ensure + io.reopen(save) + end + end; + end end diff --git a/test/ruby/test_primitive.rb b/test/ruby/test_primitive.rb index d701348f26..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 @@ -400,4 +401,24 @@ class TestRubyPrimitive < Test::Unit::TestCase #assert_equal [0,1,2,3,4], [0, *a, 4] end + def test_concatarray_ruby_dev_41933 + bug3658 = '[ruby-dev:41933]' + [0, *x=1] + assert_equal(1, x, bug3658) + [0, *x=1, 2] + assert_equal(1, x, bug3658) + class << (x = Object.new) + attr_accessor :to_a_called + def to_a + @to_a_called = true + [self] + end + end + x.to_a_called = false + [0, *x] + assert_predicate(x, :to_a_called, bug3658) + x.to_a_called = false + [0, *x, 2] + assert_predicate(x, :to_a_called, bug3658) + end end diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index 5a108d3a0f..9ae1de62ff 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestProc < Test::Unit::TestCase @@ -35,9 +36,9 @@ class TestProc < Test::Unit::TestCase }.call assert(!defined?(iii)) # out of scope - loop{iii=5; assert(eval("defined? iii")); break} + loop{iii=iii=5; assert(eval("defined? iii")); break} loop { - iii = 10 + iii=iii = 10 def self.dyna_var_check loop { assert(!defined?(iii)) @@ -62,12 +63,57 @@ class TestProc < Test::Unit::TestCase assert_equal(0, proc{}.arity) assert_equal(0, proc{||}.arity) assert_equal(1, proc{|x|}.arity) + assert_equal(0, proc{|x=1|}.arity) assert_equal(2, proc{|x, y|}.arity) + assert_equal(1, proc{|x=0, y|}.arity) + assert_equal(0, proc{|x=0, y=0|}.arity) + assert_equal(1, proc{|x, y=0|}.arity) assert_equal(-2, proc{|x, *y|}.arity) + assert_equal(-1, proc{|x=0, *y|}.arity) assert_equal(-1, proc{|*x|}.arity) assert_equal(-1, proc{|*|}.arity) assert_equal(-3, proc{|x, *y, z|}.arity) + assert_equal(-2, proc{|x=0, *y, z|}.arity) + assert_equal(2, proc{|(x, y), z|[x,y]}.arity) + assert_equal(1, proc{|(x, y), z=0|[x,y]}.arity) assert_equal(-4, proc{|x, *y, z, a|}.arity) + assert_equal(0, proc{|**|}.arity) + assert_equal(0, proc{|**o|}.arity) + assert_equal(1, proc{|x, **o|}.arity) + assert_equal(0, proc{|x=0, **o|}.arity) + assert_equal(1, proc{|x, y=0, **o|}.arity) + assert_equal(2, proc{|x, y=0, z, **o|}.arity) + assert_equal(-3, proc{|x, y=0, *z, w, **o|}.arity) + + assert_equal(2, proc{|x, y=0, z, a:1|}.arity) + assert_equal(3, proc{|x, y=0, z, a:|}.arity) + assert_equal(-4, proc{|x, y, *rest, a:, b:, c:|}.arity) + assert_equal(3, proc{|x, y=0, z, a:, **o|}.arity) + + assert_equal(0, lambda{}.arity) + assert_equal(0, lambda{||}.arity) + assert_equal(1, lambda{|x|}.arity) + assert_equal(-1, lambda{|x=1|}.arity) # different from proc + assert_equal(2, lambda{|x, y|}.arity) + assert_equal(-2, lambda{|x=0, y|}.arity) # different from proc + assert_equal(-1, lambda{|x=0, y=0|}.arity) # different from proc + assert_equal(-2, lambda{|x, y=0|}.arity) # different from proc + assert_equal(-2, lambda{|x, *y|}.arity) + assert_equal(-1, lambda{|x=0, *y|}.arity) + assert_equal(-1, lambda{|*x|}.arity) + assert_equal(-1, lambda{|*|}.arity) + assert_equal(-3, lambda{|x, *y, z|}.arity) + assert_equal(-2, lambda{|x=0, *y, z|}.arity) + assert_equal(2, lambda{|(x, y), z|[x,y]}.arity) + assert_equal(-2, lambda{|(x, y), z=0|[x,y]}.arity) + assert_equal(-4, lambda{|x, *y, z, a|}.arity) + assert_equal(-1, lambda{|**|}.arity) + assert_equal(-1, lambda{|**o|}.arity) + assert_equal(-2, lambda{|x, **o|}.arity) + assert_equal(-1, lambda{|x=0, **o|}.arity) + assert_equal(-2, lambda{|x, y=0, **o|}.arity) + assert_equal(-3, lambda{|x, y=0, z, **o|}.arity) + assert_equal(-3, lambda{|x, y=0, *z, w, **o|}.arity) assert_arity(0) {} assert_arity(0) {||} @@ -77,6 +123,10 @@ class TestProc < Test::Unit::TestCase assert_arity(-3) {|x, *y, z|} assert_arity(-1) {|*x|} assert_arity(-1) {|*|} + assert_arity(-1) {|**o|} + assert_arity(-1) {|**|} + assert_arity(-2) {|x, *y, **|} + assert_arity(-3) {|x, *y, z, **|} end def m(x) @@ -110,26 +160,34 @@ class TestProc < Test::Unit::TestCase $SAFE += 1 proc {$SAFE} }.call - assert_equal(safe, $SAFE) + + assert_equal(safe + 1, $SAFE) assert_equal(safe + 1, p.call) - assert_equal(safe, $SAFE) + assert_equal(safe + 1, $SAFE) + $SAFE = 0 c.class_eval {define_method(:safe, p)} assert_equal(safe, x.safe) - assert_equal(safe, x.method(:safe).call) - assert_equal(safe, x.method(:safe).to_proc.call) + $SAFE = 0 p = proc {$SAFE += 1} assert_equal(safe + 1, p.call) - assert_equal(safe, $SAFE) + assert_equal(safe + 1, $SAFE) + $SAFE = 0 c.class_eval {define_method(:inc, p)} assert_equal(safe + 1, proc {x.inc; $SAFE}.call) - assert_equal(safe, $SAFE) + assert_equal(safe + 1, $SAFE) + + $SAFE = 0 assert_equal(safe + 1, proc {x.method(:inc).call; $SAFE}.call) - assert_equal(safe, $SAFE) + assert_equal(safe + 1, $SAFE) + + $SAFE = 0 assert_equal(safe + 1, proc {x.method(:inc).to_proc.call; $SAFE}.call) - assert_equal(safe, $SAFE) + assert_equal(safe + 1, $SAFE) + ensure + $SAFE = 0 end def m2 @@ -140,55 +198,122 @@ class TestProc < Test::Unit::TestCase method(:m2).to_proc end + def m1(var) + var + end + + def m_block_given? + m1(block_given?) + end + # [yarv-dev:777] block made by Method#to_proc def test_method_to_proc b = block() assert_equal "OK", b.call - assert_instance_of(Binding, b.binding, '[ruby-core:25589]') + b = b.binding + assert_instance_of(Binding, b, '[ruby-core:25589]') + bug10432 = '[ruby-core:65919] [Bug #10432]' + 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 + m = method(:m_block_given?) + assert(!m.call, "without block") + assert(m.call {}, "with block") + assert(!m.call, "without block second") + end + + def test_block_given_method_to_proc + bug8341 = '[Bug #8341]' + m = method(:m_block_given?).to_proc + assert(!m.call, "#{bug8341} without block") + assert(m.call {}, "#{bug8341} with block") + assert(!m.call, "#{bug8341} without block second") + end + + def test_block_persist_between_calls + bug8341 = '[Bug #8341]' + o = Object.new + def o.m1(top=true) + if top + [block_given?, @m.call(false)] + else + block_given? + end + end + m = o.method(:m1).to_proc + o.instance_variable_set(:@m, m) + assert_equal([true, false], m.call {}, "#{bug8341} nested with block") + 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 @@ -223,16 +348,27 @@ 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 + a = lambda { |x, y| [x + y, self] } + b = a.curry.call(1) + result = instance_exec 2, &b + + assert_equal(3, result[0]) + assert_equal(self, result[1]) + end + + def test_curry_optional_params + obj = Object.new + def obj.foo(a, b=42); end + assert_raise(ArgumentError) { obj.method(:foo).to_proc.curry(3) } + assert_raise(ArgumentError) { ->(a, b=42){}.curry(3) } end def test_dup_clone @@ -265,10 +401,19 @@ class TestProc < Test::Unit::TestCase assert_equal(:foo, bc.foo) b = nil - 1.times { x, y, z = 1, 2, 3; b = binding } + 1.times { x, y, z = 1, 2, 3; [x,y,z]; b = binding } assert_equal([1, 2, 3], b.eval("[x, y, z]")) end + def test_binding_source_location + b, expected_location = binding, [__FILE__, __LINE__] + assert_equal(expected_location, b.source_location) + + file, lineno = method(:source_location_test).to_proc.binding.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427') + end + def test_proc_lambda assert_raise(ArgumentError) { proc } assert_raise(ArgumentError) { lambda } @@ -302,12 +447,7 @@ class TestProc < Test::Unit::TestCase t = Thread.new { sleep } assert_raise(ThreadError) { t.instance_eval { initialize { } } } t.kill - end - - def test_eq2 - b1 = proc { } - b2 = b1.dup - assert(b1 == b2) + t.join end def test_to_proc @@ -316,14 +456,14 @@ class TestProc < Test::Unit::TestCase end def test_localjump_error - o = Object.new + o = o = Object.new def foo; yield; end exc = foo rescue $! assert_nil(exc.exit_value) assert_equal(:noreason, exc.reason) end - def test_binding2 + def test_curry_binding assert_raise(ArgumentError) { proc {}.curry.binding } end @@ -382,7 +522,7 @@ class TestProc < Test::Unit::TestCase assert_equal [[1,2,3]], r end - def test_proc_args_rest_and_post + def test_proc_args_pos_rest_post pr = proc {|a,b,*c,d,e| [a,b,c,d,e] } @@ -405,7 +545,30 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, [3, 4, 5], 6,7], pr.call([1,2,3,4,5,6,7]) end - def test_proc_args_opt + def test_proc_args_rest_post + pr = proc {|*a,b,c| + [a,b,c] + } + assert_equal [[], nil, nil], pr.call() + assert_equal [[], 1, nil], pr.call(1) + assert_equal [[], 1, 2], pr.call(1,2) + assert_equal [[1], 2, 3], pr.call(1,2,3) + assert_equal [[1, 2], 3, 4], pr.call(1,2,3,4) + assert_equal [[1, 2, 3], 4, 5], pr.call(1,2,3,4,5) + assert_equal [[1, 2, 3, 4], 5, 6], pr.call(1,2,3,4,5,6) + assert_equal [[1, 2, 3, 4, 5], 6,7], pr.call(1,2,3,4,5,6,7) + + assert_equal [[], nil, nil], pr.call([]) + assert_equal [[], 1, nil], pr.call([1]) + assert_equal [[], 1, 2], pr.call([1,2]) + assert_equal [[1], 2, 3], pr.call([1,2,3]) + assert_equal [[1, 2], 3, 4], pr.call([1,2,3,4]) + assert_equal [[1, 2, 3], 4, 5], pr.call([1,2,3,4,5]) + assert_equal [[1, 2, 3, 4], 5, 6], pr.call([1,2,3,4,5,6]) + assert_equal [[1, 2, 3, 4, 5], 6,7], pr.call([1,2,3,4,5,6,7]) + end + + def test_proc_args_pos_opt pr = proc {|a,b,c=:c| [a,b,c] } @@ -426,7 +589,44 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3], pr.call([1,2,3,4,5,6]) end - def test_proc_args_opt_and_post + def test_proc_args_opt + pr = proc {|a=:a,b=:b,c=:c| + [a,b,c] + } + assert_equal [:a, :b, :c], pr.call() + assert_equal [1, :b, :c], pr.call(1) + assert_equal [1, 2, :c], pr.call(1,2) + assert_equal [1, 2, 3], pr.call(1,2,3) + assert_equal [1, 2, 3], pr.call(1,2,3,4) + assert_equal [1, 2, 3], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3], pr.call(1,2,3,4,5,6) + + assert_equal [:a, :b, :c], pr.call([]) + assert_equal [1, :b, :c], pr.call([1]) + assert_equal [1, 2, :c], pr.call([1,2]) + assert_equal [1, 2, 3], pr.call([1,2,3]) + assert_equal [1, 2, 3], pr.call([1,2,3,4]) + assert_equal [1, 2, 3], pr.call([1,2,3,4,5]) + assert_equal [1, 2, 3], pr.call([1,2,3,4,5,6]) + end + + def test_proc_args_opt_single + bug7621 = '[ruby-dev:46801]' + pr = proc {|a=:a| + a + } + assert_equal :a, pr.call() + assert_equal 1, pr.call(1) + assert_equal 1, pr.call(1,2) + + assert_equal [], pr.call([]), bug7621 + assert_equal [1], pr.call([1]), bug7621 + assert_equal [1, 2], pr.call([1,2]), bug7621 + assert_equal [1, 2, 3], pr.call([1,2,3]), bug7621 + assert_equal [1, 2, 3, 4], pr.call([1,2,3,4]), bug7621 + end + + def test_proc_args_pos_opt_post pr = proc {|a,b,c=:c,d,e| [a,b,c,d,e] } @@ -447,7 +647,28 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, 4, 5], pr.call([1,2,3,4,5,6]) end - def test_proc_args_opt_and_rest + def test_proc_args_opt_post + pr = proc {|a=:a,b=:b,c=:c,d,e| + [a,b,c,d,e] + } + assert_equal [:a, :b, :c, nil, nil], pr.call() + assert_equal [:a, :b, :c, 1, nil], pr.call(1) + assert_equal [:a, :b, :c, 1, 2], pr.call(1,2) + assert_equal [1, :b, :c, 2, 3], pr.call(1,2,3) + assert_equal [1, 2, :c, 3, 4], pr.call(1,2,3,4) + assert_equal [1, 2, 3, 4, 5], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3, 4, 5], pr.call(1,2,3,4,5,6) + + assert_equal [:a, :b, :c, nil, nil], pr.call([]) + assert_equal [:a, :b, :c, 1, nil], pr.call([1]) + assert_equal [:a, :b, :c, 1, 2], pr.call([1,2]) + assert_equal [1, :b, :c, 2, 3], pr.call([1,2,3]) + assert_equal [1, 2, :c, 3, 4], pr.call([1,2,3,4]) + assert_equal [1, 2, 3, 4, 5], pr.call([1,2,3,4,5]) + assert_equal [1, 2, 3, 4, 5], pr.call([1,2,3,4,5,6]) + end + + def test_proc_args_pos_opt_rest pr = proc {|a,b,c=:c,*d| [a,b,c,d] } @@ -466,7 +687,26 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, [4, 5]], pr.call([1,2,3,4,5]) end - def test_proc_args_opt_and_rest_and_post + def test_proc_args_opt_rest + pr = proc {|a=:a,b=:b,c=:c,*d| + [a,b,c,d] + } + assert_equal [:a, :b, :c, []], pr.call() + assert_equal [1, :b, :c, []], pr.call(1) + assert_equal [1, 2, :c, []], pr.call(1,2) + assert_equal [1, 2, 3, []], pr.call(1,2,3) + assert_equal [1, 2, 3, [4]], pr.call(1,2,3,4) + assert_equal [1, 2, 3, [4, 5]], pr.call(1,2,3,4,5) + + assert_equal [:a, :b, :c, []], pr.call([]) + assert_equal [1, :b, :c, []], pr.call([1]) + assert_equal [1, 2, :c, []], pr.call([1,2]) + assert_equal [1, 2, 3, []], pr.call([1,2,3]) + assert_equal [1, 2, 3, [4]], pr.call([1,2,3,4]) + assert_equal [1, 2, 3, [4, 5]], pr.call([1,2,3,4,5]) + end + + def test_proc_args_pos_opt_rest_post pr = proc {|a,b,c=:c,*d,e| [a,b,c,d,e] } @@ -487,7 +727,28 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, [4,5], 6], pr.call([1,2,3,4,5,6]) end - def test_proc_args_block + def test_proc_args_opt_rest_post + pr = proc {|a=:a,b=:b,c=:c,*d,e| + [a,b,c,d,e] + } + assert_equal [:a, :b, :c, [], nil], pr.call() + assert_equal [:a, :b, :c, [], 1], pr.call(1) + assert_equal [1, :b, :c, [], 2], pr.call(1,2) + assert_equal [1, 2, :c, [], 3], pr.call(1,2,3) + assert_equal [1, 2, 3, [], 4], pr.call(1,2,3,4) + assert_equal [1, 2, 3, [4], 5], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3, [4,5], 6], pr.call(1,2,3,4,5,6) + + assert_equal [:a, :b, :c, [], nil], pr.call([]) + assert_equal [:a, :b, :c, [], 1], pr.call([1]) + assert_equal [1, :b, :c, [], 2], pr.call([1,2]) + assert_equal [1, 2, :c, [], 3], pr.call([1,2,3]) + assert_equal [1, 2, 3, [], 4], pr.call([1,2,3,4]) + assert_equal [1, 2, 3, [4], 5], pr.call([1,2,3,4,5]) + assert_equal [1, 2, 3, [4,5], 6], pr.call([1,2,3,4,5,6]) + end + + def test_proc_args_pos_block pr = proc {|a,b,&c| [a, b, c.class, c&&c.call(:x)] } @@ -497,6 +758,12 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, NilClass, nil], pr.call(1,2,3) assert_equal [1, 2, NilClass, nil], pr.call(1,2,3,4) + assert_equal [nil, nil, NilClass, nil], pr.call([]) + assert_equal [1, nil, NilClass, nil], pr.call([1]) + assert_equal [1, 2, NilClass, nil], pr.call([1,2]) + assert_equal [1, 2, NilClass, nil], pr.call([1,2,3]) + assert_equal [1, 2, NilClass, nil], pr.call([1,2,3,4]) + assert_equal [nil, nil, Proc, :proc], (pr.call(){ :proc }) assert_equal [1, nil, Proc, :proc], (pr.call(1){ :proc }) assert_equal [1, 2, Proc, :proc], (pr.call(1, 2){ :proc }) @@ -510,7 +777,7 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) end - def test_proc_args_rest_and_block + def test_proc_args_pos_rest_block pr = proc {|a,b,*c,&d| [a, b, c, d.class, d&&d.call(:x)] } @@ -533,7 +800,24 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, [3,4], Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) end - def test_proc_args_rest_and_post_and_block + def test_proc_args_rest_block + pr = proc {|*c,&d| + [c, d.class, d&&d.call(:x)] + } + assert_equal [[], NilClass, nil], pr.call() + assert_equal [[1], NilClass, nil], pr.call(1) + assert_equal [[1, 2], NilClass, nil], pr.call(1,2) + + assert_equal [[], Proc, :proc], (pr.call(){ :proc }) + assert_equal [[1], Proc, :proc], (pr.call(1){ :proc }) + assert_equal [[1, 2], Proc, :proc], (pr.call(1, 2){ :proc }) + + assert_equal [[], Proc, :x], (pr.call(){|x| x}) + assert_equal [[1], Proc, :x], (pr.call(1){|x| x}) + assert_equal [[1, 2], Proc, :x], (pr.call(1, 2){|x| x}) + end + + def test_proc_args_pos_rest_post_block pr = proc {|a,b,*c,d,e,&f| [a, b, c, d, e, f.class, f&&f.call(:x)] } @@ -562,7 +846,30 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, [3,4], 5, 6, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6){|x| x}) end - def test_proc_args_opt_and_block + def test_proc_args_rest_post_block + pr = proc {|*c,d,e,&f| + [c, d, e, f.class, f&&f.call(:x)] + } + assert_equal [[], nil, nil, NilClass, nil], pr.call() + assert_equal [[], 1, nil, NilClass, nil], pr.call(1) + assert_equal [[], 1, 2, NilClass, nil], pr.call(1,2) + assert_equal [[1], 2, 3, NilClass, nil], pr.call(1,2,3) + assert_equal [[1, 2], 3, 4, NilClass, nil], pr.call(1,2,3,4) + + assert_equal [[], nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [[], 1, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [[], 1, 2, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [[1], 2, 3, Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [[1, 2], 3, 4, Proc, :proc], (pr.call(1, 2, 3, 4){ :proc }) + + assert_equal [[], nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [[], 1, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [[], 1, 2, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [[1], 2, 3, Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [[1, 2], 3, 4, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + end + + def test_proc_args_pos_opt_block pr = proc {|a,b,c=:c,d=:d,&e| [a, b, c, d, e.class, e&&e.call(:x)] } @@ -588,7 +895,33 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, 4, Proc, :x], (pr.call(1, 2, 3, 4, 5){|x| x}) end - def test_proc_args_opt_and_post_and_block + def test_proc_args_opt_block + pr = proc {|a=:a,b=:b,c=:c,d=:d,&e| + [a, b, c, d, e.class, e&&e.call(:x)] + } + assert_equal [:a, :b, :c, :d, NilClass, nil], pr.call() + assert_equal [1, :b, :c, :d, NilClass, nil], pr.call(1) + assert_equal [1, 2, :c, :d, NilClass, nil], pr.call(1,2) + assert_equal [1, 2, 3, :d, NilClass, nil], pr.call(1,2,3) + assert_equal [1, 2, 3, 4, NilClass, nil], pr.call(1,2,3,4) + assert_equal [1, 2, 3, 4, NilClass, nil], pr.call(1,2,3,4,5) + + assert_equal [:a, :b, :c, :d, Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, :b, :c, :d, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [1, 2, :c, :d, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, 2, 3, :d, Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [1, 2, 3, 4, Proc, :proc], (pr.call(1, 2, 3, 4){ :proc }) + assert_equal [1, 2, 3, 4, Proc, :proc], (pr.call(1, 2, 3, 4, 5){ :proc }) + + assert_equal [:a, :b, :c, :d, Proc, :x], (pr.call(){|x| x}) + assert_equal [1, :b, :c, :d, Proc, :x], (pr.call(1){|x| x}) + assert_equal [1, 2, :c, :d, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, 2, 3, :d, Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [1, 2, 3, 4, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + assert_equal [1, 2, 3, 4, Proc, :x], (pr.call(1, 2, 3, 4, 5){|x| x}) + end + + def test_proc_args_pos_opt_post_block pr = proc {|a,b,c=:c,d=:d,e,f,&g| [a, b, c, d, e, f, g.class, g&&g.call(:x)] } @@ -620,7 +953,39 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, 4, 5, 6, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6, 7){|x| x}) end - def test_proc_args_opt_and_block2 + def test_proc_args_opt_post_block + pr = proc {|a=:a,b=:b,c=:c,d=:d,e,f,&g| + [a, b, c, d, e, f, g.class, g&&g.call(:x)] + } + assert_equal [:a, :b, :c, :d, nil, nil, NilClass, nil], pr.call() + assert_equal [:a, :b, :c, :d, 1, nil, NilClass, nil], pr.call(1) + assert_equal [:a, :b, :c, :d, 1, 2, NilClass, nil], pr.call(1,2) + assert_equal [1, :b, :c, :d, 2, 3, NilClass, nil], pr.call(1,2,3) + assert_equal [1, 2, :c, :d, 3, 4, NilClass, nil], pr.call(1,2,3,4) + assert_equal [1, 2, 3, :d, 4, 5, NilClass, nil], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3, 4, 5, 6, NilClass, nil], pr.call(1,2,3,4,5,6) + assert_equal [1, 2, 3, 4, 5, 6, NilClass, nil], pr.call(1,2,3,4,5,6,7) + + assert_equal [:a, :b, :c, :d, nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [:a, :b, :c, :d, 1, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [:a, :b, :c, :d, 1, 2, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, :b, :c, :d, 2, 3, Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [1, 2, :c, :d, 3, 4, Proc, :proc], (pr.call(1, 2, 3, 4){ :proc }) + assert_equal [1, 2, 3, :d, 4, 5, Proc, :proc], (pr.call(1, 2, 3, 4, 5){ :proc }) + assert_equal [1, 2, 3, 4, 5, 6, Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6){ :proc }) + assert_equal [1, 2, 3, 4, 5, 6, Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6, 7){ :proc }) + + assert_equal [:a, :b, :c, :d, nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [:a, :b, :c, :d, 1, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [:a, :b, :c, :d, 1, 2, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, :b, :c, :d, 2, 3, Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [1, 2, :c, :d, 3, 4, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + assert_equal [1, 2, 3, :d, 4, 5, Proc, :x], (pr.call(1, 2, 3, 4, 5){|x| x}) + assert_equal [1, 2, 3, 4, 5, 6, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6){|x| x}) + assert_equal [1, 2, 3, 4, 5, 6, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6, 7){|x| x}) + end + + def test_proc_args_pos_opt_rest_block pr = proc {|a,b,c=:c,d=:d,*e,&f| [a, b, c, d, e, f.class, f&&f.call(:x)] } @@ -649,7 +1014,36 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, 4, [5,6], Proc, :x], (pr.call(1, 2, 3, 4, 5, 6){|x| x}) end - def test_proc_args_opt_and_rest_and_post_and_block + def test_proc_args_opt_rest_block + pr = proc {|a=:a,b=:b,c=:c,d=:d,*e,&f| + [a, b, c, d, e, f.class, f&&f.call(:x)] + } + assert_equal [:a, :b, :c, :d, [], NilClass, nil], pr.call() + assert_equal [1, :b, :c, :d, [], NilClass, nil], pr.call(1) + assert_equal [1, 2, :c, :d, [], NilClass, nil], pr.call(1,2) + assert_equal [1, 2, 3, :d, [], NilClass, nil], pr.call(1,2,3) + assert_equal [1, 2, 3, 4, [], NilClass, nil], pr.call(1,2,3,4) + assert_equal [1, 2, 3, 4, [5], NilClass, nil], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3, 4, [5,6], NilClass, nil], pr.call(1,2,3,4,5,6) + + assert_equal [:a, :b, :c, :d, [], Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, :b, :c, :d, [], Proc, :proc], (pr.call(1){ :proc }) + assert_equal [1, 2, :c, :d, [], Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, 2, 3, :d, [], Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [1, 2, 3, 4, [], Proc, :proc], (pr.call(1, 2, 3, 4){ :proc }) + assert_equal [1, 2, 3, 4, [5], Proc, :proc], (pr.call(1, 2, 3, 4, 5){ :proc }) + assert_equal [1, 2, 3, 4, [5,6], Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6){ :proc }) + + assert_equal [:a, :b, :c, :d, [], Proc, :x], (pr.call(){|x| x}) + assert_equal [1, :b, :c, :d, [], Proc, :x], (pr.call(1){|x| x}) + assert_equal [1, 2, :c, :d, [], Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, 2, 3, :d, [], Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [1, 2, 3, 4, [], Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + assert_equal [1, 2, 3, 4, [5], Proc, :x], (pr.call(1, 2, 3, 4, 5){|x| x}) + assert_equal [1, 2, 3, 4, [5,6], Proc, :x], (pr.call(1, 2, 3, 4, 5, 6){|x| x}) + end + + def test_proc_args_pos_opt_rest_post_block pr = proc {|a,b,c=:c,d=:d,*e,f,g,&h| [a, b, c, d, e, f, g, h.class, h&&h.call(:x)] } @@ -684,7 +1078,42 @@ class TestProc < Test::Unit::TestCase assert_equal [1, 2, 3, 4, [5,6], 7, 8, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6, 7, 8){|x| x}) end - def test_proc_args_unleashed + def test_proc_args_opt_rest_post_block + pr = proc {|a=:a,b=:b,c=:c,d=:d,*e,f,g,&h| + [a, b, c, d, e, f, g, h.class, h&&h.call(:x)] + } + assert_equal [:a, :b, :c, :d, [], nil, nil, NilClass, nil], pr.call() + assert_equal [:a, :b, :c, :d, [], 1, nil, NilClass, nil], pr.call(1) + assert_equal [:a, :b, :c, :d, [], 1, 2, NilClass, nil], pr.call(1,2) + assert_equal [1, :b, :c, :d, [], 2, 3, NilClass, nil], pr.call(1,2,3) + assert_equal [1, 2, :c, :d, [], 3, 4, NilClass, nil], pr.call(1,2,3,4) + assert_equal [1, 2, 3, :d, [], 4, 5, NilClass, nil], pr.call(1,2,3,4,5) + assert_equal [1, 2, 3, 4, [], 5, 6, NilClass, nil], pr.call(1,2,3,4,5,6) + assert_equal [1, 2, 3, 4, [5], 6, 7, NilClass, nil], pr.call(1,2,3,4,5,6,7) + assert_equal [1, 2, 3, 4, [5,6], 7, 8, NilClass, nil], pr.call(1,2,3,4,5,6,7,8) + + assert_equal [:a, :b, :c, :d, [], nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [:a, :b, :c, :d, [], 1, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [:a, :b, :c, :d, [], 1, 2, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, :b, :c, :d, [], 2, 3, Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [1, 2, :c, :d, [], 3, 4, Proc, :proc], (pr.call(1, 2, 3, 4){ :proc }) + assert_equal [1, 2, 3, :d, [], 4, 5, Proc, :proc], (pr.call(1, 2, 3, 4, 5){ :proc }) + assert_equal [1, 2, 3, 4, [], 5, 6, Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6){ :proc }) + assert_equal [1, 2, 3, 4, [5], 6, 7, Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6, 7){ :proc }) + assert_equal [1, 2, 3, 4, [5,6], 7, 8, Proc, :proc], (pr.call(1, 2, 3, 4, 5, 6, 7, 8){ :proc }) + + assert_equal [:a, :b, :c, :d, [], nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [:a, :b, :c, :d, [], 1, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [:a, :b, :c, :d, [], 1, 2, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, :b, :c, :d, [], 2, 3, Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [1, 2, :c, :d, [], 3, 4, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + assert_equal [1, 2, 3, :d, [], 4, 5, Proc, :x], (pr.call(1, 2, 3, 4, 5){|x| x}) + assert_equal [1, 2, 3, 4, [], 5, 6, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6){|x| x}) + assert_equal [1, 2, 3, 4, [5], 6, 7, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6, 7){|x| x}) + assert_equal [1, 2, 3, 4, [5,6], 7, 8, Proc, :x], (pr.call(1, 2, 3, 4, 5, 6, 7, 8){|x| x}) + end + + def test_proc_args_pos_unleashed r = proc {|a,b=1,*c,d,e| [a,b,c,d,e] }.call(1,2,3,4,5) @@ -703,11 +1132,14 @@ class TestProc < Test::Unit::TestCase assert_equal([[:opt, :a], [:rest, :b], [:opt, :c]], proc {|a, *b, c|}.parameters) assert_equal([[:opt, :a], [:rest, :b], [:opt, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters) assert_equal([[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters) - assert_equal([[:opt, nil], [:block, :b]], proc {|(a), &b|}.parameters) + assert_equal([[:opt, nil], [:block, :b]], proc {|(a), &b|a}.parameters) assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters) 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 @@ -720,7 +1152,14 @@ class TestProc < Test::Unit::TestCase def pmo5(a, *b, c) end def pmo6(a, *b, c, &d) end def pmo7(a, b = :b, *c, d, &e) end - def pma1((a), &b) end + def pma1((a), &b) a; end + def pmk1(**) end + def pmk2(**o) nil && o end + def pmk3(a, **o) nil && o end + def pmk4(a = nil, **o) nil && o end + def pmk5(a, b = nil, **o) nil && o end + def pmk6(a, b = nil, c, **o) nil && o end + def pmk7(a, b = nil, *c, d, **o) nil && o end def test_bound_parameters @@ -735,8 +1174,15 @@ class TestProc < Test::Unit::TestCase assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters) assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters) assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters) - - assert_equal([], "".method(:upcase).to_proc.parameters) + assert_equal([[:keyrest]], method(:pmk1).to_proc.parameters) + assert_equal([[:keyrest, :o]], method(:pmk2).to_proc.parameters) + assert_equal([[:req, :a], [:keyrest, :o]], method(:pmk3).to_proc.parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], method(:pmk4).to_proc.parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:pmk5).to_proc.parameters) + 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(:empty?).to_proc.parameters) assert_equal([[:rest]], "".method(:gsub).to_proc.parameters) assert_equal([[:rest]], proc {}.curry.parameters) end @@ -747,7 +1193,9 @@ class TestProc < Test::Unit::TestCase assert_match(/^#<Proc:0x\h+ \(lambda\)>$/, method(:p).to_proc.to_s) x = proc {} x.taint - assert(x.to_s.tainted?) + 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 @@ -786,10 +1234,254 @@ class TestProc < Test::Unit::TestCase assert_equal(@@line_of_attr_accessor_source_location_test, lineno) end + def block_source_location_test(*args, &block) + block.source_location + end + + def test_block_source_location + exp_lineno = __LINE__ + 3 + file, lineno = block_source_location_test(1, + 2, + 3) do + end + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp_lineno, lineno) + end + def test_splat_without_respond_to - def (obj = Object.new).respond_to?(m); false end + def (obj = Object.new).respond_to?(m,*); false end [obj].each do |a, b| assert_equal([obj, nil], [a, b], '[ruby-core:24139]') end end + + def test_curry_with_trace + # bug3751 = '[ruby-core:31871]' + set_trace_func(proc {}) + methods.grep(/\Atest_curry/) do |test| + next if test == __method__ + __send__(test) + end + ensure + set_trace_func(nil) + end + + def test_block_propagation + bug3792 = '[ruby-core:32075]' + c = Class.new do + def foo + yield + end + end + + o = c.new + f = :foo.to_proc + assert_nothing_raised(LocalJumpError, bug3792) { + assert_equal('bar', f.(o) {'bar'}, bug3792) + } + assert_nothing_raised(LocalJumpError, bug3792) { + assert_equal('zot', o.method(:foo).to_proc.() {'zot'}, bug3792) + } + end + + def test_overridden_lambda + bug8345 = '[ruby-core:54687] [Bug #8345]' + assert_normal_exit('def lambda; end; method(:puts).to_proc', bug8345) + end + + def test_overridden_proc + bug8345 = '[ruby-core:54688] [Bug #8345]' + assert_normal_exit('def proc; end; ->{}.curry', bug8345) + end + + def get_binding if: 1, case: 2, when: 3, begin: 4, end: 5 + a ||= 0 + 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)) + assert_raise(NameError){ b.local_variable_get(:b) } + + # access keyword named local variables + assert_equal(1, b.local_variable_get(:if)) + assert_equal(2, b.local_variable_get(:case)) + assert_equal(3, b.local_variable_get(:when)) + assert_equal(4, b.local_variable_get(:begin)) + assert_equal(5, b.local_variable_get(:end)) + end + + def test_local_variable_set + b = get_binding + b.local_variable_set(:a, 10) + b.local_variable_set(:b, 20) + assert_equal(10, b.local_variable_get(:a)) + assert_equal(20, b.local_variable_get(:b)) + assert_equal(10, b.eval("a")) + assert_equal(20, b.eval("b")) + end + + def test_local_variable_set_wb + assert_ruby_status([], <<-'end;', '[Bug #13605]', timeout: 30) + 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 + + def test_prepended_call + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"]) + begin; + Proc.prepend Module.new {def call() puts "call"; super; end} + def m(&blk) blk.call; end + m {} + end; + end + + def test_refined_call + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["call"]) + begin; + using Module.new {refine(Proc) {def call() puts "call"; super; end}} + def m(&blk) blk.call; end + m {} + end; + end + + def method_for_test_proc_without_block_for_symbol + binding.eval('proc') + end + + def test_proc_without_block_for_symbol + assert_equal('1', method_for_test_proc_without_block_for_symbol(&:to_s).call(1), '[Bug #14782]') + end + + def test_compose + f = proc {|x| x * 2} + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_multiple_args + f = proc {|x| x * 2} + g = proc {|x, y| x + y} + + assert_equal(6, (f << g).call(1, 2)) + assert_equal(6, (g >> f).call(1, 2)) + end + + def test_compose_with_block + f = proc {|x| x * 2} + g = proc {|&blk| blk.call(1) } + + assert_equal(8, (f << g).call { |x| x + 3 }) + assert_equal(8, (g >> f).call { |x| x + 3 }) + end + + def test_compose_with_lambda + f = lambda {|x| x * 2} + g = lambda {|x| x} + + assert_predicate((f << g), :lambda?) + assert_predicate((g >> f), :lambda?) + end + + def test_compose_with_method + f = proc {|x| x * 2} + c = Class.new { + def g(x) x + 1 end + } + g = c.new.method(:g) + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + end + + def test_compose_with_callable + f = proc {|x| x * 2} + c = Class.new { + def call(x) x + 1 end + } + g = c.new + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + end + + def test_compose_with_noncallable + f = proc {|x| x * 2} + + assert_raise(NoMethodError) { + (f << 5).call(2) + } + assert_raise(NoMethodError) { + (f >> 5).call(2) + } + end end diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 03cf36e44b..0b43c6bc48 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 'tmpdir' -require 'pathname' -require_relative 'envutil' +require 'tempfile' +require 'timeout' +require 'io/wait' require 'rbconfig' class TestProcess < Test::Unit::TestCase @@ -15,6 +17,13 @@ class TestProcess < Test::Unit::TestCase Process.waitall end + def windows? + self.class.windows? + end + def self.windows? + return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + end + def write_file(filename, content) File.open(filename, "w") {|f| f << content @@ -23,7 +32,7 @@ class TestProcess < Test::Unit::TestCase def with_tmpchdir Dir.mktmpdir {|d| - d = Pathname.new(d).realpath.to_s + d = File.realpath(d) Dir.chdir(d) { yield d } @@ -58,9 +67,14 @@ class TestProcess < Test::Unit::TestCase return unless rlimit_exist? with_tmpchdir { write_file 's', <<-"End" + # 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, 0) + Process.setrlimit(Process::RLIMIT_NOFILE, limit) rescue Errno::EINVAL result = 0 end @@ -88,11 +102,16 @@ class TestProcess < Test::Unit::TestCase :DATA, "DATA", :FSIZE, "FSIZE", :MEMLOCK, "MEMLOCK", + :MSGQUEUE, "MSGQUEUE", + :NICE, "NICE", :NOFILE, "NOFILE", :NPROC, "NPROC", :RSS, "RSS", - :STACK, "STACK", + :RTPRIO, "RTPRIO", + :RTTIME, "RTTIME", :SBSIZE, "SBSIZE", + :SIGPENDING, "SIGPENDING", + :STACK, "STACK", ].each {|name| if Process.const_defined? "RLIMIT_#{name}" assert_nothing_raised { Process.getrlimit(name) } @@ -102,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) @@ -117,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) @@ -127,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 @@ -146,7 +169,7 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_pgroup - skip "system(:pgroup) is not supported" if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + skip "system(:pgroup) is not supported" if windows? assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) } io = IO.popen([RUBY, "-e", "print Process.getpgrp"]) @@ -158,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]) @@ -215,18 +242,42 @@ 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([],"#{<<-"begin;"}\n#{<<~'end;'}") + BUG = "[ruby-core:82033] [Bug #13744]" + RUBY = "#{RUBY}" + begin; + assert(system("#{RUBY}", "-e", + "exit([3600,3600] == Process.getrlimit(:CPU))", + 'rlimit_cpu'.to_sym => 3600), BUG) + assert_raise(ArgumentError, BUG) 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] + MANDATORY_ENVS = %w[RUBYLIB MJIT_SEARCH_BUILD_DIR] case RbConfig::CONFIG['target_os'] when /linux/ MANDATORY_ENVS << 'LD_PRELOAD' when /mswin|mingw/ MANDATORY_ENVS.concat(%w[HOME USER TMPDIR]) + when /darwin/ + MANDATORY_ENVS.concat(ENV.keys.grep(/\A__CF_/)) end if e = RbConfig::CONFIG['LIBPATHENV'] MANDATORY_ENVS << e end + if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty? + MANDATORY_ENVS << e + end PREENVARG = ['-e', "%w[#{MANDATORY_ENVS.join(' ')}].each{|e|ENV.delete(e)}"] ENVARG = ['-e', 'ENV.each {|k,v| puts "#{k}=#{v}" }'] ENVCOMMAND = [RUBY].concat(PREENVARG).concat(ENVARG) @@ -291,6 +342,88 @@ 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| + assert_equal('FOO=BAR', io.read[/^FOO=.*/], message) + } + + old = ENV["hmm"] + begin + ENV["hmm"] = "fufu" + IO.popen(cmd) {|io| assert_match(/^hmm=fufu$/, io.read, message)} + IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)} + IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)} + ENV["hmm"] = "" + IO.popen(cmd) {|io| assert_match(/^hmm=$/, io.read, message)} + IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)} + IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)} + ENV["hmm"] = nil + IO.popen(cmd) {|io| assert_not_match(/^hmm=/, io.read, message)} + IO.popen({"hmm"=>""}, cmd) {|io| assert_match(/^hmm=$/, io.read, message)} + IO.popen({"hmm"=>nil}, cmd) {|io| assert_not_match(/^hmm=/, io.read, message)} + ensure + ENV["hmm"] = old + end + end + + def test_execopts_env_popen_vector + _test_execopts_env_popen(ENVCOMMAND) + end + + def test_execopts_env_popen_string + with_tmpchdir do |d| + open('test-script', 'w') do |f| + ENVCOMMAND.each_with_index do |cmd, i| + next if i.zero? or cmd == "-e" + f.puts cmd + end + end + _test_execopts_env_popen("#{RUBY} test-script") + end + end + + def test_execopts_preserve_env_on_exec_failure + with_tmpchdir {|d| + write_file 's', <<-"End" + ENV["mgg"] = nil + prog = "./nonexistent" + begin + Process.exec({"mgg" => "mggoo"}, [prog, prog]) + rescue Errno::ENOENT + end + open('out', 'w') {|f| + f.print ENV["mgg"].inspect + } + End + system(RUBY, 's') + assert_equal(nil.inspect, File.read('out'), + "[ruby-core:44093] [ruby-trunk - Bug #6249]") + } + end + + def test_execopts_env_single_word + with_tmpchdir {|d| + open("test_execopts_env_single_word.rb", "w") {|f| + f.puts "print ENV['hgga']" + } + system({"hgga"=>"ugu"}, RUBY, + :in => 'test_execopts_env_single_word.rb', + :out => 'test_execopts_env_single_word.out') + assert_equal('ugu', File.read('test_execopts_env_single_word.out')) + } + end + def test_execopts_unsetenv_others h = {} MANDATORY_ENVS.each {|k| e = ENV[k] and h[k] = e} @@ -309,16 +442,59 @@ 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 + + def test_execopts_open_chdir + with_tmpchdir {|d| + Dir.mkdir "foo" + system(*PWD, :chdir => "foo", :out => "open_chdir_test") + assert_file.exist?("open_chdir_test") + assert_file.not_exist?("foo/open_chdir_test") + assert_equal("#{d}/foo", File.read("open_chdir_test").chomp) + } + end + + def test_execopts_open_chdir_m17n_path + with_tmpchdir {|d| + Dir.mkdir "テスト" + (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_テスト", 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 - skip "umask is not supported" if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + skip "umask is not supported" if windows? IO.popen([*UMASK, :umask => 0]) {|io| assert_equal("0000", io.read.chomp) } @@ -356,11 +532,11 @@ 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) - if /mswin|mingw/ =~ RUBY_PLATFORM + if windows? # currently telling to child the file modes is not supported. open("out", "a") {|f| f.write "0\n"} else @@ -375,22 +551,22 @@ class TestProcess < Test::Unit::TestCase # problem occur with valgrind #Process.wait Process.spawn(*ECHO["a"], STDOUT=>:close, STDERR=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]) #p File.read("out") - #assert(!File.read("out").empty?) # error message such as "-e:1:in `flush': Bad file descriptor (Errno::EBADF)" + #assert_not_empty(File.read("out")) # error message such as "-e:1:in `flush': Bad file descriptor (Errno::EBADF)" Process.wait Process.spawn(*ECHO["c"], STDERR=>STDOUT, STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]) assert_equal("c", File.read("out").chomp) File.open("out", "w") {|f| - Process.wait Process.spawn(*ECHO["d"], f=>STDOUT, STDOUT=>f) + Process.wait Process.spawn(*ECHO["d"], STDOUT=>f) assert_equal("d", File.read("out").chomp) } - Process.wait Process.spawn(*ECHO["e"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644], - 3=>STDOUT, 4=>STDOUT, 5=>STDOUT, 6=>STDOUT, 7=>STDOUT) + opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]} + opts.merge(3=>STDOUT, 4=>STDOUT, 5=>STDOUT, 6=>STDOUT, 7=>STDOUT) unless windows? + Process.wait Process.spawn(*ECHO["e"], opts) assert_equal("e", File.read("out").chomp) - Process.wait Process.spawn(*ECHO["ee"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644], - 3=>0, 4=>:in, 5=>STDIN, - 6=>1, 7=>:out, 8=>STDOUT, - 9=>2, 10=>:err, 11=>STDERR) + opts = {STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]} + opts.merge(3=>0, 4=>:in, 5=>STDIN, 6=>1, 7=>:out, 8=>STDOUT, 9=>2, 10=>:err, 11=>STDERR) unless windows? + Process.wait Process.spawn(*ECHO["ee"], opts) assert_equal("ee", File.read("out").chomp) - if /mswin|mingw/ !~ RUBY_PLATFORM + unless windows? # passing non-stdio fds is not supported on Windows File.open("out", "w") {|f| h = {STDOUT=>f, f=>STDOUT} @@ -416,7 +592,7 @@ class TestProcess < Test::Unit::TestCase Process.wait Process.spawn(*SORT, STDIN=>"out", STDOUT=>"out2") assert_equal("ggg\nhhh\n", File.read("out2")) - if /mswin|mingw/ !~ RUBY_PLATFORM + unless windows? # passing non-stdio fds is not supported on Windows assert_raise(Errno::ENOENT) { Process.wait Process.spawn("non-existing-command", (3..60).to_a=>["err", File::WRONLY|File::CREAT]) @@ -428,74 +604,181 @@ 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 + + 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 + + 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 + + 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| + STDOUT.sync = true + trap(:USR1) { print "trap\n" } + puts "start" + system("cat", :in => "fifo") + EOS + assert_equal("start\n", io.gets) + sleep 0.2 # wait for the child to stop at opening "fifo" + Process.kill(:USR1, io.pid) + assert_equal("trap\n", io.readpartial(8)) + File.write("fifo", "ok\n") + assert_equal("ok\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) + } + } - with_pipe {|r1, w1| - with_pipe {|r2, w2| - pid = spawn(*SORT, STDIN=>r1, STDOUT=>w2, w1=>:close, r2=>:close) - 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) + 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; } - if /mswin|mingw/ !~ RUBY_PLATFORM - # 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; + 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 + } - 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) - } + 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 + } - 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 - } + # ensure standard FDs we redirect to are blocking for compatibility + with_pipes(3) do |pipes| + src = 'p [STDIN,STDOUT,STDERR].map(&:nonblock?)' + rdr = { 0 => pipes[0][0], 1 => pipes[1][1], 2 => pipes[2][1] } + pid = spawn(RUBY, '-rio/nonblock', '-e', src, rdr) + assert_equal("[false, false, false]\n", pipes[1][0].gets) + Process.wait pid end + end + end + 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") @@ -506,6 +789,27 @@ class TestProcess < Test::Unit::TestCase } end + def test_execopts_redirect_nonascii_path + bug9946 = '[ruby-core:63185] [Bug #9946]' + with_tmpchdir {|d| + path = "t-\u{30c6 30b9 30c8 f6}.txt" + system(*ECHO["a"], out: path) + assert_file.for(bug9946).exist?(path) + assert_equal("a\n", File.read(path), bug9946) + } + 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'", @@ -516,9 +820,7 @@ class TestProcess < Test::Unit::TestCase STDERR=>"out", STDOUT=>[:child, STDERR]) assert_equal("errout", File.read("out")) - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM - skip "inheritance of fd other than stdin,stdout and stderr is not supported" - end + skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", STDOUT=>"out", STDERR=>[:child, 3], @@ -552,6 +854,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| } } @@ -561,9 +868,12 @@ class TestProcess < Test::Unit::TestCase assert_raise(ArgumentError) { IO.popen([*ECHO["fuga"], STDOUT=>"out"]) {|io| } } - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM - skip "inheritance of fd other than stdin,stdout and stderr is not supported" - end + } + 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) @@ -580,7 +890,6 @@ class TestProcess < Test::Unit::TestCase end def test_popen_fork - return if /freebsd/ =~ RUBY_PLATFORM # this test freeze in FreeBSD IO.popen("-") {|io| if !io puts "fooo" @@ -592,11 +901,9 @@ class TestProcess < Test::Unit::TestCase end def test_fd_inheritance - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM - skip "inheritance of fd other than stdin,stdout and stderr is not supported" - end + skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? with_pipe {|r, w| - system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s) + system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w) w.close assert_equal("ba\n", r.read) } @@ -612,8 +919,9 @@ class TestProcess < Test::Unit::TestCase write_file("s", <<-"End") exec(#{RUBY.dump}, '-e', 'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil', - #{w.fileno.to_s.dump}) + #{w.fileno.to_s.dump}, :close_others=>false) End + w.close_on_exec = false Process.wait spawn(RUBY, "s", :close_others=>false) w.close assert_equal("bu\n", r.read) @@ -621,11 +929,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)"` @@ -636,9 +947,7 @@ class TestProcess < Test::Unit::TestCase end def test_execopts_close_others - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM - skip "inheritance of fd other than stdin,stdout and stderr is not supported" - end + skip "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? with_tmpchdir {|d| with_pipe {|r, w| system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true) @@ -655,6 +964,7 @@ class TestProcess < Test::Unit::TestCase File.unlink("err") } with_pipe {|r, w| + w.close_on_exec = false Process.wait spawn(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts("bi")', w.fileno.to_s, :close_others=>false) w.close assert_equal("bi\n", r.read) @@ -674,32 +984,52 @@ 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 } } end + def test_close_others_default_false + IO.pipe do |r,w| + w.close_on_exec = false + src = "IO.new(#{w.fileno}).puts(:hi)" + assert_equal true, system(*%W(#{RUBY} --disable=gems -e #{src})) + assert_equal "hi\n", r.gets + end + end unless windows? # passing non-stdio fds is not supported on Windows + def test_execopts_redirect_self begin with_pipe {|r, w| @@ -713,6 +1043,18 @@ class TestProcess < Test::Unit::TestCase rescue NotImplementedError skip "IO#close_on_exec= is not supported" end + end unless windows? # passing non-stdio fds is not supported on Windows + + def test_execopts_redirect_tempfile + bug6269 = '[ruby-core:44181]' + Tempfile.create("execopts") do |tmp| + pid = assert_nothing_raised(ArgumentError, bug6269) do + break spawn(RUBY, "-e", "print $$", out: tmp) + end + Process.wait(pid) + tmp.rewind + assert_equal(pid.to_s, tmp.read) + end end def test_execopts_duplex_io @@ -778,7 +1120,7 @@ class TestProcess < Test::Unit::TestCase ret = system(str) status = $? assert_equal(false, ret) - assert(status.exited?) + assert_predicate(status, :exited?) assert_equal(5, status.exitstatus) assert_equal("haha pid=#{status.pid} ppid=#{$$}", File.read("result")) } @@ -795,7 +1137,7 @@ class TestProcess < Test::Unit::TestCase Process.wait pid status = $? assert_equal(pid, status.pid) - assert(status.exited?) + assert_predicate(status, :exited?) assert_equal(6, status.exitstatus) assert_equal("hihi pid=#{status.pid} ppid=#{$$}", File.read("result")) } @@ -814,12 +1156,31 @@ class TestProcess < Test::Unit::TestCase io.close status = $? assert_equal(pid, status.pid) - assert(status.exited?) + assert_predicate(status, :exited?) assert_equal(7, status.exitstatus) assert_equal("fufu pid=#{status.pid} ppid=#{$$}", result) } end + def test_popen_wordsplit_beginning_and_trailing_spaces + with_tmpchdir {|d| + write_file("script", <<-'End') + print "fufumm pid=#{$$} ppid=#{Process.ppid}" + exit 7 + End + str = " #{RUBY} script " + io = IO.popen(str) + pid = io.pid + result = io.read + io.close + status = $? + assert_equal(pid, status.pid) + assert_predicate(status, :exited?) + assert_equal(7, status.exitstatus) + assert_equal("fufumm pid=#{status.pid} ppid=#{$$}", result) + } + end + def test_exec_wordsplit with_tmpchdir {|d| write_file("script", <<-'End') @@ -840,9 +1201,9 @@ class TestProcess < Test::Unit::TestCase Process.wait pid status = $? assert_equal(pid, status.pid) - assert(status.exited?) + assert_predicate(status, :exited?) assert_equal(6, status.exitstatus) - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + if windows? expected = "hehe ppid=#{status.pid}" else expected = "hehe pid=#{status.pid} ppid=#{$$}" @@ -864,14 +1225,14 @@ class TestProcess < Test::Unit::TestCase ret = system("#{RUBY} script1 || #{RUBY} script2") status = $? assert_equal(false, ret) - assert(status.exited?) + assert_predicate(status, :exited?) result1 = File.read("result1") result2 = File.read("result2") assert_match(/\Ataka pid=\d+ ppid=\d+\z/, result1) assert_match(/\Ataki pid=\d+ ppid=\d+\z/, result2) assert_not_equal(result1[/\d+/].to_i, status.pid) - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + if windows? Dir.mkdir(path = "path with space") write_file(bat = path + "/bat test.bat", "@echo %1>out") system(bat, "foo 'bar'") @@ -895,28 +1256,28 @@ class TestProcess < Test::Unit::TestCase pid = spawn("#{RUBY} script1 || #{RUBY} script2") Process.wait pid status = $? - assert(status.exited?) - assert(!status.success?) + assert_predicate(status, :exited?) + assert_not_predicate(status, :success?) result1 = File.read("result1") result2 = File.read("result2") assert_match(/\Ataku pid=\d+ ppid=\d+\z/, result1) assert_match(/\Atake pid=\d+ ppid=\d+\z/, result2) assert_not_equal(result1[/\d+/].to_i, status.pid) - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + if windows? Dir.mkdir(path = "path with space") write_file(bat = path + "/bat test.bat", "@echo %1>out") pid = spawn(bat, "foo 'bar'") Process.wait pid status = $? - assert(status.exited?) - assert(status.success?) + assert_predicate(status, :exited?) + assert_predicate(status, :success?) assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') pid = spawn(%[#{bat.dump} "foo 'bar'"]) Process.wait pid status = $? - assert(status.exited?) - assert(status.success?) + assert_predicate(status, :exited?) + assert_predicate(status, :success?) assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') end } @@ -936,12 +1297,12 @@ class TestProcess < Test::Unit::TestCase result = io.read io.close status = $? - assert(status.exited?) - assert(!status.success?) + assert_predicate(status, :exited?) + assert_not_predicate(status, :success?) assert_match(/\Atako pid=\d+ ppid=\d+\ntika pid=\d+ ppid=\d+\n\z/, result) assert_not_equal(result[/\d+/].to_i, status.pid) - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + if windows? Dir.mkdir(path = "path with space") write_file(bat = path + "/bat test.bat", "@echo %1") r = IO.popen([bat, "foo 'bar'"]) {|f| f.read} @@ -969,8 +1330,8 @@ class TestProcess < Test::Unit::TestCase pid = spawn RUBY, "s" Process.wait pid status = $? - assert(status.exited?) - assert(!status.success?) + assert_predicate(status, :exited?) + assert_not_predicate(status, :success?) result1 = File.read("result1") result2 = File.read("result2") assert_match(/\Atiki pid=\d+ ppid=\d+\z/, result1) @@ -987,8 +1348,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") @@ -1024,22 +1384,30 @@ class TestProcess < Test::Unit::TestCase with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) } with_stdin("t") { Process.wait spawn([RUBY, "edc"]) } - assert($?.success?) + assert_predicate($?, :success?) with_stdin("f") { Process.wait spawn([RUBY, "rfv"]) } - assert(!$?.success?) + assert_not_predicate($?, :success?) with_stdin("t") { IO.popen([[RUBY, "tgb"]]) {|io| assert_equal("", io.read) } } - assert($?.success?) + assert_predicate($?, :success?) with_stdin("f") { IO.popen([[RUBY, "yhn"]]) {|io| assert_equal("", io.read) } } - assert(!$?.success?) + assert_not_predicate($?, :success?) status = run_in_child "STDIN.reopen('t'); exec([#{RUBY.dump}, 'ujm'])" - assert(status.success?) + assert_predicate(status, :success?) status = run_in_child "STDIN.reopen('f'); exec([#{RUBY.dump}, 'ik,'])" - assert(!status.success?) + assert_not_predicate(status, :success?) } 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") @@ -1057,24 +1425,32 @@ class TestProcess < Test::Unit::TestCase def test_status_kill return unless Process.respond_to?(:kill) - return unless Signal.list.include?("QUIT") + return unless Signal.list.include?("KILL") + + # assume the system supports signal if SIGQUIT is available + expected = Signal.list.include?("QUIT") ? [false, true, false, nil] : [true, false, false, true] with_tmpchdir do - write_file("foo", "sleep 30") - pid = spawn(RUBY, "foo") - Thread.new { sleep 1; Process.kill(:SIGQUIT, pid) } - Process.wait(pid) + write_file("foo", "Process.kill(:KILL, $$); exit(42)") + system(RUBY, "foo") s = $? - assert_equal([false, true, false], - [s.exited?, s.signaled?, s.stopped?], - "[s.exited?, s.signaled?, s.stopped?]") - 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]) - assert_equal(false, s.exited?) - assert_equal(nil, s.success?) + assert_equal(expected, + [s.exited?, s.signaled?, s.stopped?, s.success?], + "[s.exited?, s.signaled?, s.stopped?, s.success?]") + end + end + + def test_status_quit + return unless Process.respond_to?(:kill) + return unless Signal.list.include?("QUIT") + + with_tmpchdir do + 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_equal("#<Process::Status: pid #{ s.pid } SIGQUIT (signal #{ s.termsig })>", + s.inspect.sub(/ \(core dumped\)(?=>\z)/, '')) end end @@ -1106,6 +1482,32 @@ class TestProcess < Test::Unit::TestCase end end + def test_wait_exception + bug11340 = '[ruby-dev:49176] [Bug #11340]' + t0 = t1 = nil + sec = 3 + code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})" + IO.popen([RUBY, '-e', code], 'r+') do |f| + pid = f.pid + f.gets + t0 = Time.now + th = Thread.start(Thread.current) do |main| + Thread.pass until main.stop? + main.raise Interrupt + end + begin + assert_raise(Interrupt) {Process.wait(pid)} + ensure + th.kill.join + end + t1 = Time.now + diff = t1 - t0 + assert_operator(diff, :<, sec, + ->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"}) + f.puts + end + end + def test_abort with_tmpchdir do s = run_in_child("abort") @@ -1115,6 +1517,9 @@ class TestProcess < Test::Unit::TestCase def test_sleep assert_raise(ArgumentError) { sleep(1, 1) } + [-1, -1.0, -1r].each do |sec| + assert_raise_with_message(ArgumentError, /not.*negative/) { sleep(sec) } + end end def test_getpgid @@ -1148,31 +1553,60 @@ class TestProcess < Test::Unit::TestCase end def test_maxgroups - assert_kind_of(Integer, Process.maxgroups) + max = Process.maxgroups + rescue NotImplementedError + else + assert_kind_of(Integer, max) + assert_predicate(max, :positive?) + skip "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM + gs = Process.groups + assert_operator(gs.size, :<=, max) + gs[0] ||= 0 + assert_raise(ArgumentError) {Process.groups = gs * (max / gs.size + 1)} end def test_geteuid + assert_kind_of(Integer, Process.euid) + end + + def test_seteuid + assert_nothing_raised(TypeError) {Process.euid += 0} + rescue NotImplementedError + end + + def test_seteuid_name + user = (Etc.getpwuid(Process.euid).name rescue ENV["USER"]) or return + assert_nothing_raised(TypeError) {Process.euid = user} + rescue NotImplementedError + end + + def test_getegid assert_kind_of(Integer, Process.egid) end + def test_setegid + assert_nothing_raised(TypeError) {Process.egid += 0} + rescue NotImplementedError + end + def test_uid_re_exchangeable_p r = Process::UID.re_exchangeable? - assert(true == r || false == r) + assert_include([true, false], r) end def test_gid_re_exchangeable_p r = Process::GID.re_exchangeable? - assert(true == r || false == r) + assert_include([true, false], r) end def test_uid_sid_available? r = Process::UID.sid_available? - assert(true == r || false == r) + assert_include([true, false], r) end def test_gid_sid_available? r = Process::GID.sid_available? - assert(true == r || false == r) + assert_include([true, false], r) end def test_pst_inspect @@ -1180,16 +1614,34 @@ class TestProcess < Test::Unit::TestCase end def test_wait_and_sigchild + if /freebsd|openbsd/ =~ RUBY_PLATFORM + # this relates #4173 + # When ruby can use 2 cores, signal and wait4 may miss the signal. + skip "this fails on FreeBSD and OpenBSD on multithreaded environment" + end signal_received = [] - Signal.trap(:CHLD) { signal_received << true } - pid = fork { sleep 1; exit } - Thread.start { raise } - Process.wait pid - 5.times do - sleep 1 - break unless signal_received.empty? - end - assert_equal [true], signal_received, " [ruby-core:19744]" + IO.pipe do |sig_r, sig_w| + Signal.trap(:CHLD) do + signal_received << true + sig_w.write('?') + end + pid = nil + IO.pipe do |r, w| + pid = fork { r.read(1); exit } + Thread.start { + Thread.current.report_on_exception = false + raise + } + w.puts + end + Process.wait pid + assert sig_r.wait_readable(5), 'self-pipe not readable' + end + if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE. It may trigger extra SIGCHLD. + assert_equal [true], signal_received.uniq, "[ruby-core:19744]" + else + assert_equal [true], signal_received, "[ruby-core:19744]" + end rescue NotImplementedError, ArgumentError ensure begin @@ -1199,20 +1651,799 @@ class TestProcess < Test::Unit::TestCase end def test_no_curdir - if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM - skip "removing current directory is not supported" - end with_tmpchdir {|d| Dir.mkdir("vd") status = nil Dir.chdir("vd") { dir = "#{d}/vd" # OpenSolaris cannot remove the current directory. - system(RUBY, "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}") - system({"RUBYLIB"=>nil}, RUBY, "-e", "exit true") + system(RUBY, "--disable-gems", "-e", "Dir.chdir '..'; Dir.rmdir #{dir.dump}", err: File::NULL) + system({"RUBYLIB"=>nil}, RUBY, "--disable-gems", "-e", "exit true") status = $? } - assert(status.success?, "[ruby-dev:38105]") + assert_predicate(status, :success?, "[ruby-dev:38105]") + } + end + + def test_fallback_to_sh + feature = '[ruby-core:32745]' + with_tmpchdir do |d| + open("tmp_script.#{$$}", "w") {|f| f.puts ": ;"; f.chmod(0755)} + assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature) + wpid, st = Process.waitpid2(pid) + assert_equal([pid, true], [wpid, st.success?], feature) + + open("tmp_script.#{$$}", "w") {|f| f.puts "echo $#: $@"; f.chmod(0755)} + result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read} + assert_equal("2: a b c\n", result, feature) + + open("tmp_script.#{$$}", "w") {|f| f.puts "echo $hghg"; f.chmod(0755)} + result = IO.popen([{"hghg" => "mogomogo"}, "./tmp_script.#{$$}", "a b", "c"]) {|f| f.read} + assert_equal("mogomogo\n", result, feature) + + end + end if File.executable?("/bin/sh") + + def test_spawn_too_long_path + bug4314 = '[ruby-core:34842]' + assert_fail_too_long_path(%w"echo", bug4314) + 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 + + def assert_fail_too_long_path((cmd, sep), mesg) + sep ||= "" + min = 1_000 / (cmd.size + sep.size) + cmds = Array.new(min, cmd) + exs = [Errno::ENOENT] + exs << Errno::E2BIG if defined?(Errno::E2BIG) + opts = {[STDOUT, STDERR]=>File::NULL} + opts[:rlimit_nproc] = 128 if defined?(Process::RLIMIT_NPROC) + EnvUtil.suppress_warning do + assert_raise(*exs, mesg) do + begin + loop do + Process.spawn(cmds.join(sep), opts) + min = [cmds.size, min].max + cmds *= 100 + end + rescue NoMemoryError + size = cmds.size + raise if min >= size - 1 + min = [min, size /= 2].max + cmds[size..-1] = [] + raise if size < 250 + retry + end + end + end + end + + def test_system_sigpipe + return if windows? + + pid = 0 + + with_tmpchdir do + assert_nothing_raised('[ruby-dev:12261]') do + Timeout.timeout(3) do + pid = spawn('yes | ls') + Process.waitpid pid + end + end + end + ensure + Process.kill(:KILL, pid) if (pid != 0) rescue false + end + + if Process.respond_to?(:daemon) + def test_daemon_default + data = IO.popen("-", "r+") do |f| + break f.read if f + Process.daemon + puts "ng" + end + assert_equal("", data) + end + + def test_daemon_noclose + data = IO.popen("-", "r+") do |f| + break f.read if f + Process.daemon(false, true) + puts "ok", Dir.pwd + end + assert_equal("ok\n/\n", data) + end + + def test_daemon_nochdir_noclose + data = IO.popen("-", "r+") do |f| + break f.read if f + Process.daemon(true, true) + puts "ok", Dir.pwd + end + assert_equal("ok\n#{Dir.pwd}\n", data) + end + + def test_daemon_readwrite + data = IO.popen("-", "r+") do |f| + if f + f.puts "ok?" + break f.read + end + Process.daemon(true, true) + puts STDIN.gets + end + assert_equal("ok?\n", data) + end + + def test_daemon_pid + cpid, dpid = IO.popen("-", "r+") do |f| + break f.pid, Integer(f.read) if f + Process.daemon(false, true) + puts $$ + end + assert_not_equal(cpid, dpid) + end + + if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM + def test_daemon_no_threads + pid, data = IO.popen("-", "r+") do |f| + break f.pid, f.readlines if f + Process.daemon(true, true) + puts Dir.entries("/proc/self/task") - %W[. ..] + end + bug4920 = '[ruby-dev:43873]' + assert_include(1..2, data.size, bug4920) + assert_not_include(data.map(&:to_i), pid) + end + else # darwin + def test_daemon_no_threads + data = Timeout.timeout(3) do + IO.popen("-") do |f| + break f.readlines.map(&:chomp) if f + th = Thread.start {sleep 3} + Process.daemon(true, true) + puts Thread.list.size, th.status.inspect + end + end + assert_equal(["1", "false"], data) + end + end + end + + def test_popen_cloexec + return unless defined? Fcntl::FD_CLOEXEC + IO.popen([RUBY, "-e", ""]) {|io| + assert_predicate(io, :close_on_exec?) + } + 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_popen_reopen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + io = File.open(IO::NULL) + io2 = io.dup + IO.popen("echo") {|f| io.reopen(f)} + io.reopen(io2) + end; + end + + def test_execopts_new_pgroup + return unless windows? + + assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>true) } + assert_nothing_raised { system(*TRUECOMMAND, :new_pgroup=>false) } + assert_nothing_raised { spawn(*TRUECOMMAND, :new_pgroup=>true) } + assert_nothing_raised { IO.popen([*TRUECOMMAND, :new_pgroup=>true]) {} } + end + + def test_execopts_uid + feature6975 = '[ruby-core:47414]' + + [30000, [Process.uid, ENV["USER"]]].each do |uid, user| + if user + assert_nothing_raised(feature6975) do + begin + system(*TRUECOMMAND, uid: user) + rescue Errno::EPERM, NotImplementedError + end + end + end + + assert_nothing_raised(feature6975) do + begin + system(*TRUECOMMAND, uid: uid) + rescue Errno::EPERM, NotImplementedError + end + end + + assert_nothing_raised(feature6975) do + begin + u = IO.popen([RUBY, "-e", "print Process.uid", uid: user||uid], &:read) + assert_equal(uid.to_s, u, feature6975) + rescue Errno::EPERM, NotImplementedError + end + end + end + end + + def test_execopts_gid + skip "Process.groups not implemented on Windows platform" if windows? + feature6975 = '[ruby-core:47414]' + + groups = Process.groups.map do |g| + g = Etc.getgrgid(g) rescue next + [g.name, g.gid] + end + groups.compact! + [30000, *groups].each do |group, gid| + assert_nothing_raised(feature6975) do + begin + system(*TRUECOMMAND, gid: group) + rescue Errno::EPERM, NotImplementedError + end + end + + gid = "#{gid || group}" + assert_nothing_raised(feature6975) do + begin + g = IO.popen([RUBY, "-e", "print Process.gid", gid: group], &:read) + # AIX allows a non-root process to setgid to its supplementary group, + # while other UNIXes do not. (This might be AIX's violation of the POSIX standard.) + # However, Ruby does not allow a setgid'ed Ruby process to use the -e option. + # As a result, the Ruby process invoked by "IO.popen([RUBY, "-e", ..." above fails + # with a message like "no -e allowed while running setgid (SecurityError)" to stderr, + # the exis status is set to 1, and the variable "g" is set to an empty string. + # To conclude, on AIX, if the "gid" variable is a supplementary group, + # the assert_equal next can fail, so skip it. + assert_equal(gid, g, feature6975) unless $?.exitstatus == 1 && /aix/ =~ RUBY_PLATFORM && gid != Process.gid + rescue Errno::EPERM, NotImplementedError + end + end + end + end + + def test_sigpipe + system(RUBY, "-e", "") + with_pipe {|r, w| + r.close + assert_raise(Errno::EPIPE) { w.print "a" } + } + end + + def test_sh_comment + IO.popen("echo a # fofoof") {|f| + assert_equal("a\n", f.read) + } + end if File.executable?("/bin/sh") + + def test_sh_env + IO.popen("foofoo=barbar env") {|f| + lines = f.readlines + assert_operator(lines, :include?, "foofoo=barbar\n") + } + end if File.executable?("/bin/sh") + + def test_sh_exec + IO.popen("exec echo exexexec") {|f| + assert_equal("exexexec\n", f.read) + } + end if File.executable?("/bin/sh") + + def test_setsid + return unless Process.respond_to?(:setsid) + return unless Process.respond_to?(:getsid) + # OpenBSD and AIX don't allow Process::getsid(pid) when pid is in + # different session. + return if /openbsd|aix/ =~ RUBY_PLATFORM + + IO.popen([RUBY, "-e", <<EOS]) do|io| + Marshal.dump(Process.getsid, STDOUT) + newsid = Process.setsid + Marshal.dump(newsid, STDOUT) + STDOUT.flush + # getsid() on MacOS X return ESRCH when target process is zombie + # even if it is valid process id. + sleep +EOS + begin + # test Process.getsid() w/o arg + assert_equal(Marshal.load(io), Process.getsid) + + # test Process.setsid return value and Process::getsid(pid) + assert_equal(Marshal.load(io), Process.getsid(io.pid)) + ensure + Process.kill(:KILL, io.pid) rescue nil + Process.wait(io.pid) + end + end + end + + def test_spawn_nonascii + bug1771 = '[ruby-core:24309] [Bug #1771]' + + with_tmpchdir do + [ + "\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 |name| + msg = "#{bug1771} #{name}" + exename = "./#{name}.exe" + FileUtils.cp(ENV["COMSPEC"], exename) + assert_equal(true, system("#{exename} /c exit"), msg) + system("#{exename} /c exit 12") + assert_equal(12, $?.exitstatus, msg) + _, status = Process.wait2(Process.spawn("#{exename} /c exit 42")) + assert_equal(42, status.exitstatus, msg) + assert_equal("ok\n", `#{exename} /c echo ok`, msg) + assert_equal("ok\n", IO.popen("#{exename} /c echo ok", &:read), msg) + assert_equal("ok\n", IO.popen(%W"#{exename} /c echo ok", &:read), msg) + File.binwrite("#{name}.txt", "ok") + assert_equal("ok", `type #{name}.txt`) + end + 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 + t3 = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + assert_operator(t1, :<=, t2) + assert_operator(t2, :<=, t3) + assert_raise(Errno::EINVAL) { Process.clock_gettime(:foo) } + end + + def test_clock_gettime_unit + t0 = Time.now.to_f + [ + [:nanosecond, 1_000_000_000], + [:microsecond, 1_000_000], + [:millisecond, 1_000], + [:second, 1], + [:float_microsecond, 1_000_000.0], + [:float_millisecond, 1_000.0], + [:float_second, 1.0], + [nil, 1.0], + [:foo], + ].each do |unit, num| + unless num + assert_raise(ArgumentError){ Process.clock_gettime(Process::CLOCK_REALTIME, unit) } + next + end + t1 = Process.clock_gettime(Process::CLOCK_REALTIME, unit) + assert_kind_of num.integer? ? Integer : num.class, t1, [unit, num].inspect + assert_in_delta t0, t1/num, 1, [unit, num].inspect + end + end + + def test_clock_gettime_constants + Process.constants.grep(/\ACLOCK_/).each {|n| + c = Process.const_get(n) + begin + t = Process.clock_gettime(c) + rescue Errno::EINVAL + next + end + assert_kind_of(Float, t, "Process.clock_gettime(Process::#{n})") } end + + def test_clock_gettime_GETTIMEOFDAY_BASED_CLOCK_REALTIME + n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_TIME_BASED_CLOCK_REALTIME + n = :TIME_BASED_CLOCK_REALTIME + t = Process.clock_gettime(n) + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_TIMES_BASED_CLOCK_MONOTONIC + n = :TIMES_BASED_CLOCK_MONOTONIC + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID + t = Process.clock_gettime(n) + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + def test_clock_gettime_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC + n = :MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC + begin + t = Process.clock_gettime(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_gettime(:#{n})") + end + + 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 + + def test_clock_getres_constants + Process.constants.grep(/\ACLOCK_/).each {|n| + c = Process.const_get(n) + begin + t = Process.clock_getres(c) + rescue Errno::EINVAL + next + end + assert_kind_of(Float, t, "Process.clock_getres(Process::#{n})") + } + end + + def test_clock_getres_GETTIMEOFDAY_BASED_CLOCK_REALTIME + n = :GETTIMEOFDAY_BASED_CLOCK_REALTIME + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + assert_equal(1000, Process.clock_getres(n, :nanosecond)) + end + + def test_clock_getres_TIME_BASED_CLOCK_REALTIME + n = :TIME_BASED_CLOCK_REALTIME + t = Process.clock_getres(n) + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + assert_equal(1000000000, Process.clock_getres(n, :nanosecond)) + end + + def test_clock_getres_TIMES_BASED_CLOCK_MONOTONIC + n = :TIMES_BASED_CLOCK_MONOTONIC + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + f = Process.clock_getres(n, :hertz) + assert_equal(0, f - f.floor) + end + + def test_clock_getres_GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + assert_equal(1000, Process.clock_getres(n, :nanosecond)) + end + + def test_clock_getres_TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + f = Process.clock_getres(n, :hertz) + assert_equal(0, f - f.floor) + end + + def test_clock_getres_CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID + n = :CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID + t = Process.clock_getres(n) + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + f = Process.clock_getres(n, :hertz) + assert_equal(0, f - f.floor) + end + + def test_clock_getres_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC + n = :MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC + begin + t = Process.clock_getres(n) + rescue Errno::EINVAL + return + end + assert_kind_of(Float, t, "Process.clock_getres(:#{n})") + end + + def test_deadlock_by_signal_at_forking + assert_separately(%W(--disable=gems - #{RUBY}), <<-INPUT, timeout: 100) + 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, -'--disable=gems'], -'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_rescue_exec_fail + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise(Errno::ENOENT) do + exec("", in: "") + end + end; + end + + def test_many_args + bug11418 = '[ruby-core:70251] [Bug #11418]' + assert_in_out_err([], <<-"end;", ["x"]*256, [], bug11418, timeout: 60) + 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 e22e532c53..ab9a1837f6 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestRand < Test::Unit::TestCase @@ -170,11 +171,13 @@ class TestRand < Test::Unit::TestCase def test_shuffle srand(0) - assert_equal([1,4,2,5,3], [1,2,3,4,5].shuffle) + result = [*1..5].shuffle + assert_equal([*1..5], result.sort) + assert_equal(result, [*1..5].shuffle(random: Random.new(0))) end def test_big_seed - assert_random_int(%w(1143843490), 0x100000000, 2**1000000-1) + assert_random_int(%w(2757555016), 0x100000000, 2**1000000-1) end def test_random_gc @@ -207,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 @@ -339,26 +343,49 @@ END end def test_random_bytes - r = Random.new(0) + assert_random_bytes(Random.new(0)) + end + + def assert_random_bytes(r) + srand(0) assert_equal("", r.bytes(0)) - assert_equal("\xAC".force_encoding("ASCII-8BIT"), r.bytes(1)) - assert_equal("/\xAA\xC4\x97u\xA6\x16\xB7\xC0\xCC".force_encoding("ASCII-8BIT"), r.bytes(10)) + assert_equal("", Random.bytes(0)) + x = "\xAC".force_encoding("ASCII-8BIT") + assert_equal(x, r.bytes(1)) + assert_equal(x, Random.bytes(1)) + x = "/\xAA\xC4\x97u\xA6\x16\xB7\xC0\xCC".force_encoding("ASCII-8BIT") + assert_equal(x, r.bytes(10)) + assert_equal(x, Random.bytes(10)) end def test_random_range + srand(0) r = Random.new(0) - %w(9 5 8).each {|w| assert_equal(w.to_i, r.rand(5..9)) } - %w(-237 731 383).each {|w| assert_equal(w.to_i, r.rand(-1000..1000)) } + %w(9 5 8).each {|w| + assert_equal(w.to_i, rand(5..9)) + assert_equal(w.to_i, r.rand(5..9)) + } + %w(-237 731 383).each {|w| + assert_equal(w.to_i, rand(-1000..1000)) + assert_equal(w.to_i, r.rand(-1000..1000)) + } %w(1267650600228229401496703205382 1267650600228229401496703205384 1267650600228229401496703205383).each do |w| + assert_equal(w.to_i, rand(2**100+5..2**100+9)) assert_equal(w.to_i, r.rand(2**100+5..2**100+9)) end + + v = rand(3.1..4) + assert_instance_of(Float, v, '[ruby-core:24679]') + assert_include(3.1..4, v) + v = r.rand(3.1..4) assert_instance_of(Float, v, '[ruby-core:24679]') - assert_includes(3.1..4, v) + assert_include(3.1..4, v) now = Time.now + assert_equal(now, rand(now..now)) assert_equal(now, r.rand(now..now)) end @@ -372,36 +399,84 @@ END assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) } assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) } + assert_raise(Errno::EDOM) {r.rand(1..)} r = Random.new(0) assert_in_delta(1.5488135039273248, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') assert_in_delta(1.7151893663724195, r.rand(1.0...2.0), 0.0001, '[ruby-core:24655]') assert_in_delta(7.027633760716439, r.rand(1.0...11.0), 0.0001, '[ruby-core:24655]') assert_in_delta(3.0897663659937937, r.rand(2.0...4.0), 0.0001, '[ruby-core:24655]') + + assert_nothing_raised {r.rand(-Float::MAX..Float::MAX)} end def test_random_equal r = Random.new(0) - assert(r == r) - assert(r == r.dup) + assert_equal(r, r) + assert_equal(r, r.dup) r1 = r.dup r2 = r.dup r1.rand(0x100) - assert(r1 != r2) + assert_not_equal(r1, r2) r2.rand(0x100) - assert(r1 == r2) + assert_equal(r1, r2) end def test_fork_shuffle pid = fork do - (1..10).to_a.shuffle - raise 'default seed is not set' if srand == 0 + (1..10).to_a.shuffle + raise 'default seed is not set' if srand == 0 end - p2, st = Process.waitpid2(pid) - assert(st.success?, "#{st.inspect}") + _, st = Process.waitpid2(pid) + assert_predicate(st, :success?, "#{st.inspect}") rescue NotImplementedError, ArgumentError end + def assert_fork_status(n, mesg, &block) + IO.pipe do |r, w| + (1..n).map do + 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 + end + + def test_rand_reseed_on_fork + GC.start + bug5661 = '[ruby-core:41209]' + + assert_fork_status(1, bug5661) {Random.rand(4)} + r1, r2 = *assert_fork_status(2, bug5661) {Random.rand} + assert_not_equal(r1, r2, bug5661) + + assert_fork_status(1, bug5661) {rand(4)} + r1, r2 = *assert_fork_status(2, bug5661) {rand} + assert_not_equal(r1, r2, bug5661) + + stable = Random.new + 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 + def test_seed bug3104 = '[ruby-core:29292]' rand_1 = Random.new(-1).rand @@ -415,4 +490,104 @@ END assert_equal(r1, r2) } end + + def test_default + r1 = Random::DEFAULT.dup + r2 = Random::DEFAULT.dup + 3.times do + x0 = rand + x1 = r1.rand + x2 = r2.rand + assert_equal(x0, x1) + assert_equal(x0, x2) + end + end + + def test_marshal + bug3656 = '[ruby-core:31622]' + assert_raise(TypeError, bug3656) { + Random.new.__send__(:marshal_load, 0) + } + end + + def test_initialize_frozen + r = Random.new(0) + r.freeze + assert_raise(FrozenError, '[Bug #6540]') do + r.__send__(:initialize, r) + end + end + + def test_marshal_load_frozen + r = Random.new(0) + d = r.__send__(:marshal_dump) + r.freeze + assert_raise(FrozenError, '[Bug #6540]') do + r.__send__(:marshal_load, d) + end + end + + def test_random_ulong_limited + def (gen = Object.new).rand(*) 1 end + assert_equal([2], (1..100).map {[1,2,3].sample(random: gen)}.uniq) + + def (gen = Object.new).rand(*) 100 end + assert_raise_with_message(RangeError, /big 100\z/) {[1,2,3].sample(random: gen)} + + bug7903 = '[ruby-dev:47061] [Bug #7903]' + def (gen = Object.new).rand(*) -1 end + assert_raise_with_message(RangeError, /small -1\z/, bug7903) {[1,2,3].sample(random: gen)} + + bug7935 = '[ruby-core:52779] [Bug #7935]' + class << (gen = Object.new) + def rand(limit) @limit = limit; 0 end + attr_reader :limit + 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 a2935955de..a7d34655b3 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -1,22 +1,51 @@ +# frozen_string_literal: false require 'test/unit' require 'delegate' require 'timeout' +require 'bigdecimal' +require 'rbconfig/sizeof' 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") } + + assert_equal((0..nil), Range.new(0, nil, false)) + assert_equal((0...nil), Range.new(0, nil, true)) + + obj = Object.new + def obj.<=>(other) + raise RuntimeError, "cmp" + 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) assert_equal(["a"], ("a" .. "a").to_a) assert_equal(["a"], ("a" ... "b").to_a) assert_equal(["a", "b"], ("a" .. "b").to_a) + assert_equal([*"a".."z", "aa"], ("a"..).take(27)) end def test_range_numeric_string assert_equal(["6", "7", "8"], ("6".."8").to_a, "[ruby-talk:343187]") assert_equal(["6", "7"], ("6"..."8").to_a) assert_equal(["9", "10"], ("9".."10").to_a) + assert_equal(["9", "10"], ("9"..).take(2)) assert_equal(["09", "10"], ("09".."10").to_a, "[ruby-dev:39361]") assert_equal(["9", "10"], (SimpleDelegator.new("9").."10").to_a) + assert_equal(["9", "10"], (SimpleDelegator.new("9")..).take(2)) assert_equal(["9", "10"], ("9"..SimpleDelegator.new("10")).to_a) end @@ -51,33 +80,51 @@ class TestRange < Test::Unit::TestCase assert_equal(1, (1..2).min) assert_equal(nil, (2..1).min) assert_equal(1, (1...2).min) + assert_equal(1, (1..).min) assert_equal(1.0, (1.0..2.0).min) assert_equal(nil, (2.0..1.0).min) assert_equal(1, (1.0...2.0).min) + assert_equal(1, (1.0..).min) assert_equal(0, (0..0).min) assert_equal(nil, (0...0).min) + + assert_equal([0,1,2], (0..10).min(3)) + assert_equal([0,1], (0..1).min(3)) + assert_equal([0,1,2], (0..).min(3)) + + assert_raise(RangeError) { (0..).min {|a, b| a <=> b } } end def test_max assert_equal(2, (1..2).max) assert_equal(nil, (2..1).max) assert_equal(1, (1...2).max) + assert_raise(RangeError) { (1..).max } + assert_raise(RangeError) { (1...).max } assert_equal(2.0, (1.0..2.0).max) assert_equal(nil, (2.0..1.0).max) assert_raise(TypeError) { (1.0...2.0).max } + assert_raise(TypeError) { (1...1.5).max } + assert_raise(TypeError) { (1.5...2).max } assert_equal(-0x80000002, ((-0x80000002)...(-0x80000001)).max) 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)) + assert_raise(RangeError) { (1..).max(3) } + assert_raise(RangeError) { (1...).max(3) } end def test_initialize_twice r = eval("1..2") assert_raise(NameError) { r.instance_eval { initialize 3, 4 } } + assert_raise(NameError) { r.instance_eval { initialize_copy 3..4 } } end def test_uninitialized_range @@ -87,41 +134,76 @@ 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))) + r = (1..) + assert_equal(r, Marshal.load(Marshal.dump(r))) + r = (1...) + assert_equal(r, Marshal.load(Marshal.dump(r))) + end + def test_bad_value assert_raise(ArgumentError) { (1 .. :a) } end def test_exclude_end - assert(!((0..1).exclude_end?)) - assert((0...1).exclude_end?) + assert_not_predicate(0..1, :exclude_end?) + assert_predicate(0...1, :exclude_end?) + assert_not_predicate(0.., :exclude_end?) + assert_predicate(0..., :exclude_end?) end def test_eq r = (0..1) - assert(r == r) - assert(r == (0..1)) - assert(r != 0) - assert(r != (1..2)) - assert(r != (0..2)) - assert(r != (0...1)) + assert_equal(r, r) + assert_equal(r, (0..1)) + assert_not_equal(r, 0) + assert_not_equal(r, (1..2)) + assert_not_equal(r, (0..2)) + assert_not_equal(r, (0...1)) + assert_not_equal(r, (0..nil)) + subclass = Class.new(Range) + assert_equal(r, subclass.new(0,1)) + + r = (0..nil) + assert_equal(r, r) + assert_equal(r, (0..nil)) + assert_not_equal(r, 0) + assert_not_equal(r, (0...nil)) subclass = Class.new(Range) - assert(r == subclass.new(0,1)) + assert_equal(r, subclass.new(0,nil)) end def test_eql r = (0..1) - assert(r.eql?(r)) - assert(r.eql?(0..1)) - assert(!r.eql?(0)) - assert(!r.eql?(1..2)) - assert(!r.eql?(0..2)) - assert(!r.eql?(0...1)) + assert_operator(r, :eql?, r) + assert_operator(r, :eql?, 0..1) + assert_not_operator(r, :eql?, 0) + assert_not_operator(r, :eql?, 1..2) + assert_not_operator(r, :eql?, 0..2) + assert_not_operator(r, :eql?, 0...1) subclass = Class.new(Range) - assert(r.eql?(subclass.new(0,1))) + assert_operator(r, :eql?, subclass.new(0,1)) + + r = (0..nil) + assert_operator(r, :eql?, r) + assert_operator(r, :eql?, 0..nil) + assert_not_operator(r, :eql?, 0) + assert_not_operator(r, :eql?, 0...nil) + subclass = Class.new(Range) + assert_operator(r, :eql?, subclass.new(0,nil)) end def test_hash - assert((0..1).hash.is_a?(Fixnum)) + assert_kind_of(Integer, (0..1).hash) + assert_equal((0..1).hash, (0..1).hash) + assert_not_equal((0..1).hash, (0...1).hash) + assert_equal((0..nil).hash, (0..nil).hash) + assert_not_equal((0..nil).hash, (0...nil).hash) + assert_kind_of(String, (0..1).hash.to_s) end def test_step @@ -130,31 +212,73 @@ class TestRange < Test::Unit::TestCase assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) a = [] + (0..).step {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + + a = [] (0..10).step(2) {|x| a << x } assert_equal([0, 2, 4, 6, 8, 10], a) - assert_raise(ArgumentError) { (0..10).step(-1) { } } + a = [] + (0..).step(2) {|x| a << x; break if a.size == 10 } + assert_equal([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], a) + + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(2)) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(0.5)) + assert_kind_of(Enumerator::ArithmeticSequence, (10..0).step(-1)) + assert_raise(ArgumentError) { (0..10).step(0) { } } + assert_raise(ArgumentError) { (0..).step(-1) { } } + assert_raise(ArgumentError) { (0..).step(0) { } } a = [] ("a" .. "z").step(2) {|x| a << x } assert_equal(%w(a c e g i k m o q s u w y), a) a = [] + ("a" .. ).step(2) {|x| a << x; break if a.size == 13 } + assert_equal(%w(a c e g i k m o q s u w y), a) + + a = [] ("a" .. "z").step(2**32) {|x| a << x } assert_equal(["a"], a) a = [] + (:a .. :z).step(2) {|x| a << x } + assert_equal(%i(a c e g i k m o q s u w y), a) + + a = [] + (:a .. ).step(2) {|x| a << x; break if a.size == 13 } + assert_equal(%i(a c e g i k m o q s u w y), a) + + a = [] + (:a .. :z).step(2**32) {|x| a << x } + assert_equal([:a], a) + + a = [] (2**32-1 .. 2**32+1).step(2) {|x| a << x } assert_equal([4294967295, 4294967297], a) zero = (2**32).coerce(0).first assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) { } } + a = [] + (2**32-1 .. ).step(2) {|x| a << x; break if a.size == 2 } + assert_equal([4294967295, 4294967297], a) + + max = RbConfig::LIMITS["FIXNUM_MAX"] + a = [] + (max..).step {|x| a << x; break if a.size == 2 } + assert_equal([max, max+1], a) + a = [] + (max..).step(max) {|x| a << x; break if a.size == 4 } + assert_equal([max, 2*max, 3*max, 4*max], a) o1 = Object.new o2 = Object.new def o1.<=>(x); -1; end def o2.<=>(x); 0; end assert_raise(TypeError) { (o1..o2).step(1) { } } + assert_raise(TypeError) { (o1..).step(1) { } } class << o1; self; end.class_eval do define_method(:succ) { o2 } @@ -174,12 +298,47 @@ class TestRange < Test::Unit::TestCase assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) a = [] + (0..).step(0.5) {|x| a << x; break if a.size == 5 } + assert_equal([0, 0.5, 1.0, 1.5, 2.0], a) + + a = [] (0x40000000..0x40000002).step(0.5) {|x| a << x } assert_equal([1073741824, 1073741824.5, 1073741825.0, 1073741825.5, 1073741826], a) o = Object.new def o.to_int() 1 end assert_nothing_raised("[ruby-dev:34558]") { (0..2).step(o) {|x| } } + + o = Object.new + class << o + def to_str() "a" end + def <=>(other) to_str <=> other end + end + + a = [] + (o.."c").step(1) {|x| a << x} + assert_equal(["a", "b", "c"], a) + a = [] + (o..).step(1) {|x| a << x; break if a.size >= 3} + assert_equal(["a", "b", "c"], a) + end + + def test_percent_step + aseq = (1..10) % 2 + assert_equal(Enumerator::ArithmeticSequence, aseq.class) + assert_equal(1, aseq.begin) + assert_equal(10, aseq.end) + assert_equal(2, aseq.step) + assert_equal([1, 3, 5, 7, 9], aseq.to_a) + end + + def test_step_ruby_core_35753 + assert_equal(6, (1...6.3).step.to_a.size) + assert_equal(5, (1.1...6).step.to_a.size) + assert_equal(5, (1...6).step(1.1).to_a.size) + assert_equal(3, (1.0...5.4).step(1.5).to_a.size) + assert_equal(3, (1.0...5.5).step(1.5).to_a.size) + assert_equal(4, (1.0...5.6).step(1.5).to_a.size) end def test_each @@ -187,6 +346,10 @@ class TestRange < Test::Unit::TestCase (0..10).each {|x| a << x } assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + a = [] + (0..).each {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + o1 = Object.new o2 = Object.new def o1.setcmp(v) @cmpresult = v end @@ -227,48 +390,197 @@ class TestRange < Test::Unit::TestCase a = [] r2.each {|x| a << x } assert_equal([], a) + + o = Object.new + class << o + def to_str() "a" end + def <=>(other) to_str <=> other end + end + + a = [] + (o.."c").each {|x| a << x} + assert_equal(["a", "b", "c"], a) + a = [] + (o..).each {|x| a << x; break if a.size >= 3} + assert_equal(["a", "b", "c"], a) end def test_begin_end assert_equal(0, (0..1).begin) assert_equal(1, (0..1).end) + assert_equal(1, (0...1).end) + assert_equal(0, (0..nil).begin) + assert_equal(nil, (0..nil).end) + assert_equal(nil, (0...nil).end) end def test_first_last assert_equal([0, 1, 2], (0..10).first(3)) assert_equal([8, 9, 10], (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) + + 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) + + assert_equal([0, 1, 2], (0..nil).first(3)) + assert_equal(0, (0..nil).first) + assert_equal("a", ("a"..nil).first) + assert_raise(RangeError) { (0..nil).last } + assert_raise(RangeError) { (0..nil).last(3) } end def test_to_s assert_equal("0..1", (0..1).to_s) assert_equal("0...1", (0...1).to_s) + assert_equal("0..", (0..nil).to_s) + assert_equal("0...", (0...nil).to_s) + + bug11767 = '[ruby-core:71811] [Bug #11767]' + assert_predicate(("0".taint.."1").to_s, :tainted?, bug11767) + assert_predicate(("0".."1".taint).to_s, :tainted?, bug11767) + assert_predicate(("0".."1").taint.to_s, :tainted?, bug11767) end def test_inspect assert_equal("0..1", (0..1).inspect) assert_equal("0...1", (0...1).inspect) + assert_equal("0..", (0..nil).inspect) + assert_equal("0...", (0...nil).inspect) + + bug11767 = '[ruby-core:71811] [Bug #11767]' + assert_predicate(("0".taint.."1").inspect, :tainted?, bug11767) + assert_predicate(("0".."1".taint).inspect, :tainted?, bug11767) + assert_predicate(("0".."1").taint.inspect, :tainted?, bug11767) end def test_eqq - assert((0..10) === 5) - assert(!((0..10) === 11)) + assert_operator(0..10, :===, 5) + assert_not_operator(0..10, :===, 11) + assert_operator(5..nil, :===, 11) + assert_not_operator(5..nil, :===, 0) + end + + def test_eqq_time + bug11113 = '[ruby-core:69052] [Bug #11113]' + t = Time.now + assert_nothing_raised(TypeError, bug11113) { + assert_operator(t..(t+10), :===, t+5) + assert_operator(t.., :===, t+5) + assert_not_operator(t.., :===, t-5) + } + end + + 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_eqq_non_iteratable + k = Class.new do + include Comparable + attr_reader :i + def initialize(i) @i = i; end + def <=>(o); i <=> o.i; end + end + assert_operator(k.new(0)..k.new(2), :===, k.new(1)) end def test_include - assert(("a".."z").include?("c")) - assert(!(("a".."z").include?("5"))) - assert(("a"..."z").include?("y")) - assert(!(("a"..."z").include?("z"))) - assert(!(("a".."z").include?("cc"))) - assert((0...10).include?(5)) + assert_include("a".."z", "c") + assert_not_include("a".."z", "5") + assert_include("a"..."z", "y") + assert_not_include("a"..."z", "z") + assert_not_include("a".."z", "cc") + assert_include("a".., "c") + assert_not_include("a".., "5") + assert_include(0...10, 5) + assert_include(5..., 10) + assert_not_include(5..., 0) end def test_cover - assert(("a".."z").cover?("c")) - assert(!(("a".."z").cover?("5"))) - assert(("a"..."z").cover?("y")) - assert(!(("a"..."z").cover?("z"))) - assert(("a".."z").cover?("cc")) + assert_operator("a".."z", :cover?, "c") + assert_not_operator("a".."z", :cover?, "5") + assert_operator("a"..."z", :cover?, "y") + assert_not_operator("a"..."z", :cover?, "z") + assert_operator("a".."z", :cover?, "cc") + assert_not_operator(5..., :cover?, 0) + assert_not_operator(5..., :cover?, "a") + assert_operator(5.., :cover?, 10) + + assert_operator(2..5, :cover?, 2..5) + assert_operator(2...6, :cover?, 2...6) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..5, :cover?, 2...6) + assert_operator(2..5, :cover?, 2..4) + assert_operator(2..5, :cover?, 2...4) + assert_operator(2..5, :cover?, 2...5) + assert_operator(2..5, :cover?, 3..5) + assert_operator(2..5, :cover?, 3..4) + assert_operator(2..5, :cover?, 3...6) + assert_operator(2...6, :cover?, 2...5) + assert_operator(2...6, :cover?, 2..5) + assert_operator(2..6, :cover?, 2...6) + assert_operator(2.., :cover?, 2..) + assert_operator(2.., :cover?, 3..) + assert_operator(1.., :cover?, 1..10) + assert_operator(2.0..5.0, :cover?, 2..3) + assert_operator(2..5, :cover?, 2.0..3.0) + assert_operator(2..5, :cover?, 2.0...3.0) + assert_operator(2..5, :cover?, 2.0...5.0) + assert_operator(2.0..5.0, :cover?, 2.0...3.0) + assert_operator(2.0..5.0, :cover?, 2.0...5.0) + assert_operator('aa'..'zz', :cover?, 'aa'...'bb') + + assert_not_operator(2..5, :cover?, 1..5) + assert_not_operator(2...6, :cover?, 1..5) + assert_not_operator(2..5, :cover?, 1...6) + assert_not_operator(1..3, :cover?, 1...6) + assert_not_operator(2..5, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2...7) + assert_not_operator(2..3, :cover?, 1..4) + assert_not_operator(1..2, :cover?, 1.0..3.0) + assert_not_operator(1.0..2.9, :cover?, 1.0..3.0) + assert_not_operator(1..2, :cover?, 4..3) + assert_not_operator(2..1, :cover?, 1..2) + assert_not_operator(1...2, :cover?, 1...3) + assert_not_operator(2.., :cover?, 1..) + assert_not_operator(2.., :cover?, 1..10) + assert_not_operator(1..10, :cover?, 1..) + assert_not_operator(1..5, :cover?, 3..2) + assert_not_operator(1..10, :cover?, 3...2) + assert_not_operator(1..10, :cover?, 3...3) + assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz') + assert_not_operator(1..10, :cover?, 1...10.1) end def test_beg_len @@ -279,12 +591,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]) @@ -308,14 +623,14 @@ class TestRange < Test::Unit::TestCase x = CyclicRange.allocate; x.send(:initialize, x, 1) y = CyclicRange.allocate; y.send(:initialize, y, 1) Timeout.timeout(1) { - assert x == y - assert x.eql? y + assert_equal x, y + assert_operator x, :eql?, y } z = CyclicRange.allocate; z.send(:initialize, z, :another) Timeout.timeout(1) { - assert x != z - assert !x.eql?(z) + assert_not_equal x, z + assert_not_operator x, :eql?, z } x = CyclicRange.allocate @@ -323,8 +638,8 @@ class TestRange < Test::Unit::TestCase x.send(:initialize, y, 1) y.send(:initialize, x, 1) Timeout.timeout(1) { - assert x == y - assert x.eql?(y) + assert_equal x, y + assert_operator x, :eql?, y } x = CyclicRange.allocate @@ -332,8 +647,235 @@ class TestRange < Test::Unit::TestCase x.send(:initialize, z, 1) z.send(:initialize, x, :other) Timeout.timeout(1) { - assert x != z - assert !x.eql?(z) + assert_not_equal x, z + assert_not_operator x, :eql?, z } end + + def test_size + assert_equal 42, (1..42).size + assert_equal 41, (1...42).size + assert_equal 6, (1...6.3).size + assert_equal 5, (1.1...6).size + assert_equal 42, (1..42).each.size + assert_nil ("a"..."z").size + + assert_equal Float::INFINITY, (1...).size + assert_equal Float::INFINITY, (1.0...).size + assert_nil ("a"...).size + end + + def test_bsearch_typechecks_return_values + 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 + + def test_bsearch_with_no_block + enum = (42...666).bsearch + assert_nil enum.size + assert_equal 200, enum.each{|x| x >= 200 } + end + + def test_bsearch_for_other_numerics + assert_raise(TypeError) { + (Rational(-1,2)..Rational(9,4)).bsearch + } + assert_raise(TypeError) { + (BigDecimal('0.5')..BigDecimal('2.25')).bsearch + } + end + + def test_bsearch_for_fixnum + ary = [3, 4, 7, 9, 12] + assert_equal(0, (0...ary.size).bsearch {|i| ary[i] >= 2 }) + assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 4 }) + assert_equal(2, (0...ary.size).bsearch {|i| ary[i] >= 6 }) + assert_equal(3, (0...ary.size).bsearch {|i| ary[i] >= 8 }) + assert_equal(4, (0...ary.size).bsearch {|i| ary[i] >= 10 }) + assert_equal(nil, (0...ary.size).bsearch {|i| ary[i] >= 100 }) + assert_equal(0, (0...ary.size).bsearch {|i| true }) + assert_equal(nil, (0...ary.size).bsearch {|i| false }) + + ary = [0, 100, 100, 100, 200] + assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 }) + + assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 }) + end + + def test_bsearch_for_float + inf = Float::INFINITY + assert_in_delta(10.0, (0.0...100.0).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + assert_in_delta(10.0, (0.0...inf).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + assert_in_delta(-10.0, (-inf..100.0).bsearch {|x| x >= 0 || Math.log(-x / 10) < 0 }, 0.0001) + assert_in_delta(10.0, (-inf..inf).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + assert_equal(nil, (-inf..5).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + + assert_in_delta(10.0, (-inf.. 10).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + assert_equal(nil, (-inf...10).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001) + + assert_equal(nil, (-inf..inf).bsearch { false }) + assert_equal(-inf, (-inf..inf).bsearch { true }) + + assert_equal(inf, (0..inf).bsearch {|x| x == inf }) + assert_equal(nil, (0...inf).bsearch {|x| x == inf }) + + v = (-inf..0).bsearch {|x| x != -inf } + assert_operator(-Float::MAX, :>=, v) + assert_operator(-inf, :<, v) + + v = (0.0..1.0).bsearch {|x| x > 0 } # the nearest positive value to 0.0 + assert_in_delta(0, v, 0.0001) + assert_operator(0, :<, v) + assert_equal(0.0, (-1.0..0.0).bsearch {|x| x >= 0 }) + assert_equal(nil, (-1.0...0.0).bsearch {|x| x >= 0 }) + + v = (0..Float::MAX).bsearch {|x| x >= Float::MAX } + assert_in_delta(Float::MAX, v) + assert_equal(nil, v.infinite?) + + v = (0..inf).bsearch {|x| x >= Float::MAX } + assert_in_delta(Float::MAX, v) + assert_equal(nil, v.infinite?) + + v = (-Float::MAX..0).bsearch {|x| x > -Float::MAX } + assert_operator(-Float::MAX, :<, v) + assert_equal(nil, v.infinite?) + + v = (-inf..0).bsearch {|x| x >= -Float::MAX } + assert_in_delta(-Float::MAX, v) + assert_equal(nil, v.infinite?) + + v = (-inf..0).bsearch {|x| x > -Float::MAX } + assert_operator(-Float::MAX, :<, v) + assert_equal(nil, v.infinite?) + + assert_in_delta(1.0, (0.0..inf).bsearch {|x| Math.log(x) >= 0 }) + assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x }) + + assert_equal(1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 }) + end + + def check_bsearch_values(range, search, a) + from, to = range.begin, range.end + cmp = range.exclude_end? ? :< : :<= + r = nil + + 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 + end + assert_nil r + end + + # prepare for others + yielded = [] + r = range.bsearch do |val| + yielded << val + val >= search + end + + 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 + + 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 + + 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 + + def test_range_bsearch_for_floats + 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] + + 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 + + def test_bsearch_for_bignum + bignum = 2**100 + ary = [3, 4, 7, 9, 12] + assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 2 }) + assert_equal(bignum + 1, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 4 }) + assert_equal(bignum + 2, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 6 }) + assert_equal(bignum + 3, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 8 }) + assert_equal(bignum + 4, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 10 }) + assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 100 }) + assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true }) + assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false }) + assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 }) + + assert_raise(TypeError) { ("a".."z").bsearch {} } + end + + def test_each_no_blockarg + a = "a" + def a.upto(x, e, &b) + super {|y| b.call(y) {|z| assert(false)}} + end + (a.."c").each {|x, &b| assert_nil(b)} + end + + def test_to_a + assert_equal([1,2,3,4,5], (1..5).to_a) + assert_equal([1,2,3,4], (1...5).to_a) + assert_raise(RangeError) { (1..).to_a } + end end diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb index 02d8bd61ed..2957e1bfc8 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,9 +110,45 @@ class Rational_Test < Test::Unit::TestCase assert_equal(Rational(3),Rational('3')) assert_equal(Rational(1),Rational('3.0','3.0')) assert_equal(Rational(1),Rational('3/3','3/3')) + assert_equal(Rational(111, 1), Rational('1.11e+2')) + assert_equal(Rational(111, 10), Rational('1.11e+1')) + assert_equal(Rational(111, 10), Rational('1.11e1')) + assert_equal(Rational(111, 100), Rational('1.11e0')) + assert_equal(Rational(111, 1000), Rational('1.11e-1')) assert_raise(TypeError){Rational(nil)} assert_raise(ArgumentError){Rational('')} + assert_raise_with_message(ArgumentError, /\u{221a 2668}/) { + Rational("\u{221a 2668}") + } + assert_warning('') { + assert_predicate(Rational('1e-99999999999999999999'), :zero?) + } + assert_raise(TypeError){Rational(Object.new)} + assert_raise(TypeError){Rational(Object.new, Object.new)} + assert_raise(TypeError){Rational(1, Object.new)} + + o = Object.new + def o.to_r; 1/42r; end + assert_equal(1/42r, Rational(o)) + assert_equal(1/84r, Rational(o, 2)) + assert_equal(42, Rational(1, o)) + assert_equal(1, Rational(o, o)) + + o = Object.new + def o.to_r; nil; end + assert_raise(TypeError) { Rational(o) } + assert_raise(TypeError) { Rational(o, 2) } + assert_raise(TypeError) { Rational(1, o) } + assert_raise(TypeError) { Rational(o, o) } + + o = Object.new + def o.to_r; raise; end + assert_raise(RuntimeError) { Rational(o) } + assert_raise(RuntimeError) { Rational(o, 2) } + assert_raise(RuntimeError) { Rational(1, o) } + assert_raise(RuntimeError) { Rational(o, o) } + assert_raise(ArgumentError){Rational()} assert_raise(ArgumentError){Rational(1,2,3)} @@ -178,57 +195,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?) + + assert_predicate(Rational(0), :zero?) + assert_predicate(Rational(0,1), :zero?) + assert_not_predicate(Rational(1,1), :zero?) -=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_nil(Rational(0).nonzero?) + assert_nil(Rational(0,1).nonzero?) assert_equal(Rational(1,1), Rational(1,1).nonzero?) end @@ -248,12 +223,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 @@ -297,6 +266,9 @@ class Rational_Test < Test::Unit::TestCase assert_raise(ZeroDivisionError){Rational(1, 3) / 0} assert_raise(ZeroDivisionError){Rational(1, 3) / Rational(0)} + + assert_equal(0, Rational(1, 3) / Float::INFINITY) + assert_predicate(Rational(1, 3) / 0.0, :infinite?, '[ruby-core:31626]') end def assert_eql(exp, act, *args) @@ -338,15 +310,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 @@ -373,15 +343,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 @@ -408,53 +376,14 @@ 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) - - 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 - 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(11) + c2 = Rational(3) - 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 + 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 -=end def test_remainder c = Rational(1,2) @@ -480,53 +409,14 @@ 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) - - 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 - 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)) + c = Rational(11) + c2 = Rational(3) - 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 + 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 -=end def test_quo c = Rational(1,2) @@ -546,6 +436,8 @@ class Rational_Test < Test::Unit::TestCase assert_equal(0.25, c.fdiv(2)) assert_equal(0.25, c.fdiv(2.0)) + assert_equal(0, c.fdiv(Float::INFINITY)) + assert_predicate(c.fdiv(0), :infinite?, '[ruby-core:31626]') end def test_expt @@ -573,50 +465,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) @@ -658,9 +538,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 @@ -693,67 +571,88 @@ 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(Rational(1,1) == Rational(1)) - assert(Rational(-1,1) == Rational(-1)) + 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 assert_equal([Rational(2),Rational(1)], Rational(1).coerce(2)) assert_equal([Rational(2.2),Rational(1)], Rational(1).coerce(2.2)) assert_equal([Rational(2),Rational(1)], Rational(1).coerce(Rational(2))) + + assert_nothing_raised(TypeError, '[Bug #5020] [ruby-dev:44088]') do + Rational(1,2).coerce(Complex(1,1)) + end + + assert_raise(ZeroDivisionError) do + 1 / 0r.coerce(0+0i)[0] + end + assert_raise(ZeroDivisionError) do + 1 / 0r.coerce(0.0+0i)[0] + end end - 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) + class ObjectX + def +(x) Rational(1) end + alias - + + alias * + + alias / + + alias quo + + alias div + + alias % + + alias remainder + + alias ** + + def coerce(x) [x, Rational(1)] end + end + + def test_coerce2 + x = ObjectX.new + %w(+ - * / quo div % remainder **).each do |op| + assert_kind_of(Numeric, Rational(1).__send__(op, x)) 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 @@ -763,13 +662,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) @@ -786,129 +680,170 @@ 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) + assert_predicate(c, :frozen?) + result = c.marshal_load([2,3]) rescue :fail + assert_equal(:fail, result, bug3656) + end + + def test_marshal_compatibility + bug6625 = '[ruby-core:45775]' + dump = "\x04\x08o:\x0dRational\x07:\x11@denominatori\x07:\x0f@numeratori\x06" + 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 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 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(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(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')} + 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 -=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_zero_denominator + assert_raise(ZeroDivisionError) {"1/0".to_r} + assert_raise(ZeroDivisionError) {Rational("1/0")} + end + + def test_Rational_without_exception + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Rational("5/3x", exception: false)) + } + assert_nothing_raised(ZeroDivisionError) { + assert_equal(nil, Rational("1/0", exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, Object.new, exception: false)) + } + + o = Object.new; + def o.to_r; raise; end + assert_nothing_raised(RuntimeError) { + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, o, exception: false)) + } end -=end def test_to_i assert_equal(1, Rational(3,2).to_i) @@ -918,18 +853,12 @@ class Rational_Test < Test::Unit::TestCase def test_to_f assert_equal(1.5, Rational(3,2).to_f) assert_equal(1.5, Float(Rational(3,2))) + assert_equal(1e-23, Rational(1, 10**23).to_f, "Bug #14637") end def test_to_c - 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 @@ -949,13 +878,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} @@ -1005,12 +928,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} @@ -1037,9 +955,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) @@ -1051,22 +979,24 @@ 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)) assert_equal(Rational(1,4), Rational(1,2).quo(2)) + assert_equal(0, Rational(1,2).quo(Float::INFINITY)) + assert_predicate(Rational(1,2).quo(0.0), :infinite?, '[ruby-core:31626]') assert_equal(0.5, 1.fdiv(2)) 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 @@ -1075,15 +1005,59 @@ 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 + bug5715 = '[ruby-core:41498]' + big = 1 << 66 + one = Rational( 1, 1) + assert_eql one, one ** -big , bug5715 + assert_eql one, (-one) ** -big , bug5715 + assert_eql (-one), (-one) ** -(big+1) , bug5715 + assert_equal Complex, ((-one) ** Rational(1,3)).class + end + + def test_power_of_0 + bug5713 = '[ruby-core:41494]' + big = 1 << 66 + zero = Rational(0, 1) + assert_eql zero, zero ** big + assert_eql zero, zero ** Rational(2, 3) + assert_raise(ZeroDivisionError, bug5713) { Rational(0, 1) ** -big } + 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 new file mode 100644 index 0000000000..38d0adbd36 --- /dev/null +++ b/test/ruby/test_refinement.rb @@ -0,0 +1,2263 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRefinement < Test::Unit::TestCase + module Sandbox + BINDING = binding + end + + class Foo + def x + return "Foo#x" + end + + def y + return "Foo#y" + end + + def a + return "Foo#a" + end + + def b + return "Foo#b" + end + + def call_x + return x + end + end + + module FooExt + refine Foo do + def x + return "FooExt#x" + end + + def y + return "FooExt#y " + super + end + + def z + return "FooExt#z" + end + + def a + return "FooExt#a" + end + + private def b + return "FooExt#b" + end + end + end + + module FooExt2 + refine Foo do + def x + return "FooExt2#x" + end + + def y + return "FooExt2#y " + super + end + + def z + return "FooExt2#z" + end + end + end + + class FooSub < Foo + def x + return "FooSub#x" + end + + def y + return "FooSub#y " + super + end + end + + class FooExtClient + using TestRefinement::FooExt + + begin + def self.map_x_on(foo) + [foo].map(&:x)[0] + end + + def self.invoke_x_on(foo) + return foo.x + end + + def self.invoke_y_on(foo) + return foo.y + end + + def self.invoke_z_on(foo) + return foo.z + end + + def self.send_z_on(foo) + return foo.send(:z) + end + + def self.send_b_on(foo) + return foo.send(:b) + end + + def self.public_send_z_on(foo) + return foo.public_send(:z) + end + + def self.public_send_b_on(foo) + return foo.public_send(:b) + end + + def self.method_z(foo) + return foo.method(:z) + end + + def self.invoke_call_x_on(foo) + return foo.call_x + end + + def self.return_proc(&block) + block + end + end + end + + class TestRefinement::FooExtClient2 + using TestRefinement::FooExt + using TestRefinement::FooExt2 + + begin + def self.invoke_y_on(foo) + return foo.y + end + + def self.invoke_a_on(foo) + return foo.a + end + end + end + + def test_override + foo = Foo.new + assert_equal("Foo#x", foo.x) + assert_equal("FooExt#x", FooExtClient.invoke_x_on(foo)) + assert_equal("Foo#x", foo.x) + end + + def test_super + foo = Foo.new + assert_equal("Foo#y", foo.y) + assert_equal("FooExt#y Foo#y", FooExtClient.invoke_y_on(foo)) + assert_equal("Foo#y", foo.y) + end + + def test_super_not_chained + foo = Foo.new + assert_equal("Foo#y", foo.y) + assert_equal("FooExt2#y Foo#y", FooExtClient2.invoke_y_on(foo)) + assert_equal("Foo#y", foo.y) + end + + def test_using_same_class_refinements + foo = Foo.new + assert_equal("Foo#a", foo.a) + assert_equal("FooExt#a", FooExtClient2.invoke_a_on(foo)) + assert_equal("Foo#a", foo.a) + end + + def test_new_method + foo = Foo.new + assert_raise(NoMethodError) { foo.z } + assert_equal("FooExt#z", FooExtClient.invoke_z_on(foo)) + assert_raise(NoMethodError) { foo.z } + end + + module RespondTo + class Super + def foo + end + end + + class Sub < Super + end + + module M + refine Sub do + def foo + end + end + end + end + + def test_send_should_use_refinements + foo = Foo.new + assert_raise(NoMethodError) { foo.send(:z) } + assert_equal("FooExt#z", FooExtClient.send_z_on(foo)) + assert_equal("FooExt#b", FooExtClient.send_b_on(foo)) + assert_raise(NoMethodError) { foo.send(:z) } + + assert_equal(true, RespondTo::Sub.new.respond_to?(:foo)) + end + + def test_public_send_should_use_refinements + foo = Foo.new + assert_raise(NoMethodError) { foo.public_send(:z) } + assert_equal("FooExt#z", FooExtClient.public_send_z_on(foo)) + assert_equal("Foo#b", foo.public_send(:b)) + assert_raise(NoMethodError) { FooExtClient.public_send_b_on(foo) } + end + + def test_method_should_not_use_refinements + foo = Foo.new + assert_raise(NameError) { foo.method(:z) } + assert_raise(NameError) { FooExtClient.method_z(foo) } + assert_raise(NameError) { foo.method(:z) } + end + + def test_no_local_rebinding + foo = Foo.new + assert_equal("Foo#x", foo.call_x) + assert_equal("Foo#x", FooExtClient.invoke_call_x_on(foo)) + assert_equal("Foo#x", foo.call_x) + end + + def test_subclass_is_prior + sub = FooSub.new + assert_equal("FooSub#x", sub.x) + assert_equal("FooSub#x", FooExtClient.invoke_x_on(sub)) + assert_equal("FooSub#x", sub.x) + end + + def test_super_in_subclass + sub = FooSub.new + assert_equal("FooSub#y Foo#y", sub.y) + # not "FooSub#y FooExt#y Foo#y" + assert_equal("FooSub#y Foo#y", FooExtClient.invoke_y_on(sub)) + assert_equal("FooSub#y Foo#y", sub.y) + end + + def test_new_method_on_subclass + sub = FooSub.new + assert_raise(NoMethodError) { sub.z } + assert_equal("FooExt#z", FooExtClient.invoke_z_on(sub)) + assert_raise(NoMethodError) { sub.z } + end + + def test_module_eval + foo = Foo.new + assert_equal("Foo#x", foo.x) + assert_equal("Foo#x", FooExt.module_eval { foo.x }) + assert_equal("Foo#x", FooExt.module_eval("foo.x")) + assert_equal("Foo#x", foo.x) + end + + def test_instance_eval_without_refinement + foo = Foo.new + ext_client = FooExtClient.new + assert_equal("Foo#x", foo.x) + assert_equal("Foo#x", ext_client.instance_eval { foo.x }) + assert_equal("Foo#x", foo.x) + end + + 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(IntegerSlashExt, "1 / 2")) + assert_equal(0, 1 / 2) + end + + module IntegerPlusExt + refine Integer do + def self.method_added(*args); end + def +(other) "overridden" end + end + end + + def test_override_builtin_method_with_method_added + assert_equal(3, 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 + Module.new { + result = refine(Object) { + mod = self + } + } + assert_equal mod, result + end + + module RefineSameClass + REFINEMENT1 = refine(Integer) { + def foo; return "foo" end + } + REFINEMENT2 = refine(Integer) { + def bar; return "bar" end + } + REFINEMENT3 = refine(String) { + def baz; return "baz" end + } + end + + def test_refine_same_class_twice + assert_equal("foo", eval_using(RefineSameClass, "1.foo")) + assert_equal("bar", eval_using(RefineSameClass, "1.bar")) + assert_equal(RefineSameClass::REFINEMENT1, RefineSameClass::REFINEMENT2) + assert_not_equal(RefineSameClass::REFINEMENT1, RefineSameClass::REFINEMENT3) + end + + module IntegerFooExt + refine Integer do + def foo; "foo"; end + end + end + + def test_respond_to_should_use_refinements + assert_equal(false, 1.respond_to?(:foo)) + assert_equal(true, eval_using(IntegerFooExt, "1.respond_to?(:foo)")) + end + + module StringCmpExt + refine String do + def <=>(other) return 0 end + end + end + + module ArrayEachExt + refine Array do + def each + super do |i| + yield 2 * i + end + end + end + end + + def test_builtin_method_no_local_rebinding + assert_equal(false, eval_using(StringCmpExt, '"1" >= "2"')) + assert_equal(1, eval_using(ArrayEachExt, "[1, 2, 3].min")) + end + + module RefinePrependedClass + module M1 + def foo + super << :m1 + end + end + + class C + prepend M1 + + def foo + [:c] + end + end + + module M2 + refine C do + def foo + super << :m2 + end + end + end + end + + def test_refine_prepended_class + x = eval_using(RefinePrependedClass::M2, + "TestRefinement::RefinePrependedClass::C.new.foo") + assert_equal([:c, :m1, :m2], x) + end + + 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 + "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 { + refine Object.new do + end + } + end + assert_raise(TypeError) do + Module.new { + refine 123 do + end + } + end + assert_raise(TypeError) do + Module.new { + refine "foo" do + end + } + end + end + + def test_refine_in_class + assert_raise(NoMethodError) do + Class.new { + refine Integer do + def foo + "c" + end + end + } + end + end + + def test_main_using + assert_in_out_err([], <<-INPUT, %w(:C :M), []) + class C + def foo + :C + end + end + + module M + refine C do + def foo + :M + end + end + end + + c = C.new + p c.foo + using M + p c.foo + INPUT + end + + def test_main_using_is_private + assert_raise(NoMethodError) do + eval("self.using Module.new", Sandbox::BINDING) + end + end + + def test_no_kernel_using + assert_raise(NoMethodError) do + using Module.new + end + end + + class UsingClass + end + + def test_module_using_class + assert_raise(TypeError) do + eval("using TestRefinement::UsingClass", Sandbox::BINDING) + end + end + + def test_refine_without_block + c1 = Class.new + assert_raise_with_message(ArgumentError, "no block given") { + Module.new do + refine c1 + end + } + end + + module Inspect + module M + Integer = refine(Integer) {} + end + end + + def test_inspect + assert_equal("#<refinement:Integer@TestRefinement::Inspect::M>", + Inspect::M::Integer.inspect) + end + + def test_using_method_cache + assert_in_out_err([], <<-INPUT, %w(:M1 :M2), []) + class C + def foo + "original" + end + end + + module M1 + refine C do + def foo + :M1 + end + end + end + + module M2 + refine C do + def foo + :M2 + end + end + end + + c = C.new + using M1 + p c.foo + using M2 + p c.foo + INPUT + end + + module RedefineRefinedMethod + class C + def foo + "original" + end + end + + module M + refine C do + def foo + "refined" + end + end + end + + class C + EnvUtil.suppress_warning do + def foo + "redefined" + end + end + end + end + + def test_redefine_refined_method + x = eval_using(RedefineRefinedMethod::M, + "TestRefinement::RedefineRefinedMethod::C.new.foo") + assert_equal("refined", x) + end + + module StringExt + refine String do + def foo + "foo" + end + end + end + + module RefineScoping + refine String do + def foo + "foo" + end + + def RefineScoping.call_in_refine_block + "".foo + end + end + + def self.call_outside_refine_block + "".foo + end + end + + def test_refine_scoping + assert_equal("foo", RefineScoping.call_in_refine_block) + assert_raise(NoMethodError) do + RefineScoping.call_outside_refine_block + end + end + + module StringRecursiveLength + refine String do + def recursive_length + if empty? + 0 + else + self[1..-1].recursive_length + 1 + end + end + end + end + + def test_refine_recursion + x = eval_using(StringRecursiveLength, "'foo'.recursive_length") + assert_equal(3, x) + end + + module ToJSON + refine Integer do + def to_json; to_s; end + end + + refine Array do + def to_json; "[" + map { |i| i.to_json }.join(",") + "]" end + end + + refine Hash do + def to_json; "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}" end + end + end + + def test_refine_mutual_recursion + x = eval_using(ToJSON, "[{1=>2}, {3=>4}].to_json") + assert_equal('[{"1":2},{"3":4}]', x) + end + + def test_refine_with_proc + assert_raise(ArgumentError) do + Module.new { + refine(String, &Proc.new {}) + } + end + end + + def test_using_in_module + assert_raise(RuntimeError) do + eval(<<-EOF, Sandbox::BINDING) + $main = self + module M + end + module M2 + $main.send(:using, M) + end + EOF + end + end + + def test_using_in_method + assert_raise(RuntimeError) do + eval(<<-EOF, Sandbox::BINDING) + $main = self + module M + end + class C + def call_using_in_method + $main.send(:using, M) + end + end + C.new.call_using_in_method + EOF + end + end + + module IncludeIntoRefinement + class C + def bar + return "C#bar" + end + + def baz + return "C#baz" + end + end + + module Mixin + def foo + return "Mixin#foo" + end + + def bar + return super << " Mixin#bar" + end + + def baz + return super << " Mixin#baz" + end + end + + module M + refine C do + include Mixin + + def baz + return super << " M#baz" + end + end + end + end + + eval <<-EOF, Sandbox::BINDING + using TestRefinement::IncludeIntoRefinement::M + + module TestRefinement::IncludeIntoRefinement::User + def self.invoke_foo_on(x) + x.foo + end + + def self.invoke_bar_on(x) + x.bar + end + + def self.invoke_baz_on(x) + x.baz + end + end + EOF + + def test_include_into_refinement + x = IncludeIntoRefinement::C.new + assert_equal("Mixin#foo", IncludeIntoRefinement::User.invoke_foo_on(x)) + assert_equal("C#bar Mixin#bar", + IncludeIntoRefinement::User.invoke_bar_on(x)) + assert_equal("C#baz Mixin#baz M#baz", + IncludeIntoRefinement::User.invoke_baz_on(x)) + end + + module PrependIntoRefinement + class C + def bar + return "C#bar" + end + + def baz + return "C#baz" + end + end + + module Mixin + def foo + return "Mixin#foo" + end + + def bar + return super << " Mixin#bar" + end + + def baz + return super << " Mixin#baz" + end + end + + module M + refine C do + prepend Mixin + + def baz + return super << " M#baz" + end + end + end + end + + eval <<-EOF, Sandbox::BINDING + using TestRefinement::PrependIntoRefinement::M + + module TestRefinement::PrependIntoRefinement::User + def self.invoke_foo_on(x) + x.foo + end + + def self.invoke_bar_on(x) + x.bar + end + + def self.invoke_baz_on(x) + x.baz + end + end + EOF + + def test_prepend_into_refinement + x = PrependIntoRefinement::C.new + assert_equal("Mixin#foo", PrependIntoRefinement::User.invoke_foo_on(x)) + assert_equal("C#bar Mixin#bar", + PrependIntoRefinement::User.invoke_bar_on(x)) + assert_equal("C#baz M#baz Mixin#baz", + PrependIntoRefinement::User.invoke_baz_on(x)) + end + + PrependAfterRefine_CODE = <<-EOC + module PrependAfterRefine + class C + def foo + "original" + end + end + + module M + refine C do + def foo + "refined" + end + + def bar + "refined" + end + end + end + + module Mixin + def foo + "mixin" + end + + def bar + "mixin" + end + end + + class C + 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, + "TestRefinement::PrependAfterRefine::C.new.foo") + assert_equal("refined", x) + assert_equal("mixin", TestRefinement::PrependAfterRefine::C.new.foo) + y = eval_using(PrependAfterRefine::M, + "TestRefinement::PrependAfterRefine::C.new.bar") + assert_equal("refined", y) + assert_equal("mixin", TestRefinement::PrependAfterRefine::C.new.bar) + end + + module SuperInBlock + class C + def foo(*args) + [:foo, *args] + end + end + + module R + refine C do + def foo(*args) + tap do + return super(:ref, *args) + end + end + end + end + end + + def test_super_in_block + bug7925 = '[ruby-core:52750] [Bug #7925]' + x = eval_using(SuperInBlock::R, + "TestRefinement:: SuperInBlock::C.new.foo(#{bug7925.dump})") + assert_equal([:foo, :ref, bug7925], x, bug7925) + end + + module ModuleUsing + using FooExt + + def self.invoke_x_on(foo) + return foo.x + end + + def self.invoke_y_on(foo) + return foo.y + end + + def self.invoke_z_on(foo) + return foo.z + end + + def self.send_z_on(foo) + return foo.send(:z) + end + + def self.public_send_z_on(foo) + return foo.public_send(:z) + end + + def self.method_z(foo) + return foo.method(:z) + end + + def self.invoke_call_x_on(foo) + return foo.call_x + end + end + + def test_module_using + foo = Foo.new + assert_equal("Foo#x", foo.x) + assert_equal("Foo#y", foo.y) + assert_raise(NoMethodError) { foo.z } + assert_equal("FooExt#x", ModuleUsing.invoke_x_on(foo)) + assert_equal("FooExt#y Foo#y", ModuleUsing.invoke_y_on(foo)) + assert_equal("FooExt#z", ModuleUsing.invoke_z_on(foo)) + assert_equal("Foo#x", foo.x) + assert_equal("Foo#y", foo.y) + assert_raise(NoMethodError) { foo.z } + end + + def test_module_using_in_method + assert_raise(RuntimeError) do + Module.new.send(:using, FooExt) + end + end + + def test_module_using_invalid_self + assert_raise(RuntimeError) do + eval <<-EOF, Sandbox::BINDING + module TestRefinement::TestModuleUsingInvalidSelf + Module.new.send(:using, TestRefinement::FooExt) + end + EOF + end + end + + class Bar + end + + module BarExt + refine Bar do + def x + return "BarExt#x" + end + end + end + + module FooBarExt + include FooExt + include BarExt + end + + module FooBarExtClient + using FooBarExt + + def self.invoke_x_on(foo) + return foo.x + end + end + + def test_module_inclusion + foo = Foo.new + assert_equal("FooExt#x", FooBarExtClient.invoke_x_on(foo)) + bar = Bar.new + assert_equal("BarExt#x", FooBarExtClient.invoke_x_on(bar)) + end + + module FooFoo2Ext + include FooExt + include FooExt2 + end + + module FooFoo2ExtClient + using FooFoo2Ext + + def self.invoke_x_on(foo) + return foo.x + end + + def self.invoke_y_on(foo) + return foo.y + end + end + + def test_module_inclusion2 + foo = Foo.new + assert_equal("FooExt2#x", FooFoo2ExtClient.invoke_x_on(foo)) + assert_equal("FooExt2#y Foo#y", FooFoo2ExtClient.invoke_y_on(foo)) + end + + def test_eval_scoping + assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "HELLO WORLD"], []) + module M + refine String do + def upcase + reverse + end + end + end + + puts "hello world".upcase + puts eval(%{using M; "hello world".upcase}) + puts "hello world".upcase + INPUT + end + + def test_eval_with_binding_scoping + assert_in_out_err([], <<-INPUT, ["HELLO WORLD", "dlrow olleh", "dlrow olleh"], []) + module M + refine String do + def upcase + reverse + end + end + end + + puts "hello world".upcase + b = binding + puts eval(%{using M; "hello world".upcase}, b) + puts eval(%{"hello world".upcase}, b) + INPUT + end + + def test_case_dispatch_is_aware_of_refinements + assert_in_out_err([], <<-RUBY, ["refinement used"], []) + module RefineSymbol + refine Symbol do + def ===(other) + true + end + end + end + + using RefineSymbol + + case :a + when :b + puts "refinement used" + else + puts "refinement not used" + end + RUBY + end + + def test_refine_after_using + assert_separately([], <<-"end;") + bug8880 = '[ruby-core:57079] [Bug #8880]' + module Test + refine(String) do + end + end + using Test + def t + 'Refinements are broken!'.chop! + end + t + module Test + refine(String) do + def chop! + self.sub!(/broken/, 'fine') + end + end + end + assert_equal('Refinements are fine!', t, bug8880) + end; + end + + def test_instance_methods + bug8881 = '[ruby-core:57080] [Bug #8881]' + assert_not_include(Foo.instance_methods(false), :z, bug8881) + assert_not_include(FooSub.instance_methods(true), :z, bug8881) + end + + def test_method_defined + assert_not_send([Foo, :method_defined?, :z]) + assert_not_send([FooSub, :method_defined?, :z]) + end + + def test_undef_refined_method + bug8966 = '[ruby-core:57466] [Bug #8966]' + + assert_in_out_err([], <<-INPUT, ["NameError"], [], bug8966) + module Foo + refine Object do + def foo + puts "foo" + end + end + end + + using Foo + + class Object + begin + undef foo + rescue Exception => e + p e.class + end + end + INPUT + + assert_in_out_err([], <<-INPUT, ["NameError"], [], bug8966) + module Foo + refine Object do + def foo + puts "foo" + end + end + end + + # without `using Foo' + + class Object + begin + undef foo + rescue Exception => e + p e.class + end + end + INPUT + end + + def test_refine_undefed_method_and_call + assert_in_out_err([], <<-INPUT, ["NoMethodError"], []) + class Foo + def foo + end + + undef foo + end + + module FooExt + refine Foo do + def foo + end + end + end + + begin + Foo.new.foo + rescue => e + p e.class + end + INPUT + end + + def test_refine_undefed_method_and_send + assert_in_out_err([], <<-INPUT, ["NoMethodError"], []) + class Foo + def foo + end + + undef foo + end + + module FooExt + refine Foo do + def foo + end + end + end + + begin + Foo.new.send(:foo) + rescue => e + p e.class + end + INPUT + end + + def test_adding_private_method + bug9452 = '[ruby-core:60111] [Bug #9452]' + + assert_in_out_err([], <<-INPUT, ["Success!", "NoMethodError"], [], bug9452) + module R + refine Object do + def m + puts "Success!" + end + + private(:m) + end + end + + using R + + m + 42.m rescue p($!.class) + INPUT + end + + def test_making_private_method_public + bug9452 = '[ruby-core:60111] [Bug #9452]' + + assert_in_out_err([], <<-INPUT, ["Success!", "Success!"], [], bug9452) + class Object + private + def m + end + end + + module R + refine Object do + def m + puts "Success!" + end + end + end + + using R + m + 42.m + INPUT + end + + def test_refine_basic_object + assert_separately([], <<-"end;") + bug10106 = '[ruby-core:64166] [Bug #10106]' + module RefinementBug + refine BasicObject do + def foo + 1 + end + end + end + + assert_raise(NoMethodError, bug10106) {Object.new.foo} + end; + + assert_separately([], <<-"end;") + bug10707 = '[ruby-core:67389] [Bug #10707]' + module RefinementBug + refine BasicObject do + def foo + end + end + end + + assert(methods, bug10707) + assert_raise(NameError, bug10707) {method(:foo)} + end; + end + + def test_change_refined_new_method_visibility + assert_separately([], <<-"end;") + bug10706 = '[ruby-core:67387] [Bug #10706]' + module RefinementBug + refine Object do + def foo + end + end + end + + assert_raise(NameError, bug10706) {private(:foo)} + end; + end + + def test_alias_refined_method + assert_separately([], <<-"end;") + bug10731 = '[ruby-core:67523] [Bug #10731]' + + class C + end + + module RefinementBug + refine C do + def foo + end + + def bar + end + end + end + + assert_raise(NameError, bug10731) do + class C + alias foo bar + end + end + 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]' + + c = Class.new do + def refined_public; end + def refined_protected; end + def refined_private; end + + public :refined_public + protected :refined_protected + private :refined_private + end + + m = Module.new do + refine(c) do + def refined_public; end + def refined_protected; end + def refined_private; end + + public :refined_public + protected :refined_protected + private :refined_private + end + end + + using m + + assert_equal(true, c.public_method_defined?(:refined_public), bug10753) + assert_equal(false, c.public_method_defined?(:refined_protected), bug10753) + assert_equal(false, c.public_method_defined?(:refined_private), bug10753) + + assert_equal(false, c.protected_method_defined?(:refined_public), bug10753) + assert_equal(true, c.protected_method_defined?(:refined_protected), bug10753) + assert_equal(false, c.protected_method_defined?(:refined_private), bug10753) + + assert_equal(false, c.private_method_defined?(:refined_public), bug10753) + assert_equal(false, c.private_method_defined?(:refined_protected), bug10753) + assert_equal(true, c.private_method_defined?(:refined_private), bug10753) + end; + end + + def test_undefined_refined_method_defined + assert_separately([], <<-"end;") + bug10753 = '[ruby-core:67656] [Bug #10753]' + + c = Class.new + + m = Module.new do + refine(c) do + def undefined_refined_public; end + def undefined_refined_protected; end + def undefined_refined_private; end + public :undefined_refined_public + protected :undefined_refined_protected + private :undefined_refined_private + end + end + + using m + + assert_equal(false, c.public_method_defined?(:undefined_refined_public), bug10753) + assert_equal(false, c.public_method_defined?(:undefined_refined_protected), bug10753) + assert_equal(false, c.public_method_defined?(:undefined_refined_private), bug10753) + + assert_equal(false, c.protected_method_defined?(:undefined_refined_public), bug10753) + assert_equal(false, c.protected_method_defined?(:undefined_refined_protected), bug10753) + assert_equal(false, c.protected_method_defined?(:undefined_refined_private), bug10753) + + assert_equal(false, c.private_method_defined?(:undefined_refined_public), bug10753) + assert_equal(false, c.private_method_defined?(:undefined_refined_protected), bug10753) + assert_equal(false, c.private_method_defined?(:undefined_refined_private), bug10753) + end; + end + + def test_remove_refined_method + assert_separately([], <<-"end;") + bug10765 = '[ruby-core:67722] [Bug #10765]' + + class C + def foo + "C#foo" + end + end + + module RefinementBug + refine C do + def foo + "RefinementBug#foo" + end + end + end + + using RefinementBug + + class C + remove_method :foo + end + + assert_equal("RefinementBug#foo", C.new.foo, bug10765) + end; + end + + def test_remove_undefined_refined_method + assert_separately([], <<-"end;") + bug10765 = '[ruby-core:67722] [Bug #10765]' + + class C + end + + module RefinementBug + refine C do + def foo + end + end + end + + using RefinementBug + + assert_raise(NameError, bug10765) { + class C + remove_method :foo + end + } + end; + end + + module NotIncludeSuperclassMethod + class X + def foo + end + end + + class Y < X + end + + module Bar + refine Y do + def foo + end + end + end + end + + def test_instance_methods_not_include_superclass_method + bug10826 = '[ruby-dev:48854] [Bug #10826]' + assert_not_include(NotIncludeSuperclassMethod::Y.instance_methods(false), + :foo, bug10826) + assert_include(NotIncludeSuperclassMethod::Y.instance_methods(true), + :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) + module M + refine Object do + def raise + # do nothing + end + end + + class << self + using M + def m0 + raise + end + end + + using M + def M.m1 + raise + end + end + + M.dup.m0 + M.dup.m1 + INPUT + end + + def test_check_funcall_undefined + bug11117 = '[ruby-core:69064] [Bug #11117]' + + x = Class.new + Module.new do + refine x do + def to_regexp + // + end + end + end + + assert_nothing_raised(NoMethodError, bug11117) { + assert_nil(Regexp.try_convert(x.new)) + } + end + + def test_funcall_inherited + bug11117 = '[ruby-core:69064] [Bug #11117]' + + Module.new {refine(Dir) {def to_s; end}} + x = Class.new(Dir).allocate + assert_nothing_raised(NoMethodError, bug11117) { + x.inspect + } + 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 + + module ToProc + def self.call &block + block.call + end + + class ReturnProc + c = self + using Module.new { + refine c do + def to_proc + proc { "to_proc" } + end + end + } + def call + ToProc.call &self + end + end + + class ReturnNoProc + c = self + using Module.new { + refine c do + def to_proc + true + end + end + } + + def call + ToProc.call &self + end + end + + class PrivateToProc + c = self + using Module.new { + refine c do + private + def to_proc + proc { "private_to_proc" } + end + end + } + + def call + ToProc.call &self + end + end + + + class NonProc + def call + ToProc.call &self + end + end + + class MethodMissing + def method_missing *args + proc { "method_missing" } + end + + def call + ToProc.call &self + end + end + + class ToProcAndMethodMissing + def method_missing *args + proc { "method_missing" } + end + + c = self + using Module.new { + refine c do + def to_proc + proc { "to_proc" } + end + end + } + + def call + ToProc.call &self + end + end + + class ToProcAndRefinements + def to_proc + proc { "to_proc" } + end + + c = self + using Module.new { + refine c do + def to_proc + proc { "refinements_to_proc" } + end + end + } + + def call + ToProc.call &self + end + end + end + + def test_to_proc + assert_equal("to_proc", ToProc::ReturnProc.new.call) + assert_equal("private_to_proc", ToProc::PrivateToProc.new.call) + assert_raise(TypeError){ ToProc::ReturnNoProc.new.call } + assert_raise(TypeError){ ToProc::NonProc.new.call } + assert_equal("method_missing", ToProc::MethodMissing.new.call) + assert_equal("to_proc", ToProc::ToProcAndMethodMissing.new.call) + assert_equal("refinements_to_proc", ToProc::ToProcAndRefinements.new.call) + end + + def test_unused_refinement_for_module + bug14068 = '[ruby-core:83613] [Bug #14068]' + assert_in_out_err([], <<-INPUT, ["M1#foo"], [], bug14068) + module M1 + def foo + puts "M1#foo" + end + end + + module M2 + end + + module UnusedRefinement + refine(M2) do + def foo + puts "M2#foo" + end + end + end + + include M1 + include M2 + foo() + INPUT + end + + def test_refining_module_repeatedly + bug14070 = '[ruby-core:83617] [Bug #14070]' + assert_in_out_err([], <<-INPUT, ["ok"], [], bug14070) + 1000.times do + Class.new do + include Enumerable + end + + Module.new do + refine Enumerable do + def foo + end + end + end + end + puts "ok" + INPUT + end + + def test_call_method_in_unused_refinement + bug15720 = '[ruby-core:91916] [Bug #15720]' + assert_in_out_err([], <<-INPUT, ["ok"], [], bug15720) + module M1 + refine Kernel do + def foo + 'foo called!' + end + end + end + + module M2 + refine Kernel do + def bar + 'bar called!' + end + end + end + + using M1 + + foo + + begin + bar + rescue NameError + end + + puts "ok" + INPUT + end + + def test_super_from_refined_module + a = EnvUtil.labeled_module("A") do + def foo;"[A#{super}]";end + end + b = EnvUtil.labeled_class("B") do + def foo;"[B]";end + end + c = EnvUtil.labeled_class("C", b) do + include a + def foo;"[C#{super}]";end + end + d = EnvUtil.labeled_module("D") do + refine(a) do + def foo;end + end + end + assert_equal("[C[A[B]]]", c.new.foo, '[ruby-dev:50390] [Bug #14232]') + end + + private + + def eval_using(mod, s) + eval("using #{mod}; #{s}", Sandbox::BINDING) + end +end diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 9e850b902e..038c1c830f 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1,5 +1,6 @@ +# coding: US-ASCII +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestRegexp < Test::Unit::TestCase def setup @@ -11,6 +12,12 @@ class TestRegexp < Test::Unit::TestCase $VERBOSE = @verbose end + def test_has_NOENCODING + assert Regexp::NOENCODING + re = //n + assert_equal Regexp::NOENCODING, re.options + end + def test_ruby_dev_999 assert_match(/(?<=a).*b/, "aab") assert_match(/(?<=\u3042).*b/, "\u3042ab") @@ -57,6 +64,20 @@ class TestRegexp < Test::Unit::TestCase def test_to_s assert_equal '(?-mix:\x00)', Regexp.new("\0").to_s + + str = "abcd\u3042" + [:UTF_16BE, :UTF_16LE, :UTF_32BE, :UTF_32LE].each do |es| + enc = Encoding.const_get(es) + rs = Regexp.new(str.encode(enc)).to_s + assert_equal("(?-mix:abcd\u3042)".encode(enc), rs) + assert_equal(enc, rs.encoding) + 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 @@ -69,6 +90,16 @@ class TestRegexp < Test::Unit::TestCase rescue ArgumentError :ok end + re = Regexp.union(/\//, "") + re2 = eval(re.inspect) + assert_equal(re.to_s, re2.to_s) + assert_equal(re.source, re2.source) + assert_equal(re, re2) + end + + def test_word_boundary + assert_match(/\u3042\b /, "\u3042 ") + assert_not_match(/\u3042\ba/, "\u3042a") end def test_named_capture @@ -91,21 +122,29 @@ 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) assert_equal(nil, Regexp.last_match(1)) assert_equal(nil, Regexp.last_match(:foo)) + bug11825_name = "\u{5b9d 77f3}" + bug11825_str = "\u{30eb 30d3 30fc}" + bug11825_re = /(?<#{bug11825_name}>)#{bug11825_str}/ + assert_equal(["foo", "bar"], /(?<foo>.)(?<bar>.)/.names) assert_equal(["foo"], /(?<foo>.)(?<foo>.)/.names) assert_equal([], /(.)(.)/.names) + assert_equal([bug11825_name], bug11825_re.names) assert_equal(["foo", "bar"], /(?<foo>.)(?<bar>.)/.match("ab").names) assert_equal(["foo"], /(?<foo>.)(?<foo>.)/.match("ab").names) assert_equal([], /(.)(.)/.match("ab").names) + assert_equal([bug11825_name], bug11825_re.match(bug11825_str).names) assert_equal({"foo"=>[1], "bar"=>[2]}, /(?<foo>.)(?<bar>.)/.named_captures) @@ -116,14 +155,60 @@ 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 + bug9902 = '[ruby-dev:48275] [Bug #9902]' + + m = /(?<a>.*)/.match("foo") + assert_raise(IndexError, bug9902) {m["a\0foo"]} + assert_raise(IndexError, bug9902) {m["a\0foo".to_sym]} + + m = Regexp.new("(?<foo\0bar>.*)").match("xxx") + assert_raise(IndexError, bug9902) {m["foo"]} + assert_raise(IndexError, bug9902) {m["foo".to_sym]} + assert_nothing_raised(IndexError, bug9902) { + assert_equal("xxx", m["foo\0bar"], bug9902) + assert_equal("xxx", m["foo\0bar".to_sym], bug9902) + } + end + + def test_named_capture_nonascii + bug9903 = '[ruby-dev:48278] [Bug #9903]' + + key = "\xb1\xb2".force_encoding(Encoding::EUC_JP) + m = /(?<#{key}>.*)/.match("xxx") + assert_equal("xxx", m[key]) + 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"') } @@ -132,7 +217,33 @@ class TestRegexp < Test::Unit::TestCase def test_assign_named_capture_to_reserved_word /(?<nil>.)/ =~ "a" - assert(!local_variables.include?(:nil), "[ruby-dev:32675]") + assert_not_include(local_variables, :nil, "[ruby-dev:32675]") + + def (obj = Object.new).test(s, nil: :ng) + /(?<nil>.)/ =~ s + binding.local_variable_get(:nil) + end + assert_equal("b", obj.test("b")) + + tap do |nil: :ng| + /(?<nil>.)/ =~ "c" + assert_equal("c", binding.local_variable_get(:nil)) + end + end + + def test_assign_named_capture_to_const + %W[C \u{1d402}].each do |name| + assert_equal(:ok, Class.new.class_eval("#{name} = :ok; /(?<#{name}>.*)/ =~ 'ng'; #{name}")) + end + end + + def test_assign_named_capture_trace + bug = '[ruby-core:79940] [Bug #13287]' + assert_normal_exit("#{<<-"begin;"}\n#{<<-"end;"}", bug) + begin; + / (?<foo>.*)/ =~ "bar" && + true + end; end def test_match_regexp @@ -144,7 +255,56 @@ class TestRegexp < Test::Unit::TestCase end def test_source + bug5484 = '[ruby-core:40364]' assert_equal('', //.source) + assert_equal('\:', /\:/.source, bug5484) + assert_equal(':', %r:\::.source, bug5484) + end + + def test_source_escaped + expected, result = "$*+.?^|".each_char.map {|c| + [ + ["\\#{c}", "\\#{c}", 1], + begin + re = eval("%r#{c}\\#{c}#{c}", nil, __FILE__, __LINE__) + t = eval("/\\#{c}/", nil, __FILE__, __LINE__).source + rescue SyntaxError => e + [e, t, nil] + else + [re.source, t, re =~ "a#{c}a"] + end + ] + }.transpose + assert_equal(expected, result) + end + + def test_source_escaped_paren + bug7610 = '[ruby-core:51088] [Bug #7610]' + bug8133 = '[ruby-core:53578] [Bug #8133]' + [ + ["(", ")", bug7610], ["[", "]", bug8133], + ["{", "}", bug8133], ["<", ">", bug8133], + ].each do |lparen, rparen, bug| + s = "\\#{lparen}a\\#{rparen}" + assert_equal(/#{s}/, eval("%r#{lparen}#{s}#{rparen}"), bug) + end + end + + def test_source_unescaped + expected, result = "!\"#%&',-/:;=@_`~".each_char.map {|c| + [ + ["#{c}", "\\#{c}", 1], + begin + re = eval("%r#{c}\\#{c}#{c}", nil, __FILE__, __LINE__) + t = eval("%r{\\#{c}}", nil, __FILE__, __LINE__).source + rescue SyntaxError => e + [e, t, nil] + else + [re.source, t, re =~ "a#{c}a"] + end + ] + }.transpose + assert_equal(expected, result) end def test_inspect @@ -155,8 +315,8 @@ class TestRegexp < Test::Unit::TestCase assert_equal('/\/x/i', /\/x/i.inspect) assert_equal('/\x00/i', /#{"\0"}/i.inspect) assert_equal("/\n/i", /#{"\n"}/i.inspect) - s = [0xff].pack("C") - assert_equal('/\/\xFF/i', /\/#{s}/i.inspect) + s = [0xf1, 0xf2, 0xf3].pack("C*") + assert_equal('/\/\xF1\xF2\xF3/i', /\/#{s}/i.inspect) end def test_char_to_option @@ -255,15 +415,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 @@ -279,14 +470,6 @@ class TestRegexp < Test::Unit::TestCase def test_initialize assert_raise(ArgumentError) { Regexp.new } assert_equal(/foo/, Regexp.new(/foo/, Regexp::IGNORECASE)) - re = /foo/ - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; re.instance_eval { initialize(re) } }.join - end - re.taint - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; re.instance_eval { initialize(re) } }.join - end assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", nil, "n").encoding) assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")]) @@ -297,6 +480,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 @@ -319,7 +505,7 @@ class TestRegexp < Test::Unit::TestCase assert_equal(/a/, eval(%q(s="\u0061";/#{s}/n))) assert_raise(RegexpError) { s = "\u3042"; eval(%q(/#{s}/n)) } assert_raise(RegexpError) { s = "\u0061"; eval(%q(/\u3042#{s}/n)) } - assert_raise(RegexpError) { s1=[0xff].pack("C"); s2="\u3042"; eval(%q(/#{s1}#{s2}/)) } + assert_raise(RegexpError) { s1=[0xff].pack("C"); s2="\u3042"; eval(%q(/#{s1}#{s2}/)); [s1, s2] } assert_raise(ArgumentError) { s = '\x'; /#{ s }/ } @@ -355,12 +541,16 @@ class TestRegexp < Test::Unit::TestCase s = ".........." 5.times { s.sub!(".", "") } assert_equal(".....", s) + + assert_equal("\\\u{3042}", Regexp.new("\\\u{3042}").source) end def test_equal - assert_equal(true, /abc/ == /abc/) - assert_equal(false, /abc/ == /abc/m) - assert_equal(false, /abc/ == /abd/) + bug5484 = '[ruby-core:40364]' + assert_equal(/abc/, /abc/) + assert_not_equal(/abc/, /abc/m) + assert_not_equal(/abc/, /abd/) + assert_equal(/\/foo/, Regexp.new('/foo'), bug5484) end def test_match @@ -382,6 +572,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 @@ -396,6 +610,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 @@ -437,6 +655,12 @@ class TestRegexp < Test::Unit::TestCase assert_equal('foo[\z]baz', "foobarbaz".sub!(/(b..)/, '[\z]')) end + def test_regsub_K + bug8856 = '[ruby-dev:47694] [Bug #8856]' + result = "foobarbazquux/foobarbazquux".gsub(/foo\Kbar/, "") + assert_equal('foobazquux/foobazquux', result, bug8856) + end + def test_KCODE assert_nil($KCODE) assert_nothing_raised { $KCODE = nil } @@ -444,6 +668,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 = $~ @@ -452,6 +686,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)) @@ -479,35 +740,24 @@ 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 = 4 - /foo/.match("foo") - end.value - assert(m.tainted?) - assert_nothing_raised('[ruby-core:26137]') { - m = proc {$SAFE = 4; %r"#{ }"o}.call - } - assert(m.tainted?) - end - - def check(re, ss, fs = []) + 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| s ||= e - assert_match(re, s) + assert_match(re, s, msg) m = re.match(s) - assert_equal(e, m[0]) + assert_equal(e, m[0], msg) end fs = [fs] unless fs.is_a?(Array) - fs.each {|s| assert_no_match(re, s) } + 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(/\*\+\?\{\}\|\(\)\<\>\`\'/, "*+?{}|()<>`'") @@ -524,7 +774,7 @@ class TestRegexp < Test::Unit::TestCase check(/\A\80\z/, "80", ["\100", ""]) check(/\A\77\z/, "?") check(/\A\78\z/, "\7" + '8', ["\100", ""]) - check(/\A\Qfoo\E\z/, "QfooE") + check(eval('/\A\Qfoo\E\z/'), "QfooE") check(/\Aa++\z/, "aaa") check('\Ax]\z', "x]") check(/x#foo/x, "x", "#foo") @@ -583,6 +833,9 @@ class TestRegexp < Test::Unit::TestCase failcheck('(?<!.*)') check(/(?<=A|B.)C/, [%w(C AC), %w(C BXC)], %w(C BC)) check(/(?<!A|B.)C/, [%w(C C), %w(C BC)], %w(AC BXC)) + + assert_not_match(/(?<!aa|b)c/i, "Aac") + assert_not_match(/(?<!b|aa)c/i, "Aac") end def test_parse_kg @@ -679,7 +932,7 @@ class TestRegexp < Test::Unit::TestCase check(/\A[a-b-]\z/, %w(a b -), ["", "c"]) check('\A[a-b-&&\w]\z', %w(a b), ["", "-"]) check('\A[a-b-&&\W]\z', "-", ["", "a", "b"]) - check('\A[a-c-e]\z', %w(a b c e), %w(- d)) # is it OK? + check('\A[a-c-e]\z', %w(a b c e -), %w(d)) check(/\A[a-f&&[^b-c]&&[^e]]\z/, %w(a d f), %w(b c e g 0)) check(/\A[[^b-c]&&[^e]&&a-f]\z/, %w(a d f), %w(b c e g 0)) check(/\A[\n\r\t]\z/, ["\n", "\r", "\t"]) @@ -695,9 +948,9 @@ class TestRegexp < Test::Unit::TestCase def test_posix_bracket check(/\A[[:alpha:]0]\z/, %w(0 a), %w(1 .)) - check(/\A[[:^alpha:]0]\z/, %w(0 1 .), "a") - check(/\A[[:alpha\:]]\z/, %w(a l p h a :), %w(b 0 1 .)) - check(/\A[[:alpha:foo]0]\z/, %w(0 a), %w(1 .)) + check(eval('/\A[[:^alpha:]0]\z/'), %w(0 1 .), "a") + check(eval('/\A[[:alpha\:]]\z/'), %w(a l p h a :), %w(b 0 1 .)) + check(eval('/\A[[:alpha:foo]0]\z/'), %w(0 a), %w(1 .)) check(/\A[[:xdigit:]&&[:alpha:]]\z/, "a", %w(g 0)) check('\A[[:abcdefghijklmnopqrstu:]]+\z', "[]") failcheck('[[:alpha') @@ -711,6 +964,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 324}" + assert_match /\A\X\X\z/, "\u{a 324}" + assert_match /\A\X\X\z/, "\u{d 324}" + assert_match /\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}" + assert_match /\A\X\z/, "\u{1F468 200D 1F393}" + assert_match /\A\X\z/, "\u{1F46F 200D 2642 FE0F}" + assert_match /\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}" + + assert_warning('') {/\X/ =~ "\u{a0}"} + end + def test_backward assert_equal(3, "foobar".rindex(/b.r/i)) assert_equal(nil, "foovar".rindex(/b.r/i)) @@ -736,6 +1012,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 } @@ -794,44 +1071,238 @@ class TestRegexp < Test::Unit::TestCase #assert_match(/^(\ufb05)\1\1$/i, "\ufb05\ufb06st") # this must be bug... assert_match(/^\ufb05{3}$/i, "\ufb05\ufb06st") assert_match(/^\u03b9\u0308\u0301$/i, "\u0390") - assert_nothing_raised { 0x03ffffff.chr("utf-8").size } - assert_nothing_raised { 0x7fffffff.chr("utf-8").size } end + def test_unicode_age + assert_match(/^\p{Age=6.0}$/u, "\u261c") + assert_match(/^\p{Age=1.1}$/u, "\u261c") + assert_no_match(/^\P{age=6.0}$/u, "\u261c") + + assert_match(/^\p{age=6.0}$/u, "\u31f6") + assert_match(/^\p{age=3.2}$/u, "\u31f6") + assert_no_match(/^\p{age=3.1}$/u, "\u31f6") + assert_no_match(/^\p{age=3.0}$/u, "\u31f6") + assert_no_match(/^\p{age=1.1}$/u, "\u31f6") + + assert_match(/^\p{age=6.0}$/u, "\u2754") + assert_no_match(/^\p{age=5.0}$/u, "\u2754") + assert_no_match(/^\p{age=4.0}$/u, "\u2754") + assert_no_match(/^\p{age=3.0}$/u, "\u2754") + assert_no_match(/^\p{age=2.0}$/u, "\u2754") + assert_no_match(/^\p{age=1.1}$/u, "\u2754") + + assert_no_match(/^\p{age=12.0}$/u, "\u32FF") + assert_match(/^\p{age=12.1}$/u, "\u32FF") + 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 - def test_optimize_last_anycharstar - s = "1" + " " * 5000000 - assert_nothing_raised { s.match(/(\d) (.*)/) } - assert_equal("1", $1) - assert_equal(" " * 4999999, $2) - end - def test_invalid_fragment bug2547 = '[ruby-core:27374]' assert_raise(SyntaxError, bug2547) {eval('/#{"\\\\"}y/')} end def test_dup_warn - assert_in_out_err(%w/-w -U/, "#coding:utf-8\nx=/[\u3042\u3041]/\n!x", [], []) - assert_in_out_err(%w/-w -U/, "#coding:utf-8\nx=/[\u3042\u3042]/\n!x", [], /duplicated/u, nil, - encoding: Encoding::UTF_8) - assert_in_out_err(%w/-w -U/, "#coding:utf-8\nx=/[\u3042\u3041-\u3043]/\n!x", [], /duplicated/u, nil, - encoding: Encoding::UTF_8) + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3043\u3042]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3043\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3044\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3044]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3043-\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3045\u3042-\u3043]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3045\u3044-\u3045]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3046\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u1000-\u2000\u3042-\u3046\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u3044\u3041-\u3047]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3044\u3046\u3041-\u3047]') } + + bug7471 = '[ruby-core:50344]' + assert_warning('', bug7471) { Regexp.new('[\D]') =~ "\u3042" } + + bug8151 = '[ruby-core:53649]' + assert_warning(/\A\z/, bug8151) { Regexp.new('(?:[\u{33}])').to_s } end def test_property_warn assert_in_out_err('-w', 'x=/\p%s/', [], %r"warning: invalid Unicode Property \\p: /\\p%s/") end + + def test_invalid_escape_error + bug3539 = '[ruby-core:31048]' + error = assert_raise(SyntaxError) {eval('/\x/', nil, bug3539)} + assert_match(/invalid hex escape/, error.message) + assert_equal(1, error.message.scan(/.*invalid .*escape.*/i).size, bug3539) + end + + def test_raw_hyphen_and_tk_char_type_after_range + bug6853 = '[ruby-core:47115]' + # use Regexp.new instead of literal to ignore a parser warning. + check(Regexp.new('[0-1-\\s]'), [' ', '-'], ['2', 'a'], bug6853) + end + + 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 Integer into String', bug7539) { + Regexp.quote(42) + } + end + + def test_conditional_expression + bug8583 = '[ruby-dev:47480] [Bug #8583]' + + 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 + assert_nothing_raised { + assert_match_at("(?<=(?i)ab)cd", "ABcd", [[2,4]]) + assert_match_at("(?<=(?i:ab))cd", "ABcd", [[2,4]]) + assert_match_at("(?<!(?i)ab)cd", "aacd", [[2,4]]) + assert_match_at("(?<!(?i:ab))cd", "aacd", [[2,4]]) + + assert_not_match("(?<=(?i)ab)cd", "ABCD") + assert_not_match("(?<=(?i:ab))cd", "ABCD") + assert_not_match("(?<!(?i)ab)cd", "ABcd") + assert_not_match("(?<!(?i:ab))cd", "ABcd") + } + end + + def test_once + pr1 = proc{|i| /#{i}/o} + assert_equal(/0/, pr1.call(0)) + assert_equal(/0/, pr1.call(1)) + assert_equal(/0/, pr1.call(2)) + end + + def test_once_recursive + pr2 = proc{|i| + if i > 0 + /#{pr2.call(i-1).to_s}#{i}/ + else + // + end + } + assert_equal(/(?-mix:(?-mix:(?-mix:)1)2)3/, pr2.call(3)) + end + + def test_once_multithread + m = Thread::Mutex.new + pr3 = proc{|i| + /#{m.unlock; sleep 0.5; i}/o + } + ary = [] + n = 0 + th1 = Thread.new{m.lock; ary << pr3.call(n+=1)} + th2 = Thread.new{m.lock; ary << pr3.call(n+=1)} + th1.join; th2.join + assert_equal([/1/, /1/], ary) + end + + def test_once_escape + pr4 = proc{|i| + catch(:xyzzy){ + /#{throw :xyzzy, i}/o =~ "" + :ng + } + } + assert_equal(0, pr4.call(0)) + assert_equal(1, pr4.call(1)) + end + + def test_eq_tilde_can_be_overridden + assert_separately([], <<-RUBY) + class Regexp + undef =~ + def =~(str) + "foo" + end + end + + assert_equal("foo", // =~ "") + 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) + + match = re.match(str) + + assert_not_nil match, message(msg) { + "Expected #{re.inspect} to match #{str.inspect}" + } + + if match + actual_positions = (0...match.size).map { |i| + [match.begin(i), match.end(i)] + } + + assert_equal positions, actual_positions, message(msg) { + "Expected #{re.inspect} to match #{str.inspect} at: #{positions.inspect}" + } + end + end + + def assert_match_each(re, conds, msg = nil) + errs = conds.select {|str, match| match ^ (re =~ str)} + msg = message(msg) { + "Expected #{re.inspect} to\n" + + errs.map {|str, match| "\t#{'not ' unless match}match #{str.inspect}"}.join(",\n") + } + assert_empty(errs, msg) + end end diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb index acd9551a55..af8e6e30fa 100644 --- a/test/ruby/test_require.rb +++ b/test/ruby/test_require.rb @@ -1,38 +1,47 @@ +# frozen_string_literal: false require 'test/unit' require 'tempfile' -require_relative 'envutil' require 'tmpdir' class TestRequire < Test::Unit::TestCase - def test_require_invalid_shared_object - t = Tempfile.new(["test_ruby_test_require", ".so"]) - t.puts "dummy" - t.close + def test_load_error_path + filename = "should_not_exist" + error = assert_raise(LoadError) do + require filename + end + assert_equal filename, error.path + end - assert_in_out_err([], <<-INPUT, %w(:ok), []) - begin - require \"#{ t.path }\" - rescue LoadError - p :ok - end - INPUT + def test_require_invalid_shared_object + Tempfile.create(["test_ruby_test_require", ".so"]) {|t| + t.puts "dummy" + t.close + + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + $:.replace([IO::NULL]) + assert_raise(LoadError) do + require \"#{ t.path }\" + end + end; + } end def test_require_too_long_filename - assert_in_out_err([], <<-INPUT, %w(:ok), []) - begin + assert_separately(["RUBYOPT"=>nil], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + $:.replace([IO::NULL]) + assert_raise(LoadError) do require '#{ "foo/" * 10000 }foo' - rescue LoadError - p :ok end - INPUT + end; begin - assert_in_out_err(["-S", "foo/" * 10000 + "foo"], "") do |r, e| + assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e| assert_equal([], r) assert_operator(2, :<=, e.size) - assert_equal("openpath: pathname too long (ignored)", e.first) + assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first) assert_match(/\(LoadError\)/, e.last) end rescue Errno::EINVAL @@ -40,33 +49,230 @@ class TestRequire < Test::Unit::TestCase end end - def test_require_path_home + def test_require_nonascii + bug3758 = '[ruby-core:31915]' + ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path| + assert_raise_with_message(LoadError, /#{path}\z/, bug3758) {require path} + end + end + + def test_require_nonascii_path + bug8165 = '[ruby-core:53733] [Bug #8165]' + encoding = 'filesystem' + 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 + return if Encoding.find('filesystem') == encoding + 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 + return if Encoding.find('filesystem') == encoding + 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) + Encoding::UTF_8 + end + else + def self.ospath_encoding(path) + path.encoding + end + end + + SECURITY_WARNING = + if /mswin|mingw/ =~ RUBY_PLATFORM + nil + else + proc do |require_path| + $SAFE = 1 + require(require_path) + ensure + $SAFE = 0 + end + end + + def prepare_require_path(dir, encoding) + Dir.mktmpdir {|tmp| + begin + require_path = File.join(tmp, dir, 'foo.rb').encode(encoding) + rescue + skip "cannot convert path encoding to #{encoding}" + end + Dir.mkdir(File.dirname(require_path)) + open(require_path, "wb") {|f| f.puts '$:.push __FILE__'} + 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}" + $:.clear + assert_nothing_raised(LoadError, bug) { + assert(require(require_path), bug) + assert_equal(self.class.ospath_encoding(require_path), $:.last.encoding, '[Bug #8753]') + assert(!require(require_path), bug) + } + 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 ENV["RUBYPATH"] = "~" - ENV["HOME"] = "/foo" * 10000 - assert_in_out_err(%w(-S test_ruby_test_require), "", [], /^.+$/) + ENV["HOME"] = "/foo" * 1024 + assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long) + + ensure + env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") + env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") + end - ENV["RUBYPATH"] = "~" + "/foo" * 10000 + def test_require_path_home_2 + env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] + pathname_too_long = /pathname too long \(ignored\).*\(LoadError\)/m + + ENV["RUBYPATH"] = "~" + "/foo" * 1024 ENV["HOME"] = "/foo" - assert_in_out_err(%w(-S test_ruby_test_require), "", [], /^.+$/) + assert_in_out_err(%w(-S -w test_ruby_test_require), "", [], pathname_too_long) - t = Tempfile.new(["test_ruby_test_require", ".rb"]) - t.puts "p :ok" - t.close - ENV["RUBYPATH"] = "~" - ENV["HOME"], name = File.split(t.path) - assert_in_out_err(["-S", name], "", %w(:ok), []) + ensure + env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") + env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") + end + + def test_require_path_home_3 + env_rubypath, env_home = ENV["RUBYPATH"], ENV["HOME"] + + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + t.puts "p :ok" + t.close + + ENV["RUBYPATH"] = "~" + ENV["HOME"] = t.path + assert_in_out_err(%w(-S test_ruby_test_require), "", [], /\(LoadError\)/) + ENV["HOME"], name = File.split(t.path) + assert_in_out_err(["-S", name], "", %w(:ok), []) + } ensure env_rubypath ? ENV["RUBYPATH"] = env_rubypath : ENV.delete("RUBYPATH") env_home ? ENV["HOME"] = env_home : ENV.delete("HOME") end def test_require_with_unc - assert(system(File.expand_path(EnvUtil.rubybin).sub(/\A(\w):/, '//localhost/\1$/'), "-rabbrev", "-e0")) + 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 + Dir.mktmpdir do |tmp| + req = File.join(tmp, "very_long_file_name.rb") + File.write(req, "p :ok\n") + assert_file.exist?(req) + req[/.rb$/i] = "" + assert_in_out_err(['--disable-gems'], <<-INPUT, %w(:ok), []) + require "#{req}" + require "#{req}" + INPUT + 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" @@ -74,33 +280,24 @@ class TestRequire < Test::Unit::TestCase return end - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) BasicSocket = 1 - begin + assert_raise(TypeError) do require 'socket' - p :ng - rescue TypeError - p :ok end INPUT - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) class BasicSocket; end - begin + assert_raise(TypeError) do require 'socket' - p :ng - rescue TypeError - p :ok end INPUT - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) class BasicSocket < IO; end - begin + assert_nothing_raised do require 'socket' - p :ok - rescue Exception - p :ng end INPUT end @@ -112,36 +309,27 @@ class TestRequire < Test::Unit::TestCase return end - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) module Zlib; end Zlib::Error = 1 - begin + assert_raise(TypeError) do require 'zlib' - p :ng - rescue TypeError - p :ok end INPUT - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) module Zlib; end class Zlib::Error; end - begin + assert_raise(TypeError) do require 'zlib' - p :ng - rescue NameError - p :ok end INPUT - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) module Zlib; end class Zlib::Error < StandardError; end - begin + assert_nothing_raised do require 'zlib' - p :ok - rescue Exception - p :ng end INPUT end @@ -153,13 +341,10 @@ class TestRequire < Test::Unit::TestCase return end - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) Zlib = 1 - begin + assert_raise(TypeError) do require 'zlib' - p :ng - rescue TypeError - p :ok end INPUT end @@ -171,96 +356,101 @@ class TestRequire < Test::Unit::TestCase return end - assert_in_out_err([], <<-INPUT, %w(:ok), []) + assert_separately([], <<-INPUT) class BasicSocket < IO; end class Socket < BasicSocket; end Socket::Constants = 1 - begin + assert_raise(TypeError) do require 'socket' - p :ng - rescue TypeError - p :ok end INPUT end def test_load - t = Tempfile.new(["test_ruby_test_require", ".rb"]) - t.puts "module Foo; end" - t.puts "at_exit { p :wrap_end }" - t.puts "at_exit { raise 'error in at_exit test' }" - t.puts "p :ok" - t.close - - assert_in_out_err([], <<-INPUT, %w(:ok :end :wrap_end), /error in at_exit test/) - load(#{ t.path.dump }, true) - GC.start - p :end - INPUT - - assert_raise(ArgumentError) { at_exit } + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + t.puts "module Foo; end" + t.puts "at_exit { p :wrap_end }" + t.puts "at_exit { raise 'error in at_exit test' }" + t.puts "p :ok" + t.close + + assert_in_out_err([], <<-INPUT, %w(:ok :end :wrap_end), /error in at_exit test/) + load(#{ t.path.dump }, true) + GC.start + p :end + INPUT + + assert_raise(ArgumentError) { at_exit } + } end - def test_load2 # [ruby-core:25039] - t = Tempfile.new(["test_ruby_test_require", ".rb"]) - t.puts "Hello = 'hello'" - t.puts "class Foo" - t.puts " p Hello" - t.puts "end" - t.close - - assert_in_out_err([], <<-INPUT, %w("hello"), []) - load(#{ t.path.dump }, true) - INPUT + 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" + t.puts " p Hello" + t.puts "end" + t.close + + assert_in_out_err([], <<-INPUT, %w("hello"), [], bug1982) + load(#{ t.path.dump }, true) + INPUT + } end - def test_tainted_loadpath - t = Tempfile.new(["test_ruby_test_require", ".rb"]) - abs_dir, file = File.split(t.path) - abs_dir = File.expand_path(abs_dir).untaint - - assert_in_out_err([], <<-INPUT, %w(:ok), []) - abs_dir = "#{ abs_dir }" - $: << abs_dir - require "#{ file }" - p :ok - INPUT - - assert_in_out_err([], <<-INPUT, %w(:ok), []) - abs_dir = "#{ abs_dir }" - $: << abs_dir.taint - require "#{ file }" - p :ok - INPUT + def test_load_ospath + bug = '[ruby-list:49994] path in ospath' + base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J) + path = nil + Tempfile.create([base, ".rb"]) do |t| + path = t.path - assert_in_out_err([], <<-INPUT, %w(:ok), []) - abs_dir = "#{ abs_dir }" - $: << abs_dir.taint - $SAFE = 1 - begin - require "#{ file }" - rescue SecurityError - p :ok - end - INPUT + assert_raise_with_message(LoadError, /#{base}/) { + load(File.join(File.dirname(path), base)) + } - assert_in_out_err([], <<-INPUT, %w(:ok), []) - abs_dir = "#{ abs_dir }" - $: << abs_dir.taint - $SAFE = 1 - begin - require "#{ file }" - rescue SecurityError - p :ok - end - INPUT + t.puts "warn 'ok'" + t.close + assert_include(path, base) + assert_warn("ok\n", bug) { + assert_nothing_raised(LoadError, bug) { + load(path) + } + } + end + end - assert_in_out_err([], <<-INPUT, %w(:ok), []) - abs_dir = "#{ abs_dir }" - $: << abs_dir << 'elsewhere'.taint - require "#{ file }" - p :ok - INPUT + def test_tainted_loadpath + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + abs_dir, file = File.split(t.path) + abs_dir = File.expand_path(abs_dir).untaint + + assert_separately([], <<-INPUT) + abs_dir = "#{ abs_dir }" + $: << abs_dir + assert_nothing_raised {require "#{ file }"} + INPUT + + assert_separately([], <<-INPUT) + abs_dir = "#{ abs_dir }" + $: << abs_dir.taint + assert_nothing_raised {require "#{ file }"} + INPUT + + assert_separately([], <<-INPUT) + abs_dir = "#{ abs_dir }" + $: << abs_dir.taint + $SAFE = 1 + assert_raise(SecurityError) {require "#{ file }"} + INPUT + + assert_separately([], <<-INPUT) + abs_dir = "#{ abs_dir }" + $: << abs_dir << 'elsewhere'.taint + assert_nothing_raised {require "#{ file }"} + INPUT + } end def test_relative @@ -296,12 +486,415 @@ class TestRequire < Test::Unit::TestCase File.open("a/tst.rb", "w") {|f| f.puts 'require_relative "lib"' } begin File.symlink("../a/tst.rb", "b/tst.rb") - result = IO.popen([EnvUtil.rubybin, "b/tst.rb"]).read + 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 } } end + + def test_frozen_loaded_features + bug3756 = '[ruby-core:31913]' + assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "ostruct"'], "", + [], /\$LOADED_FEATURES is frozen; cannot append feature \(RuntimeError\)$/, + bug3756) + end + + def test_race_exception + bug5754 = '[ruby-core:41618]' + path = nil + stderr = $stderr + verbose = $VERBOSE + Tempfile.create(%w"bug5754 .rb") {|tmp| + path = tmp.path + tmp.print "#{<<~"begin;"}\n#{<<~"end;"}" + begin; + th = Thread.current + t = th[:t] + scratch = th[:scratch] + + if scratch.empty? + scratch << :pre + Thread.pass until t.stop? + raise RuntimeError + else + scratch << :post + end + end; + tmp.close + + class << (output = "") + alias write concat + end + $stderr = output + + start = false + + scratch = [] + t1_res = nil + t2_res = nil + + t1 = Thread.new do + Thread.pass until start + begin + require(path) + rescue RuntimeError + end + + t1_res = require(path) + end + + t2 = Thread.new do + Thread.pass until scratch[0] + t2_res = require(path) + end + + t1[:scratch] = t2[:scratch] = scratch + t1[:t] = t2 + t2[:t] = t1 + + $VERBOSE = true + start = true + + assert_nothing_raised(ThreadError, bug5754) {t1.join} + assert_nothing_raised(ThreadError, bug5754) {t2.join} + + $VERBOSE = false + + assert_equal(true, (t1_res ^ t2_res), bug5754 + " t1:#{t1_res} t2:#{t2_res}") + assert_equal([:pre, :post], scratch, bug5754) + + assert_match(/circular require/, output) + assert_match(/in #{__method__}'$/o, output) + } + ensure + $VERBOSE = verbose + $stderr = stderr + $".delete(path) + end + + def test_loaded_features_encoding + bug6377 = '[ruby-core:44750]' + loadpath = $:.dup + features = $".dup + $".clear + $:.clear + Dir.mktmpdir {|tmp| + $: << tmp + open(File.join(tmp, "foo.rb"), "w") {} + require "foo" + assert_send([Encoding, :compatible?, tmp, $"[0]], bug6377) + } + ensure + $:.replace(loadpath) + $".replace(features) + end + + def test_require_changed_current_dir + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + Dir.mkdir("a") + Dir.mkdir("b") + open(File.join("a", "foo.rb"), "w") {} + open(File.join("b", "bar.rb"), "w") {|f| + f.puts "p :ok" + } + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; + $:.replace([IO::NULL]) + $: << "." + Dir.chdir("a") + require "foo" + Dir.chdir("../b") + p :ng unless require "bar" + Dir.chdir("..") + p :ng if require "b/bar" + end; + } + } + end + + def test_require_not_modified_load_path + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + open("foo.rb", "w") {} + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7158) + begin; + $:.replace([IO::NULL]) + a = Object.new + def a.to_str + "#{tmp}" + end + $: << a + require "foo" + last_path = $:.pop + p :ok if last_path == a && last_path.class == Object + end; + } + } + end + + def test_require_changed_home + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + open("foo.rb", "w") {} + Dir.mkdir("a") + open(File.join("a", "bar.rb"), "w") {} + 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" + end; + } + } + end + + def test_require_to_path_redefined_in_load_path + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + open("foo.rb", "w") {} + 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 + "bar" + end + $: << a + begin + require "foo" + p [:ng, $LOAD_PATH, ENV['RUBYLIB']] + rescue LoadError => e + raise unless e.path == "foo" + end + def a.to_path + "#{tmp}" + end + p :ok if require "foo" + end; + } + } + end + + def test_require_to_str_redefined_in_load_path + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + open("foo.rb", "w") {} + 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 + "foo" + end + $: << a + begin + require "foo" + p [:ng, $LOAD_PATH, ENV['RUBYLIB']] + rescue LoadError => e + raise unless e.path == "foo" + end + def a.to_str + "#{tmp}" + end + p :ok if require "foo" + end; + } + } + end + + def assert_require_with_shared_array_modified(add, del) + bug7383 = '[ruby-core:49518]' + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + open("foo.rb", "w") {} + Dir.mkdir("a") + open(File.join("a", "bar.rb"), "w") {} + assert_in_out_err(['--disable-gems'], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7383) + begin; + $:.replace([IO::NULL]) + $:.#{add} "#{tmp}" + $:.#{add} "#{tmp}/a" + require "foo" + $:.#{del} + # Expanded load path cache should be rebuilt. + begin + require "bar" + rescue LoadError => e + if e.path == "bar" + p :ok + else + raise + end + end + end; + } + } + end + + def test_require_with_array_pop + assert_require_with_shared_array_modified("push", "pop") + end + + def test_require_with_array_shift + assert_require_with_shared_array_modified("unshift", "shift") + end + + def test_require_local_var_on_toplevel + bug7536 = '[ruby-core:50701]' + 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], "#{<<~"begin;"}\n#{<<~"end;"}", %w([:lib] 2), [], bug7536) + begin; + puts TOPLEVEL_BINDING.eval("local_variables").inspect + puts TOPLEVEL_BINDING.eval("lib").inspect + end; + } + } + end + + def test_require_with_loaded_features_pop + bug7530 = '[ruby-core:50645]' + Tempfile.create(%w'bug-7530- .rb') {|script| + script.close + assert_in_out_err([{"RUBYOPT" => nil}, "-", script.path], "#{<<~"begin;"}\n#{<<~"end;"}", %w(:ok), [], bug7530, timeout: 60) + begin; + PATH = ARGV.shift + THREADS = 4 + ITERATIONS_PER_THREAD = 1000 + + THREADS.times.map { + Thread.new do + ITERATIONS_PER_THREAD.times do + require PATH + $".delete_if {|p| Regexp.new(PATH) =~ p} + end + end + }.each(&:join) + p :ok + end; + } + end + + 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], "#{<<~"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; + } + 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 e41bdf2936..69521b1d23 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1,22 +1,51 @@ +# -*- coding: us-ascii -*- require 'test/unit' +require 'timeout' require 'tmpdir' require 'tempfile' -require_relative 'envutil' +require_relative '../lib/jit_support' class TestRubyOptions < Test::Unit::TestCase + NO_JIT_DESCRIPTION = + if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + RUBY_DESCRIPTION.sub(/\+JIT /, '') + else + RUBY_DESCRIPTION + end + + def write_file(filename, content) + File.open(filename, "w") {|f| + f << content + } + end + + def with_tmpchdir + Dir.mktmpdir {|d| + d = File.realpath(d) + Dir.chdir(d) { + yield d + } + } + end + def test_source_file assert_in_out_err([], "", [], []) end def test_usage assert_in_out_err(%w(-h)) do |r, e| - assert_operator(r.size, :<=, 24) + assert_operator(r.size, :<=, 25) + longer = r[1..-1].select {|x| x.size > 80} + assert_equal([], longer) assert_equal([], e) end + end + def test_usage_long assert_in_out_err(%w(--help)) do |r, e| - assert_operator(r.size, :<=, 24) + longer = r[1..-1].select {|x| x.size > 80} + assert_equal([], longer) assert_equal([], e) end end @@ -28,7 +57,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', @@ -37,6 +66,7 @@ class TestRubyOptions < Test::Unit::TestCase end end + def test_warning save_rubyopt = ENV['RUBYOPT'] ENV['RUBYOPT'] = nil @@ -44,6 +74,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 @@ -57,15 +88,40 @@ class TestRubyOptions < Test::Unit::TestCase end def test_debug - assert_in_out_err(%w(-de) + ["p $DEBUG"], "", %w(true), []) + assert_in_out_err(["--disable-gems", "-de", "p $DEBUG"], "", %w(true), []) - assert_in_out_err(%w(--debug -e) + ["p $DEBUG"], "", %w(true), []) + assert_in_out_err(["--disable-gems", "--debug", "-e", "p $DEBUG"], + "", %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 + + VERSION_PATTERN_WITH_JIT = + case RUBY_ENGINE + when 'ruby' + /^ruby #{q[RUBY_VERSION]}(?:[p ]|dev|rc).*? \+JIT \[#{q[RUBY_PLATFORM]}\]$/ + else + VERSION_PATTERN + end + private_constant :VERSION_PATTERN_WITH_JIT + def test_verbose - assert_in_out_err(%w(-vve) + [""]) do |r, e| - assert_match(/^ruby #{RUBY_VERSION}(?:[p ]|dev).*? \[#{RUBY_PLATFORM}\]$/, r.join) - assert_equal RUBY_DESCRIPTION, r.join.chomp + assert_in_out_err(["-vve", ""]) do |r, e| + assert_match(VERSION_PATTERN, r[0]) + if RubyVM::MJIT.enabled? && !mjit_force_enabled? # checking -DMJIT_FORCE_ENABLE + assert_equal(NO_JIT_DESCRIPTION, r[0]) + else + assert_equal(RUBY_DESCRIPTION, r[0]) + end assert_equal([], e) end @@ -82,9 +138,11 @@ class TestRubyOptions < Test::Unit::TestCase end def test_enable - assert_in_out_err(%w(--enable all -e) + [""], "", [], []) - assert_in_out_err(%w(--enable-all -e) + [""], "", [], []) - assert_in_out_err(%w(--enable=all -e) + [""], "", [], []) + if JITSupport.supported? + assert_in_out_err(%w(--enable all -e) + [""], "", [], []) + assert_in_out_err(%w(--enable-all -e) + [""], "", [], []) + assert_in_out_err(%w(--enable=all -e) + [""], "", [], []) + end assert_in_out_err(%w(--enable foobarbazqux -e) + [""], "", [], /unknown argument for --enable: `foobarbazqux'/) assert_in_out_err(%w(--enable), "", [], /missing argument for --enable/) @@ -97,23 +155,70 @@ 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"], []) + assert_in_out_err(%w(--disable-gems -e) + ['p defined? DidYouMean'], "", ["nil"], []) end def test_kanji assert_in_out_err(%w(-KU), "p '\u3042'") do |r, e| assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8)) end - assert_in_out_err(%w(-KE -e) + [""], "", [], []) - assert_in_out_err(%w(-KS -e) + [""], "", [], []) - assert_in_out_err(%w(-KN -e) + [""], "", [], []) + line = '-eputs"\xc2\xa1".encoding' + env = {'RUBYOPT' => nil} + assert_in_out_err([env, '-Ke', line], "", ["EUC-JP"], []) + assert_in_out_err([env, '-KE', line], "", ["EUC-JP"], []) + assert_in_out_err([env, '-Ks', line], "", ["Windows-31J"], []) + assert_in_out_err([env, '-KS', line], "", ["Windows-31J"], []) + assert_in_out_err([env, '-Ku', line], "", ["UTF-8"], []) + assert_in_out_err([env, '-KU', line], "", ["UTF-8"], []) + assert_in_out_err([env, '-Kn', line], "", ["ASCII-8BIT"], []) + assert_in_out_err([env, '-KN', line], "", ["ASCII-8BIT"], []) + assert_in_out_err([env, '-wKe', line], "", ["EUC-JP"], /-K/) end 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]) + if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + assert_equal(EnvUtil.invoke_ruby(['-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) + else + assert_equal(RUBY_DESCRIPTION, r[0]) + end assert_equal([], e) end + + return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' + + [ + %w(--version --jit --disable=jit), + %w(--version --enable=jit --disable=jit), + %w(--version --enable-jit --disable-jit), + ].each do |args| + assert_in_out_err(args) do |r, e| + assert_match(VERSION_PATTERN, r[0]) + assert_match(NO_JIT_DESCRIPTION, r[0]) + assert_equal([], e) + end + end + + if JITSupport.supported? + [ + %w(--version --jit), + %w(--version --enable=jit), + %w(--version --enable-jit), + ].each do |args| + assert_in_out_err(args) do |r, e| + assert_match(VERSION_PATTERN_WITH_JIT, r[0]) + if RubyVM::MJIT.enabled? # checking -DMJIT_FORCE_ENABLE + assert_equal(RUBY_DESCRIPTION, r[0]) + else + assert_equal(EnvUtil.invoke_ruby(['--jit', '-e', 'print RUBY_DESCRIPTION'], '', true).first, r[0]) + end + assert_equal([], e) + end + end + end end def test_eval @@ -124,6 +229,8 @@ class TestRubyOptions < Test::Unit::TestCase require "pp" assert_in_out_err(%w(-r pp -e) + ["pp 1"], "", %w(1), []) assert_in_out_err(%w(-rpp -e) + ["pp 1"], "", %w(1), []) + assert_in_out_err(%w(-ep\ 1 -r), "", %w(1), []) + assert_in_out_err(%w(-r), "", [], []) rescue LoadError end @@ -139,6 +246,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 @@ -153,32 +264,41 @@ class TestRubyOptions < Test::Unit::TestCase d = Dir.tmpdir assert_in_out_err(["-C", d, "-e", "puts Dir.pwd"]) do |r, e| - assert(File.identical?(r.join, d)) + assert_file.identical?(r.join, d) assert_equal([], e) end end 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 def test_encoding - assert_in_out_err(%w(-Eutf-8), "p '\u3042'", [], /invalid multibyte char/) - assert_in_out_err(%w(--encoding), "", [], /missing argument for --encoding/) assert_in_out_err(%w(--encoding test_ruby_test_rubyoptions_foobarbazqux), "", [], /unknown encoding name - test_ruby_test_rubyoptions_foobarbazqux \(RuntimeError\)/) - assert_in_out_err(%w(--encoding utf-8), "p '\u3042'", [], /invalid multibyte char/) + 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/ + end + assert_in_out_err(%w(-Eutf-8), "puts '\u3042'", out, err) + assert_in_out_err(%w(--encoding utf-8), "puts '\u3042'", out, err) end def test_syntax_check @@ -207,10 +327,10 @@ class TestRubyOptions < Test::Unit::TestCase assert_in_out_err([], "", [], /invalid switch in RUBYOPT: -e \(RuntimeError\)/) ENV['RUBYOPT'] = '-T1' - assert_in_out_err([], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) + assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) ENV['RUBYOPT'] = '-T4' - assert_in_out_err([], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) + assert_in_out_err(["--disable-gems"], "", [], /no program input from stdin allowed in tainted mode \(SecurityError\)/) ENV['RUBYOPT'] = '-Eus-ascii -KN' assert_in_out_err(%w(-Eutf-8 -KU), "p '\u3042'") do |r, e| @@ -218,6 +338,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 @@ -230,20 +354,23 @@ class TestRubyOptions < Test::Unit::TestCase rubypath_orig = ENV['RUBYPATH'] path_orig = ENV['PATH'] - t = Tempfile.new(["test_ruby_test_rubyoption", ".rb"]) - t.puts "p 1" - t.close - - @verbose = $VERBOSE - $VERBOSE = nil + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| + t.puts "p 1" + t.close - ENV['PATH'] = File.dirname(t.path) + @verbose = $VERBOSE + $VERBOSE = nil - 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 if rubypath_orig @@ -256,7 +383,6 @@ class TestRubyOptions < Test::Unit::TestCase else ENV.delete('PATH') end - t.close(true) if t $VERBOSE = @verbose end @@ -267,9 +393,35 @@ 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([], "#!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) + 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 @@ -286,45 +438,132 @@ class TestRubyOptions < Test::Unit::TestCase end def test_assignment_in_conditional - t = Tempfile.new(["test_ruby_test_rubyoption", ".rb"]) - t.puts "if a = 1" - t.puts "end" - t.puts "0.times do" - t.puts " if b = 2" - t.puts " a += b" - t.puts " end" - t.puts "end" - t.close - err = ["#{t.path}:1: warning: found = in conditional, should be ==", - "#{t.path}:4: warning: found = in conditional, should be =="] - err = /\A(#{Regexp.quote(t.path)}):1(: warning: found = in conditional, should be ==)\n\1:4\2\Z/ - bug2136 = '[ruby-dev:39363]' - assert_in_out_err(["-w", t.path], "", [], err, bug2136) - assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, bug2136) - ensure - t.close(true) if t + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| + t.puts "if a = 1" + t.puts "end" + t.puts "0.times do" + t.puts " if b = 2" + t.puts " a += b" + t.puts " end" + t.puts "end" + t.flush + warning = ' warning: found `= literal\' in conditional, should be ==' + err = ["#{t.path}:1:#{warning}", + "#{t.path}:4:#{warning}", + ] + bug2136 = '[ruby-dev:39363]' + assert_in_out_err(["-w", t.path], "", [], err, bug2136) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, bug2136) + + t.rewind + t.truncate(0) + t.puts "if a = ''; end" + t.puts "if a = []; end" + t.puts "if a = [1]; end" + t.puts "if a = [a]; end" + t.puts "if a = {}; end" + t.puts "if a = {1=>2}; end" + t.puts "if a = {3=>a}; end" + t.flush + err = ["#{t.path}:1:#{warning}", + "#{t.path}:2:#{warning}", + "#{t.path}:3:#{warning}", + "#{t.path}:5:#{warning}", + "#{t.path}:6:#{warning}", + ] + feature4299 = '[ruby-dev:43083]' + assert_in_out_err(["-w", t.path], "", [], err, feature4299) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, feature4299) + } end def test_indentation_check - t = Tempfile.new(["test_ruby_test_rubyoption", ".rb"]) - t.puts "begin" - t.puts " end" - t.close - 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) - ensure - t.close(true) if t + 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"], ["-> {", "}"], + ["if false;", "else ; end"], + ["if false;", "elsif false ; end"], + ["begin", "rescue ; end"], + ["begin rescue", "else ; end"], + ["begin", "ensure ; end"], + [" case nil", "when true; end"], + ["case nil; when true", "end"], + ].each do + |b, e = 'end'| + src = ["#{b}\n", " #{e}\n"] + k = b[/\A\s*(\S+)/, 1] + e = e[/\A\s*(\S+)/, 1] + + 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 + + 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 + + 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 + + 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 = Regexp.quote(EnvUtil.rubybin) + 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]' - assert_equal(false, File.exist?(notexist)) - assert_in_out_err(["-r", notexist, "-ep"], "", [], /\A-e:.* -- #{pat} \(LoadError\)\Z/, bug1573) - assert_in_out_err([notexist], "", [], /\A#{rubybin}:.* -- #{pat} \(LoadError\)\Z/, bug1573) + assert_file.not_exist?(notexist) + assert_in_out_err(["-r", notexist, "-ep"], "", [], /.* -- #{pat} \(LoadError\)/, bug1573) + assert_in_out_err([notexist], "", [], /#{rubybin}:.* -- #{pat} \(LoadError\)/, bug1573) end def test_program_name @@ -345,10 +584,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' @@ -366,60 +609,177 @@ class TestRubyOptions < Test::Unit::TestCase } end - def test_segv_test + if /linux|freebsd|netbsd|openbsd|darwin/ =~ RUBY_PLATFORM + PSCMD = EnvUtil.find_executable("ps", "-o", "command", "-p", $$.to_s) {|out| /ruby/=~out} + PSCMD&.pop + end + + def test_set_program_name + 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") + + pid = spawn(EnvUtil.rubybin, "test-script") + ps = nil + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 + begin + sleep 0.1 + ps = `#{PSCMD.join(' ')} #{pid}` + break if /hello world/ =~ ps + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop + assert_match(/hello world/, ps) + assert_operator now, :<, stop + Process.kill :KILL, pid + Timeout.timeout(5) { Process.wait(pid) } + end + end + + def test_setproctitle + 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") + + pid = spawn(EnvUtil.rubybin, "test-script") + ps = nil + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 + begin + sleep 0.1 + ps = `#{PSCMD.join(' ')} #{pid}` + break if /hello world/ =~ ps + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop + assert_match(/hello world/, ps) + assert_operator now, :<, stop + Process.kill :KILL, pid + Timeout.timeout(5) { Process.wait(pid) } + end + end + + module SEGVTest opts = {} if /mswin|mingw/ =~ RUBY_PLATFORM - additional = '[\s\w\.\']*' + additional = /[\s\w\.\']*/ else opts[:rlimit_core] = 0 - additional = "" - end - assert_in_out_err(["-e", "Process.kill :SEGV, $$"], "", [], - %r(\A - -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault\n - #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n - --\scontrol\sframe\s----------\n - (?:c:.*\n)* - ---------------------------\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 - )? - \[NOTE\]\n - You\smay\shave\sencountered\sa\sbug\sin\sthe\sRuby\sinterpreter\sor\sextension\slibraries.\n - Bug\sreports\sare\swelcome.\n - For\sdetails:\shttp:\/\/www.ruby-lang.org/bugreport.html\n - \n - (?:#{additional}) - \z + additional = nil + end + ExecOptions = opts.freeze + + ExpectedStderrList = [ + %r( + -e:(?:1:)?\s\[BUG\]\sSegmentation\sfault.*\n + )x, + %r( + #{ Regexp.quote(NO_JIT_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, - nil, - opts) + %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:\shttps:\/\/.*\.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 + assert_segv(["--disable-gems", "-e", "Process.kill :SEGV, $$"]) + end + + def test_segv_loaded_features + bug7402 = '[ruby-core:49573]' + + 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 + bug7597 = '[ruby-dev:46786]' + Tempfile.create(["test_ruby_test_bug7597", ".rb"]) {|t| + t.write "f" * 100 + t.flush + assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], bug7597) + } end def test_DATA - t = Tempfile.new(["test_ruby_test_rubyoption", ".rb"]) - t.puts "puts DATA.read.inspect" - t.puts "__END__" - t.puts "foo" - t.puts "bar" - t.puts "baz" - t.close - assert_in_out_err([t.path], "", %w("foo\\nbar\\nbaz\\n"), []) - ensure - t.close(true) if t + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| + t.puts "puts DATA.read.inspect" + t.puts "__END__" + t.puts "foo" + t.puts "bar" + t.puts "baz" + t.flush + assert_in_out_err([t.path], "", %w("foo\\nbar\\nbaz\\n"), []) + } end def test_unused_variable feature3446 = '[ruby-dev:41620]' - assert_in_out_err(["-we", "a=1"], "", [], ["-e:1: warning: assigned but unused variable - a"], feature3446) - assert_in_out_err(["-we", "1.times do\n a=1\nend"], "", [], ["-e:2: warning: assigned but unused variable - a"], feature3446) + assert_in_out_err(["-we", "a=1"], "", [], [], feature3446) + assert_in_out_err(["-we", "def foo\n a=1\nend"], "", [], ["-e:2: warning: assigned but unused variable - a"], feature3446) + assert_in_out_err(["-we", "def foo\n eval('a=1')\nend"], "", [], [], feature3446) + assert_in_out_err(["-we", "1.times do\n a=1\nend"], "", [], [], feature3446) + assert_in_out_err(["-we", "def foo\n 1.times do\n a=1\n end\nend"], "", [], ["-e:3: warning: assigned but unused variable - a"], feature3446) + assert_in_out_err(["-we", "def foo\n"" 1.times do |a| end\n""end"], "", [], []) + feature6693 = '[ruby-core:46160]' + assert_in_out_err(["-we", "def foo\n _a=1\nend"], "", [], [], feature6693) + bug7408 = '[ruby-core:49659]' + assert_in_out_err(["-we", "def foo\n a=1\n :a\nend"], "", [], ["-e:2: warning: assigned but unused variable - a"], bug7408) + feature7730 = '[ruby-core:51580]' + assert_in_out_err(["-w", "-"], "a=1", [], ["-:1: warning: assigned but unused variable - a"], feature7730) + assert_in_out_err(["-w", "-"], "eval('a=1')", [], [], feature7730) end def test_script_from_stdin @@ -431,25 +791,284 @@ class TestRubyOptions < Test::Unit::TestCase end require 'timeout' result = nil - s, w = IO.pipe - PTY.spawn(EnvUtil.rubybin, out: w) do |r, m| - w.close - m.print("\C-d") - assert_nothing_raised('[ruby-dev:37798]') do - result = Timeout.timeout(3) {s.read} + IO.pipe {|r, w| + begin + PTY.open {|m, s| + s.echo = false + m.print("\C-d") + pid = spawn(EnvUtil.rubybin, :in => s, :out => w) + w.close + assert_nothing_raised('[ruby-dev:37798]') do + result = Timeout.timeout(3) {r.read} + end + Process.wait pid + } + rescue RuntimeError + skip $! end - end - s.close + } assert_equal("", result, '[ruby-dev:37798]') - s, w = IO.pipe - PTY.spawn(EnvUtil.rubybin, out: w) do |r, m| - w.close - m.print("$stdin.read; p $stdin.gets\n\C-d") - m.print("abc\n\C-d") - m.print("zzz\n") - result = s.read - end - s.close + IO.pipe {|r, w| + PTY.open {|m, s| + s.echo = false + pid = spawn(EnvUtil.rubybin, :in => s, :out => w) + w.close + m.print("$stdin.read; p $stdin.gets\n\C-d") + m.print("abc\n\C-d") + m.print("zzz\n") + result = r.read + Process.wait pid + } + } assert_equal("\"zzz\\n\"\n", result, '[ruby-core:30910]') end + + def test_unmatching_glob + bug3851 = '[ruby-core:32478]' + a = "a[foo" + Dir.mktmpdir do |dir| + open(File.join(dir, a), "w") {|f| f.puts("p 42")} + assert_in_out_err(["-C", dir, a], "", ["42"], [], bug3851) + File.unlink(File.join(dir, a)) + assert_in_out_err(["-C", dir, a], "", [], /LoadError/, bug3851) + 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) + end + + def test_pflag_gsub + bug7157 = '[ruby-core:47967]' + assert_in_out_err(['-p', '-e', 'gsub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157) + end + + def test_pflag_sub + 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 + [ + ['"foo" << "bar"', err], + ['"foo#{123}bar" << "bar"', err], + ['+"foo#{123}bar" << "bar"', []], + ['-"foo#{123}bar" << "bar"', freeze && debug ? with_debug_pat : wo_debug_pat], + ].each do |code, expected| + assert_in_out_err(opt, code, [], expected, [opt, code]) + end + end + end + + def test___dir__encoding + lang = {"LC_ALL"=>ENV["LC_ALL"]||ENV["LANG"]} + with_tmpchdir do + testdir = "\u30c6\u30b9\u30c8" + Dir.mkdir(testdir) + Dir.chdir(testdir) do + open("test.rb", "w") do |f| + f.puts <<-END + 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_null_script + skip "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL) + assert_in_out_err([IO::NULL], success: true) + 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 + + private + + def mjit_force_enabled? + "#{RbConfig::CONFIG['CFLAGS']} #{RbConfig::CONFIG['CPPFLAGS']}".match?(/(\A|\s)-D ?MJIT_FORCE_ENABLE\b/) + end end diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb new file mode 100644 index 0000000000..7673d8dfbe --- /dev/null +++ b/test/ruby/test_rubyvm.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRubyVM < Test::Unit::TestCase + def test_stat + assert_kind_of Hash, RubyVM.stat + assert_kind_of Integer, RubyVM.stat[:global_method_state] + + RubyVM.stat(stat = {}) + assert_not_empty stat + assert_equal stat[:global_method_state], RubyVM.stat(:global_method_state) + end + + def test_stat_unknown + assert_raise(ArgumentError){ RubyVM.stat(:unknown) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")} + end +end diff --git a/test/ruby/test_rubyvm_mjit.rb b/test/ruby/test_rubyvm_mjit.rb new file mode 100644 index 0000000000..12772320f5 --- /dev/null +++ b/test/ruby/test_rubyvm_mjit.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +require 'test/unit' +require_relative '../lib/jit_support' + +return if RbConfig::CONFIG["MJIT_SUPPORT"] == 'no' + +class TestRubyVMMJIT < Test::Unit::TestCase + include JITSupport + + def setup + unless JITSupport.supported? + skip 'JIT seems not supported on this platform' + end + end + + def test_pause + out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false) + i = 0 + while i < 5 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause + print RubyVM::MJIT.pause + while i < 10 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause # no JIT here + EOS + assert_equal('truefalsefalse', out) + assert_equal( + 5, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size, + "unexpected stdout:\n```\n#{out}```\n\nstderr:\n```\n#{err}```", + ) + end + + def test_pause_does_not_hang_on_full_units + out, _ = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, max_cache: 10, wait: false) + i = 0 + while i < 11 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause + EOS + assert_equal('true', out) + end + + def test_pause_wait_false + out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false) + i = 0 + while i < 10 + eval("def mjit#{i}; end; mjit#{i}") + i += 1 + end + print RubyVM::MJIT.pause(wait: false) + print RubyVM::MJIT.pause(wait: false) + EOS + assert_equal('truefalse', out) + assert_equal(true, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size < 10) + end + + def test_resume + out, err = eval_with_jit(<<~'EOS', verbose: 1, min_calls: 1, wait: false) + print RubyVM::MJIT.resume + print RubyVM::MJIT.pause + print RubyVM::MJIT.resume + print RubyVM::MJIT.resume + print RubyVM::MJIT.pause + EOS + assert_equal('falsetruetruefalsetrue', out) + assert_equal(0, err.scan(/#{JITSupport::JIT_SUCCESS_PREFIX}/).size) + end +end diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index f885cb0042..29b69b7ce3 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestSetTraceFunc < Test::Unit::TestCase @@ -7,29 +8,36 @@ class TestSetTraceFunc < Test::Unit::TestCase :trace_instruction => true, :specialized_instruction => false } + @target_thread = Thread.current end def teardown set_trace_func(nil) RubyVM::InstructionSequence.compile_option = @original_compile_option + @target_thread = nil + end + + def target_thread? + Thread.current == @target_thread end def test_c_call events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) 4: x = 1 + 1 5: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], + assert_equal(["c-return", 1, :set_trace_func, Kernel], 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) @@ -40,9 +48,10 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_call events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) 4: def add(x, y) 5: x + y @@ -50,13 +59,13 @@ class TestSetTraceFunc < Test::Unit::TestCase 7: x = add(1, 1) 8: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], + assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) - assert_equal(["c-call", 4, :method_added, Module], + assert_equal(["c-call", 4, :method_added, self.class], events.shift) - assert_equal(["c-return", 4, :method_added, Module], + assert_equal(["c-return", 4, :method_added, self.class], events.shift) assert_equal(["line", 7, __method__, self.class], events.shift) @@ -64,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) @@ -79,9 +88,10 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_class events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) 4: class Foo 5: def bar @@ -90,7 +100,7 @@ class TestSetTraceFunc < Test::Unit::TestCase 8: x = Foo.new.bar 9: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], + assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) @@ -131,41 +141,42 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_return # [ruby-dev:38701] events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) - 4: def foo(a) + 4: def meth_return(a) 5: return if a 6: return 7: end - 8: foo(true) - 9: foo(false) + 8: meth_return(true) + 9: meth_return(false) 10: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], + assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) - assert_equal(["c-call", 4, :method_added, Module], + assert_equal(["c-call", 4, :method_added, self.class], events.shift) - assert_equal(["c-return", 4, :method_added, Module], + assert_equal(["c-return", 4, :method_added, self.class], events.shift) assert_equal(["line", 8, __method__, self.class], events.shift) - assert_equal(["call", 4, :foo, self.class], + assert_equal(["call", 4, :meth_return, self.class], events.shift) - assert_equal(["line", 5, :foo, self.class], + assert_equal(["line", 5, :meth_return, self.class], events.shift) - assert_equal(["return", 5, :foo, self.class], + assert_equal(["return", 5, :meth_return, self.class], events.shift) assert_equal(["line", 9, :test_return, self.class], events.shift) - assert_equal(["call", 4, :foo, self.class], + assert_equal(["call", 4, :meth_return, self.class], events.shift) - assert_equal(["line", 5, :foo, self.class], + assert_equal(["line", 5, :meth_return, self.class], events.shift) - assert_equal(["return", 7, :foo, self.class], + assert_equal(["return", 7, :meth_return, self.class], events.shift) assert_equal(["line", 10, :test_return, self.class], events.shift) @@ -176,34 +187,35 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_return2 # [ruby-core:24463] events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) - 4: def foo + 4: def meth_return2 5: a = 5 6: return a 7: end - 8: foo + 8: meth_return2 9: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], + assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) assert_equal(["line", 4, __method__, self.class], events.shift) - assert_equal(["c-call", 4, :method_added, Module], + assert_equal(["c-call", 4, :method_added, self.class], events.shift) - assert_equal(["c-return", 4, :method_added, Module], + assert_equal(["c-return", 4, :method_added, self.class], events.shift) assert_equal(["line", 8, __method__, self.class], events.shift) - assert_equal(["call", 4, :foo, self.class], + assert_equal(["call", 4, :meth_return2, self.class], events.shift) - assert_equal(["line", 5, :foo, self.class], + assert_equal(["line", 5, :meth_return2, self.class], events.shift) - assert_equal(["line", 6, :foo, self.class], + assert_equal(["line", 6, :meth_return2, self.class], events.shift) - assert_equal(["return", 7, :foo, self.class], + assert_equal(["return", 7, :meth_return2, self.class], events.shift) assert_equal(["line", 9, :test_return2, self.class], events.shift) @@ -214,9 +226,10 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_raise events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) 4: begin 5: raise TypeError, "error" @@ -224,9 +237,7 @@ class TestSetTraceFunc < Test::Unit::TestCase 7: end 8: set_trace_func(nil) EOF - assert_equal(["c-return", 3, :set_trace_func, Kernel], - events.shift) - assert_equal(["line", 4, __method__, self.class], + assert_equal(["c-return", 1, :set_trace_func, Kernel], events.shift) assert_equal(["line", 5, __method__, self.class], events.shift) @@ -240,18 +251,14 @@ class TestSetTraceFunc < Test::Unit::TestCase events.shift) assert_equal(["c-return", 5, :exception, Exception], events.shift) + assert_equal(["c-return", 5, :raise, Kernel], + events.shift) assert_equal(["c-call", 5, :backtrace, Exception], events.shift) assert_equal(["c-return", 5, :backtrace, Exception], events.shift) - assert_equal(["c-call", 5, :set_backtrace, Exception], - events.shift) - assert_equal(["c-return", 5, :set_backtrace, Exception], - events.shift) assert_equal(["raise", 5, :test_raise, TestSetTraceFunc], events.shift) - assert_equal(["c-return", 5, :raise, Kernel], - events.shift) assert_equal(["c-call", 6, :===, Module], events.shift) assert_equal(["c-return", 6, :===, Module], @@ -265,24 +272,23 @@ class TestSetTraceFunc < Test::Unit::TestCase def test_break # [ruby-core:27606] [Bug #2610] events = [] - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| - 2: events << [event, lineno, mid, klass] + 2: events << [event, lineno, mid, klass] if file == name 3: }) 4: [1,2,3].any? {|n| n} 8: set_trace_func(nil) EOF - [["c-return", 3, :set_trace_func, Kernel], + [["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 @@ -300,18 +306,21 @@ class TestSetTraceFunc < Test::Unit::TestCase prc = Proc.new { |event, file, lineno, mid, binding, klass| events[:set] << [event, lineno, mid, klass, :set] } + prc = prc # suppress warning prc2 = Proc.new { |event, file, lineno, mid, binding, klass| events[:add] << [event, lineno, mid, klass, :add] } + prc2 = prc2 # suppress warning th = Thread.new do th = Thread.current - eval <<-EOF.gsub(/^.*?: /, "") + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name 1: th.set_trace_func(prc) 2: th.add_trace_func(prc2) 3: class ThreadTraceInnerClass 4: def foo - 5: x = 1 + 1 + 5: _x = 1 + 1 6: end 7: end 8: ThreadTraceInnerClass.new.foo @@ -342,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| @@ -354,4 +363,1785 @@ class TestSetTraceFunc < Test::Unit::TestCase assert_equal([], events[:set]) assert_equal([], events[:add]) end + + def test_trace_defined_method + events = [] + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name + 1: class FooBar; define_method(:foobar){}; end + 2: fb = FooBar.new + 3: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| + 4: events << [event, lineno, mid, klass] if file == name + 5: }) + 6: fb.foobar + 7: set_trace_func(nil) + EOF + + [["c-return", 3, :set_trace_func, Kernel], + ["line", 6, __method__, self.class], + ["call", 1, :foobar, FooBar], + ["return", 6, :foobar, FooBar], + ["line", 7, __method__, self.class], + ["c-call", 7, :set_trace_func, Kernel]].each{|e| + assert_equal(e, events.shift) + } + end + + def test_remove_in_trace + bug3921 = '[ruby-dev:42350]' + ok = false + func = lambda{|e, f, l, i, b, k| + set_trace_func(nil) + ok = eval("self", b) + } + + set_trace_func(func) + assert_equal(self, ok, bug3921) + end + + class << self + define_method(:method_added, Module.method(:method_added)) + end + + def trace_by_tracepoint *trace_events + events = [] + trace = nil + xyzzy = nil + _local_var = :outer + raised_exc = nil + method = :trace_by_tracepoint + _get_data = lambda{|tp| + case tp.event + when :return, :c_return + tp.return_value + when :raise + tp.raised_exception + else + :nothing + end + } + _defined_class = lambda{|tp| + klass = tp.defined_class + begin + # If it is singleton method, then return original class + # to make compatible with set_trace_func(). + # This is very ad-hoc hack. I hope I can make more clean test on it. + case klass.inspect + when /Class:TracePoint/; return TracePoint + when /Class:Exception/; return Exception + else klass + end + rescue Exception => e + e + end if klass + } + + trace = nil + begin + eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' + 1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread? + 2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy' + 3: } + 4: 1.times{|;_local_var| _local_var = :inner + 5: tap{} + 6: } + 7: class XYZZY + 8: _local_var = :XYZZY_outer + 9: def foo + 10: _local_var = :XYZZY_foo + 11: bar + 12: end + 13: def bar + 14: _local_var = :XYZZY_bar + 15: tap{} + 16: end + 17: end + 18: xyzzy = XYZZY.new + 19: xyzzy.foo + 20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end + 21: trace.disable + EOF + self.class.class_eval{remove_const(:XYZZY)} + ensure + trace.disable if trace&.enabled? + end + + answer_events = [ + # + [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace], + [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing], + [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], + [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], + [:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing], + [:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self], + [:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1], + [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing], + [:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil], + [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil], + [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy], + [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing], + [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], + [:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing], + [:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy], + [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], + [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], + [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc], + [:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil], + [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing], + [:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true], + [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing], + ] + + return events, answer_events + end + + def trace_by_set_trace_func + events = [] + trace = nil + trace = trace + xyzzy = nil + xyzzy = xyzzy + _local_var = :outer + eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' + 1: set_trace_func(lambda{|event, file, line, id, binding, klass| + 2: events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy' + 3: }) + 4: 1.times{|;_local_var| _local_var = :inner + 5: tap{} + 6: } + 7: class XYZZY + 8: _local_var = :XYZZY_outer + 9: def foo + 10: _local_var = :XYZZY_foo + 11: bar + 12: end + 13: def bar + 14: _local_var = :XYZZY_bar + 15: tap{} + 16: end + 17: end + 18: xyzzy = XYZZY.new + 19: xyzzy.foo + 20: begin; raise RuntimeError; rescue RuntimeError => raised_exc; end + 21: set_trace_func(nil) + EOF + self.class.class_eval{remove_const(:XYZZY)} + return events + end + + def test_tracepoint + events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise) + + ms = [events1, answer_events].map{|evs| + evs.map{|e| + "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" + } + } + + mesg = ms[0].zip(ms[1]).map{|a, b| + if a != b + "#{a} <-> #{b}" + end + }.compact.join("\n") + + answer_events.zip(events1){|answer, event| + assert_equal answer, event, mesg + } + + events2 = trace_by_set_trace_func + events1.zip(events2){|ev1, ev2| + ev2[0] = ev2[0].sub('-', '_').to_sym + assert_equal ev1[0..2], ev2[0..2], ev1.inspect + + # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var") + assert_equal ev1[3].nil?, ev2[3].nil? # klass + assert_equal ev1[4].nil?, ev2[4].nil? # id + assert_equal ev1[6], ev2[6] # _local_var + } + + [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event| + events1, answer_events = *trace_by_tracepoint(event) + answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line| + assert_equal answer_line, event_line + } + } + end + + def test_tracepoint_object_id + tps = [] + trace = TracePoint.trace(){|tp| + next if !target_thread? + tps << tp + } + tap{} + tap{} + tap{} + trace.disable + + # passed tp is unique, `trace' object which is generated by TracePoint.trace + tps.each{|tp| + assert_equal trace, tp + } + end + + def test_tracepoint_access_from_outside + tp_store = nil + trace = TracePoint.trace(){|tp| + next if !target_thread? + tp_store = tp + } + tap{} + trace.disable + + assert_raise(RuntimeError){tp_store.lineno} + assert_raise(RuntimeError){tp_store.event} + assert_raise(RuntimeError){tp_store.path} + assert_raise(RuntimeError){tp_store.method_id} + assert_raise(RuntimeError){tp_store.defined_class} + assert_raise(RuntimeError){tp_store.binding} + assert_raise(RuntimeError){tp_store.self} + assert_raise(RuntimeError){tp_store.return_value} + assert_raise(RuntimeError){tp_store.raised_exception} + end + + def foo + end + + def test_tracepoint_enable + ary = [] + args = nil + trace = TracePoint.new(:call){|tp| + next if !target_thread? + ary << tp.method_id + } + foo + trace.enable{|*a| + args = a + foo + } + foo + assert_equal([:foo], ary) + assert_equal([], args) + + trace = TracePoint.new{} + begin + assert_equal(false, trace.enable) + assert_equal(true, trace.enable) + trace.enable{} + assert_equal(true, trace.enable) + ensure + trace.disable + end + end + + def test_tracepoint_disable + ary = [] + args = nil + trace = TracePoint.trace(:call){|tp| + next if !target_thread? + ary << tp.method_id + } + foo + trace.disable{|*a| + args = a + foo + } + foo + trace.disable + assert_equal([:foo, :foo], ary) + assert_equal([], args) + + trace = TracePoint.new{} + trace.enable{ + assert_equal(true, trace.disable) + assert_equal(false, trace.disable) + trace.disable{} + assert_equal(false, trace.disable) + } + end + + def test_tracepoint_enabled + trace = TracePoint.trace(:call){|tp| + # + } + assert_equal(true, trace.enabled?) + trace.disable{ + assert_equal(false, trace.enabled?) + trace.enable{ + assert_equal(true, trace.enabled?) + } + } + trace.disable + assert_equal(false, trace.enabled?) + end + + def parameter_test(a, b, c) + yield + end + + def test_tracepoint_parameters + trace = TracePoint.new(:line, :class, :end, :call, :return, :b_call, :b_return, :c_call, :c_return, :raise){|tp| + next if !target_thread? + next if tp.path != __FILE__ + case tp.event + when :call, :return + assert_equal([[:req, :a], [:req, :b], [:req, :c]], tp.parameters) + when :b_call, :b_return + next if tp.parameters == [] + if tp.parameters.first == [:opt, :x] + assert_equal([[:opt, :x], [:opt, :y], [:opt, :z]], tp.parameters) + else + assert_equal([[:req, :p], [:req, :q], [:req, :r]], tp.parameters) + end + when :c_call, :c_return + assert_equal([[:req]], tp.parameters) if tp.method_id == :getbyte + when :line, :class, :end, :raise + assert_raise(RuntimeError) { tp.parameters } + end + } + obj = Object.new + trace.enable{ + parameter_test(1, 2, 3) {|x, y, z| + } + lambda {|p, q, r| }.call(4, 5, 6) + "".getbyte(0) + class << obj + end + begin + raise + rescue + end + } + end + + def method_test_tracepoint_return_value obj + obj + end + + def test_tracepoint_return_value + trace = TracePoint.new(:call, :return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + case tp.event + when :call + assert_raise(RuntimeError) {tp.return_value} + when :return + assert_equal("xyzzy", tp.return_value) + end + } + trace.enable{ + method_test_tracepoint_return_value "xyzzy" + } + end + + class XYZZYException < Exception; end + def method_test_tracepoint_raised_exception err + raise err + end + + def test_tracepoint_raised_exception + trace = TracePoint.new(:call, :return, :raise){|tp| + next if !target_thread? + case tp.event + when :call, :return + assert_raise(RuntimeError) { tp.raised_exception } + when :raise + assert_kind_of(XYZZYException, tp.raised_exception) + end + } + trace.enable{ + begin + method_test_tracepoint_raised_exception XYZZYException + rescue XYZZYException + # ok + else + raise + end + } + end + + def method_for_test_tracepoint_block + yield + end + + def test_tracepoint_block + events = [] + TracePoint.new(:call, :return, :c_call, :b_call, :c_return, :b_return){|tp| + next if !target_thread? + events << [ + tp.event, tp.method_id, tp.defined_class, tp.self.class, + /return/ =~ tp.event ? tp.return_value : nil + ] + }.enable{ + 1.times{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + # pp events + # expected_events = + [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 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, 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], + [:return, :method_for_test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4], + [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 4] + ].zip(events){|expected, actual| + assert_equal(expected, actual) + } + end + + def test_tracepoint_thread + events = [] + thread_self = nil + created_thread = nil + TracePoint.new(:thread_begin, :thread_end){|tp| + events << [Thread.current, + tp.event, + tp.lineno, #=> 0 + tp.path, #=> nil + tp.binding, #=> nil + tp.defined_class, #=> nil, + tp.self.class # tp.self return creating/ending thread + ] + }.enable{ + created_thread = Thread.new{thread_self = self} + created_thread.join + } + events.reject!{|i| i[0] != created_thread} + assert_equal(self, thread_self) + assert_equal([created_thread, :thread_begin, 0, nil, nil, nil, Thread], events[0]) + assert_equal([created_thread, :thread_end, 0, nil, nil, nil, Thread], events[1]) + assert_equal(2, events.size) + end + + def test_tracepoint_inspect + events = [] + th = nil + trace = TracePoint.new{|tp| + next if !target_thread? && th != Thread.current + events << [tp.event, tp.inspect] + } + assert_equal("#<TracePoint:disabled>", trace.inspect) + trace.enable{ + assert_equal("#<TracePoint:enabled>", trace.inspect) + th = Thread.new{} + th.join + } + assert_equal("#<TracePoint:disabled>", trace.inspect) + events.each{|(ev, str)| + case ev + when :line + assert_match(/ in /, str) + when :call, :c_call + assert_match(/call \`/, str) # #<TracePoint:c_call `inherited'@../trunk/test.rb:11> + when :return, :c_return + assert_match(/return \`/, str) # #<TracePoint:return `m'@../trunk/test.rb:3> + when /thread/ + assert_match(/\#<Thread:/, str) # #<TracePoint:thread_end of #<Thread:0x87076c0>> + else + assert_match(/\#<TracePoint:/, str) + end + } + end + + def test_tracepoint_exception_at_line + assert_raise(RuntimeError) do + TracePoint.new(:line) { + next if !target_thread? + raise + }.enable { + 1 + } + end + end + + def test_tracepoint_exception_at_return + assert_nothing_raised(Timeout::Error, 'infinite trace') do + assert_normal_exit('def m; end; TracePoint.new(:return) {raise}.enable {m}', '', timeout: 3) + 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{ + 10.times{ + Thread.pass + } + }.enable do + (1..10).map{ + Thread.new{ + 1000.times{ + } + } + }.each{|th| + th.join + } + end + end + end + + class FOO_ERROR < RuntimeError; end + class BAR_ERROR < RuntimeError; end + def m1_test_trace_point_at_return_when_exception + m2_test_trace_point_at_return_when_exception + end + def m2_test_trace_point_at_return_when_exception + raise BAR_ERROR + end + + def test_trace_point_at_return_when_exception + bug_7624 = '[ruby-core:51128] [ruby-trunk - Bug #7624]' + TracePoint.new{|tp| + next if !target_thread? + if tp.event == :return && + tp.method_id == :m2_test_trace_point_at_return_when_exception + raise FOO_ERROR + end + }.enable do + assert_raise(FOO_ERROR, bug_7624) do + m1_test_trace_point_at_return_when_exception + end + end + + bug_7668 = '[Bug #7668]' + ary = [] + trace = TracePoint.new{|tp| + next if !target_thread? + ary << tp.event + raise + } + begin + trace.enable{ + 1.times{ + raise + } + } + rescue + assert_equal([:b_call, :b_return], ary, bug_7668) + end + end + + def m1_for_test_trace_point_binding_in_ifunc(arg) + arg + nil + rescue + end + + def m2_for_test_trace_point_binding_in_ifunc(arg) + arg.inject(:+) + rescue + end + + def test_trace_point_binding_in_ifunc + bug7774 = '[ruby-dev:46908]' + src = %q{ + tp = TracePoint.new(:raise) do |tp| + tp.binding + end + tp.enable do + obj = Object.new + class << obj + include Enumerable + def each + yield 1 + end + end + %s + end + } + assert_normal_exit src % %q{obj.zip({}) {}}, bug7774 + assert_normal_exit src % %q{ + require 'continuation' + begin + c = nil + obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } + c.call + rescue RuntimeError + end + }, bug7774 + + # TracePoint + tp_b = nil + TracePoint.new(:raise) do |tp| + next if !target_thread? + tp_b = tp.binding + end.enable do + m1_for_test_trace_point_binding_in_ifunc(0) + assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]') + + m2_for_test_trace_point_binding_in_ifunc([0, nil]) + assert_equal(self, eval('self', tp_b), '[ruby-dev:46960]') + end + + # set_trace_func + stf_b = nil + set_trace_func ->(event, file, line, id, binding, klass) do + stf_b = binding if event == 'raise' + end + begin + m1_for_test_trace_point_binding_in_ifunc(0) + assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]') + + m2_for_test_trace_point_binding_in_ifunc([0, nil]) + assert_equal(self, eval('self', stf_b), '[ruby-dev:46960]') + ensure + set_trace_func(nil) + 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){ + next if !target_thread? + n += 1 + }.enable{ + 3.times{ + next + } # 3 times b_retun + } # 1 time b_return + + assert_equal 4, n + end + + def test_tracepoint_b_return_with_lambda + n = 0 + TracePoint.new(:b_return){ + next if !target_thread? + n+=1 + }.enable{ + lambda{ + return + }.call # n += 1 #=> 1 + 3.times{ + lambda{ + return # n += 3 #=> 4 + }.call + } # n += 3 #=> 7 + begin + lambda{ + raise + }.call # n += 1 #=> 8 + rescue + # ignore + end # n += 1 #=> 9 + } + + assert_equal 9, n + end + + def test_isolated_raise_in_trace + bug9088 = '[ruby-dev:47793] [Bug #9088]' + assert_in_out_err([], <<-END, [], [], bug9088) + set_trace_func proc {raise rescue nil} + 1.times {break} + END + end + + def test_a_call + events = [] + TracePoint.new(:a_call){|tp| + next if !target_thread? + events << tp.event + }.enable{ + 1.times{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + assert_equal([ + :b_call, + :c_call, + :b_call, + :call, + :b_call, + ], events) + end + + def test_a_return + events = [] + TracePoint.new(:a_return){|tp| + next if !target_thread? + events << tp.event + }.enable{ + 1.times{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + assert_equal([ + :b_return, + :c_return, + :b_return, + :return, + :b_return + ], events) + end + + def test_const_missing + bug59398 = '[ruby-core:59398]' + 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 + events << [tp.event,tp.method_id] if tp.method_id == :const_missing || tp.method_id == :rake_original_const_missing + }.enable{ + MISSING_CONSTANT_59398 rescue nil + } + if events.map{|e|e[1]}.include?(:rake_original_const_missing) + assert_equal([ + [:call, :const_missing], + [:c_call, :rake_original_const_missing], + [:c_return, :rake_original_const_missing], + [:return, :const_missing], + ], events, bug59398) + else + assert_equal([ + [:c_call, :const_missing], + [:c_return, :const_missing] + ], events, bug59398) + end + end + + class AliasedRubyMethod + def foo; 1; end; + alias bar foo + end + def test_aliased_ruby_method + events = [] + aliased = AliasedRubyMethod.new + TracePoint.new(:call, :return){|tp| + next if !target_thread? + events << [tp.event, tp.method_id] + }.enable{ + aliased.bar + } + assert_equal([ + [:call, :foo], + [:return, :foo] + ], events, "should use original method name for tracing ruby methods") + end + class AliasedCMethod < Hash + alias original_size size + def size; original_size; end + end + + def test_aliased_c_method + 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, :size], + [:c_return, :size], + [:return, :size] + ], events, "should use alias method name for tracing c methods") + end + + def test_method_missing + bug59398 = '[ruby-core:59398]' + 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 + events << [tp.event,tp.method_id] if tp.method_id == :method_missing + }.enable{ + missing_method_59398 rescue nil + } + assert_equal([ + [:c_call, :method_missing], + [:c_return, :method_missing] + ], events, bug59398) + end + + class C9759 + define_method(:foo){ + raise + } + end + + def test_define_method_on_exception + events = [] + obj = C9759.new + TracePoint.new(:call, :return){|tp| + next unless target_thread? + events << [tp.event, tp.method_id] + }.enable{ + obj.foo rescue nil + } + assert_equal([[:call, :foo], [:return, :foo]], events, 'Bug #9759') + + events = [] + begin + set_trace_func(lambda{|event, file, lineno, mid, binding, klass| + next unless target_thread? + case event + when 'call', 'return' + events << [event, mid] + end + }) + obj.foo rescue nil + set_trace_func(nil) + + assert_equal([['call', :foo], ['return', :foo]], events, 'Bug #9759') + ensure + end + end + + class C11492 + define_method(:foo_return){ + return true + } + define_method(:foo_break){ + break true + } + end + + def test_define_method_on_return + # return + events = [] + obj = C11492.new + TracePoint.new(:call, :return){|tp| + next unless target_thread? + events << [tp.event, tp.method_id] + }.enable{ + obj.foo_return + } + assert_equal([[:call, :foo_return], [:return, :foo_return]], events, 'Bug #11492') + + # break + events = [] + obj = C11492.new + TracePoint.new(:call, :return){|tp| + next unless target_thread? + events << [tp.event, tp.method_id] + }.enable{ + obj.foo_break + } + assert_equal([[:call, :foo_break], [:return, :foo_break]], events, 'Bug #11492') + + # set_trace_func + # return + events = [] + begin + set_trace_func(lambda{|event, file, lineno, mid, binding, klass| + next unless target_thread? + case event + when 'call', 'return' + events << [event, mid] + end + }) + obj.foo_return + set_trace_func(nil) + + assert_equal([['call', :foo_return], ['return', :foo_return]], events, 'Bug #11492') + ensure + end + + # break + events = [] + begin + set_trace_func(lambda{|event, file, lineno, mid, binding, klass| + next unless target_thread? + case event + when 'call', 'return' + events << [event, mid] + end + }) + obj.foo_break + set_trace_func(nil) + + assert_equal([['call', :foo_break], ['return', :foo_break]], events, 'Bug #11492') + ensure + end + end + + def test_recursive + assert_in_out_err([], %q{\ + TracePoint.new(:c_call){|tp| + p tp.method_id + }.enable{ + p 1 + } + }, %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 + begin + raise + rescue + return + end + end + + def method_test_ensure_should_not_cause_b_return + begin + raise + ensure + return + end + end + + def test_rescue_and_ensure_should_not_cause_b_return + 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 + end + end + + define_method(:method_test_argument_error_on_bmethod){|correct_key: 1|} + + def test_argument_error_on_bmethod + assert_consistent_call_return '[Bug #9959]' do + begin + method_test_argument_error_on_bmethod(wrong_key: 2) + rescue + # ignore + end + end + end + + def test_rb_rescue + assert_consistent_call_return '[Bug #9961]' do + begin + -Numeric.new + rescue + # ignore + end + end + end + + def test_b_call_with_redo + assert_consistent_call_return '[Bug #9964]' do + i = 0 + 1.times{ + break if (i+=1) > 10 + redo + } + end + end + + def test_no_duplicate_line_events + lines = [] + dummy = [] + + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + dummy << (1) + (2) + dummy << (1) + (2) + } + 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} + end + end + + def test_throwing_return_with_finish_frame + target_th = Thread.current + evs = [] + + TracePoint.new(:call, :return){|tp| + next unless target_thread? + evs << tp.event + }.enable{ + 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 + 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' + # When MJIT thread exists, t.status becomes 'sleep' even if it does not reach m2t_q.pop. + # This sleep forces it to reach m2t_q.pop for --jit-wait. + sleep 1 if RubyVM::MJIT.enabled? + + 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 + + def test_lineno_in_optimized_insn + actual, _, _ = EnvUtil.invoke_ruby [], <<-EOF.gsub(/^.*?: */, ""), true + 1: class String + 2: def -@ + 3: puts caller_locations(1, 1)[0].lineno + 4: end + 5: end + 6: + 7: -"" + EOF + assert_equal "7\n", actual, '[Bug #14809]' + end + + def method_for_enable_target1 + a = 1 + b = 2 + 1.times{|i| + x = i + } + c = a + b + end + + def method_for_enable_target2 + a = 1 + b = 2 + 1.times{|i| + x = i + } + c = a + b + end + + def check_with_events *trace_events + all_events = [[:call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_return, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:return, :method_for_enable_target1], + # repeat + [:call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_call, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:b_return, :method_for_enable_target1], + [:line, :method_for_enable_target1], + [:return, :method_for_enable_target1], + ] + events = [] + TracePoint.new(*trace_events) do |tp| + next unless target_thread? + events << [tp.event, tp.method_id] + end.enable(target: method(:method_for_enable_target1)) do + method_for_enable_target1 + method_for_enable_target2 + method_for_enable_target1 + end + assert_equal all_events.find_all{|(ev, m)| trace_events.include? ev}, events + end + + def test_tracepoint_enable_target + check_with_events :line + check_with_events :call, :return + check_with_events :line, :call, :return + check_with_events :call, :return, :b_call, :b_return + check_with_events :line, :call, :return, :b_call, :b_return + end + + def test_tracepoint_nested_enabled_with_target + code1 = proc{ + a = 1 + } + code2 = proc{ + b = 2 + } + + ## error + + # targetted TP and targetted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targetting TracePoint", ex.message + + # global TP and targetted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable{ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targetting TracePoint", ex.message + + # targetted TP and global TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable{} + } + end + assert_equal "can't nest-enable a targetting TracePoint", ex.message + + # targetted TP and disable + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.disable{} + } + end + assert_equal "can't disable a targetting TracePoint in a block", ex.message + + ## success with two nesting targetting tracepoints + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp1, :___], events + + # succss with two tracepoints (global/targetting) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events + + # succss with two tracepoints (targetting/global) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events + end + + def test_tracepoint_enable_with_target_line + events = [] + line_0 = __LINE__ + code1 = proc{ + events << 1 + events << 2 + events << 3 + } + tp = TracePoint.new(:line) do |tp| + events << :tp + end + tp.enable(target: code1, target_line: line_0 + 3) do + code1.call + end + assert_equal [1, :tp, 2, 3], events + + + e = assert_raise(ArgumentError) do + TracePoint.new(:line){}.enable(target_line: 10){} + end + assert_equal 'only target_line is specified', e.message + + e = assert_raise(ArgumentError) do + TracePoint.new(:call){}.enable(target: code1, target_line: 10){} + end + assert_equal 'target_line is specified, but line event is not specified', e.message + end + + def test_script_compiled + events = [] + tp = TracePoint.new(:script_compiled){|tp| + next unless target_thread? + events << [tp.instruction_sequence.path, + tp.eval_script] + } + + eval_script = 'a = 1' + tp.enable{ + eval(eval_script, nil, __FILE__+"/eval") + nil.instance_eval(eval_script, __FILE__+"/instance_eval") + Object.class_eval(eval_script, __FILE__+"/class_eval") + } + assert_equal [[__FILE__+"/eval", eval_script], + [__FILE__+"/instance_eval", eval_script], + [__FILE__+"/class_eval", eval_script], + ], events + events.clear + + # TODO: test for requires + return + + tp.enable{ + require '' + require_relative '' + load '' + } + assert_equal [], events + end + + def test_return_event_with_rescue + obj = Object.new + def obj.example + 1 if 1 == 1 + rescue + end + ok = false + tp = TracePoint.new(:return) {ok = true} + tp.enable {obj.example} + assert ok, "return event should be emitted" + end + + def test_disable_local_tracepoint_in_trace + assert_normal_exit <<-EOS + def foo + trace = TracePoint.new(:b_return){|tp| + tp.disable + } + trace.enable(target: method(:bar)) + end + def bar + 100.times{|i| + foo; foo + } + end + bar + EOS + end end diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index 5d9d3cd691..425dc26574 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -1,18 +1,10 @@ +# frozen_string_literal: false require 'test/unit' require 'timeout' +require 'tempfile' class TestSignal < Test::Unit::TestCase - def have_fork? - begin - Process.fork {} - return true - rescue NotImplementedError - return false - end - end - def test_signal - return unless Process.respond_to?(:kill) begin x = 0 oldtrap = Signal.trap(:INT) {|sig| x = 2 } @@ -24,66 +16,80 @@ class TestSignal < Test::Unit::TestCase assert_equal 2, x Signal.trap(:INT) { raise "Interrupt" } - ex = assert_raise(RuntimeError) { + assert_raise_with_message(RuntimeError, /Interrupt/) { Process.kill :INT, Process.pid sleep 0.1 } - assert_kind_of Exception, ex - assert_match(/Interrupt/, ex.message) ensure Signal.trap :INT, oldtrap if oldtrap end - end + end if Process.respond_to?(:kill) + + def test_signal_process_group + bug4362 = '[ruby-dev:43169]' + assert_nothing_raised(bug4362) do + cmd = [ EnvUtil.rubybin, '--disable=gems' '-e', 'sleep 10' ] + pid = Process.spawn(*cmd, :pgroup => true) + Process.kill(:"-TERM", pid) + Process.waitpid(pid) + assert_equal(true, $?.signaled?) + assert_equal(Signal.list["TERM"], $?.termsig) + end + end if Process.respond_to?(:kill) and + Process.respond_to?(:pgroup) # for mswin32 def test_exit_action - return unless have_fork? # skip this test - begin - r, w = IO.pipe - r0, w0 = IO.pipe - pid = Process.fork { - Signal.trap(:USR1, "EXIT") - w0.close - w.syswrite("a") + if Signal.list[sig = "USR1"] + term = :TERM + else + sig = "INT" + term = :KILL + end + IO.popen([EnvUtil.rubybin, '--disable=gems', '-e', <<-"End"], 'r+') do |io| + Signal.trap(:#{sig}, "EXIT") + STDOUT.syswrite("a") Thread.start { sleep(2) } - r0.sysread(4096) - } - r.sysread(1) + STDIN.sysread(4096) + End + pid = io.pid + io.sysread(1) sleep 0.1 assert_nothing_raised("[ruby-dev:26128]") { - Process.kill(:USR1, pid) + Process.kill(term, pid) begin Timeout.timeout(3) { Process.waitpid pid } rescue Timeout::Error - Process.kill(:TERM, pid) + if term + Process.kill(term, pid) + term = (:KILL if term != :KILL) + retry + end raise end } - ensure - r.close - w.close - r0.close - w0.close end - end + end if Process.respond_to?(:kill) def test_invalid_signal_name - return unless Process.respond_to?(:kill) - assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) } - end + 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) - assert_equal(SignalException.new(signm.to_sym).signo, signo) - assert_equal(SignalException.new(signo).signo, signo) + assert_equal(signo, SignalException.new(signm).signo, signm) + assert_equal(signo, SignalException.new(signm.to_sym).signo, signm) + assert_equal(signo, SignalException.new(signo).signo, signo) end + e = assert_raise(ArgumentError) {SignalException.new("-SIGEXIT")} + assert_not_match(/SIG-SIG/, e.message) end def test_interrupt @@ -91,7 +97,6 @@ class TestSignal < Test::Unit::TestCase end def test_signal2 - return unless Process.respond_to?(:kill) begin x = false oldtrap = Signal.trap(:INT) {|sig| x = true } @@ -102,21 +107,21 @@ class TestSignal < Test::Unit::TestCase Timeout.timeout(10) do x = false Process.kill(SignalException.new(:INT).signo, $$) - nil until x + sleep(0.01) until x x = false Process.kill("INT", $$) - nil until x + sleep(0.01) until x x = false Process.kill("SIGINT", $$) - nil until x + sleep(0.01) until x x = false o = Object.new def o.to_str; "SIGINT"; end Process.kill(o, $$) - nil until x + sleep(0.01) until x end assert_raise(ArgumentError) { Process.kill(Object.new, $$) } @@ -124,10 +129,9 @@ class TestSignal < Test::Unit::TestCase ensure Signal.trap(:INT, oldtrap) if oldtrap end - end + end if Process.respond_to?(:kill) def test_trap - return unless Process.respond_to?(:kill) begin oldtrap = Signal.trap(:INT) {|sig| } @@ -162,21 +166,227 @@ class TestSignal < Test::Unit::TestCase assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Signal.trap("\u{30eb 30d3 30fc}", "SIG_DFL") } + + assert_raise(ArgumentError) { Signal.trap("EXIT\0") {} } ensure Signal.trap(:INT, oldtrap) if oldtrap end + 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 - return unless have_fork? # skip this test + Signal.list[sig = "USR1"] or sig = "INT" + assert_in_out_err(["-e", <<-"end;"], "", %w"foo") + Signal.trap(:#{sig}) { STDOUT.syswrite("foo") } + Process.kill :#{sig}, $$ + end; + 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) { + Signal.trap(:SEGV) {} + } + assert_raise(ArgumentError) { + Signal.trap(:BUS) {} + } + assert_raise(ArgumentError) { + Signal.trap(:ILL) {} + } + assert_raise(ArgumentError) { + Signal.trap(:FPE) {} + } + assert_raise(ArgumentError) { + Signal.trap(:VTALRM) {} + } + 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 + args = [EnvUtil.rubybin, "--disable=gems", "-e", <<"", :err => File::NULL] + Signal.trap("INT") do |signo| + signame = Signal.signame(signo) + Marshal.dump(signame, STDOUT) + STDOUT.flush + exit 0 + end + Process.kill("INT", $$) + sleep 1 # wait signal deliver - r, w = IO.pipe - pid = Process.fork do - r.close - Signal.trap(:USR1) { w.syswrite("foo") } - Process.kill :USR1, $$ + 10.times do + IO.popen(args) do |child| + signame = Marshal.load(child) + assert_equal("INT", signame) + end + end + end if Process.respond_to?(:kill) + + def test_trap_puts + assert_in_out_err([], <<-INPUT, ["a"*10000], []) + Signal.trap(:INT) { + # for enable internal io mutex + STDOUT.sync = false + # larger than internal io buffer + print "a"*10000 + } + Process.kill :INT, $$ + sleep 0.1 + INPUT + end if Process.respond_to?(:kill) + + def test_hup_me + # [Bug #7951] [ruby-core:52864] + # This is MRI specific spec. ruby has no guarantee + # that signal will be deliverd synchronously. + # This ugly workaround was introduced to don't break + # compatibility against silly example codes. + assert_separately([], <<-RUBY) + trap(:HUP, "DEFAULT") + assert_raise(SignalException) { + Process.kill('HUP', Process.pid) + } + RUBY + bug8137 = '[ruby-dev:47182] [Bug #8137]' + assert_nothing_raised(bug8137) { + Timeout.timeout(1) { + Process.kill(0, Process.pid) + } + } + end if Process.respond_to?(:kill) and Signal.list.key?('HUP') + + def test_ignored_interrupt + bug9820 = '[ruby-dev:48203] [Bug #9820]' + assert_separately(['-', bug9820], <<-'end;') # begin + bug = ARGV.shift + trap(:INT, "IGNORE") + assert_nothing_raised(SignalException, bug) do + Process.kill(:INT, $$) + end + end; + + if trap = Signal.list['TRAP'] + bug9820 = '[ruby-dev:48592] [Bug #9820]' + status = assert_in_out_err(['-e', 'Process.kill(:TRAP, $$)']) + assert_predicate(status, :signaled?, bug9820) + assert_equal(trap, status.termsig, bug9820) + end + + if Signal.list['CONT'] + bug9820 = '[ruby-dev:48606] [Bug #9820]' + 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 + + def test_sigchld_ignore + skip 'no SIGCHLD' unless Signal.list['CHLD'] + old = trap(:CHLD, 'IGNORE') + cmd = [ EnvUtil.rubybin, '--disable=gems', '-e' ] + assert(system(*cmd, 'exit!(0)'), 'no ECHILD') + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + IO.pipe do |r, w| + pid = spawn(*cmd, "STDIN.read", in: r) + nb = Process.wait(pid, Process::WNOHANG) + th = Thread.new(Thread.current) do |parent| + Thread.pass until parent.stop? # wait for parent to Process.wait + w.close + end + assert_raise(Errno::ECHILD) { Process.wait(pid) } + assert_nil nb end - w.close - assert_equal(r.read, "foo") + + IO.pipe do |r, w| + pids = 3.times.map { spawn(*cmd, 'exit!', out: w) } + w.close + zombies = pids.dup + assert_nil r.read(1), 'children dead' + + Timeout.timeout(10) do + zombies.delete_if do |pid| + begin + Process.kill(0, pid) + false + rescue Errno::ESRCH + true + end + end while zombies[0] + end + assert_predicate zombies, :empty?, 'zombies leftover' + + pids.each do |pid| + assert_raise(Errno::ECHILD) { Process.waitpid(pid) } + end + end + ensure + trap(:CHLD, old) if Signal.list['CHLD'] end + + def test_sigwait_fd_unused + t = EnvUtil.apply_timeout_scale(0.1) + assert_separately([], <<-End) + tgt = $$ + trap(:TERM) { exit(0) } + e = "Process.daemon; sleep #{t * 2}; Process.kill(:TERM,\#{tgt})" + term = [ '#{EnvUtil.rubybin}', '--disable=gems', '-e', e ] + t2 = Thread.new { sleep } # grab sigwait_fd + Thread.pass until t2.stop? + Thread.new do + sleep #{t} + t2.kill + t2.join + end + Process.spawn(*term) + # last thread remaining, ensure it can react to SIGTERM + loop { sleep } + End + end if Process.respond_to?(:kill) && Process.respond_to?(:daemon) end diff --git a/test/ruby/test_sleep.rb b/test/ruby/test_sleep.rb index adc4876216..61002b8b18 100644 --- a/test/ruby/test_sleep.rb +++ b/test/ruby/test_sleep.rb @@ -1,12 +1,16 @@ +# 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 - assert_in_delta(5.0, slept, 0.1, "[ruby-core:18015]: longer than expected") + 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 GC.enable end diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb index 05bccde066..7986e9d141 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,49 @@ 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_inspect + obj = Object.new + def obj.inspect; "TEST"; end + assert_equal("<TEST>", sprintf("<%p>", obj)) + end + def test_invalid # Star precision before star width: assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%.**d", 5, 10, 1)} @@ -179,6 +275,10 @@ class TestSprintf < Test::Unit::TestCase assert_raise(ArgumentError) { sprintf("%!", 1) } assert_raise(ArgumentError) { sprintf("%1$1$d", 1) } assert_raise(ArgumentError) { sprintf("%0%") } + + assert_raise_with_message(ArgumentError, /unnumbered\(1\) mixed with numbered/) { sprintf("%1$*d", 3) } + assert_raise_with_message(ArgumentError, /unnumbered\(1\) mixed with numbered/) { sprintf("%1$.*d", 3) } + verbose, $VERBOSE = $VERBOSE, nil assert_nothing_raised { sprintf("", 1) } ensure @@ -189,6 +289,10 @@ class TestSprintf < Test::Unit::TestCase assert_equal("36893488147419111424", sprintf("%20.0f", 36893488147419107329.0)) assert_equal(" Inf", sprintf("% 0e", 1.0/0.0), "moved from btest/knownbug") + assert_equal(" -0.", sprintf("%#10.0f", -0.5), "[ruby-dev:42552]") + # out of spec + #assert_equal("0x1p+2", sprintf('%.0a', Float('0x1.fp+1')), "[ruby-dev:42551]") + #assert_equal("-0x1.0p+2", sprintf('%.1a', Float('-0x1.ffp+1')), "[ruby-dev:42551]") end def test_float_hex @@ -204,6 +308,49 @@ class TestSprintf < Test::Unit::TestCase assert_equal("Inf", sprintf("%E", Float::INFINITY)) assert_equal("NaN", sprintf("%e", Float::NAN)) assert_equal("NaN", sprintf("%E", Float::NAN)) + + assert_equal(" -0x1p+0", sprintf("%10a", -1)) + assert_equal(" -0x1.8p+0", sprintf("%10a", -1.5)) + assert_equal(" -0x1.4p+0", sprintf("%10a", -1.25)) + assert_equal(" -0x1.2p+0", sprintf("%10a", -1.125)) + assert_equal(" -0x1.1p+0", sprintf("%10a", -1.0625)) + assert_equal("-0x1.08p+0", sprintf("%10a", -1.03125)) + + bug3962 = '[ruby-core:32841]' + assert_equal("-0x0001p+0", sprintf("%010a", -1), bug3962) + assert_equal("-0x01.8p+0", sprintf("%010a", -1.5), bug3962) + assert_equal("-0x01.4p+0", sprintf("%010a", -1.25), bug3962) + assert_equal("-0x01.2p+0", sprintf("%010a", -1.125), bug3962) + assert_equal("-0x01.1p+0", sprintf("%010a", -1.0625), bug3962) + assert_equal("-0x1.08p+0", sprintf("%010a", -1.03125), bug3962) + + bug3964 = '[ruby-core:32848]' + assert_equal("0x000000000000000p+0", sprintf("%020a", 0), bug3964) + assert_equal("0x000000000000001p+0", sprintf("%020a", 1), bug3964) + assert_equal("-0x00000000000001p+0", sprintf("%020a", -1), bug3964) + assert_equal("0x00000000000000.p+0", sprintf("%#020a", 0), bug3964) + + bug3965 = '[ruby-dev:42431]' + assert_equal("0x1.p+0", sprintf("%#.0a", 1), bug3965) + assert_equal("0x00000000000000.p+0", sprintf("%#020a", 0), bug3965) + assert_equal("0x0000.0000000000p+0", sprintf("%#020.10a", 0), bug3965) + + bug3979 = '[ruby-dev:42453]' + assert_equal(" 0x0.000p+0", sprintf("%20.3a", 0), bug3979) + 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 @@ -271,12 +418,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) @@ -287,12 +450,97 @@ class TestSprintf < Test::Unit::TestCase s2 = sprintf("%0x", -0x40000001) b1 = (/\.\./ =~ s1) != nil b2 = (/\.\./ =~ s2) != nil - assert(b1 == b2, "[ruby-dev:33224]") + assert_equal(b1, b2, "[ruby-dev:33224]") end - def test_named + def test_named_untyped assert_equal("value", sprintf("%<key>s", :key => "value")) - assert_raise(ArgumentError) {sprintf("%1$<key2>s", :key => "value")} - assert_raise(ArgumentError) {sprintf("%<key><key2>s", :key => "value")} + 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")} + 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 + key = "\u{3012}" + [Encoding::UTF_8, Encoding::EUC_JP].each do |enc| + k = key.encode(enc) + e = assert_raise_with_message(ArgumentError, "named<#{k}> after numbered") {sprintf("%1$<#{k}>s", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named<#{k}> after unnumbered(2)") {sprintf("%s%s%<#{k}>s", "foo", "bar", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named<#{k}> after <key>") {sprintf("%<key><#{k}>s", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named<key> after <#{k}>") {sprintf("%<#{k}><key>s", k.to_sym => "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(KeyError, "key<#{k}> not found") {sprintf("%<#{k}>s", {})} + assert_equal(enc, e.message.encoding) + end + end + + def test_named_typed + assert_equal("value", sprintf("%{key}", :key => "value")) + assert_raise_with_message(ArgumentError, "named{key2} after numbered") {sprintf("%1${key2}", :key => "value")} + assert_raise_with_message(ArgumentError, "named{key2} after unnumbered(2)") {sprintf("%s%s%{key2}", "foo", "bar", :key => "value")} + assert_raise_with_message(ArgumentError, "named{key2} after <key>") {sprintf("%<key>{key2}", :key => "value")} + assert_equal("value{key2}", sprintf("%{key}{key2}", :key => "value")) + assert_raise_with_message(KeyError, "key{key} not found") {sprintf("%{key}", {})} + end + + def test_named_typed_enc + key = "\u{3012}" + [Encoding::UTF_8, Encoding::EUC_JP].each do |enc| + k = key.encode(enc) + e = assert_raise_with_message(ArgumentError, "named{#{k}} after numbered") {sprintf("%1${#{k}}s", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named{#{k}} after unnumbered(2)") {sprintf("%s%s%{#{k}}s", "foo", "bar", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named{#{k}} after <key>") {sprintf("%<key>{#{k}}s", key: "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(ArgumentError, "named{key} after <#{k}>") {sprintf("%<#{k}>{key}s", k.to_sym => "value")} + assert_equal(enc, e.message.encoding) + e = assert_raise_with_message(KeyError, "key{#{k}} not found") {sprintf("%{#{k}}", {})} + 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 + skip unless Thread.list.size == 1 + + fmt = [4, 2, 2].map { |x| "%0#{x}d" }.join('-') # defeats optimization + ObjectSpace.count_objects(res = {}) # creates strings on first call + GC.disable + before = ObjectSpace.count_objects(res)[:T_STRING] + val = sprintf(fmt, 1970, 1, 1) + after = ObjectSpace.count_objects(res)[:T_STRING] + assert_equal before + 1, after, 'only new string is the created one' + assert_equal '1970-01-01', val + ensure + GC.enable end end diff --git a/test/ruby/test_sprintf_comb.rb b/test/ruby/test_sprintf_comb.rb index 261732bcbc..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' @@ -107,7 +108,9 @@ class TestSprintfComb < Test::Unit::TestCase ] VS.reverse! - def combination(*args, &b) + FLAGS = [['', ' '], ['', '#'], ['', '+'], ['', '-'], ['', '0']] + + def self.combination(*args, &b) #AllPairs.exhaustive_each(*args, &b) AllPairs.each(*args, &b) end @@ -268,17 +271,8 @@ class TestSprintfComb < Test::Unit::TestCase str end - def test_format_integer - combination( - %w[B b d o X x], - [nil, 0, 5, 20], - ["", ".", ".0", ".8", ".20"], - ['', ' '], - ['', '#'], - ['', '+'], - ['', '-'], - ['', '0']) {|type, width, precision, sp, hs, pl, mi, zr| - format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}" + def self.assertions_format_integer(format) + proc { VS.each {|v| r = sprintf format, v e = emu_int format, v @@ -293,6 +287,14 @@ class TestSprintfComb < Test::Unit::TestCase } end + combination(%w[B b d o X x], + [nil, 0, 5, 20], + ["", ".", ".0", ".8", ".20"], + *FLAGS) {|type, width, precision, sp, hs, pl, mi, zr| + format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}" + define_method("test_format_integer(#{format})", assertions_format_integer(format)) + } + FLOAT_VALUES = [ -1e100, -123456789.0, @@ -526,17 +528,8 @@ class TestSprintfComb < Test::Unit::TestCase end - def test_format_float - combination( - %w[e E f g G], - [nil, 0, 5, 20], - ["", ".", ".0", ".8", ".20", ".200"], - ['', ' '], - ['', '#'], - ['', '+'], - ['', '-'], - ['', '0']) {|type, width, precision, sp, hs, pl, mi, zr| - format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}" + def self.assertions_format_float(format) + proc { FLOAT_VALUES.each {|v| r = sprintf format, v e = emu_float format, v @@ -550,4 +543,12 @@ class TestSprintfComb < Test::Unit::TestCase } } end + + combination(%w[e E f g G], + [nil, 0, 5, 20], + ["", ".", ".0", ".8", ".20", ".200", ".9999"], + *FLAGS) {|type, width, precision, sp, hs, pl, mi, zr| + format = "%#{sp}#{hs}#{pl}#{mi}#{zr}#{width}#{precision}#{type}" + define_method("test_format_float(#{format})", assertions_format_float(format)) + } end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 02271cefe6..23da909592 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -1,11 +1,13 @@ +# 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" + + WIDE_ENCODINGS = [ + Encoding::UTF_16BE, Encoding::UTF_16LE, + Encoding::UTF_32BE, Encoding::UTF_32LE, + ] def initialize(*args) @cls = String @@ -15,12 +17,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 # '[]' @@ -126,20 +210,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) @@ -151,6 +221,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 # '<=>' @@ -160,53 +232,44 @@ 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 def o.to_str; "bar"; end - assert_nil("foo" <=> o) + assert_equal(1, "foo" <=> o) + class << o;remove_method :to_str;end def o.<=>(x); nil; end assert_nil("foo" <=> o) + class << o;remove_method :<=>;end def o.<=>(x); 1; end assert_equal(-1, "foo" <=> o) + class << o;remove_method :<=>;end def o.<=>(x); 2**100; end - assert_equal(-(2**100), "foo" <=> o) + assert_equal(-1, "foo" <=> o) end def test_EQUAL # '==' - assert_equal(false, S("foo") == :foo) - assert(S("abcdef") == S("abcdef")) - - pre_1_7_1 do - $= = true - assert(S("CAT") == S('cat')) - assert(S("CaT") == S('cAt')) - $= = false - end + assert_not_equal(:foo, S("foo")) + assert_equal(S("abcdef"), S("abcdef")) - assert(S("CAT") != S('cat')) - assert(S("CaT") != S('cAt')) + assert_not_equal(S("CAT"), S('cat')) + assert_not_equal(S("CaT"), S('cAt')) o = Object.new def o.to_str; end def o.==(x); false; end assert_equal(false, "foo" == o) + class << o;remove_method :==;end def o.==(x); true; end assert_equal(true, "foo" == o) end def test_LSHIFT # '<<' assert_equal(S("world!"), S("world") << 33) - assert_equal(S("world!"), S("world") << S('!')) + assert_equal(S("world!"), S("world") << S("!")) s = "a" 10.times {|i| @@ -231,12 +294,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) @@ -275,11 +332,12 @@ class TestString < Test::Unit::TestCase end def casetest(a, b, rev=false) + msg = proc {"#{a} should#{' not' if rev} match #{b}"} case a - when b - assert(!rev) - else - assert(rev) + when b + assert(!rev, msg) + else + assert(rev, msg) end end @@ -287,13 +345,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 @@ -351,6 +402,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! @@ -407,6 +508,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 @@ -446,30 +608,40 @@ class TestString < Test::Unit::TestCase def test_clone for taint in [ false, true ] - for untrust in [ false, true ] - for frozen in [ false, true ] - a = S("Cool") - a.taint if taint - a.untrust if untrust - a.freeze if frozen - b = a.clone - - assert_equal(a, b) - assert(a.__id__ != b.__id__) - assert_equal(a.frozen?, b.frozen?) - assert_equal(a.untrusted?, b.untrusted?) - assert_equal(a.tainted?, b.tainted?) - end + for frozen in [ false, true ] + a = S("Cool") + a.taint if taint + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) + assert_equal(a.tainted?, b.tainted?) end 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) + result << 0x0300 + expected = S("\u0300".encode(Encoding::UTF_16LE)) + assert_equal(expected, result, bug7090) + assert_raise(TypeError) { 'foo' << :foo } + assert_raise(FrozenError) { 'foo'.freeze.concat('bar') } + end + + def test_concat_literals + s="." * 50 + assert_equal(Encoding::UTF_8, "#{s}x".encoding) end def test_count @@ -479,13 +651,37 @@ class TestString < Test::Unit::TestCase assert_equal(4, a.count(S("hello"), S("^l"))) assert_equal(4, a.count(S("ej-m"))) assert_equal(0, S("y").count(S("a\\-z"))) + assert_equal(5, "abc\u{3042 3044 3046}".count("^a")) + assert_equal(1, "abc\u{3042 3044 3046}".count("\u3042")) + assert_equal(5, "abc\u{3042 3044 3046}".count("^\u3042")) + assert_equal(2, "abc\u{3042 3044 3046}".count("a-z", "^a")) + assert_equal(0, "abc\u{3042 3044 3046}".count("a", "\u3042")) + assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "a")) + assert_equal(0, "abc\u{3042 3044 3046}".count("\u3042", "\u3044")) + assert_equal(4, "abc\u{3042 3044 3046}".count("^a", "^\u3044")) + assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3044", "^a")) + assert_equal(4, "abc\u{3042 3044 3046}".count("^\u3042", "^\u3044")) assert_raise(ArgumentError) { "foo".count } end def test_crypt assert_equal(S('aaGUC/JkO9/Sc'), S("mypassword").crypt(S("aa"))) - assert(S('aaGUC/JkO9/Sc') != S("mypassword").crypt(S("ab"))) + 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"))} + WIDE_ENCODINGS.each do |enc| + assert_raise(ArgumentError) {S("mypassword").crypt(S("aa".encode(enc)))} + assert_raise(ArgumentError) {S("mypassword".encode(enc)).crypt(S("aa"))} + end + + @cls == String and + assert_no_memory_leak([], 's = ""', "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 1000.times { s.crypt(-"..").clear } + end; end def test_delete @@ -497,7 +693,14 @@ 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")) + assert_equal("\u3042", "abc\u{3042 3044 3046}".delete("^\u3042")) + + bug6160 = '[ruby-dev:45374]' + assert_equal("", '\\'.delete('\\'), bug6160) end def test_delete! @@ -556,24 +759,96 @@ 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 } + assert_raise_with_message(RuntimeError, /invalid/) { + '"\\u{007F}".xxxxxx'.undump + } end def test_dup for taint in [ false, true ] - for untrust in [ false, true ] - for frozen in [ false, true ] - a = S("hello") - a.taint if taint - a.untrust if untrust - a.freeze if frozen - b = a.dup - - assert_equal(a, b) - assert(a.__id__ != b.__id__) - assert(!b.frozen?) - assert_equal(a.tainted?, b.tainted?) - assert_equal(a.untrusted?, b.untrusted?) - end + for frozen in [ false, true ] + a = S("hello") + a.taint if taint + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_not_predicate(b, :frozen?) + assert_equal(a.tainted?, b.tainted?) end end end @@ -588,61 +863,350 @@ 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 def test_each_byte + s = S("ABC") + res = [] - S("ABC").each_byte {|x| res << x } + assert_equal s.object_id, s.each_byte {|x| res << x }.object_id assert_equal(65, res[0]) assert_equal(66, res[1]) assert_equal(67, res[2]) + + assert_equal 65, s.each_byte.next + end + + def test_bytes + s = S("ABC") + assert_equal [65, 66, 67], s.bytes + + if ENUMERATOR_WANTARRAY + assert_warn(/block not used/) { + assert_equal [65, 66, 67], s.bytes {} + } + else + warning = /passing a block to String#bytes is deprecated/ + assert_warning(warning) { + res = [] + assert_equal s.object_id, s.bytes {|x| res << x }.object_id + assert_equal(65, res[0]) + assert_equal(66, res[1]) + assert_equal(67, res[2]) + } + assert_warning(warning) { + s = S("ABC") + res = [] + assert_same s, s.bytes {|x| res << x } + assert_equal [65, 66, 67], res + } + end + end + + def test_each_codepoint + # Single byte optimization + assert_equal 65, S("ABC").each_codepoint.next + + s = S("\u3042\u3044\u3046") + + res = [] + assert_equal s.object_id, s.each_codepoint {|x| res << x }.object_id + assert_equal(0x3042, res[0]) + assert_equal(0x3044, res[1]) + assert_equal(0x3046, res[2]) + + assert_equal 0x3042, s.each_codepoint.next + end + + def test_codepoints + # Single byte optimization + assert_equal [65, 66, 67], S("ABC").codepoints + + s = S("\u3042\u3044\u3046") + assert_equal [0x3042, 0x3044, 0x3046], s.codepoints + + if ENUMERATOR_WANTARRAY + assert_warn(/block not used/) { + assert_equal [0x3042, 0x3044, 0x3046], s.codepoints {} + } + else + warning = /passing a block to String#codepoints is deprecated/ + assert_warning(warning) { + res = [] + assert_equal s.object_id, s.codepoints {|x| res << x }.object_id + assert_equal(0x3042, res[0]) + assert_equal(0x3044, res[1]) + assert_equal(0x3046, res[2]) + } + assert_warning(warning) { + s = S("ABC") + res = [] + assert_same s, s.codepoints {|x| res << x } + assert_equal [65, 66, 67], res + } + end + end + + def test_each_char + s = S("ABC") + + res = [] + assert_equal s.object_id, s.each_char {|x| res << x }.object_id + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) + + assert_equal "A", S("ABC").each_char.next + end + + def test_chars + s = S("ABC") + assert_equal ["A", "B", "C"], s.chars + + if ENUMERATOR_WANTARRAY + assert_warn(/block not used/) { + assert_equal ["A", "B", "C"], s.chars {} + } + else + warning = /passing a block to String#chars is deprecated/ + assert_warning(warning) { + res = [] + assert_equal s.object_id, s.chars {|x| res << x }.object_id + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) + } + end + 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 + assert_predicate g.dup.taint.each_grapheme_cluster.to_a[0], :tainted? + end + + [ + ["\u{a 324}", ["\u000A", "\u0324"]], + ["\u{d 324}", ["\u000D", "\u0324"]], + ["abc", ["a", "b", "c"]], + ].each do |str, grapheme_clusters| + assert_equal grapheme_clusters, str.each_grapheme_cluster.to_a + assert_equal grapheme_clusters.size, str.each_grapheme_cluster.size + str.dup.taint.each_grapheme_cluster do |g| + assert_predicate g, :tainted? + end + 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}", + ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc| + g = g.encode(enc) + assert_equal [g], g.grapheme_clusters + assert_predicate g.taint.grapheme_clusters[0], :tainted? + end + + [ + "\u{a 324}", + "\u{d 324}", + "abc", + ].product([Encoding::UTF_8, *WIDE_ENCODINGS]) do |g, enc| + g = g.encode(enc) + assert_equal g.chars, g.grapheme_clusters + end + assert_equal ["a", "b", "c"], "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.taint + res = [] + assert_same s, s.grapheme_clusters {|x| res << x } + assert_equal(3, res.size) + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) + res.each {|g| assert_predicate(g, :tainted?)} + } + end end def test_each_line save = $/ $/ = "\n" res=[] - S("hello\nworld").lines.each {|x| res << x} + S("hello\nworld").each_line {|x| res << x} assert_equal(S("hello\n"), res[0]) assert_equal(S("world"), res[1]) 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]) + S("hello\n\n\nworld").each_line(S('')) {|x| res << x} + 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]) $/ = "!" res=[] - S("hello!world").lines.each {|x| res << x} + S("hello!world").each_line {|x| res << x} assert_equal(S("hello!"), res[0]) assert_equal(S("world"), res[1]) + $/ = "ab" + + res=[] + S("a").lines.each {|x| res << x} + assert_equal(1, res.size) + assert_equal(S("a"), res[0]) + $/ = save s = nil "foo\nbar".each_line(nil) {|s2| s = s2 } assert_equal("foo\nbar", s) + + assert_equal "hello\n", S("hello\nworld").each_line.next + assert_equal "hello\nworld", S("hello\nworld").each_line(nil).next + + bug7646 = "[ruby-dev:46827]" + 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 + s = S("hello\nworld") + assert_equal ["hello\n", "world"], s.lines + assert_equal ["hello\nworld"], s.lines(nil) + + if ENUMERATOR_WANTARRAY + assert_warn(/block not used/) { + assert_equal ["hello\n", "world"], s.lines {} + } + else + assert_warning(/passing a block to String#lines is deprecated/) { + res = [] + assert_equal s.object_id, s.lines {|x| res << x }.object_id + assert_equal(S("hello\n"), res[0]) + assert_equal(S("world"), res[1]) + } + end end def test_empty? - assert(S("").empty?) - assert(!S("not").empty?) + assert_empty(S("")) + assert_not_empty(S("not")) + end + + def test_end_with? + assert_send([S("hello"), :end_with?, S("llo")]) + assert_not_send([S("hello"), :end_with?, S("ll")]) + assert_send([S("hello"), :end_with?, S("el"), S("lo")]) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {S("str").end_with? :not_convertible_to_string} end def test_eql? a = S("hello") - assert(a.eql?(S("hello"))) - assert(a.eql?(a)) + assert_operator(a, :eql?, S("hello")) + assert_operator(a, :eql?, a) end def test_gsub @@ -652,18 +1216,37 @@ 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 - a.untrust - assert(a.gsub(/./, S('X')).tainted?) - assert(a.gsub(/./, S('X')).untrusted?) + assert_predicate(a.gsub(/./, S('X')), :tainted?) assert_equal("z", "abc".gsub(/./, "a" => "z"), "moved from btest/knownbug") assert_raise(ArgumentError) { "foo".gsub } end + def test_gsub_encoding + a = S("hello world") + a.force_encoding Encoding::UTF_8 + + b = S("hi") + b.force_encoding Encoding::US_ASCII + + assert_equal Encoding::UTF_8, a.gsub(/hello/, b).encoding + + c = S("everybody") + 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! a = S("hello") b = a.dup @@ -685,10 +1268,8 @@ class TestString < Test::Unit::TestCase r = S('X') r.taint - r.untrust a.gsub!(/./, r) - assert(a.tainted?) - assert(a.untrusted?) + assert_predicate(a, :tainted?) a = S("hello") assert_nil(a.sub!(S('X'), S('Y'))) @@ -716,19 +1297,11 @@ class TestString < Test::Unit::TestCase def test_hash assert_equal(S("hello").hash, S("hello").hash) - assert(S("hello").hash != S("helLO").hash) - 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) + assert_not_equal(S("hello").hash, S("helLO").hash) + bug4104 = '[ruby-core:33500]' + assert_not_equal(S("a").hash, S("a\0").hash, bug4104) + bug9172 = '[ruby-core:58658] [Bug #9172]' + assert_not_equal(S("sub-setter").hash, S("discover").hash, bug9172) end def test_hex @@ -742,10 +1315,10 @@ class TestString < Test::Unit::TestCase end def test_include? - assert( S("foobar").include?(?f)) - assert( S("foobar").include?(S("foo"))) - assert(!S("foobar").include?(S("baz"))) - assert(!S("foobar").include?(?z)) + assert_include(S("foobar"), ?f) + assert_include(S("foobar"), S("foo")) + assert_not_include(S("foobar"), S("baz")) + assert_not_include(S("foobar"), ?z) end def test_index @@ -765,6 +1338,20 @@ class TestString < Test::Unit::TestCase assert_nil(S("hello").index(S("z"))) assert_nil(S("hello").index(/z./)) + assert_equal(0, S("").index(S(""))) + assert_equal(0, S("").index(//)) + assert_nil(S("").index(S("hello"))) + assert_nil(S("").index(/hello/)) + assert_equal(0, S("hello").index(S(""))) + assert_equal(0, S("hello").index(//)) + + s = S("long") * 1000 << "x" + assert_nil(s.index(S("y"))) + assert_equal(4 * 1000, s.index(S("x"))) + s << "yx" + assert_equal(4 * 1000, s.index(S("x"))) + assert_equal(4 * 1000, s.index(S("xyx"))) + o = Object.new def o.to_str; "bar"; end assert_equal(3, "foobarbarbaz".index(o)) @@ -774,9 +1361,17 @@ 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(:koala != S("Koala").intern) + assert_not_equal(:koala, S("Koala").intern) end def test_length @@ -805,6 +1400,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! @@ -841,6 +1439,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 @@ -861,11 +1463,9 @@ class TestString < Test::Unit::TestCase a = S("foo") a.taint - a.untrust b = a.replace(S("xyz")) assert_equal(S("xyz"), b) - assert(b.tainted?) - assert(b.untrusted?) + assert_predicate(b, :tainted?) s = "foo" * 100 s2 = ("bar" * 100).dup @@ -877,10 +1477,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 @@ -930,6 +1530,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 @@ -956,6 +1559,24 @@ class TestString < Test::Unit::TestCase res = [] a.scan(/(...)/) { |w| res << w } assert_equal([[S("cru")], [S("el ")], [S("wor")]],res) + + a = S("hello") + a.taint + res = [] + a.scan(/./) { |w| res << w } + assert_predicate(res[0], :tainted?, '[ruby-core:33338] #4087') + + /h/ =~ a + a.scan(/x/) + assert_nil($~) + + /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 @@ -990,6 +1611,11 @@ class TestString < Test::Unit::TestCase assert_equal(S("Bar"), S("FooBar").slice(S("Bar"))) assert_nil(S("FooBar").slice(S("xyzzy"))) assert_nil(S("FooBar").slice(S("plugh"))) + + bug9882 = '[ruby-core:62842] [Bug #9882]' + substr = S("\u{30c6 30b9 30c8 2019}#{bug9882}").slice(4..-1) + assert_equal(S(bug9882).hash, substr.hash, bug9882) + assert_predicate(substr, :ascii_only?, bug9882) end def test_slice! @@ -1092,19 +1718,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(" "))) @@ -1126,9 +1744,107 @@ class TestString < Test::Unit::TestCase assert_equal([S("a"), S(""), S("b"), S("c"), S("")], S("a||b|c|").split(S('|'), -1)) assert_equal([], "".split(//, 1)) + ensure + $; = fs + end + + def test_split_with_block + fs, $; = $;, nil + result = []; S(" a b\t c ").split {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + result = []; S(" a b\t c ").split(S(" ")) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S(" a | b | c ").split(S("|")) {|s| result << s} + assert_equal([S(" a "), S(" b "), S(" c ")], result) + + result = []; S("aXXbXXcXX").split(/X./) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("abc").split(//) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("a|b|c").split(S('|'), 1) {|s| result << s} + assert_equal([S("a|b|c")], result) + + result = []; S("a|b|c").split(S('|'), 2) {|s| result << s} + assert_equal([S("a"), S("b|c")], result) + result = []; S("a|b|c").split(S('|'), 3) {|s| result << s} + assert_equal([S("a"), S("b"), S("c")], result) + + result = []; S("a|b|c|").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S("b"), S("c"), S("")], result) + result = []; S("a|b|c||").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S("b"), S("c"), S(""), S("")], result) + + result = []; S("a||b|c|").split(S('|')) {|s| result << s} + assert_equal([S("a"), S(""), S("b"), S("c")], result) + result = []; S("a||b|c|").split(S('|'), -1) {|s| result << s} + assert_equal([S("a"), S(""), S("b"), S("c"), S("")], result) + + result = []; "".split(//, 1) {|s| result << s} + assert_equal([], result) + + result = []; "aaa,bbb,ccc,ddd".split(/,/) {|s| result << s.gsub(/./, "A")} + assert_equal(["AAA"]*4, result) + 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 - assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + def test_split_wchar + bug8642 = '[ruby-core:56036] [Bug #8642]' + WIDE_ENCODINGS.each do |enc| + s = S("abc,def".encode(enc)) + assert_equal(["abc", "def"].map {|c| c.encode(enc)}, + s.split(",".encode(enc)), + "#{bug8642} in #{enc.name}") + 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 @@ -1156,6 +1872,20 @@ class TestString < Test::Unit::TestCase assert_nil(a.squeeze!) end + def test_start_with? + assert_send([S("hello"), :start_with?, S("hel")]) + assert_not_send([S("hello"), :start_with?, S("el")]) + assert_send([S("hello"), :start_with?, S("el"), S("he")]) + + 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 assert_equal(S("x"), S(" x ").strip) assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip) @@ -1190,6 +1920,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\\')) @@ -1223,10 +1954,8 @@ class TestString < Test::Unit::TestCase a = S("hello") a.taint - a.untrust x = a.sub(/./, S('X')) - assert(x.tainted?) - assert(x.untrusted?) + assert_predicate(x, :tainted?) o = Object.new def o.to_str; "bar"; end @@ -1241,6 +1970,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! @@ -1267,10 +2006,14 @@ class TestString < Test::Unit::TestCase r = S('X') r.taint - r.untrust a.sub!(/./, r) - assert(a.tainted?) - assert(a.untrusted?) + 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 @@ -1289,10 +2032,18 @@ class TestString < Test::Unit::TestCase assert_equal("abce", "abcd".succ) assert_equal("THX1139", "THX1138".succ) - assert_equal("<<koalb>>", "<<koala>>".succ) + assert_equal("<\<koalb>>", "<\<koala>>".succ) 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! @@ -1334,6 +2085,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 @@ -1344,7 +2103,9 @@ class TestString < Test::Unit::TestCase n += S("\001") assert_equal(16, n.sum(17)) n[0] = 2.chr - assert(15 != n.sum) + 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) @@ -1359,7 +2120,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 @@ -1427,6 +2188,13 @@ class TestString < Test::Unit::TestCase assert_equal(0x4000000000000000, "4611686018427387904".to_i(10)) assert_equal(1, "1__2".to_i(10)) assert_equal(1, "1_z".to_i(10)) + + bug6192 = '[ruby-core:43566]' + assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16be").to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-16le").to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32be").to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("utf-32le").to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {"0".encode("iso-2022-jp").to_i} end def test_to_s @@ -1465,6 +2233,15 @@ class TestString < Test::Unit::TestCase assert_equal(true, "\u0101".tr("\u0101", "a").ascii_only?) assert_equal(true, "\u3041".tr("\u3041", "a").ascii_only?) assert_equal(false, "\u3041\u3042".tr("\u3041", "a").ascii_only?) + + bug6156 = '[ruby-core:43335]' + bug13950 = '[ruby-core:83056] [Bug #13950]' + str, range, star = %w[b a-z *].map{|s|s.encode("utf-16le")} + 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! @@ -1652,20 +2429,12 @@ class TestString < Test::Unit::TestCase end def test_frozen_check - assert_raise(RuntimeError) { + assert_raise(FrozenError) { s = "" s.sub!(/\A/) { s.freeze; "zzz" } } end - def test_tainted_str_new - a = [] - a << a - s = a.inspect - assert(s.tainted?) - assert_equal("[[...]]", s) - end - class S2 < String end def test_str_new4 @@ -1720,10 +2489,6 @@ class TestString < Test::Unit::TestCase assert_nil(l.slice!(/\A.*\n/), "[ruby-dev:31665]") end - def test_end_with? - assert("abc".end_with?("c")) - end - def test_times2 s1 = '' 100.times {|n| @@ -1746,7 +2511,7 @@ class TestString < Test::Unit::TestCase def test_match_method assert_equal("bar", "foobarbaz".match(/bar/).to_s) - o = /foo/ + o = Regexp.new('foo') def o.match(x, y, z); x + y + z; end assert_equal("foobarbaz", "foo".match(o, "bar", "baz")) x = nil @@ -1756,6 +2521,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 @@ -1770,12 +2579,35 @@ class TestString < Test::Unit::TestCase assert_instance_of(String, s.to_s) end + def test_inspect_nul + bug8290 = '[ruby-core:54458]' + s = "\0" + "12" + assert_equal '"\u000012"', s.inspect, bug8290 + s = "\0".b + "12" + assert_equal '"\x0012"', s.inspect, bug8290 + end + def test_partition assert_equal(%w(he l lo), "hello".partition(/l/)) assert_equal(%w(he l lo), "hello".partition("l")) assert_raise(TypeError) { "hello".partition(1) } def (hyphen = Object.new).to_str; "-"; end assert_equal(%w(foo - bar), "foo-bar".partition(hyphen), '[ruby-core:23540]') + + bug6206 = '[ruby-dev:45441]' + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + s = S("a:".force_encoding(enc)) + assert_equal([enc]*3, s.partition("|").map(&:encoding), bug6206) + end + + assert_equal(["\u30E6\u30FC\u30B6", "@", "\u30C9\u30E1.\u30A4\u30F3"], + "\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 @@ -1784,10 +2616,31 @@ class TestString < Test::Unit::TestCase assert_raise(TypeError) { "hello".rpartition(1) } def (hyphen = Object.new).to_str; "-"; end assert_equal(%w(foo - bar), "foo-bar".rpartition(hyphen), '[ruby-core:23540]') + + bug6206 = '[ruby-dev:45441]' + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + s = S("a:".force_encoding(enc)) + assert_equal([enc]*3, s.rpartition("|").map(&:encoding), bug6206) + end + + 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 @@ -1808,6 +2661,7 @@ class TestString < Test::Unit::TestCase c.class_eval { attr 1 } end + class << o;remove_method :to_str;end def o.to_str; "foo"; end assert_nothing_raised do c.class_eval { attr o } @@ -1832,14 +2686,40 @@ class TestString < Test::Unit::TestCase assert_equal("\u3042", ("\u3042" * 100)[-1]) end +=begin def test_compare_different_encoding_string s1 = "\xff".force_encoding("UTF-8") s2 = "\xff".force_encoding("ISO-2022-JP") - #assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort) + assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort) end +=end def test_casecmp + assert_equal(0, "FoO".casecmp("fOO")) + assert_equal(1, "FoO".casecmp("BaR")) + assert_equal(-1, "baR".casecmp("FoO")) assert_equal(1, "\u3042B".casecmp("\u3042a")) + + assert_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 @@ -1851,16 +2731,294 @@ 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 - return assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/) ("aaaaaaaa".."zzzzzzzz").each {|s| s.to_sym } INPUT 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(//, '') @@ -1876,10 +3034,178 @@ class TestString < Test::Unit::TestCase end def test_ascii_incomat_inspect - [Encoding::UTF_16LE, Encoding::UTF_16BE, - Encoding::UTF_32LE, Encoding::UTF_32BE].each do |e| + bug4081 = '[ruby-core:33283]' + WIDE_ENCODINGS.each do |e| assert_equal('"abc"', "abc".encode(e).inspect) assert_equal('"\\u3042\\u3044\\u3046"', "\u3042\u3044\u3046".encode(e).inspect) + assert_equal('"ab\\"c"', "ab\"c".encode(e).inspect, bug4081) end + begin + verbose, $VERBOSE = $VERBOSE, nil + ext = Encoding.default_external + Encoding.default_external = "us-ascii" + $VERBOSE = verbose + i = "abc\"\\".force_encoding("utf-8").inspect + ensure + $VERBOSE = nil + Encoding.default_external = ext + $VERBOSE = verbose + end + assert_equal('"abc\\"\\\\"', i, bug4081) + end + + def test_dummy_inspect + assert_equal('"\e\x24\x42\x22\x4C\x22\x68\e\x28\x42"', + "\u{ffe2}\u{2235}".encode("cp50220").inspect) + end + + def test_prepend + 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 + "b" + end + assert_equal(S("ba"), "a".prepend(foo)) + + a = S("world") + b = S("hello ") + a.prepend(b) + assert_equal(S("hello world"), a) + assert_equal(S("hello "), b) + end + + def u(str) + str.force_encoding(Encoding::UTF_8) + end + + def test_byteslice + assert_equal("h", "hello".byteslice(0)) + assert_equal(nil, "hello".byteslice(5)) + assert_equal("o", "hello".byteslice(-1)) + assert_equal(nil, "hello".byteslice(-6)) + + assert_equal("", "hello".byteslice(0, 0)) + assert_equal("hello", "hello".byteslice(0, 6)) + assert_equal("hello", "hello".byteslice(0, 6)) + assert_equal("", "hello".byteslice(5, 1)) + assert_equal("o", "hello".byteslice(-1, 6)) + assert_equal(nil, "hello".byteslice(-6, 1)) + assert_equal(nil, "hello".byteslice(0, -1)) + + assert_equal("h", "hello".byteslice(0..0)) + assert_equal("", "hello".byteslice(5..0)) + assert_equal("o", "hello".byteslice(4..5)) + assert_equal(nil, "hello".byteslice(6..0)) + assert_equal("", "hello".byteslice(-1..0)) + assert_equal("llo", "hello".byteslice(-3..5)) + + assert_equal(u("\x81"), "\u3042".byteslice(1)) + assert_equal(u("\x81\x82"), "\u3042".byteslice(1, 2)) + assert_equal(u("\x81\x82"), "\u3042".byteslice(1..2)) + + assert_equal(u("\x82")+("\u3042"*9), ("\u3042"*10).byteslice(2, 28)) + + bug7954 = '[ruby-dev:47108]' + assert_equal(false, "\u3042".byteslice(0, 2).valid_encoding?, bug7954) + assert_equal(false, ("\u3042"*10).byteslice(0, 20).valid_encoding?, bug7954) + end + + def test_unknown_string_option + str = nil + assert_nothing_raised(SyntaxError) do + eval(%{ + str = begin"hello"end + }) + end + assert_equal "hello", str + end + + def test_eq_tilde_can_be_overridden + assert_separately([], <<-RUBY) + class String + undef =~ + def =~(str) + "foo" + end + end + + assert_equal("foo", "" =~ //) + 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: 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 + +class TestString2 < TestString + def initialize(*args) + super + @cls = S2 end end diff --git a/test/ruby/test_stringchar.rb b/test/ruby/test_stringchar.rb index b70f171c84..e13beef69c 100644 --- a/test/ruby/test_stringchar.rb +++ b/test/ruby/test_stringchar.rb @@ -1,17 +1,18 @@ +# frozen_string_literal: false require 'test/unit' class TestStringchar < Test::Unit::TestCase def test_string assert_equal("abcd", "abcd") assert_match(/abcd/, "abcd") - assert("abcd" === "abcd") + assert_operator("abcd", :===, "abcd") # compile time string concatenation assert_equal("abcd", "ab" "cd") assert_equal("22aacd44", "#{22}aa" "cd#{44}") assert_equal("22aacd445566", "#{22}aa" "cd#{44}" "55" "#{66}") - assert("abc" !~ /^$/) - assert("abc\n" !~ /^$/) - assert("abc" !~ /^d*$/) + assert_operator("abc", :!~, /^$/) + assert_operator("abc\n", :!~, /^$/) + assert_operator("abc", :!~, /^d*$/) assert_equal(3, ("abc" =~ /d*$/)) assert("" =~ /^$/) assert("\n" =~ /^$/) @@ -30,12 +31,12 @@ class TestStringchar < Test::Unit::TestCase assert(/(\s+\d+){2}/ =~ " 1 2"); assert_equal(" 1 2", $&) assert(/(?:\s+\d+){2}/ =~ " 1 2"); assert_equal(" 1 2", $&) - $x = <<END; + x = <<END; ABCD ABCD END - $x.gsub!(/((.|\n)*?)B((.|\n)*?)D/m ,'\1\3') - assert_equal("AC\nAC\n", $x) + x.gsub!(/((.|\n)*?)B((.|\n)*?)D/m ,'\1\3') + assert_equal("AC\nAC\n", x) assert_match(/foo(?=(bar)|(baz))/, "foobar") assert_match(/foo(?=(bar)|(baz))/, "foobaz") @@ -56,12 +57,12 @@ END assert_equal('-', foo * 1) assert_equal('', foo * 0) - $x = "a.gif" - assert_equal("gif", $x.sub(/.*\.([^\.]+)$/, '\1')) - assert_equal("b.gif", $x.sub(/.*\.([^\.]+)$/, 'b.\1')) - assert_equal("", $x.sub(/.*\.([^\.]+)$/, '\2')) - assert_equal("ab", $x.sub(/.*\.([^\.]+)$/, 'a\2b')) - assert_equal("<a.gif>", $x.sub(/.*\.([^\.]+)$/, '<\&>')) + x = "a.gif" + assert_equal("gif", x.sub(/.*\.([^\.]+)$/, '\1')) + assert_equal("b.gif", x.sub(/.*\.([^\.]+)$/, 'b.\1')) + assert_equal("", x.sub(/.*\.([^\.]+)$/, '\2')) + assert_equal("ab", x.sub(/.*\.([^\.]+)$/, 'a\2b')) + assert_equal("<a.gif>", x.sub(/.*\.([^\.]+)$/, '<\&>')) end def test_char @@ -78,16 +79,16 @@ END assert_equal("abc", "abcc".squeeze!("a-z")) assert_equal("ad", "abcd".delete!("bc")) - $x = "abcdef" - $y = [ ?a, ?b, ?c, ?d, ?e, ?f ] - $bad = false - $x.each_byte {|i| - if i.chr != $y.shift - $bad = true + x = "abcdef" + y = [ ?a, ?b, ?c, ?d, ?e, ?f ] + bad = false + x.each_byte {|i| + if i.chr != y.shift + bad = true break end } - assert(!$bad) + assert(!bad) s = "a string" s[0..s.size]="another string" @@ -163,4 +164,19 @@ EOS s.delete!("a-z") assert_equal("BB", s) end + + def test_dump + bug3996 = '[ruby-core:32935]' + Encoding.list.find_all {|enc| enc.ascii_compatible?}.each do |enc| + (0..256).map do |c| + begin + s = c.chr(enc) + rescue RangeError, ArgumentError + break + else + assert_not_match(/\0/, s.dump, bug3996) + end + end + end + end end diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb index 49dcdb45b2..0046e9bd04 100644 --- a/test/ruby/test_struct.rb +++ b/test/ruby/test_struct.rb @@ -1,10 +1,12 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' require 'timeout' -class TestStruct < Test::Unit::TestCase +module TestStruct def test_struct - struct_test = Struct.new("Test", :foo, :bar) - assert_equal(Struct::Test, struct_test) + struct_test = @Struct.new("Test", :foo, :bar) + assert_equal(@Struct::Test, struct_test) test = struct_test.new(1, 2) assert_equal(1, test.foo) @@ -27,7 +29,7 @@ class TestStruct < Test::Unit::TestCase def test_morethan10members list = %w( a b c d e f g h i j k l m n o p ) until list.empty? - c = Struct.new(* list.map {|ch| ch.intern }).new + c = @Struct.new(* list.map {|ch| ch.intern }).new list.each do |ch| c.__send__(ch) end @@ -39,7 +41,7 @@ class TestStruct < Test::Unit::TestCase names = [:a, :b, :c, :d] 1.upto(4) {|n| fields = names[0, n] - klass = Struct.new(*fields) + klass = @Struct.new(*fields) o = klass.new(*(0...n).to_a) fields.each_with_index {|name, i| assert_equal(i, o[name]) @@ -52,39 +54,28 @@ class TestStruct < Test::Unit::TestCase end def test_inherit - klass = Struct.new(:a) + klass = @Struct.new(:a) klass2 = Class.new(klass) o = klass2.new(1) assert_equal(1, o.a) end def test_members - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) assert_equal([:a], klass.members) assert_equal([:a], o.members) end def test_ref - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) assert_equal(1, o[:a]) assert_raise(NameError) { o[:b] } end - def test_modify - klass = Struct.new(:a) - o = klass.new(1) - assert_raise(SecurityError) do - Thread.new do - $SAFE = 4 - o.a = 2 - end.value - end - end - def test_set - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) o[:a] = 2 assert_equal(2, o[:a]) @@ -92,161 +83,350 @@ class TestStruct < Test::Unit::TestCase end def test_struct_new - assert_raise(NameError) { Struct.new("foo") } - assert_nothing_raised { Struct.new("Foo") } - Struct.instance_eval { remove_const(:Foo) } - assert_nothing_raised { Struct.new(:a) { } } - assert_raise(RuntimeError) { Struct.new(:a) { raise } } + assert_raise(NameError) { @Struct.new("foo") } + assert_nothing_raised { @Struct.new("Foo") } + @Struct.instance_eval { remove_const(:Foo) } + assert_nothing_raised { @Struct.new(:a) { } } + assert_raise(RuntimeError) { @Struct.new(:a) { raise } } 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) + 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 - klass = Struct.new(:a, :b) + klass = @Struct.new(:a, :b) o = klass.new(1, 2) assert_equal([1, 2], o.each.to_a) end def test_each_pair - klass = Struct.new(:a, :b) + klass = @Struct.new(:a, :b) o = klass.new(1, 2) assert_equal([[:a, 1], [:b, 2]], o.each_pair.to_a) + bug7382 = '[ruby-dev:46533]' + a = [] + o.each_pair {|x| a << x} + assert_equal([[:a, 1], [:b, 2]], a, bug7382) end def test_inspect - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) assert_equal("#<struct a=1>", o.inspect) o.a = o assert_match(/^#<struct a=#<struct #<.*?>:...>>$/, o.inspect) - Struct.new("Foo", :a) - o = Struct::Foo.new(1) - assert_equal("#<struct Struct::Foo a=1>", o.inspect) - Struct.instance_eval { remove_const(:Foo) } + @Struct.new("Foo", :a) + o = @Struct::Foo.new(1) + assert_equal("#<struct #@Struct::Foo a=1>", o.inspect) + @Struct.instance_eval { remove_const(:Foo) } - klass = Struct.new(:a, :b) + klass = @Struct.new(:a, :b) o = klass.new(1, 2) assert_equal("#<struct a=1, b=2>", o.inspect) - klass = Struct.new(:@a) + klass = @Struct.new(:@a) o = klass.new(1) + assert_equal(1, o.__send__(:@a)) assert_equal("#<struct :@a=1>", o.inspect) + o.__send__(:"@a=", 2) + assert_equal(2, o.__send__(:@a)) + assert_equal("#<struct :@a=2>", o.inspect) + o.__send__("@a=", 3) + assert_equal(3, o.__send__(:@a)) + assert_equal("#<struct :@a=3>", o.inspect) + + methods = klass.instance_methods(false) + assert_equal([:@a, :"@a="].sort.inspect, methods.sort.inspect, '[Bug #8756]') + assert_include(methods, :@a) + assert_include(methods, :"@a=") end def test_init_copy - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) assert_equal(o, o.dup) end def test_aref - klass = Struct.new(:a) + 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 - klass = Struct.new(:a) + klass = @Struct.new(:a) 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 - klass = Struct.new(:a, :b, :c, :d, :e, :f) + klass = @Struct.new(:a, :b, :c, :d, :e, :f) o = klass.new(1, 2, 3, 4, 5, 6) assert_equal([2, 4, 6], o.values_at(1, 3, 5)) assert_equal([2, 3, 4, 3, 4, 5], o.values_at(1..3, 2...5)) end def test_select - klass = Struct.new(:a, :b, :c, :d, :e, :f) + klass = @Struct.new(:a, :b, :c, :d, :e, :f) o = klass.new(1, 2, 3, 4, 5, 6) assert_equal([1, 3, 5], o.select {|v| v % 2 != 0 }) assert_raise(ArgumentError) { o.select(1) } end + def test_filter + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal([1, 3, 5], o.filter {|v| v % 2 != 0 }) + assert_raise(ArgumentError) { o.filter(1) } + end + + def test_big_struct + klass1 = @Struct.new(*('a'..'z').map(&:to_sym)) + o = klass1.new + 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) + klass1 = @Struct.new(:a) + klass2 = @Struct.new(:a, :b) o1 = klass1.new(1) o2 = klass1.new(1) o3 = klass2.new(1) - assert(o1.==(o2)) - assert(o1 != o3) + assert_equal(o1, o2) + assert_not_equal(o1, o3) end def test_hash - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) - assert(o.hash.is_a?(Fixnum)) + assert_kind_of(Integer, o.hash) end def test_eql - klass1 = Struct.new(:a) - klass2 = Struct.new(:a, :b) + klass1 = @Struct.new(:a) + klass2 = @Struct.new(:a, :b) o1 = klass1.new(1) o2 = klass1.new(1) o3 = klass2.new(1) - assert(o1.eql?(o2)) - assert(!(o1.eql?(o3))) + assert_operator(o1, :eql?, o2) + assert_not_operator(o1, :eql?, o3) end def test_size - klass = Struct.new(:a) + klass = @Struct.new(:a) o = klass.new(1) assert_equal(1, o.size) end def test_error assert_raise(TypeError){ - Struct.new(0) + @Struct.new(0) } end + def test_redefinition_warning + @Struct.new("RedefinitionWarning") + e = EnvUtil.verbose_warning do + @Struct.new("RedefinitionWarning") + end + assert_match(/redefining constant #@Struct::RedefinitionWarning/, e) + end + def test_nonascii - struct_test = Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") - assert_equal(Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]') + struct_test = @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") + assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]') a = struct_test.new(42) - assert_equal("#<struct Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]') + assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]') + e = EnvUtil.verbose_warning do + @Struct.new("R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") + end + assert_nothing_raised(Encoding::CompatibilityError) do + assert_match(/redefining constant #@Struct::R\u{e9}sum\u{e9}/, e) + end + end + + def test_junk + struct_test = @Struct.new("Foo", "a\000") + o = struct_test.new(1) + assert_equal(1, o.send("a\000")) + @Struct.instance_eval { remove_const(:Foo) } end def test_comparison_when_recursive - klass1 = Struct.new(:a, :b, :c) + klass1 = @Struct.new(:a, :b, :c) x = klass1.new(1, 2, nil); x.c = x y = klass1.new(1, 2, nil); y.c = y Timeout.timeout(1) { - assert x == y - assert x.eql? y + assert_equal x, y + assert_operator x, :eql?, y } z = klass1.new(:something, :other, nil); z.c = z Timeout.timeout(1) { - assert x != z - assert !x.eql?(z) + assert_not_equal x, z + assert_not_operator x, :eql?, z } x.c = y; y.c = x Timeout.timeout(1) { - assert x == y - assert x.eql?(y) + assert_equal x, y + assert_operator x, :eql?, y } x.c = z; z.c = x Timeout.timeout(1) { - assert x != z - assert !x.eql?(z) + assert_not_equal x, z + assert_not_operator x, :eql?, z } end + + def test_to_h + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal({a:1, b:2, c:3, d:4, e:5, f:6}, o.to_h) + end + + def test_to_h_block + klass = @Struct.new(:a, :b, :c, :d, :e, :f) + o = klass.new(1, 2, 3, 4, 5, 6) + assert_equal({"a" => 1, "b" => 4, "c" => 9, "d" => 16, "e" => 25, "f" => 36}, + o.to_h {|k, v| [k.to_s, v*v]}) + end + + def test_question_mark_in_member + klass = @Struct.new(:a, :b?) + x = Object.new + 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 + klass = @Struct.new(:a, :b!) + 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 + klass = @Struct.new(:a) + x = klass.new + 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 + + def initialize(*) + super + @Struct = Struct + end + end + + class SubStruct < Test::Unit::TestCase + include TestStruct + SubStruct = Class.new(Struct) + + def initialize(*) + super + @Struct = SubStruct + end + end end diff --git a/test/ruby/test_super.rb b/test/ruby/test_super.rb index 8de1e2fa7e..bb78ab516f 100644 --- a/test/ruby/test_super.rb +++ b/test/ruby/test_super.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' class TestSuper < Test::Unit::TestCase @@ -6,6 +7,7 @@ class TestSuper < Test::Unit::TestCase def double(a, b) [a,b] end def array(*a) a end def optional(a = 0) a end + def keyword(**a) a end end class Single1 < Base def single(*) super end @@ -49,6 +51,18 @@ class TestSuper < Test::Unit::TestCase class Optional5 < Base def array(a = 1, b = 2, *) super end end + class Keyword1 < Base + def keyword(foo: "keyword1") super end + end + class Keyword2 < Base + def keyword(foo: "keyword2") + foo = "changed1" + x = super + foo = "changed2" + y = super + [x, y] + end + end def test_single1 assert_equal(1, Single1.new.single(1)) @@ -88,11 +102,11 @@ class TestSuper < Test::Unit::TestCase def test_optional2 assert_raise(ArgumentError) do # call Base#optional with 2 arguments; the 2nd arg is supplied - assert_equal(9, Optional2.new.optional(9)) + Optional2.new.optional(9) end assert_raise(ArgumentError) do # call Base#optional with 2 arguments - assert_equal(9, Optional2.new.optional(9, 2)) + Optional2.new.optional(9, 2) end end def test_optional3 @@ -111,6 +125,14 @@ class TestSuper < Test::Unit::TestCase assert_equal([9, 8], Optional5.new.array(9, 8)) assert_equal([9, 8, 7], Optional5.new.array(9, 8, 7)) end + def test_keyword1 + assert_equal({foo: "keyword1"}, Keyword1.new.keyword) + bug8008 = '[ruby-core:53114] [Bug #8008]' + assert_equal({foo: bug8008}, Keyword1.new.keyword(foo: bug8008)) + end + def test_keyword2 + assert_equal([{foo: "changed1"}, {foo: "changed2"}], Keyword2.new.keyword) + end class A def tt(aa) @@ -130,13 +152,412 @@ class TestSuper < Test::Unit::TestCase a = A.new a.uu(12) assert_equal("A#tt", a.tt(12), "[ruby-core:3856]") - e = assert_raise(RuntimeError, "[ruby-core:24244]") { + assert_raise_with_message(RuntimeError, /implicit argument passing of super from method defined by define_method/, "[ruby-core:24244]") { lambda { - Class.new do - define_method(:a) {super}.call - end + Class.new { + define_method(:a) {super} + }.new.a }.call } - assert_match(/implicit argument passing of super from method defined by define_method/, e.message) + end + + class SubSeq + def initialize + @first=11 + @first or fail + end + + def subseq + @first or fail + end + end + + class Indexed + def subseq + SubSeq.new + end + end + + Overlaid = proc do + class << self + def subseq + super.instance_eval(& Overlaid) + end + end + end + + def test_overlaid + assert_nothing_raised('[ruby-dev:40959]') do + overlaid = proc do |obj| + def obj.reverse + super + end + end + overlaid.call(str = "123") + overlaid.call([1,2,3]) + str.reverse + end + + assert_nothing_raised('[ruby-core:27230]') do + mid=Indexed.new + mid.instance_eval(&Overlaid) + mid.subseq + mid.subseq + end + end + + module DoubleInclude + class Base + def foo + [:Base] + end + end + + module Override + def foo + super << :Override + end + end + + class A < Base + end + + class B < A + end + + B.send(:include, Override) + A.send(:include, Override) + end + + def test_double_include + assert_equal([:Base, :Override, :Override], DoubleInclude::B.new.foo, "[Bug #3351]") + end + + module DoubleInclude2 + class Base + def foo + [:Base] + end + end + + module Override + def foo + super << :Override + end + end + + class A < Base + def foo + super << :A + end + end + + class B < A + def foo + super << :B + end + end + + B.send(:include, Override) + A.send(:include, Override) + end + + def test_double_include2 + assert_equal([:Base, :Override, :A, :Override, :B], + DoubleInclude2::B.new.foo) + end + + def test_super_in_instance_eval + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + def foo + x = Object.new + x.instance_eval do + super() + end + end + } + obj = sub_class.new + assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do + obj.foo + end + end + + def test_super_in_instance_eval_with_define_method + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + define_method(:foo) do + x = Object.new + x.instance_eval do + super() + end + end + } + obj = sub_class.new + assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do + obj.foo + end + end + + def test_super_in_orphan_block + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + def foo + lambda { super() } + end + } + obj = sub_class.new + assert_equal([:super, obj], obj.foo.call) + end + + def test_super_in_orphan_block_with_instance_eval + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + def foo + x = Object.new + x.instance_eval do + lambda { super() } + end + end + } + obj = sub_class.new + assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do + obj.foo.call + end + end + + def test_yielding_super + a = Class.new { def yielder; yield; end } + x = Class.new { define_singleton_method(:hello) { 'hi' } } + y = Class.new(x) { + define_singleton_method(:hello) { + m = a.new + m.yielder { super() } + } + } + assert_equal 'hi', y.hello + end + + def test_super_in_thread + hoge = Class.new { + def bar; 'hoge'; end + } + foo = Class.new(hoge) { + def bar; Thread.new { super }.join.value; end + } + + assert_equal 'hoge', foo.new.bar + end + + def assert_super_in_block(type) + bug7064 = '[ruby-core:47680]' + assert_normal_exit "#{type} {super}", bug7064 + end + + def test_super_in_at_exit + assert_super_in_block("at_exit") + end + def test_super_in_END + assert_super_in_block("END") + end + + def test_super_in_BEGIN + assert_super_in_block("BEGIN") + end + + class X + def foo(*args) + args + end + end + + class Y < X + define_method(:foo) do |*args| + super(*args) + end + end + + def test_super_splat + # [ruby-list:49575] + y = Y.new + assert_equal([1, 2], y.foo(1, 2)) + assert_equal([1, false], y.foo(1, false)) + assert_equal([1, 2, 3, 4, 5], y.foo(1, 2, 3, 4, 5)) + assert_equal([false, true], y.foo(false, true)) + assert_equal([false, false], y.foo(false, false)) + 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 + def foo + super + end + end + b = Class.new do + include a + end + assert_raise(NoMethodError, bug9315) do + b.new.method(:foo).call + end + end + + def test_module_super_in_method_module + bug9315 = '[ruby-core:59589] [Bug #9315]' + a = Module.new do + def foo + super + end + end + c = Class.new do + def foo + :ok + end + end + o = c.new.extend(a) + assert_nothing_raised(NoMethodError, bug9315) do + assert_equal(:ok, o.method(:foo).call, bug9315) + end + end + + def test_missing_super_in_module_unbound_method + bug9377 = '[ruby-core:59619] [Bug #9377]' + + a = Module.new do + def foo; super end + end + + m = a.instance_method(:foo).bind(Object.new) + assert_raise(NoMethodError, bug9377) do + m.call + end + end + + def test_super_in_module_unbound_method + bug9721 = '[ruby-core:61936] [Bug #9721]' + + a = Module.new do + def foo(result) + result << "A" + end + end + + b = Module.new do + def foo(result) + result << "B" + super + end + end + + um = b.instance_method(:foo) + + m = um.bind(Object.new.extend(a)) + result = [] + assert_nothing_raised(NoMethodError, bug9721) do + m.call(result) + end + assert_equal(%w[B A], result, bug9721) + + bug9740 = '[ruby-core:62017] [Bug #9740]' + + b.module_eval do + define_method(:foo) do |res| + um.bind(self).call(res) + end + end + + result.clear + o = Object.new.extend(a).extend(b) + assert_nothing_raised(NoMethodError, SystemStackError, bug9740) do + o.foo(result) + end + assert_equal(%w[B A], result, bug9721) + end + + def test_from_eval + bug10263 = '[ruby-core:65122] [Bug #10263a]' + a = Class.new do + def foo + "A" + end + end + b = Class.new(a) do + def foo + binding.eval("super") + end + 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 9061f191bf..13d7cc9d57 100644 --- a/test/ruby/test_symbol.rb +++ b/test/ruby/test_symbol.rb @@ -1,23 +1,41 @@ +# frozen_string_literal: false require 'test/unit' class TestSymbol < Test::Unit::TestCase # [ruby-core:3573] - def assert_eval_inspected(sym) + def assert_eval_inspected(sym, valid = true) n = sym.inspect + if valid + bug5136 = '[ruby-dev:44314]' + assert_not_match(/\A:"/, n, bug5136) + end 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(:"!") - assert_eval_inspected(:"=") - assert_eval_inspected(:"0") + assert_eval_inspected(:"=", false) + assert_eval_inspected(:"0", false) assert_eval_inspected(:"$1") - assert_eval_inspected(:"@1") - assert_eval_inspected(:"@@1") - assert_eval_inspected(:"@") - assert_eval_inspected(:"@@") + assert_eval_inspected(:"@1", false) + assert_eval_inspected(:"@@1", false) + assert_eval_inspected(:"@", false) + assert_eval_inspected(:"@@", false) end def assert_inspect_evaled(n) @@ -29,7 +47,7 @@ class TestSymbol < Test::Unit::TestCase assert_inspect_evaled(':foo') assert_inspect_evaled(':foo!') assert_inspect_evaled(':bar?') - assert_inspect_evaled(':<<') + assert_inspect_evaled(":<<") assert_inspect_evaled(':>>') assert_inspect_evaled(':<=') assert_inspect_evaled(':>=') @@ -60,11 +78,10 @@ class TestSymbol < Test::Unit::TestCase def test_inspect_dollar # 4) :$- always treats next character literally: - sym = "$-".intern - assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(':$-'))} - assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(":$-\n"))} - assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(":$- "))} - assert_nothing_raised(SyntaxError) {assert_equal(sym, eval(":$-#"))} + assert_raise(SyntaxError) {eval ':$-'} + assert_raise(SyntaxError) {eval ":$-\n"} + assert_raise(SyntaxError) {eval ":$- "} + assert_raise(SyntaxError) {eval ":$-#"} assert_raise(SyntaxError) {eval ':$-('} end @@ -75,6 +92,19 @@ class TestSymbol < Test::Unit::TestCase assert_inspect_evaled(':$1') end + def test_inspect + valid = %W{$a @a @@a < << <= <=> > >> >= =~ == === * ** + +@ - -@ + | ^ & / % ~ \` [] []= ! != !~ a a? a! a= A A? A! A=} + valid.each do |sym| + assert_equal(':' + sym, sym.intern.inspect) + end + + invalid = %w{$a? $a! $a= @a? @a! @a= @@a? @@a! @@a= =} + invalid.each do |sym| + assert_equal(':"' + sym + '"', sym.intern.inspect) + end + end + def test_to_proc assert_equal %w(1 2 3), (1..3).map(&:to_s) [ @@ -90,6 +120,129 @@ 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 + + class TestToPRocArgWithRefinements; end + def _test_to_proc_arg_with_refinements_call(&block) + block.call TestToPRocArgWithRefinements.new + end + using Module.new { + refine TestToPRocArgWithRefinements do + def hoge + :hoge + end + end + } + def test_to_proc_arg_with_refinements + assert_equal(:hoge, _test_to_proc_arg_with_refinements_call(&:hoge)) + end + + def self._test_to_proc_arg_with_refinements_call(&block) + block.call TestToPRocArgWithRefinements.new + end + _test_to_proc_arg_with_refinements_call(&:hoge) + using Module.new { + refine TestToPRocArgWithRefinements do + def hoge + :hogehoge + end + end + } + def test_to_proc_arg_with_refinements_override + assert_equal(:hogehoge, _test_to_proc_arg_with_refinements_call(&:hoge)) + end + + def test_to_proc_arg_with_refinements_undefined + assert_raise(NoMethodError) do + _test_to_proc_arg_with_refinements_call(&:foo) + end + end + + private def return_from_proc + Proc.new { return 1 }.tap(&:call) + end + + 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 @@ -98,6 +251,62 @@ class TestSymbol < Test::Unit::TestCase assert_raise(ArgumentError) { :foo.to_proc.call } end + def m_block_given? + block_given? + end + + def m2_block_given?(m = nil) + if m + [block_given?, m.call(self)] + else + block_given? + end + end + + def test_block_given_to_proc + bug8531 = '[Bug #8531]' + m = :m_block_given?.to_proc + assert(!m.call(self), "#{bug8531} without block") + assert(m.call(self) {}, "#{bug8531} with block") + assert(!m.call(self), "#{bug8531} without block second") + end + + def test_block_persist_between_calls + bug8531 = '[Bug #8531]' + m2 = :m2_block_given?.to_proc + assert_equal([true, false], m2.call(self, m2) {}, "#{bug8531} nested with block") + 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 @@ -113,7 +322,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 @@ -133,7 +354,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 @@ -144,4 +431,136 @@ class TestSymbol < Test::Unit::TestCase assert_equal(':"\\u3042\\u3044\\u3046"', "\u3042\u3044\u3046".encode(e).to_sym.inspect) end end + + def test_symbol_encoding + 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_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?) + assert_equal(true, :+.frozen?) + 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 e25ad75f7a..ab462bddf4 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -1,22 +1,1361 @@ +# frozen_string_literal: false require 'test/unit' class TestSyntax < Test::Unit::TestCase - def valid_syntax?(code, fname) - 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" + 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 - #{srcdir}], + __FILE__, __LINE__, <<-'eom', timeout: Float::INFINITY) + dir = ARGV.shift + for script in Dir["#{dir}/**/*.rb"].sort + assert_valid_syntax(IO::read(script), script) + end + eom + end + + def test_syntax_lib; assert_syntax_files("lib"); end + def test_syntax_sample; assert_syntax_files("sample"); end + def test_syntax_ext; assert_syntax_files("ext"); end + def test_syntax_test; assert_syntax_files("test"); end + + def test_defined_empty_argument + bug8220 = '[ruby-core:53999] [Bug #8220]' + assert_ruby_status(%w[--disable-gem], 'puts defined? ()', bug8220) + end + + def test_must_ascii_compatible + require 'tempfile' + f = Tempfile.new("must_ac_") + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") + assert_nothing_raised(ArgumentError, enc.name) {load(f.path)} + end + Encoding.list.each do |enc| + next if enc.ascii_compatible? + make_tmpsrc(f, "# -*- coding: #{enc.name} -*-") + assert_raise(ArgumentError, enc.name) {load(f.path)} + end + ensure + f&.close! + end + + def test_script_lines + require 'tempfile' + f = Tempfile.new("bug4361_") + bug4361 = '[ruby-dev:43168]' + with_script_lines do |debug_lines| + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + make_tmpsrc(f, "# -*- coding: #{enc.name} -*-\n#----------------") + load(f.path) + assert_equal([f.path], debug_lines.keys) + assert_equal([enc, enc], debug_lines[f.path].map(&:encoding), bug4361) + end + end + ensure + f&.close! + end + + def test_newline_in_block_parameters + bug = '[ruby-dev:45292]' + ["", "a", "a, b"].product(["", ";x", [";", "x"]]) do |params| + params = ["|", *params, "|"].join("\n") + assert_valid_syntax("1.times{#{params}}", __FILE__, "#{bug} #{params.inspect}") + end + end + + tap do |_, + bug6115 = '[ruby-dev:45308]', + blockcall = '["elem"].each_with_object [] do end', + methods = [['map', 'no'], ['inject([])', 'with']], + blocks = [['do end', 'do'], ['{}', 'brace']], + *| + [%w'. dot', %w':: colon'].product(methods, blocks) do |(c, n1), (m, n2), (b, n3)| + m = m.tr_s('()', ' ').strip if n2 == 'do' + name = "test_#{n3}_block_after_blockcall_#{n1}_#{n2}_arg" + code = "#{blockcall}#{c}#{m} #{b}" + define_method(name) {assert_valid_syntax(code, bug6115)} + end + end + + def test_do_block_in_cmdarg + bug9726 = '[ruby-core:61950] [Bug #9726]' + 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) + assert_valid_syntax("def kwrest_test2(**a, &b) end", __FILE__, bug5989) + o = Object.new + def o.kw(**a) a end + assert_equal({}, o.kw, bug5989) + assert_equal({foo: 1}, o.kw(foo: 1), bug5989) + assert_equal({foo: 1, bar: 2}, o.kw(foo: 1, bar: 2), bug5989) + EnvUtil.under_gc_stress do + eval("def o.m(k: 0) k end") + end + assert_equal(42, o.m(k: 42), '[ruby-core:45744]') + bug7922 = '[ruby-core:52744] [Bug #7922]' + def o.bug7922(**) end + 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 = KW2.new + h = {k1: 11, k2: 12} + assert_equal([11, 12], o.kw(**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) + 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) + end + end + + def test_warn_balanced + warning = <<WARN +test:1: warning: `%s' after local variable or literal is interpreted as binary operator +test:1: warning: even though it seems like %s +WARN + [ + [:**, "argument prefix"], + [:*, "argument prefix"], + [:<<, "here document"], + [:&, "argument prefix"], + [:+, "unary operator"], + [:-, "unary operator"], + [:/, "regexp literal"], + [:%, "string literal"], + ].each do |op, syn| + 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 + + def test_cmd_symbol_after_keyword + bug6347 = '[ruby-dev:45563]' + assert_not_label(:foo, 'if true then not_label:foo end', bug6347) + assert_not_label(:foo, 'if false; else not_label:foo end', bug6347) + assert_not_label(:foo, 'begin not_label:foo end', bug6347) + assert_not_label(:foo, 'begin ensure not_label:foo end', bug6347) + end + + def test_cmd_symbol_in_string + bug6347 = '[ruby-dev:45563]' + assert_not_label(:foo, '"#{not_label:foo}"', bug6347) + end + + def test_cmd_symbol_singleton_class + bug6347 = '[ruby-dev:45563]' + @not_label = self + assert_not_label(:foo, 'class << not_label:foo; end', bug6347) + end + + def test_cmd_symbol_superclass + bug6347 = '[ruby-dev:45563]' + @not_label = Object + 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") + (obj = Object.new).instance_eval("def foo(_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + end + + def test_duplicated_rest + assert_syntax_error("def foo(a, *a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, *_) end") + (obj = Object.new).instance_eval("def foo(_, x, *_) x end") + assert_equal(2, obj.foo(1, 2, 3)) + end + + def test_duplicated_opt + assert_syntax_error("def foo(a, a=1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _=1) end") + (obj = Object.new).instance_eval("def foo(_, x, _=42) x end") + assert_equal(2, obj.foo(1, 2)) + end + + def test_duplicated_opt_rest + assert_syntax_error("def foo(a=1, *a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, *_) end") + (obj = Object.new).instance_eval("def foo(_, x=42, *_) x end") + assert_equal(42, obj.foo(1)) + assert_equal(2, obj.foo(1, 2)) + end + + def test_duplicated_rest_opt + assert_syntax_error("def foo(*a, a=1) end", /duplicated argument name/) + end + + def test_duplicated_rest_post + assert_syntax_error("def foo(*a, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, _) end") + (obj = Object.new).instance_eval("def foo(*_, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(*_, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) + end + + def test_duplicated_opt_post + assert_syntax_error("def foo(a=1, a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, _) end") + (obj = Object.new).instance_eval("def foo(_=1, x, _) x end") + assert_equal(2, obj.foo(1, 2, 3)) + assert_equal(2, obj.foo(2, 3)) + (obj = Object.new).instance_eval("def foo(_=1, _, x) x end") + assert_equal(3, obj.foo(1, 2, 3)) + assert_equal(3, obj.foo(2, 3)) + end + + def test_duplicated_kw + assert_syntax_error("def foo(a, a: 1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_, _: 1) end") + (obj = Object.new).instance_eval("def foo(_, x, _: 1) x end") + assert_equal(3, obj.foo(2, 3)) + assert_equal(3, obj.foo(2, 3, _: 42)) + (obj = Object.new).instance_eval("def foo(x, _, _: 1) x end") + assert_equal(2, obj.foo(2, 3)) + assert_equal(2, obj.foo(2, 3, _: 42)) + end + + def test_duplicated_rest_kw + assert_syntax_error("def foo(*a, a: 1) end", /duplicated argument name/) + assert_nothing_raised {def foo(*_, _: 1) end} + (obj = Object.new).instance_eval("def foo(*_, x: 42, _: 1) x end") + assert_equal(42, obj.foo(42)) + assert_equal(42, obj.foo(2, _: 0)) + assert_equal(2, obj.foo(x: 2, _: 0)) + end + + def test_duplicated_opt_kw + assert_syntax_error("def foo(a=1, a: 1) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, _: 1) end") + (obj = Object.new).instance_eval("def foo(_=42, x, _: 1) x end") + assert_equal(0, obj.foo(0)) + assert_equal(0, obj.foo(0, _: 3)) + end + + def test_duplicated_kw_kwrest + assert_syntax_error("def foo(a: 1, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_: 1, **_) end") + (obj = Object.new).instance_eval("def foo(_: 1, x: 42, **_) x end") + assert_equal(42, obj.foo()) + assert_equal(42, obj.foo(a: 0)) + assert_equal(42, obj.foo(_: 0, a: 0)) + assert_equal(1, obj.foo(_: 0, x: 1, a: 0)) + end + + def test_duplicated_rest_kwrest + assert_syntax_error("def foo(*a, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(*_, **_) end") + (obj = Object.new).instance_eval("def foo(*_, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(2, obj.foo(1, 2, a: 0)) + end + + def test_duplicated_opt_kwrest + assert_syntax_error("def foo(a=1, **a) end", /duplicated argument name/) + assert_valid_syntax("def foo(_=1, **_) end") + (obj = Object.new).instance_eval("def foo(_=42, x, **_) x end") + assert_equal(1, obj.foo(1)) + assert_equal(1, obj.foo(1, a: 0)) + assert_equal(1, obj.foo(0, 1, a: 0)) + end + + def test_duplicated_when + w = 'warning: duplicated when clause is ignored' + assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ + eval %q{ + case 1 + when 1, 1 + when 1, 1 + when 1, 1 + end + } } - code.force_encoding("us-ascii") - catch {|tag| eval(code, binding, fname, 0)} - rescue SyntaxError - false + assert_warning(/#{w}/){#/3: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m){ + a = a = 1 + eval %q{ + case 1 + when 1, 1 + when 1, a + when 1, 1 + end + } + } + 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_syntax - assert_nothing_raised(Exception) do - for script in Dir[File.expand_path("../../../{lib,sample,ext,test}/**/*.rb", __FILE__)].sort - assert(valid_syntax?(IO::read(script), script)) + def test_lambda_with_space + feature6390 = '[ruby-dev:45605]' + assert_valid_syntax("-> (x, y) {}", __FILE__, feature6390) + end + + def test_do_block_in_cmdarg_begin + bug6419 = '[ruby-dev:45631]' + assert_valid_syntax("p begin 1.times do 1 end end", __FILE__, bug6419) + end + + def test_do_block_in_call_args + bug9308 = '[ruby-core:59342] [Bug #9308]' + assert_valid_syntax("bar def foo; self.each do end end", bug9308) + end + + def test_do_block_in_lambda + bug11107 = '[ruby-core:69017] [Bug #11107]' + assert_valid_syntax('p ->() do a() do end end', bug11107) + end + + def test_do_block_after_lambda + bug11380 = '[ruby-core:70067] [Bug #11380]' + assert_valid_syntax('p -> { :hello }, a: 1 do end', bug11380) + end + + def test_reserved_method_no_args + bug6403 = '[ruby-dev:45626]' + assert_valid_syntax("def self; :foo; end", __FILE__, bug6403) + end + + def test_unassignable + gvar = global_variables + %w[self nil true false __FILE__ __LINE__ __ENCODING__].each do |kwd| + assert_raise(SyntaxError) {eval("#{kwd} = nil")} + assert_equal(gvar, global_variables) + end + end + + Bug7559 = '[ruby-dev:46737]' + + def test_lineno_command_call_quote + expected = __LINE__ + 1 + actual = caller_lineno "a +b +c +d +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__ + a + b + c + d +eom + assert_equal(expected, actual, bug7559) + end + + def test_dedented_heredoc_invalid_identifer + assert_syntax_error('<<~ "#{}"', /unexpected <</) + end + + def test_dedented_heredoc_concatenation + assert_equal("\n0\n1", eval("<<~0 '1'\n \n0\#{}\n0")) + end + + def test_heredoc_mixed_encoding + 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\ + {} + assert_equal(expected, actual) + end + + def assert_constant_reassignment_nested(preset, op, expected, err = [], bug = '[Bug #5449]') + [ + ["p ", ""], # no-pop + ["", "p Foo::Bar"], # pop + ].each do |p1, p2| + src = <<-EOM.gsub(/^\s*\n/, '') + class Foo + #{"Bar = " + preset if preset} + end + #{p1}Foo::Bar #{op}= 42 + #{p2} + EOM + msg = "\# #{bug}\n#{src}" + assert_valid_syntax(src, caller_locations(1, 1)[0].path, msg) + assert_in_out_err([], src, expected, err, msg) + end + end + + def test_constant_reassignment_nested + already = /already initialized constant Foo::Bar/ + uninitialized = /uninitialized constant Foo::Bar/ + assert_constant_reassignment_nested(nil, "||", %w[42]) + assert_constant_reassignment_nested("false", "||", %w[42], already) + assert_constant_reassignment_nested("true", "||", %w[true]) + assert_constant_reassignment_nested(nil, "&&", [], uninitialized) + assert_constant_reassignment_nested("false", "&&", %w[false]) + assert_constant_reassignment_nested("true", "&&", %w[42], already) + assert_constant_reassignment_nested(nil, "+", [], uninitialized) + assert_constant_reassignment_nested("false", "+", [], /undefined method/) + assert_constant_reassignment_nested("11", "+", %w[53], already) + end + + def assert_constant_reassignment_toplevel(preset, op, expected, err = [], bug = '[Bug #5449]') + [ + ["p ", ""], # no-pop + ["", "p ::Bar"], # pop + ].each do |p1, p2| + src = <<-EOM.gsub(/^\s*\n/, '') + #{"Bar = " + preset if preset} + class Foo + #{p1}::Bar #{op}= 42 + #{p2} + end + EOM + msg = "\# #{bug}\n#{src}" + assert_valid_syntax(src, caller_locations(1, 1)[0].path, msg) + assert_in_out_err([], src, expected, err, msg) + end + end + + def test_constant_reassignment_toplevel + already = /already initialized constant Bar/ + uninitialized = /uninitialized constant Bar/ + assert_constant_reassignment_toplevel(nil, "||", %w[42]) + assert_constant_reassignment_toplevel("false", "||", %w[42], already) + assert_constant_reassignment_toplevel("true", "||", %w[true]) + assert_constant_reassignment_toplevel(nil, "&&", [], uninitialized) + assert_constant_reassignment_toplevel("false", "&&", %w[false]) + assert_constant_reassignment_toplevel("true", "&&", %w[42], already) + assert_constant_reassignment_toplevel(nil, "+", [], uninitialized) + assert_constant_reassignment_toplevel("false", "+", [], /undefined method/) + assert_constant_reassignment_toplevel("11", "+", %w[53], already) + end + + def test_integer_suffix + ["1if true", "begin 1end"].each do |src| + assert_valid_syntax(src) + assert_equal(1, eval(src), src) + end + end + + def test_value_of_def + assert_separately [], <<-EOS + assert_equal(:foo, (def foo; end)) + assert_equal(:foo, (def (Object.new).foo; end)) + EOS + end + + def test_heredoc_cr + 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 + + def test_warning_for_cr + feature8699 = '[ruby-core:56240] [Feature #8699]' + assert_warning(/encountered \\r/, feature8699) do + eval("\r""__id__\r") + 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" + assert_syntax_error(code, /def n "\u{2208}"; end/, bug10114) + end + + def test_null_range_cmdarg + bug10957 = '[ruby-core:68477] [Bug #10957]' + assert_ruby_status(['-c', '-e', 'p ()..0'], "", bug10957) + assert_ruby_status(['-c', '-e', 'p ()...0'], "", bug10957) + assert_syntax_error('0..%w.', /unterminated string/, bug10957) + assert_syntax_error('0...%w.', /unterminated string/, bug10957) + end + + def test_too_big_nth_ref + bug11192 = '[ruby-core:69393] [Bug #11192]' + assert_warn(/too big/, bug11192) do + eval('$99999999999999999') + 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 rescue \(modifier\)/) + 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_rescue_do_end_ensure_in_lambda + result = [] + eval("#{<<-"begin;"}\n#{<<-"end;"}") + begin; + -> do + result << :begin + raise "An exception occurred!" + rescue + result << :rescue + else + result << :else + ensure + result << :ensure + end.call + end; + assert_equal([:begin, :rescue, :ensure], result) + end + + def test_return_in_loop + obj = Object.new + def obj.test + x = nil + return until x unless x + end + assert_nil obj.test + end + + def test_assignment_return_in_loop + obj = Object.new + def obj.test + x = nil + _y = (return until x unless x) + end + assert_nil obj.test, "[Bug #16695]" + end + + def test_method_call_location + line = __LINE__+5 + e = assert_raise(NoMethodError) do + 1.upto(0) do + end + . + foo( + 1, + 2, + ) + end + assert_equal(line, e.backtrace_locations[0].lineno) + + line = __LINE__+5 + e = assert_raise(NoMethodError) do + 1.upto 0 do + end + . + foo( + 1, + 2, + ) + end + assert_equal(line, e.backtrace_locations[0].lineno) + end + + def test_methoddef_in_cond + assert_valid_syntax('while def foo; tap do end; end; break; end') + assert_valid_syntax('while def foo a = tap do end; end; break; end') + end + + def test_classdef_in_cond + assert_valid_syntax('while class Foo; tap do end; end; break; end') + assert_valid_syntax('while class Foo a = tap do end; end; break; end') + end + + def test_command_with_cmd_brace_block + assert_valid_syntax('obj.foo (1) {}') + assert_valid_syntax('obj::foo (1) {}') + end + + def test_value_expr_in_condition + mesg = /void value expression/ + assert_syntax_error("tap {a = (true ? next : break)}", mesg) + assert_valid_syntax("tap {a = (true ? true : break)}") + end + + private + + def not_label(x) @result = x; @not_label ||= nil end + def assert_not_label(expected, src, message = nil) + @result = nil + assert_nothing_raised(SyntaxError, message) {eval(src)} + assert_equal(expected, @result, message) + end + + def make_tmpsrc(f, src) + f.open + f.truncate(0) + f.puts(src) + f.close + end + + def with_script_lines + script_lines = nil + debug_lines = {} + Object.class_eval do + if defined?(SCRIPT_LINES__) + script_lines = SCRIPT_LINES__ + remove_const :SCRIPT_LINES__ + end + const_set(:SCRIPT_LINES__, debug_lines) + end + yield debug_lines + ensure + Object.class_eval do + remove_const :SCRIPT_LINES__ + const_set(:SCRIPT_LINES__, script_lines) if script_lines + end + end + + def caller_lineno(*) + caller_locations(1, 1)[0].lineno + end end diff --git a/test/ruby/test_system.rb b/test/ruby/test_system.rb index 358fa54ef8..477767905b 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 @@ -84,11 +84,121 @@ class TestSystem < Test::Unit::TestCase ENV["PATH"] = path end File.unlink tmpfilename + + testname = '[ruby-core:44505]' + assert_match(/Windows/, `ver`, testname) + assert_equal 0, $?.to_i, testname end } end + def test_system_at + if /mswin|mingw/ =~ RUBY_PLATFORM + bug4393 = '[ruby-core:35218]' + + # @ + builtin command + assert_equal("foo\n", `@echo foo`, bug4393); + assert_equal("foo\n", `@@echo foo`, bug4393); + assert_equal("@@foo\n", `@@echo @@foo`, bug4393); + + # @ + non builtin command + Dir.mktmpdir("ruby_script_tmp") {|tmpdir| + tmpfilename = "#{tmpdir}/ruby_script_tmp.#{$$}" + + tmp = open(tmpfilename, "w") + tmp.print "foo\nbar\nbaz\n@foo"; + tmp.close + + assert_match(/\Abar\nbaz\n?\z/, `@@findstr "ba" #{tmpfilename.gsub("/", "\\")}`, bug4393); + } + end + end + + def test_system_redirect_win + if /mswin|mingw/ !~ RUBY_PLATFORM + return + end + + Dir.mktmpdir("ruby_script_tmp") do |tmpdir| + cmd = nil + message = proc do + [ + '[ruby-talk:258939]', + "out.txt:", + *File.readlines("out.txt").map{|s|" "+s.inspect}, + "err.txt:", + *File.readlines("err.txt").map{|s|" "+s.inspect}, + "system(#{cmd.inspect})" + ].join("\n") + end + class << message + alias to_s call + end + Dir.chdir(tmpdir) do + open("input.txt", "w") {|f| f.puts "BFI3CHL671"} + cmd = "%WINDIR%/system32/find.exe \"BFI3CHL671\" input.txt > out.txt 2>err.txt" + assert_equal(true, system(cmd), message) + cmd = "\"%WINDIR%/system32/find.exe\" \"BFI3CHL671\" input.txt > out.txt 2>err.txt" + assert_equal(true, system(cmd), message) + cmd = "\"%WINDIR%/system32/find.exe BFI3CHL671\" input.txt > out.txt 2>err.txt" + assert_equal(false, system(cmd), message) + end + end + end + def test_empty_evstr assert_equal("", eval('"#{}"', nil, __FILE__, __LINE__), "[ruby-dev:25113]") end + + def test_fallback_to_sh + Dir.mktmpdir("ruby_script_tmp") {|tmpdir| + tmpfilename = "#{tmpdir}/ruby_script_tmp.#{$$}" + open(tmpfilename, "w") {|f| + f.puts ": ;" + f.chmod(0755) + } + assert_equal(true, system(tmpfilename), '[ruby-core:32745]') + } + end if File.executable?("/bin/sh") + + def test_system_exception + ruby = EnvUtil.rubybin + assert_nothing_raised do + system('feature_14235', exception: false) + end + assert_nothing_raised do + system(ruby, "-e", "abort", exception: false) + end + assert_nothing_raised do + system("'#{ruby}' -e abort", exception: false) + end + assert_raise(Errno::ENOENT) do + system('feature_14235', exception: true) + end + assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do + system(ruby, "-e", "abort", exception: true) + end + assert_raise_with_message(RuntimeError, /\ACommand failed with exit /) do + system("'#{ruby}' -e abort", exception: true) + end + end + + def test_system_exception_nonascii + Dir.mktmpdir("ruby_script_tmp") do |tmpdir| + name = "\u{30c6 30b9 30c8}" + tmpfilename = "#{tmpdir}/#{name}.cmd" + message = /#{name}\.cmd/ + e = assert_raise_with_message(Errno::ENOENT, message) do + system(tmpfilename, exception: true) + end + open(tmpfilename, "w") {|f| + f.print "@" if /mingw|mswin/ =~ RUBY_PLATFORM + f.puts "exit 127" + f.chmod(0755) + } + e = assert_raise_with_message(RuntimeError, message) do + system(tmpfilename, exception: true) + end + end + end end diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 0322448c71..51c0338595 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1,13 +1,14 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false require 'test/unit' -require 'thread' -require_relative 'envutil' +require "rbconfig/sizeof" +require "timeout" class TestThread < Test::Unit::TestCase class Thread < ::Thread Threads = [] def self.new(*) th = super - th.abort_on_exception = true Threads << th th end @@ -27,110 +28,148 @@ 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 + + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + end + + def test_thread_variable_in_enumerator + Thread.new { + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + }.join + end + + def test_thread_variables + assert_equal [], Thread.new { Thread.current.thread_variables }.join.value + + t = Thread.new { + Thread.current.thread_variable_set(:foo, "bar") + Thread.current.thread_variables + } + assert_equal [:foo], t.join.value + end + + def test_thread_variable? + Thread.new { assert_not_send([Thread.current, :thread_variable?, "foo"]) }.value + t = Thread.new { + Thread.current.thread_variable_set("foo", "bar") + }.join + + assert_send([t, :thread_variable?, "foo"]) + assert_send([t, :thread_variable?, :foo]) + assert_not_send([t, :thread_variable?, :bar]) + end + + def test_thread_variable_strings_and_symbols_are_the_same_key + t = Thread.new {}.join + t.thread_variable_set("foo", "bar") + assert_equal "bar", t.thread_variable_get(:foo) + end + + def test_thread_variable_frozen + t = Thread.new { }.join + t.freeze + 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) - end - - def test_condvar - mutex = Mutex.new - condvar = ConditionVariable.new - result = [] - mutex.synchronize do - t = Thread.new do - mutex.synchronize do - result << 1 - condvar.signal - end - end - - result << 0 - condvar.wait(mutex) - result << 2 - t.join - end - assert_equal([0, 1, 2], result) + assert_equal(num_threads*loop, r) end - def test_condvar_wait_not_owner - mutex = Mutex.new - condvar = ConditionVariable.new - - assert_raise(ThreadError) { condvar.wait(mutex) } - end - - def test_condvar_wait_exception_handling - # Calling wait in the only thread running should raise a ThreadError of - # 'stopping only thread' - mutex = Mutex.new - condvar = ConditionVariable.new - - locked = false - thread = Thread.new do - Thread.current.abort_on_exception = false - mutex.synchronize do - begin - condvar.wait(mutex) - rescue Exception - locked = mutex.locked? - raise - end - end - end - - until thread.stop? - sleep(0.1) - end - - thread.raise Interrupt, "interrupt a dead condition variable" - assert_raise(Interrupt) { thread.value } - assert(locked) + def test_mutex_synchronize_yields_no_block_params + bug8097 = '[ruby-core:53424] [Bug #8097]' + 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 { - result = `#{EnvUtil.rubybin} #{lbtest}` - assert(!$?.coredump?, '[ruby-dev:30653]') - assert_equal("exit.", result[/.*\Z/], '[ruby-dev:30653]') + `#{EnvUtil.rubybin} #{lbtest}` + assert_not_predicate($?, :coredump?, '[ruby-dev:30653]') } end def test_priority c1 = c2 = 0 - t1 = Thread.new { loop { c1 += 1 } } - t1.priority = -1 - t2 = Thread.new { loop { c2 += 1 } } + run = true + t1 = Thread.new { c1 += 1 while run } + t1.priority = 3 + t2 = Thread.new { c2 += 1 while run } t2.priority = -3 - assert_equal(-1, t1.priority) + 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 + assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + t1.join + t2.join end def test_new @@ -149,34 +188,77 @@ class TestThread < Test::Unit::TestCase end ensure - t1.kill if t1 - t2.kill if t2 + t1&.kill&.join + t2&.kill&.join + end + + def test_new_symbol_proc + 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.5)) + assert_nil(t.join(0.05)) ensure - t.kill if t + t&.kill&.join end def test_join2 - t1 = Thread.new { sleep(1.5) } + ok = false + t1 = Thread.new { ok = true; sleep } + Thread.pass until ok + Thread.pass until t1.stop? t2 = Thread.new do - t1.join(1) + Thread.pass while ok + t1.join(0.01) end t3 = Thread.new do - sleep 0.5 + ok = false t1.join end assert_nil(t2.value) + t1.wakeup assert_equal(t1, t3.value) ensure - t1.kill if t1 - t2.kill if t2 - t3.kill if t3 + t1&.kill + t2&.kill + t3&.kill + end + + { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'], + 'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'], + 'INFINITY' => Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_#{name}") do + t = Thread.new {} + assert_same t, t.join(limit), "limit=#{limit.inspect}" + end + end + + { 'minus_1' => -1, + 'minus_0_1' => -0.1, + 'FIXNUM_MIN' => RbConfig::LIMITS['FIXNUM_MIN'], + 'INT64_MIN' => RbConfig::LIMITS['INT64_MIN'], + 'minus_INFINITY' => -Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_negative_#{name}") do + t = Thread.new { sleep } + begin + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(30) do + assert_nil t.join(limit), "limit=#{limit.inspect}" + end + end + ensure + t.kill + end + end end def test_kill_main_thread @@ -187,6 +269,24 @@ class TestThread < Test::Unit::TestCase INPUT end + def test_kill_wrong_argument + bug4367 = '[ruby-core:35086]' + assert_raise(TypeError, bug4367) { + Thread.kill(nil) + } + o = Object.new + assert_raise(TypeError, bug4367) { + Thread.kill(o) + } + end + + def test_kill_thread_subclass + c = Class.new(Thread) + t = c.new { sleep 10 } + assert_nothing_raised { Thread.kill(t) } + assert_equal(nil, t.value) + end + def test_exit s = 0 Thread.new do @@ -204,15 +304,15 @@ class TestThread < Test::Unit::TestCase Thread.stop s += 1 end - sleep 0.5 + Thread.pass until t.stop? + sleep 1 if RubyVM::MJIT.enabled? # t.stop? behaves unexpectedly with --jit-wait assert_equal(1, s) t.wakeup - sleep 0.5 + Thread.pass while t.alive? assert_equal(2, s) assert_raise(ThreadError) { t.wakeup } - ensure - t.kill if t + t&.kill&.join end def test_stop @@ -230,7 +330,7 @@ class TestThread < Test::Unit::TestCase assert_in_out_err([], <<-INPUT) do |r, e| t1 = Thread.new { sleep } Thread.pass - t2 = Thread.new { loop { } } + t2 = Thread.new { loop { Thread.pass } } Thread.new { }.join p [Thread.current, t1, t2].map{|t| t.object_id }.sort p Thread.list.map{|t| t.object_id }.sort @@ -251,8 +351,11 @@ class TestThread < Test::Unit::TestCase assert_in_out_err([], <<-INPUT, %w(false 1), []) p Thread.abort_on_exception begin - Thread.new { raise } - sleep 0.5 + t = Thread.new { + Thread.current.report_on_exception = false + raise + } + Thread.pass until t.stop? p 1 rescue p 2 @@ -263,7 +366,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 @@ -271,11 +377,11 @@ class TestThread < Test::Unit::TestCase end INPUT - assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), /.+/) + assert_in_out_err(%w(--disable-gems -d), <<-INPUT, %w(false 2), %r".+") p Thread.abort_on_exception begin - Thread.new { raise } - sleep 0.5 + t = Thread.new { raise } + Thread.pass until t.stop? p 1 rescue p 2 @@ -285,9 +391,15 @@ class TestThread < Test::Unit::TestCase assert_in_out_err([], <<-INPUT, %w(false true 2), []) p Thread.abort_on_exception begin - t = Thread.new { sleep 0.5; raise } + ok = false + 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 sleep 1 p 1 rescue @@ -296,44 +408,146 @@ 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_raise(RuntimeError) { th.join } + } + + assert_warn(/report 2/, "exception should be reported when true") { + th = Thread.start { + q1.push(Thread.current.report_on_exception = true) + raise "report 2" + } + assert_equal(true, q1.pop) + Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } + } + + assert_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_raise(RuntimeError) { th.join } + } + + assert_warn(/report 4/, "should defaults to the global flag at the start") { + Thread.report_on_exception = true + th = Thread.start { + q1.push(Thread.current.report_on_exception) + raise "report 4" + } + assert_equal(true, q1.pop) + Thread.pass while th.alive? + assert_raise(RuntimeError) { th.join } + } + + assert_warn(/report 5/, "should 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? + } + assert_raise(RuntimeError) { th.join } + } + 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 } - d = Thread.new { sleep } e = Thread.current - sleep 0.5 + Thread.pass while a.alive? or !b.stop? or c.alive? assert_equal(nil, a.status) - assert(a.stop?) + assert_predicate(a, :stop?) assert_equal("sleep", b.status) - assert(b.stop?) + assert_predicate(b, :stop?) assert_equal(false, c.status) assert_match(/^#<TestThread::Thread:.* dead>$/, c.inspect) - assert(c.stop?) + assert_predicate(c, :stop?) - d.kill - assert_equal(["aborting", false], [d.status, d.stop?]) - - assert_equal(["run", false], [e.status, e.stop?]) + es1 = e.status + es2 = e.stop? + assert_equal(["run", false], [es1, es2]) + assert_raise(RuntimeError) { a.join } + ensure + b&.kill&.join + c&.join + end + def test_switch_while_busy_loop + bug1402 = "[ruby-dev:38319] [Bug #1402]" + flag = true + th = Thread.current + waiter = Thread.start { + sleep 0.1 + flag = false + sleep 1 + th.raise(bug1402) + } + assert_nothing_raised(RuntimeError, bug1402) do + nil while flag + end + assert(!flag, bug1402) ensure - a.kill if a - b.kill if b - c.kill if c - d.kill if d + waiter&.kill&.join end def test_safe_level - t = Thread.new { $SAFE = 3; sleep } - sleep 0.5 - assert_equal(0, Thread.current.safe_level) - assert_equal(3, t.safe_level) - + ok = false + t = Thread.new do + EnvUtil.suppress_warning do + $SAFE = 1 + end + ok = true + sleep + end + Thread.pass until ok + assert_equal($SAFE, Thread.current.safe_level) + assert_equal($SAFE, t.safe_level) ensure - t.kill if t + $SAFE = 0 + t&.kill&.join end def test_thread_local @@ -350,43 +564,77 @@ 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 + t&.kill&.join end - def test_thread_local_security + def test_thread_local_fetch t = Thread.new { sleep } - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; t[:foo] }.join - end + assert_equal(false, t.key?(:foo)) - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; t[:foo] = :baz }.join - end + 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&.join + end - assert_raise(RuntimeError) do - Thread.new do - Thread.current[:foo] = :bar - Thread.current.freeze + def test_thread_local_security + 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 - assert_nil(IO.select(nil, nil, nil, 1)) + assert_nil(IO.select(nil, nil, nil, 0.001)) t = Thread.new do IO.select(nil, nil, nil, nil) end - sleep 0.5 - t.kill + Thread.pass until t.stop? + assert_predicate(t, :alive?) + ensure + t&.kill end def test_mutex_deadlock - m = Mutex.new + m = Thread::Mutex.new m.synchronize do assert_raise(ThreadError) do m.synchronize do @@ -397,30 +645,30 @@ 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 :foo end - sleep 0.5 + Thread.pass until t.stop? t.kill assert_nil(t.value) 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 @@ -428,7 +676,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 @@ -441,7 +689,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]') @@ -462,65 +710,239 @@ class TestThread < Test::Unit::TestCase raise "recursive_outer should short circuit intermediate calls" end assert_nothing_raised {arr.hash} - assert(obj[:visited]) + assert(obj[:visited], "obj.hash was not called") end -end -class TestThreadGroup < Test::Unit::TestCase - def test_thread_init - thgrp = ThreadGroup.new - Thread.new{ - thgrp.add(Thread.current) - assert_equal(thgrp, Thread.new{sleep 1}.group) - }.join + def test_thread_instance_variable + bug4389 = '[ruby-core:35192]' + assert_in_out_err([], <<-INPUT, %w(), [], bug4389) + class << Thread.current + @data = :data + end + INPUT end - def test_frozen_thgroup - thgrp = ThreadGroup.new + def test_no_valid_cfp + 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, bug5083) + assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083) + end - t = Thread.new{1} - Thread.new{ - thgrp.add(Thread.current) - thgrp.freeze - assert_raise(ThreadError) do - Thread.new{1}.join - end - assert_raise(ThreadError) do - thgrp.add(t) - end - assert_raise(ThreadError) do - ThreadGroup.new.add Thread.current + def make_handle_interrupt_test_thread1 flag + r = [] + ready_q = Queue.new + done_q = Queue.new + th = Thread.new{ + begin + Thread.handle_interrupt(RuntimeError => flag){ + begin + ready_q << true + done_q.pop + rescue + r << :c1 + end + } + rescue + r << :c2 end - }.join - t.join + } + ready_q.pop + th.raise + begin + done_q << true + th.join + rescue + r << :c3 + end + r end - def test_enclosed_thgroup - thgrp = ThreadGroup.new - assert_equal(false, thgrp.enclosed?) + def test_handle_interrupt + [[:never, :c2], + [:immediate, :c1], + [:on_blocking, :c1]].each{|(flag, c)| + assert_equal([flag, c], [flag] + make_handle_interrupt_test_thread1(flag)) + } + # TODO: complex cases are needed. + end - t = Thread.new{1} - Thread.new{ - thgrp.add(Thread.current) - thgrp.enclose - assert_equal(true, thgrp.enclosed?) - assert_nothing_raised do - Thread.new{1}.join - end - assert_raise(ThreadError) do - thgrp.add t + def test_handle_interrupt_invalid_argument + assert_raise(ArgumentError) { + Thread.handle_interrupt(RuntimeError => :immediate) # no block + } + assert_raise(ArgumentError) { + Thread.handle_interrupt(RuntimeError => :xyzzy) {} + } + assert_raise(TypeError) { + Thread.handle_interrupt([]) {} # array + } + end + + def for_test_handle_interrupt_with_return + Thread.handle_interrupt(Object => :never){ + Thread.current.raise RuntimeError.new("have to be rescured") + return + } + rescue + end + + def test_handle_interrupt_with_return + assert_nothing_raised do + for_test_handle_interrupt_with_return + _dummy_for_check_ints=nil + end + end + + def test_handle_interrupt_with_break + assert_nothing_raised do + begin + Thread.handle_interrupt(Object => :never){ + Thread.current.raise RuntimeError.new("have to be rescured") + break + } + rescue end - assert_raise(ThreadError) do - ThreadGroup.new.add Thread.current + _dummy_for_check_ints=nil + end + end + + def test_handle_interrupt_blocking + r=:ng + e=Class.new(Exception) + th_s = Thread.current + th = Thread.start{ + assert_raise(RuntimeError) { + Thread.handle_interrupt(Object => :on_blocking){ + begin + Thread.pass until r == :wait + Thread.current.raise RuntimeError, "will raise in sleep" + r = :ok + sleep + ensure + th_s.raise e, "raise from ensure", $@ + end + } + } + } + assert_raise(e) {r = :wait; sleep 0.2} + th.join + assert_equal(:ok,r) + end + + def test_handle_interrupt_and_io + assert_in_out_err([], <<-INPUT, %w(ok), []) + 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 + puts "ng" + } + } + + Thread.pass while t.stop? + t.raise RuntimeError + th_waiting = false + t.join rescue nil + puts "ok" + INPUT + end + + def test_handle_interrupt_and_p + assert_in_out_err([], <<-INPUT, %w(:ok :ok), []) + 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 + # p shouldn't provide interruptible point + p :ok + p :ok + } + } + + Thread.pass until th_waiting + t.raise RuntimeError + th_waiting = false + t.join rescue nil + INPUT + end + + def test_handle_interrupted? + q = Thread::Queue.new + Thread.handle_interrupt(RuntimeError => :never){ + done = false + th = Thread.new{ + q.push :e + begin + begin + Thread.pass until done + rescue => e + q.push :ng1 + end + begin + Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt? + rescue RuntimeError => e + q.push :ok + end + rescue => e + q.push :ng2 + ensure + q.push :ng3 + end + } + q.pop + th.raise + done = true + th.join + assert_equal(:ok, q.pop) + } + end + + def test_thread_timer_and_ensure + assert_normal_exit(<<_eom, 'r36492', timeout: 10) + flag = false + t = Thread.new do + begin + sleep + ensure + 1 until flag end - }.join + end + + Thread.pass until t.status == "sleep" + + t.kill + t.alive? == true + flag = true t.join +_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 @@ -532,4 +954,446 @@ class TestThreadGroup < Test::Unit::TestCase t.join assert_equal(nil, t.backtrace) end + + def test_thread_timer_and_interrupt + bug5757 = '[ruby-dev:44985]' + pid = nil + cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read' + opt = {} + opt[:new_pgroup] = true if /mswin|mingw/ =~ RUBY_PLATFORM + s, t, _err = EnvUtil.invoke_ruby(['-e', cmd], "", true, true, opt) do |in_p, out_p, err_p, cpid| + assert IO.select([out_p], nil, nil, 10), 'subprocess not ready' + out_p.gets + pid = cpid + t0 = Time.now.to_f + Process.kill(:SIGINT, pid) + Timeout.timeout(10) { Process.wait(pid) } + t1 = Time.now.to_f + [$?, t1 - t0, err_p.read] + end + 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_include(0..2, t, bug5757) + end + + def test_thread_join_in_trap + assert_separately [], <<-'EOS' + Signal.trap(:INT, "DEFAULT") + t0 = Thread.current + assert_nothing_raised{ + t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$)} + + Signal.trap :INT do + t.join + end + + t.join + } + EOS + end + + def test_thread_value_in_trap + assert_separately [], <<-'EOS' + Signal.trap(:INT, "DEFAULT") + t0 = Thread.current + t = Thread.new {Thread.pass until t0.stop?; Process.kill(:INT, $$); :normal_end} + + Signal.trap :INT do + t.value + end + assert_equal(:normal_end, t.value) + EOS + end + + def test_thread_join_current + assert_raise(ThreadError) do + Thread.current.join + end + end + + def test_thread_join_main_thread + Thread.new(Thread.current) {|t| + assert_raise(ThreadError) do + t.join + end + }.join + end + + def test_main_thread_status_at_exit + assert_in_out_err([], <<-'INPUT', ["false false aborting"], []) +q = Thread::Queue.new +Thread.new(Thread.current) {|mth| + begin + q.push nil + mth.run + Thread.pass until mth.stop? + p :mth_stopped # don't run if killed by rb_thread_terminate_all + ensure + puts "#{mth.alive?} #{mth.status} #{Thread.current.status}" + end +} +q.pop + INPUT + end + + def test_thread_status_in_trap + # when running trap handler, Thread#status must show "run" + # Even though interrupted from sleeping function + assert_in_out_err([], <<-INPUT, %w(sleep run), []) + Signal.trap(:INT) { + puts Thread.current.status + exit + } + t = Thread.current + + Thread.new(Thread.current) {|mth| + Thread.pass until t.stop? + puts mth.status + Process.kill(:INT, $$) + } + sleep 0.1 + INPUT + end + + # Bug #7450 + def test_thread_status_raise_after_kill + ary = [] + + t = Thread.new { + assert_raise(RuntimeError) do + begin + ary << Thread.current.status + sleep #1 + ensure + begin + ary << Thread.current.status + sleep #2 + ensure + ary << Thread.current.status + end + end + 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 = Thread::Mutex.new + + assert_equal(mutex.owned?, false) + mutex.synchronize { + # Now, I have the mutex + assert_equal(mutex.owned?, true) + } + assert_equal(mutex.owned?, false) + end + + def test_mutex_owned2 + begin + mutex = Thread::Mutex.new + th = Thread.new { + # lock forever + mutex.lock + sleep + } + + # acquired by another thread. + Thread.pass until mutex.locked? + assert_equal(mutex.owned?, false) + ensure + th&.kill + end + end + + def test_mutex_unlock_on_trap + assert_in_out_err([], <<-INPUT, %w(locked unlocked false), []) + m = Thread::Mutex.new + + trapped = false + Signal.trap("INT") { |signo| + m.unlock + trapped = true + puts "unlocked" + } + + m.lock + puts "locked" + Process.kill("INT", $$) + Thread.pass until trapped + puts m.locked? + INPUT + end + + def invoke_rec script, vm_stack_size, machine_stack_size, use_length = true + env = {} + env['RUBY_THREAD_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size + env['RUBY_THREAD_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size + out, = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + use_length ? out.length : out + end + + def test_stack_size + h_default = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', nil, nil, false)) + h_0 = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 0, 0, false)) + h_large = eval(invoke_rec('p RubyVM::DEFAULT_PARAMS', 1024 * 1024 * 10, 1024 * 1024 * 10, false)) + + assert_operator(h_default[:thread_vm_stack_size], :>, h_0[:thread_vm_stack_size], + "0 thread_vm_stack_size") + assert_operator(h_default[:thread_vm_stack_size], :<, h_large[:thread_vm_stack_size], + "large thread_vm_stack_size") + assert_operator(h_default[:thread_machine_stack_size], :>=, h_0[:thread_machine_stack_size], + "0 thread_machine_stack_size") + assert_operator(h_default[:thread_machine_stack_size], :<=, h_large[:thread_machine_stack_size], + "large thread_machine_stack_size") + end + + def test_vm_machine_stack_size + script = 'def rec; print "."; STDOUT.flush; rec; end; rec' + size_default = invoke_rec script, nil, nil + assert_operator(size_default, :>, 0, "default size") + size_0 = invoke_rec script, 0, nil + assert_operator(size_default, :>, size_0, "0 size") + size_large = invoke_rec script, 1024 * 1024 * 10, nil + assert_operator(size_default, :<, size_large, "large size") + end + + def test_machine_stack_size + # check machine stack size + # Note that machine stack size may not change size (depend on OSs) + script = 'def rec; print "."; STDOUT.flush; 1.times{1.times{1.times{rec}}}; end; Thread.new{rec}.join' + vm_stack_size = 1024 * 1024 + size_default = invoke_rec script, vm_stack_size, nil + size_0 = invoke_rec script, vm_stack_size, 0 + assert_operator(size_default, :>=, size_0, "0 size") + size_large = invoke_rec script, vm_stack_size, 1024 * 1024 * 10 + assert_operator(size_default, :<=, size_large, "large size") + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_blocking_mutex_unlocked_on_fork + bug8433 = '[ruby-core:55102] [Bug #8433]' + + mutex = Thread::Mutex.new + flag = false + mutex.lock + + th = Thread.new do + mutex.synchronize do + flag = true + sleep + end + end + + Thread.pass until th.stop? + mutex.unlock + + pid = Process.fork do + exit(mutex.locked?) + end + + th.kill + + pid, status = Process.waitpid2(pid) + assert_equal(false, status.success?, bug8433) + end if Process.respond_to?(:fork) + + def test_fork_in_thread + bug9751 = '[ruby-core:62070] [Bug #9751]' + f = nil + th = Thread.start do + unless f = IO.popen("-") + STDERR.reopen(STDOUT) + exit + end + Process.wait2(f.pid) + end + unless th.join(EnvUtil.apply_timeout_scale(30)) + Process.kill(:QUIT, f.pid) + Process.kill(:KILL, f.pid) unless th.join(EnvUtil.apply_timeout_scale(1)) + end + _, status = th.value + output = f.read + f.close + 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_fork_while_parent_locked + skip 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + nr = 1 + thrs = [] + m.synchronize do + thrs = nr.times.map { Thread.new { m.synchronize {} } } + thrs.each { Thread.pass } + pid = fork do + m.locked? or exit!(2) + thrs = nr.times.map { Thread.new { m.synchronize {} } } + m.unlock + thrs.each { |t| t.join(1) == t or exit!(1) } + exit!(0) + end + _, st = Process.waitpid2(pid) + assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]' + end + thrs.each { |t| assert_same t, t.join(1) } + end + + def test_fork_while_mutex_locked_by_forker + skip 'needs fork' unless Process.respond_to?(:fork) + m = Mutex.new + m.synchronize do + pid = fork do + exit!(2) unless m.locked? + m.unlock rescue exit!(3) + m.synchronize {} rescue exit!(4) + exit!(0) + end + _, st = Timeout.timeout(30) { Process.waitpid2(pid) } + assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]' + end + end + + def test_subclass_no_initialize + t = Module.new do + break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") + 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 + opts = { timeout: 5, timeout_error: nil } + + # prevent SIGABRT from slow shutdown with MJIT + opts[:reprieve] = 3 if RubyVM::MJIT.enabled? + + assert_normal_exit(<<-_end, '[Bug #8996]', opts) + Thread.report_on_exception = false + trap(:TERM){exit} + while true + t = Thread.new{sleep 0} + t.raise Interrupt + Thread.pass # allow t to finish + end + _end + end + + def test_signal_at_join + if /mswin|mingw/ =~ RUBY_PLATFORM + skip "can't trap a signal from another process on Windows" + # opt = {new_pgroup: true} + end + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120) + {# + n = 1000 + sig = :INT + trap(sig) {} + IO.popen([EnvUtil.rubybin, "-e", "#{<<~"{#1"}\n#{<<~'};#1'}"], "r+") do |f| + tpid = #{$$} + sig = :#{sig} + {#1 + STDOUT.sync = true + while gets + puts + Process.kill(sig, tpid) + end + };#1 + assert_nothing_raised do + n.times do + w = Thread.start do + sleep 30 + end + begin + f.puts + f.gets + ensure + w.kill + w.join + end + end + end + n.times do + w = Thread.start { sleep 30 } + begin + f.puts + f.gets + ensure + w.kill + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + w.join(30) + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + diff = t1 - t0 + assert_operator diff, :<=, 2 + end + end + end + }; + end end diff --git a/test/ruby/test_thread_cv.rb b/test/ruby/test_thread_cv.rb new file mode 100644 index 0000000000..38bcc3b8fa --- /dev/null +++ b/test/ruby/test_thread_cv.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestThreadConditionVariable < Test::Unit::TestCase + ConditionVariable = Thread::ConditionVariable + Mutex = Thread::Mutex + + def test_initialized + assert_raise(TypeError) { + ConditionVariable.allocate.wait(nil) + } + end + + def test_condvar_signal_and_wait + mutex = Mutex.new + condvar = ConditionVariable.new + result = [] + mutex.synchronize do + t = Thread.new do + mutex.synchronize do + result << 1 + condvar.signal + end + end + + result << 0 + condvar.wait(mutex) + result << 2 + t.join + end + assert_equal([0, 1, 2], result) + end + + def test_condvar_wait_exception_handling + # Calling wait in the only thread running should raise a ThreadError of + # 'stopping only thread' + mutex = Mutex.new + condvar = ConditionVariable.new + + locked = false + thread = Thread.new do + Thread.current.abort_on_exception = false + mutex.synchronize do + assert_raise(Interrupt) { + condvar.wait(mutex) + } + locked = mutex.locked? + end + end + + until thread.stop? + sleep(0.1) + end + + thread.raise Interrupt, "interrupt a dead condition variable" + thread.join + assert(locked) + end + + def test_condvar_wait_and_broadcast + nr_threads = 3 + threads = Array.new + mutex = Mutex.new + condvar = ConditionVariable.new + result = [] + + nr_threads.times do |i| + threads[i] = Thread.new do + mutex.synchronize do + result << "C1" + condvar.wait mutex + result << "C2" + end + end + end + sleep 0.1 + mutex.synchronize do + result << "P1" + condvar.broadcast + result << "P2" + end + Timeout.timeout(5) do + nr_threads.times do |i| + threads[i].join + end + end + + assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result + end + + def test_condvar_wait_deadlock + assert_in_out_err([], <<-INPUT, /\Afatal\nNo live threads left\. Deadlock/, []) + mutex = Mutex.new + cv = ConditionVariable.new + + klass = nil + mesg = nil + begin + mutex.lock + cv.wait mutex + mutex.unlock + rescue Exception => e + klass = e.class + mesg = e.message + end + puts klass + print mesg +INPUT + end + + def test_condvar_wait_deadlock_2 + nr_threads = 3 + threads = Array.new + mutex = Mutex.new + condvar = ConditionVariable.new + + nr_threads.times do |i| + if (i != 0) + mutex.unlock + end + threads[i] = Thread.new do + mutex.synchronize do + condvar.wait mutex + end + end + mutex.lock + end + + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) { condvar.wait mutex } + end + mutex.unlock + threads.each(&:kill) + threads.each(&:join) + end + + def test_condvar_timed_wait + mutex = Mutex.new + condvar = ConditionVariable.new + timeout = 0.3 + locked = false + + t0 = Time.now + mutex.synchronize do + begin + condvar.wait(mutex, timeout) + ensure + locked = mutex.locked? + end + end + t1 = Time.now + t = t1-t0 + + assert_operator(timeout*0.9, :<, t) + assert(locked) + end + + def test_condvar_nolock + mutex = Mutex.new + condvar = ConditionVariable.new + + assert_raise(ThreadError) {condvar.wait(mutex)} + end + + def test_condvar_nolock_2 + mutex = Mutex.new + condvar = ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex)} + end.join + end + + def test_condvar_nolock_3 + mutex = Mutex.new + condvar = ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex, 0.1)} + end.join + end + + def test_condvar_empty_signal + mutex = Mutex.new + condvar = ConditionVariable.new + + assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} } + end + + def test_condvar_empty_broadcast + mutex = Mutex.new + condvar = ConditionVariable.new + + assert_nothing_raised(Exception) { mutex.synchronize {condvar.broadcast} } + end + + def test_dup + bug9440 = '[ruby-core:59961] [Bug #9440]' + condvar = ConditionVariable.new + assert_raise(NoMethodError, bug9440) do + condvar.dup + end + end + + (DumpableCV = ConditionVariable.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + condvar = ConditionVariable.new + assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do + Marshal.dump(condvar) + end + + condvar = DumpableCV.new + assert_raise(TypeError, bug9674) do + Marshal.dump(condvar) + end + end + + def test_condvar_fork + mutex = Mutex.new + condvar = ConditionVariable.new + thrs = (1..10).map do + Thread.new { mutex.synchronize { condvar.wait(mutex) } } + end + thrs.each { 3.times { Thread.pass } } + pid = fork do + th = Thread.new do + mutex.synchronize { condvar.wait(mutex) } + :ok + end + until th.join(0.01) + mutex.synchronize { condvar.broadcast } + end + exit!(th.value == :ok ? 0 : 1) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + until thrs.empty? + mutex.synchronize { condvar.broadcast } + thrs.delete_if { |t| t.join(0.01) } + end + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb new file mode 100644 index 0000000000..8cebbbecb4 --- /dev/null +++ b/test/ruby/test_thread_queue.rb @@ -0,0 +1,619 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' +require 'timeout' + +class TestThreadQueue < Test::Unit::TestCase + Queue = Thread::Queue + SizedQueue = Thread::SizedQueue + + def test_queue_initialized + assert_raise(TypeError) { + Queue.allocate.push(nil) + } + end + + def test_sized_queue_initialized + assert_raise(TypeError) { + SizedQueue.allocate.push(nil) + } + end + + def test_queue + grind(5, 1000, 15, Queue) + end + + def test_sized_queue + grind(5, 1000, 15, SizedQueue, 1000) + end + + def grind(num_threads, num_objects, num_iterations, klass, *args) + from_workers = klass.new(*args) + to_workers = klass.new(*args) + + workers = (1..num_threads).map { + Thread.new { + while object = to_workers.pop + from_workers.push object + end + } + } + + Thread.new { + num_iterations.times { + num_objects.times { to_workers.push 99 } + num_objects.times { from_workers.pop } + } + }.join + + # close the queue the old way to test for backwards-compatibility + num_threads.times { to_workers.push nil } + workers.each { |t| t.join } + + assert_equal 0, from_workers.size + assert_equal 0, to_workers.size + end + + def test_sized_queue_initialize + q = SizedQueue.new(1) + assert_equal 1, q.max + assert_raise(ArgumentError) { SizedQueue.new(0) } + assert_raise(ArgumentError) { SizedQueue.new(-1) } + end + + def test_sized_queue_assign_max + q = SizedQueue.new(2) + assert_equal(2, q.max) + q.max = 1 + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = 0 } + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = -1 } + assert_equal(1, q.max) + + before = q.max + q.max.times { q << 1 } + t1 = Thread.new { q << 1 } + sleep 0.01 until t1.stop? + q.max = q.max + 1 + assert_equal before + 1, q.max + ensure + t1.join if t1 + end + + def test_queue_pop_interrupt + q = Queue.new + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_queue_pop_non_block + q = Queue.new + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_pop_interrupt + q = SizedQueue.new(1) + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_sized_queue_pop_non_block + q = SizedQueue.new(1) + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_push_interrupt + q = SizedQueue.new(1) + q.push(1) + assert_raise_with_message(ThreadError, /full/) do + q.push(2, true) + end + end + + def test_sized_queue_push_non_block + q = SizedQueue.new(1) + q.push(1) + t1 = Thread.new { q.push(2) } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_thr_kill + bug5343 = '[ruby-core:39634]' + Dir.mktmpdir {|d| + timeout = 60 + total_count = 250 + begin + assert_normal_exit(<<-"_eom", bug5343, {:timeout => timeout, :chdir=>d}) + #{total_count}.times do |i| + open("test_thr_kill_count", "w") {|f| f.puts i } + queue = Queue.new + r, w = IO.pipe + th = Thread.start { + queue.push(nil) + r.read 1 + } + queue.pop + th.kill + th.join + end + _eom + rescue Timeout::Error + count = File.read("#{d}/test_thr_kill_count").to_i + flunk "only #{count}/#{total_count} done in #{timeout} seconds." + end + } + end + + def test_queue_push_return_value + q = Queue.new + retval = q.push(1) + assert_same q, retval + end + + def test_queue_clear_return_value + q = Queue.new + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_clear + # Fill queue, then test that SizedQueue#clear wakes up all waiting threads + sq = SizedQueue.new(2) + 2.times { sq << 1 } + + t1 = Thread.new do + sq << 1 + end + + t2 = Thread.new do + sq << 1 + end + + t3 = Thread.new do + Thread.pass + sq.clear + end + + [t3, t2, t1].each(&:join) + assert_equal sq.length, 2 + end + + def test_sized_queue_push_return_value + q = SizedQueue.new(1) + retval = q.push(1) + assert_same q, retval + end + + def test_sized_queue_clear_return_value + q = SizedQueue.new(1) + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_throttle + q = SizedQueue.new(1) + i = 0 + consumer = Thread.new do + while q.pop + i += 1 + Thread.pass + end + end + nprod = 4 + npush = 100 + + producer = nprod.times.map do + Thread.new do + npush.times { q.push(true) } + end + end + producer.each(&:join) + q.push(nil) + consumer.join + assert_equal(nprod * npush, i) + end + + def test_queue_thread_raise + q = Queue.new + th1 = Thread.new do + begin + q.pop + rescue RuntimeError + sleep + end + end + th2 = Thread.new do + sleep 0.1 + q.pop + end + sleep 0.1 + th1.raise + sleep 0.1 + q << :s + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(1) { th2.join } + end + ensure + [th1, th2].each do |th| + if th and th.alive? + th.wakeup + th.join + end + end + end + + def test_dup + bug9440 = '[ruby-core:59961] [Bug #9440]' + q = Queue.new + assert_raise(NoMethodError, bug9440) do + q.dup + end + end + + (DumpableQueue = Queue.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + q = Queue.new + assert_raise_with_message(TypeError, /#{Queue}/, bug9674) do + Marshal.dump(q) + end + + sq = SizedQueue.new(1) + assert_raise_with_message(TypeError, /#{SizedQueue}/, bug9674) do + Marshal.dump(sq) + end + + q = DumpableQueue.new + assert_raise(TypeError, bug9674) do + Marshal.dump(q) + end + end + + def test_close + [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + q = qcreate.call + assert_equal false, q.closed? + q << :something + assert_equal q, q.close + assert q.closed? + assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing} + assert_equal q.pop, :something + assert_nil q.pop + assert_nil q.pop + # non-blocking + assert_raise_with_message(ThreadError, /queue empty/){q.pop(non_block=true)} + end + end + + # test that waiting producers are woken up on close + def close_wakeup( num_items, num_threads, &qcreate ) + raise "This test won't work with num_items(#{num_items}) >= num_threads(#{num_threads})" if num_items >= num_threads + + # create the Queue + q = yield + threads = num_threads.times.map{Thread.new{q.pop}} + num_items.times{|i| q << i} + + # wait until queue empty + (Thread.pass; sleep 0.01) until q.size == 0 + + # close the queue so remaining threads will wake up + q.close + + # wait for them to go away + Thread.pass until threads.all?{|thr| thr.status == false} + + # check that they've gone away. Convert nil to -1 so we can sort and do the comparison + expected_values = [-1] * (num_threads - num_items) + num_items.times.to_a + assert_equal expected_values, threads.map{|thr| thr.value || -1 }.sort + end + + def test_queue_close_wakeup + close_wakeup(15, 18){Queue.new} + end + + def test_size_queue_close_wakeup + close_wakeup(5, 8){SizedQueue.new 9} + end + + def test_sized_queue_one_closed_interrupt + q = SizedQueue.new 1 + q << :one + t1 = Thread.new { q << :two } + sleep 0.01 until t1.stop? + q.close + + t1.kill.join + assert_equal 1, q.size + assert_equal :one, q.pop + assert q.empty?, "queue not empty" + end + + # make sure that shutdown state is handled properly by empty? for the non-blocking case + def test_empty_non_blocking + return + q = SizedQueue.new 3 + 3.times{|i| q << i} + + # these all block cos the queue is full + prod_threads = 4.times.map{|i| Thread.new{q << 3+i}} + sleep 0.01 until prod_threads.all?{|thr| thr.status == 'sleep'} + q.close + + items = [] + # sometimes empty? is false but pop will raise ThreadError('empty'), + # meaning a value is not immediately available but will be soon. + until q.empty? + items << q.pop(non_block=true) rescue nil + end + items.compact! + + assert_equal 7.times.to_a, items.sort + assert q.empty? + end + + def test_sized_queue_closed_push_non_blocking + q = SizedQueue.new 7 + q.close + assert_raise_with_message(ClosedQueueError, /queue closed/){q.push(non_block=true)} + end + + def test_blocked_pushers + q = SizedQueue.new 3 + prod_threads = 6.times.map do |i| + thr = Thread.new{ + Thread.current.report_on_exception = false + q << i + } + thr[:pc] = i + thr + end + + # wait until some producer threads have finished, and the other 3 are blocked + sleep 0.01 while prod_threads.reject{|t| t.status}.count < 3 + # this would ensure that all producer threads call push before close + # sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + # more than prod_threads + cons_threads = 10.times.map do |i| + thr = Thread.new{q.pop}; thr[:pc] = i; thr + end + + # values that came from the queue + popped_values = cons_threads.map &:value + + # wait untl all threads have finished + sleep 0.01 until prod_threads.find_all{|t| t.status}.count == 0 + + # pick only the producer threads that got in before close + successful_prod_threads = prod_threads.reject{|thr| thr.status == nil} + assert_nothing_raised{ successful_prod_threads.map(&:value) } + + # the producer threads that tried to push after q.close should all fail + unsuccessful_prod_threads = prod_threads - successful_prod_threads + unsuccessful_prod_threads.each do |thr| + assert_raise(ClosedQueueError){ thr.value } + end + + assert_equal cons_threads.size, popped_values.size + assert_equal 0, q.size + + # check that consumer threads with values match producers that called push before close + assert_equal successful_prod_threads.map{|thr| thr[:pc]}, popped_values.compact.sort + assert_nil q.pop + end + + def test_deny_pushers + [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + synq = Queue.new + prod_threads = 20.times.map do |i| + Thread.new { + synq.pop + assert_raise(ClosedQueueError) { + q << i + } + } + end + q.close + synq.close # start producer threads + + prod_threads.each(&:join) + end + end + + # size should account for waiting pushers during shutdown + def sized_queue_size_close + q = SizedQueue.new 4 + 4.times{|i| q << i} + Thread.new{ q << 5 } + Thread.new{ q << 6 } + assert_equal 4, q.size + assert_equal 4, q.items + q.close + assert_equal 6, q.size + assert_equal 4, q.items + end + + def test_blocked_pushers_empty + q = SizedQueue.new 3 + prod_threads = 6.times.map do |i| + Thread.new{ + Thread.current.report_on_exception = false + q << i + } + end + + # this ensures that all producer threads call push before close + sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + ary = [] + until q.empty? + ary << q.pop + end + assert_equal 0, q.size + + assert_equal 3, ary.size + ary.each{|e| assert [0,1,2,3,4,5].include?(e)} + assert_nil q.pop + + prod_threads.each{|t| + begin + t.join + rescue => e + end + } + end + + # test thread wakeup on one-element SizedQueue with close + def test_one_element_sized_queue + q = SizedQueue.new 1 + t = Thread.new{ q.pop } + q.close + assert_nil t.value + end + + def test_close_twice + [->{Queue.new}, ->{SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + q.close + assert_nothing_raised(ClosedQueueError){q.close} + end + end + + def test_queue_close_multi_multi + q = SizedQueue.new rand(800..1200) + + count_items = rand(3000..5000) + count_producers = rand(10..20) + + producers = count_producers.times.map do + Thread.new do + sleep(rand / 100) + count_items.times{|i| q << [i,"#{i} for #{Thread.current.inspect}"]} + end + end + + consumers = rand(7..12).times.map do + Thread.new do + count = 0 + while e = q.pop + i, st = e + count += 1 if i.is_a?(Integer) && st.is_a?(String) + end + count + end + end + + # No dead or finished threads, give up to 10 seconds to start running + t = Time.now + Thread.pass until Time.now - t > 10 || (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/} + + assert (consumers + producers).all?{|thr| thr.status =~ /\A(?:run|sleep)\z/}, 'no threads running' + + # just exercising the concurrency of the support methods. + counter = Thread.new do + until q.closed? && q.empty? + raise if q.size > q.max + # otherwise this exercise causes too much contention on the lock + sleep 0.01 + end + end + + producers.each &:join + q.close + + # results not randomly distributed. Not sure why. + # consumers.map{|thr| thr.value}.each do |x| + # assert_not_equal 0, x + # end + + all_items_count = consumers.map{|thr| thr.value}.inject(:+) + assert_equal count_items * count_producers, all_items_count + + # don't leak this thread + assert_nothing_raised{counter.join} + end + + def test_queue_with_trap + if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/) + skip 'This test fails too often on AppVeyor vs140' + end + assert_in_out_err([], <<-INPUT, %w(INT INT exit), []) + q = Queue.new + trap(:INT){ + q.push 'INT' + } + Thread.new{ + loop{ + Process.kill :INT, $$ + } + } + puts q.pop + puts q.pop + puts 'exit' + INPUT + end + + def test_fork_while_queue_waiting + q = Queue.new + sq = SizedQueue.new(1) + thq = Thread.new { q.pop } + thsq = Thread.new { sq.pop } + Thread.pass until thq.stop? && thsq.stop? + + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(6) unless q.empty? + exit!(7) unless sq.empty? + q.push :child_q + sq.push :child_sq + exit!(3) if q.pop != :child_q + exit!(4) if sq.pop != :child_sq + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + q.push :thq + sq.push :thsq + assert_equal :thq, thq.value + assert_equal :thsq, thsq.value + + sq.push(1) + th = Thread.new { q.pop; sq.pop } + thsq = Thread.new { sq.push(2) } + Thread.pass until th.stop? && thsq.stop? + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(3) unless q.empty? + exit!(4) if sq.empty? + exit!(5) if sq.pop != 1 + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + assert_predicate thsq, :stop? + assert_equal 1, sq.pop + assert_same sq, thsq.value + q.push('restart th') + assert_equal 2, th.value + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_threadgroup.rb b/test/ruby/test_threadgroup.rb new file mode 100644 index 0000000000..ec95bd6419 --- /dev/null +++ b/test/ruby/test_threadgroup.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestThreadGroup < Test::Unit::TestCase + def test_thread_init + thgrp = ThreadGroup.new + th = Thread.new{ + thgrp.add(Thread.current) + Thread.new{sleep 1} + }.value + assert_equal(thgrp, th.group) + ensure + th.join + end + + def test_frozen_thgroup + thgrp = ThreadGroup.new + + t = Thread.new{1} + Thread.new{ + thgrp.add(Thread.current) + thgrp.freeze + assert_raise(ThreadError) do + Thread.new{1}.join + end + assert_raise(ThreadError) do + thgrp.add(t) + end + assert_raise(ThreadError) do + ThreadGroup.new.add Thread.current + end + }.join + t.join + end + + def test_enclosed_thgroup + thgrp = ThreadGroup.new + assert_equal(false, thgrp.enclosed?) + + t = Thread.new{1} + Thread.new{ + thgrp.add(Thread.current) + thgrp.enclose + assert_equal(true, thgrp.enclosed?) + assert_nothing_raised do + Thread.new{1}.join + end + assert_raise(ThreadError) do + thgrp.add t + end + assert_raise(ThreadError) do + ThreadGroup.new.add Thread.current + end + }.join + t.join + end +end diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 95d36c597f..3b3711b805 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require 'rational' require 'delegate' require 'timeout' require 'delegate' @@ -14,11 +14,39 @@ class TestTime < Test::Unit::TestCase $VERBOSE = @verbose end + def in_timezone(zone) + orig_zone = ENV['TZ'] + + ENV['TZ'] = zone + yield + ensure + ENV['TZ'] = orig_zone + end + + def no_leap_seconds? + # 1972-06-30T23:59:60Z is the first leap second. + Time.utc(1972, 7, 1, 0, 0, 0) - Time.utc(1972, 6, 30, 23, 59, 59) == 1 + end + + def get_t2000 + if no_leap_seconds? + # Sat Jan 01 00:00:00 UTC 2000 + Time.at(946684800).gmtime + else + Time.utc(2000, 1, 1) + end + end + def test_new assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, 3600*11)) assert_equal(Time.utc(2000,2,10), Time.new(2000,2,9, 13,0,0, -3600*11)) assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, "+11:00")) assert_equal(Rational(1,2), Time.new(2000,2,10, 11,0,5.5, "+11:00").subsec) + bug4090 = '[ruby-dev:42631]' + 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() @@ -28,14 +56,14 @@ class TestTime < Test::Unit::TestCase Time.utc(2000, 3, 21, 0, 30)) assert_equal(0, (Time.at(1.1) + 0.9).usec) - assert((Time.utc(2000, 4, 1) + 24).utc?) - assert(!(Time.local(2000, 4, 1) + 24).utc?) + assert_predicate((Time.utc(2000, 4, 1) + 24), :utc?) + assert_not_predicate((Time.local(2000, 4, 1) + 24), :utc?) t = Time.new(2000, 4, 1, 0, 0, 0, "+01:00") + 24 - assert(!t.utc?) + assert_not_predicate(t, :utc?) assert_equal(3600, t.utc_offset) t = Time.new(2000, 4, 1, 0, 0, 0, "+02:00") + 24 - assert(!t.utc?) + assert_not_predicate(t, :utc?) assert_equal(7200, t.utc_offset) end @@ -76,15 +104,13 @@ class TestTime < Test::Unit::TestCase assert_equal(31536000, Time.utc(1971, 1, 1, 0, 0, 0).tv_sec) assert_equal(78796799, Time.utc(1972, 6, 30, 23, 59, 59).tv_sec) - # 1972-06-30T23:59:60Z is the first leap second. - if Time.utc(1972, 7, 1, 0, 0, 0) - Time.utc(1972, 6, 30, 23, 59, 59) == 1 - # no leap second. + if no_leap_seconds? assert_equal(78796800, Time.utc(1972, 7, 1, 0, 0, 0).tv_sec) assert_equal(78796801, Time.utc(1972, 7, 1, 0, 0, 1).tv_sec) assert_equal(946684800, Time.utc(2000, 1, 1, 0, 0, 0).tv_sec) assert_equal(0x7fffffff, Time.utc(2038, 1, 19, 3, 14, 7).tv_sec) + assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) else - # leap seconds supported. assert_equal(2, Time.utc(1972, 7, 1, 0, 0, 0) - Time.utc(1972, 6, 30, 23, 59, 59)) assert_equal(78796800, Time.utc(1972, 6, 30, 23, 59, 60).tv_sec) assert_equal(78796801, Time.utc(1972, 7, 1, 0, 0, 0).tv_sec) @@ -146,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) @@ -172,7 +199,7 @@ class TestTime < Test::Unit::TestCase t = Time.at(2**40 + "1/3".to_r, 9999999999999).utc assert_equal(36812, t.year) - + t = Time.at(-0x3fff_ffff_ffff_ffff) assert_equal(-146138510344, t.year) t = Time.at(-0x4000_0000_0000_0000) @@ -209,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) @@ -273,21 +311,73 @@ class TestTime < Test::Unit::TestCase assert_equal(29700, t2.utc_offset, bug) end - # Sat Jan 01 00:00:00 UTC 2000 - T2000 = Time.at(946684800).gmtime + def test_marshal_zone + t = Time.utc(2013, 2, 24) + assert_equal('UTC', t.zone) + assert_equal('UTC', Marshal.load(Marshal.dump(t)).zone) + + in_timezone('JST-9') do + t = Time.local(2013, 2, 24) + assert_equal('JST', Time.local(2013, 2, 24).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 + + def test_marshal_zone_gc + assert_separately(%w(--disable-gems), <<-'end;', timeout: 30) + ENV["TZ"] = "JST-9" + s = Marshal.dump(Time.now) + t = Marshal.load(s) + n = 0 + done = 100000 + while t.zone.dup == "JST" && n < done + n += 1 + end + assert_equal done, n, "Bug #9652" + assert_equal "JST", t.zone, "Bug #9652" + end; + end + + def test_marshal_to_s + t1 = Time.new(2011,11,8, 0,42,25, 9*3600) + t2 = Time.at(Marshal.load(Marshal.dump(t1))) + assert_equal("2011-11-08 00:42:25 +0900", t2.to_s, + "[ruby-dev:44827] [Bug #5586]") + end + + Bug8795 = '[ruby-core:56648] [Bug #8795]' + + def test_marshal_broken_offset + data = "\x04\bIu:\tTime\r\xEFF\x1C\x80\x00\x00\x00\x00\x06:\voffset" + t1 = t2 = nil + in_timezone('UTC') do + assert_nothing_raised(TypeError, ArgumentError, Bug8795) do + t1 = Marshal.load(data + "T") + t2 = Marshal.load(data + "\"\x0ebadoffset") + end + assert_equal(0, t1.utc_offset) + assert_equal(0, t2.utc_offset) + end + end - def test_security_error - assert_raise(SecurityError) do - Thread.new do - t = Time.gm(2000) - $SAFE = 4 - t.localtime - end.join + def test_marshal_broken_zone + data = "\x04\bIu:\tTime\r\xEFF\x1C\x80\x00\x00\x00\x00\x06:\tzone" + t1 = t2 = nil + in_timezone('UTC') do + assert_nothing_raised(TypeError, ArgumentError, Bug8795) do + t1 = Marshal.load(data + "T") + t2 = Marshal.load(data + "\"\b\0\0\0") + end + assert_equal('UTC', t1.zone) + assert_equal('UTC', t2.zone) end end def test_at3 - assert_equal(T2000, Time.at(T2000)) + t2000 = get_t2000 + assert_equal(t2000, Time.at(t2000)) # assert_raise(RangeError) do # Time.at(2**31-1, 1_000_000) # Time.at(2**63-1, 1_000_000) @@ -299,13 +389,14 @@ class TestTime < Test::Unit::TestCase end def test_utc_or_local - assert_equal(T2000, Time.gm(2000)) - assert_equal(T2000, Time.gm(0, 0, 0, 1, 1, 2000, :foo, :bar, false, :baz)) - assert_equal(T2000, Time.gm(2000, "jan")) - assert_equal(T2000, Time.gm(2000, "1")) - assert_equal(T2000, Time.gm(2000, 1, 1, 0, 0, 0, 0)) - assert_equal(T2000, Time.gm(2000, 1, 1, 0, 0, 0, "0")) - assert_equal(T2000, Time.gm(2000, 1, 1, 0, 0, "0", :foo, :foo)) + t2000 = get_t2000 + assert_equal(t2000, Time.gm(2000)) + assert_equal(t2000, Time.gm(0, 0, 0, 1, 1, 2000, :foo, :bar, false, :baz)) + assert_equal(t2000, Time.gm(2000, "jan")) + assert_equal(t2000, Time.gm(2000, "1")) + assert_equal(t2000, Time.gm(2000, 1, 1, 0, 0, 0, 0)) + assert_equal(t2000, Time.gm(2000, 1, 1, 0, 0, 0, "0")) + assert_equal(t2000, Time.gm(2000, 1, 1, 0, 0, "0", :foo, :foo)) assert_raise(ArgumentError) { Time.gm(2000, 1, 1, 0, 0, -1, :foo, :foo) } assert_raise(ArgumentError) { Time.gm(2000, 1, 1, 0, 0, -1.0, :foo, :foo) } assert_raise(RangeError) do @@ -313,6 +404,7 @@ class TestTime < Test::Unit::TestCase end assert_raise(ArgumentError) { Time.gm(2000, 1, 1, 0, 0, -(2**31), :foo, :foo) } o = Object.new + def o.to_int; 0; end def o.to_r; nil; end assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) } def o.to_r; ""; end @@ -325,66 +417,91 @@ class TestTime < Test::Unit::TestCase assert_raise(ArgumentError) { Time.gm(2000, 13) } t = Time.local(2000) - assert_equal(t.gmt_offset, T2000 - t) + assert_equal(t.gmt_offset, t2000 - t) assert_equal(-4427700000, Time.utc(-4427700000,12,1).year) assert_equal(-2**30+10, Time.utc(-2**30+10,1,1).year) 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 - assert_equal(946684800.0, T2000.to_f) + t2000 = Time.at(946684800).gmtime + 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 - assert_equal(-1, T2000 <=> Time.gm(2001)) - assert_equal(1, T2000 <=> Time.gm(1999)) - assert_nil(T2000 <=> 0) + t2000 = get_t2000 + assert_equal(-1, t2000 <=> Time.gm(2001)) + assert_equal(1, t2000 <=> Time.gm(1999)) + assert_nil(t2000 <=> 0) end def test_eql - assert(T2000.eql?(T2000)) - assert(!T2000.eql?(Time.gm(2001))) + t2000 = get_t2000 + assert_operator(t2000, :eql?, t2000) + assert_not_operator(t2000, :eql?, Time.gm(2001)) end def test_utc_p - assert(Time.gm(2000).gmt?) - assert(!Time.local(2000).gmt?) - assert(!Time.at(0).gmt?) + assert_predicate(Time.gm(2000), :gmt?) + assert_not_predicate(Time.local(2000), :gmt?) + assert_not_predicate(Time.at(0), :gmt?) end def test_hash - assert_kind_of(Integer, T2000.hash) + t2000 = get_t2000 + assert_kind_of(Integer, t2000.hash) + end + + def test_reinitialize + bug8099 = '[ruby-core:53436] [Bug #8099]' + t2000 = get_t2000 + assert_raise(TypeError, bug8099) { + t2000.send(:initialize, 2013, 03, 14) + } + assert_equal(get_t2000, t2000, bug8099) end def test_init_copy - assert_equal(T2000, T2000.dup) + t2000 = get_t2000 + assert_equal(t2000, t2000.dup) assert_raise(TypeError) do - T2000.instance_eval { initialize_copy(nil) } + t2000.instance_eval { initialize_copy(nil) } end end def test_localtime_gmtime assert_nothing_raised do t = Time.gm(2000) - assert(t.gmt?) + assert_predicate(t, :gmt?) t.localtime - assert(!t.gmt?) + assert_not_predicate(t, :gmt?) t.localtime - assert(!t.gmt?) + assert_not_predicate(t, :gmt?) t.gmtime - assert(t.gmt?) + assert_predicate(t, :gmt?) t.gmtime - assert(t.gmt?) + assert_predicate(t, :gmt?) end t1 = Time.gm(2000) @@ -393,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?) @@ -411,22 +529,46 @@ class TestTime < Test::Unit::TestCase end def test_asctime - assert_equal("Sat Jan 1 00:00:00 2000", T2000.asctime) + t2000 = get_t2000 + assert_equal("Sat Jan 1 00:00:00 2000", t2000.asctime) + assert_equal(Encoding::US_ASCII, t2000.asctime.encoding) assert_kind_of(String, Time.at(0).asctime) end def test_to_s - assert_equal("2000-01-01 00:00:00 UTC", T2000.to_s) + t2000 = get_t2000 + assert_equal("2000-01-01 00:00:00 UTC", t2000.to_s) + assert_equal(Encoding::US_ASCII, t2000.to_s.encoding) assert_kind_of(String, Time.at(946684800).getlocal.to_s) 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_zone_encoding Time.now + t = Time.now.utc + assert_equal("UTC", t.zone) + assert_nil(t.getlocal(0).zone) + assert_nil(t.getlocal("+02:00").zone) + end + def test_plus_minus_succ - # assert_raise(RangeError) { T2000 + 10000000000 } - # assert_raise(RangeError) T2000 - 3094168449 } - # assert_raise(RangeError) { T2000 + 1200798848 } - assert_raise(TypeError) { T2000 + Time.now } - assert_equal(T2000 + 1, T2000.succ) + t2000 = get_t2000 + # assert_raise(RangeError) { t2000 + 10000000000 } + # assert_raise(RangeError) t2000 - 3094168449 } + # assert_raise(RangeError) { t2000 + 1200798848 } + assert_raise(TypeError) { t2000 + Time.now } + assert_equal(t2000 + 1, t2000.succ) end def test_plus_type @@ -455,25 +597,27 @@ class TestTime < Test::Unit::TestCase end def test_readers - assert_equal(0, T2000.sec) - assert_equal(0, T2000.min) - assert_equal(0, T2000.hour) - assert_equal(1, T2000.mday) - assert_equal(1, T2000.mon) - assert_equal(2000, T2000.year) - assert_equal(6, T2000.wday) - assert_equal(1, T2000.yday) - assert_equal(false, T2000.isdst) - assert_equal("UTC", T2000.zone) - assert_equal(0, T2000.gmt_offset) - assert(!T2000.sunday?) - assert(!T2000.monday?) - assert(!T2000.tuesday?) - assert(!T2000.wednesday?) - assert(!T2000.thursday?) - assert(!T2000.friday?) - assert(T2000.saturday?) - assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], T2000.to_a) + t2000 = get_t2000 + assert_equal(0, t2000.sec) + assert_equal(0, t2000.min) + assert_equal(0, t2000.hour) + assert_equal(1, t2000.mday) + assert_equal(1, t2000.mon) + assert_equal(2000, t2000.year) + assert_equal(6, t2000.wday) + assert_equal(1, t2000.yday) + assert_equal(false, t2000.isdst) + assert_equal("UTC", t2000.zone) + assert_zone_encoding(t2000) + assert_equal(0, t2000.gmt_offset) + assert_not_predicate(t2000, :sunday?) + assert_not_predicate(t2000, :monday?) + assert_not_predicate(t2000, :tuesday?) + assert_not_predicate(t2000, :wednesday?) + assert_not_predicate(t2000, :thursday?) + assert_not_predicate(t2000, :friday?) + assert_predicate(t2000, :saturday?) + assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], t2000.to_a) t = Time.at(946684800).getlocal assert_equal(t.sec, Time.at(946684800).sec) @@ -486,6 +630,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_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?) @@ -498,51 +643,76 @@ class TestTime < Test::Unit::TestCase end def test_strftime + t2000 = get_t2000 t = Time.at(946684800).getlocal - assert_equal("Sat", T2000.strftime("%a")) - assert_equal("Saturday", T2000.strftime("%A")) - assert_equal("Jan", T2000.strftime("%b")) - assert_equal("January", T2000.strftime("%B")) - assert_kind_of(String, T2000.strftime("%c")) - assert_equal("01", T2000.strftime("%d")) - assert_equal("00", T2000.strftime("%H")) - assert_equal("12", T2000.strftime("%I")) - assert_equal("001", T2000.strftime("%j")) - assert_equal("01", T2000.strftime("%m")) - assert_equal("00", T2000.strftime("%M")) - assert_equal("AM", T2000.strftime("%p")) - assert_equal("00", T2000.strftime("%S")) - assert_equal("00", T2000.strftime("%U")) - assert_equal("00", T2000.strftime("%W")) - assert_equal("6", T2000.strftime("%w")) - assert_equal("01/01/00", T2000.strftime("%x")) - assert_equal("00:00:00", T2000.strftime("%X")) - assert_equal("00", T2000.strftime("%y")) - assert_equal("2000", T2000.strftime("%Y")) - assert_equal("UTC", T2000.strftime("%Z")) - assert_equal("%", T2000.strftime("%%")) - assert_equal("0", T2000.strftime("%-S")) - - assert_equal("", T2000.strftime("")) - assert_equal("foo\0bar\x0000\x0000\x0000", T2000.strftime("foo\0bar\0%H\0%M\0%S")) - assert_equal("foo" * 1000, T2000.strftime("foo" * 1000)) + assert_equal("Sat", t2000.strftime("%a")) + assert_equal("Saturday", t2000.strftime("%A")) + assert_equal("Jan", t2000.strftime("%b")) + assert_equal("January", t2000.strftime("%B")) + assert_kind_of(String, t2000.strftime("%c")) + assert_equal("01", t2000.strftime("%d")) + assert_equal("00", t2000.strftime("%H")) + assert_equal("12", t2000.strftime("%I")) + assert_equal("001", t2000.strftime("%j")) + assert_equal("01", t2000.strftime("%m")) + assert_equal("00", t2000.strftime("%M")) + assert_equal("AM", t2000.strftime("%p")) + assert_equal("00", t2000.strftime("%S")) + assert_equal("00", t2000.strftime("%U")) + assert_equal("00", t2000.strftime("%W")) + assert_equal("6", t2000.strftime("%w")) + assert_equal("01/01/00", t2000.strftime("%x")) + assert_equal("00:00:00", t2000.strftime("%X")) + assert_equal("00", t2000.strftime("%y")) + assert_equal("2000", t2000.strftime("%Y")) + 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")) + assert_equal("foo" * 1000, t2000.strftime("foo" * 1000)) t = Time.mktime(2000, 1, 1) assert_equal("Sat", t.strftime("%a")) + end + def test_strftime_subsec t = Time.at(946684800, 123456.789) assert_equal("123", t.strftime("%3N")) assert_equal("123456", t.strftime("%6N")) assert_equal("123456789", t.strftime("%9N")) assert_equal("1234567890", t.strftime("%10N")) assert_equal("123456789", t.strftime("%0N")) + end + + def test_strftime_sec + t = get_t2000.getlocal assert_equal("000", t.strftime("%3S")) + end + + def test_strftime_seconds_from_epoch + 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 t = Time.mktime(2001, 10, 1) assert_equal("2001-10-01", t.strftime("%F")) + assert_equal(Encoding::UTF_8, t.strftime("\u3042%Z").encoding) + assert_equal(true, t.strftime("\u3042%Z").valid_encoding?) + end + def test_strftime_flags t = Time.mktime(2001, 10, 1, 2, 0, 0) assert_equal("01", t.strftime("%d")) assert_equal("01", t.strftime("%0d")) @@ -583,32 +753,74 @@ 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 + t = Time.mktime(2001, 10, 1, 2, 0, 0) + assert_equal("%4^p", t.strftime("%4^p"), 'prec after flag') + end + + def test_strftime_year + t = Time.utc(1,1,4) + assert_equal("0001", t.strftime("%Y")) + assert_equal("0001", t.strftime("%G")) + t = Time.utc(0,1,4) + assert_equal("0000", t.strftime("%Y")) + assert_equal("0000", t.strftime("%G")) + + 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 # [ruby-dev:37155] t = Time.mktime(1970, 1, 18) assert_equal("0", t.strftime("%w")) assert_equal("7", t.strftime("%u")) + end + def test_strftime_ctrlchar # [ruby-dev:37160] - assert_equal("\t", T2000.strftime("%t")) - assert_equal("\t", T2000.strftime("%0t")) - assert_equal("\t", T2000.strftime("%1t")) - assert_equal(" \t", T2000.strftime("%3t")) - assert_equal("00\t", T2000.strftime("%03t")) - assert_equal("\n", T2000.strftime("%n")) - assert_equal("\n", T2000.strftime("%0n")) - assert_equal("\n", T2000.strftime("%1n")) - assert_equal(" \n", T2000.strftime("%3n")) - assert_equal("00\n", T2000.strftime("%03n")) + t2000 = get_t2000 + assert_equal("\t", t2000.strftime("%t")) + assert_equal("\t", t2000.strftime("%0t")) + assert_equal("\t", t2000.strftime("%1t")) + assert_equal(" \t", t2000.strftime("%3t")) + assert_equal("00\t", t2000.strftime("%03t")) + assert_equal("\n", t2000.strftime("%n")) + assert_equal("\n", t2000.strftime("%0n")) + assert_equal("\n", t2000.strftime("%1n")) + assert_equal(" \n", t2000.strftime("%3n")) + assert_equal("00\n", t2000.strftime("%03n")) + end + def test_strftime_weekflags # [ruby-dev:37162] - assert_equal("SAT", T2000.strftime("%#a")) - assert_equal("SATURDAY", T2000.strftime("%#A")) - assert_equal("JAN", T2000.strftime("%#b")) - assert_equal("JANUARY", T2000.strftime("%#B")) - assert_equal("JAN", T2000.strftime("%#h")) + t2000 = get_t2000 + assert_equal("SAT", t2000.strftime("%#a")) + assert_equal("SATURDAY", t2000.strftime("%#A")) + assert_equal("JAN", t2000.strftime("%#b")) + assert_equal("JANUARY", t2000.strftime("%#B")) + assert_equal("JAN", t2000.strftime("%#h")) assert_equal("FRIDAY", Time.local(2008,1,4).strftime("%#A")) + end + def test_strftime_rational t = Time.utc(2000,3,14, 6,53,"58.979323846".to_r) # Pi Day assert_equal("03/14/2000 6:53:58.97932384600000000000000000000", t.strftime("%m/%d/%Y %l:%M:%S.%29N")) @@ -630,6 +842,94 @@ class TestTime < Test::Unit::TestCase t.strftime("%m/%d/%Y %l:%M:%S.%8N")) end + def test_strftime_far_future + # [ruby-core:33985] + assert_equal("3000000000", Time.at(3000000000).strftime('%s')) + end + + def test_strftime_too_wide + assert_equal(8192, Time.now.strftime('%8192z').size) + end + + def test_strftime_wide_precision + t2000 = get_t2000 + s = t2000.strftime("%28c") + assert_equal(28, s.size) + assert_equal(t2000.strftime("%c"), s.strip) + end + + def test_strfimte_zoneoffset + t2000 = get_t2000 + t = t2000.getlocal("+09:00:00") + assert_equal("+0900", t.strftime("%z")) + assert_equal("+09:00", t.strftime("%:z")) + assert_equal("+09:00:00", t.strftime("%::z")) + assert_equal("+09", t.strftime("%:::z")) + + t = t2000.getlocal("+09:00:01") + assert_equal("+0900", t.strftime("%z")) + assert_equal("+09:00", t.strftime("%:z")) + assert_equal("+09:00:01", t.strftime("%::z")) + assert_equal("+09:00:01", t.strftime("%:::z")) + end + + def test_strftime_padding + bug4458 = '[ruby-dev:43287]' + t2000 = get_t2000 + t = t2000.getlocal("+09:00") + assert_equal("+0900", t.strftime("%z")) + assert_equal("+09:00", t.strftime("%:z")) + assert_equal(" +900", t.strftime("%_10z"), bug4458) + assert_equal("+000000900", t.strftime("%10z"), bug4458) + assert_equal(" +9:00", t.strftime("%_10:z"), bug4458) + assert_equal("+000009:00", t.strftime("%10:z"), bug4458) + assert_equal(" +9:00:00", t.strftime("%_10::z"), bug4458) + assert_equal("+009:00:00", t.strftime("%10::z"), bug4458) + assert_equal("+000000009", t.strftime("%10:::z")) + t = t2000.getlocal("-05:00") + assert_equal("-0500", t.strftime("%z")) + assert_equal("-05:00", t.strftime("%:z")) + assert_equal(" -500", t.strftime("%_10z"), bug4458) + assert_equal("-000000500", t.strftime("%10z"), bug4458) + assert_equal(" -5:00", t.strftime("%_10:z"), bug4458) + assert_equal("-000005:00", t.strftime("%10:z"), bug4458) + assert_equal(" -5:00:00", t.strftime("%_10::z"), bug4458) + assert_equal("-005:00:00", t.strftime("%10::z"), bug4458) + assert_equal("-000000005", t.strftime("%10:::z")) + + bug6323 = '[ruby-core:44447]' + t = t2000.getlocal("+00:36") + assert_equal(" +036", t.strftime("%_10z"), bug6323) + assert_equal("+000000036", t.strftime("%10z"), bug6323) + assert_equal(" +0:36", t.strftime("%_10:z"), bug6323) + assert_equal("+000000:36", t.strftime("%10:z"), bug6323) + assert_equal(" +0:36:00", t.strftime("%_10::z"), bug6323) + assert_equal("+000:36:00", t.strftime("%10::z"), bug6323) + assert_equal("+000000:36", t.strftime("%10:::z")) + t = t2000.getlocal("-00:55") + assert_equal(" -055", t.strftime("%_10z"), bug6323) + assert_equal("-000000055", t.strftime("%10z"), bug6323) + assert_equal(" -0:55", t.strftime("%_10:z"), bug6323) + assert_equal("-000000:55", t.strftime("%10:z"), bug6323) + assert_equal(" -0:55:00", t.strftime("%_10::z"), bug6323) + assert_equal("-000:55:00", t.strftime("%10::z"), bug6323) + assert_equal("-000000:55", t.strftime("%10:::z")) + end + + def test_strftime_invalid_modifier + t2000 = get_t2000 + t = t2000.getlocal("+09:00") + assert_equal("%:y", t.strftime("%:y"), 'invalid conversion after : modifier') + assert_equal("%:0z", t.strftime("%:0z"), 'flag after : modifier') + assert_equal("%:10z", t.strftime("%:10z"), 'prec after : modifier') + assert_equal("%Ob", t.strftime("%Ob"), 'invalid conversion after locale modifier') + assert_equal("%Eb", t.strftime("%Eb"), 'invalid conversion after locale modifier') + assert_equal("%O0y", t.strftime("%O0y"), 'flag after locale modifier') + assert_equal("%E0y", t.strftime("%E0y"), 'flag after locale modifier') + assert_equal("%O10y", t.strftime("%O10y"), 'prec after locale modifier') + assert_equal("%E10y", t.strftime("%E10y"), 'prec after locale modifier') + end + def test_delegate d1 = SimpleDelegator.new(t1 = Time.utc(2000)) d2 = SimpleDelegator.new(t2 = Time.utc(2001)) @@ -676,4 +976,216 @@ class TestTime < Test::Unit::TestCase off += 0.1 } end + + def test_getlocal_dont_share_eigenclass + bug5012 = "[ruby-dev:44071]" + + t0 = Time.now + class << t0; end + t1 = t0.getlocal + + def t0.m + 0 + end + + assert_raise(NoMethodError, bug5012) { t1.m } + end + + def test_sec_str + bug6193 = '[ruby-core:43569]' + t = nil + assert_nothing_raised(bug6193) {t = Time.new(2012, 1, 2, 3, 4, "5")} + assert_equal(Time.new(2012, 1, 2, 3, 4, 5), t, bug6193) + end + + def test_past + [ + [-(1 << 100), 1, 1, 0, 0, 0], + [-4000, 1, 1, 0, 0, 0], + [-3000, 1, 1, 0, 0, 0], + ].each {|year, mon, day, hour, min, sec| + t = Time.utc(year, mon, day, hour, min, sec) + assert_equal(year, t.year) + assert_equal(mon, t.mon) + assert_equal(day, t.day) + assert_equal(hour, t.hour) + assert_equal(min, t.min) + assert_equal(sec, t.sec) + } + end + + def test_1901 + assert_equal(-0x80000001, Time.utc(1901, 12, 13, 20, 45, 51).tv_sec) + [ + [1901, 12, 13, 20, 45, 50], + [1901, 12, 13, 20, 45, 51], + [1901, 12, 13, 20, 45, 52], # -0x80000000 + [1901, 12, 13, 20, 45, 53], + ].each {|year, mon, day, hour, min, sec| + t = Time.utc(year, mon, day, hour, min, sec) + assert_equal(year, t.year) + assert_equal(mon, t.mon) + assert_equal(day, t.day) + assert_equal(hour, t.hour) + assert_equal(min, t.min) + assert_equal(sec, t.sec) + } + end + + def test_1970 + assert_equal(0, Time.utc(1970, 1, 1, 0, 0, 0).tv_sec) + [ + [1969, 12, 31, 23, 59, 59], + [1970, 1, 1, 0, 0, 0], + [1970, 1, 1, 0, 0, 1], + ].each {|year, mon, day, hour, min, sec| + t = Time.utc(year, mon, day, hour, min, sec) + assert_equal(year, t.year) + assert_equal(mon, t.mon) + assert_equal(day, t.day) + assert_equal(hour, t.hour) + assert_equal(min, t.min) + assert_equal(sec, t.sec) + } + end + + def test_2038 + if no_leap_seconds? + assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) + end + [ + [2038, 1, 19, 3, 14, 7], + [2038, 1, 19, 3, 14, 8], + [2038, 1, 19, 3, 14, 9], + [2039, 1, 1, 0, 0, 0], + ].each {|year, mon, day, hour, min, sec| + t = Time.utc(year, mon, day, hour, min, sec) + assert_equal(year, t.year) + assert_equal(mon, t.mon) + assert_equal(day, t.day) + assert_equal(hour, t.hour) + 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 + [ + [3000, 1, 1, 0, 0, 0], + [4000, 1, 1, 0, 0, 0], + [1 << 100, 1, 1, 0, 0, 0], + ].each {|year, mon, day, hour, min, sec| + t = Time.utc(year, mon, day, hour, min, sec) + assert_equal(year, t.year) + assert_equal(mon, t.mon) + assert_equal(day, t.day) + assert_equal(hour, t.hour) + assert_equal(min, t.min) + assert_equal(sec, t.sec) + } + end + + def test_getlocal_utc_offset + t = Time.gm(2000) + assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-02:30").to_a[0, 6] + assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+09:00").to_a[0, 6] + assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-02:30:40").to_a[0, 6] + assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+09:10:35").to_a[0, 6] + 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 + skip unless Thread.list.size == 1 + + fmt = %w(Y m d).map { |x| "%#{x}" }.join('-') # defeats optimization + t = Time.at(0).getutc + ObjectSpace.count_objects(res = {}) # creates strings on first call + GC.disable + before = ObjectSpace.count_objects(res)[:T_STRING] + val = t.strftime(fmt) + after = ObjectSpace.count_objects(res)[:T_STRING] + assert_equal before + 1, after, 'only new string is the created one' + assert_equal '1970-01-01', val + ensure + GC.enable + end + + def test_num_exact_error + bad = EnvUtil.labeled_class("BadValue").new + x = EnvUtil.labeled_class("Inexact") do + 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 + when 48 then expect = 94 + 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 42c4607bf3..9bba30e577 100644 --- a/test/ruby/test_time_tz.rb +++ b/test/ruby/test_time_tz.rb @@ -1,23 +1,46 @@ +# frozen_string_literal: false require 'test/unit' +require '-test-/time' class TestTimeTZ < Test::Unit::TestCase - def with_tz(tz) - if /linux/ =~ RUBY_PLATFORM || ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes" - old = ENV["TZ"] - begin - ENV["TZ"] = tz - yield - ensure - ENV["TZ"] = old + has_right_tz = true + has_lisbon_tz = true + force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes" + case RUBY_PLATFORM + when /linux/ + force_tz_test = true + when /darwin|freebsd/ + has_lisbon_tz = false + force_tz_test = true + end + + if force_tz_test + module Util + def with_tz(tz) + old = ENV["TZ"] + begin + ENV["TZ"] = tz + yield + ensure + ENV["TZ"] = old + end end - else - if ENV["TZ"] == tz - yield + end + else + module Util + def with_tz(tz) + if ENV["TZ"] == tz + yield + end end end end module Util + def have_tz_offset?(tz) + with_tz(tz) {!Time.now.utc_offset.zero?} + end + def format_gmtoff(gmtoff, colon=false) if gmtoff < 0 expected = "-" @@ -60,12 +83,21 @@ class TestTimeTZ < Test::Unit::TestCase include Util extend Util - def time_to_s(t) - if RUBY_VERSION < "1.9" - t.strftime("%Y-%m-%d %H:%M:%S ") + format_gmtoff(t.gmtoff) - else - t.to_s + 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 end @@ -76,6 +108,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]) @@ -105,14 +149,21 @@ 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="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]) assert_time_constructor(tz, "2007-11-03 23:01:00 -0230", :new, [2007,11,3,23,1,0,:dst]) assert_time_constructor(tz, "2007-11-03 23:59:59 -0230", :new, [2007,11,3,23,59,59,:dst]) @@ -128,43 +179,78 @@ 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_lisbon - with_tz(tz="Europe/Lisbon") { - assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone) + 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_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]) + def test_europe_lisbon + with_tz("Europe/Lisbon") { + assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone) } - end + end if has_lisbon_tz def test_pacific_kiritimati with_tz(tz="Pacific/Kiritimati") { - assert_time_constructor(tz, "1994-12-31 23:59:59 -1000", :local, [1994,12,31,23,59,59]) - assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,1,0,0,0]) - assert_time_constructor(tz, "1995-01-02 23:59:59 +1400", :local, [1995,1,1,23,59,59]) + assert_time_constructor(tz, "1994-12-30 00:00:00 -1000", :local, [1994,12,30,0,0,0]) + assert_time_constructor(tz, "1994-12-30 23:59:59 -1000", :local, [1994,12,30,23,59,59]) + if CORRECT_KIRITIMATI_SKIP_1994 + assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1994,12,31,0,0,0]) + assert_time_constructor(tz, "1995-01-01 23:59:59 +1400", :local, [1994,12,31,23,59,59]) + assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1995,1,1,0,0,0]) + else + assert_time_constructor(tz, "1994-12-31 23:59:59 -1000", :local, [1994,12,31,23,59,59]) + assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,1,0,0,0]) + assert_time_constructor(tz, "1995-01-02 23:59:59 +1400", :local, [1995,1,1,23,59,59]) + end assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,2,0,0,0]) } end def test_right_utc with_tz(tz="right/UTC") { + ::Bug::Time.reset_leap_second_info assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) } - end + end if has_right_tz + + def test_right_utc_switching + with_tz("UTC") { # ensure no leap second timezone + ::Bug::Time.reset_leap_second_info + assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + with_tz(tz="right/UTC") { + assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) + assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + with_tz("right/UTC") { + ::Bug::Time.reset_leap_second_info + assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + with_tz(tz="UTC") { + assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) + assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + end if has_right_tz def test_right_america_los_angeles with_tz(tz="right/America/Los_Angeles") { @@ -172,7 +258,7 @@ class TestTimeTZ < Test::Unit::TestCase assert_time_constructor(tz, "2008-12-31 15:59:60 -0800", :local, [2008,12,31,15,59,60]) assert_time_constructor(tz, "2008-12-31 16:00:00 -0800", :local, [2008,12,31,16,0,0]) } - end + end if has_right_tz MON2NUM = { "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6, @@ -186,35 +272,42 @@ class TestTimeTZ < Test::Unit::TestCase s.sub(/gen_/) { "gen" + "_#{hint}_".gsub(/[^0-9A-Za-z]+/, '_') } end - def self.gen_zdump_test + def self.parse_zdump_line(line) + return nil if /\A\#/ =~ line || /\A\s*\z/ =~ line + if /\A(\S+)\s+ + \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+UTC? + \s+=\s+ + \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+\S+ + \s+isdst=\d+\s+gmtoff=(-?\d+)\n + \z/x !~ line + raise "unexpected zdump line: #{line.inspect}" + end + tz, u_mon, u_day, u_hour, u_min, u_sec, u_year, + l_mon, l_day, l_hour, l_min, l_sec, l_year, gmtoff = $~.captures + u_year = u_year.to_i + u_mon = MON2NUM[u_mon] + u_day = u_day.to_i + u_hour = u_hour.to_i + u_min = u_min.to_i + u_sec = u_sec.to_i + l_year = l_year.to_i + l_mon = MON2NUM[l_mon] + l_day = l_day.to_i + l_hour = l_hour.to_i + l_min = l_min.to_i + l_sec = l_sec.to_i + gmtoff = gmtoff.to_i + [tz, + [u_year, u_mon, u_day, u_hour, u_min, u_sec], + [l_year, l_mon, l_day, l_hour, l_min, l_sec], + gmtoff] + end + + def self.gen_zdump_test(data) sample = [] - ZDUMP_SAMPLE.each_line {|line| - next if /\A\#/ =~ line || /\A\s*\z/ =~ line - /\A(\S+)\s+ - \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+UTC - \s+=\s+ - \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+\S+ - \s+isdst=\d+\s+gmtoff=(-?\d+)\n - \z/x =~ line - tz, u_mon, u_day, u_hour, u_min, u_sec, u_year, - l_mon, l_day, l_hour, l_min, l_sec, l_year, gmtoff = $~.captures - u_year = u_year.to_i - u_mon = MON2NUM[u_mon] - u_day = u_day.to_i - u_hour = u_hour.to_i - u_min = u_min.to_i - u_sec = u_sec.to_i - l_year = l_year.to_i - l_mon = MON2NUM[l_mon] - l_day = l_day.to_i - l_hour = l_hour.to_i - l_min = l_min.to_i - l_sec = l_sec.to_i - gmtoff = gmtoff.to_i - sample << [tz, - [u_year, u_mon, u_day, u_hour, u_min, u_sec], - [l_year, l_mon, l_day, l_hour, l_min, l_sec], - gmtoff] + data.each_line {|line| + s = parse_zdump_line(line) + sample << s if s } sample.each {|tz, u, l, gmtoff| expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u @@ -223,6 +316,7 @@ class TestTimeTZ < Test::Unit::TestCase mesg = "#{mesg_utc}.localtime" define_method(gen_test_name(tz)) { with_tz(tz) { + ::Bug::Time.reset_leap_second_info t = nil assert_nothing_raised(mesg) { t = Time.utc(*u) } assert_equal(expected_utc, time_to_s(t), mesg_utc) @@ -232,11 +326,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 @@ -274,7 +370,7 @@ class TestTimeTZ < Test::Unit::TestCase } end - ZDUMP_SAMPLE = <<'End' + gen_zdump_test <<'End' America/Lima Sun Apr 1 03:59:59 1990 UTC = Sat Mar 31 23:59:59 1990 PEST isdst=1 gmtoff=-14400 America/Lima Sun Apr 1 04:00:00 1990 UTC = Sat Mar 31 23:00:00 1990 PET isdst=0 gmtoff=-18000 America/Lima Sat Jan 1 04:59:59 1994 UTC = Fri Dec 31 23:59:59 1993 PET isdst=0 gmtoff=-18000 @@ -298,14 +394,27 @@ 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 -Canada/Newfoundland Sun Mar 11 03:30:59 2007 UTC = Sun Mar 11 00:00:59 2007 NST isdst=0 gmtoff=-12600 -Canada/Newfoundland Sun Mar 11 03:31:00 2007 UTC = Sun Mar 11 01:01:00 2007 NDT isdst=1 gmtoff=-9000 -Canada/Newfoundland Sun Nov 4 02:30:59 2007 UTC = Sun Nov 4 00:00:59 2007 NDT isdst=1 gmtoff=-9000 -Canada/Newfoundland Sun Nov 4 02:31:00 2007 UTC = Sat Nov 3 23:01:00 2007 NST isdst=0 gmtoff=-12600 +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 +America/St_Johns Sun Nov 4 02:31:00 2007 UTC = Sat Nov 3 23:01:00 2007 NST isdst=0 gmtoff=-12600 Europe/Brussels Sun Apr 30 22:59:59 1916 UTC = Sun Apr 30 23:59:59 1916 CET isdst=0 gmtoff=3600 Europe/Brussels Sun Apr 30 23:00:00 1916 UTC = Mon May 1 01:00:00 1916 CEST isdst=1 gmtoff=7200 Europe/Brussels Sat Sep 30 22:59:59 1916 UTC = Sun Oct 1 00:59:59 1916 CEST isdst=1 gmtoff=7200 @@ -318,21 +427,265 @@ 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 #right/Asia/Tokyo Fri Jun 30 23:59:60 1972 UTC = Sat Jul 1 08:59:60 1972 JST isdst=0 gmtoff=32400 #right/Asia/Tokyo Sat Dec 31 23:59:60 2005 UTC = Sun Jan 1 08:59:60 2006 JST isdst=0 gmtoff=32400 right/Europe/Paris Fri Jun 30 23:59:60 1972 UTC = Sat Jul 1 00:59:60 1972 CET isdst=0 gmtoff=3600 right/Europe/Paris Wed Dec 31 23:59:60 2008 UTC = Thu Jan 1 00:59:60 2009 CET isdst=0 gmtoff=3600 +End + + def self.gen_variational_zdump_test(hint, data) + sample = [] + data.each_line {|line| + s = parse_zdump_line(line) + sample << s if s + } + + define_method(gen_test_name(hint)) { + results = [] + sample.each {|tz, u, l, gmtoff| + expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u + expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)]) + mesg_utc = "TZ=#{tz} Time.utc(#{u.map {|arg| arg.inspect }.join(', ')})" + mesg = "#{mesg_utc}.localtime" + with_tz(tz) { + t = nil + assert_nothing_raised(mesg) { t = Time.utc(*u) } + assert_equal(expected_utc, time_to_s(t), mesg_utc) + assert_nothing_raised(mesg) { t.localtime } + + results << [ + expected == time_to_s(t), + gmtoff == t.gmtoff, + format_gmtoff(gmtoff) == t.strftime("%z"), + format_gmtoff(gmtoff, true) == t.strftime("%:z"), + format_gmtoff2(gmtoff) == t.strftime("%::z") + ] + } + } + assert_include(results, [true, true, true, true, true]) + } + end + + # tzdata-2014g fixed the offset for lisbon from -0:36:32 to -0:36:45. + # [ruby-core:65058] [Bug #10245] + 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 - gen_zdump_test + + class TZ + attr_reader :name, :abbr, :offset + + def initialize(name, abbr, offset) + @name = name + @abbr = abbr + @offset = offset + end + + def local_to_utc(t) + t - @offset + end + + def utc_to_local(t) + t + @offset + end + + def abbr(t) + @abbr + end + + def ==(other) + @name == other.name and @abbr == other.abbr(0) and @offset == other.offset + end + + def inspect + "#<TZ: #@name #@abbr #@offset>" + end + end +end + +module TestTimeTZ::WithTZ + def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + h, m = (-utc_offset / 60).divmod(60) + assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i) + end + + def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.utc(2018, 9, 1, 12, 0, 0).getlocal(tzarg) + h, m = (utc_offset / 60).divmod(60) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t) + end + + def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + h, m = (utc_offset.abs / 60).divmod(60) + h = -h if utc_offset < 0 + assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z")) + end + + def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000 + assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + m, s = (4000-utc_offset).divmod(60) + h, m = m.divmod(60) + assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t) + assert_equal(6, t.wday) + assert_equal(244, t.yday) + end + + def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset) + h, m = (utc_offset / 60).divmod(60) + utc = time_class.utc(2018, 9, 1, 12, 0, 0) + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(utc.to_i, t.to_i) + utc = utc.to_i + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(utc, t.to_i) + end + + def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + t2 = Marshal.load(Marshal.dump(t)) + assert_equal(t, t2) + assert_equal(t.utc_offset, t2.utc_offset) + assert_equal(t.utc_offset, (t2+1).utc_offset) + assert_instance_of(t.zone.class, t2.zone) + end + + def test_invalid_zone + make_timezone("INVALID", "INV", 0) + rescue => e + assert_kind_of(StandardError, e) + else + assert false, "ArgumentError expected but nothing was raised." + end + + def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset) + data = [ + "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1], + "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b, + Marshal.dump({offset: utc_offset, zone: abbr})[3..-1], + ].join('') + t = Marshal.load(data) + assert_equal(utc_offset, t.utc_offset) + assert_equal(utc_offset, (t+1).utc_offset) + # t.zone may be a mere String or timezone object. + end + + ZONES = { + "Asia/Tokyo" => ["JST", +9*3600], + "America/Los_Angeles" => ["PDT", -7*3600], + "Africa/Ndjamena" => ["WAT", +1*3600], + } + + def make_timezone(tzname, abbr, utc_offset) + self.class::TIME_CLASS.find_timezone(tzname) + end + + instance_methods(false).grep(/\Asub(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset)| + define_method("#{test}@#{tzname}") do + tz = make_timezone(tzname, abbr, utc_offset) + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tz, tz, tzname, abbr, utc_offset) + __send__(subtest, time_class, tz, tzname, tzname, abbr, utc_offset) + end + end + end + + instance_methods(false).grep(/\Aname(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset)| + define_method("#{test}@#{tzname}") do + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tzname, abbr, utc_offset) + end + end + end +end + +class TestTimeTZ::DummyTZ < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + ZONES = TestTimeTZ::WithTZ::ZONES + def self.find_timezone(tzname) + tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}" + TestTimeTZ::TZ.new(tzname, *tz) + end + end + + def self.make_timezone(tzname, abbr, utc_offset) + TestTimeTZ::TZ.new(tzname, abbr, utc_offset) + end +end + +begin + require "tzinfo" +rescue LoadError +else + class TestTimeTZ::GemTZInfo < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(tzname) + TZInfo::Timezone.get(tzname) + end + end + + def tz + @tz ||= TZInfo::Timezone.get(tzname) + end + end + + def test_fractional_second + x = Object.new + def x.local_to_utc(t); t + 8*3600; end + def x.utc_to_local(t); t - 8*3600; end + + t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00') + t2 = Time.new(2020,11,11,12,13,14.124r, x) + assert_equal(t1, t2) + end +end + +begin + require "timezone" +rescue LoadError +else + class TestTimeTZ::GemTimezone < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(name) + Timezone.fetch(name) + end + end + + def tz + @tz ||= Timezone[tzname] + end + end end diff --git a/test/ruby/test_trace.rb b/test/ruby/test_trace.rb index 45bc599314..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 @@ -46,4 +47,16 @@ class TestTrace < Test::Unit::TestCase ensure untrace_var :$x end + + def test_trace_break + bug2722 = '[ruby-core:31783]' + a = Object.new.extend(Enumerable) + def a.each + yield + end + assert(Thread.start { + Thread.current.add_trace_func(proc{}) + a.any? {true} + }.value, bug2722) + end end diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index 1b15a5b556..7f81cbf424 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -1,7 +1,9 @@ -# -*- encoding: ASCII-8BIT -*- # make sure this runs in binary mode +# 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' + class TestTranscode < Test::Unit::TestCase def test_errors assert_raise(Encoding::ConverterNotFoundError) { 'abc'.encode('foo', 'bar') } @@ -11,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 @@ -29,16 +31,16 @@ class TestTranscode < Test::Unit::TestCase end def test_noargument - default_default_internal = Encoding.default_internal - Encoding.default_internal = nil - assert_equal("\u3042".encode, "\u3042") - assert_equal("\xE3\x81\x82\x81".force_encoding("utf-8").encode, - "\xE3\x81\x82\x81".force_encoding("utf-8")) - Encoding.default_internal = 'EUC-JP' - assert_equal("\u3042".encode, "\xA4\xA2".force_encoding('EUC-JP')) - assert_equal("\xE3\x81\x82\x81".force_encoding("utf-8").encode, - "\xA4\xA2?".force_encoding('EUC-JP')) - Encoding.default_internal = default_default_internal + EnvUtil.with_default_internal(nil) do + assert_equal("\u3042".encode, "\u3042") + assert_equal("\xE3\x81\x82\x81".force_encoding("utf-8").encode, + "\xE3\x81\x82\x81".force_encoding("utf-8")) + end + EnvUtil.with_default_internal('EUC-JP') do + assert_equal("\u3042".encode, "\xA4\xA2".force_encoding('EUC-JP')) + assert_equal("\xE3\x81\x82\x81".force_encoding("utf-8").encode, + "\xA4\xA2?".force_encoding('EUC-JP')) + end end def test_length @@ -51,7 +53,7 @@ class TestTranscode < Test::Unit::TestCase end def check_both_ways(utf8, raw, encoding) - assert_equal(utf8.force_encoding('utf-8'), raw.encode('utf-8', encoding)) + assert_equal(utf8.force_encoding('utf-8'), raw.encode('utf-8', encoding),utf8.dump+raw.dump) assert_equal(raw.force_encoding(encoding), utf8.encode(encoding, 'utf-8')) end @@ -60,13 +62,42 @@ class TestTranscode < Test::Unit::TestCase assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1)) end + def test_encoding_of_ascii_originating_from_binary + binary_string = [0x82, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x61, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x6f, + 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67] + class << binary_string + # create a copy on write substring that contains + # just the ascii characters (i.e. this is...), in JRuby + # the underlying string have the same buffer backing + # it up, but the offset of the string will be 1 instead + # of 0. + def make_cow_substring + pack('C27').slice(1, 26) + end + end + + ascii_string = binary_string.make_cow_substring + assert_equal("this is a very long string", ascii_string) + assert_equal(Encoding::ASCII_8BIT, ascii_string.encoding) + utf8_string = nil + assert_nothing_raised("JRUBY-6764") do + utf8_string = ascii_string.encode(Encoding::UTF_8) + end + assert_equal("this is a very long string", utf8_string) + assert_equal(Encoding::UTF_8, utf8_string.encoding) + end + def test_encodings check_both_ways("\u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D", "\x82\xdc\x82\xc2\x82\xe0\x82\xc6 \x82\xe4\x82\xab\x82\xd0\x82\xeb", 'shift_jis') # まつもと ゆきひろ check_both_ways("\u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D", "\xa4\xde\xa4\xc4\xa4\xe2\xa4\xc8 \xa4\xe6\xa4\xad\xa4\xd2\xa4\xed", 'euc-jp') + check_both_ways("\u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D", + "\xa4\xde\xa4\xc4\xa4\xe2\xa4\xc8 \xa4\xe6\xa4\xad\xa4\xd2\xa4\xed", 'euc-jis-2004') check_both_ways("\u677E\u672C\u884C\u5F18", "\x8f\xbc\x96\x7b\x8d\x73\x8d\x4f", 'shift_jis') # 松本行弘 check_both_ways("\u677E\u672C\u884C\u5F18", "\xbe\xbe\xcb\xdc\xb9\xd4\xb9\xb0", 'euc-jp') + check_both_ways("\u677E\u672C\u884C\u5F18", "\xbe\xbe\xcb\xdc\xb9\xd4\xb9\xb0", 'euc-jis-2004') check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-1') # Dürst check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-2') check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-3') @@ -83,6 +114,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u0643\u062A\u0628", "\xE3\xCA\xC8", 'iso-8859-6') # كتب check_both_ways("\u65E5\u8A18", "\x93\xFA\x8BL", 'shift_jis') # 日記 check_both_ways("\u65E5\u8A18", "\xC6\xFC\xB5\xAD", 'euc-jp') + check_both_ways("\u65E5\u8A18", "\xC6\xFC\xB5\xAD", 'euc-jis-2004') check_both_ways("\uC560\uC778\uAD6C\uD568\u0020\u6734\uC9C0\uC778", "\xBE\xD6\xC0\xCE\xB1\xB8\xC7\xD4\x20\xDA\xD3\xC1\xF6\xC0\xCE", 'euc-kr') # 애인구함 朴지인 check_both_ways("\uC544\uD58F\uD58F\u0020\uB620\uBC29\uD6BD\uB2D8\u0020\uC0AC\uB791\uD716", @@ -331,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') # ׀ @@ -452,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 @@ -471,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 @@ -490,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 @@ -966,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] } @@ -1019,6 +1137,21 @@ class TestTranscode < Test::Unit::TestCase check_utf_16_both_ways("\u{F00FF}", "\xDB\x80\xDC\xFF") end + def test_utf_16_bom + expected = "\u{3042}\u{3044}\u{20bb7}" + assert_equal(expected, %w/fffe4230443042d8b7df/.pack("H*").encode("UTF-8","UTF-16")) + check_both_ways(expected, %w/feff30423044d842dfb7/.pack("H*"), "UTF-16") + assert_raise(Encoding::InvalidByteSequenceError){%w/feffdfb7/.pack("H*").encode("UTF-8","UTF-16")} + assert_raise(Encoding::InvalidByteSequenceError){%w/fffeb7df/.pack("H*").encode("UTF-8","UTF-16")} + end + + def test_utf_32_bom + expected = "\u{3042}\u{3044}\u{20bb7}" + assert_equal(expected, %w/fffe00004230000044300000b70b0200/.pack("H*").encode("UTF-8","UTF-32")) + check_both_ways(expected, %w/0000feff000030420000304400020bb7/.pack("H*"), "UTF-32") + assert_raise(Encoding::InvalidByteSequenceError){%w/0000feff00110000/.pack("H*").encode("UTF-8","UTF-32")} + end + def check_utf_32_both_ways(utf8, raw) copy = raw.dup 0.step(copy.length-1, 4) do |i| @@ -1140,9 +1273,15 @@ class TestTranscode < Test::Unit::TestCase assert_equal("\uFFFD!", "\xff!".encode("utf-8", "euc-jp", :invalid=>:replace)) assert_equal("\uFFFD!", + "\xff!".encode("utf-8", "euc-jis-2004", :invalid=>:replace)) + assert_equal("\uFFFD!", "\xa1!".encode("utf-8", "euc-jp", :invalid=>:replace)) assert_equal("\uFFFD!", + "\xa1!".encode("utf-8", "euc-jis-2004", :invalid=>:replace)) + assert_equal("\uFFFD!", "\x8f\xa1!".encode("utf-8", "euc-jp", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\x8f\xa1!".encode("utf-8", "euc-jis-2004", :invalid=>:replace)) assert_equal("?", "\xdc\x00".encode("EUC-JP", "UTF-16BE", :invalid=>:replace), "[ruby-dev:35776]") @@ -1159,6 +1298,10 @@ 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 @@ -1273,6 +1416,64 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u795E\u6797\u7FA9\u535A", "\xBF\xC0\xCE\xD3\xB5\xC1\xC7\xEE", 'euc-jp') # 神林義博 end + def test_euc_jis_2004 + check_both_ways("\u3000", "\xA1\xA1", 'euc-jis-2004') # full-width space + check_both_ways("\u00D7", "\xA1\xDF", 'euc-jis-2004') # × + check_both_ways("\u00F7", "\xA1\xE0", 'euc-jis-2004') # ÷ + check_both_ways("\u25C7", "\xA1\xFE", 'euc-jis-2004') # ◇ + check_both_ways("\u25C6", "\xA2\xA1", 'euc-jis-2004') # ◆ + check_both_ways("\uFF07", "\xA2\xAF", 'euc-jis-2004') # ' + check_both_ways("\u309F", "\xA2\xB9", 'euc-jis-2004') # ゟ + check_both_ways("\u2284", "\xA2\xC2", 'euc-jis-2004') # ⊄ + check_both_ways("\u2306", "\xA2\xC9", 'euc-jis-2004') # ⌆ + check_both_ways("\u2295", "\xA2\xD1", 'euc-jis-2004') # ⊕ + check_both_ways("\u3017", "\xA2\xDB", 'euc-jis-2004') # 〗 + check_both_ways("\u2262", "\xA2\xEB", 'euc-jis-2004') # ≢ + check_both_ways("\u2194", "\xA2\xF1", 'euc-jis-2004') # ↔ + check_both_ways("\u266E", "\xA2\xFA", 'euc-jis-2004') # ♮ + check_both_ways("\u2669", "\xA2\xFD", 'euc-jis-2004') # ♩ + check_both_ways("\u25EF", "\xA2\xFE", 'euc-jis-2004') # ◯ + check_both_ways("\u2935", "\xA3\xAF", 'euc-jis-2004') # ⤵ + check_both_ways("\u29BF", "\xA3\xBA", 'euc-jis-2004') # ⦿ + check_both_ways("\u2022", "\xA3\xC0", 'euc-jis-2004') # • + check_both_ways("\u2213", "\xA3\xDB", 'euc-jis-2004') # ∓ + check_both_ways("\u2127", "\xA3\xE0", 'euc-jis-2004') # ℧ + check_both_ways("\u30A0", "\xA3\xFB", 'euc-jis-2004') # ゠ + check_both_ways("\uFF54", "\xA3\xF4", 'euc-jis-2004') # t + assert_raise(Encoding::UndefinedConversionError) { "\xA5\xF7".encode("utf-8", 'euc-jis-2004') } + check_both_ways("\u2664", "\xA6\xB9", 'euc-jis-2004') # ♤ + check_both_ways("\u2663", "\xA6\xC0", 'euc-jis-2004') # ♣ + check_both_ways("\u03C2", "\xA6\xD9", 'euc-jis-2004') # ς + check_both_ways("\u23BE", "\xA7\xC2", 'euc-jis-2004') # ⎾ + check_both_ways("\u23CC", "\xA7\xD0", 'euc-jis-2004') # ⏌ + check_both_ways("\u30F7", "\xA7\xF2", 'euc-jis-2004') # ヷ + check_both_ways("\u3251", "\xA8\xC1", 'euc-jis-2004') # ㉑ + check_both_ways("\u{20B9F}", "\xCF\xD4", 'euc-jis-2004') # 𠮑 + check_both_ways("\u541E", "\xCF\xFE", 'euc-jis-2004') # 吞 + check_both_ways("\u6A97", "\xDD\xA1", 'euc-jis-2004') # 檗 + check_both_ways("\u6BEF", "\xDD\xDF", 'euc-jis-2004') # 毯 + check_both_ways("\u9EBE", "\xDD\xE0", 'euc-jis-2004') # 麾 + check_both_ways("\u6CBE", "\xDD\xFE", 'euc-jis-2004') # 沾 + check_both_ways("\u6CBA", "\xDE\xA1", 'euc-jis-2004') # 沺 + check_both_ways("\u6ECC", "\xDE\xFE", 'euc-jis-2004') # 滌 + check_both_ways("\u6F3E", "\xDF\xA1", 'euc-jis-2004') # 漾 + check_both_ways("\u70DD", "\xDF\xDF", 'euc-jis-2004') # 烝 + check_both_ways("\u70D9", "\xDF\xE0", 'euc-jis-2004') # 烙 + check_both_ways("\u71FC", "\xDF\xFE", 'euc-jis-2004') # 燼 + check_both_ways("\u71F9", "\xE0\xA1", 'euc-jis-2004') # 燹 + check_both_ways("\u73F1", "\xE0\xFE", 'euc-jis-2004') # 珱 + check_both_ways("\u5653", "\xF4\xA7", 'euc-jis-2004') # 噓 + #check_both_ways("\u9ADC", "\xFC\xE3", 'euc-jp') # 髜 (IBM extended) + + check_both_ways("\u9DD7", "\xFE\xE5", 'euc-jis-2004') # 鷗 + check_both_ways("\u{2000B}", "\xAE\xA2", 'euc-jis-2004') # 𠀋 + check_both_ways("\u{2A6B2}", "\x8F\xFE\xF6", 'euc-jis-2004') # 𪚲 + + check_both_ways("\u677E\u672C\u884C\u5F18", "\xBE\xBE\xCB\xDC\xB9\xD4\xB9\xB0", 'euc-jis-2004') # 松本行弘 + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC0\xC4\xBB\xB3\xB3\xD8\xB1\xA1\xC2\xE7\xB3\xD8", 'euc-jis-2004') # 青山学院大学 + check_both_ways("\u795E\u6797\u7FA9\u535A", "\xBF\xC0\xCE\xD3\xB5\xC1\xC7\xEE", 'euc-jis-2004') # 神林義博 + end + def test_eucjp_ms check_both_ways("\u2116", "\xAD\xE2", 'eucJP-ms') # NUMERO SIGN check_both_ways("\u221A", "\xA2\xE5", 'eucJP-ms') # SQUARE ROOT @@ -1357,7 +1558,7 @@ class TestTranscode < Test::Unit::TestCase "\xA1\xA1".encode("ISO-2022-JP", "EUC-JP")) end - def test_cp50221 + def test_from_cp50221 assert_equal("!", "\e(B\x21".encode("utf-8", "cp50221")) assert_equal("!", "\e(J\x21".encode("utf-8", "cp50221")) assert_equal("\uFF71", "\xB1".encode("utf-8", "cp50221")) @@ -1373,10 +1574,11 @@ class TestTranscode < Test::Unit::TestCase assert_equal("\u5fde", "\e$B\x7A\x21".encode("utf-8", "cp50221")) assert_equal("\u72be", "\e$B\x7B\x21".encode("utf-8", "cp50221")) assert_equal("\u91d7", "\e$B\x7C\x21".encode("utf-8", "cp50221")) - assert_equal("\e(I!_\e(B", "\xA1\xDF".encode("cp50220","sjis")) + assert_equal("\xA1\xDF".force_encoding("sjis"), + "\e(I!_\e(B".encode("sjis","cp50220")) end - def test_cp50221 + def test_to_cp50221 assert_equal("\e$B!#!,\e(B".force_encoding("cp50220"), "\xA1\xDF".encode("cp50220","sjis")) assert_equal("\e$B%*!+%,%I%J!+%N!+%P%\\%^!+%Q%]%\"\e(B".force_encoding("cp50220"), @@ -1389,14 +1591,16 @@ class TestTranscode < Test::Unit::TestCase end def test_unicode_public_review_issue_121 # see http://www.unicode.org/review/pr-121.html - # assert_equal("\x00\x61\xFF\xFD\x00\x62".force_encoding('UTF-16BE'), - # "\x61\xF1\x80\x80\xE1\x80\xC2\x62".encode('UTF-16BE', 'UTF-8', invalid: :replace)) # option 1 assert_equal("\x00\x61\xFF\xFD\xFF\xFD\xFF\xFD\x00\x62".force_encoding('UTF-16BE'), "\x61\xF1\x80\x80\xE1\x80\xC2\x62".encode('UTF-16BE', 'UTF-8', invalid: :replace)) # option 2 assert_equal("\x61\x00\xFD\xFF\xFD\xFF\xFD\xFF\x62\x00".force_encoding('UTF-16LE'), "\x61\xF1\x80\x80\xE1\x80\xC2\x62".encode('UTF-16LE', 'UTF-8', invalid: :replace)) # option 2 - # assert_equal("\x00\x61\xFF\xFD\xFF\xFD\xFF\xFD\xFF\xFD\xFF\xFD\xFF\xFD\x00\x62".force_encoding('UTF-16BE'), - # "\x61\xF1\x80\x80\xE1\x80\xC2\x62".encode('UTF-16BE', 'UTF-8', invalid: :replace)) # option 3 + + # additional clarification + assert_equal("\xFF\xFD\xFF\xFD\xFF\xFD\xFF\xFD".force_encoding('UTF-16BE'), + "\xF0\x80\x80\x80".encode('UTF-16BE', 'UTF-8', invalid: :replace)) + assert_equal("\xFD\xFF\xFD\xFF\xFD\xFF\xFD\xFF".force_encoding('UTF-16LE'), + "\xF0\x80\x80\x80".encode('UTF-16LE', 'UTF-8', invalid: :replace)) end def test_yen_sign @@ -1794,9 +1998,9 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u77AC", "\xC0\xFE", 'Big5') # 瞬 check_both_ways("\u8B96", "\xC6\x40", 'Big5') # 讖 check_both_ways("\u7C72", "\xC6\x7E", 'Big5') # 籲 - #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5') } + #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5') } #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5') } - assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5') } + #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5') } check_both_ways("\u4E42", "\xC9\x40", 'Big5') # 乂 check_both_ways("\u6C15", "\xC9\x7E", 'Big5') # 氕 check_both_ways("\u6C36", "\xC9\xA1", 'Big5') # 氶 @@ -1829,7 +2033,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u9F0A", "\xF9\x7E", 'Big5') # 鼊 check_both_ways("\u9FA4", "\xF9\xA1", 'Big5') # 龤 check_both_ways("\u9F98", "\xF9\xD5", 'Big5') # 龘 - assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5') } + #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5') } check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5') # 神林義博 end @@ -1861,7 +2065,7 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u77AC", "\xC0\xFE", 'Big5-HKSCS') # 瞬 check_both_ways("\u8B96", "\xC6\x40", 'Big5-HKSCS') # 讖 check_both_ways("\u7C72", "\xC6\x7E", 'Big5-HKSCS') # 籲 - #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5-HKSCS') } + #assert_raise(Encoding::UndefinedConversionError) { "\xC6\xA1".encode("utf-8", 'Big5-HKSCS') } #assert_raise(Encoding::UndefinedConversionError) { "\xC7\x40".encode("utf-8", 'Big5-HKSCS') } #assert_raise(Encoding::UndefinedConversionError) { "\xC8\x40".encode("utf-8", 'Big5-HKSCS') } check_both_ways("\u4E42", "\xC9\x40", 'Big5-HKSCS') # 乂 @@ -1896,15 +2100,22 @@ class TestTranscode < Test::Unit::TestCase check_both_ways("\u9F0A", "\xF9\x7E", 'Big5-HKSCS') # 鼊 check_both_ways("\u9FA4", "\xF9\xA1", 'Big5-HKSCS') # 龤 check_both_ways("\u9F98", "\xF9\xD5", 'Big5-HKSCS') # 龘 - check_both_ways("\u{23ED7}", "\x8E\x40", 'Big5-HKSCS') # 𣻗 + #check_both_ways("\u{23ED7}", "\x8E\x40", 'Big5-HKSCS') # 𣻗 #assert_raise(Encoding::UndefinedConversionError) { "\xF9\xD6".encode("utf-8", 'Big5-HKSCS') } check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5-HKSCS') # 神林義博 end - + def test_Big5_UAO 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") @@ -1913,11 +2124,13 @@ class TestTranscode < Test::Unit::TestCase end def test_utf8_mac - assert_equal("\u{fb4d}", "\u05DB\u05BF".encode("UTF-8", "UTF8-MAC")) - assert_equal("\u{1ff7}", "\u03C9\u0345\u0342".encode("UTF-8", "UTF8-MAC")) + # composition exclusion + assert_equal("\u05DB\u05BF", "\u05DB\u05BF".encode("UTF-8", "UTF8-MAC")) #"\u{fb4d}" + + assert_equal("\u{1ff7}", "\u03C9\u0342\u0345".encode("UTF-8", "UTF8-MAC")) assert_equal("\u05DB\u05BF", "\u{fb4d}".encode("UTF8-MAC").force_encoding("UTF-8")) - assert_equal("\u03C9\u0345\u0342", "\u{1ff7}".encode("UTF8-MAC").force_encoding("UTF-8")) + assert_equal("\u03C9\u0342\u0345", "\u{1ff7}".encode("UTF8-MAC").force_encoding("UTF-8")) check_both_ways("\u{e9 74 e8}", "e\u0301te\u0300", 'UTF8-MAC') end @@ -1930,4 +2143,97 @@ class TestTranscode < Test::Unit::TestCase assert_equal("[ISU]", "\u{1F4BA}".encode("SJIS-KDDI", fallback: {"\u{1F4BA}" => "[ISU]"})) end + + def test_fallback_hash_default + fallback = Hash.new {|h, x| "U+%.4X" % x.unpack("U")} + assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback)) + end + + def test_fallback_proc + fallback = proc {|x| "U+%.4X" % x.unpack("U")} + assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback)) + end + + def test_fallback_method + def (fallback = "U+%.4X").escape(x) + self % x.unpack("U") + end + assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback.method(:escape))) + end + + def test_fallback_aref + fallback = Object.new + def fallback.[](x) + "U+%.4X" % x.unpack("U") + end + assert_equal("U+3042", "\u{3042}".encode("US-ASCII", fallback: fallback)) + end + + bug8940 = '[ruby-core:57318] [Bug #8940]' + %w[UTF-32 UTF-16].each do |enc| + define_method("test_pseudo_encoding_inspect(#{enc})") do + assert_normal_exit("'aaa'.encode('#{enc}').inspect", bug8940) + assert_equal(4, 'aaa'.encode(enc).length, "should count in #{enc} with BOM") + end + end + + def test_encode_with_invalid_chars + bug8995 = '[ruby-dev:47747]' + EnvUtil.with_default_internal(Encoding::UTF_8) do + str = "\xff".force_encoding('utf-8') + assert_equal str, str.encode, bug8995 + assert_equal "\ufffd", str.encode(invalid: :replace), bug8995 + end + end + + def test_valid_dummy_encoding + bug9314 = '[ruby-core:59354] [Bug #9314]' + 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([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bug11277 = '[ruby-dev:49106] [Bug #11277]' + num = 2 + th = (0...num).map do |i| + Thread.new {"\u3042".encode("EUC-JP")} + end + result = nil + assert_warning("", bug11277) do + assert_nothing_raised(Encoding::ConverterNotFoundError, bug11277) do + result = th.map(&:value) + end + end + expected = "\xa4\xa2".force_encoding(Encoding::EUC_JP) + assert_equal([expected]*num, result, bug11277) + 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 + s = "A\nB\r\nC".force_encoding(usascii) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace, replace: ''), bug11324) + end end diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb index e1c98076c0..e0add7c3ab 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 @@ -24,7 +25,7 @@ class TestUndef < Test::Unit::TestCase y = Undef1.new assert_equal "bar", y.bar z = Undef2.new - assert_raise(NoMethodError) { z.foo } + assert_raise(NoMethodError) { z.bar } end def test_special_const_undef diff --git a/test/ruby/test_unicode_escape.rb b/test/ruby/test_unicode_escape.rb index 088f81ce14..5913bb0130 100644 --- a/test/ruby/test_unicode_escape.rb +++ b/test/ruby/test_unicode_escape.rb @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# frozen_string_literal: false require 'test/unit' @@ -47,7 +48,8 @@ EOS # \u in %x strings assert_match(/^("?)A\1$/, `echo "\u0041"`) #" assert_match(/^("?)A\1$/, %x{echo "\u0041"}) #" - assert_match(/^("?)ü\1$/, `echo "\u{FC}"`.force_encoding("utf-8")) #" + assert_match(/^("?)ü\1$/, + `#{EnvUtil.rubybin} -e "#coding:utf-8\nputs \\"\\u{FC}\\""`.force_encoding("utf-8")) #" # \u in quoted symbols assert_equal(:A, :"\u0041") @@ -254,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 53e00301dc..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 @@ -55,11 +57,17 @@ class TestVariable < Test::Unit::TestCase assert_equal("Cronus", atlas.ruler0) assert_equal("Zeus", atlas.ruler3) assert_equal("Cronus", atlas.ruler4) + assert_nothing_raised do + class << Gods + defined?(@@rule) && @@rule + end + end end def test_local_variables lvar = 1 assert_instance_of(Symbol, local_variables[0], "[ruby-dev:34008]") + lvar end def test_local_variables2 @@ -67,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 @@ -76,17 +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]' + assert_equal([:x, :bug9486], tap {|x| break local_variables}, bug9486) + end + + def test_shadowing_block_local_variables + bug9486 = '[ruby-core:60501] [Bug #9486]' + 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 new file mode 100644 index 0000000000..fb57903c98 --- /dev/null +++ b/test/ruby/test_weakmap.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestWeakMap < Test::Unit::TestCase + def setup + @wm = ObjectSpace::WeakMap.new + end + + def test_map + x = Object.new + k = "foo" + @wm[k] = x + assert_same(x, @wm[k]) + assert_not_same(x, @wm["FOO".downcase]) + end + + def test_aset_const + x = Object.new + assert_raise(ArgumentError) {@wm[true] = x} + assert_raise(ArgumentError) {@wm[false] = x} + assert_raise(ArgumentError) {@wm[nil] = x} + assert_raise(ArgumentError) {@wm[42] = x} + assert_raise(ArgumentError) {@wm[:foo] = x} + assert_raise(ArgumentError) {@wm[x] = true} + assert_raise(ArgumentError) {@wm[x] = false} + assert_raise(ArgumentError) {@wm[x] = nil} + assert_raise(ArgumentError) {@wm[x] = 42} + assert_raise(ArgumentError) {@wm[x] = :foo} + end + + def assert_weak_include(m, k, n = 100) + if n > 0 + return assert_weak_include(m, k, n-1) + end + 1.times do + x = Object.new + @wm[k] = x + assert_send([@wm, m, k]) + assert_not_send([@wm, m, "FOO".downcase]) + x = Object.new + end + end + + def test_include? + m = __callee__[/test_(.*)/, 1] + k = "foo" + 1.times do + assert_weak_include(m, k) + end + GC.start + skip('TODO: failure introduced from r60440') + assert_not_send([@wm, m, k]) + end + alias test_member? test_include? + alias test_key? test_include? + + def test_inspect + x = Object.new + k = BasicObject.new + @wm[k] = x + assert_match(/\A\#<#{@wm.class.name}:[^:]+:\s\#<BasicObject:[^:]*>\s=>\s\#<Object:[^:]*>>\z/, + @wm.inspect) + end + + def test_each + m = __callee__[/test_(.*)/, 1] + x1 = Object.new + k1 = "foo" + @wm[k1] = x1 + x2 = Object.new + k2 = "bar" + @wm[k2] = x2 + n = 0 + @wm.__send__(m) do |k, v| + assert_match(/\A(?:foo|bar)\z/, k) + case k + when /foo/ + assert_same(k1, k) + assert_same(x1, v) + when /bar/ + assert_same(k2, k) + assert_same(x2, v) + end + n += 1 + end + assert_equal(2, n) + end + + def test_each_key + x1 = Object.new + k1 = "foo" + @wm[k1] = x1 + x2 = Object.new + k2 = "bar" + @wm[k2] = x2 + n = 0 + @wm.each_key do |k| + assert_match(/\A(?:foo|bar)\z/, k) + case k + when /foo/ + assert_same(k1, k) + when /bar/ + assert_same(k2, k) + end + n += 1 + end + assert_equal(2, n) + end + + def test_each_value + x1 = "foo" + k1 = Object.new + @wm[k1] = x1 + x2 = "bar" + k2 = Object.new + @wm[k2] = x2 + n = 0 + @wm.each_value do |v| + assert_match(/\A(?:foo|bar)\z/, v) + case v + when /foo/ + assert_same(x1, v) + when /bar/ + assert_same(x2, v) + end + n += 1 + end + assert_equal(2, n) + end + + def test_size + m = __callee__[/test_(.*)/, 1] + assert_equal(0, @wm.__send__(m)) + x1 = "foo" + k1 = Object.new + @wm[k1] = x1 + assert_equal(1, @wm.__send__(m)) + x2 = "bar" + k2 = Object.new + @wm[k2] = x2 + assert_equal(2, @wm.__send__(m)) + end + alias test_length test_size +end diff --git a/test/ruby/test_whileuntil.rb b/test/ruby/test_whileuntil.rb index 5628317cb8..121c44817d 100644 --- a/test/ruby/test_whileuntil.rb +++ b/test/ruby/test_whileuntil.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false require 'test/unit' require 'tmpdir' @@ -21,7 +22,7 @@ class TestWhileuntil < Test::Unit::TestCase break if /vt100/ =~ line end - assert(!tmp.eof?) + assert_not_predicate(tmp, :eof?) assert_match(/vt100/, line) tmp.close @@ -30,7 +31,7 @@ class TestWhileuntil < Test::Unit::TestCase next if /vt100/ =~ line assert_no_match(/vt100/, line) end - assert(tmp.eof?) + assert_predicate(tmp, :eof?) assert_no_match(/vt100/, line) tmp.close @@ -45,7 +46,7 @@ class TestWhileuntil < Test::Unit::TestCase assert_no_match(/vt100/, line) assert_no_match(/VT100/, line) end - assert(tmp.eof?) + assert_predicate(tmp, :eof?) tmp.close sum=0 @@ -60,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) @@ -68,7 +69,7 @@ class TestWhileuntil < Test::Unit::TestCase tmp.close File.unlink tmpfilename or `/bin/rm -f "#{tmpfilename}"` - assert(!File.exist?(tmpfilename)) + assert_file.not_exist?(tmpfilename) } end @@ -77,6 +78,6 @@ class TestWhileuntil < Test::Unit::TestCase until i>4 i+=1 end - assert(i>4) + assert_operator(i, :>, 4) end end diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb index 3337aea078..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') @@ -379,4 +387,39 @@ class TestRubyYieldGen < Test::Unit::TestCase } end + def test_block_with_mock + y = Object.new + def y.s(a) + yield(a) + end + m = Object.new + def m.method_missing(*a) + super + 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 b7219ddb51..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 @@ -86,13 +87,13 @@ module TestEOF def test_eof_2 open_file("") {|f| assert_equal("", f.read) - assert(f.eof?) + assert_predicate(f, :eof?) } end def test_eof_3 open_file("") {|f| - assert(f.eof?) + assert_predicate(f, :eof?) } end |
