diff options
Diffstat (limited to 'test/ruby')
200 files changed, 131950 insertions, 0 deletions
diff --git a/test/ruby/allpairs.rb b/test/ruby/allpairs.rb new file mode 100644 index 0000000000..e5893e252a --- /dev/null +++ b/test/ruby/allpairs.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: false +module AllPairs + module_function + + def make_prime(v) + return 2 if v < 2 + ary = [true] * (v*2) + 2.upto(Math.sqrt(ary.length).ceil) {|i| + return i if ary[i] && v <= i + (i*2).step(ary.length, i) {|j| + ary[j] = false + } + } + v.upto(ary.length-1) {|i| + return i if ary[i] + } + raise "[bug] prime not found greater than #{v}" + end + + def make_basic_block(prime) + prime.times {|i| + prime.times {|j| + row = [i] + 0.upto(prime-1) {|m| + row << (i*m + j) % prime + } + yield row + } + } + end + + def combine_block(tbl1, tbl2) + result = [] + tbl2.each {|row| + result << row * tbl1.first.length + } + tbl1.each_with_index {|row, k| + next if k == 0 + result << row.map {|i| [i] * tbl2.first.length }.flatten + } + result + end + + def make_large_block(v, prime) + if prime <= v+1 + make_basic_block(v) {|row| + yield row + } + else + tbl = [] + make_basic_block(v) {|row| + tbl << row + } + tbls = [tbl] + while tbl.first.length ** 2 < prime + tbl = combine_block(tbl, tbl) + tbls << tbl + end + tbl1 = tbls.find {|t| prime <= t.first.length * tbl.first.length } + tbl = combine_block(tbl, tbl1) + tbl.each {|row| + yield row + } + end + end + + def each_index(*vs) + n = vs.length + max_v = vs.max + h = {} + make_large_block(max_v, n) {|row| + row = vs.zip(row).map {|v, i| i % v } + next if h[row] + h[row] = true + yield row + } + end + + # generate all pairs test. + def each(*args) + args.map! {|a| a.to_a } + each_index(*args.map {|a| a.length}) {|is| + yield is.zip(args).map {|i, a| a[i] } + } + end + + # generate all combination in cartesian product. (not all-pairs test) + def exhaustive_each(*args) + args = args.map {|a| a.to_a } + i = 0 + while true + n = i + as = [] + args.reverse_each {|a| + n, m = n.divmod(a.length) + as.unshift a[m] + } + break if 0 < n + yield as + i += 1 + end + end +end diff --git a/test/ruby/beginmainend.rb b/test/ruby/beginmainend.rb new file mode 100644 index 0000000000..b6de5d65fd --- /dev/null +++ b/test/ruby/beginmainend.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: false +BEGIN { + puts "b1" + # local_begin1 = "local_begin1" + $global_begin1 = "global_begin1" + ConstBegin1 = "ConstBegin1" +} + +BEGIN { + puts "b2" + + BEGIN { + puts "b2-1" + } +} + +# for scope check +#raise if defined?(local_begin1) +raise unless defined?($global_begin1) +raise unless defined?(::ConstBegin1) +local_for_end2 = "e2" +$global_for_end1 = "e1" + +puts "main" + +END { + puts local_for_end2 # e2 +} + +eval <<EOE + BEGIN { + puts "b3" + + BEGIN { + puts "b3-1" + } + } + + BEGIN { + puts "b4" + } + + END { + puts "e3" + } + + END { + puts "e4" + + END { + puts "e4-1" + + END { + puts "e4-1-1" + } + } + + END { + puts "e4-2" + } + } +EOE + +END { + exit + puts "should not be dumped" + + END { + puts "not reached" + } +} + +END { + puts $global_for_end1 # e1 + + END { + puts "e1-1" + } +} diff --git a/test/ruby/box/a.1_1_0.rb b/test/ruby/box/a.1_1_0.rb new file mode 100644 index 0000000000..0322585097 --- /dev/null +++ b/test/ruby/box/a.1_1_0.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BOX_A + VERSION = "1.1.0" + + def yay + "yay #{VERSION}" + end +end + +module BOX_B + VERSION = "1.1.0" + + def self.yay + "yay_b1" + end +end diff --git a/test/ruby/box/a.1_2_0.rb b/test/ruby/box/a.1_2_0.rb new file mode 100644 index 0000000000..29813ea57b --- /dev/null +++ b/test/ruby/box/a.1_2_0.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BOX_A + VERSION = "1.2.0" + + def yay + "yay #{VERSION}" + end +end + +module BOX_B + VERSION = "1.2.0" + + def self.yay + "yay_b1" + end +end diff --git a/test/ruby/box/a.rb b/test/ruby/box/a.rb new file mode 100644 index 0000000000..26a622c92b --- /dev/null +++ b/test/ruby/box/a.rb @@ -0,0 +1,15 @@ +class BOX_A + FOO = "foo_a1" + + def yay + "yay_a1" + end +end + +module BOX_B + BAR = "bar_b1" + + def self.yay + "yay_b1" + end +end diff --git a/test/ruby/box/autoloading.rb b/test/ruby/box/autoloading.rb new file mode 100644 index 0000000000..cba57ab377 --- /dev/null +++ b/test/ruby/box/autoloading.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +autoload :BOX_A, File.join(__dir__, 'a.1_1_0') +BOX_A.new.yay + +module BOX_B + autoload :BAR, File.join(__dir__, 'a') +end diff --git a/test/ruby/box/blank.rb b/test/ruby/box/blank.rb new file mode 100644 index 0000000000..6d201b0966 --- /dev/null +++ b/test/ruby/box/blank.rb @@ -0,0 +1,2 @@ +module Blank1 +end diff --git a/test/ruby/box/blank1.rb b/test/ruby/box/blank1.rb new file mode 100644 index 0000000000..6d201b0966 --- /dev/null +++ b/test/ruby/box/blank1.rb @@ -0,0 +1,2 @@ +module Blank1 +end diff --git a/test/ruby/box/blank2.rb b/test/ruby/box/blank2.rb new file mode 100644 index 0000000000..ba38c1d6db --- /dev/null +++ b/test/ruby/box/blank2.rb @@ -0,0 +1,2 @@ +module Blank2 +end diff --git a/test/ruby/box/box.rb b/test/ruby/box/box.rb new file mode 100644 index 0000000000..3b7da14e9d --- /dev/null +++ b/test/ruby/box/box.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +BOX1 = Ruby::Box.new +BOX1.require_relative('a.1_1_0') + +def yay + BOX1::BOX_B::yay +end + +yay diff --git a/test/ruby/box/call_proc.rb b/test/ruby/box/call_proc.rb new file mode 100644 index 0000000000..8acf538fc1 --- /dev/null +++ b/test/ruby/box/call_proc.rb @@ -0,0 +1,5 @@ +module Bar + def self.caller(proc_value) + proc_value.call + end +end diff --git a/test/ruby/box/call_toplevel.rb b/test/ruby/box/call_toplevel.rb new file mode 100644 index 0000000000..c311a37028 --- /dev/null +++ b/test/ruby/box/call_toplevel.rb @@ -0,0 +1,8 @@ +foo + +#### TODO: this code should be valid, but can't be for now +# module Foo +# def self.wow +# foo +# end +# end diff --git a/test/ruby/box/consts.rb b/test/ruby/box/consts.rb new file mode 100644 index 0000000000..e40cd5c50c --- /dev/null +++ b/test/ruby/box/consts.rb @@ -0,0 +1,148 @@ +$VERBOSE = nil +class String + STR_CONST1 = 111 + STR_CONST2 = 222 + STR_CONST3 = 333 +end + +class String + STR_CONST1 = 112 + + def self.set0(val) + const_set(:STR_CONST0, val) + end + + def self.remove0 + remove_const(:STR_CONST0) + end + + def refer0 + STR_CONST0 + end + + def refer1 + STR_CONST1 + end + + def refer2 + STR_CONST2 + end + + def refer3 + STR_CONST3 + end +end + +module ForConsts + CONST1 = 111 +end + +TOP_CONST = 10 + +module ForConsts + CONST1 = 112 + CONST2 = 222 + CONST3 = 333 + + def self.refer_all + ForConsts::CONST1 + ForConsts::CONST2 + ForConsts::CONST3 + String::STR_CONST1 + String::STR_CONST2 + String::STR_CONST3 + end + + def self.refer1 + CONST1 + end + + def self.get1 + const_get(:CONST1) + end + + def self.refer2 + CONST2 + end + + def self.get2 + const_get(:CONST2) + end + + def self.refer3 + CONST3 + end + + def self.get3 + const_get(:CONST3) + end + + def self.refer_top_const + TOP_CONST + end + + # for String + class Proxy + def call_str_refer0 + String.new.refer0 + end + + def call_str_get0 + String.const_get(:STR_CONST0) + end + + def call_str_set0(val) + String.set0(val) + end + + def call_str_remove0 + String.remove0 + end + + def call_str_refer1 + String.new.refer1 + end + + def call_str_get1 + String.const_get(:STR_CONST1) + end + + String::STR_CONST2 = 223 + + def call_str_refer2 + String.new.refer2 + end + + def call_str_get2 + String.const_get(:STR_CONST2) + end + + def call_str_set3 + String.const_set(:STR_CONST3, 334) + end + + def call_str_refer3 + String.new.refer3 + end + + def call_str_get3 + String.const_get(:STR_CONST3) + end + + # for Integer + Integer::INT_CONST1 = 1 + + def refer_int_const1 + Integer::INT_CONST1 + end + end +end + +# should not raise errors +ForConsts.refer_all +String::STR_CONST1 +Integer::INT_CONST1 + +# If we execute this sentence once, the constant value will be cached on ISeq inline constant cache. +# And it changes the behavior of ForConsts.refer_consts_directly called from global. +# ForConsts.refer_consts_directly # should not raise errors too diff --git a/test/ruby/box/define_toplevel.rb b/test/ruby/box/define_toplevel.rb new file mode 100644 index 0000000000..aa77db3a13 --- /dev/null +++ b/test/ruby/box/define_toplevel.rb @@ -0,0 +1,5 @@ +def foo + "foooooooooo" +end + +foo # should not raise errors diff --git a/test/ruby/box/global_vars.rb b/test/ruby/box/global_vars.rb new file mode 100644 index 0000000000..590363f617 --- /dev/null +++ b/test/ruby/box/global_vars.rb @@ -0,0 +1,37 @@ +module LineSplitter + def self.read + $-0 + end + + def self.write(char) + $-0 = char + end +end + +module FieldSplitter + def self.read + $, + end + + def self.write(char) + $, = char + end +end + +module UniqueGvar + def self.read + $used_only_in_box + end + + def self.write(val) + $used_only_in_box = val + end + + def self.write_only(val) + $write_only_var_in_box = val + end + + def self.gvars_in_box + global_variables + end +end diff --git a/test/ruby/box/instance_variables.rb b/test/ruby/box/instance_variables.rb new file mode 100644 index 0000000000..1562ad5d45 --- /dev/null +++ b/test/ruby/box/instance_variables.rb @@ -0,0 +1,21 @@ +class String + class << self + attr_reader :str_ivar1 + + def str_ivar2 + @str_ivar2 + end + end + + @str_ivar1 = 111 + @str_ivar2 = 222 +end + +class StringDelegator < BasicObject +private + def method_missing(...) + ::String.public_send(...) + end +end + +StringDelegatorObj = StringDelegator.new diff --git a/test/ruby/box/line_splitter.rb b/test/ruby/box/line_splitter.rb new file mode 100644 index 0000000000..2596975ad7 --- /dev/null +++ b/test/ruby/box/line_splitter.rb @@ -0,0 +1,9 @@ +module LineSplitter + def self.read + $-0 + end + + def self.write(char) + $-0 = char + end +end diff --git a/test/ruby/box/load_path.rb b/test/ruby/box/load_path.rb new file mode 100644 index 0000000000..7e5a83ef96 --- /dev/null +++ b/test/ruby/box/load_path.rb @@ -0,0 +1,26 @@ +module LoadPathCheck + FIRST_LOAD_PATH = $LOAD_PATH.dup + FIRST_LOAD_PATH_RESPOND_TO_RESOLVE = $LOAD_PATH.respond_to?(:resolve_feature_path) + FIRST_LOADED_FEATURES = $LOADED_FEATURES.dup + + HERE = File.dirname(__FILE__) + + def self.current_load_path + $LOAD_PATH + end + + def self.current_loaded_features + $LOADED_FEATURES + end + + def self.require_blank1 + $LOAD_PATH << HERE + require 'blank1' + end + + def self.require_blank2 + require 'blank2' + end +end + +LoadPathCheck.require_blank1 diff --git a/test/ruby/box/open_class_with_include.rb b/test/ruby/box/open_class_with_include.rb new file mode 100644 index 0000000000..ad8fd58ea0 --- /dev/null +++ b/test/ruby/box/open_class_with_include.rb @@ -0,0 +1,31 @@ +module StringExt + FOO = "foo 1" + def say_foo + "I'm saying " + FOO + end +end + +class String + include StringExt + def say + say_foo + end +end + +module OpenClassWithInclude + def self.say + String.new.say + end + + def self.say_foo + String.new.say_foo + end + + def self.say_with_obj(str) + str.say + end + + def self.refer_foo + String::FOO + end +end diff --git a/test/ruby/box/proc_callee.rb b/test/ruby/box/proc_callee.rb new file mode 100644 index 0000000000..d30ab5d9f3 --- /dev/null +++ b/test/ruby/box/proc_callee.rb @@ -0,0 +1,14 @@ +module Target + def self.foo + "fooooo" + end +end + +module Foo + def self.callee + lambda do + Target.foo + end + end +end + diff --git a/test/ruby/box/proc_caller.rb b/test/ruby/box/proc_caller.rb new file mode 100644 index 0000000000..8acf538fc1 --- /dev/null +++ b/test/ruby/box/proc_caller.rb @@ -0,0 +1,5 @@ +module Bar + def self.caller(proc_value) + proc_value.call + end +end diff --git a/test/ruby/box/procs.rb b/test/ruby/box/procs.rb new file mode 100644 index 0000000000..1c39a8231b --- /dev/null +++ b/test/ruby/box/procs.rb @@ -0,0 +1,64 @@ +class String + FOO = "foo" + def yay + "yay" + end +end + +module ProcLookupTestA + module B + VALUE = 222 + end +end + +module ProcInBox + def self.make_proc_from_block(&b) + b + end + + def self.call_proc(proc_arg) + proc_arg.call + end + + def self.make_str_proc(type) + case type + when :proc_new then Proc.new { String.new.yay } + when :proc_f then proc { String.new.yay } + when :lambda_f then lambda { String.new.yay } + when :lambda_l then ->(){ String.new.yay } + when :block then make_proc_from_block { String.new.yay } + else + raise "invalid type :#{type}" + end + end + + def self.make_const_proc(type) + case type + when :proc_new then Proc.new { ProcLookupTestA::B::VALUE } + when :proc_f then proc { ProcLookupTestA::B::VALUE } + when :lambda_f then lambda { ProcLookupTestA::B::VALUE } + when :lambda_l then ->(){ ProcLookupTestA::B::VALUE } + when :block then make_proc_from_block { ProcLookupTestA::B::VALUE } + else + raise "invalid type :#{type}" + end + end + + def self.make_str_const_proc(type) + case type + when :proc_new then Proc.new { String::FOO } + when :proc_f then proc { String::FOO } + when :lambda_f then lambda { String::FOO } + when :lambda_l then ->(){ String::FOO } + when :block then make_proc_from_block { String::FOO } + else + raise "invalid type :#{type}" + end + end + + CONST_PROC_NEW = Proc.new { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') } + CONST_PROC_F = proc { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') } + CONST_LAMBDA_F = lambda { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') } + CONST_LAMBDA_L = ->() { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') } + CONST_BLOCK = make_proc_from_block { [String.new.yay, String::FOO, ProcLookupTestA::B::VALUE.to_s].join(',') } +end diff --git a/test/ruby/box/raise.rb b/test/ruby/box/raise.rb new file mode 100644 index 0000000000..efb67f85c5 --- /dev/null +++ b/test/ruby/box/raise.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +raise "Yay!" diff --git a/test/ruby/box/returns_proc.rb b/test/ruby/box/returns_proc.rb new file mode 100644 index 0000000000..bb816e5024 --- /dev/null +++ b/test/ruby/box/returns_proc.rb @@ -0,0 +1,12 @@ +module Foo + def self.foo + "fooooo" + end + + def self.callee + lambda do + Foo.foo + end + end +end + diff --git a/test/ruby/box/singleton_methods.rb b/test/ruby/box/singleton_methods.rb new file mode 100644 index 0000000000..05470932d2 --- /dev/null +++ b/test/ruby/box/singleton_methods.rb @@ -0,0 +1,65 @@ +class String + def self.greeting + "Good evening!" + end +end + +class Integer + class << self + def answer + 42 + end + end +end + +class Array + def a + size + end + def self.blank + [] + end + def b + size + end +end + +class Hash + def a + size + end + class << self + def http_200 + {status: 200, body: 'OK'} + end + end + def b + size + end +end + +module SingletonMethods + def self.string_greeing + String.greeting + end + + def self.integer_answer + Integer.answer + end + + def self.array_blank + Array.blank + end + + def self.hash_http_200 + Hash.http_200 + end + + def self.array_instance_methods_return_size(ary) + [ary.a, ary.b] + end + + def self.hash_instance_methods_return_size(hash) + [hash.a, hash.b] + end +end diff --git a/test/ruby/box/string_ext.rb b/test/ruby/box/string_ext.rb new file mode 100644 index 0000000000..d8c5a3d661 --- /dev/null +++ b/test/ruby/box/string_ext.rb @@ -0,0 +1,13 @@ +class String + def yay + "yay" + end +end + +String.new.yay # check this doesn't raise NoMethodError + +module Bar + def self.yay + String.new.yay + end +end diff --git a/test/ruby/box/string_ext_caller.rb b/test/ruby/box/string_ext_caller.rb new file mode 100644 index 0000000000..b8345d98ed --- /dev/null +++ b/test/ruby/box/string_ext_caller.rb @@ -0,0 +1,5 @@ +module Foo + def self.yay + String.new.yay + end +end diff --git a/test/ruby/box/string_ext_calling.rb b/test/ruby/box/string_ext_calling.rb new file mode 100644 index 0000000000..6467b728dd --- /dev/null +++ b/test/ruby/box/string_ext_calling.rb @@ -0,0 +1 @@ +Foo.yay diff --git a/test/ruby/box/string_ext_eval_caller.rb b/test/ruby/box/string_ext_eval_caller.rb new file mode 100644 index 0000000000..0e6b20c19f --- /dev/null +++ b/test/ruby/box/string_ext_eval_caller.rb @@ -0,0 +1,12 @@ +module Baz + def self.yay + eval 'String.new.yay' + end + + def self.yay_with_binding + suffix = ", yay!" + eval 'String.new.yay + suffix', binding + end +end + +Baz.yay # should not raise NeMethodError diff --git a/test/ruby/box/top_level.rb b/test/ruby/box/top_level.rb new file mode 100644 index 0000000000..90df145578 --- /dev/null +++ b/test/ruby/box/top_level.rb @@ -0,0 +1,33 @@ +def yaaay + "yay!" +end + +module Foo + def self.foo + yaaay + end +end + +eval 'def foo; "foo"; end' + +Foo.foo # Should not raise NameError + +foo + +module Bar + def self.bar + foo + end +end + +Bar.bar + +$def_retval_in_namespace = def boooo + "boo" +end + +module Baz + def self.baz + raise "#{$def_retval_in_namespace}" + end +end 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 new file mode 100644 index 0000000000..5dcf93e8e3 --- /dev/null +++ b/test/ruby/enc/test_big5.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require "test/unit" + +class TestBig5 < Test::Unit::TestCase + def s(s) + s.force_encoding("big5") + end + + def test_mbc_enc_len + assert_equal(1, s("\xa1\xa1").size) + end + + def test_mbc_to_code + assert_equal(0xa1a1, s("\xa1\xa1").ord) + end + + def test_code_to_mbc + assert_equal(s("\xa1\xa1"), 0xa1a1.chr("big5")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\xa1\xa1)\\1"), "i") + assert_match(r, s("\xa1\xa1\xa1\xa1")) + end + + def test_left_adjust_char_head + assert_equal(s("\xa1\xa1"), s("\xa1\xa1\xa1\xa1").chop) + end +end diff --git a/test/ruby/enc/test_case_comprehensive.rb b/test/ruby/enc/test_case_comprehensive.rb new file mode 100644 index 0000000000..b812b88b83 --- /dev/null +++ b/test/ruby/enc/test_case_comprehensive.rb @@ -0,0 +1,306 @@ +# 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? + omit "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) + File.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_single_byte_case_mapping_tests(encoding) + all_tests + # precalculate codepoints to speed up testing for small encodings + codepoints = [] + (0..255).each do |cp| + begin + codepoints << cp.chr(encoding).encode('UTF-8') + rescue Encoding::UndefinedConversionError, RangeError + 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_single_byte_case_mapping_tests 'US-ASCII' + generate_single_byte_case_mapping_tests 'ASCII-8BIT' + generate_single_byte_case_mapping_tests 'ISO-8859-1' + generate_single_byte_case_mapping_tests 'ISO-8859-2' + generate_single_byte_case_mapping_tests 'ISO-8859-3' + generate_single_byte_case_mapping_tests 'ISO-8859-4' + generate_single_byte_case_mapping_tests 'ISO-8859-5' + generate_single_byte_case_mapping_tests 'ISO-8859-6' + generate_single_byte_case_mapping_tests 'ISO-8859-7' + generate_single_byte_case_mapping_tests 'ISO-8859-8' + generate_single_byte_case_mapping_tests 'ISO-8859-9' + generate_single_byte_case_mapping_tests 'ISO-8859-10' + generate_single_byte_case_mapping_tests 'ISO-8859-11' + generate_single_byte_case_mapping_tests 'ISO-8859-13' + generate_single_byte_case_mapping_tests 'ISO-8859-14' + generate_single_byte_case_mapping_tests 'ISO-8859-15' + generate_single_byte_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_single_byte_case_mapping_tests 'Windows-1250' + generate_single_byte_case_mapping_tests 'Windows-1251' + generate_single_byte_case_mapping_tests 'Windows-1252' + generate_single_byte_case_mapping_tests 'Windows-1253' + generate_single_byte_case_mapping_tests 'Windows-1254' + generate_single_byte_case_mapping_tests 'Windows-1255' + generate_ascii_only_case_mapping_tests 'Windows-1256' + generate_single_byte_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..a7d1ed0d16 --- /dev/null +++ b/test/ruby/enc/test_case_mapping.rb @@ -0,0 +1,231 @@ +# 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".dup.force_encoding('UTF-8').upcase } + assert_raise(ArgumentError, "Should not be possible to downcase invalid string.") { "\xEB".dup.force_encoding('UTF-8').downcase } + assert_raise(ArgumentError, "Should not be possible to capitalize invalid string.") { "\xEB".dup.force_encoding('UTF-8').capitalize } + assert_raise(ArgumentError, "Should not be possible to swapcase invalid string.") { "\xEB".dup.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 ÇŒ', 'Dz Ç… Lj Ç‹' + check_downcase_properties 'dz dž lj ÇŒ', 'DZ Ç„ LJ ÇŠ' + check_upcase_properties 'DZ Ç„ LJ ÇŠ', 'Dz Ç… Lj Ç‹' + check_upcase_properties 'DZ Ç„ LJ ÇŠ', 'dz dž lj ÇŒ' + check_capitalize_properties 'Dz', 'DZ' + check_capitalize_properties 'Ç…', 'Ç„' + check_capitalize_properties 'Lj', 'LJ' + check_capitalize_properties 'Ç‹', 'ÇŠ' + check_capitalize_properties 'Dz', 'dz' + check_capitalize_properties 'Ç…', 'dž' + check_capitalize_properties 'Lj', 'lj' + check_capitalize_properties 'Ç‹', 'ÇŒ' + end + + def test_swapcase + assert_equal 'dZ', 'Dz'.swapcase + assert_equal 'dŽ', 'Ç…'.swapcase + assert_equal 'lJ', 'Lj'.swapcase + assert_equal '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', 'ï¬ï¬‚', :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 test_shift_jis_downcase_ascii + s = ("A".."Z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS") + assert_equal s, s.downcase(:ascii) + end + + def test_shift_jis_upcase_ascii + s = ("a".."z").map {|c| "\x89#{c}"}.join("").force_encoding("Shift_JIS") + assert_equal s, s.upcase(:ascii) + end + + def no_longer_a_test_buffer_allocations + assert_equal 'TURKISH*ı'*10, ('I'*10).downcase(:turkic) + assert_equal 'TURKISH*ı'*100, ('I'*100).downcase(:turkic) + 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..e9c81d804e --- /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'.dup.downcase! :fold } + assert_raise(ArgumentError) { 'a'.dup.upcase! :fold } + assert_raise(ArgumentError) { 'a'.dup.capitalize! :fold } + assert_raise(ArgumentError) { 'a'.dup.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_cesu8.rb b/test/ruby/enc/test_cesu8.rb new file mode 100644 index 0000000000..68a08389ea --- /dev/null +++ b/test/ruby/enc/test_cesu8.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestCESU8 < Test::Unit::TestCase + + def encdump(obj) + case obj + when String + obj.dump + when Regexp + "Regexp.new(#{encdump(obj.source)}, #{obj.options})" + else + raise Argument, "unexpected: #{obj.inspect}" + end + end + + def enccall(recv, meth, *args) + desc = '' + if String === recv + desc << encdump(recv) + else + desc << recv.inspect + end + desc << '.' << meth.to_s + if !args.empty? + desc << '(' + args.each_with_index {|a, i| + desc << ',' if 0 < i + if String === a + desc << encdump(a) + else + desc << a.inspect + end + } + desc << ')' + end + result = nil + assert_nothing_raised(desc) { + result = recv.send(meth, *args) + } + result + end + + def assert_str_equal(expected, actual, message=nil) + full_message = build_message(message, <<EOT) +#{encdump expected} expected but not equal to +#{encdump actual}. +EOT + assert_equal(expected, actual, full_message) + end + + # tests start + + def test_cesu8_valid_encoding + all_assertions do |a| + [ + "\x00", + "\x7f", + "\u0080", + "\u07ff", + "\u0800", + "\ud7ff", + "\xed\xa0\x80\xed\xb0\x80", + "\xed\xaf\xbf\xed\xbf\xbf", + "\ue000", + "\uffff", + ].each {|s| + s.force_encoding("cesu-8") + a.for(s) { + assert_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + [ + "\x80", + "\xc0\x80", + "\xc0", + "\xe0\x80\x80", + "\xed\xa0\x80", + "\xed\xb0\x80\xed\xb0\x80", + "\xe0", + "\xff", + ].each {|s| + s.force_encoding("cesu-8") + a.for(s) { + assert_not_predicate(s, :valid_encoding?, "#{encdump s}.valid_encoding?") + } + } + end + end + + def test_cesu8_ord + [ + ["\x00", 0], + ["\x7f", 0x7f], + ["\u0080", 0x80], + ["\u07ff", 0x7ff], + ["\u0800", 0x800], + ["\ud7ff", 0xd7ff], + ["\xed\xa0\x80\xed\xb0\x80", 0x10000], + ["\xed\xaf\xbf\xed\xbf\xbf", 0x10ffff], + ["\xee\x80\x80", 0xe000], + ["\xef\xbf\xbf", 0xffff], + ].each do |chr, ord| + chr.force_encoding("cesu-8") + assert_equal ord, chr.ord + assert_equal chr, ord.chr("cesu-8") + end + end + + def test_cesu8_left_adjust_char_head + assert_equal("", "\u{10000}".encode("cesu-8").chop) + end +end diff --git a/test/ruby/enc/test_cp949.rb b/test/ruby/enc/test_cp949.rb new file mode 100644 index 0000000000..0684162d5b --- /dev/null +++ b/test/ruby/enc/test_cp949.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require "test/unit" + +class TestCP949 < Test::Unit::TestCase + def s(s) + s.force_encoding("cp949") + end + + def test_mbc_enc_len + assert_equal(1, s("\xa1\xa1").size) + end + + def test_mbc_to_code + assert_equal(0xa1a1, s("\xa1\xa1").ord) + end + + def test_code_to_mbc + assert_equal(s("\xa1\xa1"), 0xa1a1.chr("cp949")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\xa1\xa1)\\1"), "i") + assert_match(r, s("\xa1\xa1\xa1\xa1")) + end + + def test_left_adjust_char_head + assert_equal(s("\xa1\xa1"), s("\xa1\xa1\xa1\xa1").chop) + end +end diff --git a/test/ruby/enc/test_emoji.rb b/test/ruby/enc/test_emoji.rb new file mode 100644 index 0000000000..330ff70cb9 --- /dev/null +++ b/test/ruby/enc/test_emoji.rb @@ -0,0 +1,443 @@ +# frozen_string_literal: false +require 'test/unit' + +module Emoji + + class TestRenameSJIS < Test::Unit::TestCase + def test_shift_jis + assert_raise(ArgumentError) { "".force_encoding("Shift_JIS-DoCoMo") } + assert_raise(ArgumentError) { "".force_encoding("Shift_JIS-KDDI") } + assert_raise(ArgumentError) { "".force_encoding("Shift_JIS-SoftBank") } + end + end + + class TestUTF8_BLACK_SUN_WITH_RAYS < Test::Unit::TestCase + include Emoji + + def setup + @codes = { + "UTF8-DoCoMo" => utf8_docomo("\u{E63E}"), + "UTF8-KDDI" => utf8_kddi("\u{E488}"), + "UTF8-SoftBank" => utf8_softbank("\u{E04A}"), + "UTF-8" => "\u{2600}", + } + end + + def test_convert + @codes.each do |from_enc, from_str| + @codes.each do |to_enc, to_str| + next if from_enc == to_enc + assert_equal to_str, from_str.encode(to_enc), "convert from #{from_enc} to #{to_enc}" + end + end + end + end + + class TestDoCoMo < Test::Unit::TestCase + include Emoji + + def setup + setup_instance_variable(self) + end + + def test_encoding_name + %w(UTF8-DoCoMo + SJIS-DoCoMo).each do |n| + assert_include Encoding.name_list, n, "encoding not found: #{n}" + end + end + + def test_comparison + assert_not_equal Encoding::UTF_8, Encoding::UTF8_DoCoMo + assert_not_equal Encoding::Windows_31J, Encoding::SJIS_DoCoMo + end + + def test_from_utf8 + assert_nothing_raised { assert_equal utf8_docomo(@aiueo_utf8), to_utf8_docomo(@aiueo_utf8) } + assert_nothing_raised { assert_equal sjis_docomo(@aiueo_sjis), to_sjis_docomo(@aiueo_utf8) } + end + + def test_from_sjis + assert_nothing_raised { assert_equal utf8_docomo(@aiueo_utf8), to_utf8_docomo(@aiueo_sjis) } + assert_nothing_raised { assert_equal sjis_docomo(@aiueo_sjis), to_sjis_docomo(@aiueo_sjis) } + end + + def test_to_utf8 + assert_nothing_raised { assert_equal @utf8, to_utf8(@utf8_docomo) } + assert_nothing_raised { assert_equal @utf8, to_utf8(@sjis_docomo) } + end + + def test_to_sjis + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@utf8_docomo) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@sjis_docomo) } + end + + def test_to_eucjp + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@utf8_docomo) } + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@sjis_docomo) } + end + + def test_docomo + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@sjis_docomo) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_docomo) } + end + + def test_to_kddi + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@utf8_docomo) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@utf8_docomo) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@utf8_docomo) } + + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@sjis_docomo) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@sjis_docomo) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@sjis_docomo) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_kddi(@utf8_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_kddi(@utf8_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_iso2022jp_kddi(@utf8_docomo_only) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_kddi(@sjis_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_kddi(@sjis_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_iso2022jp_kddi(@sjis_docomo_only) } + end + + def test_to_softbank + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@utf8_docomo) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_docomo) } + + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@sjis_docomo) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@sjis_docomo) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_softbank(@utf8_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_softbank(@utf8_docomo_only) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_softbank(@sjis_docomo_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_softbank(@sjis_docomo_only) } + end + end + + class TestKDDI < Test::Unit::TestCase + include Emoji + + def setup + setup_instance_variable(self) + end + + def test_encoding_name + %w(UTF8-KDDI + SJIS-KDDI + ISO-2022-JP-KDDI + stateless-ISO-2022-JP-KDDI).each do |n| + assert_include Encoding.name_list, n, "encoding not found: #{n}" + end + end + + def test_comparison + assert_not_equal Encoding::UTF_8, Encoding::UTF8_KDDI + assert_not_equal Encoding::Windows_31J, Encoding::SJIS_KDDI + assert_not_equal Encoding::ISO_2022_JP, Encoding::ISO_2022_JP_KDDI + assert_not_equal Encoding::Stateless_ISO_2022_JP, Encoding::Stateless_ISO_2022_JP_KDDI + end + + def test_from_utf8 + assert_nothing_raised { assert_equal utf8_kddi(@aiueo_utf8), to_utf8_kddi(@aiueo_utf8) } + assert_nothing_raised { assert_equal sjis_kddi(@aiueo_sjis), to_sjis_kddi(@aiueo_utf8) } + assert_nothing_raised { assert_equal iso2022jp_kddi(@aiueo_iso2022jp), to_iso2022jp_kddi(@aiueo_utf8) } + end + + def test_from_sjis + assert_nothing_raised { assert_equal utf8_kddi(@aiueo_utf8), to_utf8_kddi(@aiueo_sjis) } + assert_nothing_raised { assert_equal sjis_kddi(@aiueo_sjis), to_sjis_kddi(@aiueo_sjis) } + assert_nothing_raised { assert_equal iso2022jp_kddi(@aiueo_iso2022jp), to_iso2022jp_kddi(@aiueo_sjis) } + end + + def test_from_iso2022jp + assert_nothing_raised { assert_equal utf8_kddi(@aiueo_utf8), to_utf8_kddi(@aiueo_iso2022jp) } + assert_nothing_raised { assert_equal sjis_kddi(@aiueo_sjis), to_sjis_kddi(@aiueo_iso2022jp) } + assert_nothing_raised { assert_equal iso2022jp_kddi(@aiueo_iso2022jp), to_iso2022jp_kddi(@aiueo_iso2022jp) } + end + + def test_to_utf8 + assert_nothing_raised { assert_equal @utf8, to_utf8(@utf8_kddi) } + assert_nothing_raised { assert_equal @utf8, to_utf8(@utf8_undoc_kddi) } + assert_nothing_raised { assert_equal @utf8, to_utf8(@sjis_kddi) } + assert_nothing_raised { assert_equal @utf8, to_utf8(@iso2022jp_kddi) } + end + + def test_to_sjis + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@utf8_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@utf8_undoc_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@sjis_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@iso2022jp_kddi) } + end + + def test_to_eucjp + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@utf8_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@utf8_undoc_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@sjis_kddi) } + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@iso2022jp_kddi) } + end + + def test_kddi + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@sjis_kddi) } + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@iso2022jp_kddi) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@sjis_kddi) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@utf8_undoc_kddi) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@iso2022jp_kddi) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@sjis_kddi) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@utf8_undoc_kddi) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@iso2022jp_kddi) } + end + + def test_to_docomo + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@utf8_kddi) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_kddi) } + + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@utf8_undoc_kddi) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_undoc_kddi) } + + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@sjis_kddi) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@sjis_kddi) } + + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@iso2022jp_kddi) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@iso2022jp_kddi) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_docomo, to_utf8_docomo(@utf8_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_docomo, to_utf8_docomo(@utf8_undoc_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_undoc_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_docomo, to_utf8_docomo(@sjis_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_docomo, to_sjis_docomo(@sjis_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_docomo, to_utf8_docomo(@iso2022jp_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_docomo, to_sjis_docomo(@iso2022jp_kddi_only) } + end + + def test_to_softbank + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@utf8_kddi) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_kddi) } + + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@utf8_undoc_kddi) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_undoc_kddi) } + + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@sjis_kddi) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@sjis_kddi) } + + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@iso2022jp_kddi) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@iso2022jp_kddi) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_softbank, to_utf8_softbank(@utf8_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_softbank, to_utf8_softbank(@utf8_undoc_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_undoc_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_softbank, to_utf8_softbank(@sjis_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_softbank, to_sjis_softbank(@sjis_kddi_only) } + + assert_raise(Encoding::UndefinedConversionError) { assert_equal @utf8_softbank, to_utf8_softbank(@iso2022jp_kddi_only) } + assert_raise(Encoding::UndefinedConversionError) { assert_equal @sjis_softbank, to_sjis_softbank(@iso2022jp_kddi_only) } + end + end + + class TestSoftBank < Test::Unit::TestCase + include Emoji + + def setup + setup_instance_variable(self) + end + + def test_encoding_name + %w(UTF8-SoftBank + SJIS-SoftBank).each do |n| + assert_include Encoding.name_list, n, "encoding not found: #{n}" + end + end + + def test_comparison + assert_not_equal Encoding::UTF_8, Encoding::UTF8_SoftBank + assert_not_equal Encoding::Windows_31J, Encoding::SJIS_SoftBank + end + + def test_from_utf8 + assert_nothing_raised { assert_equal utf8_softbank(@aiueo_utf8), to_utf8_softbank(@aiueo_utf8) } + assert_nothing_raised { assert_equal sjis_softbank(@aiueo_sjis), to_sjis_softbank(@aiueo_utf8) } + end + + def test_from_sjis + assert_nothing_raised { assert_equal utf8_softbank(@aiueo_utf8), to_utf8_softbank(@aiueo_sjis) } + assert_nothing_raised { assert_equal sjis_softbank(@aiueo_sjis), to_sjis_softbank(@aiueo_sjis) } + end + + def test_to_utf8 + assert_nothing_raised { assert_equal @utf8, to_utf8(@utf8_softbank) } + assert_nothing_raised { assert_equal @utf8, to_utf8(@sjis_softbank) } + end + + def test_to_sjis + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@utf8_softbank) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis(@sjis_softbank) } + end + + def test_to_eucjp + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@utf8_softbank) } + assert_raise(Encoding::UndefinedConversionError) { to_eucjp(@sjis_softbank) } + end + + def test_softbank + assert_nothing_raised { assert_equal @utf8_softbank, to_utf8_softbank(@sjis_softbank) } + assert_nothing_raised { assert_equal @sjis_softbank, to_sjis_softbank(@utf8_softbank) } + end + + def test_to_docomo + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@utf8_softbank) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@utf8_softbank) } + + assert_nothing_raised { assert_equal @utf8_docomo, to_utf8_docomo(@sjis_softbank) } + assert_nothing_raised { assert_equal @sjis_docomo, to_sjis_docomo(@sjis_softbank) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_docomo(@utf8_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_docomo(@utf8_softbank_only) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_docomo(@sjis_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_docomo(@sjis_softbank_only) } + end + + def test_to_kddi + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@utf8_softbank) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@utf8_softbank) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@utf8_softbank) } + + assert_nothing_raised { assert_equal @utf8_kddi, to_utf8_kddi(@sjis_softbank) } + assert_nothing_raised { assert_equal @sjis_kddi, to_sjis_kddi(@sjis_softbank) } + assert_nothing_raised { assert_equal @iso2022jp_kddi, to_iso2022jp_kddi(@sjis_softbank) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_kddi(@utf8_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_kddi(@utf8_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_iso2022jp_kddi(@utf8_softbank_only) } + + assert_raise(Encoding::UndefinedConversionError) { to_utf8_kddi(@sjis_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_sjis_kddi(@sjis_softbank_only) } + assert_raise(Encoding::UndefinedConversionError) { to_iso2022jp_kddi(@sjis_softbank_only) } + end + end + + private + + def setup_instance_variable(obj) + obj.instance_eval do + @aiueo_utf8 = "\u{3042}\u{3044}\u{3046}\u{3048}\u{304A}" + @aiueo_sjis = to_sjis(@aiueo_utf8) + @aiueo_iso2022jp = to_iso2022jp(@aiueo_utf8) + + @utf8 = "\u{2600}" + + @utf8_docomo = utf8_docomo("\u{E63E}") + @sjis_docomo = sjis_docomo("\xF8\x9F") + @utf8_docomo_only = utf8_docomo("\u{E6B1}") + @sjis_docomo_only = sjis_docomo("\xF9\x55") + + @utf8_kddi = utf8_kddi("\u{E488}") + @utf8_undoc_kddi = utf8_kddi("\u{EF60}") + @sjis_kddi = sjis_kddi("\xF6\x60") + @iso2022jp_kddi = iso2022jp_kddi("\x1B$B\x75\x41\x1B(B") + @stateless_iso2022jp_kddi = stateless_iso2022jp_kddi("\x92\xF5\xC1") + @utf8_kddi_only = utf8_kddi("\u{E5B3}") + @utf8_undoc_kddi_only = utf8_kddi("\u{F0D0}") + @sjis_kddi_only = sjis_kddi("\xF7\xD0") + @iso2022jp_kddi_only = iso2022jp_kddi("\x1B$B\x78\x52\x1B(B") + @stateless_iso2022jp_kddi_only = stateless_iso2022jp_kddi("\x92\xF8\xD2") + + @utf8_softbank = utf8_softbank("\u{E04A}") + @sjis_softbank = sjis_softbank("\xF9\x8B") + @utf8_softbank_only = utf8_softbank("\u{E524}") + @sjis_softbank_only = sjis_softbank("\xFB\xC4") + end + end + + def utf8(str) + str.force_encoding("UTF-8") + end + + def to_utf8(str) + str.encode("UTF-8") + end + + def to_sjis(str) + str.encode("Windows-31J") + end + + def to_eucjp(str) + str.encode("eucJP-ms") + end + + def to_iso2022jp(str) + str.encode("ISO-2022-JP") + end + + def utf8_docomo(str) + str.force_encoding("UTF8-DoCoMo") + end + + def to_utf8_docomo(str) + str.encode("UTF8-DoCoMo") + end + + def utf8_kddi(str) + str.force_encoding("UTF8-KDDI") + end + + def to_utf8_kddi(str) + str.encode("UTF8-KDDI") + end + + def utf8_softbank(str) + str.force_encoding("UTF8-SoftBank") + end + + def to_utf8_softbank(str) + str.encode("UTF8-SoftBank") + end + + def sjis_docomo(str) + str.force_encoding("SJIS-DoCoMo") + end + + def to_sjis_docomo(str) + str.encode("SJIS-DoCoMo") + end + + def sjis_kddi(str) + str.force_encoding("SJIS-KDDI") + end + + def to_sjis_kddi(str) + str.encode("SJIS-KDDI") + end + + def sjis_softbank(str) + str.force_encoding("SJIS-SoftBank") + end + + def to_sjis_softbank(str) + str.encode("SJIS-SoftBank") + end + + def iso2022jp_kddi(str) + str.force_encoding("ISO-2022-JP-KDDI") + end + + def to_iso2022jp_kddi(str) + str.encode("ISO-2022-JP-KDDI") + end + + def stateless_iso2022jp_kddi(str) + str.force_encoding("stateless-ISO-2022-JP-KDDI") + end + + def to_stateless_iso2022jp_kddi(str) + str.encode("stateless-ISO-2022-JP-KDDI") + 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..0873e681c3 --- /dev/null +++ b/test/ruby/enc/test_emoji_breaks.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestEmojiBreaks < Test::Unit::TestCase + class BreakTest + attr_reader :string, :comment, :filename, :line_number, :type, :shortname + + def initialize(filename, line_number, data, comment='') + @filename = filename + @line_number = line_number + @comment = comment.gsub(/\s+/, ' ').strip + if filename=='emoji-test' or filename=='emoji-variation-sequences' + codes, @type = data.split(/\s*;\s*/) + @shortname = '' + else + codes, @type, @shortname = data.split(/\s*;\s*/) + end + @type = @type.gsub(/\s+/, ' ').strip + @shortname = @shortname.gsub(/\s+/, ' ').strip + @string = codes.split(/\s+/) + .map do |ch| + c = ch.to_i(16) + # eliminate cases with surrogates + # raise ArgumentError if 0xD800 <= c and c <= 0xDFFF + c.chr('UTF-8') + end.join + end + end + + class BreakFile + attr_reader :basename, :fullname, :version + FILES = [] + + def initialize(basename, path, version) + @basename = basename + @fullname = "#{path}/#{basename}.txt" # File.expand_path(path + version, __dir__) + @version = version + FILES << self + end + + def self.files + FILES + end + end + + UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION'] + UNICODE_DATA_PATH = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}/ucd/emoji", __dir__) + EMOJI_VERSION = RbConfig::CONFIG['UNICODE_EMOJI_VERSION'] + EMOJI_DATA_PATH = File.expand_path("../../../enc/unicode/data/emoji/#{EMOJI_VERSION}", __dir__) + + EMOJI_DATA_FILES = %w[emoji-sequences emoji-test emoji-zwj-sequences].map do |basename| + BreakFile.new(basename, EMOJI_DATA_PATH, EMOJI_VERSION) + end + UNICODE_DATA_FILE = BreakFile.new('emoji-variation-sequences', UNICODE_DATA_PATH, EMOJI_VERSION) + EMOJI_DATA_FILES << UNICODE_DATA_FILE + + def self.data_files_available? + EMOJI_DATA_FILES.all? do |f| + File.exist?(f.fullname) + end + end + + def test_data_files_available + assert_equal 4, EMOJI_DATA_FILES.size # debugging test + unless TestEmojiBreaks.data_files_available? + omit "Emoji data files not available in #{EMOJI_DATA_PATH}." + end + end + + if data_files_available? + def read_data + tests = [] + EMOJI_DATA_FILES.each do |file| + version_mismatch = true + file_tests = [] + File.foreach(file.fullname, encoding: Encoding::UTF_8) do |line| + line.chomp! + if $.==1 + if line=="# #{file.basename}-#{file.version}.txt" + version_mismatch = false + elsif line!="# #{file.basename}.txt" + raise "File Name Mismatch: line: #{line}, expected filename: #{file.basename}.txt" + end + end + version_mismatch = false if line =~ /^# Version: #{file.version}/ # 13.0 and older + version_mismatch = false if line =~ /^# Used with Emoji Version #{EMOJI_VERSION}/ # 14.0 and newer + next if line.match?(/\A(#|\z)/) + if line =~ /^(\h{4,6})\.\.(\h{4,6}) *(;.+)/ # deal with Unicode ranges in emoji-sequences.txt (Bug #18028) + range_start = $1.to_i(16) + range_end = $2.to_i(16) + rest = $3 + (range_start..range_end).each do |code_point| + file_tests << BreakTest.new(file.basename, $., *(code_point.to_s(16)+rest).split('#', 2)) + end + else + file_tests << BreakTest.new(file.basename, $., *line.split('#', 2)) + end + end + raise "File Version Mismatch: file: #{file.fullname}, version: #{file.version}" if version_mismatch + tests += file_tests + end + tests + end + + def all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def test_single_emoji + all_tests.each do |test| + expected = [test.string] + actual = test.string.each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + def test_embedded_emoji + all_tests.each do |test| + expected = ["\t", test.string, "\t"] + actual = "\t#{test.string}\t".each_grapheme_cluster.to_a + assert_equal expected, actual, + "file: #{test.filename}, line #{test.line_number}, " + + "type: #{test.type}, shortname: #{test.shortname}, comment: #{test.comment}" + end + end + + # test some pseodorandom combinations of emoji + def test_mixed_emoji + srand 0 + length = all_tests.length + step = 503 # use a prime number + all_tests.each do |test1| + start = rand step + start.step(by: step, to: length-1) do |t2| + test2 = all_tests[t2] + # exclude skin tones, because they glue to previous grapheme clusters + next if (0x1F3FB..0x1F3FF).include? test2.string.ord + expected = [test1.string, test2.string] + actual = (test1.string+test2.string).each_grapheme_cluster.to_a + assert_equal expected, actual, + "file1: #{test1.filename}, line1 #{test1.line_number}, " + + "file2: #{test2.filename}, line2 #{test2.line_number},\n" + + "type1: #{test1.type}, shortname1: #{test1.shortname}, comment1: #{test1.comment},\n" + + "type2: #{test2.type}, shortname2: #{test2.shortname}, comment2: #{test2.comment}" + end + end + end + end +end diff --git a/test/ruby/enc/test_euc_jp.rb b/test/ruby/enc/test_euc_jp.rb new file mode 100644 index 0000000000..4aec69e4db --- /dev/null +++ b/test/ruby/enc/test_euc_jp.rb @@ -0,0 +1,25 @@ +# 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, "£áa£áA") + assert_match(/(£á)(a)\1\2/i, "£áa£ÁA") + end + + def test_property + assert_match(/¤¢{0}\p{Hiragana}{4}/, "¤Ò¤é¤¬¤Ê") + assert_no_match(/¤¢{0}\p{Hiragana}{4}/, "¥«¥¿¥«¥Ê") + assert_no_match(/¤¢{0}\p{Hiragana}{4}/, "´Á»ú´Á»ú") + assert_no_match(/¤¢{0}\p{Katakana}{4}/, "¤Ò¤é¤¬¤Ê") + assert_match(/¤¢{0}\p{Katakana}{4}/, "¥«¥¿¥«¥Ê") + assert_no_match(/¤¢{0}\p{Katakana}{4}/, "´Á»ú´Á»ú") + assert_raise(RegexpError) { Regexp.new('¤¢{0}\p{foobarbaz}') } + end + + def test_charboundary + assert_nil(/\xA2\xA2/ =~ "\xA1\xA2\xA2\xA3") + end +end diff --git a/test/ruby/enc/test_euc_kr.rb b/test/ruby/enc/test_euc_kr.rb new file mode 100644 index 0000000000..c9de2cc4e1 --- /dev/null +++ b/test/ruby/enc/test_euc_kr.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: false +require "test/unit" + +class TestEucKr < Test::Unit::TestCase + def s(s) + s.force_encoding("euc-kr") + end + + def test_mbc_enc_len + assert_equal(1, s("\xa1\xa1").size) + end + + def test_mbc_to_code + assert_equal(0xa1a1, s("\xa1\xa1").ord) + end + + def test_code_to_mbc + assert_equal(s("\xa1\xa1"), 0xa1a1.chr("euc-kr")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\xa1\xa1)\\1"), "i") + assert_match(r, s("\xa1\xa1\xa1\xa1")) + end + + 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 new file mode 100644 index 0000000000..649b1b81c6 --- /dev/null +++ b/test/ruby/enc/test_euc_tw.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require "test/unit" + +class TestEucTw < Test::Unit::TestCase + def s(s) + s.force_encoding("euc-tw") + end + + def test_mbc_enc_len + assert_equal(1, s("\xa1\xa1").size) + end + + def test_mbc_to_code + assert_equal(0xa1a1, s("\xa1\xa1").ord) + end + + def test_code_to_mbc + assert_equal(s("\xa1\xa1"), 0xa1a1.chr("euc-tw")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\xa1\xa1)\\1"), "i") + assert_match(r, s("\xa1\xa1\xa1\xa1")) + end + + def test_left_adjust_char_head + assert_equal(s("\xa1\xa1"), s("\xa1\xa1\xa1\xa1").chop) + end +end diff --git a/test/ruby/enc/test_gb18030.rb b/test/ruby/enc/test_gb18030.rb new file mode 100644 index 0000000000..76ac785951 --- /dev/null +++ b/test/ruby/enc/test_gb18030.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: false +require "test/unit" + +class TestGB18030 < Test::Unit::TestCase + def s(s) + s.force_encoding("gb18030") + end + + def test_mbc_enc_len + assert_equal(1, s("\x81\x40").size) + assert_equal(1, s("\x81\x30\x81\x30").size) + end + + def test_mbc_to_code + assert_equal(0x8140, s("\x81\x40").ord) + end + + def test_code_to_mbc + assert_equal(s("\x81\x40"), 0x8140.chr("gb18030")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\x81\x40)\\1"), "i") + assert_match(r, s("\x81\x40\x81\x40")) + end + + def scheck(c, i) + assert_equal(s(c.reverse.take(c.size - i).join), s(c.reverse.join).chop) + end + + def fcheck(c) + c = s(c.reverse.join) + assert_raise(ArgumentError, c) { c.chop } + end + + def test_left_adjust_char_head + # C1: 00-2f, 3a-3f, 7f, ff + # C2: 40-7e, 80 + # C4: 30-39 + # CM: 81-fe + c1 = "\x2f" + c2 = "\x40" + c4 = "\x30" + cm = "\x81" + + # S_START-c1 + # S_START-c2-S_one_C2-0 + # S_START-c2-S_one_C2-c1 + # S_START-c2-S_one_C2-cm-S_odd_CM_one_CX-c1 + # S_START-c2-S_one_C2-cm-S_odd_CM_one_CX-cm-S_even_CM_one_CX-c1 + # S_START-c2-S_one_C2-cm-S_odd_CM_one_CX-cm-S_even_CM_one_CX-cm-S_odd_CM_one_CX(rec) + # S_START-c4-S_one_C4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-cm-S_odd_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-cm-S_odd_CMC4-c4-S_one_C4_odd_CMC4(rec) + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-cm-S_odd_CMC4-cm-S_odd_CM_odd_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-cm-S_odd_CMC4-cm-S_odd_CM_odd_CMC4-cm-S_even_CM_odd_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-c4-S_one_C4_even_CMC4-cm-S_odd_CMC4-cm-S_odd_CM_odd_CMC4-cm-S_even_CM_odd_CMC4-cm-S_odd_CM_odd_CMC4(rec) + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-cm-S_odd_CM_even_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-cm-S_odd_CM_even_CMC4-cm-S_even_CM_even_CMC4-c1 + # S_START-c4-S_one_C4-cm-S_one_CMC4-c4-S_one_C4_odd_CMC4-cm-S_even_CMC4-cm-S_odd_CM_even_CMC4-cm-S_even_CM_even_CMC4-cm-S_odd_CM_even_CMC4(rec) + # S_START-c4-S_one_C4-cm-S_one_CMC4-cm-S_even_CM_one_CX(rec) + # S_START-cm-S_one_CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-cm-S_one_CM_even_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-cm-S_one_CM_even_C4CM-c4-S_odd_C4CM(rec) + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-cm-S_one_CM_even_C4CM-cm-S_even_CM_even_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-cm-S_one_CM_even_C4CM-cm-S_even_CM_even_C4CM-cm-S_odd_CM_even_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-c4-S_even_C4CM-cm-S_one_CM_even_C4CM-cm-S_even_CM_even_C4CM-cm-S_odd_CM_even_C4CM-cm-S_even_CM_even_C4CM(rec) + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-cm-S_even_CM_odd_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-cm-S_even_CM_odd_C4CM-cm-S_odd_CM_odd_C4CM-c1 + # S_START-cm-S_one_CM-c4-S_odd_C4CM-cm-S_one_CM_odd_C4CM-cm-S_even_CM_odd_C4CM-cm-S_odd_CM_odd_C4CM-cm-S_even_CM_odd_C4CM(rec) + # S_START-cm-S_one_CM-cm-S_odd_CM_one_CX(rec) + + scheck([c1], 1) + scheck([c2], 1) + scheck([c2, c1], 1) + scheck([c2, cm, c1], 2) + scheck([c2, cm, cm, c1], 1) + scheck([c2, cm, cm, cm], 2) + scheck([c4], 1) + scheck([c4, c1], 1) + scheck([c4, cm], 2) + scheck([c4, cm, c1], 2) + scheck([c4, cm, c4, c1], 2) + scheck([c4, cm, c4, cm], 4) + scheck([c4, cm, c4, cm, c1], 4) + scheck([c4, cm, c4, cm, c4], 4) + scheck([c4, cm, c4, cm, c4, c1], 4) + scheck([c4, cm, c4, cm, c4, cm], 2) + scheck([c4, cm, c4, cm, c4, cm, c1], 2) + scheck([c4, cm, c4, cm, c4, cm, c4], 2) + scheck([c4, cm, c4, cm, c4, cm, cm, c1], 4) + scheck([c4, cm, c4, cm, c4, cm, cm, cm], 2) + scheck([c4, cm, c4, cm, c4, cm, cm, cm, c1], 2) + scheck([c4, cm, c4, cm, c4, cm, cm, cm, cm], 4) + scheck([c4, cm, c4, cm, cm, c1], 2) + scheck([c4, cm, c4, cm, cm, cm], 4) + scheck([c4, cm, c4, cm, cm, cm, c1], 4) + scheck([c4, cm, c4, cm, cm, cm, cm], 2) + scheck([c4, cm, cm], 1) + scheck([cm], 1) + scheck([cm, c1], 1) + scheck([cm, c4, c1], 1) + scheck([cm, c4, cm], 3) + scheck([cm, c4, cm, c1], 3) + scheck([cm, c4, cm, c4], 3) + scheck([cm, c4, cm, c4, c1], 3) + scheck([cm, c4, cm, c4, cm], 1) + scheck([cm, c4, cm, c4, cm, c1], 1) + scheck([cm, c4, cm, c4, cm, c4], 1) + scheck([cm, c4, cm, c4, cm, cm, c1], 3) + scheck([cm, c4, cm, c4, cm, cm, cm], 1) + scheck([cm, c4, cm, c4, cm, cm, cm, c1], 1) + scheck([cm, c4, cm, c4, cm, cm, cm, cm], 3) + scheck([cm, c4, cm, cm, c1], 1) + scheck([cm, c4, cm, cm, cm], 3) + scheck([cm, c4, cm, cm, cm, c1], 3) + scheck([cm, c4, cm, cm, cm, cm], 1) + scheck([cm, cm], 2) + end +end diff --git a/test/ruby/enc/test_gbk.rb b/test/ruby/enc/test_gbk.rb new file mode 100644 index 0000000000..2e541b5821 --- /dev/null +++ b/test/ruby/enc/test_gbk.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require "test/unit" + +class TestGBK < Test::Unit::TestCase + def s(s) + s.force_encoding("gbk") + end + + def test_mbc_enc_len + assert_equal(1, s("\x81\x40").size) + end + + def test_mbc_to_code + assert_equal(0x8140, s("\x81\x40").ord) + end + + def test_code_to_mbc + assert_equal(s("\x81\x40"), 0x8140.chr("gbk")) + end + + def test_mbc_case_fold + r = Regexp.new(s("(\x81\x40)\\1"), "i") + assert_match(r, s("\x81\x40\x81\x40")) + end + + def test_left_adjust_char_head + assert_equal(s("\x81\x40"), s("\x81\x40\x81\x40").chop) + end +end diff --git a/test/ruby/enc/test_grapheme_breaks.rb b/test/ruby/enc/test_grapheme_breaks.rb new file mode 100644 index 0000000000..7e6d722d40 --- /dev/null +++ b/test/ruby/enc/test_grapheme_breaks.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +# Copyright © 2018 Martin J. Dürst (duerst@it.aoyama.ac.jp) + +require "test/unit" + +class TestGraphemeBreaksFromFile < Test::Unit::TestCase + class BreakTest + attr_reader :clusters, :string, :comment, :line_number + + def initialize(line_number, data, comment) + @line_number = line_number + @comment = comment + @clusters = data.sub(/\A\s*÷\s*/, '') + .sub(/\s*÷\s*\z/, '') + .split(/\s*÷\s*/) + .map do |cl| + cl.split(/\s*×\s*/) + .map do |ch| + c = ch.to_i(16) + # eliminate cases with surrogates + raise ArgumentError if 0xD800 <= c and c <= 0xDFFF + c.chr('UTF-8') + end.join + end + @string = @clusters.join + end + end + + UNICODE_VERSION = RbConfig::CONFIG['UNICODE_VERSION'] + path = File.expand_path("../../../enc/unicode/data/#{UNICODE_VERSION}", __dir__) + UNICODE_DATA_PATH = File.directory?("#{path}/ucd/auxiliary") ? "#{path}/ucd/auxiliary" : path + GRAPHEME_BREAK_TEST_FILE = File.expand_path("#{UNICODE_DATA_PATH}/GraphemeBreakTest.txt", __dir__) + + def self.file_available? + File.exist? GRAPHEME_BREAK_TEST_FILE + end + + def test_data_files_available + unless TestGraphemeBreaksFromFile.file_available? + omit "Unicode data file GraphemeBreakTest not available in #{UNICODE_DATA_PATH}." + end + end + + if file_available? + def read_data + tests = [] + File.foreach(GRAPHEME_BREAK_TEST_FILE, encoding: Encoding::UTF_8) do |line| + if $. == 1 and not line.start_with?("# GraphemeBreakTest-#{UNICODE_VERSION}.txt") + raise "File Version Mismatch" + end + next if /\A#/.match? line + tests << BreakTest.new($., *line.chomp.split('#')) rescue 'whatever' + end + tests + end + + def all_tests + @@tests ||= read_data + rescue Errno::ENOENT + @@tests ||= [] + end + + def test_each_grapheme_cluster + all_tests.each do |test| + expected = test.clusters + actual = test.string.each_grapheme_cluster.to_a + assert_equal expected, actual, + "line #{test.line_number}, expected '#{expected}', " + + "but got '#{actual}', comment: #{test.comment}" + end + end + + def test_backslash_X + all_tests.each do |test| + clusters = test.clusters.dup + string = test.string.dup + removals = 0 + while string.sub!(/\A\X/, '') + removals += 1 + clusters.shift + expected = clusters.join + assert_equal expected, string, + "line #{test.line_number}, removals: #{removals}, expected '#{expected}', " + + "but got '#{string}', comment: #{test.comment}" + end + assert_equal expected, string, + "line #{test.line_number}, after last removal, expected '#{expected}', " + + "but got '#{string}', comment: #{test.comment}" + end + end + end +end diff --git a/test/ruby/enc/test_iso_8859.rb b/test/ruby/enc/test_iso_8859.rb new file mode 100644 index 0000000000..ed663be243 --- /dev/null +++ b/test/ruby/enc/test_iso_8859.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestISO8859 < Test::Unit::TestCase + ASSERTS = %q( + 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") + ((0xc0..0xde).to_a - [0xd7]).each do |c| + c1 = c.chr("ENCODING") + c2 = (c + 0x20).chr("ENCODING") + 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 + assert_match(/^\xff$/i, "\xff") + ) + + def test_iso_8859_1 + eval("# encoding: iso8859-1\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-1")) + end + + def test_iso_8859_2 + eval("# encoding: iso8859-2\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-2")) + 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, *(0xaa..0xac), 0xaf].each do |c| + c1 = c.chr("iso8859-3") + c2 = (c + 0x10).chr("iso8859-3") + 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 + ([*(0xc0..0xde)] - [0xc3, 0xd0, 0xd7]).each do |c| + c1 = c.chr("iso8859-3") + c2 = (c + 0x20).chr("iso8859-3") + 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 + + def test_iso_8859_4 + eval("# encoding: iso8859-4\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-4")) + end + + def test_iso_8859_5 + eval(%q(# encoding: iso8859-5 + (0xb0..0xcf).each do |c| + c1 = c.chr("iso8859-5") + c2 = (c + 0x20).chr("iso8859-5") + 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 + ((0xa1..0xaf).to_a - [0xad]).each do |c| + c1 = c.chr("iso8859-5") + c2 = (c + 0x50).chr("iso8859-5") + 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 + + def test_iso_8859_6 + eval(%q(# encoding: iso8859-6 + [0xa4, 0xac, 0xbb, 0xbf, *(0xc1..0xda), *(0xe0..0xf2)].each do |c| + c1 = c.chr("iso8859-6") + assert_match(/^(#{ c1 })\1$/i, c1 * 2) + end + )) + end + + def test_iso_8859_7 + eval(%q(# encoding: iso8859-7 + ((0xa0..0xfe).to_a - [0xae, 0xd2]).each do |c| + c1 = c.chr("iso8859-7") + assert_match(/^(#{ c1 })\1$/i, c1 * 2) + end + ((0xc1..0xd9).to_a - [0xd2]).each do |c| + c1 = c.chr("iso8859-7") + c2 = (c + 0x20).chr("iso8859-7") + 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 + + def test_iso_8859_8 + eval(%q(# encoding: iso8859-8 + [0xa0, *(0xa2..0xbe), *(0xdf..0xfa), 0xfc, 0xfd].each do |c| + c1 = c.chr("iso8859-8") + assert_match(/^(#{ c1 })\1$/i, c1 * 2) + end + )) + end + + def test_iso_8859_9 + eval(%q(# encoding: iso8859-9 + 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") + ([*(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) + assert_match(/^(#{ c2 })\1$/i, c1 + c2) + assert_match(/^[#{ c1 }]+$/i, c2 + c1) + assert_match(/^[#{ c2 }]+$/i, c1 + c2) + end + )) + end + + def test_iso_8859_10 + eval("# encoding: iso8859-10\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-10")) + end + + def test_iso_8859_11 + eval(%q(# encoding: iso8859-11 + [*(0xa0..0xda), *(0xdf..0xfb)].each do |c| + c1 = c.chr("iso8859-11") + assert_match(/^(#{ c1 })\1$/i, c1 * 2) + end + )) + end + + def test_iso_8859_13 + eval("# encoding: iso8859-13\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-13")) + end + + def test_iso_8859_14 + eval("# encoding: iso8859-14\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-14")) + end + + def test_iso_8859_15 + eval("# encoding: iso8859-15\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-15")) + end + + def test_iso_8859_16 + eval("# encoding: iso8859-16\n" + ASSERTS.gsub(/ENCODING/m, "iso8859-16")) + end +end + diff --git a/test/ruby/enc/test_koi8.rb b/test/ruby/enc/test_koi8.rb new file mode 100644 index 0000000000..4a4d233e8d --- /dev/null +++ b/test/ruby/enc/test_koi8.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: false +require "test/unit" + +class TestKOI8 < Test::Unit::TestCase + ASSERTS = %q( + (0xc0..0xdf).each do |c| + c1 = c.chr("ENCODING") + c2 = (c + 0x20).chr("ENCODING") + 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 + ) + + def test_koi8_r + eval("# encoding: koi8-r\n" + ASSERTS.gsub("ENCODING", "koi8-r")) + end + + def test_koi8_u + eval("# encoding: koi8-u\n" + ASSERTS.gsub("ENCODING", "koi8-u")) + end +end diff --git a/test/ruby/enc/test_regex_casefold.rb b/test/ruby/enc/test_regex_casefold.rb new file mode 100644 index 0000000000..b5d5c6e337 --- /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.dup + assert_equal expected, temp.downcase!(*flags) + assert_equal expected, expected.downcase(*flags) + temp = expected + assert_nil temp.downcase!(*flags) + end + + def read_tests + File.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 ||= [] + omit 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 new file mode 100644 index 0000000000..059992d167 --- /dev/null +++ b/test/ruby/enc/test_shift_jis.rb @@ -0,0 +1,28 @@ +# 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, "‚a‚A") + assert_match(/(‚)(a)\1\2/i, "‚a‚`A") + end + + def test_property + assert_match(/‚ {0}\p{Hiragana}{4}/, "‚Ђ炪‚È") + assert_no_match(/‚ {0}\p{Hiragana}{4}/, "ƒJƒ^ƒJƒi") + assert_no_match(/‚ {0}\p{Hiragana}{4}/, "Š¿ŽšŠ¿Žš") + assert_no_match(/‚ {0}\p{Katakana}{4}/, "‚Ђ炪‚È") + assert_match(/‚ {0}\p{Katakana}{4}/, "ƒJƒ^ƒJƒi") + assert_no_match(/‚ {0}\p{Katakana}{4}/, "Š¿ŽšŠ¿Žš") + assert_raise(RegexpError) { Regexp.new('‚ {0}\p{foobarbaz}') } + end + + def test_code_to_mbclen + s = "‚ ‚¢‚¤‚¦‚¨" + s << 0x82a9 + assert_equal("‚ ‚¢‚¤‚¦‚¨‚©", s) + assert_raise(RangeError) { s << 0x82 } + end +end diff --git a/test/ruby/enc/test_utf16.rb b/test/ruby/enc/test_utf16.rb new file mode 100644 index 0000000000..e08f2ea14e --- /dev/null +++ b/test/ruby/enc/test_utf16.rb @@ -0,0 +1,397 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestUTF16 < Test::Unit::TestCase + def encdump(obj) + case obj + when String + d = obj.dump + if /\.force_encoding\("[A-Za-z0-9.:_+-]*"\)\z/ =~ d + d + else + "#{d}.force_encoding(#{obj.encoding.name.dump})" + end + when Regexp + "Regexp.new(#{encdump(obj.source)}, #{obj.options})" + else + raise Argument, "unexpected: #{obj.inspect}" + end + end + + def enccall(recv, meth, *args) + desc = '' + if String === recv + desc << encdump(recv) + else + desc << recv.inspect + end + desc << '.' << meth.to_s + if !args.empty? + desc << '(' + args.each_with_index {|a, i| + desc << ',' if 0 < i + if String === a + desc << encdump(a) + else + desc << a.inspect + end + } + desc << ')' + end + result = nil + assert_nothing_raised(desc) { + result = recv.send(meth, *args) + } + result + end + + def assert_str_equal(expected, actual, message=nil) + full_message = build_message(message, <<EOT) +#{encdump expected} expected but not equal to +#{encdump actual}. +EOT + assert_equal(expected, actual, full_message) + end + + # tests start + + def test_utf16be_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 + 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 + s = "aa".force_encoding("utf-16be") + assert_raise(ArgumentError, "Time.now.strftime(#{encdump s})") { Time.now.strftime(s) } + end + + def test_intern + s = "aaaa".force_encoding("utf-16be") + assert_equal(s.encoding, s.intern.to_s.encoding, "#{encdump s}.intern.to_s.encoding") + end + + def test_sym_eq + s = "aa".force_encoding("utf-16le") + assert_not_equal(:aa, s.intern, "#{encdump s}.intern != :aa") + end + + def test_compatible + s1 = "aa".force_encoding("utf-16be") + s2 = "z".force_encoding("us-ascii") + assert_nil(Encoding.compatible?(s1, s2), "Encoding.compatible?(#{encdump s1}, #{encdump s2})") + end + + def test_casecmp + s1 = "aa".force_encoding("utf-16be") + s2 = "AA" + assert_not_equal(0, s1.casecmp(s2), "#{encdump s1}.casecmp(#{encdump s2})") + end + + def test_end_with + s1 = "ab".force_encoding("utf-16be") + s2 = "b".force_encoding("utf-16be") + assert_equal(false, s1.end_with?(s2), "#{encdump s1}.end_with?(#{encdump s2})") + end + + def test_hex + assert_raise(Encoding::CompatibilityError) { + "ff".encode("utf-16le").hex + } + assert_raise(Encoding::CompatibilityError) { + "ff".encode("utf-16be").hex + } + end + + def test_oct + assert_raise(Encoding::CompatibilityError) { + "77".encode("utf-16le").oct + } + assert_raise(Encoding::CompatibilityError) { + "77".encode("utf-16be").oct + } + end + + def test_count + s1 = "aa".force_encoding("utf-16be") + s2 = "aa" + assert_raise(Encoding::CompatibilityError, "#{encdump s1}.count(#{encdump s2})") { + s1.count(s2) + } + end + + def test_plus + s1 = "a".force_encoding("us-ascii") + s2 = "aa".force_encoding("utf-16be") + assert_raise(Encoding::CompatibilityError, "#{encdump s1} + #{encdump s2}") { + s1 + s2 + } + end + + def test_encoding_find + assert_raise(ArgumentError) { + Encoding.find("utf-8".force_encoding("utf-16be")) + } + end + + def test_interpolation + s = "aa".force_encoding("utf-16be") + assert_raise(Encoding::CompatibilityError, "\"a\#{#{encdump s}}\"") { + "a#{s}" + } + end + + def test_slice! + enccall("aa".force_encoding("UTF-16BE"), :slice!, -1) + end + + def test_plus_empty1 + s1 = "" + s2 = "aa".force_encoding("utf-16be") + assert_nothing_raised("#{encdump s1} << #{encdump s2}") { + s1 + s2 + } + end + + def test_plus_empty2 + s1 = "aa" + s2 = "".force_encoding("utf-16be") + assert_nothing_raised("#{encdump s1} << #{encdump s2}") { + s1 + s2 + } + end + + def test_plus_nonempty + s1 = "aa" + s2 = "bb".force_encoding("utf-16be") + assert_raise(Encoding::CompatibilityError, "#{encdump s1} << #{encdump s2}") { + s1 + s2 + } + end + + def test_concat_empty1 + s1 = "" + s2 = "aa".force_encoding("utf-16be") + assert_nothing_raised("#{encdump s1} << #{encdump s2}") { + s1 << s2 + } + end + + def test_concat_empty2 + s1 = "aa" + s2 = "".force_encoding("utf-16be") + assert_nothing_raised("#{encdump s1} << #{encdump s2}") { + s1 << s2 + } + end + + def test_concat_nonempty + s1 = "aa" + s2 = "bb".force_encoding("utf-16be") + assert_raise(Encoding::CompatibilityError, "#{encdump s1} << #{encdump s2}") { + s1 << s2 + } + end + + def test_chomp + s = "\1\n".force_encoding("utf-16be") + assert_equal(s, s.chomp, "#{encdump s}.chomp") + s = "\0\n".force_encoding("utf-16be") + assert_equal("", s.chomp, "#{encdump s}.chomp") + s = "\0\r\0\n".force_encoding("utf-16be") + assert_equal("", s.chomp, "#{encdump s}.chomp") + end + + def test_succ + s = "\xff\xff".force_encoding("utf-16be") + assert_predicate(s.succ, :valid_encoding?, "#{encdump s}.succ.valid_encoding?") + + s = "\xdb\xff\xdf\xff".force_encoding("utf-16be") + assert_predicate(s.succ, :valid_encoding?, "#{encdump s}.succ.valid_encoding?") + end + + def test_regexp_union + enccall(Regexp, :union, "aa".force_encoding("utf-16be"), "bb".force_encoding("utf-16be")) + end + + def test_empty_regexp + s = "".force_encoding("utf-16be") + assert_equal(Encoding.find("utf-16be"), Regexp.new(s).encoding, + "Regexp.new(#{encdump s}).encoding") + end + + def test_regexp_match + assert_raise(Encoding::CompatibilityError) { Regexp.new("aa".force_encoding("utf-16be")) =~ "aa" } + end + + def test_gsub + s = "abcd".force_encoding("utf-16be") + assert_nothing_raised { + s.gsub(Regexp.new(".".encode("utf-16be")), "xy") + } + s = "ab\0\ncd".force_encoding("utf-16be") + assert_raise(Encoding::CompatibilityError) { + s.gsub(Regexp.new(".".encode("utf-16be")), "xy") + } + end + + def test_split_awk + s = " ab cd ".encode("utf-16be") + r = s.split(" ".encode("utf-16be")) + assert_equal(2, r.length) + assert_str_equal("ab".encode("utf-16be"), r[0]) + assert_str_equal("cd".encode("utf-16be"), r[1]) + end + + def test_count2 + e = "abc".count("^b") + assert_equal(e, "abc".encode("utf-16be").count("^b".encode("utf-16be"))) + assert_equal(e, "abc".encode("utf-16le").count("^b".encode("utf-16le"))) + end + + def test_header + assert_raise(ArgumentError) { eval("# encoding:utf-16le\nfoo") } + assert_raise(ArgumentError) { eval("# encoding:utf-16be\nfoo") } + end + + + def test_is_mbc_newline + sl = "f\0o\0o\0\n\0b\0a\0r\0\n\0b\0a\0z\0\n\0".force_encoding("utf-16le") + sb = "\0f\0o\0o\0\n\0b\0a\0r\0\n\0b\0a\0z\0\n".force_encoding("utf-16be") + al = sl.lines.to_a + ab = sb.lines.to_a + assert_equal("f\0o\0o\0\n\0".force_encoding("utf-16le"), al.shift) + assert_equal("b\0a\0r\0\n\0".force_encoding("utf-16le"), al.shift) + assert_equal("b\0a\0z\0\n\0".force_encoding("utf-16le"), al.shift) + assert_equal("\0f\0o\0o\0\n".force_encoding("utf-16be"), ab.shift) + assert_equal("\0b\0a\0r\0\n".force_encoding("utf-16be"), ab.shift) + assert_equal("\0b\0a\0z\0\n".force_encoding("utf-16be"), ab.shift) + + sl = "f\0o\0o\0\n\0".force_encoding("utf-16le") + sb = "\0f\0o\0o\0\n".force_encoding("utf-16be") + sl2 = "f\0o\0o\0".force_encoding("utf-16le") + sb2 = "\0f\0o\0o".force_encoding("utf-16be") + assert_equal(sl2, sl.chomp) + assert_equal(sl2, sl.chomp.chomp) + assert_equal(sb2, sb.chomp) + assert_equal(sb2, sb.chomp.chomp) + + sl = "f\0o\0o\0\n".force_encoding("utf-16le") + sb = "\0f\0o\0o\n".force_encoding("utf-16be") + assert_equal(sl, sl.chomp) + assert_equal(sb, sb.chomp) + end + + def test_code_to_mbc + assert_equal("a\0".force_encoding("utf-16le"), "a".ord.chr("utf-16le")) + assert_equal("\0a".force_encoding("utf-16be"), "a".ord.chr("utf-16be")) + end + + def utf8_to_utf16(s, e) + s.chars.map {|c| c.ord.chr(e) }.join + end + + def test_mbc_case_fold + rl = Regexp.new(utf8_to_utf16("^(\u3042)(a)\\1\\2$", "utf-16le"), "i") + rb = Regexp.new(utf8_to_utf16("^(\u3042)(a)\\1\\2$", "utf-16be"), "i") + assert_equal(Encoding.find("utf-16le"), rl.encoding) + assert_equal(Encoding.find("utf-16be"), rb.encoding) + assert_match(rl, utf8_to_utf16("\u3042a\u3042a", "utf-16le")) + assert_match(rb, utf8_to_utf16("\u3042a\u3042a", "utf-16be")) + end + + def test_surrogate_pair + sl = "\x42\xd8\xb7\xdf".force_encoding("utf-16le") + sb = "\xd8\x42\xdf\xb7".force_encoding("utf-16be") + + assert_equal(1, sl.size) + assert_equal(1, sb.size) + assert_equal(0x20bb7, sl.ord) + assert_equal(0x20bb7, sb.ord) + assert_equal(sl, 0x20bb7.chr("utf-16le")) + assert_equal(sb, 0x20bb7.chr("utf-16be")) + assert_equal("", sl.chop) + assert_equal("", sb.chop) + end + + def test_regexp_escape + s = "\0*".force_encoding("UTF-16BE") + r = Regexp.new(Regexp.escape(s)) + assert_match(r, s, "#{encdump(r)} =~ #{encdump(s)}") + end + + def test_casecmp2 + assert_equal(0, "\0A".force_encoding("UTF-16BE").casecmp("\0a".force_encoding("UTF-16BE"))) + assert_not_equal(0, "\0A".force_encoding("UTF-16LE").casecmp("\0a".force_encoding("UTF-16LE"))) + assert_not_equal(0, "A\0".force_encoding("UTF-16BE").casecmp("a\0".force_encoding("UTF-16BE"))) + assert_equal(0, "A\0".force_encoding("UTF-16LE").casecmp("a\0".force_encoding("UTF-16LE"))) + + ary = ["01".force_encoding("UTF-16LE"), + "10".force_encoding("UTF-16LE")] + e = ary.sort {|x,y| x <=> y } + a = ary.sort {|x,y| x.casecmp(y) } + assert_equal(e, a) + end +end diff --git a/test/ruby/enc/test_utf32.rb b/test/ruby/enc/test_utf32.rb new file mode 100644 index 0000000000..76379abca0 --- /dev/null +++ b/test/ruby/enc/test_utf32.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestUTF32 < Test::Unit::TestCase + def encdump(str) + d = str.dump + if /\.force_encoding\("[A-Za-z0-9.:_+-]*"\)\z/ =~ d + d + else + "#{d}.force_encoding(#{str.encoding.name.dump})" + end + end + + def assert_str_equal(expected, actual, message=nil) + full_message = build_message(message, <<EOT) +#{encdump expected} expected but not equal to +#{encdump actual}. +EOT + assert_equal(expected, actual, full_message) + end + + def test_substr + assert_str_equal( + "abcdefgh".force_encoding("utf-32le"), + "abcdefgh".force_encoding("utf-32le")[0,3]) + assert_str_equal( + "abcdefgh".force_encoding("utf-32be"), + "abcdefgh".force_encoding("utf-32be")[0,3]) + end + + def test_mbc_len + al = "abcdefghijkl".force_encoding("utf-32le").each_char.to_a + ab = "abcdefghijkl".force_encoding("utf-32be").each_char.to_a + assert_equal("abcd".force_encoding("utf-32le"), al.shift) + assert_equal("efgh".force_encoding("utf-32le"), al.shift) + assert_equal("ijkl".force_encoding("utf-32le"), al.shift) + assert_equal("abcd".force_encoding("utf-32be"), ab.shift) + assert_equal("efgh".force_encoding("utf-32be"), ab.shift) + assert_equal("ijkl".force_encoding("utf-32be"), ab.shift) + end + + def ascii_to_utf16le(s) + s.unpack("C*").map {|x| [x,0,0,0] }.flatten.pack("C*").force_encoding("utf-32le") + end + + def ascii_to_utf16be(s) + s.unpack("C*").map {|x| [0,0,0,x] }.flatten.pack("C*").force_encoding("utf-32be") + end + + def test_mbc_newline + al = ascii_to_utf16le("foo\nbar\nbaz\n").lines.to_a + ab = ascii_to_utf16be("foo\nbar\nbaz\n").lines.to_a + + assert_equal(ascii_to_utf16le("foo\n"), al.shift) + assert_equal(ascii_to_utf16le("bar\n"), al.shift) + assert_equal(ascii_to_utf16le("baz\n"), al.shift) + assert_equal(ascii_to_utf16be("foo\n"), ab.shift) + assert_equal(ascii_to_utf16be("bar\n"), ab.shift) + assert_equal(ascii_to_utf16be("baz\n"), ab.shift) + + sl = "a\0".force_encoding("utf-32le") + sb = "a\0".force_encoding("utf-32be") + assert_equal(sl, sl.chomp) + assert_equal(sb, sb.chomp) + end + + def test_mbc_to_code + sl = "a\0\0\0".force_encoding("utf-32le") + sb = "\0\0\0a".force_encoding("utf-32be") + assert_equal("a".ord, sl.ord) + assert_equal("a".ord, sb.ord) + end + + def utf8_to_utf32(s, e) + s.chars.map {|c| c.ord.chr(e) }.join + end + + def test_mbc_case_fold + rl = Regexp.new(utf8_to_utf32("^(\u3042)(a)\\1\\2$", "utf-32le"), "i") + rb = Regexp.new(utf8_to_utf32("^(\u3042)(a)\\1\\2$", "utf-32be"), "i") + assert_equal(Encoding.find("utf-32le"), rl.encoding) + assert_equal(Encoding.find("utf-32be"), rb.encoding) + assert_match(rl, utf8_to_utf32("\u3042a\u3042a", "utf-32le")) + assert_match(rb, utf8_to_utf32("\u3042a\u3042a", "utf-32be")) + end + + def test_code_to_mbc + sl = "a\0\0\0".force_encoding("utf-32le") + sb = "\0\0\0a".force_encoding("utf-32be") + 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 new file mode 100644 index 0000000000..002dbaa3cc --- /dev/null +++ b/test/ruby/enc/test_windows_1251.rb @@ -0,0 +1,17 @@ +# encoding:windows-1251 +# frozen_string_literal: false + +require "test/unit" + +class TestWindows1251 < Test::Unit::TestCase + def test_windows_1251 + (0xc0..0xdf).each do |c| + c1 = c.chr("windows-1251") + c2 = (c + 0x20).chr("windows-1251") + 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/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/lbtest.rb b/test/ruby/lbtest.rb new file mode 100644 index 0000000000..c7822c9e9a --- /dev/null +++ b/test/ruby/lbtest.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: false + +class LocalBarrier + def initialize(n) + @wait = Thread::Queue.new + @done = Thread::Queue.new + @keeper = begin_keeper(n) + end + + def sync + @done.push(true) + @wait.pop + end + + def join + @keeper.join + end + + private + def begin_keeper(n) + Thread.start do + n.times do + @done.pop + end + n.times do + @wait.push(true) + end + end + end +end + +n = 10 + +lb = LocalBarrier.new(n) + +(n - 1).times do |i| + Thread.start do + sleep((rand(n) + 1) / 100.0) + print "#{i}: done\n" + lb.sync + print "#{i}: cont\n" + end +end + +lb.sync +print "#{n-1}: cont\n" +# lb.join # [ruby-dev:30653] + +print "exit.\n" diff --git a/test/ruby/marshaltestlib.rb b/test/ruby/marshaltestlib.rb new file mode 100644 index 0000000000..7f100b7873 --- /dev/null +++ b/test/ruby/marshaltestlib.rb @@ -0,0 +1,439 @@ +# coding: utf-8 +# frozen_string_literal: false +module MarshalTestLib + # include this module to a Test::Unit::TestCase and define encode(o) and + # decode(s) methods. e.g. + # + # def encode(o) + # SOAPMarshal.dump(o) + # end + # + # def decode(s) + # SOAPMarshal.load(s) + # end + + NegativeZero = (-1.0 / (1.0 / 0.0)) + + module Mod1; end + module Mod2; end + + def marshaltest(o1) + str = encode(o1) + print str.dump, "\n" if $DEBUG + o2 = decode(str) + o2 + end + + def marshal_equal(o1, msg = nil) + msg = msg ? msg + "(#{ caller[0] })" : caller[0] + o2 = marshaltest(o1) + assert_equal(o1.class, o2.class, msg) + iv1 = o1.instance_variables.sort + iv2 = o2.instance_variables.sort + assert_equal(iv1, iv2) + val1 = iv1.map {|var| o1.instance_eval {eval var.to_s}} + val2 = iv1.map {|var| o2.instance_eval {eval var.to_s}} + assert_equal(val1, val2, msg) + if block_given? + assert_equal(yield(o1), yield(o2), msg) + else + assert_equal(o1, o2, msg) + 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 + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + def test_object_subclass + marshal_equal(MyObject.new(2)) {|o| o.v} + end + + def test_object_extend + o1 = Object.new + o1.extend(Mod1) + marshal_equal_with_ancestry(o1) + o1.extend(Mod2) + marshal_equal_with_ancestry(o1) + end + + def test_object_subclass_extend + o1 = MyObject.new(2) + o1.extend(Mod1) + marshal_equal_with_ancestry(o1) + o1.extend(Mod2) + 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 + + class MyArray < Array + def initialize(v, *args) + super(args) + @v = v + end + end + def test_array + marshal_equal(5) + marshal_equal([1,2,3]) + end + + def test_array_subclass + marshal_equal(MyArray.new(0, 1, 2, 3)) + end + + def test_array_ivar + o1 = Array.new + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + 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} + obj = Object.new + e = assert_raise(NoMethodError) {obj.no_such_method()} + marshal_equal(e) {|o| o.message.lines.first.chomp} + end + + def test_exception_subclass + marshal_equal(MyException.new(20, "bar")) {|o| [o.message, o.v]} + end + + def test_false + marshal_equal(false) + end + + class MyHash < Hash; def initialize(v, *args) super(*args); @v = v; end end + def test_hash + marshal_equal({1=>2, 3=>4}) + end + + def test_hash_default + h = Hash.new(:default) + h[5] = 6 + marshal_equal(h) + end + + def test_hash_subclass + h = MyHash.new(7, 8) + h[4] = 5 + marshal_equal(h) + end + + def test_hash_default_proc + h = Hash.new {} + assert_raise(TypeError) { marshaltest(h) } + end + + def test_hash_ivar + o1 = Hash.new + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + def test_hash_extend + o1 = Hash.new + o1.extend(Mod1) + marshal_equal_with_ancestry(o1) + o1.extend(Mod2) + marshal_equal_with_ancestry(o1) + end + + def test_hash_subclass_extend + o1 = MyHash.new(2) + o1.extend(Mod1) + marshal_equal_with_ancestry(o1) + o1.extend(Mod2) + marshal_equal_with_ancestry(o1) + end + + def test_bignum + marshal_equal(-0x4000_0000_0000_0001) + marshal_equal(-0x4000_0001) + marshal_equal(0x4000_0000) + marshal_equal(0x4000_0000_0000_0000) + end + + def test_fixnum + marshal_equal(-0x4000_0000) + marshal_equal(-0x3fff_ffff) + marshal_equal(-1) + marshal_equal(0) + marshal_equal(1) + marshal_equal(0x3fff_ffff) + end + + def test_float + marshal_equal(-1.0) + marshal_equal(0.0) + marshal_equal(1.0) + end + + def test_float_inf_nan + marshal_equal(1.0/0.0) + marshal_equal(-1.0/0.0) + marshal_equal(0.0/0.0) {|o| o.nan?} + marshal_equal(NegativeZero) {|o| 1.0/o} + end + + class MyRange < Range; def initialize(v, *args) super(*args); @v = v; end end + def test_range + marshal_equal(1..2) + marshal_equal(1...3) + end + + def test_range_subclass + marshal_equal(MyRange.new(4,5,8, false)) + end + + class MyRegexp < Regexp; def initialize(v, *args) super(*args); @v = v; end end + def test_regexp + marshal_equal(/a/) + marshal_equal(/A/i) + marshal_equal(/A/mx) + marshal_equal(/a\u3042/) + marshal_equal(/aã‚/) + assert_equal(Regexp.new("ã‚".force_encoding("ASCII-8BIT")), + Marshal.load("\004\b/\b\343\201\202\000")) + assert_equal(/au3042/, Marshal.load("\004\b/\fa\\u3042\000")) + #assert_equal(/au3042/u, Marshal.load("\004\b/\fa\\u3042@")) # spec + end + + def test_regexp_subclass + marshal_equal(MyRegexp.new(10, "a")) + end + + class MyString < String; def initialize(v, *args) super(*args); @v = v; end end + def test_string + marshal_equal("abc") + end + + def test_string_ivar + o1 = "" + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + def test_string_subclass + marshal_equal(MyString.new(10, "a")) + end + + def test_string_subclass_cycle + str = MyString.new(10, "b") + str.instance_eval { @v = str } + marshal_equal(str) { |o| + assert_same(o, o.instance_eval { @v }) + o.instance_eval { @v } + } + end + + def test_string_subclass_extend + o = "abc" + o.extend(Mod1) + str = MyString.new(o, "c") + marshal_equal(str) { |v| + assert_kind_of(Mod1, v.instance_eval { @v }) + } + end + + MyStruct = Struct.new("MyStruct", :a, :b) + 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 + marshal_equal(MySubStruct.new(10,1,2)) + end + + def test_struct_ivar + o1 = MyStruct.new + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + def test_struct_subclass_extend + o1 = MyStruct.new + o1.extend(Mod1) + marshal_equal_with_ancestry(o1) + o1.extend(Mod2) + marshal_equal_with_ancestry(o1) + end + + def test_symbol + marshal_equal(:a) + marshal_equal(:a?) + marshal_equal(:a!) + marshal_equal(:a=) + marshal_equal(:|) + marshal_equal(:^) + marshal_equal(:&) + marshal_equal(:<=>) + marshal_equal(:==) + marshal_equal(:===) + marshal_equal(:=~) + marshal_equal(:>) + marshal_equal(:>=) + marshal_equal(:<) + marshal_equal(:<=) + marshal_equal(:<<) + marshal_equal(:>>) + marshal_equal(:+) + marshal_equal(:-) + marshal_equal(:*) + marshal_equal(:/) + marshal_equal(:%) + marshal_equal(:**) + marshal_equal(:~) + marshal_equal(:+@) + marshal_equal(:-@) + marshal_equal(:[]) + marshal_equal(:[]=) + marshal_equal(:`) #` + marshal_equal("a b".intern) + end + + class MyTime < Time; def initialize(v, *args) super(*args); @v = v; end end + def test_time + # once there was a bug caused by usec overflow. try a little harder. + 10.times do + t = Time.now + marshal_equal(t, t.usec.to_s) + end + end + + def test_time_subclass + marshal_equal(MyTime.new(10)) + end + + def test_time_ivar + o1 = Time.now + o1.instance_eval { @iv = 1 } + marshal_equal(o1) {|o| o.instance_eval { @iv }} + end + + def test_time_in_array + t = Time.now + assert_equal([t,t], Marshal.load(Marshal.dump([t, t])), "[ruby-dev:34159]") + end + + def test_true + marshal_equal(true) + end + + def test_nil + marshal_equal(nil) + end + + def test_share + o = [:share] + o1 = [o, o] + o2 = marshaltest(o1) + assert_same(o2.first, o2.last) + end + + class CyclicRange < Range + def <=>(other); true; end + end + def test_range_cyclic + return unless CyclicRange.respond_to?(:allocate) # test for 1.8 + o1 = CyclicRange.allocate + o1.instance_eval { initialize(o1, o1) } + o2 = marshaltest(o1) + assert_same(o2, o2.begin) + assert_same(o2, o2.end) + end + + def test_singleton + 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 + class C; self; end + end + assert_raise(TypeError) { marshaltest(o) } + assert_raise(TypeError) { marshaltest(c) } + assert_raise(TypeError) { marshaltest(ARGF) } + assert_raise(TypeError) { marshaltest(ENV) } + end + + def test_extend + o = Object.new + o.extend Mod1 + marshal_equal(o) { |obj| obj.kind_of? Mod1 } + o = Object.new + o.extend Mod1 + o.extend Mod2 + marshal_equal_with_ancestry(o) + o = Object.new + o.extend Module.new + assert_raise(TypeError) { marshaltest(o) } + end + + def test_extend_string + o = "" + o.extend Mod1 + marshal_equal(o) { |obj| obj.kind_of? Mod1 } + o = "" + o.extend Mod1 + o.extend Mod2 + marshal_equal_with_ancestry(o) + o = "" + o.extend Module.new + assert_raise(TypeError) { marshaltest(o) } + end + + def test_anonymous + c = Class.new + assert_raise(TypeError) { marshaltest(c) } + o = c.new + assert_raise(TypeError) { marshaltest(o) } + m = Module.new + assert_raise(TypeError) { marshaltest(m) } + end + + def test_string_empty + marshal_equal("") + end + + def test_string_crlf + marshal_equal("\r\n") + end + + def test_string_escape + marshal_equal("\0<;;>\1;;") + end + + MyStruct2 = Struct.new(:a, :b) + def test_struct_toplevel + o = MyStruct2.new(1,2) + marshal_equal(o) + end +end diff --git a/test/ruby/sentence.rb b/test/ruby/sentence.rb new file mode 100644 index 0000000000..99ced05d2f --- /dev/null +++ b/test/ruby/sentence.rb @@ -0,0 +1,669 @@ +# frozen_string_literal: false +# == sentence library +# +# = Features +# +# * syntax based sentences generation +# * sentence operations such as substitution. +# +# = Example +# +# Some arithmetic expressions using "+", "-", "*" and "/" are generated as follows. +# +# require 'sentence' +# Sentence.each({ +# :exp => [["num"], +# [:exp, "+", :exp], +# [:exp, "-", :exp], +# [:exp, "*", :exp], +# [:exp, "/", :exp]] +# }, :exp, 2) {|sent| p sent } +# #=> +# #<Sentence: "num"> +# #<Sentence: ("num") "+" ("num")> +# #<Sentence: ("num") "+" (("num") "+" ("num"))> +# #<Sentence: ("num") "+" (("num") "-" ("num"))> +# #<Sentence: ("num") "+" (("num") "*" ("num"))> +# #<Sentence: ("num") "+" (("num") "/" ("num"))> +# #<Sentence: (("num") "+" ("num")) "+" ("num")> +# ... +# +# Sentence.each takes 3 arguments. +# The first argument is the syntax for the expressions. +# The second argument, :exp, is a generating nonterminal. +# The third argument, 2, limits derivation to restrict results finitely. +# +# Some arithmetic expressions including parenthesis can be generated as follows. +# +# syntax = { +# :factor => [["n"], +# ["(", :exp, ")"]], +# :term => [[:factor], +# [:term, "*", :factor], +# [:term, "/", :factor]], +# :exp => [[:term], +# [:exp, "+", :term], +# [:exp, "-", :term]] +# } +# Sentence.each(syntax, :exp, 2) {|sent| p sent } +# #=> +# #<Sentence: (("n"))> +# #<Sentence: (("(" ((("n"))) ")"))> +# #<Sentence: (("(" ((("(" ((("n"))) ")"))) ")"))> +# #<Sentence: (("(" (((("n")) "*" ("n"))) ")"))> +# #<Sentence: (("(" (((("n")) "/" ("n"))) ")"))> +# #<Sentence: (("(" (((("n"))) "+" (("n"))) ")"))> +# #<Sentence: (("(" (((("n"))) "-" (("n"))) ")"))> +# #<Sentence: ((("n")) "*" ("n"))> +# #<Sentence: ((("n")) "*" ("(" ((("n"))) ")"))> +# ... +# +# Sentence#to_s can be used to concatenate strings +# in a sentence: +# +# Sentence.each(syntax, :exp, 2) {|sent| p sent.to_s } +# #=> +# "n" +# "(n)" +# "((n))" +# "(n*n)" +# "(n/n)" +# "(n+n)" +# "(n-n)" +# "n*n" +# "n*(n)" +# ... +# + +# Sentence() instantiates a sentence object. +# +# Sentence("foo", "bar") +# #=> #<Sentence: "foo" "bar"> +# +# Sentence("foo", ["bar", "baz"]) +# #=> #<Sentence: "foo" ("bar" "baz")> +# +def Sentence(*ary) + Sentence.new(ary) +end + +# Sentence class represents a tree with string leaves. +# +class Sentence + # _ary_ represents a tree. + # It should be a possibly nested array which contains strings. + # + # Note that _ary_ is not copied. + # Don't modify _ary_ after the sentence object is instantiated. + # + # Sentence.new(["a", "pen"]) + # #<Sentence: "a" "pen"> + # + # Sentence.new(["I", "have", ["a", "pen"]]) + # #<Sentence: "I" "have" ("a" "pen")> + # + def initialize(ary) + @sent = ary + end + + # returns a string which is concatenation of all strings. + # No separator is used. + # + # Sentence("2", "+", "3").to_s + # "2+3" + # + # Sentence("2", "+", ["3", "*", "5"]).to_s + # "2+3*5" + # + def to_s + @sent.join('') + end + + # returns a string which is concatenation of all strings separated by _sep_. + # If _sep_ is not given, single space is used. + # + # Sentence("I", "have", ["a", "pen"]).join + # "I have a pen" + # + # Sentence("I", "have", ["a", "pen"]).join("/") + # "I/have/a/pen" + # + # Sentence("a", [], "b").join("/") + # "a/b" + # + def join(sep=' ') + @sent.flatten.join(sep) + end + + # returns a tree as a nested array. + # + # Note that the result is not copied. + # Don't modify the result. + # + # Sentence(["foo", "bar"], "baz").to_a + # #=> [["foo", "bar"], "baz"] + # + def to_a + @sent + end + + # returns <i>i</i>th element as a sentence or string. + # + # s = Sentence(["foo", "bar"], "baz") + # s #=> #<Sentence: ("foo" "bar") "baz"> + # s[0] #=> #<Sentence: "foo" "bar"> + # s[1] #=> "baz" + # + def [](i) + e = @sent[i] + e.respond_to?(:to_ary) ? Sentence.new(e) : e + end + + # returns the number of top level elements. + # + # Sentence.new(%w[foo bar]).length + # #=> 2 + # + # Sentence(%w[2 * 7], "+", %w[3 * 5]).length + # #=> 3 + # + def length + @sent.length + end + + # iterates over children. + # + # Sentence(%w[2 * 7], "+", %w[3 * 5]).each {|v| p v } + # #=> + # #<Sentence: "2" "*" "7"> + # "+" + # #<Sentence: "3" "*" "5"> + # + def each # :yield: element + @sent.each_index {|i| + yield self[i] + } + end + include Enumerable + + def inspect + "#<#{self.class}: #{inner_inspect(@sent, '')}>" + end + + # :stopdoc: + def inner_inspect(ary, r) + first = true + ary.each {|obj| + r << ' ' if !first + first = false + if obj.respond_to? :to_ary + r << '(' + inner_inspect(obj, r) + r << ')' + else + r << obj.inspect + end + } + r + end + # :startdoc: + + # returns new sentence object which + # _target_ is substituted by the block. + # + # Sentence#subst invokes <tt>target === string</tt> for each + # string in the sentence. + # The strings which === returns true are substituted by the block. + # The block is invoked with the substituting string. + # + # Sentence.new(%w[2 + 3]).subst("+") { "*" } + # #<Sentence: "2" "*" "3"> + # + # Sentence.new(%w[2 + 3]).subst(/\A\d+\z/) {|s| ((s.to_i)*2).to_s } + # #=> #<Sentence: "4" "+" "6"> + # + def subst(target, &b) # :yield: string + Sentence.new(subst_rec(@sent, target, &b)) + end + + # :stopdoc: + def subst_rec(obj, target, &b) + if obj.respond_to? :to_ary + a = [] + obj.each {|e| a << subst_rec(e, target, &b) } + a + elsif target === obj + yield obj + else + obj + end + end + # :startdoc: + + # find a subsentence and return it. + # The block is invoked for each subsentence in preorder manner. + # The first subsentence which the block returns true is returned. + # + # Sentence(%w[2 * 7], "+", %w[3 * 5]).find_subtree {|s| s[1] == "*" } + # #=> #<Sentence: "2" "*" "7"> + # + def find_subtree(&b) # :yield: sentence + find_subtree_rec(@sent, &b) + end + + # :stopdoc: + def find_subtree_rec(obj, &b) + if obj.respond_to? :to_ary + s = Sentence.new(obj) + if b.call s + return s + else + obj.each {|e| + r = find_subtree_rec(e, &b) + return r if r + } + end + end + nil + end + # :startdoc: + + # returns a new sentence object which expands according to the condition + # given by the block. + # + # The block is invoked for each subsentence. + # The subsentences which the block returns true are + # expanded into parent. + # + # s = Sentence(%w[2 * 7], "+", %w[3 * 5]) + # #=> #<Sentence: ("2" "*" "7") "+" ("3" "*" "5")> + # + # s.expand { true } + # #=> #<Sentence: "2" "*" "7" "+" "3" "*" "5"> + # + # s.expand {|s| s[0] == "3" } + # #=> #<Sentence: (("2" "*" "7") "+" "3" "*" "5")> + # + def expand(&b) # :yield: sentence + Sentence.new(expand_rec(@sent, &b)) + end + + # :stopdoc: + def expand_rec(obj, r=[], &b) + if obj.respond_to? :to_ary + obj.each {|o| + s = Sentence.new(o) + if b.call s + expand_rec(o, r, &b) + else + a = [] + expand_rec(o, a, &b) + r << a + end + } + else + r << obj + end + r + end + # :startdoc: + + # Sentence.each generates sentences + # by deriving the start symbol _sym_ using _syntax_. + # The derivation is restricted by an positive integer _limit_ to + # avoid infinite generation. + # + # Sentence.each yields the block with a generated sentence. + # + # Sentence.each({ + # :exp => [["n"], + # [:exp, "+", :exp], + # [:exp, "*", :exp]] + # }, :exp, 1) {|sent| p sent } + # #=> + # #<Sentence: "n"> + # #<Sentence: ("n") "+" ("n")> + # #<Sentence: ("n") "*" ("n")> + # + # Sentence.each({ + # :exp => [["n"], + # [:exp, "+", :exp], + # [:exp, "*", :exp]] + # }, :exp, 2) {|sent| p sent } + # #=> + # #<Sentence: "n"> + # #<Sentence: ("n") "+" ("n")> + # #<Sentence: ("n") "+" (("n") "+" ("n"))> + # #<Sentence: ("n") "+" (("n") "*" ("n"))> + # #<Sentence: (("n") "+" ("n")) "+" ("n")> + # #<Sentence: (("n") "*" ("n")) "+" ("n")> + # #<Sentence: ("n") "*" ("n")> + # #<Sentence: ("n") "*" (("n") "+" ("n"))> + # #<Sentence: ("n") "*" (("n") "*" ("n"))> + # #<Sentence: (("n") "+" ("n")) "*" ("n")> + # #<Sentence: (("n") "*" ("n")) "*" ("n")> + # + def Sentence.each(syntax, sym, limit) + Gen.new(syntax).each_tree(sym, limit) {|tree| + yield Sentence.new(tree) + } + end + + # Sentence.expand_syntax returns an expanded syntax: + # * No rule derives to empty sequence + # * Underivable rule simplified + # * No channel rule + # * Symbols which has zero or one choices are not appeared in rhs. + # + # Note that the rules which can derive empty and non-empty + # sequences are modified to derive only non-empty sequences. + # + # Sentence.expand_syntax({ + # :underivable1 => [], + # :underivable2 => [[:underivable1]], + # :underivable3 => [[:underivable3]], + # :empty_only1 => [[]], + # :empty_only2 => [[:just_empty1, :just_empty1]], + # :empty_or_not => [[], ["foo"]], + # :empty_or_not_2 => [[:empty_or_not, :empty_or_not]], + # :empty_or_not_3 => [[:empty_or_not, :empty_or_not, :empty_or_not]], + # :empty_or_not_4 => [[:empty_or_not_2, :empty_or_not_2]], + # :channel1 => [[:channeled_data]], + # :channeled_data => [["a", "b"], ["c", "d"]], + # :single_choice => [["single", "choice"]], + # :single_choice_2 => [[:single_choice, :single_choice]], + # }) + # #=> + # { + # :underivable1=>[], # underivable rules are simplified to []. + # :underivable2=>[], + # :underivable3=>[], + # :empty_only1=>[], # derivation to empty sequence are removed. + # :empty_only2=>[], + # :empty_or_not=>[["foo"]], # empty sequences are removed too. + # :empty_or_not_2=>[["foo"], ["foo", "foo"]], + # :empty_or_not_3=>[["foo"], ["foo", "foo"], ["foo", "foo", "foo"]], + # :empty_or_not_4=> [["foo"], ["foo", "foo"], [:empty_or_not_2, :empty_or_not_2]], + # :channel1=>[["a", "b"], ["c", "d"]], # channel rules are removed. + # :channeled_data=>[["a", "b"], ["c", "d"]], + # :single_choice=>[["single", "choice"]], # single choice rules are expanded. + # :single_choice_2=>[["single", "choice", "single", "choice"]], + # } + # + # Sentence.expand_syntax({ + # :factor => [["n"], + # ["(", :exp, ")"]], + # :term => [[:factor], + # [:term, "*", :factor], + # [:term, "/", :factor]], + # :exp => [[:term], + # [:exp, "+", :term], + # [:exp, "-", :term]] + # }) + # #=> + # {:exp=> [["n"], + # ["(", :exp, ")"], + # [:exp, "+", :term], + # [:exp, "-", :term], + # [:term, "*", :factor], + # [:term, "/", :factor]], + # :factor=> [["n"], + # ["(", :exp, ")"]], + # :term=> [["n"], + # ["(", :exp, ")"], + # [:term, "*", :factor], + # [:term, "/", :factor]] + # } + # + def Sentence.expand_syntax(syntax) + Sentence::Gen.expand_syntax(syntax) + end + + # :stopdoc: + class Gen + def Gen.each_tree(syntax, sym, limit, &b) + Gen.new(syntax).each_tree(sym, limit, &b) + end + + def Gen.each_string(syntax, sym, limit, &b) + Gen.new(syntax).each_string(sym, limit, &b) + end + + def initialize(syntax) + @syntax = syntax + end + + def self.expand_syntax(syntax) + syntax = simplify_underivable_rules(syntax) + syntax = simplify_emptyonly_rules(syntax) + syntax = make_rules_no_empseq(syntax) + syntax = expand_channel_rules(syntax) + + syntax = expand_noalt_rules(syntax) + syntax = reorder_rules(syntax) + end + + def self.simplify_underivable_rules(syntax) + deribable_syms = {} + changed = true + while changed + changed = false + syntax.each {|sym, rules| + next if deribable_syms[sym] + rules.each {|rhs| + if rhs.all? {|e| String === e || deribable_syms[e] } + deribable_syms[sym] = true + changed = true + break + end + } + } + end + result = {} + syntax.each {|sym, rules| + if deribable_syms[sym] + rules2 = [] + rules.each {|rhs| + rules2 << rhs if rhs.all? {|e| String === e || deribable_syms[e] } + } + result[sym] = rules2.uniq + else + result[sym] = [] + end + } + result + end + + def self.simplify_emptyonly_rules(syntax) + justempty_syms = {} + changed = true + while changed + changed = false + syntax.each {|sym, rules| + next if justempty_syms[sym] + if !rules.empty? && rules.all? {|rhs| rhs.all? {|e| justempty_syms[e] } } + justempty_syms[sym] = true + changed = true + end + } + end + result = {} + syntax.each {|sym, rules| + result[sym] = rules.map {|rhs| rhs.reject {|e| justempty_syms[e] } }.uniq + } + result + end + + def self.expand_emptyable_syms(rhs, emptyable_syms) + if rhs.empty? + yield [] + else + first = rhs[0] + rest = rhs[1..-1] + if emptyable_syms[first] + expand_emptyable_syms(rest, emptyable_syms) {|rhs2| + yield [first] + rhs2 + yield rhs2 + } + else + expand_emptyable_syms(rest, emptyable_syms) {|rhs2| + yield [first] + rhs2 + } + end + end + end + + def self.make_rules_no_empseq(syntax) + emptyable_syms = {} + changed = true + while changed + changed = false + syntax.each {|sym, rules| + next if emptyable_syms[sym] + rules.each {|rhs| + if rhs.all? {|e| emptyable_syms[e] } + emptyable_syms[sym] = true + changed = true + break + end + } + } + end + result = {} + syntax.each {|sym, rules| + rules2 = [] + rules.each {|rhs| + expand_emptyable_syms(rhs, emptyable_syms) {|rhs2| + next if rhs2.empty? + rules2 << rhs2 + } + } + result[sym] = rules2.uniq + } + result + end + + def self.expand_channel_rules(syntax) + channel_rules = {} + syntax.each {|sym, rules| + channel_rules[sym] = {sym=>true} + rules.each {|rhs| + if rhs.length == 1 && Symbol === rhs[0] + channel_rules[sym][rhs[0]] = true + end + } + } + changed = true + while changed + changed = false + channel_rules.each {|sym, set| + n1 = set.size + set.keys.each {|s| + set.update(channel_rules[s]) + } + n2 = set.size + changed = true if n1 < n2 + } + end + result = {} + syntax.each {|sym, rules| + rules2 = [] + channel_rules[sym].each_key {|s| + syntax[s].each {|rhs| + unless rhs.length == 1 && Symbol === rhs[0] + rules2 << rhs + end + } + } + result[sym] = rules2.uniq + } + result + end + + def self.expand_noalt_rules(syntax) + noalt_syms = {} + syntax.each {|sym, rules| + if rules.length == 1 + noalt_syms[sym] = true + end + } + result = {} + syntax.each {|sym, rules| + rules2 = [] + rules.each {|rhs| + rhs2 = [] + rhs.each {|e| + if noalt_syms[e] + rhs2.concat syntax[e][0] + else + rhs2 << e + end + } + rules2 << rhs2 + } + result[sym] = rules2.uniq + } + result + end + + def self.reorder_rules(syntax) + result = {} + syntax.each {|sym, rules| + result[sym] = rules.sort_by {|rhs| + [rhs.find_all {|e| Symbol === e }.length, rhs.length] + } + } + result + end + + def each_tree(sym, limit) + generate_from_sym(sym, limit) {|_, tree| + yield tree + } + nil + end + + def each_string(sym, limit) + generate_from_sym(sym, limit) {|_, tree| + yield [tree].join('') + } + nil + end + + def generate_from_sym(sym, limit, &b) + return if limit < 0 + if String === sym + yield limit, sym + else + rules = @syntax[sym] + raise "undefined rule: #{sym}" if !rules + rules.each {|rhs| + if rhs.length == 1 || rules.length == 1 + limit1 = limit + else + limit1 = limit-1 + end + generate_from_rhs(rhs, limit1, &b) + } + end + nil + end + + def generate_from_rhs(rhs, limit) + return if limit < 0 + if rhs.empty? + yield limit, [] + else + generate_from_sym(rhs[0], limit) {|limit1, child| + generate_from_rhs(rhs[1..-1], limit1) {|limit2, arr| + yield limit2, [child, *arr] + } + } + end + nil + end + end + # :startdoc: + +end + diff --git a/test/ruby/test_alias.rb b/test/ruby/test_alias.rb new file mode 100644 index 0000000000..539cd49488 --- /dev/null +++ b/test/ruby/test_alias.rb @@ -0,0 +1,344 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestAlias < Test::Unit::TestCase + class Alias0 + def foo + "foo" + end + end + + class Alias1 < Alias0 + alias bar foo + + def foo + "foo+#{super}" + end + end + + class Alias2 < Alias1 + alias baz foo + undef foo + end + + class Alias3 < Alias2 + def foo + super + end + + def bar + super + end + + def quux + super + end + end + + class Alias4 < Alias0 + alias foo1 foo + alias foo2 foo1 + alias foo3 foo2 + end + + class Alias5 < Alias4 + alias foo1 foo + alias foo3 foo2 + alias foo2 foo1 + end + + def test_alias + x = Alias2.new + assert_equal "foo", x.bar + assert_equal "foo+foo", x.baz + assert_equal "foo+foo", x.baz # test_check for cache + + x = Alias3.new + assert_raise(NoMethodError) { x.foo } + assert_equal "foo", x.bar + assert_raise(NoMethodError) { x.quux } + end + + def test_alias_inspect + o = Alias4.new + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1]) + assert_equal("TestAlias::Alias4(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1]) + + o = Alias5.new + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo()", o.method(:foo).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo1(foo)()", o.method(:foo1).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo2(foo)()", o.method(:foo2).inspect.split[1]) + assert_equal("TestAlias::Alias5(TestAlias::Alias0)#foo3(foo)()", o.method(:foo3).inspect.split[1]) + end + + def test_nonexistmethod + assert_raise(NameError){ + Class.new{ + alias_method :foobarxyzzy, :barbaz + } + } + end + + def test_send_alias + x = "abc" + class << x + alias_method :try, :__send__ + end + assert_equal("ABC", x.try(:upcase), '[ruby-dev:38824]') + end + + def test_special_const_alias + assert_raise(TypeError) do + 1.instance_eval do + alias to_string to_s + end + end + end + + def test_alias_with_zsuper_method + c = Class.new + c.class_eval do + def foo + :ok + end + def bar + :ng + end + private :foo + end + d = Class.new(c) + d.class_eval do + public :foo + alias bar foo + 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 + + def test_alias_suppressing_redefinition + assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + def foo; end + alias foo foo + def foo; end + end + end; + end + + class C2 + public :system + alias_method :bar, :system + alias_method :system, :bar + end + + def test_zsuper_alias_visibility + assert(C2.new.respond_to?(:system)) + end + + def test_alias_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true) + begin; + class A + 500.times do + 1000.times do |i| + define_method(:"foo_#{i}") {} + + alias :"foo_#{i}" :"foo_#{i}" + + remove_method :"foo_#{i}" + end + GC.start + end + end + end; + end + + def test_alias_complemented_method + assert_in_out_err(%w[-w], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + module M + def foo = 1 + self.extend M + end + + 3.times{|i| + module M + alias foo2 foo + remove_method :foo + def foo = 2 + ensure + remove_method :foo + alias foo foo2 + remove_method :foo2 + end + + M.foo + + original_foo = M.method(:foo) + + M.class_eval do + remove_method :foo + def foo = 3 + end + + M.class_eval do + remove_method :foo + define_method :foo, original_foo + end + } + end; + end + + def test_undef_method_error_message_with_zsuper_method + modules = [ + Module.new { private :class }, + Module.new { prepend Module.new { private :class } }, + ] + message = "undefined method 'class' for module '%s'" + modules.each do |mod| + assert_raise_with_message(NameError, message % mod) do + mod.alias_method :xyz, :class + end + end + end +end diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb new file mode 100644 index 0000000000..90d7c04f9b --- /dev/null +++ b/test/ruby/test_allocation.rb @@ -0,0 +1,957 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestAllocation < Test::Unit::TestCase + def setup + # The namespace changes on i686 platform triggers a bug to allocate objects unexpectedly. + # For now, skip these tests only on i686 + pend if RUBY_PLATFORM =~ /^i686/ + end + + def munge_checks(checks) + checks + end + + def check_allocations(checks) + dups = checks.split("\n").reject(&:empty?).tally.select{|_,v| v > 1} + raise "duplicate checks:\n#{dups.keys.join("\n")}" unless dups.empty? + + checks = munge_checks(checks) + + assert_separately([], <<~RUBY) + $allocations = [0, 0] + $counts = {} + failures = [] + + def self.num_allocations + ObjectSpace.count_objects($counts) + arrays = $counts[:T_ARRAY] + hashes = $counts[:T_HASH] + yield + ObjectSpace.count_objects($counts) + arrays -= $counts[:T_ARRAY] + hashes -= $counts[:T_HASH] + $allocations[0] = -arrays + $allocations[1] = -hashes + end + + define_singleton_method(:check_allocations) do |num_arrays, num_hashes, check_code| + instance_eval <<~RB + empty_array = empty_array = [] + empty_hash = empty_hash = {} + array1 = array1 = [1] + r2k_array = r2k_array = [Hash.ruby2_keywords_hash(a: 3)] + r2k_array1 = r2k_array1 = [1, Hash.ruby2_keywords_hash(a: 3)] + r2k_empty_array = r2k_empty_array = [Hash.ruby2_keywords_hash({})] + r2k_empty_array1 = r2k_empty_array1 = [1, Hash.ruby2_keywords_hash({})] + hash1 = hash1 = {a: 2} + nill = nill = nil + block = block = lambda{} + + num_allocations do + \#{check_code} + end + RB + + if num_arrays != $allocations[0] + failures << "Expected \#{num_arrays} array allocations for \#{check_code.inspect}, but \#{$allocations[0]} arrays allocated" + end + if num_hashes != $allocations[1] + failures << "Expected \#{num_hashes} hash allocations for \#{check_code.inspect}, but \#{$allocations[1]} hashes allocated" + end + end + + GC.start + GC.disable + + #{checks} + + assert_empty(failures) + RUBY + end + + class Literal < self + def test_array_literal + check_allocations(<<~RUBY) + check_allocations(1, 0, "[]") + check_allocations(1, 0, "[1]") + check_allocations(1, 0, "[*empty_array]") + check_allocations(1, 0, "[*empty_array, 1, *empty_array]") + check_allocations(1, 0, "[*empty_array, *empty_array]") + check_allocations(1, 0, "[#{'1,'*100000}]") + RUBY + end + + def test_hash_literal + check_allocations(<<~RUBY) + check_allocations(0, 1, "{}") + check_allocations(0, 1, "{a: 1}") + check_allocations(0, 1, "{**empty_hash}") + check_allocations(0, 1, "{**empty_hash, a: 1, **empty_hash}") + check_allocations(0, 1, "{**empty_hash, **empty_hash}") + check_allocations(0, 1, "{#{100000.times.map{|i| "a#{i}: 1"}.join(',')}}") + RUBY + end + end + + class MethodCall < self + def block + '' + end + alias only_block block + + def test_no_parameters + check_allocations(<<~RUBY) + def self.none(#{only_block}); end + + check_allocations(0, 0, "none(#{only_block})") + check_allocations(0, 0, "none(*nil#{block})") + check_allocations(0, 0, "none(*empty_array#{block})") + check_allocations(0, 0, "none(**empty_hash#{block})") + check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "none(*empty_array, *empty_array#{block})") + check_allocations(0, 1, "none(**empty_hash, **empty_hash#{block})") + check_allocations(1, 1, "none(*empty_array, *empty_array, **empty_hash, **empty_hash#{block})") + + check_allocations(0, 0, "none(*r2k_empty_array#{block})") + RUBY + end + + def test_required_parameter + check_allocations(<<~RUBY) + def self.required(x#{block}); end + + check_allocations(0, 0, "required(1#{block})") + check_allocations(0, 0, "required(1, *empty_array#{block})") + check_allocations(0, 0, "required(1, **empty_hash#{block})") + check_allocations(0, 0, "required(1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "required(*array1#{block})") + check_allocations(0, 1, "required(**hash1#{block})") + + check_allocations(1, 0, "required(*array1, *empty_array#{block})") + check_allocations(0, 1, "required(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "required(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "required(*r2k_empty_array1#{block})") + check_allocations(0, 1, "required(*r2k_array#{block})") + + check_allocations(0, 1, "required(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_optional_parameter + check_allocations(<<~RUBY) + def self.optional(x=nil#{block}); end + + check_allocations(0, 0, "optional(1#{block})") + check_allocations(0, 0, "optional(1, *empty_array#{block})") + check_allocations(0, 0, "optional(1, **empty_hash#{block})") + check_allocations(0, 0, "optional(1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "optional(*array1#{block})") + check_allocations(0, 1, "optional(**hash1#{block})") + + check_allocations(1, 0, "optional(*array1, *empty_array#{block})") + check_allocations(0, 1, "optional(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "optional(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "optional(*r2k_empty_array#{block})") + check_allocations(0, 0, "optional(*r2k_empty_array1#{block})") + check_allocations(0, 1, "optional(*r2k_array#{block})") + + check_allocations(0, 0, "optional(*empty_array#{block})") + check_allocations(0, 0, "optional(*nil#{block})") + check_allocations(0, 0, "optional(#{only_block})") + check_allocations(0, 1, "optional(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_positional_splat_parameter + check_allocations(<<~RUBY) + def self.splat(*x#{block}); end + + check_allocations(1, 0, "splat(1#{block})") + check_allocations(1, 0, "splat(1, *empty_array#{block})") + check_allocations(1, 0, "splat(1, **empty_hash#{block})") + check_allocations(1, 0, "splat(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(*array1#{block})") + check_allocations(1, 0, "splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(1, *array1#{block})") + check_allocations(1, 0, "splat(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(*nil#{block})") + check_allocations(1, 0, "splat(#{only_block})") + check_allocations(1, 1, "splat(**hash1#{block})") + + check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 0, "splat(*r2k_empty_array#{block})") + check_allocations(1, 0, "splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "splat(*r2k_array#{block})") + check_allocations(1, 1, "splat(*r2k_array1#{block})") + RUBY + end + + def test_required_and_positional_splat_parameters + check_allocations(<<~RUBY) + def self.req_splat(x, *y#{block}); end + + check_allocations(1, 0, "req_splat(1#{block})") + check_allocations(1, 0, "req_splat(1, *nil#{block})") + check_allocations(1, 0, "req_splat(1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(*array1#{block})") + check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(*array1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(1, *array1#{block})") + check_allocations(1, 0, "req_splat(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "req_splat(**hash1#{block})") + + check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "req_splat(*r2k_array#{block})") + check_allocations(1, 1, "req_splat(*r2k_array1#{block})") + RUBY + end + + def test_positional_splat_and_post_parameters + check_allocations(<<~RUBY) + def self.splat_post(*x, y#{block}); end + + check_allocations(1, 0, "splat_post(1#{block})") + check_allocations(1, 0, "splat_post(1, *nil#{block})") + check_allocations(1, 0, "splat_post(1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(*array1#{block})") + check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(1, *array1#{block})") + check_allocations(1, 0, "splat_post(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_post(**hash1#{block})") + + check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(*r2k_empty_array1#{block})") + check_allocations(1, 1, "splat_post(*r2k_array#{block})") + check_allocations(1, 1, "splat_post(*r2k_array1#{block})") + RUBY + end + + def test_keyword_parameter + check_allocations(<<~RUBY) + def self.keyword(a: nil#{block}); end + + check_allocations(0, 0, "keyword(a: 2#{block})") + check_allocations(0, 0, "keyword(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword(**empty_hash, a: 2#{block})") + + check_allocations(0, 0, "keyword(**nil#{block})") + check_allocations(0, 0, "keyword(**empty_hash#{block})") + check_allocations(0, 0, "keyword(**hash1#{block})") + check_allocations(0, 0, "keyword(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})") + + check_allocations(0, 0, "keyword(*nil#{block})") + check_allocations(0, 0, "keyword(*empty_array#{block})") + check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "keyword(*r2k_empty_array#{block})") + check_allocations(0, 0, "keyword(*r2k_array#{block})") + + check_allocations(0, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "keyword(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.keyword_splat(**kw#{block}); end + + check_allocations(0, 1, "keyword_splat(a: 2#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword_splat(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash, a: 2#{block})") + + check_allocations(0, 1, "keyword_splat(**nil#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**hash1#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})") + + check_allocations(0, 1, "keyword_splat(*nil#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array#{block})") + check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "keyword_splat(*r2k_empty_array#{block})") + check_allocations(0, 1, "keyword_splat(*r2k_array#{block})") + + check_allocations(0, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_keyword_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end + + check_allocations(0, 1, "keyword_and_keyword_splat(a: 2#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, a: 2#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(**nil#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(*nil#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})") + check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_empty_array#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*r2k_array#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_required_positional_and_keyword_parameter + check_allocations(<<~RUBY) + def self.required_and_keyword(b, a: nil#{block}); end + + check_allocations(0, 0, "required_and_keyword(1, a: 2#{block})") + check_allocations(0, 0, "required_and_keyword(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 0, "required_and_keyword(1, **nil#{block})") + check_allocations(0, 0, "required_and_keyword(1, **empty_hash#{block})") + check_allocations(0, 0, "required_and_keyword(1, **hash1#{block})") + check_allocations(0, 0, "required_and_keyword(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 0, "required_and_keyword(1, *nil#{block})") + check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})") + check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "required_and_keyword(*array1, a: 2#{block})") + + check_allocations(0, 0, "required_and_keyword(*array1, **nill#{block})") + check_allocations(0, 0, "required_and_keyword(*array1, **empty_hash#{block})") + check_allocations(0, 0, "required_and_keyword(*array1, **hash1#{block})") + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array#{block})") + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 0, "required_and_keyword(*r2k_empty_array1#{block})") + check_allocations(0, 0, "required_and_keyword(*r2k_array1#{block})") + + check_allocations(0, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 0, "required_and_keyword(*array1, **nil#{block})") + RUBY + end + + def test_positional_splat_and_keyword_parameter + check_allocations(<<~RUBY) + def self.splat_and_keyword(*b, a: nil#{block}); end + + check_allocations(1, 0, "splat_and_keyword(1, a: 2#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "splat_and_keyword(1, **nil#{block})") + check_allocations(1, 0, "splat_and_keyword(1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(1, **hash1#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "splat_and_keyword(1, *nil#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, a: 2#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, **nill#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **hash1#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **nil#{block})") + + check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array#{block})") + check_allocations(1, 0, "splat_and_keyword(*r2k_array#{block})") + check_allocations(1, 0, "splat_and_keyword(*r2k_empty_array1#{block})") + check_allocations(1, 0, "splat_and_keyword(*r2k_array1#{block})") + RUBY + end + + def test_required_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.required_and_keyword_splat(b, **kw#{block}); end + + check_allocations(0, 1, "required_and_keyword_splat(1, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(1, **nil#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(1, *nil#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(*r2k_empty_array1#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*r2k_array1#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **nil#{block})") + RUBY + end + + def test_positional_splat_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.splat_and_keyword_splat(*b, **kw#{block}); end + + check_allocations(1, 1, "splat_and_keyword_splat(1, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, **nil#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, *nil#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, a: 2#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nill#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nil#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*r2k_array1#{block})") + RUBY + end + + def test_anonymous_splat_parameter + only_block = block.empty? ? block : block[2..] + check_allocations(<<~RUBY) + def self.anon_splat(*#{block}); end + + check_allocations(1, 1, "anon_splat(1, a: 2#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat(1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **hash1#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat(1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat(*nil, **nill#{block})") + check_allocations(0, 0, "anon_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat(*array1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat(#{only_block})") + check_allocations(1, 1, "anon_splat(a: 2#{block})") + check_allocations(0, 0, "anon_splat(**empty_hash#{block})") + + check_allocations(1, 1, "anon_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "anon_splat(*array1, **hash1, **empty_hash#{block})") + + unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + check_allocations(0, 0, "anon_splat(*array1, **nil#{block})") + check_allocations(1, 0, "anon_splat(*r2k_empty_array#{block})") + check_allocations(1, 1, "anon_splat(*r2k_array#{block})") + check_allocations(1, 0, "anon_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "anon_splat(*r2k_array1#{block})") + end + RUBY + end + + def test_anonymous_splat_and_anonymous_keyword_splat_parameters + only_block = block.empty? ? block : block[2..] + check_allocations(<<~RUBY) + def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})") + RUBY + end + + def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters + only_block = block.empty? ? block : block[2..] + check_allocations(<<~RUBY) + def self.t(*, **#{block}); end + def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*nil, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(#{only_block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(a: 2#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(**empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_empty_array1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*r2k_array1#{block})") + RUBY + end + + def test_argument_forwarding + check_allocations(<<~RUBY) + def self.argument_forwarding(...); end + + check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})") + check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(1, **nil#{block})") + check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})") + check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 0, "argument_forwarding(1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(**nill#{block})") + check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})") + + check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})") + RUBY + end + + def test_nested_argument_forwarding + check_allocations(<<~RUBY) + def self.t(...) end + def self.argument_forwarding(...); t(...) end + + check_allocations(0, 0, "argument_forwarding(1, a: 2#{block})") + check_allocations(0, 0, "argument_forwarding(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "argument_forwarding(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(1, **nil#{block})") + check_allocations(0, 0, "argument_forwarding(1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(1, **hash1#{block})") + check_allocations(0, 0, "argument_forwarding(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 0, "argument_forwarding(1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(**nill#{block})") + check_allocations(0, 0, "argument_forwarding(*nil, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(0, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})") + check_allocations(0, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **nil#{block})") + + check_allocations(0, 0, "argument_forwarding(*r2k_empty_array#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_array#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_empty_array1#{block})") + check_allocations(0, 0, "argument_forwarding(*r2k_array1#{block})") + RUBY + end + + def test_ruby2_keywords + check_allocations(<<~RUBY) + def self.r2k(*a#{block}); end + singleton_class.send(:ruby2_keywords, :r2k) + + check_allocations(1, 1, "r2k(1, a: 2#{block})") + check_allocations(1, 1, "r2k(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "r2k(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "r2k(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "r2k(1, **nil#{block})") + check_allocations(1, 0, "r2k(1, **empty_hash#{block})") + check_allocations(1, 1, "r2k(1, **hash1#{block})") + check_allocations(1, 1, "r2k(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "r2k(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "r2k(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "r2k(1, *empty_array#{block})") + check_allocations(1, 0, "r2k(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "r2k(*array1, a: 2#{block})") + + check_allocations(1, 0, "r2k(**nill#{block})") + check_allocations(1, 0, "r2k(*nil, **nill#{block})") + check_allocations(1, 0, "r2k(*array1, **nill#{block})") + check_allocations(1, 0, "r2k(*array1, **empty_hash#{block})") + check_allocations(1, 1, "r2k(*array1, **hash1#{block})") + check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "r2k(*array1, *empty_array#{block})") + check_allocations(1, 0, "r2k(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "r2k(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "r2k(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "r2k(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "r2k(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "r2k(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "r2k(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "r2k(*array1, **nil#{block})") + + check_allocations(1, 0, "r2k(*r2k_empty_array#{block})") + unless defined?(RubyVM::YJIT.enabled?) && RubyVM::YJIT.enabled? + # YJIT may or may not allocate depending on arch? + check_allocations(1, 1, "r2k(*r2k_array#{block})") + check_allocations(1, 0, "r2k(*r2k_empty_array1#{block})") + check_allocations(1, 1, "r2k(*r2k_array1#{block})") + end + RUBY + end + + def test_no_array_allocation_with_splat_and_nonstatic_keywords + check_allocations(<<~RUBY) + def self.keyword(a: nil, b: nil#{block}); end + def self.Object; Object end + + check_allocations(0, 1, "keyword(*nil, a: empty_array#{block})") # LVAR + check_allocations(0, 1, "keyword(*empty_array, a: empty_array#{block})") # LVAR + check_allocations(0, 1, "->{keyword(*empty_array, a: empty_array#{block})}.call") # DVAR + check_allocations(0, 1, "$x = empty_array; keyword(*empty_array, a: $x#{block})") # GVAR + check_allocations(0, 1, "@x = empty_array; keyword(*empty_array, a: @x#{block})") # IVAR + check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword(*empty_array, a: X#{block})") # CONST + check_allocations(0, 1, "keyword(*empty_array, a: Object::X#{block})") # COLON2 - safe + check_allocations(1, 1, "keyword(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe + check_allocations(0, 1, "keyword(*empty_array, a: ::X#{block})") # COLON3 + check_allocations(0, 1, "T = self; #{'B = block' unless block.empty?}; class Object; @@x = X; T.keyword(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1#{block})") # INTEGER + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: 'a'#{block})") # STR + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: :b#{block})") # SYM + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: /a/#{block})") # REGX + check_allocations(0, 1, "keyword(*empty_array, a: self#{block})") # SELF + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: nil#{block})") # NIL + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: true#{block})") # TRUE + check_allocations(0, 1, "keyword(*empty_array, a: empty_array, b: false#{block})") # FALSE + check_allocations(0, 1, "keyword(*empty_array, a: ->{}#{block})") # LAMBDA + check_allocations(0, 1, "keyword(*empty_array, a: $1#{block})") # NTH_REF + check_allocations(0, 1, "keyword(*empty_array, a: $`#{block})") # BACK_REF + + # LIST: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array) + check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c]#{block})") + check_allocations(1, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x]#{block})") + # LIST unsafe: 2 (one for [Object()] and one for *empty_array) + check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [Object()]#{block})") + check_allocations(2, 1, "keyword(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})") + RUBY + end + + class WithBlock < self + def block + ', &block' + end + def only_block + '&block' + end + end + end + + class ProcCall < MethodCall + def munge_checks(checks) + return checks if @no_munge + sub = rep = nil + checks.split("\n").map do |line| + case line + when "singleton_class.send(:ruby2_keywords, :r2k)" + "r2k.ruby2_keywords" + when /\Adef self.([a-z0-9_]+)\((.*)\);(.*)end\z/ + sub = $1 + '(' + rep = $1 + '.(' + "#{$1} = #{$1} = proc{ |#{$2}| #{$3} }" + when /check_allocations/ + line.gsub(sub, rep) + else + line + end + end.join("\n") + end + + # Generic argument forwarding not supported in proc definitions + undef_method :test_argument_forwarding + undef_method :test_nested_argument_forwarding + + # Proc anonymous arguments cannot be used directly + undef_method :test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters + + def test_no_array_allocation_with_splat_and_nonstatic_keywords + @no_munge = true + + check_allocations(<<~RUBY) + keyword = keyword = proc{ |a: nil, b: nil #{block}| } + def self.Object; Object end + + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array#{block})") # LVAR + check_allocations(0, 1, "->{keyword.(*empty_array, a: empty_array#{block})}.call") # DVAR + check_allocations(0, 1, "$x = empty_array; keyword.(*empty_array, a: $x#{block})") # GVAR + check_allocations(0, 1, "@x = empty_array; keyword.(*empty_array, a: @x#{block})") # IVAR + check_allocations(0, 1, "self.class.const_set(:X, empty_array); keyword.(*empty_array, a: X#{block})") # CONST + check_allocations(0, 1, "keyword.(*empty_array, a: Object::X#{block})") # COLON2 - safe + check_allocations(1, 1, "keyword.(*empty_array, a: Object()::X#{block})") # COLON2 - unsafe + check_allocations(0, 1, "keyword.(*empty_array, a: ::X#{block})") # COLON3 + check_allocations(0, 1, "T = keyword; #{'B = block' unless block.empty?}; class Object; @@x = X; T.(*X, a: @@x#{', &B' unless block.empty?}) end") # CVAR + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1#{block})") # INTEGER + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0#{block})") # FLOAT + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0r#{block})") # RATIONAL + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 1.0i#{block})") # IMAGINARY + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: 'a'#{block})") # STR + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: :b#{block})") # SYM + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: /a/#{block})") # REGX + check_allocations(0, 1, "keyword.(*empty_array, a: self#{block})") # SELF + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: nil#{block})") # NIL + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: true#{block})") # TRUE + check_allocations(0, 1, "keyword.(*empty_array, a: empty_array, b: false#{block})") # FALSE + check_allocations(0, 1, "keyword.(*empty_array, a: ->{}#{block})") # LAMBDA + check_allocations(0, 1, "keyword.(*empty_array, a: $1#{block})") # NTH_REF + check_allocations(0, 1, "keyword.(*empty_array, a: $`#{block})") # BACK_REF + + # LIST safe: Only 1 array (literal [:c]), not 2 (one for [:c] and one for *empty_array) + check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c]#{block})") + check_allocations(1, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x]#{block})") + # LIST unsafe: 2 (one for [:c] and one for *empty_array) + check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [Object()]#{block})") + check_allocations(2, 1, "keyword.(*empty_array, a: empty_array, b: [:c, $x, Object()]#{block})") + RUBY + end + + class WithBlock < self + def block + ', &block' + end + def only_block + '&block' + end + end + end +end diff --git a/test/ruby/test_argf.rb b/test/ruby/test_argf.rb new file mode 100644 index 0000000000..55a06296aa --- /dev/null +++ b/test/ruby/test_argf.rb @@ -0,0 +1,1153 @@ +# frozen_string_literal: false +require 'test/unit' +require 'timeout' +require 'tmpdir' +require 'tempfile' +require 'fileutils' + +class TestArgf < Test::Unit::TestCase + def setup + @tmpdir = Dir.mktmpdir + @tmp_count = 0 + @t1 = make_tempfile("argf-foo", %w"1 2", binmode: true) + @t2 = make_tempfile("argf-bar", %w"3 4", binmode: true) + @t3 = make_tempfile("argf-baz", %w"5 6", binmode: true) + end + + def teardown + FileUtils.rmtree(@tmpdir) + end + + def make_tempfile(basename = "argf-qux", data = %w[foo bar baz], binmode: false) + @tmp_count += 1 + path = "#{@tmpdir}/#{basename}-#{@tmp_count}" + File.open(path, "w") do |f| + f.binmode if binmode + f.puts(*data) + f + end + end + + def ruby(*args, external_encoding: Encoding::UTF_8) + args = ['-e', '$>.write($<.read)'] if args.empty? + ruby = EnvUtil.rubybin + f = IO.popen([ruby] + args, 'r+', external_encoding: external_encoding) + yield(f) + ensure + f.close unless !f || f.closed? + end + + def no_safe_rename + /cygwin|mswin|mingw|bccwin/ =~ RUBY_PLATFORM + end + + 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| + expected.each_with_index do |e, i| + /#=> *(.*)/ =~ e or next + a = f.gets + assert_not_nil(a, "[ruby-dev:34445]: remained") + assert_equal($1, a.chomp, "[ruby-dev:34445]: line #{line+i}") + end + end + end + + def test_argf + 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", 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", 5] + p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["6", 6, "6", 6] + }; + end + + def test_lineno + 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 + $. = 2000 + a.gets; p($.) #=> 2001 + a.gets; p($.) #=> 2001 + }; + end + + def test_lineno2 + 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 + $. = 2000 + a.gets; p($.) #=> 2000 + a.gets; p($.) #=> 2000 + }; + end + + def test_lineno3 + 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 + }; + end + + def test_lineno_after_shebang + expected = %w"1 1 1 2 2 2 3 3 1 4 4 2" + assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected) + #!/usr/bin/env ruby + {# + ARGF.each do |line| + puts [$., ARGF.lineno, ARGF.file.lineno] + end + }; + end + + def test_new_lineno_each + f = ARGF.class.new(@t1.path, @t2.path, @t3.path) + result = [] + 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], + "#{<<~"{#"}\n#{<<~'};'}") + {# + ARGF.inplace_mode = '.bak' + while line = ARGF.gets + puts line.chomp + '.new' + end + }; + 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)) + assert_equal("1\n2\n", File.read(@t1.path + ".bak")) + assert_equal("3\n4\n", File.read(@t2.path + ".bak")) + assert_equal("5\n6\n", File.read(@t3.path + ".bak")) + end + + def test_inplace2 + 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' + p ARGF.inplace_mode + ARGF.inplace_mode = nil + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + p ARGF.inplace_mode + ARGF.inplace_mode = '.bak' + puts ARGF.gets.chomp + '.new' + p ARGF.inplace_mode + ARGF.inplace_mode = nil + puts ARGF.gets.chomp + '.new' + }; + 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)) + assert_equal("1\n2\n", File.read(@t1.path + ".bak")) + assert_equal(false, File.file?(@t2.path + ".bak")) + assert_equal("5\n6\n", File.read(@t3.path + ".bak")) + end + + def test_inplace3 + 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 + $-i = nil + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + p $-i + $-i = '.bak' + puts ARGF.gets.chomp + '.new' + p $-i + $-i = nil + puts ARGF.gets.chomp + '.new' + }; + 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)) + assert_equal("1\n2\n", File.read(@t1.path + ".bak")) + assert_equal(false, File.file?(@t2.path + ".bak")) + assert_equal("5\n6\n", File.read(@t3.path + ".bak")) + end + + def test_inplace_rename_impossible + t = make_tempfile + + 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 + omit "no default external encoding" + t = nil + ["\u{3042}", "\u{e9}"].any? do |n| + t = make_tempfile(n.encode(ext)) + rescue Encoding::UndefinedConversionError + end + t or omit "no name to test" + assert_in_out_err(["-i.bak", "-", t.path], + "#{<<~"{#"}\n#{<<~'};'}") + {# + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + puts ARGF.gets.chomp + '.new' + }; + assert_equal("foo.new\n""bar.new\n""baz.new\n", File.read(t.path)) + assert_equal("foo\n""bar\n""baz\n", File.read(t.path + ".bak")) + end + + def test_inplace_no_backup + t = make_tempfile + + 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 + assert_equal([], e) + assert_equal([], r) + assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path)) + end + end + end + + def test_inplace_dup + t = make_tempfile + + assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}", [], []) + {# + ARGF.inplace_mode = '.bak' + f = ARGF.dup + while line = f.gets + puts line.chomp + '.new' + end + }; + assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path)) + end + + def test_inplace_stdin + 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 + }; + end + + def test_inplace_stdin2 + 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 + }; + 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_inplace_bug_17117 + assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}") + {# + #!/usr/bin/ruby -pi.bak + BEGIN { + GC.start + arr = [] + 1000000.times { |x| arr << "fooo#{x}" } + } + puts "hello" + }; + assert_equal("hello\n1\nhello\n2\n", File.read(@t1.path)) + assert_equal("1\n2\n", File.read("#{@t1.path}.bak")) + end + + def test_encoding + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + 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', "#{<<~"{#"}\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 + }; + 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| + assert_equal(x, a.shift) + end + assert_equal('end', a.shift) + end + end + + def test_seek + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# + ARGF.seek(4) + p ARGF.gets #=> "3\n" + ARGF.seek(0, IO::SEEK_END) + p ARGF.gets #=> "5\n" + ARGF.seek(4) + p ARGF.gets #=> nil + begin + ARGF.seek(0) + rescue + puts "end" #=> end + end + }; + end + + def test_set_pos + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# + ARGF.pos = 4 + p ARGF.gets #=> "3\n" + ARGF.pos = 4 + p ARGF.gets #=> "5\n" + ARGF.pos = 4 + p ARGF.gets #=> nil + begin + ARGF.pos = 4 + rescue + puts "end" #=> end + end + }; + end + + def test_rewind + assert_src_expected("#{<<~"{#"}\n#{<<~'};'}") + {# + ARGF.pos = 4 + ARGF.rewind + p ARGF.gets #=> "1\n" + ARGF.pos = 4 + p ARGF.gets #=> "3\n" + ARGF.pos = 4 + p ARGF.gets #=> "5\n" + ARGF.pos = 4 + p ARGF.gets #=> nil + begin + ARGF.rewind + rescue + puts "end" #=> end + end + }; + end + + def test_fileno + 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) + assert_match(/^\d+$/, fd2) + assert_match(/^\d+$/, fd3) + assert_match(/^\d+$/, fd4) + assert_equal('end', tag) + end + end + + def test_to_io + 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) + assert_equal(f11, f13) + assert_equal(f21, f22) + assert_equal(f31, f32) + assert_match(/\(closed\)/, f4) + f4.sub!(/ \(closed\)/, "") + assert_equal(f31, f4) + end + end + + def test_eof + 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 + }; + a = f.read.split("\n") + (%w(false) + (%w(false true) * 3) + %w(end)).each do |x| + assert_equal(x, a.shift) + end + end + + t1 = "#{@tmpdir}/argf-hoge" + t2 = "#{@tmpdir}/argf-moge" + File.binwrite(t1, "foo\n") + File.binwrite(t2, "bar\n") + ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1, t2) do |f| + assert_equal(%w(false), f.read.split(/\n/)) + end + end + + def test_read + ruby('-e', "p ARGF.read(8)", @t1.path, @t2.path, @t3.path) do |f| + assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read) + end + end + + def test_read2 + 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', "#{<<~"{#"}\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', "#{<<~"{#"}\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 + }; + assert_equal("1\n2\n3\n4\n5\n6\n", f.read) + end + end + + def test_readpartial2 + 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 + }; + f.binmode + f.puts("foo") + f.puts("bar") + f.puts("baz") + f.close_write + assert_equal("foo\nbar\nbaz\n", f.read) + 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', "#{<<~"{#"}\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', "#{<<~"{#"}\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', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + s = +"" + begin + while c = ARGF.readchar + s << c + end + rescue EOFError + puts s + end + }; + assert_equal("1\n2\n3\n4\n5\n6\n", f.read) + end + end + + def test_readbyte + 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 + }; + 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', "#{<<~"{#"}\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 + + def test_each_line_paragraph + assert_in_out_err(['-e', 'ARGF.each_line("") {|para| p para}'], "a\n\nb\n", + ["\"a\\n\\n\"", "\"b\\n\""], []) + end + + def test_each_line_chomp + assert_in_out_err(['-e', 'ARGF.each_line(chomp: false) {|para| p para}'], "a\nb\n", + ["\"a\\n\"", "\"b\\n\""], []) + assert_in_out_err(['-e', 'ARGF.each_line(chomp: true) {|para| p para}'], "a\nb\n", + ["\"a\"", "\"b\""], []) + + t = make_tempfile + argf = ARGF.class.new(t.path) + lines = [] + begin + argf.each_line(chomp: true) do |line| + lines << line + end + ensure + argf.close + end + assert_equal(%w[foo bar baz], lines) + end + + def test_each_byte + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + 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', "#{<<~"{#"}\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', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + puts ARGF.filename.dump + end while ARGF.gets + puts ARGF.filename.dump + }; + a = f.read.split("\n") + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + end + end + + def test_filename2 + ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + begin + puts $FILENAME.dump + end while ARGF.gets + puts $FILENAME.dump + }; + a = f.read.split("\n") + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + end + end + + def test_file + 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 + }; + a = f.read.split("\n") + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t1.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t2.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + assert_equal(@t3.path.dump, a.shift) + end + end + + def test_binmode + bug5268 = '[ruby-core:39234]' + File.binwrite(@t3.path, "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\r\n6\r\n", f.read, bug5268) + end + end + + def test_textmode + bug5268 = '[ruby-core:39234]' + File.binwrite(@t3.path, "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', "#{<<~"{#"}\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', "#{<<~"{#"}\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', "#{<<~"{#"}\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 + p ARGF.closed? + }; + assert_equal("false\nfalse\nfalse\nfalse\ntrue\n", f.read) + end + end + + def test_argv + ruby('-e', "p ARGF.argv; p $*", @t1.path, @t2.path, @t3.path) do |f| + assert_equal([@t1.path, @t2.path, @t3.path].inspect, f.gets.chomp) + 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_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal(%w[foo bar baz], argf.readlines(chomp: true)) + ensure + argf.close + end + + assert_in_out_err(['-e', 'p readlines(chomp: true)'], "a\nb\n", + ["[\"a\", \"b\"]"], []) + end + + def test_readline_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal("foo", argf.readline(chomp: true)) + ensure + argf.close + end + + assert_in_out_err(['-e', 'p readline(chomp: true)'], "a\nb\n", + ["\"a\""], []) + end + + def test_gets_chomp + t = make_tempfile + argf = ARGF.class.new(t.path) + begin + assert_equal("foo", argf.gets(chomp: true)) + ensure + argf.close + end + + assert_in_out_err(['-e', 'p gets(chomp: true)'], "a\nb\n", + ["\"a\""], []) + end + + def test_readlines_twice + bug5952 = '[ruby-dev:45160]' + assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952) + end + + def test_each_codepoint + ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f| + {# + print Marshal.dump(ARGF.each_codepoint.to_a) + }; + 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 + + def test_sized_read + s = "a" + [@t1, @t2, @t3].each { |t| + File.binwrite(t.path, s) + s = s.succ + } + + ruby('-e', "print ARGF.read(3)", @t1.path, @t2.path, @t3.path) do |f| + assert_equal("abc", f.read) + end + + argf = ARGF.class.new(@t1.path, @t2.path, @t3.path) + begin + assert_equal("abc", argf.read(3)) + ensure + argf.close + end + end + + def test_putc + t = make_tempfile("argf-#{__method__}", 'bar') + ruby('-pi-', '-e', "print ARGF.putc('x')", t.path) do |f| + end + assert_equal("xxbar\n", File.read(t.path)) + end + + def test_puts + t = make_tempfile("argf-#{__method__}", 'bar') + err = "#{@tmpdir}/errout" + ruby('-pi-', '-W2', '-e', "print ARGF.puts('foo')", t.path, {err: err}) do |f| + end + assert_equal("foo\nbar\n", File.read(t.path)) + assert_empty File.read(err) + end + + def test_print + t = make_tempfile("argf-#{__method__}", 'bar') + ruby('-pi-', '-e', "print ARGF.print('foo')", t.path) do |f| + end + assert_equal("foobar\n", File.read(t.path)) + end + + def test_printf + t = make_tempfile("argf-#{__method__}", 'bar') + ruby('-pi-', '-e', "print ARGF.printf('%s', 'foo')", t.path) do |f| + end + assert_equal("foobar\n", File.read(t.path)) + end +end diff --git a/test/ruby/test_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb new file mode 100644 index 0000000000..5e2a825265 --- /dev/null +++ b/test/ruby/test_arithmetic_sequence.rb @@ -0,0 +1,491 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestArithmeticSequence < Test::Unit::TestCase + def test_new + assert_raise(NoMethodError) { Enumerator::ArithmeticSequence.new } + end + + def test_allocate + assert_raise(TypeError) { Enumerator::ArithmeticSequence.allocate } + end + + def test_begin + assert_equal(1, 1.step.begin) + assert_equal(1, 1.step(10).begin) + assert_equal(1, 1.step(to: 10).begin) + assert_equal(1, 1.step(nil).begin) + assert_equal(1, 1.step(to: nil).begin) + assert_equal(1, 1.step(by: 2).begin) + assert_equal(1, 1.step(by: -1).begin) + assert_equal(1, 1.step(by: nil).begin) + assert_equal(1, 1.step(10, 2).begin) + assert_equal(1, 1.step(10, by: 2).begin) + assert_equal(1, 1.step(to: 10, by: 2).begin) + assert_equal(10, 10.step(to: 1, by: -1).begin) + assert_equal(10, 10.step(to: 1, by: -2).begin) + assert_equal(10, 10.step(to: -1, by: -2).begin) + assert_equal(10.0, 10.0.step(to: -1.0, by: -2.0).begin) + + assert_equal(3, (3..).step(2).begin) + assert_equal(4, (4...).step(7).begin) + assert_equal(nil, (..10).step(9).begin) + assert_equal(nil, (...11).step(5).begin) + end + + def test_end + assert_equal(nil, 1.step.end) + assert_equal(10, 1.step(10).end) + assert_equal(10, 1.step(to: 10).end) + assert_equal(nil, 1.step(nil).end) + assert_equal(nil, 1.step(to: nil).end) + assert_equal(nil, 1.step(by: 2).end) + assert_equal(nil, 1.step(by: -1).end) + assert_equal(nil, 1.step(by: nil).end) + assert_equal(10, 1.step(10, 2).end) + assert_equal(10, 1.step(10, by: 2).end) + assert_equal(10, 1.step(to: 10, by: 2).end) + assert_equal(1, 10.step(to: 1, by: -1).end) + assert_equal(1, 10.step(to: 1, by: -2).end) + assert_equal(-1, 10.step(to: -1, by: -2).end) + assert_equal(-1.0, 10.0.step(to: -1.0, by: -2.0).end) + + assert_equal(nil, (3..).step(2).end) + assert_equal(nil, (4...).step(7).end) + assert_equal(10, (..10).step(9).end) + assert_equal(11, (...11).step(5).end) + end + + def test_exclude_end_p + assert_equal(false, 1.step.exclude_end?) + assert_equal(false, 1.step(10).exclude_end?) + assert_equal(false, 1.step(to: 10).exclude_end?) + assert_equal(false, 1.step(nil).exclude_end?) + assert_equal(false, 1.step(to: nil).exclude_end?) + assert_equal(false, 1.step(by: 2).exclude_end?) + assert_equal(false, 1.step(by: -1).exclude_end?) + assert_equal(false, 1.step(by: nil).exclude_end?) + assert_equal(false, 1.step(10, 2).exclude_end?) + assert_equal(false, 1.step(10, by: 2).exclude_end?) + assert_equal(false, 1.step(to: 10, by: 2).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -1).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -2).exclude_end?) + assert_equal(false, 10.step(to: -1, by: -2).exclude_end?) + + assert_equal(false, (3..).step(2).exclude_end?) + assert_equal(true, (4...).step(7).exclude_end?) + assert_equal(false, (..10).step(9).exclude_end?) + assert_equal(true, (...11).step(5).exclude_end?) + end + + def test_step + assert_equal(1, 1.step.step) + assert_equal(1, 1.step(10).step) + assert_equal(1, 1.step(to: 10).step) + assert_equal(1, 1.step(nil).step) + assert_equal(1, 1.step(to: nil).step) + assert_equal(2, 1.step(by: 2).step) + assert_equal(-1, 1.step(by: -1).step) + assert_equal(1, 1.step(by: nil).step) + assert_equal(2, 1.step(10, 2).step) + assert_equal(2, 1.step(10, by: 2).step) + assert_equal(2, 1.step(to: 10, by: 2).step) + assert_equal(-1, 10.step(to: 1, by: -1).step) + assert_equal(-2, 10.step(to: 1, by: -2).step) + assert_equal(-2, 10.step(to: -1, by: -2).step) + assert_equal(-2.0, 10.0.step(to: -1.0, by: -2.0).step) + + assert_equal(2, (3..).step(2).step) + assert_equal(7, (4...).step(7).step) + assert_equal(9, (..10).step(9).step) + assert_equal(5, (...11).step(5).step) + end + + def test_eq + seq = 1.step + assert_equal(seq, seq) + assert_equal(seq, 1.step) + assert_equal(seq, 1.step(nil)) + end + + def test_eqq + seq = 1.step + assert_operator(seq, :===, seq) + assert_operator(seq, :===, 1.step) + assert_operator(seq, :===, 1.step(nil)) + end + + def test_eql_p + seq = 1.step + assert_operator(seq, :eql?, seq) + assert_operator(seq, :eql?, 1.step) + assert_operator(seq, :eql?, 1.step(nil)) + end + + def test_hash + seq = 1.step + assert_equal(seq.hash, seq.hash) + assert_equal(seq.hash, 1.step.hash) + assert_equal(seq.hash, 1.step(nil).hash) + assert_kind_of(String, seq.hash.to_s) + end + + def test_first + seq = 1.step + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 2, 3], seq.first(3)) + + seq = 1.step(by: 2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = 10.step(by: -2) + assert_equal(10, seq.first) + assert_equal([10], seq.first(1)) + assert_equal([10, 8, 6], seq.first(3)) + + seq = 1.step(by: 4) + assert_equal([1, 5, 9], seq.first(3)) + + seq = 1.step(10, by: 4) + assert_equal([1, 5, 9], seq.first(5)) + + seq = 1.step(0) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 1.step(10, by: -1) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 10.0.step(-1.0, by: -2.0) + assert_equal(10.0, seq.first) + assert_equal([10.0], seq.first(1)) + assert_equal([10.0, 8.0, 6.0], seq.first(3)) + + seq = (1..).step(2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = (..10).step(2) + assert_equal(nil, seq.first) + assert_raise(TypeError) { seq.first(1) } + assert_raise(TypeError) { seq.first(3) } + end + + def test_first_bug15518 + bug15518 = '[Bug #15518]' + seq = (1 .. 10.0).step(1) + five_float_classes = Array.new(5) { Float } + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + seq = (1 .. Float::INFINITY).step(1r) + assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518) + assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518) + end + + def test_last + seq = 1.step(10) + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = 1.step(10, 2) + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([5, 7, 9], seq.last(3)) + + seq = 10.step(1, -2) + assert_equal(2, seq.last) + assert_equal([2], seq.last(1)) + assert_equal([6, 4, 2], seq.last(3)) + + seq = 10.step(-1, -2) + assert_equal(0, seq.last) + + seq = 1.step(10, 4) + assert_equal([1, 5, 9], seq.last(5)) + + seq = 10.step(1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = 1.step(10, -1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = (1..10).step + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = (1...10).step + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([7, 8, 9], seq.last(3)) + + seq = 10.0.step(-3.0, by: -2.0) + assert_equal(-2.0, seq.last) + assert_equal([-2.0], seq.last(1)) + assert_equal([2.0, 0.0, -2.0], seq.last(3)) + end + + def test_last_with_float + res = (1..3).step(2).last(2.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(5.0) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + end + + def test_last_with_rational + res = (1..3).step(2).last(2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + + res = (1..3).step(2).last(10/2r) + assert_equal([1, 3], res) + assert_instance_of Integer, res[0] + assert_instance_of Integer, res[1] + end + + def test_last_bug17218 + seq = (1.0997r .. 1.1r).step(0.0001r) + assert_equal(1.1r, seq.last, '[ruby-core:100312] [Bug #17218]') + end + + def test_to_a + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1.step(10).to_a) + assert_equal([1, 3, 5, 7, 9], 1.step(10, 2).to_a) + assert_equal([1, 3, 5, 7, 9], (1..10).step(2).to_a) + assert_equal([10, 8, 6, 4, 2], 10.step(1, by: -2).to_a) + assert_equal([10, 8, 6, 4, 2], (10..1).step(-2).to_a) + assert_equal([10.0, 8.0, 6.0, 4.0, 2.0], (10.0..1.0).step(-2.0).to_a) + end + + def test_to_a_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_num_equal_type([1/10r, 1/5r, 3/10r, 2/5r, 1/2r], seq.to_a, + '[ruby-core:90648] [Bug #15444]') + end + + def test_to_a_bug17218 + seq = (1.0997r .. 1.1r).step(0.0001r) + assert_equal([1.0997r, 1.0998r, 1.0999r, 1.1r], seq.to_a, '[ruby-core:100312] [Bug #17218]') + end + + def test_slice + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [7, 9]], seq.each_slice(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [4, 2]], seq.each_slice(3).to_a) + end + + def test_cons + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [3, 5, 7], [5, 7, 9]], seq.each_cons(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [8, 6, 4], [6, 4, 2]], seq.each_cons(3).to_a) + end + + def test_with_index + seq = 1.step(6, 2) + assert_equal([[1, 0], [3, 1], [5, 2]], seq.with_index.to_a) + assert_equal([[1, 10], [3, 11], [5, 12]], seq.with_index(10).to_a) + + seq = 10.step(5, -2) + assert_equal([[10, 0], [8, 1], [6, 2]], seq.with_index.to_a) + assert_equal([[10, 10], [8, 11], [6, 12]], seq.with_index(10).to_a) + end + + def test_with_object + obj = [0, 1] + seq = 1.step(10, 2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([25, 945], ret) + + obj = [0, 1] + seq = 10.step(1, -2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([30, 3840], ret) + end + + def test_next + seq = 1.step(10, 2) + [1, 3, 5, 7, 9].each do |i| + assert_equal(i, seq.next) + end + + seq = 10.step(1, -2) + [10, 8, 6, 4, 2].each do |i| + assert_equal(i, seq.next) + end + end + + def test_next_bug15444 + seq = ((1/10r)..(1/2r)).step(1/10r) + assert_equal(1/10r, seq.next, '[ruby-core:90648] [Bug #15444]') + end + + def test_next_rewind + seq = 1.step(6, 2) + assert_equal(1, seq.next) + assert_equal(3, seq.next) + seq.rewind + assert_equal(1, seq.next) + assert_equal(3, seq.next) + assert_equal(5, seq.next) + assert_raise(StopIteration) { seq.next } + + seq = 10.step(5, -2) + assert_equal(10, seq.next) + assert_equal(8, seq.next) + seq.rewind + assert_equal(10, seq.next) + assert_equal(8, seq.next) + assert_equal(6, seq.next) + assert_raise(StopIteration) { seq.next } + end + + def test_next_after_stopiteration + seq = 1.step(2, 2) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + seq.rewind + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + end + + def test_stop_result + seq = 1.step(2, 2) + res = seq.each {} + assert_equal(1, seq.next) + exc = assert_raise(StopIteration) { seq.next } + assert_equal(res, exc.result) + end + + def test_peek + seq = 1.step(2, 2) + assert_equal(1, seq.peek) + assert_equal(1, seq.peek) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + + seq = 10.step(9, -2) + assert_equal(10, seq.peek) + assert_equal(10, seq.peek) + assert_equal(10, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + end + + def test_next_values + seq = 1.step(2, 2) + assert_equal([1], seq.next_values) + end + + def test_peek_values + seq = 1.step(2, 2) + assert_equal([1], seq.peek_values) + end + + def test_num_step_inspect + assert_equal('(1.step)', 1.step.inspect) + assert_equal('(1.step(10))', 1.step(10).inspect) + assert_equal('(1.step(10, 2))', 1.step(10, 2).inspect) + assert_equal('(1.step(10, by: 2))', 1.step(10, by: 2).inspect) + assert_equal('(1.step(by: 2))', 1.step(by: 2).inspect) + end + + def test_range_step_inspect + assert_equal('((1..).step)', (1..).step.inspect) + assert_equal('((1..10).step)', (1..10).step.inspect) + assert_equal('((1..10).step(2))', (1..10).step(2).inspect) + end + + def test_num_step_size + assert_equal(10, 1.step(10).size) + assert_equal(5, 1.step(10, 2).size) + assert_equal(4, 1.step(10, 3).size) + assert_equal(1, 1.step(10, 10).size) + assert_equal(0, 1.step(0).size) + assert_equal(Float::INFINITY, 1.step.size) + + assert_equal(10, 10.step(1, -1).size) + assert_equal(5, 10.step(1, -2).size) + assert_equal(4, 10.step(1, -3).size) + assert_equal(1, 10.step(1, -10).size) + assert_equal(0, 1.step(2, -1).size) + assert_equal(Float::INFINITY, 1.step(by: -1).size) + end + + def test_range_step_size + assert_equal(10, (1..10).step.size) + assert_equal(9, (1...10).step.size) + assert_equal(5, (1..10).step(2).size) + assert_equal(5, (1...10).step(2).size) + assert_equal(4, (1...9).step(2).size) + assert_equal(Float::INFINITY, (1..).step.size) + + assert_equal(10, (10..1).step(-1).size) + assert_equal(9, (10...1).step(-1).size) + assert_equal(5, (10..1).step(-2).size) + assert_equal(5, (10...1).step(-2).size) + assert_equal(4, (10...2).step(-2).size) + assert_equal(Float::INFINITY, (1..).step(-1).size) + end + + def assert_num_equal_type(ary1, ary2, message=nil) + assert_equal(ary1.length, ary2.length, message) + ary1.zip(ary2) do |e1, e2| + assert_equal(e1.class, e2.class, message) + if e1.is_a? Complex + assert_equal(e1.real, e2.real, message) + assert_equal(e1.imag, e2.imag, message) + else + assert_equal(e1, e2, message) + end + end + end + + def test_complex + assert_num_equal_type([1, 1+1i, 1+2i], (1..).step(1i).take(3)) + assert_num_equal_type([1, 1+1.0i, 1+2.0i], (1..).step(1.0i).take(3)) + assert_num_equal_type([0.0, 0.0+1.0i, 0.0+2.0i], (0.0..).step(1.0i).take(3)) + assert_num_equal_type([0.0+0.0i, 0.0+1.0i, 0.0+2.0i], (0.0i..).step(1.0i).take(3)) + end + + def test_sum + assert_equal([1, 3, 5, 7, 9].sum, (1..10).step(2).sum) + assert_equal([1.0, 2.5, 4.0, 5.5, 7.0, 8.5, 10.0].sum, (1.0..10.0).step(1.5).sum) + assert_equal([1/2r, 1r, 3/2r, 2, 5/2r, 3, 7/2r, 4].sum, ((1/2r)...(9/2r)).step(1/2r).sum) + end +end diff --git a/test/ruby/test_arity.rb b/test/ruby/test_arity.rb new file mode 100644 index 0000000000..bd26d5f0f5 --- /dev/null +++ b/test/ruby/test_arity.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestArity < Test::Unit::TestCase + def assert_arity(expected, method_proc = nil, argc = 0) + args = (1..argc).to_a + assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b(\d+)\b.* (\d\S*?)\)/) do + case method_proc + when nil + yield + when Symbol + method(method_proc).call(*args) + else + method_proc.call(*args) + end + end + assert_equal expected, [$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_arity(%w[1 0], :a, 1) + assert_arity(%w[10 7..9], :b, 10) + assert_arity(%w[2 3+], :c, 2) + assert_arity(%w[2 1], :d, 2) + assert_arity(%w[0 1], :d, 0) + assert_arity(%w[2 1], :e, 2) + assert_arity(%w[0 1], :e, 0) + assert_arity(%w[1 2+], :f, 1) + end + + def test_proc_err_mess + assert_arity(%w[0 1..2], ->(b, c=42){}, 0) + assert_arity(%w[1 2+], ->(a, b, c=42, *d){}, 1) + assert_arity(%w[3 4+], ->(a, b, *c, d, e){}, 3) + assert_arity(%w[3 1..2], ->(b, c=42){}, 3) + assert_arity(%w[1 0], ->(&block){}, 1) + # Double checking: + p = Proc.new{|b, c=42| :ok} + assert_equal :ok, p.call(1, 2, 3) + assert_equal :ok, p.call + end + + def test_message_change_issue_6085 + assert_arity(%w[3 1..2]) { SignalException.new(1, "", nil) } + assert_arity(%w[1 0]) { Hash.new(1){} } + assert_arity(%w[3 1..2]) { Module.send :define_method, 1, 2, 3 } + assert_arity(%w[1 2]) { "".sub!(//) } + assert_arity(%w[0 1..2]) { "".sub!{} } + assert_arity(%w[0 1+]) { exec } + end +end diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb new file mode 100644 index 0000000000..04e15b6d87 --- /dev/null +++ b/test/ruby/test_array.rb @@ -0,0 +1,3660 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' +require "delegate" +require "rbconfig/sizeof" + +class TestArray < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + @cls = Array + end + + def teardown + $VERBOSE = @verbose + end + + def assert_equal_instance(x, y, *msg) + assert_equal(x, y, *msg) + assert_instance_of(x.class, y) + end + + def test_percent_i + assert_equal([:foo, :bar], %i[foo bar]) + assert_equal([:"\"foo"], %i["foo]) + 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) + assert_equal("1:2", [1, 2] * ":") + + assert_equal([1, 2].hash, [1, 2].hash) + + assert_equal([2,3], [1,2,3] & [2,3,4]) + assert_equal([1,2,3,4], [1,2,3] | [2,3,4]) + assert_equal([1,2,3] - [2,3], [1]) + + x = [0, 1, 2, 3, 4, 5] + assert_equal(2, x[2]) + assert_equal([1, 2, 3], x[1..3]) + assert_equal([1, 2, 3], x[1,3]) + assert_equal([3, 4, 5], x[3..]) + assert_equal([0, 1, 2], x[..2]) + assert_equal([0, 1], x[...2]) + + x[0, 2] = 10 + assert_equal([10, 2, 3, 4, 5], x) + + x[0, 0] = -1 + assert_equal([-1, 10, 2, 3, 4, 5], x) + + x[-1, 1] = 20 + assert_equal(20, x[-1]) + assert_equal(20, x.pop) + end + + def test_array_andor_0 + assert_equal([2], ([1,2,3]&[2,4,6])) + assert_equal([1,2,3,4,6], ([1,2,3]|[2,4,6])) + end + + def test_compact_0 + a = [nil, 1, nil, nil, 5, nil, nil] + assert_equal [1, 5], a.compact + assert_equal [nil, 1, nil, nil, 5, nil, nil], a + a.compact! + assert_equal [1, 5], a + end + + def test_uniq_0 + x = [1, 1, 4, 2, 5, 4, 5, 1, 2] + x.uniq! + assert_equal([1, 4, 2, 5], x) + end + + def test_empty_0 + assert_equal true, [].empty? + assert_equal false, [1].empty? + assert_equal false, [1, 1, 4, 2, 5, 4, 5, 1, 2].empty? + end + + def test_sort_0 + x = ["it", "came", "to", "pass", "that", "..."] + x = x.sort.join(" ") + assert_equal("... came it pass that to", x) + x = [2,5,3,1,7] + x.sort!{|a,b| a<=>b} # sort with condition + assert_equal([1,2,3,5,7], x) + x.sort!{|a,b| b-a} # reverse sort + assert_equal([7,5,3,2,1], x) + end + + def test_split_0 + x = "The Book of Mormon" + assert_equal(x.reverse, x.split(//).reverse!.join) + assert_equal(x.reverse, x.reverse!) + assert_equal("g:n:i:r:t:s: :e:t:y:b: :1", "1 byte string".split(//).reverse.join(":")) + x = "a b c d" + assert_equal(['a', 'b', 'c', 'd'], x.split) + assert_equal(['a', 'b', 'c', 'd'], x.split(' ')) + end + + def test_misc_0 + 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 + assert_equal([["a", "12"], ["b", "22"]], "a=12;b=22".scan(/(.*?)=(\d*);?/)) + + x = [1] + assert_equal('1:1:1:1:1', (x * 5).join(":")) + assert_equal('1', (x * 1).join(":")) + assert_equal('', (x * 0).join(":")) + + assert_instance_of(Array, (@cls[] * 5)) + assert_instance_of(Array, (@cls[1] * 5)) + + *x = *(1..7).to_a + assert_equal(7, x.size) + assert_equal([1, 2, 3, 4, 5, 6, 7], x) + + x = [1,2,3] + x[1,0] = x + assert_equal([1,1,2,3,2,3], x) + + x = [1,2,3] + x[-1,0] = x + assert_equal([1,2,1,2,3,3], x) + + x = [1,2,3] + x.concat(x) + assert_equal([1,2,3,1,2,3], x) + + x = [1,2,3] + x.clear + assert_equal([], x) + + x = [1,2,3] + y = x.dup + x << 4 + y << 5 + assert_equal([1,2,3,4], x) + assert_equal([1,2,3,5], y) + end + + def test_beg_end_0 + x = [1, 2, 3, 4, 5] + + 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)) + assert_equal([1, 2, 3, 4, 5], x) + + 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)) + assert_equal([1, 2, 3, 4, 5], x) + end + + 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] + assert_equal(["baz","baz"], x.find_all{ |obj| obj == "baz" }) + assert_equal([3,3], x.find_all{ |obj| obj == 3 }) + end + + def test_fill_0 + assert_equal([-1, -1, -1, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1)) + assert_equal([0, 1, 2, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1, 3)) + assert_equal([0, 1, 2, -1, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, 3, 2)) + assert_equal([0, 1, 2, -1, -1, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1, 3, 5)) + assert_equal([0, 1, -1, -1, 4, 5], [0, 1, 2, 3, 4, 5].fill(-1, 2, 2)) + assert_equal([0, 1, -1, -1, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1, 2, 5)) + assert_equal([0, 1, 2, 3, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, -2, 1)) + assert_equal([0, 1, 2, 3, -1, -1, -1], [0, 1, 2, 3, 4, 5].fill(-1, -2, 3)) + assert_equal([0, 1, 2, -1, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, 3..4)) + assert_equal([0, 1, 2, -1, 4, 5], [0, 1, 2, 3, 4, 5].fill(-1, 3...4)) + assert_equal([0, 1, -1, -1, -1, 5], [0, 1, 2, 3, 4, 5].fill(-1, 2..-2)) + assert_equal([0, 1, -1, -1, 4, 5], [0, 1, 2, 3, 4, 5].fill(-1, 2...-2)) + assert_equal([10, 11, 12, 13, 14, 15], [0, 1, 2, 3, 4, 5].fill{|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, 5], [0, 1, 2, 3, 4, 5].fill(3, 2){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 15, 16, 17], [0, 1, 2, 3, 4, 5].fill(3, 5){|i| i+10}) + assert_equal([0, 1, 2, 13, 14, 5], [0, 1, 2, 3, 4, 5].fill(3..4){|i| i+10}) + 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 + + def test_00_new + a = @cls.new() + assert_instance_of(@cls, a) + assert_equal(0, a.length) + assert_nil(a[0]) + end + + def test_01_square_brackets + a = @cls[ 5, 4, 3, 2, 1 ] + assert_instance_of(@cls, a) + assert_equal(5, a.length) + 5.times { |i| assert_equal(5-i, a[i]) } + assert_nil(a[6]) + end + + def test_AND # '&' + assert_equal(@cls[1, 3], @cls[ 1, 1, 3, 5 ] & @cls[ 1, 2, 3 ]) + assert_equal(@cls[], @cls[ 1, 1, 3, 5 ] & @cls[ ]) + assert_equal(@cls[], @cls[ ] & @cls[ 1, 2, 3 ]) + 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_intersection + assert_equal(@cls[1, 2], @cls[1, 2, 3].intersection(@cls[1, 2])) + assert_equal(@cls[ ], @cls[1].intersection(@cls[ ])) + assert_equal(@cls[ ], @cls[ ].intersection(@cls[1])) + assert_equal(@cls[1], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[1])) + assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[1, 2], @cls[3])) + assert_equal(@cls[ ], @cls[1, 2, 3].intersection(@cls[4, 5, 6])) + end + + def test_intersection_big_array + assert_equal(@cls[1, 2], (@cls[1, 2, 3] * 64).intersection(@cls[1, 2] * 64)) + assert_equal(@cls[ ], (@cls[1] * 64).intersection(@cls[ ])) + assert_equal(@cls[ ], @cls[ ].intersection(@cls[1] * 64)) + assert_equal(@cls[1], (@cls[1, 2, 3] * 64).intersection((@cls[1, 2] * 64), (@cls[1] * 64))) + assert_equal(@cls[ ], (@cls[1, 2, 3] * 64).intersection(@cls[4, 5, 6] * 64)) + end + + def test_MUL # '*' + assert_equal(@cls[], @cls[]*3) + assert_equal(@cls[1, 1, 1], @cls[1]*3) + assert_equal(@cls[1, 2, 1, 2, 1, 2], @cls[1, 2]*3) + assert_equal(@cls[], @cls[1, 2, 3] * 0) + assert_raise(ArgumentError) { @cls[1, 2]*(-3) } + + assert_equal('1-2-3-4-5', @cls[1, 2, 3, 4, 5] * '-') + assert_equal('12345', @cls[1, 2, 3, 4, 5] * '') + + end + + def test_PLUS # '+' + assert_equal(@cls[], @cls[] + @cls[]) + assert_equal(@cls[1], @cls[1] + @cls[]) + assert_equal(@cls[1], @cls[] + @cls[1]) + assert_equal(@cls[1, 1], @cls[1] + @cls[1]) + assert_equal(@cls['cat', 'dog', 1, 2, 3], %w(cat dog) + (1..3).to_a) + end + + 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]) + assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5] - @cls[2, 3, 4, 5]) + 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 + assert_equal(@cls[1], a) + a << 2 << 3 + assert_equal(@cls[1, 2, 3], a) + a << nil << 'cat' + assert_equal(@cls[1, 2, 3, nil, 'cat'], a) + a << a + assert_equal(@cls[1, 2, 3, nil, 'cat', a], a) + end + + def test_CMP # '<=>' + assert_equal(0, @cls[] <=> @cls[]) + assert_equal(0, @cls[1] <=> @cls[1]) + assert_equal(0, @cls[1, 2, 3, 'cat'] <=> @cls[1, 2, 3, 'cat']) + assert_equal(-1, @cls[] <=> @cls[1]) + assert_equal(1, @cls[1] <=> @cls[]) + assert_equal(-1, @cls[1, 2, 3] <=> @cls[1, 2, 3, 'cat']) + assert_equal(1, @cls[1, 2, 3, 'cat'] <=> @cls[1, 2, 3]) + assert_equal(-1, @cls[1, 2, 3, 'cat'] <=> @cls[1, 2, 3, 'dog']) + assert_equal(1, @cls[1, 2, 3, 'dog'] <=> @cls[1, 2, 3, 'cat']) + end + + def test_EQUAL # '==' + 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_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 # '[]' + a = @cls[*(1..100).to_a] + + assert_equal(1, a[0]) + assert_equal(100, a[99]) + assert_nil(a[100]) + assert_equal(100, a[-1]) + assert_equal(99, a[-2]) + assert_equal(1, a[-100]) + assert_nil(a[-101]) + assert_nil(a[-101,0]) + assert_nil(a[-101,1]) + assert_nil(a[-101,-1]) + assert_nil(a[10,-1]) + + assert_equal(@cls[1], a[0,1]) + assert_equal(@cls[100], a[99,1]) + assert_equal(@cls[], a[100,1]) + assert_equal(@cls[100], a[99,100]) + assert_equal(@cls[100], a[-1,1]) + assert_equal(@cls[99], a[-2,1]) + assert_equal(@cls[], a[-100,0]) + assert_equal(@cls[1], a[-100,1]) + + assert_equal(@cls[10, 11, 12], a[9, 3]) + assert_equal(@cls[10, 11, 12], a[-91, 3]) + + assert_equal(@cls[1], a[0..0]) + assert_equal(@cls[100], a[99..99]) + assert_equal(@cls[], a[100..100]) + assert_equal(@cls[100], a[99..200]) + assert_equal(@cls[100], a[-1..-1]) + assert_equal(@cls[99], a[-2..-2]) + + assert_equal(@cls[10, 11, 12], a[9..11]) + assert_equal(@cls[98, 99, 100], a[97..]) + assert_equal(@cls[1, 2, 3], a[..2]) + assert_equal(@cls[1, 2], a[...2]) + assert_equal(@cls[10, 11, 12], a[-91..-89]) + assert_equal(@cls[98, 99, 100], a[-3..]) + assert_equal(@cls[1, 2, 3], a[..-98]) + assert_equal(@cls[1, 2], a[...-98]) + + assert_nil(a[10, -3]) + assert_equal [], a[10..7] + + assert_raise(TypeError) {a['cat']} + end + + def test_ASET # '[]=' + a = @cls[*(0..99).to_a] + assert_equal(0, a[0] = 0) + assert_equal(@cls[0] + @cls[*(1..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(0, a[10,10] = 0) + assert_equal(@cls[*(0..9).to_a] + @cls[0] + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(0, a[-1] = 0) + assert_equal(@cls[*(0..98).to_a] + @cls[0], a) + + a = @cls[*(0..99).to_a] + assert_equal(0, a[-10, 10] = 0) + assert_equal(@cls[*(0..89).to_a] + @cls[0], a) + + a = @cls[*(0..99).to_a] + assert_equal(0, a[0,1000] = 0) + assert_equal(@cls[0] , a) + + a = @cls[*(0..99).to_a] + assert_equal(0, a[10..19] = 0) + assert_equal(@cls[*(0..9).to_a] + @cls[0] + @cls[*(20..99).to_a], a) + + b = @cls[*%w( a b c )] + a = @cls[*(0..99).to_a] + assert_equal(b, a[0,1] = b) + assert_equal(b + @cls[*(1..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(b, a[10,10] = b) + assert_equal(@cls[*(0..9).to_a] + b + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(b, a[-1, 1] = b) + assert_equal(@cls[*(0..98).to_a] + b, a) + + a = @cls[*(0..99).to_a] + assert_equal(b, a[-10, 10] = b) + assert_equal(@cls[*(0..89).to_a] + b, a) + + a = @cls[*(0..99).to_a] + assert_equal(b, a[0,1000] = b) + assert_equal(b , a) + + a = @cls[*(0..99).to_a] + assert_equal(b, a[10..19] = b) + assert_equal(@cls[*(0..9).to_a] + b + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[0,1] = nil) + 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[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] + @cls[nil], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[-10, 10] = nil) + 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[nil] , a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[10..19] = nil) + assert_equal(@cls[*(0..9).to_a] + @cls[nil] + @cls[*(20..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[10..] = nil) + assert_equal(@cls[*(0..9).to_a] + @cls[nil], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[..10] = nil) + assert_equal(@cls[nil] + @cls[*(11..99).to_a], a) + + a = @cls[*(0..99).to_a] + assert_equal(nil, a[...10] = nil) + assert_equal(@cls[nil] + @cls[*(10..99).to_a], a) + + a = @cls[1, 2, 3] + a[1, 0] = a + assert_equal([1, 1, 2, 3, 2, 3], a) + + 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 + def (a4 = Object.new).to_ary + %w( pork porcine ) + end + + a1 = @cls[*%w( cat feline )] + a2 = @cls[*%w( dog canine )] + a3 = @cls[*%w( mule asinine )] + + a = @cls[ a1, a2, a3, a4 ] + + assert_equal(a1, a.assoc('cat')) + assert_equal(a3, a.assoc('mule')) + assert_equal(%w( pork porcine ), a.assoc("pork")) + assert_equal(nil, a.assoc('asinine')) + assert_equal(nil, a.assoc('wombat')) + assert_equal(nil, a.assoc(1..2)) + end + + def test_at + a = @cls[*(0..99).to_a] + assert_equal(0, a.at(0)) + assert_equal(10, a.at(10)) + assert_equal(99, a.at(99)) + assert_equal(nil, a.at(100)) + assert_equal(99, a.at(-1)) + assert_equal(0, a.at(-100)) + assert_equal(nil, a.at(-101)) + assert_raise(TypeError) { a.at('cat') } + end + + def test_clear + a = @cls[1, 2, 3] + b = a.clear + assert_equal(@cls[], a) + assert_equal(@cls[], b) + assert_equal(a.__id__, b.__id__) + end + + def test_clone + for frozen in [ false, true ] + a = @cls[*(0..99).to_a] + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_equal(a.__id__, b.__id__) + assert_equal(a.frozen?, b.frozen?) + end + end + + def test_collect + a = @cls[ 1, 'cat', 1..1 ] + assert_equal([ Integer, String, Range], a.collect {|e| e.class} ) + assert_equal([ 99, 99, 99], a.collect { 99 } ) + + assert_equal([], @cls[].collect { 99 }) + + 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([ 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 } ) + assert_equal([ 99, 99, 99], a) + + a = @cls[ ] + assert_equal([], a.collect! { 99 }) + assert_equal([], a) + end + + def test_compact + a = @cls[ 1, nil, nil, 2, 3, nil, 4 ] + assert_equal(@cls[1, 2, 3, 4], a.compact) + + a = @cls[ nil, 1, nil, 2, 3, nil, 4 ] + assert_equal(@cls[1, 2, 3, 4], a.compact) + + a = @cls[ 1, nil, nil, 2, 3, nil, 4, nil ] + assert_equal(@cls[1, 2, 3, 4], a.compact) + + a = @cls[ 1, 2, 3, 4 ] + assert_equal(@cls[1, 2, 3, 4], a.compact) + end + + def test_compact! + a = @cls[ 1, nil, nil, 2, 3, nil, 4 ] + assert_equal(@cls[1, 2, 3, 4], a.compact!) + assert_equal(@cls[1, 2, 3, 4], a) + + a = @cls[ nil, 1, nil, 2, 3, nil, 4 ] + assert_equal(@cls[1, 2, 3, 4], a.compact!) + assert_equal(@cls[1, 2, 3, 4], a) + + a = @cls[ 1, nil, nil, 2, 3, nil, 4, nil ] + assert_equal(@cls[1, 2, 3, 4], a.compact!) + assert_equal(@cls[1, 2, 3, 4], a) + + a = @cls[ 1, 2, 3, 4 ] + assert_equal(nil, a.compact!) + assert_equal(@cls[1, 2, 3, 4], a) + end + + 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]])) + + a = @cls[1, 2, 3] + 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) { @cls[0].concat(:foo) } + assert_raise(FrozenError) { @cls[0].freeze.concat(:foo) } + + a = @cls[nil] + def (x = Object.new).to_ary + ary = Array.new(2) + ary << [] << [] << :ok + end + EnvUtil.under_gc_stress {a.concat(x)} + GC.start + assert_equal(:ok, a.last) + end + + def test_count + a = @cls[1, 2, 3, 1, 2] + assert_equal(5, a.count) + assert_equal(2, a.count(1)) + assert_equal(3, a.count {|x| x % 2 == 1 }) + assert_equal(2, assert_warning(/given block not used/) {a.count(1) {|x| x % 2 == 1 }}) + assert_raise(ArgumentError) { a.count(0, 1) } + + bug8654 = '[ruby-core:56072]' + 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 + a = @cls[*('cab'..'cat').to_a] + assert_equal('cap', a.delete('cap')) + assert_equal(@cls[*('cab'..'cao').to_a] + @cls[*('caq'..'cat').to_a], a) + + a = @cls[*('cab'..'cat').to_a] + assert_equal('cab', a.delete('cab')) + assert_equal(@cls[*('cac'..'cat').to_a], a) + + a = @cls[*('cab'..'cat').to_a] + assert_equal('cat', a.delete('cat')) + assert_equal(@cls[*('cab'..'cas').to_a], a) + + a = @cls[*('cab'..'cat').to_a] + assert_equal(nil, a.delete('cup')) + assert_equal(@cls[*('cab'..'cat').to_a], a) + + 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 + a = @cls[*(1..5).to_a] + assert_equal(3, a.delete_at(2)) + assert_equal(@cls[1, 2, 4, 5], a) + + a = @cls[*(1..5).to_a] + assert_equal(4, a.delete_at(-2)) + assert_equal(@cls[1, 2, 3, 5], a) + + a = @cls[*(1..5).to_a] + assert_equal(nil, a.delete_at(5)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[*(1..5).to_a] + assert_equal(nil, a.delete_at(-6)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + end + + # also reject! + def test_delete_if + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.delete_if { false }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.delete_if { true }) + assert_equal(@cls[], a) + + 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) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.delete_if do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) + end + + def test_dup + for frozen in [ false, true ] + a = @cls[*(0..99).to_a] + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_equal(a.__id__, b.__id__) + assert_equal(false, b.frozen?) + end + end + + def test_each + a = @cls[*%w( ant bat cat dog )] + i = 0 + a.each { |e| + assert_equal(a[i], e) + i += 1 + } + assert_equal(4, i) + + a = @cls[] + i = 0 + a.each { |e| + assert(false, "Never get here") + i += 1 + } + assert_equal(0, i) + + assert_equal(a, a.each {}) + end + + def test_each_index + a = @cls[*%w( ant bat cat dog )] + i = 0 + a.each_index { |ind| + assert_equal(i, ind) + i += 1 + } + assert_equal(4, i) + + a = @cls[] + i = 0 + a.each_index { |ind| + assert(false, "Never get here") + i += 1 + } + assert_equal(0, i) + + assert_equal(a, a.each_index {}) + end + + def test_empty? + assert_empty(@cls[]) + assert_not_empty(@cls[1]) + end + + def test_eql? + 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 + assert_equal(@cls[], @cls[].fill(99)) + assert_equal(@cls[], @cls[].fill(99, 0)) + assert_equal(@cls[99], @cls[].fill(99, 0, 1)) + assert_equal(@cls[99], @cls[].fill(99, 0..0)) + + assert_equal(@cls[99], @cls[1].fill(99)) + assert_equal(@cls[99], @cls[1].fill(99, 0)) + assert_equal(@cls[99], @cls[1].fill(99, 0, 1)) + assert_equal(@cls[99], @cls[1].fill(99, 0..0)) + + assert_equal(@cls[99, 99], @cls[1, 2].fill(99)) + assert_equal(@cls[99, 99], @cls[1, 2].fill(99, 0)) + assert_equal(@cls[99, 99], @cls[1, 2].fill(99, nil)) + assert_equal(@cls[1, 99], @cls[1, 2].fill(99, 1, nil)) + assert_equal(@cls[99, 2], @cls[1, 2].fill(99, 0, 1)) + assert_equal(@cls[99, 2], @cls[1, 2].fill(99, 0..0)) + end + + def test_first + assert_equal(3, @cls[3, 4, 5].first) + assert_equal(nil, @cls[].first) + end + + def test_flatten + a1 = @cls[ 1, 2, 3] + a2 = @cls[ 5, 6 ] + a3 = @cls[ 4, a2 ] + a4 = @cls[ a1, a3 ] + assert_equal_instance([1, 2, 3, 4, 5, 6], a4.flatten) + assert_equal_instance(@cls[ a1, a3], a4) + + a5 = @cls[ a1, @cls[], a3 ] + assert_equal_instance([1, 2, 3, 4, 5, 6], a5.flatten) + assert_equal_instance([1, 2, 3, 4, [5, 6]], a5.flatten(1)) + assert_equal_instance([], @cls[].flatten) + assert_equal_instance([], + @cls[@cls[@cls[@cls[],@cls[]],@cls[@cls[]],@cls[]],@cls[@cls[@cls[]]]].flatten) + end + + def test_flatten_wrong_argument + assert_raise(TypeError, "[ruby-dev:31197]") { [[]].flatten("") } + 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_recursive + a = [] + a << a + assert_raise(ArgumentError) { a.flatten } + b = [1]; c = [2, b]; b << c + assert_raise(ArgumentError) { b.flatten } + + assert_equal([1, 2, b], b.flatten(1)) + assert_equal([1, 2, 1, 2, 1, c], b.flatten(4)) + end + + def test_flatten! + a1 = @cls[ 1, 2, 3] + a2 = @cls[ 5, 6 ] + a3 = @cls[ 4, a2 ] + a4 = @cls[ a1, a3 ] + assert_equal(@cls[1, 2, 3, 4, 5, 6], a4.flatten!) + assert_equal(@cls[1, 2, 3, 4, 5, 6], a4) + + a5 = @cls[ a1, @cls[], a3 ] + 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 + + def test_flatten_empty! + assert_nil(@cls[].flatten!) + assert_equal(@cls[], + @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 + need_continuation + o = Object.new + def o.to_ary() callcc {|k| @cont = k; [1,2,3]} end + begin + assert_equal([10, 20, 1, 2, 3, 30, 1, 2, 3, 40], [10, 20, o, 30, o, 40].flatten) + rescue => e + else + o.instance_eval {@cont}.call + end + assert_instance_of(RuntimeError, e, '[ruby-dev:34798]') + 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 + need_continuation + n = 1000 + cont = nil + ary = [1,2,3] + begin + ary.permutation { + callcc {|k| cont = k} unless cont + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e) + assert_match(/reentered/, e.message) + end + + def test_product_with_callcc + need_continuation + n = 1000 + cont = nil + ary = [1,2,3] + begin + ary.product { + callcc {|k| cont = k} unless cont + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e) + assert_match(/reentered/, e.message) + end + + def test_combination_with_callcc + need_continuation + n = 1000 + cont = nil + ary = [1,2,3] + begin + ary.combination(2) { + callcc {|k| cont = k} unless cont + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e) + assert_match(/reentered/, e.message) + end + + def test_repeated_permutation_with_callcc + need_continuation + n = 1000 + cont = nil + ary = [1,2,3] + begin + ary.repeated_permutation(2) { + callcc {|k| cont = k} unless cont + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e) + assert_match(/reentered/, e.message) + end + + def test_repeated_combination_with_callcc + need_continuation + n = 1000 + cont = nil + ary = [1,2,3] + begin + ary.repeated_combination(2) { + callcc {|k| cont = k} unless cont + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e) + assert_match(/reentered/, e.message) + end + + def test_hash + a1 = @cls[ 'cat', 'dog' ] + a2 = @cls[ 'cat', 'dog' ] + a3 = @cls[ 'dog', 'cat' ] + 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_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_monkey_patch_include? + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + begin; + $-w = false + class Array + alias :old_include? :include? + def include? x + return true if x == :always + old_include?(x) + end + end + def test + a, c, always = :a, :c, :always + [ + [:a, :b].include?(a), + [:a, :b].include?(c), + [:a, :b].include?(always), + ] + end + v = test + class Array + alias :include? :old_include? + end + assert_equal [true, false, true], v + end; + end + + def test_intersect? + a = @cls[ 1, 2, 3] + assert_send([a, :intersect?, [3]]) + assert_not_send([a, :intersect?, [4]]) + assert_not_send([a, :intersect?, []]) + end + + def test_intersect_big_array + assert_send([@cls[ 1, 4, 5 ]*64, :intersect?, @cls[ 1, 2, 3 ]*64]) + assert_not_send([@cls[ 1, 2, 3 ]*64, :intersect?, @cls[ 4, 5, 6 ]*64]) + assert_not_send([@cls[], :intersect?, @cls[ 1, 2, 3 ]*64]) + end + + def test_index + a = @cls[ 'cat', 99, /a/, 99, @cls[ 1, 2, 3] ] + assert_equal(0, a.index('cat')) + assert_equal(1, a.index(99)) + assert_equal(4, a.index([1,2,3])) + assert_nil(a.index('ca')) + assert_nil(a.index([1,2])) + + assert_equal(1, assert_warn(/given block not used/) {a.index(99) {|x| x == 'cat' }}) + end + + def test_values_at + a = @cls[*('a'..'j').to_a] + assert_equal(@cls['a', 'c', 'e'], a.values_at(0, 2, 4)) + assert_equal(@cls['j', 'h', 'f'], a.values_at(-1, -3, -5)) + assert_equal(@cls['h', nil, 'a'], a.values_at(-3, 99, 0)) + end + + def test_join + assert_deprecated_warning {$, = ""} + a = @cls[] + assert_equal("", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("", a.join(',')) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {a.join}.encoding) + + assert_deprecated_warning {$, = ""} + a = @cls[1, 2] + assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("12", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) + assert_equal("1,2", a.join(',')) + + assert_deprecated_warning {$, = ""} + a = @cls[1, 2, 3] + assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("123", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) + assert_equal("1,2,3", a.join(',')) + + assert_deprecated_warning {$, = ":"} + a = @cls[1, 2, 3] + assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join}) + assert_equal("1:2:3", assert_deprecated_warn(/non-nil value/) {a.join(nil)}) + assert_equal("1,2,3", a.join(',')) + + assert_deprecated_warning {$, = ""} + + e = ''.force_encoding('EUC-JP') + u = ''.force_encoding('UTF-8') + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[]].join}.encoding) + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[1, [u]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [e]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[u, [1]].join}.encoding) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[Struct.new(:to_str).new(u)].join}.encoding) + bug5379 = '[ruby-core:39776]' + assert_equal(Encoding::US_ASCII, assert_deprecated_warn(/non-nil value/) {[[], u, nil].join}.encoding, bug5379) + assert_equal(Encoding::UTF_8, assert_deprecated_warn(/non-nil value/) {[[], "\u3042", nil].join}.encoding, bug5379) + ensure + $, = nil + end + + def test_last + assert_equal(nil, @cls[].last) + assert_equal(1, @cls[1].last) + assert_equal(99, @cls[*(3..99).to_a].last) + end + + def test_length + assert_equal(0, @cls[].length) + assert_equal(1, @cls[1].length) + assert_equal(2, @cls[1, nil].length) + assert_equal(2, @cls[nil, 1].length) + assert_equal(234, @cls[*(0..233).to_a].length) + end + + # also update collect! + def test_map! + a = @cls[ 1, 'cat', 1..1 ] + 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 } ) + assert_equal(@cls[ 99, 99, 99], a) + + a = @cls[ ] + assert_equal(@cls[], a.map! { 99 }) + assert_equal(@cls[], a) + end + + def test_pack_format_mutation + ary = [Object.new] + fmt = "c" * 0x20000 + class << ary[0]; self end.send(:define_method, :to_int) { + fmt.replace "" + 1 + } + e = assert_raise(RuntimeError) { ary.pack(fmt) } + assert_equal "format string modified", e.message + end + + def test_pack + a = @cls[*%w( cat wombat x yy)] + assert_equal("catwomx yy ", a.pack("A3A3A3A3")) + assert_equal("cat", a.pack("A*")) + assert_equal("cwx yy ", a.pack("A3@1A3@2A3A3")) + assert_equal("catwomx\000\000yy\000", a.pack("a3a3a3a3")) + assert_equal("cat", a.pack("a*")) + assert_equal("ca", a.pack("a2")) + assert_equal("cat\000\000", a.pack("a5")) + + assert_equal("\x61", @cls["01100001"].pack("B8")) + assert_equal("\x61", @cls["01100001"].pack("B*")) + assert_equal("\x61", @cls["0110000100110111"].pack("B8")) + assert_equal("\x61\x37", @cls["0110000100110111"].pack("B16")) + assert_equal("\x61\x37", @cls["01100001", "00110111"].pack("B8B8")) + assert_equal("\x60", @cls["01100001"].pack("B4")) + assert_equal("\x40", @cls["01100001"].pack("B2")) + + assert_equal("\x86", @cls["01100001"].pack("b8")) + assert_equal("\x86", @cls["01100001"].pack("b*")) + assert_equal("\x86", @cls["0110000100110111"].pack("b8")) + assert_equal("\x86\xec", @cls["0110000100110111"].pack("b16")) + assert_equal("\x86\xec", @cls["01100001", "00110111"].pack("b8b8")) + assert_equal("\x06", @cls["01100001"].pack("b4")) + assert_equal("\x02", @cls["01100001"].pack("b2")) + + assert_equal("ABC", @cls[ 65, 66, 67 ].pack("C3")) + assert_equal("\377BC", @cls[ -1, 66, 67 ].pack("C*")) + assert_equal("ABC", @cls[ 65, 66, 67 ].pack("c3")) + assert_equal("\377BC", @cls[ -1, 66, 67 ].pack("c*")) + + + assert_equal("AB\n\x10", @cls["4142", "0a", "12"].pack("H4H2H1")) + assert_equal("AB\n\x02", @cls["1424", "a0", "21"].pack("h4h2h1")) + + assert_equal("abc=02def=\ncat=\n=01=\n", + @cls["abc\002def", "cat", "\001"].pack("M9M3M4")) + + assert_equal("aGVsbG8K\n", @cls["hello\n"].pack("m")) + assert_equal(",:&5L;&\\*:&5L;&\\*\n", @cls["hello\nhello\n"].pack("u")) + + assert_equal("\u{a9 42 2260}", @cls[0xa9, 0x42, 0x2260].pack("U*")) + + + format = "c2x5CCxsdils_l_a6"; + # Need the expression in here to force ary[5] to be numeric. This avoids + # test2 failing because ary2 goes str->numeric->str and ary does not. + ary = [1, -100, 127, 128, 32767, 987.654321098/100.0, + 12345, 123456, -32767, -123456, "abcdef"] + x = ary.pack(format) + ary2 = x.unpack(format) + + assert_equal(ary.length, ary2.length) + assert_equal(ary.join(':'), ary2.join(':')) + assert_not_nil(x =~ /def/) + + # more comprehensive tests are in test_pack.rb + end + + def test_pack_with_buffer + n = [ 65, 66, 67 ] + str = "a" * 100 + assert_equal("aaaABC", n.pack("@3ccc", buffer: str.dup), "[Bug #19116]") + end + + def test_pop + a = @cls[ 'cat', 'dog' ] + assert_equal('dog', a.pop) + assert_equal(@cls['cat'], a) + assert_equal('cat', a.pop) + assert_equal(@cls[], a) + assert_nil(a.pop) + 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_tolerant_to_redefinition + *code = __FILE__, __LINE__+1, "#{<<-"{#"}\n#{<<-'};'}" + {# + module M + def <<(a) + super(a * 2) + end + end + class Array; prepend M; end + ary = [*1..10] + mapped = ary.map {|i| i} + selected = ary.select {true} + module M + remove_method :<< + end + assert_equal(ary, mapped) + assert_equal(ary, selected) + }; + assert_separately(%w[--disable-yjit], *code) + assert_separately(%w[--enable-yjit], *code) + 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)) + a.push + assert_equal @cls[1, 2, 3, 4, 5, nil], a + a.push 6, 7 + assert_equal @cls[1, 2, 3, 4, 5, nil, 6, 7], a + end + + def test_rassoc + def (a4 = Object.new).to_ary + %w( pork porcine ) + end + a1 = @cls[*%w( cat feline )] + a2 = @cls[*%w( dog canine )] + a3 = @cls[*%w( mule asinine )] + a = @cls[ a1, a2, a3, a4 ] + + assert_equal(a1, a.rassoc('feline')) + assert_equal(a3, a.rassoc('asinine')) + assert_equal(%w( pork porcine ), a.rassoc("porcine")) + assert_equal(nil, a.rassoc('dog')) + assert_equal(nil, a.rassoc('mule')) + assert_equal(nil, a.rassoc(1..2)) + end + + # also delete_if + def test_reject! + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(nil, a.reject! { false }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.reject! { true }) + assert_equal(@cls[], a) + + 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) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.reject! do + a.freeze + true + end + end + assert_equal(@cls[1, 2, 3, 42], a) + end + + def test_shared_array_reject! + c = [] + b = [1, 2, 3, 4] + 3.times do + a = b.dup + c << a.dup + + begin + a.reject! do |x| + case x + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + c << a.dup + end + + bug90781 = '[ruby-core:90781]' + assert_equal [[1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4]], c, bug90781 + end + + def test_iseq_shared_array_reject! + c = [] + 3.times do + a = [1, 2, 3, 4] + c << a.dup + + begin + a.reject! do |x| + case x + when 2 then true + when 3 then raise StandardError, 'Oops' + else false + end + end + rescue StandardError + end + + c << a.dup + end + + bug90781 = '[ruby-core:90781]' + assert_equal [[1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4], + [1, 2, 3, 4], + [1, 3, 4]], c, bug90781 + end + + def test_replace + a = @cls[ 1, 2, 3] + a_id = a.__id__ + assert_equal(@cls[4, 5, 6], a.replace(@cls[4, 5, 6])) + assert_equal(@cls[4, 5, 6], a) + assert_equal(a_id, a.__id__) + assert_equal(@cls[], a.replace(@cls[])) + + fa = a.dup.freeze + assert_nothing_raised(RuntimeError) { a.replace(a) } + assert_raise(FrozenError) { fa.replace(fa) } + assert_raise(ArgumentError) { fa.replace() } + assert_raise(TypeError) { a.replace(42) } + assert_raise(FrozenError) { fa.replace(42) } + end + + def test_replace_wb_variable_width_alloc + small_embed = [] + 4.times { GC.start } # age small_embed + large_embed = [1, 2, 3, 4, 5, Array.new] # new young object + small_embed.replace(large_embed) # adds old to young reference + GC.verify_internal_consistency + end + + def test_reverse + a = @cls[*%w( dog cat bee ant )] + assert_equal(@cls[*%w(ant bee cat dog)], a.reverse) + assert_equal(@cls[*%w(dog cat bee ant)], a) + assert_equal(@cls[], @cls[].reverse) + end + + def test_reverse! + 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) + assert_equal @cls[], @cls[].reverse! + end + + def test_reverse_each + a = @cls[*%w( dog cat bee ant )] + i = a.length + a.reverse_each { |e| + i -= 1 + assert_equal(a[i], e) + } + assert_equal(0, i) + + a = @cls[] + i = 0 + a.reverse_each { |e| + i += 1 + assert(false, "Never get here") + } + assert_equal(0, i) + end + + def test_rindex + a = @cls[ 'cat', 99, /a/, 99, [ 1, 2, 3] ] + assert_equal(0, a.rindex('cat')) + assert_equal(3, a.rindex(99)) + assert_equal(4, a.rindex([1,2,3])) + assert_nil(a.rindex('ca')) + assert_nil(a.rindex([1,2])) + + assert_equal(3, assert_warning(/given block not used/) {a.rindex(99) {|x| x == [1,2,3] }}) + + bug15951 = "[Bug #15951]" + o2 = Object.new + def o2.==(other) + other.replace([]) if Array === other + false + end + a = Array.new(10) + a.fill(o2) + assert_nil(a.rindex(a), bug15951) + end + + def test_shift + a = @cls[ 'cat', 'dog' ] + assert_equal('cat', a.shift) + assert_equal(@cls['dog'], a) + assert_equal('dog', a.shift) + assert_equal(@cls[], a) + assert_nil(a.shift) + assert_equal(@cls[], a) + end + + def test_size + assert_equal(0, @cls[].size) + assert_equal(1, @cls[1].size) + assert_equal(100, @cls[*(0..99).to_a].size) + end + + def test_slice + a = @cls[*(1..100).to_a] + + assert_equal(1, a.slice(0)) + assert_equal(100, a.slice(99)) + assert_nil(a.slice(100)) + assert_equal(100, a.slice(-1)) + assert_equal(99, a.slice(-2)) + assert_equal(1, a.slice(-100)) + assert_nil(a.slice(-101)) + + assert_equal_instance([1], a.slice(0,1)) + assert_equal_instance([100], a.slice(99,1)) + assert_equal_instance([], a.slice(100,1)) + assert_equal_instance([100], a.slice(99,100)) + assert_equal_instance([100], a.slice(-1,1)) + assert_equal_instance([99], a.slice(-2,1)) + + assert_equal_instance([10, 11, 12], a.slice(9, 3)) + assert_equal_instance([10, 11, 12], a.slice(-91, 3)) + + assert_nil(a.slice(-101, 2)) + + assert_equal_instance([1], a.slice(0..0)) + assert_equal_instance([100], a.slice(99..99)) + assert_equal_instance([], a.slice(100..100)) + assert_equal_instance([100], a.slice(99..200)) + assert_equal_instance([100], a.slice(-1..-1)) + assert_equal_instance([99], a.slice(-2..-2)) + + assert_equal_instance([10, 11, 12], a.slice(9..11)) + assert_equal_instance([98, 99, 100], a.slice(97..)) + assert_equal_instance([10, 11, 12], a.slice(-91..-89)) + assert_equal_instance([10, 11, 12], a.slice(-91..-89)) + + assert_equal_instance([5, 8, 11], a.slice((4..12)%3)) + assert_equal_instance([95, 97, 99], a.slice((94..)%2)) + + # [0] [1] [2] [3] [4] [5] [6] [7] + # ary = [ 1 2 3 4 5 6 7 8 ... ] + # (0) (1) (2) <- (..7) % 3 + # (2) (1) (0) <- (7..) % -3 + assert_equal_instance([1, 4, 7], a.slice((..7)%3)) + assert_equal_instance([8, 5, 2], a.slice((7..)% -3)) + + # [-98] [-97] [-96] [-95] [-94] [-93] [-92] [-91] [-90] + # ary = [ ... 3 4 5 6 7 8 9 10 11 ... ] + # (0) (1) (2) <- (-98..-90) % 3 + # (2) (1) (0) <- (-90..-98) % -3 + assert_equal_instance([3, 6, 9], a.slice((-98..-90)%3)) + assert_equal_instance([11, 8, 5], a.slice((-90..-98)% -3)) + + # [ 48] [ 49] [ 50] [ 51] [ 52] [ 53] + # [-52] [-51] [-50] [-49] [-48] [-47] + # ary = [ ... 49 50 51 52 53 54 ... ] + # (0) (1) (2) <- (48..-47) % 2 + # (2) (1) (0) <- (-47..48) % -2 + assert_equal_instance([49, 51, 53], a.slice((48..-47)%2)) + assert_equal_instance([54, 52, 50], a.slice((-47..48)% -2)) + + idx = ((3..90) % 2).to_a + assert_equal_instance(a.values_at(*idx), a.slice((3..90)%2)) + idx = 90.step(3, -2).to_a + assert_equal_instance(a.values_at(*idx), a.slice((90 .. 3)% -2)) + + a = [0, 1, 2, 3, 4, 5] + assert_equal([2, 1, 0], a.slice((2..).step(-1))) + assert_equal([2, 0], a.slice((2..).step(-2))) + assert_equal([2], a.slice((2..).step(-3))) + assert_equal([2], a.slice((2..).step(-4))) + + assert_equal([3, 2, 1, 0], a.slice((-3..).step(-1))) + assert_equal([3, 1], a.slice((-3..).step(-2))) + assert_equal([3, 0], a.slice((-3..).step(-3))) + assert_equal([3], a.slice((-3..).step(-4))) + assert_equal([3], a.slice((-3..).step(-5))) + + assert_equal([5, 4, 3, 2, 1, 0], a.slice((..0).step(-1))) + assert_equal([5, 3, 1], a.slice((..0).step(-2))) + assert_equal([5, 2], a.slice((..0).step(-3))) + assert_equal([5, 1], a.slice((..0).step(-4))) + assert_equal([5, 0], a.slice((..0).step(-5))) + assert_equal([5], a.slice((..0).step(-6))) + assert_equal([5], a.slice((..0).step(-7))) + + assert_equal([5, 4, 3, 2, 1], a.slice((...0).step(-1))) + assert_equal([5, 3, 1], a.slice((...0).step(-2))) + assert_equal([5, 2], a.slice((...0).step(-3))) + assert_equal([5, 1], a.slice((...0).step(-4))) + assert_equal([5], a.slice((...0).step(-5))) + assert_equal([5], a.slice((...0).step(-6))) + + assert_equal([5, 4, 3, 2], a.slice((...1).step(-1))) + assert_equal([5, 3], a.slice((...1).step(-2))) + assert_equal([5, 2], a.slice((...1).step(-3))) + assert_equal([5], a.slice((...1).step(-4))) + assert_equal([5], a.slice((...1).step(-5))) + + assert_equal([5, 4, 3, 2, 1], a.slice((..-5).step(-1))) + assert_equal([5, 3, 1], a.slice((..-5).step(-2))) + assert_equal([5, 2], a.slice((..-5).step(-3))) + assert_equal([5, 1], a.slice((..-5).step(-4))) + assert_equal([5], a.slice((..-5).step(-5))) + assert_equal([5], a.slice((..-5).step(-6))) + + assert_equal([5, 4, 3, 2], a.slice((...-5).step(-1))) + assert_equal([5, 3], a.slice((...-5).step(-2))) + assert_equal([5, 2], a.slice((...-5).step(-3))) + assert_equal([5], a.slice((...-5).step(-4))) + assert_equal([5], a.slice((...-5).step(-5))) + + assert_equal([4, 3, 2, 1], a.slice((4..1).step(-1))) + assert_equal([4, 2], a.slice((4..1).step(-2))) + assert_equal([4, 1], a.slice((4..1).step(-3))) + assert_equal([4], a.slice((4..1).step(-4))) + assert_equal([4], a.slice((4..1).step(-5))) + + assert_equal([4, 3, 2], a.slice((4...1).step(-1))) + assert_equal([4, 2], a.slice((4...1).step(-2))) + assert_equal([4], a.slice((4...1).step(-3))) + assert_equal([4], a.slice((4...1).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((-2..1).step(-1))) + assert_equal([4, 2], a.slice((-2..1).step(-2))) + assert_equal([4, 1], a.slice((-2..1).step(-3))) + assert_equal([4], a.slice((-2..1).step(-4))) + assert_equal([4], a.slice((-2..1).step(-5))) + + assert_equal([4, 3, 2], a.slice((-2...1).step(-1))) + assert_equal([4, 2], a.slice((-2...1).step(-2))) + assert_equal([4], a.slice((-2...1).step(-3))) + assert_equal([4], a.slice((-2...1).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((4..-5).step(-1))) + assert_equal([4, 2], a.slice((4..-5).step(-2))) + assert_equal([4, 1], a.slice((4..-5).step(-3))) + assert_equal([4], a.slice((4..-5).step(-4))) + assert_equal([4], a.slice((4..-5).step(-5))) + + assert_equal([4, 3, 2], a.slice((4...-5).step(-1))) + assert_equal([4, 2], a.slice((4...-5).step(-2))) + assert_equal([4], a.slice((4...-5).step(-3))) + assert_equal([4], a.slice((4...-5).step(-4))) + + assert_equal([4, 3, 2, 1], a.slice((-2..-5).step(-1))) + assert_equal([4, 2], a.slice((-2..-5).step(-2))) + assert_equal([4, 1], a.slice((-2..-5).step(-3))) + assert_equal([4], a.slice((-2..-5).step(-4))) + assert_equal([4], a.slice((-2..-5).step(-5))) + + assert_equal([4, 3, 2], a.slice((-2...-5).step(-1))) + assert_equal([4, 2], a.slice((-2...-5).step(-2))) + assert_equal([4], a.slice((-2...-5).step(-3))) + assert_equal([4], a.slice((-2...-5).step(-4))) + end + + def test_slice_out_of_range + a = @cls[*(1..100).to_a] + + assert_nil(a.slice(-101..-1)) + assert_nil(a.slice(-101..)) + + assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.slice((-101..-1)%2) } + assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.slice((-101..)%2) } + + assert_nil(a.slice(10, -3)) + assert_equal @cls[], a.slice(10..7) + + assert_equal([100], a.slice(-1, 1_000_000_000)) + end + + def test_slice_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress { assert_equal([1, 2, 3, 4, 5], (0..10).to_a[1, 5]) } + EnvUtil.under_gc_compact_stress do + a = [0, 1, 2, 3, 4, 5] + assert_equal([2, 1, 0], a.slice((2..).step(-1))) + end + end + + def test_slice! + a = @cls[1, 2, 3, 4, 5] + assert_equal(3, a.slice!(2)) + assert_equal(@cls[1, 2, 4, 5], a) + + a = @cls[1, 2, 3, 4, 5] + assert_equal(4, a.slice!(-2)) + assert_equal(@cls[1, 2, 3, 5], a) + + a = @cls[1, 2, 3, 4, 5] + s = a.slice!(2,2) + assert_equal_instance([3,4], s) + assert_equal(@cls[1, 2, 5], a) + + a = @cls[1, 2, 3, 4, 5] + s = a.slice!(-2,2) + assert_equal_instance([4,5], s) + assert_equal(@cls[1, 2, 3], a) + + a = @cls[1, 2, 3, 4, 5] + s = a.slice!(2..3) + assert_equal_instance([3,4], s) + assert_equal(@cls[1, 2, 5], a) + + a = @cls[1, 2, 3, 4, 5] + assert_equal(nil, a.slice!(20)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[1, 2, 3, 4, 5] + assert_equal(nil, a.slice!(-6)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[1, 2, 3, 4, 5] + assert_equal(nil, a.slice!(-6..4)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[1, 2, 3, 4, 5] + assert_equal(nil, a.slice!(-6,2)) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + assert_equal("[2, 3]", [1,2,3].slice!(1,10000).inspect, "moved from btest/knownbug") + + assert_raise(ArgumentError) { @cls[1].slice! } + assert_raise(ArgumentError) { @cls[1].slice!(0, 0, 0) } + end + + def test_slice_out_of_range! + a = @cls[*(1..100).to_a] + + assert_nil(a.clone.slice!(-101..-1)) + assert_nil(a.clone.slice!(-101..)) + + # assert_raise_with_message(RangeError, "((-101..-1).%(2)) out of range") { a.clone.slice!((-101..-1)%2) } + # assert_raise_with_message(RangeError, "((-101..).%(2)) out of range") { a.clone.slice!((-101..)%2) } + + assert_nil(a.clone.slice!(10, -3)) + assert_equal @cls[], a.clone.slice!(10..7) + + assert_equal([100], a.clone.slice!(-1, 1_000_000_000)) + end + + def test_sort + a = @cls[ 4, 1, 2, 3 ] + assert_equal(@cls[1, 2, 3, 4], a.sort) + assert_equal(@cls[4, 1, 2, 3], a) + + assert_equal(@cls[4, 3, 2, 1], a.sort { |x, y| y <=> x} ) + assert_equal(@cls[4, 1, 2, 3], a) + + assert_equal(@cls[1, 2, 3, 4], a.sort { |x, y| (x - y) * (2**100) }) + + a.fill(1) + assert_equal(@cls[1, 1, 1, 1], a.sort) + + assert_equal(@cls[], @cls[].sort) + end + + def test_sort! + a = @cls[ 4, 1, 2, 3 ] + assert_equal(@cls[1, 2, 3, 4], a.sort!) + assert_equal(@cls[1, 2, 3, 4], a) + + assert_equal(@cls[4, 3, 2, 1], a.sort! { |x, y| y <=> x} ) + assert_equal(@cls[4, 3, 2, 1], a) + + a.fill(1) + assert_equal(@cls[1, 1, 1, 1], a.sort!) + + assert_equal(@cls[1], @cls[1].sort!) + assert_equal(@cls[], @cls[].sort!) + + a = @cls[4, 3, 2, 1] + a.sort! {|m, n| a.replace([9, 8, 7, 6]); m <=> n } + assert_equal([1, 2, 3, 4], a) + + a = @cls[4, 3, 2, 1] + a.sort! {|m, n| a.replace([9, 8, 7]); m <=> n } + assert_equal([1, 2, 3, 4], a) + end + + def test_freeze_inside_sort! + array = [1, 2, 3, 4, 5] + frozen_array = nil + assert_raise(FrozenError) do + count = 0 + array.sort! do |a, b| + array.freeze if (count += 1) == 6 + frozen_array ||= array.map.to_a if array.frozen? + b <=> a + end + end + assert_equal(frozen_array, array) + + object = Object.new + array = [1, 2, 3, 4, 5] + object.define_singleton_method(:>){|_| array.freeze; true} + assert_raise(FrozenError) do + array.sort! do |a, b| + object + end + end + + object = Object.new + array = [object, object] + object.define_singleton_method(:>){|_| array.freeze; true} + object.define_singleton_method(:<=>){|o| object} + assert_raise(FrozenError) do + array.sort! + end + end + + def test_sort_with_callcc + need_continuation + n = 1000 + cont = nil + ary = (1..100).to_a + begin + ary.sort! {|a,b| + callcc {|k| cont = k} unless cont + assert_equal(100, ary.size, '[ruby-core:16679]') + a <=> b + } + rescue => e + end + n -= 1 + cont.call if 0 < n + assert_instance_of(RuntimeError, e, '[ruby-core:16679]') + assert_match(/reentered/, e.message, '[ruby-core:16679]') + end + + def test_sort_with_replace + bug = '[ruby-core:34732]' + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) + bug = "#{bug}" + begin; + xary = (1..100).to_a + 100.times do + ary = (1..100).to_a + ary.sort! {|a,b| ary.replace(xary); a <=> b} + GC.start + assert_equal(xary, ary, '[ruby-dev:34732]') + end + assert_nothing_raised(SystemStackError, bug) do + assert_equal(:ok, Array.new(100_000, nil).permutation {break :ok}) + end + end; + end + + def test_sort_bang_with_freeze + 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_sort_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].sort} + assert_raise(ArgumentError) {[1.0, Float::NAN].sort} + assert_raise(ArgumentError) {[Float::NAN, 1].sort} + assert_raise(ArgumentError) {[Float::NAN, 1.0].sort} + end + + def test_to_a + a = @cls[ 1, 2, 3 ] + a_id = a.__id__ + assert_equal(a, a.to_a) + assert_equal(a_id, a.to_a.__id__) + end + + def test_to_ary + a = [ 1, 2, 3 ] + b = @cls[*a] + + a_id = a.__id__ + assert_equal(a, b.to_ary) + if (@cls == Array) + assert_equal(a_id, a.to_ary.__id__) + end + + o = Object.new + def o.to_ary + [4, 5] + end + assert_equal([1, 2, 3, 4, 5], a.concat(o)) + + o = Object.new + def o.to_ary + foo_bar() + end + assert_raise_with_message(NoMethodError, /foo_bar/) {a.concat(o)} + end + + def test_to_s + assert_deprecated_warning {$, = ""} + a = @cls[] + assert_equal("[]", a.to_s) + + assert_deprecated_warning {$, = ""} + a = @cls[1, 2] + assert_equal("[1, 2]", a.to_s) + + assert_deprecated_warning {$, = ""} + a = @cls[1, 2, 3] + assert_equal("[1, 2, 3]", a.to_s) + + assert_deprecated_warning {$, = ""} + a = @cls[1, 2, 3] + assert_equal("[1, 2, 3]", a.to_s) + ensure + $, = nil + end + + StubToH = [ + [:key, :value], + Object.new.tap do |kvp| + def kvp.to_ary + [:obtained, :via_to_ary] + end + end, + ] + + def test_to_h + 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(3, [3].min) + assert_equal(1, [1, 2, 3, 1, 2].min) + assert_equal(3, [1, 2, 3, 1, 2].min {|a,b| b <=> a }) + cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } + assert_equal([3, 2], [1, 2, 3, 1, 2].each_with_index.min(&cond)) + assert_equal(1.0, [3.0, 1.0, 2.0].min) + ary = %w(albatross dog horse) + assert_equal("albatross", ary.min) + assert_equal("dog", ary.min {|a,b| a.length <=> b.length }) + 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_min_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].min} + assert_raise(ArgumentError) {[1.0, Float::NAN].min} + assert_raise(ArgumentError) {[Float::NAN, 1].min} + assert_raise(ArgumentError) {[Float::NAN, 1.0].min} + end + + def test_max + assert_equal(1, [1].max) + assert_equal(3, [1, 2, 3, 1, 2].max) + assert_equal(1, [1, 2, 3, 1, 2].max {|a,b| b <=> a }) + cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } + assert_equal([1, 3], [1, 2, 3, 1, 2].each_with_index.max(&cond)) + assert_equal(3.0, [1.0, 3.0, 2.0].max) + ary = %w(albatross dog horse) + assert_equal("horse", ary.max) + assert_equal("albatross", ary.max {|a,b| a.length <=> b.length }) + 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_max_uncomparable + assert_raise(ArgumentError) {[1, Float::NAN].max} + assert_raise(ArgumentError) {[1.0, Float::NAN].max} + assert_raise(ArgumentError) {[Float::NAN, 1].max} + assert_raise(ArgumentError) {[Float::NAN, 1.0].max} + end + + def test_minmax + assert_equal([3, 3], [3].minmax) + assert_equal([1, 3], [1, 2, 3, 1, 2].minmax) + assert_equal([3, 1], [1, 2, 3, 1, 2].minmax {|a,b| b <=> a }) + cond = ->((a, ia), (b, ib)) { (b <=> a).nonzero? or ia <=> ib } + assert_equal([[3, 2], [1, 3]], [1, 2, 3, 1, 2].each_with_index.minmax(&cond)) + ary = %w(albatross dog horse) + assert_equal(["albatross", "horse"], ary.minmax) + assert_equal(["dog", "albatross"], ary.minmax {|a,b| a.length <=> b.length }) + assert_equal([1, 3], [3,2,1].minmax) + + class << (obj = Object.new) + def <=>(x) 1 <=> x end + def coerce(x) [x, 1] end + end + ary = [obj, 1.0].minmax + assert_same(obj, ary[0]) + assert_equal(obj, ary[1]) + end + + def test_uniq + a = [] + b = a.uniq + assert_equal([], a) + assert_equal([], b) + assert_not_same(a, b) + + a = [1] + b = a.uniq + assert_equal([1], a) + assert_equal([1], b) + assert_not_same(a, b) + + a = [1,1] + b = a.uniq + assert_equal([1,1], a) + assert_equal([1], b) + assert_not_same(a, b) + + a = [1,2] + b = a.uniq + assert_equal([1,2], a) + assert_equal([1,2], b) + assert_not_same(a, b) + + a = @cls[ 1, 2, 3, 2, 1, 2, 3, 4, nil ] + b = a.dup + assert_equal(@cls[1, 2, 3, 4, nil], a.uniq) + assert_equal(b, a) + + c = @cls["a:def", "a:xyz", "b:abc", "b:xyz", "c:jkl"] + d = c.dup + assert_equal(@cls[ "a:def", "b:abc", "c:jkl" ], c.uniq {|s| s[/^\w+/]}) + 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] + + sc = Class.new(@cls) + a = sc[] + b = a.dup + assert_equal_instance([], a.uniq) + assert_equal(b, a) + + a = sc[1] + b = a.dup + assert_equal_instance([1], a.uniq) + assert_equal(b, a) + + a = sc[1, 1] + b = a.dup + assert_equal_instance([1], a.uniq) + assert_equal(b, a) + + a = sc[1, 1] + b = a.dup + assert_equal_instance([1], a.uniq{|x| x}) + assert_equal(b, a) + end + + def test_uniq_with_block + a = [] + b = a.uniq {|v| v.even? } + assert_equal([], a) + assert_equal([], b) + assert_not_same(a, b) + + a = [1] + b = a.uniq {|v| v.even? } + assert_equal([1], a) + assert_equal([1], b) + assert_not_same(a, b) + + a = [1,3] + b = a.uniq {|v| v.even? } + 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! + a = [] + b = a.uniq! + assert_equal(nil, b) + + a = [1] + b = a.uniq! + assert_equal(nil, b) + + a = [1,1] + b = a.uniq! + assert_equal([1], a) + assert_equal([1], b) + assert_same(a, b) + + a = [1,2] + b = a.uniq! + assert_equal([1,2], a) + assert_equal(nil, b) + + a = @cls[ 1, 2, 3, 2, 1, 2, 3, 4, nil ] + assert_equal(@cls[1, 2, 3, 4, nil], a.uniq!) + assert_equal(@cls[1, 2, 3, 4, nil], a) + + c = @cls["a:def", "a:xyz", "b:abc", "b:xyz", "c:jkl"] + assert_equal(@cls[ "a:def", "b:abc", "c:jkl" ], c.uniq! {|s| s[/^\w+/]}) + assert_equal(@cls[ "a:def", "b:abc", "c:jkl" ], c) + + c = @cls["a:def", "b:abc", "c:jkl"] + assert_equal(nil, c.uniq! {|s| s[/^\w+/]}) + assert_equal(@cls[ "a:def", "b:abc", "c:jkl" ], c) + + assert_nil(@cls[1, 2, 3].uniq!) + + f = a.dup.freeze + assert_raise(ArgumentError) { a.uniq!(1) } + assert_raise(ArgumentError) { f.uniq!(1) } + 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 + a = [] + b = a.uniq! {|v| v.even? } + assert_equal(nil, b) + + a = [1] + b = a.uniq! {|v| v.even? } + assert_equal(nil, b) + + a = [1,3] + b = a.uniq! {|v| v.even? } + assert_equal([1], a) + assert_equal([1], b) + assert_same(a, b) + + a = [1,2] + 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 + a = @cls[] + assert_equal(@cls['cat'], a.unshift('cat')) + assert_equal(@cls['dog', 'cat'], a.unshift('dog')) + assert_equal(@cls[nil, 'dog', 'cat'], a.unshift(nil)) + 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[]) + assert_equal(@cls[1], @cls[] | @cls[1]) + assert_equal(@cls[1], @cls[1] | @cls[1]) + + 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 + 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 + assert_equal(@cls[[1,4],[1,5],[2,4],[2,5],[3,4],[3,5]], + @cls[1,2,3].product([4,5])) + assert_equal(@cls[[1,1],[1,2],[2,1],[2,2]], @cls[1,2].product([1,2])) + + assert_equal(@cls[[1,3,5],[1,3,6],[1,4,5],[1,4,6], + [2,3,5],[2,3,6],[2,4,5],[2,4,6]], + @cls[1,2].product([3,4],[5,6])) + assert_equal(@cls[[1],[2]], @cls[1,2].product) + assert_equal(@cls[], @cls[1,2].product([])) + + bug3394 = '[ruby-dev:41540]' + acc = [] + EnvUtil.under_gc_stress {[1,2].product([3,4,5],[6,8]){|array| acc << array}} + assert_equal([[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], + [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]], + acc, bug3394) + + def (o = Object.new).to_ary; GC.start; [3,4] end + 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]], + a.permutation(2).to_a.sort) + assert_equal(@cls[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]], + a.permutation(3).sort.to_a) + assert_equal(@cls[], a.permutation(4).to_a) + assert_equal(@cls[], a.permutation(-1).to_a) + assert_equal("abcde".each_char.to_a.permutation(5).sort, + "edcba".each_char.to_a.permutation(5).sort) + assert_equal(@cls[].permutation(0).to_a, @cls[[]]) + + a = @cls[1, 2, 3, 4] + b = @cls[] + 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]], + a.repeated_permutation(2).to_a.sort) + assert_equal(@cls[[1,1,1],[1,1,2],[1,2,1],[1,2,2], + [2,1,1],[2,1,2],[2,2,1],[2,2,2]], + a.repeated_permutation(3).to_a.sort) + assert_equal(@cls[], a.repeated_permutation(-1).to_a) + assert_equal("abcde".each_char.to_a.repeated_permutation(5).sort, + "edcba".each_char.to_a.repeated_permutation(5).sort) + assert_equal(@cls[].repeated_permutation(0).to_a, @cls[[]]) + assert_equal(@cls[].repeated_permutation(1).to_a, @cls[]) + + a = @cls[1, 2, 3, 4] + b = @cls[] + 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]], + a.repeated_combination(2).to_a.sort) + assert_equal(@cls[[1,1,1],[1,1,2],[1,1,3],[1,2,2],[1,2,3], + [1,3,3],[2,2,2],[2,2,3],[2,3,3],[3,3,3]], + a.repeated_combination(3).to_a.sort) + assert_equal(@cls[[1,1,1,1],[1,1,1,2],[1,1,1,3],[1,1,2,2],[1,1,2,3], + [1,1,3,3],[1,2,2,2],[1,2,2,3],[1,2,3,3],[1,3,3,3], + [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{|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[]) + + a = @cls[1, 2, 3, 4] + b = @cls[] + 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 + assert_equal_instance([1,2,3], @cls[1,2,3,4,5,0].take(3)) + assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].take(-1) } + assert_equal_instance([1,2], @cls[1,2].take(1000000000), '[ruby-dev:34123]') + end + + def test_take_while + assert_equal_instance([1,2], @cls[1,2,3,4,5,0].take_while {|i| i < 3 }) + end + + def test_drop + assert_equal_instance([4,5,0], @cls[1,2,3,4,5,0].drop(3)) + assert_raise(ArgumentError, '[ruby-dev:34123]') { [1,2].drop(-1) } + assert_equal_instance([], @cls[1,2].drop(1000000000), '[ruby-dev:34123]') + end + + def test_drop_while + assert_equal_instance([3,4,5,0], @cls[1,2,3,4,5,0].drop_while {|i| i < 3 }) + end + + LONGP = [127, 63, 31, 15, 7].map {|x| 2**x-1 }.find do |x| + begin + [].first(x) + rescue ArgumentError + true + rescue RangeError + false + end + end + + def test_ary_new + assert_raise(ArgumentError) { [].to_enum.first(-1) } + assert_raise(ArgumentError) { [].to_enum.first(LONGP) } + end + + def test_try_convert + assert_equal([1], Array.try_convert([1])) + assert_equal(nil, Array.try_convert("1")) + end + + def test_initialize + assert_nothing_raised { [].instance_eval { initialize } } + assert_warning(/given block not used/) { Array.new { } } + assert_equal([1, 2, 3], Array.new([1, 2, 3])) + assert_raise(ArgumentError) { Array.new(-1, 1) } + assert_raise(ArgumentError) { Array.new(LONGP, 1) } + assert_equal([1, 1, 1], Array.new(3, 1)) + assert_equal([1, 1, 1], Array.new(3) { 1 }) + assert_equal([1, 1, 1], assert_warning(/block supersedes default value argument/) {Array.new(3, 1) { 1 }}) + end + + def test_aset_error + assert_raise(IndexError) { [0][-2] = 1 } + 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(FrozenError) { [0].freeze[:foo] = 0 } + + # [Bug #17271] + assert_raise_with_message(RangeError, "-7.. out of range") { [*0..5][-7..] = 1 } + end + + def test_first2 + assert_equal([0], [0].first(2)) + 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 + a = [0, 1, 2] + 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(FrozenError) { [].freeze.unshift('cat') } + assert_raise(FrozenError) { [].freeze.unshift() } + end + + def test_aref + assert_raise(ArgumentError) { [][0, 0, 0] } + assert_raise(ArgumentError) { @cls[][0, 0, 0] } + end + + def test_fetch + assert_equal(1, assert_warning(/block supersedes default value argument/) {[].fetch(0, 0) { 1 }}) + assert_equal(1, [0, 1].fetch(-1)) + assert_raise(IndexError) { [0, 1].fetch(2) } + assert_raise(IndexError) { [0, 1].fetch(-3) } + assert_equal(2, [0, 1].fetch(2, 2)) + end + + def test_fetch_values + ary = @cls[1, 2, 3] + assert_equal([], ary.fetch_values()) + assert_equal([1], ary.fetch_values(0)) + assert_equal([3, 1, 3], ary.fetch_values(2, 0, -1)) + assert_raise(TypeError) {ary.fetch_values("")} + assert_raise(IndexError) {ary.fetch_values(10)} + assert_raise(IndexError) {ary.fetch_values(-20)} + assert_equal(["10 not found"], ary.fetch_values(10) {|i| "#{i} not found"}) + assert_equal(["10 not found", 3], ary.fetch_values(10, 2) {|i| "#{i} not found"}) + end + + def test_index2 + a = [0, 1, 2] + assert_equal(a, a.index.to_a) + assert_equal(1, a.index {|x| x == 1 }) + end + + def test_rindex2 + a = [0, 1, 2] + assert_equal([2, 1, 0], a.rindex.to_a) + assert_equal(1, a.rindex {|x| x == 1 }) + + a = [0, 1] + e = a.rindex + assert_equal(1, e.next) + a.clear + assert_raise(StopIteration) { e.next } + + o = Object.new + class << o; self; end.class_eval do + define_method(:==) {|x| a.clear; false } + end + a = [nil, o] + assert_equal(nil, a.rindex(0)) + end + + def test_ary_to_ary + o = Object.new + def o.to_ary; [1, 2, 3]; end + a, b, c = o + assert_equal([1, 2, 3], [a, b, c]) + end + + def test_splice + a = [0] + assert_raise(IndexError) { a[-2, 0] = nil } + end + + def test_insert + a = [0] + 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_with_message(IndexError, /-6/) { a.insert(-6, 4) } + assert_raise(FrozenError) { [0].freeze.insert(0)} + assert_raise(ArgumentError) { [0].freeze.insert } + end + + def test_join2 + a = [] + a << a + assert_raise(ArgumentError){a.join} + + def (a = Object.new).to_ary + [self] + end + assert_raise(ArgumentError, '[ruby-core:24150]'){[a].join} + assert_equal("12345", [1,[2,[3,4],5]].join) + end + + def test_join_recheck_elements_type + x = Struct.new(:ary).new + def x.to_str + ary[2] = [0, 1, 2] + "z" + end + (x.ary = ["a", "b", "c", x]) + assert_equal("ab012z", x.ary.join("")) + end + + def test_join_recheck_array_length + x = Struct.new(:ary).new + def x.to_str + ary.clear + ary[0] = "b" + "z" + end + x.ary = Array.new(1023) {"a"*1} << x + assert_equal("b", x.ary.join("")) + end + + def test_to_a2 + klass = Class.new(Array) + a = klass.new.to_a + assert_equal([], a) + assert_equal(Array, a.class) + end + + def test_values_at2 + a = [0, 1, 2, 3, 4, 5] + assert_equal([1, 2, 3], a.values_at(1..3)) + 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 + + def test_select + assert_equal([0, 2], [0, 1, 2, 3].select {|x| x % 2 == 0 }) + end + + # also keep_if + def test_select! + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(nil, a.select! { true }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.select! { false }) + assert_equal(@cls[], a) + + 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) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.select! do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) + end + + # also select! + 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) + + assert_raise(FrozenError) do + a = @cls[1, 2, 3, 42] + a.keep_if do + a.freeze + false + end + end + assert_equal(@cls[1, 2, 3, 42], a) + end + + def test_filter + assert_equal([0, 2], [0, 1, 2, 3].filter {|x| x % 2 == 0 }) + end + + # alias for select! + def test_filter! + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(nil, a.filter! { true }) + assert_equal(@cls[1, 2, 3, 4, 5], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { false }) + assert_equal(@cls[], a) + + a = @cls[ 1, 2, 3, 4, 5 ] + assert_equal(a, a.filter! { |i| i > 3 }) + assert_equal(@cls[4, 5], a) + end + + def test_delete2 + a = [0] * 1024 + [1] + [0] * 1024 + a.delete(0) + assert_equal([1], a) + end + + def test_reject + 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"])) + a = [] + [1, 2, 3].zip([:a, :b], ["a", "b", "c", "d"]) {|x| a << x } + assert_equal([[1, :a, "a"], [2, :b, "b"], [3, nil, "c"]], a) + + ary = Object.new + def ary.to_a; [1, 2]; end + 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] + def r.respond_to?(*) + super + end + assert_equal [[42, 1]], [42].zip(r), bug8153 + end + + def test_zip_with_enumerator + bug17814 = "ruby-core:103513" + + step = 0.step + e = Enumerator.produce { step.next } + a = %w(a b c) + assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e), bug17814) + assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e), bug17814) + assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e), bug17814) + end + + def test_transpose + assert_equal([[1, :a], [2, :b], [3, :c]], + [[1, 2, 3], [:a, :b, :c]].transpose) + assert_raise(IndexError) { [[1, 2, 3], [:a, :b]].transpose } + end + + def test_clear2 + assert_equal([], ([0] * 1024).clear) + end + + def test_fill2 + assert_raise(ArgumentError) { [].fill(0, 1, LONGP) } + end + + def test_times + assert_raise(ArgumentError) { [0, 0, 0, 0] * ((LONGP + 1) / 4) } + end + + def test_equal + o = Object.new + def o.to_ary; end + def o.==(x); :foo; end + assert_equal([0, 1, 2], o) + assert_not_equal([0, 1, 2], [0, 1, 3]) + end + + def test_equal_resize + $test_equal_resize_a = Array.new(3, &:to_s) + $test_equal_resize_b = $test_equal_resize_a.dup + o = Object.new + def o.==(o) + $test_equal_resize_a.clear + $test_equal_resize_b.clear + true + end + $test_equal_resize_a[1] = o + assert_equal($test_equal_resize_a, $test_equal_resize_b) + end + + def test_flatten_error + a = [] + f = [].freeze + assert_raise(ArgumentError) { a.flatten!(1, 2) } + assert_raise(TypeError) { a.flatten!(:foo) } + assert_raise(ArgumentError) { f.flatten!(1, 2) } + 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_out_of_range + gen = random_generator {10000000} + assert_raise(RangeError) { + [*0..2].shuffle(random: gen) + } + gen = random_generator {-1} + assert_raise(RangeError) { + [*0..2].shuffle(random: gen) + } + end + + def test_shuffle_random_clobbering + ary = (0...10000).to_a + gen = random_generator do + ary.replace([]) + 0.5 + end + assert_raise(RuntimeError) {ary.shuffle!(random: gen)} + end + + def test_shuffle_random_zero + zero = Struct.new(:to_int).new(0) + gen_to_int = random_generator {|max| zero} + ary = (0...10000).to_a + assert_equal(ary.rotate, ary.shuffle(random: gen_to_int)) + end + + def test_shuffle_random_invalid_generator + ary = (0...10).to_a + assert_raise(NoMethodError) { + ary.shuffle(random: Object.new) + } + assert_raise(NoMethodError) { + ary.shuffle!(random: Object.new) + } + end + + def test_sample + 100.times do + assert_include([0, 1, 2], [2, 1, 0].sample) + samples = [2, 1, 0].sample(2) + samples.each{|sample| + assert_include([0, 1, 2], sample) + } + end + end + + def test_sample_statistics + srand(0) + a = (1..18).to_a + (0..20).each do |n| + 100.times do + b = a.sample(n) + assert_equal([n, 18].min, b.size) + assert_equal(a, (a | b).sort) + assert_equal(b.sort, (a & b).sort) + end + + h = Hash.new(0) + 1000.times do + a.sample(n).each {|x| h[x] += 1 } + end + assert_operator(h.values.min * 2, :>=, h.values.max) if n != 0 + end + end + + def test_sample_invalid_argument + assert_raise(ArgumentError, '[ruby-core:23374]') {[1, 2].sample(-1)} + end + + def test_sample_random_srand0 + gen = Random.new(0) + srand(0) + a = (1..18).to_a + (0..20).each do |n| + 100.times do |i| + assert_equal(a.sample(n), a.sample(n, random: gen), "#{i}/#{n}") + end + end + end + + def test_sample_unknown_keyword + assert_raise_with_message(ArgumentError, /unknown keyword/) do + [0, 1, 2].sample(xawqij: "a") + end + end + + def test_sample_random_generator + ary = (0...10000).to_a + assert_raise(ArgumentError) {ary.sample(1, 2, random: nil)} + gen0 = random_generator {|max| max/2} + gen1 = random_generator do |max| + ary.replace([]) + max/2 + 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) + end + + def test_sample_random_generator_half + half = Struct.new(:to_int).new(5000) + gen_to_int = random_generator {|max| half} + ary = (0...10000).to_a + assert_equal(5000, ary.sample(random: gen_to_int)) + end + + def test_sample_random_out_of_range + gen = random_generator {10000000} + assert_raise(RangeError) { + [*0..2].sample(random: gen) + } + gen = random_generator {-1} + assert_raise(RangeError) { + [*0..2].sample(random: gen) + } + end + + def test_sample_random_invalid_generator + ary = (0..10).to_a + assert_raise(NoMethodError) { + ary.sample(random: Object.new) + } + end + + def test_cycle + a = [] + [0, 1, 2].cycle do |i| + a << i + break if a.size == 10 + end + assert_equal([0, 1, 2, 0, 1, 2, 0, 1, 2, 0], a) + + a = [0, 1, 2] + assert_nil(a.cycle { a.clear }) + + 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 + a = [0, 1, 2, 3, 4, 5] + r = [] + a.reverse_each do |x| + r << x + a.pop + a.pop + end + assert_equal([5, 3, 1], r) + end + + def test_combination2 + 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 + a = (0..100).to_a + assert_raise(RangeError) do + a.product(a, a, a, a, a, a, a, a, a, a) + end + assert_nothing_raised(RangeError) do + a.product(a, a, a, a, a, a, a, a, a, a) { break } + end + end + + def test_initialize2 + a = [1] * 1000 + a.instance_eval { initialize } + assert_equal([], a) + end + + def test_shift_shared_ary + a = (1..100).to_a + b = [] + 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 + a = [1] * 100 + b = [] + b.replace(a) + a.replace([1, 2, 3]) + assert_equal([1, 2, 3], a) + assert_equal([1] * 100, b) + end + + def test_fill_negative_length + a = (1..10).to_a + a.fill(:foo, 5, -3) + assert_equal((1..10).to_a, a) + end + + def test_slice_frozen_array + a = [1,2,3,4,5].freeze + assert_equal([1,2,3,4], a[0,4]) + assert_equal([2,3,4,5], a[1,4]) + end + + def test_sort_by! + a = [1,3,5,2,4] + a.sort_by! {|x| -x } + assert_equal([5,4,3,2,1], a) + end + + def test_rotate + a = [1,2,3,4,5].freeze + assert_equal([2,3,4,5,1], a.rotate) + assert_equal([5,1,2,3,4], a.rotate(-1)) + assert_equal([3,4,5,1,2], a.rotate(2)) + assert_equal([4,5,1,2,3], a.rotate(-2)) + assert_equal([4,5,1,2,3], a.rotate(13)) + assert_equal([3,4,5,1,2], a.rotate(-13)) + a = [1].freeze + assert_equal([1], a.rotate) + assert_equal([1], a.rotate(2)) + assert_equal([1], a.rotate(-4)) + assert_equal([1], a.rotate(13)) + assert_equal([1], a.rotate(-13)) + a = [].freeze + assert_equal([], a.rotate) + assert_equal([], a.rotate(2)) + assert_equal([], a.rotate(-4)) + assert_equal([], a.rotate(13)) + assert_equal([], a.rotate(-13)) + a = [1,2,3] + assert_raise(ArgumentError) { a.rotate(1, 1) } + assert_equal([1,2,3,4,5].rotate(2**31-1), [1,2,3,4,5].rotate(2**31-0.1)) + assert_equal([1,2,3,4,5].rotate(-2**31), [1,2,3,4,5].rotate(-2**31-0.9)) + end + + def test_rotate! + a = [1,2,3,4,5] + assert_equal([2,3,4,5,1], a.rotate!) + assert_equal([2,3,4,5,1], a) + assert_equal([4,5,1,2,3], a.rotate!(2)) + assert_equal([5,1,2,3,4], a.rotate!(-4)) + assert_equal([3,4,5,1,2], a.rotate!(13)) + assert_equal([5,1,2,3,4], a.rotate!(-13)) + a = [1] + assert_equal([1], a.rotate!) + assert_equal([1], a.rotate!(2)) + assert_equal([1], a.rotate!(-4)) + assert_equal([1], a.rotate!(13)) + assert_equal([1], a.rotate!(-13)) + a = [] + assert_equal([], a.rotate!) + assert_equal([], a.rotate!(2)) + assert_equal([], a.rotate!(-4)) + assert_equal([], a.rotate!(13)) + assert_equal([], a.rotate!(-13)) + a = [].freeze + 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_equal(4, a.bsearch {|x| (4 - x).to_r }) + + assert_include([4, 7], a.bsearch {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) + end + + 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_equal(1, a.bsearch_index {|x| (4 - x).to_r }) + + assert_include([1, 2], a.bsearch_index {|x| (2**100).coerce((1 - x / 4) * (2**100)).first }) + end + + 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 + omit 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: 120) + 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_shrink_shared_array + assert_normal_exit(<<~'RUBY', '[Feature #20589]') + array = [] + # Make sure the array is allocated + 10.times { |i| array << i } + # Simulate a C extension using OBJ_FREEZE + Object.instance_method(:freeze).bind_call(array) + array.dup + RUBY + 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 + + def test_big_array_literal_with_kwsplat + lit = "[" + 10000.times { lit << "{}," } + lit << "**{}]" + + assert_equal(10000, eval(lit).size) + end + + def test_array_safely_modified_by_sort_block + var_0 = (1..70).to_a + var_0.sort! do |var_0_block_129, var_1_block_129| + var_0.pop + var_1_block_129 <=> var_0_block_129 + end.shift(3) + assert_equal((1..67).to_a.reverse, var_0) + end + + def test_find + ary = [1, 2, 3, 4, 5] + assert_equal(2, ary.find {|x| x % 2 == 0 }) + assert_equal(nil, ary.find {|x| false }) + assert_equal(:foo, ary.find(proc { :foo }) {|x| false }) + end + + def test_rfind + ary = [1, 2, 3, 4, 5] + assert_equal(4, ary.rfind {|x| x % 2 == 0 }) + assert_equal(1, ary.rfind {|x| x < 2 }) + assert_equal(5, ary.rfind {|x| x > 4 }) + assert_equal(nil, ary.rfind {|x| false }) + assert_equal(:foo, ary.rfind(proc { :foo }) {|x| false }) + assert_equal(nil, ary.rfind {|x| ary.clear; false }) + end + + private + def need_continuation + unless respond_to?(:callcc, true) + EnvUtil.suppress_warning {require 'continuation'} + end + omit 'requires callcc support' unless respond_to?(:callcc, true) + end + + def random_generator(&block) + class << block + alias rand call + end + block + end +end + +class TestArraySubclass < TestArray + def setup + @verbose = $VERBOSE + @cls = Class.new(Array) + end + + def test_to_a + a = @cls[ 1, 2, 3 ] + a_id = a.__id__ + assert_equal_instance([1, 2, 3], a.to_a) + assert_not_equal(a_id, a.to_a.__id__) + end + + def test_array_subclass + assert_equal(Array, @cls[1,2,3].uniq.class, "[ruby-dev:34581]") + assert_equal(Array, @cls[1,2][0,1].class) # embedded + assert_equal(Array, @cls[*(1..100)][1..99].class) #not embedded + end +end diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb new file mode 100644 index 0000000000..3d0e773c82 --- /dev/null +++ b/test/ruby/test_assignment.rb @@ -0,0 +1,964 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestAssignment < Test::Unit::TestCase + def test_assign + a=[]; a[0] ||= "bar"; + assert_equal("bar", a[0]) + h={}; h["foo"] ||= "bar"; + assert_equal("bar", h["foo"]) + + aa = 5 + aa ||= 25 + assert_equal(5, aa) + bb ||= 25 + assert_equal(25, bb) + cc &&=33 + assert_nil(cc) + 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) + a = [1]; assert_equal([1], a) + a = [nil]; assert_equal([nil], a) + a = [[]]; assert_equal([[]], a) + a = [1,2]; assert_equal([1,2], a) + 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) + a = *[[]]; assert_equal([[]], a) + a = *[1,2]; assert_equal([1,2], a) + 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) + *a = [1]; assert_equal([1], a) + *a = [nil]; assert_equal([nil], a) + *a = [[]]; assert_equal([[]], a) + *a = [1,2]; assert_equal([1,2], a) + *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) + *a = *[[]]; assert_equal([[]], a) + *a = *[1,2]; assert_equal([1,2], a) + *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]) + a,b,*c = [1]; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = [nil]; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = [[]]; assert_equal([[],nil,[]], [a,b,c]) + a,b,*c = [1,2]; assert_equal([1,2,[]], [a,b,c]) + 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_order + order = [] + define_singleton_method(:x1){order << :x1; self} + define_singleton_method(:y1){order << :y1; self} + define_singleton_method(:z=){|x| order << [:z=, x]} + define_singleton_method(:x2){order << :x2; self} + define_singleton_method(:x3){order << :x3; self} + define_singleton_method(:x4){order << :x4; self} + define_singleton_method(:x5=){|x| order << [:x5=, x]; self} + define_singleton_method(:[]=){|*args| order << [:[]=, *args]} + define_singleton_method(:r1){order << :r1; :r1} + define_singleton_method(:r2){order << :r2; :r2} + + x1.y1.z, x2[1, 2, 3], self[4] = r1, 6, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, 6], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], self[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :x3, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4], x4.x5 = r1, 6, 7, r2, 8 + assert_equal([:x1, :y1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x2.x5), _a = [r1, r2], 7 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:x5=, :r2]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3] = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7, :r2, 8]]], order) + order.clear + + *x2[1, 2, 3], (x3[4], x4.x5) = 6, 7, [r2, 8] + assert_equal([:x2, :x3, :x4, :r2, [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], x3[4], x4.x5 = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], (x3[4], x4.x5) = [r1, 5], 6, 7, [r2, 8] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, x1.x5), _a), *x2[1, 2, 3], ((x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, *x1.x5), _a), *x2[1, 2, 3], ((*x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, [5]], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, [:r2]], [:x5=, 8]], order) + order.clear + end + + def test_massign_const_order + order = [] + + test_mod_class = Class.new(Module) do + define_method(:x1){order << :x1; self} + define_method(:y1){order << :y1; self} + define_method(:x2){order << :x2; self} + define_method(:x3){order << :x3; self} + define_method(:x4){order << :x4; self} + define_method(:[]){|*args| order << [:[], *args]; self} + define_method(:r1){order << :r1; :r1} + define_method(:r2){order << :r2; :r2} + + define_method(:constant_values) do + h = {} + constants.each do |sym| + h[sym] = const_get(sym) + end + h + end + + define_singleton_method(:run) do |code| + m = new + m.instance_eval(code) + ret = [order.dup, m.constant_values] + order.clear + ret + end + end + + ord, constants = test_mod_class.run( + "x1.y1::A, x2[1, 2, 3]::B, self[4]::C = r1, 6, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>6, :C=>:r2}, constants) + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, self[4]::C = r1, 6, 7, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants) + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C = r1, 6, 7, r2" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2}, constants) + + + ord, constants = test_mod_class.run( + "x1.y1::A, *x2[1, 2, 3]::B, x3[4]::C, x4::D = r1, 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>[6, 7], :C=>:r2, :D=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x2::B), _a = [r1, r2], 7" + ) + assert_equal([:x1, :y1, :x2, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>:r2}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C = [r1, 5], 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7, :r2, 8]}, constants) + + ord, constants = test_mod_class.run( + "*x2[1, 2, 3]::A, (x3[4]::B, x4::C) = 6, 7, [r2, 8]" + ) + assert_equal([:x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r2], ord) + assert_equal({:A=>[6, 7], :B=>:r2, :C=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, x3[4]::D, x4::E = [r1, 5], 6, 7, r2, 8" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "(x1.y1::A, x1::B), *x2[1, 2, 3]::C, (x3[4]::D, x4::E) = [r1, 5], 6, 7, [r2, 8]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>:r2, :E=>8}, constants) + + ord, constants = test_mod_class.run( + "((x1.y1::A, x1::B), _a), *x2[1, 2, 3]::C, ((*x3[4]::D, x4::E), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11]" + ) + assert_equal([:x1, :y1, :x1, :x2, [:[], 1, 2, 3], :x3, [:[], 4], :x4, :r1, :r2], ord) + assert_equal({:A=>:r1, :B=>5, :C=>[6, 7], :D=>[:r2], :E=>8}, constants) + end + + def test_massign_splat + a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = *[nil]; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = *[[]]; assert_equal([[],nil,[]], [a,b,c]) + a,b,*c = *[1,2]; assert_equal([1,2,[]], [a,b,c]) + 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_optimized_literal_bug_21012 + a = [] + def a.[]=(*args) + push args + end + a["a", "b"], = 1 + a["a", 10], = 2 + assert_equal [["a", "b", 1], ["a", 10, 2]], a + end + + def test_assign_rescue + a = raise rescue 2; assert_equal(2, a) + a, b = raise rescue [3,4]; assert_equal([3, 4], [a, b]) + end + + def test_assign_abbreviated + bug2050 = '[ruby-core:25629]' + a = Hash.new {[]} + b = [1, 2] + assert_equal([1, 2, 3], a[:x] += [*b, 3], bug2050) + assert_equal([1, 2, 3], a[:x], bug2050) + assert_equal([1, 2, 3, [1, 2, 3]], a[:x] <<= [*b, 3], bug2050) + 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, bug11096) { + o.instance_eval {o.foo = 1} + } + assert_nothing_raised(NoMethodError, bug11096) { + assert_equal(1, o.instance_eval {self.foo = 1}) + } + + assert_raise(NoMethodError, bug11096) { + o.instance_eval {o[0] = 1} + } + assert_nothing_raised(NoMethodError, bug11096) { + assert_equal(1, o.instance_eval {self[0] = 1}) + } + + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self.foo += 1} + } + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self.foo &&= 1} + } + + assert_nothing_raised(NoMethodError, bug11096) { + o.instance_eval {self[0] += 1} + } + assert_nothing_raised(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 + def f; yield([]); end; f {|a| assert_equal([], a)}; undef f + def f; yield([1]); end; f {|a| assert_equal([1], a)}; undef f + def f; yield([nil]); end; f {|a| assert_equal([nil], a)}; undef f + def f; yield([[]]); end; f {|a| assert_equal([[]], a)}; undef f + def f; yield([*[]]); end; f {|a| assert_equal([], a)}; undef f + def f; yield([*[1]]); end; f {|a| assert_equal([1], a)}; undef f + def f; yield([*[1,2]]); end; f {|a| assert_equal([1,2], a)}; undef f + + def f; yield(*[1]); end; f {|a| assert_equal(1, a)}; undef f + def f; yield(*[nil]); end; f {|a| assert_equal(nil, a)}; undef f + def f; yield(*[[]]); end; f {|a| assert_equal([], a)}; undef f + def f; yield(*[*[1]]); end; f {|a| assert_equal(1, a)}; undef f + + def f; yield; end; f {|*a| assert_equal([], a)}; undef f + def f; yield(nil); end; f {|*a| assert_equal([nil], a)}; undef f + def f; yield(1); end; f {|*a| assert_equal([1], a)}; undef f + def f; yield([]); end; f {|*a| assert_equal([[]], a)}; undef f + def f; yield([1]); end; f {|*a| assert_equal([[1]], a)}; undef f + def f; yield([nil]); end; f {|*a| assert_equal([[nil]], a)}; undef f + def f; yield([[]]); end; f {|*a| assert_equal([[[]]], a)}; undef f + def f; yield([1,2]); end; f {|*a| assert_equal([[1,2]], a)}; undef f + def f; yield([*[]]); end; f {|*a| assert_equal([[]], a)}; undef f + def f; yield([*[1]]); end; f {|*a| assert_equal([[1]], a)}; undef f + def f; yield([*[1,2]]); end; f {|*a| assert_equal([[1,2]], a)}; undef f + + def f; yield(*[]); end; f {|*a| assert_equal([], a)}; undef f + def f; yield(*[1]); end; f {|*a| assert_equal([1], a)}; undef f + def f; yield(*[nil]); end; f {|*a| assert_equal([nil], a)}; undef f + def f; yield(*[[]]); end; f {|*a| assert_equal([[]], a)}; undef f + def f; yield(*[*[]]); end; f {|*a| assert_equal([], a)}; undef f + def f; yield(*[*[1]]); end; f {|*a| assert_equal([1], a)}; undef f + def f; yield(*[*[1,2]]); end; f {|*a| assert_equal([1,2], a)}; undef f + + def f; yield; end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(nil); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(1); end; f {|a,b,*c| assert_equal([1,nil,[]], [a,b,c])}; undef f + def f; yield([]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield([1]); end; f {|a,b,*c| assert_equal([1,nil,[]], [a,b,c])}; undef f + def f; yield([nil]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield([[]]); end; f {|a,b,*c| assert_equal([[],nil,[]], [a,b,c])}; undef f + def f; yield([*[]]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield([*[1]]); end; f {|a,b,*c| assert_equal([1,nil,[]], [a,b,c])}; undef f + def f; yield([*[1,2]]); end; f {|a,b,*c| assert_equal([1,2,[]], [a,b,c])}; undef f + + def f; yield(*[]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(*[1]); end; f {|a,b,*c| assert_equal([1,nil,[]], [a,b,c])}; undef f + def f; yield(*[nil]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(*[[]]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(*[*[]]); end; f {|a,b,*c| assert_equal([nil,nil,[]], [a,b,c])}; undef f + def f; yield(*[*[1]]); end; f {|a,b,*c| assert_equal([1,nil,[]], [a,b,c])}; undef f + def f; yield(*[*[1,2]]); end; f {|a,b,*c| assert_equal([1,2,[]], [a,b,c])}; undef f + end + + def test_return + def r; return; end; a = r(); assert_nil(a); undef r + def r; return nil; end; a = r(); assert_nil(a); undef r + def r; return 1; end; a = r(); assert_equal(1, a); undef r + def r; return []; end; a = r(); assert_equal([], a); undef r + def r; return [1]; end; a = r(); assert_equal([1], a); undef r + def r; return [nil]; end; a = r(); assert_equal([nil], a); undef r + def r; return [[]]; end; a = r(); assert_equal([[]], a); undef r + def r; return [*[]]; end; a = r(); assert_equal([], a); undef r + def r; return [*[1]]; end; a = r(); assert_equal([1], a); undef r + def r; return [*[1,2]]; end; a = r(); assert_equal([1,2], a); undef r + + def r; return *[]; end; a = r(); assert_equal([], a); undef r + def r; return *[1]; end; a = r(); assert_equal([1], a); undef r + def r; return *[nil]; end; a = r(); assert_equal([nil], a); undef r + def r; return *[[]]; end; a = r(); assert_equal([[]], a); undef r + def r; return *[*[]]; end; a = r(); assert_equal([], a); undef r + def r; return *[*[1]]; end; a = r(); assert_equal([1], a); undef r + def r; return *[*[1,2]]; end; a = r(); assert_equal([1,2], a); undef r + + def r; return *[[]]; end; a = *r(); assert_equal([[]], a); undef r + def r; return *[*[1,2]]; end; a = *r(); assert_equal([1,2], a); undef r + + def r; return; end; *a = r(); assert_equal([nil], a); undef r + def r; return nil; end; *a = r(); assert_equal([nil], a); undef r + def r; return 1; end; *a = r(); assert_equal([1], a); undef r + def r; return []; end; *a = r(); assert_equal([], a); undef r + def r; return [1]; end; *a = r(); assert_equal([1], a); undef r + def r; return [nil]; end; *a = r(); assert_equal([nil], a); undef r + def r; return [[]]; end; *a = r(); assert_equal([[]], a); undef r + def r; return [1,2]; end; *a = r(); assert_equal([1,2], a); undef r + def r; return [*[]]; end; *a = r(); assert_equal([], a); undef r + def r; return [*[1]]; end; *a = r(); assert_equal([1], a); undef r + def r; return [*[1,2]]; end; *a = r(); assert_equal([1,2], a); undef r + + def r; return *[]; end; *a = r(); assert_equal([], a); undef r + def r; return *[1]; end; *a = r(); assert_equal([1], a); undef r + def r; return *[nil]; end; *a = r(); assert_equal([nil], a); undef r + def r; return *[[]]; end; *a = r(); assert_equal([[]], a); undef r + def r; return *[1,2]; end; *a = r(); assert_equal([1,2], a); undef r + def r; return *[*[]]; end; *a = r(); assert_equal([], a); undef r + def r; return *[*[1]]; end; *a = r(); assert_equal([1], a); undef r + def r; return *[*[1,2]]; end; *a = r(); assert_equal([1,2], a); undef r + + def r; return *[[]]; end; *a = *r(); assert_equal([[]], a); undef r + def r; return *[1,2]; end; *a = *r(); assert_equal([1,2], a); undef r + def r; return *[*[1,2]]; end; *a = *r(); assert_equal([1,2], a); undef r + + def r; return; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return nil; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return 1; end; a,b,*c = r(); assert_equal([1,nil,[]], [a,b,c]); undef r + def r; return []; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return [1]; end; a,b,*c = r(); assert_equal([1,nil,[]], [a,b,c]); undef r + def r; return [nil]; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return [[]]; end; a,b,*c = r(); assert_equal([[],nil,[]], [a,b,c]); undef r + def r; return [1,2]; end; a,b,*c = r(); assert_equal([1,2,[]], [a,b,c]); undef r + def r; return [*[]]; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return [*[1]]; end; a,b,*c = r(); assert_equal([1,nil,[]], [a,b,c]); undef r + def r; return [*[1,2]]; end; a,b,*c = r(); assert_equal([1,2,[]], [a,b,c]); undef r + + def r; return *[]; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return *[1]; end; a,b,*c = r(); assert_equal([1,nil,[]], [a,b,c]); undef r + def r; return *[nil]; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return *[[]]; end; a,b,*c = r(); assert_equal([[],nil,[]], [a,b,c]); undef r + def r; return *[1,2]; end; a,b,*c = r(); assert_equal([1,2,[]], [a,b,c]); undef r + def r; return *[*[]]; end; a,b,*c = r(); assert_equal([nil,nil,[]], [a,b,c]); undef r + def r; return *[*[1]]; end; a,b,*c = r(); assert_equal([1,nil,[]], [a,b,c]); undef r + def r; return *[*[1,2]]; end; a,b,*c = r(); assert_equal([1,2,[]], [a,b,c]); undef r + + def r; return 1, *[]; end; a,b = r(); assert_equal([1,nil], [a,b]); undef r + def r; return 1,2,*[1]; end; a,b = r(); assert_equal([1,2], [a,b]); undef r + def r; return 1,2,3,*[1,2]; end; a,b = r(); assert_equal([1,2], [a,b]); undef r + end + + def test_lambda + f = lambda {|r,| assert_equal([], r)} + f.call([], *[]) + + f = lambda {|r,*l| assert_equal([], r); assert_equal([1], l)} + f.call([], *[1]) + + f = lambda{|x| x} + assert_equal(42, f.call(42)) + assert_equal([42], f.call([42])) + assert_equal([[42]], f.call([[42]])) + assert_equal([42,55], f.call([42,55])) + + f = lambda{|x,| x} + assert_equal(42, f.call(42)) + assert_equal([42], f.call([42])) + assert_equal([[42]], f.call([[42]])) + assert_equal([42,55], f.call([42,55])) + + f = lambda{|*x| x} + assert_equal([42], f.call(42)) + assert_equal([[42]], f.call([42])) + assert_equal([[[42]]], f.call([[42]])) + assert_equal([[42,55]], f.call([42,55])) + assert_equal([42,55], f.call(42,55)) + end + + def test_multi + a,=*[1] + assert_equal(1, a) + a,=*[[1]] + assert_equal([1], a) + a,=*[[[1]]] + assert_equal([[1]], a) + + x, (y, z) = 1, 2, 3 + assert_equal([1,2,nil], [x,y,z]) + x, (y, z) = 1, [2,3] + assert_equal([1,2,3], [x,y,z]) + x, (y, z) = 1, [2] + assert_equal([1,2,nil], [x,y,z]) + end + + def test_break + a = loop do break; end; assert_nil(a) + a = loop do break nil; end; assert_nil(a) + a = loop do break 1; end; assert_equal(1, a) + a = loop do break []; end; assert_equal([], a) + a = loop do break [1]; end; assert_equal([1], a) + a = loop do break [nil]; end; assert_equal([nil], a) + a = loop do break [[]]; end; assert_equal([[]], a) + a = loop do break [*[]]; end; assert_equal([], a) + a = loop do break [*[1]]; end; assert_equal([1], a) + a = loop do break [*[1,2]]; end; assert_equal([1,2], a) + + a = loop do break *[]; end; assert_equal([], a) + a = loop do break *[1]; end; assert_equal([1], a) + a = loop do break *[nil]; end; assert_equal([nil], a) + a = loop do break *[[]]; end; assert_equal([[]], a) + a = loop do break *[*[]]; end; assert_equal([], a) + a = loop do break *[*[1]]; end; assert_equal([1], a) + a = loop do break *[*[1,2]]; end; assert_equal([1,2], a) + + *a = loop do break; end; assert_equal([nil], a) + *a = loop do break nil; end; assert_equal([nil], a) + *a = loop do break 1; end; assert_equal([1], a) + *a = loop do break []; end; assert_equal([], a) + *a = loop do break [1]; end; assert_equal([1], a) + *a = loop do break [nil]; end; assert_equal([nil], a) + *a = loop do break [[]]; end; assert_equal([[]], a) + *a = loop do break [1,2]; end; assert_equal([1,2], a) + *a = loop do break [*[]]; end; assert_equal([], a) + *a = loop do break [*[1]]; end; assert_equal([1], a) + *a = loop do break [*[1,2]]; end; assert_equal([1,2], a) + + *a = loop do break *[]; end; assert_equal([], a) + *a = loop do break *[1]; end; assert_equal([1], a) + *a = loop do break *[nil]; end; assert_equal([nil], a) + *a = loop do break *[[]]; end; assert_equal([[]], a) + *a = loop do break *[1,2]; end; assert_equal([1,2], a) + *a = loop do break *[*[]]; end; assert_equal([], a) + *a = loop do break *[*[1]]; end; assert_equal([1], a) + *a = loop do break *[*[1,2]]; end; assert_equal([1,2], a) + + *a = *loop do break *[[]]; end; assert_equal([[]], a) + *a = *loop do break *[1,2]; end; assert_equal([1,2], a) + *a = *loop do break *[*[1,2]]; end; assert_equal([1,2], a) + + a,b,*c = loop do break; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break nil; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break 1; end; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = loop do break []; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break [1]; end; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = loop do break [nil]; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break [[]]; end; assert_equal([[],nil,[]], [a,b,c]) + a,b,*c = loop do break [1,2]; end; assert_equal([1,2,[]], [a,b,c]) + a,b,*c = loop do break [*[]]; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break [*[1]]; end; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = loop do break [*[1,2]]; end; assert_equal([1,2,[]], [a,b,c]) + + a,b,*c = loop do break *[]; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break *[1]; end; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = loop do break *[nil]; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break *[[]]; end; assert_equal([[],nil,[]], [a,b,c]) + a,b,*c = loop do break *[1,2]; end; assert_equal([1,2,[]], [a,b,c]) + a,b,*c = loop do break *[*[]]; end; assert_equal([nil,nil,[]], [a,b,c]) + a,b,*c = loop do break *[*[1]]; end; assert_equal([1,nil,[]], [a,b,c]) + a,b,*c = loop do break *[*[1,2]]; end; assert_equal([1,2,[]], [a,b,c]) + end + + def test_next + def r(val); a = yield(); assert_equal(val, a); end + r(nil){next} + r(nil){next nil} + r(1){next 1} + r([]){next []} + r([1]){next [1]} + r([nil]){next [nil]} + r([[]]){next [[]]} + r([]){next [*[]]} + r([1]){next [*[1]]} + r([1,2]){next [*[1,2]]} + + r([]){next *[]} + r([1]){next *[1]} + r([nil]){next *[nil]} + r([[]]){next *[[]]} + r([]){next *[*[]]} + r([1]){next *[*[1]]} + r([1,2]){next *[*[1,2]]} + undef r + + def r(val); *a = yield(); assert_equal(val, a); end + r([nil]){next} + r([nil]){next nil} + r([1]){next 1} + r([]){next []} + r([1]){next [1]} + r([nil]){next [nil]} + r([[]]){next [[]]} + r([1,2]){next [1,2]} + r([]){next [*[]]} + r([1]){next [*[1]]} + r([1,2]){next [*[1,2]]} + undef r + + def r(val); *a = *yield(); assert_equal(val, a); end + r([[]]){next *[[]]} + r([1,2]){next *[1,2]} + r([1,2]){next *[*[1,2]]} + undef r + + def r(val); a,b,*c = yield(); assert_equal(val, [a,b,c]); end + r([nil,nil,[]]){next} + r([nil,nil,[]]){next nil} + r([1,nil,[]]){next 1} + r([nil,nil,[]]){next []} + r([1,nil,[]]){next [1]} + r([nil,nil,[]]){next [nil]} + r([[],nil,[]]){next [[]]} + r([1,2,[]]){next [1,2]} + r([nil,nil,[]]){next [*[]]} + r([1,nil,[]]){next [*[1]]} + r([1,2,[]]){next [*[1,2]]} + undef r + + def r(val); a,b,*c = *yield(); assert_equal(val, [a,b,c]); end + r([[],nil,[]]){next *[[]]} + r([1,2,[]]){next *[1,2]} + r([1,2,[]]){next *[*[1,2]]} + undef r + end + + def test_massign + a = nil + assert(defined?(a)) + assert_nil(a) + + # multiple assignment + a, b = 1, 2 + assert_equal 1, a + assert_equal 2, b + + a, b, c = 1, 2, 3 + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + + a = 1 + b = 2 + a, b = b, a + assert_equal 2, a + assert_equal 1, b + + a, = 1, 2 + assert_equal 1, a + + a, = 1, 2, 3 + assert_equal 1, a + + a, * = 1, 2, 3 + assert_equal 1, a + + a, *b = 1, 2, 3 + assert_equal 1, a + assert_equal [2, 3], b + + 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 + + a = [1, 2], [3, 4] + assert_equal [[1,2], [3,4]], a + + a, (b, c), d = 1, [2, 3], 4 + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + assert_equal 4, d + + *a = 1, 2, 3 + assert_equal([1, 2, 3], a) + + *a = 4 + assert_equal([4], a) + + *a = nil + assert_equal([nil], a) + + a, b = 1 + assert_equal 1, a + assert_nil b + + a, b = [1, 2] + assert_equal 1, a + assert_equal 2, b + end + + def test_nested_massign + (a, b), c = [[1, 2], 3]; assert_equal [1,2,3], [a,b,c] + a, (b, c) = [[1, 2], 3]; assert_equal [[1,2], 3, nil], [a,b,c] + a, (b, c) = [1, [2, 3]]; assert_equal [1,2,3], [a,b,c] + (a, b), *c = [[1, 2], 3]; assert_equal [1,2,[3]], [a,b,c] + (a,b),c,(d,e) = [[1,2],3,[4,5]]; assert_equal [1,2,3,4,5],[a,b,c,d,e] + (a,*b),c,(d,e,*) = [[1,2],3,[4,5]]; assert_equal [1,[2],3,4,5],[a,b,c,d,e] + (a,b),c,(d,*e) = [[1,2,3],4,[5,6,7,8]]; assert_equal [1,2,4,5,[6,7,8]],[a,b,c,d,e] + (a,(b1,b2)),c,(d,e) = [[1,2],3,[4,5]]; assert_equal [1,2,nil,3,4,5],[a,b1,b2,c,d,e] + (a,(b1,b2)),c,(d,e) = [[1,[21,22]],3,[4,5]]; assert_equal [1,21,22,3,4,5],[a,b1,b2,c,d,e] + end + + class MyObj + def to_ary + [[1,2],[3,4]] + end + end + + def test_to_ary_splat + a, b = MyObj.new + assert_equal [[1,2],[3,4]], [a,b] + end + + A = 1 + B = 2 + X, Y = A, B + class Base + A = 3 + B = 4 + end + + def test_const_massign + assert_equal [1,2], [X,Y] + 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 + + def test_const_assign_order + assert_raise(RuntimeError) do + eval('raise("recv")::C = raise(ArgumentError, "bar")') + end + + assert_raise(RuntimeError) do + eval('m = 1; m::C = raise("bar")') + end + end +end + +require_relative 'sentence' +class TestAssignmentGen < Test::Unit::TestCase + Syntax = { + :exp => [["0"], + ["nil"], + ["false"], + ["[]"], + ["[",:exps,"]"]], + :exps => [[:exp], + [:exp,",",:exps]], + :arg => [[:exp]], + :mrhs => [[:args,",",:arg], + [:args,",","*",:arg], + ["*",:arg]], + :args => [[:arg], + ["*",:arg], + [:args,",",:arg], + [:args,",","*",:arg]], + :mlhs => [[:mlhs_basic], + ["(",:mlhs_inner,")"]], + :mlhs_inner => [[:mlhs_basic], + ["(",:mlhs_inner,")"]], + :mlhs_basic => [[:mlhs_head], + [:mlhs_head,:mlhs_item], + [:mlhs_head,"*",:mlhs_node], + [:mlhs_head,"*",:mlhs_node,",",:mlhs_post], + [:mlhs_head,"*"], + [:mlhs_head,"*",",", :mlhs_post], + [ "*",:mlhs_node], + [ "*",:mlhs_node,",",:mlhs_post], + [ "*"], + [ "*",",", :mlhs_post]], + :mlhs_head => [[:mlhs_item,","], + [:mlhs_head,:mlhs_item,","]], + :mlhs_post => [[:mlhs_item], + [:mlhs_post,",",:mlhs_item]], + :mlhs_item => [[:mlhs_node], + ["(",:mlhs_inner,")"]], + :mlhs_node => [["var"]], + :xassign => [["var"," = ",:exp], + ["var"," = ",:mrhs], + [:mlhs," = ",:exp], + [:mlhs," = ",:mrhs]], + } + + def rename_var(obj) + vars = [] + r = obj.subst('var') { + var = "v#{vars.length}" + vars << var + var + } + return r, vars + end + + def expand_except_paren(obj) + return obj if obj.respond_to? :to_str + obj.expand {|s| + !(s[0] == '(' && s[-1] == ')') && + !(s[0] == '[' && s[-1] == ']') + } + end + + def extract_single_element(ary) + raise "not a single element array: #{ary.inspect}" if ary.length != 1 + ary[0] + end + + def emu_assign_ary(lhs, rv, h) + rv = rv.respond_to?(:to_ary) ? rv : [rv] + rv = rv.dup + a = [[]] + lhs.each {|e| + if e == ',' + a << [] + else + a.last << e + end + } + a.pop if a.last == [] + pre = [] + star = post = nil + a.each {|e| + if post + post << e + elsif e[0] == '*' + star = e + post = [] + else + pre << e + end + } + pre.map! {|e| extract_single_element(e) } + if star + if star == ['*'] + star = nil + else + star = extract_single_element(star[1..-1]) + end + end + post.map! {|e| extract_single_element(e) } if post + + until pre.empty? + emu_assign_single(pre.shift, rv.shift, h) + end + + if post + if rv.length < post.length + until post.empty? + emu_assign_single(post.shift, rv.shift, h) + end + else + until post.empty? + emu_assign_single(post.pop, rv.pop, h) + end + end + end + + if star + emu_assign_single(star, rv, h) + end + end + + def emu_assign_single(lhs, rv, h={}) + if lhs.respond_to? :to_str + if /\A[a-z0-9]+\z/ =~ lhs + h[lhs] = rv + else + raise "unexpected lhs string: #{lhs.inspect}" + end + elsif Sentence === lhs + if lhs[0] == '(' && lhs[-1] == ')' + emu_assign_ary(lhs[1...-1], rv, h) + elsif lhs.length == 1 && String === lhs[0] && /\A[a-z0-9]+\z/ =~ lhs[0] + h[lhs[0]] = rv + else + raise "unexpected lhs sentence: #{lhs.inspect}" + end + else + raise "unexpected lhs: #{lhs.inspect}" + end + h + end + + def emu_assign(assign) + lhs = expand_except_paren(assign[0]) + rhs = expand_except_paren(assign[2]) + lopen = Sentence === lhs && lhs[-1] != ')' && lhs.any? {|e| e == '*' || e == ',' } + ropen = Sentence === rhs && rhs[-1] != ']' && rhs.any? {|e| e == '*' || e == ',' } + lhs = Sentence.new(['(']+lhs.to_a+[')']) if lopen + begin + rv = eval((ropen ? ["[",assign[2],"]"] : assign[2]).join('')) + rescue Exception + rv = $!.message + end + emu_assign_single(lhs, rv) + end + + def do_assign(assign, vars) + assign = assign.to_s + code1 = "#{assign}; [#{vars.join(",")}]" + assign.gsub!(/\bv\d+\b/, "o.a") + code2 = "o=[];class << o; self end.send(:define_method,:a=){|v|self << v};#{assign};o" + begin + vals1 = eval(code1) + rescue Exception + return {:ex=>$!.message} + end + begin + vals2 = eval(code2) + rescue Exception + return {:ex=>$!.message} + end + assert_equal(vals1, vals2, code1) + vals = vals1 + h = {} + [vars, vals].transpose.each {|k,v| h[k] = v } + h + end + + def check(assign) + assign, vars = rename_var(assign) + sent = assign.to_s + bruby = do_assign(assign, vars).to_a.sort + bemu = emu_assign(assign).to_a.sort + assert_equal(bemu, bruby, sent) + end + + def test_assignment + syntax = Sentence.expand_syntax(Syntax) + Sentence.each(syntax, :xassign, 4) {|assign| + 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..22ccbfb604 --- /dev/null +++ b/test/ruby/test_ast.rb @@ -0,0 +1,1753 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' +require 'pp' +require_relative '../lib/parser_support' + +class RubyVM + module AbstractSyntaxTree + class Node + class CodePosition + include Comparable + attr_reader :lineno, :column + def initialize(lineno, column) + @lineno = lineno + @column = column + end + + def <=>(other) + case + when lineno < other.lineno + -1 + when lineno == other.lineno + column <=> other.column + when lineno > other.lineno + 1 + end + end + end + + def beg_pos + CodePosition.new(first_lineno, first_column) + end + + def end_pos + CodePosition.new(last_lineno, last_column) + end + + alias to_s inspect + end + end +end + +class TestAst < Test::Unit::TestCase + class Helper + attr_reader :errors + + def initialize(path, src: nil) + @path = path + @errors = [] + @debug = false + @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse(src) } if src + end + + def validate_range + @errors = [] + validate_range0(ast) + + @errors.empty? + end + + def validate_not_cared + @errors = [] + validate_not_cared0(ast) + + @errors.empty? + end + + def ast + return @ast if defined?(@ast) + @ast = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file(@path) } + end + + private + + def validate_range0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + return true if children.empty? + # These NODE_D* has NODE_LIST as nd_next->nd_next whose last locations + # we can not update when item is appended. + return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type + + min = children.map(&:beg_pos).min + max = children.map(&:end_pos).max + + unless beg_pos <= min + @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node } + end + + unless max <= end_pos + @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node } + end + + p "#{node} => #{children}" if @debug + + children.each do |child| + p child if @debug + validate_range0(child) + end + end + + def validate_not_cared0(node) + beg_pos, end_pos = node.beg_pos, node.end_pos + children = node.children.grep(RubyVM::AbstractSyntaxTree::Node) + + @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0 + @errors << { type: :first_column, node: node } if beg_pos.column == -1 + @errors << { type: :last_lineno, node: node } if end_pos.lineno == 0 + @errors << { type: :last_column, node: node } if end_pos.column == -1 + + children.each {|c| validate_not_cared0(c) } + end + end + + SRCDIR = File.expand_path("../../..", __FILE__) + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_ranges:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_range + + assert_equal([], helper.errors) + end + end + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_not_cared:#{path}") do + helper = Helper.new("#{SRCDIR}/#{path}") + helper.validate_not_cared + + assert_equal([], helper.errors) + end + end + + Dir.glob("test/**/*.rb", base: SRCDIR).each do |path| + define_method("test_all_tokens:#{path}") do + node = EnvUtil.suppress_warning { RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true) } + tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] } + tokens_bytes = tokens.map { _1[2]}.join.bytes + source_bytes = File.read("#{SRCDIR}/#{path}").bytes + + assert_equal(source_bytes, tokens_bytes) + + (tokens.count - 1).times do |i| + token_0 = tokens[i] + token_1 = tokens[i + 1] + end_pos = token_0.last[2..3] + beg_pos = token_1.last[0..1] + + if end_pos[0] == beg_pos[0] + # When both tokens are same line, column should be consecutives + assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}") + else + # Line should be next + assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}") + # It should be on the beginning of the line + assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}") + end + end + end + end + + private def parse(src) + EnvUtil.suppress_warning { + RubyVM::AbstractSyntaxTree.parse(src) + } + end + + def test_allocate + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate} + end + + def test_parse_argument_error + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)} + assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)} + end + + def test_column_with_long_heredoc_identifier + term = "A"*257 + ast = parse("<<-#{term}\n""ddddddd\n#{term}\n") + node = ast.children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + end + + def test_column_of_heredoc + node = parse("<<-SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(6, node.last_column) + + node = parse("<<SRC\nddddddd\nSRC\n").children[2] + assert_equal(:STR, node.type) + assert_equal(0, node.first_column) + assert_equal(5, node.last_column) + end + + def test_parse_raises_syntax_error + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse("end") + end + end + + def test_parse_file_raises_syntax_error + Tempfile.create(%w"test_ast .rb") do |f| + f.puts "end" + f.close + assert_raise_with_message(SyntaxError, /\bend\b/) do + RubyVM::AbstractSyntaxTree.parse_file(f.path) + end + end + end + + def assert_parse(code, warning: '') + node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)} + assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code) + end + + def assert_invalid_parse(msg, code) + assert_raise_with_message(SyntaxError, msg, code) do + RubyVM::AbstractSyntaxTree.parse(code) + end + end + + def test_invalid_exit + [ + "break", + "break true", + "next", + "next true", + "redo", + ].each do |code, *args| + msg = /Invalid #{code[/\A\w+/]}/ + assert_parse("while false; #{code}; end") + assert_parse("until true; #{code}; end") + assert_parse("begin #{code}; end while false") + assert_parse("begin #{code}; end until true") + assert_parse("->{#{code}}") + assert_parse("->{class X; #{code}; end}") + assert_invalid_parse(msg, "#{code}") + assert_invalid_parse(msg, "def m; #{code}; end") + assert_invalid_parse(msg, "begin; #{code}; end") + assert_parse("END {#{code}}") + + assert_parse("!defined?(#{code})") + assert_parse("def m; defined?(#{code}); end") + assert_parse("!begin; defined?(#{code}); end") + + next if code.include?(" ") + assert_parse("!defined? #{code}") + assert_parse("def m; defined? #{code}; end") + assert_parse("!begin; defined? #{code}; end") + end + end + + def test_invalid_retry + msg = /Invalid retry/ + assert_invalid_parse(msg, "retry") + assert_invalid_parse(msg, "def m; retry; end") + assert_invalid_parse(msg, "begin retry; end") + assert_parse("begin rescue; retry; end") + assert_invalid_parse(msg, "begin rescue; else; retry; end") + assert_invalid_parse(msg, "begin rescue; ensure; retry; end") + assert_parse("nil rescue retry") + assert_invalid_parse(msg, "END {retry}") + assert_invalid_parse(msg, "begin rescue; END {retry}; end") + + assert_parse("!defined?(retry)") + assert_parse("def m; defined?(retry); end") + assert_parse("!begin defined?(retry); end") + assert_parse("begin rescue; else; defined?(retry); end") + assert_parse("begin rescue; ensure; p defined?(retry); end") + assert_parse("END {defined?(retry)}") + assert_parse("begin rescue; END {defined?(retry)}; end") + assert_parse("!defined? retry") + + assert_parse("def m; defined? retry; end") + assert_parse("!begin defined? retry; end") + assert_parse("begin rescue; else; defined? retry; end") + assert_parse("begin rescue; ensure; p defined? retry; end") + assert_parse("END {defined? retry}") + assert_parse("begin rescue; END {defined? retry}; end") + + assert_parse("#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def foo + begin + yield + rescue StandardError => e + begin + puts "hi" + retry + rescue + retry unless e + raise e + else + retry + ensure + retry + end + end + end + end; + end + + def test_invalid_yield + msg = /Invalid yield/ + assert_invalid_parse(msg, "yield") + assert_invalid_parse(msg, "class C; yield; end") + assert_invalid_parse(msg, "BEGIN {yield}") + assert_invalid_parse(msg, "END {yield}") + assert_invalid_parse(msg, "-> {yield}") + + assert_invalid_parse(msg, "yield true") + assert_invalid_parse(msg, "class C; yield true; end") + assert_invalid_parse(msg, "BEGIN {yield true}") + assert_invalid_parse(msg, "END {yield true}") + assert_invalid_parse(msg, "-> {yield true}") + + assert_parse("!defined?(yield)") + assert_parse("class C; defined?(yield); end") + assert_parse("BEGIN {defined?(yield)}") + assert_parse("END {defined?(yield)}") + + assert_parse("!defined?(yield true)") + assert_parse("class C; defined?(yield true); end") + assert_parse("BEGIN {defined?(yield true)}") + assert_parse("END {defined?(yield true)}") + + assert_parse("!defined? yield") + assert_parse("class C; defined? yield; end") + assert_parse("BEGIN {defined? yield}") + assert_parse("END {defined? yield}") + end + + def test_invalid_yield_no_memory_leak + # [Bug #21383] + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + code = proc do + eval("class C; yield; end") + rescue SyntaxError + end + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_node_id_for_location + omit if ParserSupport.prism_enabled? + + exception = begin + raise + rescue => e + e + end + loc = exception.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true) + + assert_equal node.node_id, node_id + end + + def add(x, y) + end + + def test_node_id_for_backtrace_location_of_method_definition + omit if ParserSupport.prism_enabled? + + begin + add(1) + rescue ArgumentError => exc + loc = exc.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(method(:add)) + assert_equal node.node_id, node_id + end + end + + def test_node_id_for_backtrace_location_of_lambda + omit if ParserSupport.prism_enabled? + + v = -> {} + begin + v.call(1) + rescue ArgumentError => exc + loc = exc.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(v) + assert_equal node.node_id, node_id + end + end + + def test_node_id_for_backtrace_location_of_lambda_method + omit if ParserSupport.prism_enabled? + + v = lambda {} + begin + v.call(1) + rescue ArgumentError => exc + loc = exc.backtrace_locations.first + node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc) + node = RubyVM::AbstractSyntaxTree.of(v) + assert_equal node.node_id, node_id + end + end + + def test_node_id_for_backtrace_location_raises_argument_error + bug19262 = '[ruby-core:111435]' + + assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) } + end + + def test_of_proc_and_method + omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess? + + proc = Proc.new { 1 + 2 } + method = self.method(__method__) + + node_proc = RubyVM::AbstractSyntaxTree.of(proc) + node_method = RubyVM::AbstractSyntaxTree.of(method) + + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method) + + Tempfile.create(%w"test_of .rb") do |tmp| + tmp.print "#{<<-"begin;"}\n#{<<-'end;'}" + begin; + SCRIPT_LINES__ = {} + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x})) + end; + tmp.close + assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + load ARGV[0] + assert_empty(SCRIPT_LINES__) + end; + end + end + + def sample_backtrace_location + [caller_locations(0).first, __LINE__] + end + + def test_of_backtrace_location + omit if ParserSupport.prism_enabled? + + backtrace_location, lineno = sample_backtrace_location + node = RubyVM::AbstractSyntaxTree.of(backtrace_location) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node) + assert_equal(lineno, node.first_lineno) + end + + def test_of_error + assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") } + end + + def test_of_proc_and_method_under_eval + omit if ParserSupport.prism_enabled? + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = false + + method = self.method(eval("def example_method_#{$$}; end")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("def self.example_singleton_method_#{$$}; end")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("proc{}") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("Class.new{def example_method; end}.instance_method(:example_method)") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("Class.new{def example_method; end}.instance_method(:example_method)") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_proc_and_method_under_eval_with_keep_script_lines + omit if ParserSupport.prism_enabled? + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + + method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("proc{}") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}")) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)") + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method)) + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_backtrace_location_under_eval + omit if ParserSupport.prism_enabled? + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = false + + m = Module.new do + eval(<<-END, nil, __FILE__, __LINE__) + def self.sample_backtrace_location + caller_locations(0).first + end + END + end + backtrace_location = m.sample_backtrace_location + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) } + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_backtrace_location_under_eval_with_keep_script_lines + omit if ParserSupport.prism_enabled? + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + keep_script_lines_back = RubyVM.keep_script_lines + RubyVM.keep_script_lines = true + + m = Module.new do + eval(<<-END, nil, __FILE__, __LINE__) + def self.sample_backtrace_location + caller_locations(0).first + end + END + end + backtrace_location = m.sample_backtrace_location + node = RubyVM::AbstractSyntaxTree.of(backtrace_location) + assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node) + assert_equal(2, node.first_lineno) + + ensure + RubyVM.keep_script_lines = keep_script_lines_back + end + + def test_of_c_method + c = Class.new { attr_reader :foo } + assert_nil(RubyVM::AbstractSyntaxTree.of(c.instance_method(:foo))) + end + + def test_scope_local_variables + node = RubyVM::AbstractSyntaxTree.parse("_x = 0") + lv, _, body = *node.children + assert_equal([:_x], lv) + assert_equal(:LASGN, body.type) + end + + def test_call + node = RubyVM::AbstractSyntaxTree.parse("nil.foo") + _, _, body = *node.children + assert_equal(:CALL, body.type) + recv, mid, args = body.children + assert_equal(:NIL, recv.type) + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_fcall + node = RubyVM::AbstractSyntaxTree.parse("foo()") + _, _, body = *node.children + assert_equal(:FCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_vcall + node = RubyVM::AbstractSyntaxTree.parse("foo") + _, _, body = *node.children + assert_equal(:VCALL, body.type) + mid, args = body.children + assert_equal(:foo, mid) + assert_nil(args) + end + + def test_defn + node = RubyVM::AbstractSyntaxTree.parse("def a; end") + _, _, body = *node.children + assert_equal(:DEFN, body.type) + mid, defn = body.children + assert_equal(:a, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_defn_endless + node = RubyVM::AbstractSyntaxTree.parse("def a = nil") + _, _, body = *node.children + assert_equal(:DEFN, body.type) + mid, defn = body.children + assert_equal(:a, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_defs + node = RubyVM::AbstractSyntaxTree.parse("def a.b; end") + _, _, body = *node.children + assert_equal(:DEFS, body.type) + recv, mid, defn = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:b, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_defs_endless + node = RubyVM::AbstractSyntaxTree.parse("def a.b = nil") + _, _, body = *node.children + assert_equal(:DEFS, body.type) + recv, mid, defn = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:b, mid) + assert_equal(:SCOPE, defn.type) + _, args, = defn.children + assert_equal(:ARGS, args.type) + end + + def test_dstr + node = parse('"foo#{1}bar"') + _, _, body = *node.children + assert_equal(:DSTR, body.type) + head, body = body.children + assert_equal("foo", head) + assert_equal(:EVSTR, body.type) + body, = body.children + assert_equal(:INTEGER, body.type) + assert_equal([1], body.children) + end + + def test_while + node = RubyVM::AbstractSyntaxTree.parse('1 while qux') + _, _, body = *node.children + assert_equal(:WHILE, body.type) + type1 = body.children[2] + node = RubyVM::AbstractSyntaxTree.parse('begin 1 end while qux') + _, _, body = *node.children + assert_equal(:WHILE, body.type) + type2 = body.children[2] + assert_not_equal(type1, type2) + end + + def test_until + node = RubyVM::AbstractSyntaxTree.parse('1 until qux') + _, _, body = *node.children + assert_equal(:UNTIL, body.type) + type1 = body.children[2] + node = RubyVM::AbstractSyntaxTree.parse('begin 1 end until qux') + _, _, body = *node.children + assert_equal(:UNTIL, body.type) + type2 = body.children[2] + assert_not_equal(type1, type2) + end + + def test_rest_arg + rest_arg = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-4] + end + + assert_equal(nil, rest_arg.call('')) + assert_equal(:r, rest_arg.call('*r')) + assert_equal(:r, rest_arg.call('a, *r')) + assert_equal(:*, rest_arg.call('*')) + assert_equal(:*, rest_arg.call('a, *')) + end + + def test_block_arg + block_arg = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-1] + end + + assert_equal(nil, block_arg.call('')) + assert_equal(:block, block_arg.call('&block')) + assert_equal(:&, block_arg.call('&')) + end + + def test_keyword_rest + kwrest = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1].children[-2] + node ? node.children : node + end + + assert_equal(nil, kwrest.call('')) + assert_equal([:**], kwrest.call('**')) + assert_equal(false, kwrest.call('**nil')) + assert_equal([:a], kwrest.call('**a')) + end + + def test_argument_forwarding + forwarding = lambda do |arg_str| + node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end") + node = node.children.last.children.last.children[1] + node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : [] + end + + assert_equal([:*, [:**], :&], forwarding.call('...')) + end + + def test_ranges_numbered_parameter + helper = Helper.new(__FILE__, src: "1.times {_1}") + helper.validate_range + assert_equal([], helper.errors) + end + + def test_op_asgn2 + node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo") + _, _, body = *node.children + assert_equal(:OP_ASGN2, body.type) + recv, _, mid, op, value = body.children + assert_equal(:VCALL, recv.type) + assert_equal(:field, mid) + assert_equal(:+, op) + assert_equal(:VCALL, value.type) + end + + def test_args + rest = 6 + node = RubyVM::AbstractSyntaxTree.parse("proc { |a| }") + _, args = *node.children.last.children[1].children + assert_equal(nil, args.children[rest]) + + node = RubyVM::AbstractSyntaxTree.parse("proc { |a,| }") + _, args = *node.children.last.children[1].children + assert_equal(:NODE_SPECIAL_EXCESSIVE_COMMA, args.children[rest]) + + node = RubyVM::AbstractSyntaxTree.parse("proc { |*a| }") + _, args = *node.children.last.children[1].children + assert_equal(:a, args.children[rest]) + end + + def test_return + assert_ast_eqaul(<<~STR, <<~EXP) + def m(a) + return a + end + STR + (SCOPE@1:0-3:3 + tbl: [] + args: nil + body: + (DEFN@1:0-3:3 + mid: :m + body: + (SCOPE@1:0-3:3 + tbl: [:a] + args: + (ARGS@1:6-1:7 + pre_num: 1 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (RETURN@2:2-2:10 (LVAR@2:9-2:10 :a))))) + EXP + end + + def test_keep_script_lines_for_parse + node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_script_lines: true) +1.times do + 2.times do + end +end +__END__ +dummy + END + + expected = [ + "1.times do\n", + " 2.times do\n", + " end\n", + "end\n", + "__END__\n", + ] + assert_equal(expected, node.script_lines) + + expected = + "1.times do\n" + + " 2.times do\n" + + " end\n" + + "end" + assert_equal(expected, node.source) + + expected = + "do\n" + + " 2.times do\n" + + " end\n" + + "end" + assert_equal(expected, node.children.last.children.last.source) + + expected = + "2.times do\n" + + " end" + assert_equal(expected, node.children.last.children.last.children.last.source) + end + + def test_keep_script_lines_for_of + omit if ParserSupport.prism_enabled? + + proc = Proc.new { 1 + 2 } + method = self.method(__method__) + + node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true) + node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true) + + assert_equal("Proc.new { 1 + 2 }", node_proc.source) + assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first) + end + + def test_keep_script_lines_for_of_with_existing_SCRIPT_LINES__that_has__FILE__as_a_key + omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess? + + # This test confirms that the bug that previously occurred because of + # `AbstractSyntaxTree.of`s unnecessary dependence on SCRIPT_LINES__ does not reproduce. + # The bug occurred only if SCRIPT_LINES__ included __FILE__ as a key. + lines = [ + "SCRIPT_LINES__ = {__FILE__ => []}", + "puts RubyVM::AbstractSyntaxTree.of(->{ 1 + 2 }, keep_script_lines: true).script_lines", + "p SCRIPT_LINES__" + ] + test_stdout = lines + ['{"-e" => []}'] + assert_in_out_err(["-e", lines.join("\n")], "", test_stdout, []) + end + + def test_source_with_multibyte_characters + ast = RubyVM::AbstractSyntaxTree.parse(%{a("\u00a7");b("\u00a9")}, keep_script_lines: true) + a_fcall, b_fcall = ast.children[2].children + + assert_equal(%{a("\u00a7")}, a_fcall.source) + assert_equal(%{b("\u00a9")}, b_fcall.source) + end + + def test_keep_tokens_for_parse + node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_tokens: true) + 1.times do + end + __END__ + dummy + END + + expected = [ + [:tINTEGER, "1"], + [:".", "."], + [:tIDENTIFIER, "times"], + [:tSP, " "], + [:keyword_do, "do"], + [:tIGNORED_NL, "\n"], + [:keyword_end, "end"], + [:nl, "\n"], + ] + assert_equal(expected, node.all_tokens.map { [_2, _3]}) + end + + def test_keep_tokens_unexpected_backslash + assert_raise_with_message(SyntaxError, /unexpected backslash/) do + RubyVM::AbstractSyntaxTree.parse("\\", keep_tokens: true) + end + end + + def test_encoding_with_keep_script_lines + # Stop a warning "possibly useless use of a literal in void context" + verbose_bak, $VERBOSE = $VERBOSE, nil + + enc = Encoding::EUC_JP + code = "__ENCODING__".encode(enc) + + assert_equal(enc, eval(code)) + + node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false) + assert_equal(enc, node.children[2].children[0]) + + node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true) + assert_equal(enc, node.children[2].children[0]) + + ensure + $VERBOSE = verbose_bak + end + + def test_e_option + omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess? + + assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"], + "", [":DEFN"], []) + end + + def test_error_tolerant + verbose_bak, $VERBOSE = $VERBOSE, false + node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true) + class A + def m + if; + a = 10 + end + end + STR + assert_nil($!) + + assert_equal(:SCOPE, node.type) + ensure + $VERBOSE = verbose_bak + end + + def test_error_tolerant_end_is_short_for_method_define + assert_error_tolerant(<<~STR, <<~EXP) + def m + m2 + STR + (SCOPE@1:0-2:4 + tbl: [] + args: nil + body: + (DEFN@1:0-2:4 + mid: :m + body: + (SCOPE@1:0-2:4 + tbl: [] + args: + (ARGS@1:5-1:5 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:4 :m2)))) + EXP + end + + def test_error_tolerant_end_is_short_for_singleton_method_define + assert_error_tolerant(<<~STR, <<~EXP) + def obj.m + m2 + STR + (SCOPE@1:0-2:4 + tbl: [] + args: nil + body: + (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m + (SCOPE@1:0-2:4 + tbl: [] + args: + (ARGS@1:9-1:9 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:4 :m2)))) + EXP + end + + def test_error_tolerant_end_is_short_for_begin + assert_error_tolerant(<<~STR, <<~EXP) + begin + a = 1 + STR + (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))) + EXP + end + + def test_error_tolerant_end_is_short_for_if + assert_error_tolerant(<<~STR, <<~EXP) + if cond + a = 1 + STR + (SCOPE@1:0-2:7 + tbl: [:a] + args: nil + body: + (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)) + nil)) + EXP + + assert_error_tolerant(<<~STR, <<~EXP) + if cond + a = 1 + else + STR + (SCOPE@1:0-3:4 + tbl: [:a] + args: nil + body: + (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)) + (BEGIN@3:4-3:4 nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_unless + assert_error_tolerant(<<~STR, <<~EXP) + unless cond + a = 1 + STR + (SCOPE@1:0-2:7 + tbl: [:a] + args: nil + body: + (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)) + nil)) + EXP + + assert_error_tolerant(<<~STR, <<~EXP) + unless cond + a = 1 + else + STR + (SCOPE@1:0-3:4 + tbl: [:a] + args: nil + body: + (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)) + (BEGIN@3:4-3:4 nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_while + assert_error_tolerant(<<~STR, <<~EXP) + while true + m + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true)) + EXP + end + + def test_error_tolerant_end_is_short_for_until + assert_error_tolerant(<<~STR, <<~EXP) + until true + m + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true)) + EXP + end + + def test_error_tolerant_end_is_short_for_case + assert_error_tolerant(<<~STR, <<~EXP) + case a + when 1 + STR + (SCOPE@1:0-2:6 + tbl: [] + args: nil + body: + (CASE@1:0-2:6 (VCALL@1:5-1:6 :a) + (WHEN@2:0-2:6 (LIST@2:5-2:6 (INTEGER@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil) + nil))) + EXP + + + assert_error_tolerant(<<~STR, <<~EXP) + case + when a == 1 + STR + (SCOPE@1:0-2:11 + tbl: [] + args: nil + body: + (CASE2@1:0-2:11 nil + (WHEN@2:0-2:11 + (LIST@2:5-2:11 + (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :== + (LIST@2:10-2:11 (INTEGER@2:10-2:11 1) nil)) nil) + (BEGIN@2:11-2:11 nil) nil))) + EXP + + + assert_error_tolerant(<<~STR, <<~EXP) + case a + in {a: String} + STR + (SCOPE@1:0-2:14 + tbl: [] + args: nil + body: + (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a) + (IN@2:0-2:14 + (HSHPTN@2:4-2:13 + const: nil + kw: + (HASH@2:4-2:13 + (LIST@2:4-2:13 (SYM@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil)) + kwrest: nil) (BEGIN@2:14-2:14 nil) nil))) + EXP + end + + def test_error_tolerant_end_is_short_for_for + assert_error_tolerant(<<~STR, <<~EXP) + for i in ary + m + STR + (SCOPE@1:0-2:3 + tbl: [:i] + args: nil + body: + (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary) + (SCOPE@1:0-2:3 + tbl: [nil] + args: + (ARGS@1:4-1:5 + pre_num: 1 + pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil)) + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:3 :m)))) + EXP + end + + def test_error_tolerant_end_is_short_for_class + assert_error_tolerant(<<~STR, <<~EXP) + class C + STR + (SCOPE@1:0-1:7 + tbl: [] + args: nil + body: + (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil + (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil)))) + EXP + end + + def test_error_tolerant_end_is_short_for_module + assert_error_tolerant(<<~STR, <<~EXP) + module M + STR + (SCOPE@1:0-1:8 + tbl: [] + args: nil + body: + (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M) + (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do + assert_error_tolerant(<<~STR, <<~EXP) + m do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil) + (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do_block + assert_error_tolerant(<<~STR, <<~EXP) + m 1 do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (INTEGER@1:2-1:3 1) nil)) + (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_end_is_short_for_do_LAMBDA + assert_error_tolerant(<<~STR, <<~EXP) + -> do + a + STR + (SCOPE@1:0-2:3 + tbl: [] + args: nil + body: + (LAMBDA@1:0-2:3 + (SCOPE@1:0-2:3 + tbl: [] + args: + (ARGS@1:2-1:2 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (VCALL@2:2-2:3 :a)))) + EXP + end + + def test_error_tolerant_treat_end_as_keyword_based_on_indent + assert_error_tolerant(<<~STR, <<~EXP) + module Z + class Foo + foo. + end + + def bar + end + end + STR + (SCOPE@1:0-8:3 + tbl: [] + args: nil + body: + (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z) + (SCOPE@1:0-8:3 + tbl: [] + args: nil + body: + (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil) + (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil + (SCOPE@2:2-4:5 + tbl: [] + args: nil + body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5)))) + (DEFN@6:2-7:5 + mid: :bar + body: + (SCOPE@6:2-7:5 + tbl: [] + args: + (ARGS@6:9-6:9 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: nil)))))) + EXP + end + + def test_error_tolerant_expr_value_can_be_error + assert_error_tolerant(<<~STR, <<~EXP) + def m + if + end + STR + (SCOPE@1:0-3:3 + tbl: [] + args: nil + body: + (DEFN@1:0-3:3 + mid: :m + body: + (SCOPE@1:0-3:3 + tbl: [] + args: + (ARGS@1:5-1:5 + pre_num: 0 + pre_init: nil + opt: nil + first_post: nil + post_num: 0 + post_init: nil + rest: nil + kw: nil + kwrest: nil + block: nil) + body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil)))) + EXP + end + + def test_error_tolerant_unexpected_backslash + node = assert_error_tolerant("\\", <<~EXP, keep_tokens: true) + (SCOPE@1:0-1:1 tbl: [] args: nil body: (ERROR@1:0-1:1)) + EXP + assert_equal([[0, :backslash, "\\", [1, 0, 1, 1]]], node.children.last.tokens) + end + + def test_with_bom + assert_error_tolerant("\u{feff}nil", <<~EXP) + (SCOPE@1:0-1:3 tbl: [] args: nil body: (NIL@1:0-1:3)) + EXP + end + + def test_unused_block_local_variable + assert_warning('') do + RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}}) + end + end + + def test_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "\n#{<<~'end;'}", rss: true) + begin; + 1_000_000.times do + eval("") + end + end; + end + + def test_locations + begin + verbose_bak, $VERBOSE = $VERBOSE, false + node = RubyVM::AbstractSyntaxTree.parse("1 + 2") + ensure + $VERBOSE = verbose_bak + end + locations = node.locations + + assert_equal(RubyVM::AbstractSyntaxTree::Location, locations[0].class) + end + + private + + def assert_error_tolerant(src, expected, keep_tokens: false) + assert_ast_eqaul(src, expected, error_tolerant: true, keep_tokens: keep_tokens) + end + + def assert_ast_eqaul(src, expected, **options) + begin + verbose_bak, $VERBOSE = $VERBOSE, false + node = RubyVM::AbstractSyntaxTree.parse(src, **options) + ensure + $VERBOSE = verbose_bak + end + assert_nil($!) + str = "" + PP.pp(node, str, 80) + assert_equal(expected, str) + node + end + + class TestLocation < Test::Unit::TestCase + def test_lineno_and_column + node = ast_parse("1 + 2") + assert_locations(node.locations, [[1, 0, 1, 5]]) + end + + def test_alias_locations + node = ast_parse("alias foo bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + end + + def test_and_locations + node = ast_parse("1 and 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 2, 1, 5]]) + + node = ast_parse("1 && 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + end + + def test_block_pass_locations + node = ast_parse("foo(&bar)") + assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 8], [1, 4, 1, 5]]) + + node = ast_parse("def a(&); b(&) end") + assert_locations(node.children[-1].children[-1].children[-1].children[-1].children[-1].locations, [[1, 12, 1, 13], [1, 12, 1, 13]]) + end + + def test_break_locations + node = ast_parse("loop { break 1 }") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 14], [1, 7, 1, 12]]) + end + + def test_case_locations + node = ast_parse("case a; when 1; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 4], [1, 16, 1, 19]]) + end + + def test_case2_locations + node = ast_parse("case; when 1; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]]) + end + + def test_case3_locations + node = ast_parse("case a; in 1; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]]) + end + + def test_class_locations + node = ast_parse("class A end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 5], nil, [1, 8, 1, 11]]) + + node = ast_parse("class A < B; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]]) + end + + def test_colon2_locations + node = ast_parse("A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + + node = ast_parse("A::B::C") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + end + + def test_colon3_locations + node = ast_parse("::A") + assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + + node = ast_parse("::A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + end + + def test_defined_locations + node = ast_parse("defined? x") + assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 0, 1, 8]]) + + node = ast_parse("defined?(x)") + assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 8]]) + end + + def test_dot2_locations + node = ast_parse("1..2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]]) + + node = ast_parse("foo(1..2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]]) + + node = ast_parse("foo(1..2, 3)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]]) + + node = ast_parse("foo(..2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 7], [1, 4, 1, 6]]) + end + + def test_dot3_locations + node = ast_parse("1...2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 1, 1, 4]]) + + node = ast_parse("foo(1...2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]]) + + node = ast_parse("foo(1...2, 3)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]]) + + node = ast_parse("foo(...2)") + assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 4, 1, 7]]) + end + + def test_evstr_locations + node = ast_parse('"#{foo}"') + assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 8], [1, 1, 1, 3], [1, 6, 1, 7]]) + + node = ast_parse('"#$1"') + assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 5], [1, 1, 1, 2], nil]) + end + + def test_flip2_locations + node = ast_parse("if 'a'..'z'; foo; end") + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 11], [1, 6, 1, 8]]) + + node = ast_parse('if 1..5; foo; end') + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 7], [1, 4, 1, 6]]) + end + + def test_flip3_locations + node = ast_parse("if 'a'...('z'); foo; end") + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 14], [1, 6, 1, 9]]) + + node = ast_parse('if 1...5; foo; end') + assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 8], [1, 4, 1, 7]]) + end + + def test_for_locations + node = ast_parse("for a in b; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 3], [1, 6, 1, 8], nil, [1, 12, 1, 15]]) + + node = ast_parse("for a in b do; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 3], [1, 6, 1, 8], [1, 11, 1, 13], [1, 15, 1, 18]]) + end + + def test_lambda_locations + node = ast_parse("-> (a, b) { foo }") + assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 2], [1, 10, 1, 11], [1, 16, 1, 17]]) + + node = ast_parse("-> (a, b) do foo end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 20], [1, 0, 1, 2], [1, 10, 1, 12], [1, 17, 1, 20]]) + end + + def test_module_locations + node = ast_parse('module A end') + assert_locations(node.children[-1].locations, [[1, 0, 1, 12], [1, 0, 1, 6], [1, 9, 1, 12]]) + end + + def test_if_locations + node = ast_parse("if cond then 1 else 2 end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 25], [1, 0, 1, 2], [1, 8, 1, 12], [1, 22, 1, 25]]) + + node = ast_parse("1 if 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4], nil, nil]) + + node = ast_parse("if 1; elsif 2; else end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 23], [1, 0, 1, 2], [1, 4, 1, 5], [1, 20, 1, 23]]) + assert_locations(node.children[-1].children[-1].locations, [[1, 6, 1, 19], [1, 6, 1, 11], [1, 13, 1, 14], [1, 20, 1, 23]]) + + node = ast_parse("true ? 1 : 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 12], nil, [1, 9, 1, 10], nil]) + + node = ast_parse("case a; in b if c; end") + assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil]) + end + + def test_in_locations + node = ast_parse("case 1; in 2 then 3; end") + assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 20], [1, 8, 1, 10], [1, 13, 1, 17], nil]) + + node = ast_parse("1 => a") + assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], nil, nil, [1, 2, 1, 4]]) + + node = ast_parse("1 in a") + assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], [1, 2, 1, 4], nil, nil]) + + node = ast_parse("case 1; in 2; 3; end") + assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 16], [1, 8, 1, 10], [1, 12, 1, 13], nil]) + end + + def test_next_locations + node = ast_parse("loop { next 1 }") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]]) + end + + def test_op_asgn1_locations + node = ast_parse("ary[1] += foo") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], nil, [1, 3, 1, 4], [1, 5, 1, 6], [1, 7, 1, 9]]) + + node = ast_parse("ary[1, 2] += foo") + assert_locations(node.children[-1].locations, [[1, 0, 1, 16], nil, [1, 3, 1, 4], [1, 8, 1, 9], [1, 10, 1, 12]]) + end + + def test_or_locations + node = ast_parse("1 or 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + + node = ast_parse("1 || 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]]) + end + + def test_op_asgn2_locations + node = ast_parse("a.b += 1") + assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 1, 1, 2], [1, 2, 1, 3], [1, 4, 1, 6]]) + + node = ast_parse("A::B.c += d") + assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 4, 1, 5], [1, 5, 1, 6], [1, 7, 1, 9]]) + + node = ast_parse("a = b.c += d") + assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 12], [1, 5, 1, 6], [1, 6, 1, 7], [1, 8, 1, 10]]) + + node = ast_parse("a = A::B.c += d") + assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 15], [1, 8, 1, 9], [1, 9, 1, 10], [1, 11, 1, 13]]) + end + + def test_postexe_locations + node = ast_parse("END { }") + assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 3], [1, 4, 1, 5], [1, 7, 1, 8]]) + + node = ast_parse("END { 1 }") + assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 3], [1, 4, 1, 5], [1, 8, 1, 9]]) + end + + def test_redo_locations + node = ast_parse("loop { redo }") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 11], [1, 7, 1, 11]]) + end + + def test_regx_locations + node = ast_parse("/foo/") + assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 5]]) + + node = ast_parse("/foo/i") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 6]]) + end + + def test_return_locations + node = ast_parse("return 1") + assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 6]]) + + node = ast_parse("return") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]]) + end + + def test_sclass_locations + node = ast_parse("class << self; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 5], [1, 6, 1, 8], [1, 15, 1, 18]]) + + node = ast_parse("class << obj; foo; end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 22], [1, 0, 1, 5], [1, 6, 1, 8], [1, 19, 1, 22]]) + end + + def test_splat_locations + node = ast_parse("a = *1") + assert_locations(node.children[-1].children[1].locations, [[1, 4, 1, 6], [1, 4, 1, 5]]) + + node = ast_parse("a = *1, 2") + assert_locations(node.children[-1].children[1].children[0].locations, [[1, 4, 1, 6], [1, 4, 1, 5]]) + + node = ast_parse("case a; when *1; end") + assert_locations(node.children[-1].children[1].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]]) + + node = ast_parse("case a; when *1, 2; end") + assert_locations(node.children[-1].children[1].children[0].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]]) + end + + def test_super_locations + node = ast_parse("super 1") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 0, 1, 5], nil, nil]) + + node = ast_parse("super(1)") + assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 5], [1, 5, 1, 6], [1, 7, 1, 8]]) + end + + def test_unless_locations + node = ast_parse("unless cond then 1 else 2 end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]]) + + node = ast_parse("1 unless 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 2, 1, 8], nil, nil]) + end + + def test_undef_locations + node = ast_parse("undef foo") + assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 5]]) + + node = ast_parse("undef foo, bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 14], [1, 0, 1, 5]]) + end + + def test_valias_locations + node = ast_parse("alias $foo $bar") + assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 5]]) + + node = ast_parse("alias $foo $&") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + + node = ast_parse("alias $foo $`") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + + node = ast_parse("alias $foo $'") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + + node = ast_parse("alias $foo $+") + assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]]) + end + + def test_when_locations + node = ast_parse("case a; when 1 then 2; end") + assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 22], [1, 8, 1, 12], [1, 15, 1, 19]]) + end + + def test_while_locations + node = ast_parse("while cond do 1 end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]]) + + node = ast_parse("1 while 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil]) + end + + def test_until_locations + node = ast_parse("until cond do 1 end") + assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]]) + + node = ast_parse("1 until 2") + assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil]) + end + + def test_yield_locations + node = ast_parse("def foo; yield end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 14], [1, 9, 1, 14], nil, nil]) + + node = ast_parse("def foo; yield() end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 16], [1, 9, 1, 14], [1, 14, 1, 15], [1, 15, 1, 16]]) + + node = ast_parse("def foo; yield 1, 2 end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 19], [1, 9, 1, 14], nil, nil]) + + node = ast_parse("def foo; yield(1, 2) end") + assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 20], [1, 9, 1, 14], [1, 14, 1, 15], [1, 19, 1, 20]]) + end + + private + def ast_parse(src, **options) + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + RubyVM::AbstractSyntaxTree.parse(src, **options) + ensure + $VERBOSE = verbose_bak + end + end + + def assert_locations(locations, expected) + ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] } + + assert_equal(ary, expected) + end + end +end diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb new file mode 100644 index 0000000000..82bf2d9d2c --- /dev/null +++ b/test/ruby/test_autoload.rb @@ -0,0 +1,626 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' + +class TestAutoload < Test::Unit::TestCase + def test_autoload_so + # Date is always available, unless excluded intentionally. + assert_in_out_err([], <<-INPUT, [], []) + 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(tmpfile, a.autoload?(:X, false)) + assert_equal(tmpfile, a.autoload?(:X, nil)) + assert_nil(b.autoload?(:X, false)) + assert_nil(b.autoload?(:X, nil)) + assert_equal(true, a.const_defined?("Y")) + assert_equal(true, b.const_defined?("Y")) + assert_equal(tmpfile2, a.autoload?("Y")) + assert_equal(tmpfile2, b.autoload?("Y")) + } + end + + def test_autoload_p_with_static_extensions + require 'rbconfig' + omit unless RbConfig::CONFIG['EXTSTATIC'] == 'static' + begin + require 'fcntl.so' + rescue LoadError + omit('fcntl not included in the build') + end + + assert_separately(['--disable-all'], <<~RUBY) + autoload :Fcntl, 'fcntl.so' + + assert_equal('fcntl.so', autoload?(:Fcntl)) + assert(Object.const_defined?(:Fcntl)) + assert_equal('constant', defined?(Fcntl), '[Bug #19115]') + RUBY + end + + def test_autoload_with_unqualified_file_name # [ruby-core:69206] + Object.send(:remove_const, :A) if Object.const_defined?(:A) + + lp = $LOAD_PATH.dup + lf = $LOADED_FEATURES.dup + + Dir.mktmpdir('autoload') { |tmpdir| + $LOAD_PATH << tmpdir + + Dir.chdir(tmpdir) do + eval <<-END + class ::Object + module A + autoload :C, 'test-ruby-core-69206' + end + end + END + + File.write("test-ruby-core-69206.rb", '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 + verbose_bak, $VERBOSE = $VERBOSE, nil + 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 + } + ensure + $VERBOSE = verbose_bak + 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 + Ruby::Box.module_eval do + alias old_require require + end + called_with = [] + Kernel.send :define_method, :require do |path| + called_with << path + old_require path + end + Ruby::Box.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 + Ruby::Box.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 + # .dup to prevent breaking called_with by autoloading pp, etc + assert_equal [file.path], called_with.dup + } + 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 + # .dup to prevent breaking called_with by autoloading pp, etc + assert_equal [a.path, b.path], called_with.dup + end + end + end + end + + def test_bug_13526 + # Skip this on macOS 10.13 because of the following error: + # http://rubyci.s3.amazonaws.com/osx1013/ruby-master/log/20231011T014505Z.fail.html.gz + require "rbconfig" + + 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+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + private_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-14469.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + deprecate_constant :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[ruby-core:85516] [Bug #14469]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-14469.rb" + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_private_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + private_constant :ZZZ + end + assert_raise(NameError, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_deprecate_constant_before_autoload + Dir.mktmpdir('autoload') do |tmpdir| + File.write(tmpdir+"/test-bug-11055.rb", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class AutoloadTest + ZZZ = :ZZZ + end + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {class AutoloadTest; ZZZ; end} + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-'end;'}") + bug = '[Bug #11055]' + begin; + class AutoloadTest + autoload :ZZZ, "test-bug-11055.rb" + deprecate_constant :ZZZ + end + assert_warning(/ZZZ is deprecated/, bug) {AutoloadTest::ZZZ} + end; + end + end + + def test_autoload_fork + EnvUtil.default_warning do + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'sleep 0.3; class AutoloadTest; end' + file.close + add_autoload(file.path) + begin + thrs = [] + 3.times do + thrs << Thread.new { AutoloadTest && nil } + thrs << Thread.new { fork { AutoloadTest } } + end + thrs.each(&:join) + thrs.each do |th| + pid = th.value or next + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end + ensure + remove_autoload_constant + assert_nil $!, '[ruby-core:86410] [Bug #14634]' + end + } + end + end if Process.respond_to?(:fork) + + def test_autoload_same_file + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-14742.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + module Foo; end + module Bar; end + end; + 3.times do # timing-dependent, needs a few times to hit [Bug #14742] + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-14742' + autoload :Bar, 'test-bug-14742' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + bug = '[ruby-core:86935] [Bug #14742]' + assert_instance_of Module, t1.value, bug + assert_instance_of Module, t2.value, bug + end; + end + end + end + + def test_autoload_same_file_with_raise + Dir.mktmpdir('autoload') do |tmpdir| + File.write("#{tmpdir}/test-bug-16177.rb", "#{<<~'begin;'}\n#{<<~'end;'}") + begin; + raise '[ruby-core:95055] [Bug #16177]' + end; + assert_raise(RuntimeError, '[ruby-core:95055] [Bug #16177]') do + assert_separately(%W[-I #{tmpdir}], "#{<<-'begin;'}\n#{<<-'end;'}") + begin; + autoload :Foo, 'test-bug-16177' + autoload :Bar, 'test-bug-16177' + t1 = Thread.new do Foo end + t2 = Thread.new do Bar end + t1.join + t2.join + end; + end + end + end + + def test_source_location + bug = "Bug16764" + Dir.mktmpdir('autoload') do |tmpdir| + path = "#{tmpdir}/test-#{bug}.rb" + File.write(path, "C::#{bug} = __FILE__\n") + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class C; end + C.autoload(:Bug16764, #{path.dump}) + assert_equal [__FILE__, __LINE__-1], C.const_source_location(#{bug.dump}) + assert_equal #{path.dump}, C.const_get(#{bug.dump}) + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + end; + end + end + + def test_source_location_after_require + bug = "Bug18624" + Dir.mktmpdir('autoload') do |tmpdir| + path = "#{tmpdir}/test-#{bug}.rb" + File.write(path, "C::#{bug} = __FILE__\n") + assert_separately(%W[-I #{tmpdir}], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + class C; end + C.autoload(:Bug18624, #{path.dump}) + require #{path.dump} + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + assert_equal #{path.dump}, C.const_get(#{bug.dump}) + assert_equal [#{path.dump}, 1], C.const_source_location(#{bug.dump}) + end; + end + end + + def test_no_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", 'many autoloads', timeout: 60) + begin; + 200000.times do |i| + m = Module.new + m.instance_eval do + autoload :Foo, 'x' + autoload :Bar, i.to_s + end + end + end; + end + + def test_autoload_after_failed_and_removed_from_loaded_features + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "test-bug-15790.rb") + File.write(autoload_path, '') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + $VERBOSE = nil + path = #{File.realpath(autoload_path).inspect} + autoload :X, path + assert_equal(path, Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_nil(Object.autoload?(:X)) + assert_equal(false, Object.const_defined?(:X)) + + $LOADED_FEATURES.delete(path) + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + + assert_raise(NameError){X} + assert_equal(false, Object.const_defined?(:X)) + assert_nil(Object.autoload?(:X)) + RUBY + end + end + + def add_autoload(path) + (@autoload_paths ||= []) << path + ::Object.class_eval {autoload(:AutoloadTest, path)} + end + + def remove_autoload_constant + $".replace($" - @autoload_paths) + ::Object.class_eval {remove_const(:AutoloadTest)} if defined? Object::AutoloadTest + TestAutoload.class_eval {remove_const(:AutoloadTest)} if defined? TestAutoload::AutoloadTest + end + + def test_autoload_module_gc + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_module_gc.rb") + File.write(autoload_path, "X = 1; Y = 2;") + + x = Module.new + x.autoload :X, "./feature.rb" + + 1000.times do + y = Module.new + y.autoload :Y, "./feature.rb" + end + + x = y = nil + + # Ensure the internal data structures are cleaned up correctly / don't crash: + GC.start + end + end + + def test_autoload_parallel_race + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "autoload_parallel_race.rb") + File.write(autoload_path, 'module Foo; end; module Bar; end') + + assert_ruby_status([], <<-RUBY, timeout: 100) + autoload_path = #{File.realpath(autoload_path).inspect} + + # This should work with no errors or failures. + 1000.times do + autoload :Foo, autoload_path + autoload :Bar, autoload_path + + t1 = Thread.new {Foo} + t2 = Thread.new {Bar} + + t1.join + GC.start # force GC. + t2.join + + Object.send(:remove_const, :Foo) + Object.send(:remove_const, :Bar) + + $LOADED_FEATURES.delete(autoload_path) + end + RUBY + end + end + + def test_autoload_parent_namespace + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, "some_const.rb") + File.write(autoload_path, 'class SomeConst; end') + + assert_separately(%W[-I #{tmpdir}], <<-RUBY) + module SomeNamespace + autoload :SomeConst, #{File.realpath(autoload_path).inspect} + assert_warning(%r{/some_const\.rb to define SomeNamespace::SomeConst but it didn't}) do + assert_not_nil SomeConst + end + end + RUBY + end + end + + private + + def assert_separately(*args, **kwargs) + super(*args, timeout: 60, **kwargs) + end + + def assert_ruby_status(*args, **kwargs) + super(*args, timeout: 60, **kwargs) + end +end diff --git a/test/ruby/test_backtrace.rb b/test/ruby/test_backtrace.rb new file mode 100644 index 0000000000..dad7dfcb55 --- /dev/null +++ b/test/ruby/test_backtrace.rb @@ -0,0 +1,469 @@ +# 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_limit + x = nil + c = Class.new do + define_method(:bar) do + x = caller(1, 1) + end + end + [c.new].group_by(&:bar) + assert_equal 1, x.length + assert_equal caller(0), caller(0, nil) + end + + def test_caller_with_nil_length + assert_equal caller(0), caller(0, nil) + end + + def test_each_backtrace_location + assert_nil(Thread.each_caller_location {}) + + assert_raise(LocalJumpError) {Thread.each_caller_location} + + i = 0 + cl = caller_locations(1, 1)[0]; ecl = Thread.each_caller_location{|x| i+=1; break x if i == 1} + assert_equal(cl.to_s, ecl.to_s) + assert_kind_of(Thread::Backtrace::Location, ecl) + + i = 0 + ary = [] + cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2} + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + assert_kind_of(Thread::Backtrace::Location, last) + + i = 0 + ary = [] + ->{->{ + cllr = caller_locations(1, 2); last = Thread.each_caller_location{|x| ary << x; i+=1; break x if i == 2} + }.()}.() + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + assert_kind_of(Thread::Backtrace::Location, last) + + cllr = caller_locations(1, 2); ary = Thread.to_enum(:each_caller_location).to_a[2..3] + assert_equal(cllr.map(&:to_s), ary.map(&:to_s)) + + ecl = Thread.to_enum(:each_caller_location) + assert_raise(StopIteration) { + ecl.next + } + + ary = [] + cl = caller_locations(1, 2); Thread.each_caller_location(1, 2) {|x| ary << x} + assert_equal(cl.map(&:to_s), ary.map(&:to_s)) + end + + def test_caller_locations_first_label + def self.label + caller_locations.first.label + end + + def self.label_caller + label + end + + assert_equal 'label_caller', label_caller + + [1].group_by do + assert_equal 'label_caller', label_caller + end + end + + def test_caller_limit_cfunc_iseq_no_pc + def self.a; [1].group_by { b } end + def self.b + [ + caller_locations(2, 1).first.base_label, + caller_locations(3, 1).first.base_label + ] + end + assert_equal({["each", "group_by"]=>[1]}, a) + end + + def test_caller_location_inspect_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1).inspect + end + @line = __LINE__ + 1 + [1].map.map { [1].map.map { foo } } + assert_equal("[\"#{__FILE__}:#{@line}:in 'Array#map'\"]", @res) + end + + def test_caller_location_path_cfunc_iseq_no_pc + def self.foo + @res = caller_locations(2, 1)[0].path + end + [1].map.map { [1].map.map { foo } } + assert_equal(__FILE__, @res) + 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("TestBacktrace##{__method__}", caller_locations(0, 1)[0].label) + loc, = tap {break caller_locations(0, 1)} + assert_equal("block in TestBacktrace##{__method__}", loc.label) + begin + raise + rescue + assert_equal("TestBacktrace##{__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 + {**1} + 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 'Object#foo': foo! (RuntimeError)", + "\tfrom -:4:in '<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + foo + end; + + err = ["-:7:in 'Object#bar': bar! (RuntimeError)", + "\tfrom -:9:in '<main>'", + "-:2:in 'Object#foo': foo! (RuntimeError)", + "\tfrom -:5:in 'Object#bar'", + "\tfrom -:9:in '<main>'"] + assert_in_out_err([], <<-"end;", [], err) + def foo + raise "foo!" + end + def bar + foo + rescue + raise "bar!" + end + bar + end; + end + + def test_caller_to_enum + err = ["-:3:in 'Object#foo': unhandled exception", "\tfrom -:in 'Enumerator#each'"] + assert_in_out_err([], <<-"end;", [], err, "[ruby-core:91911]") + def foo + return to_enum(__method__) unless block_given? + raise + yield 1 + end + + enum = foo + enum.next + end; + end + + def test_no_receiver_for_anonymous_class + err = ["-:2:in 'bar': unhandled exception", # Not '#<Class:0xXXX>.bar' + "\tfrom -:3:in '<main>'"] + assert_in_out_err([], <<-"end;", [], err) + foo = Class.new + def foo.bar = raise + foo.bar + end; + + err = ["-:3:in 'baz': unhandled exception", # Not '#<Class:0xXXX>::Bar.baz' + "\tfrom -:4:in '<main>'"] + assert_in_out_err([], <<-"end;", [], err) + foo = Class.new + foo::Bar = Class.new + def (foo::Bar).baz = raise + foo::Bar.baz + end; + end + + def test_backtrace_internal_frame + backtrace = tap { break caller_locations(0) } + assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>" + assert_equal("Kernel#tap", backtrace[1].label) + end + + def test_backtrace_on_argument_error + lineno = __LINE__; [1, 2].inject(:tap) + rescue ArgumentError + assert_equal("#{ __FILE__ }:#{ lineno }:in 'Kernel#tap'", $!.backtrace[0].to_s) + end +end diff --git a/test/ruby/test_basicinstructions.rb b/test/ruby/test_basicinstructions.rb new file mode 100644 index 0000000000..f6b69cc1e5 --- /dev/null +++ b/test/ruby/test_basicinstructions.rb @@ -0,0 +1,725 @@ +# frozen_string_literal: false +require 'test/unit' + +ConstTest = 3 +class Class + alias _remove_const remove_const + public :_remove_const +end + +class TestBasicInstructions < Test::Unit::TestCase + + def test_immediates + assert_equal((1==1), true) + assert_equal((1==2), false) + assert_equal [][0], nil + assert_equal "sym".intern, :sym + assert_equal "sym".intern, :"sym" + assert_equal 1234 + 0, 1234 + assert_equal 1234, 1_2_3_4 + assert_equal 41, 0b0101001 + assert_equal 420, 0644 + assert_equal 18, 0x12 + assert_equal 123456789012345678901234567890 + 0, + 123456789012345678901234567890 + assert_equal 1.234 + 0.0, 1.234 + end + + def test_self + assert_equal self, self + assert_equal false, (self == false) # Qfalse==0 in C + assert_equal false, (self == nil) + assert_equal false, (self == 0) + end + + def test_string + expected = "str" + "ing" + assert_equal expected, 'string' + assert_equal expected, "string" + assert_equal expected, %q(string) + assert_equal expected, %Q(string) + assert_equal expected, %(string) + end + + def test_dstring + assert_equal "2", "#{1+1}" + s = "OK" + assert_equal "OK", "#{s}" + assert_equal "OKx", "#{s}x" + assert_equal "xOK", "x#{s}" + assert_equal "xOKx", "x#{s}x" + end + + def test_dsym + assert_equal :a3c, :"a#{1+2}c" + s = "sym" + assert_equal :sym, :"#{s}" + assert_equal :sym, :"#{"#{"#{s}"}"}" + end + + def test_xstr + assert_equal 'hoge', `echo hoge`.chomp + assert_equal '3', `echo #{1 + 2}`.chomp + hoge = 'huga' + assert_equal 'huga', `echo #{hoge}`.chomp + end + + def test_regexp + assert_equal(/test/, /test/) + assert_equal 'test', /test/.source + assert_equal 'TEST', /TEST/.source + assert_equal true, !!(/test/ =~ 'test') + assert_equal false, !!(/test/ =~ 'does not match') + + re = /test/ + assert_equal re, re + assert_equal 'test', re.source + assert_equal true, !!(re =~ 'test') + assert_equal false, !!(re =~ 'does not match') + + assert_equal(/x#{1+1}x/, /x#{1+1}x/) + s = "OK" + assert_equal(/x#{s}x/, /x#{s}x/) + assert_equal true, !!(/x#{s}x/ =~ "xOKx") + assert_equal false, !!(/x#{s}x/ =~ "does not match") + + s = "OK" + prev = nil + 3.times do + re = /#{s}/o + assert_same prev, re if prev + prev = re + end + end + + def test_array + assert_equal [], [] + assert_equal 0, [].size + assert_equal [1, 2, 3], [1, 2, 3] + assert_equal [3, 7, 11], [1+2, 3+4, 5+6] + assert_equal [[1], [2], [3]], [[1], [2], [3]] + + a = [1, 2, 3] + assert_equal 1, a[0] + assert_equal 2, a[1] + assert_equal 3, a[2] + assert_nil a[3] + + a = %w( a b c ) + assert_equal 'a', a[0] + assert_equal 'b', a[1] + assert_equal 'c', a[2] + assert_nil a[3] + end + + def test_hash + assert_equal({}, {}) + 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({"key"=>"val"}, {"key"=>"val"}) + end + + def test_range + assert_equal((1..3), (1..3)) + assert_equal((1...3), (1...3)) + assert_not_equal((1...3), (1..3)) + assert_not_equal((1..3), (1...3)) + assert_equal((1..3), (1..2+1)) + assert_equal((1...3), (1...2+1)) + assert_equal(('a'..'z'), ('a'..'z')) + end + + def test_not + assert_equal true, !nil + assert_equal true, !false + assert_equal false, !true + assert_equal false, !3 + assert_equal false, !(1+1) + + assert_equal false, !!nil + assert_equal false, !!false + assert_equal true, !!true + assert_equal true, !!3 + assert_equal true, !!(1+1) + + assert_equal true, (not nil) + assert_equal true, (not false) + assert_equal false, (not true) + assert_equal false, (not 3) + assert_equal false, (not (1 + 1)) + + assert_equal false, (not not nil) + assert_equal false, (not not false) + assert_equal true, (not not true) + assert_equal true, (not not 3) + assert_equal true, (not not (1+1)) + end + + def test_local_variable + a = 7 + assert_equal 7, a + assert_equal a, a + b = 17 + assert_equal 7, a + assert_equal 17, b + assert_equal a, a + assert_equal b, b + assert_not_equal a, b + assert_not_equal b, a + a = b + assert_equal 17, a + assert_equal 17, b + assert_equal a, a + assert_equal b, b + assert_equal a, b + assert_equal b, a + c = 28 + assert_equal 17, a + assert_equal 17, b + assert_equal 28, c + assert_equal a, a + assert_equal b, b + assert_equal a, b + assert_equal c, c + assert_not_equal a, c + assert_not_equal b, c + a = b = c + assert_equal 28, a + assert_equal 28, b + assert_equal 28, c + assert_equal a, a + assert_equal b, b + assert_equal a, b + assert_equal b, a + assert_equal a, c + assert_equal c, a + assert_equal b, c + assert_equal c, b + + a = 1 + b = 2 + c = 3 + set_lvar_in_another_method + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + end + + def set_lvar_in_another_method + assert_raise(NameError) { a } + assert_raise(NameError) { b } + assert_raise(NameError) { c } + a = a = "NOT OK" + b = b = "NOT OK" + c = c = "NOT OK" + end + + class Const + $Const = self + C = 'Const::C' + def self.c() C end + def c() C end + + class A + C = 'Const::A::C' + def self.c() C end + def c() C end + CC = 'Const::A::CC' + def self.cc() CC end + def cc() CC end + + class B + C = 'Const::A::B::C' + def self.c() C end + def c() C end + def self.cc() CC end + def cc() CC end + end + end + + class AA < A + def self.cc() CC end + def cc() CC end + end + + class AAA < AA + def self.cc() CC end + def cc() CC end + end + end + C = 0 + + def test_const_path + do_test_const_path + do_test_const_path + do_test_const_path + end + + def do_test_const_path + assert_equal 0, C + assert_equal 0, C + assert_equal 3, ::ConstTest + assert_equal 3, ::ConstTest + assert_equal $Const, Const + + assert_equal 'Const::C', Const::C + assert_equal 'Const::C', Const::C + assert_equal 'Const::A::C', Const::A::C + assert_equal 'Const::A::C', Const::A::C + assert_equal 'Const::A::B::C', Const::A::B::C + assert_equal 'Const::A::B::C', Const::A::B::C + + Const::A::B._remove_const :C + assert_equal 'Const::C', Const::C + assert_equal 'Const::A::C', Const::A::C + assert_raise(NameError) { Const::A::B::C } + + Const::A._remove_const :C + assert_equal 'Const::C', Const::C + assert_raise(NameError) { Const::A::C } + assert_raise(NameError) { Const::A::B::C } + + Const._remove_const :C + assert_raise(NameError) { Const::C } + assert_raise(NameError) { Const::A::C } + assert_raise(NameError) { Const::A::B::C } + + Const::A.const_set :C, 'Const::A::C' + assert_raise(NameError) { Const::C } + assert_equal 'Const::A::C', Const::A::C + assert_raise(NameError) { Const::A::B::C } + + Const::A::B.const_set :C, 'Const::A::B::C' + assert_raise(NameError) { Const::C } + assert_equal 'Const::A::C', Const::A::C + assert_equal 'Const::A::B::C', Const::A::B::C + + Const.const_set :C, 'Const::C' + assert_equal 'Const::C', Const::C + assert_equal 'Const::A::C', Const::A::C + assert_equal 'Const::A::B::C', Const::A::B::C + end + + def test_const_cref + do_test_const_cref + do_test_const_cref + do_test_const_cref + end + + def do_test_const_cref + assert_equal 'Const::C', Const.new.c + assert_equal 'Const::A::C', Const::A.new.c + assert_equal 'Const::A::B::C', Const::A::B.new.c + + assert_equal 'Const::C', Const.c + assert_equal 'Const::A::C', Const::A.c + assert_equal 'Const::A::B::C', Const::A::B.c + + Const::A::B._remove_const :C + assert_equal 'Const::C', Const.c + assert_equal 'Const::A::C', Const::A.c + assert_equal 'Const::A::C', Const::A::B.c + assert_equal 'Const::C', Const.new.c + assert_equal 'Const::A::C', Const::A.new.c + assert_equal 'Const::A::C', Const::A::B.new.c + + Const::A._remove_const :C + assert_equal 'Const::C', Const.c + assert_equal 'Const::C', Const::A.c + assert_equal 'Const::C', Const::A::B.c + assert_equal 'Const::C', Const.new.c + assert_equal 'Const::C', Const::A.new.c + assert_equal 'Const::C', Const::A::B.new.c + + Const::A::B.const_set :C, 'Const::A::B::C' + assert_equal 'Const::C', Const.c + assert_equal 'Const::C', Const::A.c + assert_equal 'Const::A::B::C', Const::A::B.c + assert_equal 'Const::C', Const.new.c + assert_equal 'Const::C', Const::A.new.c + assert_equal 'Const::A::B::C', Const::A::B.new.c + + Const::A.const_set :C, 'Const::A::C' + assert_equal 'Const::C', Const.c + assert_equal 'Const::A::C', Const::A.c + assert_equal 'Const::A::B::C', Const::A::B.c + assert_equal 'Const::C', Const.new.c + assert_equal 'Const::A::C', Const::A.new.c + assert_equal 'Const::A::B::C', Const::A::B.new.c + ensure + # reset + Const.const_set :C, 'Const::C' unless Const.const_defined?(:C) + Const::A.const_set :C, 'Const::A::C' unless Const::A.const_defined?(:C) + Const::A::B.const_set :C, 'Const::A::B::C' unless Const::A::B.const_defined?(:C) + end + + def test_const_inherit + do_test_const_inherit + do_test_const_inherit + do_test_const_inherit + end + + def do_test_const_inherit + assert_equal 'Const::A::CC', Const::A.cc + assert_equal 'Const::A::CC', Const::AA.cc + assert_equal 'Const::A::CC', Const::AAA.cc + assert_equal 'Const::A::CC', Const::A.new.cc + assert_equal 'Const::A::CC', Const::AA.new.cc + assert_equal 'Const::A::CC', Const::AAA.new.cc + + Const::AA.const_set :CC, 'Const::AA::CC' + assert_equal 'Const::A::CC', Const::A.cc + assert_equal 'Const::AA::CC', Const::AA.cc + assert_equal 'Const::AA::CC', Const::AAA.cc + assert_equal 'Const::A::CC', Const::A.new.cc + assert_equal 'Const::AA::CC', Const::AA.new.cc + assert_equal 'Const::AA::CC', Const::AAA.new.cc + + Const::AAA.const_set :CC, 'Const::AAA::CC' + assert_equal 'Const::A::CC', Const::A.cc + assert_equal 'Const::AA::CC', Const::AA.cc + assert_equal 'Const::AAA::CC', Const::AAA.cc + assert_equal 'Const::A::CC', Const::A.new.cc + assert_equal 'Const::AA::CC', Const::AA.new.cc + assert_equal 'Const::AAA::CC', Const::AAA.new.cc + + Const::AA._remove_const :CC + assert_equal 'Const::A::CC', Const::A.cc + assert_equal 'Const::A::CC', Const::AA.cc + assert_equal 'Const::AAA::CC', Const::AAA.cc + assert_equal 'Const::A::CC', Const::A.new.cc + assert_equal 'Const::A::CC', Const::AA.new.cc + assert_equal 'Const::AAA::CC', Const::AAA.new.cc + + Const::AAA._remove_const :CC + assert_equal 'Const::A::CC', Const::A.cc + assert_equal 'Const::A::CC', Const::AA.cc + assert_equal 'Const::A::CC', Const::AAA.cc + assert_equal 'Const::A::CC', Const::A.new.cc + assert_equal 'Const::A::CC', Const::AA.new.cc + assert_equal 'Const::A::CC', Const::AAA.new.cc + end + + def test_global_variable + $gvar1 = 1 + assert_equal 1, $gvar1 + $gvar1 = 2 + assert_equal 2, $gvar1 + $gvar2 = 77 + assert_equal 2, $gvar1 + assert_equal 77, $gvar2 + $gvar2 = $gvar1 + assert_equal 2, $gvar1 + assert_equal 2, $gvar2 + $gvar1 = 1 + assert_equal 1, $gvar1 + assert_equal 2, $gvar2 + set_gvar_in_another_method + assert_equal "OK1", $gvar1 + assert_equal "OK2", $gvar2 + end + + def set_gvar_in_another_method + assert_equal 1, $gvar1 + assert_equal 2, $gvar2 + $gvar1 = "OK1" + $gvar2 = "OK2" + end + + class CVarA + def self.setup + @@cv = 'CVarA@@cv' + end + def self.cv() @@cv end + def self.cv=(v) @@cv = v end + class << self + def cv2() @@cv end + end + def cv() @@cv end + def cv=(v) @@cv = v end + end + + class CVarB < CVarA + def self.cvB() @@cv end + def self.cvB=(v) @@cv = v end + class << self + def cvB2() @@cv end + end + def cvB() @@cv end + def cvB=(v) @@cv = v end + end + + def test_class_variable + CVarA.setup + assert_equal 'CVarA@@cv', CVarA.cv + assert_equal 'CVarA@@cv', CVarA.cv2 + assert_equal 'CVarA@@cv', CVarA.new.cv + CVarA.cv = 'singleton' + assert_equal 'singleton', CVarA.cv + assert_equal 'singleton', CVarA.cv2 + assert_equal 'singleton', CVarA.new.cv + CVarA.new.cv = 'instance' + assert_equal 'instance', CVarA.cv + assert_equal 'instance', CVarA.cv2 + assert_equal 'instance', CVarA.new.cv + + CVarA.cv = 'CVarA@@cv' + CVarB.cv = 'B/singleton' + assert_equal 'B/singleton', CVarB.cv + assert_equal 'B/singleton', CVarB.cv2 + assert_equal 'B/singleton', CVarB.new.cv + assert_equal 'B/singleton', CVarA.cv + assert_equal 'B/singleton', CVarA.cv2 + assert_equal 'B/singleton', CVarA.new.cv + CVarB.new.cv = 'B/instance' + assert_equal 'B/instance', CVarB.cv + assert_equal 'B/instance', CVarB.cv2 + assert_equal 'B/instance', CVarB.new.cv + assert_equal 'B/instance', CVarA.cv + assert_equal 'B/instance', CVarA.cv2 + assert_equal 'B/instance', CVarA.new.cv + + CVarA.cv = 'CVarA@@cv' + assert_equal('CVarA@@cv', CVarB.cvB) + assert_equal('CVarA@@cv', CVarB.cvB2) + assert_equal('CVarA@@cv', CVarB.new.cvB) + CVarB.cvB = 'B/singleton' + assert_equal 'B/singleton', CVarB.cvB + assert_equal 'B/singleton', CVarB.cvB2 + assert_equal 'B/singleton', CVarB.new.cvB + assert_equal 'B/singleton', CVarA.cv + assert_equal 'B/singleton', CVarA.cv2 + assert_equal 'B/singleton', CVarA.new.cv + CVarB.new.cvB = 'B/instance' + assert_equal 'B/instance', CVarB.cvB + assert_equal 'B/instance', CVarB.cvB2 + assert_equal 'B/instance', CVarB.new.cvB + assert_equal 'B/instance', CVarA.cv + assert_equal 'B/instance', CVarA.cv2 + assert_equal 'B/instance', CVarA.new.cv + + CVarA.cv = 'CVarA@@cv' + CVarB.cvB = 'CVarB@@cv' + end + + class OP + attr_reader :x + attr_accessor :foo + def x=(x) + @x = x + :Bug1996 + end + Bug1996 = '[ruby-dev:39163], [ruby-core:25143]' + def [](i) + @x + end + def []=(i, x) + @x = x + :Bug2050 + end + Bug2050 = '[ruby-core:25387]' + end + + def test_opassign2_1 + x = nil + assert_equal 1, x ||= 1 + assert_equal 1, x + assert_equal 2, x &&= 2 + assert_equal 2, x + assert_equal 2, x ||= 3 + assert_equal 2, x + assert_equal 4, x &&= 4 + assert_equal 4, x + assert_equal 5, x += 1 + assert_equal 5, x + assert_equal 4, x -= 1 + assert_equal 4, x + end + + def test_opassign2_2 + y = OP.new + y.x = nil + assert_equal 1, y.x ||= 1, OP::Bug1996 + assert_equal 1, y.x + assert_equal 2, y.x &&= 2, OP::Bug1996 + assert_equal 2, y.x + assert_equal 2, y.x ||= 3 + assert_equal 2, y.x + assert_equal 4, y.x &&= 4, OP::Bug1996 + assert_equal 4, y.x + assert_equal 5, y.x += 1, OP::Bug1996 + assert_equal 5, y.x + assert_equal 4, y.x -= 1, OP::Bug1996 + assert_equal 4, y.x + end + + def test_opassign2_3 + z = OP.new + z.x = OP.new + z.x.x = nil + assert_equal 1, z.x.x ||= 1, OP::Bug1996 + assert_equal 1, z.x.x + assert_equal 2, z.x.x &&= 2, OP::Bug1996 + assert_equal 2, z.x.x + assert_equal 2, z.x.x ||= 3 + assert_equal 2, z.x.x + assert_equal 4, z.x.x &&= 4, OP::Bug1996 + assert_equal 4, z.x.x + assert_equal 5, z.x.x += 1, OP::Bug1996 + assert_equal 5, z.x.x + assert_equal 4, z.x.x -= 1, OP::Bug1996 + assert_equal 4, z.x.x + end + + def test_opassign1_1 + a = [] + a[0] = nil + assert_equal 1, a[0] ||= 1 + assert_equal 1, a[0] + assert_equal 2, a[0] &&= 2 + assert_equal 2, a[0] + assert_equal 2, a[0] ||= 3 + assert_equal 2, a[0] + assert_equal 4, a[0] &&= 4 + assert_equal 4, a[0] + assert_equal 5, a[0] += 1 + assert_equal 5, a[0] + assert_equal 4, a[0] -= 1 + assert_equal 4, a[0] + end + + def test_opassign1_2 + x = OP.new + x[0] = nil + assert_equal 1, x[0] ||= 1, OP::Bug2050 + assert_equal 1, x[0] + assert_equal 2, x[0] &&= 2, OP::Bug2050 + assert_equal 2, x[0] + assert_equal 2, x[0] ||= 3, OP::Bug2050 + assert_equal 2, x[0] + assert_equal 4, x[0] &&= 4, OP::Bug2050 + assert_equal 4, x[0] + assert_equal 5, x[0] += 1, OP::Bug2050 + assert_equal 5, x[0] + assert_equal 4, x[0] -= 1, OP::Bug2050 + 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 $~ + assert_nil $` + assert_nil $& + assert_nil $' + assert_nil $+ + assert_nil $1 + assert_nil $2 + assert_nil $3 + assert_nil $4 + assert_nil $5 + assert_nil $6 + assert_nil $7 + assert_nil $8 + assert_nil $9 + + /(a)(b)(c)(d)(e)(f)(g)(h)(i)/ =~ 'xabcdefghiy' + assert_not_nil $~ + assert_instance_of MatchData, $~ + assert_equal 'abcdefghi', $~[0] + assert_equal 'a', $~[1] + assert_equal 'b', $~[2] + assert_equal 'c', $~[3] + assert_equal 'd', $~[4] + assert_equal 'e', $~[5] + assert_equal 'f', $~[6] + assert_equal 'g', $~[7] + assert_equal 'h', $~[8] + assert_equal 'i', $~[9] + assert_equal 'x', $` + assert_equal 'abcdefghi', $& + assert_equal "y", $' + assert_equal 'i', $+ + assert_equal 'a', $1 + assert_equal 'b', $2 + assert_equal 'c', $3 + assert_equal 'd', $4 + assert_equal 'e', $5 + assert_equal 'f', $6 + assert_equal 'g', $7 + assert_equal 'h', $8 + assert_equal 'i', $9 + + /re/ =~ 'not match' + assert_nil $~ + assert_nil $` + assert_nil $& + assert_nil $' + assert_nil $+ + assert_nil $1 + assert_nil $2 + assert_nil $3 + assert_nil $4 + assert_nil $5 + assert_nil $6 + assert_nil $7 + assert_nil $8 + assert_nil $9 + 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 new file mode 100644 index 0000000000..3706efab52 --- /dev/null +++ b/test/ruby/test_beginendblock.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: false +require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} + +class TestBeginEndBlock < Test::Unit::TestCase + DIR = File.dirname(File.expand_path(__FILE__)) + + def test_beginendblock + target = File.join(DIR, 'beginmainend.rb') + 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_endblock_variable + assert_in_out_err(["-n", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(:ok)) + assert_in_out_err(["-p", "-ea = :ok", "-eEND{p a}"], "foo\n", %w(foo :ok)) + end + + def test_begininmethod + assert_raise_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do + eval("def foo; BEGIN {}; end") + end + + 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_with_message(SyntaxError, /BEGIN is permitted only at toplevel/) do + eval("class TestBeginEndBlock; BEGIN {}; end") + end + end + + def test_endblockwarn + 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;'}", [], ['test.rb:1: warning: END in method; use at_exit']) + begin; + eval <<-EOE, nil, "test.rb", 0 + def end2 + END {} + end + EOE + end; + end + + def test_raise_in_at_exit + 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_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_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 status.exitstatus + assert_equal Signal.list["INT"], status.termsig + end + + def test_endblock_raise + 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 + omit 'requires callcc support' unless respond_to?(:callcc) + + 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 new file mode 100644 index 0000000000..c366f794b2 --- /dev/null +++ b/test/ruby/test_bignum.rb @@ -0,0 +1,877 @@ +# 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 + + f = BIGNUM_MIN + n = 0 + until f == 0 + f >>= 1 + n += 1 + end + BIGNUM_MIN_BITS = n + + T_ZERO = Bug::Integer.to_bignum(0) + T_ONE = Bug::Integer.to_bignum(1) + T_MONE = Bug::Integer.to_bignum(-1) + T31 = Bug::Integer.to_bignum(2**31) # 2147483648 + T31P = Bug::Integer.to_bignum(T31 - 1) # 2147483647 + T32 = Bug::Integer.to_bignum(2**32) # 4294967296 + T32P = Bug::Integer.to_bignum(T32 - 1) # 4294967295 + T64 = Bug::Integer.to_bignum(2**64) # 18446744073709551616 + T64P = Bug::Integer.to_bignum(T64 - 1) # 18446744073709551615 + T128 = Bug::Integer.to_bignum(2**128) + T128P = Bug::Integer.to_bignum(T128 - 1) + T1024 = Bug::Integer.to_bignum(2**1024) + T1024P = Bug::Integer.to_bignum(T1024 - 1) + + def setup + @verbose = $VERBOSE + @fmax = Float::MAX.to_i + @fmax2 = @fmax * 2 + @big = (1 << BIGNUM_MIN_BITS) - 1 + end + + def teardown + $VERBOSE = @verbose + end + + def fact(n) + return 1 if n == 0 + f = 1 + while n>0 + f *= n + n -= 1 + end + 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_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) + + 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) + end + + n1 = 1 << 1000 + for i in 1000..1014 + assert_equal(n1, 1 << i) + n1 *= 2 + end + + n2=n1 + for i in 1..10 + n1 = n1 / 2 + n2 = n2 >> 1 + assert_equal(n1, n2) + end + + for i in 4000..4096 + n1 = 1 << i; + assert_equal(n1-1, (n1**2-1) / (n1+1)) + end + end + + def test_calc + b = 10**80 + a = b * 9 + 7 + assert_equal(7, a.modulo(b)) + assert_equal(-b + 7, a.modulo(-b)) + assert_equal(b + -7, (-a).modulo(b)) + assert_equal(-7, (-a).modulo(-b)) + assert_equal(7, a.remainder(b)) + assert_equal(7, a.remainder(-b)) + assert_equal(-7, (-a).remainder(b)) + assert_equal(-7, (-a).remainder(-b)) + + assert_equal(10000000000000000000100000000000000000000, 10**40+10**20) + assert_equal(100000000000000000000, 10**40/10**20) + + a = 677330545177305025495135714080 + b = 14269972710765292560 + assert_equal(0, a % b) + assert_equal(0, -a % b) + end + + def shift_test(a) + b = a / (2 ** 32) + c = a >> 32 + assert_equal(b, c) + + b = a * (2 ** 32) + c = a << 32 + assert_equal(b, c) + end + + def test_shift + shift_test(-4518325415524767873) + shift_test(-0xfffffffffffffffff) + end + + def test_to_s + assert_equal("fvvvvvvvvvvvv" ,18446744073709551615.to_s(32), "[ruby-core:10686]") + assert_equal("g000000000000" ,18446744073709551616.to_s(32), "[ruby-core:10686]") + assert_equal("3w5e11264sgsf" ,18446744073709551615.to_s(36), "[ruby-core:10686]") + assert_equal("3w5e11264sgsg" ,18446744073709551616.to_s(36), "[ruby-core:10686]") + assert_equal("nd075ib45k86f" ,18446744073709551615.to_s(31), "[ruby-core:10686]") + 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 + + def test_big_2comp + assert_equal("-4294967296", (~T32P).to_s) + assert_equal("..f00000000", "%x" % -T32) + end + + def test_int2inum + assert_equal([T31P], [T31P].pack("I").unpack("I")) + assert_equal([T31P], [T31P].pack("i").unpack("i")) + end + + def test_quad_pack + assert_equal([ 1], [ 1].pack("q").unpack("q")) + assert_equal([- 1], [- 1].pack("q").unpack("q")) + assert_equal([ T31P], [ T31P].pack("q").unpack("q")) + assert_equal([-T31P], [-T31P].pack("q").unpack("q")) + assert_equal([ T64P], [ T64P].pack("Q").unpack("Q")) + assert_equal([ 0], [ T64 ].pack("Q").unpack("Q")) + end + + def test_str_to_inum + assert_equal(1, " +1".to_i) + assert_equal(-1, " -1".to_i) + assert_equal(0, "++1".to_i) + assert_equal(73, "111".oct) + assert_equal(273, "0x111".oct) + assert_equal(7, "0b111".oct) + assert_equal(73, "0o111".oct) + assert_equal(111, "0d111".oct) + assert_equal(73, "0111".oct) + assert_equal(111, Integer("111")) + assert_equal(13, "111".to_i(3)) + assert_raise(ArgumentError) { "111".to_i(37) } + assert_equal(1333, "111".to_i(36)) + assert_equal(1057, "111".to_i(32)) + assert_equal(0, "00a".to_i) + assert_equal(1, Integer("1 ")) + assert_raise(ArgumentError) { Integer("1_") } + assert_raise(ArgumentError) { Integer("1__") } + assert_raise(ArgumentError) { Integer("1_0 x") } + assert_equal(T31P, "1111111111111111111111111111111".to_i(2)) + assert_equal(0_2, '0_2'.to_i) + assert_equal(00_2, '00_2'.to_i) + assert_equal(00_02, '00_02'.to_i) + end + + def test_very_big_str_to_inum + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + digits = [["3", 700], ["0", 2700], ["1", 1], ["0", 26599]] + num = digits.inject(+"") {|s,(c,n)|s << c*n}.to_i + assert_equal digits.sum {|c,n|n}, num.to_s.size + end; + end + + def test_to_s2 + assert_raise(ArgumentError) { T31P.to_s(37) } + assert_equal("9" * 32768, (10**32768-1).to_s) + assert_raise(RangeError) { Process.wait(1, T64P) } + assert_equal("0", T_ZERO.to_s) + assert_equal("1", T_ONE.to_s) + end + + def test_to_f + assert_nothing_raised { T31P.to_f.to_i } + assert_raise(FloatDomainError) { + assert_warning(/out of Float range/) {(1024**1024).to_f}.to_i + } + assert_equal(1, assert_warning(/out of Float range/) {(2**50000).to_f}.infinite?) + assert_equal(-1, assert_warning(/out of Float range/) {(-(2**50000)).to_f}.infinite?) + end + + def test_cmp + 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_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_send([T31P, :eql?, T31P]) + end + + def test_convert + assert_equal([255], [T_MONE].pack("C").unpack("C")) + assert_equal([0], [T32].pack("C").unpack("C")) + assert_raise(RangeError) { 0.to_s(T32) } + end + + def test_sub + assert_equal(-T31, T32 - (T32 + T31)) + x = 2**100 + assert_equal(1, (x+2) - (x+1)) + assert_equal(-1, (x+1) - (x+2)) + assert_equal(0, (2**100) - (2.0**100)) + o = Object.new + def o.coerce(x); [x, 2**100+2]; end + assert_equal(-1, (2**100+1) - o) + assert_equal(-1, T_ONE - 2) + end + + def test_plus + assert_equal(T32.to_f, T32P + 1.0) + assert_raise(TypeError) { T32 + "foo" } + assert_equal(1267651809154049016125877911552, (2**100) + (2**80)) + assert_equal(1267651809154049016125877911552, (2**80) + (2**100)) + assert_equal(2**101, (2**100) + (2.0**100)) + o = Object.new + def o.coerce(x); [x, 2**80]; end + assert_equal(1267651809154049016125877911552, (2**100) + o) + end + + def test_minus + assert_equal(T32P.to_f, T32 - 1.0) + assert_raise(TypeError) { T32 - "foo" } + end + + def test_mul + assert_equal(T32.to_f, T32 * 1.0) + assert_raise(TypeError) { T32 * "foo" } + o = Object.new + def o.coerce(x); [x, 2**100]; end + 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, assert_warning(/out of Float range/) {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 + assert_equal(715827882, 1073741824.div(Rational(3,2)), ' [ruby-dev:34066]') + end + + def test_modulo + assert_raise(TypeError) { T32 % "foo" } + end + + def test_remainder + assert_equal(0, T32.remainder(1)) + assert_raise(TypeError) { T32.remainder("foo") } + end + + def test_divmod + assert_equal([T32, 0], T32.divmod(1)) + assert_equal([2, 0], T32.divmod(T31)) + assert_raise(TypeError) { T32.divmod("foo") } + 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)) + + assert_raise(TypeError) { T32.quo("foo") } + + assert_equal(1024**1024, (1024**1024).quo(1)) + 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_send([(1024**1024*2).quo(nan), :nan?]) + end + + def test_pow + assert_equal(1.0, T32 ** 0.0) + assert_equal(1.0 / T32, T32 ** -1) + assert_raise(ArgumentError) { T32 ** T32 } + assert_raise(ArgumentError) { T32 ** (2**30-1) } + + ### 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 + assert_equal(0, T32 & 1) + assert_equal(-T32, (-T32) & (-T31)) + assert_equal(0, T32 & T64) + end + + def test_or + assert_equal(T32 + 1, T32 | 1) + 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 + assert_equal(T32 + 1, T32 ^ 1) + assert_equal(T32 + T31, T32 ^ T31) + assert_equal(T31, (-T32) ^ (-T31)) + assert_equal(T64 + T32, T32 ^ T64) + end + + class DummyNumeric < Numeric + def to_int + 1 + end + end + + def test_and_with_float + assert_raise(TypeError) { + assert_warning(/out of Float range/) {T1024 & 1.5} + } + end + + def test_and_with_rational + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 & Rational(3, 2)} + } + end + + def test_and_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 & DummyNumeric.new} + } + end + + def test_or_with_float + assert_raise(TypeError) { + assert_warn(/out of Float range/) {T1024 | 1.5} + } + end + + def test_or_with_rational + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 | Rational(3, 2)} + } + end + + def test_or_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 | DummyNumeric.new} + } + end + + def test_xor_with_float + assert_raise(TypeError) { + assert_warn(/out of Float range/) {T1024 ^ 1.5} + } + end + + def test_xor_with_rational + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 ^ Rational(3, 2)} + } + end + + def test_xor_with_nonintegral_numeric + assert_raise(TypeError, "#1792") { + assert_warn(/out of Float range/) {T1024 ^ DummyNumeric.new} + } + end + + def test_shift2 + 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, 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_aref2 + x = (0x123456789abcdef << (BIGNUM_MIN_BITS + 32)) | 0x12345678 + assert_equal(x, x[0, x.bit_length]) + assert_equal(x >> 10, x[10, x.bit_length]) + assert_equal(0x45678, x[0, 20]) + assert_equal(0x6780, x[-4, 16]) + assert_equal(0x123456, x[x.bit_length - 21, 40]) + assert_equal(0x6789ab, x[x.bit_length - 41, 24]) + assert_equal(0, x[-20, 10]) + assert_equal(0, x[x.bit_length + 10, 10]) + + assert_equal(0, x[5, 0]) + assert_equal(0, (-x)[5, 0]) + + assert_equal(x >> 5, x[5, -1]) + assert_equal(x << 5, x[-5, -1]) + assert_equal((-x) >> 5, (-x)[5, -1]) + assert_equal((-x) << 5, (-x)[-5, -1]) + + assert_equal(x << 5, x[-5, FIXNUM_MAX]) + assert_equal(x >> 5, x[5, FIXNUM_MAX]) + assert_equal(0, x[FIXNUM_MIN, 100]) + assert_equal(0, (-x)[FIXNUM_MIN, 100]) + + y = (x << 160) | 0x1234_0000_0000_0000_1234_0000_0000_0000 + assert_equal(0xffffedcc00, (-y)[40, 40]) + assert_equal(0xfffffffedc, (-y)[52, 40]) + assert_equal(0xffffedcbff, (-y)[104, 40]) + assert_equal(0xfffff6e5d4, (-y)[y.bit_length - 20, 40]) + assert_equal(0, (-y)[-20, 10]) + assert_equal(0xfff, (-y)[y.bit_length + 10, 12]) + + z = (1 << (BIGNUM_MIN_BITS * 2)) - 1 + assert_equal(0x400, (-z)[-10, 20]) + assert_equal(1, (-z)[0, 20]) + assert_equal(0, (-z)[10, 20]) + assert_equal(1, (-z)[0, z.bit_length]) + assert_equal(0, (-z)[z.bit_length - 10, 10]) + assert_equal(0x400, (-z)[z.bit_length - 10, 11]) + assert_equal(0xfff, (-z)[z.bit_length, 12]) + assert_equal(0xfff00, (-z)[z.bit_length - 8, 20]) + end + + def test_hash + assert_nothing_raised { T31P.hash } + end + + 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 + assert_equal(T31P, (-T31P).abs) + end + + def test_size + assert_kind_of(Integer, T31P.size) + end + + def test_odd + assert_equal(true, (BIGNUM_MIN+1).odd?) + assert_equal(false, BIGNUM_MIN.odd?) + end + + def test_even + assert_equal(false, (BIGNUM_MIN+1).even?) + assert_equal(true, BIGNUM_MIN.even?) + end + + def test_interrupt_during_to_s + if defined?(Integer::GMP_VERSION) + return # GMP doesn't support interrupt during an operation. + end + time = Time.now + end_flag = false + num = (65536 ** 65536) + q = Thread::Queue.new + thread = Thread.new do + assert_raise(RuntimeError) { + q << true + num.to_s + end_flag = true + } + end + q.pop # sync + thread.raise + thread.join + time = Time.now - time + omit "too fast cpu" if end_flag + assert_operator(time, :<, 10) + end + + 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 + omit "cannot create suitable test case" + ensure + Signal.trap(:INT, oldtrap) if oldtrap + end + end + + def test_too_big_to_s + if Bug::Integer.fixnum?(big = 2**31-1) + return + end + assert_raise_with_message(RangeError, /too big to convert/) {(1 << big).to_s} + end + + def test_fix_fdiv + assert_not_equal(0, 1.fdiv(@fmax2)) + assert_in_delta(0.5, 1.fdiv(@fmax2) * @fmax, 0.01) + end + + def test_big_fdiv + assert_equal(1, @big.fdiv(@big)) + assert_not_equal(0, @big.fdiv(@fmax2)) + assert_not_equal(0, @fmax2.fdiv(@big)) + assert_not_equal(0, @fmax2.fdiv(@fmax2)) + assert_in_delta(0.5, @fmax.fdiv(@fmax2), 0.01) + assert_in_delta(1.0, @fmax2.fdiv(@fmax2), 0.01) + end + + def test_float_fdiv + b = 1E+300.to_i + assert_equal(b, (b ** 2).fdiv(b)) + assert_send([@big.fdiv(0.0 / 0.0), :nan?]) + assert_in_delta(1E+300, (10**500).fdiv(1E+200), 1E+285) + end + + def test_obj_fdiv + 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], Bug::Integer.to_bignum(1234567890).digits(100)) + assert_equal([7215, 2413, 6242], T1024P.digits(10_000).first(3)) + assert_equal([11], 11.digits(T1024P)) + assert_equal([T1024P - 1, 1], (T1024P + T1024P - 1).digits(T1024P)) + bug21680 = '[ruby-core:123769] [Bug #21680]' + assert_equal([0] * 64 + [1], (2**512).digits(256), bug21680) + assert_equal([0] * 128 + [1], (123**128).digits(123), bug21680) + 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) { Bug::Integer.to_bignum(T1024P).digits(0) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-1) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(0)) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(Bug::Integer.to_bignum(1)) } + assert_raise(ArgumentError) { Bug::Integer.to_bignum(T1024P).digits(-T1024P) } + assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(0)) } + assert_raise(ArgumentError) { 10.digits(Bug::Integer.to_bignum(1)) } + end + + def test_digits_for_non_integral_base_numbers + 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 + + def test_gmp_version + if RbConfig::CONFIG.fetch('configure_args').include?("'--with-gmp'") + assert_kind_of(String, Integer::GMP_VERSION) + end + end if ENV['GITHUB_WORKFLOW'] == 'Compilations' +end +end diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb new file mode 100644 index 0000000000..bb98a2efbe --- /dev/null +++ b/test/ruby/test_box.rb @@ -0,0 +1,874 @@ +# frozen_string_literal: true + +require 'test/unit' +require 'rbconfig' + +class TestBox < Test::Unit::TestCase + EXPERIMENTAL_WARNING_LINE_PATTERNS = [ + /#{RbConfig::CONFIG["ruby_install_name"] || "ruby"}(\.exe)?: warning: Ruby::Box is experimental, and the behavior may change in the future!/, + %r{See https://docs.ruby-lang.org/en/(master|\d\.\d)/Ruby/Box.html for known issues, etc.} + ] + ENV_ENABLE_BOX = {'RUBY_BOX' => '1', 'TEST_DIR' => __dir__} + + def setup + @box = nil + @dir = __dir__ + end + + def teardown + @box = nil + end + + def setup_box + pend unless Ruby::Box.enabled? + @box = Ruby::Box.new + end + + def test_box_availability_in_default + assert_separately(['RUBY_BOX'=>nil], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert_nil ENV['RUBY_BOX'] + assert_not_predicate Ruby::Box, :enabled? + end; + end + + def test_box_availability_when_enabled + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert_equal '1', ENV['RUBY_BOX'] + assert_predicate Ruby::Box, :enabled? + end; + end + + def test_current_box_in_main + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + assert_equal Ruby::Box.main, Ruby::Box.current + assert_predicate Ruby::Box.main, :main? + end; + end + + def test_require_rb_separately + setup_box + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + + @box.require(File.join(__dir__, 'box', 'a.1_1_0')) + + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + end + + def test_require_relative_rb_separately + setup_box + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + + @box.require_relative('box/a.1_1_0') + + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + end + + def test_load_separately + setup_box + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + + @box.load(File.join(__dir__, 'box', 'a.1_1_0.rb')) + + assert_not_nil @box::BOX_A + assert_not_nil @box::BOX_B + assert_equal "1.1.0", @box::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX_B.yay + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + end + + def test_box_in_box + setup_box + + assert_raise(NameError) { BOX1 } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + + @box.require_relative('box/box') + + assert_not_nil @box::BOX1 + assert_not_nil @box::BOX1::BOX_A + assert_not_nil @box::BOX1::BOX_B + assert_equal "1.1.0", @box::BOX1::BOX_A::VERSION + assert_equal "yay 1.1.0", @box::BOX1::BOX_A.new.yay + assert_equal "1.1.0", @box::BOX1::BOX_B::VERSION + assert_equal "yay_b1", @box::BOX1::BOX_B.yay + + assert_raise(NameError) { BOX1 } + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + end + + def test_require_rb_2versiobox + setup_box + + assert_raise(NameError) { BOX_A } + + @box.require(File.join(__dir__, 'box', 'a.1_2_0')) + assert_equal "1.2.0", @box::BOX_A::VERSION + assert_equal "yay 1.2.0", @box::BOX_A.new.yay + + n2 = Ruby::Box.new + n2.require(File.join(__dir__, 'box', 'a.1_1_0')) + assert_equal "1.1.0", n2::BOX_A::VERSION + assert_equal "yay 1.1.0", n2::BOX_A.new.yay + + # recheck @box is not affected by the following require + assert_equal "1.2.0", @box::BOX_A::VERSION + assert_equal "yay 1.2.0", @box::BOX_A.new.yay + + assert_raise(NameError) { BOX_A } + end + + def test_raising_errors_in_require + setup_box + + assert_raise(RuntimeError, "Yay!") { @box.require(File.join(__dir__, 'box', 'raise')) } + assert_include Ruby::Box.current.inspect, "main" + end + + def test_autoload_in_box + setup_box + + assert_raise(NameError) { BOX_A } + + @box.require_relative('box/autoloading') + # autoloaded A is visible from global + assert_equal '1.1.0', @box::BOX_A::VERSION + + assert_raise(NameError) { BOX_A } + + # autoload trigger BOX_B::BAR is valid even from global + assert_equal 'bar_b1', @box::BOX_B::BAR + + assert_raise(NameError) { BOX_A } + assert_raise(NameError) { BOX_B } + end + + def test_continuous_top_level_method_in_a_box + setup_box + + @box.require_relative('box/define_toplevel') + @box.require_relative('box/call_toplevel') + + assert_raise(NameError) { foo } + end + + def test_top_level_methods_in_box + pend # TODO: fix loading/current box detection + setup_box + @box.require_relative('box/top_level') + assert_equal "yay!", @box::Foo.foo + assert_raise(NameError) { yaaay } + assert_equal "foo", @box::Bar.bar + assert_raise_with_message(RuntimeError, "boooo") { @box::Baz.baz } + end + + def test_proc_defined_in_box_refers_module_in_box + setup_box + + # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box1 = Ruby::Box.new + box1.require("#{here}/box/proc_callee") + proc_v = box1::Foo.callee + assert_raise(NameError) { Target } + assert box1::Target + assert_equal "fooooo", proc_v.call # refers Target in the box box1 + box1.require("#{here}/box/proc_caller") + assert_equal "fooooo", box1::Bar.caller(proc_v) + + box2 = Ruby::Box.new + box2.require("#{here}/box/proc_caller") + assert_raise(NameError) { box2::Target } + assert_equal "fooooo", box2::Bar.caller(proc_v) # refers Target in the box box1 + end; + end + + def test_proc_defined_globally_refers_global_module + setup_box + + # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require("#{here}/box/proc_callee") + def Target.foo + "yay" + end + proc_v = Foo.callee + assert Target + assert_equal "yay", proc_v.call # refers global Foo + box1 = Ruby::Box.new + box1.require("#{here}/box/proc_caller") + assert_equal "yay", box1::Bar.caller(proc_v) + + box2 = Ruby::Box.new + box2.require("#{here}/box/proc_callee") + box2.require("#{here}/box/proc_caller") + assert_equal "fooooo", box2::Foo.callee.call + assert_equal "yay", box2::Bar.caller(proc_v) # should refer the global Target, not Foo in box2 + end; + end + + def test_instance_variable + setup_box + + @box.require_relative('box/instance_variables') + + assert_equal [], String.instance_variables + assert_equal [:@str_ivar1, :@str_ivar2], @box::StringDelegatorObj.instance_variables + assert_equal 111, @box::StringDelegatorObj.str_ivar1 + assert_equal 222, @box::StringDelegatorObj.str_ivar2 + assert_equal 222, @box::StringDelegatorObj.instance_variable_get(:@str_ivar2) + + @box::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333) + assert_equal 333, @box::StringDelegatorObj.instance_variable_get(:@str_ivar3) + @box::StringDelegatorObj.remove_instance_variable(:@str_ivar1) + assert_nil @box::StringDelegatorObj.str_ivar1 + assert_equal [:@str_ivar2, :@str_ivar3], @box::StringDelegatorObj.instance_variables + + assert_equal [], String.instance_variables + end + + def test_methods_added_in_box_are_invisible_globally + setup_box + + @box.require_relative('box/string_ext') + + assert_equal "yay", @box::Bar.yay + + assert_raise(NoMethodError){ String.new.yay } + end + + def test_continuous_method_definitions_in_a_box + setup_box + + @box.require_relative('box/string_ext') + assert_equal "yay", @box::Bar.yay + + @box.require_relative('box/string_ext_caller') + assert_equal "yay", @box::Foo.yay + + @box.require_relative('box/string_ext_calling') + end + + def test_methods_added_in_box_later_than_caller_code + setup_box + + @box.require_relative('box/string_ext_caller') + @box.require_relative('box/string_ext') + + assert_equal "yay", @box::Bar.yay + assert_equal "yay", @box::Foo.yay + end + + def test_method_added_in_box_are_available_on_eval + setup_box + + @box.require_relative('box/string_ext') + @box.require_relative('box/string_ext_eval_caller') + + assert_equal "yay", @box::Baz.yay + end + + def test_method_added_in_box_are_available_on_eval_with_binding + setup_box + + @box.require_relative('box/string_ext') + @box.require_relative('box/string_ext_eval_caller') + + assert_equal "yay, yay!", @box::Baz.yay_with_binding + end + + def test_methods_and_constants_added_by_include + setup_box + + @box.require_relative('box/open_class_with_include') + + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_foo + assert_equal "I'm saying foo 1", @box::OpenClassWithInclude.say_with_obj("wow") + + assert_raise(NameError) { String::FOO } + + assert_equal "foo 1", @box::OpenClassWithInclude.refer_foo + end +end + +module ProcLookupTestA + module B + VALUE = 111 + end +end + +class TestBox < Test::Unit::TestCase + def make_proc_from_block(&b) + b + end + + def test_proc_from_main_works_with_global_definitions + setup_box + + @box.require_relative('box/procs') + + proc_and_labels = [ + [Proc.new { String.new.yay }, "Proc.new"], + [proc { String.new.yay }, "proc{}"], + [lambda { String.new.yay }, "lambda{}"], + [->(){ String.new.yay }, "->(){}"], + [make_proc_from_block { String.new.yay }, "make_proc_from_block"], + [@box::ProcInBox.make_proc_from_block { String.new.yay }, "make_proc_from_block in @box"], + ] + + proc_and_labels.each do |str_pr| + pr, pr_label = str_pr + assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in main") { pr.call } + assert_raise(NoMethodError, "NoMethodError expected: #{pr_label}, called in @box") { @box::ProcInBox.call_proc(pr) } + end + + const_and_labels = [ + [Proc.new { ProcLookupTestA::B::VALUE }, "Proc.new"], + [proc { ProcLookupTestA::B::VALUE }, "proc{}"], + [lambda { ProcLookupTestA::B::VALUE }, "lambda{}"], + [->(){ ProcLookupTestA::B::VALUE }, "->(){}"], + [make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block"], + [@box::ProcInBox.make_proc_from_block { ProcLookupTestA::B::VALUE }, "make_proc_from_block in @box"], + ] + + const_and_labels.each do |const_pr| + pr, pr_label = const_pr + assert_equal 111, pr.call, "111 expected, #{pr_label} called in main" + assert_equal 111, @box::ProcInBox.call_proc(pr), "111 expected, #{pr_label} called in @box" + end + end + + def test_proc_from_box_works_with_definitions_in_box + setup_box + + @box.require_relative('box/procs') + + proc_types = [:proc_new, :proc_f, :lambda_f, :lambda_l, :block] + + proc_types.each do |proc_type| + assert_equal 222, @box::ProcInBox.make_const_proc(proc_type).call, "ProcLookupTestA::B::VALUE should be 222 in @box" + assert_equal "foo", @box::ProcInBox.make_str_const_proc(proc_type).call, "String::FOO should be \"foo\" in @box" + assert_equal "yay", @box::ProcInBox.make_str_proc(proc_type).call, "String#yay should be callable in @box" + # + # TODO: method calls not-in-methods nor procs can't handle the current box correctly. + # + # assert_equal "yay,foo,222", + # @box::ProcInBox.const_get(('CONST_' + proc_type.to_s.upcase).to_sym).call, + # "Proc assigned to constants should refer constants correctly in @box" + end + end + + def test_class_module_singleton_methods + setup_box + + @box.require_relative('box/singleton_methods') + + assert_equal "Good evening!", @box::SingletonMethods.string_greeing # def self.greeting + assert_equal 42, @box::SingletonMethods.integer_answer # class << self; def answer + assert_equal([], @box::SingletonMethods.array_blank) # def self.blank w/ instance methods + assert_equal({status: 200, body: 'OK'}, @box::SingletonMethods.hash_http_200) # class << self; def ... w/ instance methods + + assert_equal([4, 4], @box::SingletonMethods.array_instance_methods_return_size([1, 2, 3, 4])) + assert_equal([3, 3], @box::SingletonMethods.hash_instance_methods_return_size({a: 2, b: 4, c: 8})) + + assert_raise(NoMethodError) { String.greeting } + assert_raise(NoMethodError) { Integer.answer } + assert_raise(NoMethodError) { Array.blank } + assert_raise(NoMethodError) { Hash.http_200 } + end + + def test_add_constants_in_box + setup_box + + @box.require('envutil') + + String.const_set(:STR_CONST0, 999) + assert_equal 999, String::STR_CONST0 + assert_equal 999, String.const_get(:STR_CONST0) + + assert_raise(NameError) { String.const_get(:STR_CONST1) } + assert_raise(NameError) { String::STR_CONST2 } + assert_raise(NameError) { String::STR_CONST3 } + assert_raise(NameError) { Integer.const_get(:INT_CONST1) } + + EnvUtil.verbose_warning do + @box.require_relative('box/consts') + end + + assert_equal 999, String::STR_CONST0 + assert_raise(NameError) { String::STR_CONST1 } + assert_raise(NameError) { String::STR_CONST2 } + assert_raise(NameError) { Integer::INT_CONST1 } + + assert_not_nil @box::ForConsts.refer_all + + assert_equal 112, @box::ForConsts.refer1 + assert_equal 112, @box::ForConsts.get1 + assert_equal 112, @box::ForConsts::CONST1 + assert_equal 222, @box::ForConsts.refer2 + assert_equal 222, @box::ForConsts.get2 + assert_equal 222, @box::ForConsts::CONST2 + assert_equal 333, @box::ForConsts.refer3 + assert_equal 333, @box::ForConsts.get3 + assert_equal 333, @box::ForConsts::CONST3 + + @box::EnvUtil.suppress_warning do + @box::ForConsts.const_set(:CONST3, 334) + end + assert_equal 334, @box::ForConsts::CONST3 + assert_equal 334, @box::ForConsts.refer3 + assert_equal 334, @box::ForConsts.get3 + + assert_equal 10, @box::ForConsts.refer_top_const + + # use Proxy object to use usual methods instead of singleton methods + proxy = @box::ForConsts::Proxy.new + + assert_raise(NameError){ proxy.call_str_refer0 } + assert_raise(NameError){ proxy.call_str_get0 } + + proxy.call_str_set0(30) + assert_equal 30, proxy.call_str_refer0 + assert_equal 30, proxy.call_str_get0 + assert_equal 999, String::STR_CONST0 + + proxy.call_str_remove0 + assert_raise(NameError){ proxy.call_str_refer0 } + assert_raise(NameError){ proxy.call_str_get0 } + + assert_equal 112, proxy.call_str_refer1 + assert_equal 112, proxy.call_str_get1 + assert_equal 223, proxy.call_str_refer2 + assert_equal 223, proxy.call_str_get2 + assert_equal 333, proxy.call_str_refer3 + assert_equal 333, proxy.call_str_get3 + + EnvUtil.suppress_warning do + proxy.call_str_set3 + end + assert_equal 334, proxy.call_str_refer3 + assert_equal 334, proxy.call_str_get3 + + assert_equal 1, proxy.refer_int_const1 + + assert_equal 999, String::STR_CONST0 + assert_raise(NameError) { String::STR_CONST1 } + assert_raise(NameError) { String::STR_CONST2 } + assert_raise(NameError) { String::STR_CONST3 } + assert_raise(NameError) { Integer::INT_CONST1 } + end + + def test_global_variables + default_l = $-0 + default_f = $, + + setup_box + + assert_equal "\n", $-0 # equal to $/, line splitter + assert_equal nil, $, # field splitter + + @box.require_relative('box/global_vars') + + # read first + assert_equal "\n", @box::LineSplitter.read + @box::LineSplitter.write("\r\n") + assert_equal "\r\n", @box::LineSplitter.read + assert_equal "\n", $-0 + + # write first + @box::FieldSplitter.write(",") + assert_equal ",", @box::FieldSplitter.read + assert_equal nil, $, + + # used only in box + assert_not_include? global_variables, :$used_only_in_box + @box::UniqueGvar.write(123) + assert_equal 123, @box::UniqueGvar.read + assert_nil $used_only_in_box + + # Kernel#global_variables returns the sum of all gvars. + global_gvars = global_variables.sort + assert_equal global_gvars, @box::UniqueGvar.gvars_in_box.sort + @box::UniqueGvar.write_only(456) + assert_equal (global_gvars + [:$write_only_var_in_box]).sort, @box::UniqueGvar.gvars_in_box.sort + assert_equal (global_gvars + [:$write_only_var_in_box]).sort, global_variables.sort + ensure + EnvUtil.suppress_warning do + $-0 = default_l + $, = default_f + end + end + + def test_load_path_and_loaded_features + setup_box + + assert_respond_to $LOAD_PATH, :resolve_feature_path + + @box.require_relative('box/load_path') + + assert_not_equal $LOAD_PATH, @box::LoadPathCheck::FIRST_LOAD_PATH + + assert @box::LoadPathCheck::FIRST_LOAD_PATH_RESPOND_TO_RESOLVE + + box_dir = File.join(__dir__, 'box') + # TODO: $LOADED_FEATURES in method calls should refer the current box in addition to the loading box. + # assert_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank1.rb') + # assert_not_include @box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb') + # assert_predicate @box::LoadPathCheck, :require_blank2 + # assert_include(@box::LoadPathCheck.current_loaded_features, File.join(box_dir, 'blank2.rb')) + + assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank1.rb') + assert_not_include $LOADED_FEATURES, File.join(box_dir, 'blank2.rb') + end + + def test_eval_basic + setup_box + + # Test basic evaluation + result = @box.eval("1 + 1") + assert_equal 2, result + + # Test string evaluation + result = @box.eval("'hello ' + 'world'") + assert_equal "hello world", result + end + + def test_eval_with_constants + setup_box + + # Define a constant in the box via eval + @box.eval("TEST_CONST = 42") + assert_equal 42, @box::TEST_CONST + + # Constant should not be visible in main box + assert_raise(NameError) { TEST_CONST } + end + + def test_eval_with_classes + setup_box + + # Define a class in the box via eval + @box.eval("class TestClass; def hello; 'from box'; end; end") + + # Class should be accessible in the box + instance = @box::TestClass.new + assert_equal "from box", instance.hello + + # Class should not be visible in main box + assert_raise(NameError) { TestClass } + end + + def test_eval_isolation + setup_box + + # Create another box + n2 = Ruby::Box.new + + # Define different constants in each box + @box.eval("ISOLATION_TEST = 'first'") + n2.eval("ISOLATION_TEST = 'second'") + + # Each box should have its own constant + assert_equal "first", @box::ISOLATION_TEST + assert_equal "second", n2::ISOLATION_TEST + + # Constants should not interfere with each other + assert_not_equal @box::ISOLATION_TEST, n2::ISOLATION_TEST + end + + def test_eval_with_variables + setup_box + + # Test local variable access (should work within the eval context) + result = @box.eval("x = 10; y = 20; x + y") + assert_equal 30, result + end + + def test_eval_error_handling + setup_box + + # Test syntax error + assert_raise(SyntaxError) { @box.eval("1 +") } + + # Test name error + assert_raise(NameError) { @box.eval("undefined_variable") } + + # Test that box is properly restored after error + begin + @box.eval("raise RuntimeError, 'test error'") + rescue RuntimeError + # Should be able to continue using the box + result = @box.eval("2 + 2") + assert_equal 4, result + end + end + + # Tests which run always (w/o RUBY_BOX=1 globally) + + def test_prelude_gems_and_loaded_features + assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + # No additional warnings except for experimental warnings + assert_equal 2, error.size + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0] + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1] + + assert_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + assert_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_prelude_gems_and_loaded_features_with_disable_gems + assert_in_out_err([ENV_ENABLE_BOX, "--disable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["before:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + + require "error_highlight" + + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/bundled_gems.rb") }&.first].join + puts ["after:", $LOADED_FEATURES.select{ it.end_with?("/error_highlight.rb") }&.first].join + end; + + assert_equal 2, error.size + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[0], error[0] + assert_match EXPERIMENTAL_WARNING_LINE_PATTERNS[1], error[1] + + refute_includes output.grep(/^before:/).join("\n"), '/bundled_gems.rb' + refute_includes output.grep(/^before:/).join("\n"), '/error_highlight.rb' + refute_includes output.grep(/^after:/).join("\n"), '/bundled_gems.rb' + assert_includes output.grep(/^after:/).join("\n"), '/error_highlight.rb' + end + end + + def test_root_and_main_methods + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + pend unless Ruby::Box.respond_to?(:root) and Ruby::Box.respond_to?(:main) # for RUBY_DEBUG > 0 + + assert_respond_to Ruby::Box.root, :root? + assert_respond_to Ruby::Box.main, :main? + + assert_predicate Ruby::Box.root, :root? + assert_predicate Ruby::Box.main, :main? + assert_equal Ruby::Box.main, Ruby::Box.current + + $a = 1 + $LOADED_FEATURES.push("/tmp/foobar") + + assert_equal 2, Ruby::Box.root.eval('$a = 2; $a') + assert_not_include Ruby::Box.root.eval('$LOADED_FEATURES.push("/tmp/barbaz"); $LOADED_FEATURES'), "/tmp/foobar" + assert_equal "FooClass", Ruby::Box.root.eval('class FooClass; end; Object.const_get(:FooClass).to_s') + + assert_equal 1, $a + assert_not_include $LOADED_FEATURES, "/tmp/barbaz" + assert_not_operator Object, :const_defined?, :FooClass + end; + end + + def test_basic_box_detections + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box = Ruby::Box.new + $gvar1 = 'bar' + code = <<~EOC + BOX1 = Ruby::Box.current + $gvar1 = 'foo' + + def toplevel = $gvar1 + + class Foo + BOX2 = Ruby::Box.current + BOX2_proc = ->(){ BOX2 } + BOX3_proc = ->(){ Ruby::Box.current } + + def box4 = Ruby::Box.current + def self.box5 = BOX2 + def self.box6 = Ruby::Box.current + def self.box6_proc = ->(){ Ruby::Box.current } + def self.box7 + res = [] + [1,2].chunk{ it.even? }.each do |bool, members| + res << Ruby::Box.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") + end + res + end + + def self.yield_block = yield + def self.call_block(&b) = b.call + + def self.gvar1 = $gvar1 + def self.call_toplevel = toplevel + end + FOO_NAME = Foo.name + + module Kernel + def foo_box = Ruby::Box.current + module_function :foo_box + end + + BOX_X = Foo.new.box4 + BOX_Y = foo_box + EOC + box.eval(code) + outer = Ruby::Box.current + assert_equal box, box::BOX1 # on TOP frame + assert_equal box, box::Foo::BOX2 # on CLASS frame + assert_equal box, box::Foo::BOX2_proc.call # proc -> a const on CLASS + assert_equal box, box::Foo::BOX3_proc.call # proc -> the current + assert_equal box, box::Foo.new.box4 # instance method -> the current + assert_equal box, box::Foo.box5 # singleton method -> a const on CLASS + assert_equal box, box::Foo.box6 # singleton method -> the current + assert_equal box, box::Foo.box6_proc.call # method returns a proc -> the current + + # a block after CFUNC/IFUNC in a method -> the current + assert_equal ["#{box.object_id}:false:1", "#{box.object_id}:true:2"], box::Foo.box7 + + assert_equal outer, box::Foo.yield_block{ Ruby::Box.current } # method yields + assert_equal outer, box::Foo.call_block{ Ruby::Box.current } # method calls a block + + assert_equal 'foo', box::Foo.gvar1 # method refers gvar + assert_equal 'bar', $gvar1 # gvar value out of the box + assert_equal 'foo', box::Foo.call_toplevel # toplevel method referring gvar + + assert_equal box, box::BOX_X # on TOP frame, referring a class in the current + assert_equal box, box::BOX_Y # on TOP frame, referring Kernel method defined by a CFUNC method + + assert_equal "Foo", box::FOO_NAME + assert_equal "Foo", box::Foo.name + end; + end + + def test_loading_extension_libs_in_main_box_1 + pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require "prism" + require "optparse" + require "date" + require "time" + require "delegate" + require "singleton" + require "pp" + require "fileutils" + require "tempfile" + require "tmpdir" + require "json" + require "psych" + require "yaml" + expected = 1 + assert_equal expected, 1 + end; + end + + def test_loading_extension_libs_in_main_box_2 + pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require "zlib" + require "open3" + require "ipaddr" + require "net/http" + require "openssl" + require "socket" + require "uri" + require "digest" + require "erb" + require "stringio" + require "monitor" + require "timeout" + require "securerandom" + expected = 1 + assert_equal expected, 1 + end; + end + + def test_mark_box_object_referred_only_from_binding + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box = Ruby::Box.new + box.eval('class Integer; def +(*)=42; end') + b = box.eval('binding') + box = nil # remove direct reference to the box + + assert_equal 42, b.eval('1+2') + + GC.stress = true + GC.start + + assert_equal 42, b.eval('1+2') + end; + end + + def test_loaded_extension_deleted_in_user_box + require 'tmpdir' + Dir.mktmpdir do |tmpdir| + env = ENV_ENABLE_BOX.merge({'TMPDIR'=>tmpdir}) + assert_ruby_status([env], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + require "json" + end; + assert_empty(Dir.children(tmpdir)) + end + end + + def test_root_box_iclasses_should_be_boxable + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + Ruby::Box.root.eval("class IMath; include Math; end") # (*) + module Math + def foo = :foo + end + # This test crashes here if iclasses (created at the line (*) is not boxable) + class IMath2; include Math; end + assert_equal :foo, IMath2.new.foo + assert_raise NoMethodError do + Ruby::Box.root.eval("IMath.new.foo") + end + end; + end +end diff --git a/test/ruby/test_call.rb b/test/ruby/test_call.rb new file mode 100644 index 0000000000..dd1936c4e2 --- /dev/null +++ b/test/ruby/test_call.rb @@ -0,0 +1,1489 @@ +# frozen_string_literal: false +require 'test/unit' +require '-test-/iter' + +class TestCall < Test::Unit::TestCase + # These dummy method definitions prevent warnings "the block passed to 'a'..." + def a(&) = nil + def b(&) = nil + def c(&) = nil + def d(&) = nil + def e(&) = nil + def f(&) = nil + def g(&) = nil + def h(&) = nil + def i(&) = nil + def j(&) = nil + def k(&) = nil + def l(&) = nil + def m(&) = nil + def n(&) = nil + def o(&) = nil + + def aaa(a, b=100, *rest, &) + res = [a, b] + res += rest if rest + return res + end + + def test_call + assert_raise(ArgumentError) {aaa()} + assert_raise(ArgumentError) {aaa} + + assert_equal([1, 100], aaa(1)) + assert_equal([1, 2], aaa(1, 2)) + 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&.z &&= 7 + assert_equal(7, o.z) + + o = nil + assert_nil(o&.x) + assert_nothing_raised(NoMethodError) {o&.x = raise} + assert_nothing_raised(NoMethodError) {o&.x = raise; nil} + assert_nothing_raised(NoMethodError) {o&.x *= raise} + assert_nothing_raised(NoMethodError) {o&.x *= raise; nil} + assert_nothing_raised(NoMethodError) {o&.x ||= raise} + assert_nothing_raised(NoMethodError) {o&.x ||= raise; nil} + assert_nothing_raised(NoMethodError) {o&.x &&= raise} + assert_nothing_raised(NoMethodError) {o&.x &&= raise; nil} + end + + def test_safe_call_evaluate_arguments_only_method_call_is_made + 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_frozen_splat_and_keywords + a = [1, 2].freeze + def self.f(*a); a end + assert_equal([1, 2, {kw: 3}], f(*a, kw: 3)) + end + + def test_forward_argument_init + o = Object.new + def o.simple_forward_argument_init(a=eval('b'), b=1) + [a, b] + end + + def o.complex_forward_argument_init(a=eval('b'), b=eval('kw'), kw: eval('kw2'), kw2: 3) + [a, b, kw, kw2] + end + + def o.keyword_forward_argument_init(a: eval('b'), b: eval('kw'), kw: eval('kw2'), kw2: 3) + [a, b, kw, kw2] + end + + assert_equal [nil, 1], o.simple_forward_argument_init + assert_equal [nil, nil, 3, 3], o.complex_forward_argument_init + assert_equal [nil, nil, 3, 3], o.keyword_forward_argument_init + end + + def test_call_bmethod_proc + pr = proc{|sym| sym} + define_singleton_method(:a, &pr) + ary = [10] + assert_equal(10, a(*ary)) + end + + def test_call_bmethod_proc_restarg + pr = proc{|*sym| sym} + define_singleton_method(:a, &pr) + ary = [10] + assert_equal([10], a(*ary)) + assert_equal([10], a(10)) + end + + def test_call_op_asgn_keywords + h = Class.new do + attr_reader :get, :set + def v; yield; [*@get, *@set] end + def [](*a, **b, &c) @get = [a, b, c]; @set = []; 3 end + def []=(*a, **b, &c) @set = [a, b, c] end + end.new + + a = [] + kw = {} + b = lambda{} + + # Prevent "assigned but unused variable" warnings + _ = [h, a, kw, b] + + message = /keyword arg given in index assignment/ + + # +=, without block, non-popped + assert_syntax_error(%q{h[**kw] += 1}, message) + assert_syntax_error(%q{h[0, **kw] += 1}, message) + assert_syntax_error(%q{h[0, *a, **kw] += 1}, message) + assert_syntax_error(%q{h[kw: 5] += 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] += 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1}, message) + + # +=, with block, non-popped + assert_syntax_error(%q{h[**kw, &b] += 1}, message) + assert_syntax_error(%q{h[0, **kw, &b] += 1}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] += 1}, message) + assert_syntax_error(%q{h[kw: 5, &b] += 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1}, message) + + # +=, without block, popped + assert_syntax_error(%q{h[**kw] += 1; nil}, message) + assert_syntax_error(%q{h[0, **kw] += 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] += 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] += 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] += 1; nil}, message) + + # +=, with block, popped + assert_syntax_error(%q{h[**kw, &b] += 1; nil}, message) + assert_syntax_error(%q{h[0, **kw, &b] += 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, &b] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] += 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] += 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] += 1; nil}, message) + + # &&=, without block, non-popped + assert_syntax_error(%q{h[**kw] &&= 1}, message) + assert_syntax_error(%q{h[0, **kw] &&= 1}, message) + assert_syntax_error(%q{h[0, *a, **kw] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1}, message) + + # &&=, with block, non-popped + assert_syntax_error(%q{h[**kw, &b] &&= 1}, message) + assert_syntax_error(%q{h[0, **kw, &b] &&= 1}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5, &b] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1}, message) + + # &&=, without block, popped + assert_syntax_error(%q{h[**kw] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, **kw] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] &&= 1; nil}, message) + + # &&=, with block, popped + assert_syntax_error(%q{h[**kw, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, **kw, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] &&= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] &&= 1; nil}, message) + + # ||=, without block, non-popped + assert_syntax_error(%q{h[**kw] ||= 1}, message) + assert_syntax_error(%q{h[0, **kw] ||= 1}, message) + assert_syntax_error(%q{h[0, *a, **kw] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1}, message) + + # ||=, with block, non-popped + assert_syntax_error(%q{h[**kw, &b] ||= 1}, message) + assert_syntax_error(%q{h[0, **kw, &b] ||= 1}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5, &b] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1}, message) + + # ||=, without block, popped + assert_syntax_error(%q{h[**kw] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, **kw] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, nil: 3] ||= 1; nil}, message) + + # ||=, with block, popped + assert_syntax_error(%q{h[**kw, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, **kw, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, **kw, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[kw: 5, a: 2, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, kw: 5, a: 2, &b] ||= 1; nil}, message) + assert_syntax_error(%q{h[0, *a, kw: 5, a: 2, b: 3, &b] ||= 1; nil}, message) + + end + + def test_kwsplat_block_order_op_asgn + o = Object.new + ary = [] + o.define_singleton_method(:to_a) {ary << :to_a; []} + o.define_singleton_method(:to_hash) {ary << :to_hash; {}} + o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}} + + def o.[](...) 2 end + def o.[]=(...) end + + message = /keyword arg given in index assignment/ + + assert_syntax_error(%q{o[kw: 1] += 1}, message) + assert_syntax_error(%q{o[**o] += 1}, message) + assert_syntax_error(%q{o[**o, &o] += 1}, message) + assert_syntax_error(%q{o[*o, **o, &o] += 1}, message) + end + + def test_call_op_asgn_keywords_mutable + h = Class.new do + attr_reader :get, :set + def v; yield; [*@get, *@set] end + def [](*a, **b) + @get = [a.dup, b.dup] + a << :splat_modified + b[:kw_splat_modified] = true + @set = [] + 3 + end + def []=(*a, **b) @set = [a, b] end + end.new + + message = /keyword arg given in index assignment/ + + a = [] + kw = {} + + # Prevent "assigned but unused variable" warnings + _ = [h, a, kw] + + assert_syntax_error(%q{h[*a, 2, b: 5, **kw] += 1}, message) + end + + def test_call_splat_post_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 + + def test_call_splat_block_order + bug16504 = '[ruby-core:96769] [Bug# 16504]' + b = proc{} + ary = [1, 2, b] + assert_equal([1, 2, b], aaa(*ary, &ary.pop), bug16504) + ary = [1, 2, b] + assert_equal([0, 1, 2, b], aaa(0, *ary, &ary.pop), bug16504) + end + + def test_call_splat_kw_order + b = {} + ary = [1, 2, b] + assert_equal([1, 2, b, {a: b}], aaa(*ary, a: ary.pop)) + ary = [1, 2, b] + assert_equal([0, 1, 2, b, {a: b}], aaa(0, *ary, a: ary.pop)) + end + + def test_call_splat_kw_splat_order + b = {} + ary = [1, 2, b] + assert_equal([1, 2, b], aaa(*ary, **ary.pop)) + ary = [1, 2, b] + assert_equal([0, 1, 2, b], aaa(0, *ary, **ary.pop)) + end + + def test_call_args_splat_with_nonhash_keyword_splat + o = Object.new + def o.to_hash; {a: 1} end + def self.f(*a, **kw) + kw + end + assert_equal Hash, f(*[], **o).class + end + + def test_call_args_splat_with_pos_arg_kw_splat_is_not_mutable + o = Object.new + def o.foo(a, **h)= h[:splat_modified] = true + + a = [] + b = {splat_modified: false} + + o.foo(*a, :x, **b) + + assert_equal({splat_modified: false}, b) + end + + def test_anon_splat + r2kh = Hash.ruby2_keywords_hash(kw: 2) + r2kea = [r2kh] + r2ka = [1, r2kh] + + def self.s(*) ->(*a){a}.call(*) end + assert_equal([], s) + assert_equal([1], s(1)) + assert_equal([{kw: 2}], s(kw: 2)) + assert_equal([{kw: 2}], s(**{kw: 2})) + assert_equal([1, {kw: 2}], s(1, kw: 2)) + assert_equal([1, {kw: 2}], s(1, **{kw: 2})) + assert_equal([{kw: 2}], s(*r2kea)) + assert_equal([1, {kw: 2}], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, kw: 0) [*->(*a){a}.call(*), kw] end + assert_equal([0], s) + assert_equal([1, 0], s(1)) + assert_equal([2], s(kw: 2)) + assert_equal([2], s(**{kw: 2})) + assert_equal([1, 2], s(1, kw: 2)) + assert_equal([1, 2], s(1, **{kw: 2})) + assert_equal([2], s(*r2kea)) + assert_equal([1, 2], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, **kw) [*->(*a){a}.call(*), kw] end + assert_equal([{}], s) + assert_equal([1, {}], s(1)) + assert_equal([{kw: 2}], s(kw: 2)) + assert_equal([{kw: 2}], s(**{kw: 2})) + assert_equal([1, {kw: 2}], s(1, kw: 2)) + assert_equal([1, {kw: 2}], s(1, **{kw: 2})) + assert_equal([{kw: 2}], s(*r2kea)) + assert_equal([1, {kw: 2}], s(*r2ka)) + + singleton_class.remove_method(:s) + def self.s(*, kw: 0, **kws) [*->(*a){a}.call(*), kw, kws] end + assert_equal([0, {}], s) + assert_equal([1, 0, {}], s(1)) + assert_equal([2, {}], s(kw: 2)) + assert_equal([2, {}], s(**{kw: 2})) + assert_equal([1, 2, {}], s(1, kw: 2)) + assert_equal([1, 2, {}], s(1, **{kw: 2})) + assert_equal([2, {}], s(*r2kea)) + assert_equal([1, 2, {}], s(*r2ka)) + end + + def test_anon_splat_mutated_bug_21757 + args = [1, 2] + kw = {bug: true} + + def self.m(*); end + m(*args, bug: true) + assert_equal(2, args.length) + + proc = ->(*) { } + proc.(*args, bug: true) + assert_equal(2, args.length) + + def self.m2(*); end + m2(*args, **kw) + assert_equal(2, args.length) + + proc = ->(*) { } + proc.(*args, **kw) + assert_equal(2, args.length) + + def self.m3(*, **nil); end + assert_raise(ArgumentError) { m3(*args, bug: true) } + assert_equal(2, args.length) + + proc = ->(*, **nil) { } + assert_raise(ArgumentError) { proc.(*args, bug: true) } + assert_equal(2, args.length) + end + + def test_kwsplat_block_eval_order + def self.t(**kw, &b) [kw, b] end + + pr = ->{} + h = {a: pr} + a = [] + + ary = t(**h, &h.delete(:a)) + assert_equal([{a: pr}, pr], ary) + + h = {a: pr} + ary = t(*a, **h, &h.delete(:a)) + assert_equal([{a: pr}, pr], ary) + end + + def test_kwsplat_block_order + o = Object.new + ary = [] + o.define_singleton_method(:to_a) {ary << :to_a; []} + o.define_singleton_method(:to_hash) {ary << :to_hash; {}} + o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}} + + def self.t(...) end + + t(**o, &o) + assert_equal([:to_hash, :to_proc], ary) + + ary.clear + t(*o, **o, &o) + assert_equal([:to_a, :to_hash, :to_proc], ary) + end + + def test_kwsplat_block_order_super + def self.t(splat) + o = Object.new + ary = [] + o.define_singleton_method(:to_a) {ary << :to_a; []} + o.define_singleton_method(:to_hash) {ary << :to_hash; {}} + o.define_singleton_method(:to_proc) {ary << :to_proc; lambda{}} + if splat + super(*o, **o, &o) + else + super(**o, &o) + end + ary + end + extend Module.new{def t(...) end} + + assert_equal([:to_hash, :to_proc], t(false)) + assert_equal([:to_a, :to_hash, :to_proc], t(true)) + end + + OVER_STACK_LEN = (ENV['RUBY_OVER_STACK_LEN'] || 150).to_i # Greater than VM_ARGC_STACK_MAX + OVER_STACK_ARGV = OVER_STACK_LEN.times.to_a.freeze + + def test_call_cfunc_splat_large_array_bug_4040 + a = OVER_STACK_ARGV + + assert_equal(a, [].push(*a)) + assert_equal(a, [].push(a[0], *a[1..])) + assert_equal(a, [].push(a[0], a[1], *a[2..])) + assert_equal(a, [].push(*a[0..1], *a[2..])) + assert_equal(a, [].push(*a[...-1], a[-1])) + assert_equal(a, [].push(a[0], *a[1...-1], a[-1])) + assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1])) + assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1])) + assert_equal(a, [].push(*a[...-2], a[-2], a[-1])) + assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1])) + assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1])) + assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1])) + + kw = {x: 1} + a_kw = a + [kw] + + assert_equal(a_kw, [].push(*a, **kw)) + assert_equal(a_kw, [].push(a[0], *a[1..], **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2..], **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2..], **kw)) + assert_equal(a_kw, [].push(*a[...-1], a[-1], **kw)) + assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], **kw)) + assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], **kw)) + assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw)) + + assert_equal(a_kw, [].push(*a, x: 1)) + assert_equal(a_kw, [].push(a[0], *a[1..], x: 1)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1)) + assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1)) + assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1)) + assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1)) + assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1)) + assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1)) + + a_kw[-1][:y] = 2 + kw = {y: 2} + + assert_equal(a_kw, [].push(*a, x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], *a[1..], x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2..], x: 1, **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2..], x: 1, **kw)) + assert_equal(a_kw, [].push(*a[...-1], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], *a[1...-1], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-1], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-1], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(*a[...-2], a[-2], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], *a[1...-2], a[-2], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], x: 1, **kw)) + assert_equal(a_kw, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], x: 1, **kw)) + + kw = {} + + assert_equal(a, [].push(*a, **kw)) + assert_equal(a, [].push(a[0], *a[1..], **kw)) + assert_equal(a, [].push(a[0], a[1], *a[2..], **kw)) + assert_equal(a, [].push(*a[0..1], *a[2..], **kw)) + assert_equal(a, [].push(*a[...-1], a[-1], **kw)) + assert_equal(a, [].push(a[0], *a[1...-1], a[-1], **kw)) + assert_equal(a, [].push(a[0], a[1], *a[2...-1], a[-1], **kw)) + assert_equal(a, [].push(*a[0..1], *a[2...-1], a[-1], **kw)) + assert_equal(a, [].push(*a[...-2], a[-2], a[-1], **kw)) + assert_equal(a, [].push(a[0], *a[1...-2], a[-2], a[-1], **kw)) + assert_equal(a, [].push(a[0], a[1], *a[2...-2], a[-2], a[-1], **kw)) + assert_equal(a, [].push(*a[0..1], *a[2...-2], a[-2], a[-1], **kw)) + + a_kw = a + [Hash.ruby2_keywords_hash({})] + assert_equal(a, [].push(*a_kw)) + + # Single test with value that would cause SystemStackError. + # Not all tests use such a large array to reduce testing time. + assert_equal(1380888, [].push(*1380888.times.to_a).size) + end + + def test_call_iseq_large_array_splat_fail + def self.a; end + def self.b(a=1); end + def self.c(k: 1); end + def self.d(**kw); end + def self.e(k: 1, **kw); end + def self.f(a=1, k: 1); end + def self.g(a=1, **kw); end + def self.h(a=1, k: 1, **kw); end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_iseq_large_array_splat_pass + def self.a(*a); a.length end + assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV) + + def self.b(_, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV) + + def self.c(_, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV) + + def self.d(b=1, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV) + + def self.e(b=1, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV) + + def self.f(b, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV) + + def self.g(*a, k: 1); a.length end + assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV) + + def self.h(*a, **kw); a.length end + assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV) + + def self.i(*a, k: 1, **kw); a.length end + assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV) + + def self.j(b=1, *a, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV) + + def self.k(b=1, *a, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV) + + def self.l(b=1, *a, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV) + + def self.m(b=1, *a, _, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV) + + def self.n(b=1, *a, _, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV) + + def self.o(b=1, *a, _, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV) + end + + def test_call_iseq_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + singleton_class.class_eval("def a(#{args}); [#{args}] end") + assert_equal OVER_STACK_ARGV, a(*OVER_STACK_ARGV) + + singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [0], b(*OVER_STACK_ARGV)) + + singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [[]], c(*OVER_STACK_ARGV)) + + singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], d(*OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_proc_large_array_splat_pass + [ + proc{0} , + proc{|a=1|a}, + proc{|k: 1|0}, + proc{|**kw| 0}, + proc{|k: 1, **kw| 0}, + proc{|a=1, k: 1| a}, + proc{|a=1, **kw| a}, + proc{|a=1, k: 1, **kw| a}, + ].each do |l| + assert_equal 0, l.call(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, proc{|*a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|_, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|_, *a, _| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, proc{|*a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), proc{|b=1, *a, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, **kw| a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), proc{|b=1, *a, _, k: 1, **kw| a.length}.(*OVER_STACK_ARGV) + end + + def test_call_proc_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + l = instance_eval("proc{|#{args}| [#{args}]}") + assert_equal OVER_STACK_ARGV, l.(*OVER_STACK_ARGV) + + l = instance_eval("proc{|#{args}, b| [#{args}, b]}") + assert_equal(OVER_STACK_ARGV + [nil], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args1}| [#{args1}]}") + assert_equal(OVER_STACK_ARGV[0...-1], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, *b| [#{args}, b]}") + assert_equal(OVER_STACK_ARGV + [[]], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args1}, *b| [#{args1}, b]}") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, b, *c| [#{args}, b, c]}") + assert_equal(OVER_STACK_ARGV + [nil, []], l.(*OVER_STACK_ARGV)) + + l = instance_eval("proc{|#{args}, b, *c, d| [#{args}, b, c, d]}") + assert_equal(OVER_STACK_ARGV + [nil, [], nil], l.(*OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_lambda_large_array_splat_fail + [ + ->{} , + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + l.call(*OVER_STACK_ARGV) + end + end + end + + def test_call_lambda_large_array_splat_pass + assert_equal OVER_STACK_LEN, ->(*a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(_, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(_, *a, _){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b, *a){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, ->(*a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 1), ->(b=1, *a, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, **kw){a.length}.(*OVER_STACK_ARGV) + assert_equal (OVER_STACK_LEN - 2), ->(b=1, *a, _, k: 1, **kw){a.length}.(*OVER_STACK_ARGV) + end + + def test_call_yield_block_large_array_splat_pass + def self.a + yield(*OVER_STACK_ARGV) + end + + [ + proc{0} , + proc{|a=1|a}, + proc{|k: 1|0}, + proc{|**kw| 0}, + proc{|k: 1, **kw| 0}, + proc{|a=1, k: 1| a}, + proc{|a=1, **kw| a}, + proc{|a=1, k: 1, **kw| a}, + ].each do |l| + assert_equal 0, a(&l) + end + + assert_equal OVER_STACK_LEN, a{|*a| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b, *a| a.length} + assert_equal OVER_STACK_LEN, a{|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, a{|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, a{|*a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), a{|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), a{|b=1, *a, _, k: 1, **kw| a.length} + end + + def test_call_yield_large_array_splat_with_large_number_of_parameters + def self.a + yield(*OVER_STACK_ARGV) + end + + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + assert_equal OVER_STACK_ARGV, instance_eval("a{|#{args}| [#{args}]}", __FILE__, __LINE__) + assert_equal(OVER_STACK_ARGV + [nil], instance_eval("a{|#{args}, b| [#{args}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV[0...-1], instance_eval("a{|#{args1}| [#{args1}]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [[]], instance_eval("a{|#{args}, *b| [#{args}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], instance_eval("a{|#{args1}, *b| [#{args1}, b]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [nil, []], instance_eval("a{|#{args}, b, *c| [#{args}, b, c]}", __FILE__, __LINE__)) + assert_equal(OVER_STACK_ARGV + [nil, [], nil], instance_eval("a{|#{args}, b, *c, d| [#{args}, b, c, d]}", __FILE__, __LINE__)) + end if OVER_STACK_LEN < 200 + + def test_call_yield_lambda_large_array_splat_fail + def self.a + yield(*OVER_STACK_ARGV) + end + [ + ->{} , + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + a(&l) + end + end + end + + def test_call_yield_lambda_large_array_splat_pass + def self.a + yield(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, a(&->(*a){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(_, *a){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(_, *a, _){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b, *a){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, k: 1){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, **kw){a.length}) + assert_equal OVER_STACK_LEN, a(&->(*a, k: 1, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 1), a(&->(b=1, *a, k: 1, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, **kw){a.length}) + assert_equal (OVER_STACK_LEN - 2), a(&->(b=1, *a, _, k: 1, **kw){a.length}) + end + + def test_call_send_iseq_large_array_splat_fail + def self.a; end + def self.b(a=1); end + def self.c(k: 1); end + def self.d(**kw); end + def self.e(k: 1, **kw); end + def self.f(a=1, k: 1); end + def self.g(a=1, **kw); end + def self.h(a=1, k: 1, **kw); end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + send(meth, *OVER_STACK_ARGV) + end + end + end + + def test_call_send_iseq_large_array_splat_pass + def self.a(*a); a.length end + assert_equal OVER_STACK_LEN, send(:a, *OVER_STACK_ARGV) + + def self.b(_, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:b, *OVER_STACK_ARGV) + + def self.c(_, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:c, *OVER_STACK_ARGV) + + def self.d(b=1, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:d, *OVER_STACK_ARGV) + + def self.e(b=1, *a, _); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:e, *OVER_STACK_ARGV) + + def self.f(b, *a); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:f, *OVER_STACK_ARGV) + + def self.g(*a, k: 1); a.length end + assert_equal OVER_STACK_LEN, send(:g, *OVER_STACK_ARGV) + + def self.h(*a, **kw); a.length end + assert_equal OVER_STACK_LEN, send(:h, *OVER_STACK_ARGV) + + def self.i(*a, k: 1, **kw); a.length end + assert_equal OVER_STACK_LEN, send(:i, *OVER_STACK_ARGV) + + def self.j(b=1, *a, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:j, *OVER_STACK_ARGV) + + def self.k(b=1, *a, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:k, *OVER_STACK_ARGV) + + def self.l(b=1, *a, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 1), send(:l, *OVER_STACK_ARGV) + + def self.m(b=1, *a, _, k: 1); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:m, *OVER_STACK_ARGV) + + def self.n(b=1, *a, _, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:n, *OVER_STACK_ARGV) + + def self.o(b=1, *a, _, k: 1, **kw); a.length end + assert_equal (OVER_STACK_LEN - 2), send(:o, *OVER_STACK_ARGV) + end + + def test_call_send_iseq_large_array_splat_with_large_number_of_parameters + args = OVER_STACK_ARGV.map{|i| "a#{i}"}.join(',') + args1 = (OVER_STACK_LEN-1).times.map{|i| "a#{i}"}.join(',') + + singleton_class.class_eval("def a(#{args}); [#{args}] end") + assert_equal OVER_STACK_ARGV, send(:a, *OVER_STACK_ARGV) + + singleton_class.class_eval("def b(#{args}, b=0); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [0], send(:b, *OVER_STACK_ARGV)) + + singleton_class.class_eval("def c(#{args}, *b); [#{args}, b] end") + assert_equal(OVER_STACK_ARGV + [[]], send(:c, *OVER_STACK_ARGV)) + + singleton_class.class_eval("def d(#{args1}, *b); [#{args1}, b] end") + assert_equal(OVER_STACK_ARGV[0...-1] + [[OVER_STACK_ARGV.last]], send(:d, *OVER_STACK_ARGV)) + end if OVER_STACK_LEN < 200 + + def test_call_send_cfunc_large_array_splat_fail + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + send(:object_id, *OVER_STACK_ARGV) + end + end + + def test_call_send_cfunc_large_array_splat_pass + assert_equal OVER_STACK_LEN, [].send(:push, *OVER_STACK_ARGV).length + end + + def test_call_attr_reader_large_array_splat_fail + singleton_class.send(:attr_reader, :a) + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_attr_writer_large_array_splat_fail + singleton_class.send(:attr_writer, :a) + singleton_class.send(:alias_method, :a, :a=) + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_struct_aref_large_array_splat_fail + s = Struct.new(:a).new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + s.a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + s.send(:a, *OVER_STACK_ARGV) + end + end + + def test_call_struct_aset_large_array_splat_fail + s = Struct.new(:a) do + alias b a= + end.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + s.b(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + s.send(:b, *OVER_STACK_ARGV) + end + end + + def test_call_alias_large_array_splat + c = Class.new do + def a; end + def c(*a); a.length end + attr_accessor :e + end + sc = Class.new(c) do + alias b a + alias d c + alias f e + alias g e= + end + + obj = sc.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.b(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.f(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 1)") do + obj.g(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, obj.d(*OVER_STACK_ARGV) + end + + def test_call_zsuper_large_array_splat + c = Class.new do + private + def a; end + def c(*a); a.length end + attr_reader :e + end + sc = Class.new(c) do + public :a + public :c + public :e + end + + obj = sc.new + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.a(*OVER_STACK_ARGV) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + obj.e(*OVER_STACK_ARGV) + end + + assert_equal OVER_STACK_LEN, obj.c(*OVER_STACK_ARGV) + end + + class RefinedModuleLargeArrayTest + c = self + using(Module.new do + refine c do + def a; end + def c(*a) a.length end + attr_reader :e + end + end) + + def b + a(*OVER_STACK_ARGV) + end + + def d + c(*OVER_STACK_ARGV) + end + + def f + e(*OVER_STACK_ARGV) + end + end + + def test_call_refined_large_array_splat_fail + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + RefinedModuleLargeArrayTest.new.b + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN}, expected 0)") do + RefinedModuleLargeArrayTest.new.f + end + end + + def test_call_refined_large_array_splat_pass + assert_equal OVER_STACK_LEN, RefinedModuleLargeArrayTest.new.d + end + + def test_call_method_missing_iseq_large_array_splat_fail + def self.method_missing(_) end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + nonexistent_method(*OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send(:nonexistent_method, *OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send("nonexistent_method123", *OVER_STACK_ARGV) + end + end + + def test_call_method_missing_iseq_large_array_splat_pass + def self.method_missing(m, *a) + a.length + end + assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV) + end + + def test_call_bmethod_large_array_splat_fail + define_singleton_method(:a){} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval("#{meth}(*OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_bmethod_large_array_splat_pass + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, a(*OVER_STACK_ARGV) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), b(*OVER_STACK_ARGV) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), c(*OVER_STACK_ARGV) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), d(*OVER_STACK_ARGV) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), e(*OVER_STACK_ARGV) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), f(*OVER_STACK_ARGV) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, g(*OVER_STACK_ARGV) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, h(*OVER_STACK_ARGV) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, i(*OVER_STACK_ARGV) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), j(*OVER_STACK_ARGV) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), k(*OVER_STACK_ARGV) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), l(*OVER_STACK_ARGV) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), m(*OVER_STACK_ARGV) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), n(*OVER_STACK_ARGV) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), o(*OVER_STACK_ARGV) + end + + def test_call_method_missing_bmethod_large_array_splat_fail + define_singleton_method(:method_missing){|_|} + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + nonexistent_method(*OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send(:nonexistent_method, *OVER_STACK_ARGV) + end + + assert_raise_with_message(ArgumentError, "wrong number of arguments (given #{OVER_STACK_LEN+1}, expected 1)") do + send("nonexistent_method123", *OVER_STACK_ARGV) + end + end + + def test_call_method_missing_bmethod_large_array_splat_pass + define_singleton_method(:method_missing){|_, *a| a.length} + assert_equal OVER_STACK_LEN, nonexistent_method(*OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send(:nonexistent_method, *OVER_STACK_ARGV) + assert_equal OVER_STACK_LEN, send("nonexistent_method123", *OVER_STACK_ARGV) + end + + def test_call_symproc_large_array_splat_fail + define_singleton_method(:a){} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + instance_eval(":#{meth}.to_proc.(self, *OVER_STACK_ARGV)", __FILE__, __LINE__) + end + end + end + + def test_call_symproc_large_array_splat_pass + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, :a.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :b.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), :c.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :d.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), :e.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), :f.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, :g.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, :h.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, :i.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), :j.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), :k.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), :l.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), :m.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), :n.to_proc.(self, *OVER_STACK_ARGV) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), :o.to_proc.(self, *OVER_STACK_ARGV) + end + + def test_call_rb_call_iseq_large_array_splat_fail + extend Bug::Iter::Yield + l = ->(*a){} + + def self.a; end + def self.b(a=1) end + def self.c(k: 1) end + def self.d(**kw) end + def self.e(k: 1, **kw) end + def self.f(a=1, k: 1) end + def self.g(a=1, **kw) end + def self.h(a=1, k: 1, **kw) end + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(meth, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_rb_call_iseq_large_array_splat_pass + extend Bug::Iter::Yield + l = ->(*a){a.length} + + def self.a(*a) a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + def self.b(_, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l) + + def self.c(_, *a, _) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l) + + def self.d(b=1, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l) + + def self.e(b=1, *a, _) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l) + + def self.f(b, *a) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l) + + def self.g(*a, k: 1) a.length end + assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l) + + def self.h(*a, **kw) a.length end + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + def self.i(*a, k: 1, **kw) a.length end + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + def self.j(b=1, *a, k: 1) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l) + + def self.k(b=1, *a, **kw) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l) + + def self.l(b=1, *a, k: 1, **kw) a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l) + + def self.m(b=1, *a, _, k: 1) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l) + + def self.n(b=1, *a, _, **kw) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l) + + def self.o(b=1, *a, _, k: 1, **kw) a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l) + end + + def test_call_rb_call_bmethod_large_array_splat_fail + extend Bug::Iter::Yield + l = ->(*a){} + + define_singleton_method(:a){||} + define_singleton_method(:b){|a=1|} + define_singleton_method(:c){|k: 1|} + define_singleton_method(:d){|**kw|} + define_singleton_method(:e){|k: 1, **kw|} + define_singleton_method(:f){|a=1, k: 1|} + define_singleton_method(:g){|a=1, **kw|} + define_singleton_method(:h){|a=1, k: 1, **kw|} + + (:a..:h).each do |meth| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(meth, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_rb_call_bmethod_large_array_splat_pass + extend Bug::Iter::Yield + l = ->(*a){a.length} + + define_singleton_method(:a){|*a| a.length} + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + define_singleton_method(:b){|_, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:b, *OVER_STACK_ARGV, &l) + + define_singleton_method(:c){|_, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:c, *OVER_STACK_ARGV, &l) + + define_singleton_method(:d){|b=1, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:d, *OVER_STACK_ARGV, &l) + + define_singleton_method(:e){|b=1, *a, _| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:e, *OVER_STACK_ARGV, &l) + + define_singleton_method(:f){|b, *a| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:f, *OVER_STACK_ARGV, &l) + + define_singleton_method(:g){|*a, k: 1| a.length} + assert_equal OVER_STACK_LEN, yield_block(:g, *OVER_STACK_ARGV, &l) + + define_singleton_method(:h){|*a, **kw| a.length} + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + define_singleton_method(:i){|*a, k: 1, **kw| a.length} + assert_equal OVER_STACK_LEN, yield_block(:h, *OVER_STACK_ARGV, &l) + + define_singleton_method(:j){|b=1, *a, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:j, *OVER_STACK_ARGV, &l) + + define_singleton_method(:k){|b=1, *a, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:k, *OVER_STACK_ARGV, &l) + + define_singleton_method(:l){|b=1, *a, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 1), yield_block(:l, *OVER_STACK_ARGV, &l) + + define_singleton_method(:m){|b=1, *a, _, k: 1| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:m, *OVER_STACK_ARGV, &l) + + define_singleton_method(:n){|b=1, *a, _, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:n, *OVER_STACK_ARGV, &l) + + define_singleton_method(:o){|b=1, *a, _, k: 1, **kw| a.length} + assert_equal (OVER_STACK_LEN - 2), yield_block(:o, *OVER_STACK_ARGV, &l) + end + + def test_call_ifunc_iseq_large_array_splat_fail + extend Bug::Iter::Yield + def self.a(*a) + yield(*a) + end + [ + ->(){}, + ->(a=1){}, + ->(k: 1){}, + ->(**kw){}, + ->(k: 1, **kw){}, + ->(a=1, k: 1){}, + ->(a=1, **kw){}, + ->(a=1, k: 1, **kw){}, + ].each do |l| + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given #{OVER_STACK_LEN}, expected 0(\.\.[12])?\)/) do + yield_block(:a, *OVER_STACK_ARGV, &l) + end + end + end + + def test_call_ifunc_iseq_large_array_splat_pass + extend Bug::Iter::Yield + def self.a(*a) + yield(*a) + end + + l = ->(*a) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(_, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(_, *a, _) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b, *a) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, k: 1) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, **kw) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(*a, k: 1, **kw) do a.length end + assert_equal OVER_STACK_LEN, yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, k: 1) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, k: 1, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 1), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, k: 1) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + + l = ->(b=1, *a, _, k: 1, **kw) do a.length end + assert_equal (OVER_STACK_LEN - 2), yield_block(:a, *OVER_STACK_ARGV, &l) + end +end diff --git a/test/ruby/test_case.rb b/test/ruby/test_case.rb new file mode 100644 index 0000000000..9e8502fb27 --- /dev/null +++ b/test/ruby/test_case.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestCase < Test::Unit::TestCase + def test_case + case 5 + when 1, 2, 3, 4, 6, 7, 8 + assert(false) + when 5 + assert(true) + end + + case 5 + when 5 + assert(true) + when 1..10 + assert(false) + end + + case 5 + when 1..10 + assert(true) + else + assert(false) + end + + case 5 + when 5 + assert(true) + else + assert(false) + end + + case "foobar" + when /^f.*r$/ + assert(true) + else + assert(false) + end + + case + when true + assert(true) + when false, nil + assert(false) + else + assert(false) + end + + case "+" + when *%w/. +/ + assert(true) + else + assert(false) + end + + case + when *[], false + assert(false) + else + assert(true) + end + + case + when *false, [] + assert(true) + else + assert(false) + end + + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_raise(NameError) do + eval("case; when false, *x, false; end") + end + ensure + $VERBOSE = verbose_bak + end + end + + def test_deoptimization + assert_in_out_err(['-e', <<-EOS], '', %w[42], []) + class Symbol; undef ===; def ===(o); p 42; true; end; end; case :foo; when :foo; end + EOS + + assert_in_out_err(['-e', <<-EOS], '', %w[42], []) + 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 + case 0 + when 0r + assert(true) + else + assert(false) + end + case 0 + when 0i + assert(true) + else + assert(false) + end + end + + def test_method_missing + 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 new file mode 100644 index 0000000000..8f12e06685 --- /dev/null +++ b/test/ruby/test_class.rb @@ -0,0 +1,948 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestClass < Test::Unit::TestCase + # ------------------ + # Various test classes + # ------------------ + + class ClassOne + attr :num_args + @@subs = [] + def initialize(*args) + @num_args = args.size + @args = args + end + def [](n) + @args[n] + end + def ClassOne.inherited(klass) + @@subs.push klass + end + def subs + @@subs + end + end + + class ClassTwo < ClassOne + end + + class ClassThree < ClassOne + end + + class ClassFour < ClassThree + end + + # ------------------ + # Start of tests + # ------------------ + + def test_s_inherited + assert_equal([ClassTwo, ClassThree, ClassFour], ClassOne.new.subs) + end + + def test_s_new + c = Class.new + assert_same(Class, c.class) + assert_same(Object, c.superclass) + + c = Class.new(Integer) + assert_same(Class, c.class) + assert_same(Integer, c.superclass) + end + + def test_00_new_basic + a = ClassOne.new + assert_equal(ClassOne, a.class) + assert_equal(0, a.num_args) + + a = ClassOne.new(1, 2, 3) + assert_equal(3, a.num_args) + assert_equal(1, a[0]) + end + + def test_01_new_inherited + a = ClassTwo.new + assert_equal(ClassTwo, a.class) + assert_equal(0, a.num_args) + + a = ClassTwo.new(1, 2, 3) + assert_equal(3, a.num_args) + assert_equal(1, a[0]) + end + + def test_superclass + assert_equal(ClassOne, ClassTwo.superclass) + assert_equal(Object, ClassTwo.superclass.superclass) + assert_equal(BasicObject, ClassTwo.superclass.superclass.superclass) + end + + def test_class_cmp + assert_raise(TypeError) { Class.new <= 1 } + assert_raise(TypeError) { Class.new >= 1 } + assert_nil(Class.new <=> 1) + end + + def test_class_initialize + assert_raise(TypeError) do + Class.new.instance_eval { initialize } + end + end + + def test_instantiate_singleton_class + c = class << Object.new; self; end + assert_raise(TypeError) { c.new } + end + + def test_superclass_of_basicobject + assert_equal(nil, BasicObject.superclass) + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + module Mod end + BasicObject.include(Mod) + assert_equal(nil, BasicObject.superclass) + end; + end + + def test_module_function + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:module_function).bind(c).call(:foo) + 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_visibility_inside_method + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + private + end + foo + end + end + + assert_warn(/calling protected without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + protected + end + foo + end + end + + assert_warn(/calling public without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + public + end + foo + end + end + + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + class << self + alias priv private + end + + def self.foo + priv + end + foo + end + end + end + + def test_method_redefinition + feature2155 = '[ruby-dev:39400]' + + line = __LINE__+4 + stderr = EnvUtil.verbose_warning do + Class.new do + def foo; end + def foo; end + end + end + assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) + assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) + + assert_warning '' do + Class.new do + def foo; end + alias bar foo + def foo; end + end + end + + assert_warning '' do + Class.new do + def foo; end + alias bar foo + alias bar foo + end + end + + line = __LINE__+4 + stderr = EnvUtil.verbose_warning do + Class.new do + define_method(:foo) do end + def foo; end + end + end + assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) + assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) + + assert_warning '' do + Class.new do + define_method(:foo) do end + alias bar foo + alias bar foo + end + end + + assert_warning '' do + Class.new do + def foo; end + undef foo + end + end + end + + def test_check_inheritable + assert_raise(TypeError) { Class.new(Object.new) } + + o = Object.new + c = class << o; self; end + 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 + c = Class.new + assert_raise(TypeError) { c.instance_eval { initialize_copy(1) } } + + o = Object.new + c = class << o; self; end + assert_raise(TypeError) { c.dup } + + assert_raise(TypeError) { BasicObject.dup } + end + + def test_class_hierarchy_inside_initialize_dup_bug_21538 + ancestors = sc_ancestors = nil + b = Class.new + b.define_singleton_method(:initialize_dup) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + a = Class.new(b) + + c = a.dup + + expected_ancestors = [c, b, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + + def test_class_hierarchy_inside_initialize_clone_bug_21538 + ancestors = sc_ancestors = nil + a = Class.new + a.define_singleton_method(:initialize_clone) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + c = a.clone + + expected_ancestors = [c, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + + 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), []) + module Foo; def foo; :foo; end; end + false.extend(Foo) + true.extend(Foo) + p false.foo + p true.foo + p FalseClass.include?(Foo) + p TrueClass.include?(Foo) + INPUT + end + + 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_nothing_raised { allocator.bind(Rational).call } + assert_nothing_raised { allocator.bind_call(Rational) } + end + + def test_nonascii_name + c = eval("class ::C\u{df}; self; end") + assert_equal("C\u{df}", c.name, '[ruby-core:24600]') + c = eval("class C\u{df}; self; end") + assert_equal("TestClass::C\u{df}", c.name, '[ruby-core:24600]') + end + + def test_invalid_next_from_class_definition + assert_syntax_error("class C; next; end", /Invalid next/) + end + + def test_invalid_break_from_class_definition + assert_syntax_error("class C; break; end", /Invalid break/) + end + + def test_invalid_redo_from_class_definition + assert_syntax_error("class C; redo; end", /Invalid redo/) + end + + def test_invalid_retry_from_class_definition + assert_syntax_error("class C; retry; end", /Invalid retry/) + end + + def test_invalid_return_from_class_definition + assert_syntax_error("class C; return; end", /Invalid return/) + assert_syntax_error("class << Object; return; end", /Invalid return/) + end + + def test_invalid_yield_from_class_definition + assert_raise(SyntaxError) { + EnvUtil.suppress_warning {eval("class C; yield; end")} + } + end + + def test_clone + original = Class.new { + def foo + return super() + end + } + mod = Module.new { + def foo + return "mod#foo" + end + } + copy = original.clone + 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 + + def test_private_const_access + assert_raise_with_message NameError, /uninitialized/ do + begin + eval('class ::TestClass::PrivateClass; end') + rescue NameError + end + + Object.const_get "NOT_AVAILABLE_CONST_NAME_#{__LINE__}" + end + 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 + + class CloneTest + def foo; TEST; end + end + + CloneTest1 = CloneTest.clone + CloneTest2 = CloneTest.clone + class CloneTest1 + TEST = :C1 + end + class CloneTest2 + TEST = :C2 + end + + def test_constant_access_from_method_in_cloned_class + assert_equal :C1, CloneTest1.new.foo, '[Bug #15877]' + assert_equal :C2, CloneTest2.new.foo, '[Bug #15877]' + 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" + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /A\u{3042}/) { + o::Foo + } + end + end + + def test_redefinition_mismatch + m = Module.new + m.module_eval "A = 1", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /is not a class/) { + m.module_eval "class A; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + n = "M\u{1f5ff}" + m.module_eval "#{n} = 42", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /#{n} is not a class/) { + m.module_eval "class #{n}; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + module Bug + module Class + TestClassDefinedInC = (class C\u{1f5ff}; self; end).new + end + end + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + require '-test-/class' + } + 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 + + def test_assign_frozen_class_to_const + c = Class.new.freeze + assert_same(c, Module.new.module_eval("self::Foo = c")) + c = Class.new.freeze + assert_same(c, Module.new.const_set(:Foo, c)) + end + + def test_subclasses + c = Class.new + sc = Class.new(c) + ssc = Class.new(sc) + [c, sc, ssc].each do |k| + k.include Module.new + k.new.define_singleton_method(:force_singleton_class){} + end + assert_equal([sc], c.subclasses) + assert_equal([ssc], sc.subclasses) + assert_equal([], ssc.subclasses) + + object_subclasses = Object.subclasses + assert_include(object_subclasses, c) + assert_not_include(object_subclasses, sc) + assert_not_include(object_subclasses, ssc) + object_subclasses.each do |subclass| + assert_equal Object, subclass.superclass, "Expected #{subclass}.superclass to be Object" + end + end + + def test_attached_object + c = Class.new + sc = c.singleton_class + obj = c.new + + assert_equal(obj, obj.singleton_class.attached_object) + assert_equal(c, sc.attached_object) + + assert_raise_with_message(TypeError, /is not a singleton class/) do + c.attached_object + end + + assert_raise_with_message(TypeError, /'NilClass' is not a singleton class/) do + nil.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /'FalseClass' is not a singleton class/) do + false.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /'TrueClass' is not a singleton class/) do + true.singleton_class.attached_object + end + end + + def test_subclass_gc + c = Class.new + 10_000.times do + cc = Class.new(c) + 100.times { Class.new(cc) } + end + assert(c.subclasses.size <= 10_000) + end + + def test_subclass_gc_stress + 10000.times do + c = Class.new + 100.times { Class.new(c) } + assert(c.subclasses.size <= 100) + end + end + + def test_classext_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { Class.new } +1_000.times(&code) +PREP +3_000_000.times(&code) +CODE + end + + def test_instance_freeze_dont_freeze_the_class_bug_19164 + klass = Class.new + klass.prepend(Module.new) + + klass.new.freeze + klass.define_method(:bar) {} + assert_equal klass, klass.remove_method(:bar), '[Bug #19164]' + end + + def test_method_table_assignment_just_after_class_init + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", 'm_tbl assignment should be done only when Class object is not promoted' + begin; + GC.stress = true + class C; end + end; + end + + def test_singleton_cc_invalidation + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class T + def hi + "hi" + end + end + + t = T.new + t.singleton_class + + def hello(t) + t.hi + end + + 5.times do + hello(t) # populate inline cache on `t.singleton_class`. + end + + class T + remove_method :hi # invalidate `t.singleton_class` ccs for `hi` + end + + assert_raise NoMethodError do + hello(t) + end + end; + end + + def test_safe_multi_ractor_subclasses_list_mutation + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + 4.times.map do + Ractor.new do + 20_000.times do + Object.new.singleton_class + end + end + end.each(&:join) + end; + end + + def test_safe_multi_ractor_singleton_class_access + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class A; end + 4.times.map do + Ractor.new do + a = A + 100.times do + a = a.singleton_class + end + end + end.each(&:join) + end; + end +end diff --git a/test/ruby/test_clone.rb b/test/ruby/test_clone.rb new file mode 100644 index 0000000000..775c9ed848 --- /dev/null +++ b/test/ruby/test_clone.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestClone < Test::Unit::TestCase + module M001; end + module M002; end + module M003; include M002; end + module M002; include M001; end + module M003; include M002; end + + def test_clone + foo = Object.new + def foo.test + "test" + end + bar = foo.clone + def bar.test2 + "test2" + end + + assert_equal("test2", bar.test2) + assert_equal("test", bar.test) + assert_equal("test", foo.test) + + assert_raise(NoMethodError) {foo.test2} + + assert_equal([M003, M002, M001], M003.ancestors) + end + + def test_frozen_properties_retained_on_clone + obj = Object.new.freeze + cloned_obj = obj.clone + + assert_predicate(obj, :frozen?) + assert_predicate(cloned_obj, :frozen?) + end + + def test_ivar_retained_on_clone + obj = Object.new + obj.instance_variable_set(:@a, 1) + cloned_obj = obj.clone + + assert_equal(obj.instance_variable_get(:@a), 1) + assert_equal(cloned_obj.instance_variable_get(:@a), 1) + end + + def test_ivars_retained_on_extended_obj_clone + ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 } + obj = Object.new + ivars.each do |ivar_name, val| + obj.instance_variable_set(ivar_name, val) + end + + cloned_obj = obj.clone + + ivars.each do |ivar_name, val| + assert_equal(obj.instance_variable_get(ivar_name), val) + assert_equal(cloned_obj.instance_variable_get(ivar_name), val) + end + end + + def test_frozen_properties_and_ivars_retained_on_clone_with_ivar + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + + cloned_obj = obj.clone + + assert_predicate(obj, :frozen?) + assert_equal(obj.instance_variable_get(:@a), 1) + + assert_predicate(cloned_obj, :frozen?) + assert_equal(cloned_obj.instance_variable_get(:@a), 1) + end + + def test_proc_obj_id_flag_reset + # [Bug #20250] + proc = Proc.new { } + proc.object_id + proc.clone.object_id # Would crash with RUBY_DEBUG=1 + end + + def test_user_flags + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1, 2, 3].clone + assert_equal [], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1,2,3,4,5,6,7][1..-2].clone + x.push(1,1,1,1,1) + assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Hash + undef initialize_copy + def initialize_copy(*); end + end + h = {} + h.default_proc = proc { raise } + h = h.clone + assert_equal nil, h[:not_exist], '[Bug #14847]' + EOS + end +end diff --git a/test/ruby/test_comparable.rb b/test/ruby/test_comparable.rb new file mode 100644 index 0000000000..b689469d9e --- /dev/null +++ b/test/ruby/test_comparable.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestComparable < Test::Unit::TestCase + def setup + @o = Object.new + @o.extend(Comparable) + end + def cmp(b) + class << @o; self; end.class_eval { + undef :<=> + define_method(:<=>, b) + } + end + + def test_equal + cmp->(x) do 0; end + assert_equal(true, @o == nil) + cmp->(x) do 1; end + assert_equal(false, @o == nil) + 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 + cmp->(x) do 1; end + assert_equal(true, @o > nil) + cmp->(x) do 0; end + assert_equal(false, @o > nil) + cmp->(x) do -1; end + assert_equal(false, @o > nil) + end + + def test_ge + cmp->(x) do 1; end + assert_equal(true, @o >= nil) + cmp->(x) do 0; end + assert_equal(true, @o >= nil) + cmp->(x) do -1; end + assert_equal(false, @o >= nil) + end + + def test_lt + cmp->(x) do 1; end + assert_equal(false, @o < nil) + cmp->(x) do 0; end + assert_equal(false, @o < nil) + cmp->(x) do -1; end + assert_equal(true, @o < nil) + end + + def test_le + cmp->(x) do 1; end + assert_equal(false, @o <= nil) + cmp->(x) do 0; end + assert_equal(true, @o <= nil) + cmp->(x) do -1; end + assert_equal(true, @o <= nil) + end + + def test_between + cmp->(x) do 0 <=> x end + assert_equal(false, @o.between?(1, 2)) + assert_equal(false, @o.between?(-2, -1)) + assert_equal(true, @o.between?(-1, 1)) + 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_equal(@o, @o.clamp(nil, 2)) + assert_equal(-2, @o.clamp(nil, -2)) + assert_equal(@o, @o.clamp(-2, nil)) + assert_equal(2, @o.clamp(2, nil)) + assert_equal(@o, @o.clamp(nil, nil)) + + assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to max argument') { + @o.clamp(2, 1) + } + end + + def test_clamp_with_range + cmp->(x) do 0 <=> x end + assert_equal(1, @o.clamp(1..2)) + assert_equal(-1, @o.clamp(-2..-1)) + assert_equal(@o, @o.clamp(-1..3)) + + assert_equal(1, @o.clamp(1..1)) + assert_equal(@o, @o.clamp(0..0)) + + assert_equal(1, @o.clamp(1..)) + assert_equal(1, @o.clamp(1...)) + assert_equal(@o, @o.clamp(0..)) + assert_equal(@o, @o.clamp(0...)) + assert_equal(@o, @o.clamp(..2)) + assert_equal(-1, @o.clamp(-2..-1)) + assert_equal(@o, @o.clamp(-2..0)) + assert_equal(@o, @o.clamp(-2..)) + assert_equal(@o, @o.clamp(-2...)) + + exc = [ArgumentError, 'cannot clamp with an exclusive range'] + assert_raise_with_message(*exc) {@o.clamp(1...2)} + assert_raise_with_message(*exc) {@o.clamp(0...2)} + assert_raise_with_message(*exc) {@o.clamp(-1...0)} + assert_raise_with_message(*exc) {@o.clamp(...2)} + + assert_raise_with_message(ArgumentError, 'min argument must be less than or equal to 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_compile_prism.rb b/test/ruby/test_compile_prism.rb new file mode 100644 index 0000000000..76b961b37e --- /dev/null +++ b/test/ruby/test_compile_prism.rb @@ -0,0 +1,2755 @@ +# frozen_string_literal: true + +# This file is organized to match itemization in https://github.com/ruby/prism/issues/1335 +module Prism + class TestCompilePrism < Test::Unit::TestCase + def test_iseq_has_node_id + code = "proc { <<END }\n hello\nEND" + iseq = RubyVM::InstructionSequence.compile_prism(code) + assert_operator iseq.to_a[4][:node_id], :>, -1 + end + + # Subclass is used for tests which need it + class Subclass; end + ############################################################################ + # Literals # + ############################################################################ + + def test_FalseNode + assert_prism_eval("false") + end + + def test_FloatNode + assert_prism_eval("1.2") + assert_prism_eval("1.2e3") + assert_prism_eval("+1.2e+3") + assert_prism_eval("-1.2e-3") + end + + def test_ImaginaryNode + assert_prism_eval("1i") + assert_prism_eval("+1.0i") + assert_prism_eval("1ri") + end + + def test_IntegerNode + assert_prism_eval("1") + assert_prism_eval("+1") + assert_prism_eval("-1") + assert_prism_eval("0x10") + assert_prism_eval("0b10") + assert_prism_eval("0o10") + assert_prism_eval("010") + assert_prism_eval("(0o00)") + end + + def test_NilNode + assert_prism_eval("nil") + end + + def test_RationalNode + assert_prism_eval("1.2r") + assert_prism_eval("+1.2r") + end + + def test_SelfNode + assert_prism_eval("self") + end + + def test_SourceEncodingNode + assert_prism_eval("__ENCODING__") + end + + def test_SourceFileNode + assert_prism_eval("__FILE__") + end + + def test_SourceLineNode + assert_prism_eval("__LINE__", raw: true) + end + + def test_TrueNode + assert_prism_eval("true") + end + + ############################################################################ + # Reads # + ############################################################################ + + def test_BackReferenceReadNode + assert_prism_eval("$+") + end + + def test_ClassVariableReadNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit; end") + end + + def test_ConstantPathNode + assert_prism_eval("Prism::TestCompilePrism") + end + + def test_ConstantReadNode + assert_prism_eval("Prism") + end + + Z = 1 + + def test_DefinedNode + assert_prism_eval("defined? nil") + assert_prism_eval("defined? self") + assert_prism_eval("defined? true") + assert_prism_eval("defined? false") + assert_prism_eval("defined? 1") + assert_prism_eval("defined? 1i") + assert_prism_eval("defined? 1.0") + assert_prism_eval("defined? 1..2") + assert_prism_eval("defined? [A, B, C]") + assert_prism_eval("defined? [1, 2, 3]") + assert_prism_eval("defined?({ a: 1 })") + assert_prism_eval("defined? 'str'") + assert_prism_eval('defined?("#{expr}")') + assert_prism_eval("defined? :sym") + assert_prism_eval("defined? /foo/") + assert_prism_eval('defined?(/#{1}/)') + assert_prism_eval("defined? -> { 1 + 1 }") + assert_prism_eval("defined? a && b") + assert_prism_eval("defined? a || b") + assert_prism_eval("defined? __ENCODING__") + assert_prism_eval("defined? __FILE__") + assert_prism_eval("defined? __LINE__") + + assert_prism_eval("defined? %[1,2,3]") + assert_prism_eval("defined? %q[1,2,3]") + assert_prism_eval("defined? %Q[1,2,3]") + assert_prism_eval("defined? %r[1,2,3]") + assert_prism_eval("defined? %i[1,2,3]") + assert_prism_eval("defined? %I[1,2,3]") + assert_prism_eval("defined? %w[1,2,3]") + assert_prism_eval("defined? %W[1,2,3]") + assert_prism_eval("defined? %s[1,2,3]") + assert_prism_eval("defined? %x[1,2,3]") + + assert_prism_eval("defined? [*b]") + assert_prism_eval("defined? [[*1..2], 3, *4..5]") + assert_prism_eval("defined? [a: [:b, :c]]") + assert_prism_eval("defined? 1 in 1") + + assert_prism_eval("defined? @a") + assert_prism_eval("defined? $a") + assert_prism_eval("defined? @@a") + assert_prism_eval("defined? A") + assert_prism_eval("defined? ::A") + assert_prism_eval("defined? A::B") + assert_prism_eval("defined? A::B::C") + assert_prism_eval("defined? #{self.class.name}::Z::A") + assert_prism_eval("defined? yield") + assert_prism_eval("defined? super") + + assert_prism_eval("defined? X = 1") + assert_prism_eval("defined? X *= 1") + assert_prism_eval("defined? X /= 1") + assert_prism_eval("defined? X &= 1") + assert_prism_eval("defined? X ||= 1") + + assert_prism_eval("defined? $1") + assert_prism_eval("defined? $2") + assert_prism_eval("defined? $`") + assert_prism_eval("defined? $'") + assert_prism_eval("defined? $+") + + assert_prism_eval("defined? $X = 1") + assert_prism_eval("defined? $X *= 1") + assert_prism_eval("defined? $X /= 1") + assert_prism_eval("defined? $X &= 1") + assert_prism_eval("defined? $X ||= 1") + + assert_prism_eval("defined? @@X = 1") + assert_prism_eval("defined? @@X *= 1") + assert_prism_eval("defined? @@X /= 1") + assert_prism_eval("defined? @@X &= 1") + assert_prism_eval("defined? @@X ||= 1") + + assert_prism_eval("defined? @X = 1") + assert_prism_eval("defined? @X *= 1") + assert_prism_eval("defined? @X /= 1") + assert_prism_eval("defined? @X &= 1") + assert_prism_eval("defined? @X ||= 1") + + assert_prism_eval("x = 1; defined? x = 1") + assert_prism_eval("x = 1; defined? x *= 1") + assert_prism_eval("x = 1; defined? x /= 1") + assert_prism_eval("x = 1; defined? x &= 1") + assert_prism_eval("x = 1; defined? x ||= 1") + + assert_prism_eval("if defined? A; end") + + assert_prism_eval("defined?(())") + assert_prism_eval("defined?(('1'))") + + # method chain starting with self that's truthy + assert_prism_eval("defined?(self.itself.itself.itself)") + + # method chain starting with self that's false (exception swallowed) + assert_prism_eval("defined?(self.itself.itself.neat)") + + # single self with method, truthy + assert_prism_eval("defined?(self.itself)") + + # single self with method, false + assert_prism_eval("defined?(self.neat!)") + + # method chain implicit self that's truthy + assert_prism_eval("defined?(itself.itself.itself)") + + # method chain implicit self that's false + assert_prism_eval("defined?(itself.neat.itself)") + + ## single method implicit self that's truthy + assert_prism_eval("defined?(itself)") + + ## single method implicit self that's false + assert_prism_eval("defined?(neatneat)") + + assert_prism_eval("defined?(a(itself))") + assert_prism_eval("defined?(itself(itself))") + + # method chain with a block on the inside + assert_prism_eval("defined?(itself { 1 }.itself)") + + # method chain with parenthesized receiver + assert_prism_eval("defined?((itself).itself)") + + # Method chain on a constant + assert_prism_eval(<<~RUBY) + class PrismDefinedNode + def m1; end + end + + defined?(PrismDefinedNode.new.m1) + RUBY + + assert_prism_eval("defined?(next)") + assert_prism_eval("defined?(break)") + assert_prism_eval("defined?(redo)") + assert_prism_eval("defined?(retry)") + + assert_prism_eval(<<~RUBY) + class PrismDefinedReturnNode + def self.m1; defined?(return) end + end + + PrismDefinedReturnNode.m1 + RUBY + + assert_prism_eval("defined?(begin; 1; end)") + + assert_prism_eval("defined?(defined?(a))") + assert_prism_eval('defined?(:"#{1}")') + assert_prism_eval("defined?(`echo #{1}`)") + + assert_prism_eval("defined?(PrismTestSubclass.test_call_and_write_node &&= 1)") + assert_prism_eval("defined?(PrismTestSubclass.test_call_operator_write_node += 1)") + assert_prism_eval("defined?(PrismTestSubclass.test_call_or_write_node ||= 1)") + assert_prism_eval("defined?(Prism::CPAWN &&= 1)") + assert_prism_eval("defined?(Prism::CPOWN += 1)") + assert_prism_eval("defined?(Prism::CPOrWN ||= 1)") + assert_prism_eval("defined?(Prism::CPWN = 1)") + assert_prism_eval("defined?([0][0] &&= 1)") + assert_prism_eval("defined?([0][0] += 1)") + assert_prism_eval("defined?([0][0] ||= 1)") + + assert_prism_eval("defined?(case :a; when :a; 1; else; 2; end)") + assert_prism_eval("defined?(case [1, 2, 3]; in [1, 2, 3]; 4; end)") + assert_prism_eval("defined?(class PrismClassA; end)") + assert_prism_eval("defined?(def prism_test_def_node; end)") + assert_prism_eval("defined?(for i in [1,2] do; i; end)") + assert_prism_eval("defined?(if true; 1; end)") + assert_prism_eval("defined?(/(?<foo>bar)/ =~ 'barbar')") + assert_prism_eval("defined?(1 => 1)") + assert_prism_eval("defined?(module M; end)") + assert_prism_eval("defined?(1.2r)") + assert_prism_eval("defined?(class << self; end)") + assert_prism_eval("defined?(while a != 1; end)") + assert_prism_eval("defined?(until a == 1; end)") + assert_prism_eval("defined?(unless true; 1; end)") + end + + def test_GlobalVariableReadNode + assert_prism_eval("$pit = 1; $pit") + end + + def test_InstanceVariableReadNode + assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; @pit; end") + end + + def test_LocalVariableReadNode + assert_prism_eval("pit = 1; pit") + end + + def test_NumberedReferenceReadNode + assert_prism_eval("$1") + assert_prism_eval("$99999") + end + + ############################################################################ + # Writes # + ############################################################################ + + def test_ClassVariableAndWriteNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit &&= 1; end") + end + + def test_ClassVariableOperatorWriteNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit = 0; @@pit += 1; end") + end + + def test_ClassVariableOrWriteNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; @@pit ||= 0; end") + assert_prism_eval("class Prism::TestCompilePrism; @@pit = nil; @@pit ||= 1; end") + end + + def test_ClassVariableWriteNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit = 1; end") + end + + def test_ConstantAndWriteNode + assert_prism_eval("Constant = 1; Constant &&= 1") + end + + def test_ConstantOperatorWriteNode + assert_prism_eval("Constant = 1; Constant += 1") + end + + def test_ConstantOrWriteNode + assert_prism_eval("Constant = 1; Constant ||= 1") + end + + def test_ConstantWriteNode + # We don't call assert_prism_eval directly in this case because we + # don't want to assign the constant multiple times if we run + # with `--repeat-count` + # Instead, we eval manually here, and remove the constant to + constant_name = "YCT" + source = "#{constant_name} = 1" + prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval + assert_equal prism_eval, 1 + Object.send(:remove_const, constant_name) + end + + def test_ConstantPathWriteNode + assert_prism_eval("Prism::CPWN = 1") + assert_prism_eval("::CPWN = 1") + end + + def test_ConstantPathAndWriteNode + assert_prism_eval("Prism::CPAWN = 1; Prism::CPAWN &&= 2") + assert_prism_eval("Prism::CPAWN &&= 1") + assert_prism_eval("::CPAWN = 1; ::CPAWN &&= 2") + end + + def test_ConstantPathOrWriteNode + assert_prism_eval("Prism::CPOrWN = nil; Prism::CPOrWN ||= 1") + assert_prism_eval("Prism::CPOrWN ||= 1") + assert_prism_eval("::CPOrWN = nil; ::CPOrWN ||= 1") + end + + def test_ConstantPathOperatorWriteNode + assert_prism_eval("Prism::CPOWN = 0; Prism::CPOWN += 1") + assert_prism_eval("::CPOWN = 0; ::CPOWN += 1") + end + + def test_GlobalVariableAndWriteNode + assert_prism_eval("$pit = 0; $pit &&= 1") + end + + def test_GlobalVariableOperatorWriteNode + assert_prism_eval("$pit = 0; $pit += 1") + end + + def test_GlobalVariableOrWriteNode + assert_prism_eval("$pit ||= 1") + end + + def test_GlobalVariableWriteNode + assert_prism_eval("$pit = 1") + end + + def test_InstanceVariableAndWriteNode + assert_prism_eval("@pit = 0; @pit &&= 1") + end + + def test_InstanceVariableOperatorWriteNode + assert_prism_eval("@pit = 0; @pit += 1") + end + + def test_InstanceVariableOrWriteNode + assert_prism_eval("@pit ||= 1") + end + + def test_InstanceVariableWriteNode + assert_prism_eval("class Prism::TestCompilePrism; @pit = 1; end") + end + + def test_LocalVariableAndWriteNode + assert_prism_eval("pit = 0; pit &&= 1") + end + + def test_LocalVariableOperatorWriteNode + assert_prism_eval("pit = 0; pit += 1") + end + + def test_LocalVariableOrWriteNode + assert_prism_eval("pit ||= 1") + end + + def test_LocalVariableWriteNode + assert_prism_eval("pit = 1") + assert_prism_eval(<<-CODE) + a = 0 + [].each do + a = 1 + end + a + CODE + + assert_prism_eval(<<-CODE) + a = 1 + d = 1 + [1].each do + b = 2 + a = 2 + [2].each do + c = 3 + d = 4 + a = 2 + end + end + [a, d] + CODE + end + + def test_MatchWriteNode + assert_prism_eval("/(?<foo>bar)(?<baz>bar>)/ =~ 'barbar'") + assert_prism_eval("/(?<foo>bar)/ =~ 'barbar'") + end + + ############################################################################ + # Multi-writes # + ############################################################################ + + def test_ClassVariableTargetNode + assert_prism_eval("class Prism::TestCompilePrism; @@pit, @@pit1 = 1; end") + end + + def test_ConstantTargetNode + # We don't call assert_prism_eval directly in this case because we + # don't want to assign the constant multiple times if we run + # with `--repeat-count` + # Instead, we eval manually here, and remove the constant to + constant_names = ["YCT", "YCT2"] + source = "#{constant_names.join(",")} = 1" + prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval + assert_equal prism_eval, 1 + constant_names.map { |name| + Object.send(:remove_const, name) + } + end + + def test_ConstantPathTargetNode + assert_separately([], <<~'RUBY') + verbose = $VERBOSE + # Create some temporary nested constants + Object.send(:const_set, "MyFoo", Object) + Object.const_get("MyFoo").send(:const_set, "Bar", Object) + + constant_names = ["MyBar", "MyFoo::Bar", "MyFoo::Bar::Baz"] + source = "#{constant_names.join(",")} = Object" + iseq = RubyVM::InstructionSequence.compile_prism(source) + $VERBOSE = nil + prism_eval = iseq.eval + $VERBOSE = verbose + assert_equal prism_eval, Object + RUBY + end + + def test_GlobalVariableTargetNode + assert_prism_eval("$pit, $pit1 = 1") + end + + def test_InstanceVariableTargetNode + assert_prism_eval("class Prism::TestCompilePrism; @pit, @pit1 = 1; end") + end + + def test_LocalVariableTargetNode + assert_prism_eval("pit, pit1 = 1") + assert_prism_eval(<<-CODE) + a = 1 + [1].each do + c = 2 + a, b = 2 + end + a + CODE + end + + def test_MultiTargetNode + assert_prism_eval("a, (b, c) = [1, 2, 3]") + assert_prism_eval("a, (b, c) = [1, 2, 3]; a") + assert_prism_eval("a, (b, c) = [1, 2, 3]; b") + assert_prism_eval("a, (b, c) = [1, 2, 3]; c") + assert_prism_eval("a, (b, c) = [1, [2, 3]]; c") + assert_prism_eval("a, (b, *c) = [1, [2, 3]]; c") + assert_prism_eval("a, (b, *c) = 1, [2, 3]; c") + assert_prism_eval("a, (b, *) = 1, [2, 3]; b") + assert_prism_eval("a, (b, *c, d) = 1, [2, 3, 4]; [a, b, c, d]") + assert_prism_eval("(a, (b, c, d, e), f, g), h = [1, [2, 3]], 4, 5, [6, 7]; c") + end + + def test_MultiWriteNode + assert_prism_eval("foo, bar = [1, 2]") + assert_prism_eval("foo, = [1, 2]") + assert_prism_eval("foo, *, bar = [1, 2]") + assert_prism_eval("foo, bar = 1, 2") + assert_prism_eval("foo, *, bar = 1, 2") + assert_prism_eval("foo, *, bar = 1, 2, 3, 4") + assert_prism_eval("a, b, *, d = 1, 2, 3, 4") + assert_prism_eval("a, b, *, d = 1, 2") + assert_prism_eval("(a, b), *, c = [1, 3], 4, 5") + assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; a") + assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; b") + assert_prism_eval("(a, b), *, c = [1, 3], 4, 5; c") + assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; a") + assert_prism_eval("a, *, (c, d) = [1, 3], 4, 5; c") + assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]") + assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; b") + assert_prism_eval("(a, b, c), *, (d, e) = [1, 3], 4, 5, [6, 7]; d") + assert_prism_eval("((a, *, b), *, (c, *, (d, *, e, f, g))), *, ((h, i, *, j), *, (k, l, m, *, n, o, p), q, r) = 1; a") + assert_prism_eval("*a = 1; a") + assert_prism_eval("_, {}[:foo] = 1") + assert_prism_eval("_, {}[:foo], _ = 1") + assert_prism_eval("_, {}[:foo], _ = 1") + assert_prism_eval("_,{}[:foo], _, {}[:bar] = 1") + assert_prism_eval("* = :foo") + assert_prism_eval("* = *[]") + assert_prism_eval("a, * = :foo") + + + assert_prism_eval(<<~CODE) + class Foo + def bar=(x); end + def baz=(c); end + end + foo = Foo.new + foo.bar, foo.baz = 1 + CODE + assert_prism_eval(<<~CODE) + class Foo + def bar=(x); end + def baz=(c); end + end + foo = Foo.new + _, foo.bar, foo.baz = 1 + CODE + assert_prism_eval(<<~CODE) + class Foo + def bar=(x); end + def baz=(c); end + end + foo = Foo.new + _, foo.bar, _, foo.baz = 1 + CODE + + # Test nested writes with method calls + assert_prism_eval(<<~RUBY) + class Foo + attr_accessor :bar + end + + a = Foo.new + + (a.bar, a.bar), b = [1], 2 + RUBY + assert_prism_eval(<<~RUBY) + h = {} + (h[:foo], h[:bar]), a = [1], 2 + RUBY + end + + ############################################################################ + # String-likes # + ############################################################################ + + def test_EmbeddedStatementsNode + assert_prism_eval('"foo #{to_s} baz"') + end + + def test_EmbeddedVariableNode + assert_prism_eval('class Prism::TestCompilePrism; @pit = 1; "#@pit"; end') + assert_prism_eval('class Prism::TestCompilePrism; @@pit = 1; "#@@pit"; end') + assert_prism_eval('$pit = 1; "#$pit"') + end + + def test_InterpolatedMatchLastLineNode + assert_prism_eval('$pit = ".oo"; if /"#{$pit}"/mix; end') + end + + def test_InterpolatedRegularExpressionNode + assert_prism_eval('$pit = 1; /1 #$pit 1/') + assert_prism_eval('$pit = 1; /#$pit/i') + assert_prism_eval('/1 #{1 + 2} 1/') + assert_prism_eval('/1 #{"2"} #{1 + 2} 1/') + end + + def test_InterpolatedStringNode + assert_prism_eval('$pit = 1; "1 #$pit 1"') + assert_prism_eval('"1 #{1 + 2} 1"') + assert_prism_eval('"Prism" "::" "TestCompilePrism"') + assert_prism_eval(<<-'RUBY') + # frozen_string_literal: true + + !("a""b""#{1}").frozen? + RUBY + assert_prism_eval(<<-'RUBY') + # frozen_string_literal: true + + !("a""#{1}""b").frozen? + RUBY + + # Test encoding of interpolated strings + assert_prism_eval(<<~'RUBY') + "#{"foo"}s".encoding + RUBY + assert_prism_eval(<<~'RUBY') + a = "foo" + b = "#{a}" << "Bar" + [a, b, b.encoding] + RUBY + end + + def test_concatenated_StringNode + assert_prism_eval('("a""b").frozen?') + assert_prism_eval(<<-CODE) + # frozen_string_literal: true + + ("a""b").frozen? + CODE + end + + def test_InterpolatedSymbolNode + assert_prism_eval('$pit = 1; :"1 #$pit 1"') + assert_prism_eval(':"1 #{1 + 2} 1"') + end + + def test_InterpolatedXStringNode + assert_prism_eval(<<~RUBY) + def self.`(command) = command * 2 + `echo \#{1}` + RUBY + + assert_prism_eval(<<~RUBY) + def self.`(command) = command * 2 + `echo \#{"100"}` + RUBY + end + + def test_MatchLastLineNode + assert_prism_eval("if /foo/; end") + assert_prism_eval("if /foo/i; end") + assert_prism_eval("if /foo/x; end") + assert_prism_eval("if /foo/m; end") + assert_prism_eval("if /foo/im; end") + assert_prism_eval("if /foo/mx; end") + assert_prism_eval("if /foo/xi; end") + assert_prism_eval("if /foo/ixm; end") + end + + def test_RegularExpressionNode + assert_prism_eval('/pit/') + assert_prism_eval('/pit/i') + assert_prism_eval('/pit/x') + assert_prism_eval('/pit/m') + assert_prism_eval('/pit/im') + assert_prism_eval('/pit/mx') + assert_prism_eval('/pit/xi') + assert_prism_eval('/pit/ixm') + + assert_prism_eval('/pit/u') + assert_prism_eval('/pit/e') + assert_prism_eval('/pit/s') + assert_prism_eval('/pit/n') + + assert_prism_eval('/pit/me') + assert_prism_eval('/pit/ne') + + assert_prism_eval('2.times.map { /#{1}/o }') + assert_prism_eval('2.times.map { foo = 1; /#{foo}/o }') + end + + def test_StringNode + assert_prism_eval('"pit"') + assert_prism_eval('"a".frozen?') + end + + def test_StringNode_frozen_string_literal_true + [ + # Test that string literal is frozen + <<~RUBY, + # frozen_string_literal: true + "a".frozen? + RUBY + # Test that two string literals with the same contents are the same string + <<~RUBY, + # frozen_string_literal: true + "hello".equal?("hello") + RUBY + ].each do |src| + assert_prism_eval(src, raw: true) + end + end + + def test_StringNode_frozen_string_literal_false + [ + # Test that string literal is frozen + <<~RUBY, + # frozen_string_literal: false + !"a".frozen? + RUBY + # Test that two string literals with the same contents are the same string + <<~RUBY, + # frozen_string_literal: false + !"hello".equal?("hello") + RUBY + ].each do |src| + assert_prism_eval(src, raw: true) + end + end + + def test_StringNode_frozen_string_literal_default + # Test that string literal is chilled + assert_prism_eval('"a".frozen?') + + # Test that two identical chilled string literals aren't the same object + assert_prism_eval('!"hello".equal?("hello")') + end + + def test_SymbolNode + assert_prism_eval(":pit") + + # Test UTF-8 symbol in a US-ASCII file + assert_prism_eval(<<~'RUBY', raw: true) + # -*- coding: us-ascii -*- + :"\u{e9}" + RUBY + + # Test ASCII-8BIT symbol in a US-ASCII file + assert_prism_eval(<<~'RUBY', raw: true) + # -*- coding: us-ascii -*- + :"\xff" + RUBY + + # Test US-ASCII symbol in a ASCII-8BIT file + assert_prism_eval(<<~'RUBY', raw: true) + # -*- coding: ascii-8bit -*- + :a + RUBY + end + + def test_XStringNode + assert_prism_eval(<<~RUBY) + class Prism::TestCompilePrism + def self.`(command) = command * 2 + `pit` + end + RUBY + end + + ############################################################################ + # Structures # + ############################################################################ + + def test_ArrayNode + assert_prism_eval("[]") + assert_prism_eval("[1, 2, 3]") + assert_prism_eval("%i[foo bar baz]") + assert_prism_eval("%w[foo bar baz]") + assert_prism_eval("[*1..2]") + assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8]") + assert_prism_eval("[*1..2, 3, 4, *5..6, 7, 8, *9..11]") + assert_prism_eval("[0, *1..2, 3, 4, *5..6, 7, 8, *9..11]") + assert_prism_eval("[-1, true, 0, *1..2, 3, 4, *5..6, 7, 8, *9..11]") + assert_prism_eval("a = [1,2]; [0, *a, 3, 4, *5..6, 7, 8, *9..11]") + assert_prism_eval("[[*1..2], 3, *4..5]") + + elements = Array.new(64) { ":foo" } + assert_prism_eval("[#{elements.join(", ")}, bar: 1, baz: 2]") + + # Test keyword splat inside of array + assert_prism_eval("[**{x: 'hello'}]") + + # Test UTF-8 string array literal in a US-ASCII file + assert_prism_eval(<<~'RUBY', raw: true) + # -*- coding: us-ascii -*- + # frozen_string_literal: true + %W"\u{1f44b} \u{1f409}" + RUBY + end + + def test_AssocNode + assert_prism_eval("{ foo: :bar }") + end + + def test_AssocSplatNode + assert_prism_eval("foo = { a: 1 }; { **foo }") + assert_prism_eval("foo = { a: 1 }; bar = foo; { **foo, b: 2, **bar, c: 3 }") + assert_prism_eval("foo = { a: 1 }; { b: 2, **foo, c: 3}") + + # Test anonymous AssocSplatNode + assert_prism_eval(<<~RUBY) + o = Object.new + def o.bar(**) = Hash(**) + + o.bar(hello: "world") + RUBY + + # Test that AssocSplatNode is evaluated before BlockArgumentNode using + # the splatkw instruction + assert_prism_eval(<<~RUBY) + o = Struct.new(:ary) do + def to_hash + ary << :to_hash + {} + end + + def to_proc + ary << :to_proc + -> {} + end + + def t(...); end + end.new + o.ary = [] + + o.t(**o, &o) + o.ary + RUBY + end + + def test_HashNode + assert_prism_eval("{}") + assert_prism_eval("{ a: :a }") + assert_prism_eval("{ a: :a, b: :b }") + assert_prism_eval("a = 1; { a: a }") + assert_prism_eval("a = 1; { a: }") + assert_prism_eval("{ to_s: }") + assert_prism_eval("{ Prism: }") + assert_prism_eval("[ Prism: [:b, :c]]") + assert_prism_eval("{ [] => 1}") + end + + def test_ImplicitNode + assert_prism_eval("{ to_s: }") + end + + def test_RangeNode + assert_prism_eval("1..2") + assert_prism_eval("1...2") + assert_prism_eval("..2") + assert_prism_eval("...2") + assert_prism_eval("1..") + assert_prism_eval("1...") + assert_prism_eval("a1 = 1; a2 = 2; a1..a2") + assert_prism_eval("a1 = 1; a2 = 2; a1...a2") + assert_prism_eval("a2 = 2; ..a2") + assert_prism_eval("a2 = 2; ...a2") + assert_prism_eval("a1 = 1; a1..") + assert_prism_eval("a1 = 1; a1...") + assert_prism_eval("1..2; nil") + assert_prism_eval("1...2; nil") + assert_prism_eval("..2; nil") + assert_prism_eval("...2; nil") + assert_prism_eval("1..; nil") + assert_prism_eval("1...; nil") + assert_prism_eval("a1 = 1; a2 = 2; a1..a2; nil") + assert_prism_eval("a1 = 1; a2 = 2; a1...a2; nil") + assert_prism_eval("a2 = 2; ..a2; nil") + assert_prism_eval("a2 = 2; ...a2; nil") + assert_prism_eval("a1 = 1; a1..; nil") + assert_prism_eval("a1 = 1; a1...; nil") + end + + def test_SplatNode + assert_prism_eval("*b = []; b") + assert_prism_eval("*b = [1, 2, 3]; b") + assert_prism_eval("a, *b = [1, 2, 3]; a") + assert_prism_eval("a, *b = [1, 2, 3]; b") + assert_prism_eval("a, *b, c = [1, 2, 3]; a") + assert_prism_eval("a, *b, c = [1, 2, 3]; b") + assert_prism_eval("a, *b, c = [1, 2, 3]; c") + assert_prism_eval("*b, c = [1, 2, 3]; b") + assert_prism_eval("*b, c = [1, 2, 3]; c") + assert_prism_eval("a, *, c = [1, 2, 3]; a") + assert_prism_eval("a, *, c = [1, 2, 3]; c") + + # Test anonymous splat node + assert_prism_eval(<<~RUBY) + def self.bar(*) = Array(*) + + bar([1, 2, 3]) + RUBY + end + + ############################################################################ + # Jumps # + ############################################################################ + + def test_AndNode + assert_prism_eval("true && 1") + assert_prism_eval("false && 1") + end + + def test_CaseNode + assert_prism_eval("case :a; when :a; 1; else; 2; end") + assert_prism_eval("case :a; when :b; 1; else; 2; end") + assert_prism_eval("case :a; when :a; 1; else; 2; end") + assert_prism_eval("case :a; when :a; end") + assert_prism_eval("case :a; when :b, :c; end") + assert_prism_eval("case; when :a; end") + assert_prism_eval("case; when :a, :b; 1; else; 2 end") + assert_prism_eval("case :a; when :b; else; end") + assert_prism_eval("b = 1; case :a; when b; else; end") + assert_prism_eval(<<-CODE) + def self.prism_test_case_node + case :a + when :b + else + return 2 + end + 1 + end + prism_test_case_node + CODE + + # Test splat in when + assert_prism_eval(<<~RUBY) + ary = [1, 2] + case 1 + when *ary + :ok + else + :ng + end + RUBY + + # Test splat in when + assert_prism_eval(<<~RUBY) + ary = [1, 2] + case 1 + when :foo, *ary + :ok + else + :ng + end + RUBY + + # Test case without predicate + assert_prism_eval(<<~RUBY) + case + when 1 == 2 + :ng + else + :ok + end + RUBY + + # test splat with no predicate + assert_prism_eval(<<~RUBY) + case + when *[true] + :ok + else + :ng + end + RUBY + end + + def test_ElseNode + assert_prism_eval("if false; 0; else; 1; end") + assert_prism_eval("if true; 0; else; 1; end") + assert_prism_eval("true ? 1 : 0") + assert_prism_eval("false ? 0 : 1") + end + + def test_FlipFlopNode + assert_prism_eval("not (1 == 1) .. (2 == 2)") + assert_prism_eval("not (1 == 1) ... (2 == 2)") + end + + def test_IfNode + assert_prism_eval("if true; 1; end") + assert_prism_eval("1 if true") + assert_prism_eval('a = b = 1; if a..b; end') + assert_prism_eval('if "a".."b"; end') + assert_prism_eval('if "a"..; end') + assert_prism_eval('if .."b"; end') + assert_prism_eval('if ..1; end') + assert_prism_eval('if 1..; end') + assert_prism_eval('if 1..2; end') + assert_prism_eval('if true or true; end'); + end + + def test_OrNode + assert_prism_eval("true || 1") + assert_prism_eval("false || 1") + end + + def test_UnlessNode + assert_prism_eval("1 unless true") + assert_prism_eval("1 unless false") + assert_prism_eval("unless true; 1; end") + assert_prism_eval("unless false; 1; end") + end + + def test_UntilNode + assert_prism_eval("a = 0; until a == 1; a = a + 1; end") + + # Test UntilNode in rescue + assert_prism_eval(<<~RUBY) + o = Object.new + o.instance_variable_set(:@ret, []) + def o.foo = @ret << @ret.length + def o.bar = @ret.length > 3 + begin + raise + rescue + o.foo until o.bar + end + o.instance_variable_get(:@ret) + RUBY + end + + def test_WhileNode + assert_prism_eval("a = 0; while a != 1; a = a + 1; end") + + # Test WhileNode in rescue + assert_prism_eval(<<~RUBY) + o = Object.new + o.instance_variable_set(:@ret, []) + def o.foo = @ret << @ret.length + def o.bar = @ret.length < 3 + begin + raise + rescue + o.foo while o.bar + end + o.instance_variable_get(:@ret) + RUBY + end + + def test_ForNode + assert_prism_eval("r = []; for i in [1,2] do; r << i; end; r") + assert_prism_eval("r = []; for @i in [1,2] do; r << @i; end; r") + assert_prism_eval("r = []; for $i in [1,2] do; r << $i; end; r") + + assert_prism_eval("r = []; for foo, in [1,2,3] do r << foo end; r") + + assert_prism_eval("r = []; for i, j in {a: 'b'} do; r << [i, j]; end; r") + + # Test splat node as index in for loop + assert_prism_eval("r = []; for *x in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for * in [[1,2], [3,4]] do; r << 'ok'; end; r") + assert_prism_eval("r = []; for x, * in [[1,2], [3,4]] do; r << x; end; r") + assert_prism_eval("r = []; for x, *y in [[1,2], [3,4]] do; r << [x, y]; end; r") + end + + ############################################################################ + # Throws # + ############################################################################ + + def test_BeginNode + assert_prism_eval("begin; 1; end") + assert_prism_eval("begin; end; 1") + end + + def test_BreakNode + assert_prism_eval("while true; break; end") + assert_prism_eval("while true; break 1; end") + assert_prism_eval("while true; break 1, 2; end") + + assert_prism_eval("[].each { break }") + assert_prism_eval("[true].map { break }") + end + + def test_ensure_in_methods + assert_prism_eval(<<-CODE) +def self.m + a = [] +ensure + a << 5 + return a +end +m + CODE + end + + def test_break_runs_ensure + assert_prism_eval(<<-CODE) +a = [] +while true + begin + break + ensure + a << 1 + end +end +a + CODE + end + + def test_EnsureNode + assert_prism_eval("begin; 1; ensure; 2; end") + assert_prism_eval("begin; 1; begin; 3; ensure; 4; end; ensure; 2; end") + assert_prism_eval(<<-CODE) + begin + a = 2 + ensure + end + CODE + assert_prism_eval(<<-CODE) + begin + a = 2 + ensure + a = 3 + end + a + CODE + + # Test that ensure block only evaluated once + assert_prism_eval(<<~RUBY) + res = [] + begin + begin + raise + ensure + res << $!.to_s + end + rescue + res + end + RUBY + + assert_prism_eval(<<-CODE) + a = 1 + begin + a = 2 + ensure + a = 3 + end + a + CODE + assert_prism_eval(<<-CODE) + a = 1 + begin + b = 2 + ensure + c = 3 + end + a + b + c + CODE + assert_prism_eval(<<~CODE) + foo = 1 + begin + ensure + begin + ensure + foo.nil? + end + end + CODE + assert_prism_eval(<<~CODE) + def test + ensure + {}.each do |key, value| + {}[key] = value + end + end + CODE + assert_prism_eval(<<~CODE) + def test + a = 1 + ensure + {}.each do |key, value| + {}[key] = a + end + end + CODE + assert_prism_eval(<<-CODE) + def self.prism_test_ensure_node + begin + ensure + end + return + end + prism_test_ensure_node + CODE + + # Test empty ensure block + assert_prism_eval(<<~RUBY) + res = [] + + begin + begin + raise + ensure + end + rescue + res << "rescue" + end + + res + RUBY + + # Bug #21001 + assert_prism_eval(<<~RUBY) + RUN_ARRAY = [1,2] + + MAP_PROC = Proc.new do |&blk| + block_results = [] + RUN_ARRAY.each do |value| + block_value = blk.call(value) + block_results.push block_value + end + block_results + ensure + next block_results + end + + MAP_PROC.call do |value| + break if value > 1 + next value + end + RUBY + end + + def test_NextNode + assert_prism_eval("2.times do |i|; next if i == 1; end") + + assert_prism_eval(<<-CODE) + res = [] + i = 0 + while i < 5 + i += 1 + next if i == 3 + res << i + end + res + CODE + + assert_prism_eval(<<-CODE) + res = [] + (1..5).each do |i| + next if i.even? + res << i + end + res + CODE + + assert_prism_eval(<<-CODE) + (1..5).map do |i| + next i, :even if i.even? + i + end + CODE + + assert_prism_eval(<<-CODE) + res = [] + i = 0 + begin + i += 1 + next if i == 3 + res << i + end while i < 5 + res + CODE + + assert_prism_eval(<<-CODE) + while false + begin + ensure + end + next + end + CODE + + assert_prism_eval(<<~CODE) + [].each do + begin + rescue + next + end + end + CODE + end + + def test_RedoNode + assert_prism_eval(<<-CODE) + counter = 0 + + 5.times do |i| + counter += 1 + if i == 2 && counter < 3 + redo + end + end + CODE + + assert_prism_eval(<<-CODE) + for i in 1..5 + if i == 3 + i = 0 + redo + end + end + CODE + + assert_prism_eval(<<-CODE) + i = 0 + begin + i += 1 + redo if i == 3 + end while i < 5 + CODE + end + + def test_RescueNode + assert_prism_eval("begin; 1; rescue; 2; end") + assert_prism_eval(<<~CODE) + begin + 1 + rescue SyntaxError + 2 + end + CODE + assert_prism_eval(<<~CODE) + begin + 1 + raise 'boom' + rescue StandardError + 2 + end + CODE + assert_prism_eval(<<~CODE) + begin + a = 1 + rescue StandardError => e + end + CODE + assert_prism_eval(<<~CODE) + begin + raise StandardError + rescue StandardError => e + end + CODE + assert_prism_eval(<<~CODE) + begin + 1 + rescue StandardError => e + e + rescue SyntaxError => f + f + else + 4 + end + CODE + assert_prism_eval(<<-CODE) + begin + a = 2 + rescue + a = 3 + end + a + CODE + assert_prism_eval(<<-CODE) + a = 1 + begin + a = 2 + rescue + a = 3 + end + a + CODE + assert_prism_eval(<<-CODE) + a = 1 + begin + b = 2 + raise "bang" + rescue + c = 3 + end + a + b + c + CODE + assert_prism_eval("begin; rescue; end") + + assert_prism_eval(<<~CODE) + begin + rescue + args.each do |key, value| + tmp[key] = 1 + end + end + CODE + assert_prism_eval(<<~CODE) + 10.times do + begin + rescue + break + end + end + CODE + + # Test RescueNode with ElseNode + assert_prism_eval(<<~RUBY) + calls = [] + begin + begin + rescue RuntimeError + calls << 1 + else + calls << 2 + raise RuntimeError + end + rescue RuntimeError + end + + calls + RUBY + end + + def test_RescueModifierNode + assert_prism_eval("1.nil? rescue false") + assert_prism_eval("1.nil? rescue 1") + assert_prism_eval("raise 'bang' rescue nil") + assert_prism_eval("raise 'bang' rescue a = 1; a.nil?") + assert_prism_eval("a = 0 rescue (a += 1 && retry if a <= 1)") + end + + def test_RetryNode + assert_prism_eval(<<~CODE) + a = 1 + begin + a + raise "boom" + rescue + a += 1 + retry unless a > 1 + ensure + a = 3 + end + CODE + + assert_prism_eval(<<~CODE) + begin + rescue + foo = 2 + retry + end + CODE + + assert_prism_eval(<<~CODE) + begin + a = 2 + rescue + retry + end + CODE + end + + def test_ReturnNode + assert_prism_eval(<<-CODE) + def self.prism_test_return_node + return 1 + end + prism_test_return_node + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_return_node + return 1, 2 + end + prism_test_return_node + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_return_node + [1].each do |e| + return true + end + end + prism_test_return_node + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_return_node + [1].map do |i| + return i if i == 1 + 2 + end + end + prism_test_return_node + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_return_node(*args, **kwargs) + return *args, *args, **kwargs + end + prism_test_return_node(1, foo: 0) + CODE + end + + ############################################################################ + # Scopes/statements # + ############################################################################ + + def test_BlockNode + assert_prism_eval("[1, 2, 3].each { |num| num }") + + assert_prism_eval("[].tap { _1 }") + + assert_prism_eval("[].each { |a,| }") + assert_prism_eval("[[1, 2, 3]].map { |_, _, a| a }") + assert_prism_eval("[[1, 2, 3]].map { |_, a| a }") + + assert_prism_eval("[[]].map { |a| a }") + assert_prism_eval("[[]].map { |a| a }") + assert_prism_eval("[[]].map { |a, &block| a }") + assert_prism_eval("[[]].map { |a, &block| a }") + assert_prism_eval("[{}].map { |a,| }") + assert_prism_eval("[[]].map { |a,b=1| a }") + assert_prism_eval("[{}].map { |a,| }") + assert_prism_eval("[{}].map { |a| a }") + + # Test blocks with MultiTargetNode + assert_prism_eval("[[1, 2]].each.map { |(a), (b)| [a, b] }") + end + + def test_ClassNode + assert_prism_eval("class PrismClassA; end") + assert_prism_eval("class PrismClassA; end; class PrismClassB < PrismClassA; end") + assert_prism_eval("class PrismClassA; end; class PrismClassA::PrismClassC; end") + assert_prism_eval(<<-HERE + class PrismClassA; end + class PrismClassA::PrismClassC; end + class PrismClassB; end + class PrismClassB::PrismClassD < PrismClassA::PrismClassC; end + HERE + ) + end + + # Many of these tests are versions of tests at bootstraptest/test_method.rb + def test_DefNode + assert_prism_eval("def prism_test_def_node; end") + assert_prism_eval("a = Object.new; def a.prism_singleton; :ok; end; a.prism_singleton") + assert_prism_eval("def self.prism_test_def_node() 1 end; prism_test_def_node()") + assert_prism_eval("def self.prism_test_def_node(a,b) [a, b] end; prism_test_def_node(1,2)") + assert_prism_eval("def self.prism_test_def_node(a,x=7,y=1) x end; prism_test_def_node(7,1)") + assert_prism_eval("def self.prism_test_def_node(a = 1); x = 2; end; prism_test_def_node") + + # rest argument + assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node().inspect") + assert_prism_eval("def self.prism_test_def_node(*a) a end; prism_test_def_node(1).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,*a) a end; prism_test_def_node(7,7,1,2).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y=7,*a) a end; prism_test_def_node(7).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,z=7,*a) a end; prism_test_def_node(7,7).inspect") + assert_prism_eval("def self.prism_test_def_node(x,y,z=7,zz=7,*a) a end; prism_test_def_node(7,7,7).inspect") + + # keyword arguments + assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(a: 2)") + assert_prism_eval("def self.prism_test_def_node(a: 1, b: 2, c: 4) a + b + c; end; prism_test_def_node(b: 3)") + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(x = 1, y, a: 8, b: 2, c: 4) + a + b + c + x + y + end + prism_test_def_node(10, b: 3) + CODE + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a: []) + a + end + prism_test_def_node + CODE + + # block arguments + assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node{}.class") + assert_prism_eval("def self.prism_test_def_node(&block) block end; prism_test_def_node().inspect") + assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) b end; prism_test_def_node(7,1).inspect") + assert_prism_eval("def self.prism_test_def_node(a,b=7,*c,&block) c end; prism_test_def_node(7,7,1).inspect") + + # splat + assert_prism_eval("def self.prism_test_def_node(a) a end; prism_test_def_node(*[1])") + assert_prism_eval("def self.prism_test_def_node(x,a) a end; prism_test_def_node(7,*[1])") + assert_prism_eval("def self.prism_test_def_node(x,y,a) a end; prism_test_def_node(7,7,*[1])") + assert_prism_eval("def self.prism_test_def_node(x,y,a,b,c) a end; prism_test_def_node(7,7,*[1,7,7])") + + # recursive call + assert_prism_eval("def self.prism_test_def_node(n) n == 0 ? 1 : prism_test_def_node(n-1) end; prism_test_def_node(5)") + + # instance method + assert_prism_eval("class PrismTestDefNode; def prism_test_def_node() 1 end end; PrismTestDefNode.new.prism_test_def_node") + assert_prism_eval("class PrismTestDefNode; def prism_test_def_node(*a) a end end; PrismTestDefNode.new.prism_test_def_node(1).inspect") + + # block argument + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(&block) prism_test_def_node2(&block) end + def self.prism_test_def_node2() yield 1 end + prism_test_def_node2 {|a| a } + CODE + + # multi argument + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a, (b, *c, d)) + [a, b, c, d] + end + prism_test_def_node("a", ["b", "c", "d"]) + CODE + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a, (b, c, *)) + [a, b, c] + end + prism_test_def_node("a", ["b", "c"]) + CODE + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a, (*, b, c)) + [a, b, c] + end + prism_test_def_node("a", ["b", "c"]) + CODE + + # recursive multis + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a, (b, *c, (d, *e, f))) + [a, b, c, d, d, e, f] + end + prism_test_def_node("a", ["b", "c", ["d", "e", "f"]]) + CODE + + # Many arguments + assert_prism_eval(<<-CODE) + def self.prism_test_def_node(a, (b, *c, d), e = 1, *f, g, (h, *i, j), k:, l: 1, **m) + [a, b, c, d, e, f, g, h, i, j, k, l, m] + end + prism_test_def_node( + "a", + ["b", "c1", "c2", "d"], + "e", + "f1", "f2", + "g", + ["h", "i1", "i2", "j"], + k: "k", + l: "l", + m1: "m1", + m2: "m2" + ) + CODE + end + + def test_pow_parameters + assert_prism_eval("def self.m(a, **); end; method(:m).parameters") + end + + def test_star_parameters + assert_prism_eval("def self.m(a, *, b); end; method(:m).parameters") + end + + def test_repeated_block_params + assert_prism_eval("def self.x(&blk); blk; end; x { |_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters") + end + + def test_repeated_proc_params + assert_prism_eval("proc {|_, _, _ = 1, *_, _:, _: 2, **_, &_| }.parameters") + end + + def test_forward_parameters_block + assert_prism_eval("def self.m(&); end; method(:m).parameters") + end + + def test_forward_parameters + assert_prism_eval("def self.m(...); end; method(:m).parameters") + end + + def test_repeated_block_underscore + assert_prism_eval("def self.m(_, **_, &_); _; end; method(:m).parameters") + end + + def test_repeated_kw_rest_underscore + assert_prism_eval("def self.m(_, **_); _; end; method(:m).parameters") + end + + def test_repeated_required_keyword_underscore + assert_prism_eval("def self.m(_, _, *_, _, _:); _; end; method(:m).parameters") + assert_prism_eval("def self.m(_, _, *_, _, _:, _: 2); _; end; method(:m).parameters") + end + + def test_repeated_required_post_underscore + assert_prism_eval("def self.m(_, _, *_, _); _; end; method(:m).parameters") + end + + def test_repeated_splat_underscore + assert_prism_eval("def self.m(_, _, _ = 1, _ = 2, *_); end; method(:m).parameters") + end + + def test_repeated_optional_underscore + assert_prism_eval("def self.m(a, _, _, _ = 1, _ = 2, b); end; method(:m).parameters") + end + + def test_repeated_required_underscore + assert_prism_eval("def self.m(a, _, _, b); end; method(:m).parameters") + end + + def test_locals_in_parameters + assert_prism_eval("def self.m(a = b = c = 1); [a, b, c]; end; self.m") + end + + def test_trailing_comma_on_block + assert_prism_eval("def self.m; yield [:ok]; end; m {|v0,| v0 }") + end + + def test_complex_default_params + assert_prism_eval("def self.foo(a:, b: '2'.to_i); [a, b]; end; foo(a: 1)") + assert_prism_eval("def self.foo(a:, b: 2, c: '3'.to_i); [a, b, c]; end; foo(a: 1)") + end + + def test_numbered_params + assert_prism_eval("[1, 2, 3].then { _3 }") + assert_prism_eval("1.then { one = 1; one + _1 }") + end + + def test_rescue_with_ensure + assert_prism_eval(<<-CODE) +begin + begin + raise "a" + rescue + raise "b" + ensure + raise "c" + end +rescue => e + e.message +end + CODE + end + + def test_required_kwarg_ordering + assert_prism_eval("def self.foo(a: 1, b:); [a, b]; end; foo(b: 2)") + end + + def test_trailing_keyword_method_params + # foo(1, b: 2, c: 3) # argc -> 3 + assert_prism_eval("def self.foo(a, b:, c:); [a, b, c]; end; foo(1, b: 2, c: 3)") + end + + def test_keyword_method_params_only + # foo(a: 1, b: 2) # argc -> 2 + assert_prism_eval("def self.foo(a:, b:); [a, b]; end; foo(a: 1, b: 2)") + end + + def test_keyword_method_params_with_splat + # foo(a: 1, **b) # argc -> 1 + assert_prism_eval("def self.foo(a:, b:); [a, b]; end; b = { b: 2 }; foo(a: 1, **b)") + end + + def test_positional_and_splat_keyword_method_params + # foo(a, **b) # argc -> 2 + assert_prism_eval("def self.foo(a, b); [a, b]; end; b = { b: 2 }; foo(1, **b)") + end + + def test_positional_and_splat_method_params + # foo(a, *b, c, *d, e) # argc -> 2 + assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, 5)") + end + + def test_positional_with_splat_and_splat_keyword_method_params + # foo(a, *b, c, *d, **e) # argc -> 3 + assert_prism_eval("def self.foo(a, b, c, d, e); [a, b, c, d, e]; end; b = [2]; d = [4]; e = { e: 5 }; foo(1, *b, 3, *d, **e)") + end + + def test_positional_with_splat_and_keyword_method_params + # foo(a, *b, c, *d, e:) # argc -> 3 + assert_prism_eval("def self.foo(a, b, c, d, e:); [a, b, c, d, e]; end; b = [2]; d = [4]; foo(1, *b, 3, *d, e: 5)") + end + + def test_leading_splat_and_keyword_method_params + # foo(*a, b:) # argc -> 2 + assert_prism_eval("def self.foo(a, b:); [a, b]; end; a = [1]; foo(*a, b: 2)") + end + + def test_repeated_method_params + assert_prism_eval("def self.foo(_a, _a); _a; end; foo(1, 2)") + end + + def test_splat_params_with_no_lefties + assert_prism_eval("def self.foo(v, (*)); v; end; foo(1, [2, 3, 4])") + end + + def test_method_parameters + assert_prism_eval(<<-CODE) + def self.prism_test_method_parameters(a, b=1, *c, d:, e: 2, **f, &g) + end + + method(:prism_test_method_parameters).parameters + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_method_parameters(d:, e: 2, **f, &g) + end + + method(:prism_test_method_parameters).parameters + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_method_parameters(**f, &g) + end + + method(:prism_test_method_parameters).parameters + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_method_parameters(&g) + end + + method(:prism_test_method_parameters).parameters + CODE + end + + def test_LambdaNode + assert_prism_eval("-> { to_s }.call") + end + + def test_LambdaNode_with_multiline_args + assert_prism_eval(<<-CODE) + -> (a, + b) { + a + b + }.call(1, 2) + CODE + end + + def test_ModuleNode + assert_prism_eval("module M; end") + assert_prism_eval("module M::N; end") + assert_prism_eval("module ::O; end") + end + + def test_ParenthesesNode + assert_prism_eval("()") + assert_prism_eval("(1)") + end + + def test_PreExecutionNode + assert_prism_eval("BEGIN { a = 1 }; 2", raw: true) + assert_prism_eval("b = 2; BEGIN { a = 1 }; a + b", raw: true) + end + + def test_PostExecutionNode + assert_prism_eval("END { 1 }") + assert_prism_eval("END { @b }; @b = 1") + assert_prism_eval("END { @b; 0 }; @b = 1") + assert_prism_eval("foo = 1; END { foo.nil? }") + assert_prism_eval("foo = 1; END { END { foo.nil? }}") + end + + def test_ProgramNode + assert_prism_eval("") + assert_prism_eval("1") + end + + def test_SingletonClassNode + assert_prism_eval("class << self; end") + end + + def test_StatementsNode + assert_prism_eval("1") + end + + def test_YieldNode + assert_prism_eval("def prism_test_yield_node; yield; end") + assert_prism_eval("def prism_test_yield_node; yield 1, 2; end") + assert_prism_eval("def prism_test_yield_node; yield **kw if condition; end") + + # Test case where there's a call directly after the yield call + assert_prism_eval("def prism_test_yield_node; yield; 1; end") + assert_prism_eval("def prism_test_yield_node; yield 1, 2; 1; end") + end + + ############################################################################ + # Calls / arguments # + ############################################################################ + + def test_ArgumentsNode + # assert_prism_eval("[].push 1") + end + + def test_BlockArgumentNode + assert_prism_eval("1.then(&:to_s)") + + # Test anonymous block forwarding + assert_prism_eval(<<~RUBY) + o = Object.new + def o.foo(&) = yield + def o.bar(&) = foo(&) + + o.bar { :ok } + RUBY + end + + def test_BlockLocalVariableNode + assert_prism_eval(<<-CODE + pm_var = "outer scope variable" + + 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var } + CODE + ) + + assert_prism_eval(<<-CODE + pm_var = "outer scope variable" + + 1.times { |;pm_var| pm_var = "inner scope variable"; pm_var } + pm_var + CODE + ) + end + + def test_CallNode + assert_prism_eval("to_s") + + # with arguments + assert_prism_eval("eval '1'") + + # with arguments and popped + assert_prism_eval("eval '1'; 1") + + # With different types of calling arguments + assert_prism_eval(<<-CODE) + def self.prism_test_call_node_double_splat(**); end + prism_test_call_node_double_splat(b: 1, **{}) + CODE + assert_prism_eval(<<-CODE) + prism_test_call_node_double_splat(:b => 1) + CODE + + assert_prism_eval(<<-CODE) + def self.prism_test_call_node_splat(*); end + prism_test_call_node_splat(*[], 1) + CODE + + assert_prism_eval("prism_test_call_node_splat(*[], 1, 2)") + + assert_prism_eval(<<~RUBY) + def self.prism_test_call_node_splat_and_double_splat(a, b, **opts); end + prism_test_call_node_splat_and_double_splat(*[1], 2, **{}) + RUBY + + assert_prism_eval(<<-CODE) + class Foo + def []=(a, b) + 1234 + end + end + + def self.foo(i, j) + tbl = Foo.new + tbl[i] = j + end + foo(1, 2) + CODE + + assert_prism_eval(<<-CODE) + class Foo + def i=(a) + 1234 + end + end + + def self.foo(j) + tbl = Foo.new + tbl.i = j + end + foo(1) + CODE + + assert_prism_eval(<<-CODE) + foo = Object.new + def foo.[]=(k,v); 42; end + foo.[]=(1,2) + CODE + + # With splat inside of []= + assert_prism_eval(<<~RUBY) + obj = Object.new + def obj.[]=(a, b); 10; end + obj[*[1]] = 3 + RUBY + + assert_prism_eval(<<-CODE) + def self.prism_opt_var_trail_hash(a = nil, *b, c, **d); end + prism_opt_var_trail_hash("a") + prism_opt_var_trail_hash("a", c: 1) + prism_opt_var_trail_hash("a", "b") + prism_opt_var_trail_hash("a", "b", "c") + prism_opt_var_trail_hash("a", "b", "c", c: 1) + prism_opt_var_trail_hash("a", "b", "c", "c" => 0, c: 1) + CODE + + assert_prism_eval(<<-CODE) + def self.foo(*args, **kwargs) = [args, kwargs] + + [ + foo(2 => 3), + foo([] => 42), + foo(a: 42, b: 61), + foo(1, 2, 3, a: 42, "b" => 61), + foo(:a => 42, :b => 61), + ] + CODE + + assert_prism_eval(<<-CODE) + class PrivateMethod + def initialize + self.instance_var + end + private + attr_accessor :instance_var + end + pm = PrivateMethod.new + pm.send(:instance_var) + CODE + + # Testing safe navigation operator + assert_prism_eval(<<-CODE) + def self.test_prism_call_node + if [][0]&.first + 1 + end + end + test_prism_call_node + CODE + + # Specialized instructions + assert_prism_eval(%{-"literal"}) + assert_prism_eval(%{"literal".freeze}) + end + + def test_CallAndWriteNode + assert_prism_eval(<<-CODE + class PrismTestSubclass; end + def PrismTestSubclass.test_call_and_write_node; end; + PrismTestSubclass.test_call_and_write_node &&= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def PrismTestSubclass.test_call_and_write_node + "str" + end + def PrismTestSubclass.test_call_and_write_node=(val) + val + end + PrismTestSubclass.test_call_and_write_node &&= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def self.test_call_and_write_node; end; + self.test_call_and_write_node &&= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def self.test_call_and_write_node + "str" + end + def self.test_call_and_write_node=(val) + val + end + self.test_call_and_write_node &&= 1 + CODE + ) + + assert_prism_eval(<<-CODE) + def self.test_prism_call_node; end + def self.test_prism_call_node=(val) + val + end + self&.test_prism_call_node &&= 1 + CODE + + assert_prism_eval(<<-CODE) + def self.test_prism_call_node + 2 + end + def self.test_prism_call_node=(val) + val + end + self&.test_prism_call_node &&= 1 + CODE + end + + def test_CallOrWriteNode + assert_prism_eval(<<-CODE + class PrismTestSubclass; end + def PrismTestSubclass.test_call_or_write_node; end; + def PrismTestSubclass.test_call_or_write_node=(val) + val + end + PrismTestSubclass.test_call_or_write_node ||= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def PrismTestSubclass.test_call_or_write_node + "str" + end + PrismTestSubclass.test_call_or_write_node ||= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def self.test_call_or_write_node; end; + def self.test_call_or_write_node=(val) + val + end + self.test_call_or_write_node ||= 1 + CODE + ) + + assert_prism_eval(<<-CODE + def self.test_call_or_write_node + "str" + end + self.test_call_or_write_node ||= 1 + CODE + ) + + assert_prism_eval(<<-CODE) + def self.test_prism_call_node + 2 + end + def self.test_prism_call_node=(val) + val + end + self&.test_prism_call_node ||= 1 + CODE + + assert_prism_eval(<<-CODE) + def self.test_prism_call_node; end + def self.test_prism_call_node=(val) + val + end + self&.test_prism_call_node ||= 1 + CODE + end + + def test_CallOperatorWriteNode + assert_prism_eval(<<-CODE + class PrismTestSubclass; end + def PrismTestSubclass.test_call_operator_write_node + 2 + end + def PrismTestSubclass.test_call_operator_write_node=(val) + val + end + PrismTestSubclass.test_call_operator_write_node += 1 + CODE + ) + end + + def test_ForwardingArgumentsNode + assert_prism_eval(<<-CODE) + def prism_test_forwarding_arguments_node(...); end; + def prism_test_forwarding_arguments_node1(...) + prism_test_forwarding_arguments_node(...) + end + CODE + + assert_prism_eval(<<-CODE) + def prism_test_forwarding_arguments_node(...); end; + def prism_test_forwarding_arguments_node1(a, ...) + prism_test_forwarding_arguments_node(1,2, 3, ...) + end + CODE + + assert_prism_eval(<<~RUBY) + o = Object.new + def o.bar(a, b, c) = [a, b, c] + def o.foo(...) = 1.times { bar(...) } + + o.foo(1, 2, 3) + RUBY + end + + def test_ForwardingArgumentsNode_instruction_sequence_consistency + # Test that both parsers generate identical instruction sequences for forwarding arguments + # This prevents regressions like the one fixed in prism_compile.c for PM_FORWARDING_ARGUMENTS_NODE + + # Test case from the bug report: def bar(buz, ...) = foo(buz, ...) + source = <<~RUBY + def foo(*, &block) = block + def bar(buz, ...) = foo(buz, ...) + RUBY + + compare_instruction_sequences(source) + + # Test simple forwarding + source = <<~RUBY + def target(...) = nil + def forwarder(...) = target(...) + RUBY + + compare_instruction_sequences(source) + + # Test mixed forwarding with regular arguments + source = <<~RUBY + def target(a, b, c) = [a, b, c] + def forwarder(x, ...) = target(x, ...) + RUBY + + compare_instruction_sequences(source) + + # Test forwarding with splat + source = <<~RUBY + def target(a, b, c) = [a, b, c] + def forwarder(x, ...); target(*x, ...); end + RUBY + + compare_instruction_sequences(source) + end + + private + + def compare_instruction_sequences(source) + # Get instruction sequences from both parsers + parsey_iseq = RubyVM::InstructionSequence.compile_parsey(source) + prism_iseq = RubyVM::InstructionSequence.compile_prism(source) + + # Compare instruction sequences + assert_equal parsey_iseq.disasm, prism_iseq.disasm + end + + public + + def test_ForwardingSuperNode + assert_prism_eval("class Forwarding; def to_s; super; end; end") + assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end") + assert_prism_eval(<<-CODE) + class A + def initialize(a, b) + end + end + + class B < A + attr_reader :res + def initialize(a, b, *) + super + @res = [a, b] + end + end + + B.new(1, 2).res + CODE + end + + def test_KeywordHashNode + assert_prism_eval("[a: [:b, :c]]") + end + + def test_SuperNode + assert_prism_eval("def to_s; super 1; end") + assert_prism_eval("def to_s; super(); end") + assert_prism_eval("def to_s; super('a', :b, [1,2,3]); end") + assert_prism_eval("def to_s; super(1, 2, 3, &:foo); end") + end + + ############################################################################ + # Methods / parameters # + ############################################################################ + + def test_AliasGlobalVariableNode + assert_prism_eval("alias $prism_foo $prism_bar") + end + + def test_AliasMethodNode + assert_prism_eval("alias :prism_a :to_s") + end + + def test_BlockParameterNode + assert_prism_eval("def prism_test_block_parameter_node(&bar) end") + assert_prism_eval("->(b, c=1, *d, e, &f){}") + + # Test BlockParameterNode with no name + assert_prism_eval("->(&){}") + assert_prism_eval("def prism_test_block_parameter_node(&); end") + end + + def test_BlockParametersNode + assert_prism_eval("Object.tap { || }") + assert_prism_eval("[1].map { |num| num }") + assert_prism_eval("[1].map { |a; b| b = 2; a + b}") + + # Test block parameters with multiple _ + assert_prism_eval(<<~RUBY) + [[1, 2, 3, 4, 5, 6]].map { |(_, _, _, _, _, _)| _ } + RUBY + end + + def test_FowardingParameterNode + assert_prism_eval("def prism_test_forwarding_parameter_node(...); end") + end + + def test_KeywordRestParameterNode + assert_prism_eval("def prism_test_keyword_rest_parameter_node(a, **b); end") + assert_prism_eval("Object.tap { |**| }") + + # Test that KeywordRestParameterNode creates a copy + assert_prism_eval(<<~RUBY) + hash = {} + o = Object.new + def o.foo(**a) = a[:foo] = 1 + + o.foo(**hash) + hash + RUBY + end + + def test_NoKeywordsParameterNode + assert_prism_eval("def prism_test_no_keywords(**nil); end") + assert_prism_eval("def prism_test_no_keywords(a, b = 2, **nil); end") + end + + def test_OptionalParameterNode + assert_prism_eval("def prism_test_optional_param_node(bar = nil); end") + end + + def test_OptionalKeywordParameterNode + assert_prism_eval("def prism_test_optional_keyword_param_node(bar: nil); end") + + # Test with optional argument and method call in OptionalKeywordParameterNode + assert_prism_eval(<<~RUBY) + o = Object.new + def o.foo = 1 + def o.bar(a = nil, b: foo) = b + o.bar + RUBY + end + + def test_ParametersNode + assert_prism_eval("def prism_test_parameters_node(bar, baz); end") + assert_prism_eval("def prism_test_parameters_node(a, b = 2); end") + end + + def test_RequiredParameterNode + assert_prism_eval("def prism_test_required_param_node(bar); end") + assert_prism_eval("def prism_test_required_param_node(foo, bar); end") + end + + def test_RequiredKeywordParameterNode + assert_prism_eval("def prism_test_required_param_node(bar:); end") + assert_prism_eval("def prism_test_required_param_node(foo:, bar:); end") + assert_prism_eval("-> a, b = 1, c:, d:, &e { a }") + end + + def test_RestParameterNode + assert_prism_eval("def prism_test_rest_parameter_node(*a); end") + end + + def test_UndefNode + assert_prism_eval("def prism_undef_node_1; end; undef prism_undef_node_1") + assert_prism_eval(<<-HERE + def prism_undef_node_2 + end + def prism_undef_node_3 + end + undef prism_undef_node_2, prism_undef_node_3 + HERE + ) + assert_prism_eval(<<-HERE + def prism_undef_node_4 + end + undef :'prism_undef_node_#{4}' + HERE + ) + end + + ############################################################################ + # Pattern matching # + ############################################################################ + + def test_AlternationPatternNode + assert_prism_eval("1 in 1 | 2") + assert_prism_eval("1 in 2 | 1") + assert_prism_eval("1 in 2 | 3 | 4 | 1") + assert_prism_eval("1 in 2 | 3") + end + + def test_ArrayPatternNode + assert_prism_eval("[] => []") + + ["in", "=>"].each do |operator| + ["", "Array"].each do |constant| + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3]") + + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *]") + + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, *foo]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, *foo]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[1, 2, 3, *foo]") + + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 3]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 2, 3]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*, 1, 2, 3]") + + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 3]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 2, 3]") + assert_prism_eval("[1, 2, 3] #{operator} #{constant}[*foo, 1, 2, 3]") + end + end + + assert_prism_eval("begin; Object.new => [1, 2, 3]; rescue NoMatchingPatternError; true; end") + assert_prism_eval("begin; [1, 2, 3] => Object[1, 2, 3]; rescue NoMatchingPatternError; true; end") + end + + def test_CapturePatternNode + assert_prism_eval("[1] => [Integer => foo]") + end + + def test_CaseMatchNode + assert_prism_eval(<<~RUBY) + case [1, 2, 3] + in [1, 2, 3] + 4 + end + RUBY + + assert_prism_eval(<<~RUBY) + case { a: 5, b: 6 } + in [1, 2, 3] + 4 + in { a: 5, b: 6 } + 7 + end + RUBY + + assert_prism_eval(<<~RUBY) + case [1, 2, 3, 4] + in [1, 2, 3] + 4 + in { a: 5, b: 6 } + 7 + else + end + RUBY + + assert_prism_eval(<<~RUBY) + case [1, 2, 3, 4] + in [1, 2, 3] + 4 + in { a: 5, b: 6 } + 7 + else + 8 + end + RUBY + + assert_prism_eval(<<~RUBY) + case [1, 2, 3] + in [1, 2, 3] unless to_s + in [1, 2, 3] if to_s.nil? + in [1, 2, 3] + true + end + RUBY + end + + def test_FindPatternNode + ["in", "=>"].each do |operator| + ["", "Array"].each do |constant| + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, 5, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 5, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 4, 5, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 2, 3, 4, 5, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, *foo]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, *foo]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 3, 4, 5, *foo]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*, 1, 2, 3, 4, *foo]") + + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, *bar]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, *bar]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 3, 4, 5, *bar]") + assert_prism_eval("[1, 2, 3, 4, 5] #{operator} #{constant}[*foo, 1, 2, 3, 4, *bar]") + end + end + + assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [*, [*, [*, [*, [*]]]]]") + assert_prism_eval("[1, [2, [3, [4, [5]]]]] => [1, [2, [3, [4, [5]]]]]") + + assert_prism_eval("begin; Object.new => [*, 2, *]; rescue NoMatchingPatternError; true; end") + assert_prism_eval("begin; [1, 2, 3] => Object[*, 2, *]; rescue NoMatchingPatternError; true; end") + end + + def test_HashPatternNode + assert_prism_eval("{} => {}") + + [["{ ", " }"], ["Hash[", "]"]].each do |(prefix, suffix)| + assert_prism_eval("{} => #{prefix} **nil #{suffix}") + + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1 #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2 #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3 #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3 #{suffix}") + + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} ** #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, ** #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, ** #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, ** #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, ** #{suffix}") + + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} **foo #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, **foo #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, **foo #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} b: 2, c: 3, **foo #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **foo #{suffix}") + + assert_prism_eval("{ a: 1 } => #{prefix} a: 1, **nil #{suffix}") + assert_prism_eval("{ a: 1, b: 2, c: 3 } => #{prefix} a: 1, b: 2, c: 3, **nil #{suffix}") + end + + assert_prism_eval("{ a: { b: { c: 1 } } } => { a: { b: { c: 1 } } }") + end + + def test_MatchPredicateNode + assert_prism_eval("1 in 1") + assert_prism_eval("1.0 in 1.0") + assert_prism_eval("1i in 1i") + assert_prism_eval("1r in 1r") + + assert_prism_eval("\"foo\" in \"foo\"") + assert_prism_eval("\"foo \#{1}\" in \"foo \#{1}\"") + + assert_prism_eval("false in false") + assert_prism_eval("nil in nil") + assert_prism_eval("self in self") + assert_prism_eval("true in true") + + assert_prism_eval("5 in 0..10") + assert_prism_eval("5 in 0...10") + + assert_prism_eval("[\"5\"] in %w[5]") + + assert_prism_eval("Prism in Prism") + assert_prism_eval("Prism in ::Prism") + + assert_prism_eval(":prism in :prism") + assert_prism_eval("%s[prism\#{1}] in %s[prism\#{1}]") + assert_prism_eval("\"foo\" in /.../") + assert_prism_eval("\"foo1\" in /...\#{1}/") + assert_prism_eval("4 in ->(v) { v.even? }") + + assert_prism_eval("5 in foo") + + assert_prism_eval("1 in 2") + + # Bug: https://bugs.ruby-lang.org/issues/20956 + assert_prism_eval("1 in [1 | [1]]") + end + + def test_MatchRequiredNode + assert_prism_eval("1 => 1") + assert_prism_eval("1.0 => 1.0") + assert_prism_eval("1i => 1i") + assert_prism_eval("1r => 1r") + + assert_prism_eval("\"foo\" => \"foo\"") + assert_prism_eval("\"foo \#{1}\" => \"foo \#{1}\"") + + assert_prism_eval("false => false") + assert_prism_eval("nil => nil") + assert_prism_eval("true => true") + + assert_prism_eval("5 => 0..10") + assert_prism_eval("5 => 0...10") + + assert_prism_eval("[\"5\"] => %w[5]") + + assert_prism_eval(":prism => :prism") + assert_prism_eval("%s[prism\#{1}] => %s[prism\#{1}]") + assert_prism_eval("\"foo\" => /.../") + assert_prism_eval("\"foo1\" => /...\#{1}/") + assert_prism_eval("4 => ->(v) { v.even? }") + + assert_prism_eval("5 => foo") + end + + def test_PinnedExpressionNode + assert_prism_eval("4 in ^(4)") + end + + def test_PinnedVariableNode + assert_prism_eval("module Prism; @@prism = 1; 1 in ^@@prism; end") + assert_prism_eval("module Prism; @prism = 1; 1 in ^@prism; end") + assert_prism_eval("$prism = 1; 1 in ^$prism") + assert_prism_eval("prism = 1; 1 in ^prism") + assert_prism_eval("[1].each { 1 => ^it }") + end + + ############################################################################ + # Miscellaneous # + ############################################################################ + + def test_eval + assert_prism_eval("eval('1 + 1')", raw: true) + assert_prism_eval("a = 1; eval('a + 1')", raw: true) + + assert_prism_eval(<<~CODE, raw: true) + def prism_eval_splat(**bar) + eval("bar") + end + prism_eval_splat(bar: 10) + CODE + + assert_prism_eval(<<~CODE, raw: true) + def prism_eval_keywords(baz:) + eval("baz") + end + prism_eval_keywords(baz: 10) + CODE + + assert_prism_eval(<<~CODE, raw: true) + [1].each do |a| + [2].each do |b| + c = 3 + eval("a + b + c") + end + end + CODE + + assert_prism_eval(<<~CODE, raw: true) + def prism_eval_binding(b) + eval("bar", b) + end + + bar = :ok + prism_eval_binding(binding) + CODE + end + + def test_ScopeNode + assert_separately(%w[], <<~'RUBY') + def compare_eval(source) + ruby_eval = RubyVM::InstructionSequence.compile("module A; " + source + "; end").eval + prism_eval = RubyVM::InstructionSequence.compile_prism("module B; " + source + "; end").eval + + assert_equal ruby_eval, prism_eval + end + + def assert_prism_eval(source) + $VERBOSE, verbose_bak = nil, $VERBOSE + + begin + compare_eval(source) + + # Test "popped" functionality + compare_eval("#{source}; 1") + ensure + $VERBOSE = verbose_bak + end + end + + assert_prism_eval("a = 1; 1.times do; { a: }; end") + assert_prism_eval("a = 1; def foo(a); a; end") + RUBY + end + + ############################################################################ + # Errors # + ############################################################################ + + def test_MissingNode + # TODO + end + + ############################################################################ + # Encoding # + ############################################################################ + + def test_encoding + assert_prism_eval('"però"') + assert_prism_eval(":però") + end + + def test_parse_file + assert_nothing_raised do + RubyVM::InstructionSequence.compile_file_prism(__FILE__) + end + + error = assert_raise Errno::ENOENT do + RubyVM::InstructionSequence.compile_file_prism("idontexist.rb") + end + + assert_equal "No such file or directory - idontexist.rb", error.message + + assert_raise TypeError do + RubyVM::InstructionSequence.compile_file_prism(nil) + end + end + + private + + def compare_eval(source, raw:, location:) + source = raw ? source : "class Prism::TestCompilePrism\n#{source}\nend" + + ruby_eval = RubyVM::InstructionSequence.compile_parsey(source).eval + prism_eval = RubyVM::InstructionSequence.compile_prism(source).eval + + if ruby_eval.is_a? Proc + assert_equal ruby_eval.class, prism_eval.class, "@#{location.path}:#{location.lineno}" + else + assert_equal ruby_eval, prism_eval, "@#{location.path}:#{location.lineno}" + end + end + + def assert_prism_eval(source, raw: false) + location = caller_locations(1, 1).first + $VERBOSE, verbose_bak = nil, $VERBOSE + + begin + compare_eval(source, raw:, location:) + + # Test "popped" functionality + compare_eval("#{source}; 1", raw:, location:) + ensure + $VERBOSE = verbose_bak + end + end + end +end diff --git a/test/ruby/test_complex.rb b/test/ruby/test_complex.rb new file mode 100644 index 0000000000..bb131cee91 --- /dev/null +++ b/test/ruby/test_complex.rb @@ -0,0 +1,1296 @@ +# frozen_string_literal: false +require 'test/unit' + +class ComplexSub < Complex; end + +class Complex_Test < Test::Unit::TestCase + + 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 + c = ComplexSub.__send__(:convert, 1) + + assert_kind_of(Numeric, c) + + assert_instance_of(ComplexSub, c) + + c2 = c + 1 + assert_instance_of(ComplexSub, c2) + c2 = c - 1 + assert_instance_of(ComplexSub, c2) + + 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) + + c1 = Complex(1) + assert_equal(c1.hash, c.hash, '[ruby-dev:38850]') + assert_equal([true, true], [c.eql?(c1), c1.eql?(c)]) + end + + def test_eql_p + c = Complex(0) + c2 = Complex(0) + c3 = Complex(1) + + assert_operator(c, :eql?, c2) + assert_not_operator(c, :eql?, c3) + + assert_not_operator(c, :eql?, 0) + end + + def test_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 + h[Complex(0,1)] = 1 + h[Complex(1,0)] = 2 + h[Complex(1,1)] = 3 + + assert_equal(4, h.size) + assert_equal(2, h[Complex(1,0)]) + + h[Complex(0,0)] = 9 + assert_equal(4, h.size) + + h[Complex(0.0,0.0)] = 9.0 + assert_equal(5, h.size) + + if (0.0/0).nan? && !((0.0/0).eql?(0.0/0)) + h = {} + 3.times{h[Complex(0.0/0)] = 1} + assert_equal(3, h.size) + end + end + + def test_freeze + c = Complex(1) + assert_predicate(c, :frozen?) + assert_instance_of(String, c.to_s) + end + + def test_conv + c = Complex(0,0) + assert_equal(Complex(0,0), c) + + c = Complex(2**32, 2**32) + assert_equal(Complex(2**32,2**32), c) + assert_equal([2**32,2**32], [c.real,c.imag]) + + c = Complex(-2**32, 2**32) + assert_equal(Complex(-2**32,2**32), c) + assert_equal([-2**32,2**32], [c.real,c.imag]) + + c = Complex(2**32, -2**32) + assert_equal(Complex(2**32,-2**32), c) + assert_equal([2**32,-2**32], [c.real,c.imag]) + + c = Complex(-2**32, -2**32) + assert_equal(Complex(-2**32,-2**32), c) + assert_equal([-2**32,-2**32], [c.real,c.imag]) + + c = Complex(Complex(1,2),2) + assert_equal(Complex(1,4), c) + + c = Complex(2,Complex(1,2)) + assert_equal(Complex(0,1), c) + + c = Complex(Complex(1,2),Complex(1,2)) + assert_equal(Complex(-1,3), c) + + c = Complex::I + assert_equal(Complex(0,1), c) + + assert_equal(Complex(1),Complex(1)) + assert_equal(Complex(1),Complex('1')) + assert_equal(Complex(3.0,3.0),Complex('3.0','3.0')) + 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()} + assert_raise(ArgumentError){Complex(1,2,3)} + c = Complex(1,0) + assert_same(c, Complex(c)) + assert_same(c, Complex(c, exception: false)) + assert_raise(ArgumentError){Complex(c, bad_keyword: true)} + + if (0.0/0).nan? + assert_nothing_raised{Complex(0.0/0)} + end + if (1.0/0).infinite? + assert_nothing_raised{Complex(1.0/0)} + end + end + + def test_attr + c = Complex(4) + + assert_equal(4, c.real) + assert_equal(0, c.imag) + + c = Complex(4,5) + + assert_equal(4, c.real) + assert_equal(5, c.imag) + + if -0.0.to_s == '-0.0' + c = Complex(-0.0,-0.0) + + assert_equal('-0.0', c.real.to_s) + assert_equal('-0.0', c.imag.to_s) + end + + c = Complex(4) + + assert_equal(4, c.real) + assert_equal(0, c.imag) + assert_equal(c.imag, c.imaginary) + + c = Complex(4,5) + + assert_equal(4, c.real) + assert_equal(5, c.imag) + assert_equal(c.imag, c.imaginary) + + if -0.0.to_s == '-0.0' + c = Complex(-0.0,-0.0) + + assert_equal('-0.0', c.real.to_s) + assert_equal('-0.0', c.imag.to_s) + assert_equal(c.imag.to_s, c.imaginary.to_s) + end + + c = Complex(4) + + assert_equal(4, c.real) + assert_equal(c.imag, c.imaginary) + assert_equal(0, c.imag) + + c = Complex(4,5) + + assert_equal(4, c.real) + assert_equal(5, c.imag) + assert_equal(c.imag, c.imaginary) + + c = Complex(-0.0,-0.0) + + assert_equal('-0.0', c.real.to_s) + assert_equal('-0.0', c.imag.to_s) + assert_equal(c.imag.to_s, c.imaginary.to_s) + end + + def test_attr2 + c = Complex(1) + + assert_not_predicate(c, :integer?) + assert_not_predicate(c, :real?) + + 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?) + assert_equal(Complex(1,0), Complex(1,0).nonzero?) + assert_equal(Complex(0,1), Complex(0,1).nonzero?) + assert_equal(Complex(1,1), Complex(1,1).nonzero?) + end + + def test_rect + assert_equal([1,2], Complex.rectangular(1,2).rectangular) + assert_equal([1,2], Complex.rect(1,2).rect) + end + + def test_polar + assert_equal([1,2], Complex.polar(1,2).polar) + assert_equal(Complex.polar(1.0, Math::PI * 2 / 3), Complex.polar(1, Math::PI * 2 / 3)) + + one = 1+0i + c = Complex.polar(0, one) + assert_equal(0, c) + assert_predicate(c.real, :real?) + c = Complex.polar(one, 0) + assert_equal(1, c) + assert_predicate(c.real, :real?) + c = Complex.polar(one) + assert_equal(1, c) + assert_predicate(c.real, :real?) + end + + def test_uplus + assert_equal(Complex(1), +Complex(1)) + assert_equal(Complex(-1), +Complex(-1)) + assert_equal(Complex(1,1), +Complex(1,1)) + assert_equal(Complex(-1,1), +Complex(-1,1)) + assert_equal(Complex(1,-1), +Complex(1,-1)) + assert_equal(Complex(-1,-1), +Complex(-1,-1)) + + if -0.0.to_s == '-0.0' + c = +Complex(0.0,0.0) + assert_equal('0.0', c.real.to_s) + assert_equal('0.0', c.imag.to_s) + + c = +Complex(-0.0,-0.0) + assert_equal('-0.0', c.real.to_s) + assert_equal('-0.0', c.imag.to_s) + end + end + + def test_negate + assert_equal(Complex(-1), -Complex(1)) + assert_equal(Complex(1), -Complex(-1)) + assert_equal(Complex(-1,-1), -Complex(1,1)) + assert_equal(Complex(1,-1), -Complex(-1,1)) + assert_equal(Complex(-1,1), -Complex(1,-1)) + assert_equal(Complex(1,1), -Complex(-1,-1)) + + if -0.0.to_s == '-0.0' + c = -Complex(0.0,0.0) + assert_equal('-0.0', c.real.to_s) + assert_equal('-0.0', c.imag.to_s) + + c = -Complex(-0.0,-0.0) + assert_equal('0.0', c.real.to_s) + assert_equal('0.0', c.imag.to_s) + end + end + + def test_add + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(3,5), c + c2) + + assert_equal(Complex(3,2), c + 2) + assert_equal(Complex(3.0,2), c + 2.0) + + 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 + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(-1,-1), c - c2) + + assert_equal(Complex(-1,2), c - 2) + assert_equal(Complex(-1.0,2), c - 2.0) + + 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 + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(-4,7), c * c2) + + assert_equal(Complex(2,4), c * 2) + assert_equal(Complex(2.0,4.0), c * 2.0) + + 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) + + assert_equal(Complex(Rational(8,13),Rational(1,13)), c / c2) + + c = Complex(1.0,2.0) + c2 = Complex(2.0,3.0) + + r = c / c2 + assert_in_delta(0.615, r.real, 0.001) + assert_in_delta(0.076, r.imag, 0.001) + + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(Rational(1,2),1), c / 2) + assert_equal(Complex(0.5,1.0), c / 2.0) + + assert_equal(Complex(Rational(1,2),Rational(1)), c / Rational(2)) + assert_equal(Complex(Rational(3,2),Rational(3)), c / Rational(2,3)) + + c = Complex(1) + [ 1, Rational(1), c ].each do |d| + r = c / d + assert_instance_of(Complex, r) + assert_equal(1, r) + assert_predicate(r.real, :integer?) + assert_predicate(r.imag, :integer?) + end + end + + def test_quo + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(Rational(8,13),Rational(1,13)), c.quo(c2)) + + c = Complex(1.0,2.0) + c2 = Complex(2.0,3.0) + + r = c.quo(c2) + assert_in_delta(0.615, r.real, 0.001) + assert_in_delta(0.076, r.imag, 0.001) + + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(Rational(1,2),1), c.quo(2)) + assert_equal(Complex(0.5,1.0), c.quo(2.0)) + + 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 + c = Complex(1,2) + c2 = Complex(2,3) + + r = c.fdiv(c2) + assert_in_delta(0.615, r.real, 0.001) + assert_in_delta(0.076, r.imag, 0.001) + + c = Complex(1.0,2.0) + c2 = Complex(2.0,3.0) + + r = c.fdiv(c2) + assert_in_delta(0.615, r.real, 0.001) + assert_in_delta(0.076, r.imag, 0.001) + + c = Complex(1,2) + c2 = Complex(2,3) + + assert_equal(Complex(0.5,1.0), c.fdiv(2)) + assert_equal(Complex(0.5,1.0), c.fdiv(2.0)) + end + + def test_expt + c = Complex(1,2) + c2 = Complex(2,3) + + r = c ** c2 + assert_in_delta(-0.015, r.real, 0.001) + assert_in_delta(-0.179, r.imag, 0.001) + + assert_equal(Complex(-3,4), c ** 2) + 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) + + r = c ** -2.0 + assert_in_delta(-0.12, r.real, 0.001) + assert_in_delta(-0.16, r.imag, 0.001) + + 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(0.432, r.real, 0.001) + assert_in_delta(-0.393, r.imag, 0.001) + end + + def test_expt_for_special_angle + c = Complex(1, 0) ** 100000000000000000000000000000000 + assert_equal(Complex(1, 0), c) + + c = Complex(-1, 0) ** 10000000000000000000000000000000 + assert_equal(Complex(1, 0), c) + + c = Complex(-1, 0) ** 10000000000000000000000000000001 + assert_equal(Complex(-1, 0), c) + + c = Complex(0, 1) ** 100000000000000000000000000000000 + assert_equal(Complex(1, 0), c) + + c = Complex(0, 1) ** 100000000000000000000000000000001 + assert_equal(Complex(0, 1), c) + + c = Complex(0, 1) ** 100000000000000000000000000000002 + assert_equal(Complex(-1, 0), c) + + c = Complex(0, 1) ** 100000000000000000000000000000003 + assert_equal(Complex(0, -1), c) + + c = Complex(0, -1) ** 100000000000000000000000000000000 + assert_equal(Complex(1, 0), c) + + c = Complex(0, -1) ** 100000000000000000000000000000001 + assert_equal(Complex(0, -1), c) + + c = Complex(0, -1) ** 100000000000000000000000000000002 + assert_equal(Complex(-1, 0), c) + + c = Complex(0, -1) ** 100000000000000000000000000000003 + assert_equal(Complex(0, 1), c) + + c = Complex(1, 1) ** 1 + assert_equal(Complex(1, 1), c) + + c = Complex(1, 1) ** 2 + assert_equal(Complex(0, 2), c) + + c = Complex(1, 1) ** 3 + assert_equal(Complex(-2, 2), c) + + c = Complex(1, 1) ** 4 + assert_equal(Complex(-4, 0), c) + + c = Complex(1, 1) ** 5 + assert_equal(Complex(-4, -4), c) + + c = Complex(1, 1) ** 6 + assert_equal(Complex(0, -8), c) + + c = Complex(1, 1) ** 7 + assert_equal(Complex(8, -8), c) + + c = Complex(-2, -2) ** 3 + assert_equal(Complex(16, -16), c) + + c = Complex(2, -2) ** 3 + assert_equal(Complex(-16, -16), c) + + c = Complex(-2, 2) ** 3 + assert_equal(Complex(16, 16), c) + + c = Complex(0.0, -888888888888888.0)**8888 + assert_not_predicate(c.real, :nan?) + assert_not_predicate(c.imag, :nan?) + end + + def test_cmp + assert_nil(Complex(5, 1) <=> Complex(2)) + assert_nil(5 <=> Complex(2, 1)) + + assert_equal(1, Complex(5) <=> Complex(2)) + assert_equal(-1, Complex(2) <=> Complex(3)) + assert_equal(0, Complex(2) <=> Complex(2)) + + assert_equal(1, Complex(5) <=> 2) + assert_equal(-1, Complex(2) <=> 3) + assert_equal(0, Complex(2) <=> 2) + end + + def test_eqeq + assert_equal(Complex(1), Complex(1,0)) + assert_equal(Complex(-1), Complex(-1,0)) + + 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_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 + + def test_coerce + assert_equal([Complex(2),Complex(1)], Complex(1).coerce(2)) + assert_equal([Complex(2.2),Complex(1)], Complex(1).coerce(2.2)) + 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 < Numeric + def initialize(real = true, n = 1) @n = n; @real = real; end + def +(x) Rational(@n) end + alias - + + alias * + + alias / + + alias quo + + alias ** + + def coerce(x) [x, Complex(@n)] end + def real?; @real; end + end + + def test_coerce2 + x = ObjectX.new + y = ObjectX.new(false) + %w(+ - * / quo ** <=>).each do |op| + assert_kind_of(Numeric, Complex(1).__send__(op, x), op) + assert_kind_of(Numeric, Complex(1).__send__(op, y), op) + end + end + + def test_math + c = Complex(1,2) + + assert_in_delta(2.236, c.abs, 0.001) + assert_in_delta(2.236, c.magnitude, 0.001) + assert_equal(5, c.abs2) + + assert_equal(c.abs, Math.sqrt(c * c.conj)) + assert_equal(c.abs, Math.sqrt(c.real**2 + c.imag**2)) + assert_equal(c.abs2, c * c.conj) + assert_equal(c.abs2, c.real**2 + c.imag**2) + + assert_in_delta(1.107, c.arg, 0.001) + assert_in_delta(1.107, c.angle, 0.001) + assert_in_delta(1.107, c.phase, 0.001) + + r = c.polar + assert_in_delta(2.236, r[0], 0.001) + 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.numerator) + assert_equal(1, c.denominator) + end + + def test_to_s + c = Complex(1,2) + + assert_instance_of(String, c.to_s) + assert_equal('1+2i', c.to_s) + + assert_equal('0+2i', Complex(0,2).to_s) + assert_equal('0-2i', Complex(0,-2).to_s) + assert_equal('1+2i', Complex(1,2).to_s) + assert_equal('-1+2i', Complex(-1,2).to_s) + assert_equal('-1-2i', Complex(-1,-2).to_s) + assert_equal('1-2i', Complex(1,-2).to_s) + assert_equal('-1-2i', Complex(-1,-2).to_s) + + assert_equal('0+2.0i', Complex(0,2.0).to_s) + assert_equal('0-2.0i', Complex(0,-2.0).to_s) + 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) + 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) + assert_equal('-1.0-2.0i', Complex(-1.0,-2.0).to_s) + + 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 + if nan.nan? + assert_equal('NaN+NaN*i', Complex(nan,nan).to_s) + end + if inf.infinite? + assert_equal('Infinity+Infinity*i', Complex(inf,inf).to_s) + assert_equal('Infinity-Infinity*i', Complex(inf,-inf).to_s) + end + end + + def test_inspect + c = Complex(1,2) + + assert_instance_of(String, c.inspect) + assert_equal('(1+2i)', c.inspect) + end + + def test_inspect_to_s_frozen_bug_20337 + assert_separately([], <<~'RUBY') + class Numeric + def inspect = super.freeze + end + c = Complex(Numeric.new, 1) + assert_match(/\A\(#<Numeric:/, c.inspect) + assert_match(/\A#<Numeric:/, c.to_s) + RUBY + end + + def test_marshal + c = Complex(1,2) + + s = Marshal.dump(c) + c2 = Marshal.load(s) + assert_equal(c, c2) + assert_instance_of(Complex, c2) + + 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 + + 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 + + def test_parse + assert_equal(Complex(5), '5'.to_c) + assert_equal(Complex(-5), '-5'.to_c) + assert_equal(Complex(5,3), '5+3i'.to_c) + assert_equal(Complex(-5,3), '-5+3i'.to_c) + assert_equal(Complex(5,-3), '5-3i'.to_c) + assert_equal(Complex(-5,-3), '-5-3i'.to_c) + assert_equal(Complex(0,3), '3i'.to_c) + assert_equal(Complex(0,-3), '-3i'.to_c) + assert_equal(Complex(5,1), '5+i'.to_c) + assert_equal(Complex(0,1), 'i'.to_c) + assert_equal(Complex(0,1), '+i'.to_c) + assert_equal(Complex(0,-1), '-i'.to_c) + + assert_equal(Complex(5,3), '5+3I'.to_c) + assert_equal(Complex(5,3), '5+3j'.to_c) + assert_equal(Complex(5,3), '5+3J'.to_c) + assert_equal(Complex(0,3), '3I'.to_c) + assert_equal(Complex(0,3), '3j'.to_c) + assert_equal(Complex(0,3), '3J'.to_c) + assert_equal(Complex(0,1), 'I'.to_c) + assert_equal(Complex(0,1), 'J'.to_c) + + assert_equal(Complex(5.0), '5.0'.to_c) + assert_equal(Complex(-5.0), '-5.0'.to_c) + assert_equal(Complex(5.0,3.0), '5.0+3.0i'.to_c) + assert_equal(Complex(-5.0,3.0), '-5.0+3.0i'.to_c) + assert_equal(Complex(5.0,-3.0), '5.0-3.0i'.to_c) + assert_equal(Complex(-5.0,-3.0), '-5.0-3.0i'.to_c) + 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) + assert_equal(Complex(-5.0,3.0), '-5e0+3e0i'.to_c) + assert_equal(Complex(5.0,-3.0), '5e0-3e0i'.to_c) + assert_equal(Complex(-5.0,-3.0), '-5e0-3e0i'.to_c) + 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) + assert_equal(Complex(-0.33), '-0.33'.to_c) + assert_equal(Complex(-0.33), '-0.3_3'.to_c) + + assert_equal(Complex.polar(10,10), '10@10'.to_c) + assert_equal(Complex.polar(-10,-10), '-10@-10'.to_c) + assert_equal(Complex.polar(10.5,10.5), '10.5@10.5'.to_c) + assert_equal(Complex.polar(-10.5,-10.5), '-10.5@-10.5'.to_c) + + assert_equal(Complex(5), Complex('5')) + assert_equal(Complex(-5), Complex('-5')) + assert_equal(Complex(5,3), Complex('5+3i')) + assert_equal(Complex(-5,3), Complex('-5+3i')) + assert_equal(Complex(5,-3), Complex('5-3i')) + assert_equal(Complex(-5,-3), Complex('-5-3i')) + assert_equal(Complex(0,3), Complex('3i')) + assert_equal(Complex(0,-3), Complex('-3i')) + assert_equal(Complex(5,1), Complex('5+i')) + assert_equal(Complex(0,1), Complex('i')) + assert_equal(Complex(0,1), Complex('+i')) + assert_equal(Complex(0,-1), Complex('-i')) + + assert_equal(Complex(5,3), Complex('5+3I')) + assert_equal(Complex(5,3), Complex('5+3j')) + assert_equal(Complex(5,3), Complex('5+3J')) + assert_equal(Complex(0,3), Complex('3I')) + assert_equal(Complex(0,3), Complex('3j')) + assert_equal(Complex(0,3), Complex('3J')) + assert_equal(Complex(0,1), Complex('I')) + assert_equal(Complex(0,1), Complex('J')) + + assert_equal(Complex(5.0), Complex('5.0')) + assert_equal(Complex(-5.0), Complex('-5.0')) + assert_equal(Complex(5.0,3.0), Complex('5.0+3.0i')) + assert_equal(Complex(-5.0,3.0), Complex('-5.0+3.0i')) + assert_equal(Complex(5.0,-3.0), Complex('5.0-3.0i')) + assert_equal(Complex(-5.0,-3.0), Complex('-5.0-3.0i')) + 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')) + assert_equal(Complex(-5.0,3.0), Complex('-5e0+3e0i')) + assert_equal(Complex(5.0,-3.0), Complex('5e0-3e0i')) + assert_equal(Complex(-5.0,-3.0), Complex('-5e0-3e0i')) + 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')) + assert_equal(Complex(-0.33), Complex('-0.33')) + assert_equal(Complex(-0.33), Complex('-0.3_3')) + + assert_equal(Complex.polar(10,10), Complex('10@10')) + assert_equal(Complex.polar(-10,-10), Complex('-10@-10')) + assert_equal(Complex.polar(10.5,10.5), Complex('10.5@10.5')) + assert_equal(Complex.polar(-10.5,-10.5), Complex('-10.5@-10.5')) + + assert_equal(Complex(0), ''.to_c) + assert_equal(Complex(0), ' '.to_c) + assert_equal(Complex(5), "\f\n\r\t\v5\0".to_c) + assert_equal(Complex(0), '_'.to_c) + assert_equal(Complex(0), '_5'.to_c) + assert_equal(Complex(5), '5_'.to_c) + assert_equal(Complex(5), '5x'.to_c) + assert_equal(Complex(51), '5_1'.to_c) + assert_equal(Complex(5), '5__1'.to_c) + assert_equal(Complex(5), '5+_3i'.to_c) + assert_equal(Complex(5), '5+3_i'.to_c) + assert_equal(Complex(5,3), '5+3i_'.to_c) + assert_equal(Complex(5,3), '5+3ix'.to_c) + assert_equal(Complex(5,31), '5+3_1i'.to_c) + assert_equal(Complex(5), '5+3__1i'.to_c) + assert_equal(Complex(51), Complex('5_1')) + assert_equal(Complex(5,31), Complex('5+3_1i')) + assert_equal(Complex(5,31), Complex('5+3_1I')) + assert_equal(Complex(5,31), Complex('5+3_1j')) + assert_equal(Complex(5,31), Complex('5+3_1J')) + assert_equal(Complex(0,31), Complex('3_1i')) + assert_equal(Complex(0,31), Complex('3_1I')) + assert_equal(Complex(0,31), Complex('3_1j')) + assert_equal(Complex(0,31), Complex('3_1J')) + assert_raise(ArgumentError){ Complex('')} + assert_raise(ArgumentError){ Complex('_')} + assert_raise(ArgumentError){ Complex("\f\n\r\t\v5\0")} + assert_raise(ArgumentError){ Complex('_5')} + assert_raise(ArgumentError){ Complex('5_')} + assert_raise(ArgumentError){ Complex('5__1')} + assert_raise(ArgumentError){ Complex('5x')} + assert_raise(ArgumentError){ Complex('5+_3i')} + assert_raise(ArgumentError){ Complex('5+3_i')} + assert_raise(ArgumentError){ Complex('5+3i_')} + assert_raise(ArgumentError){ Complex('5+3ix')} + assert_raise(ArgumentError){ Complex('5+3__1i')} + assert_raise(ArgumentError){ Complex('5+3__1I')} + assert_raise(ArgumentError){ Complex('5+3__1j')} + assert_raise(ArgumentError){ Complex('5+3__1J')} + assert_raise(ArgumentError){ Complex('3__1i')} + assert_raise(ArgumentError){ Complex('3__1I')} + assert_raise(ArgumentError){ Complex('3__1j')} + assert_raise(ArgumentError){ Complex('3__1J')} + + assert_equal(Complex(Rational(1,5)), '1/5'.to_c) + assert_equal(Complex(Rational(-1,5)), '-1/5'.to_c) + 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_with_invalid_exception + assert_raise(ArgumentError) { + Complex("0", exception: 1) + } + end + + def assert_complex_with_exception(error, *args, message: "") + assert_raise(error, message) do + Complex(*args, exception: true) + end + assert_nothing_raised(error, message) do + assert_nil(Complex(*args, exception: false)) + assert_nil($!) + end + end + + def test_Complex_with_exception + assert_complex_with_exception(ArgumentError, '5x') + assert_complex_with_exception(TypeError, nil) + assert_complex_with_exception(TypeError, Object.new) + assert_complex_with_exception(TypeError, 1, nil) + assert_complex_with_exception(TypeError, 1, Object.new) + + o = Object.new + def o.to_c; raise; end + assert_complex_with_exception(RuntimeError, o) + assert_complex_with_exception(TypeError, 1, o) + end + + def test_respond + c = Complex(1,1) + assert_not_respond_to(c, :%) + assert_not_respond_to(c, :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 + assert_equal(3, Complex(3).to_i) + assert_equal(3, Integer(Complex(3))) + assert_raise(RangeError){Complex(3,2).to_i} + assert_raise(RangeError){Integer(Complex(3,2))} + end + + def test_to_f + assert_equal(3.0, Complex(3).to_f) + assert_equal(3.0, Float(Complex(3))) + assert_raise(RangeError){Complex(3,2).to_f} + assert_raise(RangeError){Float(Complex(3,2))} + end + + def test_to_r + 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_r_with_float + assert_equal(Rational(3), Complex(3, 0.0).to_r) + assert_raise(RangeError){Complex(3, 1.0).to_r} + end + + def test_to_r_with_numeric_obj + c = Class.new(Numeric) + + num = 0 + c.define_method(:to_s) { num.to_s } + c.define_method(:==) { num == it } + c.define_method(:<) { num < it } + + o = c.new + assert_equal(Rational(3), Complex(3, o).to_r) + + num = 1 + assert_raise(RangeError){Complex(3, o).to_r} + + c.define_method(:to_r) { 0r } + assert_equal(Rational(3), Complex(3, o).to_r) + end + + def test_to_c + c = nil.to_c + assert_equal([0,0], [c.real, c.imag]) + + c = 0.to_c + assert_equal([0,0], [c.real, c.imag]) + + c = 1.to_c + assert_equal([1,0], [c.real, c.imag]) + + c = 1.1.to_c + assert_equal([1.1, 0], [c.real, c.imag]) + + 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]) + + if (0.0/0).nan? + assert_nothing_raised{(0.0/0).to_c} + end + if (1.0/0).infinite? + assert_nothing_raised{(1.0/0).to_c} + 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_predicate(1, :real?) + assert_predicate(1.1, :real?) + + assert_equal(1, 1.real) + assert_equal(0, 1.imag) + assert_equal(0, 1.imaginary) + + assert_equal(1.1, 1.1.real) + assert_equal(0, 1.1.imag) + assert_equal(0, 1.1.imaginary) + + assert_equal(1, 1.magnitude) + assert_equal(1, -1.magnitude) + assert_equal(1, 1.0.magnitude) + assert_equal(1, -1.0.magnitude) + + assert_equal(4, 2.abs2) + assert_equal(4, -2.abs2) + assert_equal(4.0, 2.0.abs2) + assert_equal(4.0, -2.0.abs2) + + assert_equal(0, 1.arg) + assert_equal(0, 1.angle) + assert_equal(0, 1.phase) + + assert_equal(0, 1.0.arg) + assert_equal(0, 1.0.angle) + assert_equal(0, 1.0.phase) + + if (0.0/0).nan? + nan = 0.0/0 + assert_same(nan, nan.arg) + assert_same(nan, nan.angle) + assert_same(nan, nan.phase) + end + + assert_equal(Math::PI, -1.arg) + assert_equal(Math::PI, -1.angle) + assert_equal(Math::PI, -1.phase) + + assert_equal(Math::PI, -1.0.arg) + assert_equal(Math::PI, -1.0.angle) + assert_equal(Math::PI, -1.0.phase) + + assert_equal([1,0], 1.rect) + assert_equal([-1,0], -1.rect) + assert_equal([1,0], 1.rectangular) + assert_equal([-1,0], -1.rectangular) + + assert_equal([1.0,0], 1.0.rect) + assert_equal([-1.0,0], -1.0.rect) + assert_equal([1.0,0], 1.0.rectangular) + assert_equal([-1.0,0], -1.0.rectangular) + + assert_equal([1,0], 1.polar) + assert_equal([1, Math::PI], -1.polar) + + assert_equal([1.0,0], 1.0.polar) + assert_equal([1.0, Math::PI], -1.0.polar) + + assert_equal(1, 1.conjugate) + assert_equal(-1, -1.conjugate) + assert_equal(1, 1.conj) + assert_equal(-1, -1.conj) + + assert_equal(1.1, 1.1.conjugate) + assert_equal(-1.1, -1.1.conjugate) + assert_equal(1.1, 1.1.conj) + assert_equal(-1.1, -1.1.conj) + + 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)) + assert_equal(0.25, Rational(1,2).fdiv(2)) + assert_equal(Complex(0.5,1.0), Complex(1,2).quo(2)) + end + + def test_ruby19 + assert_raise(NoMethodError){ Complex.new(1) } + assert_raise(NoMethodError){ Complex.new!(1) } + assert_raise(NoMethodError){ Complex.reduce(1) } + end + + def test_fixed_bug + 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 + error = "not a real" + assert_raise_with_message(TypeError, error) do + Complex.polar(1i) + end + assert_raise_with_message(TypeError, error) do + Complex.polar(1i, 0) + end + assert_raise_with_message(TypeError, error) do + Complex.polar(0, 1i) + end + n = Class.new(Numeric) do + def initialize(x = 1) + @x = x + end + def real? + (@x -= 1) > 0 + end + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(obj) + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(obj, 0) + end + obj = n.new + assert_raise_with_message(TypeError, error) do + Complex.polar(1, obj) + end + end +end diff --git a/test/ruby/test_complex2.rb b/test/ruby/test_complex2.rb new file mode 100644 index 0000000000..b89e83efb2 --- /dev/null +++ b/test/ruby/test_complex2.rb @@ -0,0 +1,736 @@ +# frozen_string_literal: false +require 'test/unit' + +class Complex_Test2 < Test::Unit::TestCase + + def test_kumi + omit unless defined?(Rational) + + assert_equal(Complex(1, 0), +Complex(1, 0)) + assert_equal(Complex(-1, 0), -Complex(1, 0)) + assert_equal(Complex(2, 0), + Complex(1, 0) + Complex(1, 0)) + assert_equal(Complex(0, 0), + Complex(1, 0) - Complex(1, 0)) + assert_equal(Complex(1, 0), + Complex(1, 0) * Complex(1, 0)) + assert_equal(Complex(1, 0), + Complex(1, 0) / Complex(1, 0)) + assert_equal(Complex(1073741790, 0), + Complex(1, 0) + Complex(1073741789, 0)) + assert_equal(Complex(-1073741788, 0), + Complex(1, 0) - Complex(1073741789, 0)) + assert_equal(Complex(1073741789, 0), + Complex(1, 0) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1, 1073741789), 0), + Complex(1, 0) / Complex(1073741789, 0)) + assert_equal(Complex(1073741828, 0), + Complex(1, 0) + Complex(1073741827, 0)) + assert_equal(Complex(-1073741826, 0), + Complex(1, 0) - Complex(1073741827, 0)) + assert_equal(Complex(1073741827, 0), + Complex(1, 0) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1, 1073741827), 0), + Complex(1, 0) / Complex(1073741827, 0)) + assert_equal(Complex(1073741790, 1073741789), + Complex(1, 0) + Complex(1073741789, 1073741789)) + assert_equal(Complex(-1073741788, -1073741789), + Complex(1, 0) - Complex(1073741789, 1073741789)) + assert_equal(Complex(1073741789, 1073741789), + Complex(1, 0) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1, 2147483578), Rational(-1, 2147483578)), + Complex(1, 0) / Complex(1073741789, 1073741789)) + assert_equal(Complex(1073741790, 1073741827), + Complex(1, 0) + Complex(1073741789, 1073741827)) + assert_equal(Complex(-1073741788, -1073741827), + Complex(1, 0) - Complex(1073741789, 1073741827)) + assert_equal(Complex(1073741789, 1073741827), + Complex(1, 0) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1073741789, 2305842940494218450), Rational(-1073741827, 2305842940494218450)), + Complex(1, 0) / Complex(1073741789, 1073741827)) + assert_equal(Complex(1073741828, 1073741827), + Complex(1, 0) + Complex(1073741827, 1073741827)) + assert_equal(Complex(-1073741826, -1073741827), + Complex(1, 0) - Complex(1073741827, 1073741827)) + assert_equal(Complex(1073741827, 1073741827), + Complex(1, 0) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1, 2147483654), Rational(-1, 2147483654)), + Complex(1, 0) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(2147483616, 1073741827), Rational(1073741789, 1073741827)), + Complex(1, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(38, 1073741827), Rational(-1073741789, 1073741827)), + Complex(1, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)), + Complex(1, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1073741827, 2147483578), Rational(-1073741827, 2147483578)), + Complex(1, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(2147483616, 1073741789), Rational(1073741827, 1073741789)), + Complex(1, 0) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-38, 1073741789), Rational(-1073741827, 1073741789)), + Complex(1, 0) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)), + Complex(1, 0) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741789, 2147483654), Rational(-1073741789, 2147483654)), + Complex(1, 0) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2147483616, 1073741827), Rational(1073741827, 1073741789)), + Complex(1, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(38, 1073741827), Rational(-1073741827, 1073741789)), + Complex(1, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)), + Complex(1, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1329227869515035739611240300898290063, 2658455833113515253509575011810600482), Rational(-1329227963598474519442525600436190287, 2658455833113515253509575011810600482)), + Complex(1, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741789, 0), +Complex(1073741789, 0)) + assert_equal(Complex(-1073741789, 0), -Complex(1073741789, 0)) + assert_equal(Complex(1073741790, 0), + Complex(1073741789, 0) + Complex(1, 0)) + assert_equal(Complex(1073741788, 0), + Complex(1073741789, 0) - Complex(1, 0)) + assert_equal(Complex(1073741789, 0), + Complex(1073741789, 0) * Complex(1, 0)) + assert_equal(Complex(1073741789, 0), + Complex(1073741789, 0) / Complex(1, 0)) + assert_equal(Complex(2147483578, 0), + Complex(1073741789, 0) + Complex(1073741789, 0)) + assert_equal(Complex(0, 0), + Complex(1073741789, 0) - Complex(1073741789, 0)) + assert_equal(Complex(1152921429444920521, 0), + Complex(1073741789, 0) * Complex(1073741789, 0)) + assert_equal(Complex(1, 0), + Complex(1073741789, 0) / Complex(1073741789, 0)) + assert_equal(Complex(2147483616, 0), + Complex(1073741789, 0) + Complex(1073741827, 0)) + assert_equal(Complex(-38, 0), + Complex(1073741789, 0) - Complex(1073741827, 0)) + assert_equal(Complex(1152921470247108503, 0), + Complex(1073741789, 0) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), 0), + Complex(1073741789, 0) / Complex(1073741827, 0)) + assert_equal(Complex(2147483578, 1073741789), + Complex(1073741789, 0) + Complex(1073741789, 1073741789)) + assert_equal(Complex(0, -1073741789), + Complex(1073741789, 0) - Complex(1073741789, 1073741789)) + assert_equal(Complex(1152921429444920521, 1152921429444920521), + Complex(1073741789, 0) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1, 2), Rational(-1, 2)), + Complex(1073741789, 0) / Complex(1073741789, 1073741789)) + assert_equal(Complex(2147483578, 1073741827), + Complex(1073741789, 0) + Complex(1073741789, 1073741827)) + assert_equal(Complex(0, -1073741827), + Complex(1073741789, 0) - Complex(1073741789, 1073741827)) + assert_equal(Complex(1152921429444920521, 1152921470247108503), + Complex(1073741789, 0) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921429444920521, 2305842940494218450), Rational(-1152921470247108503, 2305842940494218450)), + Complex(1073741789, 0) / Complex(1073741789, 1073741827)) + assert_equal(Complex(2147483616, 1073741827), + Complex(1073741789, 0) + Complex(1073741827, 1073741827)) + assert_equal(Complex(-38, -1073741827), + Complex(1073741789, 0) - Complex(1073741827, 1073741827)) + assert_equal(Complex(1152921470247108503, 1152921470247108503), + Complex(1073741789, 0) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1073741789, 2147483654), Rational(-1073741789, 2147483654)), + Complex(1073741789, 0) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1073741789, 1073741827)), + Complex(1073741789, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(-1073741789, 1073741827)), + Complex(1073741789, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921429444920521, 1073741827), Rational(1152921429444920521, 1073741827)), + Complex(1073741789, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1073741827, 2), Rational(-1073741827, 2)), + Complex(1073741789, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1073741827, 1073741789)), + Complex(1073741789, 0) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921428371178694, 1073741789), Rational(-1073741827, 1073741789)), + Complex(1073741789, 0) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741827, 1073741827), + Complex(1073741789, 0) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921429444920521, 2147483654), Rational(-1152921429444920521, 2147483654)), + Complex(1073741789, 0) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1073741827, 1073741789)), + Complex(1073741789, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(-1073741827, 1073741789)), + Complex(1073741789, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921429444920521, 1073741827), 1073741827), + Complex(1073741789, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1427247510601733037449111325195428279286542707, 2658455833113515253509575011810600482), Rational(-1427247611623052908177132720890654139107803443, 2658455833113515253509575011810600482)), + Complex(1073741789, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741827, 0), +Complex(1073741827, 0)) + assert_equal(Complex(-1073741827, 0), -Complex(1073741827, 0)) + assert_equal(Complex(1073741828, 0), + Complex(1073741827, 0) + Complex(1, 0)) + assert_equal(Complex(1073741826, 0), + Complex(1073741827, 0) - Complex(1, 0)) + assert_equal(Complex(1073741827, 0), + Complex(1073741827, 0) * Complex(1, 0)) + assert_equal(Complex(1073741827, 0), + Complex(1073741827, 0) / Complex(1, 0)) + assert_equal(Complex(2147483616, 0), + Complex(1073741827, 0) + Complex(1073741789, 0)) + assert_equal(Complex(38, 0), + Complex(1073741827, 0) - Complex(1073741789, 0)) + assert_equal(Complex(1152921470247108503, 0), + Complex(1073741827, 0) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1073741827, 1073741789), 0), + Complex(1073741827, 0) / Complex(1073741789, 0)) + assert_equal(Complex(2147483654, 0), + Complex(1073741827, 0) + Complex(1073741827, 0)) + assert_equal(Complex(0, 0), + Complex(1073741827, 0) - Complex(1073741827, 0)) + assert_equal(Complex(1152921511049297929, 0), + Complex(1073741827, 0) * Complex(1073741827, 0)) + assert_equal(Complex(1, 0), + Complex(1073741827, 0) / Complex(1073741827, 0)) + assert_equal(Complex(2147483616, 1073741789), + Complex(1073741827, 0) + Complex(1073741789, 1073741789)) + assert_equal(Complex(38, -1073741789), + Complex(1073741827, 0) - Complex(1073741789, 1073741789)) + assert_equal(Complex(1152921470247108503, 1152921470247108503), + Complex(1073741827, 0) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1073741827, 2147483578), Rational(-1073741827, 2147483578)), + Complex(1073741827, 0) / Complex(1073741789, 1073741789)) + assert_equal(Complex(2147483616, 1073741827), + Complex(1073741827, 0) + Complex(1073741789, 1073741827)) + assert_equal(Complex(38, -1073741827), + Complex(1073741827, 0) - Complex(1073741789, 1073741827)) + assert_equal(Complex(1152921470247108503, 1152921511049297929), + Complex(1073741827, 0) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921470247108503, 2305842940494218450), Rational(-1152921511049297929, 2305842940494218450)), + Complex(1073741827, 0) / Complex(1073741789, 1073741827)) + assert_equal(Complex(2147483654, 1073741827), + Complex(1073741827, 0) + Complex(1073741827, 1073741827)) + assert_equal(Complex(0, -1073741827), + Complex(1073741827, 0) - Complex(1073741827, 1073741827)) + assert_equal(Complex(1152921511049297929, 1152921511049297929), + Complex(1073741827, 0) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1, 2), Rational(-1, 2)), + Complex(1073741827, 0) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1073741789, 1073741827)), + Complex(1073741827, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921509975556140, 1073741827), Rational(-1073741789, 1073741827)), + Complex(1073741827, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(1073741789, 1073741789), + Complex(1073741827, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921511049297929, 2147483578), Rational(-1152921511049297929, 2147483578)), + Complex(1073741827, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921471320850330, 1073741789), Rational(1073741827, 1073741789)), + Complex(1073741827, 0) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921469173366676, 1073741789), Rational(-1073741827, 1073741789)), + Complex(1073741827, 0) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921511049297929, 1073741789), Rational(1152921511049297929, 1073741789)), + Complex(1073741827, 0) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741789, 2), Rational(-1073741789, 2)), + Complex(1073741827, 0) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1073741827, 1073741789)), + Complex(1073741827, 0) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921509975556140, 1073741827), Rational(-1073741827, 1073741789)), + Complex(1073741827, 0) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741789, Rational(1152921511049297929, 1073741789)), + Complex(1073741827, 0) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1427247561112392079020469430422559713421565101, 2658455833113515253509575011810600482), Rational(-1427247662133715524919164459706626955683034349, 2658455833113515253509575011810600482)), + Complex(1073741827, 0) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741789, 1073741789), +Complex(1073741789, 1073741789)) + assert_equal(Complex(-1073741789, -1073741789), -Complex(1073741789, 1073741789)) + assert_equal(Complex(1073741790, 1073741789), + Complex(1073741789, 1073741789) + Complex(1, 0)) + assert_equal(Complex(1073741788, 1073741789), + Complex(1073741789, 1073741789) - Complex(1, 0)) + assert_equal(Complex(1073741789, 1073741789), + Complex(1073741789, 1073741789) * Complex(1, 0)) + assert_equal(Complex(1073741789, 1073741789), + Complex(1073741789, 1073741789) / Complex(1, 0)) + assert_equal(Complex(2147483578, 1073741789), + Complex(1073741789, 1073741789) + Complex(1073741789, 0)) + assert_equal(Complex(0, 1073741789), + Complex(1073741789, 1073741789) - Complex(1073741789, 0)) + assert_equal(Complex(1152921429444920521, 1152921429444920521), + Complex(1073741789, 1073741789) * Complex(1073741789, 0)) + assert_equal(Complex(1, 1), + Complex(1073741789, 1073741789) / Complex(1073741789, 0)) + assert_equal(Complex(2147483616, 1073741789), + Complex(1073741789, 1073741789) + Complex(1073741827, 0)) + assert_equal(Complex(-38, 1073741789), + Complex(1073741789, 1073741789) - Complex(1073741827, 0)) + assert_equal(Complex(1152921470247108503, 1152921470247108503), + Complex(1073741789, 1073741789) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)), + Complex(1073741789, 1073741789) / Complex(1073741827, 0)) + assert_equal(Complex(2147483578, 2147483578), + Complex(1073741789, 1073741789) + Complex(1073741789, 1073741789)) + assert_equal(Complex(0, 0), + Complex(1073741789, 1073741789) - Complex(1073741789, 1073741789)) + assert_equal(Complex(0, 2305842858889841042), + Complex(1073741789, 1073741789) * Complex(1073741789, 1073741789)) + assert_equal(Complex(1, 0), + Complex(1073741789, 1073741789) / Complex(1073741789, 1073741789)) + assert_equal(Complex(2147483578, 2147483616), + Complex(1073741789, 1073741789) + Complex(1073741789, 1073741827)) + assert_equal(Complex(0, -38), + Complex(1073741789, 1073741789) - Complex(1073741789, 1073741827)) + assert_equal(Complex(-40802187982, 2305842899692029024), + Complex(1073741789, 1073741789) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921449846014512, 1152921470247109225), Rational(-20401093991, 1152921470247109225)), + Complex(1073741789, 1073741789) / Complex(1073741789, 1073741827)) + assert_equal(Complex(2147483616, 2147483616), + Complex(1073741789, 1073741789) + Complex(1073741827, 1073741827)) + assert_equal(Complex(-38, -38), + Complex(1073741789, 1073741789) - Complex(1073741827, 1073741827)) + assert_equal(Complex(0, 2305842940494217006), + Complex(1073741789, 1073741789) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1073741789, 1073741827), 0), + Complex(1073741789, 1073741789) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921471320850292, 1073741827)), + Complex(1073741789, 1073741789) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(1152921469173366714, 1073741827)), + Complex(1073741789, 1073741789) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, Rational(2305842858889841042, 1073741827)), + Complex(1073741789, 1073741789) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(1073741827, 0), + Complex(1073741789, 1073741789) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1152921430518662348, 1073741789)), + Complex(1073741789, 1073741789) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921428371178694, 1073741789), Rational(1152921428371178694, 1073741789)), + Complex(1073741789, 1073741789) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, 2147483654), + Complex(1073741789, 1073741789) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921429444920521, 1073741827), 0), + Complex(1073741789, 1073741789) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921430518662348, 1073741789)), + Complex(1073741789, 1073741789) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(1152921428371178694, 1073741789)), + Complex(1073741789, 1073741789) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1073741827), Rational(2305842940494218450, 1073741827)), + Complex(1073741789, 1073741789) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1427247561112392972813122023043041209197173075, 1329227916556757626754787505905300241), Rational(-50510659935364010697847612929910630368, 1329227916556757626754787505905300241)), + Complex(1073741789, 1073741789) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741789, 1073741827), +Complex(1073741789, 1073741827)) + assert_equal(Complex(-1073741789, -1073741827), -Complex(1073741789, 1073741827)) + assert_equal(Complex(1073741790, 1073741827), + Complex(1073741789, 1073741827) + Complex(1, 0)) + assert_equal(Complex(1073741788, 1073741827), + Complex(1073741789, 1073741827) - Complex(1, 0)) + assert_equal(Complex(1073741789, 1073741827), + Complex(1073741789, 1073741827) * Complex(1, 0)) + assert_equal(Complex(1073741789, 1073741827), + Complex(1073741789, 1073741827) / Complex(1, 0)) + assert_equal(Complex(2147483578, 1073741827), + Complex(1073741789, 1073741827) + Complex(1073741789, 0)) + assert_equal(Complex(0, 1073741827), + Complex(1073741789, 1073741827) - Complex(1073741789, 0)) + assert_equal(Complex(1152921429444920521, 1152921470247108503), + Complex(1073741789, 1073741827) * Complex(1073741789, 0)) + assert_equal(Complex(1, Rational(1073741827, 1073741789)), + Complex(1073741789, 1073741827) / Complex(1073741789, 0)) + assert_equal(Complex(2147483616, 1073741827), + Complex(1073741789, 1073741827) + Complex(1073741827, 0)) + assert_equal(Complex(-38, 1073741827), + Complex(1073741789, 1073741827) - Complex(1073741827, 0)) + assert_equal(Complex(1152921470247108503, 1152921511049297929), + Complex(1073741789, 1073741827) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), 1), + Complex(1073741789, 1073741827) / Complex(1073741827, 0)) + assert_equal(Complex(2147483578, 2147483616), + Complex(1073741789, 1073741827) + Complex(1073741789, 1073741789)) + assert_equal(Complex(0, 38), + Complex(1073741789, 1073741827) - Complex(1073741789, 1073741789)) + assert_equal(Complex(-40802187982, 2305842899692029024), + Complex(1073741789, 1073741827) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1073741808, 1073741789), Rational(19, 1073741789)), + Complex(1073741789, 1073741827) / Complex(1073741789, 1073741789)) + assert_equal(Complex(2147483578, 2147483654), + Complex(1073741789, 1073741827) + Complex(1073741789, 1073741827)) + assert_equal(Complex(0, 0), + Complex(1073741789, 1073741827) - Complex(1073741789, 1073741827)) + assert_equal(Complex(-81604377408, 2305842940494217006), + Complex(1073741789, 1073741827) * Complex(1073741789, 1073741827)) + assert_equal(Complex(1, 0), + Complex(1073741789, 1073741827) / Complex(1073741789, 1073741827)) + assert_equal(Complex(2147483616, 2147483654), + Complex(1073741789, 1073741827) + Complex(1073741827, 1073741827)) + assert_equal(Complex(-38, 0), + Complex(1073741789, 1073741827) - Complex(1073741827, 1073741827)) + assert_equal(Complex(-40802189426, 2305842981296406432), + Complex(1073741789, 1073741827) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1073741808, 1073741827), Rational(19, 1073741827)), + Complex(1073741789, 1073741827) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921512123039718, 1073741827)), + Complex(1073741789, 1073741827) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(1152921509975556140, 1073741827)), + Complex(1073741789, 1073741827) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(-40802187982, 1073741827), Rational(2305842899692029024, 1073741827)), + Complex(1073741789, 1073741827) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921490648203216, 1073741789), Rational(20401094713, 1073741789)), + Complex(1073741789, 1073741827) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1152921471320850330, 1073741789)), + Complex(1073741789, 1073741827) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921428371178694, 1073741789), Rational(1152921469173366676, 1073741789)), + Complex(1073741789, 1073741827) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-40802189426, 1073741789), Rational(2305842981296406432, 1073741789)), + Complex(1073741789, 1073741827) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921449846014512, 1073741827), Rational(20401093991, 1073741827)), + Complex(1073741789, 1073741827) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921471320850330, 1073741789)), + Complex(1073741789, 1073741827) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921469173366714, 1073741827), Rational(1152921469173366676, 1073741789)), + Complex(1073741789, 1073741827) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-131433047608170424214, 1152921470247108503), 2147483616), + Complex(1073741789, 1073741827) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1427247586367724281184137892451027617484788528, 1329227916556757626754787505905300241), Rational(-25255330414578331645234047212843119171, 1329227916556757626754787505905300241)), + Complex(1073741789, 1073741827) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741827, 1073741827), +Complex(1073741827, 1073741827)) + assert_equal(Complex(-1073741827, -1073741827), -Complex(1073741827, 1073741827)) + assert_equal(Complex(1073741828, 1073741827), + Complex(1073741827, 1073741827) + Complex(1, 0)) + assert_equal(Complex(1073741826, 1073741827), + Complex(1073741827, 1073741827) - Complex(1, 0)) + assert_equal(Complex(1073741827, 1073741827), + Complex(1073741827, 1073741827) * Complex(1, 0)) + assert_equal(Complex(1073741827, 1073741827), + Complex(1073741827, 1073741827) / Complex(1, 0)) + assert_equal(Complex(2147483616, 1073741827), + Complex(1073741827, 1073741827) + Complex(1073741789, 0)) + assert_equal(Complex(38, 1073741827), + Complex(1073741827, 1073741827) - Complex(1073741789, 0)) + assert_equal(Complex(1152921470247108503, 1152921470247108503), + Complex(1073741827, 1073741827) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)), + Complex(1073741827, 1073741827) / Complex(1073741789, 0)) + assert_equal(Complex(2147483654, 1073741827), + Complex(1073741827, 1073741827) + Complex(1073741827, 0)) + assert_equal(Complex(0, 1073741827), + Complex(1073741827, 1073741827) - Complex(1073741827, 0)) + assert_equal(Complex(1152921511049297929, 1152921511049297929), + Complex(1073741827, 1073741827) * Complex(1073741827, 0)) + assert_equal(Complex(1, 1), + Complex(1073741827, 1073741827) / Complex(1073741827, 0)) + assert_equal(Complex(2147483616, 2147483616), + Complex(1073741827, 1073741827) + Complex(1073741789, 1073741789)) + assert_equal(Complex(38, 38), + Complex(1073741827, 1073741827) - Complex(1073741789, 1073741789)) + assert_equal(Complex(0, 2305842940494217006), + Complex(1073741827, 1073741827) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1073741827, 1073741789), 0), + Complex(1073741827, 1073741827) / Complex(1073741789, 1073741789)) + assert_equal(Complex(2147483616, 2147483654), + Complex(1073741827, 1073741827) + Complex(1073741789, 1073741827)) + assert_equal(Complex(38, 0), + Complex(1073741827, 1073741827) - Complex(1073741789, 1073741827)) + assert_equal(Complex(-40802189426, 2305842981296406432), + Complex(1073741827, 1073741827) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921490648203216, 1152921470247109225), Rational(-20401094713, 1152921470247109225)), + Complex(1073741827, 1073741827) / Complex(1073741789, 1073741827)) + assert_equal(Complex(2147483654, 2147483654), + Complex(1073741827, 1073741827) + Complex(1073741827, 1073741827)) + assert_equal(Complex(0, 0), + Complex(1073741827, 1073741827) - Complex(1073741827, 1073741827)) + assert_equal(Complex(0, 2305843022098595858), + Complex(1073741827, 1073741827) * Complex(1073741827, 1073741827)) + assert_equal(Complex(1, 0), + Complex(1073741827, 1073741827) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1152921512123039718, 1073741827)), + Complex(1073741827, 1073741827) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921509975556140, 1073741827), Rational(1152921509975556140, 1073741827)), + Complex(1073741827, 1073741827) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, 2147483578), + Complex(1073741827, 1073741827) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921511049297929, 1073741789), 0), + Complex(1073741827, 1073741827) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921471320850330, 1073741789), Rational(1152921471320850330, 1073741789)), + Complex(1073741827, 1073741827) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921469173366676, 1073741789), Rational(1152921469173366676, 1073741789)), + Complex(1073741827, 1073741827) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, Rational(2305843022098595858, 1073741789)), + Complex(1073741827, 1073741827) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(1073741789, 0), + Complex(1073741827, 1073741827) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1152921471320850330, 1073741789)), + Complex(1073741827, 1073741827) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921509975556140, 1073741827), Rational(1152921469173366676, 1073741789)), + Complex(1073741827, 1073741827) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1073741789), Rational(2305842940494218450, 1073741789)), + Complex(1073741827, 1073741827) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1427247611623053801969816945064593334552299725, 1329227916556757626754787505905300241), Rational(-50510661722949347514642033621130734624, 1329227916556757626754787505905300241)), + Complex(1073741827, 1073741827) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)), +Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(-1073741789, 1073741827), Rational(-1073741789, 1073741827)), -Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(2147483616, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1, 0)) + assert_equal(Complex(Rational(-38, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1, 0)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1073741789, 0)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1073741789, 0)) + assert_equal(Complex(Rational(1152921429444920521, 1073741827), Rational(1152921429444920521, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1, 1073741827), Rational(1, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1073741789, 0)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1073741827, 0)) + assert_equal(Complex(Rational(-1152921509975556140, 1073741827), Rational(1073741789, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1073741827, 0)) + assert_equal(Complex(1073741789, 1073741789), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1073741789, 1152921511049297929), Rational(1073741789, 1152921511049297929)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1073741827, 0)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921471320850292, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(-1152921469173366714, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1073741789, 1073741789)) + assert_equal(Complex(0, Rational(2305842858889841042, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1, 1073741827), 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921512123039718, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(-1152921509975556140, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-40802187982, 1073741827), Rational(2305842899692029024, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921449846014512, 1237940005850657200720054075), Rational(-20401093991, 1237940005850657200720054075)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1152921512123039718, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(-1152921509975556140, 1073741827), Rational(-1152921509975556140, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(1073741827, 1073741827)) + assert_equal(Complex(0, 2147483578), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1073741789, 1152921511049297929), 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(2147483578, 1073741827), Rational(2147483578, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, Rational(2305842858889841042, 1152921511049297929)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(1, 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(2305842940494218450, 1152921470247108503), Rational(2305842940494218450, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1152921470247108503), Rational(-81604377408, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, 2), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921429444920521, 1152921511049297929), 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2147483578, 1073741827), Rational(2305842940494218450, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, Rational(-81604377408, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1152921511049297929), Rational(2305842940494218450, 1152921511049297929)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1329227869515036572020512360130906225, 1329227916556757626754787505905300241), Rational(-47041717725097069072123994784, 1329227916556757626754787505905300241)), + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827)) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)), +Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-1073741827, 1073741789), Rational(-1073741827, 1073741789)), -Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2147483616, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1, 0)) + assert_equal(Complex(Rational(38, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1, 0)) + assert_equal(Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1, 0)) + assert_equal(Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1, 0)) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1073741789, 0)) + assert_equal(Complex(Rational(-1152921428371178694, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1073741789, 0)) + assert_equal(Complex(1073741827, 1073741827), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1073741827, 1152921429444920521), Rational(1073741827, 1152921429444920521)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1073741789, 0)) + assert_equal(Complex(Rational(1152921471320850330, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1073741827, 0)) + assert_equal(Complex(Rational(-1152921469173366676, 1073741789), Rational(1073741827, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1073741827, 0)) + assert_equal(Complex(Rational(1152921511049297929, 1073741789), Rational(1152921511049297929, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1, 1073741789), Rational(1, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1073741827, 0)) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1152921430518662348, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(-1152921428371178694, 1073741789), Rational(-1152921428371178694, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1073741789, 1073741789)) + assert_equal(Complex(0, 2147483654), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1073741827, 1152921429444920521), 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1152921430518662348, 1073741789), Rational(1152921471320850330, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-1152921428371178694, 1073741789), Rational(-1152921469173366676, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-40802189426, 1073741789), Rational(2305842981296406432, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921490648203216, 1237939962039641331329903525), Rational(-20401094713, 1237939962039641331329903525)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921471320850330, 1073741789), Rational(1152921471320850330, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(-1152921469173366676, 1073741789), Rational(-1152921469173366676, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(1073741827, 1073741827)) + assert_equal(Complex(0, Rational(2305843022098595858, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1, 1073741789), 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(2305842940494218450, 1152921470247108503), Rational(2305842940494218450, 1152921470247108503)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(81604377408, 1152921470247108503), Rational(81604377408, 1152921470247108503)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, 2), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921511049297929, 1152921429444920521), 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(2147483654, 1073741789), Rational(2147483654, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, Rational(2305843022098595858, 1152921429444920521)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(1, 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2305842940494218450, 1152921470247108503), Rational(2147483654, 1073741789)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(81604377408, 1152921470247108503), 0), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1152921429444920521), Rational(2305842940494218450, 1152921429444920521)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1329227963598475351851856578029295025, 1329227916556757626754787505905300241), Rational(-47041721054734275145774394016, 1329227916556757626754787505905300241)), + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789)) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)), +Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-1073741789, 1073741827), Rational(-1073741827, 1073741789)), -Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2147483616, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1, 0)) + assert_equal(Complex(Rational(-38, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1, 0)) + assert_equal(Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1, 0)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1073741789, 0)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1073741789, 0)) + assert_equal(Complex(Rational(1152921429444920521, 1073741827), 1073741827), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1073741789, 0)) + assert_equal(Complex(Rational(1, 1073741827), Rational(1073741827, 1152921429444920521)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1073741789, 0)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1073741827, 0)) + assert_equal(Complex(Rational(-1152921509975556140, 1073741827), Rational(1073741827, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1073741827, 0)) + assert_equal(Complex(1073741789, Rational(1152921511049297929, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1073741827, 0)) + assert_equal(Complex(Rational(1073741789, 1152921511049297929), Rational(1, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1073741827, 0)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921430518662348, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(-1152921428371178694, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(-81604377408, 1073741827), Rational(2305842940494218450, 1073741827)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1152921470247109225, 1237939962039640556088331867), Rational(40802188704, 1237939962039640556088331867)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1073741789, 1073741789)) + assert_equal(Complex(Rational(1152921471320850292, 1073741827), Rational(1152921471320850330, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-1152921469173366714, 1073741827), Rational(-1152921469173366676, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(-131433047608170424214, 1152921470247108503), 2147483616), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1237939983945150041266564176, 1329227916556755129526882950667240175), Rational(19, 1152921470247109225)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1073741789, 1073741827)) + assert_equal(Complex(Rational(1152921512123039718, 1073741827), Rational(1152921471320850330, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(-1152921509975556140, 1073741827), Rational(-1152921469173366676, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(-81604377408, 1073741789), Rational(2305842940494218450, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(1152921470247109225, 1237940005850656425478454981), Rational(40802188704, 1237940005850656425478454981)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(1073741827, 1073741827)) + assert_equal(Complex(Rational(2147483578, 1073741827), Rational(2305842940494218450, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(0, Rational(81604377408, 1152921470247108503)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(-81604377408, 1152921511049297929), Rational(2305842940494218450, 1152921511049297929)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(1152921470247109225, 1152921429444920521), Rational(40802188704, 1152921429444920521)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(Rational(1073741789, 1073741827), Rational(1073741789, 1073741827))) + assert_equal(Complex(Rational(2305842940494218450, 1152921470247108503), Rational(2147483654, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1152921470247108503), 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-81604377408, 1152921429444920521), Rational(2305842940494218450, 1152921429444920521)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(1152921470247109225, 1152921511049297929), Rational(40802188704, 1152921511049297929)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(Rational(1073741827, 1073741789), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(2147483578, 1073741827), Rational(2147483654, 1073741789)), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(0, 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) - Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(Rational(-188166877559662688435796777600, 1329227916556754297117581432254901009), 2), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) * Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + assert_equal(Complex(1, 0), + Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789)) / Complex(Rational(1073741789, 1073741827), Rational(1073741827, 1073741789))) + end + + def test_kumi2 + assert_equal('0.0+0.0i', (+Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0-0.0i', (-Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) + Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) - Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) * Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) + Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) - Complex(-0.0, +0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(+0.0, +0.0) * Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) + Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) - Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) * Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) + Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, +0.0) - Complex(-0.0, -0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, +0.0) * Complex(-0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (+Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (-Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) + Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, +0.0) - Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, +0.0) * Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, +0.0) + Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) - Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(-0.0, +0.0) * Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) + Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, +0.0) - Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) * Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, +0.0) + Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) - Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, +0.0) * Complex(-0.0, -0.0)).to_s) + assert_equal('0.0-0.0i', (+Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (-Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) + Complex(+0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, -0.0) - Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) * Complex(+0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) + Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, -0.0) - Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) * Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, -0.0) + Complex(+0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) - Complex(+0.0, -0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, -0.0) * Complex(+0.0, -0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(+0.0, -0.0) + Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(+0.0, -0.0) - Complex(-0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(+0.0, -0.0) * Complex(-0.0, -0.0)).to_s) + assert_equal('-0.0-0.0i', (+Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (-Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, -0.0) + Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0-0.0i', (Complex(-0.0, -0.0) - Complex(+0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(-0.0, -0.0) * Complex(+0.0, +0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, -0.0) + Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(-0.0, -0.0) - Complex(-0.0, +0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, -0.0) * Complex(-0.0, +0.0)).to_s) + assert_equal('0.0-0.0i', (Complex(-0.0, -0.0) + Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, -0.0) - Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0+0.0i', (Complex(-0.0, -0.0) * Complex(+0.0, -0.0)).to_s) + assert_equal('-0.0-0.0i', (Complex(-0.0, -0.0) + Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, -0.0) - Complex(-0.0, -0.0)).to_s) + assert_equal('0.0+0.0i', (Complex(-0.0, -0.0) * Complex(-0.0, -0.0)).to_s) + end + +end diff --git a/test/ruby/test_complexrational.rb b/test/ruby/test_complexrational.rb new file mode 100644 index 0000000000..31d11fe317 --- /dev/null +++ b/test/ruby/test_complexrational.rb @@ -0,0 +1,408 @@ +# frozen_string_literal: false +require 'test/unit' + +class ComplexRational_Test < Test::Unit::TestCase + + def test_rat_srat + omit unless defined?(Rational) + + c = SimpleRat(1,3) + cc = Rational(3,2) + + assert_kind_of(Numeric, c) + assert_kind_of(Numeric, cc) + + assert_instance_of(SimpleRat, c) + assert_instance_of(Rational, cc) + + assert_equal(SimpleRat(1,3), +c) + assert_equal(SimpleRat(-1,3), -c) + + assert_equal(SimpleRat(7,3), c + 2) + assert_equal(SimpleRat(-5,3), c - 2) + assert_equal(SimpleRat(2,3), c * 2) + assert_equal(SimpleRat(1,6), c / 2) + assert_equal(SimpleRat(1,9), c ** 2) + assert_equal(-1, c <=> 2) + + assert_equal(SimpleRat(7,3), 2 + c) + assert_equal(SimpleRat(5,3), 2 - c) + assert_equal(SimpleRat(2,3), 2 * c) + assert_equal(SimpleRat(6,1), 2 / c) + assert_in_delta(1.2599, 2 ** c, 0.001) + assert_equal(1, 2 <=> c) + + assert_equal(SimpleRat(11,6), c + cc) + assert_equal(SimpleRat(-7,6), c - cc) + assert_equal(SimpleRat(1,2), c * cc) + assert_equal(SimpleRat(2,9), c / cc) + assert_in_delta(0.1924, c ** cc, 0.001) + assert_equal(-1, c <=> cc) + + assert_equal(SimpleRat(11,6), cc + c) + assert_equal(SimpleRat(7,6), cc - c) + assert_equal(SimpleRat(1,2), cc * c) + assert_equal(SimpleRat(9,2), cc / c) + assert_in_delta(1.1447, cc ** c, 0.001) + assert_equal(1, cc <=> c) + + assert_equal(SimpleRat, (+c).class) + assert_equal(SimpleRat, (-c).class) + + assert_equal(SimpleRat, (c + 2).class) + assert_equal(SimpleRat, (c - 2).class) + assert_equal(SimpleRat, (c * 2).class) + assert_equal(SimpleRat, (c / 2).class) + assert_equal(SimpleRat, (c ** 2).class) + + assert_equal(SimpleRat, (2 + c).class) + assert_equal(SimpleRat, (2 - c).class) + assert_equal(SimpleRat, (2 * c).class) + assert_equal(SimpleRat, (2 / c).class) + assert_equal(Float, (2 ** c).class) + + assert_equal(SimpleRat, (c + cc).class) + assert_equal(SimpleRat, (c - cc).class) + assert_equal(SimpleRat, (c * cc).class) + assert_equal(SimpleRat, (c / cc).class) + assert_equal(Float, (c ** cc).class) + + assert_equal(SimpleRat, (cc + c).class) + assert_equal(SimpleRat, (cc - c).class) + assert_equal(SimpleRat, (cc * c).class) + assert_equal(SimpleRat, (cc / c).class) + assert_equal(Float, (cc ** c).class) + + assert_equal(0, Rational(2,3) <=> SimpleRat(2,3)) + assert_equal(0, 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) + assert_equal(SimpleRat, (c * 0).class) + assert_equal(SimpleRat, (c * 1).class) + assert_equal(SimpleRat, (0 + c).class) + assert_equal(SimpleRat, (0 - c).class) + assert_equal(SimpleRat, (0 * c).class) + assert_equal(SimpleRat, (1 * c).class) + end + + def test_comp_srat + omit unless defined?(Rational) + + c = Complex(SimpleRat(2,3),SimpleRat(1,2)) + cc = Complex(Rational(3,2),Rational(2,1)) + + assert_equal(Complex(SimpleRat(2,3),SimpleRat(1,2)), +c) + assert_equal(Complex(SimpleRat(-2,3),SimpleRat(-1,2)), -c) + + assert_equal(Complex(SimpleRat(8,3),SimpleRat(1,2)), c + 2) + assert_equal(Complex(SimpleRat(-4,3),SimpleRat(1,2)), c - 2) + assert_equal(Complex(SimpleRat(4,3),SimpleRat(1,1)), c * 2) + assert_equal(Complex(SimpleRat(1,3),SimpleRat(1,4)), c / 2) + assert_equal(Complex(SimpleRat(7,36),SimpleRat(2,3)), c ** 2) + assert_nil(c <=> 2) + + assert_equal(Complex(SimpleRat(8,3),SimpleRat(1,2)), 2 + c) + assert_equal(Complex(SimpleRat(4,3),SimpleRat(-1,2)), 2 - c) + assert_equal(Complex(SimpleRat(4,3),SimpleRat(1,1)), 2 * c) + assert_equal(Complex(SimpleRat(48,25),SimpleRat(-36,25)), 2 / c) + r = 2 ** c + assert_in_delta(1.4940, r.real, 0.001) + assert_in_delta(0.5392, r.imag, 0.001) + assert_nil(2 <=> c) + + assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), c + cc) + assert_equal(Complex(SimpleRat(-5,6),SimpleRat(-3,2)), c - cc) + assert_equal(Complex(SimpleRat(0,1),SimpleRat(25,12)), c * cc) + assert_equal(Complex(SimpleRat(8,25),SimpleRat(-7,75)), c / cc) + r = c ** cc + assert_in_delta(0.1732, r.real, 0.001) + assert_in_delta(0.1186, r.imag, 0.001) + assert_nil(c <=> cc) + + assert_equal(Complex(SimpleRat(13,6),SimpleRat(5,2)), cc + c) + assert_equal(Complex(SimpleRat(5,6),SimpleRat(3,2)), cc - c) + assert_equal(Complex(SimpleRat(0,1),SimpleRat(25,12)), cc * c) + assert_equal(Complex(SimpleRat(72,25),SimpleRat(21,25)), cc / c) + r = cc ** c + assert_in_delta(0.5498, r.real, 0.001) + assert_in_delta(1.0198, r.imag, 0.001) + assert_nil(cc <=> c) + + assert_equal([SimpleRat,SimpleRat], + (+c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (-c).instance_eval{[real.class, imag.class]}) + + assert_equal([SimpleRat,SimpleRat], + (c + 2).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c - 2).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c * 2).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c / 2).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c ** 2).instance_eval{[real.class, imag.class]}) + + assert_equal([SimpleRat,SimpleRat], + (c + cc).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c - cc).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c * cc).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c / cc).instance_eval{[real.class, imag.class]}) + assert_equal([Float,Float], + (c ** cc).instance_eval{[real.class, imag.class]}) + + assert_equal([SimpleRat,SimpleRat], + (cc + c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (cc - c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (cc * c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (cc / c).instance_eval{[real.class, imag.class]}) + assert_equal([Float,Float], + (cc ** c).instance_eval{[real.class, imag.class]}) + + 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]}) + assert_equal([SimpleRat,SimpleRat], + (c - 0).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c * 0).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (c * 1).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (0 + c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (0 - c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (0 * c).instance_eval{[real.class, imag.class]}) + assert_equal([SimpleRat,SimpleRat], + (1 * c).instance_eval{[real.class, imag.class]}) + end + +end + +def SimpleRat(*a) SimpleRat.new(*a) end + +class SimpleRat < Numeric + + def initialize(num, den = 1) + if den == 0 + raise ZeroDivisionError, "divided by zero" + end + if den < 0 + num = -num + den = -den + end + gcd = num.gcd(den) + @num = num.div(gcd) + @den = den.div(gcd) + end + + def numerator() @num end + def denominator() @den end + + def +@() self end + def -@() self.class.new(-@num, @den) end + + def +(o) + case o + when SimpleRat, Rational + a = @num * o.denominator + b = o.numerator * @den + self.class.new(a + b, @den * o.denominator) + when Integer + self + self.class.new(o) + when Float + to_f + o + else + x, y = o.coerce(self) + x + y + end + end + + def -(o) + case o + when SimpleRat, Rational + a = @num * o.denominator + b = o.numerator * @den + self.class.new(a - b, @den * o.denominator) + when Integer + self - self.class.new(o) + when Float + to_f - o + else + x, y = o.coerce(self) + x - y + end + end + + def *(o) + case o + when SimpleRat, Rational + a = @num * o.numerator + b = @den * o.denominator + self.class.new(a, b) + when Integer + self * self.class.new(o) + when Float + to_f * o + else + x, y = o.coerce(self) + x * y + end + end + + def quo(o) + case o + when SimpleRat, Rational + a = @num * o.denominator + b = @den * o.numerator + self.class.new(a, b) + when Integer + if o == 0 + raise ZeroDivisionError, "divided by zero" + end + self.quo(self.class.new(o)) + when Float + to_f.quo(o) + else + x, y = o.coerce(self) + x.quo(y) + end + end + + alias / quo + + def floor + @num.div(@den) + end + + def ceil + -((-@num).div(@den)) + end + + def truncate + if @num < 0 + return -((-@num).div(@den)) + end + @num.div(@den) + end + + alias to_i truncate + + def round + if @num < 0 + num = -@num + num = num * 2 + @den + den = @den * 2 + -(num.div(den)) + else + num = @num * 2 + @den + den = @den * 2 + num.div(den) + end + end + + def div(o) (self / o).floor end + def quot(o) (self / o).truncate end + + def modulo(o) + q = div(o) + self - o * q + end + + def remainder(o) + q = quot(o) + self - o * q + end + + alias % modulo + + def divmod(o) [div(o), modulo(o)] end + def quotrem(o) [quot(o), remainder(o)] end + + def **(o) + case o + when SimpleRat, Rational + Float(self) ** o + when Integer + if o > 0 + a = @num ** o + b = @den ** o + elsif o < 0 + a = @den ** -o + b = @num ** -o + else + a = b = 1 + end + self.class.new(a, b) + when Float + to_f ** o + else + x, y = o.coerce(self) + x ** y + end + end + + def <=>(o) + case o + when SimpleRat, Rational + a = @num * o.denominator + b = o.numerator * @den + return a <=> b + when Integer + self <=> self.class.new(o) + when Float + to_f <=> o + else + x, y = o.coerce(self) + x <=> y + end + end + + def ==(o) + begin + (self <=> o) == 0 + rescue + false + end + end + + def coerce(o) + case o + when Rational + [self.class.new(o.numerator, o.denominator), self] + when Integer + [self.class.new(o), self] + when Float + [o, self.to_f] + else + super + end + end + + def hash() @num.hash ^ @den.hash end + + def to_f() @num.to_f / @den.to_f end + def to_r() self end + def to_s() format('%s/%s', @num, @den) end + + def inspect() format('#SR(%s)', to_s) end + + def marshal_dump() [@num, @den] end + def marshal_load(a) @num, @den = a end + +end diff --git a/test/ruby/test_condition.rb b/test/ruby/test_condition.rb new file mode 100644 index 0000000000..ab0ffc4b6a --- /dev/null +++ b/test/ruby/test_condition.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestCondition < Test::Unit::TestCase + + # [should] first test to see if we can run the tests. + + def test_condition + $x = '0'; + + $x == $x && assert(true) + $x != $x && assert(false) + $x == $x || assert(false) + $x != $x || assert(true) + + end +end diff --git a/test/ruby/test_const.rb b/test/ruby/test_const.rb new file mode 100644 index 0000000000..f6b9ea83d3 --- /dev/null +++ b/test/ruby/test_const.rb @@ -0,0 +1,93 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestConst < Test::Unit::TestCase + + Constants_Setup = -> do + remove_const :TEST1 if defined? ::TestConst::TEST1 + remove_const :TEST2 if defined? ::TestConst::TEST2 + remove_const :Const if defined? ::TestConst::Const + remove_const :Const2 if defined? ::TestConst::Const2 + + TEST1 = 1 + TEST2 = 2 + + module Const + TEST3 = 3 + TEST4 = 4 + end + + module Const2 + TEST3 = 6 + TEST4 = 8 + end + end + + def test_const + Constants_Setup.call + + assert defined?(TEST1) + assert_equal 1, TEST1 + assert defined?(TEST2) + assert_equal 2, TEST2 + + self.class.class_eval { + include Const + } + assert defined?(TEST1) + assert_equal 1, TEST1 + assert defined?(TEST2) + assert_equal 2, TEST2 + assert defined?(TEST3) + assert_equal 3, TEST3 + assert defined?(TEST4) + assert_equal 4, TEST4 + + self.class.class_eval { + include Const2 + } + # STDERR.print "intentionally redefines TEST3, TEST4\n" if $VERBOSE + assert defined?(TEST1) + assert_equal 1, TEST1 + assert defined?(TEST2) + assert_equal 2, TEST2 + assert defined?(TEST3) + assert_equal 6, TEST3 + assert defined?(TEST4) + assert_equal 8, TEST4 + end + + def test_const_access_from_nil + assert_raise(TypeError) { eval("nil::Object") } + assert_nil eval("defined?(nil::Object)") + + assert_raise(TypeError) { eval("c = nil; c::Object") } + assert_nil eval("c = nil; defined?(c::Object)") + + assert_raise(TypeError) { eval("sc = Class.new; sc::C = nil; sc::C::Object") } + assert_nil eval("sc = Class.new; sc::C = nil; defined?(sc::C::Object)") + end + + def test_redefinition + c = Class.new + name = "X\u{5b9a 6570}" + 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 new file mode 100644 index 0000000000..612dbf28c9 --- /dev/null +++ b/test/ruby/test_continuation.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: false +require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} +require 'fiber' + +class TestContinuation < Test::Unit::TestCase + def setup + omit 'requires callcc support' unless respond_to?(:callcc) + end + + def test_create + assert_equal(:ok, callcc{:ok}) + assert_equal(:ok, callcc{|c| c.call :ok}) + end + + def test_call + assert_equal(:ok, callcc{|c| c.call :ok}) + + ary = [] + ary << callcc{|c| + @cont = c + :a + } + @cont.call :b if ary.length < 3 + assert_equal([:a, :b, :b], ary) + end + + def test_check_localvars + vv = 0 + @v = 0 + @ary = [] + [1, 2, 3].each{|i| + callcc {|k| @k = k} + @v += 1 + vv += 1 + } + @ary << [vv, @v] + @k.call if @v < 10 + assert_equal((3..10).map{|e| [e, e]}, @ary) + end + + def test_error + cont = callcc{|c| c} + Thread.new{ + assert_raise(RuntimeError){ + cont.call + } + }.join + assert_raise(LocalJumpError){ + callcc + } + assert_raise(RuntimeError){ + c = nil + Fiber.new do + callcc {|c2| c = c2 } + end.resume + c.call + } + end + + def test_ary_flatten + assert_normal_exit %q{ + require 'continuation' + n = 0 + o = Object.new + def o.to_ary() callcc {|k| $k = k; [1,2,3]} end + [10,20,o,30,o,40].flatten.inspect + n += 1 + $k.call if n < 100 + }, '[ruby-dev:34798]' + end + + def test_marshal_dump + assert_normal_exit %q{ + require 'continuation' + n = 0 + o = Object.new + def o.marshal_dump() callcc {|k| $k = k }; "fofof" end + a = [1,2,3,o,4,5,6] + Marshal.dump(a).inspect + n += 1 + $k.call if n < 100 + }, '[ruby-dev:34802]' + 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_data.rb b/test/ruby/test_data.rb new file mode 100644 index 0000000000..fbc3205d63 --- /dev/null +++ b/test/ruby/test_data.rb @@ -0,0 +1,290 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' +require 'timeout' + +class TestData < Test::Unit::TestCase + def test_define + klass = Data.define(:foo, :bar) + assert_kind_of(Class, klass) + assert_equal(%i[foo bar], klass.members) + + assert_raise(NoMethodError) { Data.new(:foo) } + assert_raise(TypeError) { Data.define(0) } + + # Because some code is shared with Struct, check we don't share unnecessary functionality + assert_raise(TypeError) { Data.define(:foo, keyword_init: true) } + + assert_not_respond_to(Data.define, :define, "Cannot define from defined Data class") + end + + def test_define_edge_cases + # non-ascii + klass = Data.define(:"r\u{e9}sum\u{e9}") + o = klass.new(1) + assert_equal(1, o.send(:"r\u{e9}sum\u{e9}")) + + # junk string + klass = Data.define(:"a\000") + o = klass.new(1) + assert_equal(1, o.send(:"a\000")) + + # special characters in attribute names + klass = Data.define(:a, :b?) + x = Object.new + o = klass.new("test", x) + assert_same(x, o.b?) + + klass = Data.define(:a, :b!) + x = Object.new + o = klass.new("test", x) + assert_same(x, o.b!) + + assert_raise(ArgumentError) { Data.define(:x=) } + assert_raise(ArgumentError, /duplicate member/) { Data.define(:x, :x) } + end + + def test_define_with_block + klass = Data.define(:a, :b) do + def c + a + b + end + end + + assert_equal(3, klass.new(1, 2).c) + end + + def test_initialize + klass = Data.define(:foo, :bar) + + # Regular + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(1, 2)) + assert_predicate(test, :frozen?) + + # Keywords + test_kw = klass.new(foo: 1, bar: 2) + assert_equal(1, test_kw.foo) + assert_equal(2, test_kw.bar) + assert_equal(test_kw, klass.new(foo: 1, bar: 2)) + assert_equal(test_kw, test) + + # Wrong protocol + assert_raise(ArgumentError) { klass.new(1) } + assert_raise(ArgumentError) { klass.new(1, 2, 3) } + assert_raise(ArgumentError) { klass.new(foo: 1) } + assert_raise(ArgumentError) { klass.new(foo: 1, bar: 2, baz: 3) } + # Could be converted to foo: 1, bar: 2, but too smart is confusing + assert_raise(ArgumentError) { klass.new(1, bar: 2) } + end + + def test_initialize_redefine + klass = Data.define(:foo, :bar) do + attr_reader :passed + + def initialize(*args, **kwargs) + @passed = [args, kwargs] + super(foo: 1, bar: 2) # so we can experiment with passing wrong numbers of args + end + end + + assert_equal([[], {foo: 1, bar: 2}], klass.new(foo: 1, bar: 2).passed) + + # Positional arguments are converted to keyword ones + assert_equal([[], {foo: 1, bar: 2}], klass.new(1, 2).passed) + + # Missing arguments can be fixed in initialize + assert_equal([[], {foo: 1}], klass.new(foo: 1).passed) + assert_equal([[], {foo: 42}], klass.new(42).passed) + + # Extra keyword arguments can be dropped in initialize + assert_equal([[], {foo: 1, bar: 2, baz: 3}], klass.new(foo: 1, bar: 2, baz: 3).passed) + end + + def test_instance_behavior + klass = Data.define(:foo, :bar) + + test = klass.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(%i[foo bar], test.members) + assert_equal(1, test.public_send(:foo)) + assert_equal(0, test.method(:foo).arity) + assert_equal([], test.method(:foo).parameters) + + assert_equal({foo: 1, bar: 2}, test.to_h) + assert_equal({"foo"=>"1", "bar"=>"2"}, test.to_h { [_1.to_s, _2.to_s] }) + + assert_equal([1, 2], test.deconstruct) + assert_equal({foo: 1, bar: 2}, test.deconstruct_keys(nil)) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo])) + assert_equal({foo: 1}, test.deconstruct_keys(%i[foo baz])) + assert_equal({}, test.deconstruct_keys(%i[foo bar baz])) + assert_raise(TypeError) { test.deconstruct_keys(0) } + + assert_kind_of(Integer, test.hash) + end + + def test_hash + measure = Data.define(:amount, :unit) + + assert_equal(measure[1, 'km'].hash, measure[1, 'km'].hash) + assert_not_equal(measure[1, 'km'].hash, measure[10, 'km'].hash) + assert_not_equal(measure[1, 'km'].hash, measure[1, 'm'].hash) + assert_not_equal(measure[1, 'km'].hash, measure[1.0, 'km'].hash) + + # Structurally similar data class, but shouldn't be considered + # the same hash key + measurement = Data.define(:amount, :unit) + + assert_not_equal(measure[1, 'km'].hash, measurement[1, 'km'].hash) + end + + def test_inspect + klass = Data.define(:a) + o = klass.new(1) + assert_equal("#<data a=1>", o.inspect) + + Object.const_set(:Foo, klass) + assert_equal("#<data Foo a=1>", o.inspect) + Object.instance_eval { remove_const(:Foo) } + + klass = Data.define(:@a) + o = klass.new(1) + assert_equal("#<data :@a=1>", o.inspect) + end + + def test_equal + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_equal(o1, o2) + assert_not_equal(o1, o3) + end + + def test_eql + klass1 = Data.define(:a) + klass2 = Data.define(:a) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_operator(o1, :eql?, o2) + assert_not_operator(o1, :eql?, o3) + end + + def test_with + klass = Data.define(:foo, :bar) + source = klass.new(foo: 1, bar: 2) + + # Simple + test = source.with + assert_equal(source.object_id, test.object_id) + + # Changes + test = source.with(foo: 10) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(2, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 2)) + + test = source.with(foo: 10, bar: 20) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Keyword splat + changes = { foo: 10, bar: 20 } + test = source.with(**changes) + + assert_equal(1, source.foo) + assert_equal(2, source.bar) + assert_equal(source, klass.new(foo: 1, bar: 2)) + + assert_equal(10, test.foo) + assert_equal(20, test.bar) + assert_equal(test, klass.new(foo: 10, bar: 20)) + + # Wrong protocol + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(10) + end + assert_raise_with_message(ArgumentError, "unknown keywords: :baz, :quux") do + source.with(foo: 1, bar: 2, baz: 3, quux: 4) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with(1, bar: 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 2, expected 0)") do + source.with(1, 2) + end + assert_raise_with_message(ArgumentError, "wrong number of arguments (given 1, expected 0)") do + source.with({ bar: 2 }) + end + end + + def test_with_initialize + oddclass = Data.define(:odd) do + def initialize(odd:) + raise ArgumentError, "Not odd" unless odd.odd? + super(odd: odd) + end + end + assert_raise_with_message(ArgumentError, "Not odd") { + oddclass.new(odd: 0) + } + odd = oddclass.new(odd: 1) + assert_raise_with_message(ArgumentError, "Not odd") { + odd.with(odd: 2) + } + end + + def test_memberless + klass = Data.define + + test = klass.new + + assert_equal(klass.new, test) + assert_not_equal(Data.define.new, test) + + assert_equal('#<data>', test.inspect) + assert_equal([], test.members) + assert_equal({}, test.to_h) + assert_predicate(test, :frozen?) + end + + def test_dup + klass = Data.define(:foo, :bar) + test = klass.new(foo: 1, bar: 2) + assert_equal(klass.new(foo: 1, bar: 2), test.dup) + assert_predicate(test.dup, :frozen?) + end + + Klass = Data.define(:foo, :bar) + + def test_marshal + test = Klass.new(foo: 1, bar: 2) + loaded = Marshal.load(Marshal.dump(test)) + assert_equal(test, loaded) + assert_not_same(test, loaded) + assert_predicate(loaded, :frozen?) + end + + def test_frozen_subclass + test = Class.new(Data.define(:a)).freeze.new(a: 0) + assert_kind_of(Data, test) + assert_equal([:a], test.members) + end +end diff --git a/test/ruby/test_default_gems.rb b/test/ruby/test_default_gems.rb new file mode 100644 index 0000000000..b82e304cbd --- /dev/null +++ b/test/ruby/test_default_gems.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: false +require 'rubygems' + +class TestDefaultGems < Test::Unit::TestCase + def self.load(file) + code = File.read(file, mode: "r:UTF-8:-", &:read) + + # These regex patterns are from load_gemspec method of rbinstall.rb. + # - `git ls-files` is useless under ruby's repository + # - `2>/dev/null` works only on Unix-like platforms + code.gsub!(/(?:`git[^\`]*`|%x\[git[^\]]*\])\.split\([^\)]*\)/m, '[]') + code.gsub!(/IO\.popen\(.*git.*?\)/, '[] || itself') + + eval(code, binding, file) + end + + def test_validate_gemspec + srcdir = File.expand_path('../../..', __FILE__) + specs = 0 + Dir.chdir(srcdir) do + all_assertions_foreach(nil, *Dir["{lib,ext}/**/*.gemspec"]) do |src| + specs += 1 + assert_kind_of(Gem::Specification, self.class.load(src), "invalid spec in #{src}") + end + end + assert_operator specs, :>, 0, "gemspecs not found" + end + +end diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb new file mode 100644 index 0000000000..db1fdc8e25 --- /dev/null +++ b/test/ruby/test_defined.rb @@ -0,0 +1,500 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestDefined < Test::Unit::TestCase + class Foo + def foo + p :foo + end + protected :foo + def bar(f) + yield(defined?(self.foo)) + yield(defined?(f.foo)) + end + def baz(f) + end + attr_accessor :attr + def attrasgn_test + yield(defined?(self.attr = 1)) + end + end + + def defined_test + return !defined?(yield) + end + + 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?($`) + assert_equal nil, 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 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?($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?($1) + assert_equal nil, defined?($2) + end + + def test_defined_assignment + assert_equal("assignment", defined?(a = 1)) + assert_equal("assignment", defined?(a += 1)) + assert_equal("assignment", defined?(a &&= 1)) + assert_equal("assignment", eval('defined?(A = 1)')) + assert_equal("assignment", eval('defined?(A += 1)')) + assert_equal("assignment", eval('defined?(A &&= 1)')) + assert_equal("assignment", eval('defined?(A::B = 1)')) + assert_equal("assignment", eval('defined?(A::B += 1)')) + assert_equal("assignment", eval('defined?(A::B &&= 1)')) + end + + def test_defined_splat + assert_nil(defined?([*a])) + assert_nil(defined?(itself(*a))) + assert_equal("expression", defined?([*itself])) + assert_equal("method", defined?(itself(*itself))) + end + + def test_defined_hash + assert_nil(defined?({a: a})) + assert_nil(defined?({a => 1})) + assert_nil(defined?({a => a})) + assert_nil(defined?({**a})) + assert_nil(defined?(itself(a: a))) + assert_nil(defined?(itself(a => 1))) + assert_nil(defined?(itself(a => a))) + assert_nil(defined?(itself(**a))) + assert_nil(defined?(itself({a: a}))) + assert_nil(defined?(itself({a => 1}))) + assert_nil(defined?(itself({a => a}))) + assert_nil(defined?(itself({**a}))) + + assert_equal("expression", defined?({a: itself})) + assert_equal("expression", defined?({itself => 1})) + assert_equal("expression", defined?({itself => itself})) + assert_equal("expression", defined?({**itself})) + assert_equal("method", defined?(itself(a: itself))) + assert_equal("method", defined?(itself(itself => 1))) + assert_equal("method", defined?(itself(itself => itself))) + assert_equal("method", defined?(itself(**itself))) + assert_equal("method", defined?(itself({a: itself}))) + assert_equal("method", defined?(itself({itself => 1}))) + assert_equal("method", defined?(itself({itself => itself}))) + assert_equal("method", defined?(itself({**itself}))) + end + + def test_defined_literal + assert_equal("nil", defined?(nil)) + assert_equal("true", defined?(true)) + assert_equal("false", defined?(false)) + assert_equal("expression", defined?(1)) + end + + def test_defined_method + self_ = self + assert_equal("method", defined?(test_defined_method)) + assert_equal("method", defined?(self.test_defined_method)) + assert_equal("method", defined?(self_.test_defined_method)) + + assert_equal(nil, defined?(1.test_defined_method)) + assert_equal("method", defined?(1.to_i)) + assert_equal(nil, defined?(1.to_i.test_defined_method)) + assert_equal(nil, defined?(1.test_defined_method.to_i)) + + assert_equal("method", defined?("x".reverse)) + assert_equal("method", defined?("x".reverse(1))) + assert_equal("method", defined?("x".reverse.reverse)) + assert_equal(nil, defined?("x".reverse(1).reverse)) + + assert_equal("method", defined?(1.to_i(10))) + assert_equal("method", defined?(1.to_i("x"))) + assert_equal(nil, defined?(1.to_i("x").undefined)) + assert_equal(nil, defined?(1.to_i(undefined).to_i)) + assert_equal(nil, defined?(1.to_i("x").undefined.to_i)) + assert_equal(nil, defined?(1.to_i(undefined).to_i.to_i)) + end + + def test_defined_method_single_call + times_called = 0 + define_singleton_method(:t) do + times_called += 1 + self + end + assert_equal("method", defined?(t)) + assert_equal(0, times_called) + + assert_equal("method", defined?(t.t)) + assert_equal(1, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t)) + assert_equal(2, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t.t)) + assert_equal(3, times_called) + + times_called = 0 + assert_equal("method", defined?(t.t.t.t.t)) + assert_equal(4, times_called) + end + + def test_defined_empty_paren_expr + bug8224 = '[ruby-core:54024] [Bug #8224]' + (1..3).each do |level| + expr = "("*level+")"*level + 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_paren_void_stmts + assert_equal("expression", defined? (;x)) + assert_equal("expression", defined? (x;)) + assert_nil(defined? ( + + x + + )) + + x = 1 + + assert_equal("expression", defined? (;x)) + assert_equal("expression", defined? (x;)) + assert_equal("local-variable", defined? ( + + x + + )) + 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_in_basic_object + BasicObject.class_eval do + def a + defined?(super) + end + end + + assert_nil(a) + ensure + BasicObject.class_eval do + undef_method :a if defined?(a) + end + end + + def test_super_toplevel + assert_separately([], "assert_nil(defined?(super))") + end + + def test_respond_to + obj = "#{self.class.name}##{__method__}" + class << obj + def respond_to?(mid) + true + end + end + assert_warn(/deprecated method signature.*\n.*respond_to\? is defined here/) do + Warning[:deprecated] = true + defined?(obj.foo) + end + assert_warn('') do + Warning[:deprecated] = false + defined?(obj.foo) + end + end + + class ExampleRespondToMissing + attr_reader :called + + 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 + + class RefinedClass + end + + module RefiningModule + refine RefinedClass do + def pub + end + + private + + def priv + end + end + + def self.call_without_using(x = RefinedClass.new) + defined?(x.pub) + end + + def self.vcall_without_using(x = RefinedClass.new) + x.instance_eval {defined?(priv)} + end + + using self + + def self.call_with_using(x = RefinedClass.new) + defined?(x.pub) + end + + def self.vcall_with_using(x = RefinedClass.new) + x.instance_eval {defined?(priv)} + end + end + + def test_defined_refined_call_without_using + assert(!RefiningModule.call_without_using, "refined public method without using") + end + + def test_defined_refined_vcall_without_using + assert(!RefiningModule.vcall_without_using, "refined private method without using") + end + + def test_defined_refined_call_with_using + assert(RefiningModule.call_with_using, "refined public method with using") + end + + def test_defined_refined_vcall_with_using + assert(RefiningModule.vcall_with_using, "refined private method with using") + end +end diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb new file mode 100644 index 0000000000..edb5210af1 --- /dev/null +++ b/test/ruby/test_dir.rb @@ -0,0 +1,785 @@ +# frozen_string_literal: false +require 'test/unit' + +require 'tmpdir' +require 'fileutils' + +class TestDir < Test::Unit::TestCase + + def setup + @verbose = $VERBOSE + @root = File.realpath(Dir.mktmpdir('__test_dir__')) + @nodir = File.join(@root, "dummy") + @dirs = [] + for i in "a".."z" + if i.ord % 2 == 0 + FileUtils.touch(File.join(@root, i)) + else + FileUtils.mkdir(File.join(@root, i)) + @dirs << File.join(i, "") + end + end + @envs = nil + end + + def teardown + $VERBOSE = @verbose + FileUtils.remove_entry_secure @root if File.directory?(@root) + ENV.update(@envs) if @envs + end + + def setup_envs(envs = %w"HOME LOGDIR") + @envs ||= {} + envs.each do |e, v| + @envs[e] = ENV.delete(e) + ENV[e] = v if v + end + end + + def test_seek + dir = Dir.open(@root) + begin + cache = [] + loop do + pos = dir.tell + break unless name = dir.read + cache << [pos, name] + end + for x,y in cache.sort_by {|z| z[0] % 3 } # shuffle + dir.seek(x) + assert_equal(y, dir.read) + end + ensure + dir.close + end + end + + def test_nodir + assert_raise(Errno::ENOENT) { Dir.open(@nodir) } + end + + def test_inspect + d = Dir.open(@root) + assert_match(/^#<Dir:#{ Regexp.quote(@root) }>$/, d.inspect) + assert_match(/^#<Dir:.*>$/, Dir.allocate.inspect) + ensure + d.close + end + + def test_path + d = Dir.open(@root) + assert_equal(@root, d.path) + assert_nil(Dir.allocate.path) + ensure + d.close + end + + def test_set_pos + d = Dir.open(@root) + loop do + i = d.pos + break unless x = d.read + d.pos = i + assert_equal(x, d.read) + end + ensure + d.close + end + + def test_rewind + d = Dir.open(@root) + a = (0..5).map { d.read } + d.rewind + b = (0..5).map { d.read } + assert_equal(a, b) + ensure + d.close + end + + def test_class_chdir + pwd = Dir.pwd + setup_envs + + assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) } + assert_raise(ArgumentError) { Dir.chdir } + ENV["HOME"] = pwd + Dir.chdir do + conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/ + assert_warning(conflicting) { Dir.chdir(pwd) } + + assert_warning(conflicting) { Dir.chdir(@root) } + assert_equal(@root, Dir.pwd) + + assert_warning(conflicting) { Dir.chdir(pwd) } + + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) }.join } + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) { } }.join } + + assert_warning(conflicting) { Dir.chdir(pwd) } + + assert_warning(conflicting) { Dir.chdir(@root) } + assert_equal(@root, Dir.pwd) + + assert_warning(conflicting) { Dir.chdir(pwd) } + Dir.chdir(@root) do + assert_equal(@root, Dir.pwd) + end + assert_equal(pwd, Dir.pwd) + end + + ensure + begin + Dir.chdir(pwd) + rescue + abort("cannot return the original directory: #{ pwd }") + end + end + + def test_instance_chdir + pwd = Dir.pwd + dir = Dir.new(pwd) + root_dir = Dir.new(@root) + setup_envs + + ENV["HOME"] = pwd + ret = root_dir.chdir do |*a| + conflicting = /conflicting chdir during another chdir block\n^#{Regexp.quote(__FILE__)}:#{__LINE__-1}:/ + + assert_empty(a) + + assert_warning(conflicting) { dir.chdir } + assert_warning(conflicting) { root_dir.chdir } + + assert_equal(@root, Dir.pwd) + + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir }.join } + assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir{} }.join } + + assert_warning(conflicting) { dir.chdir } + assert_equal(pwd, Dir.pwd) + + assert_warning(conflicting) { root_dir.chdir } + assert_equal(@root, Dir.pwd) + + assert_warning(conflicting) { dir.chdir } + + root_dir.chdir do + assert_equal(@root, Dir.pwd) + end + assert_equal(pwd, Dir.pwd) + + 42 + end + + assert_separately(["-", @root], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + root = ARGV.shift + + $dir_warnings = [] + + def Warning.warn(message) + $dir_warnings << message + end + + line2 = line1 = __LINE__; Dir.chdir(root) do + line2 = __LINE__; Dir.chdir + end + + message = $dir_warnings.shift + assert_include(message, "#{__FILE__}:#{line2}:") + assert_include(message, "#{__FILE__}:#{line1}:") + assert_empty($dir_warnings) + end; + + assert_equal(42, ret) + ensure + begin + assert_equal(0, dir.chdir) + rescue + abort("cannot return the original directory: #{ pwd }") + end + dir.close + root_dir.close + end + + def test_chdir_conflict + pwd = Dir.pwd + q = Thread::Queue.new + t = Thread.new do + q.pop + Dir.chdir(pwd) rescue $! + end + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) + end + + t = Thread.new do + q.pop + Dir.chdir(pwd){} rescue $! + end + Dir.chdir(pwd) do + q.push nil + assert_instance_of(RuntimeError, t.value) + end + end + + def test_chroot_nodir + omit if RUBY_PLATFORM =~ /android/ + assert_raise(NotImplementedError, Errno::ENOENT, Errno::EPERM + ) { Dir.chroot(File.join(@nodir, "")) } + end + + 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) }, + Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH)) + assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }, + Dir.glob([@root, File.join(@root, "*")])) + assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }, + Dir.glob([@root, File.join(@root, "*")], sort: false).sort) + assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }, + Dir.glob([@root, File.join(@root, "*")], sort: true)) + assert_raise_with_message(ArgumentError, /nul-separated/) do + Dir.glob(@root + "\0\0\0" + File.join(@root, "*")) + end + assert_raise_with_message(ArgumentError, /expected true or false/) do + Dir.glob(@root, sort: 1) + end + assert_raise_with_message(ArgumentError, /expected true or false/) do + Dir.glob(@root, sort: nil) + end + + assert_equal(("a".."z").each_slice(2).map {|f,_| File.join(File.join(@root, f), "") }, + Dir.glob(File.join(@root, "*/"))) + assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a')) + + FileUtils.touch(File.join(@root, "{}")) + assert_equal(%w({} a).map{|f| File.join(@root, f) }, + Dir.glob(File.join(@root, '{\{\},a}'))) + assert_equal([], Dir.glob(File.join(@root, '['))) + 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) }, Dir.glob(File.join(@root, '[abc/def]'))) + + open(File.join(@root, "}}{}"), "wb") {} + open(File.join(@root, "}}a"), "wb") {} + assert_equal(%w(}}{} }}a).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '}}{\{\},a}'))) + assert_equal(%w(}}{} }}a b c).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '{\}\}{\{\},a},b,c}'))) + assert_raise(ArgumentError) { + Dir.glob([[@root, File.join(@root, "*")].join("\0")]) + } + end + + def test_glob_recursive + bug6977 = '[ruby-core:47418]' + bug8006 = '[ruby-core:53108] [Bug #8006]' + Dir.chdir(@root) do + assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006) + + Dir.mkdir("a/b") + assert_not_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/b/.") + + FileUtils.mkdir_p("a/b/c/d/e/f") + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977) + assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977) + 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"), bug8283) + assert_equal(["a/.x/z"], Dir.glob("a/**/.x/z"), bug8283) + assert_equal(["a/.x/z"], Dir.glob("a/.x/**/z"), bug8283) + assert_equal(["a/b/.y/z"], Dir.glob("a/**/.y/z"), bug8283) + end + end + + def test_glob_recursive_directory + Dir.chdir(@root) do + ['d', 'e'].each do |path| + FileUtils.mkdir_p("c/#{path}/a/b/c") + FileUtils.touch("c/#{path}/a/a.file") + FileUtils.touch("c/#{path}/a/b/b.file") + FileUtils.touch("c/#{path}/a/b/c/c.file") + end + bug15540 = '[ruby-core:91110] [Bug #15540]' + assert_equal(["c/d/a/", "c/d/a/b/", "c/d/a/b/c/", "c/e/a/", "c/e/a/b/", "c/e/a/b/c/"], + Dir.glob('c/{d,e}/a/**/'), bug15540) + + assert_equal(["c/e/a/", "c/e/a/b/", "c/e/a/b/c/", "c/d/a/", "c/d/a/b/", "c/d/a/b/c/"], + Dir.glob('c/{e,d}/a/**/')) + end + end + + def test_glob_starts_with_brace + Dir.chdir(@root) do + bug15649 = '[ruby-core:91728] [Bug #15649]' + assert_equal(["#{@root}/a", "#{@root}/b"], + Dir.glob("{#{@root}/a,#{@root}/b}"), bug15649) + end + end + + def test_glob_recursive_with_brace + Dir.chdir(@root) do + bug19042 = '[ruby-core:110220] [Bug #19042]' + %w"c/dir_a c/dir_b c/dir_b/dir".each do |d| + Dir.mkdir(d) + end + expected = %w"c/dir_a/file c/dir_b/dir/file" + expected.each do |f| + File.write(f, "") + end + assert_equal(expected, Dir.glob("**/{dir_a,dir_b/dir}/file"), bug19042) + end + end + + def test_glob_order + Dir.chdir(@root) do + assert_equal(["#{@root}/a", "#{@root}/b"], Dir.glob("#{@root}/[ba]")) + assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob(%W"#{@root}/b #{@root}/a")) + assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob("#{@root}/{b,a}")) + end + assert_equal(["a", "b"], Dir.glob("[ba]", base: @root)) + assert_equal(["b", "a"], Dir.glob(%W"b a", base: @root)) + assert_equal(["b", "a"], Dir.glob("{b,a}", base: @root)) + end + + if Process.const_defined?(:RLIMIT_NOFILE) + def test_glob_too_may_open_files + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", chdir: @root) + 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)) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".")}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a")}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "")}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil)}) + assert_equal(@dirs, Dir.glob("*/", base: @root)) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".")}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a")}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "")}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil)}) + assert_equal(dirs, Dir.glob("**/*/", base: @root)) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".")}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a")}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "")}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil)}) + + assert_equal(files, Dir.glob("*/*.c", base: @root, sort: false).sort) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".", sort: false).sort}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a", sort: false).sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "", sort: false).sort}) + assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil, sort: false).sort}) + assert_equal(@dirs, Dir.glob("*/", base: @root)) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".", sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a", sort: false).sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "", sort: false).sort}) + assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil, sort: false).sort}) + assert_equal(dirs, Dir.glob("**/*/", base: @root)) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".", sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a", sort: false).sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "", sort: false).sort}) + assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil, sort: false).sort}) + end + + def test_glob_base_dir + files = %w[a/foo.c c/bar.c] + files.each {|n| File.write(File.join(@root, n), "")} + Dir.mkdir(File.join(@root, "a/dir")) + dirs = @dirs + %w[a/dir/] + dirs.sort! + + assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d)}}) + assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d)}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d)}}) + assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d)}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d)}}) + + assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d, sort: false).sort}) + assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d, sort: false).sort}}) + assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d, sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d, sort: false).sort}}) + assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d, sort: false).sort}) + assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d, sort: false).sort}}) + end + + def test_glob_ignore_casefold_invalid_encoding + bug14456 = "[ruby-core:85448]" + filename = "\u00AAa123".encode('ISO-8859-1') + File.write(File.join(@root, filename), "") + matches = Dir.chdir(@root) {|d| Dir.glob("*a123".encode('UTF-8'), File::FNM_CASEFOLD)} + assert_equal(1, matches.size, bug14456) + matches.each{|f| f.force_encoding('ISO-8859-1')} + # Handle MacOS/Windows, which saves under a different filename + assert_include([filename, "\u00C2\u00AAa123".encode('ISO-8859-1')], matches.first, bug14456) + end + + def assert_entries(entries, children_only = false) + 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)) + assert_raise(ArgumentError) {Dir.entries(@root+"\0")} + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + assert_equal(enc, Dir.entries(@root, encoding: enc).first.encoding) + end + end + + def test_foreach + assert_entries(Dir.open(@root) {|dir| dir.each.to_a}) + assert_entries(Dir.foreach(@root).to_a) + assert_raise(ArgumentError) {Dir.foreach(@root+"\0").to_a} + newdir = @root+"/new" + e = Dir.foreach(newdir) + assert_raise(Errno::ENOENT) {e.to_a} + Dir.mkdir(newdir) + File.write(newdir+"/a", "") + assert_equal(%w[. .. a], e.to_a.sort) + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + e = Dir.foreach(newdir, encoding: enc) + assert_equal(enc, e.to_a.first.encoding) + end + end + + def test_children + assert_entries(Dir.open(@root) {|dir| dir.children}, true) + assert_entries(Dir.children(@root), true) + assert_raise(ArgumentError) {Dir.children(@root+"\0")} + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + assert_equal(enc, Dir.children(@root, encoding: enc).first.encoding) + end + end + + def test_each_child + assert_entries(Dir.open(@root) {|dir| dir.each_child.to_a}, true) + assert_entries(Dir.each_child(@root).to_a, true) + assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a} + newdir = @root+"/new" + e = Dir.each_child(newdir) + assert_raise(Errno::ENOENT) {e.to_a} + Dir.mkdir(newdir) + File.write(newdir+"/a", "") + assert_equal(%w[a], e.to_a) + [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc| + e = Dir.each_child(newdir, encoding: enc) + assert_equal(enc, e.to_a.first.encoding) + end + end + + def test_dir_enc + dir = Dir.open(@root, encoding: "UTF-8") + begin + while name = dir.read + assert_equal(Encoding.find("UTF-8"), name.encoding) + end + ensure + dir.close + end + + dir = Dir.open(@root, encoding: "ASCII-8BIT") + begin + while name = dir.read + assert_equal(Encoding.find("ASCII-8BIT"), name.encoding) + end + ensure + dir.close + 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| + File.symlink(File.join(@root, f), + File.join(@root, "symlink-#{ f }")) + end + rescue NotImplementedError, Errno::EACCES + return + end + + assert_equal([*"a".."z", *"symlink-a".."symlink-z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort, + Dir.glob(File.join(@root, "*/"))) + + assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }], + Dir.glob(File.join(@root, "**/"))) + 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]' + omit unless /\A\w:/ =~ ENV["ProgramFiles"] + short = "#$&/PROGRA~1" + omit 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 + + def test_home_windows + setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH]) + + ENV['HOME'] = "C:\\ruby\\home" + assert_equal("C:/ruby/home", Dir.home) + + ENV['USERPROFILE'] = "C:\\ruby\\userprofile" + assert_equal("C:/ruby/home", Dir.home) + ENV.delete('HOME') + assert_equal("C:/ruby/userprofile", Dir.home) + + ENV['HOMEDRIVE'] = "C:" + ENV['HOMEPATH'] = "\\ruby\\homepath" + assert_equal("C:/ruby/userprofile", Dir.home) + ENV.delete('USERPROFILE') + assert_equal("C:/ruby/homepath", Dir.home) + end + + def test_home_at_startup_windows + env = {'HOME' => "C:\\ruby\\home"} + args = [env] + assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_equal("C:/ruby/home", Dir.home) + end; + + env['USERPROFILE'] = "C:\\ruby\\userprofile" + assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_equal("C:/ruby/home", Dir.home) + end; + + env['HOME'] = nil + assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_equal("C:/ruby/userprofile", Dir.home) + end; + + env['HOMEDRIVE'] = "C:" + env['HOMEPATH'] = "\\ruby\\homepath" + assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_equal("C:/ruby/userprofile", Dir.home) + end; + + env['USERPROFILE'] = nil + assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_equal("C:/ruby/homepath", Dir.home) + end; + end + + def test_children_long_name + Dir.mktmpdir do |dirname| + longest_possible_component = "b" * 255 + long_path = File.join(dirname, longest_possible_component) + Dir.mkdir(long_path) + File.write("#{long_path}/c", "") + assert_equal(%w[c], Dir.children(long_path)) + ensure + File.unlink("#{long_path}/c") + Dir.rmdir(long_path) + end + rescue Errno::ENOENT + omit "File system does not support long file name" + end + end + + def test_home + setup_envs + + 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 + end + + if Encoding.find("filesystem") == Encoding::UTF_8 + # On Windows and macOS, file system encoding is always UTF-8. + def test_home_utf8 + setup_envs + + ENV["HOME"] = "/\u{e4}~\u{1f3e0}" + assert_equal("/\u{e4}~\u{1f3e0}", Dir.home) + end + end + + def test_symlinks_not_resolved + 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['*'] + assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*'] + 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_for_fd + if Dir.respond_to? :for_fd + begin + new_dir = Dir.new('..') + for_fd_dir = Dir.for_fd(new_dir.fileno) + assert_equal(new_dir.chdir{Dir.pwd}, for_fd_dir.chdir{Dir.pwd}) + ensure + new_dir&.close + if for_fd_dir + assert_raise(Errno::EBADF) { for_fd_dir.close } + end + end + else + assert_raise(NotImplementedError) { Dir.for_fd(0) } + 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 + fs = [] + tap {tap {tap {(0..100).each {fs << open(IO::NULL)}}}} + rescue Errno::EMFILE + ensure + fs.clear + end + list = Dir.glob("*") + assert_not_empty(list) + assert_equal([*"a".."z"], list) + end; + end if defined?(Process::RLIMIT_NOFILE) + + def test_glob_array_with_destructive_element + args = Array.new(100, "") + pat = Struct.new(:ary).new(args) + args.push(pat, *Array.new(100) {"."*40}) + def pat.to_path + ary.clear + GC.start + "" + end + assert_empty(Dir.glob(args)) + end +end diff --git a/test/ruby/test_dir_m17n.rb b/test/ruby/test_dir_m17n.rb new file mode 100644 index 0000000000..cdf8b44ef2 --- /dev/null +++ b/test/ruby/test_dir_m17n.rb @@ -0,0 +1,428 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' +require '-test-/file' + +class TestDir_M17N < Test::Unit::TestCase + def with_tmpdir + Dir.mktmpdir {|dir| + Dir.chdir(dir) { + yield dir + } + } + 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") {} + ents = Dir.entries(".") + if /mswin|mingw/ =~ RUBY_PLATFORM + filename = filename.encode("UTF-8") + end + assert_include(ents, filename) + EOS + + return if /cygwin/ =~ RUBY_PLATFORM + assert_separately(%w[-EASCII-8BIT], <<-EOS, :chdir=>dir) + filename = #{code}.chr('UTF-8').force_encoding("ASCII-8BIT") + ents = Dir.entries(".") + if /mswin|mingw/ =~ RUBY_PLATFORM + filename.force_encoding("UTF-8") + end + assert_include(ents, filename) + EOS + } + end + + ## UTF-8 default_external, no default_internal + + def test_filename_extutf8 + with_tmpdir {|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||{})) + assert_include(ents, filename) + EOS + } + end + + def test_filename_extutf8_invalid + return if /cygwin/ =~ RUBY_PLATFORM + # High Sierra's APFS cannot use invalid filenames + return if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs" + with_tmpdir {|d| + assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) + filename = "\xff".dup.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||{})) + filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") + assert_include(ents, filename) + EOS + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) + filename = "\xff".dup.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||{})) + filename = "%FF" if /darwin/ =~ RUBY_PLATFORM && ents.include?("%FF") + assert_include(ents, filename) + EOS + } + end unless /mswin|mingw/ =~ RUBY_PLATFORM + + def test_filename_as_bytes_extutf8 + with_tmpdir {|d| + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) + filename = "\xc2\xa1".dup.force_encoding("utf-8") + File.open(filename, "w") {} + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", **(opts||{})) + assert_include(ents, filename) + EOS + assert_separately(%w[-EUTF-8], <<-'EOS', :chdir=>d) + if /mswin|mingw|darwin/ =~ RUBY_PLATFORM + filename = "\x8f\xa2\xc2".dup.force_encoding("euc-jp") + else + filename = "\xc2\xa1".dup.force_encoding("euc-jp") + end + assert_nothing_raised(Errno::ENOENT) do + open(filename) {} + end + EOS + # no meaning test on windows + unless /mswin|mingw|darwin/ =~ RUBY_PLATFORM + assert_separately(%W[-EUTF-8], <<-'EOS', :chdir=>d) + filename1 = "\xc2\xa1".dup.force_encoding("utf-8") + filename2 = "\xc2\xa1".dup.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 + + ## UTF-8 default_external, EUC-JP default_internal + + def test_filename_extutf8_inteucjp_representable + with_tmpdir {|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||{})) + assert_include(ents, filename) + EOS + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.force_encoding("euc-jp") + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", **(opts||{})) + assert_include(ents, filename) + EOS + assert_separately(%w[-EUTF-8:EUC-JP], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.force_encoding("euc-jp") + assert_nothing_raised(Errno::ENOENT) do + open(filename) {} + end + EOS + } + end + + def test_filename_extutf8_inteucjp_unrepresentable + with_tmpdir {|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||{})) + assert_include(ents, filename1) + assert_include(ents, filename2) + EOS + 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".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", **(opts||{})) + assert_include(ents, filename1) + assert_include(ents, filename2) + EOS + 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".dup.force_encoding("euc-jp") # HIRAGANA LETTER A in EUC-JP + assert_file.stat(filename1) + assert_file.stat(filename2) + assert_file.stat(filename3) + EOS + } + end + + ## others + + def test_filename_bytes_euc_jp + return if /cygwin/ =~ RUBY_PLATFORM + with_tmpdir {|d| + assert_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.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") } + 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_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.force_encoding("euc-jp") + File.open(filename, "w") {} + ents = Dir.entries(".") + if /darwin/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8").force_encoding("euc-jp") + elsif /mswin|mingw/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8") + end + assert_include(ents, filename) + EOS + assert_separately(%w[-EASCII-8BIT], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.force_encoding('ASCII-8BIT') + ents = Dir.entries(".") + unless ents.include?(filename) + case RUBY_PLATFORM + when /darwin/ + filename = filename.encode("utf-8", "euc-jp").b + when /mswin|mingw/ + filename = filename.encode("utf-8", "euc-jp") + end + end + assert_include(ents, filename) + EOS + } + end + + 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_separately(%w[-EEUC-JP], <<-'EOS', :chdir=>d) + filename = "\xA4\xA2".dup.force_encoding("euc-jp") + File.open(filename, "w") {} + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", **(opts||{})) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.encode("utf-8", "euc-jp").force_encoding("euc-jp") + end + assert_include(ents, filename) + EOS + assert_separately(%w[-EEUC-JP:UTF-8], <<-'EOS', :chdir=>d) + filename = "\u3042".dup + opts = {:encoding => Encoding.default_external} if /mswin|mingw/ =~ RUBY_PLATFORM + ents = Dir.entries(".", **(opts||{})) + if /darwin/ =~ RUBY_PLATFORM + filename = filename.force_encoding("euc-jp") + end + assert_include(ents, filename) + EOS + } + 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| + File.binwrite("#{dir}/x", "") + 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| + File.binwrite("#{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_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\\".dup.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| File.binwrite(f, "")} + a = "file_one*".dup.force_encoding Encoding::IBM437 + b = "file_two*".dup.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 = "*".dup.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 + orig.each {|o| o.force_encoding(enc) } + ents = Dir.entries(".").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| + enc = Encoding.find("filesystem") + enc = Encoding::ASCII_8BIT if enc == Encoding::US_ASCII + n = o.dup.force_encoding(enc) + expected << n + with_tmpdir { + Dir.mkdir(o) + results << File.basename(Dir.chdir(o) {Dir.pwd}) + } + } + PP.assert_equal(expected, results) + end +end diff --git a/test/ruby/test_dup.rb b/test/ruby/test_dup.rb new file mode 100644 index 0000000000..75c4fc0339 --- /dev/null +++ b/test/ruby/test_dup.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestDup < Test::Unit::TestCase + module M001; end + module M002; end + module M003; include M002; end + module M002; include M001; end + module M003; include M002; end + + def test_dup + foo = Object.new + def foo.test + "test" + end + bar = foo.dup + def bar.test2 + "test2" + end + + assert_equal("test2", bar.test2) + assert_raise(NoMethodError) { bar.test } + assert_equal("test", foo.test) + + assert_raise(NoMethodError) {foo.test2} + + assert_equal([M003, M002, M001], M003.ancestors) + end + + def test_frozen_properties_not_retained_on_dup + obj = Object.new.freeze + duped_obj = obj.dup + + assert_predicate(obj, :frozen?) + refute_predicate(duped_obj, :frozen?) + end + + def test_ivar_retained_on_dup + obj = Object.new + obj.instance_variable_set(:@a, 1) + duped_obj = obj.dup + + assert_equal(obj.instance_variable_get(:@a), 1) + assert_equal(duped_obj.instance_variable_get(:@a), 1) + end + + def test_ivars_retained_on_extended_obj_dup + ivars = { :@a => 1, :@b => 2, :@c => 3, :@d => 4 } + obj = Object.new + ivars.each do |ivar_name, val| + obj.instance_variable_set(ivar_name, val) + end + + duped_obj = obj.dup + + ivars.each do |ivar_name, val| + assert_equal(obj.instance_variable_get(ivar_name), val) + assert_equal(duped_obj.instance_variable_get(ivar_name), val) + end + end + + def test_frozen_properties_not_retained_on_dup_with_ivar + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + + duped_obj = obj.dup + + assert_predicate(obj, :frozen?) + assert_equal(obj.instance_variable_get(:@a), 1) + + refute_predicate(duped_obj, :frozen?) + assert_equal(duped_obj.instance_variable_get(:@a), 1) + end + + def test_user_flags + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1, 2, 3].dup + assert_equal [], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Array + undef initialize_copy + def initialize_copy(*); end + end + x = [1,2,3,4,5,6,7][1..-2].dup + x.push(1,1,1,1,1) + assert_equal [1, 1, 1, 1, 1], x, '[Bug #14847]' + EOS + + assert_separately([], <<-EOS) + # + class Hash + undef initialize_copy + def initialize_copy(*); end + end + h = {} + h.default_proc = proc { raise } + h = h.dup + assert_equal nil, h[:not_exist], '[Bug #14847]' + EOS + end +end diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb new file mode 100644 index 0000000000..1d0641e918 --- /dev/null +++ b/test/ruby/test_econv.rb @@ -0,0 +1,943 @@ +# 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) + case opts + when Hash + res = ec.primitive_convert(src, dst, off, len, **opts) + else + res = ec.primitive_convert(src, dst, off, len, opts) + end + assert_equal([edst.b, esrc.b, eres], + [dst.b, src.b, res]) + end + + def assert_econv(converted, eres, obuf_bytesize, ec, consumed, rest, opts=nil) + ec = Encoding::Converter.new(*ec) if Array === ec + i = consumed + rest + o = "" + ret = ec.primitive_convert(i, o, 0, obuf_bytesize, opts) + assert_equal([converted, eres, rest], + [o, ret, i]) + end + + 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&.b, + e_readagain_bytes&.b], + ec.primitive_errinfo) + end + + def test_s_asciicompat_encoding + assert_equal(Encoding::STATELESS_ISO_2022_JP, Encoding::Converter.asciicompat_encoding("ISO-2022-JP")) + assert_equal(Encoding::STATELESS_ISO_2022_JP, Encoding::Converter.asciicompat_encoding(Encoding::ISO_2022_JP)) + assert_equal(Encoding::UTF_8, Encoding::Converter.asciicompat_encoding("UTF-16BE")) + assert_equal(Encoding::UTF_8, Encoding::Converter.asciicompat_encoding("UTF-16LE")) + assert_equal(Encoding::UTF_8, Encoding::Converter.asciicompat_encoding("UTF-32BE")) + assert_equal(Encoding::UTF_8, Encoding::Converter.asciicompat_encoding("UTF-32LE")) + assert_nil(Encoding::Converter.asciicompat_encoding("EUC-JP")) + assert_nil(Encoding::Converter.asciicompat_encoding("UTF-8")) + assert_nil(Encoding::Converter.asciicompat_encoding(Encoding::UTF_8)) + assert_nil(Encoding::Converter.asciicompat_encoding("xml_attr_escape")) + assert_nil(Encoding::Converter.asciicompat_encoding("encoding-not-exist")) + end + + def test_asciicompat_encoding_iso2022jp + acenc = Encoding::Converter.asciicompat_encoding("ISO-2022-JP") + str = "\e$B~~\e(B".force_encoding("iso-2022-jp") + str2 = str.encode(acenc) + str3 = str2.encode("ISO-2022-JP") + assert_equal(str, str3) + end + + def test_s_new + assert_kind_of(Encoding::Converter, Encoding::Converter.new("UTF-8", "EUC-JP")) + assert_kind_of(Encoding::Converter, Encoding::Converter.new(Encoding::UTF_8, Encoding::EUC_JP)) + end + + def test_s_new_convpath + assert_equal([], Encoding::Converter.new([]).convpath) + assert_equal([[Encoding::UTF_8, Encoding::EUC_JP]], + Encoding::Converter.new([["UTF-8", "EUC-JP"]]).convpath) + assert_equal([[Encoding::UTF_8, Encoding::WINDOWS_31J]], + Encoding::Converter.new([["utf-8", "cp932"]]).convpath) + assert_equal([[Encoding::UTF_8, Encoding::EUC_JP]], + Encoding::Converter.new([[Encoding::UTF_8, Encoding::EUC_JP]]).convpath) + assert_equal([[Encoding::ISO_8859_1, Encoding::UTF_8], + [Encoding::UTF_8, Encoding::EUC_JP]], + Encoding::Converter.new([["iso-8859-1", "euc-jp"]]).convpath) + assert_equal([[Encoding::ISO_8859_1, Encoding::UTF_8], + [Encoding::UTF_8, Encoding::EUC_JP], + "universal_newline"], + Encoding::Converter.new([["iso-8859-1", "euc-jp"], "universal_newline"]).convpath) + assert_equal(["universal_newline", + [Encoding::ISO_8859_1, Encoding::UTF_8], + [Encoding::UTF_8, Encoding::EUC_JP], + "universal_newline"], + Encoding::Converter.new(["universal_newline", ["iso-8859-1", "euc-jp"], "universal_newline"]).convpath) + end + + def test_s_new_fail + name1 = "encoding-which-is-not-exist-1" + name2 = "encoding-which-is-not-exist-2" + + assert_raise(Encoding::ConverterNotFoundError) { + Encoding::Converter.new(name1, name2) + } + + encoding_list = Encoding.list.map {|e| e.name } + assert_not_include(encoding_list, name1) + assert_not_include(encoding_list, name2) + end + + def test_newline_converter_with_ascii_incompatible + assert_nothing_raised { + Encoding::Converter.new("UTF-8", "UTF-16BE", Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR) + } + assert_nothing_raised { + Encoding::Converter.new("UTF-16BE", "UTF-8", Encoding::Converter::CRLF_NEWLINE_DECORATOR) + } + assert_nothing_raised { + Encoding::Converter.new("UTF-16BE", "UTF-8", Encoding::Converter::CR_NEWLINE_DECORATOR) + } + + assert_nothing_raised { + Encoding::Converter.new("UTF-16BE", "UTF-8", Encoding::Converter::UNIVERSAL_NEWLINE_DECORATOR) + } + assert_nothing_raised { + Encoding::Converter.new("UTF-8", "UTF-16BE", Encoding::Converter::CRLF_NEWLINE_DECORATOR) + } + assert_nothing_raised { + Encoding::Converter.new("UTF-8", "UTF-16BE", Encoding::Converter::CR_NEWLINE_DECORATOR) + } + end + + def test_get_encoding + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + assert_equal(Encoding::UTF_8, ec.source_encoding) + assert_equal(Encoding::EUC_JP, ec.destination_encoding) + end + + def test_result_encoding + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + dst = "".force_encoding("ASCII-8BIT") + assert_equal(Encoding::ASCII_8BIT, dst.encoding) + ec.primitive_convert("\u{3042}", dst, nil, 10) + assert_equal(Encoding::EUC_JP, dst.encoding) + end + + def test_output_region + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + ec.primitive_convert(src="a", dst="b", nil, 1, :partial_input=>true) + assert_equal("ba", dst) + ec.primitive_convert(src="a", dst="b", 0, 1, :partial_input=>true) + assert_equal("a", dst) + ec.primitive_convert(src="a", dst="b", 1, 1, :partial_input=>true) + assert_equal("ba", dst) + assert_raise(ArgumentError) { + ec.primitive_convert(src="a", dst="b", 2, 1, :partial_input=>true) + } + assert_raise(ArgumentError) { + ec.primitive_convert(src="a", dst="b", -1, 1, :partial_input=>true) + } + assert_raise(ArgumentError) { + ec.primitive_convert(src="a", dst="b", 1, -1, :partial_input=>true) + } + end + + def test_nil_source_buffer + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + ret = ec.primitive_convert(nil, "", nil, 10) + assert_equal(:finished, ret) + end + + def test_nil_destination_bytesize + ec = Encoding::Converter.new("Shift_JIS", "UTF-8") + n = 10000 + src = "\xa1".force_encoding("Shift_JIS") * n + ret = ec.primitive_convert(src, dst="", nil, nil) + assert_equal(:finished, ret) + assert_equal("\xEF\xBD\xA1".force_encoding("UTF-8") * n, dst) + end + + def test_nil_destination_bytesize2 + ec = Encoding::Converter.new("Shift_JIS", "UTF-8") + n = 10000 + src = "\xa1".force_encoding("Shift_JIS") * n + ret = ec.primitive_convert(src, dst="") + assert_equal(:finished, ret) + assert_equal("\xEF\xBD\xA1".force_encoding("UTF-8") * n, dst) + end + + def test_nil_destination_bytesize_with_nonnil_byteoffset + ec = Encoding::Converter.new("Shift_JIS", "UTF-8") + n = 2000 + src = "\xa1".force_encoding("Shift_JIS") * n + dst = "abcd" * 2000 + ret = ec.primitive_convert(src, dst, 3, nil) + assert_equal(:finished, ret) + assert_equal("abc" + "\xEF\xBD\xA1".force_encoding("UTF-8") * n, dst) + end + + def test_partial_input + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + ret = ec.primitive_convert(src="", dst="", nil, 10, :partial_input=>true) + assert_equal(:source_buffer_empty, ret) + ret = ec.primitive_convert(src="", dst="", nil, 10) + assert_equal(:finished, ret) + end + + def test_accumulate_dst1 + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + a = ["", "abc\u{3042}def", ec, nil, 1] + check_ec("a", "c\u{3042}def", :destination_buffer_full, *a) + check_ec("ab", "\u{3042}def", :destination_buffer_full, *a) + check_ec("abc", "def", :destination_buffer_full, *a) + check_ec("abc\xA4", "def", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2", "ef", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2d", "f", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2de", "", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2def", "", :finished, *a) + end + + def test_accumulate_dst2 + ec = Encoding::Converter.new("UTF-8", "EUC-JP") + a = ["", "abc\u{3042}def", ec, nil, 2] + check_ec("ab", "\u{3042}def", :destination_buffer_full, *a) + check_ec("abc\xA4", "def", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2d", "f", :destination_buffer_full, *a) + check_ec("abc\xA4\xA2def", "", :finished, *a) + end + + def test_eucjp_to_utf8 + assert_econv("", :finished, 100, ["UTF-8", "EUC-JP"], "", "") + assert_econv("a", :finished, 100, ["UTF-8", "EUC-JP"], "a", "") + end + + def test_iso2022jp + assert_econv("", :finished, 100, ["Shift_JIS", "ISO-2022-JP"], "", "") + end + + def test_iso2022jp_encode + ec = Encoding::Converter.new("EUC-JP", "ISO-2022-JP") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "a"; check_ec("a", "", :source_buffer_empty, *a) + src << "\xA2"; check_ec("a", "", :source_buffer_empty, *a) + src << "\xA4"; check_ec("a\e$B\"$", "", :source_buffer_empty, *a) + src << "\xA1"; check_ec("a\e$B\"$", "", :source_buffer_empty, *a) + src << "\xA2"; check_ec("a\e$B\"$!\"", "", :source_buffer_empty, *a) + src << "b"; check_ec("a\e$B\"$!\"\e(Bb", "", :source_buffer_empty, *a) + src << "\xA2\xA6"; check_ec("a\e$B\"$!\"\e(Bb\e$B\"&", "", :source_buffer_empty, *a) + a[-1] = 0; check_ec("a\e$B\"$!\"\e(Bb\e$B\"&\e(B", "", :finished, *a) + end + + def test_iso2022jp_decode + ec = Encoding::Converter.new("ISO-2022-JP", "EUC-JP") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "a"; check_ec("a", "", :source_buffer_empty, *a) + src << "\e"; check_ec("a", "", :source_buffer_empty, *a) + src << "$"; check_ec("a", "", :source_buffer_empty, *a) + src << "B"; check_ec("a", "", :source_buffer_empty, *a) + src << "\x21"; check_ec("a", "", :source_buffer_empty, *a) + src << "\x22"; check_ec("a\xA1\xA2", "", :source_buffer_empty, *a) + src << "\n"; check_ec("a\xA1\xA2", "", :invalid_byte_sequence, *a) + src << "\x23"; check_ec("a\xA1\xA2", "", :source_buffer_empty, *a) + src << "\x24"; check_ec("a\xA1\xA2\xA3\xA4", "", :source_buffer_empty, *a) + src << "\e"; check_ec("a\xA1\xA2\xA3\xA4", "", :source_buffer_empty, *a) + src << "("; check_ec("a\xA1\xA2\xA3\xA4", "", :source_buffer_empty, *a) + src << "B"; check_ec("a\xA1\xA2\xA3\xA4", "", :source_buffer_empty, *a) + src << "c"; check_ec("a\xA1\xA2\xA3\xA4c", "", :source_buffer_empty, *a) + src << "\n"; check_ec("a\xA1\xA2\xA3\xA4c\n","", :source_buffer_empty, *a) + end + + def test_invalid + assert_econv("", :invalid_byte_sequence, 100, ["UTF-8", "EUC-JP"], "\x80", "") + assert_econv("a", :invalid_byte_sequence, 100, ["UTF-8", "EUC-JP"], "a\x80", "") + assert_econv("a", :invalid_byte_sequence, 100, ["UTF-8", "EUC-JP"], "a\x80", "\x80") + assert_econv("abc", :invalid_byte_sequence, 100, ["UTF-8", "EUC-JP"], "abc\xFF", "def") + assert_econv("abc", :invalid_byte_sequence, 100, ["Shift_JIS", "EUC-JP"], "abc\xFF", "def") + assert_econv("abc", :invalid_byte_sequence, 100, ["ISO-2022-JP", "EUC-JP"], "abc\xFF", "def") + end + + def test_invalid2 + ec = Encoding::Converter.new("Shift_JIS", "EUC-JP") + a = ["", "abc\xFFdef", ec, nil, 1] + check_ec("a", "c\xFFdef", :destination_buffer_full, *a) + check_ec("ab", "\xFFdef", :destination_buffer_full, *a) + check_ec("abc", "def", :invalid_byte_sequence, *a) + check_ec("abcd", "f", :destination_buffer_full, *a) + check_ec("abcde", "", :destination_buffer_full, *a) + check_ec("abcdef", "", :finished, *a) + end + + def test_invalid3 + ec = Encoding::Converter.new("Shift_JIS", "EUC-JP") + a = ["", "abc\xFFdef", ec, nil, 10] + check_ec("abc", "def", :invalid_byte_sequence, *a) + check_ec("abcdef", "", :finished, *a) + end + + def test_invalid4 + ec = Encoding::Converter.new("Shift_JIS", "EUC-JP") + a = ["", "abc\xFFdef", ec, nil, 10, :after_output=>true] + check_ec("a", "bc\xFFdef", :after_output, *a) + check_ec("ab", "c\xFFdef", :after_output, *a) + check_ec("abc", "\xFFdef", :after_output, *a) + check_ec("abc", "def", :invalid_byte_sequence, *a) + check_ec("abcd", "ef", :after_output, *a) + check_ec("abcde", "f", :after_output, *a) + check_ec("abcdef", "", :after_output, *a) + check_ec("abcdef", "", :finished, *a) + end + + def test_invalid_utf16le + ec = Encoding::Converter.new("UTF-16LE", "UTF-8") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "A"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x01"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x02"; check_ec("A", "", :invalid_byte_sequence, *a) + src << "\x03"; check_ec("A\u{0201}", "", :source_buffer_empty, *a) + src << "\x04"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A\u{0201}\u{0403}", "", :invalid_byte_sequence, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xdc"; check_ec("A\u{0201}\u{0403}\u{10000}", "", :source_buffer_empty, *a) + end + + def test_invalid_utf16be + ec = Encoding::Converter.new("UTF-16BE", "UTF-8") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "A"; check_ec("A", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x02"; check_ec("A", "", :invalid_byte_sequence, *a) + src << "\x01"; check_ec("A\u{0201}", "", :source_buffer_empty, *a) + src << "\x04"; check_ec("A\u{0201}", "", :source_buffer_empty, *a) + src << "\x03"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xd8"; check_ec("A\u{0201}\u{0403}", "", :invalid_byte_sequence, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\xdc"; check_ec("A\u{0201}\u{0403}", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A\u{0201}\u{0403}\u{10000}", "", :source_buffer_empty, *a) + end + + def test_invalid_utf32be + ec = Encoding::Converter.new("UTF-32BE", "UTF-8") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "A"; check_ec("A", "", :source_buffer_empty, *a) + + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\xdc"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :invalid_byte_sequence, *a) + + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "B"; check_ec("AB", "", :source_buffer_empty, *a) + + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + src << "C"; check_ec("ABC", "", :source_buffer_empty, *a) + end + + def test_invalid_utf32le + ec = Encoding::Converter.new("UTF-32LE", "UTF-8") + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "A"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\xdc"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :invalid_byte_sequence, *a) + + src << "B"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("A", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + + src << "C"; check_ec("AB", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("AB", "", :source_buffer_empty, *a) + src << "\x00"; check_ec("ABC", "", :source_buffer_empty, *a) + end + + def test_errors + ec = Encoding::Converter.new("UTF-16BE", "EUC-JP") + a = ["", "\xFF\xFE\x00A\xDC\x00\x00B", ec, nil, 10] + check_ec("", "\x00A\xDC\x00\x00B", :undefined_conversion, *a) + check_ec("A", "\x00B", :invalid_byte_sequence, *a) # \xDC\x00 is invalid as UTF-16BE + check_ec("AB", "", :finished, *a) + end + + def test_errors2 + ec = Encoding::Converter.new("UTF-16BE", "EUC-JP") + a = ["", "\xFF\xFE\x00A\xDC\x00\x00B", ec, nil, 10, :after_output=>true] + check_ec("", "\x00A\xDC\x00\x00B", :undefined_conversion, *a) + check_ec("A", "\xDC\x00\x00B", :after_output, *a) + check_ec("A", "\x00B", :invalid_byte_sequence, *a) + check_ec("AB", "", :after_output, *a) + check_ec("AB", "", :finished, *a) + end + + def test_universal_newline + ec = Encoding::Converter.new("UTF-8", "EUC-JP", universal_newline: true) + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "abc\r\ndef"; check_ec("abc\ndef", "", :source_buffer_empty, *a) + src << "ghi\njkl"; check_ec("abc\ndefghi\njkl", "", :source_buffer_empty, *a) + src << "mno\rpqr"; check_ec("abc\ndefghi\njklmno\npqr", "", :source_buffer_empty, *a) + src << "stu\r"; check_ec("abc\ndefghi\njklmno\npqrstu", "", :source_buffer_empty, *a) + src << "\nvwx"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx", "", :source_buffer_empty, *a) + src << "\nyz"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx\nyz", "", :source_buffer_empty, *a) + end + + def test_universal_newline2 + ec = Encoding::Converter.new("", "", universal_newline: true) + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "abc\r\ndef"; check_ec("abc\ndef", "", :source_buffer_empty, *a) + src << "ghi\njkl"; check_ec("abc\ndefghi\njkl", "", :source_buffer_empty, *a) + src << "mno\rpqr"; check_ec("abc\ndefghi\njklmno\npqr", "", :source_buffer_empty, *a) + src << "stu\r"; check_ec("abc\ndefghi\njklmno\npqrstu", "", :source_buffer_empty, *a) + src << "\nvwx"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx", "", :source_buffer_empty, *a) + src << "\nyz"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx\nyz", "", :source_buffer_empty, *a) + end + + def test_universal_newline3 + ec = Encoding::Converter.new("", "", universal_newline: true) + a = ["", src="", ec, nil, 50, :partial_input=>true] + src << "abc\r\ndef"; check_ec("abc\ndef", "", :source_buffer_empty, *a) + src << "ghi\njkl"; check_ec("abc\ndefghi\njkl", "", :source_buffer_empty, *a) + src << "mno\rpqr"; check_ec("abc\ndefghi\njklmno\npqr", "", :source_buffer_empty, *a) + src << "stu\r"; check_ec("abc\ndefghi\njklmno\npqrstu", "", :source_buffer_empty, *a) + src << "\nvwx"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx", "", :source_buffer_empty, *a) + src << "\nyz"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx\nyz", "", :source_buffer_empty, *a) + src << "\r"; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx\nyz", "", :source_buffer_empty, *a) + a[-1] = nil + src << ""; check_ec("abc\ndefghi\njklmno\npqrstu\nvwx\nyz\n", "", :finished, *a) + end + + def test_crlf_newline + ec = Encoding::Converter.new("UTF-8", "EUC-JP", crlf_newline: true) + assert_econv("abc\r\ndef", :finished, 50, ec, "abc\ndef", "") + end + + def test_crlf_newline2 + ec = Encoding::Converter.new("", "", crlf_newline: true) + assert_econv("abc\r\ndef", :finished, 50, ec, "abc\ndef", "") + end + + def test_cr_newline + ec = Encoding::Converter.new("UTF-8", "EUC-JP", cr_newline: true) + assert_econv("abc\rdef", :finished, 50, ec, "abc\ndef", "") + end + + def test_cr_newline2 + ec = Encoding::Converter.new("", "", cr_newline: true) + 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] + check_ec("a", "bc\u{3042}def", :after_output, *a) + check_ec("ab", "c\u{3042}def", :after_output, *a) + check_ec("abc", "\u{3042}def", :after_output, *a) + check_ec("abc\xA4\xA2", "def", :after_output, *a) + check_ec("abc\xA4\xA2d", "ef", :after_output, *a) + check_ec("abc\xA4\xA2de", "f", :after_output, *a) + check_ec("abc\xA4\xA2def", "", :after_output, *a) + check_ec("abc\xA4\xA2def", "", :finished, *a) + end + + def test_errinfo_invalid_euc_jp + ec = Encoding::Converter.new("EUC-JP", "Shift_JIS") + 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("\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("\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("\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("\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@", "", 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", "", nil, 10) + assert_errinfo(:invalid_byte_sequence, "UTF-16LE", "UTF-8", "\x00\xD8", "@\x00", ec) + assert_equal("", src) + end + + def test_output_iso2022jp + ec = Encoding::Converter.new("EUC-JP", "ISO-2022-JP") + ec.primitive_convert(src="\xa1\xa1", dst="", nil, 10, :partial_input=>true) + assert_equal("\e$B!!".force_encoding("ISO-2022-JP"), dst) + assert_equal(nil, ec.insert_output("???")) + ec.primitive_convert("", dst, nil, 10, :partial_input=>true) + assert_equal("\e$B!!\e(B???".force_encoding("ISO-2022-JP"), dst) + ec.primitive_convert(src="\xa1\xa2", dst, nil, 10, :partial_input=>true) + assert_equal("\e$B!!\e(B???\e$B!\"".force_encoding("ISO-2022-JP"), dst) + + assert_equal(nil, ec.insert_output("\xA1\xA1".force_encoding("EUC-JP"))) + ec.primitive_convert("", dst, nil, 10, :partial_input=>true) + assert_equal("\e$B!!\e(B???\e$B!\"!!".force_encoding("ISO-2022-JP"), dst) + + ec.primitive_convert(src="\xa1\xa3", dst, nil, 10, :partial_input=>true) + assert_equal("\e$B!!\e(B???\e$B!\"!!!\#".force_encoding("ISO-2022-JP"), dst) + + assert_equal(nil, ec.insert_output("\u3042")) + ec.primitive_convert("", dst, nil, 10, :partial_input=>true) + assert_equal("\e$B!!\e(B???\e$B!\"!!!\#$\"".force_encoding("ISO-2022-JP"), dst) + + assert_raise(Encoding::UndefinedConversionError) { + ec.insert_output("\uFFFD") + } + + assert_equal("\e$B!!\e(B???\e$B!\"!!!\#$\"".force_encoding("ISO-2022-JP"), dst) + + ec.primitive_convert("", dst, nil, 10) + assert_equal("\e$B!!\e(B???\e$B!\"!!!\#$\"\e(B".force_encoding("ISO-2022-JP"), dst) + end + + def test_exc_invalid + err = assert_raise(Encoding::InvalidByteSequenceError) { + "abc\xa4def".encode("ISO-8859-1", "EUC-JP") + } + assert_equal("EUC-JP", err.source_encoding_name) + assert_equal("UTF-8", err.destination_encoding_name) + assert_equal(Encoding::EUC_JP, err.source_encoding) + assert_equal(Encoding::UTF_8, err.destination_encoding) + assert_equal("\xA4".force_encoding("ASCII-8BIT"), err.error_bytes) + assert_equal("d", err.readagain_bytes) + assert_equal(false, err.incomplete_input?) + end + + def test_exc_incomplete + err = assert_raise(Encoding::InvalidByteSequenceError) { + "abc\xa4".encode("ISO-8859-1", "EUC-JP") + } + assert_equal("EUC-JP", err.source_encoding_name) + assert_equal("UTF-8", err.destination_encoding_name) + assert_equal(Encoding::EUC_JP, err.source_encoding) + assert_equal(Encoding::UTF_8, err.destination_encoding) + assert_equal("\xA4".force_encoding("ASCII-8BIT"), err.error_bytes) + assert_equal(nil, err.readagain_bytes) + assert_equal(true, err.incomplete_input?) + end + + def test_exc_undef + err = assert_raise(Encoding::UndefinedConversionError) { + "abc\xa4\xa2def".encode("ISO-8859-1", "EUC-JP") + } + assert_equal("UTF-8", err.source_encoding_name) + assert_equal("ISO-8859-1", err.destination_encoding_name) + assert_equal(Encoding::UTF_8, err.source_encoding) + assert_equal(Encoding::ISO_8859_1, err.destination_encoding) + assert_equal("\u{3042}", err.error_char) + end + + def test_putback + ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1") + ret = ec.primitive_convert(src="abc\xa1def", dst="", nil, 10) + assert_equal(:invalid_byte_sequence, ret) + assert_equal(["abc", "ef"], [dst, src]) + src = ec.putback + src + assert_equal(["abc", "def"], [dst, src]) + ret = ec.primitive_convert(src, dst, nil, 10) + assert_equal(:finished, ret) + assert_equal(["abcdef", ""], [dst, src]) + end + + def test_putback2 + ec = Encoding::Converter.new("utf-16le", "euc-jp") + 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)) + assert_equal("", ec.putback(1)) + end + + def test_invalid_replace + ec = Encoding::Converter.new("UTF-8", "EUC-JP", invalid: :replace) + ret = ec.primitive_convert(src="abc\x80def", dst="", nil, 100) + assert_equal(:finished, ret) + assert_equal("", src) + assert_equal("abc?def", dst) + end + + def test_invalid_ignore + ec = Encoding::Converter.new("UTF-8", "EUC-JP", :invalid => :replace, :replace => "") + ret = ec.primitive_convert(src="abc\x80def", dst="", nil, 100) + assert_equal(:finished, ret) + assert_equal("", src) + assert_equal("abcdef", dst) + end + + def test_undef_replace + ec = Encoding::Converter.new("UTF-8", "EUC-JP", :undef => :replace) + ret = ec.primitive_convert(src="abc\u{fffd}def", dst="", nil, 100) + assert_equal(:finished, ret) + assert_equal("", src) + assert_equal("abc?def", dst) + end + + def test_undef_ignore + ec = Encoding::Converter.new("UTF-8", "EUC-JP", :undef => :replace, :replace => "") + ret = ec.primitive_convert(src="abc\u{fffd}def", dst="", nil, 100) + assert_equal(:finished, ret) + assert_equal("", src) + assert_equal("abcdef", dst) + end + + def test_noconv + ec = Encoding::Converter.new("", "") + assert_equal(nil, ec.source_encoding) + assert_equal(nil, ec.destination_encoding) + assert_equal([:source_buffer_empty, nil, nil, nil, nil], ec.primitive_errinfo) + a = ["", "abcdefg", ec, nil, 2] + check_ec("ab", "cdefg", :destination_buffer_full, *a) + check_ec("abcd", "efg", :destination_buffer_full, *a) + check_ec("abcdef", "g", :destination_buffer_full, *a) + check_ec("abcdefg", "", :finished, *a) + end + + def test_noconv_partial + ec = Encoding::Converter.new("", "") + a = ["", "abcdefg", ec, nil, 2, :partial_input=>true] + check_ec("ab", "cdefg", :destination_buffer_full, *a) + check_ec("abcd", "efg", :destination_buffer_full, *a) + check_ec("abcdef", "g", :destination_buffer_full, *a) + check_ec("abcdefg", "", :source_buffer_empty, *a) + end + + def test_noconv_after_output + ec = Encoding::Converter.new("", "") + a = ["", "abcdefg", ec, nil, 2, :after_output=>true] + check_ec("a", "bcdefg", :after_output, *a) + check_ec("ab", "cdefg", :after_output, *a) + check_ec("abc", "defg", :after_output, *a) + check_ec("abcd", "efg", :after_output, *a) + check_ec("abcde", "fg", :after_output, *a) + check_ec("abcdef", "g", :after_output, *a) + check_ec("abcdefg", "", :after_output, *a) + check_ec("abcdefg", "", :finished, *a) + end + + def test_noconv_insert_output + ec = Encoding::Converter.new("", "") + ec.insert_output("xyz") + ret = ec.primitive_convert(src="abc", dst="", nil, 20) + assert_equal(:finished, ret) + assert_equal(["xyzabc", ""], [dst, src]) + end + + def test_convert + ec = Encoding::Converter.new("utf-8", "euc-jp") + assert_raise(Encoding::InvalidByteSequenceError) { ec.convert("a\x80") } + assert_raise(Encoding::UndefinedConversionError) { ec.convert("\ufffd") } + ret = ec.primitive_convert(nil, "", nil, nil) + assert_equal(:finished, ret) + assert_raise(ArgumentError) { ec.convert("a") } + end + + def test_finish_iso2022jp + ec = Encoding::Converter.new("utf-8", "iso-2022-jp") + assert_equal("\e$B$\"".force_encoding("iso-2022-jp"), ec.convert("\u3042")) + assert_equal("\e(B".force_encoding("iso-2022-jp"), ec.finish) + + end + + def test_finish_incomplete_error + ec = Encoding::Converter.new("utf-8", "euc-jp") + ec.convert("\xEF") + assert_raise(Encoding::InvalidByteSequenceError) { ec.finish } + end + + def test_last_error1 + ec = Encoding::Converter.new("sjis", "euc-jp") + assert_equal(nil, ec.last_error) + 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("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("\u{3042}", "") + err = ec.last_error + assert_kind_of(Encoding::UndefinedConversionError, err) + assert_equal("\u{3042}", err.error_char) + end + + def test_88591 + ec = Encoding::Converter.new("UTF-8", "ISO-8859-1") + ec.primitive_convert("\u{3042}", "") + err = ec.last_error + assert_kind_of(Encoding::UndefinedConversionError, err) + assert_equal("\u{3042}", err.error_char) + end + + def test_get_replacement + ec = Encoding::Converter.new("euc-jp", "iso-8859-1") + assert_equal("?", ec.replacement) + + ec = Encoding::Converter.new("euc-jp", "utf-8") + assert_equal("\uFFFD", ec.replacement) + end + + def test_set_replacement + ec = Encoding::Converter.new("utf-8", "us-ascii", :undef => :replace) + ec.replacement = "<undef>" + assert_equal("a <undef> b", ec.convert("a \u3042 b")) + end + + def test_econv_new_hash + ec = Encoding::Converter.new("utf-8", "us-ascii", :undef => :replace) + assert_equal("a ? b", ec.convert("a \u3042 b")) + ec = Encoding::Converter.new("utf-8", "us-ascii", :undef => :replace, :replace => "X") + assert_equal("a X b", ec.convert("a \u3042 b")) + end + + def test_hex_charref + ec = Encoding::Converter.new("UTF-8", "US-ASCII", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("あ", ec.convert("\u3042")) + + ec = Encoding::Converter.new("UTF-8", "EUC-JP", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("\xa4\xcf\xa4\xa1\xa4\xa4♥\xa1\xa3".force_encoding("euc-jp"), + ec.convert("\u{306f 3041 3044 2665 3002}")) + + ec = Encoding::Converter.new("UTF-8", "ISO-2022-JP", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("\e$B$O$!$$\e(B♥\e$B!#".force_encoding("ISO-2022-JP"), + ec.convert("\u{306f 3041 3044 2665 3002}")) + assert_equal("\e(B".force_encoding("ISO-2022-JP"), + ec.finish) + + ec = Encoding::Converter.new("EUC-JP", "US-ASCII", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("交換法則: n×m=m×n".force_encoding("ISO-8859-1"), + ec.convert("\xB8\xF2\xB4\xB9\xCB\xA1\xC2\xA7: n\xA1\xDFm=m\xA1\xDFn")) + + ec = Encoding::Converter.new("EUC-JP", "ISO-8859-1", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("交換法則: n\xD7m=m\xD7n".force_encoding("ISO-8859-1"), + ec.convert("\xB8\xF2\xB4\xB9\xCB\xA1\xC2\xA7: n\xA1\xDFm=m\xA1\xDFn")) + + ec = Encoding::Converter.new("UTF-8", "US-ASCII", Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal("&", ec.convert("&")) + end + + def test_xml_escape_text + ec = Encoding::Converter.new("", "amp_escape") + assert_equal('&<>"', ec.convert("&<>\"")) + assert_equal('', ec.finish) + + ec = Encoding::Converter.new("", "xml_text_escape") + assert_equal('&<>"', ec.convert("&<>\"")) + assert_equal('', ec.finish) + end + + def test_xml_escape_attr_content + ec = Encoding::Converter.new("", "xml_attr_content_escape") + assert_equal('', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_content_escape") + assert_equal('', ec.convert("")) + assert_equal('', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_content_escape") + assert_equal('"', ec.convert('"')) + assert_equal('', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_content_escape") + assert_equal('&<>"'', ec.convert("&<>\"'")) + assert_equal('', ec.finish) + end + + def test_xml_escape_attr_quote + ec = Encoding::Converter.new("", "xml_attr_quote") + assert_equal('""', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_quote") + assert_equal('', ec.convert("")) + assert_equal('""', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_quote") + assert_equal('""', ec.convert('"')) + assert_equal('"', ec.finish) + + ec = Encoding::Converter.new("", "xml_attr_quote") + assert_equal('"&<>"', ec.convert("&<>\"")) + assert_equal('"', ec.finish) + end + + def test_xml_escape_with_charref + ec = Encoding::Converter.new("utf-8", "euc-jp", Encoding::Converter::XML_TEXT_DECORATOR|Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal('<♥>&"♡"', ec.convert("<\u2665>&\"\u2661\"")) + assert_equal('', ec.finish) + + ec = Encoding::Converter.new("utf-8", "euc-jp", + Encoding::Converter::XML_ATTR_CONTENT_DECORATOR| + Encoding::Converter::XML_ATTR_QUOTE_DECORATOR| + Encoding::Converter::UNDEF_HEX_CHARREF) + assert_equal('"<♥>&"♡"', ec.convert("<\u2665>&\"\u2661\"")) + assert_equal('"', ec.finish) + + ec = Encoding::Converter.new("utf-8", "iso-2022-jp", Encoding::Converter::XML_TEXT_DECORATOR) + assert_equal("&\e$B$&\e(B&".force_encoding("iso-2022-jp"), ec.convert("&\u3046&")) + assert_equal('', ec.finish) + end + + def test_xml_hasharg + assert_equal("&\e$B$&\e(B♥&\"'".force_encoding("iso-2022-jp"), + "&\u3046\u2665&\"'".encode("iso-2022-jp", xml: :text)) + assert_equal("\"&\e$B$&\e(B♡&"'\"".force_encoding("iso-2022-jp"), + "&\u3046\u2661&\"'".encode("iso-2022-jp", xml: :attr)) + + assert_equal("&\u3046\u2661&\"'".force_encoding("utf-8"), + "&\u3046\u2661&\"'".encode("utf-8", xml: :text)) + end + + def test_iso2022jp_invalid_replace + assert_equal("?x".force_encoding("iso-2022-jp"), + "\222\xA1x".encode("iso-2022-jp", "stateless-iso-2022-jp", :invalid => :replace)) + end + + def test_convpath + eucjp = Encoding::EUC_JP + utf8 = Encoding::UTF_8 + utf16be = Encoding::UTF_16BE + utf16le = Encoding::UTF_16LE + iso88591 = Encoding::ISO_8859_1 + iso2022jp = Encoding::ISO_2022_JP + siso2022jp = Encoding::STATELESS_ISO_2022_JP + + assert_equal([], Encoding::Converter.new("", "").convpath) + assert_equal([[eucjp, utf8], [utf8, iso88591]], + Encoding::Converter.new(eucjp, iso88591).convpath) + assert_equal([[eucjp, siso2022jp], [siso2022jp, iso2022jp]], + Encoding::Converter.new(eucjp, iso2022jp).convpath) + assert_equal([[iso2022jp, siso2022jp], + [siso2022jp, eucjp], + [eucjp, utf8], + [utf8, iso88591]], + Encoding::Converter.new(iso2022jp, iso88591).convpath) + assert_equal(["universal_newline", [utf8, utf16be]], + Encoding::Converter.new(utf8, utf16be, universal_newline: true).convpath) + assert_equal([[utf16be, utf8], "universal_newline"], + Encoding::Converter.new(utf16be, utf8, universal_newline: true).convpath) + assert_equal([[utf16be, utf8], "universal_newline", [utf8, utf16le]], + Encoding::Converter.new(utf16be, utf16le, universal_newline: true).convpath) + end + + def test_search_convpath + eucjp = Encoding::EUC_JP + utf8 = Encoding::UTF_8 + utf32be = Encoding::UTF_32BE + iso88591 = Encoding::ISO_8859_1 + assert_equal([[iso88591,utf8], [utf8,eucjp]], + Encoding::Converter.search_convpath("ISO-8859-1", "EUC-JP")) + assert_equal([[iso88591,utf8], [utf8,eucjp]], + Encoding::Converter.search_convpath(iso88591, eucjp)) + assert_equal([[iso88591,utf8], [utf8,eucjp], "universal_newline"], + Encoding::Converter.search_convpath("ISO-8859-1", "EUC-JP", universal_newline: true)) + assert_equal([[iso88591,utf8], "universal_newline", [utf8,utf32be]], + Encoding::Converter.search_convpath("ISO-8859-1", "UTF-32BE", universal_newline: true)) + end + + def test_invalid_replace2 + assert_raise(ArgumentError) { + broken = "\x80".force_encoding("euc-jp") + "".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) + } + newlines = %i[universal_newline crlf_newline cr_newline] + (2..newlines.size).each do |i| + newlines.combination(i) do |opts| + assert_raise(Encoding::ConverterNotFoundError, "#{opts} are mutually exclusive") do + Encoding::Converter.new("", "", **opts.inject({}) {|o,nl|o[nl]=true;o}) + end + end + end + newlines.each do |nl| + opts = {newline: :universal, nl => true} + ec2 = assert_warning(/:newline option precedes/, opts.inspect) do + Encoding::Converter.new("", "", **opts) + end + assert_equal(ec1, ec2) + end + end + + def test_default_external + Encoding.list.grep(->(enc) {/\AISO-8859-\d+\z/i =~ enc.name}) do |enc| + assert_separately(%W[-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 new file mode 100644 index 0000000000..0cd5bf49dc --- /dev/null +++ b/test/ruby/test_encoding.rb @@ -0,0 +1,179 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestEncoding < Test::Unit::TestCase + + # Test basic encoding methods: list, find, name + def test_encoding + encodings = Encoding.list + assert_equal(encodings.empty?, false) + + encodings.each do |e| + assert_equal(e, Encoding.find(e.name)) + 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 + + def test_enc_names + aliases = Encoding.aliases + aliases.each do |a, en| + e = Encoding.find(a) + assert_equal(e.name, en) + assert_include(e.names, a) + end + end + + # Test that Encoding objects can't be copied + # And that they can be compared by object_id + def test_singleton + encodings = Encoding.list + encodings.each do |e| + assert_raise(TypeError) { e.dup } + assert_raise(TypeError) { e.clone } + assert_same(e, Marshal.load(Marshal.dump(e))) + end + end + + def test_find + assert_raise(ArgumentError) { Encoding.find("foobarbazqux") } + assert_nothing_raised{Encoding.find("locale")} + assert_nothing_raised{Encoding.find("filesystem")} + + 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 + Encoding.default_external = Encoding::EUC_JP + exit Encoding.find("filesystem") == Encoding::EUC_JP + EOS + end + + bug5150 = '[ruby-dev:44327]' + assert_raise(TypeError, bug5150) {Encoding.find(1)} + end + + def test_dummy_p + assert_equal(true, Encoding::ISO_2022_JP.dummy?) + assert_equal(false, Encoding::UTF_8.dummy?) + end + + def test_ascii_compatible_p + assert_equal(true, Encoding::ASCII_8BIT.ascii_compatible?) + assert_equal(true, Encoding::UTF_8.ascii_compatible?) + assert_equal(false, Encoding::UTF_16BE.ascii_compatible?) + assert_equal(false, Encoding::ISO_2022_JP.ascii_compatible?) + end + + def test_name_list + assert_instance_of(Array, Encoding.name_list) + Encoding.name_list.each do |x| + assert_instance_of(String, x) + end + end + + def test_aliases + assert_instance_of(Hash, Encoding.aliases) + Encoding.aliases.each do |k, v| + assert_include(Encoding.name_list, k) + assert_include(Encoding.name_list, v) + assert_instance_of(String, k) + assert_instance_of(String, v) + end + end + + def test_marshal + str = "".force_encoding("EUC-JP") + str2 = Marshal.load(Marshal.dump(str)) + assert_equal(str, str2) + 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([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug9038 = '[ruby-core:57949] [Bug #9038]' + begin; + e = assert_raise_with_message(SyntaxError, /unknown regexp option - Q/, bug9038) { + eval("/regexp/sQ") + } + assert_include(e.message, "/regexp/sQ\n") + end; + end + + def test_nonascii_library_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}".force_encoding("US-ASCII")) + begin; + assert_equal(Encoding::US_ASCII, __ENCODING__) + $:.unshift("/\x80") + assert_raise_with_message(LoadError, /\[Bug #16382\]/) do + require "[Bug #16382]" + end + end; + end + + def test_ractor_load_encoding + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Ractor.new{}.join + $-w = nil + Encoding.default_external = Encoding::ISO8859_2 + assert "[Bug #19562]" + end; + end + + def test_ractor_lazy_load_encoding_concurrently + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + rs = [] + autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze + 7.times do + rs << Ractor.new(autoload_encodings) do |encodings| + str = "abc".dup + encodings.each do |enc| + str.force_encoding(enc) + end + end + end + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end + + def test_ractor_set_default_external_string + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $-w = nil + rs = [] + 7.times do |i| + rs << Ractor.new(i) do |i| + Encoding.default_external = "us-ascii" + end + end + + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end +end diff --git a/test/ruby/test_enum.rb b/test/ruby/test_enum.rb new file mode 100644 index 0000000000..237bdc8a4d --- /dev/null +++ b/test/ruby/test_enum.rb @@ -0,0 +1,1359 @@ +# frozen_string_literal: false +require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} +require 'stringio' + +class TestEnumerable < Test::Unit::TestCase + def setup + @obj = Object.new + class << @obj + include Enumerable + def each + yield 1 + yield 2 + 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 + end + + def teardown + $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_grep_optimization + bug17030 = '[ruby-core:99156]' + 'set last match' =~ /set last (.*)/ + assert_equal([:a, 'b', :c], [:a, 'b', 'z', :c, 42, nil].grep(/[a-d]/), bug17030) + assert_equal(['z', 42, nil], [:a, 'b', 'z', :c, 42, nil].grep_v(/[a-d]/), bug17030) + assert_equal('match', $1, bug17030) + + regexp = Regexp.new('x') + assert_equal([], @obj.grep(regexp), bug17030) # sanity check + def regexp.===(other) + true + end + assert_equal([1, 2, 3, 1, 2], @obj.grep(regexp), bug17030) + + o = Object.new + def o.to_str + 'hello' + end + assert_same(o, [o].grep(/ll/).first, bug17030) + end + + def test_count + assert_equal(5, @obj.count) + assert_equal(2, @obj.count(1)) + assert_equal(3, @obj.count {|x| x % 2 == 1 }) + assert_equal(2, assert_warning(/given block not used/) {@obj.count(1) {|x| x % 2 == 1 }}) + assert_raise(ArgumentError) { @obj.count(0, 1) } + + if RUBY_ENGINE == "ruby" + en = Class.new { + include Enumerable + alias :size :count + def each + yield 1 + end + } + assert_equal(1, en.new.count, '[ruby-core:24794]') + end + end + + def test_find + 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 + assert_equal(1, @obj.find_index(2)) + assert_equal(1, @obj.find_index {|x| x % 2 == 0 }) + assert_equal(nil, @obj.find_index {|x| false }) + assert_raise(ArgumentError) { @obj.find_index(0, 1) } + assert_equal(1, assert_warning(/given block not used/) {@obj.find_index(2) {|x| x == 1 }}) + end + + 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_keywords + @obj.singleton_class.remove_method(:each) + def @obj.each(foo:) yield foo end + assert_equal([1], @obj.to_a(foo: 1)) + end + + def test_to_a_size_symbol + sym = Object.new + class << sym + 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, assert_warning(/given block not used/) {@obj.inject(2, :*) {|z, x| z * x }}) + assert_equal(nil, @empty.inject() {9}) + + assert_raise(ArgumentError) {@obj.inject} + end + + FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] + 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(3*FIXNUM_MAX, Array.new(3, FIXNUM_MAX).inject(:+)) + assert_equal(2*(FIXNUM_MAX+1), Array.new(2, FIXNUM_MAX+1).inject(:+)) + assert_equal(10*FIXNUM_MAX, Array.new(10, FIXNUM_MAX).inject(:+)) + assert_equal(0, ([FIXNUM_MAX, 1, -FIXNUM_MAX, -1]*10).inject(:+)) + assert_equal(FIXNUM_MAX*10, ([FIXNUM_MAX+1, -1]*10).inject(:+)) + assert_equal(2*FIXNUM_MIN, Array.new(2, FIXNUM_MIN).inject(:+)) + assert_equal(3*FIXNUM_MIN, Array.new(3, FIXNUM_MIN).inject(:+)) + assert_equal((FIXNUM_MAX+1).to_f, [FIXNUM_MAX, 1, 0.0].inject(:+)) + assert_float_equal(10.0, [3.0, 5].inject(2.0, :+)) + assert_float_equal((FIXNUM_MAX+1).to_f, [0.0, FIXNUM_MAX+1].inject(:+)) + assert_equal(2.0+3.0i, [2.0, 3.0i].inject(:+)) + end + + def test_inject_op_redefined + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-dev:49510] [Bug#12178] should respect redefinition' + begin + Integer.class_eval do + alias_method :orig, op + define_method(op) do |x| + 0 + end + end + assert_equal(0, k.new.inject(op), bug) + ensure + Integer.class_eval do + undef_method op + alias_method op, :orig + end + end + end; + end + + def test_inject_op_private + assert_separately([], "#{<<~"end;"}\n""end") + k = Class.new do + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + all_assertions_foreach("", *%i[+ * / - %]) do |op| + bug = '[ruby-core:81349] [Bug #13592] should respect visibility' + assert_raise_with_message(NoMethodError, /private method/, bug) do + begin + Integer.class_eval do + private op + end + k.new.inject(op) + ensure + Integer.class_eval do + public op + end + end + end + end; + end + + def test_inject_array_op_redefined + assert_separately([], "#{<<~"end;"}\n""end") + all_assertions_foreach("", *%i[+ * / - %]) do |op| + 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_refine_Enumerable_then_include + assert_separately([], "#{<<~"end;"}\n") + module RefinementBug + refine Enumerable do + def refined_method + :rm + end + end + end + using RefinementBug + + class A + include Enumerable + end + + assert_equal(:rm, [].refined_method) + end; + end + + def test_partition + assert_equal([[1, 3, 1], [2, 2]], @obj.partition {|x| x % 2 == 1 }) + cond = ->(x, i) { x % 2 == 1 } + 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_tally + h = {1 => 2, 2 => 2, 3 => 1} + assert_equal(h, @obj.tally) + + h = {1 => 5, 2 => 2, 3 => 1, 4 => "x"} + assert_equal(h, @obj.tally({1 => 3, 4 => "x"})) + + assert_raise(TypeError) do + @obj.tally({1 => ""}) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_same(h, @obj.tally(h)) + + h = {1 => 2, 2 => 2, 3 => 1}.freeze + assert_raise(FrozenError) do + @obj.tally(h) + end + assert_equal({1 => 2, 2 => 2, 3 => 1}, h) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"} + end + assert_equal({1 => 5, 2 => 2, 3 => 1, 4 => "x"}, @obj.tally(hash_convertible)) + + hash_convertible = Object.new + def hash_convertible.to_hash + {1 => 3, 4 => "x"}.freeze + end + assert_raise(FrozenError) do + @obj.tally(hash_convertible) + end + assert_equal({1 => 3, 4 => "x"}, hash_convertible.to_hash) + + assert_raise(TypeError) do + @obj.tally(BasicObject.new) + end + + h = {1 => 2, 2 => 2, 3 => 1} + assert_equal(h, @obj.tally(Hash.new(100))) + assert_equal(h, @obj.tally(Hash.new {100})) + end + + def test_first + assert_equal(1, @obj.first) + assert_equal([1, 2, 3], @obj.first(3)) + 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; + + bug18475 = '[ruby-dev:107059]' + assert_in_out_err([], <<-'end;', [], /unexpected break/, bug18475) + e = Enumerator.new do |g| + Thread.new do + g << 1 + end.join + end + + e.first + end; + end + + def test_sort + 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 + assert_equal(true, @obj.all? {|x| x <= 3 }) + 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?(Integer)) + assert_equal(false, @obj.all?(1..2)) + end + + def test_all_with_unused_block + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + [1, 2].all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + (1..2).all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + 3.times.all?(1) {|x| x == 3 } + EOS + assert_in_out_err [], <<-EOS, [], ["-:1: warning: given block not used"] + {a: 1, b: 2}.all?([:b, 2]) {|x| x == 4 } + EOS + end + + def test_any + assert_equal(true, @obj.any? {|x| x >= 3 }) + 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 }) + 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 }) + 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 + assert_equal([1, 3], @obj.minmax) + assert_equal([3, 1], @obj.minmax {|a,b| b <=> a }) + ary = %w(albatross dog horse) + assert_equal(["albatross", "horse"], ary.minmax) + assert_equal(["dog", "albatross"], ary.minmax {|a,b| a.length <=> b.length }) + assert_equal([1, 3], [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 }) + end + + def test_member + assert(@obj.member?(1)) + assert(!(@obj.member?(4))) + assert([1,2,3].member?(1)) + assert(!([1,2,3].member?(4))) + end + + class Foo + include Enumerable + def each + yield 1 + yield 1,2 + end + end + + def test_each_with_index + a = [] + @obj.each_with_index {|x, i| a << [x, i] } + assert_equal([[1,0],[2,1],[3,2],[1,3],[2,4]], a) + + hash = Hash.new + %w(cat dog wombat).each_with_index do |item, index| + hash[item] = index + end + assert_equal({"cat"=>0, "wombat"=>2, "dog"=>1}, hash) + assert_equal([[1, 0], [[1, 2], 1]], Foo.new.each_with_index.to_a) + end + + def test_each_with_object + obj = [0, 1] + ret = (1..10).each_with_object(obj) {|i, memo| + memo[0] += i + memo[1] *= i + } + assert_same(obj, ret) + assert_equal([55, 3628800], ret) + assert_equal([[1, nil], [[1, 2], nil]], Foo.new.each_with_object(nil).to_a) + end + + 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) + + assert_equal(1..10, (1..10).each_slice(3) { }) + assert_equal([], [].each_slice(3) { }) + 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) + + assert_equal(1..5, (1..5).each_cons(3) { }) + assert_equal([], [].each_cons(3) { }) + 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 = [] + 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(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 + assert_equal([1,2,3], @obj.take(3)) + end + + 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 + assert_equal([3,1,2], @obj.drop(2)) + end + + 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 + omit 'requires callcc support' unless respond_to?(:callcc) + + assert_raise(RuntimeError) do + c = nil + @obj.sort_by {|x| callcc {|c2| c ||= c2 }; x } + c.call + end + + assert_raise(RuntimeError) do + c = nil + o = Object.new + class << o; self; end.class_eval do + define_method(:<=>) do |x| + callcc {|c2| c ||= c2 } + 0 + end + end + [o, o].sort_by {|x| x } + c.call + end + + assert_raise(RuntimeError) do + c = nil + o = Object.new + class << o; self; end.class_eval do + define_method(:<=>) do |x| + callcc {|c2| c ||= c2 } + 0 + end + end + [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) + + 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 {|elt| elt < 3 ? :_alone : true } + assert_equal([[:_alone, [1]], + [:_alone, [2]], + [true, [3]], + [:_alone, [1]], + [:_alone, [2]]], e.to_a) + + e = @obj.chunk {|elt| elt == 3 ? :_separator : true } + assert_equal([[true, [1, 2]], + [true, [1, 2]]], e.to_a) + + e = @obj.chunk {|elt| elt == 3 ? nil : true } + assert_equal([[true, [1, 2]], + [true, [1, 2]]], e.to_a) + + 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 + e = [].slice_before {|elt| true } + assert_equal([], e.to_a) + + e = @obj.slice_before {|elt| elt.even? } + assert_equal([[1], [2,3,1], [2]], e.to_a) + + e = @obj.slice_before {|elt| elt.odd? } + assert_equal([[1,2], [3], [1,2]], e.to_a) + + ss = %w[abc defg h ijk l mno pqr st u vw xy z] + assert_equal([%w[abc defg h], %w[ijk l], %w[mno], %w[pqr st u vw xy z]], + ss.slice_before(/\A...\z/).to_a) + assert_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 + + 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([[1], [2]], e.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_compact + class << (enum = Object.new) + include Enumerable + def each + yield 3 + yield nil + yield 7 + yield 9 + yield nil + end + end + + assert_equal([3, 7, 9], enum.compact) + end + + def test_transient_heap_sort_by + klass = Class.new do + include Comparable + attr_reader :i + def initialize e + @i = e + end + def <=> other + GC.start + i <=> other.i + end + end + assert_equal [1, 2, 3, 4, 5], (1..5).sort_by{|e| klass.new e} + end + + def test_filter_map + @obj = (1..8).to_a + assert_equal([4, 8, 12, 16], @obj.filter_map { |i| i * 2 if i.even? }) + assert_equal([2, 4, 6, 8, 10, 12, 14, 16], @obj.filter_map { |i| i * 2 }) + assert_equal([0, 0, 0, 0, 0, 0, 0, 0], @obj.filter_map { 0 }) + assert_equal([], @obj.filter_map { false }) + assert_equal([], @obj.filter_map { nil }) + assert_instance_of(Enumerator, @obj.filter_map) + end + + def test_ruby_svar + klass = Class.new do + include Enumerable + def each + %w(bar baz).each{|e| yield e} + end + end + svars = [] + klass.new.grep(/(b.)/) { svars << $1 } + assert_equal(["ba", "ba"], svars) + end + + def test_all_fast + data = { "key" => { "key2" => 1 } } + kk = vv = nil + data.all? { |(k, v)| kk, vv = k, v } + assert_equal(kk, "key") + assert_equal(vv, { "key2" => 1 }) + end +end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb new file mode 100644 index 0000000000..9b972d7b22 --- /dev/null +++ b/test/ruby/test_enumerator.rb @@ -0,0 +1,1073 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestEnumerator < Test::Unit::TestCase + def setup + @obj = Object.new + class << @obj + include Enumerable + def foo(*a) + a.each {|x| yield x } + end + end + @sized = @obj.clone + def @sized.size + 42 + end + end + + def enum_test obj + obj.map{|e| + e + }.sort + end + + 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}.each) + end + + ## Enumerator as Iterator + + def test_next + e = 3.times + 3.times{|i| + assert_equal i, e.next + } + assert_raise(StopIteration){e.next} + end + + def test_loop + e = 3.times + i = 0 + loop{ + assert_equal(i, e.next) + i += 1 + } + end + + def test_loop_return_value + assert_equal nil, loop { break } + assert_equal 42, loop { break 42 } + + e = Enumerator.new { |y| y << 1; y << 2; :stopped } + assert_equal :stopped, loop { e.next while true } + end + + def test_nested_iteration + def (o = Object.new).each + yield :ok1 + yield [:ok2, :x].each.next + end + e = o.to_enum + assert_equal :ok1, e.next + assert_equal :ok2, e.next + assert_raise(StopIteration){e.next} + end + + + def test_initialize + assert_equal([1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).to_a) + assert_raise(ArgumentError) { + Enumerator.new(@obj, :foo, 1, 2, 3) + } + assert_equal([1, 2, 3], Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.take(3)) + assert_raise(ArgumentError) { Enumerator.new } + + enum = @obj.to_enum + assert_raise(NoMethodError) { enum.each {} } + enum.freeze + assert_raise(ArgumentError) { + capture_output do + # warning: Enumerator.new without a block is deprecated; use Object#to_enum + enum.__send__(:initialize, @obj, :foo) + end + } + end + + def test_initialize_copy + assert_equal([1, 2, 3], @obj.to_enum(:foo, 1, 2, 3).dup.to_a) + e = @obj.to_enum(:foo, 1, 2, 3) + assert_nothing_raised { assert_equal(1, e.next) } + assert_raise(TypeError) { e.dup } + + e = Enumerator.new { |y| i = 0; loop { y << (i += 1) } }.dup + assert_nothing_raised { assert_equal(1, e.next) } + assert_raise(TypeError) { e.dup } + end + + def test_gc + assert_nothing_raised do + 1.times do + foo = [1,2,3].to_enum + GC.start + foo + end + GC.start + end + end + + def test_slice + assert_equal([[1,2,3],[4,5,6],[7,8,9],[10]], (1..10).each_slice(3).to_a) + end + + def test_each_slice_size + assert_equal(4, (1..10).each_slice(3).size) + assert_equal(Float::INFINITY, 1.step.each_slice(3).size) + end + + def test_cons + a = [[1,2,3], [2,3,4], [3,4,5], [4,5,6], [5,6,7], [6,7,8], [7,8,9], [8,9,10]] + assert_equal(a, (1..10).each_cons(3).to_a) + end + + def test_with_index + assert_equal([[1,0],[2,1],[3,2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a) + 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_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + assert_equal([[1, 0], [2, 1], [3, 2]], @obj.to_enum(:foo, 1, 2, 3).with_index.to_a) + assert_equal([[1, 5], [2, 6], [3, 7]], @obj.to_enum(:foo, 1, 2, 3).with_index(5).to_a) + + 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) + end + 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| + memo[0] += i + memo[1] *= i + } + assert_same(obj, ret) + assert_equal([55, 3628800], ret) + + a = [2,5,2,1,5,3,4,2,1,0] + obj = {} + ret = a.delete_if.with_object(obj) {|i, seen| + if seen.key?(i) + true + else + seen[i] = true + false + end + } + assert_same(obj, ret) + assert_equal([2, 5, 1, 3, 4, 0], a) + end + + def test_next_rewind + e = @obj.to_enum(:foo, 1, 2, 3) + assert_equal(1, e.next) + assert_equal(2, e.next) + e.rewind + assert_equal(1, e.next) + assert_equal(2, e.next) + assert_equal(3, e.next) + assert_raise(StopIteration) { e.next } + end + + def test_peek + a = [1] + e = a.each + assert_equal(1, e.peek) + assert_equal(1, e.peek) + assert_equal(1, e.next) + assert_raise(StopIteration) { e.peek } + assert_raise(StopIteration) { e.peek } + end + + def test_peek_modify + o = Object.new + def o.each + yield 1,2 + end + e = o.to_enum + a = e.peek + a << 3 + assert_equal([1,2], e.peek) + end + + def test_peek_values_modify + o = Object.new + def o.each + yield 1,2 + end + e = o.to_enum + a = e.peek_values + a << 3 + assert_equal([1,2], e.peek) + end + + def test_next_after_stopiteration + a = [1] + e = a.each + assert_equal(1, e.next) + assert_raise(StopIteration) { e.next } + assert_raise(StopIteration) { e.next } + e.rewind + assert_equal(1, e.next) + assert_raise(StopIteration) { e.next } + assert_raise(StopIteration) { e.next } + end + + def test_stop_result + a = [1] + res = a.each {} + e = a.each + assert_equal(1, e.next) + exc = assert_raise(StopIteration) { e.next } + assert_equal(res, exc.result) + end + + def test_stopiteration_rescue + e = [1].each + res = e.each {} + e.next + exc0 = assert_raise(StopIteration) { e.peek } + assert_include(exc0.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_nil(exc0.cause) + assert_equal(res, exc0.result) + + exc1 = assert_raise(StopIteration) { e.next } + assert_include(exc1.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_same(exc0, exc1.cause) + assert_equal(res, exc1.result) + + exc2 = assert_raise(StopIteration) { e.next } + assert_include(exc2.backtrace.first, "test_enumerator.rb:#{__LINE__-1}:") + assert_same(exc0, exc2.cause) + assert_equal(res, exc2.result) + end + + def test_next_values + o = Object.new + def o.each + yield + yield 1 + yield 1, 2 + end + e = o.to_enum + assert_equal(nil, e.next) + assert_equal(1, e.next) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal([], e.next_values) + assert_equal([1], e.next_values) + assert_equal([1,2], e.next_values) + end + + def test_peek_values + o = Object.new + def o.each + yield + yield 1 + yield 1, 2 + end + e = o.to_enum + assert_equal(nil, e.peek) + assert_equal(nil, e.next) + assert_equal(1, e.peek) + assert_equal(1, e.next) + assert_equal([1,2], e.peek) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal([], e.peek_values) + assert_equal([], e.next_values) + assert_equal([1], e.peek_values) + assert_equal([1], e.next_values) + assert_equal([1,2], e.peek_values) + assert_equal([1,2], e.next_values) + e = o.to_enum + assert_equal([], e.peek_values) + assert_equal(nil, e.next) + assert_equal([1], e.peek_values) + assert_equal(1, e.next) + assert_equal([1,2], e.peek_values) + assert_equal([1,2], e.next) + e = o.to_enum + assert_equal(nil, e.peek) + assert_equal([], e.next_values) + assert_equal(1, e.peek) + assert_equal([1], e.next_values) + assert_equal([1,2], e.peek) + 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) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.next + e.feed 1 + e.next + e.feed 2 + e.next + e.feed 3 + assert_raise(StopIteration) { e.next } + assert_equal([1,2,3], ary) + end + + def test_feed_mixed + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.next + e.feed 1 + e.next + e.next + e.feed 3 + assert_raise(StopIteration) { e.next } + assert_equal([1,nil,3], ary) + end + + def test_feed_twice + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.feed 1 + assert_raise(TypeError) { e.feed 2 } + end + + def test_feed_before_first_next + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.feed 1 + e.next + e.next + assert_equal([1], ary) + end + + def test_rewind_clear_feed + o = Object.new + def o.each(ary) + ary << yield + ary << yield + ary << yield + end + ary = [] + e = o.to_enum(:each, ary) + e.next + e.feed 1 + e.next + e.feed 2 + e.rewind + e.next + e.next + assert_equal([1,nil], ary) + end + + def test_feed_yielder + x = nil + e = Enumerator.new {|y| x = y.yield; 10 } + e.next + e.feed 100 + exc = assert_raise(StopIteration) { e.next } + assert_equal(100, x) + assert_equal(10, exc.result) + end + + def test_inspect + e = (0..10).each_cons(2) + assert_equal("#<Enumerator: 0..10:each_cons(2)>", e.inspect) + + e = (0..10).each_with_object({}) + assert_equal("#<Enumerator: 0..10:each_with_object({})>", e.inspect) + + e = (0..10).each_with_object(a: 1) + assert_equal("#<Enumerator: 0..10:each_with_object(a: 1)>", e.inspect) + + e = Enumerator.new {|y| y.yield; 10 } + assert_match(/\A#<Enumerator: .*:each>/, e.inspect) + + a = [] + e = a.each_with_object(a) + a << e + assert_equal("#<Enumerator: [#<Enumerator: ...>]:each_with_object([#<Enumerator: ...>])>", + 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 } + g2 = g.dup + a = [] + assert_equal(:foo, g.each {|x| a << x }) + assert_equal([1, 2, 3], a) + 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 + # note: Enumerator::Yielder is a class just for internal + a = [] + y = Enumerator::Yielder.new {|x| a << x } + assert_equal(y, y << 1 << 2 << 3) + assert_equal([1, 2, 3], a) + + a = [] + y = Enumerator::Yielder.new {|x| a << x } + assert_equal([1], y.yield(1)) + assert_equal([1, 2], y.yield(2)) + assert_equal([1, 2, 3], y.yield(3)) + assert_equal([1, 2, 3, 4], y.yield(4, 5)) + + a = [] + y = Enumerator::Yielder.new {|*x| a.concat(x) } + assert_equal([1], y.yield(1)) + assert_equal([1, 2, 3], y.yield(2, 3)) + + assert_raise(LocalJumpError) { Enumerator::Yielder.new } + + # to_proc (explicit) + a = [] + y = Enumerator::Yielder.new {|x| a << x } + b = y.to_proc + assert_kind_of(Proc, b) + assert_equal([1], b.call(1)) + assert_equal([1], a) + + # to_proc (implicit) + e = Enumerator.new { |y| + assert_kind_of(Enumerator::Yielder, y) + [1, 2, 3].each(&y) + } + assert_equal([1, 2, 3], e.to_a) + end + + def test_size + 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 filter_map].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_compact + u = [0, 1, nil, 2, 3, nil].to_enum.lazy.compact + assert_equal([0, 1, 2, 3], u.force) + end + + def test_enum_chain_and_plus + r = 1..5 + + e1 = r.chain() + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = r.chain([6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = r.chain([6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + # `a + b + c` should not return `Enumerator::Chain.new(a, b, c)` + # because it is expected that `(a + b).each` be called. + e4 = e2.dup + class << e4 + attr_reader :each_is_called + def each + super + @each_is_called = true + end + end + e5 = e4 + 9.step + assert_kind_of(Enumerator::Chain, e5) + assert_equal(Float::INFINITY, e5.size) + ary = [] + e5.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + assert_equal(true, e4.each_is_called) + end + + def test_chained_enums + a = (1..5).each + + e0 = Enumerator::Chain.new() + assert_kind_of(Enumerator::Chain, e0) + assert_equal(0, e0.size) + ary = [] + e0.each { |x| ary << x } + assert_equal([], ary) + + e1 = Enumerator::Chain.new(a) + assert_kind_of(Enumerator::Chain, e1) + assert_equal(5, e1.size) + ary = [] + e1.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5], ary) + + e2 = Enumerator::Chain.new(a, [6, 7, 8]) + assert_kind_of(Enumerator::Chain, e2) + assert_equal(8, e2.size) + ary = [] + e2.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e3 = Enumerator::Chain.new(a, [6, 7], 8.step) + assert_kind_of(Enumerator::Chain, e3) + assert_equal(Float::INFINITY, e3.size) + ary = [] + e3.take(10).each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary) + + e4 = Enumerator::Chain.new(a, Enumerator.new { |y| y << 6 << 7 << 8 }) + assert_kind_of(Enumerator::Chain, e4) + assert_equal(nil, e4.size) + ary = [] + e4.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8], ary) + + e5 = Enumerator::Chain.new(e1, e2) + assert_kind_of(Enumerator::Chain, e5) + assert_equal(13, e5.size) + ary = [] + e5.each { |x| ary << x } + assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 7, 8], ary) + + rewound = [] + e1.define_singleton_method(:rewind) { rewound << object_id } + e2.define_singleton_method(:rewind) { rewound << object_id } + e5.rewind + assert_equal(rewound, [e2.object_id, e1.object_id]) + + rewound = [] + a = [1] + e6 = Enumerator::Chain.new(a) + a.define_singleton_method(:rewind) { rewound << object_id } + e6.rewind + assert_equal(rewound, []) + + assert_equal( + '#<Enumerator::Chain: [' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>' + + ']>, ' + + '#<Enumerator::Chain: [' + + '#<Enumerator: 1..5:each>, ' + + '[6, 7, 8]' + + ']>' + + ']>', + e5.inspect + ) + end + + def test_chain_with_index + assert_equal([[3, 0], [4, 1]], [3].chain([4]).with_index.to_a) + end + + def test_lazy_chain + ea = (10..).lazy.select(&:even?).take(10) + ed = (20..).lazy.select(&:odd?) + chain = (ea + ed).select{|x| x % 3 == 0} + assert_equal(12, chain.next) + assert_equal(18, chain.next) + assert_equal(24, chain.next) + assert_equal(21, chain.next) + assert_equal(27, chain.next) + assert_equal(33, chain.next) + end + + def test_lazy_chain_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + ea = (10..).lazy.select(&:even?).take(10) + ed = (20..).lazy.select(&:odd?) + chain = (ea + ed).select{|x| x % 3 == 0} + assert_equal(12, chain.next) + assert_equal(18, chain.next) + assert_equal(24, chain.next) + assert_equal(21, chain.next) + assert_equal(27, chain.next) + assert_equal(33, chain.next) + end + end + + def test_chain_undef_methods + chain = [1].to_enum + [2].to_enum + meths = (chain.methods & [:feed, :next, :next_values, :peek, :peek_values]) + assert_equal(0, meths.size) + end + + def test_produce + assert_raise(ArgumentError) { Enumerator.produce } + assert_raise(ArgumentError) { Enumerator.produce(a: 1, b: 1) {} } + + # Without initial object + passed_args = [] + enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ } + assert_instance_of(Enumerator, enum) + assert_equal Float::INFINITY, enum.size + assert_equal [1, 2, 3], enum.take(3) + assert_equal [nil, 1, 2], passed_args + + # With initial object + passed_args = [] + enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ } + assert_instance_of(Enumerator, enum) + assert_equal Float::INFINITY, enum.size + assert_equal [1, 2, 3], enum.take(3) + assert_equal [1, 2], passed_args + + # Raising StopIteration + words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/) + enum = Enumerator.produce { words.shift or raise StopIteration } + assert_equal Float::INFINITY, enum.size + assert_instance_of(Enumerator, enum) + assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a + + # Raising StopIteration + object = [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"] + enum = Enumerator.produce(object) { |obj| + obj.respond_to?(:first) or raise StopIteration + obj.first + } + assert_equal Float::INFINITY, enum.size + assert_instance_of(Enumerator, enum) + assert_nothing_raised { + assert_equal [ + [[[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], "stuv", "wxyz"], + [[["abc", "def"], "ghi", "jkl"], "mno", "pqr"], + [["abc", "def"], "ghi", "jkl"], + ["abc", "def"], + "abc", + ], enum.to_a + } + + # With size keyword argument + enum = Enumerator.produce(1, size: 10) { |obj| obj.succ } + assert_equal 10, enum.size + assert_equal [1, 2, 3], enum.take(3) + + enum = Enumerator.produce(1, size: -> { 5 }) { |obj| obj.succ } + assert_equal 5, enum.size + + enum = Enumerator.produce(1, size: nil) { |obj| obj.succ } + assert_equal nil, enum.size + + enum = Enumerator.produce(1, size: Float::INFINITY) { |obj| obj.succ } + assert_equal Float::INFINITY, enum.size + + # Without initial value but with size + enum = Enumerator.produce(size: 3) { |obj| (obj || 0).succ } + assert_equal 3, enum.size + assert_equal [1, 2, 3], enum.take(3) + end + + def test_chain_each_lambda + c = Class.new do + include Enumerable + attr_reader :is_lambda + def each(&block) + return to_enum unless block + @is_lambda = block.lambda? + end + end + e = c.new + e.chain.each{} + assert_equal(false, e.is_lambda) + e.chain.each(&->{}) + assert_equal(true, e.is_lambda) + end + + def test_product_new + # 0-dimensional + e = Enumerator::Product.new + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(1, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [nil], heads + + # 1-dimensional + e = Enumerator::Product.new(1..3) + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(3, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[1], [2], [3]], elts + assert_equal elts, e.to_a + + # 2-dimensional + e = Enumerator::Product.new(1..3, %w[a b]) + assert_instance_of(Enumerator::Product, e) + assert_kind_of(Enumerator, e) + assert_equal(3 * 2, e.size) + elts = [] + e.each { |x| elts << x } + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]], elts + assert_equal elts, e.to_a + heads = [] + e.each { |x,| heads << x } + assert_equal [1, 1, 2, 2, 3, 3], heads + + # Any enumerable is 0 size + assert_equal(0, Enumerator::Product.new([], 1..).size) + + # Reject keyword arguments + assert_raise(ArgumentError) { + Enumerator::Product.new(1..3, foo: 1, bar: 2) + } + end + + def test_s_product + # without a block + e = Enumerator.product(1..3, %w[a b]) + assert_instance_of(Enumerator::Product, e) + + # with a block + elts = [] + ret = Enumerator.product(1..3) { |x| elts << x } + assert_equal(nil, ret) + assert_equal [[1], [2], [3]], elts + assert_equal elts, Enumerator.product(1..3).to_a + + # an infinite enumerator and a finite enumerable + e = Enumerator.product(1.., 'a'..'c') + assert_equal(Float::INFINITY, e.size) + assert_equal [[1, "a"], [1, "b"], [1, "c"], [2, "a"]], e.take(4) + + # an infinite enumerator and an unknown enumerator + e = Enumerator.product(1.., Enumerator.new { |y| y << 'a' << 'b' }) + assert_equal(Float::INFINITY, e.size) + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + + # an infinite enumerator and an unknown enumerator + e = Enumerator.product(1..3, Enumerator.new { |y| y << 'a' << 'b' }) + assert_equal(nil, e.size) + assert_equal [[1, "a"], [1, "b"], [2, "a"], [2, "b"]], e.take(4) + + assert_equal(0, Enumerator.product([], 1..).size) + + # Reject keyword arguments + assert_raise(ArgumentError) { + Enumerator.product(1..3, foo: 1, bar: 2) + } + end + + def test_freeze + e = 3.times.freeze + assert_raise(FrozenError) { e.next } + assert_raise(FrozenError) { e.next_values } + assert_raise(FrozenError) { e.peek } + assert_raise(FrozenError) { e.peek_values } + assert_raise(FrozenError) { e.feed 1 } + assert_raise(FrozenError) { e.rewind } + end + + def test_sum_of_numeric + num = Class.new(Numeric) do + attr_reader :to_f + def initialize(val) + @to_f = Float(val) + end + end + + ary = [5, 10, 20].map {|i| num.new(i)} + + assert_equal(35.0, ary.sum) + enum = ary.each + assert_equal(35.0, enum.sum) + end +end diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb new file mode 100644 index 0000000000..d17e300bce --- /dev/null +++ b/test/ruby/test_env.rb @@ -0,0 +1,1511 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestEnv < Test::Unit::TestCase + windows = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM + IGNORE_CASE = windows + ENCODING = windows ? Encoding::UTF_8 : Encoding.find("locale") + 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 + @backup = ENV.to_hash + ENV.delete('test') + ENV.delete('TEST') + end + + def teardown + $VERBOSE = @verbose + ENV.clear + @backup.each {|k, v| ENV[k] = v } + end + + def test_bracket + assert_nil(ENV['test']) + assert_nil(ENV['TEST']) + ENV['test'] = 'foo' + assert_equal('foo', ENV['test']) + if IGNORE_CASE + assert_equal('foo', ENV['TEST']) + else + assert_nil(ENV['TEST']) + end + ENV['TEST'] = 'bar' + assert_equal('bar', ENV['TEST']) + if IGNORE_CASE + assert_equal('bar', ENV['test']) + else + assert_equal('foo', ENV['test']) + end + + assert_raise(TypeError) { + ENV[1] + } + assert_raise(TypeError) { + ENV[1] = 'foo' + } + assert_raise(TypeError) { + ENV['test'] = 0 + } + end + + def test_dup + assert_raise(TypeError) { + ENV.dup + } + end + + def test_clone + message = /Cannot clone ENV/ + assert_raise_with_message(TypeError, message) { + ENV.clone + } + assert_raise_with_message(TypeError, message) { + ENV.clone(freeze: false) + } + assert_raise_with_message(TypeError, message) { + ENV.clone(freeze: nil) + } + assert_raise_with_message(TypeError, message) { + ENV.clone(freeze: true) + } + + assert_raise(ArgumentError) { + ENV.clone(freeze: 1) + } + assert_raise(ArgumentError) { + ENV.clone(foo: false) + } + assert_raise(ArgumentError) { + ENV.clone(1) + } + assert_raise(ArgumentError) { + ENV.clone(1, foo: false) + } + end + + def test_has_value + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + + assert_equal(false, ENV.has_value?(val)) + assert_equal(false, ENV.has_value?(val.upcase)) + ENV['test'] = val + assert_equal(true, ENV.has_value?(val)) + assert_equal(false, ENV.has_value?(val.upcase)) + ENV['test'] = val.upcase + assert_equal(false, ENV.has_value?(val)) + assert_equal(true, ENV.has_value?(val.upcase)) + end + + def test_key + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + + assert_nil(ENV.key(val)) + assert_nil(ENV.key(val.upcase)) + ENV['test'] = val + if IGNORE_CASE + assert_equal('TEST', ENV.key(val).upcase) + else + assert_equal('test', ENV.key(val)) + end + assert_nil(ENV.key(val.upcase)) + ENV['test'] = val.upcase + assert_nil(ENV.key(val)) + if IGNORE_CASE + assert_equal('TEST', ENV.key(val.upcase).upcase) + else + assert_equal('test', ENV.key(val.upcase)) + end + end + + def test_delete + assert_invalid_env {|v| ENV.delete(v)} + assert_nil(ENV.delete("TEST")) + assert_nothing_raised { ENV.delete(PATH_ENV) } + assert_equal("NO TEST", ENV.delete("TEST") {|name| "NO "+name}) + end + + def test_getenv + assert_invalid_env {|v| ENV[v]} + ENV[PATH_ENV] = "" + assert_equal("", ENV[PATH_ENV]) + assert_nil(ENV[""]) + end + + def test_fetch + ENV["test"] = "foo" + assert_equal("foo", ENV.fetch("test")) + ENV.delete("test") + feature8649 = '[ruby-core:56062] [Feature #8649]' + e = assert_raise_with_message(KeyError, /key not found: "test"/, feature8649) do + 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" }) + 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)) + end + + def test_aset + assert_nothing_raised { ENV["test"] = nil } + assert_equal(nil, ENV["test"]) + 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"]) + rescue Errno::EINVAL + end + end + + def test_keys + a = ENV.keys + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end + + def test_each_key + ENV.each_key {|k| assert_kind_of(String, k) } + end + + def test_values + a = ENV.values + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end + + def test_each_value + ENV.each_value {|k| assert_kind_of(String, k) } + end + + def test_each_pair + ENV.each_pair do |k, v| + assert_kind_of(String, k) + assert_kind_of(String, v) + end + end + + def test_reject_bang + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.reject! {|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.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" + ENV.delete_if {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } + 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 + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.select! {|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.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" + ENV.keep_if {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" } + 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 + ENV["test"] = "foo" + assert_equal(["foo", "foo"], ENV.values_at("test", "test")) + end + + def test_select + ENV["test"] = "foo" + h = ENV.select {|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_filter + ENV["test"] = "foo" + h = ENV.filter {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } + assert_equal(1, h.size) + k = h.keys.first + v = h.values.first + if IGNORE_CASE + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end + + def test_slice + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + assert_equal({}, ENV.slice()) + assert_equal({}, ENV.slice("")) + assert_equal({}, ENV.slice("unknown")) + assert_equal({"foo"=>"bar", "baz"=>"qux"}, ENV.slice("foo", "baz")) + end + + def test_except + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except()) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except("")) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, ENV.except("unknown")) + assert_equal({"bar"=>"rab"}, ENV.except("foo", "baz")) + end + + def test_clear + ENV.clear + assert_equal(0, ENV.size) + end + + def test_to_s + assert_equal("ENV", ENV.to_s) + end + + def test_inspect + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + s = ENV.inspect + expected = [%("foo" => "bar"), %("baz" => "qux")] + unless s.start_with?(/\{"foo"/i) + expected.reverse! + end + expected = '{' + expected.join(', ') + '}' + if IGNORE_CASE + s = s.upcase + expected = expected.upcase + end + assert_equal(expected, s) + end + + def test_inspect_encoding + ENV.clear + key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" + ENV[key] = "foo" + assert_equal(%{{#{(key.encode(ENCODING) rescue key.b).inspect} => "foo"}}, ENV.inspect) + end + + def test_to_a + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.to_a + assert_equal(2, a.size) + check([%w(baz qux), %w(foo bar)], a) + end + + def test_rehash + assert_nil(ENV.rehash) + end + + def test_size + s = ENV.size + ENV["test"] = "foo" + assert_equal(s + 1, ENV.size) + end + + def test_empty_p + ENV.clear + assert_predicate(ENV, :empty?) + ENV["test"] = "foo" + assert_not_predicate(ENV, :empty?) + end + + def test_has_key + assert_not_send([ENV, :has_key?, "test"]) + ENV["test"] = "foo" + assert_send([ENV, :has_key?, "test"]) + assert_invalid_env {|v| ENV.has_key?(v)} + end + + def test_assoc + assert_nil(ENV.assoc("test")) + ENV["test"] = "foo" + k, v = ENV.assoc("test") + if IGNORE_CASE + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + assert_invalid_env {|var| ENV.assoc(var)} + assert_equal(ENCODING, v.encoding) + end + + def test_has_value2 + ENV.clear + assert_not_send([ENV, :has_value?, "foo"]) + ENV["test"] = "foo" + assert_send([ENV, :has_value?, "foo"]) + end + + def test_rassoc + ENV.clear + assert_nil(ENV.rassoc("foo")) + ENV["foo"] = "bar" + ENV["test"] = "foo" + ENV["baz"] = "qux" + k, v = ENV.rassoc("foo") + 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_to_hash + h = {} + ENV.each {|k, v| h[k] = v } + 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 } + ENV["test"] = "foo" + h2 = ENV.reject {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } + assert_equal(h1, h2) + end + + def assert_equal_env(as, bs) + if IGNORE_CASE + as = as.map {|k, v| [k.upcase, v] } + bs = bs.map {|k, v| [k.upcase, v] } + end + assert_equal(as.sort, bs.sort) + end + alias check assert_equal_env + + def test_shift + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.shift + b = ENV.shift + check([a, b], [%w(foo bar), %w(baz qux)]) + assert_nil(ENV.shift) + end + + def test_invert + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + check(ENV.invert.to_a, [%w(bar foo), %w(qux baz)]) + end + + def test_replace + 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 + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + ENV.update + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + ENV.update({"foo"=>"zot"}, {"c"=>"d"}) + check(ENV.to_hash.to_a, [%w(foo zot), %w(baz quux), %w(a b), %w(c d)]) + + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + ENV.update {|k, v1, v2| k + "_" + v1 + "_" + v2 } + check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + ENV.update({"foo"=>"zot"}, {"c"=>"d"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } + check(ENV.to_hash.to_a, [%w(foo foo_bar_zot), %w(baz baz_qux_quux), %w(a b), %w(c d)]) + end + + def test_huge_value + if /mswin|ucrt/ =~ RUBY_PLATFORM + # On Windows >= Vista each environment variable can be max 32768 characters + huge_value = "bar" * 10900 + else + huge_value = "bar" * 40960 + end + ENV["foo"] = "overwritten" + assert_nothing_raised { ENV["foo"] = huge_value } + assert_equal(huge_value, ENV["foo"]) + end + + if windows + def windows_version + @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i + end + + def test_win32_blocksize + keys = [] + len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1} + 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 + assert_raise(TypeError) { ENV.freeze } + 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 + + def ignore_case_str + IGNORE_CASE ? "true" : "false" + end + + def str_for_yielding_exception_class(code_str, exception_var: "raised_exception") + <<-"end;" + #{exception_var} = nil + begin + #{code_str} + rescue Exception => e + #{exception_var} = e + end + port.send #{exception_var}.class + end; + end + + def str_for_assert_raise_on_yielded_exception_class(expected_error_class, ractor_var) + <<-"end;" + error_class = #{ractor_var}.receive + assert_raise(#{expected_error_class}) do + if error_class < Exception + raise error_class + end + end + end; + end + + def str_to_yield_invalid_envvar_errors(var_name, code_str) + <<-"end;" + envvars_to_check = [ + "foo\0bar", + "#{'\xa1\xa1'}".dup.force_encoding(Encoding::UTF_16LE), + "foo".dup.force_encoding(Encoding::ISO_2022_JP), + ] + envvars_to_check.each do |#{var_name}| + #{str_for_yielding_exception_class(code_str)} + end + end; + end + + def str_to_receive_invalid_envvar_errors(ractor_var) + <<-"end;" + 3.times do + #{str_for_assert_raise_on_yielded_exception_class(ArgumentError, ractor_var)} + end + end; + end + + STR_DEFINITION_FOR_CHECK = %Q{ + def check(as, bs) + if #{IGNORE_CASE ? "true" : "false"} + as = as.map {|k, v| [k.upcase, v] } + bs = bs.map {|k, v| [k.upcase, v] } + end + assert_equal(as.sort, bs.sort) + end + } + + def test_bracket_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + port << ENV['test'] + port << ENV['TEST'] + ENV['test'] = 'foo' + port << ENV['test'] + port << ENV['TEST'] + ENV['TEST'] = 'bar' + port << ENV['TEST'] + port << ENV['test'] + #{str_for_yielding_exception_class("ENV[1]")} + #{str_for_yielding_exception_class("ENV[1] = 'foo'")} + #{str_for_yielding_exception_class("ENV['test'] = 0")} + end + assert_nil(port.receive) + assert_nil(port.receive) + assert_equal('foo', port.receive) + if #{ignore_case_str} + assert_equal('foo', port.receive) + else + assert_nil(port.receive) + end + assert_equal('bar', port.receive) + if #{ignore_case_str} + assert_equal('bar', port.receive) + else + assert_equal('foo', port.receive) + end + 3.times do + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} + end + end; + end + + def test_dup_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + #{str_for_yielding_exception_class("ENV.dup")} + end + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} + end; + end + + def test_has_value_in_ractor + assert_ractor(<<-"end;") + port = Ractor::Port.new + Ractor.new port do |port| + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + port.send(ENV.has_value?(val)) + port.send(ENV.has_value?(val.upcase)) + ENV['test'] = val + port.send(ENV.has_value?(val)) + port.send(ENV.has_value?(val.upcase)) + ENV['test'] = val.upcase + port.send ENV.has_value?(val) + port.send ENV.has_value?(val.upcase) + end + assert_equal(false, port.receive) + assert_equal(false, port.receive) + assert_equal(true, port.receive) + assert_equal(false, port.receive) + assert_equal(false, port.receive) + assert_equal(true, port.receive) + end; + end + + def test_key_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + val = 'a' + val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase) + ENV['test'] = val[0...-1] + port.send ENV.key(val) + port.send ENV.key(val.upcase) + ENV['test'] = val + port.send ENV.key(val) + port.send ENV.key(val.upcase) + ENV['test'] = val.upcase + port.send ENV.key(val) + port.send ENV.key(val.upcase) + end + assert_nil(port.receive) + assert_nil(port.receive) + if #{ignore_case_str} + assert_equal('TEST', port.receive.upcase) + else + assert_equal('test', port.receive) + end + assert_nil(port.receive) + assert_nil(port.receive) + if #{ignore_case_str} + assert_equal('TEST', port.receive.upcase) + else + assert_equal('test', port.receive) + end + end; + + end + + def test_delete_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + #{str_to_yield_invalid_envvar_errors("v", "ENV.delete(v)")} + port.send ENV.delete("TEST") + #{str_for_yielding_exception_class("ENV.delete('#{PATH_ENV}')")} + port.send(ENV.delete("TEST"){|name| "NO "+name}) + end + #{str_to_receive_invalid_envvar_errors("port")} + assert_nil(port.receive) + exception_class = port.receive + assert_equal(NilClass, exception_class) + assert_equal("NO TEST", port.receive) + end; + end + + def test_getenv_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + #{str_to_yield_invalid_envvar_errors("v", "ENV[v]")} + ENV["#{PATH_ENV}"] = "" + port.send ENV["#{PATH_ENV}"] + port.send ENV[""] + end + #{str_to_receive_invalid_envvar_errors("port")} + assert_equal("", port.receive) + assert_nil(port.receive) + end; + end + + def test_fetch_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV["test"] = "foo" + port.send ENV.fetch("test") + ENV.delete("test") + #{str_for_yielding_exception_class("ENV.fetch('test')", exception_var: "ex")} + port.send ex.receiver.object_id + port.send ex.key + port.send ENV.fetch("test", "foo") + port.send(ENV.fetch("test"){"bar"}) + #{str_to_yield_invalid_envvar_errors("v", "ENV.fetch(v)")} + #{str_for_yielding_exception_class("ENV.fetch('#{PATH_ENV}', 'foo')")} + ENV['#{PATH_ENV}'] = "" + port.send ENV.fetch('#{PATH_ENV}') + end + assert_equal("foo", port.receive) + #{str_for_assert_raise_on_yielded_exception_class(KeyError, "port")} + assert_equal(ENV.object_id, port.receive) + assert_equal("test", port.receive) + assert_equal("foo", port.receive) + assert_equal("bar", port.receive) + #{str_to_receive_invalid_envvar_errors("port")} + exception_class = port.receive + assert_equal(NilClass, exception_class) + assert_equal("", port.receive) + end; + end + + def test_aset_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + #{str_for_yielding_exception_class("ENV['test'] = nil")} + ENV["test"] = nil + port.send ENV["test"] + #{str_to_yield_invalid_envvar_errors("v", "ENV[v] = 'test'")} + #{str_to_yield_invalid_envvar_errors("v", "ENV['test'] = v")} + end + exception_class = port.receive + assert_equal(NilClass, exception_class) + assert_nil(port.receive) + #{str_to_receive_invalid_envvar_errors("port")} + #{str_to_receive_invalid_envvar_errors("port")} + end; + end + + def test_keys_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + a = ENV.keys + port.send a + end + a = port.receive + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end; + + end + + def test_each_key_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.each_key {|k| port.send(k)} + port.send "finished" + end + while((x=port.receive) != "finished") + assert_kind_of(String, x) + end + end; + end + + def test_values_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + a = ENV.values + port.send a + end + a = port.receive + assert_kind_of(Array, a) + a.each {|k| assert_kind_of(String, k) } + end; + end + + def test_each_value_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.each_value {|k| port.send(k)} + port.send "finished" + end + while((x=port.receive) != "finished") + assert_kind_of(String, x) + end + end; + end + + def test_each_pair_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.each_pair {|k, v| port.send([k,v])} + port.send "finished" + end + while((k,v=port.receive) != "finished") + assert_kind_of(String, k) + assert_kind_of(String, v) + end + end; + end + + def test_reject_bang_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + port.send [h1, h2] + port.send(ENV.reject! {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) + end + h1, h2 = port.receive + assert_equal(h1, h2) + assert_nil(port.receive) + end; + end + + def test_delete_if_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + port.send [h1, h2] + port.send (ENV.delete_if {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" }) + end + h1, h2 = port.receive + assert_equal(h1, h2) + assert_same(ENV, port.receive) + end; + end + + def test_select_bang_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + port.send [h1, h2] + port.send(ENV.select! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + end + h1, h2 = port.receive + assert_equal(h1, h2) + assert_nil(port.receive) + end; + end + + def test_filter_bang_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + port.send [h1, h2] + port.send(ENV.filter! {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + end + h1, h2 = port.receive + assert_equal(h1, h2) + assert_nil(port.receive) + end; + end + + def test_keep_if_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" } + h2 = {} + ENV.each_pair {|k, v| h2[k] = v } + port.send [h1, h2] + port.send (ENV.keep_if {|k, v| #{ignore_case_str} ? k.upcase != "TEST" : k != "test" }) + end + h1, h2 = port.receive + assert_equal(h1, h2) + assert_equal(ENV, port.receive) + end; + end + + def test_values_at_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV["test"] = "foo" + port.send ENV.values_at("test", "test") + end + assert_equal(["foo", "foo"], port.receive) + end; + end + + def test_select_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV["test"] = "foo" + h = ENV.select {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + port.send h.size + k = h.keys.first + v = h.values.first + port.send [k, v] + end + assert_equal(1, port.receive) + k, v = port.receive + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_filter_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV["test"] = "foo" + h = ENV.filter {|k| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + port.send(h.size) + k = h.keys.first + v = h.values.first + port.send [k, v] + end + assert_equal(1, port.receive) + k, v = port.receive + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_slice_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + port.send(ENV.slice()) + port.send(ENV.slice("")) + port.send(ENV.slice("unknown")) + port.send(ENV.slice("foo", "baz")) + end + assert_equal({}, port.receive) + assert_equal({}, port.receive) + assert_equal({}, port.receive) + assert_equal({"foo"=>"bar", "baz"=>"qux"}, port.receive) + end; + end + + def test_except_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV["bar"] = "rab" + port.send ENV.except() + port.send ENV.except("") + port.send ENV.except("unknown") + port.send ENV.except("foo", "baz") + end + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab", "baz"=>"qux", "foo"=>"bar"}, port.receive) + assert_equal({"bar"=>"rab"}, port.receive) + end; + end + + def test_clear_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + port.send ENV.size + end + assert_equal(0, port.receive) + end; + end + + def test_to_s_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.to_s + end + assert_equal("ENV", r.value) + end; + end + + def test_inspect_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + s = ENV.inspect + port.send s + end + s = port.receive + expected = ['"foo" => "bar"', '"baz" => "qux"'] + unless s.start_with?(/\{"foo"/i) + expected.reverse! + end + expected = "{" + expected.join(', ') + "}" + if #{ignore_case_str} + s = s.upcase + expected = expected.upcase + end + assert_equal(expected, s) + end; + end + + def test_to_a_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.to_a + port.send a + end + a = port.receive + assert_equal(2, a.size) + expected = [%w(baz qux), %w(foo bar)] + if #{ignore_case_str} + a = a.map {|x, y| [x.upcase, y]} + expected.map! {|x, y| [x.upcase, y]} + end + a.sort! + assert_equal(expected, a) + end; + end + + def test_rehash_in_ractor + assert_ractor(<<-"end;") + r = Ractor.new do + ENV.rehash + end + assert_nil(r.value) + end; + end + + def test_size_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + s = ENV.size + ENV["test"] = "foo" + port.send [s, ENV.size] + end + s, s2 = port.receive + assert_equal(s + 1, s2) + end; + end + + def test_empty_p_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + port.send ENV.empty? + ENV["test"] = "foo" + port.send ENV.empty? + end + assert port.receive + assert !port.receive + end; + end + + def test_has_key_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + port.send ENV.has_key?("test") + ENV["test"] = "foo" + port.send ENV.has_key?("test") + #{str_to_yield_invalid_envvar_errors("v", "ENV.has_key?(v)")} + end + assert !port.receive + assert port.receive + #{str_to_receive_invalid_envvar_errors("port")} + end; + end + + def test_assoc_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + port.send ENV.assoc("test") + ENV["test"] = "foo" + port.send ENV.assoc("test") + #{str_to_yield_invalid_envvar_errors("v", "ENV.assoc(v)")} + end + assert_nil(port.receive) + k, v = port.receive + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + #{str_to_receive_invalid_envvar_errors("port")} + encoding = /mswin|mingw/ =~ RUBY_PLATFORM ? Encoding::UTF_8 : Encoding.find("locale") + assert_equal(encoding, v.encoding) + end; + end + + def test_has_value2_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + port.send ENV.has_value?("foo") + ENV["test"] = "foo" + port.send ENV.has_value?("foo") + end + assert !port.receive + assert port.receive + end; + end + + def test_rassoc_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + port.send ENV.rassoc("foo") + ENV["foo"] = "bar" + ENV["test"] = "foo" + ENV["baz"] = "qux" + port.send ENV.rassoc("foo") + end + assert_nil(port.receive) + k, v = port.receive + if #{ignore_case_str} + assert_equal("TEST", k.upcase) + assert_equal("FOO", v.upcase) + else + assert_equal("test", k) + assert_equal("foo", v) + end + end; + end + + def test_to_hash_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h = {} + ENV.each {|k, v| h[k] = v } + port.send [h, ENV.to_hash] + end + h, h2 = port.receive + assert_equal(h, h2) + end; + end + + def test_to_h_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + port.send [ENV.to_hash, ENV.to_h] + port.send [ENV.map {|k, v| ["$\#{k}", v.size]}.to_h, ENV.to_h {|k, v| ["$\#{k}", v.size]}] + end + a, b = port.receive + assert_equal(a,b) + c, d = port.receive + assert_equal(c,d) + end; + end + + def test_reject_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + h1 = {} + ENV.each_pair {|k, v| h1[k] = v } + ENV["test"] = "foo" + h2 = ENV.reject {|k, v| #{ignore_case_str} ? k.upcase == "TEST" : k == "test" } + port.send [h1, h2] + end + h1, h2 = port.receive + assert_equal(h1, h2) + end; + end + + def test_shift_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + a = ENV.shift + b = ENV.shift + port.send [a,b] + port.send ENV.shift + end + a,b = port.receive + check([a, b], [%w(foo bar), %w(baz qux)]) + assert_nil(port.receive) + end; + end + + def test_invert_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + port.send(ENV.invert) + end + check(port.receive.to_a, [%w(bar foo), %w(qux baz)]) + end; + end + + def test_replace_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + Ractor.new port = Ractor::Port.new do |port| + ENV["foo"] = "xxx" + ENV.replace({"foo"=>"bar", "baz"=>"qux"}) + port.send ENV.to_hash + ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"}) + port.send ENV.to_hash + end + check(port.receive.to_a, [%w(foo bar), %w(baz qux)]) + check(port.receive.to_a, [%w(Foo Bar), %w(Baz Qux)]) + end; + end + + def test_update_in_ractor + assert_ractor(<<-"end;") + #{STR_DEFINITION_FOR_CHECK} + Ractor.new port = Ractor::Port.new do |port| + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) + port.send ENV.to_hash + ENV.clear + ENV["foo"] = "bar" + ENV["baz"] = "qux" + ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| k + "_" + v1 + "_" + v2 } + port.send ENV.to_hash + end + check(port.receive.to_a, [%w(foo bar), %w(baz quux), %w(a b)]) + check(port.receive.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)]) + end; + end + + def test_huge_value_in_ractor + assert_ractor(<<-"end;") + huge_value = "bar" * 40960 + Ractor.new port = Ractor::Port.new, huge_value do |port, v| + ENV["foo"] = "bar" + #{str_for_yielding_exception_class("ENV['foo'] = v ")} + port.send ENV["foo"] + end + + if /mswin|ucrt/ =~ RUBY_PLATFORM + #{str_for_assert_raise_on_yielded_exception_class(Errno::EINVAL, "port")} + result = port.receive + assert_equal("bar", result) + else + exception_class = port.receive + assert_equal(NilClass, exception_class) + result = port.receive + assert_equal(huge_value, result) + end + end; + end + + def test_frozen_env_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + #{str_for_yielding_exception_class("ENV.freeze")} + end + #{str_for_assert_raise_on_yielded_exception_class(TypeError, "port")} + end; + end + + def test_frozen_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + ENV["#{PATH_ENV}"] = "/" + ENV.each do |k, v| + port.send [k] + port.send [v] + end + ENV.each_key do |k| + port.send [k] + end + ENV.each_value do |v| + port.send [v] + end + ENV.each_key do |k| + port.send [ENV[k], "[\#{k.dump}]"] + port.send [ENV.fetch(k), "fetch(\#{k.dump})"] + end + port.send "finished" + end + while((params=port.receive) != "finished") + value, *params = params + assert_predicate(value, :frozen?, *params) + end + end; + end + + def test_shared_substring_in_ractor + assert_ractor(<<-"end;") + Ractor.new port = Ractor::Port.new do |port| + 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] + port.send [n, e0, e1, bug12475] + end + n, e0, e1, bug12475 = port.receive + assert_equal("T\#{n}", e0, bug12475) + assert_nil(e1, bug12475) + end; + end + + def test_ivar_in_env_should_not_be_access_from_non_main_ractors + assert_ractor <<~RUBY + ENV.instance_eval{ @a = "hello" } + assert_equal "hello", ENV.instance_variable_get(:@a) + + r_get = Ractor.new do + ENV.instance_variable_get(:@a) + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, r_get.value.class + + r_get = Ractor.new do + ENV.instance_eval{ @a } + rescue Ractor::IsolationError => e + e + end + + assert_equal Ractor::IsolationError, r_get.value.class + + r_set = Ractor.new do + ENV.instance_eval{ @b = "hello" } + rescue Ractor::IsolationError => e + e + end + + assert_equal Ractor::IsolationError, r_set.value.class + RUBY + 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 + + def test_utf8 + text = "testing \u{e5 e1 e2 e4 e3 101 3042}" + ENV["test"] = text + assert_equal text, ENV["test"] + end + + def test_utf8_empty + key = "VAR\u{e5 e1 e2 e4 e3 101 3042}" + ENV[key] = "x" + assert_equal "x", ENV[key] + ENV[key] = "" + assert_equal "", ENV[key] + ENV[key] = nil + assert_nil ENV[key] + end + end +end diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb new file mode 100644 index 0000000000..d2145bec5d --- /dev/null +++ b/test/ruby/test_eval.rb @@ -0,0 +1,655 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestEval < Test::Unit::TestCase + + @ivar = 12 + @@cvar = 13 + $gvar__eval = 14 + Const = 15 + + def ruby(*args) + args = ['-e', '$>.write($<.read)'] if args.empty? + ruby = EnvUtil.rubybin + f = IO.popen([ruby] + args, 'r+') + yield(f) + ensure + f.close unless !f || f.closed? + end + + def test_eval_basic + assert_equal nil, eval("nil") + assert_equal true, eval("true") + assert_equal false, eval("false") + assert_equal self, eval("self") + assert_equal 1, eval("1") + assert_equal :sym, eval(":sym") + + assert_equal 11, eval("11") + @ivar = 12 + assert_equal 12, eval("@ivar") + assert_equal 13, eval("@@cvar") + assert_equal 14, eval("$gvar__eval") + assert_equal 15, eval("Const") + + assert_equal 16, eval("7 + 9") + assert_equal 17, eval("17.to_i") + assert_equal "18", eval(%q("18")) + assert_equal "19", eval(%q("1#{9}")) + + 1.times { + assert_equal 12, eval("@ivar") + assert_equal 13, eval("@@cvar") + assert_equal 14, eval("$gvar__eval") + assert_equal 15, eval("Const") + } + end + + def test_eval_binding_basic + assert_equal nil, eval("nil", binding()) + assert_equal true, eval("true", binding()) + assert_equal false, eval("false", binding()) + assert_equal self, eval("self", binding()) + assert_equal 1, eval("1", binding()) + assert_equal :sym, eval(":sym", binding()) + + assert_equal 11, eval("11", binding()) + @ivar = 12 + assert_equal 12, eval("@ivar", binding()) + assert_equal 13, eval("@@cvar", binding()) + assert_equal 14, eval("$gvar__eval", binding()) + assert_equal 15, eval("Const", binding()) + + assert_equal 16, eval("7 + 9", binding()) + assert_equal 17, eval("17.to_i", binding()) + assert_equal "18", eval(%q("18"), binding()) + assert_equal "19", eval(%q("1#{9}"), binding()) + + 1.times { + assert_equal 12, eval("@ivar") + assert_equal 13, eval("@@cvar") + assert_equal 14, eval("$gvar__eval") + assert_equal 15, eval("Const") + } + end + + def test_module_eval_string_basic + c = self.class + assert_equal nil, c.module_eval("nil") + assert_equal true, c.module_eval("true") + assert_equal false, c.module_eval("false") + assert_equal c, c.module_eval("self") + assert_equal :sym, c.module_eval(":sym") + assert_equal 11, c.module_eval("11") + @ivar = 12 + assert_equal 12, c.module_eval("@ivar") + assert_equal 13, c.module_eval("@@cvar") + assert_equal 14, c.module_eval("$gvar__eval") + assert_equal 15, c.module_eval("Const") + assert_equal 16, c.module_eval("7 + 9") + assert_equal 17, c.module_eval("17.to_i") + assert_equal "18", c.module_eval(%q("18")) + assert_equal "19", c.module_eval(%q("1#{9}")) + + @ivar = 12 + 1.times { + assert_equal 12, c.module_eval("@ivar") + assert_equal 13, c.module_eval("@@cvar") + assert_equal 14, c.module_eval("$gvar__eval") + assert_equal 15, c.module_eval("Const") + } + end + + def test_module_eval_block_basic + c = self.class + assert_equal nil, c.module_eval { nil } + assert_equal true, c.module_eval { true } + assert_equal false, c.module_eval { false } + assert_equal c, c.module_eval { self } + assert_equal :sym, c.module_eval { :sym } + assert_equal 11, c.module_eval { 11 } + @ivar = 12 + assert_equal 12, c.module_eval { @ivar } + assert_equal 13, c.module_eval { @@cvar } + assert_equal 14, c.module_eval { $gvar__eval } + assert_equal 15, c.module_eval { Const } + assert_equal 16, c.module_eval { 7 + 9 } + assert_equal 17, c.module_eval { "17".to_i } + assert_equal "18", c.module_eval { "18" } + assert_equal "19", c.module_eval { "1#{9}" } + + @ivar = 12 + 1.times { + assert_equal 12, c.module_eval { @ivar } + assert_equal 13, c.module_eval { @@cvar } + assert_equal 14, c.module_eval { $gvar__eval } + assert_equal 15, c.module_eval { Const } + } + end + + def test_module_eval_block_symbol + assert_equal "Math", Math.module_eval(&:to_s) + end + + def forall_TYPE + objects = [Object.new, [], nil, true, false] # TODO: check + objects.each do |obj| + obj.instance_variable_set :@ivar, 12 unless obj.frozen? + yield obj + end + end + + def test_instance_eval_string_basic + 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 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 + + def test_instance_eval_block_self + # instance_eval(&block)'s self must not be sticky (jruby/jruby#2060) + pr = proc { self } + assert_equal self, pr.call + o = Object.new + assert_equal o, o.instance_eval(&pr) + assert_equal self, pr.call + end + + def test_instance_eval_block_symbol + forall_TYPE do |o| + assert_equal o.to_s, o.instance_eval(&:to_s) + end + end + + def test_instance_eval_cvar + [Object.new, [], 7, :sym, true, false, nil].each do |obj| + assert_equal(13, obj.instance_eval("@@cvar")) + assert_equal(13, obj.instance_eval{@@cvar}) + # assert_raise(NameError){obj.instance_eval("@@cvar")} + # assert_raise(NameError){obj.instance_eval{@@cvar}} + end + end + + def test_instance_exec_cvar + [Object.new, [], 7, :sym, true, false, nil].each do |obj| + assert_equal(13, obj.instance_exec{@@cvar}) + end + end + + def test_instance_eval_method + bug2788 = '[ruby-core:28324]' + [Object.new, [], nil, true, false].each do |o| + 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 + + def test_instance_exec_block_basic + forall_TYPE do |o| + assert_equal nil, o.instance_exec { nil } + assert_equal true, o.instance_exec { true } + assert_equal false, o.instance_exec { false } + assert_equal o, o.instance_exec { self } + assert_equal 1, o.instance_exec { 1 } + assert_equal :sym, o.instance_exec { :sym } + + assert_equal 11, o.instance_exec { 11 } + assert_equal 12, o.instance_exec { @ivar } unless o.frozen? + assert_equal 13, o.instance_exec { @@cvar } + assert_equal 14, o.instance_exec { $gvar__eval } + assert_equal 15, o.instance_exec { Const } + assert_equal 16, o.instance_exec { 7 + 9 } + assert_equal 17, o.instance_exec { 17.to_i } + assert_equal "18", o.instance_exec { "18" } + assert_equal "19", o.instance_exec { "1#{9}" } + + 1.times { + assert_equal 12, o.instance_exec { @ivar } unless o.frozen? + assert_equal 13, o.instance_exec { @@cvar } + assert_equal 14, o.instance_exec { $gvar__eval } + assert_equal 15, o.instance_exec { Const } + } + end + end + + def test_instance_exec_method_definition + klass = Class.new + o = klass.new + + o.instance_exec do + def foo + :foo_result + end + end + + assert_respond_to o, :foo + refute_respond_to klass, :foo + refute_respond_to klass.new, :foo + + assert_equal :foo_result, o.foo + end + + def test_instance_exec_eval_method_definition + klass = Class.new + o = klass.new + + o.instance_exec do + eval %{ + def foo + :foo_result + end + } + end + + assert_respond_to o, :foo + refute_respond_to klass, :foo + refute_respond_to klass.new, :foo + + assert_equal :foo_result, o.foo + end + + # + # From ruby/test/ruby/test_eval.rb + # + + def make_test_binding + local1 = local1 = "local1" + lambda { + 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) + + assert(eval('Object')) + assert(eval('true')) + assert(!eval('nil')) + assert(!eval('false')) + + $foo = 'assert(true)' + begin + eval $foo + rescue + assert(false) + end + + assert_equal('assert(true)', eval("$foo")) + assert_equal(true, eval("true")) + i = i = 5 + assert(eval("i == 5")) + assert_equal(5, eval("i")) + assert(eval("defined? i")) + + 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 + end + assert(!bad) + + # !! use class_eval to avoid nested definition + x = self.class.class_eval %q( + module EvTest + EVTEST1 = 25 + evtest2 = 125 + 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 + begin + eval("EVTEST1") + rescue NameError # must raise error + bad = false + end + assert(!bad) + + x = binding + eval "i = 1", x + assert_equal(1, eval("i", x)) + x = proc{binding}.call + eval "i = 22", x + assert_equal(22, eval("i", x)) + t = [] + x = proc{binding}.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)) + assert(eval("defined? j6", x)) + + proc { + p = binding + eval "foo11 = 1", p + foo22 = 5 + 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")) + assert_equal(55, foo22) + }.call + + self.class.class_eval do + remove_const :EvTest + end + end + + def test_nil_instance_eval_cvar + def nil.test_binding + binding + end + bb = eval("nil.instance_eval \"binding\"", nil.test_binding) + assert_raise(NameError, "[ruby-dev:24103]") { eval("@@a", bb) } + class << nil + remove_method :test_binding + end + end + + def test_fixnum_instance_eval_cvar + assert_raise(NameError, "[ruby-dev:24213]") { 1.instance_eval "@@a" } + end + + def test_cvar_scope_with_instance_eval + # TODO: check + 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]") + Integer.__send__(:remove_class_variable, :@@test_cvar_scope_with_instance_eval) + end + + def test_eval_and_define_method + assert_nothing_raised("[ruby-dev:24228]") { + def temporally_method_for_test_eval_and_define_method(&block) + lambda { + class << Object.new; self end.send(:define_method, :zzz, &block) + } + end + v = eval("temporally_method_for_test_eval_and_define_method {}") + {}[0] = {} + v.call + } + end + + def test_define_method_block + cc = Class.new do + define_method(:foo) {|&block| + block.call if block + } + end + + c = cc.new + x = "ng" + c.foo() {x = "ok"} + 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) + ensure + Object.undef_method(:feature6609_block) rescue nil + Object.undef_method(:feature6609_method) rescue nil + end + + def test_eval_using_integer_as_binding + assert_raise(TypeError) { eval("", 1) } + end + + def test_eval_raise + assert_raise(RuntimeError) { eval("raise ''") } + end + + def test_eval_with_toplevel_binding # [ruby-dev:37142] + ruby("-e", "x = 0; eval('p x', TOPLEVEL_BINDING)") do |f| + f.close_write + assert_equal("0", f.read.chomp) + end + end + + def test_eval_ascii_incompatible + assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-16be"))} + assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-16le"))} + 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_invalid_block_exit_bug_20597 + assert_raise(SyntaxError){eval("break if false")} + assert_raise(SyntaxError){eval("next if false")} + assert_raise(SyntaxError){eval("redo if false")} + 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_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", nil)) + assert_equal(["(eval at #{__FILE__}:#{__LINE__})", 1], eval("[__FILE__, __LINE__]", binding)) + assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo')) + assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo')) + assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2)) + assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", binding, 'foo', 2)) + end + + def test_fstring_instance_eval + bug = "[ruby-core:78116] [Bug #12930]".freeze + assert_same bug, (bug.instance_eval {self}) + assert_raise(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 + + def test_syntax_error_no_memory_leak + assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + begin; + 100_000.times do + eval("/[/=~s") + rescue SyntaxError + else + raise "Expected SyntaxError to be raised" + end + end; + + assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + begin; + a = 1 + + 100_000.times do + eval("if a in [0, 0] | [0, a]; end") + rescue SyntaxError + else + raise "Expected SyntaxError to be raised" + end + end; + end + + def test_outer_local_variable_under_gc_compact_stress + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + omit "compaction is not supported on s390x" if /s390x/ =~ RUBY_PLATFORM + + assert_separately([], <<~RUBY) + o = Object.new + def o.m = 1 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + EnvUtil.under_gc_compact_stress do + assert_equal(1, eval("o.m")) + end + RUBY + end +end diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb new file mode 100644 index 0000000000..31e5aa9f6b --- /dev/null +++ b/test/ruby/test_exception.rb @@ -0,0 +1,1555 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' + +class TestException < Test::Unit::TestCase + def test_exception_rescued + begin + raise "this must be handled" + assert(false) + rescue + assert(true) + end + end + + def test_exception_retry + bad = true + begin + raise "this must be handled no.2" + rescue + if bad + bad = false + retry + end + assert(!bad) + end + assert(true) + end + + 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 + end + assert(false) + end + end + + 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 + assert_instance_of(RuntimeError, $!) + assert_equal("this must be handled no.4", $!.message) + raise "exception in ensure clause" + end + assert(false) + end + end + + def test_exception_ensure + bad = true + begin + begin + raise "this must be handled no.5" + ensure + bad = false + end + rescue + end + assert(!bad) + end + + def test_exception_ensure_2 # just duplication? + bad = true + begin + begin + raise "this must be handled no.6" + ensure + bad = false + end + rescue + end + assert(!bad) + end + + def test_exception_in_ensure_with_next + string = "[ruby-core:82936] [Bug #13930]" + assert_raise_with_message(RuntimeError, string) do + lambda do + next + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + + assert_raise_with_message(RuntimeError, string) do + flag = true + while flag + flag = false + begin + next + rescue + assert(false) + ensure + raise string + end + end + end + + iseq = RubyVM::InstructionSequence.compile(<<-RUBY) + begin + while true + break + end + rescue + end + RUBY + + assert_equal false, iseq.to_a[13].any?{|(e,_)| e == :throw} + end + + def test_exception_in_ensure_with_redo + string = "[ruby-core:82936] [Bug #13930]" + + assert_raise_with_message(RuntimeError, string) do + i = 0 + lambda do + i += 1 + redo if i < 2 + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + end + + def test_exception_in_ensure_with_return + @string = "[ruby-core:97104] [Bug #16618]" + def self.meow + return if true # This if modifier suppresses "warning: statement not reached" + assert(false) + rescue + assert(false) + ensure + raise @string + end + assert_raise_with_message(RuntimeError, @string) do + meow + end + end + + def test_errinfo_in_debug + bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do + def to_s + require '\0' + rescue LoadError + self.class.to_s + end + end + + 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 + end + end + 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 + + 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_catch_throw_in_require_cant_be_rescued + bug18562 = '[ruby-core:107403]' + Tempfile.create(["dep", ".rb"]) {|t| + t.puts("throw :extdep, 42") + t.close + + rescue_all = Class.new(Exception) + def rescue_all.===(_) + raise "should not reach here" + end + + v = assert_throw(:extdep, bug18562) do + require t.path + rescue rescue_all + assert(false, "should not reach here") + end + + assert_equal(42, v, bug18562) + } + end + + def test_throw_false + bug12743 = '[ruby-core:77229] [Bug #12743]' + Thread.start { + e = assert_raise_with_message(UncaughtThrowError, /false/, bug12743) { + throw false + } + assert_same(false, e.tag, bug12743) + }.join + end + + def test_else_no_exception + begin + assert(true) + rescue + assert(false) + else + assert(true) + end + end + + def test_else_raised + begin + assert(true) + raise + assert(false) + rescue + assert(true) + else + assert(false) + end + end + + def test_else_nested_no_exception + begin + assert(true) + begin + assert(true) + rescue + assert(false) + else + assert(true) + end + assert(true) + rescue + assert(false) + else + assert(true) + end + end + + def test_else_nested_rescued + begin + assert(true) + begin + assert(true) + raise + assert(false) + rescue + assert(true) + else + assert(false) + end + assert(true) + rescue + assert(false) + else + assert(true) + end + end + + def test_else_nested_unrescued + begin + assert(true) + begin + assert(true) + rescue + assert(false) + else + assert(true) + end + assert(true) + raise + assert(false) + rescue + assert(true) + else + assert(false) + end + end + + def test_else_nested_rescued_reraise + begin + assert(true) + begin + assert(true) + raise + assert(false) + rescue + assert(true) + else + assert(false) + end + assert(true) + raise + assert(false) + rescue + assert(true) + else + assert(false) + end + end + + def test_raise_with_wrong_number_of_arguments + assert_raise(TypeError) { raise nil } + assert_raise(TypeError) { raise 1, 1 } + 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), []) + + assert_in_out_err([], "$@ = 1", [], /\$! not set \(ArgumentError\)$/) + + assert_in_out_err([], <<-INPUT, [], /backtrace must be an Array of String or an Array of Thread::Backtrace::Location \(TypeError\)$/) + begin + raise + rescue + $@ = 1 + end + INPUT + + assert_in_out_err([], <<-INPUT, [], /^foo: unhandled exception$/) + begin + raise + rescue + $@ = 'foo' + raise + end + INPUT + + assert_in_out_err([], <<-INPUT, [], /^foo: unhandled exception\s+from bar\s+from baz$/) + begin + raise + rescue + $@ = %w(foo bar baz) + raise + end + INPUT + end + + def test_thread_signal_location + # pend('TODO: a known bug [Bug #14474]') + _, stderr, _ = EnvUtil.invoke_ruby(%w"--disable-gems -d", <<-RUBY, false, true) +Thread.start do + Thread.current.report_on_exception = false + begin + Process.kill(:INT, $$) + ensure + raise "in ensure" + end +end.join + RUBY + assert_not_match(/:0/, stderr, "[ruby-dev:39116]") + end + + def test_errinfo + begin + raise "foo" + assert(false) + rescue => e + assert_equal(e, $!) + 1.times { assert_equal(e, $!) } + end + + assert_equal(nil, $!) + end + + def test_inspect + assert_equal("#<Exception: Exception>", Exception.new.inspect) + + e = Class.new(Exception) + e.class_eval do + def to_s; ""; end + end + assert_equal(e.inspect, e.new.inspect) + + # https://bugs.ruby-lang.org/issues/18170#note-13 + assert_equal('#<Exception:"foo\nbar">', Exception.new("foo\nbar").inspect) + assert_equal('#<Exception: foo bar>', Exception.new("foo bar").inspect) + assert_equal('#<Exception: foo\bar>', Exception.new("foo\\bar").inspect) + assert_equal('#<Exception: "foo\nbar">', Exception.new('"foo\nbar"').inspect) + end + + def test_to_s + 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 + + e.set_backtrace("foo") + assert_equal(["foo"], e.backtrace) + + e.set_backtrace(%w(foo bar baz)) + assert_equal(%w(foo bar baz), e.backtrace) + + assert_raise(TypeError) { e.set_backtrace(1) } + assert_raise(TypeError) { e.set_backtrace([1]) } + + error = assert_raise(TypeError) do + e.set_backtrace(caller_locations(1, 1) + ["foo"]) + end + assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location" + + error = assert_raise(TypeError) do + e.set_backtrace(["foo"] + caller_locations(1, 1)) + end + assert_include error.message, "backtrace must be an Array of String or an Array of Thread::Backtrace::Location" + end + + def test_exit_success_p + begin + exit + rescue SystemExit => e + end + 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_not_send([e, :success?], "abort means failure") + end + + def test_errno + assert_equal(Encoding.find("locale"), Errno::EINVAL.new.message.encoding) + end + + def test_errno_constants + assert_equal [:NOERROR], Errno.constants.grep_v(/\AE/) + all_assertions_foreach("should be a subclass of SystemCallError", *Errno.constants) do |c| + e = Errno.const_get(c) + assert_operator e, :<, SystemCallError, proc {e.ancestors.inspect} + end + 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 + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug5575 = '[ruby-core:41612]' + begin; + begin + BasicObject.new.inspect + rescue + assert_nothing_raised(NameError, bug5575) {$!.inspect} + end + end; + end + + def test_ensure_after_nomemoryerror + omit "Forcing NoMemoryError causes problems in some environments" + assert_separately([], "$_ = 'a' * 1_000_000_000_000_000_000") + rescue NoMemoryError + assert_raise(NoMemoryError) do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug15779 = bug15779 = '[ruby-core:92342]' + begin; + require 'open-uri' + + begin + 'a' * 1_000_000_000_000_000_000 + ensure + URI.open('http://www.ruby-lang.org/') + end + end; + end + rescue Test::Unit::AssertionFailedError + # Possibly compiled with -DRUBY_DEBUG, in which + # case rb_bug is used instead of NoMemoryError, + # and we cannot test ensure after NoMemoryError. + rescue RangeError + # MingW can raise RangeError instead of NoMemoryError, + # so we cannot test this case. + rescue Timeout::Error + # Solaris 11 CI times out instead of raising NoMemoryError, + # so we cannot test this case. + end + + def test_equal + 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 + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + bug5865 = '[ruby-core:41979]' + begin; + o = Object.new + def o.exception(arg) + end + assert_nothing_raised(ArgumentError, bug5865) do + RuntimeError.new("a") == o + end + end; + end + + def test_backtrace_by_exception + begin + line = __LINE__; raise "foo" + rescue => e + end + e2 = e.exception("bar") + assert_not_equal(e.message, e2.message) + assert_equal(e.backtrace, e2.backtrace) + loc = e2.backtrace_locations[0] + assert_equal([__FILE__, line], [loc.path, loc.lineno]) + end + + Bug4438 = '[ruby-core:35364]' + + 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 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([], <<-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([], <<-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) + assert_raise(TypeError) {raise msg, {cause: 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 = Thread::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 + + 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_predicate err, :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 assert_null_char(src, *args, **opts) + begin + eval(src) + rescue => e + end + assert_not_nil(e) + assert_include(e.message, "\0") + # Disabled by [Feature #18367] + #assert_in_out_err([], src, [], [], *args, **opts) do |_, err,| + # err.each do |e| + # assert_not_include(e, "\0") + # end + #end + e + end + + def test_control_in_message + bug7574 = '[ruby-dev:46749]' + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_defined?("String\0") + end; + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_get("String\0") + end; + end + + def test_encoding_in_message + name = "\u{e9}t\u{e9}" + e = EnvUtil.with_default_external("US-ASCII") do + assert_raise(NameError) do + Object.const_get(name) + end + end + assert_include(e.message, name) + end + + def test_method_missing_reason_clear + bug10969 = '[ruby-core:68515] [Bug #10969]' + a = Class.new {def method_missing(*) super end}.new + 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(category: false) + verbose = $VERBOSE + categories = Warning.categories.to_h {|cat| [cat, Warning[cat]]} + warning = [] + + ::Warning.class_eval do + alias_method :warn2, :warn + remove_method :warn + + if category + define_method(:warn) do |str, category: nil| + warning << [str, category] + end + else + define_method(:warn) do |str, category: nil| + warning << str + end + end + end + + $VERBOSE = true + Warning.categories.each {|cat| Warning[cat] = true} + yield + + return warning + ensure + $VERBOSE = verbose + categories.each {|cat, flag| Warning[cat] = flag} + + ::Warning.class_eval do + remove_method :warn + alias_method :warn, :warn2 + remove_method :warn2 + end + end + + def test_warning_warn + warning = capture_warning_warn {$asdfasdsda_test_warning_warn} + assert_match(/global variable '\$asdfasdsda_test_warning_warn' not initialized/, warning[0]) + + assert_equal(["a\nz\n"], capture_warning_warn {warn "a\n", "z"}) + assert_equal([], capture_warning_warn {warn}) + assert_equal(["\n"], capture_warning_warn {warn ""}) + end + + def test_warn_deprecated_backwards_compatibility_category + (message, category), = capture_warning_warn(category: true) do + $; = "www" + $; = nil + end + + assert_include message, 'deprecated' + assert_equal :deprecated, category + end + + def test_kernel_warn_uplevel + warning = capture_warning_warn {warn("test warning", uplevel: 0)} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + def (obj = Object.new).w(n) warn("test warning", uplevel: n) end + warning = capture_warning_warn {obj.w(0)} + assert_equal("#{__FILE__}:#{__LINE__-2}: warning: test warning\n", warning[0]) + warning = capture_warning_warn {obj.w(1)} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", uplevel: -1)} + assert_in_out_err(["-e", "warn 'ok', uplevel: 1"], '', [], /warning:/) + warning = capture_warning_warn {warn("test warning", {uplevel: 0})} + assert_match(/test warning.*{uplevel: 0}/m, warning[0]) + warning = capture_warning_warn {warn("test warning", **{uplevel: 0})} + assert_equal("#{__FILE__}:#{__LINE__-1}: warning: test warning\n", warning[0]) + warning = capture_warning_warn {warn("test warning", {uplevel: 0}, **{})} + assert_equal("test warning\n{uplevel: 0}\n", warning[0]) + assert_raise(ArgumentError) {warn("test warning", foo: 1)} + end + + def test_warning_warn_invalid_argument + assert_raise(TypeError) do + ::Warning.warn nil + 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 { + assert require(basename) + } + ensure + $LOAD_PATH.pop + $LOADED_FEATURES.delete(t.path) + 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#{<<~'};'}", [], /global variable '\$asdfiasdofa_test_warning_warn_super' not initialized/) + {# + module Warning + def warn(message) + super + end + end + + $VERBOSE = true + $asdfiasdofa_test_warning_warn_super + }; + end + + def test_warning_category + assert_raise(TypeError) {Warning[nil]} + assert_raise(ArgumentError) {Warning[:XXXX]} + + all_assertions_foreach("categories", *Warning.categories) do |cat| + value = Warning[cat] + assert_include([true, false], value) + + enabled = EnvUtil.verbose_warning do + Warning[cat] = true + Warning.warn "#{cat} feature", category: cat + end + disabled = EnvUtil.verbose_warning do + Warning[cat] = false + Warning.warn "#{cat} feature", category: cat + end + ensure + Warning[cat] = value + assert_equal "#{cat} feature", enabled + assert_empty disabled + end + end + + def test_undef_Warning_warn + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + Warning.undef_method(:warn) + assert_raise(NoMethodError) { warn "" } + end; + end + + def test_undefined_backtrace + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + 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 + File.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 at .*\):1:/, bug) + end + + def test_full_message + message = RuntimeError.new("testerror").full_message + assert_operator(message, :end_with?, "\n") + + test_method = "def foo; raise 'testerror'; end" + + out1, err1, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; rescue => e; puts e.full_message; end"], '', true, true) + assert_predicate(status1, :success?) + assert_empty(err1, "expected nothing wrote to $stdout by #full_message") + + _, err2, status1 = EnvUtil.invoke_ruby(['-e', "#{test_method}; begin; foo; end"], '', true, true) + assert_equal(err2, out1) + + e = RuntimeError.new("a\n") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_operator(message, :end_with?, "\n") + message = message.gsub(/\e\[[\d;]*m/, '') + assert_not_operator(message, :end_with?, "\n\n") + e = RuntimeError.new("a\n\nb\n\nc") + message = assert_nothing_raised(ArgumentError, proc {e.pretty_inspect}) do + e.full_message + end + assert_all?(message.lines) do |m| + /\e\[\d[;\d]*m[^\e]*\n/ !~ m + end + + e = RuntimeError.new("testerror") + message = e.full_message(highlight: false) + assert_not_match(/\e/, message) + + bt = ["test:100", "test:99", "test:98", "test:1"] + e = assert_raise(RuntimeError) {raise RuntimeError, "testerror", bt} + + bottom = "test:100: testerror (RuntimeError)\n" + top = "test:1\n" + remark = "Traceback (most recent call last):" + + message = e.full_message(highlight: false, order: :top) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + + message = e.full_message(highlight: false, order: :bottom) + assert_not_match(/\e/, message) + assert_operator(message.count("\n"), :>, 2) + assert_operator(message, :start_with?, remark) + assert_operator(message, :end_with?, bottom) + + assert_raise_with_message(ArgumentError, /:top or :bottom/) { + e.full_message(highlight: false, order: :middle) + } + + message = e.full_message(highlight: true) + assert_match(/\e/, message) + assert_not_match(/(\e\[1)m\1/, message) + e2 = assert_raise(RuntimeError) {raise RuntimeError, "", bt} + assert_not_match(/(\e\[1)m\1/, e2.full_message(highlight: true)) + + message = e.full_message + if Exception.to_tty? + assert_match(/\e/, message) + message = message.gsub(/\e\[[\d;]*m/, '') + else + assert_not_match(/\e/, message) + end + assert_operator(message, :start_with?, bottom) + assert_operator(message, :end_with?, top) + end + + def test_exception_in_message + code = "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class Bug14566 < StandardError + def message; raise self.class; end + end + raise Bug14566 + end; + assert_in_out_err([], code, [], /Bug14566/, success: false, timeout: 2) + end + + def test_non_exception_cause + assert_raise_with_message(TypeError, /exception/) do + raise "foo", cause: 1 + end; + end + + def test_circular_cause_handle + assert_raise_with_message(ArgumentError, /circular cause/) do + begin + raise "error 1" + rescue => e1 + raise "error 2" rescue raise e1, cause: $! + end + end; + end + + def test_marshal_circular_cause + dump = "\x04\bo:\x11RuntimeError\b:\tmesgI\"\berr\x06:\x06ET:\abt[\x00:\ncause@\x05" + assert_raise_with_message(ArgumentError, /circular cause/, ->{dump.inspect}) do + e = Marshal.load(dump) + assert_same(e, e.cause) + end + end + + def test_super_in_method_missing + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class Object + def method_missing(name, *args, &block) + super + end + end + + bug14670 = '[ruby-dev:50522] [Bug #14670]' + assert_raise_with_message(NoMethodError, /'foo'/, bug14670) do + Object.new.foo + end + end; + end + + def test_detailed_message + e = RuntimeError.new("message") + assert_equal("message (RuntimeError)", e.detailed_message) + assert_equal("\e[1mmessage (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new("foo\nbar\nbaz") + assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message) + assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new("") + assert_equal("unhandled exception", e.detailed_message) + assert_equal("\e[1;4munhandled exception\e[m", e.detailed_message(highlight: true)) + + e = RuntimeError.new + assert_equal("RuntimeError (RuntimeError)", e.detailed_message) + assert_equal("\e[1mRuntimeError (\e[1;4mRuntimeError\e[m\e[1m)\e[m", e.detailed_message(highlight: true)) + end + + def test_detailed_message_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + e = RuntimeError.new("foo\nbar\nbaz") + assert_equal("foo (RuntimeError)\nbar\nbaz", e.detailed_message) + assert_equal("\e[1mfoo (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n\e[1mbar\e[m\n\e[1mbaz\e[m", e.detailed_message(highlight: true)) + end + end + + def test_full_message_with_custom_detailed_message + e = RuntimeError.new("message") + opt_ = nil + e.define_singleton_method(:detailed_message) do |**opt| + opt_ = opt + "BOO!" + end + assert_match("BOO!", e.full_message.lines.first) + assert_equal({ highlight: Exception.to_tty? }, opt_) + end + + def test_full_message_with_encoding + message = "\u{dc}bersicht" + begin + begin + raise message + rescue => e + raise "\n#{e.message}" + end + rescue => e + end + assert_include(e.full_message, message) + end + + def test_syntax_error_detailed_message + Dir.mktmpdir do |dir| + File.write(File.join(dir, "detail.rb"), "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class SyntaxError + def detailed_message(**) + Thread.new {}.join + "<#{super}>\n""<#{File.basename(__FILE__)}>" + rescue ThreadError => e + e.message + end + end + end; + pattern = /^<detail\.rb>/ + assert_in_out_err(%W[-r#{dir}/detail -], "1+", [], pattern) + + File.write(File.join(dir, "main.rb"), "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 1 + + end; + assert_in_out_err(%W[-r#{dir}/detail #{dir}/main.rb]) do |stdout, stderr,| + assert_empty(stdout) + assert_not_empty(stderr.grep(pattern)) + error, = stderr.grep(/unexpected end-of-input/) + assert_not_nil(error) + assert_match(/<.*unexpected end-of-input.*>|\^ unexpected end-of-input,/, error) + end + end + end + + def test_syntax_error_path + e = assert_raise(SyntaxError) { + eval("1+", nil, "test_syntax_error_path.rb") + } + assert_equal("test_syntax_error_path.rb", e.path) + + Dir.mktmpdir do |dir| + lib = File.join(dir, "syntax_error-path.rb") + File.write(lib, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class SyntaxError + def detailed_message(**) + STDERR.puts "\n""path=#{path}\n" + super + end + end + end; + main = File.join(dir, "syntax_error.rb") + File.write(main, "1+\n") + assert_in_out_err(%W[-r#{lib} #{main}], "", [], [:*, "\n""path=#{main}\n", :*]) + end + end + + class Ex; end + + def test_exception_message_for_unexpected_implicit_conversion_type + a = Ex.new + def self.x(a) = nil + + assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Hash") do + x(**a) + end + assert_raise_with_message(TypeError, "no implicit conversion of TestException::Ex into Proc") do + x(&a) + end + + def a.to_a = 1 + def a.to_hash = 1 + def a.to_proc = 1 + assert_raise_with_message(TypeError, "can't convert TestException::Ex to Array (TestException::Ex#to_a gives Integer)") do + x(*a) + end + assert_raise_with_message(TypeError, "can't convert TestException::Ex to Hash (TestException::Ex#to_hash gives Integer)") do + x(**a) + end + assert_raise_with_message(TypeError, "can't convert TestException::Ex to Proc (TestException::Ex#to_proc gives Integer)") do + x(&a) + end + end +end diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb new file mode 100644 index 0000000000..b7d2b71c19 --- /dev/null +++ b/test/ruby/test_fiber.rb @@ -0,0 +1,509 @@ +# frozen_string_literal: false +require 'test/unit' +require 'fiber' +EnvUtil.suppress_warning {require 'continuation'} +require 'tmpdir' + +class TestFiber < Test::Unit::TestCase + def test_normal + assert_equal(:ok2, + Fiber.new{|e| + assert_equal(:ok1, e) + Fiber.yield :ok2 + }.resume(:ok1) + ) + assert_equal([:a, :b], Fiber.new{|a, b| [a, b]}.resume(:a, :b)) + end + + def test_argument + assert_equal(4, Fiber.new {|i=4| i}.resume) + end + + def test_term + assert_equal(:ok, Fiber.new{:ok}.resume) + assert_equal([:a, :b, :c, :d, :e], + Fiber.new{ + Fiber.new{ + Fiber.new{ + Fiber.new{ + [:a] + }.resume + [:b] + }.resume + [:c] + }.resume + [:d] + }.resume + [:e]) + end + + def test_many_fibers + max = 1000 + 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 + 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 + } + SRC + end + + def test_error + assert_raise(ArgumentError){ + Fiber.new # Fiber without block + } + 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 + } + if respond_to?(:callcc) + assert_raise(RuntimeError){ + Fiber.new{ + @c = callcc{|c| @c = c} + }.resume + @c.call # cross fiber callcc + } + end + assert_raise(RuntimeError){ + Fiber.new{ + raise + }.resume + } + assert_raise(FiberError){ + Fiber.yield + } + assert_raise(FiberError){ + fib = Fiber.new{ + fib.resume + } + fib.resume + } + assert_raise(FiberError){ + fib = Fiber.new{ + Fiber.new{ + fib.resume + }.resume + } + fib.resume + } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.raise "raise in unborn fiber" + } + assert_raise(FiberError){ + fib = Fiber.new{} + fib.resume + fib.raise "raise in dead fiber" + } + end + + def test_return + assert_raise(LocalJumpError){ + Fiber.new do + return + end.resume + } + end + + def test_throw + assert_raise(UncaughtThrowError){ + Fiber.new do + throw :a + end.resume + } + end + + def test_raise + assert_raise(ZeroDivisionError){ + Fiber.new do + 1/0 + end.resume + } + assert_raise(RuntimeError){ + fib = Fiber.new{ Fiber.yield } + fib.resume + fib.raise "raise and propagate" + } + assert_nothing_raised{ + fib = Fiber.new do + begin + Fiber.yield + rescue + end + end + fib.resume + fib.raise "rescue in fiber" + } + fib = Fiber.new do + begin + Fiber.yield + rescue + Fiber.yield :ok + end + end + fib.resume + assert_equal(:ok, fib.raise) + end + + def test_raise_transferring_fiber + root = Fiber.current + fib = Fiber.new { root.transfer } + fib.transfer + assert_raise(RuntimeError){ + fib.raise "can raise with transfer: true" + } + assert_not_predicate(fib, :alive?) + end + + def test_transfer + ary = [] + f2 = nil + f1 = Fiber.new{ + ary << f2.transfer(:foo) + :ok + } + f2 = Fiber.new{ + ary << f1.transfer(:baz) + :ng + } + assert_equal(:ok, f1.transfer) + assert_equal([:baz], ary) + end + + def test_terminate_transferred_fiber + log = [] + fa1 = fa2 = fb1 = r1 = nil + + fa1 = Fiber.new{ + fa2 = Fiber.new{ + log << :fa2_terminate + } + fa2.resume + log << :fa1_terminate + } + fb1 = Fiber.new{ + fa1.transfer + log << :fb1_terminate + } + + r1 = Fiber.new{ + fb1.transfer + log << :r1_terminate + } + + r1.resume + log << :root_terminate + + assert_equal [:fa2_terminate, :fa1_terminate, :r1_terminate, :root_terminate], log + end + + def test_tls + # + def tvar(var, val) + old = Thread.current[var] + begin + Thread.current[var] = val + yield + ensure + Thread.current[var] = old + end + end + + fb = Fiber.new { + assert_equal(nil, Thread.current[:v]); tvar(:v, :x) { + assert_equal(:x, Thread.current[:v]); Fiber.yield + assert_equal(:x, Thread.current[:v]); } + assert_equal(nil, Thread.current[:v]); Fiber.yield + raise # unreachable + } + + assert_equal(nil, Thread.current[:v]); tvar(:v,1) { + assert_equal(1, Thread.current[:v]); tvar(:v,3) { + assert_equal(3, Thread.current[:v]); fb.resume + assert_equal(3, Thread.current[:v]); } + assert_equal(1, Thread.current[:v]); } + assert_equal(nil, Thread.current[:v]); fb.resume + assert_equal(nil, Thread.current[:v]); + end + + def test_fiber_variables + assert_equal "bar", Fiber.new {Fiber[:foo] = "bar"; Fiber[:foo]}.resume + + key = :"#{self.class.name}#.#{self.object_id}" + Fiber[key] = 42 + assert_equal 42, Fiber[key] + + key = Object.new + def key.to_str; "foo"; end + assert_equal "Bar", Fiber.new {Fiber[key] = "Bar"; Fiber[key]}.resume + end + + def test_alive + fib = Fiber.new{Fiber.yield} + assert_equal(true, fib.alive?) + fib.resume + assert_equal(true, fib.alive?) + fib.resume + assert_equal(false, fib.alive?) + end + + def test_resume_self + f = Fiber.new {f.resume} + assert_raise(FiberError, '[ruby-core:23651]') {f.transfer} + end + + def test_fiber_transfer_segv + assert_normal_exit %q{ + require 'fiber' + f2 = nil + f1 = Fiber.new{ f2.resume } + 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 + + 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_transfer_to_resuming_fiber + root_fiber = Fiber.current + + assert_raise(FiberError){ + fiber = Fiber.new{ root_fiber.transfer } + fiber.resume + } + + fa1 = Fiber.new{ + _fa2 = Fiber.new{ root_fiber.transfer } + } + fb1 = Fiber.new{ + _fb2 = Fiber.new{ root_fiber.transfer } + } + fa1.transfer + fb1.transfer + + assert_raise(FiberError){ + fa1.transfer + } + assert_raise(FiberError){ + fb1.transfer + } + end + + def test_prohibit_transfer_to_yielding_fiber + f1 = f2 = f3 = nil + + f1 = Fiber.new{ + f2 = Fiber.new{ + f3 = Fiber.new{ + p f3: Fiber.yield + } + f3.resume + } + f2.resume + } + f1.resume + + assert_raise(FiberError){ f3.transfer 10 } + end + + def test_prohibit_resume_to_transferring_fiber + root_fiber = Fiber.current + + assert_raise(FiberError){ + Fiber.new{ + root_fiber.resume + }.transfer + } + + f1 = f2 = nil + f1 = Fiber.new do + f2.transfer + end + f2 = Fiber.new do + f1.resume # attempt to resume transferring fiber + end + + assert_raise(FiberError){ + f1.transfer + } + end + + + def test_fork_from_fiber + omit '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 = 1000 + count.times do + Fiber.new {}.transfer + Fiber.new { Fiber.yield } + end + exit!(true) + end + }.transfer + _, status = Process.waitpid2(xpid) + exit!(status.success?) + end + end.resume + end + pid, status = Process.waitpid2(pid) + assert_not_predicate(status, :signaled?, bug5700) + assert_predicate(status, :success?, bug5700) + + pid = Fiber.new {fork}.resume + pid, status = Process.waitpid2(pid) + assert_not_predicate(status, :signaled?) + assert_predicate(status, :success?) + end + + def test_exit_in_fiber + 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", "-e", <<-EOS], "", [], /ok/) + Fiber.new{ + Bug.rb_fatal "ok" + }.resume + puts :ng # unreachable. + EOS + 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: 60 + 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 new file mode 100644 index 0000000000..a3d6221c0f --- /dev/null +++ b/test/ruby/test_file.rb @@ -0,0 +1,861 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' +require "-test-/file" +require_relative 'ut_eof' + +class TestFile < Test::Unit::TestCase + + # I don't know Ruby's spec about "unlink-before-close" exactly. + # This test asserts current behaviour. + def test_unlink_before_close + Dir.mktmpdir('rubytest-file') {|tmpdir| + filename = tmpdir + '/' + File.basename(__FILE__) + ".#{$$}" + w = File.open(filename, "w") + w << "foo" + w.close + r = File.open(filename, "r") + begin + if /(mswin|bccwin|mingw|emx)/ =~ RUBY_PLATFORM + assert_raise(Errno::EACCES) {File.unlink(filename)} + else + assert_nothing_raised {File.unlink(filename)} + end + ensure + r.close + File.unlink(filename) if File.exist?(filename) + end + } + end + + include TestEOF + def open_file(content) + 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 + 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 + 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 + 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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + [{}, {:textmode=>true}, {:binmode=>true}].each do |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 + + def test_s_chown + assert_nothing_raised { File.chown(-1, -1) } + assert_nothing_raised { File.chown nil, nil } + end + + def test_chown + Tempfile.create("test-chown") {|f| + assert_nothing_raised {f.chown(-1, -1)} + assert_nothing_raised("[ruby-dev:27140]") {f.chown(nil, nil)} + } + end + + def test_uninitialized + assert_raise(TypeError) { File::Stat.allocate.readable? } + assert_nothing_raised { File::Stat.allocate.inspect } + end + + def test_realpath + Dir.mktmpdir('rubytest-realpath') {|tmpdir| + realdir = File.realpath(tmpdir) + tst = realdir + (File::SEPARATOR*3 + ".") + assert_equal(realdir, File.realpath(tst)) + assert_equal(realdir, File.realpath(".", tst)) + assert_equal(realdir, Dir.chdir(realdir) {File.realpath(".")}) + realpath = File.join(realdir, "test") + File.write(realpath, "") + assert_equal(realpath, Dir.chdir(realdir) {File.realpath("test")}) + if File::ALT_SEPARATOR + bug2961 = '[ruby-core:28653]' + assert_equal(realdir, File.realpath(realdir.tr(File::SEPARATOR, File::ALT_SEPARATOR)), bug2961) + end + } + 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 + omit "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_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 + (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)) + assert_equal(realdir, Dir.chdir(realdir) {File.realdirpath(".")}) + assert_equal(File.join(realdir, "foo"), Dir.chdir(realdir) {File.realdirpath("foo")}) + } + begin + result = File.realdirpath("bar", "//:/foo") + 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') + omit "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 measure_time + log = [] + 30.times do + t1 = Process.clock_gettime(Process::CLOCK_REALTIME) + yield + t2 = Process.clock_gettime(Process::CLOCK_REALTIME) + log << (t2 - t1) + return (t1 + t2) / 2 if t2 - t1 < 1 + sleep 1 + end + omit "failed to setup; the machine is stupidly slow #{log.inspect}" + end + + def test_stat + btime = Process.clock_gettime(Process::CLOCK_REALTIME) + Tempfile.create("stat") {|file| + btime = (btime + Process.clock_gettime(Process::CLOCK_REALTIME)) / 2 + file.close + path = file.path + + measure_time do + File.write(path, "foo") + end + + sleep 2 + + mtime = measure_time do + File.write(path, "bar") + end + + sleep 2 + + ctime = measure_time do + File.chmod(0644, path) + end + + sleep 2 + + atime = measure_time do + File.read(path) + end + + delta = 1 + stat = File.stat(path) + assert_in_delta btime, stat.birthtime.to_f, delta + assert_in_delta mtime, stat.mtime.to_f, delta + if stat.birthtime != stat.ctime + assert_in_delta ctime, stat.ctime.to_f, delta + end + if /mswin|mingw/ !~ RUBY_PLATFORM && !Bug::File::Fs.noatime?(path) + # Windows delays updating atime + assert_in_delta atime, 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 + + if /mswin|mingw/ =~ 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_initialize + Dir.mktmpdir(__method__.to_s) do |tmpdir| + path = File.join(tmpdir, "foo") + + assert_raise(Errno::ENOENT) {File.new(path)} + f = File.new(path, "w") + f.write("FOO\n") + f.close + f = File.new(path) + data = f.read + f.close + assert_equal("FOO\n", data) + + f = File.new(path, File::WRONLY) + f.write("BAR\n") + f.close + f = File.new(path, File::RDONLY) + data = f.read + f.close + assert_equal("BAR\n", data) + + data = File.open(path) {|file| + File.new(file.fileno, mode: File::RDONLY, autoclose: false).read + } + assert_equal("BAR\n", data) + + data = File.open(path) {|file| + File.new(file.fileno, File::RDONLY, autoclose: false).read + } + assert_equal("BAR\n", data) + end + end + + def test_file_open_newline_option + Dir.mktmpdir(__method__.to_s) do |tmpdir| + path = File.join(tmpdir, "foo") + test = lambda do |newline| + File.open(path, "wt", newline: newline) do |f| + f.write "a\n" + f.puts "b" + end + File.binread(path) + end + assert_equal("a\nb\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\r\nb\r\n", test.(:crlf)) + assert_equal("a\rb\r", test.(:cr)) + + test = lambda do |newline| + File.open(path, "rt", newline: newline) do |f| + f.read + end + end + + File.binwrite(path, "a\nb\n") + assert_equal("a\nb\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\nb\n", test.(:crlf)) + assert_equal("a\nb\n", test.(:cr)) + + File.binwrite(path, "a\r\nb\r\n") + assert_equal("a\r\nb\r\n", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + # Work on both Windows and non-Windows + assert_include(["a\r\nb\r\n", "a\nb\n"], test.(:crlf)) + assert_equal("a\r\nb\r\n", test.(:cr)) + + File.binwrite(path, "a\rb\r") + assert_equal("a\rb\r", test.(:lf)) + assert_equal("a\nb\n", test.(:universal)) + assert_equal("a\rb\r", test.(:crlf)) + assert_equal("a\rb\r", test.(:cr)) + end + end + + def test_open_nul + Dir.mktmpdir(__method__.to_s) do |tmpdir| + path = File.join(tmpdir, "foo") + 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 + omit 'O_TMPFILE not supported (EINVAL)' + rescue Errno::EISDIR + omit 'O_TMPFILE not supported (EISDIR)' + rescue Errno::EOPNOTSUPP + omit 'O_TMPFILE not supported (EOPNOTSUPP)' + end + + io.write "foo" + io.flush + assert_equal 3, io.size + assert_nil io.path + ensure + io&.close + end + end if File::Constants.const_defined?(:TMPFILE) + + def test_absolute_path? + assert_file.absolute_path?(File.absolute_path(__FILE__)) + assert_file.absolute_path?("//foo/bar\\baz") + assert_file.not_absolute_path?(File.basename(__FILE__)) + assert_file.not_absolute_path?("C:foo\\bar") + assert_file.not_absolute_path?("~") + assert_file.not_absolute_path?("~user") + + if /cygwin|mswin|mingw/ =~ RUBY_PLATFORM + assert_file.absolute_path?("C:\\foo\\bar") + assert_file.absolute_path?("C:/foo/bar") + else + assert_file.not_absolute_path?("C:\\foo\\bar") + assert_file.not_absolute_path?("C:/foo/bar") + end + if /mswin|mingw/ =~ RUBY_PLATFORM + assert_file.not_absolute_path?("/foo/bar\\baz") + else + assert_file.absolute_path?("/foo/bar\\baz") + end + end + + class NewlineConvTests < Test::Unit::TestCase + TEST_STRING_WITH_CRLF = "line1\r\nline2\r\n".freeze + TEST_STRING_WITH_LF = "line1\nline2\n".freeze + + def setup + @tmpdir = Dir.mktmpdir(self.class.name) + @read_path_with_crlf = File.join(@tmpdir, "read_path_with_crlf") + File.binwrite(@read_path_with_crlf, TEST_STRING_WITH_CRLF) + @read_path_with_lf = File.join(@tmpdir, "read_path_with_lf") + File.binwrite(@read_path_with_lf, TEST_STRING_WITH_LF) + @write_path = File.join(@tmpdir, "write_path") + File.binwrite(@write_path, '') + end + + def teardown + FileUtils.rm_rf @tmpdir + end + + def windows? + /cygwin|mswin|mingw/ =~ RUBY_PLATFORM + end + + def open_file_with(method, filename, mode) + read_or_write = mode.include?('w') ? :write : :read + binary_or_text = mode.include?('b') ? :binary : :text + + f = case method + when :ruby_file_open + File.open(filename, mode) + when :c_rb_file_open + Bug::File::NewlineConv.rb_file_open(filename, read_or_write, binary_or_text) + when :c_rb_io_fdopen + Bug::File::NewlineConv.rb_io_fdopen(filename, read_or_write, binary_or_text) + else + raise "Don't know how to open with #{method}" + end + + begin + yield f + ensure + f.close + end + end + + def assert_file_contents_has_lf(f) + assert_equal TEST_STRING_WITH_LF, f.read + end + + def assert_file_contents_has_crlf(f) + assert_equal TEST_STRING_WITH_CRLF, f.read + end + + def assert_file_contents_has_lf_on_windows(f) + if windows? + assert_file_contents_has_lf(f) + else + assert_file_contents_has_crlf(f) + end + end + + def assert_file_contents_has_crlf_on_windows(f) + if windows? + assert_file_contents_has_crlf(f) + else + assert_file_contents_has_lf(f) + end + end + + def test_ruby_file_open_text_mode_read_crlf + open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) } + end + + def test_ruby_file_open_bin_mode_read_crlf + open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_ruby_file_open_text_mode_read_lf + open_file_with(:ruby_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) } + end + + def test_ruby_file_open_bin_mode_read_lf + open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_ruby_file_open_text_mode_read_crlf_with_utf8_encoding + open_file_with(:ruby_file_open, @read_path_with_crlf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf_on_windows(f) + end + end + + def test_ruby_file_open_bin_mode_read_crlf_with_utf8_encoding + open_file_with(:ruby_file_open, @read_path_with_crlf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_crlf(f) + end + end + + def test_ruby_file_open_text_mode_read_lf_with_utf8_encoding + open_file_with(:ruby_file_open, @read_path_with_lf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + + def test_ruby_file_open_bin_mode_read_lf_with_utf8_encoding + open_file_with(:ruby_file_open, @read_path_with_lf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + + def test_ruby_file_open_text_mode_write_lf + open_file_with(:ruby_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) } + end + + def test_ruby_file_open_bin_mode_write_lf + open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_ruby_file_open_bin_mode_write_crlf + open_file_with(:ruby_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_c_rb_file_open_text_mode_read_crlf + open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) } + end + + def test_c_rb_file_open_bin_mode_read_crlf + open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_c_rb_file_open_text_mode_read_lf + open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_file_open_bin_mode_read_lf + open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_file_open_text_mode_write_lf + open_file_with(:c_rb_file_open, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) } + end + + def test_c_rb_file_open_bin_mode_write_lf + open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_file_open_bin_mode_write_crlf + open_file_with(:c_rb_file_open, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_c_rb_file_open_text_mode_read_crlf_with_utf8_encoding + open_file_with(:c_rb_file_open, @read_path_with_crlf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf_on_windows(f) + end + end + + def test_c_rb_file_open_bin_mode_read_crlf_with_utf8_encoding + open_file_with(:c_rb_file_open, @read_path_with_crlf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_crlf(f) + end + end + + def test_c_rb_file_open_text_mode_read_lf_with_utf8_encoding + open_file_with(:c_rb_file_open, @read_path_with_lf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + + def test_c_rb_file_open_bin_mode_read_lf_with_utf8_encoding + open_file_with(:c_rb_file_open, @read_path_with_lf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + + def test_c_rb_io_fdopen_text_mode_read_crlf + open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') { |f| assert_file_contents_has_lf_on_windows(f) } + end + + def test_c_rb_io_fdopen_bin_mode_read_crlf + open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_c_rb_io_fdopen_text_mode_read_lf + open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_io_fdopen_bin_mode_read_lf + open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_io_fdopen_text_mode_write_lf + open_file_with(:c_rb_io_fdopen, @write_path, 'w') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf_on_windows(f) } + end + + def test_c_rb_io_fdopen_bin_mode_write_lf + open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_LF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_lf(f) } + end + + def test_c_rb_io_fdopen_bin_mode_write_crlf + open_file_with(:c_rb_io_fdopen, @write_path, 'wb') { |f| f.write TEST_STRING_WITH_CRLF } + File.open(@write_path, 'rb') { |f| assert_file_contents_has_crlf(f) } + end + + def test_c_rb_io_fdopen_text_mode_read_crlf_with_utf8_encoding + open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf_on_windows(f) + end + end + + def test_c_rb_io_fdopen_bin_mode_read_crlf_with_utf8_encoding + open_file_with(:c_rb_io_fdopen, @read_path_with_crlf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_crlf(f) + end + end + + def test_c_rb_io_fdopen_text_mode_read_lf_with_utf8_encoding + open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'r') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + + def test_c_rb_io_fdopen_bin_mode_read_lf_with_utf8_encoding + open_file_with(:c_rb_io_fdopen, @read_path_with_lf, 'rb') do |f| + f.set_encoding Encoding::UTF_8, '-' + assert_file_contents_has_lf(f) + end + end + end +end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb new file mode 100644 index 0000000000..394dc47603 --- /dev/null +++ b/test/ruby/test_file_exhaustive.rb @@ -0,0 +1,1840 @@ +# frozen_string_literal: false +require "test/unit" +require "fileutils" +require "tmpdir" +require "socket" +require '-test-/file' + +class TestFileExhaustive < Test::Unit::TestCase + ROOT_REGEXP = %r'\A(?:[a-z]:(?=(/))|//[^/]+/[^/]+)'i + DRIVE = Dir.pwd[ROOT_REGEXP] + 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} + m = Class.new {define_method(:to_path) {d}} + assert_raise(Encoding::CompatibilityError) {yield m.new} + end + + def setup + @dir = Dir.mktmpdir("ruby-test") + File.chown(-1, Process.gid, @dir) + 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") + @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) + @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 != File.stat("/").uid + @notownedfile = '/' + else + @notownedfile = nil + end + @notownedfile + end + + def grpownedfile + return nil unless POSIX + return @grpownedfile if defined? @grpownedfile + if group = (Process.groups - [Process.egid]).last + grpownedfile = make_tmp_filename("grpownedfile") + make_file("grpowned", grpownedfile) + File.chown(nil, group, grpownedfile) + return @grpownedfile = grpownedfile + end + rescue + @grpownedfile = nil + end + + def suidfile + return @suidfile if defined? @suidfile + if POSIX + @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(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(regular_file, @hardlinkfile) + rescue NotImplementedError, Errno::EINVAL, Errno::EACCES # EINVAL for Windows Vista, EACCES for Android Termux + @hardlinkfile = nil + end + @hardlinkfile + end + + 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 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 chardev + File::NULL + end + + 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 + + def root_without_capabilities? + return false unless Process.uid == 0 + return false unless system('command', '-v', 'capsh', out: File::NULL) + !system('capsh', '--has-p=CAP_DAC_OVERRIDE', out: File::NULL, err: File::NULL) + end + + 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 = Struct.new(:to_path).new(file) + assert_equal(file, File.path(o)) + o = Struct.new(:to_str).new(file) + assert_equal(file, File.path(o)) + end + + conv_error = ->(method, msg = "converting with #{method}") { + test = ->(&new) do + o = new.(42) + assert_raise(TypeError, msg) {File.path(o)} + + o = new.("abc".encode(Encoding::UTF_32BE)) + assert_raise(Encoding::CompatibilityError, msg) {File.path(o)} + + ["\0", "a\0", "a\0c"].each do |path| + o = new.(path) + assert_raise(ArgumentError, msg) {File.path(o)} + end + end + + test.call(&:itself) + test.call(&Struct.new(method).method(:new)) + } + + conv_error[:to_path] + conv_error[:to_str] + end + + def assert_integer(n) + assert_kind_of(Integer, n) + end + + def assert_integer_or_nil(n) + 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 + 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) + assert_equal(1, fs2 <=> fs1) + assert_nil(fs1 <=> nil) + assert_integer(fs1.dev) + assert_integer_or_nil(fs1.rdev) + assert_integer_or_nil(fs1.dev_major) + assert_integer_or_nil(fs1.dev_minor) + assert_integer_or_nil(fs1.rdev_major) + 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_integer(fs1.uid) + assert_integer(fs1.gid) + assert_equal(3, fs1.size) + assert_integer_or_nil(fs1.blksize) + assert_integer_or_nil(fs1.blocks) + assert_kind_of(Time, fs1.atime) + assert_kind_of(Time, fs1.mtime) + 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(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.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 + 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.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 + 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 + 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 + 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) + end + + def test_exist_p + 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 Process.euid == 0 + 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 Process.euid == 0 + 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 + 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 Process.euid == 0 + 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 Process.euid == 0 + 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 + 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 + 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 + File.chmod(0100, regular_file) + assert_file.executable_real?(regular_file) + File.chmod(0600, regular_file) + assert_file.not_executable_real?(regular_file) + + File.chmod(0100, utf8_file) + assert_file.executable_real?(utf8_file) + File.chmod(0600, utf8_file) + assert_file.not_executable_real?(utf8_file) + + assert_file.not_executable_real?(nofile) + end if POSIX + + def test_file_p + assert_file.not_file?(@dir) + assert_file.file?(regular_file) + assert_file.file?(utf8_file) + assert_file.not_file?(nofile) + end + + def test_zero_p + assert_nothing_raised { File.zero?(@dir) } + assert_file.not_zero?(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?(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) + if file = grpownedfile + assert_file.grpowned?(file) + end + end if POSIX + + def io_open(file_name) + # avoid File.open since we do not want #to_path + io = IO.for_fd(IO.sysopen(file_name)) + yield io + ensure + io&.close + end + + def test_suid + assert_file.not_setuid?(regular_file) + assert_file.not_setuid?(utf8_file) + 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_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 path_identical_p(file) + [regular_file, utf8_file].each do |file| + assert_file.identical?(file, file) + assert_file.not_identical?(file, zerofile) + assert_file.not_identical?(file, nofile) + assert_file.not_identical?(nofile, file) + end + end + + def test_io_identical_p + [regular_file, utf8_file].each do |file| + open(file) {|f| + assert_file.identical?(f, f) + assert_file.identical?(file, f) + assert_file.identical?(f, file) + } + end + end + + def test_closed_io_identical_p + [regular_file, utf8_file].each do |file| + io = open(file) {|f| f} + assert_raise(IOError) { + File.identical?(file, io) + } + File.unlink(file) + assert_file.not_exist?(file) + end + end + + def test_s_size + assert_integer(File.size(@dir)) + assert_equal(3, File.size(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(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 + [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 + [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 + [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_birthtime + omit if RUBY_PLATFORM =~ /android/ + [regular_file, utf8_file].each do |file| + t1 = File.birthtime(file) + t2 = File.open(file) {|f| f.birthtime} + assert_kind_of(Time, t1) + assert_kind_of(Time, t2) + assert_equal(t1, t2) + rescue Errno::ENOSYS, NotImplementedError + # ignore unsupporting filesystems + rescue Errno::EPERM + # Docker prohibits statx syscall by the default. + omit("statx(2) is prohibited by seccomp") + end + assert_raise(Errno::ENOENT) { File.birthtime(nofile) } + end if File.respond_to?(:birthtime) + + def test_chmod + [regular_file, utf8_file].each do |file| + assert_equal(1, File.chmod(0444, file)) + 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 + [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 if POSIX + + def test_chown ## xxx + end + + def test_lchown ## xxx + end + + def test_symlink + 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)) + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + path = "foo\u{30b3 30d4 30fc}" + File.write(path, "") rescue next + assert_equal(1, File.utime(nil, nil, path)) + end + end + end + + def test_utime_symlinkfile + return unless symlinkfile + t = Time.local(2000) + assert_equal(1, File.utime(t, t, symlinkfile)) + assert_equal(t, File.stat(regular_file).atime) + assert_equal(t, File.stat(regular_file).mtime) + end + + def test_lutime + return unless File.respond_to?(:lutime) + return unless symlinkfile + + r = File.stat(regular_file) + t = Time.local(2000) + File.lutime(t + 1, t + 2, symlinkfile) + rescue NotImplementedError => e + skip(e.message) + else + stat = File.stat(regular_file) + assert_equal(r.atime, stat.atime) + assert_equal(r.mtime, stat.mtime) + + stat = File.lstat(symlinkfile) + assert_equal(t + 1, stat.atime) + assert_equal(t + 2, stat.mtime) + end + + def test_hardlink + 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) + omit 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(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 + [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 + prev = File.umask(0777) + assert_equal(0777, File.umask) + 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 if POSIX + + def test_expand_path + assert_equal(regular_file, File.expand_path(File.basename(regular_file), File.dirname(regular_file))) + assert_equal(utf8_file, File.expand_path(File.basename(utf8_file), File.dirname(utf8_file))) + 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 + 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[:BASE_SLOT_SIZE], bug9934) + path = File.expand_path("/a"*25) + assert_operator(ObjectSpace.memsize_of(path), :<=, + (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], 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)} + + assert_equal(Encoding::UTF_8, File.expand_path("foo", "#{drive}/").encoding) + 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_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'] + omit "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(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_not_predicate(s, :frozen?, '[ruby-core:24199]') + assert_equal("foo", s = File.basename("foo")) + 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", ".*")) + 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_equal(@dir, File.dirname(regular_file)) + assert_equal(@dir, File.dirname(utf8_file)) + assert_equal(".", File.dirname("")) + assert_equal(regular_file, File.dirname(regular_file, 0)) + assert_equal(@dir, File.dirname(regular_file, 1)) + assert_equal(File.dirname(@dir), File.dirname(regular_file, 2)) + assert_raise(ArgumentError) {File.dirname(regular_file, -1)} + root = "#{@dir[ROOT_REGEXP]||?/}#{$1}" + assert_equal(root, File.dirname(regular_file, regular_file.count('/'))) + assert_equal(root, File.dirname(regular_file, regular_file.count('/') + 100)) + 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(regular_file)) + assert_equal(".test", File.extname(utf8_file)) + prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."] + infixes = ["", " "] + infixes2 = infixes + [".ext "] + appendixes = [""] + if NTFS + appendixes << " " << "." << "::$DATA" << "::$DATA.bar" + else + appendixes << [".", "."] + end + prefixes.each do |prefix| + appendixes.each do |appendix, ext = ""| + infixes.each do |infix| + path = "#{prefix}foo#{infix}#{appendix}" + assert_equal(ext, File.extname(path), "File.extname(#{path.inspect})") + end + infixes2.each do |infix| + path = "#{prefix}foo#{infix}.ext#{appendix}" + assert_equal(ext.empty? ? ".ext" : appendix, File.extname(path), "File.extname(#{path.inspect})") + end + end + end + bug3175 = '[ruby-core:29627]' + assert_equal(".rb", File.extname("/tmp//bla.rb"), bug3175) + + assert_incompatible_encoding {|d| File.extname(d)} + end + + def test_split + [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 + [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 + + def test_flock_exclusive + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + timeout = EnvUtil.apply_timeout_scale(1).to_s + File.open(regular_file, "r+") do |f| + f.flock(File::LOCK_EX) + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f + open(ARGV[0], "r") do |f| + Timeout.timeout(timeout) do + assert(!f.flock(File::LOCK_SH|File::LOCK_NB)) + end + end + end; + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f + open(ARGV[0], "r") do |f| + assert_raise(Timeout::Error) do + Timeout.timeout(timeout) do + f.flock(File::LOCK_SH) + end + end + end + end; + f.flock(File::LOCK_UN) + end + rescue NotImplementedError + end + + def test_flock_shared + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + timeout = EnvUtil.apply_timeout_scale(1).to_s + File.open(regular_file, "r+") do |f| + f.flock(File::LOCK_SH) + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f + open(ARGV[0], "r") do |f| + Timeout.timeout(timeout) do + assert(f.flock(File::LOCK_SH)) + end + end + end; + assert_separately(["-rtimeout", "-", regular_file, timeout], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + timeout = ARGV[1].to_f + open(ARGV[0], "r+") do |f| + assert_raise(Timeout::Error) do + Timeout.timeout(timeout) do + f.flock(File::LOCK_EX) + end + end + end + end; + f.flock(File::LOCK_UN) + end + rescue NotImplementedError + end + + def test_test + omit 'timestamp check is unstable on macOS' if RUBY_PLATFORM =~ /darwin/ + fn1 = regular_file + hardlinkfile + sleep(1.1) + fn2 = fn1 + "2" + make_file("foo", fn2) + [ + @dir, + fn1, + zerofile, + notownedfile, + grpownedfile, + suidfile, + sgidfile, + stickyfile, + symlinkfile, + hardlinkfile, + chardev, + blockdev, + fifo, + socket + ].compact.each do |f| + assert_equal(File.atime(f), test(?A, f), f) + assert_equal(File.ctime(f), test(?C, f), f) + assert_equal(File.mtime(f), test(?M, f), f) + assert_equal(File.blockdev?(f), test(?b, f), f) + assert_equal(File.chardev?(f), test(?c, f), f) + assert_equal(File.directory?(f), test(?d, f), f) + assert_equal(File.exist?(f), test(?e, f), f) + assert_equal(File.file?(f), test(?f, f), f) + assert_equal(File.setgid?(f), test(?g, f), f) + assert_equal(File.grpowned?(f), test(?G, f), f) + assert_equal(File.sticky?(f), test(?k, f), f) + assert_equal(File.symlink?(f), test(?l, f), f) + assert_equal(File.owned?(f), test(?o, f), f) + assert_nothing_raised(f) { test(?O, f) } + assert_equal(File.pipe?(f), test(?p, f), f) + assert_equal(File.readable?(f), test(?r, f), f) + assert_equal(File.readable_real?(f), test(?R, f), f) + assert_equal(File.size?(f), test(?s, f), f) + assert_equal(File.socket?(f), test(?S, f), f) + assert_equal(File.setuid?(f), test(?u, f), f) + assert_equal(File.writable?(f), test(?w, f), f) + assert_equal(File.writable_real?(f), test(?W, f), f) + assert_equal(File.executable?(f), test(?x, f), f) + assert_equal(File.executable_real?(f), test(?X, f), f) + assert_equal(File.zero?(f), test(?z, f), f) + + stat = File.stat(f) + unless stat.chardev? + # null device may be accessed by other processes + assert_equal(stat.atime, File.atime(f), f) + assert_equal(stat.ctime, File.ctime(f), f) + assert_equal(stat.mtime, File.mtime(f), f) + end + assert_bool_equal(stat.blockdev?, File.blockdev?(f), f) + assert_bool_equal(stat.chardev?, File.chardev?(f), f) + assert_bool_equal(stat.directory?, File.directory?(f), f) + assert_bool_equal(stat.file?, File.file?(f), f) + assert_bool_equal(stat.setgid?, File.setgid?(f), f) + assert_bool_equal(stat.grpowned?, File.grpowned?(f), f) + assert_bool_equal(stat.sticky?, File.sticky?(f), f) + assert_bool_equal(File.lstat(f).symlink?, File.symlink?(f), f) + assert_bool_equal(stat.owned?, File.owned?(f), f) + assert_bool_equal(stat.pipe?, File.pipe?(f), f) + assert_bool_equal(stat.readable?, File.readable?(f), f) + assert_bool_equal(stat.readable_real?, File.readable_real?(f), f) + assert_equal(stat.size?, File.size?(f), f) + assert_bool_equal(stat.socket?, File.socket?(f), f) + assert_bool_equal(stat.setuid?, File.setuid?(f), f) + # It's possible in Linux to be uid 0, but not to have the CAP_DAC_OVERRIDE + # capability that allows skipping file permissions checks (e.g. some kinds + # of "rootless" container setups). In these cases, stat.writable? will be + # true (because it always returns true if Process.uid == 0), but + # File.writeable? will be false (because it actually asks the kernel to do + # an access check). + # Skip these two assertions in that case. + unless root_without_capabilities? + assert_bool_equal(stat.writable?, File.writable?(f), f) + assert_bool_equal(stat.writable_real?, File.writable_real?(f), f) + end + assert_bool_equal(stat.executable?, File.executable?(f), f) + assert_bool_equal(stat.executable_real?, File.executable_real?(f), f) + assert_bool_equal(stat.zero?, File.zero?(f), f) + end + assert_equal(false, test(?-, @dir, fn1)) + assert_equal(true, test(?-, fn1, fn1)) + 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(?=, 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(ArgumentError) { test(?a) } + assert_raise(ArgumentError) { test("\0".ord) } + end + + def test_stat_init + 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) + assert_equal(1, fs2 <=> fs1) + assert_nil(fs1 <=> nil) + assert_integer(fs1.dev) + assert_integer_or_nil(fs1.rdev) + assert_integer_or_nil(fs1.dev_major) + assert_integer_or_nil(fs1.dev_minor) + assert_integer_or_nil(fs1.rdev_major) + assert_integer_or_nil(fs1.rdev_minor) + assert_integer(fs1.ino) + assert_integer(fs1.mode) + assert_equal(hardlinkfile ? 2 : 1, fs1.nlink) + assert_integer(fs1.uid) + assert_integer(fs1.gid) + assert_equal(3, fs1.size) + assert_integer_or_nil(fs1.blksize) + assert_integer_or_nil(fs1.blocks) + assert_kind_of(Time, fs1.atime) + assert_kind_of(Time, fs1.mtime) + 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(fn1).dup) + assert_raise(TypeError) do + 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(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 + end + + def test_stat_directory_p + assert_predicate(File::Stat.new(@dir), :directory?) + assert_not_predicate(File::Stat.new(regular_file), :directory?) + end + + 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_not_predicate(File::Stat.new(@dir), :symlink?) + assert_not_predicate(File::Stat.new(regular_file), :symlink?) + # File::Stat uses stat + 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 + 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 + 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 + assert_not_predicate(File::Stat.new(@dir), :chardev?) + assert_not_predicate(File::Stat.new(regular_file), :chardev?) + assert_predicate(File::Stat.new(chardev), :chardev?) + end + + def test_stat_readable_p + return if Process.euid == 0 + 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 Process.euid == 0 + 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 + 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 Process.euid == 0 + 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 Process.euid == 0 + 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 + 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 + 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 + 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_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_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(regular_file).size?) + assert_not_predicate(File::Stat.new(zerofile), :size?) + end + + 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?) + if file = grpownedfile + assert_predicate(File::Stat.new(file), :grpowned?) + end + 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_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(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_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 + + def test_absolute_path + assert_equal(File.join(Dir.pwd, "~foo"), File.absolute_path("~foo")) + dir = File.expand_path("/bar") + assert_equal(File.join(dir, "~foo"), File.absolute_path("~foo", dir)) + end + + def assert_bool_equal(expected, result, *messages) + assert_equal(expected, true & result, *messages) + end +end diff --git a/test/ruby/test_fixnum.rb b/test/ruby/test_fixnum.rb new file mode 100644 index 0000000000..2878258920 --- /dev/null +++ b/test/ruby/test_fixnum.rb @@ -0,0 +1,351 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFixnum < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $VERBOSE = @verbose + end + + def test_pow + [1, 2, 2**64, 2**63*3, 2**64*3].each do |y| + [-1, 0, 1].each do |x| + z1 = x**y + z2 = (-x)**y + if y % 2 == 1 + assert_equal(z2, -z1) + else + assert_equal(z2, z1) + end + end + end + end + + def test_succ + assert_equal(0x40000000, 0x3fffffff.succ, "[ruby-dev:31189]") + assert_equal(0x4000000000000000, 0x3fffffffffffffff.succ, "[ruby-dev:31190]") + end + + def test_pred + assert_equal(-0x40000001, (-0x40000000).pred) + assert_equal(-0x4000000000000001, (-0x4000000000000000).pred) + end + + 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 + assert_equal(0x40000000, 0x3fffffff-(-1)) + assert_equal(0x4000000000000000, 0x3fffffffffffffff-(-1)) + 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 + assert_equal(0x40000000, 0x20000000*2) + assert_equal(0x4000000000000000, 0x2000000000000000*2) + assert_equal(-0x40000001, 33025*(-32513)) + assert_equal(-0x4000000000000001, 1380655685*(-3340214413)) + assert_equal(0x40000000, (-0x40000000)*(-1)) + end + + def test_div + assert_equal(2, 5/2) + assert_equal(0, 1/2) + assert_equal(-1, -1/2) + assert_equal(0, -(1/2)) + assert_equal(-1, (-1)/2) + assert_equal(0, (-1)/(-2)) + assert_equal(-1, 1/(-2)) + assert_equal(1, -(1/(-2))) + assert_equal(0x3fffffff, 0xbffffffd/3) + assert_equal(0x40000000, 0xc0000000/3) + assert_equal(0x4000000000000000, 0xc000000000000000/3) + assert_equal(-0x40000001, 0xc0000003/(-3)) + 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 + assert_equal(2, (-0x40000000) % 3) + assert_equal(0, (-0x40000000) % (-1)) + end + + def test_divmod + (-5).upto(5) {|a| + (-5).upto(5) {|b| + next if b == 0 + q, r = a.divmod(b) + assert_equal(a, b*q+r) + 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 + assert_equal(-0x40000000, ~0x3fffffff) + assert_equal(0x3fffffff, ~-0x40000000) + end + + def test_lshift + assert_equal(0x40000000, 0x20000000 << 1) + assert_equal(-0x40000000, (-0x20000000) << 1) + assert_equal(-0x80000000, (-0x40000000) << 1) + end + + def test_rshift + assert_equal(0x20000000, 0x40000000 >> 1) + assert_equal(-0x20000000, (-0x40000000) >> 1) + assert_equal(-0x40000000, (-0x80000000) >> 1) + end + + def test_abs + assert_equal(0x40000000, (-0x40000000).abs) + assert_equal(0x4000000000000000, (-0x4000000000000000).abs) + end + + def test_to_s + assert_equal("1010", 10.to_s(2)) + assert_equal("a", 10.to_s(36)) + assert_raise(ArgumentError) { 10.to_s(1) } + end + + def test_plus2 + assert_equal(2, 1 + 1) + assert_equal(4294967297, 1 + 2**32) + assert_equal(2.0, 1 + 1.0) + assert_raise(TypeError) { 1 + nil } + end + + def test_minus + assert_equal(0, 1 - 1) + assert_equal(-4294967295, 1 - 2**32) + assert_equal(0.0, 1 - 1.0) + assert_raise(TypeError) { 1 - nil } + end + + def test_mul + assert_equal(6, 2.send(:*, 3)) + a = 2**30-1 + assert_equal(1152921502459363329, a.send(:*, a)) + + assert_equal(6.0, 2 * 3.0) + assert_raise(TypeError) { 2 * nil } + end + + def test_divide + assert_equal(2.0, 4.quo(2)) + assert_equal(2.0, 4 / 2) + assert_equal(2.0, 4.div(2)) + + assert_equal(0.5**32, 1.quo(2**32)) + 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)) + + ### rational changes the behavior of Fixnum#quo + #assert_raise(TypeError) { 2.quo(nil) } + assert_raise(TypeError, NoMethodError) { 2.quo(nil) } + assert_raise(TypeError) { 2 / nil } + assert_raise(TypeError) { 2.div(nil) } + + assert_equal(0, 4.modulo(2)) + assert_equal(1, 1.modulo(2**32)) + assert_equal(1, 1.modulo(2.0)) + assert_raise(TypeError) { 2.modulo(nil) } + + assert_equal([2, 0], 4.divmod(2)) + assert_equal([0, 1], 1.divmod(2**32)) + assert_equal([0, 1], 1.divmod(2.0)) + assert_raise(TypeError) { 2.divmod(nil) } + end + + def test_pow2 + assert_equal(65536, 2**16) + assert_equal(4294967296, 2**32) + assert_equal(0.5**16, 2**-16) + assert_equal(1, (-1)**4294967296) + assert_equal(-1, (-1)**4294967295) + assert_equal(4, 2**((2**32).coerce(2).first)) + assert_equal(2, 4**0.5) + assert_equal(0, 0**0.5) + assert_equal(1, (0**-1.0).infinite?) + ### rational changes the behavior of Fixnum#** + #assert_raise(TypeError) { 1 ** nil } + assert_raise(TypeError, NoMethodError) { 1 ** nil } + end + + def test_cmp + 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_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..9c58f5f497 --- /dev/null +++ b/test/ruby/test_flip.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFlip < Test::Unit::TestCase + def test_flip_flop + assert_equal [4,5], (1..9).select {|n| true if (n==4)..(n==5)} + assert_equal [4,5], (1..9).select {|n| true if (n==4)...(n==5)} + assert_equal [2], (1..9).select {|n| true if (n==2)..(n%2).zero?} + assert_equal [2,3,4], (1..9).select {|n| true if (n==2)...(n%2).zero?} + assert_equal [4,5,7,8], (1..9).select {|n| true if (n==4)...(n==5) or (n==7)...(n==8)} + assert_equal [nil, 2, 3, 4, nil], (1..5).map {|x| x if (x==2..x==4)} + assert_equal [1, nil, nil, nil, 5], (1..5).map {|x| x if !(x==2..x==4)} + end + + def test_hidden_key + bug6899 = '[ruby-core:47253]' + foo = "foor" + 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 + vs.select {|n| if n==2..n==16 then 1 end} + v = eval("vs.select {|n| if n==3..n==6 then 1 end}") + assert_equal([*3..6], v, bug7671) + vs + end + + def test_shared_thread + ff = 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 new file mode 100644 index 0000000000..d0d180593a --- /dev/null +++ b/test/ruby/test_float.rb @@ -0,0 +1,1051 @@ +# 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) + assert_equal(3, 2.6.ceil) + assert_equal(-2, (-2.6).ceil) + assert_equal(2, 2.6.truncate) + assert_equal(-2, (-2.6).truncate) + assert_equal(3, 2.6.round) + assert_equal(-2, (-2.4).truncate) + 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_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 + nan_test(nan, nan) + nan_test(nan, 0) + nan_test(nan, 1) + nan_test(nan, -1) + nan_test(nan, 1000) + nan_test(nan, -1000) + nan_test(nan, 1_000_000_000_000) + nan_test(nan, -1_000_000_000_000) + nan_test(nan, 100.0); + nan_test(nan, -100.0); + nan_test(nan, 0.001); + nan_test(nan, -0.001); + nan_test(nan, 1.0/0); + nan_test(nan, -1.0/0); + end + + def test_precision + u = 3.7517675036461267e+17 + v = sprintf("%.16e", u).to_f + assert_in_delta(u, v, u.abs * Float::EPSILON) + assert_in_delta(u, v, v.abs * Float::EPSILON) + end + + def test_symmetry_bignum # [ruby-bugs-ja:118] + a = 100000000000000000000000 + b = 100000000000000000000000.0 + 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_in_delta(a, 0, Float::EPSILON) + a = Float("0.0") + assert_in_delta(a, 0, Float::EPSILON) + a = Float("+0.0") + assert_in_delta(a, 0, Float::EPSILON) + a = Float("-0.0") + assert_in_delta(a, 0, Float::EPSILON) + a = Float("0.0000000000000000001") + assert_not_equal(0.0, a) + a = Float("+0.0000000000000000001") + assert_not_equal(0.0, a) + a = Float("-0.0000000000000000001") + assert_not_equal(0.0, a) + a = Float(".0") + assert_in_delta(a, 0, Float::EPSILON) + a = Float("+.0") + assert_in_delta(a, 0, Float::EPSILON) + a = Float("-.0") + assert_in_delta(a, 0, Float::EPSILON) + assert_equal(0.0, Float("0.")) + assert_equal(0.0, Float("+0.")) + assert_equal(0.0, Float("-0.")) + assert_raise(ArgumentError){Float(".")} + assert_raise(ArgumentError){Float("+")} + assert_raise(ArgumentError){Float("+.")} + assert_raise(ArgumentError){Float("-")} + assert_raise(ArgumentError){Float("-.")} + assert_raise(ArgumentError){Float("1e")} + assert_raise(ArgumentError){Float("1__1")} + assert_equal(1.0, Float("1.")) + assert_equal(1.0, Float("1.e+00")) + assert_equal(0.0625, Float("0x.1")) + assert_equal(1.0, Float("0x1.")) + assert_equal(1.0, Float("0x1.0")) + assert_equal(1.0, 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(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_equal(1.0, x, ->{"%a" % x}) + end + + def test_divmod + assert_equal([2, 3.5], 11.5.divmod(4)) + 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 + assert_equal(2, 11.5.div(4)) + 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 + assert_equal(3.5, 11.5.modulo(4)) + assert_equal(-0.5, 11.5.modulo(-4)) + assert_equal(0.5, (-11.5).modulo(4)) + assert_equal(-3.5, (-11.5).modulo(-4)) + end + + def test_remainder + 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_equal(-3.5, (-11.5).remainder(-4)) + assert_predicate(Float::NAN.remainder(4), :nan?) + assert_predicate(4.remainder(Float::NAN), :nan?) + + ten = Object.new + def ten.coerce(other) + [other, 10] + end + assert_equal(4, 14.0.remainder(ten)) + end + + def test_to_s + inf = Float::INFINITY + assert_equal("Infinity", inf.to_s) + assert_equal("-Infinity", (-inf).to_s) + assert_equal("NaN", (inf / inf).to_s) + + assert_equal("1.0e+18", 1000_00000_00000_00000.0.to_s) + + bug3273 = '[ruby-core:30145]' + [0.21611564636388508, 0.56].each do |f| + s = f.to_s + assert_equal(f, s.to_f, bug3273) + assert_not_equal(f, s.chop.to_f, bug3273) + end + end + + def test_coerce + assert_equal(Float, 1.0.coerce(1).first.class) + end + + def test_plus + 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 + + def test_minus + 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 + + def test_mul + 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 + + def test_div2 + 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 + + def test_modulo2 + 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_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)) + assert_equal([1.0, 0.0], 2.0.divmod(2.0)) + assert_raise(TypeError) { 2.0.divmod(nil) } + + inf = Float::INFINITY + assert_raise(ZeroDivisionError) {inf.divmod(0)} + + a, b = (2.0**32).divmod(1.0) + assert_equal(2**32, a) + assert_equal(0, b) + end + + def test_pow + assert_equal(1.0, 1.0 ** (2**32)) + assert_equal(1.0, 1.0 ** 1.0) + assert_raise(TypeError) { 1.0 ** nil } + assert_equal(9.0, 3.0 ** 2) + end + + def test_eql + inf = Float::INFINITY + nan = Float::NAN + 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_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 + inf = Float::INFINITY + nan = Float::NAN + assert_equal(0, 1.0 <=> 1.0) + assert_equal(1, 1.0 <=> 0.0) + assert_equal(-1, 1.0 <=> 2.0) + assert_nil(1.0 <=> nil) + assert_nil(1.0 <=> nan) + assert_nil(nan <=> 1.0) + + assert_equal(0, 1.0 <=> 1) + assert_equal(1, 1.0 <=> 0) + assert_equal(-1, 1.0 <=> 2) + + assert_equal(-1, 1.0 <=> 2**32) + + assert_equal(1, inf <=> (Float::MAX.to_i*2)) + assert_equal(-1, -inf <=> (-Float::MAX.to_i*2)) + 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 } + assert_raise(ArgumentError) { 1.0 <= nil } + end + + def test_zero_p + 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 + inf = Float::INFINITY + assert_equal(1, inf.infinite?) + assert_equal(-1, (-inf).infinite?) + assert_nil(1.0.infinite?) + end + + def test_finite_p + inf = Float::INFINITY + assert_not_predicate(inf, :finite?) + assert_not_predicate(-inf, :finite?) + assert_predicate(1.0, :finite?) + end + + def test_floor_ceil_round_truncate + assert_equal(1, 1.5.floor) + assert_equal(2, 1.5.ceil) + assert_equal(2, 1.5.round) + assert_equal(1, 1.5.truncate) + + assert_equal(2, 2.0.floor) + assert_equal(2, 2.0.ceil) + assert_equal(2, 2.0.round) + assert_equal(2, 2.0.truncate) + + assert_equal(-2, (-1.5).floor) + assert_equal(-1, (-1.5).ceil) + assert_equal(-2, (-1.5).round) + assert_equal(-1, (-1.5).truncate) + + assert_equal(-2, (-2.0).floor) + assert_equal(-2, (-2.0).ceil) + assert_equal(-2, (-2.0).round) + assert_equal(-2, (-2.0).truncate) + + inf = Float::INFINITY + assert_raise(FloatDomainError) { inf.floor } + 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_round_half_even_with_precision + assert_equal(767573.18759, 767573.1875850001.round(5, half: :even)) + assert_equal(767573.18758, 767573.187585.round(5, half: :even)) + assert_equal(767573.18758, 767573.1875849998.round(5, half: :even)) + assert_equal(767573.18758, 767573.187575.round(5, half: :even)) + assert_equal(-767573.18759, -767573.1875850001.round(5, half: :even)) + assert_equal(-767573.18758, -767573.187585.round(5, half: :even)) + assert_equal(-767573.18758, -767573.1875849998.round(5, half: :even)) + assert_equal(-767573.18758, -767573.187575.round(5, half: :even)) + 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)) + + assert_equal(-10000000000, -1.0.floor(-10), "[Bug #20654]") + assert_equal(-100000000000000000000, -1.0.floor(-20), "[Bug #20654]") + assert_equal(-100000000000000000000000000000000000000000000000000, -1.0.floor(-50), "[Bug #20654]") + end + + def test_ceil_with_precision + 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)) + + assert_equal(10000000000, 1.0.ceil(-10), "[Bug #20654]") + assert_equal(100000000000000000000, 1.0.ceil(-20), "[Bug #20654]") + assert_equal(100000000000000000000000000000000000000000000000000, 1.0.ceil(-50), "[Bug #20654]") + end + + def test_truncate_with_precision + 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 = [ + 18446744073709551617.0, + 18446744073709551616.0, + 18446744073709551615.8, + 18446744073709551615.5, + 18446744073709551615.2, + 18446744073709551615.0, + 18446744073709551614.0, + + 4611686018427387905.0, + 4611686018427387904.0, + 4611686018427387903.8, + 4611686018427387903.5, + 4611686018427387903.2, + 4611686018427387903.0, + 4611686018427387902.0, + + 4294967297.0, + 4294967296.0, + 4294967295.8, + 4294967295.5, + 4294967295.2, + 4294967295.0, + 4294967294.0, + + 1073741825.0, + 1073741824.0, + 1073741823.8, + 1073741823.5, + 1073741823.2, + 1073741823.0, + 1073741822.0, + + -1073741823.0, + -1073741824.0, + -1073741824.2, + -1073741824.5, + -1073741824.8, + -1073741825.0, + -1073741826.0, + + -4294967295.0, + -4294967296.0, + -4294967296.2, + -4294967296.5, + -4294967296.8, + -4294967297.0, + -4294967298.0, + + -4611686018427387903.0, + -4611686018427387904.0, + -4611686018427387904.2, + -4611686018427387904.5, + -4611686018427387904.8, + -4611686018427387905.0, + -4611686018427387906.0, + + -18446744073709551615.0, + -18446744073709551616.0, + -18446744073709551616.2, + -18446744073709551616.5, + -18446744073709551616.8, + -18446744073709551617.0, + -18446744073709551618.0, + ] + + def test_truncate + VS.each {|f| + i = f.truncate + assert_equal(i, f.to_i) + if f < 0 + assert_operator(i, :<, 0) + else + assert_operator(i, :>, 0) + end + assert_operator(i.abs, :<=, f.abs) + d = f.abs - i.abs + assert_operator(0, :<=, d) + assert_operator(d, :<, 1) + } + end + + def test_ceil + VS.each {|f| + i = f.ceil + if f < 0 + assert_operator(i, :<, 0) + else + assert_operator(i, :>, 0) + end + assert_operator(i, :>=, f) + d = f - i + assert_operator(-1, :<, d) + assert_operator(d, :<=, 0) + } + end + + def test_floor + VS.each {|f| + i = f.floor + if f < 0 + assert_operator(i, :<, 0) + else + assert_operator(i, :>, 0) + end + assert_operator(i, :<=, f) + d = f - i + assert_operator(0, :<=, d) + assert_operator(d, :<, 1) + } + end + + def test_round + VS.each {|f| + msg = "round(#{f})" + i = f.round + if f < 0 + assert_operator(i, :<, 0, msg) + else + assert_operator(i, :>, 0, msg) + end + d = f - i + 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") + } + assert_raise_with_message(Encoding::CompatibilityError, /ASCII incompatible/) { + 1.0.round(half: "up".force_encoding("utf-16be")) + } + end + + def test_Float + assert_in_delta(0.125, Float("0.1_2_5"), 0.00001) + assert_in_delta(0.125, "0.1_2_5__".to_f, 0.00001) + assert_in_delta(0.0, "0_.125".to_f, 0.00001) + assert_in_delta(0.0, "0._125".to_f, 0.00001) + assert_in_delta(0.1, "0.1__2_5".to_f, 0.00001) + assert_in_delta(0.1, "0.1_e10".to_f, 0.00001) + assert_in_delta(0.1, "0.1e_10".to_f, 0.00001) + assert_in_delta(1.0, "0.1e1__0".to_f, 0.00001) + assert_equal(1, suppress_warning {Float(([1] * 10000).join)}.infinite?) + assert_not_predicate(Float(([1] * 10000).join("_")), :infinite?) # is it really OK? + assert_raise(ArgumentError) { Float("1.0\x001") } + 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_equal(15, Float('0xf.')) + assert_raise(ArgumentError) { Float('0xf.p') } + assert_equal(15, Float('0xf.p0')) + assert_equal(15.9375, Float('0xf.f')) + assert_raise(ArgumentError) { Float('0xf.fp') } + assert_equal(0x10a, Float("0x1_0a")) + assert_equal(1.625, Float("0x1.a_0")) + assert_equal(3.25, Float("0x1.ap0_1")) + assert_raise(ArgumentError) { Float("0x1.ap0a") } + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_equal(Float::INFINITY, Float('0xf.fp1000000000000000')) + ensure + $VERBOSE = verbose_bak + end + assert_equal(1, suppress_warning {Float("1e10_00")}.infinite?) + assert_raise(TypeError) { Float(nil) } + assert_raise(TypeError) { Float(:test) } + o = Object.new + def o.to_f; inf = Float::INFINITY; inf/inf; end + assert_predicate(Float(o), :nan?) + + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16be"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-16le"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32be"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("utf-32le"))} + assert_raise(Encoding::CompatibilityError) {Float("0".encode("iso-2022-jp"))} + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Float("\u{1f4a1}")} + end + end + + def test_invalid_str + bug4310 = '[ruby-core:34820]' + assert_raise(ArgumentError, bug4310) {under_gc_stress {Float('a'*10000)}} + end + + def test_Float_with_invalid_exception + assert_raise(ArgumentError) { + Float("0", exception: 1) + } + end + + def test_Float_with_exception_keyword + assert_raise(ArgumentError) { + Float(".", exception: true) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Float(".", exception: false)) + } + assert_raise(RangeError) { + Float(1i, exception: true) + } + assert_nothing_raised(RangeError) { + assert_equal(nil, Float(1i, exception: false)) + } + assert_raise(TypeError) { + Float(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(:test, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Float(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + o = Object.new + def o.to_f; 3.14; end + assert_equal(3.14, Float(o, exception: false)) + } + assert_nothing_raised(RuntimeError) { + o = Object.new + def o.to_f; raise; end + assert_equal(nil, Float(o, exception: false)) + } + end + + def test_num2dbl + assert_raise(ArgumentError, "comparison of String with 0 failed") do + 1.0.step(2.0, "0.5") {} + end + assert_raise(TypeError) do + 1.0.step(2.0, nil) {} + end + end + + def test_sleep_with_Float + assert_nothing_raised("[ruby-core:23282]") do + 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) + + assert_equal(11, ((0.24901079128550474)..(340.2500808898068)).step(34.00010700985213).to_a.size) + assert_equal(11, ((0.24901079128550474)..(340.25008088980684)).step(34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)..(-340.2500808898068)).step(-34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)..(-340.25008088980684)).step(-34.00010700985213).to_a.size) + end + + def test_step2 + 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 + b = a + s*9.999999 + seq = (a...b).step(s) + assert_equal(10, seq.to_a.length, seq.inspect) + end + + assert_equal([1.0, 2.9, 4.8, 6.699999999999999], (1.0...6.8).step(1.9).to_a) + + e = 1+1E-12 + (1.0 ... e).step(1E-16) do |n| + assert_operator(n, :<=, e) + end + + assert_equal(10, ((0.24901079128550474)...(340.2500808898068)).step(34.00010700985213).to_a.size) + assert_equal(11, ((0.24901079128550474)...(340.25008088980684)).step(34.00010700985213).to_a.size) + assert_equal(10, ((-0.24901079128550474)...(-340.2500808898068)).step(-34.00010700985213).to_a.size) + assert_equal(11, ((-0.24901079128550474)...(-340.25008088980684)).step(-34.00010700985213).to_a.size) + end + + def test_singleton_method + # 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 new file mode 100644 index 0000000000..16f1076e48 --- /dev/null +++ b/test/ruby/test_fnmatch.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFnmatch < Test::Unit::TestCase + + def bracket_test(s, t) # `s' should start with neither '!' nor '^' + 0x21.upto(0x7E) do |i| + assert_equal(t.include?(i.chr), File.fnmatch("[#{s}]", i.chr, File::FNM_DOTMATCH)) + assert_equal(t.include?(i.chr), !File.fnmatch("[^#{s}]", i.chr, File::FNM_DOTMATCH)) + assert_equal(t.include?(i.chr), !File.fnmatch("[!#{s}]", i.chr, File::FNM_DOTMATCH)) + end + end + + def test_fnmatch + assert_file.for("[ruby-dev:22819]").fnmatch('\[1\]' , '[1]') + assert_file.for("[ruby-dev:22815]").fnmatch('*?', 'a') + 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.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.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.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.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.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.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.not_fnmatch('foo?boo', 'foo/boo', File::FNM_PATHNAME) + assert_file.not_fnmatch('foo*', 'foo/boo', File::FNM_PATHNAME) + end + + def test_fnm_dotmatch + # wildcard matches leading period if FNM_DOTMATCH is set + assert_file.not_fnmatch('*', '.profile') + assert_file.fnmatch('*', '.profile', File::FNM_DOTMATCH) + 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.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_frozen.rb b/test/ruby/test_frozen.rb new file mode 100644 index 0000000000..6721cb1128 --- /dev/null +++ b/test/ruby/test_frozen.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestFrozen < Test::Unit::TestCase + def test_setting_ivar_on_frozen_obj + obj = Object.new + obj.freeze + assert_raise(FrozenError) { obj.instance_variable_set(:@a, 1) } + end + + def test_setting_ivar_on_frozen_obj_with_ivars + obj = Object.new + obj.instance_variable_set(:@a, 1) + obj.freeze + assert_raise(FrozenError) { obj.instance_variable_set(:@b, 1) } + end + + def test_setting_ivar_on_frozen_string + str = "str" + str.freeze + assert_raise(FrozenError) { str.instance_variable_set(:@a, 1) } + end + + def test_setting_ivar_on_frozen_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + assert_raise(FrozenError) { str.instance_variable_set(:@b, 1) } + end + + def test_setting_ivar_on_frozen_string_with_singleton_class + str = "str" + str.singleton_class + str.freeze + assert_raise_with_message(FrozenError, "can't modify frozen String: \"str\"") { str.instance_variable_set(:@a, 1) } + end + + class A + freeze + end + + def test_setting_ivar_on_frozen_class + assert_raise_with_message(FrozenError, "can't modify frozen Class: TestFrozen::A") { A.instance_variable_set(:@a, 1) } + assert_raise_with_message(FrozenError, "can't modify frozen Class: #<Class:TestFrozen::A>") { A.singleton_class.instance_variable_set(:@a, 1) } + end +end diff --git a/test/ruby/test_frozen_error.rb b/test/ruby/test_frozen_error.rb new file mode 100644 index 0000000000..ace1e4c775 --- /dev/null +++ b/test/ruby/test_frozen_error.rb @@ -0,0 +1,57 @@ +require 'test/unit' + +class TestFrozenError < Test::Unit::TestCase + def test_new_default + exc = FrozenError.new + assert_equal("FrozenError", exc.message) + assert_raise_with_message(ArgumentError, "no receiver is available") { + exc.receiver + } + end + + def test_new_message + exc = FrozenError.new("bar") + assert_equal("bar", exc.message) + assert_raise_with_message(ArgumentError, "no receiver is available") { + exc.receiver + } + end + + def test_new_receiver + obj = Object.new + exc = FrozenError.new("bar", receiver: obj) + assert_equal("bar", exc.message) + assert_same(obj, exc.receiver) + end + + def test_message + obj = Object.new.freeze + e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { + obj.instance_variable_set(:@test, true) + } + assert_include(e.message, obj.inspect) + + klass = Class.new do + def init + @x = true + end + def inspect + init + super + end + end + obj = klass.new.freeze + e = assert_raise_with_message(FrozenError, /can't modify frozen #{obj.class}/) { + obj.init + } + assert_include(e.message, klass.inspect) + end + + def test_receiver + obj = Object.new.freeze + e = assert_raise(FrozenError) {def obj.foo; end} + assert_same(obj, e.receiver) + e = assert_raise(FrozenError) {obj.singleton_class.const_set(:A, 2)} + assert_same(obj.singleton_class, e.receiver) + end +end diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb new file mode 100644 index 0000000000..09199c34b1 --- /dev/null +++ b/test/ruby/test_gc.rb @@ -0,0 +1,922 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestGc < Test::Unit::TestCase + class S + def initialize(a) + @a = a + end + end + + def test_gc + prev_stress = GC.stress + GC.stress = false + + assert_nothing_raised do + tmp = nil + 1.upto(10000) { + tmp = [0,1,2,3,4,5,6,7,8,9] + } + tmp + end + l=nil + 100000.times { + l = S.new(l) + } + GC.start + assert true # reach here or dumps core + l = [] + 100000.times { + l.push([l]) + } + GC.start + assert true # reach here or dumps core + + GC.stress = prev_stress + end + + def use_rgengc? + GC::OPTS.include? 'USE_RGENGC'.freeze + end + + def test_enable_disable + EnvUtil.without_gc do + GC.enable + assert_equal(false, GC.enable) + assert_equal(false, GC.disable) + assert_equal(true, GC.disable) + assert_equal(true, GC.disable) + assert_nil(GC.start) + assert_equal(true, GC.enable) + assert_equal(false, GC.enable) + end + end + + def test_gc_config_full_mark_by_default + config = GC.config + assert_not_empty(config) + assert_true(config[:rgengc_allow_full_mark]) + end + + def test_gc_config_invalid_args + assert_raise(ArgumentError) { GC.config(0) } + end + + def test_gc_config_setting_returns_updated_config_hash + old_value = GC.config[:rgengc_allow_full_mark] + assert_true(old_value) + + new_value = GC.config(rgengc_allow_full_mark: false)[:rgengc_allow_full_mark] + assert_false(new_value) + new_value = GC.config(rgengc_allow_full_mark: nil)[:rgengc_allow_full_mark] + assert_false(new_value) + ensure + GC.config(rgengc_allow_full_mark: old_value) + GC.start + end + + def test_gc_config_setting_returns_config_hash + hash = GC.config(no_such_key: true) + assert_equal(GC.config, hash) + end + + def test_gc_config_disable_major + GC.enable + GC.start + + GC.config(rgengc_allow_full_mark: false) + major_count = GC.stat[:major_gc_count] + minor_count = GC.stat[:minor_gc_count] + + arr = [] + (GC.stat_heap[0][:heap_eden_slots] * 2).times do + arr << Object.new + Object.new + end + + assert_equal(major_count, GC.stat[:major_gc_count]) + assert_operator(minor_count, :<=, GC.stat[:minor_gc_count]) + assert_nil(GC.start) + ensure + GC.config(rgengc_allow_full_mark: true) + GC.start + end + + def test_gc_config_disable_major_gc_start_always_works + GC.config(full_mark: false) + + major_count = GC.stat[:major_gc_count] + GC.start + + assert_operator(major_count, :<, GC.stat[:major_gc_count]) + ensure + GC.config(full_mark: true) + GC.start + end + + def test_gc_config_implementation + omit unless /darwin|linux/.match(RUBY_PLATFORM) + + gc_name = (ENV['RUBY_GC_LIBRARY'] || "default") + assert_equal gc_name, GC.config[:implementation] + end + + def test_gc_config_implementation_is_readonly + omit unless /darwin|linux/.match(RUBY_PLATFORM) + + assert_raise(ArgumentError) { GC.config(implementation: "somethingelse") } + end + + def test_start_full_mark + return unless use_rgengc? + omit 'stress' if GC.stress + + 3.times { GC.start } # full mark and next time it should be minor mark + GC.start(full_mark: false) + assert_nil GC.latest_gc_info(:major_by) + + GC.start(full_mark: true) + assert_not_nil GC.latest_gc_info(:major_by) + end + + def test_start_immediate_sweep + omit 'stress' if GC.stress + + GC.start(immediate_sweep: false) + assert_equal false, GC.latest_gc_info(:immediate_sweep) + + 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 = {}, {} + 2.times{ # to ignore const cache imemo creation + GC.start + GC.stat(stat) + ObjectSpace.count_objects(count) + # repeat same methods invocation for cache object creation. + GC.stat(stat) + ObjectSpace.count_objects(count) + } + assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots]) + assert_equal(count[:FREE], stat[:heap_free_slots]) + + # measure again without GC.start + 2.times{ # to ignore const cache imemo creation + 1000.times{ "a" + "b" } + GC.stat(stat) + ObjectSpace.count_objects(count) + } + assert_equal(count[:FREE], stat[:heap_free_slots]) + end + + def test_stat_argument + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.stat(:"\u{30eb 30d3 30fc}")} + end + + def test_stat_single + omit 'stress' if GC.stress + + stat = GC.stat + assert_equal stat[:count], GC.stat(:count) + assert_raise(ArgumentError){ GC.stat(:invalid) } + end + + def test_stat_constraints + omit 'stress' if GC.stress + + stat = GC.stat + # marking_time + sweeping_time could differ from time by 1 because they're stored in nanoseconds + assert_in_delta stat[:time], stat[:marking_time] + stat[:sweeping_time], 1 + assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages] + 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_empty_pages] + + if use_rgengc? + assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count] + end + end + + def test_stat_heap + omit 'stress' if GC.stress + + stat_heap = {} + stat = {} + # Initialize to prevent GC in future calls + GC.stat_heap(0, stat_heap) + GC.stat(stat) + + GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i| + EnvUtil.without_gc do + GC.stat_heap(i, stat_heap) + GC.stat(stat) + end + + assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] + assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] + assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] + assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots] + assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages] + assert_operator stat_heap[:heap_eden_slots], :>=, 0 + assert_operator stat_heap[:total_allocated_pages], :>=, 0 + assert_operator stat_heap[:force_major_gc_count], :>=, 0 + assert_operator stat_heap[:force_incremental_marking_finish_count], :>=, 0 + assert_operator stat_heap[:total_allocated_objects], :>=, 0 + assert_operator stat_heap[:total_freed_objects], :>=, 0 + assert_operator stat_heap[:total_freed_objects], :<=, stat_heap[:total_allocated_objects] + end + + GC.stat_heap(0, stat_heap) + assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size) + assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size] + + assert_raise(ArgumentError) { GC.stat_heap(-1) } + assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:HEAP_COUNT]) } + end + + def test_stat_heap_all + stat_heap_all = {} + stat_heap = {} + # Initialize to prevent GC in future calls + GC.stat_heap(0, stat_heap) + GC.stat_heap(nil, stat_heap_all) + + GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i| + GC.stat_heap(nil, stat_heap_all) + GC.stat_heap(i, stat_heap) + + # Remove keys that can vary between invocations + %i(total_allocated_objects heap_live_slots heap_free_slots).each do |sym| + stat_heap[sym] = stat_heap_all[i][sym] = 0 + end + + assert_equal stat_heap, stat_heap_all[i] + end + + assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) } + end + + def test_stat_heap_constraints + omit 'stress' if GC.stress + + stat = GC.stat + stat_heap = GC.stat_heap + 2.times do + GC.stat(stat) + GC.stat_heap(nil, stat_heap) + end + + stat_heap_sum = Hash.new(0) + stat_heap.values.each do |hash| + hash.each { |k, v| stat_heap_sum[k] += v } + end + + assert_equal stat[:heap_live_slots], stat_heap_sum[:heap_live_slots] + assert_equal stat[:heap_free_slots], stat_heap_sum[:heap_free_slots] + assert_equal stat[:heap_final_slots], stat_heap_sum[:heap_final_slots] + assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages] + assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + assert_equal stat[:total_allocated_objects], stat_heap_sum[:total_allocated_objects] + assert_equal stat[:total_freed_objects], stat_heap_sum[:total_freed_objects] + end + + def test_measure_total_time + assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60) + GC.measure_total_time = false + + time_before = GC.stat(:time) + + # Generate some garbage + Random.new.bytes(100 * 1024 * 1024) + GC.start + + time_after = GC.stat(:time) + + # If time measurement is disabled, the time stat should not change + assert_equal time_before, time_after + RUBY + end + + def test_latest_gc_info + omit 'stress' if GC.stress + + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + GC.start + count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_slots) + count.times{ "a" + "b" } + assert_equal :newobj, GC.latest_gc_info[:gc_by] + RUBY + + GC.latest_gc_info(h = {}) # allocate hash and rehearsal + GC.start + GC.start + GC.start + GC.latest_gc_info(h) + + assert_equal :force, h[:major_by] if use_rgengc? + assert_equal :method, h[:gc_by] + assert_equal true, h[:immediate_sweep] + assert_equal true, h.key?(:need_major_by) + + 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_latest_gc_info_need_major_by + return unless use_rgengc? + omit 'stress' if GC.stress + + 3.times { GC.start } + assert_nil GC.latest_gc_info(:need_major_by) + + EnvUtil.without_gc do + # allocate objects until need_major_by is set or major GC happens + objects = [] + while GC.latest_gc_info(:need_major_by).nil? + objects.append(100.times.map { '*' }) + GC.start(full_mark: false) + end + + # We need to ensure that no GC gets ran before the call to GC.start since + # it would trigger a major GC. Assertions could allocate objects and + # trigger a GC so we don't run assertions until we perform the major GC. + need_major_by = GC.latest_gc_info(:need_major_by) + GC.start(full_mark: false) # should be upgraded to major + major_by = GC.latest_gc_info(:major_by) + + assert_not_nil(need_major_by) + assert_not_nil(major_by) + end + end + + def test_latest_gc_info_weak_references_count + assert_separately([], __FILE__, __LINE__, <<~RUBY) + GC.disable + COUNT = 10_000 + # Some weak references may be created, so allow some margin of error + error_tolerance = 100 + + # Run full GC to collect stats about weak references + GC.start + + before_weak_references_count = GC.latest_gc_info(:weak_references_count) + + # Create some WeakMaps + ary = Array.new(COUNT) + COUNT.times.with_index do |i| + ary[i] = ObjectSpace::WeakMap.new + end + + # Run full GC to collect stats about weak references + GC.start + + assert_operator(GC.latest_gc_info(:weak_references_count), :>=, before_weak_references_count + COUNT - error_tolerance) + + before_weak_references_count = GC.latest_gc_info(:weak_references_count) + + # Clear ary, so if ary itself is somewhere on the stack, it won't hold all references + ary.clear + ary = nil + + # Free ary, which should GC all the WeakMaps + GC.start + + assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - COUNT + error_tolerance) + RUBY + end + + def test_stress_compile_send + assert_in_out_err([], <<-EOS, [], [], "") + GC.stress = true + begin + eval("A::B.c(1, 1, d: 234)") + rescue + end + EOS + end + + def test_singleton_method + assert_in_out_err([], <<-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([], <<-EOS, [], [], "[ruby-dev:44436]", timeout: 30) + 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 = {} + GC.stat_heap.keys.each do |heap| + env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "200000" + end + assert_normal_exit("exit", "", :child_env => env) + + env = {} + GC.stat_heap.keys.each do |heap| + env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = "0" + end + assert_normal_exit("exit", "", :child_env => env) + + 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]") + + if use_rgengc? + env = { + "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.4", + } + # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0 + assert_in_out_err([env, "-e", "GC.start; 1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") + end + + 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 + + ["0.01", "0.1", "1.0"].each do |i| + env = {"RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0", "RUBY_GC_HEAP_REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO" => i} + assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY) + GC.disable + GC.start + assert_equal((GC.stat[:old_objects] * #{i}).to_i, GC.stat[:remembered_wb_unprotected_objects_limit]) + RUBY + end + end + + def test_gc_parameter_init_slots + omit "[Bug #21203] This test is flaky and intermittently failing now" + + assert_separately([], __FILE__, __LINE__, <<~RUBY, timeout: 60) + # Constant from gc.c. + GC_HEAP_INIT_SLOTS = 10_000 + + gc_count = GC.stat(:count) + # Fill up all of the size pools to the init slots + GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i| + capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"] + while GC.stat_heap(i, :heap_eden_slots) < GC_HEAP_INIT_SLOTS + Array.new(capa) + end + end + + assert_equal gc_count, GC.stat(:count) + RUBY + + env = {} + sizes = GC.stat_heap.keys.reverse.map { 20_000 } + GC.stat_heap.keys.each do |heap| + env["RUBY_GC_HEAP_#{heap}_INIT_SLOTS"] = sizes[heap].to_s + end + assert_separately([env, "-W0"], __FILE__, __LINE__, <<~RUBY, timeout: 60) + SIZES = #{sizes} + + gc_count = GC.stat(:count) + # Fill up all of the size pools to the init slots + GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times do |i| + capa = (GC.stat_heap(i, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] - (2 * RbConfig::SIZEOF["void*"])) / RbConfig::SIZEOF["void*"] + while GC.stat_heap(i, :heap_eden_slots) < SIZES[i] + Array.new(capa) + end + end + + assert_equal gc_count, GC.stat(:count) + RUBY + 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 + omit "for now" + assert_separately([], __FILE__, __LINE__, <<-'RUBY', 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) + RUBY + end + + def test_profiler_raw_data + GC::Profiler.enable + GC.start + assert GC::Profiler.raw_data + ensure + GC::Profiler.disable + 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([], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]") + ObjectSpace.define_finalizer(Thread.main) { p 'finalize' } + EOS + end + + def test_expand_heap + assert_separately([], __FILE__, __LINE__, <<~'RUBY') + 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 + RUBY + end + + def test_thrashing_for_young_objects + # This test prevents bugs like [Bug #18929] + + assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 60) + # Grow the heap + @ary = 100_000.times.map { Object.new } + + # Warmup to make sure heap stabilizes + 1_000_000.times { Object.new } + + # We need to pre-allocate all the hashes for GC.stat calls, because + # otherwise the call to GC.stat/GC.stat_heap itself could cause a new + # page to be allocated and the before/after assertions will fail + before_stats = {} + after_stats = {} + # stat_heap needs a hash of hashes for each heap; easiest way to get the + # right shape for that is just to call stat_heap with no argument + before_stat_heap = GC.stat_heap + after_stat_heap = GC.stat_heap + + # Now collect the actual stats + GC.stat before_stats + GC.stat_heap nil, before_stat_heap + + 1_000_000.times { Object.new } + + # Previous loop may have caused GC to be in an intermediate state, + # running a minor GC here will guarantee that GC will be complete + GC.start(full_mark: false) + + GC.stat after_stats + GC.stat_heap nil, after_stat_heap + + # Debugging output to for failures in trunk-repeat50@phosphorus-docker + debug_msg = "before_stats: #{before_stats}\nbefore_stat_heap: #{before_stat_heap}\nafter_stats: #{after_stats}\nafter_stat_heap: #{after_stat_heap}" + + # Should not be thrashing in page creation + assert_equal before_stats[:heap_allocated_pages], after_stats[:heap_allocated_pages], debug_msg + assert_equal 0, after_stats[:total_freed_pages], debug_msg + # Only young objects, so should not trigger major GC + assert_equal before_stats[:major_gc_count], after_stats[:major_gc_count], debug_msg + RUBY + end + + def test_heaps_grow_independently + # [Bug #21214] + + assert_separately([], __FILE__, __LINE__, <<-'RUBY', timeout: 60) + COUNT = 1_000_000 + + def allocate_small_object = [] + def allocate_large_object = Array.new(10) + + @arys = Array.new(COUNT) do + # Allocate 10 small transient objects + 10.times { allocate_small_object } + # Allocate 1 large object that is persistent + allocate_large_object + end + + # Running GC here is required to prevent this test from being flaky because + # the heap for the small transient objects may not have been cleared by the + # GC causing heap_available_slots to be slightly over 2 * COUNT. + GC.start + + heap_available_slots = GC.stat(:heap_available_slots) + + assert_operator(heap_available_slots, :<, COUNT * 2, "GC.stat: #{GC.stat}\nGC.stat_heap: #{GC.stat_heap}") + RUBY + end + + def test_gc_internals + assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT] + assert_not_nil GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + end + + def test_sweep_in_finalizer + bug9205 = '[ruby-core:58833] [Bug #9205]' + 2.times do + assert_ruby_status([], <<-'end;', bug9205, timeout: 120) + 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 + omit 'randomly hangs on many platforms' if ENV.key?('GITHUB_ACTIONS') + 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, timeout: 100) 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_finalizer_passed_object_id + assert_in_out_err([], <<~RUBY, ["true"], []) + o = Object.new + obj_id = o.object_id + ObjectSpace.define_finalizer(o, ->(id){ p id == obj_id }) + RUBY + end + + def test_verify_internal_consistency + assert_nil(GC.verify_internal_consistency) + end + + def test_gc_stress_on_realloc + assert_normal_exit(<<-'end;', '[Bug #9859]') + class C + def initialize + @a = nil + @b = nil + @c = nil + @d = nil + @e = nil + @f = nil + end + end + + GC.stress = true + C.new + end; + end + + def test_gc_stress_at_startup + assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60) + end + + def test_gc_disabled_start + EnvUtil.without_gc do + c = GC.count + GC.start + assert_equal 1, GC.count - c + end + + EnvUtil.without_gc do + c = GC.count + GC.start(immediate_mark: false, immediate_sweep: false) + 10_000.times { Object.new } + assert_equal 1, GC.count - c + 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 + require '-test-/stack' + omit 'failing with ASAN' if Thread.asan? + assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) + c1 = proc do + puts "c1" + raise + end + c2 = proc do + puts "c2" + raise + end + begin; + tap do + obj = Object.new + ObjectSpace.define_finalizer(obj, c1) + ObjectSpace.define_finalizer(obj, c2) + obj = nil + end + end; + end + + def test_exception_in_finalizer_method + require '-test-/stack' + omit 'failing with ASAN' if Thread.asan? + assert_in_out_err(["-W0"], "#{<<~"begin;"}\n#{<<~'end;'}", %w[c1 c2]) + def self.c1(x) + puts "c1" + raise + end + def self.c2(x) + puts "c2" + raise + end + begin; + tap do + obj = Object.new + ObjectSpace.define_finalizer(obj, method(:c1)) + ObjectSpace.define_finalizer(obj, method(:c2)) + obj = nil + end + end; + + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #20042]' + begin; + def (f = Object.new).call = nil # missing ID + o = Object.new + ObjectSpace.define_finalizer(o, f) + o = nil + GC.start + end; + end + + def test_object_ids_never_repeat + GC.start + a = 1000.times.map { Object.new.object_id } + GC.start + b = 1000.times.map { Object.new.object_id } + assert_empty(a & b) + end + + def test_ast_node_buffer + # https://github.com/ruby/ruby/pull/4416 + Module.new.class_eval( (["# shareable_constant_value: literal"] + + (0..100000).map {|i| "M#{ i } = {}" }).join("\n")) + end + + def test_old_to_young_reference + EnvUtil.without_gc do + require "objspace" + + old_obj = Object.new + 4.times { GC.start } + + assert_include ObjectSpace.dump(old_obj), '"old":true' + + young_obj = Object.new + old_obj.instance_variable_set(:@test, young_obj) + + # Not immediately promoted to old generation + 3.times do + assert_not_include ObjectSpace.dump(young_obj), '"old":true' + GC.start + end + + # Takes 4 GC to promote to old generation + GC.start + assert_include ObjectSpace.dump(young_obj), '"old":true' + end + end + + def test_finalizer_not_run_with_vm_lock + assert_ractor(<<~'RUBY') + Thread.new do + loop do + Encoding.list.each do |enc| + enc.names + end + end + end + + o = Object.new + ObjectSpace.define_finalizer(o, proc do + sleep 0.5 # finalizer shouldn't be run with VM lock, otherwise this context switch will crash + end) + o = nil + 4.times do + GC.start + end + RUBY + end +end diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb new file mode 100644 index 0000000000..7e0c499dd9 --- /dev/null +++ b/test/ruby/test_gc_compact.rb @@ -0,0 +1,488 @@ +# frozen_string_literal: true +require 'test/unit' + +if RUBY_PLATFORM =~ /s390x/ + warn "Currently, it is known that the compaction does not work well on s390x; contribution is welcome https://github.com/ruby/ruby/pull/5077" + return +end + +class TestGCCompact < Test::Unit::TestCase + module CompactionSupportInspector + def supports_compact? + GC.respond_to?(:compact) + end + end + + module OmitUnlessCompactSupported + include CompactionSupportInspector + + def setup + omit "GC compaction not supported on this platform" unless supports_compact? + super + end + end + + include OmitUnlessCompactSupported + + class AutoCompact < Test::Unit::TestCase + include OmitUnlessCompactSupported + + def test_enable_autocompact + before = GC.auto_compact + GC.auto_compact = true + assert_predicate GC, :auto_compact + ensure + GC.auto_compact = before + end + + def test_disable_autocompact + before = GC.auto_compact + GC.auto_compact = false + refute GC.auto_compact + ensure + GC.auto_compact = before + end + + def test_major_compacts + before = GC.auto_compact + GC.auto_compact = true + compact = GC.stat :compact_count + GC.start + assert_operator GC.stat(:compact_count), :>, compact + ensure + GC.auto_compact = before + end + + def test_implicit_compaction_does_something + before = GC.auto_compact + list = [] + list2 = [] + + # Try to make some fragmentation + 500.times { + list << Object.new + Object.new + Object.new + } + count = GC.stat :compact_count + GC.auto_compact = true + n = 1_000_000 + n.times do + break if count < GC.stat(:compact_count) + list2 << Object.new + end and omit "implicit compaction didn't happen within #{n} objects" + compact_stats = GC.latest_compact_info + refute_predicate compact_stats[:considered], :empty? + refute_predicate compact_stats[:moved], :empty? + ensure + GC.auto_compact = before + end + end + + class CompactMethodsNotImplemented < Test::Unit::TestCase + include CompactionSupportInspector + + def assert_not_implemented(method, *args) + omit "autocompact is supported on this platform" if supports_compact? + + assert_raise(NotImplementedError) { GC.send(method, *args) } + refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement") + end + + def test_gc_compact_not_implemented + assert_not_implemented(:compact) + end + + def test_gc_auto_compact_get_not_implemented + assert_not_implemented(:auto_compact) + end + + def test_gc_auto_compact_set_not_implemented + assert_not_implemented(:auto_compact=, true) + end + + def test_gc_latest_compact_info_not_implemented + assert_not_implemented(:latest_compact_info) + end + + def test_gc_verify_compaction_references_not_implemented + assert_not_implemented(:verify_compaction_references) + end + end + + def test_gc_compact_stats + list = [] + + # Try to make some fragmentation + 500.times { + list << Object.new + Object.new + Object.new + } + compact_stats = GC.compact + refute_predicate compact_stats[:considered], :empty? + refute_predicate compact_stats[:moved], :empty? + end + + def big_list(level = 10) + if level > 0 + big_list(level - 1) + else + 1000.times.map { + # try to make some empty slots by allocating an object and discarding + Object.new + Object.new + } # likely next to each other + end + end + + def test_complex_hash_keys + list_of_objects = big_list + hash = list_of_objects.hash + GC.verify_compaction_references(toward: :empty) + assert_equal hash, list_of_objects.hash + GC.verify_compaction_references(expand_heap: false) + assert_equal hash, list_of_objects.hash + end + + def test_ast_compacts + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + def walk_ast ast + children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node) + children.each do |child| + assert_predicate child, :type + walk_ast child + end + end + ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump} + assert_predicate GC, :compact + walk_ast ast + end; + end + + def test_compact_count + count = GC.stat(:compact_count) + GC.compact + assert_equal count + 1, GC.stat(:compact_count) + end + + def test_compacting_from_trace_point + obj = Object.new + def obj.tracee + :ret # expected to emit both line and call event from one instruction + end + + results = [] + TracePoint.new(:call, :line) do |tp| + results << tp.event + GC.verify_compaction_references + end.enable(target: obj.method(:tracee)) do + obj.tracee + end + + assert_equal([:call, :line], results) + end + + def test_updating_references_for_heap_allocated_shared_arrays + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ary = [] + 50.times { |i| ary << i } + + # Pointer in slice should point to buffer of ary + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_embed_shared_arrays + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ary = Array.new(50) + 50.times { |i| ary[i] = i } + + # Ensure ary is embedded + assert_include(ObjectSpace.dump(ary), '"embedded":true') + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_heap_allocated_frozen_shared_arrays + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ary = [] + 50.times { |i| ary << i } + # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG + ary.freeze + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_updating_references_for_embed_frozen_shared_arrays + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ary = Array.new(50) + 50.times { |i| ary[i] = i } + # Frozen arrays can become shared root without RARRAY_SHARED_ROOT_FLAG + ary.freeze + + # Ensure ary is embedded + assert_include(ObjectSpace.dump(ary), '"embedded":true') + + slice = ary[10..40] + + # Check that slice is pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + + # Run compaction to re-embed ary + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + # Assert that slice is pointer to updated buffer in ary + assert_equal(10, slice[0]) + # Check that slice is still pointing to buffer of ary + assert_include(ObjectSpace.dump(slice), '"shared":true') + end; + end + + def test_moving_arrays_down_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ARY_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $arys = ARY_COUNT.times.map do + ary = "abbbbbbbbbb".chars + ary.uniq! + end + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + assert_operator(stats.dig(:moved_down, :T_ARRAY) || 0, :>=, ARY_COUNT - 10) + refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_arrays_up_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + ARY_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + ary = "hello".chars + $arys = ARY_COUNT.times.map do + x = [] + ary.each { |e| x << e } + x + end + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + assert_operator(stats.dig(:moved_up, :T_ARRAY) || 0, :>=, (0.9995 * ARY_COUNT).to_i) + refute_empty($arys.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_objects_between_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60) + begin; + class Foo + def add_ivars + 10.times do |i| + instance_variable_set("@foo" + i.to_s, 0) + end + end + end + + OBJ_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $ary = OBJ_COUNT.times.map { Foo.new } + $ary.each(&:add_ivars) + + GC.start + Foo.new.add_ivars + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats.dig(:moved_up, :T_OBJECT) || 0, :>=, OBJ_COUNT - 15) + refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_compact_objects_of_varying_sizes + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + begin; + $objects = [] + 160.times do |n| + obj = Class.new.new + n.times { |i| obj.instance_variable_set("@foo" + i.to_s, 0) } + $objects << obj + end + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + end; + end + + def test_moving_strings_up_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30) + begin; + STR_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4 + $ary = STR_COUNT.times.map { +"" << str } + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT - 15) + refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_strings_down_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30) + begin; + STR_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4).squeeze! } + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT - 10) + refute_empty($ary.keep_if { |o| ObjectSpace.dump(o).include?('"embedded":true') }) + end; + end + + def test_moving_hashes_down_heaps + omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + # AR and ST hashes are in the same size pool on 32 bit + omit unless RbConfig::SIZEOF["uint64_t"] <= RbConfig::SIZEOF["void*"] + + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30) + begin; + HASH_COUNT = 50000 + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + Fiber.new { + base_hash = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8 } + $ary = HASH_COUNT.times.map { base_hash.dup } + $ary.each_with_index { |h, i| h[:i] = 9 } + }.resume + + stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_operator(stats[:moved_down][:T_HASH], :>=, HASH_COUNT - 10) + end; + end + + def test_moving_objects_between_heaps_keeps_shape_frozen_status + # [Bug #19536] + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class A + def add_ivars + @a = @b = @c = @d = 1 + end + + def set_a + @a = 10 + end + end + + a = A.new + a.add_ivars + a.freeze + + b = A.new + b.add_ivars + b.set_a # Set the inline cache in set_a + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_raise(FrozenError) { a.set_a } + end; + end + + def test_moving_too_complex_generic_ivar + omit "not compiled with SHAPE_DEBUG" unless defined?(RubyVM::Shape) + + assert_separately([], <<~RUBY) + RubyVM::Shape.exhaust_shapes + + obj = [] + obj.instance_variable_set(:@fixnum, 123) + obj.instance_variable_set(:@str, "hello") + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(123, obj.instance_variable_get(:@fixnum)) + assert_equal("hello", obj.instance_variable_get(:@str)) + RUBY + end +end diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb new file mode 100644 index 0000000000..32384f5a5c --- /dev/null +++ b/test/ruby/test_hash.rb @@ -0,0 +1,2434 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} + +class TestHash < Test::Unit::TestCase + def test_hash + 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]) + + assert(begin + for k,v in y + raise if k*2 != v + end + true + rescue + false + end) + + assert_equal(3, x.length) + 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) + + z = y.keys.join(":") + assert_equal("1:2:3", z) + + z = y.values.join(":") + assert_equal("2:4:6", z) + assert_equal(x, y) + + y.shift + assert_equal(2, y.length) + + z = [1,2] + y[z] = 256 + assert_equal(256, y[z]) + + x = Hash.new(0) + x[1] = 1 + assert_equal(1, x[1]) + assert_equal(0, x[2]) + + x = Hash.new([]) + assert_equal([], x[22]) + assert_same(x[22], x[22]) + + x = Hash.new{[]} + assert_equal([], x[22]) + assert_not_same(x[22], x[22]) + + x = Hash.new{|h,kk| z = kk; h[kk] = kk*2} + z = 0 + assert_equal(44, x[22]) + assert_equal(22, z) + z = 0 + assert_equal(44, x[22]) + assert_equal(0, z) + x.default = 5 + assert_equal(5, x[23]) + + x = Hash.new + def x.default(k) + $z = k + self[k] = k*2 + end + $z = 0 + assert_equal(44, x[22]) + assert_equal(22, $z) + $z = 0 + assert_equal(44, x[22]) + assert_equal(0, $z) + end + + # From rubicon + + def setup + @cls ||= Hash + @h = @cls[ + 1 => 'one', 2 => 'two', 3 => 'three', + self => 'self', true => 'true', nil => 'nil', + 'nil' => nil + ] + end + + def teardown + 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_s_AREF_from_hash + 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[Hash.new(42)] + assert_nil(h['a']) + + h = @cls[Hash.new {42}] + assert_nil(h['a']) + end + + def test_s_AREF_from_list + h = @cls["a", 100, "b", 200] + assert_equal(100, h['a']) + assert_equal(200, h['b']) + assert_nil(h['c']) + end + + def test_s_AREF_from_pairs + h = @cls[[["a", 100], ["b", 200]]] + assert_equal(100, h['a']) + assert_equal(200, h['b']) + 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']) + + assert_raise(ArgumentError) do + @cls[[["a", 100], "b", ["c", 300]]] + end + 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 + h = @cls.new + assert_instance_of(@cls, h) + assert_nil(h.default) + assert_nil(h['spurious']) + + h = @cls.new('default') + assert_instance_of(@cls, h) + assert_equal('default', h.default) + assert_equal('default', h['spurious']) + end + + def test_st_literal_memory_leak + assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true) + begin; + 1_000_000.times do + # >8 element hashes are ST allocated rather than AR allocated + {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9} + end + end; + 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 # '[]' + t = Time.now + h = @cls[ + 1 => 'one', 2 => 'two', 3 => 'three', + self => 'self', t => 'time', nil => 'nil', + 'nil' => nil + ] + + assert_equal('one', h[1]) + assert_equal('two', h[2]) + assert_equal('three', h[3]) + assert_equal('self', h[self]) + assert_equal('time', h[t]) + assert_equal('nil', h[nil]) + assert_equal(nil, h['nil']) + assert_equal(nil, h['koala']) + + h1 = h.dup + h1.default = :default + + assert_equal('one', h1[1]) + assert_equal('two', h1[2]) + assert_equal('three', h1[3]) + assert_equal('self', h1[self]) + assert_equal('time', h1[t]) + assert_equal('nil', h1[nil]) + assert_equal(nil, h1['nil']) + assert_equal(:default, h1['koala']) + + + end + + def test_ASET # '[]=' + t = Time.now + h = @cls.new + h[1] = 'one' + h[2] = 'two' + h[3] = 'three' + h[self] = 'self' + h[t] = 'time' + h[nil] = 'nil' + h['nil'] = nil + assert_equal('one', h[1]) + assert_equal('two', h[2]) + assert_equal('three', h[3]) + assert_equal('self', h[self]) + assert_equal('time', h[t]) + assert_equal('nil', h[nil]) + assert_equal(nil, h['nil']) + assert_equal(nil, h['koala']) + + h[1] = 1 + h[nil] = 99 + h['nil'] = nil + z = [1,2] + h[z] = 256 + assert_equal(1, h[1]) + assert_equal('two', h[2]) + assert_equal('three', h[3]) + assert_equal('self', h[self]) + assert_equal('time', h[t]) + assert_equal(99, h[nil]) + assert_equal(nil, h['nil']) + assert_equal(nil, h['koala']) + assert_equal(256, h[z]) + 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_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_operator(@h.size, :>, 0) + @h.clear + assert_equal(0, @h.size) + assert_nil(@h[1]) + end + + def test_clone + for frozen in [ false, true ] + a = @h.clone + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) + end + end + + def test_default + assert_nil(@h.default) + h = @cls.new(:xyzzy) + assert_equal(:xyzzy, h.default) + end + + def test_default= + assert_nil(@h.default) + @h.default = :xyzzy + assert_equal(:xyzzy, @h.default) + end + + def test_delete + h1 = @cls[ 1 => 'one', 2 => 'two', true => 'true' ] + h2 = @cls[ 1 => 'one', 2 => 'two' ] + h3 = @cls[ 2 => 'two' ] + + assert_equal('true', h1.delete(true)) + assert_equal(h2, h1) + + assert_equal('one', h1.delete(1)) + assert_equal(h3, h1) + + assert_equal('two', h1.delete(2)) + assert_equal(@cls[], h1) + + assert_nil(h1.delete(99)) + assert_equal(@cls[], h1) + + assert_equal('default 99', h1.delete(99) {|i| "default #{i}" }) + end + + def test_delete_if + base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ] + h2 = @cls[ 2 => false, 'cat' => 99 ] + h3 = @cls[ 2 => false ] + + h = base.dup + assert_equal(h, h.delete_if { false }) + assert_equal(@cls[], h.delete_if { true }) + + h = base.dup + assert_equal(h1, h.delete_if {|k,v| k.instance_of?(String) }) + assert_equal(h1, h) + + h = base.dup + assert_equal(h2, h.delete_if {|k,v| v.instance_of?(String) }) + assert_equal(h2, h) + + h = base.dup + assert_equal(h3, h.delete_if {|k,v| v }) + assert_equal(h3, h) + + h = base.dup + n = 0 + h.delete_if {|*a| + n += 1 + assert_equal(2, a.size) + assert_equal(base[a[0]], a[1]) + h.shift + true + } + assert_equal(base.size, n) + + h = base.dup + assert_raise(FrozenError) do + h.delete_if do + h.freeze + true + end + end + assert_equal(base.dup, h) + + h = base.dup + assert_same h, h.delete_if {h.assoc(nil); true} + assert_empty h + end + + def test_keep_if + h = @cls[1=>2,3=>4,5=>6] + assert_equal({3=>4,5=>6}, h.keep_if {|k, v| k + v >= 7 }) + h = @cls[1=>2,3=>4,5=>6] + assert_equal({1=>2,3=>4,5=>6}, h.keep_if{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.keep_if do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) + end + + def test_compact + 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 frozen in [ false, true ] + a = @h.dup + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(false, b.frozen?) + end + end + + 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 + h1.rehash + assert_equal(h1, h1.dup) + end + + def test_each + count = 0 + @cls[].each { |k, v| count + 1 } + assert_equal(0, count) + + h = @h + h.each do |k, v| + 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 + count = 0 + @cls[].each_key { |k| count + 1 } + assert_equal(0, count) + + h = @h + h.each_key do |k| + h.delete(k) + end + assert_equal(@cls[], h) + end + + def test_each_pair + count = 0 + @cls[].each_pair { |k, v| count + 1 } + assert_equal(0, count) + + h = @h + h.each_pair do |k, v| + assert_equal(v, h.delete(k)) + end + assert_equal(@cls[], h) + end + + def test_each_value + res = [] + @cls[].each_value { |v| res << v } + assert_equal(0, [].length) + + @h.each_value { |v| res << v } + assert_equal(0, [].length) + + expected = [] + @h.each { |k, v| expected << v } + + assert_equal([], expected - res) + assert_equal([], res - expected) + end + + def test_empty? + assert_empty(@cls[]) + assert_not_empty(@h) + end + + def test_fetch + assert_equal('gumbygumby', @h.fetch('gumby') {|k| k * 2 }) + assert_equal('pokey', @h.fetch('gumby', 'pokey')) + + assert_equal('one', @h.fetch(1)) + assert_equal(nil, @h.fetch('nil')) + 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_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_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_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 + assert_equal(1, @h.key('one')) + assert_equal(nil, @h.key('nil')) + assert_equal('nil', @h.key(nil)) + + assert_equal(nil, @h.key('gumby')) + assert_equal(nil, @cls[].key('gumby')) + end + + def test_values_at + res = @h.values_at('dog', 'cat', 'horse') + assert_equal(3, res.length) + assert_equal([nil, nil, nil], res) + + res = @h.values_at + assert_equal(0, res.length) + + res = @h.values_at(3, 2, 1, nil) + assert_equal 4, res.length + assert_equal %w( three two one nil ), res + + res = @h.values_at(3, 99, 1, nil) + assert_equal 4, res.length + 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 + assert_equal(1, h['one']) + assert_equal(true, h['true']) + assert_equal(nil, h['nil']) + + h.each do |k, v| + 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_include(%w[a c], h[1]) + assert_equal('b', h[2]) + end + + def test_key? + 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 + assert_equal([], @cls[].keys) + + keys = @h.keys + expected = [] + @h.each { |k, v| expected << k } + assert_equal([], keys - expected) + assert_equal([], expected - keys) + end + + def test_length + assert_equal(0, @cls[].length) + assert_equal(7, @h.length) + end + + def test_member? + 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 hash_hint hv + hv & 0xff + end + + def test_rehash + a = [ "a", "b" ] + c = [ "c", "d" ] + h = @cls[ a => 100, c => 300 ] + assert_equal(100, h[a]) + + hv = a.hash + begin + a[0] << "z" + end while hash_hint(a.hash) == hash_hint(hv) + + assert_nil(h[a]) + h.rehash + assert_equal(100, h[a]) + 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 ] + h3 = @cls[ 2 => false ] + + h = base.dup + assert_equal(h, h.reject { false }) + assert_equal(@cls[], h.reject { true }) + + h = base.dup + assert_equal(h1, h.reject {|k,v| k.instance_of?(String) }) + + assert_equal(h2, h.reject {|k,v| v.instance_of?(String) }) + + assert_equal(h3, h.reject {|k,v| v }) + assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h = EnvUtil.suppress_warning {h.reject {false}} + assert_instance_of(Hash, h) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) + end + + def test_reject_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + h2 = h.reject{|k,| k != 'str'} + assert_equal(expected, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.reject{true} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.reject{true} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.reject{|k,| k != 'str'} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + + def test_reject! + base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ] + h2 = @cls[ 2 => false, 'cat' => 99 ] + h3 = @cls[ 2 => false ] + + h = base.dup + assert_equal(nil, h.reject! { false }) + assert_equal(@cls[], h.reject! { true }) + + h = base.dup + assert_equal(h1, h.reject! {|k,v| k.instance_of?(String) }) + assert_equal(h1, h) + + h = base.dup + assert_equal(h2, h.reject! {|k,v| v.instance_of?(String) }) + assert_equal(h2, h) + + h = base.dup + assert_equal(h3, h.reject! {|k,v| v }) + assert_equal(h3, h) + + h = base.dup + assert_raise(FrozenError) do + h.reject! do + h.freeze + true + end + end + assert_equal(base.dup, h) + end + + def test_replace + h = @cls[ 1 => 2, 3 => 4 ] + h1 = h.replace(@cls[ 9 => 8, 7 => 6 ]) + assert_equal(h, h1) + assert_equal(8, h[9]) + assert_equal(6, h[7]) + assert_nil(h[1]) + 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_shift + h = @h.dup + + @h.length.times { + k, v = h.shift + assert_send([@h, :key?, k]) + assert_equal(@h[k], v) + } + + assert_equal(0, h.length) + end + + def test_size + assert_equal(0, @cls[].length) + assert_equal(7, @h.length) + end + + def test_sort + h = @cls[].sort + assert_equal([], h) + + h = @cls[ 1 => 1, 2 => 1 ].sort + assert_equal([[1,1], [2,1]], h) + + h = @cls[ 'cat' => 'feline', 'ass' => 'asinine', 'bee' => 'beeline' ] + h1 = h.sort + assert_equal([ %w(ass asinine), %w(bee beeline), %w(cat feline)], h1) + end + + def test_store + t = Time.now + h = @cls.new + h.store(1, 'one') + h.store(2, 'two') + h.store(3, 'three') + h.store(self, 'self') + h.store(t, 'time') + h.store(nil, 'nil') + h.store('nil', nil) + assert_equal('one', h[1]) + assert_equal('two', h[2]) + assert_equal('three', h[3]) + assert_equal('self', h[self]) + assert_equal('time', h[t]) + assert_equal('nil', h[nil]) + assert_equal(nil, h['nil']) + assert_equal(nil, h['koala']) + + h.store(1, 1) + h.store(nil, 99) + h.store('nil', nil) + assert_equal(1, h[1]) + assert_equal('two', h[2]) + assert_equal('three', h[3]) + assert_equal('self', h[self]) + assert_equal('time', h[t]) + assert_equal(99, h[nil]) + assert_equal(nil, h['nil']) + assert_equal(nil, h['koala']) + end + + def test_to_a + assert_equal([], @cls[].to_a) + assert_equal([[1,2]], @cls[ 1=>2 ].to_a) + a = @cls[ 1=>2, 3=>4, 5=>6 ].to_a + assert_equal([1,2], a.delete([1,2])) + assert_equal([3,4], a.delete([3,4])) + assert_equal([5,6], a.delete([5,6])) + assert_equal(0, a.length) + 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_to_s + h = @cls[ 1 => 2, "cat" => "dog", 1.5 => :fred ] + assert_equal(h.inspect, h.to_s) + assert_deprecated_warning { $, = ":" } + assert_equal(h.inspect, h.to_s) + h = @cls[] + assert_equal(h.inspect, h.to_s) + ensure + $, = nil + end + + def test_inspect + no_quote = '{a: 1, a!: 1, a?: 1}' + quote0 = '{"": 1}' + quote1 = '{"0": 1, "!": 1, "%": 1, "&": 1, "*": 1, "+": 1, "-": 1, "/": 1, "<": 1, ">": 1, "^": 1, "`": 1, "|": 1, "~": 1}' + quote2 = '{"@a": 1, "$a": 1, "+@": 1, "a=": 1, "[]": 1}' + quote3 = '{"a\"b": 1, "@@a": 1, "<=>": 1, "===": 1, "[]=": 1}' + assert_equal(no_quote, eval(no_quote).inspect) + assert_equal(quote0, eval(quote0).inspect) + assert_equal(quote1, eval(quote1).inspect) + assert_equal(quote2, eval(quote2).inspect) + assert_equal(quote3, eval(quote3).inspect) + + EnvUtil.with_default_external(Encoding::ASCII) do + utf8_ascii_hash = '{"\\u3042": 1}' + assert_equal(eval(utf8_ascii_hash).inspect, utf8_ascii_hash) + end + + EnvUtil.with_default_external(Encoding::UTF_8) do + utf8_hash = "{\u3042: 1}" + assert_equal(eval(utf8_hash).inspect, utf8_hash) + end + + EnvUtil.with_default_external(Encoding::Windows_31J) do + sjis_hash = "{\x87]: 1}".force_encoding('sjis') + assert_equal(eval(sjis_hash).inspect, sjis_hash) + end + end + + def test_update + h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ] + h2 = @cls[ 2 => 'two', 4 => 'four' ] + + ha = @cls[ 1 => 2, 2 => 'two', 3 => 4, 4 => 'four' ] + hb = @cls[ 1 => 2, 2 => 3, 3 => 4, 4 => 'four' ] + + assert_equal(ha, h1.update(h2)) + assert_equal(ha, h1) + + h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ] + h2 = @cls[ 2 => 'two', 4 => 'four' ] + + assert_equal(hb, h2.update(h1)) + assert_equal(hb, h2) + end + + def test_value2? + 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 + assert_equal([], @cls[].values) + + vals = @h.values + expected = [] + @h.each { |k, v| expected << v } + assert_equal([], vals - expected) + assert_equal([], expected - vals) + end + + def test_create + assert_equal({1=>2, 3=>4}, @cls[[[1,2],[3,4]]]) + assert_raise(ArgumentError) { @cls[0, 1, 2] } + assert_raise(ArgumentError) { @cls[[[0, 1], 2]] } + bug5406 = '[ruby-core:39945]' + assert_raise(ArgumentError, bug5406) { @cls[[[1, 2], [3, 4, 5]]] } + assert_equal({1=>2, 3=>4}, @cls[1,2,3,4]) + o = Object.new + def o.to_hash() {1=>2} end + assert_equal({1=>2}, @cls[o], "[ruby-dev:34555]") + end + + def test_rehash2 + h = @cls[1 => 2, 3 => 4] + assert_equal(h.dup, h.rehash) + assert_raise(RuntimeError) { h.each { h.rehash } } + assert_equal({}, @cls[].rehash) + end + + def test_fetch2 + assert_equal(:bar, assert_warning(/block supersedes default value argument/) {@h.fetch(0, :foo) { :bar }}) + end + + def test_default_proc + h = @cls.new {|hh, k| hh + k + "baz" } + assert_equal("foobarbaz", h.default_proc.call("foo", "bar")) + 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 = @cls.new {|hh, k| :foo } + h[1] = 2 + assert_equal([1, 2], h.shift) + assert_nil(h.shift) + assert_nil(h.shift) + + h = @cls.new(:foo) + h[1] = 2 + assert_equal([1, 2], h.shift) + assert_nil(h.shift) + assert_nil(h.shift) + + 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_nil(h.shift) + end + + def test_shift_for_empty_hash + # [ruby-dev:51159] + h = @cls[] + 100.times{|n| + while h.size < n + k = Random.rand 0..1<<30 + h[k] = 1 + end + 0 while h.shift + assert_equal({}, h) + } + end + + def test_reject_bang2 + 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}, @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 = h.select {true} + assert_instance_of(Hash, h) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) + end + + def test_select_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + h2 = h.select{|k,| k == 'str'} + assert_equal(expected, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.select{false} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.select{false} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + h2 = h.select{|k,| k == 'str'} + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + + def test_select! + h = @cls[1=>2,3=>4,5=>6] + assert_equal(h, h.select! {|k, v| k + v >= 7 }) + assert_equal({3=>4,5=>6}, h) + h = @cls[1=>2,3=>4,5=>6] + assert_equal(nil, h.select!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.select! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) + end + + def test_slice + 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_slice_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + sliced = h.slice(str1, str2) + expected = {}.compare_by_identity + expected[str1] = 1 + expected[str2] = 2 + assert_equal(expected, sliced) + assert_equal(true, sliced.compare_by_identity?) + sliced = h.slice + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + sliced= h.slice + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + sliced = h.slice(str1, str2) + assert_equal({}.compare_by_identity, sliced) + assert_equal(true, sliced.compare_by_identity?) + end + + def test_except + h = @cls[1=>2,3=>4,5=>6] + assert_equal({5=>6}, h.except(1, 3)) + assert_equal({1=>2,3=>4,5=>6}, h.except(7)) + assert_equal({1=>2,3=>4,5=>6}, h.except) + assert_equal({}, {}.except) + end + + def test_except_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + excepted = h.except(str1, str2) + assert_equal({1=>2,3=>4,5=>6}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + excepted = h.except + assert_equal(h, excepted) + assert_equal(true, excepted.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + excepted = h.except + assert_equal({}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + excepted = h.except(str1, str2) + assert_equal({}.compare_by_identity, excepted) + assert_equal(true, excepted.compare_by_identity?) + end + + def test_filter + assert_equal({3=>4,5=>6}, @cls[1=>2,3=>4,5=>6].filter {|k, v| k + v >= 7 }) + + base = @cls[ 1 => 'one', '2' => false, true => 'true', 'cat' => 99 ] + h1 = @cls[ '2' => false, 'cat' => 99 ] + h2 = @cls[ 1 => 'one', true => 'true' ] + h3 = @cls[ 1 => 'one', true => 'true', 'cat' => 99 ] + + h = base.dup + assert_equal(h, h.filter { true }) + assert_equal(@cls[], h.filter { false }) + + h = base.dup + assert_equal(h1, h.filter {|k,v| k.instance_of?(String) }) + + assert_equal(h2, h.filter {|k,v| v.instance_of?(String) }) + + assert_equal(h3, h.filter {|k,v| v }) + assert_equal(base, h) + + h.instance_variable_set(:@foo, :foo) + h.default = 42 + h = h.filter {true} + assert_instance_of(Hash, h) + assert_nil(h.default) + assert_not_send([h, :instance_variable_defined?, :@foo]) + end + + def test_filter! + h = @cls[1=>2,3=>4,5=>6] + assert_equal(h, h.filter! {|k, v| k + v >= 7 }) + assert_equal({3=>4,5=>6}, h) + h = @cls[1=>2,3=>4,5=>6] + assert_equal(nil, h.filter!{true}) + h = @cls[1=>2,3=>4,5=>6] + assert_raise(FrozenError) do + h.filter! do + h.freeze + false + end + end + assert_equal(@cls[1=>2,3=>4,5=>6], h) + end + + def test_clear2 + assert_equal({}, @cls[1=>2,3=>4,5=>6].clear) + h = @cls[1=>2,3=>4,5=>6] + h.each { h.clear } + assert_equal({}, h) + end + + def test_replace2 + h1 = @cls.new { :foo } + h2 = @cls.new + h2.replace h1 + assert_equal(:foo, h2[0]) + + assert_raise(ArgumentError) { h2.replace() } + assert_raise(TypeError) { h2.replace(1) } + h2.freeze + assert_raise(ArgumentError) { h2.replace() } + assert_raise(FrozenError) { h2.replace(h1) } + assert_raise(FrozenError) { h2.replace(42) } + end + + def test_size2 + assert_equal(0, @cls[].size) + end + + def test_equal2 + assert_not_equal(0, @cls[]) + o = Object.new + o.instance_variable_set(:@cls, @cls) + def o.to_hash; @cls[]; end + def o.==(x); true; end + assert_equal({}, o) + o.singleton_class.remove_method(:==) + def o.==(x); false; end + assert_not_equal({}, o) + + 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_not_send([@cls[], :eql?, 0]) + o = Object.new + o.instance_variable_set(:@cls, @cls) + def o.to_hash; @cls[]; end + def o.eql?(x); true; end + assert_send([@cls[], :eql?, o]) + o.singleton_class.remove_method(:eql?) + def o.eql?(x); false; end + assert_not_send([@cls[], :eql?, o]) + end + + def test_hash2 + 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 = @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_update5 + h = @cls[a: 1, b: 2, c: 3] + assert_raise(FrozenError) do + h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3], h) + + h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + h.update({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h) + end + + def test_update_modify_in_block + a = @cls[] + (1..1337).each {|k| a[k] = k} + b = {1=>1338} + assert_raise_with_message(RuntimeError, /rehash during iteration/) do + a.update(b) {|k, o, n| + a.rehash + } + end + end + + def test_update_on_identhash + key = +'a' + i = @cls[].compare_by_identity + i[key] = 0 + h = @cls[].update(i) + key.upcase! + assert_equal(0, h.fetch('a')) + end + + def test_merge + h1 = @cls[1=>2, 3=>4] + h2 = {1=>3, 5=>7} + h3 = {1=>1, 2=>4} + assert_equal({1=>2, 3=>4}, h1.merge()) + assert_equal({1=>3, 3=>4, 5=>7}, h1.merge(h2)) + assert_equal({1=>6, 3=>4, 5=>7}, h1.merge(h2) {|k, v1, v2| k + v1 + v2 }) + assert_equal({1=>1, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3)) + assert_equal({1=>8, 2=>4, 3=>4, 5=>7}, h1.merge(h2, h3) {|k, v1, v2| k + v1 + v2 }) + end + + def test_merge_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + expected = h.dup + expected[7] = 8 + h2 = h.merge(7=>8) + assert_equal(expected, h2) + assert_predicate(h2, :compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_predicate(h2, :compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h1 = @cls[7=>8] + h1.compare_by_identity + h2 = h.merge(7=>8) + assert_equal(h1, h2) + assert_predicate(h2, :compare_by_identity?) + h2 = h.merge({}) + assert_equal(h, h2) + assert_predicate(h2, :compare_by_identity?) + end + + def test_merge! + h = @cls[a: 1, b: 2, c: 3] + assert_raise(FrozenError) do + h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3], h) + + h = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + h.merge!({a: 10, b: 20}){ |key, v1, v2| key == :b && h.freeze; v2 } + end + assert_equal(@cls[a: 10, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], h) + end + + def test_assoc + 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], @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]], @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 + omit 'requires callcc support' unless respond_to?(:callcc) + + h = @cls[1=>2] + c = nil + f = false + h.each { callcc {|c2| c = c2 } } + unless f + f = true + c.call + end + assert_raise(RuntimeError) { h.each { h.rehash } } + + h = @cls[1=>2] + c = nil + assert_raise(RuntimeError) do + h.each { callcc {|c2| c = c2 } } + h.clear + c.call + end + end + + def test_callcc_iter_level + omit 'requires callcc support' unless respond_to?(:callcc) + + 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 + omit 'requires callcc support' unless respond_to?(:callcc) + + 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 + omit 'requires callcc support' unless respond_to?(:callcc) + + 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_not_predicate(@cls[], :compare_by_identity?) + h = @cls[a => "bar"] + assert_not_predicate(h, :compare_by_identity?) + 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_compare_by_identy_memory_leak + assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20145]", rss: true) + begin; + h = { 1 => 2 }.compare_by_identity + 1_000_000.times do + h.select { false } + end + end; + 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 + def initialize(value, hash) + @value = value + @hash = hash + end + attr_reader :value, :hash + + def eql?(other) + @value == other.value + end + end + + def test_hash_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, @cls[o2=>2,o1=>1].hash) + end + + def test_hash_bignum_hash + x = 2<<(32-3)-1 + assert_equal({x=>1}.hash, @cls[x=>1].hash) + x = 2<<(64-3)-1 + assert_equal({x=>1}.hash, @cls[x=>1].hash) + + o = Object.new + def o.hash; 2 << 100; end + assert_equal({o=>1}.hash, @cls[o=>1].hash) + end + + def test_hash_popped + assert_nothing_raised { eval("a = 1; @cls[a => a]; a") } + end + + def test_recursive_key + 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_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) {@cls[foo: o].dig(:foo, :foo)} + end + + def test_cmp + h1 = @cls[a:1, b:2] + h2 = @cls[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 = @cls[a:1] + h2 = @cls[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 = @cls[ + 1 => 10, + 2 => 20, + 3 => 30, + ] + + assert_equal([10, 20, 30], [1, 2, 3].map(&h)) + + assert_predicate(h.to_proc, :lambda?) + 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) + + assert_equal({A: 1, B: 2, c: 3}, x.transform_keys({a: :A, b: :B, d: :D})) + assert_equal({A: 1, B: 2, "c" => 3}, x.transform_keys({a: :A, b: :B, d: :D}, &:to_s)) + end + + def test_transform_keys_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + h2 = h.transform_keys(&:itself) + assert_equal(Hash[h.to_a], h2) + assert_equal(false, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.transform_keys(&:itself) + assert_equal({}, h2) + assert_equal(false, h2.compare_by_identity?) + end + + def test_transform_keys_bang + 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[a: 1, b: 2, c: 3] + x.transform_keys! { |k| k == :b && break } + assert_equal({false => 1, b: 2, c: 3}, x) + + x = @cls[true => :a, false => :b] + x.transform_keys! {|k| !k } + assert_equal([false, :a, true, :b], x.flatten) + + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys!({a: :A, b: :B, d: :D}) + assert_equal({A: 1, B: 2, c: 3}, x) + x = @cls[a: 1, b: 2, c: 3] + x.transform_keys!({a: :A, b: :B, d: :D}, &:to_s) + assert_equal({A: 1, B: 2, "c" => 3}, x) + end + + def test_transform_values + x = @cls[a: 1, b: 2, c: 3] + x.default = 42 + y = x.transform_values {|v| v ** 2 } + assert_equal([1, 4, 9], y.values_at(:a, :b, :c)) + assert_not_same(x, y) + assert_nil(y.default) + + x.default_proc = proc {|h, k| k} + y = x.transform_values {|v| v ** 2 } + assert_nil(y.default_proc) + assert_nil(y.default) + + y = x.transform_values.with_index {|v, i| "#{v}.#{i}" } + assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c)) + end + + def test_transform_values_on_identhash + h = @cls[1=>2,3=>4,5=>6] + h.compare_by_identity + str1 = +'str' + str2 = +'str' + h[str1] = 1 + h[str2] = 2 + h2 = h.transform_values(&:itself) + assert_equal(h, h2) + assert_equal(true, h2.compare_by_identity?) + + h = @cls[] + h.compare_by_identity + h2 = h.transform_values(&:itself) + assert_equal({}.compare_by_identity, h2) + assert_equal(true, h2.compare_by_identity?) + end + + def test_transform_values_bang + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_values! {|v| v ** 2 } + assert_equal([1, 4, 9], y.values_at(:a, :b, :c)) + assert_same(x, y) + + x = @cls[a: 1, b: 2, c: 3] + x.transform_values! { |v| v == 2 && break } + assert_equal({a: false, b: 2, c: 3}, x) + + x = @cls[a: 1, b: 2, c: 3] + y = x.transform_values!.with_index {|v, i| "#{v}.#{i}" } + assert_equal(%w(1.0 2.1 3.2), y.values_at(:a, :b, :c)) + + x = @cls[a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10] + assert_raise(FrozenError) do + x.transform_values!() do |v| + x.freeze if v == 2 + v.succ + end + end + assert_equal(@cls[a: 2, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10], x) + + x = (1..1337).to_h {|k| [k, k]} + assert_raise_with_message(RuntimeError, /rehash during iteration/) do + x.transform_values! {|v| + x.rehash if v == 1337 + v * 2 + } + end + end + + def hrec h, n, &b + if n > 0 + h.each{hrec(h, n-1, &b)} + else + yield + end + end + + def test_huge_iter_level + nrec = 200 + + h = @cls[a: 1] + hrec(h, nrec){} + h[:c] = 3 + assert_equal(3, h[:c]) + + h = @cls[a: 1] + h.freeze # set hidden attribute for a frozen object + hrec(h, nrec){} + assert_equal(1, h.size) + + h = @cls[a: 1] + assert_raise(RuntimeError){ + hrec(h, nrec){ h[:c] = 3 } + } + rescue SystemStackError + # ignore + end + + # Previously this test would fail because rb_hash inside opt_aref would look + # at the current method name + def test_hash_recursion_independent_of_mid + o = Class.new do + def hash(h, k) + h[k] + end + + def any_other_name(h, k) + h[k] + end + end.new + + rec = []; rec << rec + + h = @cls[] + h[rec] = 1 + assert o.hash(h, rec) + assert o.any_other_name(h, rec) + end + + class TestSubHash < TestHash + class SubHash < Hash + end + + def setup + @cls = SubHash + super + end + end +end + +class TestHashOnly < Test::Unit::TestCase + 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_dup_will_not_rehash + assert_hash_does_not_rehash(&:dup) + end + + def assert_hash_does_not_rehash + obj = Object.new + class << obj + attr_accessor :hash_calls + def hash + @hash_calls += 1 + super + end + end + obj.hash_calls = 0 + hash = {obj => 42} + assert_equal(1, obj.hash_calls) + yield hash + assert_equal(1, obj.hash_calls) + end + + def test_select_reject_will_not_rehash + assert_hash_does_not_rehash do |hash| + hash.select { true } + end + assert_hash_does_not_rehash do |hash| + hash.reject { false } + end + end + + def test_st_literal_memory_leak + assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", rss: true) + begin; + 1_000_000.times do + # >8 element hashes are ST allocated rather than AR allocated + {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9} + end + end; + end + + def test_compare_by_id_memory_leak + assert_no_memory_leak([], "", <<~RUBY, rss: true) + 1_000_000.times do + {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8}.compare_by_identity + end + RUBY + 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_fstring_key + # warmup ObjectSpace.count_objects + ObjectSpace.count_objects + + h = {"abc" => 1} + + EnvUtil.without_gc do + before = ObjectSpace.count_objects[:T_STRING] + 5.times{ h["abc".freeze] } + assert_equal before, ObjectSpace.count_objects[:T_STRING] + end + end + + def test_AREF_fstring_key_default_proc + assert_separately(['--disable-frozen-string-literal'], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + h = Hash.new do |h, k| + k.frozen? + end + + str = "foo" + refute str.frozen? + refute h[str] + refute h["foo"] + end; + 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_rehash_memory_leak + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + ar_hash = 1.times.map { |i| [i, i] }.to_h + st_hash = 10.times.map { |i| [i, i] }.to_h + + code = proc do + ar_hash.rehash + st_hash.rehash + end + 1_000.times(&code) + PREP + 1_000_000.times(&code) + CODE + 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_replace_st_with_ar + # ST hash + h1 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 } + # AR hash + h2 = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } + # Replace ST hash with AR hash + h1.replace(h2) + assert_equal(h2, h1) + 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_initialize_wrong_arguments + assert_raise(ArgumentError) do + Hash.new(0) { } + end + end + + def test_replace_memory_leak + assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + h = ("aa".."zz").each_with_index.to_h + 10_000.times {h.dup} + begin; + 500_000.times {h.dup.replace(h)} + end; + end + + def hash_iter_recursion(h, level) + return if level == 0 + h.each_key {} + h.each_value { hash_iter_recursion(h, level - 1) } + end + + def test_iterlevel_in_ivar_bug19589 + h = { a: nil } + # Recursion level should be over 127 to actually test iterlevel being set in an instance variable, + # but it should be under 131 not to overflow the stack under MN threads/ractors. + hash_iter_recursion(h, 130) + assert true + end + + def test_exception_in_rehash_memory_leak + 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_memory_size_after_delete + require 'objspace' + h = {} + 1000.times {|i| h[i] = true} + big = ObjectSpace.memsize_of(h) + 1000.times {|i| h.delete(i)} + assert_operator ObjectSpace.memsize_of(h), :<, big/10 + end + + def test_wrapper + bug9381 = '[ruby-core:59638] [Bug #9381]' + + 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 + 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_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 + + ruby2_keywords def get_flagged_hash(*args) + args.last + end + + def check_flagged_hash(k: :NG) + k + end + + def test_ruby2_keywords_hash? + flagged_hash = get_flagged_hash(k: 1) + assert_equal(true, Hash.ruby2_keywords_hash?(flagged_hash)) + assert_equal(false, Hash.ruby2_keywords_hash?({})) + assert_raise(TypeError) { Hash.ruby2_keywords_hash?(1) } + end + + def test_ruby2_keywords_hash + hash = {k: 1} + assert_equal(false, Hash.ruby2_keywords_hash?(hash)) + hash = Hash.ruby2_keywords_hash(hash) + assert_equal(true, Hash.ruby2_keywords_hash?(hash)) + assert_equal(1, check_flagged_hash(*[hash])) + assert_raise(TypeError) { Hash.ruby2_keywords_hash(1) } + end + + def ar2st_object + class << (obj = Object.new) + attr_reader :h + end + obj.instance_variable_set(:@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 + obj + end + + def test_ar2st_insert + obj = ar2st_object + h = obj.h + + 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 + end + + def test_ar2st_delete + obj = ar2st_object + h = obj.h + + 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 + end + + def test_ar2st_lookup + obj = ar2st_object + h = obj.h + + obj2 = Object.new + def obj2.hash + 0 + end + + h[obj2] = true + assert_equal true, h[obj] + end + + def test_bug_12706 + assert_raise(ArgumentError) do + {a: 1}.each(&->(k, v) {}) + end + end + + def test_bug_21357 + h = {x: []}.merge(x: nil) { |_k, v1, _v2| v1 } + assert_equal({x: []}, h) + end + + def test_any_hash_fixable + 20.times do + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + require "delegate" + typename = DelegateClass(String) + + hash = { + "Int" => true, + "Float" => true, + "String" => true, + "Boolean" => true, + "WidgetFilter" => true, + "WidgetAggregation" => true, + "WidgetEdge" => true, + "WidgetSortOrder" => true, + "WidgetGrouping" => true, + } + + hash.each_key do |key| + assert_send([hash, :key?, typename.new(key)]) + end + end; + end + end + + def test_compare_by_identity_during_iteration + h = { 1 => 1 } + h.each do + assert_raise(RuntimeError, "compare_by_identity during iteration") do + h.compare_by_identity + end + end + end + + def test_ar_hash_to_st_hash + assert_normal_exit("#{<<~"begin;"}\n#{<<~'end;'}", 'https://bugs.ruby-lang.org/issues/20050#note-5') + begin; + srand(0) + class Foo + def to_a + [] + end + + def hash + $h.delete($h.keys.sample) if rand < 0.1 + to_a.hash + end + end + + 1000.times do + $h = {} + (0..10).each {|i| $h[Foo.new] ||= {} } + end + end; + end + + def test_ar_to_st_reserved_value + klass = Class.new do + attr_reader :hash + def initialize(val) = @hash = val + end + + values = 0.downto(-16).to_a + hash = {} + values.each do |val| + hash[klass.new(val)] = val + end + assert_equal values, hash.values, "[ruby-core:121239] [Bug #21170]" + end +end diff --git a/test/ruby/test_ifunless.rb b/test/ruby/test_ifunless.rb new file mode 100644 index 0000000000..f68e5154a2 --- /dev/null +++ b/test/ruby/test_ifunless.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: false +require 'test/unit' + +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 + end + assert(!bad) + assert(unless x != x then true else false end) + end +end diff --git a/test/ruby/test_inlinecache.rb b/test/ruby/test_inlinecache.rb new file mode 100644 index 0000000000..d48d95d74e --- /dev/null +++ b/test/ruby/test_inlinecache.rb @@ -0,0 +1,110 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +require 'test/unit' + +class TestMethodInlineCache < Test::Unit::TestCase + def test_alias + m0 = Module.new do + def foo; :M0 end + end + m1 = Module.new do + include m0 + end + c = Class.new do + include m1 + alias bar foo + end + d = Class.new(c) do + end + + test = -> do + d.new.bar + end + + assert_equal :M0, test[] + + c.class_eval do + def bar + :C + end + end + + assert_equal :C, test[] + end + + def test_zsuper + assert_separately [], <<-EOS + class C + private def foo + :C + end + end + + class D < C + public :foo + end + + class E < D; end + class F < E; end + + test = -> do + F.new().foo + end + + assert_equal :C, test[] + + class E + def foo; :E; end + end + + assert_equal :E, test[] + EOS + end + + def test_module_methods_redefiniton + m0 = Module.new do + def foo + super + end + end + + c1 = Class.new do + def foo + :C1 + end + end + + c2 = Class.new do + def foo + :C2 + end + end + + d1 = Class.new(c1) do + include m0 + end + + d2 = Class.new(c2) do + include m0 + end + + assert_equal :C1, d1.new.foo + + m = Module.new do + def foo + super + end + end + + d1.class_eval do + include m + end + + d2.class_eval do + include m + end + + assert_equal :C2, d2.new.foo + end +end diff --git a/test/ruby/test_insns_leaf.rb b/test/ruby/test_insns_leaf.rb new file mode 100644 index 0000000000..9c9a4324cb --- /dev/null +++ b/test/ruby/test_insns_leaf.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestInsnsLeaf < Test::Unit::TestCase + require "set" + + class Id + attr_reader :db_id + def initialize(db_id) + @db_id = db_id + end + + def ==(other) + other.class == self.class && other.db_id == db_id + end + alias_method :eql?, :== + + def hash + 10 + end + + def <=>(other) + db_id <=> other.db_id if other.is_a?(self.class) + end + end + + class Namespace + IDS = Set[ + Id.new(1).freeze, + Id.new(2).freeze, + Id.new(3).freeze, + Id.new(4).freeze, + ].freeze + + class << self + def test?(id) + IDS.include?(id) + end + end + end + + def test_insns_leaf + assert Namespace.test?(Id.new(1)), "IDS should include 1" + assert !Namespace.test?(Id.new(5)), "IDS should not include 5" + end +end diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb new file mode 100644 index 0000000000..f9bf4fa20c --- /dev/null +++ b/test/ruby/test_integer.rb @@ -0,0 +1,777 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestInteger < Test::Unit::TestCase + FIXNUM_MIN = RbConfig::LIMITS['FIXNUM_MIN'] + FIXNUM_MAX = RbConfig::LIMITS['FIXNUM_MAX'] + LONG_MAX = RbConfig::LIMITS['LONG_MAX'] + + def test_aref + + [ + *-16..16, + *(FIXNUM_MIN-2)..(FIXNUM_MIN+2), + *(FIXNUM_MAX-2)..(FIXNUM_MAX+2), + ].each do |n| + (-64..64).each do |idx| + assert_equal((n >> idx) & 1, n[idx]) + end + [*-66..-62, *-34..-30, *-5..5, *30..34, *62..66].each do |idx| + (0..100).each do |len| + assert_equal((n >> idx) & ((1 << len) - 1), n[idx, len], "#{ n }[#{ idx }, #{ len }]") + end + (0..100).each do |len| + assert_equal((n >> idx) & ((1 << (len + 1)) - 1), n[idx..idx+len], "#{ n }[#{ idx }..#{ idx+len }]") + assert_equal((n >> idx) & ((1 << len) - 1), n[idx...idx+len], "#{ n }[#{ idx }...#{ idx+len }]") + end + + # endless + assert_equal((n >> idx), n[idx..], "#{ n }[#{ idx }..]") + assert_equal((n >> idx), n[idx...], "#{ n }[#{ idx }...#]") + + # beginless + if idx >= 0 && n & ((1 << (idx + 1)) - 1) != 0 + assert_raise(ArgumentError, "#{ n }[..#{ idx }]") { n[..idx] } + else + assert_equal(0, n[..idx], "#{ n }[..#{ idx }]") + end + if idx >= 0 && n & ((1 << idx) - 1) != 0 + assert_raise(ArgumentError, "#{ n }[...#{ idx }]") { n[...idx] } + else + assert_equal(0, n[...idx], "#{ n }[...#{ idx }]") + end + end + end + + # assert_equal(1, (1 << 0x40000000)[0x40000000], "[ruby-dev:31271]") + # assert_equal(0, (-1 << 0x40000001)[0x40000000], "[ruby-dev:31271]") + big_zero = 0x40000000.coerce(0)[0] + assert_equal(0, (-0x40000002)[big_zero], "[ruby-dev:31271]") + assert_equal(1, 0x400000001[big_zero], "[ruby-dev:31271]") + end + + def test_pow + assert_not_equal(0, begin + 0**-1 + rescue + nil + end, "[ruby-dev:32084] [ruby-dev:34547]") + + assert_raise(ArgumentError) {2 ** -0x4000000000000000} + + <<~EXPRS.each_line.with_index(__LINE__+1) do |expr, line| + crash01: 111r+11**-11111161111111 + crash02: 1118111111111**-1111111111111111**1+1==11111 + crash03: -1111111**-1111*11 - -11** -1111111 + 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 - -11** -1111111 + 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", "begin; #{ expr }; rescue ArgumentError; end", name) + end + end + + def test_lshift + assert_equal(0, 1 << -0x40000000) + assert_equal(0, 1 << -0x40000001) + assert_equal(0, 1 << -0x80000000) + assert_equal(0, 1 << -0x80000001) + + char_bit = RbConfig::LIMITS["UCHAR_MAX"].bit_length + size_max = RbConfig::LIMITS["SIZE_MAX"] + size_bit_max = size_max * char_bit + assert_raise_with_message(RangeError, /shift width/) { + 1 << size_bit_max + } + end + + def test_rshift + assert_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 + + def test_Integer + assert_raise(ArgumentError) {Integer("0x-1")} + assert_raise(ArgumentError) {Integer("-0x-1")} + assert_raise(ArgumentError) {Integer("0x 123")} + assert_raise(ArgumentError) {Integer("0x 123")} + assert_raise(ArgumentError) {Integer("0x0x5")} + assert_raise(ArgumentError) {Integer("0x0x000000005")} + assert_nothing_raised(ArgumentError) { + assert_equal(1540841, "0x0x5".to_i(36)) + } + assert_raise(ArgumentError) { Integer("--0") } + assert_raise(ArgumentError) { Integer("-+0") } + assert_raise(ArgumentError) { Integer("++1") } + assert_raise(ArgumentError) { Integer("") } + assert_raise(ArgumentError) { Integer("10 x") } + assert_raise(ArgumentError) { Integer("1__2") } + assert_raise(ArgumentError) { Integer("1z") } + assert_raise(ArgumentError) { Integer("46116860184273__87904") } + assert_raise(ArgumentError) { Integer("4611686018427387904_") } + assert_raise(ArgumentError) { Integer("4611686018427387904 :") } + assert_equal(0x4000000000000000, Integer("46_11_686_0184273_87904")) + assert_raise(ArgumentError) { Integer("\0") } + assert_nothing_raised(ArgumentError, "[ruby-core:13873]") { + assert_equal(0, Integer("0 ")) + } + assert_nothing_raised(ArgumentError, "[ruby-core:14139]") { + assert_equal(0377, Integer("0_3_7_7")) + } + assert_raise(ArgumentError, "[ruby-core:14139]") {Integer("0__3_7_7")} + assert_equal(1234, Integer(1234)) + assert_equal(1, Integer(1.234)) + + assert_equal(2 ** 50, Integer(2.0 ** 50)) + assert_raise(TypeError) { Integer(nil) } + + bug14552 = '[ruby-core:85813]' + obj = Object.new + def obj.to_int; "str"; end + assert_raise(TypeError, bug14552) { Integer(obj) } + def obj.to_i; 42; end + assert_equal(42, Integer(obj), bug14552) + + obj = Object.new + def obj.to_i; "str"; end + assert_raise(TypeError) { Integer(obj) } + + bug6192 = '[ruby-core:43566]' + assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16be"))} + assert_raise(Encoding::CompatibilityError, bug6192) {Integer("0".encode("utf-16le"))} + 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"))} + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{1f4a1}/) {Integer("\u{1f4a1}")} + end + + 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_Integer_with_invalid_exception + assert_raise(ArgumentError) { + Integer("0", exception: 1) + } + end + + def test_Integer_with_exception_keyword + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer("1z", exception: false)) + } + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Integer(Object.new, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; 42.5; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_i; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(ArgumentError) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Integer(o, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(-Float::INFINITY, exception: false)) + } + assert_nothing_raised(FloatDomainError) { + assert_equal(nil, Integer(Float::NAN, exception: false)) + } + + assert_raise(ArgumentError) { + Integer("1z", exception: true) + } + assert_raise(TypeError) { + Integer(nil, exception: true) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Integer(nil, exception: false)) + } + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Integer;def method_missing(*);"";end;end + assert_equal(0, Integer("0", 2)) + end; + end + + def test_Integer_when_to_str + def (obj = Object.new).to_str + "0x10" + end + assert_equal(16, Integer(obj)) + end + + def test_Integer_with_base + assert_equal(1234, Integer("1234", 10)) + assert_equal(668, Integer("1234", 8)) + assert_equal(4660, Integer("1234", 16)) + assert_equal(49360, Integer("1234", 36)) + # decimal, not octal + assert_equal(1234, Integer("01234", 10)) + assert_raise(ArgumentError) { Integer("0x123", 10) } + assert_raise(ArgumentError) { Integer(1234, 10) } + assert_raise(ArgumentError) { Integer(12.34, 10) } + assert_raise(ArgumentError) { Integer(Object.new, 1) } + + assert_raise(ArgumentError) { Integer(1, 1, 1) } + + def (base = Object.new).to_int + 8 + end + assert_equal(8, Integer("10", base)) + + assert_raise(TypeError) { Integer("10", "8") } + def (base = Object.new).to_int + "8" + end + assert_raise(TypeError) { Integer("10", base) } + 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 + assert_equal("a", "a".ord.chr) + assert_raise(RangeError) { (-1).chr } + assert_raise(RangeError) { 0x100.chr } + assert_raise_with_message(RangeError, "3000000000 out of char range") { 3_000_000_000.chr } + end + + def test_upto + a = [] + assert_equal(1, 1.upto(3) {|x| a << x }) + assert_equal([1, 2, 3], a) + + a = [] + assert_equal(1, 1.upto(0) {|x| a << x }) + assert_equal([], a) + + y = 2**30 - 1 + a = [] + assert_equal(y, y.upto(y+2) {|x| a << x }) + assert_equal([y, y+1, y+2], a) + end + + def test_downto + a = [] + assert_equal(-1, -1.downto(-3) {|x| a << x }) + assert_equal([-1, -2, -3], a) + + a = [] + assert_equal(1, 1.downto(2) {|x| a << x }) + assert_equal([], a) + + y = -(2**30) + a = [] + assert_equal(y, y.downto(y-2) {|x| a << x }) + assert_equal([y, y-1, y-2], a) + end + + def test_times + (2**32).times do |i| + break if i == 2 + end + end + + def test_times_bignum_redefine_plus_lt + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + called = false + Integer.class_eval do + alias old_succ succ + undef succ + define_method(:succ){|x| called = true; x+1} + alias old_lt < + undef < + define_method(:<){|x| called = true} + end + + fix = 1 + fix.times{break 0} + fix_called = called + + called = false + + big = 2**65 + big.times{break 0} + big_called = called + + Integer.class_eval do + undef succ + alias succ old_succ + undef < + alias < old_lt + end + + # Asssert that Fixnum and Bignum behave consistently + bug18377 = "[ruby-core:106361]" + assert_equal(fix_called, big_called, bug18377) + 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_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)) + + assert_int_equal(-10000000000, -1.floor(-10), "[Bug #20654]") + assert_int_equal(-100000000000000000000, -1.floor(-20), "[Bug #20654]") + assert_int_equal(-100000000000000000000000000000000000000000000000000, -1.floor(-50), "[Bug #20654]") + end + + def test_ceil + 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)) + + assert_int_equal(10000000000, 1.ceil(-10), "[Bug #20654]") + assert_int_equal(100000000000000000000, 1.ceil(-20), "[Bug #20654]") + assert_int_equal(100000000000000000000000000000000000000000000000000, 1.ceil(-50), "[Bug #20654]") + end + + def test_truncate + 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)) + assert_equal((2 ** 1024).to_s(7).chars.map(&:to_i).reverse, (2 ** 1024).digits(7)) + assert_equal([0] * 100 + [1], (2 ** (128 * 100)).digits(2 ** 128)) + end + + def test_digits_for_negative_numbers + 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_bug_21217 + assert_equal(0x10000 * 2**10, Integer.sqrt(0x100000008 * 2**20)) + end + + def test_fdiv + assert_equal(1.0, 1.fdiv(1)) + assert_equal(0.5, 1.fdiv(2)) + + m = 50 << Float::MANT_DIG + prev = 1.0 + (1..100).each do |i| + val = (m + i).fdiv(m) + assert_operator val, :>=, prev, "1+epsilon*(#{i}/100)" + prev = val + end + 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 + + def test_try_convert + assert_equal(1, Integer.try_convert(1)) + assert_equal(1, Integer.try_convert(1.0)) + assert_nil Integer.try_convert("1") + o = Object.new + assert_nil Integer.try_convert(o) + def o.to_i; 1; end + assert_nil Integer.try_convert(o) + o = Object.new + def o.to_int; 1; end + assert_equal(1, Integer.try_convert(o)) + + o = Object.new + def o.to_int; Object.new; end + assert_raise_with_message(TypeError, /can't convert Object to Integer/) {Integer.try_convert(o)} + end + + def test_ceildiv + assert_equal(0, 0.ceildiv(3)) + assert_equal(1, 1.ceildiv(3)) + assert_equal(1, 3.ceildiv(3)) + assert_equal(2, 4.ceildiv(3)) + + assert_equal(-1, 4.ceildiv(-3)) + assert_equal(-1, -4.ceildiv(3)) + assert_equal(2, -4.ceildiv(-3)) + + assert_equal(3, 3.ceildiv(1.2)) + assert_equal(3, 3.ceildiv(6/5r)) + + assert_equal(10, (10**100-11).ceildiv(10**99-1)) + assert_equal(11, (10**100-9).ceildiv(10**99-1)) + + o = Object.new + def o.coerce(other); [other, 10]; end + assert_equal(124, 1234.ceildiv(o)) + end +end diff --git a/test/ruby/test_integer_comb.rb b/test/ruby/test_integer_comb.rb new file mode 100644 index 0000000000..150f45cfd7 --- /dev/null +++ b/test/ruby/test_integer_comb.rb @@ -0,0 +1,647 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestIntegerComb < Test::Unit::TestCase + VS = [ + -0x1000000000000000000000000000000000000000000000002, + -0x1000000000000000000000000000000000000000000000001, + -0x1000000000000000000000000000000000000000000000000, + -0xffffffffffffffffffffffffffffffffffffffffffffffff, + -0x1000000000000000000000002, + -0x1000000000000000000000001, + -0x1000000000000000000000000, + -0xffffffffffffffffffffffff, + -0x10000000000000002, + -0x10000000000000001, + -0x10000000000000000, + -0xffffffffffffffff, + -0x4000000000000002, + -0x4000000000000001, + -0x4000000000000000, + -0x3fffffffffffffff, + -0x100000002, + -0x100000001, + -0x100000000, + -0xffffffff, + -0xc717a08d, # 0xc717a08d * 0x524b2245 = 0x4000000000000001 + -0x80000002, + -0x80000001, + -0x80000000, + -0x7fffffff, + -0x524b2245, + -0x40000002, + -0x40000001, + -0x40000000, + -0x3fffffff, + -0x10002, + -0x10001, + -0x10000, + -0xffff, + -0x8101, # 0x8101 * 0x7f01 = 0x40000001 + -0x8002, + -0x8001, + -0x8000, + -0x7fff, + -0x7f01, + -65, + -64, + -63, + -62, + -33, + -32, + -31, + -30, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 30, + 31, + 32, + 33, + 62, + 63, + 64, + 65, + 0x7f01, + 0x7ffe, + 0x7fff, + 0x8000, + 0x8001, + 0x8101, + 0xfffe, + 0xffff, + 0x10000, + 0x10001, + 0x3ffffffe, + 0x3fffffff, + 0x40000000, + 0x40000001, + 0x524b2245, + 0x7ffffffe, + 0x7fffffff, + 0x80000000, + 0x80000001, + 0xc717a08d, + 0xfffffffe, + 0xffffffff, + 0x100000000, + 0x100000001, + 0x3ffffffffffffffe, + 0x3fffffffffffffff, + 0x4000000000000000, + 0x4000000000000001, + 0xfffffffffffffffe, + 0xffffffffffffffff, + 0x10000000000000000, + 0x10000000000000001, + 0xffffffffffffffffffffffff, + 0x1000000000000000000000000, + 0x1000000000000000000000001, + 0xffffffffffffffffffffffffffffffffffffffffffffffff, + 0x1000000000000000000000000000000000000000000000000, + 0x1000000000000000000000000000000000000000000000001 + ] + + #VS.map! {|v| 0x4000000000000000.coerce(v)[0] } + #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| + 100.times {|i| + assert_equal((a >> i).odd? ? 1 : 0, a[i], "(#{a})[#{i}]") + } + } + VS.each {|a| + VS.each {|b| + c = nil + assert_nothing_raised("(#{a})[#{b}]") { c = a[b] } + assert_kind_of(Integer, c) + if b < 0 + assert_equal(0, c, "(#{a})[#{b}]") + else + assert_equal((a >> b).odd? ? 1 : 0, c, "(#{a})[#{b}]") + end + } + } + end + + def test_plus + VS.each {|a| + VS.each {|b| + c = a + b + 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 + assert_equal((a^b)+2*(a&b), c, "#{a} + #{b}") # Hacker's Delight + assert_equal((a|b)+(a&b), c, "#{a} + #{b}") # Hacker's Delight + assert_equal(2*(a|b)-(a^b), c, "#{a} + #{b}") # Hacker's Delight + } + } + end + + def test_minus + VS.each {|a| + VS.each {|b| + c = a - b + 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 + assert_equal((a^b)-2*(b&~a), c, "#{a} - #{b}") # Hacker's Delight + assert_equal((a&~b)-(b&~a), c, "#{a} - #{b}") # Hacker's Delight + assert_equal(2*(a&~b)-(a^b), c, "#{a} - #{b}") # Hacker's Delight + } + } + end + + def test_mult + VS.each {|a| + VS.each {|b| + c = a * b + 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}") + assert_equal((a+100)*(b+100)-(a+100)*100-(b+100)*100+10000, c, "#{a} * #{b}") + } + } + end + + def test_divmod + VS.each {|a| + VS.each {|b| + if b == 0 + assert_raise(ZeroDivisionError) { a.divmod(b) } + else + q, r = a.divmod(b) + assert_kind_of(Integer, q) + assert_kind_of(Integer, r) + assert_equal(a, b*q+r) + assert_operator(r.abs, :<, b.abs) + if 0 < b + 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)) + end + } + } + end + + def test_pow + small_values = VS.find_all {|v| 0 <= v && v < 1000 } + VS.each {|a| + small_values.each {|b| + c = a ** b + assert_kind_of(Integer, c) + d = 1 + b.times { d *= a } + assert_equal(d, c, "(#{a}) ** #{b}") + if a != 0 + d = c + b.times { d /= a } + assert_equal(1, d, "((#{a}) ** #{b}) / #{a} / ...(#{b} times)...") + end + } + } + end + + def test_not + VS.each {|a| + b = ~a + 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}") + assert_equal(-1, a | b, "#{a} | ~#{a}") + } + end + + def test_or + VS.each {|a| + VS.each {|b| + c = a | b + 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 + assert_equal(-1, c | ~a, "(#{a} | #{b}) | ~#{a})") + } + } + end + + def test_and + VS.each {|a| + VS.each {|b| + c = a & b + 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 + assert_equal(0, c & ~a, "(#{a} & #{b}) & ~#{a}") + } + } + end + + def test_xor + VS.each {|a| + VS.each {|b| + c = a ^ b + 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}") + } + } + end + + def test_lshift + small_values = VS.find_all {|v| v < 8000 } + VS.each {|a| + small_values.each {|b| + c = a << b + 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.bit_length+10) {|nth| + assert_equal(a[nth-b], c[nth], "(#{a} << #{b})[#{nth}]") + } + } + } + end + + def test_rshift + small_values = VS.find_all {|v| -8000 < v } + VS.each {|a| + small_values.each {|b| + c = a >> b + 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.bit_length+10) {|nth| + assert_equal(a[nth+b], c[nth], "(#{a} >> #{b})[#{nth}]") + } + } + } + end + + def test_succ + VS.each {|a| + b = a.succ + 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") + } + end + + def test_pred + VS.each {|a| + b = a.pred + 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") + } + end + + def test_unary_plus + VS.each {|a| + b = +a + assert_kind_of(Integer, b) + assert_equal(a, b, "+(#{a})") + } + end + + def test_unary_minus + VS.each {|a| + b = -a + assert_kind_of(Integer, b) + assert_equal(0-a, b, "-(#{a})") + assert_equal(~a+1, b, "-(#{a})") + assert_equal(0, a+b, "#{a}+(-(#{a}))") + } + end + + def test_cmp + VS.each_with_index {|a, i| + VS.each_with_index {|b, j| + assert_equal(i <=> j, a <=> b, "#{a} <=> #{b}") + assert_equal(i < j, a < b, "#{a} < #{b}") + assert_equal(i <= j, a <= b, "#{a} <= #{b}") + assert_equal(i > j, a > b, "#{a} > #{b}") + assert_equal(i >= j, a >= b, "#{a} >= #{b}") + } + } + end + + def test_eq + VS.each_with_index {|a, i| + VS.each_with_index {|b, j| + c = a == b + assert_equal(b == a, c, "#{a} == #{b}") + assert_equal(i == j, c, "#{a} == #{b}") + } + } + end + + def test_abs + VS.each {|a| + b = a.abs + assert_kind_of(Integer, b) + if a < 0 + assert_equal(-a, b, "(#{a}).abs") + else + assert_equal(a, b, "(#{a}).abs") + end + } + end + + def test_ceil + VS.each {|a| + b = a.ceil + assert_kind_of(Integer, b) + assert_equal(a, b, "(#{a}).ceil") + } + end + + def test_floor + VS.each {|a| + b = a.floor + assert_kind_of(Integer, b) + assert_equal(a, b, "(#{a}).floor") + } + end + + def test_round + VS.each {|a| + b = a.round + assert_kind_of(Integer, b) + assert_equal(a, b, "(#{a}).round") + } + end + + def test_truncate + VS.each {|a| + b = a.truncate + assert_kind_of(Integer, b) + assert_equal(a, b, "(#{a}).truncate") + } + end + + def test_remainder + coerce = EnvUtil.labeled_class("CoerceNum") do + def initialize(num) + @num = num + end + def coerce(other) + [other, @num] + end + def inspect + "#{self.class.name}(#@num)" + end + alias to_s inspect + end + + VS.each {|a| + (VS + VS.map {|b| [coerce.new(b), b]}).each {|b, i = b| + if i == 0 + assert_raise(ZeroDivisionError) { a.divmod(b) } + else + r = assert_nothing_raised(ArgumentError, "#{a}.remainder(#{b})") {a.remainder(b)} + assert_kind_of(Integer, r) + if a < 0 + assert_operator(-i.abs, :<, r, "#{a}.remainder(#{b})") + assert_operator(0, :>=, r, "#{a}.remainder(#{b})") + elsif 0 < a + assert_operator(0, :<=, r, "#{a}.remainder(#{b})") + assert_operator(i.abs, :>, r, "#{a}.remainder(#{b})") + else + assert_equal(0, r, "#{a}.remainder(#{b})") + end + end + } + } + end + + def test_zero_nonzero + VS.each {|a| + z = a.zero? + n = a.nonzero? + if a == 0 + assert_equal(true, z, "(#{a}).zero?") + assert_equal(nil, n, "(#{a}).nonzero?") + else + assert_equal(false, z, "(#{a}).zero?") + assert_equal(a, n, "(#{a}).nonzero?") + assert_kind_of(Integer, n) + end + assert(z ^ n, "(#{a}).zero? ^ (#{a}).nonzero?") + } + end + + def test_even_odd + VS.each {|a| + e = a.even? + o = a.odd? + assert_equal((a % 2) == 0, e, "(#{a}).even?") + assert_equal((a % 2) == 1, o, "(#{a}).odd") + assert_equal((a & 1) == 0, e, "(#{a}).even?") + assert_equal((a & 1) == 1, o, "(#{a}).odd") + assert(e ^ o, "(#{a}).even? ^ (#{a}).odd?") + } + 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| + s = a.to_s(radix) + b = s.to_i(radix) + assert_equal(a, b, "(#{a}).to_s(#{radix}).to_i(#{radix})") + } + } + end + + def test_printf_x + VS.reverse_each {|a| + s = sprintf("%x", a) + if /\A\.\./ =~ s + b = -($'.tr('0123456789abcdef', 'fedcba9876543210').to_i(16) + 1) + else + b = s.to_i(16) + end + assert_equal(a, b, "sprintf('%x', #{a}) = #{s.inspect}") + } + end + + def test_printf_x_sign + VS.reverse_each {|a| + s = sprintf("%+x", a) + b = s.to_i(16) + assert_equal(a, b, "sprintf('%+x', #{a}) = #{s.inspect}") + s = sprintf("% x", a) + b = s.to_i(16) + assert_equal(a, b, "sprintf('% x', #{a}) = #{s.inspect}") + } + end + + def test_printf_o + VS.reverse_each {|a| + s = sprintf("%o", a) + if /\A\.\./ =~ s + b = -($'.tr('01234567', '76543210').to_i(8) + 1) + else + b = s.to_i(8) + end + assert_equal(a, b, "sprintf('%o', #{a}) = #{s.inspect}") + } + end + + def test_printf_o_sign + VS.reverse_each {|a| + s = sprintf("%+o", a) + b = s.to_i(8) + assert_equal(a, b, "sprintf('%+o', #{a}) = #{s.inspect}") + s = sprintf("% o", a) + b = s.to_i(8) + assert_equal(a, b, "sprintf('% o', #{a}) = #{s.inspect}") + } + end + + def test_printf_b + VS.reverse_each {|a| + s = sprintf("%b", a) + if /\A\.\./ =~ s + b = -($'.tr('01', '10').to_i(2) + 1) + else + b = s.to_i(2) + end + assert_equal(a, b, "sprintf('%b', #{a}) = #{s.inspect}") + } + end + + def test_printf_b_sign + VS.reverse_each {|a| + s = sprintf("%+b", a) + b = s.to_i(2) + assert_equal(a, b, "sprintf('%+b', #{a}) = #{s.inspect}") + s = sprintf("% b", a) + b = s.to_i(2) + assert_equal(a, b, "sprintf('% b', #{a}) = #{s.inspect}") + } + end + + def test_printf_diu + VS.reverse_each {|a| + s = sprintf("%d", a) + b = s.to_i + assert_equal(a, b, "sprintf('%d', #{a}) = #{s.inspect}") + s = sprintf("%i", a) + b = s.to_i + assert_equal(a, b, "sprintf('%i', #{a}) = #{s.inspect}") + s = sprintf("%u", a) + b = s.to_i + assert_equal(a, b, "sprintf('%u', #{a}) = #{s.inspect}") + } + end + + def test_marshal + VS.reverse_each {|a| + s = Marshal.dump(a) + b = Marshal.load(s) + assert_equal(a, b, "Marshal.load(Marshal.dump(#{a}))") + } + end + + def test_pack + %w[c C s S s! S! i I i! I! l L l! L! q Q n N v V].each {|template| + size = [0].pack(template).size + mask = (1 << (size * 8)) - 1 + if /[A-Znv]/ =~ template + min = 0 + max = (1 << (size * 8))-1 + else + min = -(1 << (size * 8 - 1)) + max = (1 << (size * 8 - 1)) - 1 + end + VS.reverse_each {|a| + s = [a].pack(template) + b = s.unpack(template)[0] + if min <= a && a <= max + assert_equal(a, b, "[#{a}].pack(#{template.dump}).unpack(#{template.dump})[0]") + end + assert_operator(min, :<=, b) + assert_operator(b, :<=, max) + assert_equal(a & mask, b & mask, "[#{a}].pack(#{template.dump}).unpack(#{template.dump})[0] & #{mask}") + } + } + end + + def test_pack_ber + template = "w" + VS.reverse_each {|a| + if a < 0 + assert_raise(ArgumentError) { [a].pack(template) } + else + s = [a].pack(template) + b = s.unpack(template)[0] + assert_equal(a, b, "[#{a}].pack(#{template.dump}).unpack(#{template.dump})") + end + } + end + + def test_pack_utf8 + template = "U" + VS.reverse_each {|a| + if a < 0 || 0x7fffffff < a + assert_raise(RangeError) { [a].pack(template) } + else + s = [a].pack(template) + b = s.unpack(template)[0] + assert_equal(a, b, "[#{a}].pack(#{template.dump}).unpack(#{template.dump})") + end + } + end +end diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb new file mode 100644 index 0000000000..1adf47ac51 --- /dev/null +++ b/test/ruby/test_io.rb @@ -0,0 +1,4428 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' +require "fcntl" +require 'io/nonblock' +require 'pathname' +require 'socket' +require 'stringio' +require 'timeout' +require 'tempfile' +require 'weakref' + +class TestIO < Test::Unit::TestCase + 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 + + 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) + [ + Thread.start{ + w.print "abc" + w.close + }, + Thread.start{ + assert_equal("abc", r.read) + r.close + } + ].each{|thr| thr.join} + end + + def test_binmode_pipe + EnvUtil.with_default_internal(Encoding::UTF_8) do + EnvUtil.with_default_external(Encoding::UTF_8) do + begin + reader0, writer0 = IO.pipe + reader0.binmode + writer0.binmode + + reader1, writer1 = IO.pipe + + reader2, writer2 = IO.pipe(binmode: true) + assert_predicate writer0, :binmode? + assert_predicate writer2, :binmode? + assert_equal writer0.binmode?, writer2.binmode? + assert_equal writer0.external_encoding, writer2.external_encoding + assert_equal writer0.internal_encoding, writer2.internal_encoding + assert_predicate reader0, :binmode? + assert_predicate reader2, :binmode? + assert_equal reader0.binmode?, reader2.binmode? + assert_equal reader0.external_encoding, reader2.external_encoding + assert_equal reader0.internal_encoding, reader2.internal_encoding + + reader3, writer3 = IO.pipe("UTF-8:UTF-8", binmode: true) + assert_predicate writer3, :binmode? + assert_equal writer1.external_encoding, writer3.external_encoding + assert_equal writer1.internal_encoding, writer3.internal_encoding + assert_predicate reader3, :binmode? + assert_equal reader1.external_encoding, reader3.external_encoding + assert_equal reader1.internal_encoding, reader3.internal_encoding + + reader4, writer4 = IO.pipe("UTF-8:UTF-8", binmode: true) + assert_predicate writer4, :binmode? + assert_equal writer1.external_encoding, writer4.external_encoding + assert_equal writer1.internal_encoding, writer4.internal_encoding + assert_predicate reader4, :binmode? + assert_equal reader1.external_encoding, reader4.external_encoding + assert_equal reader1.internal_encoding, reader4.internal_encoding + + reader5, writer5 = IO.pipe("UTF-8", "UTF-8", binmode: true) + assert_predicate writer5, :binmode? + assert_equal writer1.external_encoding, writer5.external_encoding + assert_equal writer1.internal_encoding, writer5.internal_encoding + assert_predicate reader5, :binmode? + assert_equal reader1.external_encoding, reader5.external_encoding + assert_equal reader1.internal_encoding, reader5.internal_encoding + ensure + [ + reader0, writer0, + reader1, writer1, + reader2, writer2, + reader3, writer3, + reader4, writer4, + reader5, writer5, + ].compact.map(&:close) + end + end + end + end + + def test_pipe_block + x = nil + ret = IO.pipe {|r, w| + x = [r,w] + assert_instance_of(IO, r) + assert_instance_of(IO, w) + [ + 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_predicate(x[0], :closed?) + assert_predicate(x[1], :closed?) + end + + def test_pipe_block_close + 4.times {|i| + x = nil + IO.pipe {|r, w| + x = [r,w] + r.close if (i&1) == 0 + w.close if (i&2) == 0 + } + assert_predicate(x[0], :closed?) + assert_predicate(x[1], :closed?) + } + end + + def test_gets_rs + rs = ":" + pipe(proc do |w| + w.print "aaa:bbb" + w.close + end, proc do |r| + assert_equal "aaa:", r.gets(rs) + assert_equal "bbb", r.gets(rs) + assert_nil r.gets(rs) + r.close + end) + end + + def test_gets_default_rs + pipe(proc do |w| + w.print "aaa\nbbb\n" + w.close + 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\n", r.gets(nil, chomp: true), "[Bug #18770]") + assert_nil r.gets("") + r.close + end) + end + + def test_gets_chomp_paragraph + pipe(proc do |w| + w.print "a\n\nb\n\n" + w.close + end, proc do |r| + assert_equal "a", r.gets("", chomp: true) + assert_equal "b", r.gets("", chomp: true) + assert_nil r.gets("", chomp: true) + r.close + end) + end + + def test_gets_limit_extra_arg + pipe(proc do |w| + w << "0123456789\n0123456789" + 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 + 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_ungetc_with_seek + make_tempfile {|t| + t.open + t.write('0123456789') + t.rewind + + t.ungetc('a') + t.seek(2, :SET) + + assert_equal('2', t.getc) + } + end + + def test_ungetbyte + 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_ungetbyte_with_seek + make_tempfile {|t| + t.open + t.write('0123456789') + t.rewind + + t.ungetbyte('a'.ord) + t.seek(2, :SET) + + assert_equal('2'.ord, t.getbyte) + } + end + + def test_each_byte + 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_byte_closed + pipe(proc do |w| + w << "abc def" + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_byte {|byte| r.close if byte == 32 } + end + end) + make_tempfile {|t| + File.open(t, 'rt') {|f| + assert_raise(IOError) do + f.each_byte {|c| f.close if c == 10} + end + } + } + end + + def test_each_codepoint + make_tempfile {|t| + bug2959 = '[ruby-core:28650]' + a = "" + File.open(t, 'rt') {|f| + f.each_codepoint {|c| a << c} + } + assert_equal("foo\nbar\nbaz\n", a, bug2959) + } + end + + def test_each_codepoint_closed + pipe(proc do |w| + w.print("abc def") + w.close + end, proc do |r| + assert_raise(IOError) do + r.each_codepoint {|c| r.close if c == 32} + end + end) + make_tempfile {|t| + File.open(t, 'rt') {|f| + assert_raise(IOError) do + f.each_codepoint {|c| f.close if c == 10} + end + } + } + end + + def test_each_codepoint_with_ungetc + bug21562 = '[ruby-core:123176] [Bug #21562]' + with_read_pipe("") {|p| + p.binmode + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + with_read_pipe("") {|p| + p.set_encoding("ascii-8bit", universal_newline: true) + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + end + + def test_rubydev33072 + t = make_tempfile + path = t.path + t.close! + assert_raise(Errno::ENOENT, "[ruby-dev:33072]") do + File.read(path, nil, nil, **{}) + end + end + + def with_srccontent(content = "baz") + src = "src" + mkcdtmpdir { + File.open(src, "w") {|f| f << content } + yield src, content + } + 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 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_append_to_nonempty + with_srccontent("foobar") {|src, content| + preface = 'preface' + File.write('dst', preface) + File.open('dst', 'ab') do |dst| + ret = IO.copy_stream(src, dst) + assert_equal(content.bytesize, ret) + assert_equal(preface + content, File.read("dst")) + end + } + end + + def test_copy_stream_smaller + with_srccontent {|src, content| + + # overwrite by smaller file. + 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)) + + ret = IO.copy_stream(src, dst, 2) + assert_equal(2, ret) + assert_equal(content[0,2], File.read(dst)) + + ret = IO.copy_stream(src, dst, 0) + assert_equal(0, ret) + assert_equal("", File.read(dst)) + + ret = IO.copy_stream(src, dst, nil, 1) + assert_equal(content.bytesize-1, ret) + 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") + } + } + end + + 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) } + } + } + end + + def with_pipecontent + mkcdtmpdir { + yield "abc" + } + end + + 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) + assert_equal(pipe_content.bytesize, ret) + assert_equal(pipe_content, File.read(dst)) + } + } + end + + def test_copy_stream_read_pipe + with_pipecontent {|pipe_content| + with_read_pipe(pipe_content) {|r1| + assert_equal("a", r1.getc) + 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(pipe_content) {|r1| + assert_equal("a", r1.getc) + 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(pipe_content) {|r1| + assert_equal("a", r1.getc) + 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(pipe_content) {|r1| + assert_equal("a", r1.getc) + 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(pipe_content) {|r1| + assert_equal("a", r1.getc) + 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) + } + + pipe(proc do |w1| + w1 << "abc" + w1 << "def" + w1.close + end, proc do |r1| + assert_equal("a", r1.getc) + 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 + + 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 + omit "multiple threads already active" if Thread.list.size > 1 + + msg = 'r58534 [ruby-core:80969] [Backport #13533]' + IO.pipe do |r,w| + r.nonblock = true + assert_cpu_usage_low(msg, stop: ->{w.close}) do + IO.copy_stream(r, IO::NULL) + end + end + end + + def test_copy_stream_pipe_nonblock + mkcdtmpdir { + with_read_pipe("abc") {|r1| + assert_equal("a", r1.getc) + with_pipe {|r2, w2| + begin + w2.nonblock = true + rescue Errno::EBADF + omit "nonblocking IO for pipe is not implemented" + end + s = w2.syswrite("a" * 100000) + t = Thread.new { sleep 0.1; r2.read } + ret = IO.copy_stream(r1, w2) + w2.close + assert_equal(2, ret) + assert_equal("a" * s + "bc", t.value) + } + } + } + end + end + + def with_bigcontent + yield "abc" * 123456 + end + + 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 + + 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 + + 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 + + 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) + assert_equal(bigcontent.bytesize-10, ret) + assert_equal(bigcontent[10..-1], File.read("bigdst")) + assert_equal(0, f.pos) + ret = IO.copy_stream(f, "bigdst", 40, 30) + assert_equal(40, ret) + assert_equal(bigcontent[30, 40], File.read("bigdst")) + assert_equal(0, f.pos) + rescue NotImplementedError + #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) } + } + } + end + + def with_megacontent + yield "abc" * 1234567 + end + + 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 + r1.nonblock = true + w2.nonblock = true + rescue Errno::EBADF + omit "nonblocking IO for pipe is not implemented" + end + t1 = Thread.new { w1 << megacontent; w1.close } + t2 = Thread.new { r2.read } + 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 + + 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 } + 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| + 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 + + def test_copy_stream_rbuf + mkcdtmpdir { + begin + 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 + omit "pread(2) is not implemtented." + end + } + end + + def with_socketpair + s1, s2 = UNIXSocket.pair + begin + yield s1, s2 + ensure + s1.close unless s1.closed? + s2.close unless s2.closed? + end + end + + def test_copy_stream_socket1 + with_srccontent("foobar") {|src, content| + with_socketpair {|s1, s2| + ret = IO.copy_stream(src, s1) + assert_equal(content.bytesize, ret) + s1.close + assert_equal(content, s2.read) + } + } + end if defined? UNIXSocket + + def test_copy_stream_socket2 + with_bigsrc {|bigsrc, bigcontent| + with_socketpair {|s1, s2| + 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| + 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 + + def test_copy_stream_socket4 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + + with_bigsrc {|bigsrc, bigcontent| + File.open(bigsrc) {|f| + assert_equal(0, f.pos) + with_socketpair {|s1, s2| + 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 + + def test_copy_stream_socket5 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + + with_bigsrc {|bigsrc, bigcontent| + File.open(bigsrc) {|f| + assert_equal(bigcontent[0,100], f.read(100)) + assert_equal(100, f.pos) + with_socketpair {|s1, s2| + 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 + + def test_copy_stream_socket6 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + + mkcdtmpdir { + megacontent = "abc" * 1234567 + File.open("megasrc", "w") {|f| f << megacontent } + + with_socketpair {|s1, s2| + begin + s1.nonblock = true + rescue Errno::EBADF + omit "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, _ = assert_join_threads([t1, t2]) + assert_equal(megacontent, result) + } + } + end + + def test_copy_stream_socket7 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + + GC.start + mkcdtmpdir { + megacontent = "abc" * 1234567 + File.open("megasrc", "w") {|f| f << megacontent } + + with_socketpair {|s1, s2| + begin + s1.nonblock = true + rescue Errno::EBADF + omit "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 + + def test_copy_stream_strio + src = StringIO.new("abcd") + dst = StringIO.new + ret = IO.copy_stream(src, dst) + assert_equal(4, ret) + assert_equal("abcd", dst.string) + assert_equal(4, src.pos) + end + + def test_copy_stream_strio_len + src = StringIO.new("abcd") + dst = StringIO.new + ret = IO.copy_stream(src, dst, 3) + assert_equal(3, ret) + assert_equal("abc", dst.string) + assert_equal(3, src.pos) + end + + def test_copy_stream_strio_off + src = StringIO.new("abcd") + with_pipe {|r, w| + assert_raise(ArgumentError) { + IO.copy_stream(src, w, 3, 1) + } + } + end + + def test_copy_stream_fname_to_strio + mkcdtmpdir { + File.open("foo", "w") {|f| f << "abcd" } + src = "foo" + dst = StringIO.new + ret = IO.copy_stream(src, dst, 3) + assert_equal(3, ret) + assert_equal("abc", dst.string) + } + end + + def test_copy_stream_strio_to_fname + mkcdtmpdir { + # StringIO to filename + src = StringIO.new("abcd") + ret = IO.copy_stream(src, "fooo", 3) + assert_equal(3, ret) + assert_equal("abc", File.read("fooo")) + assert_equal(3, src.pos) + } + end + + def test_copy_stream_io_to_strio + mkcdtmpdir { + # IO to StringIO + File.open("bar", "w") {|f| f << "abcd" } + File.open("bar") {|src| + dst = StringIO.new + ret = IO.copy_stream(src, dst, 3) + assert_equal(3, ret) + assert_equal("abc", dst.string) + assert_equal(3, src.pos) + } + } + end + + def test_copy_stream_strio_to_io + mkcdtmpdir { + # StringIO to IO + src = StringIO.new("abcd") + ret = File.open("baz", "w") {|dst| + IO.copy_stream(src, dst, 3) + } + assert_equal(3, ret) + assert_equal("abc", File.read("baz")) + assert_equal(3, src.pos) + } + 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_dup_buffer + bug21131 = '[ruby-core:120961] [Bug #21131]' + mkcdtmpdir do + dst_class = Class.new do + def initialize(&block) + @block = block + end + + def write(data) + @block.call(data.dup) + data.bytesize + end + end + + rng = Random.new(42) + body = Tempfile.new("ruby-bug", binmode: true) + body.write(rng.bytes(16_385)) + body.rewind + + payload = [] + IO.copy_stream(body, dst_class.new{payload << it}) + body.rewind + assert_equal(body.read, payload.join, bug21131) + ensure + body&.close + end + end + + def test_copy_stream_write_in_binmode + bug8767 = '[ruby-core:56518] [Bug #8767]' + mkcdtmpdir { + 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 + end + + def readpartial(*args) + ret = @io.readpartial(*args) + ret.tr!('a-zA-Z', 'n-za-mN-ZA-M') + ret + end + + def write(str) + @io.write(str.tr('a-zA-Z', 'n-za-mN-ZA-M')) + end + + def to_io + @io + end + end + + def test_copy_stream_io_to_rot13 + mkcdtmpdir { + File.open("bar", "w") {|f| f << "vex" } + File.open("bar") {|src| + File.open("baz", "w") {|dst0| + dst = Rot13IO.new(dst0) + ret = IO.copy_stream(src, dst, 3) + assert_equal(3, ret) + } + assert_equal("irk", File.read("baz")) + } + } + end + + def test_copy_stream_rot13_to_io + mkcdtmpdir { + File.open("bar", "w") {|f| f << "flap" } + File.open("bar") {|src0| + src = Rot13IO.new(src0) + File.open("baz", "w") {|dst| + ret = IO.copy_stream(src, dst, 4) + assert_equal(4, ret) + } + } + assert_equal("sync", File.read("baz")) + } + end + + def test_copy_stream_rot13_to_rot13 + mkcdtmpdir { + File.open("bar", "w") {|f| f << "bin" } + File.open("bar") {|src0| + src = Rot13IO.new(src0) + File.open("baz", "w") {|dst0| + dst = Rot13IO.new(dst0) + ret = IO.copy_stream(src, dst, 3) + assert_equal(3, ret) + } + } + assert_equal("bin", File.read("baz")) + } + end + + def test_copy_stream_strio_flush + with_pipe {|r, w| + w.sync = false + w.write "zz" + src = StringIO.new("abcd") + IO.copy_stream(src, w) + t1 = Thread.new { + w.close + } + t2 = Thread.new { r.read } + _, result = assert_join_threads([t1, t2]) + assert_equal("zzabcd", result) + } + end + + def test_copy_stream_strio_rbuf + 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 { + pipe(proc do |w| + File.open("foe", "w+") {|f| + f.write "abcd\n" + f.rewind + f.write "xy" + IO.copy_stream(f, w) + } + 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 { + pipe(proc do |w| + w << "xyz" + w.close + end, proc do |r| + File.open("fom", "w+b") {|f| + f.write "abcd\n" + f.rewind + assert_equal("abc", f.read(3)) + f.ungetc "c" + IO.copy_stream(r, f) + } + assert_equal("abxyz", File.read("fom")) + end) + } + end + + 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 + 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 + assert_equal(STDOUT, IO.try_convert(STDOUT)) + assert_equal(nil, IO.try_convert("STDOUT")) + end + + def test_ungetc2 + f = false + pipe(proc do |w| + Thread.pass until f + w.write("1" * 10000) + w.close + end, proc do |r| + r.ungetc("0" * 10000) + f = true + assert_equal("0" * 10000 + "1" * 10000, r.read) + 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 + r.write "foobarbaz" + end + end + end + + def test_dup + ruby do |f| + 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 + opts = {} + opts[:rlimit_nofile] = 1024 if defined?(Process::RLIMIT_NOFILE) + assert_separately([], <<-'End', **opts) + a = [] + assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do + loop {a << IO.pipe} + end + assert_raise(Errno::EMFILE, Errno::ENFILE, Errno::ENOMEM) do + loop {a << [a[-1][0].dup, a[-1][1].dup]} + end + End + end + + def test_dup_timeout + with_pipe do |r, w| + r.timeout = 0.1 + r2 = r.dup + assert_equal(0.1, r2.timeout) + ensure + r2&.close + end + end + + def test_inspect + with_pipe do |r, w| + assert_match(/^#<IO:fd \d+>$/, r.inspect) + r.freeze + assert_match(/^#<IO:fd \d+>$/, r.inspect) + end + end + + def test_readpartial + pipe(proc do |w| + w.write "foobarbaz" + w.close + end, proc do |r| + assert_raise(ArgumentError) { r.readpartial(-1) } + assert_equal("fooba", r.readpartial(5)) + r.readpartial(5, s = "") + assert_equal("rbaz", s) + end) + end + + def test_readpartial_lock + with_pipe do |r, w| + s = "" + t = Thread.new { r.readpartial(5, s) } + Thread.pass until t.stop? + assert_raise(RuntimeError) { s.clear } + w.write "foobarbaz" + w.close + assert_equal("fooba", t.value) + end + end + + def test_readpartial_pos + mkcdtmpdir { + open("foo", "w") {|f| f << "abc" } + open("foo") {|f| + f.seek(0) + assert_equal("ab", f.readpartial(2)) + assert_equal(2, f.pos) + } + } + 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_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.readpartial(0, s = "01234567")) + assert_empty(s) + end + end + + def test_readpartial_buffer_error + with_pipe do |r, w| + s = "" + 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" + w.close + end, proc do |r| + assert_raise(ArgumentError) { r.read(-1) } + assert_equal("fooba", r.read(5)) + r.read(nil, s = "") + assert_equal("rbaz", s) + end) + end + + def test_read_lock + with_pipe do |r, w| + s = "" + t = Thread.new { r.read(5, s) } + Thread.pass until t.stop? + assert_raise(RuntimeError) { s.clear } + w.write "foobarbaz" + w.close + assert_equal("fooba", t.value) + 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_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.read(0, s = "01234567")) + assert_empty(s) + end + end + + def test_read_buffer_error + with_pipe do |r, w| + s = "" + 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 + pipe(proc do |w| + w.write_nonblock(1) + w.close + end, proc do |r| + assert_equal("1", r.read) + 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_read_nonblock_zero_size + File.open(IO::NULL) do |r| + assert_empty(r.read_nonblock(0, s = "01234567")) + assert_empty(s) + end + end + + def test_read_nonblock_file + make_tempfile do |path| + File.open(path, 'r') do |file| + file.read_nonblock(4) + end + end + end + + def test_write_nonblock_file + make_tempfile do |path| + File.open(path, 'w') do |file| + file.write_nonblock("Ruby") + end + end + end + + def test_explicit_path + io = IO.for_fd(0, path: "Fake Path", autoclose: false) + assert_match %r"Fake Path", io.inspect + assert_equal "Fake Path", io.path + end + + def test_write_nonblock_simple_no_exceptions + pipe(proc do |w| + w.write_nonblock('1', exception: false) + w.close + end, proc do |r| + assert_equal("1", r.read) + end) + end + + def test_read_nonblock_error + with_pipe {|r, w| + begin + r.read_nonblock 4096 + rescue Errno::EWOULDBLOCK + assert_kind_of(IO::WaitReadable, $!) + 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_invalid_exception + with_pipe {|r, w| + assert_raise(ArgumentError) {r.read_nonblock(4096, exception: 1)} + } + end if have_nonblock? + + def test_read_nonblock_no_exceptions + 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 + with_pipe {|r, w| + begin + loop { + w.write_nonblock "a"*100000 + } + rescue Errno::EWOULDBLOCK + assert_kind_of(IO::WaitWritable, $!) + end + } + end if have_nonblock? + + def test_write_nonblock_invalid_exception + with_pipe {|r, w| + assert_raise(ArgumentError) {w.write_nonblock(4096, exception: 1)} + } + end if have_nonblock? + + def test_write_nonblock_no_exceptions + with_pipe {|r, w| + loop { + 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| + w.write "foobarbaz" + w.close + end, proc do |r| + assert_equal("", r.gets(0)) + assert_equal("foobarbaz", r.gets(9)) + end) + end + + def test_close_read + ruby do |f| + 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 + + def test_close_read_pipe + with_pipe do |r, w| + r.close_read + assert_raise(Errno::EPIPE) { w.write "foobarbaz" } + assert_nothing_raised(IOError) {r.close_read} + assert_nothing_raised(IOError) {r.close} + assert_nothing_raised(IOError) {r.close_read} + end + end + + def test_write_epipe_nosync + assert_separately([], <<-"end;") + r, w = IO.pipe + r.close + w.sync = false + assert_raise(Errno::EPIPE) { + loop { w.write "a" } + } + end; + end + + def test_close_read_non_readable + with_pipe do |r, w| + assert_raise(IOError) do + w.close_read + end + end + end + + def test_close_write + ruby do |f| + 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_non_readable + with_pipe do |r, w| + assert_raise(IOError) do + r.close_write + end + end + end + + 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 + 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("test_io") + t.binmode + t.puts "foo" + t.puts "bar" + t.puts "baz" + t.close + if block_given? + begin + yield t + ensure + t.close(true) + end + else + t + end + end + + def test_set_lineno + 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 + } + end + + def test_set_lineno_gets + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + r.gets; assert_equal(1, $.) + r.gets; assert_equal(2, $.) + r.lineno = 1000; assert_equal(2, $.) + r.gets; assert_equal(1001, $.) + r.gets; assert_equal(1001, $.) + end) + end + + def test_readline_bad_param_raises + File.open(__FILE__) do |f| + assert_raise(TypeError) do + f.readline Object.new + end + end + + File.open(__FILE__) do |f| + assert_raise(TypeError) do + f.readline 1, 2 + end + end + end + + def test_readline_raises + File.open(__FILE__) do |f| + assert_equal File.read(__FILE__), f.readline(nil) + assert_raise(EOFError) do + f.readline + end + end + end + + def test_readline_separators + File.open(__FILE__) do |f| + line = f.readline("def") + assert_equal File.read(__FILE__)[/\A.*?def/m], line + end + + File.open(__FILE__) do |f| + line = f.readline("def", chomp: true) + assert_equal File.read(__FILE__)[/\A.*?(?=def)/m], line + end + end + + def test_readline_separators_limits + t = Tempfile.open("readline_limit") + str = "#" * 50 + sep = "def" + + t.write str + t.write sep + t.write str + t.flush + + # over limit + File.open(t.path) do |f| + line = f.readline sep, str.bytesize + assert_equal(str, line) + end + + # under limit + File.open(t.path) do |f| + line = f.readline(sep, str.bytesize + 5) + assert_equal(str + sep, line) + end + + # under limit + chomp + File.open(t.path) do |f| + line = f.readline(sep, str.bytesize + 5, chomp: true) + assert_equal(str, line) + end + ensure + t&.close! + end + + def test_readline_limit_without_separator + t = Tempfile.open("readline_limit") + str = "#" * 50 + sep = "\n" + + t.write str + t.write sep + t.write str + t.flush + + # over limit + File.open(t.path) do |f| + line = f.readline str.bytesize + assert_equal(str, line) + end + + # under limit + File.open(t.path) do |f| + line = f.readline(str.bytesize + 5) + assert_equal(str + sep, line) + end + + # under limit + chomp + File.open(t.path) do |f| + line = f.readline(str.bytesize + 5, chomp: true) + assert_equal(str, line) + end + ensure + t&.close! + end + + def test_readline_chomp_true + File.open(__FILE__) do |f| + line = f.readline(chomp: true) + assert_equal File.readlines(__FILE__).first.chomp, line + end + end + + def test_readline_incompatible_rs + first_line = File.open(__FILE__, &:gets).encode("utf-32le") + File.open(__FILE__, encoding: "utf-8:utf-32le") {|f| + assert_equal first_line, f.readline + assert_raise(ArgumentError) {f.readline("\0")} + } + end + + def test_readline_limit_nonascii + mkcdtmpdir do + i = 0 + + File.open("text#{i+=1}", "w+:utf-8") do |f| + f.write("Test\nok\u{bf}ok\n") + f.rewind + + assert_equal("Test\nok\u{bf}", f.readline("\u{bf}")) + assert_equal("ok\n", f.readline("\u{bf}")) + end + + File.open("text#{i+=1}", "w+b:utf-32le") do |f| + f.write("0123456789") + f.rewind + + assert_equal(4, f.readline(4).bytesize) + assert_equal(4, f.readline(3).bytesize) + end + + File.open("text#{i+=1}", "w+:utf-8:utf-32le") do |f| + f.write("0123456789") + f.rewind + + assert_equal(4, f.readline(4).bytesize) + assert_equal(4, f.readline(3).bytesize) + end + end + end + + def test_set_lineno_readline + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + r.readline; assert_equal(1, $.) + r.readline; assert_equal(2, $.) + r.lineno = 1000; assert_equal(2, $.) + r.readline; assert_equal(1001, $.) + assert_raise(EOFError) { r.readline } + end) + end + + def test_each_char + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + a = [] + r.each_char {|c| a << c } + assert_equal(%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"], a) + end) + end + + def test_each_line + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + e = nil + assert_warn('') { + e = r.each_line + } + assert_equal("foo\n", e.next) + assert_equal("bar\n", e.next) + assert_equal("baz\n", e.next) + assert_raise(StopIteration) { e.next } + end) + + pipe(proc do |w| + w.write "foo\n" + w.close + end, proc do |r| + assert_equal(["foo\n"], r.each_line(nil, chomp: true).to_a, "[Bug #18770]") + end) + + pipe(proc do |w| + w.write "foo\n" + w.close + end, proc do |r| + assert_equal(["fo", "o\n"], r.each_line(nil, 2, chomp: true).to_a, "[Bug #18770]") + end) + end + + def test_each_byte2 + pipe(proc do |w| + w.binmode + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + e = nil + assert_warn('') { + e = r.each_byte + } + (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| + assert_equal(c.ord, e.next) + end + assert_raise(StopIteration) { e.next } + end) + end + + def test_each_char2 + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + e = nil + assert_warn('') { + e = r.each_char + } + (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| + assert_equal(c, e.next) + end + assert_raise(StopIteration) { e.next } + end) + end + + def test_readbyte + pipe(proc do |w| + w.binmode + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + r.binmode + (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| + assert_equal(c.ord, r.readbyte) + end + assert_raise(EOFError) { r.readbyte } + end) + end + + def test_readchar + pipe(proc do |w| + w.puts "foo" + w.puts "bar" + w.puts "baz" + w.close + end, proc do |r| + (%w(f o o) + ["\n"] + %w(b a r) + ["\n"] + %w(b a z) + ["\n"]).each do |c| + assert_equal(c, r.readchar) + end + assert_raise(EOFError) { r.readchar } + end) + end + + def test_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?) + f.close_on_exec = false + assert_equal(false, f.close_on_exec?) + 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_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_seek + make_tempfile {|t| + open(t.path) { |f| + f.seek(9) + assert_equal("az\n", f.read) + } + + 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_seek_symwhence + make_tempfile {|t| + open(t.path) { |f| + f.seek(9, :SET) + assert_equal("az\n", f.read) + } + + open(t.path) { |f| + f.seek(-4, :END) + assert_equal("baz\n", f.read) + } + + 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_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) 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 + 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 + + 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_sysread_with_negative_length + make_tempfile {|t| + open(t.path) do |f| + assert_raise(ArgumentError) { f.sysread(-1) } + end + } + end + + def test_flag + make_tempfile {|t| + assert_raise(ArgumentError) do + open(t.path, "z") { } + end + + assert_raise(ArgumentError) do + open(t.path, "rr") { } + end + + assert_raise(ArgumentError) do + open(t.path, "rbt") { } + end + } + end + + def test_sysopen + 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, "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, "r") + assert_kind_of(Integer, fd) + f = IO.for_fd(fd) + assert_equal("FOO\n", f.read) + f.close + } + end + + def try_fdopen(fd, autoclose = true, level = 50) + if level > 0 + begin + 1.times {return try_fdopen(fd, autoclose, level - 1)} + ensure + GC.start + end + else + WeakRef.new(IO.for_fd(fd, autoclose: autoclose)) + end + end + + def test_autoclose + feature2250 = '[ruby-core:26222]' + pre = 'ft2250' + + 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, feature2250) {t.close} + + 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, feature2250) {t.close} + } + end + + def test_autoclose_true_closed_by_finalizer + 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 + omit "expect IO object was GC'ed but not recycled yet" + rescue WeakRef::RefError + assert_raise(Errno::EBADF, feature2250) {t.close} + end + ensure + t&.close! + end + + 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 + omit "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 + o = Object.new + def o.to_open; self; end + assert_equal(o, open(o)) + o2 = nil + open(o) do |f| + o2 = f + end + assert_equal(o, o2) + end + + def test_open_redirect_keyword + o = Object.new + def o.to_open(**kw); kw; end + assert_equal({:a=>1}, open(o, a: 1)) + + assert_raise(ArgumentError) { open(o, {a: 1}) } + + class << o + remove_method(:to_open) + end + def o.to_open(kw); kw; end + assert_equal({:a=>1}, open(o, a: 1)) + assert_equal({:a=>1}, open(o, {a: 1})) + end + + def test_path_with_pipe + mkcdtmpdir do + cmd = "|echo foo" + assert_file.not_exist?(cmd) + + pipe_errors = [Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EPERM] + assert_raise(*pipe_errors) { open(cmd, "r+") } + assert_raise(*pipe_errors) { IO.read(cmd) } + assert_raise(*pipe_errors) { IO.foreach(cmd) {|x| assert false } } + 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") + 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 + assert_equal("outerr", File.read("out")) + } + end + + 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 test_reopen_binmode + f1 = File.open(__FILE__) + f2 = File.open(__FILE__) + f1.binmode + f1.reopen(f2) + assert_not_operator(f1, :binmode?) + ensure + f2.close + f1.close + 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 + + 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) + } + + 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 + + def test_reopen_encoding_from_io + f1 = File.open(__FILE__, "rb:UTF-16LE") + f2 = File.open(__FILE__, "r:UTF-8") + f1.reopen(f2) + assert_equal(Encoding::UTF_8, f1.external_encoding) + ensure + f2.close + f1.close + end + + 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_reopen_ivar + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + f = File.open(IO::NULL) + f.instance_variable_set(:@foo, 42) + f.reopen(STDIN) + f.instance_variable_defined?(:@foo) + f.instance_variable_get(:@foo) + end; + end + + def test_foreach + make_tempfile {|t| + a = [] + IO.foreach(t.path) {|x| a << x } + assert_equal(["foo\n", "bar\n", "baz\n"], a) + + a = [] + IO.foreach(t.path, :mode => "r") {|x| a << x } + 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} + + assert_raise(ArgumentError, "[Bug #18767] [ruby-core:108499]") {IO.foreach(__FILE__, 0){}} + + a = nil + assert_nothing_raised(ArgumentError, bug) {a = IO.foreach(t.path).to_a} + assert_equal(["foo\n", "bar\n", "baz\n"], a, bug) + + bug6054 = '[ruby-dev:45267]' + assert_raise_with_message(IOError, /not opened for reading/, bug6054) do + IO.foreach(t.path, mode:"w").next + end + + assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.foreach(t, "\n", 10, true){}} + } + end + + def test_s_readlines + 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)) + assert_raise(ArgumentError, "[Bug #18771] [ruby-core:108503]") {IO.readlines(t, "\n", 10, true){}} + } + end + + def test_printf + pipe(proc do |w| + printf(w, "foo %s baz\n", "bar") + w.close_write + end, proc do |r| + assert_equal("foo bar baz\n", r.read) + end) + end + + def test_print + make_tempfile {|t| + assert_in_out_err(["-", t.path], + "print while $<.gets", + %w(foo bar baz), []) + } + end + + def test_print_separators + assert_deprecated_warning(/non-nil '\$,'/) {$, = ":"} + assert_raise(TypeError) {$, = 1} + assert_deprecated_warning(/non-nil '\$\\'/) {$\ = "\n"} + assert_raise(TypeError) {$/ = 1} + pipe(proc do |w| + w.print('a') + EnvUtil.suppress_warning {w.print('a','b','c')} + w.close + end, proc do |r| + assert_equal("a\n", r.gets) + assert_equal("a:b:c\n", r.gets) + assert_nil r.gets + r.close + end) + ensure + $, = nil + $\ = nil + end + + def test_putc + pipe(proc do |w| + w.putc "A" + w.putc "BC" + w.putc 68 + w.close_write + end, proc do |r| + assert_equal("ABD", r.read) + end) + + assert_in_out_err([], "putc 65", %w(A), []) + end + + def test_puts_recursive_array + a = ["foo"] + a << a + pipe(proc do |w| + w.puts a + w.close + end, proc do |r| + assert_equal("foo\n[...]\n", r.read) + end) + end + + def test_puts_parallel + omit "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_deprecated_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) + w.close + end, proc do |r| + assert_equal("foo", r.read) + end) + + assert_in_out_err([], "'foo'.display", %w(foo), []) + end + + def test_set_stdout + 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) + + 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)) + + fd = IO.sysopen(t.path) + %w[w r+ w+ a+].each do |mode| + assert_raise(Errno::EINVAL, "#{mode} [ruby-dev:38571]") {IO.new(fd, mode)} + end + f = IO.new(fd, "r") + data = f.read + f.close + assert_equal("FOO\n", data) + } + end + + def test_reinitialize + 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; 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 + assert_in_out_err(["-e", <<-SRC], "foo\nbar\nbaz\n", %w(foo bar baz end), []) + puts readline + puts readline + puts readline + begin + puts readline + rescue EOFError + puts "end" + end + SRC + end + + def test_readlines + assert_in_out_err(["-e", "p readlines"], "foo\nbar\nbaz\n", + ["[\"foo\\n\", \"bar\\n\", \"baz\\n\"]"], []) + end + + def test_s_read + 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)) + + assert_raise(ArgumentError) { File.read(t.path, -1) } + assert_raise(ArgumentError) { File.read(t.path, 1, -1) } + } + end + + def test_uninitialized + assert_raise(IOError) { IO.allocate.print "" } + end + + def test_nofollow + # O_NOFOLLOW is not standard. + mkcdtmpdir { + open("file", "w") {|f| f << "content" } + begin + File.symlink("file", "slnk") + rescue NotImplementedError + return + end + assert_raise(Errno::EMLINK, Errno::ELOOP) { + open("slnk", File::RDONLY|File::NOFOLLOW) {} + } + assert_raise(Errno::EMLINK, Errno::ELOOP) { + File.foreach("slnk", :open_args=>[File::RDONLY|File::NOFOLLOW]) {} + } + } + end if /freebsd|linux/ =~ RUBY_PLATFORM and defined? File::NOFOLLOW + + def test_binmode_after_closed + 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.create("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 + } + 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 + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + assert_separately([], <<-'end;') + IO.pipe do |r,w| + $stdin.reopen(r) + r.close + read_thread = Thread.new do + 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)) + File.write(path, "foo", Object.new => Object.new) + assert_equal("foo", File.read(path)) + 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 + omit 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? + + 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_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 omit "fadvise(2) is not implemented" + } + assert_raise(Errno::ESPIPE, Errno::EINVAL) { + w.advise(:willneed) or omit "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 + 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).map {|i| + tempfiles << Tempfile.create("test_io_select_with_many_files") + } + + begin + IO.select(tempfiles) + ensure + tempfiles.each { |t| + t.close + File.unlink(t.path) + } + end + }, bug8080, timeout: 100 + end if defined?(Process::RLIMIT_NOFILE) + + def test_read_32bit_boundary + 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 + omit "non-sparse file system" + rescue SystemCallError + else + assert_equal(0x1_0000_0000, pos, msg) + end + end; + rescue Timeout::Error + omit "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 do + File.mkfifo("fifo") + rescue NotImplementedError + else + 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 + end + + def test_open_fifo_restart_at_signal_intterupt + mkcdtmpdir do + File.mkfifo("fifo") + rescue NotImplementedError + else + wait = EnvUtil.apply_timeout_scale(0.1) + data = "writing to fifo" + + # Do not use assert_separately, because reading from stdin + # prevents to reproduce [Bug #20708] + assert_in_out_err(["-e", "#{<<~"begin;"}\n#{<<~'end;'}"], [], [data]) + wait, data = #{wait}, #{data.dump} + ; + begin; + trap(:USR1) {} + Thread.new do + sleep wait; Process.kill(:USR1, $$) + sleep wait; File.write("fifo", data) + end + puts File.read("fifo") + end; + end + end if Signal.list[:USR1] # Pointless on platforms without signal + + 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 + binary_enc = Encoding.find("BINARY") + make_tempfile do |t| + open(t.path, File::RDONLY, flags: File::BINARY) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, 'r', flags: File::BINARY) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, mode: 'r', flags: File::BINARY) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, File::RDONLY|File::BINARY) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + open(t.path, File::RDONLY|File::BINARY, autoclose: true) do |f| + assert_equal true, f.binmode? + assert_equal binary_enc, f.external_encoding + end + end + end if File::BINARY != 0 + + def test_exclusive_mode + make_tempfile do |t| + assert_raise(Errno::EEXIST){ open(t.path, 'wx'){} } + assert_raise(ArgumentError){ open(t.path, 'rx'){} } + assert_raise(ArgumentError){ open(t.path, 'ax'){} } + end + end + + def test_race_gets_and_close + opt = { signal: :ABRT, timeout: 10 } + 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 + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug13158 = '[ruby-core:79262] [Bug #13158]' + closed = nil + q = Thread::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 + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + 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 + end + + def test_pread + make_tempfile { |t| + open(t.path) do |f| + assert_equal("bar", f.pread(3, 4)) + buf = "asdf" + assert_equal("bar", f.pread(3, 4, buf)) + assert_equal("bar", buf) + assert_raise(EOFError) { f.pread(1, f.size) } + end + } + end + + def test_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 + + def test_select_exceptfds + if Etc.uname[:sysname] == 'SunOS' + str = 'h'.freeze #(???) Only 1 byte with MSG_OOB on Solaris + else + str = 'hello'.freeze + end + + TCPServer.open('localhost', 0) do |svr| + con = TCPSocket.new('localhost', svr.addr[1]) + acc = svr.accept + assert_equal str.length, con.send(str, Socket::MSG_OOB) + set = IO.select(nil, nil, [acc], 30) + assert_equal([[], [], [acc]], set, 'IO#select exceptions array OK') + acc.close + con.close + end + end if Socket.const_defined?(:MSG_OOB) + + def test_select_timeout + assert_equal(nil, IO.select(nil,nil,nil,0)) + assert_equal(nil, IO.select(nil,nil,nil,0.0)) + assert_raise(TypeError) { IO.select(nil,nil,nil,"invalid-timeout") } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-1) } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-0.1) } + assert_raise(ArgumentError) { IO.select(nil,nil,nil,-Float::INFINITY) } + assert_raise(RangeError) { IO.select(nil,nil,nil,Float::NAN) } + IO.pipe {|r, w| + w << "x" + ret = [[r], [], []] + assert_equal(ret, IO.select([r],nil,nil,0.1)) + assert_equal(ret, IO.select([r],nil,nil,1)) + assert_equal(ret, IO.select([r],nil,nil,Float::INFINITY)) + } + end + + def test_recycled_fd_close + dot = -'.' + IO.pipe do |sig_rd, sig_wr| + noex = Thread.new do # everything right and never see exceptions :) + until sig_rd.wait_readable(0) + IO.pipe do |r, w| + assert_nil r.timeout + assert_nil w.timeout + + th = Thread.new { r.read(1) } + w.write(dot) + + assert_same th, th.join(15), '"good" reader timeout' + assert_equal(dot, th.value) + end + end + sig_rd.read(4) + end + 1000.times do |i| # stupid things and make exceptions: + IO.pipe do |r,w| + th = Thread.new do + begin + while r.gets + end + rescue IOError => e + e + end + end + Thread.pass until th.stop? + + r.close + assert_same th, th.join(30), '"bad" reader timeout' + assert_match(/stream closed/, th.value.message) + end + end + sig_wr.write 'done' + assert_same noex, noex.join(20), '"good" writer timeout' + assert_equal 'done', noex.value ,'r63216' + end + end + + def test_select_memory_leak + # avoid malloc arena explosion from glibc and jemalloc: + env = { + 'MALLOC_ARENA_MAX' => '1', + 'MALLOC_ARENA_TEST' => '1', + 'MALLOC_CONF' => 'narenas:1', + } + assert_no_memory_leak([env], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", rss: true, timeout: 60) + begin; + r, w = IO.pipe + rset = [r] + wset = [w] + exc = StandardError.new(-"select used to leak on exception") + exc.set_backtrace([]) + Thread.new { IO.select(rset, wset, nil, 0) }.join + else; + th = Thread.new do + Thread.handle_interrupt(StandardError => :on_blocking) do + begin + IO.select(rset, wset) + rescue + retry + end while true + end + end + 50_000.times do + Thread.pass until th.stop? + th.raise(exc) + end + th.kill + th.join + end; + end + + def test_external_encoding_index + IO.pipe {|r, w| + assert_raise(TypeError) {Marshal.dump(r)} + assert_raise(TypeError) {Marshal.dump(w)} + } + end + + def test_marshal_closed_io + bug18077 = '[ruby-core:104927] [Bug #18077]' + r, w = IO.pipe + r.close; w.close + assert_raise(TypeError, bug18077) {Marshal.dump(r)} + + class << r + undef_method :closed? + end + assert_raise(TypeError, bug18077) {Marshal.dump(r)} + end + + def test_stdout_to_closed_pipe + EnvUtil.invoke_ruby(["-e", "loop {puts :ok}"], "", true, true) do + |in_p, out_p, err_p, pid| + out = out_p.gets + out_p.close + err = err_p.read + ensure + status = Process.wait2(pid)[1] + assert_equal("ok\n", out) + assert_empty(err) + assert_not_predicate(status, :success?) + if Signal.list["PIPE"] + assert_predicate(status, :signaled?) + assert_equal("PIPE", Signal.signame(status.termsig) || status.termsig) + end + end + end + + def test_blocking_timeout + assert_separately([], <<~'RUBY') + IO.pipe do |r, w| + trap(:INT) do + w.puts "INT" + end + + main = Thread.current + thread = Thread.new do + # Wait until the main thread has entered `$stdin.gets`: + Thread.pass until main.status == 'sleep' + + # Cause an interrupt while handling `$stdin.gets`: + Process.kill :INT, $$ + end + + r.timeout = 1 + assert_equal("INT", r.gets.chomp) + rescue IO::TimeoutError + # Ignore - some platforms don't support interrupting `gets`. + ensure + thread&.join + end + RUBY + end + + def test_fork_close + omit "fork is not supported" unless Process.respond_to?(:fork) + + assert_separately([], <<~'RUBY') + r, w = IO.pipe + + thread = Thread.new do + r.read + end + + Thread.pass until thread.status == "sleep" + + pid = fork do + r.close + end + + w.close + + status = Process.wait2(pid).last + thread.join + + assert_predicate(status, :success?) + RUBY + end +end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb new file mode 100644 index 0000000000..706ce16c42 --- /dev/null +++ b/test/ruby/test_io_buffer.rb @@ -0,0 +1,933 @@ +# frozen_string_literal: false + +require 'tempfile' +require 'rbconfig/sizeof' + +class TestIOBuffer < Test::Unit::TestCase + experimental = Warning[:experimental] + begin + Warning[:experimental] = false + IO::Buffer.new(0) + ensure + Warning[:experimental] = experimental + end + + def assert_negative(value) + assert(value < 0, "Expected #{value} to be negative!") + end + + def assert_positive(value) + assert(value > 0, "Expected #{value} to be positive!") + end + + def test_flags + assert_equal 1, IO::Buffer::EXTERNAL + assert_equal 2, IO::Buffer::INTERNAL + assert_equal 4, IO::Buffer::MAPPED + + assert_equal 32, IO::Buffer::LOCKED + assert_equal 64, IO::Buffer::PRIVATE + + assert_equal 128, IO::Buffer::READONLY + end + + def test_endian + assert_equal 4, IO::Buffer::LITTLE_ENDIAN + assert_equal 8, IO::Buffer::BIG_ENDIAN + assert_equal 8, IO::Buffer::NETWORK_ENDIAN + + assert_include [IO::Buffer::LITTLE_ENDIAN, IO::Buffer::BIG_ENDIAN], IO::Buffer::HOST_ENDIAN + end + + def test_default_size + assert_equal IO::Buffer::DEFAULT_SIZE, IO::Buffer.new.size + end + + def test_new_internal + buffer = IO::Buffer.new(1024, IO::Buffer::INTERNAL) + assert_equal 1024, buffer.size + refute_predicate buffer, :external? + assert_predicate buffer, :internal? + refute_predicate buffer, :mapped? + end + + def test_new_mapped + buffer = IO::Buffer.new(1024, IO::Buffer::MAPPED) + assert_equal 1024, buffer.size + refute_predicate buffer, :external? + refute_predicate buffer, :internal? + assert_predicate buffer, :mapped? + end + + def test_new_readonly + buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::READONLY) + assert_predicate buffer, :readonly? + + assert_raise IO::Buffer::AccessError do + buffer.set_string("") + end + + assert_raise IO::Buffer::AccessError do + buffer.set_string("!", 1) + end + end + + def test_file_mapped + buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, nil, 0, IO::Buffer::READONLY)} + assert_equal File.size(__FILE__), buffer.size + + contents = buffer.get_string + assert_include contents, "Hello World" + assert_equal Encoding::BINARY, contents.encoding + end + + def test_file_mapped_with_size + buffer = File.open(__FILE__) {|file| IO::Buffer.map(file, 30, 0, IO::Buffer::READONLY)} + assert_equal 30, buffer.size + + contents = buffer.get_string + assert_equal "# frozen_string_literal: false", contents + assert_equal Encoding::BINARY, contents.encoding + end + + def test_file_mapped_size_too_large + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 200_000, 0, IO::Buffer::READONLY)} + end + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, File.size(__FILE__) + 1, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_size_just_enough + File.open(__FILE__) {|file| + assert_equal File.size(__FILE__), IO::Buffer.map(file, File.size(__FILE__), 0, IO::Buffer::READONLY).size + } + end + + def test_file_mapped_offset_too_large + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, nil, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + end + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 20, IO::Buffer::PAGE_SIZE * 100, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_zero_size + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 0, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_negative_size + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, -10, 0, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_negative_offset + assert_raise ArgumentError do + File.open(__FILE__) {|file| IO::Buffer.map(file, 20, -1, IO::Buffer::READONLY)} + end + end + + def test_file_mapped_invalid + assert_raise TypeError do + IO::Buffer.map("foobar") + end + end + + def test_string_mapped + string = "Hello World" + buffer = IO::Buffer.for(string) + assert_predicate buffer, :readonly? + end + + def test_string_mapped_frozen + string = "Hello World".freeze + buffer = IO::Buffer.for(string) + assert_predicate buffer, :readonly? + end + + def test_string_mapped_mutable + string = "Hello World" + IO::Buffer.for(string) do |buffer| + refute_predicate buffer, :readonly? + + buffer.set_value(:U8, 0, "h".ord) + + # Buffer releases it's ownership of the string: + buffer.free + + assert_equal "hello World", string + end + end + + def test_string_mapped_buffer_locked + string = "Hello World" + IO::Buffer.for(string) do |buffer| + # Cannot modify string as it's locked by the buffer: + assert_raise RuntimeError do + string[0] = "h" + end + end + end + + def test_string_mapped_buffer_frozen + string = "Hello World".freeze + IO::Buffer.for(string) do |buffer| + assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do + buffer.set_string("abc") + end + assert_equal "H".ord, buffer.get_value(:U8, 0) + end + end + + def test_non_string + not_string = Object.new + + assert_raise TypeError do + IO::Buffer.for(not_string) + end + end + + def test_string + result = IO::Buffer.string(12) do |buffer| + buffer.set_string("Hello World!") + end + + assert_equal "Hello World!", result + end + + def test_string_negative + assert_raise ArgumentError do + IO::Buffer.string(-1) + end + end + + def test_resize_mapped + buffer = IO::Buffer.new + + buffer.resize(2048) + assert_equal 2048, buffer.size + + buffer.resize(4096) + assert_equal 4096, buffer.size + end + + def test_resize_preserve + message = "Hello World" + buffer = IO::Buffer.new(1024) + buffer.set_string(message) + buffer.resize(2048) + assert_equal message, buffer.get_string(0, message.bytesize) + end + + def test_resize_zero_internal + buffer = IO::Buffer.new(1) + + buffer.resize(0) + assert_equal 0, buffer.size + + buffer.resize(1) + assert_equal 1, buffer.size + end + + def test_resize_zero_external + buffer = IO::Buffer.for('1') + + assert_raise IO::Buffer::AccessError do + buffer.resize(0) + end + end + + def test_compare_same_size + buffer1 = IO::Buffer.new(1) + assert_equal buffer1, buffer1 + + buffer2 = IO::Buffer.new(1) + buffer1.set_value(:U8, 0, 0x10) + buffer2.set_value(:U8, 0, 0x20) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + + def test_compare_different_size + buffer1 = IO::Buffer.new(3) + buffer2 = IO::Buffer.new(5) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + + def test_compare_zero_length + buffer1 = IO::Buffer.new(0) + buffer2 = IO::Buffer.new(1) + + assert_negative buffer1 <=> buffer2 + assert_positive buffer2 <=> buffer1 + end + + def test_slice + buffer = IO::Buffer.new(128) + slice = buffer.slice(8, 32) + slice.set_string("Hello World") + assert_equal("Hello World", buffer.get_string(8, 11)) + end + + def test_slice_arguments + buffer = IO::Buffer.for("Hello World") + + slice = buffer.slice + assert_equal "Hello World", slice.get_string + + slice = buffer.slice(2) + assert_equal("llo World", slice.get_string) + end + + def test_slice_bounds_error + buffer = IO::Buffer.new(128) + + assert_raise ArgumentError do + buffer.slice(128, 10) + end + + assert_raise ArgumentError do + buffer.slice(-10, 10) + end + end + + def test_slice_readonly + hello = %w"Hello World".join(" ").freeze + buffer = IO::Buffer.for(hello) + slice = buffer.slice + assert_predicate slice, :readonly? + assert_raise IO::Buffer::AccessError do + # This breaks the literal in string pool and many other tests in this file. + slice.set_string("Adios", 0, 5) + end + assert_equal "Hello World", hello + end + + def test_transfer + hello = %w"Hello World".join(" ") + buffer = IO::Buffer.for(hello) + transferred = buffer.transfer + assert_equal "Hello World", transferred.get_string + assert_predicate buffer, :null? + assert_raise IO::Buffer::AccessError do + transferred.set_string("Goodbye") + end + assert_equal "Hello World", hello + end + + def test_transfer_in_block + hello = %w"Hello World".join(" ") + buffer = IO::Buffer.for(hello, &:transfer) + assert_equal "Hello World", buffer.get_string + buffer.set_string("Ciao!") + assert_equal "Ciao! World", hello + hello.freeze + assert_raise IO::Buffer::AccessError do + buffer.set_string("Hola") + end + assert_equal "Ciao! World", hello + end + + def test_locked + buffer = IO::Buffer.new(128, IO::Buffer::INTERNAL|IO::Buffer::LOCKED) + + assert_raise IO::Buffer::LockedError do + buffer.resize(256) + end + + assert_equal 128, buffer.size + + assert_raise IO::Buffer::LockedError do + buffer.free + end + + assert_equal 128, buffer.size + end + + def test_get_string + message = "Hello World 🤓" + + buffer = IO::Buffer.new(128) + buffer.set_string(message) + + chunk = buffer.get_string(0, message.bytesize, Encoding::UTF_8) + assert_equal message, chunk + assert_equal Encoding::UTF_8, chunk.encoding + + chunk = buffer.get_string(0, message.bytesize, Encoding::BINARY) + assert_equal Encoding::BINARY, chunk.encoding + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(0, 129) + end + + assert_raise_with_message(ArgumentError, /bigger than the buffer size/) do + buffer.get_string(129) + end + + assert_raise_with_message(ArgumentError, /Offset can't be negative/) do + buffer.get_string(-1) + end + end + + def test_zero_length_get_string + buffer = IO::Buffer.new.slice(0, 0) + assert_equal "", buffer.get_string + + buffer = IO::Buffer.new(0) + assert_equal "", buffer.get_string + end + + # We check that values are correctly round tripped. + RANGES = { + :U8 => [0, 2**8-1], + :S8 => [-2**7, 0, 2**7-1], + + :U16 => [0, 2**16-1], + :S16 => [-2**15, 0, 2**15-1], + :u16 => [0, 2**16-1], + :s16 => [-2**15, 0, 2**15-1], + + :U32 => [0, 2**32-1], + :S32 => [-2**31, 0, 2**31-1], + :u32 => [0, 2**32-1], + :s32 => [-2**31, 0, 2**31-1], + + :U64 => [0, 2**64-1], + :S64 => [-2**63, 0, 2**63-1], + :u64 => [0, 2**64-1], + :s64 => [-2**63, 0, 2**63-1], + + :U128 => [0, 2**64, 2**127-1, 2**128-1], + :S128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1], + :u128 => [0, 2**64, 2**127-1, 2**128-1], + :s128 => [-2**127, -2**63-1, -1, 0, 2**63, 2**127-1], + + :F32 => [-1.0, 0.0, 0.5, 1.0, 128.0], + :F64 => [-1.0, 0.0, 0.5, 1.0, 128.0], + } + + SIZE_MAX = RbConfig::LIMITS["SIZE_MAX"] + + def test_get_set_value + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + values.each do |value| + buffer.set_value(data_type, 0, value) + assert_equal value, buffer.get_value(data_type, 0), "Converting #{value} as #{data_type}." + end + assert_raise(ArgumentError) {buffer.get_value(data_type, 128)} + assert_raise(ArgumentError) {buffer.set_value(data_type, 128, 0)} + case data_type + when :U8, :S8 + else + assert_raise(ArgumentError) {buffer.get_value(data_type, 127)} + assert_raise(ArgumentError) {buffer.set_value(data_type, 127, 0)} + assert_raise(ArgumentError) {buffer.get_value(data_type, SIZE_MAX)} + assert_raise(ArgumentError) {buffer.set_value(data_type, SIZE_MAX, 0)} + end + end + end + + def test_get_set_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.get_values(format, 0), "Converting #{values} as #{format}." + end + end + + def test_zero_length_get_set_values + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.get_values([], 0) + assert_equal 0, buffer.set_values([], 0, []) + end + + def test_values + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + + buffer.set_values(format, 0, values) + assert_equal values, buffer.values(data_type, 0, values.size), "Reading #{values} as #{format}." + end + end + + def test_each + buffer = IO::Buffer.new(128) + + RANGES.each do |data_type, values| + format = [data_type] * values.size + data_type_size = IO::Buffer.size_of(data_type) + values_with_offsets = values.map.with_index{|value, index| [index * data_type_size, value]} + + buffer.set_values(format, 0, values) + assert_equal values_with_offsets, buffer.each(data_type, 0, values.size).to_a, "Reading #{values} as #{data_type}." + end + end + + def test_zero_length_each + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.each(:U8).to_a + end + + def test_each_byte + string = "The quick brown fox jumped over the lazy dog." + buffer = IO::Buffer.for(string) + + assert_equal string.bytes, buffer.each_byte.to_a + assert_equal string.bytes[3, 5], buffer.each_byte(3, 5).to_a + end + + def test_zero_length_each_byte + buffer = IO::Buffer.new(0) + + assert_equal [], buffer.each_byte.to_a + end + + def test_clear + buffer = IO::Buffer.new(16) + assert_equal "\0" * 16, buffer.get_string + buffer.clear(1) + assert_equal "\1" * 16, buffer.get_string + buffer.clear(2, 1, 2) + assert_equal "\1" + "\2"*2 + "\1"*13, buffer.get_string + buffer.clear(2, 1) + assert_equal "\1" + "\2"*15, buffer.get_string + buffer.clear(260) + assert_equal "\4" * 16, buffer.get_string + assert_raise(TypeError) {buffer.clear("x")} + + assert_raise(ArgumentError) {buffer.clear(0, 20)} + assert_raise(ArgumentError) {buffer.clear(0, 0, 20)} + assert_raise(ArgumentError) {buffer.clear(0, 10, 10)} + assert_raise(ArgumentError) {buffer.clear(0, SIZE_MAX-7, 10)} + end + + def test_invalidation + input, output = IO.pipe + + # (1) rb_write_internal creates IO::Buffer object, + buffer = IO::Buffer.new(128) + + # (2) it is passed to (malicious) scheduler + # (3) scheduler starts a thread which call system call with the buffer object + thread = Thread.new{buffer.locked{input.read}} + + Thread.pass until thread.stop? + + # (4) scheduler returns + # (5) rb_write_internal invalidate the buffer object + assert_raise IO::Buffer::LockedError do + buffer.free + end + + # (6) the system call access the memory area after invalidation + output.write("Hello World") + output.close + thread.join + + input.close + end + + def hello_world_tempfile(repeats = 1) + io = Tempfile.new + repeats.times do + io.write("Hello World") + end + io.seek(0) + + yield io + ensure + io&.close! + end + + def test_read + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_length + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, 5) + assert_equal "Hello", buffer.get_string(0, 5) + end + end + + def test_read_with_with_offset + hello_world_tempfile do |io| + buffer = IO::Buffer.new(128) + buffer.read(io, nil, 6) + assert_equal "Hello", buffer.get_string(6, 5) + end + end + + def test_read_with_length_and_offset + hello_world_tempfile(100) do |io| + buffer = IO::Buffer.new(1024) + # Only read 24 bytes from the file, as we are starting at offset 1000 in the buffer. + assert_equal 24, buffer.read(io, 0, 1000) + assert_equal "Hello World", buffer.get_string(1000, 11) + end + end + + def test_write + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello") + buffer.write(io) + + io.seek(0) + assert_equal "Hello", io.read(5) + ensure + io.close! + end + + def test_write_with_length_and_offset + io = Tempfile.new + + buffer = IO::Buffer.new(5) + buffer.set_string("Hello") + buffer.write(io, 4, 1) + + io.seek(0) + assert_equal "ello", io.read(4) + ensure + io.close! + end + + def test_pread + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5) + + assert_equal "World", buffer.get_string(0, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pread_offset + io = Tempfile.new + io.write("Hello World") + io.seek(0) + + buffer = IO::Buffer.new(128) + buffer.pread(io, 6, 5, 6) + + assert_equal "World", buffer.get_string(6, 5) + assert_equal 0, io.tell + ensure + io.close! + end + + def test_pwrite + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("World") + buffer.pwrite(io, 6, 5) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_pwrite_offset + io = Tempfile.new + + buffer = IO::Buffer.new(128) + buffer.set_string("Hello World") + buffer.pwrite(io, 6, 5, 6) + + assert_equal 0, io.tell + + io.seek(6) + assert_equal "World", io.read(5) + ensure + io.close! + end + + def test_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), (source & mask) + assert_equal IO::Buffer.for("1334133413"), (source | mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), (source ^ mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), ~source + end + + def test_inplace_operators + source = IO::Buffer.for("1234123412") + mask = IO::Buffer.for("133\x00") + + assert_equal IO::Buffer.for("123\x00123\x0012"), source.dup.and!(mask) + assert_equal IO::Buffer.for("1334133413"), source.dup.or!(mask) + assert_equal IO::Buffer.for("\x00\x01\x004\x00\x01\x004\x00\x01"), source.dup.xor!(mask) + assert_equal IO::Buffer.for("\xce\xcd\xcc\xcb\xce\xcd\xcc\xcb\xce\xcd"), source.dup.not! + end + + def test_shared + message = "Hello World" + buffer = IO::Buffer.new(64, IO::Buffer::MAPPED | IO::Buffer::SHARED) + + pid = fork do + buffer.set_string(message) + end + + Process.wait(pid) + string = buffer.get_string(0, message.bytesize) + assert_equal message, string + rescue NotImplementedError + omit "Fork/shared memory is not supported." + end + + def test_private + Tempfile.create(%w"buffer .txt") do |file| + file.write("Hello World") + + buffer = IO::Buffer.map(file, nil, 0, IO::Buffer::PRIVATE) + begin + assert_predicate buffer, :private? + refute_predicate buffer, :readonly? + + buffer.set_string("J") + + # It was not changed because the mapping was private: + file.seek(0) + assert_equal "Hello World", file.read + ensure + buffer&.free + end + end + end + + def test_copy_overlapped_fwd + buf = IO::Buffer.for('0123456789').dup + buf.copy(buf, 3, 7) + assert_equal '0120123456', buf.get_string + end + + def test_copy_overlapped_bwd + buf = IO::Buffer.for('0123456789').dup + buf.copy(buf, 0, 7, 3) + assert_equal '3456789789', buf.get_string + end + + def test_copy_null_destination + buf = IO::Buffer.new(0) + assert_predicate buf, :null? + buf.copy(IO::Buffer.for('a'), 0, 0) + assert_predicate buf, :empty? + end + + def test_copy_null_source + buf = IO::Buffer.for('a').dup + src = IO::Buffer.new(0) + assert_predicate src, :null? + buf.copy(src, 0, 0) + assert_equal 'a', buf.get_string + end + + def test_set_string_overlapped_fwd + str = +'0123456789' + IO::Buffer.for(str) do |buf| + buf.set_string(str, 3, 7) + end + assert_equal '0120123456', str + end + + def test_set_string_overlapped_bwd + str = +'0123456789' + IO::Buffer.for(str) do |buf| + buf.set_string(str, 0, 7, 3) + end + assert_equal '3456789789', str + end + + def test_set_string_null_destination + buf = IO::Buffer.new(0) + assert_predicate buf, :null? + buf.set_string('a', 0, 0) + assert_predicate buf, :empty? + end + + # https://bugs.ruby-lang.org/issues/21210 + def test_bug_21210 + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + str = +"hello" + buf = IO::Buffer.for(str) + assert_predicate buf, :valid? + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_predicate buf, :valid? + end + + def test_128_bit_integers + buffer = IO::Buffer.new(32) + + # Test unsigned 128-bit integers + test_values_u128 = [ + 0, + 1, + 2**64 - 1, + 2**64, + 2**127 - 1, + 2**128 - 1, + ] + + test_values_u128.each do |value| + buffer.set_value(:u128, 0, value) + assert_equal value, buffer.get_value(:u128, 0), "u128: #{value}" + + buffer.set_value(:U128, 0, value) + assert_equal value, buffer.get_value(:U128, 0), "U128: #{value}" + end + + # Test signed 128-bit integers + test_values_s128 = [ + -2**127, + -2**63 - 1, + -1, + 0, + 1, + 2**63, + 2**127 - 1, + ] + + test_values_s128.each do |value| + buffer.set_value(:s128, 0, value) + assert_equal value, buffer.get_value(:s128, 0), "s128: #{value}" + + buffer.set_value(:S128, 0, value) + assert_equal value, buffer.get_value(:S128, 0), "S128: #{value}" + end + + # Test size_of + assert_equal 16, IO::Buffer.size_of(:u128) + assert_equal 16, IO::Buffer.size_of(:U128) + assert_equal 16, IO::Buffer.size_of(:s128) + assert_equal 16, IO::Buffer.size_of(:S128) + assert_equal 32, IO::Buffer.size_of([:u128, :u128]) + end + + def test_integer_endianness_swapping + # Test that byte order is swapped correctly for all signed and unsigned integers > 1 byte + host_is_le = IO::Buffer::HOST_ENDIAN == IO::Buffer::LITTLE_ENDIAN + host_is_be = IO::Buffer::HOST_ENDIAN == IO::Buffer::BIG_ENDIAN + + # Test values that will produce different byte patterns when swapped + # Format: [little_endian_type, big_endian_type, test_value, expected_swapped_value] + # expected_swapped_value is the result when writing as le_type and reading as be_type + # (or vice versa) on a little-endian host + test_cases = [ + [:u16, :U16, 0x1234, 0x3412], + [:s16, :S16, 0x1234, 0x3412], + [:u32, :U32, 0x12345678, 0x78563412], + [:s32, :S32, 0x12345678, 0x78563412], + [:u64, :U64, 0x0123456789ABCDEF, 0xEFCDAB8967452301], + [:s64, :S64, 0x0123456789ABCDEF, -1167088121787636991], + [:u128, :U128, 0x0123456789ABCDEF0123456789ABCDEF, 0xEFCDAB8967452301EFCDAB8967452301], + [:u128, :U128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301], + [:u128, :U128, 0xFEDCBA98765432100123456789ABCDEF, 0xEFCDAB89674523011032547698BADCFE], + [:u128, :U128, 0x123456789ABCDEF0FEDCBA9876543210, 0x1032547698BADCFEF0DEBC9A78563412], + [:s128, :S128, 0x0123456789ABCDEF0123456789ABCDEF, -21528975894082904073953971026863512831], + [:s128, :S128, 0x0123456789ABCDEFFEDCBA9876543210, 0x1032547698BADCFEEFCDAB8967452301], + ] + + test_cases.each do |le_type, be_type, value, expected_swapped| + buffer_size = IO::Buffer.size_of(le_type) + buffer = IO::Buffer.new(buffer_size * 2) + + # Test little-endian round-trip + buffer.set_value(le_type, 0, value) + result_le = buffer.get_value(le_type, 0) + assert_equal value, result_le, "#{le_type}: round-trip failed" + + # Test big-endian round-trip + buffer.set_value(be_type, buffer_size, value) + result_be = buffer.get_value(be_type, buffer_size) + assert_equal value, result_be, "#{be_type}: round-trip failed" + + # Verify byte patterns are different when endianness differs from host + if host_is_le + # On little-endian host: le_type should match host, be_type should be swapped + # So the byte patterns should be different (unless value is symmetric) + # Read back with opposite endianness to verify swapping + result_le_read_as_be = buffer.get_value(be_type, 0) + result_be_read_as_le = buffer.get_value(le_type, buffer_size) + + # The swapped reads should NOT equal the original value (unless it's symmetric) + # For most values, this will be different + if value != 0 && value != -1 && value.abs != 1 + refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on LE host" + refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on LE host" + end + + # Verify that reading back with correct endianness works + assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on LE host" + assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on LE host (with swapping)" + elsif host_is_be + # On big-endian host: be_type should match host, le_type should be swapped + result_le_read_as_be = buffer.get_value(be_type, 0) + result_be_read_as_le = buffer.get_value(le_type, buffer_size) + + # The swapped reads should NOT equal the original value (unless it's symmetric) + if value != 0 && value != -1 && value.abs != 1 + refute_equal value, result_le_read_as_be, "#{le_type} written, read as #{be_type} should be swapped on BE host" + refute_equal value, result_be_read_as_le, "#{be_type} written, read as #{le_type} should be swapped on BE host" + end + + # Verify that reading back with correct endianness works + assert_equal value, buffer.get_value(be_type, buffer_size), "#{be_type} should read correctly on BE host" + assert_equal value, buffer.get_value(le_type, 0), "#{le_type} should read correctly on BE host (with swapping)" + end + + # Verify that when we write with one endianness and read with the opposite, + # we get the expected swapped value + buffer.set_value(le_type, 0, value) + swapped_value_le_to_be = buffer.get_value(be_type, 0) + assert_equal expected_swapped, swapped_value_le_to_be, "#{le_type} written, read as #{be_type} should produce expected swapped value" + + # Also verify the reverse direction + buffer.set_value(be_type, buffer_size, value) + swapped_value_be_to_le = buffer.get_value(le_type, buffer_size) + assert_equal expected_swapped, swapped_value_be_to_le, "#{be_type} written, read as #{le_type} should produce expected swapped value" + + # Verify that writing the swapped value back and reading with original endianness + # gives us the original value (double-swap should restore original) + buffer.set_value(be_type, 0, swapped_value_le_to_be) + round_trip_value = buffer.get_value(le_type, 0) + assert_equal value, round_trip_value, "#{le_type}/#{be_type}: double-swap should restore original value" + end + end +end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb new file mode 100644 index 0000000000..83d4fb0c7b --- /dev/null +++ b/test/ruby/test_io_m17n.rb @@ -0,0 +1,2822 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' +require 'tempfile' +require 'timeout' + +class TestIO_M17N < Test::Unit::TestCase + ENCS = [ + Encoding::ASCII_8BIT, + Encoding::EUC_JP, + Encoding::Shift_JIS, + Encoding::UTF_8 + ] + + def with_tmpdir + Dir.mktmpdir {|dir| + Dir.chdir(dir) { + yield dir + } + } + end + + def pipe(*args, wp, rp) + re, we = nil, nil + kw = args.last.is_a?(Hash) ? args.pop : {} + r, w = IO.pipe(*args, **kw) + 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 + yield r, w + ensure + r.close if !r.closed? + w.close if !w.closed? + end + end + + def generate_file(path, content) + open(path, "wb") {|f| f.write content } + end + + def encdump(str) + "#{str.dump}.force_encoding(#{str.encoding.name.dump})" + end + + def assert_str_equal(expected, actual, message=nil) + full_message = build_message(message, <<EOT) +#{encdump expected} expected but not equal to +#{encdump actual}. +EOT + assert_equal(expected, actual, full_message) + end + + def test_open_r + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r") {|f| + assert_equal(Encoding.default_external, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_rb + with_tmpdir { + generate_file('tmp', "") + open("tmp", "rb") {|f| + assert_equal(Encoding.find("ASCII-8BIT"), f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_r_enc + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r:euc-jp") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + 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', "") + open("tmp", "r", encoding: "euc-jp") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + 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| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + 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) + } + } + end + + def test_open_r_encname_encname_in_opt + with_tmpdir { + generate_file('tmp', "") + open("tmp", "r", encoding: "euc-jp:utf-8") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + 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| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_w + with_tmpdir { + open("tmp", "w") {|f| + assert_equal(nil, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_wb + with_tmpdir { + open("tmp", "wb") {|f| + assert_equal(Encoding.find("ASCII-8BIT"), f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_w_enc + with_tmpdir { + open("tmp", "w:euc-jp") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_w_enc_in_opt + with_tmpdir { + open("tmp", "w", encoding: "euc-jp") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_w_enc_in_opt2 + with_tmpdir { + open("tmp", "w", external_encoding: "euc-jp") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + } + } + end + + def test_open_w_enc_enc + with_tmpdir { + open("tmp", "w:euc-jp:utf-8") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_w_enc_enc_in_opt + with_tmpdir { + open("tmp", "w", encoding: "euc-jp:utf-8") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_w_enc_enc_in_opt2 + with_tmpdir { + open("tmp", "w", external_encoding: "euc-jp", internal_encoding: "utf-8") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + end + + def test_open_w_enc_enc_perm + with_tmpdir { + open("tmp", "w:euc-jp:utf-8", 0600) {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + } + } + 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::Windows_31J, f.read.encoding) + ensure + f.close + end + } + end + + def test_s_pipe_invalid + 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 + 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 + 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 + 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 + 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 + assert_equal(Encoding.default_external, STDIN.external_encoding) + assert_equal(nil, STDIN.internal_encoding) + end + + def test_stdout + assert_equal(nil, STDOUT.external_encoding) + assert_equal(nil, STDOUT.internal_encoding) + end + + def test_stderr + assert_equal(nil, STDERR.external_encoding) + assert_equal(nil, STDERR.internal_encoding) + end + + def test_terminator_conversion + with_tmpdir { + generate_file('tmp', "before \u00FF after") + s = open("tmp", "r:utf-8:iso-8859-1") {|f| + f.gets("\xFF".force_encoding("iso-8859-1")) + } + assert_equal(Encoding.find("iso-8859-1"), s.encoding) + assert_str_equal("before \xFF".force_encoding("iso-8859-1"), s, '[ruby-core:14288]') + } + end + + def test_terminator_conversion2 + with_tmpdir { + generate_file('tmp', "before \xA1\xA2\xA2\xA3 after") + s = open("tmp", "r:euc-jp:utf-8") {|f| + f.gets("\xA2\xA2".force_encoding("euc-jp").encode("utf-8")) + } + assert_equal(Encoding.find("utf-8"), s.encoding) + assert_str_equal("before \xA1\xA2\xA2\xA3 after".force_encoding("euc-jp").encode("utf-8"), s, '[ruby-core:14319]') + } + end + + def test_terminator_stateful_conversion + with_tmpdir { + src = "before \e$B\x23\x30\x23\x31\e(B after".force_encoding("iso-2022-jp") + generate_file('tmp', src) + s = open("tmp", "r:iso-2022-jp:euc-jp") {|f| + f.gets("0".force_encoding("euc-jp")) + } + assert_equal(Encoding.find("euc-jp"), s.encoding) + assert_str_equal(src.encode("euc-jp"), s) + } + end + + def test_nonascii_terminator + with_tmpdir { + generate_file('tmp', "before \xA2\xA2 after") + open("tmp", "r:euc-jp") {|f| + assert_raise(ArgumentError) { + f.gets("\xA2\xA2".force_encoding("utf-8")) + } + } + } + end + + def test_pipe_terminator_conversion + 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 + 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 + 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 + 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 + with_tmpdir { + src = "\e$B\x23\x30\x23\x31\e(B".force_encoding("iso-2022-jp") + generate_file('tmp', src) + open("tmp", "r:iso-2022-jp:euc-jp") {|f| + assert_equal("\xa3\xb0".force_encoding("euc-jp"), f.getc) + assert_equal("\xa3\xb1".force_encoding("euc-jp"), f.getc) + } + } + end + + def test_getc_newlineconv + with_tmpdir { + src = "\u3042" + generate_file('tmp', src) + 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 + + def test_getc_newlineconv_invalid + with_tmpdir { + src = "\xE3\x81" + generate_file('tmp', src) + 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 + } + assert_equal(Encoding::GB18030, s.encoding) + assert_str_equal(0x8431A439.chr("GB18030")+"A", s) + } + end + + def test_ungetc_stateful_conversion + with_tmpdir { + src = "before \e$B\x23\x30\x23\x31\e(B after".force_encoding("iso-2022-jp") + generate_file('tmp', src) + s = open("tmp", "r:iso-2022-jp:euc-jp") {|f| + f.ungetc("0".force_encoding("euc-jp")) + f.read + } + assert_equal(Encoding.find("euc-jp"), s.encoding) + assert_str_equal("0" + src.encode("euc-jp"), s) + } + end + + def test_ungetc_stateful_conversion2 + with_tmpdir { + src = "before \e$B\x23\x30\x23\x31\e(B after".force_encoding("iso-2022-jp") + former = "before \e$B\x23\x30\e(B".force_encoding("iso-2022-jp") + rs = "\e$B\x23\x30\e(B".force_encoding("iso-2022-jp") + latter = "\e$B\x23\x31\e(B after".force_encoding("iso-2022-jp") + generate_file('tmp', src) + s = open("tmp", "r:iso-2022-jp:euc-jp") {|f| + assert_equal(former.encode("euc-jp", "iso-2022-jp"), + f.gets(rs.encode("euc-jp", "iso-2022-jp"))) + f.ungetc("0") + f.read + } + assert_equal(Encoding.find("euc-jp"), s.encoding) + assert_str_equal("0" + latter.encode("euc-jp"), s) + } + end + + def test_open_ascii + with_tmpdir { + src = "abc\n" + generate_file('tmp', "abc\n") + ENCS.each {|enc| + s = open('tmp', "r:#{enc}") {|f| f.gets } + assert_equal(enc, s.encoding) + assert_str_equal(src, s) + } + } + end + + def test_open_nonascii + with_tmpdir { + src = "\xc2\xa1\n" + generate_file('tmp', src) + ENCS.each {|enc| + content = src.dup.force_encoding(enc) + s = open('tmp', "r:#{enc}") {|f| f.gets } + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + } + end + + def test_read_encoding + with_tmpdir { + src = "\xc2\xa1\n".force_encoding("ASCII-8BIT") + generate_file('tmp', "\xc2\xa1\n") + ENCS.each {|enc| + content = src.dup.force_encoding(enc) + open('tmp', "r:#{enc}") {|f| + s = f.getc + assert_equal(enc, s.encoding) + assert_str_equal(content[0], s) + } + open('tmp', "r:#{enc}") {|f| + s = f.readchar + assert_equal(enc, s.encoding) + assert_str_equal(content[0], s) + } + open('tmp', "r:#{enc}") {|f| + s = f.gets + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + open('tmp', "r:#{enc}") {|f| + s = f.readline + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + open('tmp', "r:#{enc}") {|f| + lines = f.readlines + assert_equal(1, lines.length) + s = lines[0] + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + open('tmp', "r:#{enc}") {|f| + f.each_line {|s| + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + } + open('tmp', "r:#{enc}") {|f| + s = f.read + assert_equal(enc, s.encoding) + assert_str_equal(content, s) + } + open('tmp', "r:#{enc}") {|f| + s = f.read(1) + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_str_equal(src[0], s) + } + open('tmp', "r:#{enc}") {|f| + s = f.readpartial(1) + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_str_equal(src[0], s) + } + open('tmp', "r:#{enc}") {|f| + s = f.sysread(1) + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_str_equal(src[0], s) + } + } + } + end + + def test_write_noenc + src = "\xc2\xa1\n".force_encoding("ascii-8bit") + with_tmpdir { + open('tmp', "w") {|f| + ENCS.each {|enc| + f.write src.dup.force_encoding(enc) + } + } + open('tmp', 'r:ascii-8bit') {|f| + assert_equal(src*ENCS.length, f.read) + } + } + end + + def test_write_conversion + utf8 = "\u6666" + eucjp = "\xb3\xa2".force_encoding("EUC-JP") + with_tmpdir { + open('tmp', "w:EUC-JP") {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(nil, f.internal_encoding) + f.print utf8 + } + assert_equal(eucjp, File.read('tmp').force_encoding("EUC-JP")) + open('tmp', 'r:EUC-JP:UTF-8') {|f| + assert_equal(Encoding::EUC_JP, f.external_encoding) + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(utf8, f.read) + } + } + end + + def test_pipe + utf8 = "\u6666" + eucjp = "\xb3\xa2".force_encoding("EUC-JP") + + 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) + 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 encoding name/) do + with_pipe("UTF-8", "UTF-8".encode("UTF-32BE")) {} + end + assert_raise_with_message(ArgumentError, /invalid encoding name/) do + with_pipe("UTF-8".encode("UTF-32BE")) {} + end + + ENCS.each {|enc| + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + with_tmpdir { + generate_file('tst', 'a' * 8191 + "\xa1\xa1") + assert_nothing_raised { + File.foreach('tst', :encoding=>"euc-jp") {|line| line.inspect } + } + } + end + + def test_set_encoding + 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 + 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 + 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 + 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 + 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 + 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 + 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 + assert_raise(ArgumentError) { + open(__FILE__, "rt") {|f| + f.set_encoding("iso-2022-jp") + } + } + assert_raise(ArgumentError) { + open(__FILE__, "r") {|f| + f.set_encoding("iso-2022-jp") + } + } + assert_nothing_raised { + open(__FILE__, "rb") {|f| + f.set_encoding("iso-2022-jp") + } + } + assert_nothing_raised { + open(__FILE__, "r") {|f| + f.binmode + f.set_encoding("iso-2022-jp") + } + } + assert_nothing_raised { + open(__FILE__, "rt") {|f| + f.binmode + 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_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")} + w.puts("foo") + assert_equal("foo\n", r.gets) + assert_warning(/Unsupported/, bug5567) {r.set_encoding("fffffffffffxx", "us-ascii")} + w.puts("bar") + assert_equal("bar\n", r.gets) + assert_warning(/Unsupported/, bug5567) {r.set_encoding("us-ascii", "fffffffffffxx")} + w.puts("zot") + begin + assert_equal("zot\n", r.gets) + rescue Encoding::ConverterNotFoundError => e + assert_match(/\((\S+) to \1\)/, e.message) + end + end + end + end + + def test_set_encoding_argument_parsing + File.open(File::NULL) do |f| + f.set_encoding('binary') + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding(Encoding.find('binary')) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('binary:utf-8') + assert_equal(nil, f.internal_encoding) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('binary', 'utf-8') + assert_equal(nil, f.internal_encoding) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding(Encoding.find('binary'), Encoding.find('utf-8')) + assert_equal(nil, f.internal_encoding) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('binary', Encoding.find('utf-8')) + assert_equal(nil, f.internal_encoding) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding(Encoding.find('binary'), 'utf-8') + assert_equal(nil, f.internal_encoding) + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('iso-8859-1:utf-8') + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(Encoding::ISO_8859_1, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('iso-8859-1', 'utf-8') + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(Encoding::ISO_8859_1, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding(Encoding.find('iso-8859-1'), Encoding.find('utf-8')) + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(Encoding::ISO_8859_1, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding('iso-8859-1', Encoding.find('utf-8')) + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(Encoding::ISO_8859_1, f.external_encoding) + end + + File.open(File::NULL) do |f| + f.set_encoding(Encoding.find('iso-8859-1'), 'utf-8') + assert_equal(Encoding::UTF_8, f.internal_encoding) + assert_equal(Encoding::ISO_8859_1, f.external_encoding) + 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 + 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 + 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 + 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 + 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 + with_tmpdir { + open("tst", "w+") {|f| + pid = spawn(EnvUtil.rubybin, '-e', <<-'End', 10=>f) + io = IO.new(10, "r+") + STDIN.reopen(io) + STDIN.external_encoding + STDIN.write "\u3042" + STDIN.flush + End + Process.wait pid + f.rewind + result = f.read.force_encoding("ascii-8bit") + assert_equal("\u3042".force_encoding("ascii-8bit"), result) + } + } + 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| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + s = f.read + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + end + + def test_popen_r_enc_in_opt + IO.popen("#{EnvUtil.rubybin} -e 'putc 255'", "r", encoding: "ascii-8bit") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + s = f.read + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + end + + def test_popen_r_enc_in_opt2 + IO.popen("#{EnvUtil.rubybin} -e 'putc 255'", "r", external_encoding: "ascii-8bit") {|f| + assert_equal(Encoding::ASCII_8BIT, f.external_encoding) + assert_equal(nil, f.internal_encoding) + s = f.read + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + end + + def test_popen_r_enc_enc + IO.popen("#{EnvUtil.rubybin} -e 'putc 0xa1'", "r:shift_jis:euc-jp") {|f| + assert_equal(Encoding::Shift_JIS, f.external_encoding) + assert_equal(Encoding::EUC_JP, f.internal_encoding) + s = f.read + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\x8e\xa1".force_encoding("euc-jp"), s) + } + end + + def test_popen_r_enc_enc_in_opt + IO.popen("#{EnvUtil.rubybin} -e 'putc 0xa1'", "r", encoding: "shift_jis:euc-jp") {|f| + assert_equal(Encoding::Shift_JIS, f.external_encoding) + assert_equal(Encoding::EUC_JP, f.internal_encoding) + s = f.read + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\x8e\xa1".force_encoding("euc-jp"), s) + } + end + + def test_popen_r_enc_enc_in_opt2 + IO.popen("#{EnvUtil.rubybin} -e 'putc 0xa1'", "r", external_encoding: "shift_jis", internal_encoding: "euc-jp") {|f| + assert_equal(Encoding::Shift_JIS, f.external_encoding) + assert_equal(Encoding::EUC_JP, f.internal_encoding) + s = f.read + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\x8e\xa1".force_encoding("euc-jp"), s) + } + end + + def test_popenv_r_enc_enc_in_opt2 + IO.popen([EnvUtil.rubybin, "-e", "putc 0xa1"], "r", external_encoding: "shift_jis", internal_encoding: "euc-jp") {|f| + assert_equal(Encoding::Shift_JIS, f.external_encoding) + assert_equal(Encoding::EUC_JP, f.internal_encoding) + s = f.read + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\x8e\xa1".force_encoding("euc-jp"), s) + } + end + + def test_s_foreach_enc + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :mode => "r:ascii-8bit") {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_enc_in_opt + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :encoding => "ascii-8bit") {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_enc_in_opt2 + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :external_encoding => "ascii-8bit") {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_enc_enc + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :mode => "r:utf-8:euc-jp") {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_s_foreach_enc_enc_in_opt + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :mode => "r", :encoding => "utf-8:euc-jp") {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_s_foreach_enc_enc_in_opt2 + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :mode => "r", :external_encoding => "utf-8", :internal_encoding => "euc-jp") {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_s_foreach_open_args_enc + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :open_args => ["r:ascii-8bit"]) {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_open_args_enc_in_opt + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :open_args => ["r", encoding: "ascii-8bit"]) {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_open_args_enc_in_opt2 + with_tmpdir { + generate_file("t", "\xff") + IO.foreach("t", :open_args => ["r", external_encoding: "ascii-8bit"]) {|s| + assert_equal(Encoding::ASCII_8BIT, s.encoding) + assert_equal("\xff".force_encoding("ascii-8bit"), s) + } + } + end + + def test_s_foreach_open_args_enc_enc + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :open_args => ["r:utf-8:euc-jp"]) {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_s_foreach_open_args_enc_enc_in_opt + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :open_args => ["r", encoding: "utf-8:euc-jp"]) {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_s_foreach_open_args_enc_enc_in_opt2 + with_tmpdir { + generate_file("t", "\u3042") + IO.foreach("t", :open_args => ["r", external_encoding: "utf-8", internal_encoding: "euc-jp"]) {|s| + assert_equal(Encoding::EUC_JP, s.encoding) + assert_equal("\xa4\xa2".force_encoding("euc-jp"), s) + } + } + end + + def test_both_textmode_binmode + 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 + with_tmpdir { + generate_file("t.crlf", "a\r\nb\r\nc\r\n") + assert_equal("a\nb\nc\n", File.read("t.crlf", mode:"rt:euc-jp:utf-8")) + assert_equal("a\nb\nc\n", File.read("t.crlf", mode:"rt")) + 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")) + assert_equal("a\nb\nc\n", File.read("t.cr", mode:"rt")) + + generate_file("t.lf", "a\nb\nc\n") + assert_equal("a\nb\nc\n", File.read("t.cr", mode:"rt:euc-jp:utf-8")) + assert_equal("a\nb\nc\n", File.read("t.cr", mode:"rt")) + } + end + + def test_textmode_decode_universal_newline_getc + with_tmpdir { + generate_file("t.crlf", "a\r\nb\r\nc\r\n") + open("t.crlf", "rt") {|f| + assert_equal("a", f.getc) + assert_equal("\n", f.getc) + assert_equal("b", f.getc) + assert_equal("\n", f.getc) + assert_equal("c", f.getc) + assert_equal("\n", f.getc) + assert_equal(nil, f.getc) + } + + generate_file("t.cr", "a\rb\rc\r") + open("t.cr", "rt") {|f| + assert_equal("a", f.getc) + assert_equal("\n", f.getc) + assert_equal("b", f.getc) + assert_equal("\n", f.getc) + assert_equal("c", f.getc) + assert_equal("\n", f.getc) + assert_equal(nil, f.getc) + } + + generate_file("t.lf", "a\nb\nc\n") + open("t.lf", "rt") {|f| + assert_equal("a", f.getc) + assert_equal("\n", f.getc) + assert_equal("b", f.getc) + assert_equal("\n", f.getc) + assert_equal("c", f.getc) + assert_equal("\n", f.getc) + assert_equal(nil, f.getc) + } + } + end + + def test_textmode_decode_universal_newline_gets + with_tmpdir { + generate_file("t.crlf", "a\r\nb\r\nc\r\n") + open("t.crlf", "rt") {|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", "rt") {|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", "rt") {|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_textmode_decode_universal_newline_utf16 + with_tmpdir { + generate_file("t.utf16be.crlf", "\0a\0\r\0\n\0b\0\r\0\n\0c\0\r\0\n") + assert_equal("a\nb\nc\n", File.read("t.utf16be.crlf", mode:"rt:utf-16be:utf-8")) + + generate_file("t.utf16le.crlf", "a\0\r\0\n\0b\0\r\0\n\0c\0\r\0\n\0") + assert_equal("a\nb\nc\n", File.read("t.utf16le.crlf", mode:"rt:utf-16le:utf-8")) + + generate_file("t.utf16be.cr", "\0a\0\r\0b\0\r\0c\0\r") + assert_equal("a\nb\nc\n", File.read("t.utf16be.cr", mode:"rt:utf-16be:utf-8")) + + generate_file("t.utf16le.cr", "a\0\r\0b\0\r\0c\0\r\0") + assert_equal("a\nb\nc\n", File.read("t.utf16le.cr", mode:"rt:utf-16le:utf-8")) + + generate_file("t.utf16be.lf", "\0a\0\n\0b\0\n\0c\0\n") + assert_equal("a\nb\nc\n", File.read("t.utf16be.lf", mode:"rt:utf-16be:utf-8")) + + generate_file("t.utf16le.lf", "a\0\n\0b\0\n\0c\0\n\0") + assert_equal("a\nb\nc\n", File.read("t.utf16le.lf", mode:"rt:utf-16le:utf-8")) + } + end + + SYSTEM_NEWLINE = [] + def system_newline + return SYSTEM_NEWLINE.first if !SYSTEM_NEWLINE.empty? + with_tmpdir { + open("newline", "wt") {|f| + f.print "\n" + } + open("newline", "rb") {|f| + SYSTEM_NEWLINE << f.read + } + } + SYSTEM_NEWLINE.first + end + + def test_textmode_encode_newline + with_tmpdir { + open("t.txt", "wt") {|f| + f.puts "abc" + f.puts "def" + } + content = File.read("t.txt", :mode=>"rb") + nl = system_newline + assert_equal("abc#{nl}def#{nl}", content) + } + end + + def test_textmode_encode_newline_enc + with_tmpdir { + open("t.txt", "wt:euc-jp") {|f| + f.puts "abc\u3042" + f.puts "def\u3044" + } + content = File.read("t.txt", :mode=>"rb:ascii-8bit") + nl = system_newline + assert_equal("abc\xA4\xA2#{nl}def\xA4\xA4#{nl}", content) + } + 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") + open("t.utf8.crlf", "rb:utf-8:utf-16be") {|f| + content = f.read + assert_equal("\0a\0\r\0\n\0b\0\r\0\n".force_encoding("UTF-16BE"), content) + } + open("t.utf8.crlf", "rt:utf-8:utf-16be") {|f| + content = f.read + assert_equal("\0a\0\n\0b\0\n".force_encoding("UTF-16BE"), content) + } + open("t.utf8.crlf", "r:utf-8:utf-16be") {|f| + content = f.read + if system_newline == "\n" + assert_equal("\0a\0\r\0\n\0b\0\r\0\n".force_encoding("UTF-16BE"), content) + else + assert_equal("\0a\0\n\0b\0\n".force_encoding("UTF-16BE"), content) + end + } + } + end + + def test_read_newline_conversion_without_encoding_conversion + with_tmpdir { + generate_file("t.utf16.crlf", "\0a\0\r\0\n\0b\0\r\0\n") + open("t.utf16.crlf", "rb:utf-16be") {|f| + content = f.read + assert_equal("\0a\0\r\0\n\0b\0\r\0\n".force_encoding("UTF-16BE"), + content) + } + } + end + + def test_read_newline_conversion_error + with_tmpdir { + generate_file("empty.txt", "") + # ascii incompatible encoding without conversion needs binmode. + assert_raise(ArgumentError) { + open("empty.txt", "rt:utf-16be") {|f| } + } + assert_raise(ArgumentError) { + open("empty.txt", "r:utf-16be") {|f| } + } + } + end + + def test_read_mode + with_tmpdir { + generate_file("t", "a\rb\r\nc\n\xc2\xa2") + generate_file("ie", "a\rb\r\nc\n\e$B\x42\x22\e(B") + generate_file("iu", "a\rb\r\nc\n\e$B\x21\x71\e(B") + generate_file("be", "\0a\0\r\0b\0\r\0\n\0c\0\n\x85\x35") + generate_file("bu", "\0a\0\r\0b\0\r\0\n\0c\0\n\0\xa2") + # "\xc2\xa2" is valid as EUC-JP and UTF-8 + # EUC-JP UTF-8 Unicode + # 0xC2A2 0xE894B5 U+8535 + # 0xA1F1 0xC2A2 U+00A2 + + open("t","rt") {|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding(Encoding.default_external), f.read) } + open("t","rb") {|f| assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding(Encoding::ASCII_8BIT), f.read) } + + open("t","rt:euc-jp") {|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("EUC-JP"), f.read) } + open("t","rb:euc-jp") {|f| assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("EUC-JP"), f.read) } + open("t","rt:utf-8") {|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("UTF-8"), f.read) } + open("t","rb:utf-8") {|f| assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("UTF-8"), f.read) } + assert_raise(ArgumentError) { open("t", "rt:iso-2022-jp") {|f| } } + open("t","rb:iso-2022-jp") {|f| assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("ISO-2022-JP"), f.read) } + + open("t","rt:euc-jp:utf-8") {|f| assert_equal("a\nb\nc\n\u8535", f.read) } + open("t","rt:utf-8:euc-jp") {|f| assert_equal("a\nb\nc\n\xa1\xf1".force_encoding("EUC-JP"), f.read) } + open("t","rb:euc-jp:utf-8") {|f| assert_equal("a\rb\r\nc\n\u8535", f.read) } + open("t","rb:utf-8:euc-jp") {|f| assert_equal("a\rb\r\nc\n\xa1\xf1".force_encoding("EUC-JP"), f.read) } + + open("t","rt:euc-jp:iso-2022-jp"){|f| assert_equal("a\nb\nc\n\e$B\x42\x22\e(B".force_encoding("ISO-2022-JP"), f.read) } + open("t","rt:utf-8:iso-2022-jp"){|f| assert_equal("a\nb\nc\n\e$B\x21\x71\e(B".force_encoding("ISO-2022-JP"), f.read) } + open("t","rt:euc-jp:utf-16be"){|f| assert_equal("\0a\0\n\0b\0\n\0c\0\n\x85\x35".force_encoding("UTF-16BE"), f.read) } + open("t","rt:utf-8:utf-16be"){|f| assert_equal("\0a\0\n\0b\0\n\0c\0\n\0\xa2".force_encoding("UTF-16BE"), f.read) } + open("t","rb:euc-jp:iso-2022-jp"){|f|assert_equal("a\rb\r\nc\n\e$B\x42\x22\e(B".force_encoding("ISO-2022-JP"),f.read)} + open("t","rb:utf-8:iso-2022-jp"){|f|assert_equal("a\rb\r\nc\n\e$B\x21\x71\e(B".force_encoding("ISO-2022-JP"),f.read)} + open("t","rb:euc-jp:utf-16be"){|f|assert_equal("\0a\0\r\0b\0\r\0\n\0c\0\n\x85\x35".force_encoding("UTF-16BE"),f.read)} + open("t","rb:utf-8:utf-16be"){|f|assert_equal("\0a\0\r\0b\0\r\0\n\0c\0\n\0\xa2".force_encoding("UTF-16BE"),f.read)} + + open("ie","rt:iso-2022-jp:euc-jp"){|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("EUC-JP"), f.read) } + open("iu","rt:iso-2022-jp:utf-8"){|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("UTF-8"), f.read) } + open("be","rt:utf-16be:euc-jp"){|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("EUC-JP"), f.read) } + open("bu","rt:utf-16be:utf-8"){|f| assert_equal("a\nb\nc\n\xc2\xa2".force_encoding("UTF-8"), f.read) } + open("ie","rb:iso-2022-jp:euc-jp"){|f|assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("EUC-JP"),f.read)} + open("iu","rb:iso-2022-jp:utf-8"){|f|assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("UTF-8"),f.read)} + open("be","rb:utf-16be:euc-jp"){|f|assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("EUC-JP"),f.read)} + open("bu","rb:utf-16be:utf-8"){|f|assert_equal("a\rb\r\nc\n\xc2\xa2".force_encoding("UTF-8"),f.read)} + + open("ie","rt:iso-2022-jp:utf-16be"){|f|assert_equal("\0a\0\n\0b\0\n\0c\0\n\x85\x35".force_encoding("UTF-16BE"),f.read)} + open("be","rt:utf-16be:iso-2022-jp"){|f|assert_equal("a\nb\nc\n\e$B\x42\x22\e(B".force_encoding("ISO-2022-JP"),f.read)} + open("ie","rb:iso-2022-jp:utf-16be"){|f|assert_equal("\0a\0\r\0b\0\r\0\n\0c\0\n\x85\x35".force_encoding("UTF-16BE"),f.read)} + open("be","rb:utf-16be:iso-2022-jp"){|f|assert_equal("a\rb\r\nc\n\e$B\x42\x22\e(B".force_encoding("ISO-2022-JP"),f.read)} + } + end + + def assert_write(expected, mode, *args) + with_tmpdir { + open("t", mode) {|f| + args.each {|arg| f.print arg } + } + content = File.read("t", :mode=>"rb:ascii-8bit") + assert_equal(expected.b, content.b) + } + end + + def test_write_mode + # "\xc2\xa2" is valid as EUC-JP and UTF-8 + # EUC-JP UTF-8 Unicode + # 0xC2A2 0xE894B5 U+8535 + # 0xA1F1 0xC2A2 U+00A2 + a = "a\rb\r\nc\n" + e = "\xc2\xa2".force_encoding("euc-jp") + u8 = "\xc2\xa2".force_encoding("utf-8") + 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 + n.encode("utf-16be").force_encoding("ascii-8bit") + + assert_write("a\rb\r#{n}c#{n}", "wt", a) + assert_write("\xc2\xa2", "wt", e) + assert_write("\xc2\xa2", "wt", u8) + + assert_write("a\rb\r\nc\n", "wb", a) + assert_write("\xc2\xa2", "wb", e) + assert_write("\xc2\xa2", "wb", u8) + + #assert_write("\x85\x35\0\r\x00\xa2\0\r\0\n\0\n", "wt", u16) should raise + #assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r\n\n", "wt", i) should raise + assert_write("\x85\x35\0\r\x00\xa2\0\r\0\n\0\n", "wb", u16) + assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r\n\n", "wb", i) + + t_write_mode_enc + t_write_mode_enc(":utf-8") + end + + def t_write_mode_enc(enc="") + # "\xc2\xa2" is valid as EUC-JP and UTF-8 + # EUC-JP UTF-8 Unicode + # 0xC2A2 0xE894B5 U+8535 + # 0xA1F1 0xC2A2 U+00A2 + a = "a\rb\r\nc\n" + e = "\xc2\xa2".force_encoding("euc-jp") + u8 = "\xc2\xa2".force_encoding("utf-8") + 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") + + assert_write("a\rb\r#{n}c#{n}", "wt:euc-jp#{enc}", a) + assert_write("\xc2\xa2", "wt:euc-jp#{enc}", e) + assert_write("\xa1\xf1", "wt:euc-jp#{enc}", u8) + + assert_write("a\rb\r\nc\n", "wb:euc-jp#{enc}", a) + assert_write("\xc2\xa2", "wb:euc-jp#{enc}", e) + assert_write("\xa1\xf1", "wb:euc-jp#{enc}", u8) + + assert_write("\xc2\xa2\r\xa1\xf1\r#{n}#{n}", "wt:euc-jp#{enc}", u16) + assert_write("\xc2\xa2\r\xa1\xf1\r#{n}#{n}", "wt:euc-jp#{enc}", i) + assert_write("\xc2\xa2\r\xa1\xf1\r\n\n", "wb:euc-jp#{enc}", u16) + assert_write("\xc2\xa2\r\xa1\xf1\r\n\n", "wb:euc-jp#{enc}", i) + + assert_write("\0a\0\r\0b\0\r#{un}\0c#{un}", "wt:utf-16be#{enc}", a) + assert_write("\x85\x35", "wt:utf-16be#{enc}", e) + assert_write("\x00\xa2", "wt:utf-16be#{enc}", u8) + assert_write("a\rb\r#{n}c#{n}", "wt:iso-2022-jp#{enc}", a) + assert_write("\e$B\x42\x22\e(B", "wt:iso-2022-jp#{enc}", e) + assert_write("\e$B\x21\x71\e(B", "wt:iso-2022-jp#{enc}", u8) + + assert_write("\0a\0\r\0b\0\r\0\n\0c\0\n", "wb:utf-16be#{enc}", a) + assert_write("\x85\x35", "wb:utf-16be#{enc}", e) + assert_write("\x00\xa2", "wb:utf-16be#{enc}", u8) + assert_write("a\rb\r\nc\n", "wb:iso-2022-jp#{enc}", a) + assert_write("\e$B\x42\x22\e(B", "wb:iso-2022-jp#{enc}", e) + assert_write("\e$B\x21\x71\e(B", "wb:iso-2022-jp#{enc}", u8) + + assert_write("\x85\x35\0\r\x00\xa2\0\r#{un}#{un}", "wt:utf-16be#{enc}", u16) + assert_write("\x85\x35\0\r\x00\xa2\0\r#{un}#{un}", "wt:utf-16be#{enc}", i) + assert_write("\x85\x35\0\r\x00\xa2\0\r\0\n\0\n", "wb:utf-16be#{enc}", u16) + assert_write("\x85\x35\0\r\x00\xa2\0\r\0\n\0\n", "wb:utf-16be#{enc}", i) + assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r#{n}#{n}", "wt:iso-2022-jp#{enc}", u16) + assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r#{n}#{n}", "wt:iso-2022-jp#{enc}", i) + assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r\n\n", "wb:iso-2022-jp#{enc}", u16) + assert_write("\e$B\x42\x22\e(B\r\e$B\x21\x71\e(B\r\n\n", "wb:iso-2022-jp#{enc}", i) + end + + def test_write_mode_fail + return if system_newline == "\n" + with_tmpdir { + open("t", "wt") {|f| + assert_raise(ArgumentError) { f.print "\0\r\0\r\0\n\0\n".force_encoding("utf-16be") } + } + } + end + + def test_write_ascii_incompat + with_tmpdir { + open("t.utf8", "wb:utf-8:utf-16be") {|f| } + open("t.utf8", "wt:utf-8:utf-16be") {|f| } + open("t.utf8", "w:utf-8:utf-16be") {|f| } + open("t.utf16", "wb:utf-16be") {|f| } + open("t.utf16", "wt:utf-16be") {|f| } + open("t.utf16", "w:utf-16be") {|f| } + } + end + + def test_binmode_write_ascii_incompat_internal + with_tmpdir { + open("t.utf8.lf", "wb:utf-8:utf-16be") {|f| + f.print "\0a\0\n\0b\0\n".force_encoding("UTF-16BE") + } + content = File.read("t.utf8.lf", :mode=>"rb:ascii-8bit") + assert_equal("a\nb\n", content) + + open("t.utf8.lf", "wb:utf-16be") {|f| + f.print "\0a\0\n\0b\0\n".force_encoding("UTF-16BE") + } + content = File.read("t.utf8.lf", :mode=>"rb:ascii-8bit") + assert_equal("\0a\0\n\0b\0\n", content) + } + end + + def test_binary + with_tmpdir { + src = "a\nb\rc\r\nd\n" + generate_file("t.txt", src) + open("t.txt", "rb") {|f| + assert_equal(src, f.read) + } + open("t.txt", "r", :binmode=>true) {|f| + assert_equal(src, f.read) + } + if system_newline == "\n" + open("t.txt", "r") {|f| + assert_equal(src, f.read) + } + end + } + end + + def test_binmode + with_tmpdir { + src = "a\r\nb\r\nc\r\n" + generate_file("t.txt", src) + open("t.txt", "rt") {|f| + assert_equal("a", f.getc) + assert_equal("\n", f.getc) + f.binmode + assert_equal("b", f.getc) + assert_equal("\r", f.getc) + assert_equal("\n", f.getc) + assert_equal("c", f.getc) + assert_equal("\r", f.getc) + assert_equal("\n", f.getc) + assert_equal(nil, f.getc) + } + } + end + + def test_binmode2 + with_tmpdir { + src = "a\r\nb\r\nc\r\n" + generate_file("t.txt", src) + open("t.txt", "rt:euc-jp:utf-8") {|f| + assert_equal("a", f.getc) + assert_equal("\n", f.getc) + f.binmode + assert_equal("b", f.getc) + assert_equal("\r", f.getc) + assert_equal("\n", f.getc) + assert_equal("c", f.getc) + assert_equal("\r", f.getc) + assert_equal("\n", f.getc) + assert_equal(nil, f.getc) + } + } + end + + def test_binmode3 + with_tmpdir { + src = "\u3042\r\n" + generate_file("t.txt", src) + srcbin = src.b + open("t.txt", "rt:utf-8:euc-jp") {|f| + f.binmode + result = f.read + assert_str_equal(srcbin, result) + assert_equal(Encoding::ASCII_8BIT, result.encoding) + } + } + end + + def test_invalid_r + with_tmpdir { + generate_file("t.txt", "a\x80b") + open("t.txt", "r:utf-8:euc-jp", :invalid => :replace) {|f| + assert_equal("a?b", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :invalid => :replace, :replace => "") {|f| + assert_equal("ab", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :undef => :replace) {|f| + assert_raise(Encoding::InvalidByteSequenceError) { f.read } + assert_equal("b", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :undef => :replace, :replace => "") {|f| + assert_raise(Encoding::InvalidByteSequenceError) { f.read } + assert_equal("b", f.read) + } + } + end + + def test_undef_r + with_tmpdir { + generate_file("t.txt", "a\uFFFDb") + open("t.txt", "r:utf-8:euc-jp", :undef => :replace) {|f| + assert_equal("a?b", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :undef => :replace, :replace => "") {|f| + assert_equal("ab", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :invalid => :replace) {|f| + assert_raise(Encoding::UndefinedConversionError) { f.read } + assert_equal("b", f.read) + } + open("t.txt", "r:utf-8:euc-jp", :invalid => :replace, :replace => "") {|f| + assert_raise(Encoding::UndefinedConversionError) { f.read } + assert_equal("b", f.read) + } + } + end + + def test_invalid_w + with_tmpdir { + invalid_utf8 = "a\x80b".force_encoding("utf-8") + open("t.txt", "w:euc-jp", :invalid => :replace) {|f| + assert_nothing_raised { f.write invalid_utf8 } + } + assert_equal("a?b", File.read("t.txt")) + + open("t.txt", "w:euc-jp", :invalid => :replace, :replace => "") {|f| + assert_nothing_raised { f.write invalid_utf8 } + } + assert_equal("ab", File.read("t.txt")) + + open("t.txt", "w:euc-jp", :undef => :replace) {|f| + assert_raise(Encoding::InvalidByteSequenceError) { f.write invalid_utf8 } + } + open("t.txt", "w:euc-jp", :undef => :replace, :replace => "") {|f| + assert_raise(Encoding::InvalidByteSequenceError) { f.write invalid_utf8 } + } + } + end + + def test_undef_w_stateless + with_tmpdir { + generate_file("t.txt", "a\uFFFDb") + open("t.txt", "w:euc-jp:utf-8", :undef => :replace) {|f| + assert_nothing_raised { f.write "a\uFFFDb" } + } + assert_equal("a?b", File.read("t.txt")) + open("t.txt", "w:euc-jp:utf-8", :undef => :replace, :replace => "") {|f| + assert_nothing_raised { f.write "a\uFFFDb" } + } + assert_equal("ab", File.read("t.txt")) + open("t.txt", "w:euc-jp:utf-8", :invalid => :replace) {|f| + assert_raise(Encoding::UndefinedConversionError) { f.write "a\uFFFDb" } + } + open("t.txt", "w:euc-jp:utf-8", :invalid => :replace, :replace => "") {|f| + assert_raise(Encoding::UndefinedConversionError) { f.write "a\uFFFDb" } + } + } + end + + def test_undef_w_stateful + with_tmpdir { + generate_file("t.txt", "a\uFFFDb") + open("t.txt", "w:iso-2022-jp:utf-8", :undef => :replace) {|f| + assert_nothing_raised { f.write "a\uFFFDb" } + } + assert_equal("a?b", File.read("t.txt")) + open("t.txt", "w:iso-2022-jp:utf-8", :undef => :replace, :replace => "") {|f| + assert_nothing_raised { f.write "a\uFFFDb" } + } + assert_equal("ab", File.read("t.txt")) + open("t.txt", "w:iso-2022-jp:utf-8", :invalid => :replace) {|f| + assert_raise(Encoding::UndefinedConversionError) { f.write "a\uFFFDb" } + } + open("t.txt", "w:iso-2022-jp:utf-8", :invalid => :replace, :replace => "") {|f| + assert_raise(Encoding::UndefinedConversionError) { f.write "a\uFFFDb" } + } + } + end + + def test_w_xml_attr + with_tmpdir { + open("raw.txt", "wb", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } + content = File.read("raw.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"&<>"'\u4E02\u3042\n\"".force_encoding("ascii-8bit"), content) + + open("ascii.txt", "wb:us-ascii", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } + content = File.read("ascii.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"&<>"'丂あ\n\"".force_encoding("ascii-8bit"), content) + + open("iso-2022-jp.txt", "wb:iso-2022-jp", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } + content = File.read("iso-2022-jp.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"&<>"'丂\e$B$\"\e(B\n\"".force_encoding("ascii-8bit"), content) + + open("utf-16be.txt", "wb:utf-16be", xml: :attr) {|f| f.print '&<>"\''; f.puts "\u4E02\u3042" } + content = File.read("utf-16be.txt", :mode=>"rb:ascii-8bit") + assert_equal("\0\"\0&\0a\0m\0p\0;\0&\0l\0t\0;\0&\0g\0t\0;\0&\0q\0u\0o\0t\0;\0&\0a\0p\0o\0s\0;\x4E\x02\x30\x42\0\n\0\"".force_encoding("ascii-8bit"), content) + + open("eucjp.txt", "w:euc-jp:utf-8", xml: :attr) {|f| + f.print "\u4E02" # U+4E02 is 0x3021 in JIS X 0212 + } + content = File.read("eucjp.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"\x8F\xB0\xA1\"".force_encoding("ascii-8bit"), content) + + open("sjis.txt", "w:sjis:utf-8", xml: :attr) {|f| + f.print "\u4E02" # U+4E02 is 0x3021 in JIS X 0212 + } + content = File.read("sjis.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"丂\"".force_encoding("ascii-8bit"), content) + + open("iso-2022-jp.txt", "w:iso-2022-jp:utf-8", xml: :attr) {|f| + f.print "\u4E02" # U+4E02 is 0x3021 in JIS X 0212 + } + content = File.read("iso-2022-jp.txt", :mode=>"rb:ascii-8bit") + assert_equal("\"丂\"".force_encoding("ascii-8bit"), content) + } + end + + %w/UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE/.each do |name| + define_method("test_strip_bom:#{name}") do + path = "#{name}-bom.txt" + with_tmpdir { + text = "\uFEFF\u0100a" + stripped = "\u0100a" + content = text.encode(name) + generate_file(path, content) + result = File.read(path, mode: 'rb:BOM|UTF-8') + assert_equal(Encoding.find(name), result.encoding, name) + assert_equal(content[1..-1].b, result.b, name) + %w[rb rt r].each do |mode| + message = "#{name}, mode: #{mode.dump}" + result = File.read(path, mode: "#{mode}:BOM|UTF-8:UTF-8") + assert_equal(Encoding::UTF_8, result.encoding, message) + assert_equal(stripped, result, message) + end + + File.open(path, "rb") {|f| + assert_equal(Encoding.find(name), f.set_encoding_by_bom) + } + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } + } + end + end + + def test_strip_bom_no_conv + with_tmpdir { + path = 'UTF-8-bom.txt' + generate_file(path, "\uFEFFa") + + bug3407 = '[ruby-core:30641]' + result = File.read(path, encoding: 'BOM|UTF-8') + assert_equal("a", result.b, bug3407) + + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } + } + end + + def test_strip_bom_invalid + with_tmpdir { + path = 'UTF-8-bom.txt' + generate_file(path, "\uFEFFa") + + bug8323 = '[ruby-core:54563] [Bug #8323]' + expected = "a\xff".force_encoding("utf-8") + 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) + } + end + + def test_strip_bom_no_bom + with_tmpdir { + bug8323 = '[ruby-core:54563] [Bug #8323]' + path = 'ascii.txt' + stripped = "a" + generate_file(path, stripped) + result = File.read(path, encoding: 'BOM|UTF-8') + assert_equal(stripped, result, bug8323) + result = File.read(path, encoding: 'BOM|UTF-8:UTF-8') + assert_equal(stripped, result, bug8323) + + File.open(path, "rb") {|f| + assert_nil(f.set_encoding_by_bom) + } + File.open(path, "rb", encoding: "iso-8859-1") {|f| + assert_raise(ArgumentError) {f.set_encoding_by_bom} + } + } + end + + def test_bom_too_long_utfname + assert_separately([], "#{<<~"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 + + def test_cbuf + with_tmpdir { + fn = "tst" + open(fn, "w") {|f| f.print "foo" } + open(fn, "r+t") {|f| + f.ungetc(f.getc) + assert_raise(IOError, "[ruby-dev:40493]") { f.readpartial(2) } + assert_raise(IOError) { f.read(2) } + assert_raise(IOError) { f.each_byte {|c| } } + assert_raise(IOError) { f.getbyte } + assert_raise(IOError) { f.ungetbyte(0) } + assert_raise(IOError) { f.sysread(2) } + assert_raise(IOError) { IO.copy_stream(f, "tmpout") } + assert_raise(IOError) { f.sysseek(2) } + } + open(fn, "r+t") {|f| + f.ungetc(f.getc) + assert_equal("foo", f.read) + } + } + end + + def test_text_mode_ungetc_eof + with_tmpdir { + open("ff", "w") {|f| } + open("ff", "rt") {|f| + f.ungetc "a" + assert_not_predicate(f, :eof?, "[ruby-dev:40506] (3)") + } + } + end + + def test_cbuf_select + 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 + 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 + 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]'] + 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]'] + 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 + + 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 8192. This generates '\r' at 8192. + lines = ["X" * 8188, "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 + + def test_each_codepoint_encoding_with_ungetc + File.open(File::NULL, "rt:utf-8") do |f| + f.ungetc(%Q[\u{3042}\u{3044}\u{3046}]) + assert_equal [0x3042, 0x3044, 0x3046], f.each_codepoint.to_a + end + File.open(File::NULL, "rt:us-ascii") do |f| + f.ungetc(%Q[\u{3042}\u{3044}\u{3046}]) + assert_raise(ArgumentError) do + f.each_codepoint.to_a + end + end + end +end diff --git a/test/ruby/test_io_timeout.rb b/test/ruby/test_io_timeout.rb new file mode 100644 index 0000000000..e017395980 --- /dev/null +++ b/test/ruby/test_io_timeout.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: false + +require 'io/nonblock' + +class TestIOTimeout < Test::Unit::TestCase + def with_pipe + omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + + begin + i, o = UNIXSocket.pair + + yield i, o + ensure + i.close + o.close + end + end + + def test_timeout_attribute + with_pipe do |i, o| + assert_nil i.timeout + + i.timeout = 10 + assert_equal 10, i.timeout + assert_nil o.timeout + + o.timeout = 20 + assert_equal 20, o.timeout + assert_equal 10, i.timeout + end + end + + def test_timeout_read_exception + with_pipe do |i, o| + i.timeout = 0.0001 + + assert_raise(IO::TimeoutError) {i.read} + end + end + + def test_timeout_gets_exception + with_pipe do |i, o| + i.timeout = 0.0001 + + assert_raise(IO::TimeoutError) {i.gets} + end + end + + def test_timeout_puts + with_pipe do |i, o| + i.timeout = 0.0001 + o.puts("Hello World") + o.close + + assert_equal "Hello World", i.gets.chomp + end + end +end diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb new file mode 100644 index 0000000000..fa716787fe --- /dev/null +++ b/test/ruby/test_iseq.rb @@ -0,0 +1,996 @@ +require 'test/unit' +require 'tempfile' + +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) + unless line + line = caller_locations(1).first.lineno + end + EnvUtil.suppress_warning do + ISeq.new(src, __FILE__, __FILE__, line, opt) + end + end + + def lines src, lines = nil + body = compile(src, lines).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 + assert_equal [__LINE__+1, __LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1) + p __LINE__ # 1 + p __LINE__ # 2 + # 3 + p __LINE__ # 4 + EOS + + assert_equal [__LINE__+2, __LINE__+4], lines(<<-EOS, __LINE__+1) + # 1 + p __LINE__ # 2 + # 3 + p __LINE__ # 4 + # 5 + EOS + + assert_equal [__LINE__+3, __LINE__+4, __LINE__+7, __LINE__+9], lines(<<~EOS, __LINE__+1) + 1 # should be optimized out + 2 # should be optimized out + p __LINE__ # 3 + p __LINE__ # 4 + 5 # should be optimized out + 6 # should be optimized out + p __LINE__ # 7 + 8 # should be optimized out + 9 + EOS + end + + def test_unsupported_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_cdhash_after_roundtrip + # CDHASH was not built properly when loading from binary and + # was causing opt_case_dispatch to clobber its stack canary + # for its "leaf" instruction attribute. + iseq = compile(<<~EOF, __LINE__+1) + case Class.new(String).new("foo") + when "foo" + 42 + end + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_forwardable + iseq = compile(<<~EOF, __LINE__+1) + Class.new { + def bar(a, b); a + b; end + def foo(...); bar(...); end + } + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval.new.foo(40, 2)) + end + + def test_super_with_block + iseq = compile(<<~EOF, __LINE__+1) + def (Object.new).touch(*) # :nodoc: + foo { super } + end + 42 + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_super_with_block_hash_0 + iseq = compile(<<~EOF, __LINE__+1) + # [Bug #18250] `req` specifically cause `Assertion failed: (key != 0), function hash_table_raw_insert` + def (Object.new).touch(req, *) + foo { super } + end + 42 + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_super_with_block_and_kwrest + iseq = compile(<<~EOF, __LINE__+1) + def (Object.new).touch(**) # :nodoc: + foo { super } + end + 42 + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_lambda_with_ractor_roundtrip + iseq = compile(<<~EOF, __LINE__+1) + x = 42 + y = Ractor.shareable_lambda{x} + y.call + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_super_with_anonymous_block + iseq = compile(<<~EOF, __LINE__+1) + def (Object.new).touch(&) # :nodoc: + foo { super } + end + 42 + EOF + assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) + end + + def test_ractor_unshareable_outer_variable + name = "\u{2603 26a1}" + assert_raise_with_message(ArgumentError, /\(#{name}\)/) do + eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}") + end + + assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do + eval("#{name} = []; Ractor.shareable_proc{#{name}}") + end + + obj = Object.new + def obj.foo(*) Ractor.shareable_proc{super} end + assert_raise_with_message(Ractor::IsolationError, /cannot make a shareable Proc because it can refer unshareable object \[\]/) do + obj.foo(*[]) + end + end + + def test_ractor_shareable_value_frozen_core + iseq = RubyVM::InstructionSequence.compile(<<~'RUBY') + # shareable_constant_value: literal + REGEX = /#{}/ # [Bug #20569] + RUBY + assert_includes iseq_to_binary(iseq), "REGEX".b + end + + 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 + + def test_compile_file_encoding + Tempfile.create(%w"test_iseq .rb") do |f| + f.puts "{ '\u00de' => 'Th', '\u00df' => 'ss', '\u00e0' => 'a' }" + f.close + + EnvUtil.with_default_external(Encoding::US_ASCII) do + assert_warn('') { + load f.path + } + assert_nothing_raised(SyntaxError) { + RubyVM::InstructionSequence.compile_file(f.path) + } + end + end + end + + LINE_BEFORE_METHOD = __LINE__ + def method_test_line_trace + + _a = 1 + + _b = 2 + + end + + def test_line_trace + iseq = compile(<<~EOF, __LINE__+1) + a = 1 + b = 2 + c = 3 + # d = 4 + e = 5 + # f = 6 + g = 7 + EOF + + assert_equal([1, 2, 3, 5, 7], iseq.line_trace_all) + iseq.line_trace_specify(1, true) # line 2 + iseq.line_trace_specify(3, true) # line 5 + + 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_invalid_source_no_memory_leak + # [Bug #21394] + assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + code = proc do |t| + RubyVM::InstructionSequence.new(nil) + rescue TypeError + else + raise "TypeError was not raised during RubyVM::InstructionSequence.new" + end + + 10.times(&code) + begin; + 1_000_000.times(&code) + end; + + # [Bug #21394] + # RubyVM::InstructionSequence.new calls rb_io_path, which dups the string + # and can leak memory if the dup raises + assert_no_memory_leak(["-rtempfile"], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true) + MyError = Class.new(StandardError) + String.prepend(Module.new do + def initialize_dup(_) + if $raise_on_dup + raise MyError + else + super + end + end + end) + + code = proc do |t| + Tempfile.create do |f| + $raise_on_dup = true + t.times do + RubyVM::InstructionSequence.new(f) + rescue MyError + else + raise "MyError was not raised during RubyVM::InstructionSequence.new" + end + ensure + $raise_on_dup = false + end + end + + code.call(100) + begin; + code.call(1_000_000) + end; + 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_not_predicate(s3, :frozen?) + assert_not_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) + + if e1.message.lines[0] == "#{__FILE__}:#{line}: syntax errors found\n" + # Prism lays out the error messages in line with the source, so the + # following assertions do not make sense in that context. + else + message = e1.message.each_line + message.with_index(1) do |line, i| + next if /^ / =~ line + assert_send([line, :start_with?, __FILE__], + proc {message.map {|l, j| (i == j ? ">" : " ") + l}.join("")}) + end + end + end + + # [Bug #19173] + def test_compile_error + assert_raise SyntaxError do + RubyVM::InstructionSequence.compile 'using Module.new; yield' + end + end + + def test_compile_file_error + Tempfile.create(%w"test_iseq .rb") do |f| + f.puts "end" + 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 anon_star(*); end + + def test_anon_rest_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_star)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:*], param_names + end + + def anon_keyrest(**); end + + def test_anon_keyrest_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_keyrest)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:**], param_names + end + + def anon_block(&); end + + def test_anon_block_param_in_disasm + iseq = RubyVM::InstructionSequence.of(method(:anon_block)) + param_names = iseq.to_a[iseq.to_a.index(:method) + 1] + assert_equal [:&], param_names + end + + def strip_lineno(source) + source.gsub(/^.*?: /, "") + end + + def sample_iseq + ISeq.compile(strip_lineno(<<-EOS)) + 1: class C + 2: def foo + 3: begin + 4: rescue + 5: p :rescue + 6: ensure + 7: p :ensure + 8: end + 9: end + 10: def bar + 11: 1.times{ + 12: 2.times{ + 13: } + 14: } + 15: end + 16: end + 17: class D < C + 18: end + EOS + end + + def test_each_child + iseq = sample_iseq + + collect_iseq = lambda{|iseq| + iseqs = [] + iseq.each_child{|child_iseq| + iseqs << collect_iseq.call(child_iseq) + } + ["#{iseq.label}@#{iseq.first_lineno}", *iseqs.sort_by{|k, *| k}] + } + + expected = ["<compiled>@1", + ["<class:C>@1", + ["bar@10", ["block in bar@11", + ["block (2 levels) in bar@12"]]], + ["foo@2", ["ensure in foo@7"], + ["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@7", [[7, :line]]]], + [["rescue in foo@4", [[5, :line], + [5, :rescue]]]]]], + [["<class:D>@17", [[17, :class], + [18, :end]]]]], collect_iseq.call(sample_iseq) + end + + def test_empty_iseq_lineno + iseq = ISeq.compile(<<-EOS) + # 1 + # 2 + def foo # line 3 empty method + end # line 4 + 1.time do # line 5 empty block + end # line 6 + class C # line 7 empty class + end + EOS + + iseq.each_child{|ci| + ary = ci.to_a + type = ary[9] + name = ary[5] + line = ary[13].first + case type + when :method + assert_equal "foo", name + assert_equal 3, line + when :class + assert_equal '<class:C>', name + assert_equal 7, line + when :block + assert_equal 'block in <compiled>', name + assert_equal 5, line + else + raise "unknown ary: " + ary.inspect + end + } + end + + def hexdump(bin) + bin.unpack1("H*").gsub(/.{1,32}/) {|s| + "#{'%04x:' % $~.begin(0)}#{s.gsub(/../, " \\&").tap{|_|_[24]&&="-"}}\n" + } + end + + def iseq_to_binary(iseq) + iseq.to_binary + rescue RuntimeError => e + omit e.message if /compile with coverage/ =~ e.message + raise + end + + def assert_iseq_to_binary(code, mesg = nil) + iseq = RubyVM::InstructionSequence.compile(code) + bin = assert_nothing_raised(mesg) do + iseq_to_binary(iseq) + end + 10.times do + bin2 = iseq_to_binary(iseq) + assert_equal(bin, bin2, message(mesg) {diff hexdump(bin), hexdump(bin2)}) + end + iseq2 = RubyVM::InstructionSequence.load_from_binary(bin) + a1 = iseq.to_a + a2 = iseq2.to_a + assert_equal(a1, a2, message(mesg) {diff iseq.disassemble, iseq2.disassemble}) + if iseq2.script_lines + assert_kind_of(Array, iseq2.script_lines) + else + assert_nil(iseq2.script_lines) + end + iseq2 + end + + def test_to_binary_with_hidden_local_variables + assert_iseq_to_binary("for _foo in bar; end") + + bin = iseq_to_binary(RubyVM::InstructionSequence.compile(<<-RUBY)) + Object.new.instance_eval do + a = [] + def self.bar; [1] end + for foo in bar + a << (foo * 2) + end + a + end + RUBY + v = RubyVM::InstructionSequence.load_from_binary(bin).eval + assert_equal([2], v) + end + + def test_to_binary_with_objects + assert_iseq_to_binary("[]"+100.times.map{|i|"<</#{i}/"}.join) + assert_iseq_to_binary("@x ||= (1..2)") + end + + def test_to_binary_pattern_matching + code = "case foo; in []; end" + iseq = compile(code) + assert_include(iseq.disasm, "TypeError") + assert_include(iseq.disasm, "NoMatchingPatternError") + EnvUtil.suppress_warning do + assert_iseq_to_binary(code, "[Feature #14912]") + end + end + + def test_to_binary_dumps_nokey + iseq = assert_iseq_to_binary(<<-RUBY) + o = Object.new + class << o + def foo(**nil); end + end + o + RUBY + assert_equal([[:nokey]], iseq.eval.singleton_method(:foo).parameters) + end + + def test_to_binary_line_info + assert_iseq_to_binary("#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #14660]').eval + begin; + class P + def p; end + def q; end + E = "" + N = "#{E}" + attr_reader :i + end + end; + + # cleanup + ::Object.class_eval do + remove_const :P + end + end + + def collect_from_binary_tracepoint_lines(tracepoint_type, filename) + iseq = RubyVM::InstructionSequence.compile(strip_lineno(<<-RUBY), filename) + class A + class B + 2.times { + def self.foo + _a = 'good day' + raise + rescue + 'dear reader' + end + } + end + B.foo + end + RUBY + + iseq_bin = iseq_to_binary(iseq) + iseq = ISeq.load_from_binary(iseq_bin) + lines = [] + TracePoint.new(tracepoint_type){|tp| + next unless tp.path == filename + lines << tp.lineno + }.enable{ + EnvUtil.suppress_warning {iseq.eval} + } + + lines + ensure + Object.send(:remove_const, :A) rescue nil + 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__), + begin; raise "error"; rescue => error; error.backtrace_locations[0]; end + ].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"), + begin; raise "error"; rescue => error; error.backtrace_locations[0]; end + ].each{|src| + iseq1 = RubyVM::InstructionSequence.of(src) + iseq2 = RubyVM::InstructionSequence.of(src) + + # ISeq objects should be same for same src + assert_equal iseq1.object_id, iseq2.object_id + } + end + + def test_iseq_builtin_to_a + invokebuiltin = eval(EnvUtil.invoke_ruby(['-e', <<~EOS], '', true).first) + insns = RubyVM::InstructionSequence.of([].method(:pack)).to_a.last + p insns.find { |insn| insn.is_a?(Array) && insn[0] == :opt_invokebuiltin_delegate_leave } + EOS + assert_not_nil(invokebuiltin) + assert_equal([:func_ptr, :argc, :index, :name], invokebuiltin[1].keys) + end + + def test_iseq_builtin_load + Tempfile.create(["builtin", ".iseq"]) do |f| + f.binmode + f.write(iseq_to_binary(RubyVM::InstructionSequence.of(1.method(:abs)))) + f.close + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + bin = File.binread(ARGV[0]) + assert_raise(ArgumentError) do + RubyVM::InstructionSequence.load_from_binary(bin) + end + end; + end + end + + def test_iseq_option_debug_level + assert_raise(TypeError) {ISeq.compile("", debug_level: "")} + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + RubyVM::InstructionSequence.compile("", debug_level: 5) + end; + end + + def test_mandatory_only + assert_separately [], <<~RUBY + at0 = Time.at(0) + assert_equal at0, Time.public_send(:at, 0, 0) + RUBY + end + + def test_mandatory_only_redef + assert_separately ['-W0'], <<~RUBY + r = Ractor.new{ + Float(10) + module Kernel + undef Float + def Float(n) + :new + end + end + GC.start + Float(30) + } + assert_equal :new, r.value + RUBY + end + + def test_ever_condition_loop + assert_ruby_status([], "BEGIN {exit}; while true && true; end") + end + + def test_unreachable_syntax_error + mesg = /Invalid break/ + assert_syntax_error("false and break", mesg) + assert_syntax_error("if false and break; end", mesg) + end + + def test_unreachable_pattern_matching + assert_in_out_err([], "true or 1 in 1") + assert_in_out_err([], "true or (case 1; in 1; 1; in 2; 2; end)") + end + + def test_unreachable_pattern_matching_in_if_condition + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", %w[1]) + begin; + if true or {a: 0} in {a:} + p 1 + else + p a + end + end; + end + + def test_unreachable_next_in_block + bug20344 = '[ruby-core:117210] [Bug #20344]' + assert_nothing_raised(SyntaxError, bug20344) do + compile(<<~RUBY) + proc do + next + + case nil + when "a" + next + when "b" + when "c" + proc {} + end + + next + end + RUBY + end + end + + def test_serialize_anonymous_outer_variables + iseq = RubyVM::InstructionSequence.compile(<<~'RUBY') + obj = Object.new + def obj.test + [1].each do + raise "Oops" + rescue + return it + end + end + obj + RUBY + + binary = iseq.to_binary # [Bug # 21370] + roundtripped_iseq = RubyVM::InstructionSequence.load_from_binary(binary) + object = roundtripped_iseq.eval + assert_equal 1, object.test + end + + def test_loading_kwargs_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", rss: true) + a = RubyVM::InstructionSequence.compile("foo(bar: :baz)").to_binary + begin; + 1_000_000.times do + RubyVM::InstructionSequence.load_from_binary(a) + end + end; + end + + def test_ibf_bignum + iseq = RubyVM::InstructionSequence.compile("0x0"+"_0123_4567_89ab_cdef"*5) + expected = iseq.eval + result = RubyVM::InstructionSequence.load_from_binary(iseq_to_binary(iseq)).eval + assert_equal expected, result, proc {sprintf("expected: %x, result: %x", expected, result)} + end + + def test_compile_prism_with_file + Tempfile.create(%w"test_iseq .rb") do |f| + f.puts "_name = 'Prism'; puts 'hello'" + f.close + + assert_nothing_raised(TypeError) do + RubyVM::InstructionSequence.compile_prism(f) + end + end + end + + def block_using_method + yield + end + + def block_unused_method + end + + def test_unused_param + a = RubyVM::InstructionSequence.of(method(:block_using_method)).to_a + + assert_equal true, a.dig(11, :use_block) + + b = RubyVM::InstructionSequence.of(method(:block_unused_method)).to_a + assert_equal nil, b.dig(11, :use_block) + end + + def test_compile_prism_with_invalid_object_type + assert_raise(TypeError) do + RubyVM::InstructionSequence.compile_prism(Object.new) + end + end + + def test_load_from_binary_only_accepts_string_param + assert_raise(TypeError) do + var_0 = 0 + RubyVM::InstructionSequence.load_from_binary(var_0) + end + end + + def test_while_in_until_condition + assert_in_out_err(["--dump=i", "-e", "until while 1; end; end"]) do |stdout, stderr, status| + assert_include(stdout.shift, "== disasm:") + assert_include(stdout.pop, "leave") + assert_predicate(status, :success?) + end + end + + def test_compile_empty_under_gc_stress + EnvUtil.under_gc_stress do + RubyVM::InstructionSequence.compile_file(File::NULL) + end + end +end diff --git a/test/ruby/test_iterator.rb b/test/ruby/test_iterator.rb new file mode 100644 index 0000000000..1bb655d52e --- /dev/null +++ b/test/ruby/test_iterator.rb @@ -0,0 +1,520 @@ +# frozen_string_literal: false +require 'test/unit' + +class Array + def iter_test1 + collect{|e| [e, yield(e)]}.sort{|a,b|a[1]<=>b[1]} + end + def iter_test2 + ary = collect{|e| [e, yield(e)]} + ary.sort{|a,b|a[1]<=>b[1]} + end +end + +class TestIterator < Test::Unit::TestCase + def test_yield_at_toplevel + assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert(!block_given?) + assert(!defined?(yield)) + end; + end + + def test_array + x = [1, 2, 3, 4] + y = [] + + # iterator over array + for i in x + y.push i + end + assert_equal(x, y) + end + + def tt + 1.upto(10) {|i| + yield i + } + end + + def tt2(dummy) + yield 1 + end + + def tt3(&block) + tt2(raise(ArgumentError,""),&block) + end + + def test_nested_iterator + i = 0 + tt{|j| break if j == 5} + assert_equal(0, i) + + assert_raise(ArgumentError) do + tt3{} + end + end + + def tt4 &block + tt2(raise(ArgumentError,""),&block) + end + + def test_block_argument_without_paren + assert_raise(ArgumentError) do + tt4{} + end + end + + # iterator break/redo/next + def test_break + done = true + loop{ + break if true + done = false # should not reach here + } + assert(done) + + done = false + bad = false + loop { + break if done + done = true + next if true + bad = true # should not reach here + } + assert(!bad) + + done = false + bad = false + loop { + break if done + done = true + redo if true + bad = true # should not reach here + } + assert(!bad) + + x = [] + for i in 1 .. 7 + x.push i + end + 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{|e|e}, x.iter_test2{|e|e}) + end + + class IterTest + def initialize(e); @body = e; end + + def each0(&block); @body.each(&block); end + def each1(&block); @body.each {|*x| block.call(*x) } end + def each2(&block); @body.each {|*x| block.call(x) } end + def each3(&block); @body.each {|x| block.call(*x) } end + def each4(&block); @body.each {|x| block.call(x) } end + def each5; @body.each {|*x| yield(*x) } end + def each6; @body.each {|*x| yield(x) } end + def each7; @body.each {|x| yield(*x) } end + def each8; @body.each {|x| yield(x) } end + + def f(a) + a + end + end + + def test_itertest + assert_equal([1], IterTest.new(nil).method(:f).to_proc.call([1])) + m = /\w+/.match("abc") + assert_equal([m], IterTest.new(nil).method(:f).to_proc.call([m])) + + IterTest.new([0]).each0 {|x| assert_equal(0, x)} + IterTest.new([1]).each1 {|x| assert_equal(1, x)} + IterTest.new([2]).each2 {|x| assert_equal([2], x)} + IterTest.new([4]).each4 {|x| assert_equal(4, x)} + IterTest.new([5]).each5 {|x| assert_equal(5, x)} + IterTest.new([6]).each6 {|x| assert_equal([6], x)} + IterTest.new([8]).each8 {|x| assert_equal(8, x)} + + IterTest.new([[0]]).each0 {|x| assert_equal([0], x)} + IterTest.new([[1]]).each1 {|x| assert_equal([1], x)} + IterTest.new([[2]]).each2 {|x| assert_equal([[2]], x)} + IterTest.new([[3]]).each3 {|x| assert_equal(3, x)} + IterTest.new([[4]]).each4 {|x| assert_equal([4], x)} + IterTest.new([[5]]).each5 {|x| assert_equal([5], x)} + IterTest.new([[6]]).each6 {|x| assert_equal([[6]], x)} + IterTest.new([[7]]).each7 {|x| assert_equal(7, x)} + IterTest.new([[8]]).each8 {|x| assert_equal([8], x)} + + IterTest.new([[0,0]]).each0 {|*x| assert_equal([[0,0]], x)} + IterTest.new([[8,8]]).each8 {|*x| assert_equal([[8,8]], x)} + end + + def m(var) + var + end + + def m1 + m(block_given?) + end + + def m2 + m(block_given?,&proc{}) + end + + def test_block_given + verbose_bak, $VERBOSE = $VERBOSE, nil + assert(m1{p 'test'}) + assert(m2{p 'test'}) + assert(!m1()) + assert(!m2()) + ensure + $VERBOSE = verbose_bak + end + + def m3(var, &block) + m(yield(var), &block) + end + + def m4(&block) + m(m1(), &block) + end + + def test_block_passing + assert(!m4()) + assert(!m4 {}) + assert_equal(100, m3(10) {|x|x*x}) + end + + class C + include Enumerable + def initialize + @a = [1,2,3] + end + def each(&block) + @a.each(&block) + end + end + + def test_collect + assert_equal([1,2,3], C.new.collect{|n| n}) + end + + def test_proc + assert_instance_of(Proc, lambda{}) + assert_instance_of(Proc, Proc.new{}) + lambda{|a|assert_equal(a, 1)}.call(1) + end + + def test_block + assert_instance_of(NilClass, get_block) + assert_instance_of(Proc, get_block{}) + end + + def test_argument + assert_nothing_raised {lambda{||}.call} + assert_raise(ArgumentError) {lambda{||}.call(1)} + assert_nothing_raised {lambda{|a,|}.call(1)} + assert_raise(ArgumentError) {lambda{|a,|}.call()} + assert_raise(ArgumentError) {lambda{|a,|}.call(1,2)} + end + + def get_block(&block) + block + end + + def test_get_block + assert_instance_of(Proc, get_block{}) + assert_nothing_raised {get_block{||}.call()} + assert_nothing_raised {get_block{||}.call(1)} + assert_nothing_raised {get_block{|a,|}.call(1)} + assert_nothing_raised {get_block{|a,|}.call()} + assert_nothing_raised {get_block{|a,|}.call(1,2)} + + assert_nothing_raised {get_block(&lambda{||}).call()} + assert_raise(ArgumentError) {get_block(&lambda{||}).call(1)} + assert_nothing_raised {get_block(&lambda{|a,|}).call(1)} + assert_raise(ArgumentError) {get_block(&lambda{|a,|}).call(1,2)} + + block = get_block{11} + assert_instance_of(Proc, block) + assert_instance_of(Proc, block.to_proc) + assert_equal(block.clone.call, 11) + assert_instance_of(Proc, get_block(&block)) + + lmd = lambda{44} + assert_instance_of(Proc, lmd) + assert_instance_of(Proc, lmd.to_proc) + assert_equal(lmd.clone.call, 44) + assert_instance_of(Proc, get_block(&lmd)) + + assert_equal(1, Proc.new{|a,| a}.call(1,2,3)) + assert_nothing_raised {Proc.new{|a,|}.call(1,2)} + end + + def return1_test + Proc.new { + return 55 + }.call + 5 + end + + def test_return1 + assert_equal(55, return1_test()) + end + + def return2_test + lambda { + return 55 + }.call + 5 + end + + def test_return2 + assert_equal(60, return2_test()) + end + + def proc_call(&b) + b.call + end + def proc_call2(b) + b.call + end + def proc_yield() + yield + end + def proc_return1 + proc_call{return 42}+1 + end + + def test_proc_return1 + assert_equal(42, proc_return1()) + end + + def proc_return2 + proc_yield{return 42}+1 + end + + def test_proc_return2 + assert_equal(42, proc_return2()) + end + + def test_ljump + assert_raise(LocalJumpError) {get_block{break}.call} + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + # See the commit https://github.com/ruby/ruby/commit/7d8a415bc2d08a1b5e9d1ea802493b6eeb99c219 + # This block is not used but this is intentional. + # | + # +-----------------------------------------------------+ + # | + # vv + assert_raise(LocalJumpError) {proc_call2(get_block{break}){}} + ensure + $VERBOSE = verbose_bak + end + + # cannot use assert_nothing_raised due to passing block. + begin + val = lambda{break 11}.call + rescue LocalJumpError + assert(false, "LocalJumpError occurred from break in lambda") + else + assert_equal(11, val) + end + + block = get_block{11} + lmd = lambda{44} + assert_equal(0, block.arity) + assert_equal(0, lmd.arity) + assert_equal(0, lambda{||}.arity) + assert_equal(1, lambda{|a|}.arity) + assert_equal(1, lambda{|a,|}.arity) + assert_equal(2, lambda{|a,b|}.arity) + end + + def marity_test(m) + mobj = method(m) + assert_equal(mobj.arity, mobj.to_proc.arity) + end + + def test_marity + marity_test(:assert) + marity_test(:marity_test) + marity_test(:p) + + get_block{|a,n| assert(a,n)}.call(true, "marity") + end + + def foo + yield(:key, :value) + end + def bar(&blk) + blk.call(:key, :value) + end + + def test_yield_vs_call + foo{|k,v| assert_equal([:key, :value], [k,v])} + bar{|k,v| assert_equal([:key, :value], [k,v])} + end + + class H + def each + yield [:key, :value] + end + alias each_pair each + end + + def test_assoc_yield + [{:key=>:value}, H.new].each {|h| + h.each{|a| assert_equal([:key, :value], a)} + h.each{|a,| assert_equal(:key, a)} + h.each{|*a| assert_equal([[:key, :value]], a)} + h.each{|k,v| assert_equal([:key, :value], [k,v])} + h.each_pair{|a| assert_equal([:key, :value], a)} + h.each_pair{|a,| assert_equal(:key, a)} + h.each_pair{|*a| assert_equal([[:key, :value]], a)} + h.each_pair{|k,v| assert_equal([:key, :value], [k,v])} + } + end + + class ITER_TEST1 + def a + block_given? + end + end + + class ITER_TEST2 < ITER_TEST1 + include Test::Unit::Assertions + def a + assert(super) + super + end + end + + def test_iter_test2 + assert(ITER_TEST2.new.a {}) + end + + class ITER_TEST3 + def foo x + return yield if block_given? + x + end + end + + class ITER_TEST4 < ITER_TEST3 + include Test::Unit::Assertions + def foo x + assert_equal(super, yield) + assert_equal(x, super(x, &nil)) + end + end + + def test_iter4 + ITER_TEST4.new.foo(44){55} + end + + def test_break__nested_loop1 + _test_break__nested_loop1 do + break + end + end + + def _test_break__nested_loop1 + while true + yield + end + assert(false, "must not reach here") + end + + def test_break__nested_loop2 + _test_break__nested_loop2 do + break + end + end + + def _test_break__nested_loop2 + until false + yield + end + assert(false, "must not reach here") + end + + def test_break__nested_loop3 + _test_break__nested_loop3 do + break + end + end + + def _test_break__nested_loop3 + loop do + yield + end + assert(false, "must not reach here") + end + + def test_break_from_enum + result = ["a"].inject("ng") {|x,y| break "ok"} + assert_equal("ok", result) + end + + def _test_return_trace_func(x) + set_trace_func(proc {}) + [].fetch(2) {return x} + ensure + set_trace_func(nil) + end + + def test_return_trace_func + ok = "returned gracefully" + result = "skipped" + result = _test_return_trace_func(ok) + ensure + assert_equal(ok, result) + return + end + + class IterString < ::String + def ===(other) + super if !block_given? + end + end + + # Check that the block passed to an iterator + # does not get propagated inappropriately + def test_block_given_within_iterator + assert_equal(["b"], ["a", "b", "c"].grep(IterString.new("b")) {|s| s}) + end + + def test_enumerator + [1,2,3].each.with_index {|x,i| + assert_equal(x, i+1) + } + + e = [1,2,3].each + assert_equal(1, e.next) + assert_equal(2, e.next) + assert_equal(3, e.next) + assert_raise(StopIteration){e.next} + e.rewind + assert_equal(1, e.next) + e.rewind + a = [] + loop{a.push e.next} + assert_equal([1,2,3], a) + + assert_equal([[8, 1, 10], [6, 2, 11], [4, 3, 12]], + [8,6,4].zip((1..10),(10..100)).to_a) + 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..c836abd0c6 --- /dev/null +++ b/test/ruby/test_keyword.rb @@ -0,0 +1,4597 @@ +# frozen_string_literal: false +require 'test/unit' +require '-test-/rb_call_super_kw' +require '-test-/iter' + +class TestKeywordArguments < Test::Unit::TestCase + def f1(str: "foo", num: 424242) + [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_raise(ArgumentError) { 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 f11(**nil) + local_variables + end + + def test_f11 + h = {} + + assert_equal([], f11) + assert_equal([], f11(**{})) + assert_equal([], f11(**h)) + end + + def f12(**nil, &b) + [b, local_variables] + end + + def test_f12 + h = {} + b = proc{} + + assert_equal([nil, [:b]], f12) + assert_equal([nil, [:b]], f12(**{})) + assert_equal([nil, [:b]], f12(**h)) + assert_equal([b, [:b]], f12(&b)) + assert_equal([b, [:b]], f12(**{}, &b)) + assert_equal([b, [:b]], f12(**h, &b)) + end + + def f13(a, **nil) + a + end + + def test_f13 + assert_equal(1, f13(1)) + assert_equal(1, f13(1, **{})) + assert_raise(ArgumentError) { f13(a: 1) } + assert_raise(ArgumentError) { f13(1, a: 1) } + assert_raise(ArgumentError) { f13(**{a: 1}) } + assert_raise(ArgumentError) { f13(1, **{a: 1}) } + end + + def test_method_parameters + assert_equal([[:key, :str], [:key, :num]], method(:f1).parameters); + assert_equal([[:req, :x], [:key, :str], [:key, :num]], method(:f2).parameters); + 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_keyword_with_anonymous_keyword_splat + def self.a(b: 1, **) [b, **] end + kw = {b: 2, c: 3} + assert_equal([2, {c: 3}], a(**kw)) + assert_equal({b: 2, c: 3}, kw) + end + + def test_keyword_splat_nil + # cfunc call + assert_equal(nil, p(**nil)) + + def self.a0(&); end + assert_equal(nil, a0(**nil)) + assert_equal(nil, :a0.to_proc.call(self, **nil)) + assert_equal(nil, a0(**nil, &:block)) + + def self.o(x=1); x end + assert_equal(1, o(**nil)) + assert_equal(2, o(2, **nil)) + assert_equal(1, o(*nil, **nil)) + assert_equal(1, o(**nil, **nil)) + assert_equal({a: 1}, o(a: 1, **nil)) + assert_equal({a: 1}, o(**nil, a: 1)) + + # symproc call + assert_equal(1, :o.to_proc.call(self, **nil)) + + def self.s(*a); a end + assert_equal([], s(**nil)) + assert_equal([1], s(1, **nil)) + assert_equal([], s(*nil, **nil)) + + def self.kws(**a); a end + assert_equal({}, kws(**nil)) + assert_equal({}, kws(*nil, **nil)) + + def self.skws(*a, **kw); [a, kw] end + assert_equal([[], {}], skws(**nil)) + assert_equal([[1], {}], skws(1, **nil)) + assert_equal([[], {}], skws(*nil, **nil)) + + assert_equal({}, {**nil}) + assert_equal({a: 1}, {a: 1, **nil}) + assert_equal({a: 1}, {**nil, a: 1}) + 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 test_unset_hash_flag + bug18625 = "[ruby-core: 107847]" + singleton_class.class_eval do + ruby2_keywords def foo(*args) + args + end + + def single(arg) + arg + end + + def splat(*args) + args.last + end + + def kwargs(**kw) + kw + end + end + + h = { a: 1 } + args = foo(**h) + marked = args.last + assert_equal(true, Hash.ruby2_keywords_hash?(marked)) + + after_usage = single(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage)) + + after_usage = splat(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage, bug18625) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage), bug18625) + + after_usage = kwargs(*args) + assert_equal(h, after_usage) + assert_same(marked, args.last) + assert_not_same(marked, after_usage, bug18625) + assert_not_same(marked, after_usage) + assert_equal(false, Hash.ruby2_keywords_hash?(after_usage)) + + assert_equal(true, Hash.ruby2_keywords_hash?(marked)) + end + + def assert_equal_not_same(kw, res) + assert_instance_of(Hash, res) + assert_equal(kw, res) + assert_not_same(kw, res) + end + + def test_keyword_splat_new + kw = {} + h = {a: 1} + + def self.yo(**kw) kw end + m = method(:yo) + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + m = method(:send) + assert_equal(false, m.(:yo, **{}).frozen?) + assert_equal_not_same(kw, m.(:yo, **kw)) + assert_equal_not_same(h, m.(:yo, **h)) + assert_equal(false, m.send(:call, :yo, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, :yo, **kw)) + assert_equal_not_same(h, m.send(:call, :yo, **h)) + + singleton_class.send(:remove_method, :yo) + define_singleton_method(:yo) { |**kw| kw } + m = method(:yo) + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + yo = lambda { |**kw| kw } + m = yo.method(:call) + assert_equal(false, yo.(**{}).frozen?) + assert_equal_not_same(kw, yo.(**kw)) + assert_equal_not_same(h, yo.(**h)) + assert_equal(false, yo.send(:call, **{}).frozen?) + assert_equal_not_same(kw, yo.send(:call, **kw)) + assert_equal_not_same(h, yo.send(:call, **h)) + assert_equal(false, yo.public_send(:call, **{}).frozen?) + assert_equal_not_same(kw, yo.public_send(:call, **kw)) + assert_equal_not_same(h, yo.public_send(:call, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + yo = :yo.to_proc + m = yo.method(:call) + assert_equal(false, yo.(self, **{}).frozen?) + assert_equal_not_same(kw, yo.(self, **kw)) + assert_equal_not_same(h, yo.(self, **h)) + assert_equal(false, yo.send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, yo.send(:call, self, **kw)) + assert_equal_not_same(h, yo.send(:call, self, **h)) + assert_equal(false, yo.public_send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, yo.public_send(:call, self, **kw)) + assert_equal_not_same(h, yo.public_send(:call, self, **h)) + assert_equal(false, m.(self, **{}).frozen?) + assert_equal_not_same(kw, m.(self, **kw)) + assert_equal_not_same(h, m.(self, **h)) + assert_equal(false, m.send(:call, self, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, self, **kw)) + assert_equal_not_same(h, m.send(:call, self, **h)) + + c = Class.new do + def yo(**kw) kw end + end + o = c.new + def o.yo(**kw) super end + m = o.method(:yo) + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + o.singleton_class.send(:remove_method, :yo) + def o.yo(**kw) super(**kw) end + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + c = Class.new do + def method_missing(_, **kw) kw end + end + o = c.new + def o.yo(**kw) super end + m = o.method(:yo) + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + o.singleton_class.send(:remove_method, :yo) + def o.yo(**kw) super(**kw) end + assert_equal(false, o.yo(**{}).frozen?) + assert_equal_not_same(kw, o.yo(**kw)) + assert_equal_not_same(h, o.yo(**h)) + assert_equal(false, o.send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.send(:yo, **kw)) + assert_equal_not_same(h, o.send(:yo, **h)) + assert_equal(false, o.public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, o.public_send(:yo, **kw)) + assert_equal_not_same(h, o.public_send(:yo, **h)) + assert_equal(false, m.(**{}).frozen?) + assert_equal_not_same(kw, m.(**kw)) + assert_equal_not_same(h, m.(**h)) + assert_equal(false, m.send(:call, **{}).frozen?) + assert_equal_not_same(kw, m.send(:call, **kw)) + assert_equal_not_same(h, m.send(:call, **h)) + + c = Class.new do + attr_reader :kw + def initialize(**kw) @kw = kw end + end + m = c.method(:new) + assert_equal(false, c.new(**{}).kw.frozen?) + assert_equal_not_same(kw, c.new(**kw).kw) + assert_equal_not_same(h, c.new(**h).kw) + assert_equal(false, c.send(:new, **{}).kw.frozen?) + assert_equal_not_same(kw, c.send(:new, **kw).kw) + assert_equal_not_same(h, c.send(:new, **h).kw) + assert_equal(false, c.public_send(:new, **{}).kw.frozen?) + assert_equal_not_same(kw, c.public_send(:new, **kw).kw) + assert_equal_not_same(h, c.public_send(:new, **h).kw) + assert_equal(false, m.(**{}).kw.frozen?) + assert_equal_not_same(kw, m.(**kw).kw) + assert_equal_not_same(h, m.(**h).kw) + assert_equal(false, m.send(:call, **{}).kw.frozen?) + assert_equal_not_same(kw, m.send(:call, **kw).kw) + assert_equal_not_same(h, m.send(:call, **h).kw) + + singleton_class.send(:attr_writer, :y) + m = method(:y=) + assert_equal_not_same(h, send(:y=, **h)) + assert_equal_not_same(h, public_send(:y=, **h)) + assert_equal_not_same(h, m.(**h)) + assert_equal_not_same(h, m.send(:call, **h)) + + singleton_class.send(:remove_method, :yo) + def self.method_missing(_, **kw) kw end + assert_equal(false, yo(**{}).frozen?) + assert_equal_not_same(kw, yo(**kw)) + assert_equal_not_same(h, yo(**h)) + assert_equal(false, send(:yo, **{}).frozen?) + assert_equal_not_same(kw, send(:yo, **kw)) + assert_equal_not_same(h, send(:yo, **h)) + assert_equal(false, public_send(:yo, **{}).frozen?) + assert_equal_not_same(kw, public_send(:yo, **kw)) + assert_equal_not_same(h, public_send(:yo, **h)) + + def self.yo(*a, **kw) = kw + assert_equal_not_same kw, yo(**kw) + assert_equal_not_same kw, yo(**kw, **kw) + + singleton_class.send(:remove_method, :yo) + def self.yo(opts) = opts + assert_equal_not_same h, yo(*[], **h) + a = [] + assert_equal_not_same h, yo(*a, **h) + end + + def test_keyword_splat_to_non_keyword_method + h = {a: 1}.freeze + + def self.yo(kw) kw end + assert_equal_not_same(h, yo(**h)) + assert_equal_not_same(h, method(:yo).(**h)) + assert_equal_not_same(h, :yo.to_proc.(self, **h)) + + def self.yoa(*kw) kw[0] end + assert_equal_not_same(h, yoa(**h)) + assert_equal_not_same(h, method(:yoa).(**h)) + assert_equal_not_same(h, :yoa.to_proc.(self, **h)) + end + + def test_regular_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(kw, c.m(kw, **kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + assert_equal([h, kw], c.m(h)) + assert_equal([h2, kw], c.m(h2)) + assert_equal([h3, kw], c.m(h3)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + assert_equal([h, kw], c.m(h)) + assert_equal([h2, kw], c.m(h2)) + assert_equal([h3, kw], c.m(h3)) + end + + def test_implicit_super_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new + c = sc.new + def c.m(*args, **kw) + super(*args, **kw) + end + sc.class_eval do + def m(*args) + args + end + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m; end + end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + sc.class_eval do + remove_method(:m) + def m(args) + args + end + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m(**args) + args + end + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + sc.class_eval do + remove_method(:m) + def m(arg, **args) + [arg, args] + end + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + sc.class_eval do + remove_method(:m) + def m(arg=1, **args) + [arg, args] + end + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_explicit_super_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new + c = sc.new + def c.m(*args, **kw) + super(*args, **kw) + end + sc.class_eval do + def m(*args) + args + end + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m; end + end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + sc.class_eval do + remove_method(:m) + def m(args) + args + end + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + sc.class_eval do + remove_method(:m) + def m(**args) + args + end + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + sc.class_eval do + remove_method(:m) + def m(arg, **args) + [arg, args] + end + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + sc.class_eval do + remove_method(:m) + def m(arg=1, **args) + [arg, args] + end + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_lambda_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + f = -> { true } + assert_equal(true, f[**{}]) + assert_equal(true, f[**kw]) + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + + f = ->(a) { a } + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + + f = ->(**x) { x } + assert_equal(kw, f[**{}]) + assert_equal(kw, f[**kw]) + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + assert_raise(ArgumentError) { f[h] } + assert_raise(ArgumentError) { f[h2] } + assert_raise(ArgumentError) { f[h3] } + + f = ->(a, **x) { [a,x] } + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + assert_raise(ArgumentError) { f[a: 1, **h2] } + + f = ->(a=1, **x) { [a, x] } + assert_equal([1, kw], f[**{}]) + assert_equal([1, kw], f[**kw]) + assert_equal([1, h], f[**h]) + assert_equal([1, h], f[a: 1]) + assert_equal([1, h2], f[**h2]) + assert_equal([1, h3], f[**h3]) + assert_equal([1, h3], f[a: 1, **h2]) + end + + def test_lambda_method_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + f = -> { true } + f = f.method(:call) + assert_equal(true, f[**{}]) + assert_equal(true, f[**kw]) + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + + f = ->(a) { a } + f = f.method(:call) + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + + f = ->(**x) { x } + f = f.method(:call) + assert_equal(kw, f[**{}]) + assert_equal(kw, f[**kw]) + assert_equal(h, f[**h]) + assert_equal(h, f[a: 1]) + assert_equal(h2, f[**h2]) + assert_equal(h3, f[**h3]) + assert_equal(h3, f[a: 1, **h2]) + assert_raise(ArgumentError) { f[h] } + assert_raise(ArgumentError) { f[h2] } + assert_raise(ArgumentError) { f[h3] } + + f = ->(a, **x) { [a,x] } + f = f.method(:call) + assert_raise(ArgumentError) { f[**{}] } + assert_raise(ArgumentError) { f[**kw] } + assert_raise(ArgumentError) { f[**h] } + assert_raise(ArgumentError) { f[a: 1] } + assert_raise(ArgumentError) { f[**h2] } + assert_raise(ArgumentError) { f[**h3] } + assert_raise(ArgumentError) { f[a: 1, **h2] } + + f = ->(a=1, **x) { [a, x] } + f = f.method(:call) + assert_equal([1, kw], f[**{}]) + assert_equal([1, kw], f[**kw]) + assert_equal([1, h], f[**h]) + assert_equal([1, h], f[a: 1]) + assert_equal([1, h2], f[**h2]) + assert_equal([1, h3], f[**h3]) + assert_equal([1, h3], f[a: 1, **h2]) + end + + def test_Thread_new_kwsplat + Thread.report_on_exception = false + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + t = Thread + f = -> { true } + assert_equal(true, t.new(**{}, &f).value) + assert_equal(true, t.new(**kw, &f).value) + assert_raise(ArgumentError) { t.new(**h, &f).value } + assert_raise(ArgumentError) { t.new(a: 1, &f).value } + assert_raise(ArgumentError) { t.new(**h2, &f).value } + assert_raise(ArgumentError) { t.new(**h3, &f).value } + + f = ->(a) { a } + assert_raise(ArgumentError) { t.new(**{}, &f).value } + assert_raise(ArgumentError) { t.new(**kw, &f).value } + assert_equal(h, t.new(**h, &f).value) + assert_equal(h, t.new(a: 1, &f).value) + assert_equal(h2, t.new(**h2, &f).value) + assert_equal(h3, t.new(**h3, &f).value) + assert_equal(h3, t.new(a: 1, **h2, &f).value) + + f = ->(**x) { x } + assert_equal(kw, t.new(**{}, &f).value) + assert_equal(kw, t.new(**kw, &f).value) + assert_equal(h, t.new(**h, &f).value) + assert_equal(h, t.new(a: 1, &f).value) + assert_equal(h2, t.new(**h2, &f).value) + assert_equal(h3, t.new(**h3, &f).value) + assert_equal(h3, t.new(a: 1, **h2, &f).value) + assert_raise(ArgumentError) { t.new(h, &f).value } + assert_raise(ArgumentError) { t.new(h2, &f).value } + assert_raise(ArgumentError) { t.new(h3, &f).value } + + f = ->(a, **x) { [a,x] } + assert_raise(ArgumentError) { t.new(**{}, &f).value } + assert_raise(ArgumentError) { t.new(**kw, &f).value } + assert_raise(ArgumentError) { t.new(**h, &f).value } + assert_raise(ArgumentError) { t.new(a: 1, &f).value } + assert_raise(ArgumentError) { t.new(**h2, &f).value } + assert_raise(ArgumentError) { t.new(**h3, &f).value } + assert_raise(ArgumentError) { t.new(a: 1, **h2, &f).value } + + f = ->(a=1, **x) { [a, x] } + assert_equal([1, kw], t.new(**{}, &f).value) + assert_equal([1, kw], t.new(**kw, &f).value) + assert_equal([1, h], t.new(**h, &f).value) + assert_equal([1, h], t.new(a: 1, &f).value) + assert_equal([1, h2], t.new(**h2, &f).value) + assert_equal([1, h3], t.new(**h3, &f).value) + assert_equal([1, h3], t.new(a: 1, **h2, &f).value) + ensure + Thread.report_on_exception = true + end + + def test_Fiber_resume_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + t = Fiber + f = -> { true } + assert_equal(true, t.new(&f).resume(**{})) + assert_equal(true, t.new(&f).resume(**kw)) + assert_raise(ArgumentError) { t.new(&f).resume(**h) } + assert_raise(ArgumentError) { t.new(&f).resume(a: 1) } + assert_raise(ArgumentError) { t.new(&f).resume(**h2) } + assert_raise(ArgumentError) { t.new(&f).resume(**h3) } + + f = ->(a) { a } + assert_raise(ArgumentError) { t.new(&f).resume(**{}) } + assert_raise(ArgumentError) { t.new(&f).resume(**kw) } + assert_equal(h, t.new(&f).resume(**h)) + assert_equal(h, t.new(&f).resume(a: 1)) + assert_equal(h2, t.new(&f).resume(**h2)) + assert_equal(h3, t.new(&f).resume(**h3)) + assert_equal(h3, t.new(&f).resume(a: 1, **h2)) + + f = ->(**x) { x } + assert_equal(kw, t.new(&f).resume(**{})) + assert_equal(kw, t.new(&f).resume(**kw)) + assert_equal(h, t.new(&f).resume(**h)) + assert_equal(h, t.new(&f).resume(a: 1)) + assert_equal(h2, t.new(&f).resume(**h2)) + assert_equal(h3, t.new(&f).resume(**h3)) + assert_equal(h3, t.new(&f).resume(a: 1, **h2)) + assert_raise(ArgumentError) { t.new(&f).resume(h) } + assert_raise(ArgumentError) { t.new(&f).resume(h2) } + assert_raise(ArgumentError) { t.new(&f).resume(h3) } + + f = ->(a, **x) { [a,x] } + assert_raise(ArgumentError) { t.new(&f).resume(**{}) } + assert_raise(ArgumentError) { t.new(&f).resume(**kw) } + assert_raise(ArgumentError) { t.new(&f).resume(**h) } + assert_raise(ArgumentError) { t.new(&f).resume(a: 1) } + assert_raise(ArgumentError) { t.new(&f).resume(**h2) } + assert_raise(ArgumentError) { t.new(&f).resume(**h3) } + assert_raise(ArgumentError) { t.new(&f).resume(a: 1, **h2) } + + f = ->(a=1, **x) { [a, x] } + assert_equal([1, kw], t.new(&f).resume(**{})) + assert_equal([1, kw], t.new(&f).resume(**kw)) + assert_equal([1, h], t.new(&f).resume(**h)) + assert_equal([1, h], t.new(&f).resume(a: 1)) + assert_equal([1, h2], t.new(&f).resume(**h2)) + assert_equal([1, h3], t.new(&f).resume(**h3)) + assert_equal([1, h3], t.new(&f).resume(a: 1, **h2)) + end + + def test_Enumerator_Generator_each_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + g = Enumerator::Generator + f = ->(_) { true } + assert_equal(true, g.new(&f).each(**{})) + assert_equal(true, g.new(&f).each(**kw)) + assert_raise(ArgumentError) { g.new(&f).each(**h) } + assert_raise(ArgumentError) { g.new(&f).each(a: 1) } + assert_raise(ArgumentError) { g.new(&f).each(**h2) } + assert_raise(ArgumentError) { g.new(&f).each(**h3) } + + f = ->(_, a) { a } + assert_raise(ArgumentError) { g.new(&f).each(**{}) } + assert_raise(ArgumentError) { g.new(&f).each(**kw) } + assert_equal(h, g.new(&f).each(**h)) + assert_equal(h, g.new(&f).each(a: 1)) + assert_equal(h2, g.new(&f).each(**h2)) + assert_equal(h3, g.new(&f).each(**h3)) + assert_equal(h3, g.new(&f).each(a: 1, **h2)) + + f = ->(_, **x) { x } + assert_equal(kw, g.new(&f).each(**{})) + assert_equal(kw, g.new(&f).each(**kw)) + assert_equal(h, g.new(&f).each(**h)) + assert_equal(h, g.new(&f).each(a: 1)) + assert_equal(h2, g.new(&f).each(**h2)) + assert_equal(h3, g.new(&f).each(**h3)) + assert_equal(h3, g.new(&f).each(a: 1, **h2)) + assert_raise(ArgumentError) { g.new(&f).each(h) } + assert_raise(ArgumentError) { g.new(&f).each(h2) } + assert_raise(ArgumentError) { g.new(&f).each(h3) } + + f = ->(_, a, **x) { [a,x] } + assert_raise(ArgumentError) { g.new(&f).each(**{}) } + assert_raise(ArgumentError) { g.new(&f).each(**kw) } + assert_raise(ArgumentError) { g.new(&f).each(**h) } + assert_raise(ArgumentError) { g.new(&f).each(a: 1) } + assert_raise(ArgumentError) { g.new(&f).each(**h2) } + assert_raise(ArgumentError) { g.new(&f).each(**h3) } + assert_raise(ArgumentError) { g.new(&f).each(a: 1, **h2) } + + f = ->(_, a=1, **x) { [a, x] } + assert_equal([1, kw], g.new(&f).each(**{})) + assert_equal([1, kw], g.new(&f).each(**kw)) + assert_equal([1, h], g.new(&f).each(**h)) + assert_equal([1, h], g.new(&f).each(a: 1)) + assert_equal([1, h2], g.new(&f).each(**h2)) + assert_equal([1, h3], g.new(&f).each(**h3)) + assert_equal([1, h3], g.new(&f).each(a: 1, **h2)) + end + + def test_Enumerator_Yielder_yield_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + g = Enumerator::Generator + f = -> { true } + assert_equal(true, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(true, g.new{|y| y.yield(**kw)}.each(&f)) + assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) } + + f = ->(a) { a } + assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) } + assert_equal(h, g.new{|y| y.yield(**h)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + + f = ->(**x) { x } + assert_equal(kw, g.new{|y| y.yield(**{})}.each(&f)) + assert_equal(kw, g.new{|y| y.yield(**kw)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(**h)}.each(&f)) + assert_equal(h, g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal(h2, g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal(h3, g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + assert_raise(ArgumentError) { g.new{|y| y.yield(h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(h3)}.each(&f) } + + f = ->(a, **x) { [a,x] } + assert_raise(ArgumentError) { g.new{|y| y.yield(**{})}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**kw)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h2)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(**h3)}.each(&f) } + assert_raise(ArgumentError) { g.new{|y| y.yield(a: 1, **h2)}.each(&f) } + + f = ->(a=1, **x) { [a, x] } + assert_equal([1, kw], g.new{|y| y.yield(**{})}.each(&f)) + assert_equal([1, kw], g.new{|y| y.yield(**kw)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(**h)}.each(&f)) + assert_equal([1, h], g.new{|y| y.yield(a: 1)}.each(&f)) + assert_equal([1, h2], g.new{|y| y.yield(**h2)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(**h3)}.each(&f)) + assert_equal([1, h3], g.new{|y| y.yield(a: 1, **h2)}.each(&f)) + end + + def test_Class_new_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new do + attr_reader :args + class << self + alias [] new + end + end + + c = Class.new(sc) do + def initialize(*args) + @args = args + end + end + assert_equal([], c[**{}].args) + assert_equal([], c[**kw].args) + assert_equal([h], c[**h].args) + assert_equal([h], c[a: 1].args) + assert_equal([h2], c[**h2].args) + assert_equal([h3], c[**h3].args) + assert_equal([h3], c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize; end + end + assert_nil(c[**{}].args) + assert_nil(c[**kw].args) + assert_raise(ArgumentError) { c[**h] } + assert_raise(ArgumentError) { c[a: 1] } + assert_raise(ArgumentError) { c[**h2] } + assert_raise(ArgumentError) { c[**h3] } + assert_raise(ArgumentError) { c[a: 1, **h2] } + + c = Class.new(sc) do + def initialize(args) + @args = args + end + end + assert_raise(ArgumentError) { c[**{}] } + assert_raise(ArgumentError) { c[**kw] } + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize(**args) + @args = args + end + end + assert_equal(kw, c[**{}].args) + assert_equal(kw, c[**kw].args) + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + assert_raise(ArgumentError) { c[h].args } + assert_raise(ArgumentError) { c[h2].args } + assert_raise(ArgumentError) { c[h3].args } + + c = Class.new(sc) do + def initialize(arg, **args) + @args = [arg, args] + end + end + assert_raise(ArgumentError) { c[**{}].args } + assert_raise(ArgumentError) { c[**kw].args } + assert_raise(ArgumentError) { c[**h].args } + assert_raise(ArgumentError) { c[a: 1].args } + assert_raise(ArgumentError) { c[**h2].args } + assert_raise(ArgumentError) { c[**h3].args } + assert_raise(ArgumentError) { c[a: 1, **h2].args } + + c = Class.new(sc) do + def initialize(arg=1, **args) + @args = [arg, args] + end + end + assert_equal([1, kw], c[**{}].args) + assert_equal([1, kw], c[**kw].args) + assert_equal([1, h], c[**h].args) + assert_equal([1, h], c[a: 1].args) + assert_equal([1, h2], c[**h2].args) + assert_equal([1, h3], c[**h3].args) + assert_equal([1, h3], c[a: 1, **h2].args) + end + + def test_Class_new_method_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + sc = Class.new do + attr_reader :args + end + + c = Class.new(sc) do + def initialize(*args) + @args = args + end + end.method(:new) + assert_equal([], c[**{}].args) + assert_equal([], c[**kw].args) + assert_equal([h], c[**h].args) + assert_equal([h], c[a: 1].args) + assert_equal([h2], c[**h2].args) + assert_equal([h3], c[**h3].args) + assert_equal([h3], c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize; end + end.method(:new) + assert_nil(c[**{}].args) + assert_nil(c[**kw].args) + assert_raise(ArgumentError) { c[**h] } + assert_raise(ArgumentError) { c[a: 1] } + assert_raise(ArgumentError) { c[**h2] } + assert_raise(ArgumentError) { c[**h3] } + assert_raise(ArgumentError) { c[a: 1, **h2] } + + c = Class.new(sc) do + def initialize(args) + @args = args + end + end.method(:new) + assert_raise(ArgumentError) { c[**{}] } + assert_raise(ArgumentError) { c[**kw] } + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + + c = Class.new(sc) do + def initialize(**args) + @args = args + end + end.method(:new) + assert_equal(kw, c[**{}].args) + assert_equal(kw, c[**kw].args) + assert_equal(h, c[**h].args) + assert_equal(h, c[a: 1].args) + assert_equal(h2, c[**h2].args) + assert_equal(h3, c[**h3].args) + assert_equal(h3, c[a: 1, **h2].args) + assert_raise(ArgumentError) { c[h].args } + assert_raise(ArgumentError) { c[h2].args } + assert_raise(ArgumentError) { c[h3].args } + + c = Class.new(sc) do + def initialize(arg, **args) + @args = [arg, args] + end + end.method(:new) + assert_raise(ArgumentError) { c[**{}].args } + assert_raise(ArgumentError) { c[**kw].args } + assert_raise(ArgumentError) { c[**h].args } + assert_raise(ArgumentError) { c[a: 1].args } + assert_raise(ArgumentError) { c[**h2].args } + assert_raise(ArgumentError) { c[**h3].args } + assert_raise(ArgumentError) { c[a: 1, **h2].args } + + c = Class.new(sc) do + def initialize(arg=1, **args) + @args = [arg, args] + end + end.method(:new) + assert_equal([1, kw], c[**{}].args) + assert_equal([1, kw], c[**kw].args) + assert_equal([1, h], c[**h].args) + assert_equal([1, h], c[a: 1].args) + assert_equal([1, h2], c[**h2].args) + assert_equal([1, h3], c[**h3].args) + assert_equal([1, h3], c[a: 1, **h2].args) + end + + def test_Method_call_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.method(:m)[**{}]) + assert_equal([], c.method(:m)[**kw]) + assert_equal([h], c.method(:m)[**h]) + assert_equal([h], c.method(:m)[a: 1]) + assert_equal([h2], c.method(:m)[**h2]) + assert_equal([h3], c.method(:m)[**h3]) + assert_equal([h3], c.method(:m)[a: 1, **h2]) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.method(:m)[**{}]) + assert_nil(c.method(:m)[**kw]) + assert_raise(ArgumentError) { c.method(:m)[**h] } + assert_raise(ArgumentError) { c.method(:m)[a: 1] } + assert_raise(ArgumentError) { c.method(:m)[**h2] } + assert_raise(ArgumentError) { c.method(:m)[**h3] } + assert_raise(ArgumentError) { c.method(:m)[a: 1, **h2] } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.method(:m)[**{}] } + assert_raise(ArgumentError) { c.method(:m)[**kw] } + assert_equal(h, c.method(:m)[**h]) + assert_equal(h, c.method(:m)[a: 1]) + assert_equal(h2, c.method(:m)[**h2]) + assert_equal(h3, c.method(:m)[**h3]) + assert_equal(h3, c.method(:m)[a: 1, **h2]) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.method(:m)[**{}]) + assert_equal(kw, c.method(:m)[**kw]) + assert_equal(h, c.method(:m)[**h]) + assert_equal(h, c.method(:m)[a: 1]) + assert_equal(h2, c.method(:m)[**h2]) + assert_equal(h3, c.method(:m)[**h3]) + assert_equal(h3, c.method(:m)[a: 1, **h2]) + assert_raise(ArgumentError) { c.method(:m)[h] } + assert_raise(ArgumentError) { c.method(:m)[h2] } + assert_raise(ArgumentError) { c.method(:m)[h3] } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.method(:m)[**{}] } + assert_raise(ArgumentError) { c.method(:m)[**kw] } + assert_raise(ArgumentError) { c.method(:m)[**h] } + assert_raise(ArgumentError) { c.method(:m)[a: 1] } + assert_raise(ArgumentError) { c.method(:m)[**h2] } + assert_raise(ArgumentError) { c.method(:m)[**h3] } + assert_raise(ArgumentError) { c.method(:m)[a: 1, **h2] } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.method(:m)[**{}]) + assert_equal([1, kw], c.method(:m)[**kw]) + assert_equal([1, h], c.method(:m)[**h]) + assert_equal([1, h], c.method(:m)[a: 1]) + assert_equal([1, h2], c.method(:m)[**h2]) + assert_equal([1, h3], c.method(:m)[**h3]) + assert_equal([1, h3], c.method(:m)[a: 1, **h2]) + end + + def test_UnboundMethod_bindcall_kwsplat_call + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + sc = c.singleton_class + def c.m(*args) + args + end + assert_equal([], sc.instance_method(:m).bind_call(c, **{})) + assert_equal([], sc.instance_method(:m).bind_call(c, **kw)) + assert_equal([h], sc.instance_method(:m).bind_call(c, **h)) + assert_equal([h], sc.instance_method(:m).bind_call(c, a: 1)) + assert_equal([h2], sc.instance_method(:m).bind_call(c, **h2)) + assert_equal([h3], sc.instance_method(:m).bind_call(c, **h3)) + assert_equal([h3], sc.instance_method(:m).bind_call(c, a: 1, **h2)) + + sc.remove_method(:m) + def c.m; end + assert_nil(sc.instance_method(:m).bind_call(c, **{})) + assert_nil(sc.instance_method(:m).bind_call(c, **kw)) + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h2) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h3) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1, **h2) } + + sc.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) } + assert_equal(h, sc.instance_method(:m).bind_call(c, **h)) + assert_equal(h, sc.instance_method(:m).bind_call(c, a: 1)) + assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2)) + + sc.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, sc.instance_method(:m).bind_call(c, **{})) + assert_equal(kw, sc.instance_method(:m).bind_call(c, **kw)) + assert_equal(h, sc.instance_method(:m).bind_call(c, **h)) + assert_equal(h, sc.instance_method(:m).bind_call(c, a: 1)) + assert_equal(h2, sc.instance_method(:m).bind_call(c, **h2)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, **h3)) + assert_equal(h3, sc.instance_method(:m).bind_call(c, a: 1, **h2)) + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h2) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, h3) } + + sc.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **{}) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **kw) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h2) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, **h3) } + assert_raise(ArgumentError) { sc.instance_method(:m).bind_call(c, a: 1, **h2) } + + sc.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **{})) + assert_equal([1, kw], sc.instance_method(:m).bind_call(c, **kw)) + assert_equal([1, h], sc.instance_method(:m).bind_call(c, **h)) + assert_equal([1, h], sc.instance_method(:m).bind_call(c, a: 1)) + assert_equal([1, h2], sc.instance_method(:m).bind_call(c, **h2)) + assert_equal([1, h3], sc.instance_method(:m).bind_call(c, **h3)) + assert_equal([1, h3], sc.instance_method(:m).bind_call(c, a: 1, **h2)) + end + + def test_send_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.send(:m, **{})) + assert_equal([], c.send(:m, **kw)) + assert_equal([h], c.send(:m, **h)) + assert_equal([h], c.send(:m, a: 1)) + assert_equal([h2], c.send(:m, **h2)) + assert_equal([h3], c.send(:m, **h3)) + assert_equal([h3], c.send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.send(:m, **{})) + assert_nil(c.send(:m, **kw)) + assert_raise(ArgumentError) { c.send(:m, **h) } + assert_raise(ArgumentError) { c.send(:m, a: 1) } + assert_raise(ArgumentError) { c.send(:m, **h2) } + assert_raise(ArgumentError) { c.send(:m, **h3) } + assert_raise(ArgumentError) { c.send(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_raise(ArgumentError) { c.send(:m, **kw) } + assert_equal(h, c.send(:m, **h)) + assert_equal(h, c.send(:m, a: 1)) + assert_equal(h2, c.send(:m, **h2)) + assert_equal(h3, c.send(:m, **h3)) + assert_equal(h3, c.send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.send(:m, **{})) + assert_equal(kw, c.send(:m, **kw)) + assert_equal(h, c.send(:m, **h)) + assert_equal(h, c.send(:m, a: 1)) + assert_equal(h2, c.send(:m, **h2)) + assert_equal(h3, c.send(:m, **h3)) + assert_equal(h3, c.send(:m, a: 1, **h2)) + assert_raise(ArgumentError) { c.send(:m, h) } + assert_raise(ArgumentError) { c.send(:m, h2) } + assert_raise(ArgumentError) { c.send(:m, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.send(:m, **{}) } + assert_raise(ArgumentError) { c.send(:m, **kw) } + assert_raise(ArgumentError) { c.send(:m, **h) } + assert_raise(ArgumentError) { c.send(:m, a: 1) } + assert_raise(ArgumentError) { c.send(:m, **h2) } + assert_raise(ArgumentError) { c.send(:m, **h3) } + assert_raise(ArgumentError) { c.send(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.send(:m, **{})) + assert_equal([1, kw], c.send(:m, **kw)) + assert_equal([1, h], c.send(:m, **h)) + assert_equal([1, h], c.send(:m, a: 1)) + assert_equal([1, h2], c.send(:m, **h2)) + assert_equal([1, h3], c.send(:m, **h3)) + assert_equal([1, h3], c.send(:m, a: 1, **h2)) + end + + def test_public_send_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.public_send(:m, **{})) + assert_equal([], c.public_send(:m, **kw)) + assert_equal([h], c.public_send(:m, **h)) + assert_equal([h], c.public_send(:m, a: 1)) + assert_equal([h2], c.public_send(:m, **h2)) + assert_equal([h3], c.public_send(:m, **h3)) + assert_equal([h3], c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.public_send(:m, **{})) + assert_nil(c.public_send(:m, **kw)) + assert_raise(ArgumentError) { c.public_send(:m, **h) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1) } + assert_raise(ArgumentError) { c.public_send(:m, **h2) } + assert_raise(ArgumentError) { c.public_send(:m, **h3) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.public_send(:m, **{}) } + assert_raise(ArgumentError) { c.public_send(:m, **kw) } + assert_equal(h, c.public_send(:m, **h)) + assert_equal(h, c.public_send(:m, a: 1)) + assert_equal(h2, c.public_send(:m, **h2)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.public_send(:m, **{})) + assert_equal(kw, c.public_send(:m, **kw)) + assert_equal(h, c.public_send(:m, **h)) + assert_equal(h, c.public_send(:m, a: 1)) + assert_equal(h2, c.public_send(:m, **h2)) + assert_equal(h3, c.public_send(:m, **h3)) + assert_equal(h3, c.public_send(:m, a: 1, **h2)) + assert_raise(ArgumentError) { c.public_send(:m, h) } + assert_raise(ArgumentError) { c.public_send(:m, h2) } + assert_raise(ArgumentError) { c.public_send(:m, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.public_send(:m, **{}) } + assert_raise(ArgumentError) { c.public_send(:m, **kw) } + assert_raise(ArgumentError) { c.public_send(:m, **h) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1) } + assert_raise(ArgumentError) { c.public_send(:m, **h2) } + assert_raise(ArgumentError) { c.public_send(:m, **h3) } + assert_raise(ArgumentError) { c.public_send(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.public_send(:m, **{})) + assert_equal([1, kw], c.public_send(:m, **kw)) + assert_equal([1, h], c.public_send(:m, **h)) + assert_equal([1, h], c.public_send(:m, a: 1)) + assert_equal([1, h2], c.public_send(:m, **h2)) + assert_equal([1, h3], c.public_send(:m, **h3)) + assert_equal([1, h3], c.public_send(:m, a: 1, **h2)) + end + + def test_send_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = c.method(:send) + assert_equal([], m.call(:m, **{})) + assert_equal([], m.call(:m, **kw)) + assert_equal([h], m.call(:m, **h)) + assert_equal([h], m.call(:m, a: 1)) + assert_equal([h2], m.call(:m, **h2)) + assert_equal([h3], m.call(:m, **h3)) + assert_equal([h3], m.call(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + m = c.method(:send) + assert_nil(m.call(:m, **{})) + assert_nil(m.call(:m, **kw)) + assert_raise(ArgumentError) { m.call(:m, **h) } + assert_raise(ArgumentError) { m.call(:m, a: 1) } + assert_raise(ArgumentError) { m.call(:m, **h2) } + assert_raise(ArgumentError) { m.call(:m, **h3) } + assert_raise(ArgumentError) { m.call(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + m = c.method(:send) + assert_raise(ArgumentError) { m.call(:m, **{}) } + assert_raise(ArgumentError) { m.call(:m, **kw) } + assert_equal(h, m.call(:m, **h)) + assert_equal(h, m.call(:m, a: 1)) + assert_equal(h2, m.call(:m, **h2)) + assert_equal(h3, m.call(:m, **h3)) + assert_equal(h3, m.call(:m, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + m = c.method(:send) + assert_equal(kw, m.call(:m, **{})) + assert_equal(kw, m.call(:m, **kw)) + assert_equal(h, m.call(:m, **h)) + assert_equal(h, m.call(:m, a: 1)) + assert_equal(h2, m.call(:m, **h2)) + assert_equal(h3, m.call(:m, **h3)) + assert_equal(h3, m.call(:m, a: 1, **h2)) + assert_raise(ArgumentError) { m.call(:m, h) } + assert_raise(ArgumentError) { m.call(:m, h2) } + assert_raise(ArgumentError) { m.call(:m, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + m = c.method(:send) + assert_raise(ArgumentError) { m.call(:m, **{}) } + assert_raise(ArgumentError) { m.call(:m, **kw) } + assert_raise(ArgumentError) { m.call(:m, **h) } + assert_raise(ArgumentError) { m.call(:m, a: 1) } + assert_raise(ArgumentError) { m.call(:m, **h2) } + assert_raise(ArgumentError) { m.call(:m, **h3) } + assert_raise(ArgumentError) { m.call(:m, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + m = c.method(:send) + assert_equal([1, kw], m.call(:m, **{})) + assert_equal([1, kw], m.call(:m, **kw)) + assert_equal([1, h], m.call(:m, **h)) + assert_equal([1, h], m.call(:m, a: 1)) + assert_equal([1, h2], m.call(:m, **h2)) + assert_equal([1, h3], m.call(:m, **h3)) + assert_equal([1, h3], m.call(:m, a: 1, **h2)) + end + + def test_sym_proc_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], :m.to_proc.call(c, **{})) + assert_equal([], :m.to_proc.call(c, **kw)) + assert_equal([h], :m.to_proc.call(c, **h)) + assert_equal([h], :m.to_proc.call(c, a: 1)) + assert_equal([h2], :m.to_proc.call(c, **h2)) + assert_equal([h3], :m.to_proc.call(c, **h3)) + assert_equal([h3], :m.to_proc.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(:m.to_proc.call(c, **{})) + assert_nil(:m.to_proc.call(c, **kw)) + assert_raise(ArgumentError) { :m.to_proc.call(c, **h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) } + assert_equal(h, :m.to_proc.call(c, **h)) + assert_equal(h, :m.to_proc.call(c, a: 1)) + assert_equal(h2, :m.to_proc.call(c, **h2)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + assert_equal(h3, :m.to_proc.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, :m.to_proc.call(c, **{})) + assert_equal(kw, :m.to_proc.call(c, **kw)) + assert_equal(h, :m.to_proc.call(c, **h)) + assert_equal(h, :m.to_proc.call(c, a: 1)) + assert_equal(h2, :m.to_proc.call(c, **h2)) + assert_equal(h3, :m.to_proc.call(c, **h3)) + assert_equal(h3, :m.to_proc.call(c, a: 1, **h2)) + assert_raise(ArgumentError) { :m.to_proc.call(c, h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { :m.to_proc.call(c, **{}) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **kw) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h2) } + assert_raise(ArgumentError) { :m.to_proc.call(c, **h3) } + assert_raise(ArgumentError) { :m.to_proc.call(c, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], :m.to_proc.call(c, **{})) + assert_equal([1, kw], :m.to_proc.call(c, **kw)) + assert_equal([1, h], :m.to_proc.call(c, **h)) + assert_equal([1, h], :m.to_proc.call(c, a: 1)) + assert_equal([1, h2], :m.to_proc.call(c, **h2)) + assert_equal([1, h3], :m.to_proc.call(c, **h3)) + assert_equal([1, h3], :m.to_proc.call(c, a: 1, **h2)) + end + + def test_sym_proc_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = :m.to_proc.method(:call) + assert_equal([], m.call(c, **{})) + assert_equal([], m.call(c, **kw)) + assert_equal([h], m.call(c, **h)) + assert_equal([h], m.call(c, a: 1)) + assert_equal([h2], m.call(c, **h2)) + assert_equal([h3], m.call(c, **h3)) + assert_equal([h3], m.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(m.call(c, **{})) + assert_nil(m.call(c, **kw)) + assert_raise(ArgumentError) { m.call(c, **h) } + assert_raise(ArgumentError) { m.call(c, a: 1) } + assert_raise(ArgumentError) { m.call(c, **h2) } + assert_raise(ArgumentError) { m.call(c, **h3) } + assert_raise(ArgumentError) { m.call(c, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { m.call(c, **{}) } + assert_raise(ArgumentError) { m.call(c, **kw) } + assert_equal(h, m.call(c, **h)) + assert_equal(h, m.call(c, a: 1)) + assert_equal(h2, m.call(c, **h2)) + assert_equal(h3, m.call(c, **h3)) + assert_equal(h3, m.call(c, a: 1, **h2)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, m.call(c, **{})) + assert_equal(kw, m.call(c, **kw)) + assert_equal(h, m.call(c, **h)) + assert_equal(h, m.call(c, a: 1)) + assert_equal(h2, m.call(c, **h2)) + assert_equal(h3, m.call(c, **h3)) + assert_equal(h3, m.call(c, a: 1, **h2)) + assert_raise(ArgumentError) { m.call(c, h) } + assert_raise(ArgumentError) { m.call(c, h2) } + assert_raise(ArgumentError) { m.call(c, h3) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { m.call(c, **{}) } + assert_raise(ArgumentError) { m.call(c, **kw) } + assert_raise(ArgumentError) { m.call(c, **h) } + assert_raise(ArgumentError) { m.call(c, a: 1) } + assert_raise(ArgumentError) { m.call(c, **h2) } + assert_raise(ArgumentError) { m.call(c, **h3) } + assert_raise(ArgumentError) { m.call(c, a: 1, **h2) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], m.call(c, **{})) + assert_equal([1, kw], m.call(c, **kw)) + assert_equal([1, h], m.call(c, **h)) + assert_equal([1, h], m.call(c, a: 1)) + assert_equal([1, h2], m.call(c, **h2)) + assert_equal([1, h3], m.call(c, **h3)) + assert_equal([1, h3], m.call(c, a: 1, **h2)) + end + + def test_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_super_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Class.new do + def m(*args, **kw) + super + end + end.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_rb_call_super_kw_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.extend Bug::RbCallSuperKw + def c.method_missing(_, *args) + args + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_); end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + end + + def test_define_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + define_method(:m) { } + end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c = Object.new + class << c + define_method(:m) {|arg| arg } + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args| args } + end + assert_equal([], c.m(**{})) + assert_equal([], c.m(**kw)) + assert_equal([h], c.m(**h)) + assert_equal([h], c.m(a: 1)) + assert_equal([h2], c.m(**h2)) + assert_equal([h3], c.m(**h3)) + assert_equal([h3], c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|**opt| opt} + end + assert_equal(kw, c.m(**{})) + assert_equal(kw, c.m(**kw)) + assert_equal(h, c.m(**h)) + assert_equal(h, c.m(a: 1)) + assert_equal(h2, c.m(**h2)) + assert_equal(h3, c.m(**h3)) + assert_equal(h3, c.m(a: 1, **h2)) + assert_raise(ArgumentError) { c.m(h) } + assert_raise(ArgumentError) { c.m(h2) } + assert_raise(ArgumentError) { c.m(h3) } + + c = Object.new + class << c + define_method(:m) {|arg, **opt| [arg, opt] } + end + assert_raise(ArgumentError) { c.m(**{}) } + assert_raise(ArgumentError) { c.m(**kw) } + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + + c = Object.new + class << c + define_method(:m) {|arg=1, **opt| [arg, opt] } + end + assert_equal([1, kw], c.m(**{})) + assert_equal([1, kw], c.m(**kw)) + assert_equal([1, h], c.m(**h)) + assert_equal([1, h], c.m(a: 1)) + assert_equal([1, h2], c.m(**h2)) + assert_equal([1, h3], c.m(**h3)) + assert_equal([1, h3], c.m(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args, **opt| [args, opt] } + end + assert_equal([[h], kw], c.m(h)) + assert_equal([[h, h], kw], c.m(h, h)) + + c = Object.new + class << c + define_method(:m) {|arg=nil, a: nil| [arg, a] } + end + assert_equal([h3, nil], c.m(h3)) + assert_raise(ArgumentError) { c.m(**h3) } + end + + def test_define_method_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + define_method(:m) { } + end + m = c.method(:m) + assert_nil(m.call(**{})) + assert_nil(m.call(**kw)) + assert_raise(ArgumentError) { m.call(**h) } + assert_raise(ArgumentError) { m.call(a: 1) } + assert_raise(ArgumentError) { m.call(**h2) } + assert_raise(ArgumentError) { m.call(**h3) } + assert_raise(ArgumentError) { m.call(a: 1, **h2) } + + c = Object.new + class << c + define_method(:m) {|arg| arg } + end + m = c.method(:m) + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } + assert_equal(h, m.call(**h)) + assert_equal(h, m.call(a: 1)) + assert_equal(h2, m.call(**h2)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args| args } + end + m = c.method(:m) + assert_equal([], m.call(**{})) + assert_equal([], m.call(**kw)) + assert_equal([h], m.call(**h)) + assert_equal([h], m.call(a: 1)) + assert_equal([h2], m.call(**h2)) + assert_equal([h3], m.call(**h3)) + assert_equal([h3], m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|**opt| opt} + end + m = c.method(:m) + assert_equal(kw, m.call(**{})) + assert_equal(kw, m.call(**kw)) + assert_equal(h, m.call(**h)) + assert_equal(h, m.call(a: 1)) + assert_equal(h2, m.call(**h2)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + assert_raise(ArgumentError) { m.call(h) } + assert_raise(ArgumentError) { m.call(h2) } + assert_raise(ArgumentError) { m.call(h3) } + + c = Object.new + class << c + define_method(:m) {|arg, **opt| [arg, opt] } + end + m = c.method(:m) + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } + assert_raise(ArgumentError) { m.call(**h) } + assert_raise(ArgumentError) { m.call(a: 1) } + assert_raise(ArgumentError) { m.call(**h2) } + assert_raise(ArgumentError) { m.call(**h3) } + assert_raise(ArgumentError) { m.call(a: 1, **h2) } + + c = Object.new + class << c + define_method(:m) {|arg=1, **opt| [arg, opt] } + end + m = c.method(:m) + assert_equal([1, kw], m.call(**{})) + assert_equal([1, kw], m.call(**kw)) + assert_equal([1, h], m.call(**h)) + assert_equal([1, h], m.call(a: 1)) + assert_equal([1, h2], m.call(**h2)) + assert_equal([1, h3], m.call(**h3)) + assert_equal([1, h3], m.call(a: 1, **h2)) + + c = Object.new + class << c + define_method(:m) {|*args, **opt| [args, opt] } + end + m = c.method(:m) + assert_equal([[h], kw], m.call(h)) + assert_equal([[h, h], kw], m.call(h, h)) + + c = Object.new + class << c + define_method(:m) {|arg=nil, a: nil| [arg, a] } + end + m = c.method(:m) + assert_equal([h3, nil], m.call(h3)) + assert_raise(ArgumentError) { m.call(**h3) } + end + + def test_attr_reader_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_reader :m + end + assert_nil(c.m(**{})) + assert_nil(c.m(**kw)) + assert_raise(ArgumentError) { c.m(**h) } + assert_raise(ArgumentError) { c.m(a: 1) } + assert_raise(ArgumentError) { c.m(**h2) } + assert_raise(ArgumentError) { c.m(**h3) } + assert_raise(ArgumentError) { c.m(a: 1, **h2) } + end + + def test_attr_reader_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_reader :m + end + m = c.method(:m) + assert_nil(m.call(**{})) + assert_nil(m.call(**kw)) + assert_raise(ArgumentError) { m.call(**h) } + assert_raise(ArgumentError) { m.call(a: 1) } + assert_raise(ArgumentError) { m.call(**h2) } + assert_raise(ArgumentError) { m.call(**h3) } + assert_raise(ArgumentError) { m.call(a: 1, **h2) } + end + + def test_attr_writer_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_writer :m + end + assert_raise(ArgumentError) { c.send(:m=, **{}) } + assert_raise(ArgumentError) { c.send(:m=, **kw) } + assert_equal(h, c.send(:m=, **h)) + assert_equal(h, c.send(:m=, a: 1)) + assert_equal(h2, c.send(:m=, **h2)) + assert_equal(h3, c.send(:m=, **h3)) + assert_equal(h3, c.send(:m=, a: 1, **h2)) + + assert_equal(42, c.send(:m=, 42, **{})) + assert_equal(42, c.send(:m=, 42, **kw)) + assert_raise(ArgumentError) { c.send(:m=, 42, **h) } + assert_raise(ArgumentError) { c.send(:m=, 42, a: 1) } + assert_raise(ArgumentError) { c.send(:m=, 42, **h2) } + assert_raise(ArgumentError) { c.send(:m=, 42, **h3) } + assert_raise(ArgumentError) { c.send(:m=, 42, a: 1, **h2) } + end + + def test_attr_writer_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + class << c + attr_writer :m + end + m = c.method(:m=) + assert_raise(ArgumentError) { m.call(**{}) } + assert_raise(ArgumentError) { m.call(**kw) } + assert_equal(h, m.call(**h)) + assert_equal(h, m.call(a: 1)) + assert_equal(h2, m.call(**h2)) + assert_equal(h3, m.call(**h3)) + assert_equal(h3, m.call(a: 1, **h2)) + + assert_equal(42, m.call(42, **{})) + assert_equal(42, m.call(42, **kw)) + assert_raise(ArgumentError) { m.call(42, **h) } + assert_raise(ArgumentError) { m.call(42, a: 1) } + assert_raise(ArgumentError) { m.call(42, **h2) } + assert_raise(ArgumentError) { m.call(42, **h3) } + assert_raise(ArgumentError) { m.call(42, a: 1, **h2) } + end + + def test_ruby2_keywords_post_arg + def self.a(*c, **kw) [c, kw] end + def self.b(*a, b) a(*a, b) end + assert_warn(/Skipping set of ruby2_keywords flag for b \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(singleton_class.send(:ruby2_keywords, :b)) + end + assert_equal([[{foo: 1}, {bar: 1}], {}], b({foo: 1}, bar: 1)) + + b = ->(*a, b){a(*a, b)} + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do + b.ruby2_keywords + end + assert_equal([[{foo: 1}, {bar: 1}], {}], b.({foo: 1}, bar: 1)) + end + + def test_proc_ruby2_keywords + h1 = {:a=>1} + foo = ->(*args, &block){block.call(*args)} + assert_same(foo, foo.ruby2_keywords) + + assert_equal([[1], h1], foo.call(1, :a=>1, &->(*args, **kw){[args, kw]})) + assert_equal([1, h1], foo.call(1, :a=>1, &->(*args){args})) + assert_equal([[1, h1], {}], foo.call(1, {:a=>1}, &->(*args, **kw){[args, kw]})) + assert_equal([1, h1], foo.call(1, {:a=>1}, &->(*args){args})) + assert_raise(ArgumentError) { foo.call(:a=>1, &->(arg, **kw){[arg, kw]}) } + assert_equal(h1, foo.call(:a=>1, &->(arg){arg})) + + [->(){}, ->(arg){}, ->(*args, x){}, ->(*args, **kw){}, ->(*args, k: 1){}, ->(*args, k: ){}].each do |pr| + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc accepts keywords or post arguments or proc does not accept argument splat\)/) do + pr.ruby2_keywords + end + end + + o = Object.new + def o.foo(*args) + yield(*args) + end + foo = o.method(:foo).to_proc + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc created from method\)/) do + foo.ruby2_keywords + end + + foo = :foo.to_proc + assert_warn(/Skipping set of ruby2_keywords flag for proc \(proc not defined in Ruby\)/) do + foo.ruby2_keywords + end + + assert_raise(FrozenError) { ->(*args){}.freeze.ruby2_keywords } + end + + def test_ruby2_keywords + assert_raise(ArgumentError) do + Class.new do + ruby2_keywords + end + end + + c = Class.new do + ruby2_keywords def foo(meth, *args) + send(meth, *args) + end + + ruby2_keywords(define_method(:bfoo) do |meth, *args| + send(meth, *args) + end) + + ruby2_keywords def foo_bar(*args) + bar(*args) + end + + ruby2_keywords def foo_baz(*args) + baz(*args) + end + + define_method(:block_splat) {|*args| } + ruby2_keywords :block_splat, def foo_bar_after_bmethod(*args) + bar(*args) + end + + ruby2_keywords def foo_baz2(*args) + baz(*args) + baz(*args) + end + + ruby2_keywords def foo_foo_bar(meth, *args) + foo_bar(meth, *args) + end + + ruby2_keywords def foo_foo_baz(meth, *args) + foo_baz(meth, *args) + end + + ruby2_keywords def foo_mod(meth, *args) + args << 1 + send(meth, *args) + end + + ruby2_keywords def foo_bar_mod(*args) + args << 1 + bar(*args) + end + + ruby2_keywords def foo_baz_mod(*args) + args << 1 + baz(*args) + end + + def pass_bar(*args) + bar(*args) + end + + def bar(*args, **kw) + [args, kw] + end + + def baz(*args) + args + end + + def empty_method + end + + def opt(arg = :opt) + arg + end + + ruby2_keywords def foo_dbar(*args) + dbar(*args) + end + + ruby2_keywords def foo_dbaz(*args) + dbaz(*args) + end + + ruby2_keywords def clear_last_empty_method(*args) + args.last.clear + empty_method(*args) + end + + ruby2_keywords def clear_last_opt(*args) + args.last.clear + opt(*args) + end + + define_method(:dbar) do |*args, **kw| + [args, kw] + end + + define_method(:dbaz) do |*args| + args + end + + def pass_cfunc(*args) + self.class.new(*args).init_args + end + + ruby2_keywords def block(*args) + ->(*args, **kw){[args, kw]}.(*args) + end + + ruby2_keywords def cfunc(*args) + self.class.new(*args).init_args + end + + ruby2_keywords def store_foo(meth, *args) + @stored_args = args + use(meth) + end + def use(meth) + send(meth, *@stored_args) + end + + attr_reader :init_args + def initialize(*args, **kw) + @init_args = [args, kw] + end + end + + mmkw = Class.new do + def method_missing(*args, **kw) + [args, kw] + end + end + + mmnokw = Class.new do + def method_missing(*args) + args + end + end + + implicit_super = Class.new(c) do + ruby2_keywords def bar(*args) + super + end + + ruby2_keywords def baz(*args) + super + end + end + + explicit_super = Class.new(c) do + ruby2_keywords def bar(*args) + super(*args) + end + + ruby2_keywords def baz(*args) + super(*args) + end + end + + h1 = {a: 1} + o = c.new + + assert_equal([1, h1], o.foo_baz2(1, :a=>1)) + assert_equal([1], o.foo_baz2(1, **{})) + assert_equal([h1], o.foo_baz2(h1, **{})) + + assert_equal([[1], h1], o.foo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.store_foo(:bar, 1, :a=>1)) + assert_equal([1, h1], o.store_foo(:baz, 1, :a=>1)) + assert_equal([[1], h1], o.foo_bar(1, :a=>1)) + assert_equal([1, h1], o.foo_baz(1, :a=>1)) + assert_equal([[1], h1], o.foo(:foo, :bar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:foo, :baz, 1, :a=>1)) + assert_equal([[1], h1], o.foo(:foo_bar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:foo_baz, 1, :a=>1)) + assert_equal([[1], h1], o.foo_foo_bar(1, :a=>1)) + assert_equal([1, h1], o.foo_foo_baz(1, :a=>1)) + assert_equal([[1], h1], o.foo_bar_after_bmethod(1, :a=>1)) + + assert_equal([[1], h1], o.foo(:bar, 1, **h1)) + assert_equal([1, h1], o.foo(:baz, 1, **h1)) + assert_equal([[1], h1], o.bfoo(:bar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:baz, 1, **h1)) + assert_equal([[1], h1], o.store_foo(:bar, 1, **h1)) + assert_equal([1, h1], o.store_foo(:baz, 1, **h1)) + assert_equal([[1], h1], o.foo_bar(1, **h1)) + assert_equal([1, h1], o.foo_baz(1, **h1)) + assert_equal([[1], h1], o.foo(:foo, :bar, 1, **h1)) + assert_equal([1, h1], o.foo(:foo, :baz, 1, **h1)) + assert_equal([[1], h1], o.foo(:foo_bar, 1, **h1)) + assert_equal([1, h1], o.foo(:foo_baz, 1, **h1)) + assert_equal([[1], h1], o.foo_foo_bar(1, **h1)) + assert_equal([1, h1], o.foo_foo_baz(1, **h1)) + assert_equal([[1], h1], o.foo_bar_after_bmethod(1, **h1)) + + assert_equal([[h1], {}], o.foo(:bar, h1, **{})) + assert_equal([h1], o.foo(:baz, h1, **{})) + assert_equal([[h1], {}], o.bfoo(:bar, h1, **{})) + assert_equal([h1], o.bfoo(:baz, h1, **{})) + assert_equal([[h1], {}], o.store_foo(:bar, h1, **{})) + assert_equal([h1], o.store_foo(:baz, h1, **{})) + assert_equal([[h1], {}], o.foo_bar(h1, **{})) + assert_equal([h1], o.foo_baz(h1, **{})) + assert_equal([[h1], {}], o.foo(:foo, :bar, h1, **{})) + assert_equal([h1], o.foo(:foo, :baz, h1, **{})) + assert_equal([[h1], {}], o.foo(:foo_bar, h1, **{})) + assert_equal([h1], o.foo(:foo_baz, h1, **{})) + assert_equal([[h1], {}], o.foo_foo_bar(h1, **{})) + assert_equal([h1], o.foo_foo_baz(h1, **{})) + assert_equal([[h1], {}], o.foo_bar_after_bmethod(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:bar, 1, h1)) + assert_equal([1, h1], o.foo(:baz, 1, h1)) + assert_equal([[1, h1], {}], o.bfoo(:bar, 1, h1)) + assert_equal([1, h1], o.bfoo(:baz, 1, h1)) + assert_equal([[1, h1], {}], o.store_foo(:bar, 1, h1)) + assert_equal([1, h1], o.store_foo(:baz, 1, h1)) + assert_equal([[1, h1], {}], o.foo_bar(1, h1)) + assert_equal([1, h1], o.foo_baz(1, h1)) + assert_equal([[1, h1], {}], o.foo_bar_after_bmethod(1, h1)) + + assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, :a=>1)) + assert_equal([1, h1, 1], o.foo_mod(:baz, 1, :a=>1)) + assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, :a=>1)) + assert_equal([1, h1, 1], o.foo_baz_mod(1, :a=>1)) + + assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, **h1)) + assert_equal([1, h1, 1], o.foo_mod(:baz, 1, **h1)) + assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, **h1)) + assert_equal([1, h1, 1], o.foo_baz_mod(1, **h1)) + + assert_equal([[h1, 1], {}], o.foo_mod(:bar, h1, **{})) + assert_equal([h1, 1], o.foo_mod(:baz, h1, **{})) + assert_equal([[h1, 1], {}], o.foo_bar_mod(h1, **{})) + assert_equal([h1, 1], o.foo_baz_mod(h1, **{})) + + assert_equal([[1, h1, 1], {}], o.foo_mod(:bar, 1, h1)) + assert_equal([1, h1, 1], o.foo_mod(:baz, 1, h1)) + assert_equal([[1, h1, 1], {}], o.foo_bar_mod(1, h1)) + assert_equal([1, h1, 1], o.foo_baz_mod(1, h1)) + + assert_equal([[1], h1], o.foo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.foo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.store_foo(:dbar, 1, :a=>1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, :a=>1)) + assert_equal([[1], h1], o.foo_dbar(1, :a=>1)) + assert_equal([1, h1], o.foo_dbaz(1, :a=>1)) + + assert_equal([[1], h1], o.foo(:dbar, 1, **h1)) + assert_equal([1, h1], o.foo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.bfoo(:dbar, 1, **h1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.store_foo(:dbar, 1, **h1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, **h1)) + assert_equal([[1], h1], o.foo_dbar(1, **h1)) + assert_equal([1, h1], o.foo_dbaz(1, **h1)) + + assert_equal([[h1], {}], o.foo(:dbar, h1, **{})) + assert_equal([h1], o.foo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.bfoo(:dbar, h1, **{})) + assert_equal([h1], o.bfoo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.store_foo(:dbar, h1, **{})) + assert_equal([h1], o.store_foo(:dbaz, h1, **{})) + assert_equal([[h1], {}], o.foo_dbar(h1, **{})) + assert_equal([h1], o.foo_dbaz(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:dbar, 1, h1)) + assert_equal([1, h1], o.foo(:dbaz, 1, h1)) + assert_equal([[1, h1], {}], o.bfoo(:dbar, 1, h1)) + assert_equal([1, h1], o.bfoo(:dbaz, 1, h1)) + assert_equal([[1, h1], {}], o.store_foo(:dbar, 1, h1)) + assert_equal([1, h1], o.store_foo(:dbaz, 1, h1)) + assert_equal([[1, h1], {}], o.foo_dbar(1, h1)) + assert_equal([1, h1], o.foo_dbaz(1, h1)) + + assert_equal([[1], h1], o.block(1, :a=>1)) + assert_equal([[1], h1], o.block(1, **h1)) + assert_equal([[1, h1], {}], o.block(1, h1)) + assert_equal([[h1], {}], o.block(h1, **{})) + + assert_equal([[1], h1], o.cfunc(1, :a=>1)) + assert_equal([[1], h1], o.cfunc(1, **h1)) + assert_equal([[1, h1], {}], o.cfunc(1, h1)) + assert_equal([[h1], {}], o.cfunc(h1, **{})) + + o = mmkw.new + assert_equal([[:b, 1], h1], o.b(1, :a=>1)) + assert_equal([[:b, 1], h1], o.b(1, **h1)) + assert_equal([[:b, 1, h1], {}], o.b(1, h1)) + assert_equal([[:b, h1], {}], o.b(h1, **{})) + + o = mmnokw.new + assert_equal([:b, 1, h1], o.b(1, :a=>1)) + assert_equal([:b, 1, h1], o.b(1, **h1)) + assert_equal([:b, 1, h1], o.b(1, h1)) + assert_equal([:b, h1], o.b(h1, **{})) + + o = implicit_super.new + assert_equal([[1], h1], o.bar(1, :a=>1)) + assert_equal([[1], h1], o.bar(1, **h1)) + assert_equal([[1, h1], {}], o.bar(1, h1)) + assert_equal([[h1], {}], o.bar(h1, **{})) + + assert_equal([1, h1], o.baz(1, :a=>1)) + assert_equal([1, h1], o.baz(1, **h1)) + assert_equal([1, h1], o.baz(1, h1)) + assert_equal([h1], o.baz(h1, **{})) + + o = explicit_super.new + assert_equal([[1], h1], o.bar(1, :a=>1)) + assert_equal([[1], h1], o.bar(1, **h1)) + assert_equal([[1, h1], {}], o.bar(1, h1)) + assert_equal([[h1], {}], o.bar(h1, **{})) + + assert_equal([1, h1], o.baz(1, :a=>1)) + assert_equal([1, h1], o.baz(1, **h1)) + assert_equal([1, h1], o.baz(1, h1)) + assert_equal([h1], o.baz(h1, **{})) + + assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1)) + assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1)) + + assert_equal(:opt, o.clear_last_opt(a: 1)) + assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) } + + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(c.send(:ruby2_keywords, :bar)) + end + + c.class_eval do + def bar_post(*a, x) = nil + define_method(:bar_post_bmethod) { |*a, x| } + end + assert_warn(/Skipping set of ruby2_keywords flag for bar_post \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(c.send(:ruby2_keywords, :bar_post)) + end + assert_warn(/Skipping set of ruby2_keywords flag for bar_post_bmethod \(method accepts keywords or post arguments or method does not accept argument splat\)/) do + assert_nil(c.send(:ruby2_keywords, :bar_post_bmethod)) + end + + utf16_sym = "abcdef".encode("UTF-16LE").to_sym + c.send(:define_method, utf16_sym, c.instance_method(:itself)) + assert_warn(/abcdef/) do + assert_nil(c.send(:ruby2_keywords, utf16_sym)) + end + + o = Object.new + class << o + alias bar p + end + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method not defined in Ruby\)/) do + assert_nil(o.singleton_class.send(:ruby2_keywords, :bar)) + end + sc = Class.new(c) + assert_warn(/Skipping set of ruby2_keywords flag for bar \(can only set in method defining module\)/) do + sc.send(:ruby2_keywords, :bar) + end + m = Module.new + assert_warn(/Skipping set of ruby2_keywords flag for system \(can only set in method defining module\)/) do + m.send(:ruby2_keywords, :system) + end + + assert_raise(NameError) { c.send(:ruby2_keywords, "a5e36ccec4f5080a1d5e63f8") } + assert_raise(NameError) { c.send(:ruby2_keywords, :quux) } + + c.freeze + assert_raise(FrozenError) { c.send(:ruby2_keywords, :baz) } + end + + def test_anon_splat_ruby2_keywords + singleton_class.class_exec do + def bar(*a, **kw) + [a, kw] + end + + ruby2_keywords def bar_anon(*) + bar(*) + end + end + + a = [1, 2] + kw = {a: 1} + assert_equal([[1, 2], {a: 1}], bar_anon(*a, **kw)) + assert_equal([1, 2], a) + assert_equal({a: 1}, kw) + end + + def test_anon_splat_ruby2_keywords_bug_20388 + extend(Module.new{def process(action, ...) 1 end}) + extend(Module.new do + def process(action, *args) + args.freeze + super + end + ruby2_keywords :process + end) + + assert_equal(1, process(:foo, bar: :baz)) + end + + def test_ruby2_keywords_bug_20679 + c = Class.new do + def self.get(_, _, h, &block) + h[1] + end + + ruby2_keywords def get(*args, &block) + self.class.get(*args, &block) + end + end + + assert_equal 2, c.new.get(true, {}, 1 => 2) + end + + def test_top_ruby2_keywords + assert_in_out_err([], <<-INPUT, ["[1, 2, 3]", "{k: 1}"], []) + def bar(*a, **kw) + p a, kw + end + ruby2_keywords def foo(*a) + bar(*a) + end + foo(1, 2, 3, k:1) + INPUT + end + + def test_dig_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.dig(*args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h], [c].dig(0, **h)) + assert_equal([h], [c].dig(0, a: 1)) + assert_equal([h2], [c].dig(0, **h2)) + assert_equal([h3], [c].dig(0, **h3)) + assert_equal([h3], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig; end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + + c.singleton_class.remove_method(:dig) + def c.dig(args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal(kw, [c].dig(0, kw, **kw)) + assert_equal(h, [c].dig(0, **h)) + assert_equal(h, [c].dig(0, a: 1)) + assert_equal(h2, [c].dig(0, **h2)) + assert_equal(h3, [c].dig(0, **h3)) + assert_equal(h3, [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig(**args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + assert_raise(ArgumentError) { [c].dig(0, h) } + assert_raise(ArgumentError) { [c].dig(0, h2) } + assert_raise(ArgumentError) { [c].dig(0, h3) } + + c.singleton_class.remove_method(:dig) + def c.dig(arg, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) + assert_equal([h2, kw], [c].dig(0, **h2)) + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:dig) + def c.dig(arg=1, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) + assert_equal([h2, kw], [c].dig(0, **h2)) + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + assert_equal([h, {}], [c].dig(0, h)) + assert_equal([h2, kw], [c].dig(0, h2)) + assert_equal([h3, kw], [c].dig(0, h3)) + assert_equal([h, kw], [c].dig(0, h, **{})) + assert_equal([h2, kw], [c].dig(0, h2, **{})) + assert_equal([h3, kw], [c].dig(0, h3, **{})) + end + + def test_dig_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.method_missing(_, *args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h], [c].dig(0, **h)) + assert_equal([h], [c].dig(0, a: 1)) + assert_equal([h2], [c].dig(0, **h2)) + assert_equal([h3], [c].dig(0, **h3)) + assert_equal([h3], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing; end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal(kw, [c].dig(0, kw, **kw)) + assert_equal(h, [c].dig(0, **h)) + assert_equal(h, [c].dig(0, a: 1)) + assert_equal(h2, [c].dig(0, **h2)) + assert_equal(h3, [c].dig(0, **h3)) + assert_equal(h3, [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_raise(ArgumentError) { [c].dig(0, **h) } + assert_raise(ArgumentError) { [c].dig(0, a: 1) } + assert_raise(ArgumentError) { [c].dig(0, **h2) } + assert_raise(ArgumentError) { [c].dig(0, **h3) } + assert_raise(ArgumentError) { [c].dig(0, a: 1, **h2) } + assert_raise(ArgumentError) { [c].dig(0, h) } + assert_raise(ArgumentError) { [c].dig(0, h2) } + assert_raise(ArgumentError) { [c].dig(0, h3) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) + assert_equal([h2, kw], [c].dig(0, **h2)) + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal(c, [c].dig(0, **{})) + assert_equal(c, [c].dig(0, **kw)) + assert_equal([h, kw], [c].dig(0, **h)) + assert_equal([h, kw], [c].dig(0, a: 1)) + assert_equal([h2, kw], [c].dig(0, **h2)) + assert_equal([h3, kw], [c].dig(0, **h3)) + assert_equal([h3, kw], [c].dig(0, a: 1, **h2)) + assert_equal([h, kw], [c].dig(0, h)) + assert_equal([h2, kw], [c].dig(0, h2)) + assert_equal([h3, kw], [c].dig(0, h3)) + assert_equal([h, kw], [c].dig(0, h, **{})) + assert_equal([h2, kw], [c].dig(0, h2, **{})) + assert_equal([h3, kw], [c].dig(0, h3, **{})) + end + + def test_enumerator_size_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.to_enum(:each){|*args| args}.size + m = ->(*args){ args } + assert_equal([], c.to_enum(:each, **{}, &m).size) + assert_equal([], c.to_enum(:each, **kw, &m).size) + assert_equal([h], c.to_enum(:each, **h, &m).size) + assert_equal([h], c.to_enum(:each, a: 1, &m).size) + assert_equal([h2], c.to_enum(:each, **h2, &m).size) + assert_equal([h3], c.to_enum(:each, **h3, &m).size) + assert_equal([h3], c.to_enum(:each, a: 1, **h2, &m).size) + + m = ->(){ } + assert_nil(c.to_enum(:each, **{}, &m).size) + assert_nil(c.to_enum(:each, **kw, &m).size) + assert_raise(ArgumentError) { c.to_enum(:each, **h, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h2, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h3, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size } + + m = ->(args){ args } + assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size } + assert_equal(kw, c.to_enum(:each, kw, **kw, &m).size) + assert_equal(h, c.to_enum(:each, **h, &m).size) + assert_equal(h, c.to_enum(:each, a: 1, &m).size) + assert_equal(h2, c.to_enum(:each, **h2, &m).size) + assert_equal(h3, c.to_enum(:each, **h3, &m).size) + assert_equal(h3, c.to_enum(:each, a: 1, **h2, &m).size) + + m = ->(**args){ args } + assert_equal(kw, c.to_enum(:each, **{}, &m).size) + assert_equal(kw, c.to_enum(:each, **kw, &m).size) + assert_equal(h, c.to_enum(:each, **h, &m).size) + assert_equal(h, c.to_enum(:each, a: 1, &m).size) + assert_equal(h2, c.to_enum(:each, **h2, &m).size) + assert_equal(h3, c.to_enum(:each, **h3, &m).size) + assert_equal(h3, c.to_enum(:each, a: 1, **h2, &m).size) + assert_raise(ArgumentError) { c.to_enum(:each, h, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, h2, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, h3, &m).size } + + m = ->(arg, **args){ [arg, args] } + assert_raise(ArgumentError) { c.to_enum(:each, **{}, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **kw, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h2, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, **h3, &m).size } + assert_raise(ArgumentError) { c.to_enum(:each, a: 1, **h2, &m).size } + assert_equal([h, kw], c.to_enum(:each, h, &m).size) + assert_equal([h2, kw], c.to_enum(:each, h2, &m).size) + assert_equal([h3, kw], c.to_enum(:each, h3, &m).size) + + m = ->(arg=1, **args){ [arg, args] } + assert_equal([1, kw], c.to_enum(:each, **{}, &m).size) + assert_equal([1, kw], c.to_enum(:each, **kw, &m).size) + assert_equal([1, h], c.to_enum(:each, **h, &m).size) + assert_equal([1, h], c.to_enum(:each, a: 1, &m).size) + assert_equal([1, h2], c.to_enum(:each, **h2, &m).size) + assert_equal([1, h3], c.to_enum(:each, **h3, &m).size) + assert_equal([1, h3], c.to_enum(:each, a: 1, **h2, &m).size) + assert_equal([h, kw], c.to_enum(:each, h, &m).size) + assert_equal([h2, kw], c.to_enum(:each, h2, &m).size) + assert_equal([h3, kw], c.to_enum(:each, h3, &m).size) + end + + def test_instance_exec_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + m = ->(*args) { args } + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + m = ->() { nil } + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + m = ->(args) { args } + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + m = ->(**args) { args } + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + + m = ->(arg, **args) { [arg, args] } + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + m = ->(arg=1, **args) { [arg, args] } + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + end + + def test_instance_exec_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + def c.m + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + m = c.method(:m) + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + m = c.method(:m) + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + end + + def test_instance_exec_define_method_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.define_singleton_method(:m) do |*args| + args + end + m = c.method(:m) + assert_equal([], c.instance_exec(**{}, &m)) + assert_equal([], c.instance_exec(**kw, &m)) + assert_equal([h], c.instance_exec(**h, &m)) + assert_equal([h], c.instance_exec(a: 1, &m)) + assert_equal([h2], c.instance_exec(**h2, &m)) + assert_equal([h3], c.instance_exec(**h3, &m)) + assert_equal([h3], c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do + end + m = c.method(:m) + assert_nil(c.instance_exec(**{}, &m)) + assert_nil(c.instance_exec(**kw, &m)) + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |args| + args + end + m = c.method(:m) + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_equal(kw, c.instance_exec(kw, **kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |**args| + args + end + m = c.method(:m) + assert_equal(kw, c.instance_exec(**{}, &m)) + assert_equal(kw, c.instance_exec(**kw, &m)) + assert_equal(h, c.instance_exec(**h, &m)) + assert_equal(h, c.instance_exec(a: 1, &m)) + assert_equal(h2, c.instance_exec(**h2, &m)) + assert_equal(h3, c.instance_exec(**h3, &m)) + assert_equal(h3, c.instance_exec(a: 1, **h2, &m)) + assert_raise(ArgumentError) { c.instance_exec(h, &m) } + assert_raise(ArgumentError) { c.instance_exec(h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(h3, &m) } + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |arg, **args| + [arg, args] + end + m = c.method(:m) + assert_raise(ArgumentError) { c.instance_exec(**{}, &m) } + assert_raise(ArgumentError) { c.instance_exec(**kw, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h2, &m) } + assert_raise(ArgumentError) { c.instance_exec(**h3, &m) } + assert_raise(ArgumentError) { c.instance_exec(a: 1, **h2, &m) } + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + + c.singleton_class.remove_method(:m) + c.define_singleton_method(:m) do |arg=1, **args| + [arg, args] + end + m = c.method(:m) + assert_equal([1, kw], c.instance_exec(**{}, &m)) + assert_equal([1, kw], c.instance_exec(**kw, &m)) + assert_equal([1, h], c.instance_exec(**h, &m)) + assert_equal([1, h], c.instance_exec(a: 1, &m)) + assert_equal([1, h2], c.instance_exec(**h2, &m)) + assert_equal([1, h3], c.instance_exec(**h3, &m)) + assert_equal([1, h3], c.instance_exec(a: 1, **h2, &m)) + assert_equal([h, kw], c.instance_exec(h, &m)) + assert_equal([h2, kw], c.instance_exec(h2, &m)) + assert_equal([h3, kw], c.instance_exec(h3, &m)) + end + + def test_instance_exec_sym_proc_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + def c.m(*args) + args + end + assert_equal([], c.instance_exec(c, **{}, &:m)) + assert_equal([], c.instance_exec(c, **kw, &:m)) + assert_equal([h], c.instance_exec(c, **h, &:m)) + assert_equal([h], c.instance_exec(c, a: 1, &:m)) + assert_equal([h2], c.instance_exec(c, **h2, &:m)) + assert_equal([h3], c.instance_exec(c, **h3, &:m)) + assert_equal([h3], c.instance_exec(c, a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m + end + assert_nil(c.instance_exec(c, **{}, &:m)) + assert_nil(c.instance_exec(c, **kw, &:m)) + assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) } + assert_equal(kw, c.instance_exec(c, kw, **kw, &:m)) + assert_equal(h, c.instance_exec(c, **h, &:m)) + assert_equal(h, c.instance_exec(c, a: 1, &:m)) + assert_equal(h2, c.instance_exec(c, **h2, &:m)) + assert_equal(h3, c.instance_exec(c, **h3, &:m)) + assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.instance_exec(c, **{}, &:m)) + assert_equal(kw, c.instance_exec(c, **kw, &:m)) + assert_equal(h, c.instance_exec(c, **h, &:m)) + assert_equal(h, c.instance_exec(c, a: 1, &:m)) + assert_equal(h2, c.instance_exec(c, **h2, &:m)) + assert_equal(h3, c.instance_exec(c, **h3, &:m)) + assert_equal(h3, c.instance_exec(c, a: 1, **h2, &:m)) + assert_raise(ArgumentError) { c.instance_exec(c, h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, h3, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.instance_exec(c, **{}, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **kw, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h2, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, **h3, &:m) } + assert_raise(ArgumentError) { c.instance_exec(c, a: 1, **h2, &:m) } + assert_equal([h, kw], c.instance_exec(c, h, &:m)) + assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) + assert_equal([h3, kw], c.instance_exec(c, h3, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.instance_exec(c, **{}, &:m)) + assert_equal([1, kw], c.instance_exec(c, **kw, &:m)) + assert_equal([1, h], c.instance_exec(c, **h, &:m)) + assert_equal([1, h], c.instance_exec(c, a: 1, &:m)) + assert_equal([1, h2], c.instance_exec(c, **h2, &:m)) + assert_equal([1, h3], c.instance_exec(c, **h3, &:m)) + assert_equal([1, h3], c.instance_exec(c, a: 1, **h2, &:m)) + assert_equal([h, kw], c.instance_exec(c, h, &:m)) + assert_equal([h2, kw], c.instance_exec(c, h2, &:m)) + assert_equal([h3, kw], c.instance_exec(c, h3, &:m)) + end + + def test_rb_yield_block_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = Object.new + c.extend Bug::Iter::Yield + class << c + alias m yield_block + end + def c.c(*args) + args + end + assert_equal([], c.m(:c, **{})) + assert_equal([], c.m(:c, **kw)) + assert_equal([h], c.m(:c, **h)) + assert_equal([h], c.m(:c, a: 1)) + assert_equal([h2], c.m(:c, **h2)) + assert_equal([h3], c.m(:c, **h3)) + assert_equal([h3], c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c; end + assert_nil(c.m(:c, **{})) + assert_nil(c.m(:c, **kw)) + assert_raise(ArgumentError) { c.m(:c, **h) } + assert_raise(ArgumentError) { c.m(:c, a: 1) } + assert_raise(ArgumentError) { c.m(:c, **h2) } + assert_raise(ArgumentError) { c.m(:c, **h3) } + assert_raise(ArgumentError) { c.m(:c, a: 1, **h2) } + + c.singleton_class.remove_method(:c) + def c.c(args) + args + end + assert_raise(ArgumentError) { c.m(:c, **{}) } + assert_raise(ArgumentError) { c.m(:c, **kw) } + assert_equal(kw, c.m(:c, kw, **kw)) + assert_equal(h, c.m(:c, **h)) + assert_equal(h, c.m(:c, a: 1)) + assert_equal(h2, c.m(:c, **h2)) + assert_equal(h3, c.m(:c, **h3)) + assert_equal(h3, c.m(:c, a: 1, **h2)) + + c.singleton_class.remove_method(:c) + def c.c(**args) + [args, yield(**args)] + end + m = ->(**args){ args } + assert_equal([kw, kw], c.m(:c, **{}, &m)) + assert_equal([kw, kw], c.m(:c, **kw, &m)) + assert_equal([h, h], c.m(:c, **h, &m)) + assert_equal([h, h], c.m(:c, a: 1, &m)) + assert_equal([h2, h2], c.m(:c, **h2, &m)) + assert_equal([h3, h3], c.m(:c, **h3, &m)) + assert_equal([h3, h3], c.m(:c, a: 1, **h2, &m)) + assert_raise(ArgumentError) { c.m(:c, h, &m) } + assert_raise(ArgumentError) { c.m(:c, h2, &m) } + assert_raise(ArgumentError) { c.m(:c, h3, &m) } + + c.singleton_class.remove_method(:c) + def c.c(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.m(:c, **{}, &m) } + assert_raise(ArgumentError) { c.m(:c, **kw, &m) } + assert_raise(ArgumentError) { c.m(:c, **h, &m) } + assert_raise(ArgumentError) { c.m(:c, a: 1, &m) } + assert_raise(ArgumentError) { c.m(:c, **h2, &m) } + assert_raise(ArgumentError) { c.m(:c, **h3, &m) } + assert_raise(ArgumentError) { c.m(:c, a: 1, **h2, &m) } + assert_equal([h, kw], c.m(:c, h)) + assert_equal([h2, kw], c.m(:c, h2)) + assert_equal([h3, kw], c.m(:c, h3)) + + c.singleton_class.remove_method(:c) + def c.c(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.m(:c, **{})) + assert_equal([1, kw], c.m(:c, **kw)) + assert_equal([1, h], c.m(:c, **h)) + assert_equal([1, h], c.m(:c, a: 1)) + assert_equal([1, h2], c.m(:c, **h2)) + assert_equal([1, h3], c.m(:c, **h3)) + assert_equal([1, h3], c.m(:c, a: 1, **h2)) + assert_equal([h, kw], c.m(:c, h)) + assert_equal([h2, kw], c.m(:c, h2)) + assert_equal([h3, kw], c.m(:c, h3)) + end + + def p1 + Proc.new do |str: "foo", num: 424242| + [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, **options) + yield(*args, **options) + 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]' + a = [*%w[foo bar], {zzz: 42}] + splat_expect = a + [{}] + nonsplat_expect = [a, {}] + assert_equal(splat_expect, rest_keyrest(*a), bug7665) + assert_equal(nonsplat_expect, rest_keyrest(a), bug7665) + + pr = proc {|*args, **opt| next *args, opt} + assert_equal(splat_expect, pr.call(*a), bug7665) + assert_equal(nonsplat_expect, pr.call(a), bug8463) + + pr = proc {|a, *b, **opt| next a, *b, opt} + assert_equal(splat_expect, pr.call(a), bug8463) + + pr = proc {|a, **opt| next a, opt} + assert_equal([splat_expect, {}], pr.call(splat_expect), bug8463) + end + + def req_plus_keyword(x, **h) + [x, h] + end + + def opt_plus_keyword(x=1, **h) + [x, h] + end + + def splat_plus_keyword(*a, **h) + [a, h] + end + + def test_keyword_no_split + assert_raise(ArgumentError) { req_plus_keyword(:a=>1) } + assert_raise(ArgumentError) { req_plus_keyword("a"=>1) } + assert_raise(ArgumentError) { req_plus_keyword("a"=>1, :a=>1) } + assert_equal([{:a=>1}, {}], req_plus_keyword({:a=>1})) + assert_equal([{"a"=>1}, {}], req_plus_keyword({"a"=>1})) + assert_equal([{"a"=>1, :a=>1}, {}], req_plus_keyword({"a"=>1, :a=>1})) + + assert_equal([1, {:a=>1}], opt_plus_keyword(:a=>1)) + assert_equal([1, {"a"=>1}], opt_plus_keyword("a"=>1)) + assert_equal([1, {"a"=>1, :a=>1}], opt_plus_keyword("a"=>1, :a=>1)) + assert_equal([{:a=>1}, {}], opt_plus_keyword({:a=>1})) + assert_equal([{"a"=>1}, {}], opt_plus_keyword({"a"=>1})) + assert_equal([{"a"=>1, :a=>1}, {}], opt_plus_keyword({"a"=>1, :a=>1})) + + assert_equal([[], {:a=>1}], splat_plus_keyword(:a=>1)) + assert_equal([[], {"a"=>1}], splat_plus_keyword("a"=>1)) + assert_equal([[], {"a"=>1, :a=>1}], splat_plus_keyword("a"=>1, :a=>1)) + assert_equal([[{:a=>1}], {}], splat_plus_keyword({:a=>1})) + assert_equal([[{"a"=>1}], {}], splat_plus_keyword({"a"=>1})) + assert_equal([[{"a"=>1, :a=>1}], {}], splat_plus_keyword({"a"=>1, :a=>1})) + end + + def test_bare_kwrest + # 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_super_with_keyword_kwrest + base = Class.new do + def foo(**h) + h + end + end + a = Class.new(base) do + attr_reader :h + def foo(a:, b:, **h) + @h = h + super + end + end + + o = a.new + assert_equal({a: 1, b: 2, c: 3}, o.foo(a: 1, b: 2, c: 3)) + assert_equal({c: 3}, o.h) + end + + def test_zsuper_only_named_kwrest + bug8416 = '[ruby-core:55033] [Bug #8416]' + base = Class.new do + def foo(**h) + h + end + end + a = Class.new(base) do + attr_reader :h + def foo(**h) + @h = h + super + end + end + o = a.new + assert_equal({:bar=>"x"}, o.foo(bar: "x"), bug8416) + assert_equal({:bar=>"x"}, o.h) + end + + def test_zsuper_only_anonymous_kwrest + 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 + 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_raise(ArgumentError) { a.new.foo(1, 2, f:5) } + 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_no_implicit_hash_conversion + o = Object.new + def o.to_hash() { k: 9 } end + assert_equal([1, 42, [], o, :key, {}, nil], f9(1, o)) + assert_equal([1, 0], m1(1, o) {|a, k: 0| break [a, k]}) + assert_raise(ArgumentError) { m1(1, o, &->(a, k: 0) {break [a, k]}) } + end + + def test_splat_hash + m = Object.new + def m.f() :ok; end + def m.f1(a) a; end + def m.f2(a = nil) a; end + def m.f3(**a) a; end + def m.f4(*a) a; end + o = {a: 1} + assert_raise_with_message(ArgumentError, /wrong number of arguments \(given 1, expected 0\)/) { + m.f(**o) + } + o = {} + assert_equal(:ok, m.f(**o), '[ruby-core:68124] [Bug #10856]') + a = [] + assert_equal(:ok, m.f(*a, **o), '[ruby-core:83638] [Bug #10856]') + assert_equal(:OK, m.f1(*a, :OK, **o), '[ruby-core:91825] [Bug #10856]') + assert_equal({}, m.f1(*a, o), '[ruby-core:91825] [Bug #10856]') + + o = {a: 42} + assert_warning('', 'splat to mandatory') do + assert_equal({a: 42}, m.f1(**o)) + end + assert_warning('') do + assert_equal({a: 42}, m.f2(**o), '[ruby-core:82280] [Bug #13791]') + end + assert_warning('', 'splat to kwrest') do + assert_equal({a: 42}, m.f3(**o)) + end + assert_warning('', 'splat to rest') do + assert_equal([{a: 42}], m.f4(**o)) + end + + assert_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, timeout: 30 + assert_normal_exit %q{ + prc = Proc.new {|a: []|} + GC.stress = true + tap { prc.call } + GC.start + tap { prc.call } + }, bug8964 + end + + def test_large_kwsplat_to_method_taking_kw_and_kwsplat + assert_separately(['-'], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + n = 100000 + x = Fiber.new do + h = {kw: 2} + n.times{|i| h[i.to_s.to_sym] = i} + def self.f(kw: 1, **kws) kws.size end + f(**h) + end.resume + assert_equal(n, x) + end; + 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 + + def test_do_not_use_newarraykwsplat + assert_equal([42, "foo", 424242], f2(*[], 42, **{})) + a = [1, 2, 3] + assert_equal([[1,2,3,4,5,6], "foo", 424242, {:k=>:k}], f7(*a, 4,5,6, k: :k)) + end +end + +class TestKeywordArgumentsSymProcRefinements < Test::Unit::TestCase + class C + def call(*args, **kw) + yield(self, *args, **kw) + end + end + using(Module.new do + refine C do + def m(*args, **kw) + super + end + end + end) + + def test_sym_proc_refine_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.m(*args) + args + end + assert_equal([], c.call(**{}, &:m)) + assert_equal([], c.call(**kw, &:m)) + assert_equal([h], c.call(**h, &:m)) + assert_equal([h], c.call(h, **{}, &:m)) + assert_equal([h], c.call(a: 1, &:m)) + assert_equal([h2], c.call(**h2, &:m)) + assert_equal([h3], c.call(**h3, &:m)) + assert_equal([h3], c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m; end + assert_nil(c.call(**{}, &:m)) + assert_nil(c.call(**kw, &:m)) + assert_raise(ArgumentError) { c.call(**h, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, &:m) } + assert_raise(ArgumentError) { c.call(**h2, &:m) } + assert_raise(ArgumentError) { c.call(**h3, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(args) + args + end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } + assert_equal(h, c.call(**h, &:m)) + assert_equal(h, c.call(a: 1, &:m)) + assert_equal(h2, c.call(**h2, &:m)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:m) + def c.m(**args) + args + end + assert_equal(kw, c.call(**{}, &:m)) + assert_equal(kw, c.call(**kw, &:m)) + assert_equal(h, c.call(**h, &:m)) + assert_equal(h, c.call(a: 1, &:m)) + assert_equal(h2, c.call(**h2, &:m)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + assert_raise(ArgumentError) { c.call(h, &:m) } + assert_raise(ArgumentError) { c.call(h2, &:m) } + assert_raise(ArgumentError) { c.call(h3, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } + assert_raise(ArgumentError) { c.call(**h, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, &:m) } + assert_raise(ArgumentError) { c.call(**h2, &:m) } + assert_raise(ArgumentError) { c.call(**h3, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } + + c.singleton_class.remove_method(:m) + def c.m(arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m)) + assert_equal([1, kw], c.call(**kw, &:m)) + assert_equal([1, h], c.call(**h, &:m)) + assert_equal([1, h], c.call(a: 1, &:m)) + assert_equal([1, h2], c.call(**h2, &:m)) + assert_equal([1, h3], c.call(**h3, &:m)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m)) + end + + def test_sym_proc_refine_super_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.call(**{}, &:m)) + assert_equal([], c.call(**kw, &:m)) + assert_equal([h], c.call(**h, &:m)) + assert_equal([h], c.call(h, **{}, &:m)) + assert_equal([h], c.call(a: 1, &:m)) + assert_equal([h2], c.call(**h2, &:m)) + assert_equal([h3], c.call(**h3, &:m)) + assert_equal([h3], c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_) end + assert_nil(c.call(**{}, &:m)) + assert_nil(c.call(**kw, &:m)) + assert_raise(ArgumentError) { c.call(**h, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, &:m) } + assert_raise(ArgumentError) { c.call(**h2, &:m) } + assert_raise(ArgumentError) { c.call(**h3, &:m) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_raise(ArgumentError) { c.call(**{}, &:m) } + assert_raise(ArgumentError) { c.call(**kw, &:m) } + assert_equal(h, c.call(**h, &:m)) + assert_equal(h, c.call(a: 1, &:m)) + assert_equal(h2, c.call(**h2, &:m)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.call(**{}, &:m)) + assert_equal(kw, c.call(**kw, &:m)) + assert_equal(h, c.call(**h, &:m)) + assert_equal(h, c.call(a: 1, &:m)) + assert_equal(h2, c.call(**h2, &:m)) + assert_equal(h3, c.call(**h3, &:m)) + assert_equal(h3, c.call(a: 1, **h2, &:m)) + assert_raise(ArgumentError) { c.call(h, &:m2) } + assert_raise(ArgumentError) { c.call(h2, &:m2) } + assert_raise(ArgumentError) { c.call(h3, &:m2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } + assert_raise(ArgumentError) { c.call(**h, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, &:m2) } + assert_raise(ArgumentError) { c.call(**h2, &:m2) } + assert_raise(ArgumentError) { c.call(**h3, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m)) + assert_equal([1, kw], c.call(**kw, &:m)) + assert_equal([1, h], c.call(**h, &:m)) + assert_equal([1, h], c.call(a: 1, &:m)) + assert_equal([1, h2], c.call(**h2, &:m)) + assert_equal([1, h3], c.call(**h3, &:m)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m)) + end + + def test_sym_proc_refine_method_missing_kwsplat + kw = {} + h = {:a=>1} + h2 = {'a'=>1} + h3 = {'a'=>1, :a=>1} + + c = C.new + def c.method_missing(_, *args) + args + end + assert_equal([], c.call(**{}, &:m2)) + assert_equal([], c.call(**kw, &:m2)) + assert_equal([h], c.call(**h, &:m2)) + assert_equal([h], c.call(h, **{}, &:m2)) + assert_equal([h], c.call(a: 1, &:m2)) + assert_equal([h2], c.call(**h2, &:m2)) + assert_equal([h3], c.call(**h3, &:m2)) + assert_equal([h3], c.call(a: 1, **h2, &:m2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_) end + assert_nil(c.call(**{}, &:m2)) + assert_nil(c.call(**kw, &:m2)) + assert_raise(ArgumentError) { c.call(**h, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, &:m2) } + assert_raise(ArgumentError) { c.call(**h2, &:m2) } + assert_raise(ArgumentError) { c.call(**h3, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, args) + args + end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } + assert_equal(h, c.call(**h, &:m2)) + assert_equal(h, c.call(a: 1, &:m2)) + assert_equal(h2, c.call(**h2, &:m2)) + assert_equal(h3, c.call(**h3, &:m2)) + assert_equal(h3, c.call(a: 1, **h2, &:m2)) + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, **args) + args + end + assert_equal(kw, c.call(**{}, &:m2)) + assert_equal(kw, c.call(**kw, &:m2)) + assert_equal(h, c.call(**h, &:m2)) + assert_equal(h, c.call(a: 1, &:m2)) + assert_equal(h2, c.call(**h2, &:m2)) + assert_equal(h3, c.call(**h3, &:m2)) + assert_equal(h3, c.call(a: 1, **h2, &:m2)) + assert_raise(ArgumentError) { c.call(h, &:m2) } + assert_raise(ArgumentError) { c.call(h2, &:m2) } + assert_raise(ArgumentError) { c.call(h3, &:m2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg, **args) + [arg, args] + end + assert_raise(ArgumentError) { c.call(**{}, &:m2) } + assert_raise(ArgumentError) { c.call(**kw, &:m2) } + assert_raise(ArgumentError) { c.call(**h, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, &:m2) } + assert_raise(ArgumentError) { c.call(**h2, &:m2) } + assert_raise(ArgumentError) { c.call(**h3, &:m2) } + assert_raise(ArgumentError) { c.call(a: 1, **h2, &:m2) } + + c.singleton_class.remove_method(:method_missing) + def c.method_missing(_, arg=1, **args) + [arg, args] + end + assert_equal([1, kw], c.call(**{}, &:m2)) + assert_equal([1, kw], c.call(**kw, &:m2)) + assert_equal([1, h], c.call(**h, &:m2)) + assert_equal([1, h], c.call(a: 1, &:m2)) + assert_equal([1, h2], c.call(**h2, &:m2)) + assert_equal([1, h3], c.call(**h3, &:m2)) + assert_equal([1, h3], c.call(a: 1, **h2, &:m2)) + end + + def test_protected_kwarg + mock = Class.new do + def foo + bar('x', y: 'z') + end + protected + def bar(x, y) + nil + end + end + + assert_nothing_raised do + mock.new.foo + end + end + + def test_splat_fixnum + bug16603 = '[ruby-core:97047] [Bug #16603]' + assert_raise(TypeError, bug16603) { p(**42) } + assert_raise(TypeError, bug16603) { p(k:1, **42) } + end + + def test_value_omission + f = ->(**kwargs) { kwargs } + x = 1 + y = 2 + assert_equal({x: 1, y: 2}, f.call(x:, y:)) + assert_equal({x: 1, y: 2, z: 3}, f.call(x:, y:, z: 3)) + assert_equal({one: 1, two: 2}, f.call(one:, two:)) + end + + def m_bug20570(*a, **nil) + a + end + + def test_splat_arg_with_prohibited_keyword + assert_equal([], m_bug20570(*[])) + assert_equal([1], m_bug20570(*[1])) + assert_equal([1, 2], m_bug20570(*[1, 2])) + h = nil + assert_equal([], m_bug20570(*[], **h)) + assert_equal([1], m_bug20570(*[1], **h)) + assert_equal([1, 2], m_bug20570(*[1, 2], **h)) + + assert_equal([], m_bug20570(*[], **nil)) + assert_equal([1], m_bug20570(*[1], **nil)) + assert_equal([1, 2], m_bug20570(*[1, 2], **nil)) + end + + private def one + 1 + end + + private def two + 2 + end +end diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb new file mode 100644 index 0000000000..c1858a36dd --- /dev/null +++ b/test/ruby/test_lambda.rb @@ -0,0 +1,328 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestLambdaParameters < Test::Unit::TestCase + + def test_exact_parameter + assert_raise(ArgumentError){(1..3).each(&lambda{})} + end + + def test_call_simple + assert_equal(1, lambda{|a| a}.call(1)) + assert_equal([1,2], lambda{|a, b| [a,b]}.call(1,2)) + assert_raise(ArgumentError) { lambda{|a|}.call(1,2) } + assert_raise(ArgumentError) { lambda{|a|}.call() } + assert_raise(ArgumentError) { lambda{}.call(1) } + assert_raise(ArgumentError) { lambda{|a, b|}.call(1,2,3) } + + assert_equal(1, ->(a){ a }.call(1)) + assert_equal([1,2], ->(a,b){ [a,b] }.call(1,2)) + assert_raise(ArgumentError) { ->(a){ }.call(1,2) } + assert_raise(ArgumentError) { ->(a){ }.call() } + assert_raise(ArgumentError) { ->(){ }.call(1) } + assert_raise(ArgumentError) { ->(a,b){ }.call(1,2,3) } + end + + def test_lambda_as_iterator + a = 0 + 2.times(&->(_){ a += 1 }) + 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 + assert_equal([1,2], ->(*a){ a }.call(1,2)) + assert_equal([1,2,[]], ->(a,b,*c){ [a,b,c] }.call(1,2)) + assert_raise(ArgumentError){ ->(a,*b){ }.call() } + end + + def test_call_opt_args + assert_equal([1,2,3,4], ->(a,b,c=3,d=4){ [a,b,c,d] }.call(1,2)) + assert_equal([1,2,3,4], ->(a,b,c=0,d=4){ [a,b,c,d] }.call(1,2,3)) + assert_raise(ArgumentError){ ->(a,b=1){ }.call() } + assert_raise(ArgumentError){ ->(a,b=1){ }.call(1,2,3) } + end + + def test_call_rest_and_opt + assert_equal([1,2,3,[]], ->(a,b=2,c=3,*d){ [a,b,c,d] }.call(1)) + assert_equal([1,2,3,[]], ->(a,b=0,c=3,*d){ [a,b,c,d] }.call(1,2)) + assert_equal([1,2,3,[4,5,6]], ->(a,b=0,c=0,*d){ [a,b,c,d] }.call(1,2,3,4,5,6)) + assert_raise(ArgumentError){ ->(a,b=1,*c){ }.call() } + end + + def test_call_with_block + f = ->(a,b,c=3,*d,&e){ [a,b,c,d,e.call(d + [a,b,c])] } + assert_equal([1,2,3,[],6], f.call(1,2){|z| z.inject{|s,x| s+x} } ) + 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) + 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_proc_inside_lambda_inside_method_return_inside_lambda_inside_method + def self.a + -> do + p = Proc.new{return :a} + p.call + end.call + end + assert_equal(:a, a) + + def self.b + lambda do + p = Proc.new{return :b} + p.call + end.call + end + assert_equal(:b, b) + end + + def test_proc_inside_lambda_inside_method_return_inside_lambda_outside_method + def self.a + -> do + p = Proc.new{return :a} + p.call + end + end + assert_equal(:a, a.call) + + def self.b + lambda do + p = Proc.new{return :b} + p.call + end + end + assert_equal(:b, b.call) + end + + def test_proc_inside_lambda_inside_method_return_outside_lambda_inside_method + def self.a + -> do + Proc.new{return :a} + end.call.call + end + assert_raise(LocalJumpError) {a} + + def self.b + lambda do + Proc.new{return :b} + end.call.call + end + assert_raise(LocalJumpError) {b} + end + + def test_proc_inside_lambda_inside_method_return_outside_lambda_outside_method + def self.a + -> do + Proc.new{return :a} + end + end + assert_raise(LocalJumpError) {a.call.call} + + def self.b + lambda do + Proc.new{return :b} + end + end + assert_raise(LocalJumpError) {b.call.call} + end + + def test_proc_inside_lambda2_inside_method_return_outside_lambda1_inside_method + def self.a + -> do + -> do + Proc.new{return :a} + end.call.call + end.call + end + assert_raise(LocalJumpError) {a} + + def self.b + lambda do + lambda do + Proc.new{return :a} + end.call.call + end.call + end + assert_raise(LocalJumpError) {b} + end + + def test_proc_inside_lambda_toplevel + assert_ruby_status [], <<~RUBY + lambda{ + $g = proc{ return :pr } + }.call + begin + $g.call + rescue LocalJumpError + # OK! + else + raise + end + RUBY + 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 break_in_current(val) + 1.tap(&->(*) {break 0}) + val + end + + def break_in_callee(val) + yield_block(&->(*) {break 0}) + val + end + + def test_break + assert_equal(42, break_in_current(42)) + assert_equal(42, break_in_callee(42)) + end + + def test_do_lambda_source_location + exp = [__LINE__ + 1, 10, __LINE__ + 5, 7] + lmd = ->(x, + y, + z) do + # + end + file, *loc = lmd.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp, loc) + end + + def test_brace_lambda_source_location + exp = [__LINE__ + 1, 10, __LINE__ + 5, 5] + lmd = ->(x, + y, + z) { + # + } + file, *loc = lmd.source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp, loc) + end + + def test_not_orphan_return + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { return 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { return 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { return 42 }) end }.m2.call) + end + + def test_not_orphan_break + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { break 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { break 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { break 42 }) end }.m2.call) + end + + def test_not_orphan_next + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1(&-> { next 42 }) end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { next 42 }).call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1(&-> { next 42 }) end }.m2.call) + end +end diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb new file mode 100644 index 0000000000..4dddbab50c --- /dev/null +++ b/test/ruby/test_lazy_enumerator.rb @@ -0,0 +1,718 @@ +# 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_flat_map_take + assert_equal([1,2]*3, [[1,2]].cycle.lazy.take(3).flat_map {|x| x}.to_a) + end + + def test_reject + a = Step.new(1..6) + assert_equal(4, a.reject {|x| x < 4}.first) + 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_zip_map_lambda_bug_19569 + ary = [1, 2, 3].to_enum.lazy.zip([:a, :b, :c]).map(&:last).to_a + assert_equal([:a, :b, :c], ary) + 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_0_bug_18971 + def (bomb = Object.new.extend(Enumerable)).each + raise + end + [2..10, bomb].each do |e| + assert_equal([], e.lazy.take(0).map(&:itself).to_a) + assert_equal([], e.lazy.take(0).select(&:even?).to_a) + assert_equal([], e.lazy.take(0).select(&:odd?).to_a) + assert_equal([], e.lazy.take(0).reject(&:even?).to_a) + assert_equal([], e.lazy.take(0).reject(&:odd?).to_a) + assert_equal([], e.lazy.take(0).take(1).to_a) + assert_equal([], e.lazy.take(0).take(0).take(1).to_a) + assert_equal([], e.lazy.take(0).drop(0).to_a) + assert_equal([], e.lazy.take(0).find_all {|_| true}.to_a) + assert_equal([], e.lazy.take(0).zip((12..20)).to_a) + assert_equal([], e.lazy.take(0).uniq.to_a) + assert_equal([], e.lazy.take(0).sort.to_a) + end + end + + def test_take_bad_arg + a = Step.new(1..10) + assert_raise(ArgumentError) { a.lazy.take(-1) } + 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_eager + lazy = [1, 2, 3].lazy.map { |x| x * 2 } + enum = lazy.eager + assert_equal Enumerator, enum.class + assert_equal 3, enum.size + assert_equal [1, 2, 3], enum.map { |x| x / 2 } + end + + def test_lazy_zip_map_yield_arity_bug_20623 + assert_equal([[1, 2]], [1].lazy.zip([2].lazy).map { |x| x }.force) + 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_lazy_to_enum_lazy_methods + a = [1, 2, 3].to_enum + pr = proc{|x| [x, x * 2]} + selector = proc{|x| x*2 if x % 2 == 0} + + [ + [:with_index, nil], + [:with_index, 10, nil], + [:with_index, 10, pr], + [:map, nil], + [:map, pr], + [:collect, nil], + [:flat_map, nil], + [:flat_map, pr], + [:collect_concat, nil], + [:select, nil], + [:select, selector], + [:find_all, nil], + [:filter, nil], + [:filter_map, selector], + [:filter_map, nil], + [:reject, selector], + [:grep, selector, nil], + [:grep, selector, pr], + [:grep_v, selector, nil], + [:grep_v, selector, pr], + [:zip, a, nil], + [:take, 3, nil], + [:take_while, nil], + [:take_while, selector], + [:drop, 1, nil], + [:drop_while, nil], + [:drop_while, selector], + [:uniq, nil], + [:uniq, proc{|x| x.odd?}], + ].each do |args| + block = args.pop + assert_equal [1, 2, 3].to_enum.to_enum(*args).first(2).to_a, [1, 2, 3].to_enum.lazy.to_enum(*args).first(2).to_a + assert_equal (0..50).to_enum.to_enum(*args).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).first(2).to_a + if block + assert_equal [1, 2, 3, 4].to_enum.to_enum(*args).map(&block).first(2).to_a, [1, 2, 3, 4].to_enum.lazy.to_enum(*args).map(&block).first(2).to_a + unless args.first == :take_while || args.first == :drop_while + assert_equal (0..50).to_enum.to_enum(*args).map(&block).first(2).to_a, (0..50000).to_enum.lazy.to_enum(*args).map(&block).first(2).to_a + end + end + end + end + + def test_size + lazy = [1, 2, 3].lazy + assert_equal 3, lazy.size + 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 + + def test_filter_map + e = (1..Float::INFINITY).lazy.filter_map do |x| + raise "too big" if x > 10000 + (x**2) % 10 if x.even? + end + assert_equal([4, 6, 6, 4, 0, 4], e.first(6)) + assert_equal([4, 6, 6, 4, 0, 4], e.first(6)) + end + + def test_with_index + feature7877 = '[ruby-dev:47025] [Feature #7877]' + leibniz = ->(n) { + (0..Float::INFINITY).lazy.with_index.map {|i, j| + raise IndexError, "limit exceeded (#{n})" unless j < n + ((-1) ** j) / (2*i+1).to_f + }.take(n).reduce(:+) + } + assert_nothing_raised(IndexError, feature7877) { + assert_in_epsilon(Math::PI/4, leibniz[1000]) + } + + a = [] + ary = (0..Float::INFINITY).lazy.with_index(2) {|i, j| a << [i-1, j] }.take(2).to_a + assert_equal([[-1, 2], [0, 3]], a) + assert_equal([0, 1], ary) + + a = [] + ary = (0..Float::INFINITY).lazy.with_index(2, &->(i,j) { a << [i-1, j] }).take(2).to_a + assert_equal([[-1, 2], [0, 3]], a) + assert_equal([0, 1], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).map {|i, j| [i-1, j] }.take(2).to_a + assert_equal([[-1, 2], [0, 3]], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).map(&->(i, j) { [i-1, j] }).take(2).to_a + assert_equal([[-1, 2], [0, 3]], ary) + + ary = (0..Float::INFINITY).lazy.with_index(2).take(2).to_a + assert_equal([[0, 2], [1, 3]], ary) + + ary = (0..Float::INFINITY).lazy.with_index.take(2).to_a + assert_equal([[0, 0], [1, 1]], ary) + end + + def test_with_index_size + assert_equal(3, Enumerator::Lazy.new([1, 2, 3], 3){|y, v| y << v}.with_index.size) + end +end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb new file mode 100644 index 0000000000..cff888d4b3 --- /dev/null +++ b/test/ruby/test_literal.rb @@ -0,0 +1,700 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestRubyLiteral < Test::Unit::TestCase + + def test_special_const + assert_equal 'true', true.inspect + assert_instance_of TrueClass, true + assert_equal 'false', false.inspect + assert_instance_of FalseClass, false + assert_equal 'nil', nil.inspect + assert_instance_of NilClass, nil + assert_equal ':sym', :sym.inspect + assert_instance_of Symbol, :sym + assert_equal '1234', 1234.inspect + assert_instance_of Integer, 1234 + assert_equal '1234', 1_2_3_4.inspect + assert_instance_of Integer, 1_2_3_4 + assert_equal '18', 0x12.inspect + assert_instance_of Integer, 0x12 + assert_raise(SyntaxError) { eval("0x") } + assert_equal '15', 0o17.inspect + assert_instance_of Integer, 0o17 + assert_raise(SyntaxError) { eval("0o") } + assert_equal '5', 0b101.inspect + assert_instance_of Integer, 0b101 + assert_raise(SyntaxError) { eval("0b") } + assert_equal '123456789012345678901234567890', 123456789012345678901234567890.to_s + assert_instance_of Integer, 123456789012345678901234567890 + assert_instance_of Float, 1.3 + assert_equal '2', eval("0x00+2").inspect + end + + def test_self + assert_equal self, self + assert_instance_of TestRubyLiteral, self + assert_respond_to self, :test_self + end + + def test_string + verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings + + assert_instance_of String, ?a + assert_equal "a", ?a + assert_instance_of String, ?A + assert_equal "A", ?A + assert_instance_of String, ?\n + assert_equal "\n", ?\n + assert_equal " ", ?\s + assert_equal " ", ?\ # space + assert_equal '', '' + assert_equal 'string', 'string' + assert_equal 'string string', 'string string' + assert_equal ' ', ' ' + assert_equal ' ', " " + assert_equal "\0", "\0" + assert_equal "\1", "\1" + 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| + ["?", ['"', '"']].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") + + assert_raise(SyntaxError) {eval('"\C-' "\u3042" '"')} + assert_raise(SyntaxError) {eval('"\C-\\' "\u3042" '"')} + assert_raise(SyntaxError) {eval('"\M-' "\u3042" '"')} + assert_raise(SyntaxError) {eval('"\M-\\' "\u3042" '"')} + + assert_equal "\x09 \xC9 \x89", eval('"\C-\111 \M-\111 \M-\C-\111"') + ensure + $VERBOSE = verbose_bak + end + + def test_dstring + assert_equal '2', "#{1+1}" + assert_equal '16', "#{2 ** 4}" + s = "string" + assert_equal s, "#{s}" + a = 'Foo' + b = "#{a}" << 'Bar' + assert_equal('Foo', a, 'r3842') + 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 + default = eval("'test'").frozen? + + 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_equal(default, str.frozen?) + end + a.for("true with preceding garbage") do + str = eval("# x frozen-string-literal: true\n""'foo'") + assert_equal(default, str.frozen?) + end + a.for("false with succeeding garbage") do + str = eval("# frozen-string-literal: false x\n""'foo'") + assert_equal(default, str.frozen?) + end + a.for("true with succeeding garbage") do + str = eval("# frozen-string-literal: true x\n""'foo'") + assert_equal(default, 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 + + def test_string_in_hash_literal + hash = eval("# frozen-string-literal: false\n""{foo: 'foo'}") + assert_not_predicate(hash[:foo], :frozen?) + end + + if defined?(RubyVM::InstructionSequence.compile_option) and + RubyVM::InstructionSequence.compile_option.key?(:debug_frozen_string_literal) + def test_debug_frozen_string + src = '_="foo-1"'; f = "test.rb"; n = 1 + opt = {frozen_string_literal: true, debug_frozen_string_literal: true} + str = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval + assert_equal("foo-1", str) + assert_predicate(str, :frozen?) + assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { + str << "x" + } unless ENV['RUBY_ISEQ_DUMP_DEBUG'] + end + + def test_debug_frozen_string_in_array_literal + src = '["foo"]'; f = "test.rb"; n = 1 + opt = {frozen_string_literal: true, debug_frozen_string_literal: true} + ary = RubyVM::InstructionSequence.compile(src, f, f, n, **opt).eval + assert_equal("foo", ary.first) + assert_predicate(ary.first, :frozen?) + assert_raise_with_message(FrozenError, /created at #{Regexp.quote(f)}:#{n}/) { + ary.first << "x" + } unless ENV['RUBY_ISEQ_DUMP_DEBUG'] + end + end + + def test_regexp + assert_instance_of Regexp, // + assert_match(//, 'a') + assert_match(//, '') + assert_instance_of Regexp, /a/ + assert_match(/a/, 'a') + assert_no_match(/test/, 'tes') + re = /test/ + assert_match re, 'test' + str = 'test' + assert_match re, str + assert_match(/test/, str) + assert_equal 0, (/test/ =~ 'test') + assert_equal 0, (re =~ 'test') + assert_equal 0, (/test/ =~ str) + assert_equal 0, (re =~ str) + assert_equal 0, ('test' =~ /test/) + assert_equal 0, ('test' =~ re) + assert_equal 0, (str =~ /test/) + assert_equal 0, (str =~ re) + end + + def test_dregexp + assert_instance_of Regexp, /re#{'ge'}xp/ + assert_equal(/regexp/, /re#{'ge'}xp/) + + # [ruby-core:32682] + eval('/[#{"\x80"}]/') + end + + def test_array + assert_instance_of Array, [] + assert_equal [], [] + assert_equal 0, [].size + assert_instance_of Array, [0] + assert_equal [3], [3] + assert_equal 1, [3].size + a = [3] + assert_equal 3, a[0] + assert_instance_of Array, [1,2] + assert_equal [1,2], [1,2] + assert_instance_of Array, [1,2,3,4,5] + assert_equal [1,2,3,4,5], [1,2,3,4,5] + assert_equal 5, [1,2,3,4,5].size + a = [1,2] + assert_equal 1, a[0] + assert_equal 2, a[1] + a = [1 + 2, 3 + 4, 5 + 6] + assert_instance_of Array, a + assert_equal [3, 7, 11], a + assert_equal 7, a[1] + assert_equal 1, ([0][0] += 1) + assert_equal 1, ([2][0] -= 1) + a = [obj = Object.new] + assert_instance_of Array, a + assert_equal 1, a.size + assert_equal obj, a[0] + a = [1,2,3] + a[1] = 5 + assert_equal 5, a[1] + end + + def test_hash + assert_instance_of Hash, {} + assert_equal({}, {}) + assert_instance_of Hash, {1 => 2} + assert_equal({1 => 2}, {1 => 2}) + h = {1 => 2} + assert_equal 2, h[1] + h = {"string" => "literal", "goto" => "hell"} + assert_equal h, h + assert_equal 2, h.size + assert_equal h, h + 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 "#{<<-"begin;"}\n#{<<-'end;'}" + begin; + # This is a syntax that renders warning at very early stage. + # eval used to delay warning, to be suppressible by EnvUtil. + {"a" => 100, "b" => 200, "a" => 300, "a" => 400} + end; + end + assert_equal(2, h.size) + assert_equal(400, h['a']) + assert_equal(200, h['b']) + assert_nil(h['c']) + assert_equal(nil, h.key('300')) + + a = [] + h = EnvUtil.suppress_warning do + eval <<~end + # This is a syntax that renders warning at very early stage. + # eval used to delay warning, to be suppressible by EnvUtil. + {"a" => a.push(100).last, "b" => a.push(200).last, "a" => a.push(300).last, "a" => a.push(400).last} + end + end + assert_equal({'a' => 400, 'b' => 200}, h) + assert_equal([100, 200, 300, 400], a) + + assert_all_assertions_foreach( + "duplicated literal key", + ':foo', + '"a"', + '1000', + '1.0', + '1_000_000_000_000_000_000_000', + '1.0r', + '1.0i', + '1.72723e-77', + '//', + '__LINE__', + '__FILE__', + '__ENCODING__', + ) do |key| + assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) { eval("{#{key} => :bar, #{key} => :foo}") } + end + 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 + + FOO = "foo" + + def test_hash_value_omission + x = 1 + y = 2 + assert_equal({x: 1, y: 2}, {x:, y:}) + assert_equal({x: 1, y: 2, z: 3}, {x:, y:, z: 3}) + assert_equal({one: 1, two: 2}, {one:, two:}) + b = binding + b.local_variable_set(:if, "if") + b.local_variable_set(:self, "self") + assert_equal({FOO: "foo", if: "if", self: "self"}, + eval('{FOO:, if:, self:}', b)) + assert_syntax_error('{"#{x}":}', /'\}'/) + end + + private def one + 1 + end + + private def two + 2 + end + + def test_range + assert_instance_of Range, (1..2) + assert_equal(1..2, 1..2) + r = 1..2 + assert_equal 1, r.begin + assert_equal 2, r.end + assert_equal false, r.exclude_end? + assert_instance_of Range, (1...3) + assert_equal(1...3, 1...3) + r = 1...3 + assert_equal 1, r.begin + assert_equal 3, r.end + assert_equal true, r.exclude_end? + r = 1+2 .. 3+4 + assert_instance_of Range, r + assert_equal 3, r.begin + assert_equal 7, r.end + assert_equal false, r.exclude_end? + r = 1+2 ... 3+4 + assert_instance_of Range, r + assert_equal 3, r.begin + assert_equal 7, r.end + assert_equal true, r.exclude_end? + assert_instance_of Range, 'a'..'c' + r = 'a'..'c' + assert_equal 'a', r.begin + assert_equal 'c', r.end + end + + def test__FILE__ + assert_instance_of String, __FILE__ + assert_equal __FILE__, __FILE__ + assert_equal 'test_literal.rb', File.basename(__FILE__) + end + + def test__LINE__ + assert_instance_of Integer, __LINE__ + assert_equal __LINE__, __LINE__ + end + + def test_integer + verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings + + head = ['', '0x', '0o', '0b', '0d', '-', '+'] + chars = ['0', '1', '_', '9', 'f'] + head.each {|h| + 4.times {|len| + a = [h] + len.times { a = a.product(chars).map {|x| x.join('') } } + a.each {|s| + next if s.empty? + begin + r1 = Integer(s) + rescue ArgumentError + r1 = :err + end + begin + r2 = eval(s) + rescue NameError, SyntaxError + r2 = :err + end + assert_equal(r1, r2, "Integer(#{s.inspect}) != eval(#{s.inspect})") + } + } + } + bug2407 = '[ruby-dev:39798]' + 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 + + ensure + $VERBOSE = verbose_bak + end + + def test_float + verbose_bak, $VERBOSE = $VERBOSE, nil # prevent syntax warnings + + head = ['', '-', '+'] + chars = ['0', '1', '_', '9', 'f', '.'] + head.each {|h| + 6.times {|len| + a = [h] + len.times { a = a.product(chars).map {|x| x.join('') } } + a.each {|s| + next if s.empty? + next if /\.\z/ =~ s + next if /\A[-+]?\./ =~ s + next if /\A[-+]?0/ =~ s + begin + r1 = Float(s) + rescue ArgumentError + r1 = :err + end + begin + r2 = eval(s) + rescue ArgumentError + # Debug log for a random failure: ArgumentError: SyntaxError#path changed + $stderr.puts "TestRubyLiteral#test_float failed: %p" % s + raise + rescue SyntaxError => e + r2 = :err + rescue NameError + r2 = :err + end + r2 = :err if Range === r2 + s = s.inspect + mesg = "Float(#{s}) != eval(#{s})" + mesg << ":" << e.message if e + assert_equal(r1, r2, mesg) + } + } + } + assert_equal(100.0, 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100e100) + + ensure + $VERBOSE = verbose_bak + end + + def test_rational_float + assert_equal(12, 0.12r * 100) + assert_equal(12, 0.1_2r * 100) + 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 new file mode 100644 index 0000000000..9f7a3c7f4b --- /dev/null +++ b/test/ruby/test_m17n.rb @@ -0,0 +1,1733 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' + +class TestM17N < Test::Unit::TestCase + def assert_encoding(encname, actual, message=nil) + assert_equal(Encoding.find(encname), actual, message) + end + + module AESU + def ua(str) str.dup.force_encoding("US-ASCII") 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 + end + include AESU + extend AESU + + def assert_strenc(bytes, enc, actual, message=nil) + 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_regexp_generic_encoding(r) + 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) } + } + end + + def assert_regexp_fixed_encoding(r) + assert_predicate(r, :fixed_encoding?) + %w[ASCII-8BIT EUC-JP Windows-31J UTF-8].each {|ename| + enc = Encoding.find(ename) + if enc == r.encoding + assert_nothing_raised { r =~ "\xc2\xa1".force_encoding(enc) } + else + assert_raise(Encoding::CompatibilityError) { r =~ "\xc2\xa1".force_encoding(enc) } + end + } + end + + def assert_regexp_generic_ascii(r) + assert_encoding("US-ASCII", 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("Windows-31J", 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) + end + + def encdump(str) + d = str.dump + if /\.force_encoding\("[A-Za-z0-9.:_+-]*"\)\z/ =~ d + d + else + "#{d}.force_encoding(#{str.encoding.name.dump})" + end + end + + def encdumpargs(args) + r = '(' + args.each_with_index {|a, i| + r << ',' if 0 < i + if String === a + r << encdump(a) + else + r << a.inspect + end + } + r << ')' + r + end + + def assert_str_enc_propagation(t, s1, s2) + if !s1.ascii_only? + assert_equal(s1.encoding, t.encoding) + elsif !s2.ascii_only? + assert_equal(s2.encoding, t.encoding) + else + assert_include([s1.encoding, s2.encoding], t.encoding) + end + end + + def assert_same_result(expected_proc, actual_proc) + e = nil + begin + t = expected_proc.call + rescue + e = $! + end + if e + assert_raise(e.class) { actual_proc.call } + else + assert_equal(t, actual_proc.call) + end + end + + def str_enc_compatible?(*strs) + encs = [] + strs.each {|s| + encs << s.encoding if !s.ascii_only? + } + encs.uniq! + encs.length <= 1 + end + + # tests start + + def test_string_ascii_literal + assert_encoding("ASCII-8BIT", eval(a(%{""})).encoding) + assert_encoding("ASCII-8BIT", eval(a(%{"a"})).encoding) + end + + def test_string_eucjp_literal + assert_encoding("EUC-JP", eval(e(%{""})).encoding) + assert_encoding("EUC-JP", eval(e(%{"a"})).encoding) + assert_encoding("EUC-JP", eval(e(%{"\xa1\xa1"})).encoding) + assert_encoding("EUC-JP", eval(e(%{"\\xa1\\xa1"})).encoding) + assert_encoding("EUC-JP", eval(e(%{"\\x20"})).encoding) + assert_encoding("EUC-JP", eval(e(%{"\\n"})).encoding) + assert_encoding("EUC-JP", eval(e(%{"\\x80"})).encoding) + end + + def test_utf8_literal + assert_equal(Encoding::UTF_8, "\u3042".encoding, "[ruby-dev:33406] \"\\u3042\".encoding") + assert_raise(SyntaxError) { eval(a('\u3052\x80')) } + end + + def test_string_mixed_unicode + assert_raise(SyntaxError) { eval(a(%{"\xc2\xa1\\u{6666}"})) } + assert_raise(SyntaxError) { eval(e(%{"\xc2\xa1\\u{6666}"})) } + assert_raise(SyntaxError) { eval(s(%{"\xc2\xa1\\u{6666}"})) } + assert_nothing_raised { eval(u(%{"\xc2\xa1\\u{6666}"})) } + assert_raise(SyntaxError) { eval(a(%{"\\u{6666}\xc2\xa1"})) } + assert_raise(SyntaxError) { eval(e(%{"\\u{6666}\xc2\xa1"})) } + assert_raise(SyntaxError) { eval(s(%{"\\u{6666}\xc2\xa1"})) } + assert_nothing_raised { eval(u(%{"\\u{6666}\xc2\xa1"})) } + end + + def test_string_inspect_invalid + assert_equal('"\xFE"', e("\xfe").inspect) + assert_equal('"\x8E"', e("\x8e").inspect) + assert_equal('"\x8F"', e("\x8f").inspect) + assert_equal('"\x8F\xA1"', e("\x8f\xa1").inspect) + assert_equal('"\xEF"', s("\xef").inspect) + assert_equal('"\xC2"', u("\xc2").inspect) + assert_equal('"\xE0\x80"', u("\xe0\x80").inspect) + assert_equal('"\xF0\x80\x80"', u("\xf0\x80\x80").inspect) + assert_equal('"\xF8\x80\x80\x80"', u("\xf8\x80\x80\x80").inspect) + assert_equal('"\xFC\x80\x80\x80\x80"', u("\xfc\x80\x80\x80\x80").inspect) + + assert_equal('"\xFE "', e("\xfe ").inspect) + assert_equal('"\x8E "', e("\x8e ").inspect) + assert_equal('"\x8F "', e("\x8f ").inspect) + assert_equal('"\x8F\xA1 "', e("\x8f\xa1 ").inspect) + assert_equal('"\xEF "', s("\xef ").inspect) + assert_equal('"\xC2 "', u("\xc2 ").inspect) + assert_equal('"\xE0\x80 "', u("\xe0\x80 ").inspect) + assert_equal('"\xF0\x80\x80 "', u("\xf0\x80\x80 ").inspect) + assert_equal('"\xF8\x80\x80\x80 "', u("\xf8\x80\x80\x80 ").inspect) + assert_equal('"\xFC\x80\x80\x80\x80 "', u("\xfc\x80\x80\x80\x80 ").inspect) + + assert_equal('"\x81."', s("\x81.").inspect) + assert_equal('"\xFC"', u("\xfc").inspect) + end + + def test_string_inspect_encoding + [ + Encoding::UTF_8, + Encoding::EUC_JP, + Encoding::Windows_31J, + Encoding::GB18030, + ].each do |e| + EnvUtil.with_default_external(e) do + 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 + end + + EnvUtil.with_default_external(Encoding::UTF_8) do + [ + 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 + end + end + + def test_utf_dummy_are_like_regular_dummy_encodings + [Encoding::UTF_16, Encoding::UTF_32].each do |enc| + s = "\u3042".encode("UTF-32BE") + assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect) + s = "\x00\x00\xFE\xFF" + assert_equal(s.dup.force_encoding("ISO-2022-JP").inspect, s.dup.force_encoding(enc).inspect) + + assert_equal [0, 0, 254, 255], "\x00\x00\xFE\xFF".force_encoding(enc).codepoints + assert_equal 0, "\x00\x00\xFE\xFF".force_encoding(enc).ord + assert_equal 255, "\xFF\xFE\x00\x00".force_encoding(enc).ord + end + end + + 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.with_default_external(Encoding::UTF_8) do + 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 + end + end + + def test_object_inspect_external + omit "https://bugs.ruby-lang.org/issues/18338" + + o = Object.new + + EnvUtil.with_default_external(Encoding::UTF_16BE) do + def o.inspect + "abc" + end + assert_nothing_raised(Encoding::CompatibilityError) { [o].inspect } + + def o.inspect + "abc".encode(Encoding.default_external) + end + assert_equal '[abc]', [o].inspect + end + + EnvUtil.with_default_external(Encoding::US_ASCII) do + 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 + end + end + + def test_str_dump + [ + e("\xfe"), + e("\x8e"), + e("\x8f"), + e("\x8f\xa1"), + s("\xef"), + u("\xc2"), + u("\xe0\x80"), + u("\xf0\x80\x80"), + u("\xf8\x80\x80\x80"), + u("\xfc\x80\x80\x80\x80"), + + e("\xfe "), + e("\x8e "), + e("\x8f "), + e("\x8f\xa1 "), + s("\xef "), + u("\xc2 "), + u("\xe0\x80 "), + u("\xf0\x80\x80 "), + u("\xf8\x80\x80\x80 "), + u("\xfc\x80\x80\x80\x80 "), + + + e("\xa1\x8f\xa1\xa1"), + + s("\x81."), + s("\x81@"), + + u("\xfc"), + "\u3042", + "ascii", + + "\u3042".encode("UTF-16LE"), + "\u3042".encode("UTF-16BE"), + ].each do |str| + 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 + + def test_validate_redundant_utf8 + bits_0x10ffff = "11110100 10001111 10111111 10111111" + [ + "0xxxxxxx", + "110XXXXx 10xxxxxx", + "1110XXXX 10Xxxxxx 10xxxxxx", + "11110XXX 10XXxxxx 10xxxxxx 10xxxxxx", + "111110XX 10XXXxxx 10xxxxxx 10xxxxxx 10xxxxxx", + "1111110X 10XXXXxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx", + "11111110 10XXXXXx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx", + "11111111 10XXXXXX 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx", + ].each {|pat0| + [ + pat0.gsub(/x/, '1'), + pat0.gsub(/x/, '0') + ].each {|pat1| + [ + pat1.sub(/X([^X]*)\z/, '1\1').gsub(/X/, "0"), + pat1.gsub(/X/, "1"), + ].each {|pat2| + s = [pat2.gsub(/ /, "")].pack("B*").force_encoding("utf-8") + if pat2 <= bits_0x10ffff + assert_predicate(s, :valid_encoding?, "#{pat2}") + else + 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_not_predicate(s, :valid_encoding?, "#{pat3}") + end + } + } + end + + def test_validate_surrogate + # 1110XXXX 10Xxxxxx 10xxxxxx : 3 bytes UTF-8 + pats = [ + "11101101 10011111 10111111", # just before surrogate high + "11101101 1010xxxx 10xxxxxx", # surrogate high + "11101101 1011xxxx 10xxxxxx", # surrogate low + "11101110 10000000 10000000", # just after surrogate low + ] + pats.values_at(1,2).each {|pat0| + [ + pat0.gsub(/x/, '0'), + pat0.gsub(/x/, '1'), + ].each {|pat1| + s = [pat1.gsub(/ /, "")].pack("B*").force_encoding("utf-8") + assert_not_predicate(s, :valid_encoding?, "#{pat1}") + } + } + pats.values_at(0,3).each {|pat| + s = [pat.gsub(/ /, "")].pack("B*").force_encoding("utf-8") + assert_predicate(s, :valid_encoding?, "#{pat}") + } + end + + def test_regexp_too_short_multibyte_character + assert_raise(SyntaxError) { eval('/\xfe/e') } + assert_raise(SyntaxError) { eval('/\x8e/e') } + assert_raise(SyntaxError) { eval('/\x8f/e') } + assert_raise(SyntaxError) { eval('/\x8f\xa1/e') } + assert_raise(SyntaxError) { eval('/\xef/s') } + assert_raise(SyntaxError) { eval('/\xc2/u') } + assert_raise(SyntaxError) { eval('/\xe0\x80/u') } + assert_raise(SyntaxError) { eval('/\xf0\x80\x80/u') } + assert_raise(SyntaxError) { eval('/\xf8\x80\x80\x80/u') } + assert_raise(SyntaxError) { eval('/\xfc\x80\x80\x80\x80/u') } + + # raw 8bit + assert_raise(SyntaxError) { eval("/\xfe/e") } + assert_raise(SyntaxError) { eval("/\xc2/u") } + + # invalid suffix + assert_raise(SyntaxError) { eval('/\xc2\xff/u') } + assert_raise(SyntaxError) { eval('/\xc2 /u') } + assert_raise(SyntaxError) { eval('/\xc2\x20/u') } + end + + def test_regexp_generic + assert_regexp_generic_ascii(/a/) + assert_regexp_generic_ascii(Regexp.new(a("a"))) + assert_regexp_generic_ascii(Regexp.new(e("a"))) + assert_regexp_generic_ascii(Regexp.new(s("a"))) + assert_regexp_generic_ascii(Regexp.new(u("a"))) + + [/a/, Regexp.new(a("a"))].each {|r| + assert_equal(0, r =~ a("a")) + assert_equal(0, r =~ e("a")) + assert_equal(0, r =~ s("a")) + assert_equal(0, r =~ u("a")) + assert_equal(nil, r =~ a("\xc2\xa1")) + assert_equal(nil, r =~ e("\xc2\xa1")) + assert_equal(nil, r =~ s("\xc2\xa1")) + assert_equal(nil, r =~ u("\xc2\xa1")) + } + end + + def test_regexp_ascii_none + r = /a/n + + assert_warning(%r{historical binary regexp match /\.\.\./n against}) { + assert_regexp_generic_ascii(r) + } + + assert_equal(0, r =~ a("a")) + assert_equal(0, r =~ e("a")) + assert_equal(0, r =~ s("a")) + assert_equal(0, r =~ u("a")) + assert_equal(nil, r =~ a("\xc2\xa1")) + assert_warning(%r{historical binary regexp match /\.\.\./n against EUC-JP string}) { + assert_equal(nil, r =~ e("\xc2\xa1")) + } + assert_warning(%r{historical binary regexp match /\.\.\./n against Windows-31J string}) { + assert_equal(nil, r =~ s("\xc2\xa1")) + } + assert_warning(%r{historical binary regexp match /\.\.\./n against UTF-8 string}) { + assert_equal(nil, r =~ u("\xc2\xa1")) + } + + assert_nothing_raised { eval(e("/\\x80/n")) } + end + + def test_regexp_ascii + assert_regexp_fixed_ascii8bit(/\xc2\xa1/n) + assert_regexp_fixed_ascii8bit(eval(a(%{/\xc2\xa1/}))) + 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| + assert_equal(nil, r =~ a("a")) + assert_equal(nil, r =~ e("a")) + assert_equal(nil, r =~ s("a")) + assert_equal(nil, r =~ u("a")) + assert_equal(0, r =~ a("\xc2\xa1")) + assert_raise(Encoding::CompatibilityError) { r =~ e("\xc2\xa1") } + assert_raise(Encoding::CompatibilityError) { r =~ s("\xc2\xa1") } + assert_raise(Encoding::CompatibilityError) { r =~ u("\xc2\xa1") } + } + end + + def test_regexp_euc + assert_regexp_fixed_eucjp(/a/e) + assert_regexp_fixed_eucjp(/\xc2\xa1/e) + assert_regexp_fixed_eucjp(eval(e(%{/\xc2\xa1/}))) + assert_regexp_fixed_eucjp(eval(e(%q{/\xc2\xa1/}))) + + [/a/e].each {|r| + assert_equal(0, r =~ a("a")) + assert_equal(0, r =~ e("a")) + assert_equal(0, r =~ s("a")) + assert_equal(0, r =~ u("a")) + assert_raise(Encoding::CompatibilityError) { r =~ a("\xc2\xa1") } + assert_equal(nil, r =~ e("\xc2\xa1")) + assert_raise(Encoding::CompatibilityError) { r =~ s("\xc2\xa1") } + assert_raise(Encoding::CompatibilityError) { r =~ u("\xc2\xa1") } + } + + [/\xc2\xa1/e, eval(e(%{/\xc2\xa1/})), eval(e(%q{/\xc2\xa1/}))].each {|r| + assert_equal(nil, r =~ a("a")) + assert_equal(nil, r =~ e("a")) + assert_equal(nil, r =~ s("a")) + assert_equal(nil, r =~ u("a")) + assert_raise(Encoding::CompatibilityError) { r =~ a("\xc2\xa1") } + assert_equal(0, r =~ e("\xc2\xa1")) + assert_raise(Encoding::CompatibilityError) { r =~ s("\xc2\xa1") } + assert_raise(Encoding::CompatibilityError) { r =~ u("\xc2\xa1") } + } + end + + def test_regexp_sjis + assert_regexp_fixed_sjis(/a/s) + assert_regexp_fixed_sjis(/\xc2\xa1/s) + assert_regexp_fixed_sjis(eval(s(%{/\xc2\xa1/}))) + assert_regexp_fixed_sjis(eval(s(%q{/\xc2\xa1/}))) + end + + def test_regexp_windows_31j + begin + Regexp.new("\xa1".force_encoding("windows-31j")) =~ "\xa1\xa1".force_encoding("euc-jp") + rescue Encoding::CompatibilityError + err = $! + end + assert_match(/windows-31j/i, err.message) + end + + def test_regexp_embed + r = eval(e("/\xc2\xa1/")) + assert_raise(RegexpError) { eval(s("/\xc2\xa1\#{r}/s")) } + assert_raise(RegexpError) { eval(s("/\#{r}\xc2\xa1/s")) } + + r = /\xc2\xa1/e + assert_raise(RegexpError) { eval(s("/\xc2\xa1\#{r}/s")) } + assert_raise(RegexpError) { eval(s("/\#{r}\xc2\xa1/s")) } + + r = eval(e("/\xc2\xa1/")) + assert_raise(RegexpError) { /\xc2\xa1#{r}/s } + + r = /\xc2\xa1/e + assert_raise(RegexpError) { /\xc2\xa1#{r}/s } + + r1 = Regexp.new('foo'.force_encoding("ascii-8bit")) + r2 = eval('/bar#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::US_ASCII, r2.encoding) + + r1 = Regexp.new('foo'.force_encoding("us-ascii")) + r2 = eval('/bar#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::US_ASCII, r2.encoding) + + r1 = Regexp.new('foo'.force_encoding("ascii-8bit")) + r2 = eval('/bar#{r1}/'.force_encoding('us-ascii')) + assert_equal(Encoding::US_ASCII, r2.encoding) + + r1 = Regexp.new('foo'.force_encoding("us-ascii")) + r2 = eval('/bar#{r1}/'.force_encoding('us-ascii')) + assert_equal(Encoding::US_ASCII, r2.encoding) + + r1 = Regexp.new('\xa1'.force_encoding("ascii-8bit")) + r2 = eval('/bar#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + r1 = Regexp.new('\xa1'.force_encoding("ascii-8bit")) + r2 = eval('/bar#{r1}/'.force_encoding('us-ascii')) + assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + r1 = Regexp.new('foo'.force_encoding("ascii-8bit")) + r2 = eval('/\xa1#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + r1 = Regexp.new('foo'.force_encoding("us-ascii")) + r2 = eval('/\xa1#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + r1 = Regexp.new('\xa1'.force_encoding("ascii-8bit")) + r2 = eval('/\xa1#{r1}/'.force_encoding('ascii-8bit')) + assert_equal(Encoding::ASCII_8BIT, r2.encoding) + + [r1, r2] + end + + def test_regexp_named_class + assert_match(/[[:space:]]/u, "\u{00a0}") + assert_match(/[[:space:]]/, "\u{00a0}") + end + + def test_regexp_property + s = '\p{Hiragana}'.force_encoding("euc-jp") + assert_equal(Encoding::EUC_JP, s.encoding) + r = nil + assert_nothing_raised { + r = Regexp.new(s) + } + assert_predicate(r, :fixed_encoding?) + assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) + + r = eval('/\p{Hiragana}/'.force_encoding("euc-jp")) + assert_predicate(r, :fixed_encoding?) + assert_match(r, "\xa4\xa2".force_encoding("euc-jp")) + + r = /\p{Hiragana}/e + 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_predicate(r, :fixed_encoding?) + assert_equal(Encoding::UTF_8, r.encoding) + + r = eval('/\p{Hiragana}\u{3042}/'.force_encoding("euc-jp")) + 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_include(r2.source, r1.source) + end + + def test_begin_end_offset + str = e("\244\242\244\244\244\246\244\250\244\252a") + assert(/(a)/ =~ str) + assert_equal("a", $&) + assert_equal(5, $~.begin(0)) + assert_equal(6, $~.end(0)) + assert_equal([5,6], $~.offset(0)) + assert_equal(5, $~.begin(1)) + assert_equal(6, $~.end(1)) + assert_equal([5,6], $~.offset(1)) + end + + def test_begin_end_offset_sjis + str = s("\x81@@") + assert(/@/ =~ str) + assert_equal(s("\x81@"), $`) + assert_equal("@", $&) + assert_equal("", $') + assert_equal([1,2], $~.offset(0)) + end + + def test_quote + assert_regexp_generic_ascii(/#{Regexp.quote(a("a"))}#{Regexp.quote(e("e"))}/) + + assert_encoding("US-ASCII", Regexp.quote(a("")).encoding) + assert_encoding("US-ASCII", Regexp.quote(e("")).encoding) + assert_encoding("US-ASCII", Regexp.quote(s("")).encoding) + assert_encoding("US-ASCII", Regexp.quote(u("")).encoding) + assert_encoding("US-ASCII", Regexp.quote(a("a")).encoding) + assert_encoding("US-ASCII", Regexp.quote(e("a")).encoding) + assert_encoding("US-ASCII", Regexp.quote(s("a")).encoding) + assert_encoding("US-ASCII", Regexp.quote(u("a")).encoding) + + assert_encoding("ASCII-8BIT", Regexp.quote(a("\xc2\xa1")).encoding) + assert_encoding("EUC-JP", Regexp.quote(e("\xc2\xa1")).encoding) + assert_encoding("Windows-31J", Regexp.quote(s("\xc2\xa1")).encoding) + assert_encoding("UTF-8", Regexp.quote(u("\xc2\xa1")).encoding) + end + + def test_union_0 + r = Regexp.union + assert_regexp_generic_ascii(r) + 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 + assert_regexp_generic_ascii(Regexp.union(a(""))) + assert_regexp_generic_ascii(Regexp.union(e(""))) + assert_regexp_generic_ascii(Regexp.union(s(""))) + assert_regexp_generic_ascii(Regexp.union(u(""))) + assert_regexp_generic_ascii(Regexp.union(a("a"))) + assert_regexp_generic_ascii(Regexp.union(e("a"))) + assert_regexp_generic_ascii(Regexp.union(s("a"))) + assert_regexp_generic_ascii(Regexp.union(u("a"))) + assert_regexp_generic_ascii(Regexp.union(a("\t"))) + assert_regexp_generic_ascii(Regexp.union(e("\t"))) + assert_regexp_generic_ascii(Regexp.union(s("\t"))) + assert_regexp_generic_ascii(Regexp.union(u("\t"))) + end + + def test_union_1_nonascii_string + assert_regexp_fixed_ascii8bit(Regexp.union(a("\xc2\xa1"))) + assert_regexp_fixed_eucjp(Regexp.union(e("\xc2\xa1"))) + assert_regexp_fixed_sjis(Regexp.union(s("\xc2\xa1"))) + assert_regexp_fixed_utf8(Regexp.union(u("\xc2\xa1"))) + end + + def test_union_1_regexp + assert_regexp_generic_ascii(Regexp.union(//)) + assert_warning(%r{historical binary regexp match /.../n against}) { + assert_regexp_generic_ascii(Regexp.union(//n)) + } + assert_regexp_fixed_eucjp(Regexp.union(//e)) + assert_regexp_fixed_sjis(Regexp.union(//s)) + assert_regexp_fixed_utf8(Regexp.union(//u)) + end + + def test_union_2 + ary = [ + a(""), e(""), s(""), u(""), + a("\xc2\xa1"), e("\xc2\xa1"), s("\xc2\xa1"), u("\xc2\xa1") + ] + ary.each {|s1| + ary.each {|s2| + if s1.empty? + if s2.empty? + assert_regexp_generic_ascii(Regexp.union(s1, s2)) + else + r = Regexp.union(s1, s2) + assert_regexp_fixed_encoding(r) + assert_equal(s2.encoding, r.encoding) + end + else + if s2.empty? + r = Regexp.union(s1, s2) + assert_regexp_fixed_encoding(r) + assert_equal(s1.encoding, r.encoding) + else + if s1.encoding == s2.encoding + r = Regexp.union(s1, s2) + assert_regexp_fixed_encoding(r) + assert_equal(s1.encoding, r.encoding) + else + assert_raise(ArgumentError) { Regexp.union(s1, s2) } + end + end + end + } + } + end + + def test_dynamic_ascii_regexp + assert_warning(%r{historical binary regexp match /.../n against}) { + assert_regexp_generic_ascii(/#{ }/n) + } + assert_regexp_fixed_ascii8bit(/#{ }\xc2\xa1/n) + assert_regexp_fixed_ascii8bit(/\xc2\xa1#{ }/n) + assert_nothing_raised { s1, s2 = a('\xc2'), a('\xa1'); /#{s1}#{s2}/ } + end + + def test_dynamic_eucjp_regexp + assert_regexp_fixed_eucjp(/#{ }/e) + assert_regexp_fixed_eucjp(/#{ }\xc2\xa1/e) + assert_regexp_fixed_eucjp(/\xc2\xa1#{ }/e) + assert_raise(SyntaxError) { eval('/\xc2#{ }/e') } + assert_raise(SyntaxError) { eval('/#{ }\xc2/e') } + assert_raise(SyntaxError) { eval('/\xc2#{ }\xa1/e') } + assert_raise(ArgumentError) { s1, s2 = e('\xc2'), e('\xa1'); /#{s1}#{s2}/ } + end + + def test_dynamic_sjis_regexp + assert_regexp_fixed_sjis(/#{ }/s) + assert_regexp_fixed_sjis(/#{ }\xc2\xa1/s) + assert_regexp_fixed_sjis(/\xc2\xa1#{ }/s) + assert_raise(SyntaxError) { eval('/\x81#{ }/s') } + assert_raise(SyntaxError) { eval('/#{ }\x81/s') } + assert_raise(SyntaxError) { eval('/\x81#{ }\xa1/s') } + assert_raise(ArgumentError) { s1, s2 = s('\x81'), s('\xa1'); /#{s1}#{s2}/ } + end + + def test_dynamic_utf8_regexp + assert_regexp_fixed_utf8(/#{ }/u) + assert_regexp_fixed_utf8(/#{ }\xc2\xa1/u) + assert_regexp_fixed_utf8(/\xc2\xa1#{ }/u) + assert_raise(SyntaxError) { eval('/\xc2#{ }/u') } + assert_raise(SyntaxError) { eval('/#{ }\xc2/u') } + assert_raise(SyntaxError) { eval('/\xc2#{ }\xa1/u') } + assert_raise(ArgumentError) { s1, s2 = u('\xc2'), u('\xa1'); /#{s1}#{s2}/ } + end + + def test_regexp_unicode + assert_nothing_raised { eval '/\u{0}/u' } + assert_nothing_raised { eval '/\u{D7FF}/u' } + assert_raise(SyntaxError) { eval '/\u{D800}/u' } + assert_raise(SyntaxError) { eval '/\u{DFFF}/u' } + assert_nothing_raised { eval '/\u{E000}/u' } + assert_nothing_raised { eval '/\u{10FFFF}/u' } + assert_raise(SyntaxError) { eval '/\u{110000}/u' } + end + + def test_regexp_mixed_unicode + assert_raise(SyntaxError) { eval(a(%{/\xc2\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(e(%{/\xc2\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(s(%{/\xc2\xa1\\u{6666}/})) } + assert_nothing_raised { eval(u(%{/\xc2\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(a(%{/\\u{6666}\xc2\xa1/})) } + assert_raise(SyntaxError) { eval(e(%{/\\u{6666}\xc2\xa1/})) } + assert_raise(SyntaxError) { eval(s(%{/\\u{6666}\xc2\xa1/})) } + assert_nothing_raised { eval(u(%{/\\u{6666}\xc2\xa1/})) } + + assert_raise(SyntaxError) { eval(a(%{/\\xc2\\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(e(%{/\\xc2\\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(s(%{/\\xc2\\xa1\\u{6666}/})) } + assert_nothing_raised { eval(u(%{/\\xc2\\xa1\\u{6666}/})) } + assert_raise(SyntaxError) { eval(a(%{/\\u{6666}\\xc2\\xa1/})) } + assert_raise(SyntaxError) { eval(e(%{/\\u{6666}\\xc2\\xa1/})) } + assert_raise(SyntaxError) { eval(s(%{/\\u{6666}\\xc2\\xa1/})) } + assert_nothing_raised { eval(u(%{/\\u{6666}\\xc2\\xa1/})) } + + assert_raise(SyntaxError) { eval(a(%{/\xc2\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(e(%{/\xc2\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(s(%{/\xc2\xa1#{ }\\u{6666}/})) } + assert_nothing_raised { eval(u(%{/\xc2\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(a(%{/\\u{6666}#{ }\xc2\xa1/})) } + assert_raise(SyntaxError) { eval(e(%{/\\u{6666}#{ }\xc2\xa1/})) } + assert_raise(SyntaxError) { eval(s(%{/\\u{6666}#{ }\xc2\xa1/})) } + assert_nothing_raised { eval(u(%{/\\u{6666}#{ }\xc2\xa1/})) } + + assert_raise(SyntaxError) { eval(a(%{/\\xc2\\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(e(%{/\\xc2\\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(s(%{/\\xc2\\xa1#{ }\\u{6666}/})) } + assert_nothing_raised { eval(u(%{/\\xc2\\xa1#{ }\\u{6666}/})) } + assert_raise(SyntaxError) { eval(a(%{/\\u{6666}#{ }\\xc2\\xa1/})) } + assert_raise(SyntaxError) { eval(e(%{/\\u{6666}#{ }\\xc2\\xa1/})) } + assert_raise(SyntaxError) { eval(s(%{/\\u{6666}#{ }\\xc2\\xa1/})) } + assert_nothing_raised { eval(u(%{/\\u{6666}#{ }\\xc2\\xa1/})) } + end + + def test_str_allocate + s = String.allocate + assert_equal(Encoding::ASCII_8BIT, s.encoding) + end + + def test_str_String + s = String(10) + assert_equal(Encoding::US_ASCII, s.encoding) + end + + def test_sprintf_c + assert_strenc("\x80", 'ASCII-8BIT', a("%c") % 128) + #assert_raise(ArgumentError) { a("%c") % 0xc2a1 } + assert_strenc("\xc2\xa1", 'EUC-JP', e("%c") % 0xc2a1) + assert_raise(ArgumentError) { e("%c") % 0xc2 } + assert_strenc("\xc2", 'Windows-31J', s("%c") % 0xc2) + #assert_raise(ArgumentError) { s("%c") % 0xc2a1 } + assert_strenc("\u{c2a1}", 'UTF-8', u("%c") % 0xc2a1) + assert_strenc("\u{c2}", 'UTF-8', u("%c") % 0xc2) + assert_raise(Encoding::CompatibilityError) { + "%s%s" % [s("\xc2\xa1"), e("\xc2\xa1")] + } + + assert_equal("\u3042".encode('Windows-31J'), "%c" % "\u3042\u3044".encode('Windows-31J')) + end + + def test_sprintf_p + Encoding.list.each do |e| + unless e.ascii_compatible? + format = e.dummy? ? "%p".force_encoding(e) : "%p".encode(e) + assert_raise(Encoding::CompatibilityError) do + sprintf(format, nil) + end + assert_raise(Encoding::CompatibilityError) do + format % nil + end + next + end + format = "%p".force_encoding(e) + ['', 'a', "\xC2\xA1", "\x00"].each do |s| + s.force_encoding(e) + 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 + + def test_sprintf_s + assert_strenc('', 'ASCII-8BIT', a("%s") % a("")) + assert_strenc('', 'EUC-JP', e("%s") % e("")) + assert_strenc('', 'Windows-31J', s("%s") % s("")) + assert_strenc('', 'UTF-8', u("%s") % u("")) + + assert_strenc('a', 'ASCII-8BIT', a("%s") % a("a")) + assert_strenc('a', 'EUC-JP', e("%s") % e("a")) + assert_strenc('a', 'Windows-31J', s("%s") % s("a")) + assert_strenc('a', 'UTF-8', u("%s") % u("a")) + + assert_strenc("\xC2\xA1", 'ASCII-8BIT', a("%s") % a("\xc2\xa1")) + assert_strenc("\xC2\xA1", 'EUC-JP', e("%s") % e("\xc2\xa1")) + #assert_strenc("\xC2\xA1", 'Windows-31J', s("%s") % s("\xc2\xa1")) + assert_strenc("\xC2\xA1", 'UTF-8', u("%s") % u("\xc2\xa1")) + + assert_strenc(" \xC2\xA1", 'ASCII-8BIT', "%10s" % a("\xc2\xa1")) + assert_strenc(" \xA1\xA1", 'EUC-JP', "%10s" % e("\xa1\xa1")) + #assert_strenc(" \xC2\xA1", 'Windows-31J', "%10s" % s("\xc2\xa1")) + assert_strenc(" \xC2\xA1", 'UTF-8', "%10s" % u("\xc2\xa1")) + + assert_strenc("\x00", 'ASCII-8BIT', a("%s") % a("\x00")) + assert_strenc("\x00", 'EUC-JP', e("%s") % e("\x00")) + assert_strenc("\x00", 'Windows-31J', s("%s") % s("\x00")) + assert_strenc("\x00", 'UTF-8', u("%s") % u("\x00")) + assert_equal("EUC-JP", (e("\xc2\xa1 %s") % "foo").encoding.name) + end + + def test_str_lt + assert_operator(a("a"), :<, a("\xa1")) + assert_operator(a("a"), :<, s("\xa1")) + assert_operator(s("a"), :<, a("\xa1")) + end + + def test_str_multiply + str = "\u3042" + assert_equal(true, (str * 0).ascii_only?, "[ruby-dev:33895]") + assert_equal(false, (str * 1).ascii_only?) + assert_equal(false, (str * 2).ascii_only?) + end + + def test_str_aref + assert_equal(a("\xc2"), a("\xc2\xa1")[0]) + assert_equal(a("\xa1"), a("\xc2\xa1")[1]) + assert_equal(nil, a("\xc2\xa1")[2]) + assert_equal(e("\xc2\xa1"), e("\xc2\xa1")[0]) + assert_equal(nil, e("\xc2\xa1")[1]) + assert_equal(s("\xc2"), s("\xc2\xa1")[0]) + assert_equal(s("\xa1"), s("\xc2\xa1")[1]) + assert_equal(nil, s("\xc2\xa1")[2]) + assert_equal(u("\xc2\xa1"), u("\xc2\xa1")[0]) + assert_equal(nil, u("\xc2\xa1")[1]) + + str = "\u3042" + assert_equal(true, str[0, 0].ascii_only?, "[ruby-dev:33895]") + assert_equal(false, str[0, 1].ascii_only?) + assert_equal(false, str[0..-1].ascii_only?) + end + + def test_utf8str_aref + s = "abcdefghijklmnopqrstuvwxyz\u{3042 3044 3046 3048 304A}" + assert_equal("a", s[0]) + assert_equal("h", s[7]) + assert_equal("i", s[8]) + assert_equal("j", s[9]) + assert_equal("\u{3044}", s[27]) + assert_equal("\u{3046}", s[28]) + assert_equal("\u{3048}", s[29]) + s = "abcdefghijklmnopqrstuvw\u{3042 3044 3046 3048 304A}" + assert_equal("\u{3044}", s[24]) + end + + def test_str_aref_len + assert_equal(a("\xa1"), a("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 1]) + assert_equal(a("\xa1\xc2"), a("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 2]) + + assert_equal(e("\xc2\xa2"), e("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 1]) + assert_equal(e("\xc2\xa2\xc2\xa3"), e("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 2]) + + assert_equal(s("\xa1"), s("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 1]) + assert_equal(s("\xa1\xc2"), s("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 2]) + + assert_equal(u("\xc2\xa2"), u("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 1]) + assert_equal(u("\xc2\xa2\xc2\xa3"), u("\xc2\xa1\xc2\xa2\xc2\xa3")[1, 2]) + end + + def test_str_aref_substr + assert_equal(a("\xa1\xc2"), a("\xc2\xa1\xc2\xa2\xc2\xa3")[a("\xa1\xc2")]) + assert_raise(Encoding::CompatibilityError) { a("\xc2\xa1\xc2\xa2\xc2\xa3")[e("\xa1\xc2")] } + + assert_equal(nil, e("\xc2\xa1\xc2\xa2\xc2\xa3")[e("\xa1\xc2")]) + assert_raise(Encoding::CompatibilityError) { e("\xc2\xa1\xc2\xa2\xc2\xa3")[s("\xa1\xc2")] } + + assert_equal(s("\xa1\xc2"), s("\xc2\xa1\xc2\xa2\xc2\xa3")[s("\xa1\xc2")]) + assert_raise(Encoding::CompatibilityError) { s("\xc2\xa1\xc2\xa2\xc2\xa3")[u("\xa1\xc2")] } + + assert_equal(nil, u("\xc2\xa1\xc2\xa2\xc2\xa3")[u("\xa1\xc2")]) + assert_raise(Encoding::CompatibilityError) { u("\xc2\xa1\xc2\xa2\xc2\xa3")[a("\xa1\xc2")] } + assert_nil(e("\xa1\xa2\xa3\xa4")[e("\xa2\xa3")]) + + bug2379 = '[ruby-core:26787]' + assert_equal("\u{439}", "\u{439}"[0, 30], bug2379) + assert_equal("\u{439}", "a\u{439}"[1, 30], bug2379) + 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 + assert_encoding("EUC-JP", "a".center(5, e("\xa1\xa2")).encoding) + assert_encoding("EUC-JP", e("\xa3\xb0").center(10).encoding) + end + + def test_squeeze + s = e("\xa3\xb0\xa3\xb1\xa3\xb1\xa3\xb3\xa3\xb4") + assert_equal(e("\xa3\xb0\xa3\xb1\xa3\xb3\xa3\xb4"), s.squeeze) + end + + def test_tr + s = s("\x81\x41") + assert_equal(s.tr("A", "B"), s) + assert_equal(s.tr_s("A", "B"), s) + + assert_nothing_raised { + "a".force_encoding("ASCII-8BIT").tr(a("a"), a("a")) + } + + assert_equal(e("\xA1\xA1"), a("a").tr(a("a"), e("\xA1\xA1"))) + + 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 + assert_equal("\xA1\xA1".force_encoding("EUC-JP"), + "a".force_encoding("ASCII-8BIT").tr("a".force_encoding("ASCII-8BIT"), "\xA1\xA1".force_encoding("EUC-JP"))) + end + + def test_count + assert_equal(0, e("\xa1\xa2").count("z")) + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + 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") + assert_raise(Encoding::CompatibilityError){s.delete(a("\xa3\xb2"))} + + a = "\u3042\u3044\u3046\u3042\u3044\u3046" + a.delete!("\u3042\u3044", "^\u3044") + assert_equal("\u3044\u3046\u3044\u3046", a) + end + + def test_include? + 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 + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + assert_nil(s.index(e("\xb3\xa3"))) + assert_nil(e("\xa1\xa2\xa3\xa4").index(e("\xa3"))) + assert_nil(e("\xa1\xa2\xa3\xa4").rindex(e("\xa3"))) + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + + a_with_e = /EUC-JP and BINARY \(ASCII-8BIT\)/ + assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do + s.index(a("\xb1\xa3")) + end + assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do + s.rindex(a("\xb1\xa3")) + end + + a_with_e = /BINARY \(ASCII-8BIT\) regexp with EUC-JP string/ + assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do + s.index(Regexp.new(a("\xb1\xa3"))) + end + assert_raise_with_message(Encoding::CompatibilityError, a_with_e) do + s.rindex(Regexp.new(a("\xb1\xa3"))) + end + + 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 + s1 = e("\xa1\xa1") + s2 = s1.dup + (94*94+94).times { s2.next! } + assert_not_equal(s1, s2) + end + + def test_sub + s = "abc".sub(/b/, "\xa1\xa1".force_encoding("euc-jp")) + assert_encoding("EUC-JP", s.encoding) + assert_equal(Encoding::EUC_JP, "\xa4\xa2".force_encoding("euc-jp").sub(/./, '\&').encoding) + assert_equal(Encoding::EUC_JP, "\xa4\xa2".force_encoding("euc-jp").gsub(/./, '\&').encoding) + end + + def test_sub2 + s = "\x80".force_encoding("ASCII-8BIT") + r = Regexp.new("\x80".force_encoding("ASCII-8BIT")) + s2 = s.sub(r, "") + 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_predicate(s, :valid_encoding?) + end + + def test_insert + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + assert_equal(e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4a"), s.insert(-1, "a")) + end + + def test_scan + assert_equal(["a"], e("\xa1\xa2a\xa3\xa4").scan(/a/)) + end + + def test_dup_scan + s1 = e("\xa4\xa2")*100 + s2 = s1.b + s2.scan(/\A./n) {|f| + assert_equal(Encoding::ASCII_8BIT, f.encoding) + } + end + + def test_dup_aref + s1 = e("\xa4\xa2")*100 + s2 = s1.b + assert_equal(Encoding::ASCII_8BIT, s2[10..-1].encoding) + end + + def test_upto + s1 = e("\xa1\xa2") + s2 = s("\xa1\xa2") + assert_raise(Encoding::CompatibilityError){s1.upto(s2) {|x| break }} + end + + def test_casecmp + s1 = s("\x81\x41") + s2 = s("\x81\x61") + assert_not_equal(0, s1.casecmp(s2)) + end + + def test_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 + s = u("abcdefghij\xf0") + s.reverse! + assert_equal(u("\xf0jihgfedcba"), s) + end + + def test_plus + assert_raise(Encoding::CompatibilityError){u("\xe3\x81\x82") + a("\xa1")} + end + + def test_chomp + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + assert_raise(Encoding::CompatibilityError){s.chomp(s("\xa3\xb4"))} + end + + def test_gsub + s = 'abc' + s.ascii_only? + s.gsub!(/b/, "\x80") + assert_equal(false, s.ascii_only?, "[ruby-core:14566] reported by Sam Ruby") + + s = "abc".force_encoding(Encoding::ASCII_8BIT) + assert_equal(Encoding::ASCII_8BIT, s.encoding) + + assert_raise(Encoding::CompatibilityError) { + "abc".gsub(/[ac]/) { + $& == "a" ? "\xc2\xa1".force_encoding("euc-jp") : + "\xc2\xa1".force_encoding("utf-8") + } + } + 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::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 + s = e("\xa3\xb0\xa3\xb1\xa3\xb2\xa3\xb3\xa3\xb4") + assert_raise(Encoding::CompatibilityError){s.each_line(a("\xa3\xb1")) {|l| }} + s = e("\xa4\xa2\nfoo") + + actual = [] + s.each_line {|line| actual << line } + expected = [e("\xa4\xa2\n"), e("foo")] + assert_equal(expected, actual) + end + + def test_each_char + a = [e("\xa4\xa2"), "b", e("\xa4\xa4"), "c"] + s = "\xa4\xa2b\xa4\xa4c".force_encoding("euc-jp") + 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) + end + + def test_split + 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 + eval(e("def \xc2\xa1() @nonascii_method_name = :e end")) + eval(u("def \xc2\xa1() @nonascii_method_name = :u end")) + eval(e("\xc2\xa1()")) + assert_equal(:e, @nonascii_method_name) + eval(u("\xc2\xa1()")) + assert_equal(:u, @nonascii_method_name) + me = method(e("\xc2\xa1")) + mu = method(u("\xc2\xa1")) + assert_not_equal(me.name, mu.name) + assert_not_equal(me.inspect, mu.inspect) + assert_equal(e("\xc2\xa1"), me.name.to_s) + assert_equal(u("\xc2\xa1"), mu.name.to_s) + end + + def test_symbol + s1 = "\xc2\xa1".force_encoding("euc-jp").intern + s2 = "\xc2\xa1".force_encoding("utf-8").intern + assert_not_equal(s1, s2) + end + + def test_symbol_op + ops = %w" + .. ... + - * / % ** +@ -@ | ^ & ! <=> > >= < <= == + === != =~ !~ ~ ! [] []= << >> :: ` + " + ops.each do |op| + assert_equal(Encoding::US_ASCII, op.intern.encoding, "[ruby-dev:33449]") + end + end + + def test_chr + 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 + s1 = "\xa1\xa1".force_encoding("euc-jp") + s2 = Marshal.load(Marshal.dump(s1)) + assert_equal(s1, s2) + end + + def test_env + if RUBY_PLATFORM =~ /bccwin|mswin|mingw/ + env_encoding = Encoding::UTF_8 + else + env_encoding = Encoding.find("locale") + end + ENV.each {|k, v| + assert_equal(env_encoding, k.encoding, proc {"key(#{k.encoding})=#{k.dump}"}) + assert_equal(env_encoding, v.encoding, proc {"key(#{k.encoding})=#{k.dump}\n" "value(#{v.encoding})=#{v.dump}"}) + } + end + + def test_empty_string + assert_equal(Encoding::US_ASCII, "".encoding) + end + + def test_nil_to_s + assert_equal(Encoding::US_ASCII, nil.to_s.encoding) + end + + def test_nil_inspect + assert_equal(Encoding::US_ASCII, nil.inspect.encoding) + end + + def test_true_to_s + assert_equal(Encoding::US_ASCII, true.to_s.encoding) + end + + def test_false_to_s + assert_equal(Encoding::US_ASCII, false.to_s.encoding) + end + + def test_fixnum_to_s + assert_equal(Encoding::US_ASCII, 1.to_s.encoding) + end + + def test_float_to_s + assert_equal(Encoding::US_ASCII, 1.0.to_s.encoding) + end + + def test_bignum_to_s + assert_equal(Encoding::US_ASCII, (1 << 129).to_s.encoding) + end + + def test_array_to_s + assert_equal(Encoding::US_ASCII, [].to_s.encoding) + assert_equal(Encoding::US_ASCII, [nil].to_s.encoding) + assert_equal(Encoding::US_ASCII, [1].to_s.encoding) + assert_equal("".inspect.encoding, [""].to_s.encoding) + assert_equal("a".inspect.encoding, ["a"].to_s.encoding) + assert_equal(Encoding::US_ASCII, [nil,1,"","a","\x20",[]].to_s.encoding) + end + + def test_hash_to_s + assert_equal(Encoding::US_ASCII, {}.to_s.encoding) + assert_equal(Encoding::US_ASCII, {1=>nil,"foo"=>""}.to_s.encoding) + end + + def test_encoding_find + assert_raise(TypeError) {Encoding.find(nil)} + assert_raise(TypeError) {Encoding.find(0)} + assert_raise(TypeError) {Encoding.find([])} + assert_raise(TypeError) {Encoding.find({})} + end + + def test_encoding_to_s + assert_equal(Encoding::US_ASCII, Encoding::US_ASCII.to_s.encoding) + assert_equal(Encoding::US_ASCII, Encoding::US_ASCII.inspect.encoding) + end + + def test_regexp_source + s = "\xa4\xa2".force_encoding("euc-jp") + r = Regexp.new(s) + t = r.source + assert_equal(s, t, "[ruby-dev:33377] Regexp.new(#{encdump s}).source") + end + + def test_magic_comment + assert_equal(Encoding::US_ASCII, eval("__ENCODING__".force_encoding("US-ASCII"))) + assert_equal(Encoding::ASCII_8BIT, eval("__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::US_ASCII, eval("# -*- encoding: US-ASCII -*-\n__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::ASCII_8BIT, eval("# -*- encoding: ASCII-8BIT -*-\n__ENCODING__".force_encoding("US-ASCII"))) + end + + def test_magic_comment_vim + assert_equal(Encoding::US_ASCII, eval("# vim: filetype=ruby, fileencoding: US-ASCII, ts=3, sw=3\n__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::ASCII_8BIT, eval("# vim: filetype=ruby, fileencoding: ASCII-8BIT, ts=3, sw=3\n__ENCODING__".force_encoding("US-ASCII"))) + end + + def test_magic_comment_at_various_positions + # after shebang + assert_equal(Encoding::US_ASCII, eval("#!/usr/bin/ruby\n# -*- encoding: US-ASCII -*-\n__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::ASCII_8BIT, eval("#!/usr/bin/ruby\n# -*- encoding: ASCII-8BIT -*-\n__ENCODING__".force_encoding("US-ASCII"))) + # wrong position + assert_equal(Encoding::ASCII_8BIT, eval("\n# -*- encoding: US-ASCII -*-\n__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::US_ASCII, eval("\n# -*- encoding: ASCII-8BIT -*-\n__ENCODING__".force_encoding("US-ASCII"))) + + # leading expressions + assert_equal(Encoding::ASCII_8BIT, eval("v=1 # -*- encoding: US-ASCII -*-\n__ENCODING__".force_encoding("ASCII-8BIT"))) + assert_equal(Encoding::US_ASCII, eval("v=1 # -*- encoding: ASCII-8BIT -*-\n__ENCODING__".force_encoding("US-ASCII"))) + end + + def test_regexp_usascii + tests = [ + [__LINE__, '//', Encoding::US_ASCII], + [__LINE__, '/#{ }/', Encoding::US_ASCII], + [__LINE__, '/#{"a"}/', Encoding::US_ASCII], + [__LINE__, '/#{%q"\x80"}/', Encoding::US_ASCII], + [__LINE__, '/#{"\x80"}/', Encoding::ASCII_8BIT], + + [__LINE__, '/a/', Encoding::US_ASCII], + [__LINE__, '/a#{ }/', Encoding::US_ASCII], + [__LINE__, '/a#{"a"}/', Encoding::US_ASCII], + [__LINE__, '/a#{%q"\x80"}/', Encoding::ASCII_8BIT], + [__LINE__, '/a#{"\x80"}/', Encoding::ASCII_8BIT], + + [__LINE__, '/\x80/', Encoding::ASCII_8BIT], + [__LINE__, '/\x80#{ }/', Encoding::ASCII_8BIT], + [__LINE__, '/\x80#{"a"}/', Encoding::ASCII_8BIT], + [__LINE__, '/\x80#{%q"\x80"}/', Encoding::ASCII_8BIT], + [__LINE__, '/\x80#{"\x80"}/', Encoding::ASCII_8BIT], + + [__LINE__, '/\u1234/', Encoding::UTF_8], + [__LINE__, '/\u1234#{ }/', Encoding::UTF_8], + [__LINE__, '/\u1234#{"a"}/', Encoding::UTF_8], + + [__LINE__, '/\u1234#{%q"\x80"}/', nil, SyntaxError], + [__LINE__, '/\u1234#{"\x80"}/', nil, SyntaxError], + [__LINE__, '/\u1234\x80/', nil, SyntaxError], + [__LINE__, '/\u1234#{ }\x80/', nil, RegexpError], + ] + all_assertions_foreach(nil, *tests) do |line, r, enc, ex| + code = "# -*- encoding: US-ASCII -*-\n#{r}.encoding" + if ex + assert_raise(ex) {eval(code, nil, __FILE__, line-1)} + else + assert_equal(enc, eval(code, nil, __FILE__, line-1)) + end + end + end + + def test_gbk + assert_equal("", "\x81\x40".force_encoding("GBK").chop) + end + + def test_euc_tw + assert_equal("a", "a\x8e\xa2\xa1\xa1".force_encoding("euc-tw").chop) + end + + def test_valid_encoding + s = "\xa1".force_encoding("euc-jp") + assert_equal(false, s.valid_encoding?) + assert_equal(true, (s+s).valid_encoding?, "[ruby-dev:33826]") + assert_equal(true, (s*2).valid_encoding?, "[ruby-dev:33826]") + assert_equal(true, ("%s%s" % [s, s]).valid_encoding?) + assert_equal(true, (s.dup << s).valid_encoding?) + assert_equal(true, "".center(2, s).valid_encoding?) + + 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 + assert_equal(0x82, u("\xE3\x81\x82\xE3\x81\x84").getbyte(2)) + assert_equal(0x82, u("\xE3\x81\x82\xE3\x81\x84").getbyte(-4)) + assert_nil(u("\xE3\x81\x82\xE3\x81\x84").getbyte(100)) + end + + def test_setbyte + s = u("\xE3\x81\x82\xE3\x81\x84") + s.setbyte(2, 0x84) + assert_equal(u("\xE3\x81\x84\xE3\x81\x84"), s) + + s = u("\xE3\x81\x82\xE3\x81\x84") + assert_raise(IndexError) { s.setbyte(100, 0) } + + 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 + assert_nil Encoding.compatible?("",0) + assert_equal(Encoding::UTF_8, Encoding.compatible?(u(""), ua("abc"))) + assert_equal(Encoding::UTF_8, Encoding.compatible?(Encoding::UTF_8, Encoding::UTF_8)) + assert_equal(Encoding::UTF_8, Encoding.compatible?(Encoding::UTF_8, Encoding::US_ASCII)) + assert_equal(Encoding::ASCII_8BIT, + Encoding.compatible?(Encoding::ASCII_8BIT, Encoding::US_ASCII)) + assert_nil Encoding.compatible?(Encoding::UTF_8, Encoding::ASCII_8BIT) + end + + def test_force_encoding + 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 = "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) + str = "\u3042\u3044" + assert_equal(str, str.scrub) + assert_not_same(str, str.scrub) + 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)} + end + + def test_scrub_modification_inside_block + str = ("abc\u3042".b << "\xE3\x80".b).force_encoding('UTF-8') + assert_raise(RuntimeError) {str.scrub{|_| str << "1234567890"; "?" }} + + str = "\x00\xD8\x42\x30".force_encoding(Encoding::UTF_16LE) + assert_raise(RuntimeError) do + str.scrub do |_| + str << "1\x002\x00".force_encoding('UTF-16LE') + "?\x00".force_encoding('UTF-16LE') + end + end + end + + def test_scrub_replace_default + assert_equal("\uFFFD\uFFFD\uFFFD", u("\x80\x80\x80").scrub) + assert_equal("\uFFFDA", u("\xF4\x80\x80A").scrub) + + # 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_equal("\u3042\u3044", u("\xE3\x81\x82\xE3\x81\x84").scrub("\u3013")) + assert_equal("\u3042\u3013", u("\xE3\x81\x82\xE3\x81").scrub("\u3013")) + 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_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_encoding_names_of_default_internal + # [Bug #20595] [Bug #20598] + [ + "default_internal.names", + "name_list", + "aliases.keys" + ].each do |method| + assert_separately(%w(-W0), <<~RUBY) + exp_name = "int" + "ernal" + Encoding.default_internal = Encoding::ASCII_8BIT + name = Encoding.#{method}.find { |x| x == exp_name } + Encoding.default_internal = nil + assert_equal exp_name, name, "Encoding.#{method} [Bug #20595] [Bug #20598]" + RUBY + end + 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 new file mode 100644 index 0000000000..e48a1948be --- /dev/null +++ b/test/ruby/test_m17n_comb.rb @@ -0,0 +1,1674 @@ +# frozen_string_literal: false +require 'test/unit' +require 'etc' +require_relative 'allpairs' + +class TestM17NComb < Test::Unit::TestCase + def assert_encoding(encname, actual, message=nil) + assert_equal(Encoding.find(encname), actual, message) + end + + module AESU + 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 + + def assert_strenc(bytes, enc, actual, message=nil) + assert_instance_of(String, actual, message) + enc = Encoding.find(enc) if String === enc + assert_equal(enc, actual.encoding, message) + assert_equal(b(bytes), b(actual), message) + end + + STRINGS = [ + b(""), e(""), s(""), u(""), + b("a"), e("a"), s("a"), u("a"), + b("."), e("."), s("."), u("."), + + # single character + b("\x80"), b("\xff"), + e("\xa1\xa1"), e("\xfe\xfe"), + e("\x8e\xa1"), e("\x8e\xfe"), + e("\x8f\xa1\xa1"), e("\x8f\xfe\xfe"), + s("\x81\x40"), s("\xfc\xfc"), + s("\xa1"), s("\xdf"), + u("\xc2\x80"), u("\xf4\x8f\xbf\xbf"), + + # same byte sequence + 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" + + # invalid + e("\xa1"), e("\x80"), + s("\x81"), s("\x80"), + u("\xc2"), u("\x80"), + + # for transitivity test + u("\xe0\xa0\xa1"), e("\xe0\xa0\xa1"), s("\xe0\xa0\xa1"), # [ruby-dev:32693] + e("\xa1\xa1"), b("\xa1\xa1"), s("\xa1\xa1"), # [ruby-dev:36484] + ] + + WSTRINGS = [ + "aa".force_encoding("utf-16be"), + "aaaa".force_encoding("utf-32be"), + "aaa".force_encoding("utf-32be"), + ] + + def combination(*args, &b) + AllPairs.each(*args, &b) + #AllPairs.exhaustive_each(*args, &b) + end + + def encdump(str) + d = str.dump + if /\.force_encoding\("[A-Za-z0-9.:_+-]*"\)\z/ =~ d + d + else + "#{d}.force_encoding(#{str.encoding.name.dump})" + end + end + + def encdumpargs(args) + r = '(' + args.each_with_index {|a, i| + r << ',' if 0 < i + if String === a + r << encdump(a) + else + r << a.inspect + end + } + r << ')' + r + end + + def encdumpcall(recv, meth, *args, &block) + desc = '' + if String === recv + desc << encdump(recv) + else + desc << recv.inspect + end + desc << '.' << meth.to_s + if !args.empty? + desc << '(' + args.each_with_index {|a, i| + desc << ',' if 0 < i + if String === a + desc << encdump(a) + else + desc << a.inspect + end + } + desc << ')' + end + 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? + assert_equal(s1.encoding, t.encoding) + elsif !s2.ascii_only? + assert_equal(s2.encoding, t.encoding) + else + assert_include([s1.encoding, s2.encoding], t.encoding) + end + end + + def assert_same_result(expected_proc, actual_proc) + e = nil + begin + t = expected_proc.call + rescue + e = $! + end + if e + assert_raise(e.class) { actual_proc.call } + else + assert_equal(t, actual_proc.call) + end + end + + def each_slice_call + combination(STRINGS, -2..2) {|s, nth| + yield s, nth + } + combination(STRINGS, -2..2, 0..2) {|s, nth, len| + yield s, nth, len + } + combination(STRINGS, STRINGS) {|s, substr| + yield s, substr + } + combination(STRINGS, -2..2, 0..2) {|s, first, last| + yield s, first..last + yield s, first...last + } + combination(STRINGS, STRINGS) {|s1, s2| + if !s2.valid_encoding? + next + end + yield s1, Regexp.new(Regexp.escape(s2)) + } + combination(STRINGS, STRINGS, 0..2) {|s1, s2, nth| + if !s2.valid_encoding? + next + end + yield s1, Regexp.new(Regexp.escape(s2)), nth + } + end + + ASCII_INCOMPATIBLE_ENCODINGS = %w[ + UTF-16BE + UTF-16LE + UTF-32BE + UTF-32LE + ] + def str_enc_compatible?(*strs) + encs = [] + ascii_incompatible_encodings = {} + has_ascii_compatible = false + strs.each {|s| + encs << s.encoding if !s.ascii_only? + if /\A#{Regexp.union ASCII_INCOMPATIBLE_ENCODINGS}\z/o =~ s.encoding.name + ascii_incompatible_encodings[s.encoding] = true + else + has_ascii_compatible = true + end + } + if ascii_incompatible_encodings.empty? + encs.uniq! + encs.length <= 1 + else + !has_ascii_compatible && ascii_incompatible_encodings.size == 1 + end + end + + # tests start + + def test_str_new + STRINGS.each {|s| + t = String.new(s) + assert_strenc(b(s), s.encoding, t) + } + end + + def test_str_plus + combination(STRINGS, STRINGS) {|s1, s2| + if s1.encoding != s2.encoding && !s1.ascii_only? && !s2.ascii_only? + assert_raise(Encoding::CompatibilityError) { s1 + s2 } + else + t = enccall(s1, :+, s2) + 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 + } + end + + def test_str_times + STRINGS.each {|s| + [0,1,2].each {|n| + t = s * n + 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(b(s), s.encoding, "%s".force_encoding(s.encoding) % s) + if !s.empty? # xxx + t = enccall(b("%s"), :%, s) + assert_strenc(b(s), (b('')+s).encoding, t) + end + } + end + + def test_str_eq_reflexive + STRINGS.each {|s| + assert_equal(s, s, "#{encdump s} == #{encdump s}") + } + end + + def test_str_eq_symmetric + combination(STRINGS, STRINGS) {|s1, s2| + if s1 == s2 + assert_equal(s2, s1, "#{encdump s2} == #{encdump s1}") + else + assert_not_equal(s2, s1, "!(#{encdump s2} == #{encdump s1})") + end + } + end + + def test_str_eq_transitive + combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| + if s1 == s2 && s2 == s3 + assert_equal(s1, s3, "transitive: #{encdump s1} == #{encdump s2} == #{encdump s3}") + end + } + end + + def test_str_eq + combination(STRINGS, STRINGS) {|s1, s2| + desc_eq = "#{encdump s1} == #{encdump s2}" + if b(s1) == b(s2) and + (s1.ascii_only? && s2.ascii_only? or + s1.encoding == s2.encoding) then + assert_operator(s1, :==, s2, desc_eq) + assert_not_operator(s1, :!=, s2) + assert_equal(0, s1 <=> s2) + assert_operator(s1, :eql?, s2, desc_eq) + else + assert_not_operator(s1, :==, s2, "!(#{desc_eq})") + assert_operator(s1, :!=, s2) + assert_not_equal(0, s1 <=> s2) + assert_not_operator(s1, :eql?, s2) + end + } + end + + def test_str_concat + combination(STRINGS, STRINGS) {|s1, s2| + s = s1.dup + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + s << 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 } + end + } + end + + def test_str_aref + STRINGS.each {|s| + t = ''.force_encoding(s.encoding) + 0.upto(s.length-1) {|i| + u = s[i] + assert_predicate(u, :valid_encoding?) if s.valid_encoding? + t << u + } + assert_equal(t, s) + } + end + + def test_str_aref_len + STRINGS.each {|s| + t = ''.force_encoding(s.encoding) + 0.upto(s.length-1) {|i| + u = s[i,1] + assert_predicate(u, :valid_encoding?) if s.valid_encoding? + t << u + } + assert_equal(t, s) + } + + STRINGS.each {|s| + t = ''.force_encoding(s.encoding) + 0.step(s.length-1, 2) {|i| + u = s[i,2] + assert_predicate(u, :valid_encoding?) if s.valid_encoding? + t << u + } + assert_equal(t, s) + } + end + + def test_str_aref_substr + combination(STRINGS, STRINGS) {|s1, s2| + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + t = enccall(s1, :[], s2) + if t != nil + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + assert_equal(s2, t) + assert_match(/#{Regexp.escape(b(s2))}/, b(s1)) + if s1.valid_encoding? + assert_match(/#{Regexp.escape(s2)}/, s1) + end + end + else + assert_raise(Encoding::CompatibilityError) { s1[s2] } + end + } + end + + def test_str_aref_range2 + combination(STRINGS, -2..2, -2..2) {|s, first, last| + desc = "#{encdump s}[#{first}..#{last}]" + t = s[first..last] + if first < 0 + first += s.length + if first < 0 + assert_nil(t, desc) + next + end + end + if s.length < first + assert_nil(t, desc) + next + end + assert_predicate(t, :valid_encoding?) if s.valid_encoding? + if last < 0 + last += s.length + end + t2 = '' + first.upto(last) {|i| + c = s[i] + t2 << c if c + } + assert_equal(t2, t, desc) + } + end + + def test_str_aref_range3 + combination(STRINGS, -2..2, -2..2) {|s, first, last| + desc = "#{encdump s}[#{first}..#{last}]" + t = s[first...last] + if first < 0 + first += s.length + if first < 0 + assert_nil(t, desc) + next + end + end + if s.length < first + assert_nil(t, desc) + next + end + if last < 0 + last += s.length + end + assert_predicate(t, :valid_encoding?) if s.valid_encoding? + t2 = '' + first.upto(last-1) {|i| + c = s[i] + t2 << c if c + } + assert_equal(t2, t, desc) + } + end + + def test_str_assign + combination(STRINGS, STRINGS) {|s1, s2| + (-2).upto(2) {|i| + t = s1.dup + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + if i < -s1.length || s1.length < i + assert_raise(IndexError) { t[i] = s2 } + else + t[i] = 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]) + elsif i < 0 + assert_equal(s2, t[i-s2.length+1,s2.length], + "t = #{encdump(s1)}; t[#{i}] = #{encdump(s2)}; t[#{i-s2.length+1},#{s2.length}]") + else + assert_equal(s2, t[i,s2.length], + "t = #{encdump(s1)}; t[#{i}] = #{encdump(s2)}; t[#{i},#{s2.length}]") + end + end + end + else + assert_raise(Encoding::CompatibilityError) { t[i] = s2 } + end + } + } + end + + def test_str_assign_len + combination(STRINGS, -2..2, 0..2, STRINGS) {|s1, i, len, s2| + t = s1.dup + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + if i < -s1.length || s1.length < i + assert_raise(IndexError) { t[i,len] = s2 } + else + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? + t[i,len] = 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]) + elsif i < 0 + if -i < len + len = -i + end + assert_equal(s2, t[i-s2.length+len,s2.length], + "t = #{encdump(s1)}; t[#{i},#{len}] = #{encdump(s2)}; t[#{i-s2.length+len},#{s2.length}]") + else + assert_equal(s2, t[i,s2.length], + "t = #{encdump(s1)}; t[#{i},#{len}] = #{encdump(s2)}; t[#{i},#{s2.length}]") + end + end + end + else + assert_raise(Encoding::CompatibilityError) { t[i,len] = s2 } + end + } + end + + def test_str_assign_substr + combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| + t = s1.dup + encs = [ + !s1.ascii_only? ? s1.encoding : nil, + !s2.ascii_only? ? s2.encoding : nil, + !s3.ascii_only? ? s3.encoding : nil].uniq.compact + if 1 < encs.length + assert_raise(Encoding::CompatibilityError, IndexError) { t[s2] = s3 } + else + if encs.empty? + encs = [ + s1.encoding, + s2.encoding, + s3.encoding].uniq.reject {|e| e == Encoding.find("ASCII-8BIT") } + if encs.empty? + encs = [Encoding.find("ASCII-8BIT")] + end + end + if !t[s2] + else + enccall(t, :[]=, s2, s3) + assert_predicate(t, :valid_encoding?) if s1.valid_encoding? && s2.valid_encoding? && s3.valid_encoding? + end + end + } + end + + def test_str_assign_range2 + combination(STRINGS, -2..2, -2..2, STRINGS) {|s1, first, last, s2| + t = s1.dup + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + if first < -s1.length || s1.length < first + assert_raise(RangeError) { t[first..last] = s2 } + else + enccall(t, :[]=, first..last, 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]) + else + assert_equal(s2, t[first, s2.length]) + end + end + end + else + assert_raise(Encoding::CompatibilityError, RangeError, + "t=#{encdump(s1)};t[#{first}..#{last}]=#{encdump(s2)}") { + t[first..last] = s2 + } + end + } + end + + def test_str_assign_range3 + combination(STRINGS, -2..2, -2..2, STRINGS) {|s1, first, last, s2| + t = s1.dup + if s1.ascii_only? || s2.ascii_only? || s1.encoding == s2.encoding + if first < -s1.length || s1.length < first + assert_raise(RangeError) { t[first...last] = s2 } + else + enccall(t, :[]=, first...last, 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]) + else + assert_equal(s2, t[first, s2.length]) + end + end + end + else + assert_raise(Encoding::CompatibilityError, RangeError, + "t=#{encdump(s1)};t[#{first}...#{last}]=#{encdump(s2)}") { + t[first...last] = s2 + } + end + } + end + + def test_str_cmp + combination(STRINGS, STRINGS) {|s1, s2| + desc = "#{encdump s1} <=> #{encdump s2}" + r = s1 <=> s2 + if s1 == s2 + assert_equal(0, r, desc) + else + assert_not_equal(0, r, desc) + end + } + end + + def test_str_capitalize + STRINGS.each {|s| + begin + t1 = s.capitalize + rescue ArgumentError + assert_not_predicate(s, :valid_encoding?) + next + end + 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| b(ch).upcase } + assert_equal(r, t1) + } + end + + def test_str_casecmp + combination(STRINGS, STRINGS) {|s1, s2| + #puts "#{encdump(s1)}.casecmp(#{encdump(s2)})" + next unless s1.valid_encoding? && s2.valid_encoding? && Encoding.compatible?(s1, s2) + r = s1.casecmp(s2) + assert_equal(s1.upcase <=> s2.upcase, r) + } + end + + def test_str_casecmp? + strings = STRINGS.dup + strings.push( + # prevent wrong single byte optimization + "\xC0".force_encoding("ISO-8859-1"), + "\xE0".force_encoding("ISO-8859-1"), + ) + combination(strings, strings) {|s1, s2| + #puts "#{encdump(s1)}.casecmp(#{encdump(s2)})" + next unless s1.valid_encoding? && s2.valid_encoding? && Encoding.compatible?(s1, s2) + r = s1.casecmp?(s2) + assert_equal(s1.downcase(:fold) == s2.downcase(:fold), r) + } + end + + def test_str_center + combination(STRINGS, [0,1,2,3,10]) {|s1, width| + t = s1.center(width) + assert_send([b(t), :index, b(s1)]) + } + combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| + if s2.empty? + assert_raise(ArgumentError) { s1.center(width, s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.center(width, s2) } + next + end + t = enccall(s1, :center, width, s2) + 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 + + def test_str_ljust + combination(STRINGS, [0,1,2,3,10]) {|s1, width| + t = s1.ljust(width) + assert_send([b(t), :index, b(s1)]) + } + combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| + if s2.empty? + assert_raise(ArgumentError) { s1.ljust(width, s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.ljust(width, s2) } + next + end + t = enccall(s1, :ljust, width, s2) + 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 + + def test_str_rjust + combination(STRINGS, [0,1,2,3,10]) {|s1, width| + t = s1.rjust(width) + assert_send([b(t), :index, b(s1)]) + } + combination(STRINGS, [0,1,2,3,10], STRINGS) {|s1, width, s2| + if s2.empty? + assert_raise(ArgumentError) { s1.rjust(width, s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.rjust(width, s2) } + next + end + t = enccall(s1, :rjust, width, s2) + 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 + + def test_str_chomp + 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, "#{encdump(s1)}.chomp(#{encdump(s2)})") do + s1.chomp(s2) + end + end + next + end + t = enccall(s1, :chomp, s2) + 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) + assert_equal(t, t2) + } + 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_predicate(t, :valid_encoding?) if s.valid_encoding? + assert_send([b(s), :index, b(t)]) + t2 = s.dup + t2.chop! + assert_equal(t, t2) + } + end + + def test_str_clear + STRINGS.each {|s| + t = s.dup + t.clear + assert_predicate(t, :valid_encoding?) + assert_empty(t) + } + end + + def test_str_clone + STRINGS.each {|s| + t = s.clone + assert_equal(s, t) + assert_equal(s.encoding, t.encoding) + assert_equal(b(s), b(t)) + } + end + + def test_str_dup + STRINGS.each {|s| + t = s.dup + assert_equal(s, t) + assert_equal(s.encoding, t.encoding) + 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, desc) { s1.count(s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError, desc) { s1.count(s2) } + next + end + n = enccall(s1, :count, s2) + n0 = b(s1).count(b(s2)) + assert_operator(n, :<=, n0) + } + end + + def crypt_supports_des_crypt? + /openbsd/ !~ RUBY_PLATFORM + end + + # glibc 2.16 or later denies salt contained other than [0-9A-Za-z./] #7312 + # we use this check to test strict and non-strict behavior separately #11045 + strict_crypt = if defined? Etc::CS_GNU_LIBC_VERSION + begin + confstr = Etc.confstr(Etc::CS_GNU_LIBC_VERSION) + rescue Errno::EINVAL + false + else + glibcver = confstr.scan(/\d+/).map(&:to_i) + (glibcver <=> [2, 16]) >= 0 + end + end + + def test_str_crypt + combination(STRINGS, STRINGS) {|str, salt| + # 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 && /openbsd/ !~ RUBY_PLATFORM + def test_str_crypt_nonstrict + combination(STRINGS, STRINGS) {|str, salt| + # only test input other than [0-9A-Za-z./] to confirm non-strict behavior + 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 crypt_supports_des_crypt? + if b(salt).length < 2 + assert_raise(ArgumentError) { str.crypt(salt) } + return + end + else + return if b(salt).length < 2 + salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmH#{salt}" + end + t = str.crypt(salt) + assert_equal(b(str).crypt(b(salt)), t, "#{encdump(str)}.crypt(#{encdump(salt)})") + assert_encoding('ASCII-8BIT', t.encoding) + end + + def test_str_delete + combination(STRINGS, STRINGS) {|s1, s2| + if s1.empty? + assert_equal(s1, s1.delete(s2)) + next + end + if !s1.valid_encoding? || !s2.valid_encoding? + assert_raise(ArgumentError, Encoding::CompatibilityError) { s1.delete(s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.delete(s2) } + next + end + t = enccall(s1, :delete, s2) + assert_predicate(t, :valid_encoding?) + assert_equal(t.encoding, s1.encoding) + assert_operator(t.length, :<=, s1.length) + t2 = s1.dup + t2.delete!(s2) + assert_equal(t, t2) + } + end + + def test_str_downcase + STRINGS.each {|s| + if !s.valid_encoding? + assert_raise(ArgumentError, "Offending string: #{s.inspect}, encoding: #{s.encoding}") { s.downcase } + next + end + t = s.downcase + assert_predicate(t, :valid_encoding?) + assert_equal(t.encoding, s.encoding) + assert_operator(t, :casecmp, s) + t2 = s.dup + t2.downcase! + assert_equal(t, t2) + } + end + + def test_str_dump + STRINGS.each {|s| + t = s.dump + assert_predicate(t, :valid_encoding?) + assert_predicate(t, :ascii_only?) + u = eval(t) + assert_equal(b(s), b(u)) + } + end + + def test_str_each_line + combination(STRINGS, STRINGS) {|s1, s2| + 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_equal(s1.encoding, line.encoding) + lines << line + } + next if lines.size == 0 + s2 = lines.join('') + assert_equal(s1.encoding, s2.encoding) + assert_equal(s1, s2) + } + end + + def test_str_each_byte + STRINGS.each {|s| + bytes = [] + s.each_byte {|b| + bytes << b + } + b(s).split(//).each_with_index {|ch, i| + assert_equal(ch.ord, bytes[i]) + } + } + end + + def test_str_empty? + STRINGS.each {|s| + if s.length == 0 + assert_empty(s) + else + assert_not_empty(s) + end + } + end + + def test_str_hex + STRINGS.each {|s| + t = s.hex + t2 = b(s)[/\A[0-9a-fA-Fx]*/].hex + assert_equal(t2, t) + } + end + + def test_str_include? + combination(STRINGS, STRINGS) {|s1, s2| + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.include?(s2) } + assert_raise(Encoding::CompatibilityError) { s1.index(s2) } + assert_raise(Encoding::CompatibilityError) { s1.rindex(s2) } + next + end + t = enccall(s1, :include?, s2) + if t + assert_include(b(s1), b(s2)) + assert_send([s1, :index, s2]) + assert_send([s1, :rindex, s2]) + else + 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) + next + end + if !s1.valid_encoding? || !s2.valid_encoding? + assert_equal(false, t, "#{encdump s1}.include?(#{encdump s2})") + next + end + if t && s1.valid_encoding? && s2.valid_encoding? + assert_match(/#{Regexp.escape(s2)}/, s1) + else + assert_no_match(/#{Regexp.escape(s2)}/, s1) + end + } + end + + def test_str_index + combination(STRINGS, STRINGS, -2..2) {|s1, s2, pos| + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.index(s2) } + next + end + t = enccall(s1, :index, s2, pos) + if s2.empty? + if pos < 0 && pos+s1.length < 0 + assert_equal(nil, t, "#{encdump s1}.index(#{encdump s2}, #{pos})"); + elsif pos < 0 + assert_equal(s1.length+pos, t, "#{encdump s1}.index(#{encdump s2}, #{pos})"); + elsif s1.length < pos + assert_equal(nil, t, "#{encdump s1}.index(#{encdump s2}, #{pos})"); + else + assert_equal(pos, t, "#{encdump s1}.index(#{encdump s2}, #{pos})"); + end + next + end + if !s1.valid_encoding? || !s2.valid_encoding? + assert_equal(nil, t, "#{encdump s1}.index(#{encdump s2}, #{pos})"); + next + end + if t + re = /#{Regexp.escape(s2)}/ + assert(re.match(s1, pos)) + assert_equal($`.length, t, "#{encdump s1}.index(#{encdump s2}, #{pos})") + else + assert_no_match(/#{Regexp.escape(s2)}/, s1[pos..-1]) + end + } + end + + def test_str_rindex + combination(STRINGS, STRINGS, -2..2) {|s1, s2, pos| + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.rindex(s2) } + next + end + t = enccall(s1, :rindex, s2, pos) + if s2.empty? + if pos < 0 && pos+s1.length < 0 + assert_equal(nil, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + elsif pos < 0 + assert_equal(s1.length+pos, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + elsif s1.length < pos + assert_equal(s1.length, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + else + assert_equal(pos, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + end + next + end + if !s1.valid_encoding? || !s2.valid_encoding? + assert_equal(nil, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + next + end + if t + #puts "#{encdump s1}.rindex(#{encdump s2}, #{pos}) => #{t}" + assert_send([b(s1), :index, b(s2)]) + pos2 = pos + pos2 += s1.length if pos < 0 + re = /\A(.{0,#{pos2}})#{Regexp.escape(s2)}/m + m = enccall(re, :match, s1) + assert(m, "#{re.inspect}.match(#{encdump(s1)})") + assert_equal(m[1].length, t, "#{encdump s1}.rindex(#{encdump s2}, #{pos})") + else + re = /#{Regexp.escape(s2)}/ + n = re =~ s1 + if n + if pos < 0 + assert_operator(n, :>, s1.length+pos) + else + assert_operator(n, :>, pos) + end + end + end + } + end + + def test_str_insert + combination(STRINGS, 0..2, STRINGS) {|s1, nth, s2| + t1 = s1.dup + t2 = s1.dup + begin + t1[nth, 0] = s2 + rescue Encoding::CompatibilityError, IndexError => e1 + end + begin + t2.insert(nth, s2) + rescue Encoding::CompatibilityError, IndexError => e2 + end + assert_equal(t1, t2, "t=#{encdump s1}; t.insert(#{nth},#{encdump s2}); t") + assert_equal(e1.class, e2.class, "begin #{encdump s1}.insert(#{nth},#{encdump s2}); rescue ArgumentError, IndexError => e; e end") + } + combination(STRINGS, -2..-1, STRINGS) {|s1, nth, s2| + next if s1.length + nth < 0 + next unless s1.valid_encoding? + next unless s2.valid_encoding? + t1 = s1.dup + begin + 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 + end + } + end + + def test_str_intern + STRINGS.each {|s| + if /\0/ =~ b(s) + assert_raise(ArgumentError) { s.intern } + elsif s.valid_encoding? + sym = s.intern + assert_equal(s, sym.to_s, "#{encdump s}.intern.to_s") + assert_equal(sym, s.to_sym) + else + assert_raise(EncodingError) { s.intern } + end + } + end + + def test_str_length + STRINGS.each {|s| + assert_operator(s.length, :<=, s.bytesize) + } + end + + def test_str_oct + STRINGS.each {|s| + t = s.oct + t2 = b(s)[/\A[0-9a-fA-FxX]*/].oct + assert_equal(t2, t) + } + end + + def test_str_replace + combination(STRINGS, STRINGS) {|s1, s2| + t = s1.dup + t.replace s2 + assert_equal(s2, t) + assert_equal(s2.encoding, t.encoding) + } + end + + def test_str_reverse + STRINGS.each {|s| + t = s.reverse + assert_equal(s.bytesize, t.bytesize) + if !s.valid_encoding? + assert_operator(t.length, :<=, s.length) + next + end + assert_equal(s, t.reverse) + } + end + + def test_str_scan + combination(STRINGS, STRINGS) {|s1, s2| + desc = proc {"#{s1.dump}.scan(#{s2.dump})"} + if !s2.valid_encoding? + 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, desc) { s1.scan(s2) } + else + assert_raise_with_message(ArgumentError, /invalid byte sequence/, desc) { s1.scan(s2) } + end + next + end + if !s1.valid_encoding? + assert_raise(ArgumentError, desc) { s1.scan(s2) } + next + end + r = enccall(s1, :scan, s2) + r.each {|t| + assert_equal(s2, t, desc) + } + } + end + + def test_str_slice + each_slice_call {|obj, *args| + assert_same_result(lambda { obj[*args] }, lambda { obj.slice(*args) }) + } + end + + def test_str_slice! + each_slice_call {|s, *args| + desc_slice = "#{encdump s}.slice#{encdumpargs args}" + desc_slice_bang = "#{encdump s}.slice!#{encdumpargs args}" + t = s.dup + begin + r = t.slice!(*args) + rescue + e = $! + end + if e + assert_raise(e.class, desc_slice) { s.slice(*args) } + next + end + if !r + assert_nil(s.slice(*args), desc_slice) + next + end + assert_equal(s.slice(*args), r, desc_slice_bang) + assert_equal(s.bytesize, r.bytesize + t.bytesize) + if args.length == 1 && String === args[0] + assert_equal(args[0].encoding, r.encoding, + "#{encdump s}.slice!#{encdumpargs args}.encoding") + else + assert_equal(s.encoding, r.encoding, + "#{encdump s}.slice!#{encdumpargs args}.encoding") + end + if [s, *args].all? {|o| !(String === o) || o.valid_encoding? } + assert_predicate(r, :valid_encoding?) + assert_predicate(t, :valid_encoding?) + assert_equal(s.length, r.length + t.length) + end + } + end + + def test_str_split + combination(STRINGS, STRINGS) {|s1, s2| + if !s2.valid_encoding? + assert_raise(ArgumentError, RegexpError) { s1.split(s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(ArgumentError, Encoding::CompatibilityError) { s1.split(s2) } + next + end + if !s1.valid_encoding? + assert_raise(ArgumentError) { s1.split(s2) } + next + end + t = enccall(s1, :split, s2) + t.each {|r| + assert_include(b(s1), b(r)) + assert_equal(s1.encoding, r.encoding) + } + assert_include(b(s1), t.map {|u| b(u) }.join(b(s2))) + if s1.valid_encoding? && s2.valid_encoding? + t.each {|r| + assert_predicate(r, :valid_encoding?) + } + end + } + end + + def test_str_squeeze + combination(STRINGS, STRINGS) {|s1, s2| + if !s1.valid_encoding? || !s2.valid_encoding? + assert_raise(ArgumentError, Encoding::CompatibilityError, "#{encdump s1}.squeeze(#{encdump s2})") { s1.squeeze(s2) } + next + end + if !s1.ascii_only? && !s2.ascii_only? && s1.encoding != s2.encoding + assert_raise(Encoding::CompatibilityError) { s1.squeeze(s2) } + next + end + t = enccall(s1, :squeeze, s2) + assert_operator(t.length, :<=, s1.length) + t2 = s1.dup + t2.squeeze!(s2) + assert_equal(t, t2) + } + end + + def test_str_strip + STRINGS.each {|s| + if !s.valid_encoding? + assert_raise(ArgumentError, "#{encdump s}.strip") { s.strip } + next + end + t = s.strip + l = s.lstrip + r = s.rstrip + assert_operator(l.length, :<=, s.length) + assert_operator(r.length, :<=, s.length) + assert_operator(t.length, :<=, l.length) + assert_operator(t.length, :<=, r.length) + t2 = s.dup + t2.strip! + assert_equal(t, t2) + l2 = s.dup + l2.lstrip! + assert_equal(l, l2) + r2 = s.dup + r2.rstrip! + assert_equal(r, r2) + } + end + + def test_str_sum + STRINGS.each {|s| + assert_equal(b(s).sum, s.sum) + } + end + + def test_str_swapcase + STRINGS.each {|s| + if !s.valid_encoding? + assert_raise(ArgumentError, "#{encdump s}.swapcase") { s.swapcase } + next + end + t1 = s.swapcase + assert_predicate(t1, :valid_encoding?) if s.valid_encoding? + assert_operator(t1, :casecmp, s) + t2 = s.dup + t2.swapcase! + assert_equal(t1, t2) + t3 = t1.swapcase + assert_equal(s, t3); + } + end + + + def test_str_to_f + STRINGS.each {|s| + assert_nothing_raised { s.to_f } + } + end + + def test_str_to_i + STRINGS.each {|s| + assert_nothing_raised { s.to_i } + 2.upto(36) {|radix| + assert_nothing_raised { s.to_i(radix) } + } + } + end + + def test_str_to_s + STRINGS.each {|s| + assert_same(s, s.to_s) + assert_same(s, s.to_str) + } + end + + def test_tr + combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| + desc = "#{encdump s1}.tr(#{encdump s2}, #{encdump s3})" + if s1.empty? + assert_equal(s1, s1.tr(s2, s3), desc) + next + end + if !str_enc_compatible?(s1, s2, s3) + assert_raise(Encoding::CompatibilityError, desc) { s1.tr(s2, s3) } + next + end + if !s1.valid_encoding? + assert_raise(ArgumentError, desc) { s1.tr(s2, s3) } + next + end + if s2.empty? + t = enccall(s1, :tr, s2, s3) + assert_equal(s1, t, desc) + next + end + if !s2.valid_encoding? || !s3.valid_encoding? + assert_raise(ArgumentError, desc) { s1.tr(s2, s3) } + next + end + t = enccall(s1, :tr, s2, s3) + assert_operator(s1.length, :>=, t.length, desc) + } + 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})" + if s1.empty? + assert_equal(s1, s1.tr_s(s2, s3), desc) + next + end + if !s1.valid_encoding? + assert_raise(ArgumentError, Encoding::CompatibilityError, desc) { s1.tr_s(s2, s3) } + next + end + if !str_enc_compatible?(s1, s2, s3) + assert_raise(Encoding::CompatibilityError, desc) { s1.tr(s2, s3) } + next + end + if s2.empty? + t = enccall(s1, :tr_s, s2, s3) + assert_equal(s1, t, desc) + next + end + if !s2.valid_encoding? || !s3.valid_encoding? + assert_raise(ArgumentError, desc) { s1.tr_s(s2, s3) } + next + end + + t = enccall(s1, :tr_s, s2, s3) + assert_operator(s1.length, :>=, t.length, desc) + } + end + + def test_str_upcase + STRINGS.each {|s| + desc = "#{encdump s}.upcase" + if !s.valid_encoding? + assert_raise(ArgumentError, desc) { s.upcase } + next + end + t1 = s.upcase + assert_predicate(t1, :valid_encoding?) + assert_operator(t1, :casecmp, s) + t2 = s.dup + t2.upcase! + assert_equal(t1, t2) + } + end + + def test_str_succ + STRINGS.each {|s0| + next if s0.empty? + s = s0.dup + n = 300 + h = {} + n.times {|i| + if h[s] + assert(false, "#{encdump s} cycle with succ #{i-h[s]} times") + end + h[s] = i + assert_operator(s.length, :<=, s0.length + Math.log2(i+1) + 1, "#{encdump s0} succ #{i} times => #{encdump s}") + #puts encdump(s) + t = s.succ + if s.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 + combination(STRINGS, STRINGS) {|s1, s2| + if s1.eql?(s2) + assert_equal(s1.hash, s2.hash, "#{encdump s1}.hash == #{encdump s2}.dump") + end + } + end + + def test_marshal + STRINGS.each {|s| + m = Marshal.dump(s) + t = Marshal.load(m) + assert_equal(s, t) + } + end + + def test_str_sub + combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| + if !s2.valid_encoding? + assert_raise(RegexpError) { Regexp.new(Regexp.escape(s2)) } + next + end + r2 = Regexp.new(Regexp.escape(s2)) + [ + [ + "#{encdump s1}.sub(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { s1.sub(r2, s3) }, + false + ], + [ + "#{encdump s1}.sub(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { s1.sub(r2) { s3 } }, + false + ], + [ + "#{encdump s1}.gsub(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { s1.gsub(r2, s3) }, + true + ], + [ + "#{encdump s1}.gsub(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { s1.gsub(r2) { s3 } }, + true + ] + ].each {|desc, doit, g| + if !s1.valid_encoding? + assert_raise(ArgumentError, desc) { doit.call } + next + end + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { doit.call } + next + end + if !enccall(s1, :include?, s2) + assert_equal(s1, doit.call) + next + end + if !str_enc_compatible?(g ? s1.gsub(r2, '') : s1.sub(r2, ''), s3) + assert_raise(Encoding::CompatibilityError, desc) { doit.call } + next + end + t = nil + assert_nothing_raised(desc) { + t = doit.call + } + if s2 == s3 + assert_equal(s1, t, desc) + else + assert_not_equal(s1, t, desc) + end + } + } + end + + def test_str_sub! + combination(STRINGS, STRINGS, STRINGS) {|s1, s2, s3| + if !s2.valid_encoding? + assert_raise(RegexpError) { Regexp.new(Regexp.escape(s2)) } + next + end + r2 = Regexp.new(Regexp.escape(s2)) + [ + [ + "t=#{encdump s1}.dup;t.sub!(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { t=s1.dup; [t, t.sub!(r2, s3)] }, + false + ], + [ + "t=#{encdump s1}.dup;t.sub!(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { t=s1.dup; [t, t.sub!(r2) { s3 }] }, + false + ], + [ + "t=#{encdump s1}.dup;t.gsub!(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { t=s1.dup; [t, t.gsub!(r2, s3)] }, + true + ], + [ + "t=#{encdump s1}.dup;t.gsub!(Regexp.new(#{encdump s2}), #{encdump s3})", + lambda { t=s1.dup; [t, t.gsub!(r2) { s3 }] }, + true + ] + ].each {|desc, doit, g| + if !s1.valid_encoding? + assert_raise(ArgumentError, desc) { doit.call } + next + end + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { doit.call } + next + end + if !enccall(s1, :include?, s2) + assert_equal([s1, nil], doit.call) + next + end + if !str_enc_compatible?(g ? s1.gsub(r2, '') : s1.sub(r2, ''), s3) + assert_raise(Encoding::CompatibilityError, desc) { doit.call } + next + end + t = ret = nil + assert_nothing_raised(desc) { + t, ret = doit.call + } + assert(ret) + if s2 == s3 + assert_equal(s1, t, desc) + else + assert_not_equal(s1, t, desc) + end + } + } + end + + def test_str_bytes + STRINGS.each {|s1| + ary = [] + s1.bytes.each {|b| + ary << b + } + assert_equal(s1.unpack("C*"), ary) + } + end + + def test_str_bytesize + STRINGS.each {|s1| + assert_equal(s1.unpack("C*").length, s1.bytesize) + } + end + + def test_str_chars + STRINGS.each {|s1| + ary = [] + s1.chars.each {|c| + ary << c + } + expected = [] + s1.length.times {|i| + expected << s1[i] + } + assert_equal(expected, ary) + } + end + + def test_str_chr + STRINGS.each {|s1| + if s1.empty? + assert_equal("", s1.chr) + next + end + assert_equal(s1[0], s1.chr) + } + end + + def test_str_end_with? + combination(STRINGS, STRINGS) {|s1, s2| + desc = "#{encdump s1}.end_with?(#{encdump s2})" + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { s1.end_with?(s2) } + next + end + if s1.length < s2.length + assert_equal(false, enccall(s1, :end_with?, s2), desc) + next + end + if s1[s1.length-s2.length, s2.length] == s2 + assert_equal(true, enccall(s1, :end_with?, s2), desc) + next + end + assert_equal(false, enccall(s1, :end_with?, s2), desc) + } + end + + def test_str_start_with? + combination(STRINGS, STRINGS) {|s1, s2| + desc = "#{encdump s1}.start_with?(#{encdump s2})" + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { s1.start_with?(s2) } + next + end + s1 = s1.b + s2 = s2.b + if s1.length < s2.length + assert_equal(false, enccall(s1, :start_with?, s2), desc) + next + end + if s1[0, s2.length] == s2 + assert_equal(true, enccall(s1, :start_with?, s2), desc) + next + end + assert_equal(false, enccall(s1, :start_with?, s2), desc) + } + end + + def test_str_ord + STRINGS.each {|s1| + if s1.empty? + assert_raise(ArgumentError) { s1.ord } + next + end + if !s1.valid_encoding? + assert_raise(ArgumentError) { s1.ord } + next + end + assert_equal(s1[0].ord, s1.ord) + } + end + + def test_str_partition + combination(STRINGS, STRINGS) {|s1, s2| + desc = "#{encdump s1}.partition(#{encdump s2})" + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { s1.partition(s2) } + next + end + i = enccall(s1, :index, s2) + if !i + assert_equal([s1, "", ""], s1.partition(s2), desc) + next + end + assert_equal([s1[0,i], s2, s1[(i+s2.length)..-1]], s1.partition(s2), desc) + } + end + + def test_str_rpartition + combination(STRINGS, STRINGS) {|s1, s2| + desc = "#{encdump s1}.rpartition(#{encdump s2})" + if !str_enc_compatible?(s1, s2) + assert_raise(Encoding::CompatibilityError, desc) { s1.rpartition(s2) } + next + end + i = enccall(s1, :rindex, s2) + if !i + assert_equal(["", "", s1], s1.rpartition(s2), desc) + next + end + assert_equal([s1[0,i], s2, s1[(i+s2.length)..-1]], s1.rpartition(s2), desc) + } + 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 new file mode 100644 index 0000000000..eb66994801 --- /dev/null +++ b/test/ruby/test_marshal.rb @@ -0,0 +1,1006 @@ +# frozen_string_literal: false +require 'test/unit' +require_relative 'marshaltestlib' + +class TestMarshal < Test::Unit::TestCase + include MarshalTestLib + + def setup + @verbose = $VERBOSE + end + + def teardown + $VERBOSE = @verbose + end + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s) + end + + def fact(n) + return 1 if n == 0 + f = 1 + while n>0 + f *= n + n -= 1 + end + return f + end + + def test_marshal + a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)] + assert_equal a, Marshal.load(Marshal.dump(a)) + + [[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z| + 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 + + def test_marshal_integers + a = [] + [-2, -1, 0, 1, 2].each do |i| + 0.upto(65).map do |exp| + a << 2**exp + i + end + end + assert_equal a, Marshal.load(Marshal.dump(a)) + + a = [2**32, []]*2 + assert_equal a, Marshal.load(Marshal.dump(a)) + + a = [2**32, 2**32, []]*2 + assert_equal a, Marshal.load(Marshal.dump(a)) + end + + def test_marshal_small_bignum_backref + assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06") + end + + StrClone = String.clone + def test_marshal_cloned_class + assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc")))) + end + + def test_inconsistent_struct + TestMarshal.const_set :StructOrNot, Struct.new(:a) + s = Marshal.dump(StructOrNot.new(1)) + TestMarshal.instance_eval { remove_const :StructOrNot } + TestMarshal.const_set :StructOrNot, Class.new + assert_raise(TypeError, "[ruby-dev:31709]") { Marshal.load(s) } + ensure + TestMarshal.instance_eval { remove_const :StructOrNot } + end + + def test_struct_invalid_members + TestMarshal.const_set :StructInvalidMembers, Struct.new(:a) + assert_raise(TypeError, "[ruby-dev:31759]") { + Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo") + TestMarshal::StructInvalidMembers.members + } + ensure + TestMarshal.instance_eval { remove_const :StructInvalidMembers } + end + + def test_load_range_as_struct + assert_raise(TypeError, 'GH-6832') do + # Can be obtained with: + # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))' + Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0") + end + end + + class C + def initialize(str) + @str = str + end + attr_reader :str + def _dump(limit) + @str + end + def self._load(s) + new(s) + end + end + + def test_too_long_string + data = Marshal.dump(C.new("a".force_encoding("ascii-8bit"))) + data[-2, 1] = "\003\377\377\377" + assert_raise_with_message(ArgumentError, "marshal data too short", "[ruby-dev:32054]") { + Marshal.load(data) + } + end + + + def test_userdef_encoding + s1 = "\xa4\xa4".force_encoding("euc-jp") + o1 = C.new(s1) + m = Marshal.dump(o1) + o2 = Marshal.load(m) + s2 = o2.str + assert_equal(s1, s2) + end + + def test_pipe + o1 = C.new("a" * 10000) + + 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 + + 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) } + end + + def test_limit + assert_equal([[[]]], Marshal.load(Marshal.dump([[[]]], 3))) + assert_raise(ArgumentError) { Marshal.dump([[[]]], 2) } + assert_nothing_raised(ArgumentError, '[ruby-core:24100]') { Marshal.dump("\u3042", 1) } + end + + def test_userdef_invalid + o = C.new(nil) + assert_raise(TypeError) { Marshal.dump(o) } + end + + def test_class + o = class << Object.new; self; end + assert_raise(TypeError) { Marshal.dump(o) } + assert_equal(Object, Marshal.load(Marshal.dump(Object))) + assert_equal(Enumerable, Marshal.load(Marshal.dump(Enumerable))) + end + + class C2 + def initialize(ary) + @ary = ary + end + def _dump(s) + @ary.clear + "foo" + end + end + + def test_modify_array_during_dump + a = [] + o = C2.new(a) + a << o << nil + assert_raise(RuntimeError) { Marshal.dump(a) } + end + + def test_change_class_name + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) + eval("class C3; def _dump(s); 'foo'; end; end") + m = Marshal.dump(C3.new) + assert_raise(TypeError) { Marshal.load(m) } + self.class.__send__(:remove_const, :C3) + eval("C3 = nil") + assert_raise(TypeError) { Marshal.load(m) } + ensure + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) + end + + def test_change_struct + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) + eval("C3 = Struct.new(:foo, :bar)") + m = Marshal.dump(C3.new("FOO", "BAR")) + self.class.__send__(:remove_const, :C3) + eval("C3 = Struct.new(:foo)") + assert_raise(TypeError) { Marshal.load(m) } + self.class.__send__(:remove_const, :C3) + eval("C3 = Struct.new(:foo, :baz)") + assert_raise(TypeError) { Marshal.load(m) } + ensure + self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3) + end + + class C4 + def initialize(gc) + @gc = gc + end + def _dump(s) + GC.start if @gc + "foo" + end + end + + def test_gc + assert_nothing_raised do + Marshal.dump((0..1000).map {|x| C4.new(x % 50 == 25) }) + end + end + + def test_symbol2 + [:ruby, :"\u{7d05}\u{7389}"].each do |sym| + assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]') + end + bug2548 = '[ruby-core:27375]' + ary = [:$1, nil] + 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 + + structISO8859_1 = Struct.new("r\xe9sum\xe9".force_encoding(iso_8859_1).intern) + const_set("R\xe9sum\xe9".force_encoding(iso_8859_1), structISO8859_1) + structISO8859_1.name + StructISO8859_1 = structISO8859_1 + classISO8859_1 = Class.new do + attr_accessor "r\xe9sum\xe9".force_encoding(iso_8859_1) + eval("def initialize(x) @r\xe9sum\xe9 = x; end".force_encoding(iso_8859_1)) + end + const_set("R\xe9sum\xe92".force_encoding(iso_8859_1), classISO8859_1) + classISO8859_1.name + ClassISO8859_1 = classISO8859_1 + + moduleUTF8 = const_set("C\u{30af 30e9 30b9}", Module.new) + moduleUTF8.name + ModuleUTF8 = moduleUTF8 + + def test_nonascii_class_instance + a = ClassUTF8.new + assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]') + + bug1932 = '[ruby-core:24882]' + + a = StructISO8859_1.new(10) + assert_nothing_raised(bug1932) do + assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932) + end + a.__send__("#{StructISO8859_1.members[0]}=", a) + assert_nothing_raised(bug1932) do + assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932) + end + + a = ClassISO8859_1.new(10) + assert_nothing_raised(bug1932) do + b = Marshal.load(Marshal.dump(a)) + assert_equal(ClassISO8859_1, b.class, bug1932) + assert_equal(a.instance_variables, b.instance_variables, bug1932) + a.instance_variables.each do |i| + assert_equal(a.instance_variable_get(i), b.instance_variable_get(i), bug1932) + end + end + a.__send__(a.methods(true).grep(/=\z/)[0], a) + assert_nothing_raised(bug1932) do + b = Marshal.load(Marshal.dump(a)) + assert_equal(ClassISO8859_1, b.class, bug1932) + assert_equal(a.instance_variables, b.instance_variables, bug1932) + assert_equal(b, b.instance_variable_get(a.instance_variables[0]), bug1932) + end + end + + def test_nonascii_class_module + assert_same(ClassUTF8, Marshal.load(Marshal.dump(ClassUTF8))) + assert_same(ClassISO8859_1, Marshal.load(Marshal.dump(ClassISO8859_1))) + assert_same(ModuleUTF8, Marshal.load(Marshal.dump(ModuleUTF8))) + end + + 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")) + + bug2109 = '[ruby-core:25625]' + a = "\x82\xa0".force_encoding(Encoding::Windows_31J) + b = "\x82\xa2".force_encoding(Encoding::Windows_31J) + c = [/#{a}/, /#{b}/] + assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109) + + assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do + re = IO.pipe do |r, w| + w.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII") + # Marshal.load would not overread and block + Marshal.load(r) + end + assert_equal(//, re) + end + end + + class DumpTest + def marshal_dump + @@block.call(:marshal_dump) + end + + def dump_each(&block) + @@block = block + Marshal.dump(self) + end + end + + class LoadTest + def marshal_dump + nil + end + def marshal_load(obj) + @@block.call(:marshal_load) + end + def self.load_each(m, &block) + @@block = block + Marshal.load(m) + end + end + + def test_context_switch + o = DumpTest.new + e = o.enum_for(:dump_each) + assert_equal(:marshal_dump, e.next) + GC.start + assert(true, '[ruby-dev:39425]') + assert_raise(StopIteration) {e.next} + + o = LoadTest.new + m = Marshal.dump(o) + e = LoadTest.enum_for(:load_each, m) + assert_equal(:marshal_load, e.next) + GC.start + assert(true, '[ruby-dev:39425]') + assert_raise(StopIteration) {e.next} + end + + def test_dump_buffer + bug2390 = '[ruby-dev:39744]' + w = "" + def w.write(str) + self << str.to_s + end + Marshal.dump(Object.new, w) + assert_not_empty(w, bug2390) + end + + class C5 + def marshal_dump + "foo" + end + def marshal_load(foo) + @foo = foo + end + def initialize(x) + @x = x + end + end + def test_marshal_dump + c = C5.new("bar") + s = Marshal.dump(c) + d = Marshal.load(s) + assert_equal("foo", d.instance_variable_get(:@foo)) + assert_equal(false, d.instance_variable_defined?(:@x)) + end + + class C6 + def initialize + @stdin = STDIN + end + attr_reader :stdin + def marshal_dump + 1 + end + def marshal_load(x) + @stdin = STDIN + end + end + def test_marshal_dump_extra_iv + o = C6.new + m = nil + assert_nothing_raised("[ruby-dev:21475] [ruby-dev:39845]") { + m = Marshal.dump(o) + } + o2 = Marshal.load(m) + assert_equal(STDIN, o2.stdin) + end + + def test_marshal_string_encoding + o1 = ["foo".force_encoding("EUC-JP")] + [ "bar" ] * 2 + m = Marshal.dump(o1) + o2 = Marshal.load(m) + assert_equal(o1, o2, "[ruby-dev:40388]") + end + + def test_marshal_regexp_encoding + o1 = [Regexp.new("r1".force_encoding("EUC-JP"))] + ["r2"] * 2 + m = Marshal.dump(o1) + o2 = Marshal.load(m) + assert_equal(o1, o2, "[ruby-dev:40416]") + end + + def test_marshal_encoding_encoding + o1 = [Encoding.find("EUC-JP")] + ["r2"] * 2 + m = Marshal.dump(o1) + 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 + + class TooComplex + def initialize + @marshal_too_complex = 1 + end + end + + def test_complex_shape_object_id_not_dumped + if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS) + assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + 8.times do |i| + TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1) + end + obj = TooComplex.new + ivar = "@a#{rand(10_000).to_s.rjust(5, '0')}" + obj.instance_variable_set(ivar, 1) + + if defined?(RubyVM::Shape) + assert_predicate(RubyVM::Shape.of(obj), :too_complex?) + end + obj.object_id + assert_equal "\x04\bo:\x1CTestMarshal::TooComplex\a:\x19@marshal_too_complexi\x06:\f#{ivar}i\x06".b, Marshal.dump(obj) + 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 + + 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?, :@ivar_bug) + + assert_raise(TypeError) {Marshal.load("\x04\x08[\x07c\x1bTestMarshal::TestClassI@\x06\x06:\x0e@ivar_bug\"\x08bug")} + assert_not_operator(TestClass, :instance_variable_defined?, :@ivar_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?, :@ivar_bug) + + assert_raise(TypeError) {Marshal.load("\x04\x08[\x07m\x1cTestMarshal::TestModuleI@\x06\x06:\x0e@ivar_bug\"\x08bug")} + assert_not_operator(TestModule, :instance_variable_defined?, :@ivar_bug) + end + + class TestForRespondToFalse + def respond_to?(a, priv = false) + 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 + + class Bug9523 + attr_reader :cc + def marshal_dump + callcc {|c| @cc = c } + nil + end + def marshal_load(v) + end + end + + def test_continuation + EnvUtil.suppress_warning {require "continuation"} + omit 'requires callcc support' unless respond_to?(:callcc) + + 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 + name = "Unloadable\u{23F0 23F3}" + c = eval("class #{name} < 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) + } + + ensure + self.class.class_eval do + remove_const name + end if c + end + + def test_unloadable_userdef + name = "Userdef\u{23F0 23F3}" + c = eval("class #{name} < 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) + } + + ensure + self.class.class_eval do + remove_const name + end if c + end + + def test_recursive_userdef + t = Time.utc(0) + t.instance_eval {@v = t} + assert_raise_with_message(RuntimeError, /recursive\b.*\b_dump/) do + Marshal.dump(t) + end + 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] + kw = {encoding: Encoding::ASCII_8BIT} + out, err, status = EnvUtil.invoke_ruby(*args, **kw) + assert_empty(err) + assert_predicate(status, :success?) + expected = out + + opt << "--enable=frozen-string-literal" + opt << "--debug=frozen-string-literal" + out, err, status = EnvUtil.invoke_ruby(*args, **kw) + 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_proc_string_encoding + string = "foo" + payload = Marshal.dump(string) + Marshal.load(payload, ->(v) { + if v.is_a?(String) + assert_equal(string, v) + assert_equal(string.encoding, v.encoding) + end + v + }) + end + + def test_marshal_proc_freeze + object = { foo: [42, "bar"] } + assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc) + end + + def test_marshal_load_extended_class_crash + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + 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 + + ruby2_keywords def ruby2_keywords_hash(*a) + a.last + end + + def ruby2_keywords_test(key: 1) + key + end + + def test_marshal_with_ruby2_keywords_hash + flagged_hash = ruby2_keywords_hash(key: 42) + data = Marshal.dump(flagged_hash) + hash = Marshal.load(data) + assert_equal(42, ruby2_keywords_test(*[hash])) + + hash2 = Marshal.load(data.sub(/\x06K(?=T\z)/, "\x08KEY")) + assert_raise(ArgumentError, /\(given 1, expected 0\)/) { + ruby2_keywords_test(*[hash2]) + } + hash2 = Marshal.load(data.sub(/:\x06K(?=T\z)/, "I\\&\x06:\x0dencoding\"\x0aUTF-7")) + assert_raise(ArgumentError, /\(given 1, expected 0\)/) { + ruby2_keywords_test(*[hash2]) + } + end + + def test_invalid_byte_sequence_symbol + data = Marshal.dump(:K) + data = data.sub(/:\x06K/, "I\\&\x06:\x0dencoding\"\x0dUTF-16LE") + assert_raise(ArgumentError, /UTF-16LE: "\\x4B"/) { + Marshal.load(data) + } + end + + def exception_test + raise + end + + def test_marshal_exception + begin + exception_test + rescue => e + e2 = Marshal.load(Marshal.dump(e)) + assert_equal(e.message, e2.message) + assert_equal(e.backtrace, e2.backtrace) + assert_nil(e2.backtrace_locations) # temporal + end + end + + def nameerror_test + unknown_method + end + + def test_marshal_nameerror + begin + nameerror_test + rescue NameError => e + e2 = Marshal.load(Marshal.dump(e)) + assert_equal(e.message.lines.first.chomp, e2.message.lines.first) + assert_equal(e.name, e2.name) + assert_equal(e.backtrace, e2.backtrace) + assert_nil(e2.backtrace_locations) # temporal + end + end + + class TestMarshalFreezeProc < Test::Unit::TestCase + include MarshalTestLib + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s, :freeze.to_proc) + end + end + + def _test_hash_compared_by_identity(h) + h.compare_by_identity + h["a" + "0"] = 1 + h["a" + "0"] = 2 + h = Marshal.load(Marshal.dump(h)) + assert_predicate(h, :compare_by_identity?) + a = h.to_a + assert_equal([["a0", 1], ["a0", 2]], a.sort) + assert_not_same(a[1][0], a[0][0]) + end + + def test_hash_compared_by_identity + _test_hash_compared_by_identity(Hash.new) + end + + def test_hash_default_compared_by_identity + _test_hash_compared_by_identity(Hash.new(true)) + end + + class TestMarshalFreeze < Test::Unit::TestCase + include MarshalTestLib + + def encode(o) + Marshal.dump(o) + end + + def decode(s) + Marshal.load(s, freeze: true) + end + + def test_return_objects_are_frozen + source = ["foo", {}, /foo/, 1..2] + objects = decode(encode(source)) + assert_equal source, objects + assert_predicate objects, :frozen? + objects.each do |obj| + assert_predicate obj, :frozen? + end + end + + def test_proc_returned_object_are_not_frozen + source = ["foo", {}, /foo/, 1..2] + objects = Marshal.load(encode(source), ->(o) { o.dup }, freeze: true) + assert_equal source, objects + refute_predicate objects, :frozen? + objects.each do |obj| + refute_predicate obj, :frozen? + end + end + + def test_modules_and_classes_are_not_frozen + _objects = Marshal.load(encode([Object, Kernel]), freeze: true) + refute_predicate Object, :frozen? + refute_predicate Kernel, :frozen? + end + end +end diff --git a/test/ruby/test_math.rb b/test/ruby/test_math.rb new file mode 100644 index 0000000000..e134600cc4 --- /dev/null +++ b/test/ruby/test_math.rb @@ -0,0 +1,397 @@ +# 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_predicate(a, :infinite?, *rest) + assert_predicate(a, :positive?, *rest) + end + + def assert_nan(a, *rest) + rest = ["not nan: #{a.inspect}"] if rest.empty? + assert_predicate(a, :nan?, *rest) + end + + def assert_float(a, b) + err = [Float::EPSILON * 4, [a.abs, b.abs].max * Float::EPSILON * 256].max + assert_in_delta(a, b, err) + end + alias check assert_float + + def assert_float_and_int(exp_ary, act_ary) + flo_exp, int_exp, flo_act, int_act = *exp_ary, *act_ary + assert_float(flo_exp, flo_act) + assert_equal(int_exp, int_act) + end + + def test_atan2 + check(+0.0, Math.atan2(+0.0, +0.0)) + check(-0.0, Math.atan2(-0.0, +0.0)) + check(+Math::PI, Math.atan2(+0.0, -0.0)) + check(-Math::PI, Math.atan2(-0.0, -0.0)) + + 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)) + end + + def test_cos + check(1.0, Math.cos(0 * Math::PI / 4)) + check(1.0 / Math.sqrt(2), Math.cos(1 * Math::PI / 4)) + 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 + check(0.0, Math.sin(0 * Math::PI / 4)) + check(1.0 / Math.sqrt(2), Math.sin(1 * Math::PI / 4)) + check(1.0, Math.sin(2 * Math::PI / 4)) + check(0.0, Math.sin(4 * Math::PI / 4)) + check(-1.0, Math.sin(6 * Math::PI / 4)) + end + + def test_tan + check(0.0, Math.tan(0 * Math::PI / 4)) + check(1.0, Math.tan(1 * Math::PI / 4)) + assert_operator(Math.tan(2 * Math::PI / 4).abs, :>, 1024) + check(0.0, Math.tan(4 * Math::PI / 4)) + assert_operator(Math.tan(6 * Math::PI / 4).abs, :>, 1024) + end + + def test_acos + check(0 * Math::PI / 4, Math.acos( 1.0)) + check(1 * Math::PI / 4, Math.acos( 1.0 / Math.sqrt(2))) + check(2 * Math::PI / 4, Math.acos( 0.0)) + check(4 * Math::PI / 4, Math.acos(-1.0)) + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(-1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacos\b/) { Math.acos(2.0) } + end + + def test_asin + check( 0 * Math::PI / 4, Math.asin( 0.0)) + check( 1 * Math::PI / 4, Math.asin( 1.0 / Math.sqrt(2))) + check( 2 * Math::PI / 4, Math.asin( 1.0)) + check(-2 * Math::PI / 4, Math.asin(-1.0)) + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(-1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\basin\b/) { Math.asin(2.0) } + end + + def test_atan + check( 0 * Math::PI / 4, Math.atan( 0.0)) + check( 1 * Math::PI / 4, Math.atan( 1.0)) + check( 2 * Math::PI / 4, Math.atan(1.0 / 0.0)) + check(-1 * Math::PI / 4, Math.atan(-1.0)) + end + + def test_cosh + check(1, Math.cosh(0)) + check((Math::E ** 1 + Math::E ** -1) / 2, Math.cosh(1)) + check((Math::E ** 2 + Math::E ** -2) / 2, Math.cosh(2)) + end + + def test_sinh + check(0, Math.sinh(0)) + check((Math::E ** 1 - Math::E ** -1) / 2, Math.sinh(1)) + check((Math::E ** 2 - Math::E ** -2) / 2, Math.sinh(2)) + end + + def test_tanh + 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 + check(0, Math.acosh(1)) + check(1, Math.acosh((Math::E ** 1 + Math::E ** -1) / 2)) + check(2, Math.acosh((Math::E ** 2 + Math::E ** -2) / 2)) + assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(1.0 - Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\bacosh\b/) { Math.acosh(0) } + end + + def test_asinh + check(0, Math.asinh(0)) + check(1, Math.asinh((Math::E ** 1 - Math::E ** -1) / 2)) + check(2, Math.asinh((Math::E ** 2 - Math::E ** -2) / 2)) + end + + def test_atanh + check(0, Math.atanh(Math.sinh(0) / Math.cosh(0))) + check(1, Math.atanh(Math.sinh(1) / Math.cosh(1))) + check(2, Math.atanh(Math.sinh(2) / Math.cosh(2))) + assert_nothing_raised { assert_infinity(Math.atanh(1)) } + assert_nothing_raised { assert_infinity(-Math.atanh(-1)) } + assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(+1.0 + Float::EPSILON) } + assert_raise_with_message(Math::DomainError, /\batanh\b/) { Math.atanh(-1.0 - Float::EPSILON) } + end + + def test_exp + check(1, Math.exp(0)) + check(Math.sqrt(Math::E), Math.exp(0.5)) + check(Math::E, Math.exp(1)) + check(Math::E ** 2, Math.exp(2)) + end + + def test_expm1 + check(0, Math.expm1(0)) + check(Math.sqrt(Math::E) - 1, Math.expm1(0.5)) + check(Math::E - 1, Math.expm1(1)) + check(Math::E ** 2 - 1, Math.expm1(2)) + end + + def test_log + check(0, Math.log(1)) + check(1, Math.log(Math::E)) + check(0, Math.log(1, 10)) + check(1, Math.log(10, 10)) + check(2, Math.log(100, 10)) + 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_with_message(Math::DomainError, /\blog\b/) { Math.log(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(-Float::EPSILON) } + assert_raise(TypeError) { Math.log(1,nil) } + assert_raise_with_message(Math::DomainError, /\blog\b/, '[ruby-core:62309] [ruby-Bug #9797]') { Math.log(1.0, -1.0) } + assert_raise_with_message(Math::DomainError, /\blog\b/) { Math.log(1.0, -Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.log(0.0, 0.0)) } + assert_nothing_raised { assert_nan(Math.log(Float::NAN)) } + assert_nothing_raised { assert_nan(Math.log(1.0, Float::NAN)) } + assert_nothing_raised { assert_infinity(-Math.log(0)) } + assert_nothing_raised { assert_infinity(-Math.log(0, 2)) } + check(307.95368556425274, Math.log(2**1023, 10)) + end + + def test_log2 + check(0, Math.log2(1)) + check(1, Math.log2(2)) + check(2, Math.log2(4)) + 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_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog2\b/) { Math.log2(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.log2(Float::NAN)) } + assert_nothing_raised { assert_infinity(-Math.log2(0)) } + end + + def test_log10 + check(0, Math.log10(1)) + check(1, Math.log10(10)) + check(2, Math.log10(100)) + 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_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-1.0) } + assert_raise_with_message(Math::DomainError, /\blog10\b/) { Math.log10(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.log10(Float::NAN)) } + assert_nothing_raised { assert_infinity(-Math.log10(0)) } + end + + def test_log1p + check(0, Math.log1p(0)) + check(1, Math.log1p(Math::E - 1)) + check(Math.log(2.0 ** 64 + 1), Math.log1p(1 << 64)) + check(Math.log(2) * 1024.0, Math.log1p(2 ** 1024)) + assert_nothing_raised { assert_infinity(Math.log1p(1.0/0)) } + assert_nothing_raised { assert_infinity(-Math.log1p(-1.0)) } + assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-1.1) } + assert_raise_with_message(Math::DomainError, /\blog1p\b/) { Math.log1p(-Float::EPSILON-1) } + assert_nothing_raised { assert_nan(Math.log1p(Float::NAN)) } + assert_nothing_raised { assert_infinity(-Math.log1p(-1)) } + end + + def test_sqrt + check(0, Math.sqrt(0)) + check(1, Math.sqrt(1)) + check(2, Math.sqrt(4)) + assert_nothing_raised { assert_infinity(Math.sqrt(1.0/0)) } + assert_equal("0.0", Math.sqrt(-0.0).to_s) # insure it is +0.0, not -0.0 + assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-1.0) } + assert_raise_with_message(Math::DomainError, /\bsqrt\b/) { Math.sqrt(-Float::EPSILON) } + assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) } + end + + def test_cbrt + check(1, Math.cbrt(1)) + check(-2, Math.cbrt(-8)) + check(3, Math.cbrt(27)) + check(-0.1, Math.cbrt(-0.001)) + check(0.0, Math.cbrt(0.0)) + assert_nothing_raised { assert_infinity(Math.cbrt(1.0/0)) } + assert_operator(Math.cbrt(1.0 - Float::EPSILON), :<=, 1.0) + assert_nothing_raised { assert_nan(Math.sqrt(Float::NAN)) } + assert_nothing_raised { assert_nan(Math.cbrt(Float::NAN)) } + end + + def test_frexp + 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)) + assert_nan(Math.frexp(Float::NAN)[0]) + end + + def test_ldexp + check(0.0, Math.ldexp(0.0, 0.0)) + check(0.5, Math.ldexp(0.5, 0.0)) + check(1.0, Math.ldexp(0.5, 1.0)) + check(2.0, Math.ldexp(0.5, 2.0)) + check(3.0, Math.ldexp(0.75, 2.0)) + end + + def test_hypot + check(5, Math.hypot(3, 4)) + end + + def test_erf + check(0, Math.erf(0)) + check(1, Math.erf(1.0 / 0.0)) + assert_nan(Math.erf(Float::NAN)) + end + + def test_erfc + check(1, Math.erfc(0)) + check(0, Math.erfc(1.0 / 0.0)) + assert_nan(Math.erfc(Float::NAN)) + end + + def test_gamma + sqrt_pi = Math.sqrt(Math::PI) + check(4 * sqrt_pi / 3, Math.gamma(-1.5)) + check(-2 * sqrt_pi, Math.gamma(-0.5)) + check(sqrt_pi, Math.gamma(0.5)) + check(1, Math.gamma(1)) + check(sqrt_pi / 2, Math.gamma(1.5)) + check(1, Math.gamma(2)) + check(3 * sqrt_pi / 4, Math.gamma(2.5)) + 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| + i = 1 << i + assert_infinity(Math.gamma(i), "Math.gamma(#{i}) should be INF") + assert_infinity(Math.gamma(i-1), "Math.gamma(#{i-1}) should be INF") + end + + assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-Float::INFINITY) } + assert_raise_with_message(Math::DomainError, /\bgamma\b/) { Math.gamma(-1.0) } + x = Math.gamma(-0.0) + mesg = "Math.gamma(-0.0) should be -INF" + assert_infinity(-x, mesg) + assert_nan(Math.gamma(Float::NAN)) + 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)) + + assert_raise_with_message(Math::DomainError, /\blgamma\b/) { Math.lgamma(-Float::INFINITY) } + + x, sign = Math.lgamma(+0.0) + mesg = "Math.lgamma(+0.0) should be [INF, +1]" + assert_infinity(x, mesg) + assert_equal(+1, sign, mesg) + + x, sign = Math.lgamma(-0.0) + mesg = "Math.lgamma(-0.0) should be [INF, -1]" + assert_infinity(x, mesg) + assert_equal(-1, sign, mesg) + + x, = Math.lgamma(-1) + assert_infinity(x, "Math.lgamma(-1) should be +INF") + + x, = Math.lgamma(Float::NAN) + assert_nan(x) + end + + def test_fixnum_to_f + check(12.0, Math.sqrt(144)) + end + + def test_override_integer_to_f + Integer.class_eval do + alias _to_f to_f + def to_f + (self + 1)._to_f + end + end + + 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 + + def test_bignum_to_f + check((1 << 65).to_f, Math.sqrt(1 << 130)) + end + + def test_override_bignum_to_f + Integer.class_eval do + alias _to_f to_f + def to_f + (self << 1)._to_f + end + end + + 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 + + def test_rational_to_f + check((2 ** 31).fdiv(3 ** 20), Math.sqrt((2 ** 62)/(3 ** 40).to_r)) + end + + 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_memory_view.rb b/test/ruby/test_memory_view.rb new file mode 100644 index 0000000000..d0122ddd59 --- /dev/null +++ b/test/ruby/test_memory_view.rb @@ -0,0 +1,341 @@ +require "-test-/memory_view" +require "rbconfig/sizeof" + +class TestMemoryView < Test::Unit::TestCase + NATIVE_ENDIAN = MemoryViewTestUtils::NATIVE_ENDIAN + LITTLE_ENDIAN = :little_endian + BIG_ENDIAN = :big_endian + + %I(SHORT INT INT16 INT32 INT64 INTPTR LONG LONG_LONG FLOAT DOUBLE).each do |type| + name = :"#{type}_ALIGNMENT" + const_set(name, MemoryViewTestUtils.const_get(name)) + end + + def test_rb_memory_view_register_duplicated + assert_warning(/Duplicated registration of memory view to/) do + MemoryViewTestUtils.register(MemoryViewTestUtils::ExportableString) + end + end + + def test_rb_memory_view_register_nonclass + assert_raise(TypeError) do + MemoryViewTestUtils.register(Object.new) + end + end + + def sizeof(type) + RbConfig::SIZEOF[type.to_s] + end + + def test_rb_memory_view_item_size_from_format + [ + [nil, 1], ['c', 1], ['C', 1], + ['n', 2], ['v', 2], + ['l', 4], ['L', 4], ['N', 4], ['V', 4], ['f', 4], ['e', 4], ['g', 4], + ['q', 8], ['Q', 8], ['d', 8], ['E', 8], ['G', 8], + ['s', sizeof(:short)], ['S', sizeof(:short)], ['s!', sizeof(:short)], ['S!', sizeof(:short)], + ['i', sizeof(:int)], ['I', sizeof(:int)], ['i!', sizeof(:int)], ['I!', sizeof(:int)], + ['l!', sizeof(:long)], ['L!', sizeof(:long)], + ['q!', sizeof('long long')], ['Q!', sizeof('long long')], + ['j', sizeof(:intptr_t)], ['J', sizeof(:intptr_t)], + ].each do |format, expected| + actual, err = MemoryViewTestUtils.item_size_from_format(format) + assert_nil(err) + assert_equal(expected, actual, "rb_memory_view_item_size_from_format(#{format || 'NULL'}) == #{expected}") + end + end + + def test_rb_memory_view_item_size_from_format_composed + actual, = MemoryViewTestUtils.item_size_from_format("ccc") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("c3") + assert_equal(3, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fd") + assert_equal(12, actual) + + actual, = MemoryViewTestUtils.item_size_from_format("fx2d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_with_spaces + # spaces should be ignored + actual, = MemoryViewTestUtils.item_size_from_format("f x2 d") + assert_equal(14, actual) + end + + def test_rb_memory_view_item_size_from_format_error + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccca")) + assert_equal([-1, "a"], MemoryViewTestUtils.item_size_from_format("ccc4a")) + end + + def test_rb_memory_view_parse_item_format + total_size, members, err = MemoryViewTestUtils.parse_item_format("ccc2f3x2d4q!<") + assert_equal(58, total_size) + assert_nil(err) + assert_equal([ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 4, size: 4, repeat: 3}, + {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 18, size: 8, repeat: 4}, + {format: 'q', native_size_p: true, endianness: :little_endian, offset: 50, size: sizeof('long long'), repeat: 1} + ], + members) + end + + def test_rb_memory_view_parse_item_format_with_alignment_signle + [ + ["c", false, NATIVE_ENDIAN, 1, 1, 1], + ["C", false, NATIVE_ENDIAN, 1, 1, 1], + ["s", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S", false, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["s!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["S!", true, NATIVE_ENDIAN, SHORT_ALIGNMENT, sizeof(:short), 1], + ["n", false, :big_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["v", false, :little_endian, INT16_ALIGNMENT, sizeof(:int16_t), 1], + ["i", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I", false, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["i!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["I!", true, NATIVE_ENDIAN, INT_ALIGNMENT, sizeof(:int), 1], + ["l", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["L", false, NATIVE_ENDIAN, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["l!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["L!", true, NATIVE_ENDIAN, LONG_ALIGNMENT, sizeof(:long), 1], + ["N", false, :big_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["V", false, :little_endian, INT32_ALIGNMENT, sizeof(:int32_t), 1], + ["f", false, NATIVE_ENDIAN, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["e", false, :little_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["g", false, :big_endian, FLOAT_ALIGNMENT, sizeof(:float), 1], + ["q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["Q", false, NATIVE_ENDIAN, INT64_ALIGNMENT, sizeof(:int64_t), 1], + ["q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["Q!", true, NATIVE_ENDIAN, LONG_LONG_ALIGNMENT, sizeof("long long"), 1], + ["d", false, NATIVE_ENDIAN, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["E", false, :little_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["G", false, :big_endian, DOUBLE_ALIGNMENT, sizeof(:double), 1], + ["j", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ["J", false, NATIVE_ENDIAN, INTPTR_ALIGNMENT, sizeof(:intptr_t), 1], + ].each do |type, native_size_p, endianness, alignment, size, repeat, total_size| + total_size, members, err = MemoryViewTestUtils.parse_item_format("|c#{type}") + assert_nil(err) + + padding_size = alignment - 1 + expected_total_size = 1 + padding_size + size + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: type[0], native_size_p: native_size_p, endianness: endianness, offset: alignment, size: size, repeat: repeat}, + ] + assert_equal(expected_result, members) + end + end + + def alignment_padding(total_size, alignment) + res = total_size % alignment + if res > 0 + alignment - res + else + 0 + end + end + + def test_rb_memory_view_parse_item_format_with_alignment_total_size_with_tail_padding + total_size, _members, err = MemoryViewTestUtils.parse_item_format("|lqc") + assert_nil(err) + + expected_total_size = sizeof(:int32_t) + expected_total_size += alignment_padding(expected_total_size, INT32_ALIGNMENT) + expected_total_size += sizeof(:int64_t) + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + expected_total_size += 1 + expected_total_size += alignment_padding(expected_total_size, INT64_ALIGNMENT) + assert_equal(expected_total_size, total_size) + end + + def test_rb_memory_view_parse_item_format_with_alignment_compound + total_size, members, err = MemoryViewTestUtils.parse_item_format("|ccc2f3x2d4cq!<") + assert_nil(err) + + expected_total_size = 1 + 1 + 1*2 + expected_total_size += alignment_padding(expected_total_size, FLOAT_ALIGNMENT) + expected_total_size += sizeof(:float)*3 + 1*2 + expected_total_size += alignment_padding(expected_total_size, DOUBLE_ALIGNMENT) + expected_total_size += sizeof(:double)*4 + 1 + expected_total_size += alignment_padding(expected_total_size, LONG_LONG_ALIGNMENT) + expected_total_size += sizeof("long long") + assert_equal(expected_total_size, total_size) + + expected_result = [ + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 0, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 1, size: 1, repeat: 1}, + {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: 2, size: 1, repeat: 2}, + ] + offset = 4 + + res = offset % FLOAT_ALIGNMENT + offset += FLOAT_ALIGNMENT - res if res > 0 + expected_result << {format: 'f', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 4, repeat: 3} + offset += 12 + + offset += 2 # 2x + + res = offset % DOUBLE_ALIGNMENT + offset += DOUBLE_ALIGNMENT - res if res > 0 + expected_result << {format: 'd', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 8, repeat: 4} + offset += 32 + + expected_result << {format: 'c', native_size_p: false, endianness: NATIVE_ENDIAN, offset: offset, size: 1, repeat: 1} + offset += 1 + + res = offset % LONG_LONG_ALIGNMENT + offset += LONG_LONG_ALIGNMENT - res if res > 0 + expected_result << {format: 'q', native_size_p: true, endianness: :little_endian, offset: offset, size: 8, repeat: 1} + + assert_equal(expected_result, members) + end + + def test_rb_memory_view_extract_item_members + m = MemoryViewTestUtils + assert_equal(1, m.extract_item_members([1].pack("c"), "c")) + assert_equal([1, 2], m.extract_item_members([1, 2].pack("ii"), "ii")) + assert_equal([1, 2, 3], m.extract_item_members([1, 2, 3].pack("cls"), "cls")) + end + + def test_rb_memory_view_extract_item_members_endianness + m = MemoryViewTestUtils + assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S>2")) + assert_equal([0x0102, 0x0304], m.extract_item_members([1, 2, 3, 4].pack("c*"), "n2")) + assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "S<2")) + assert_equal([0x0201, 0x0403], m.extract_item_members([1, 2, 3, 4].pack("c*"), "v2")) + assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L>")) + assert_equal(0x01020304, m.extract_item_members([1, 2, 3, 4].pack("c*"), "N")) + assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "L<")) + assert_equal(0x04030201, m.extract_item_members([1, 2, 3, 4].pack("c*"), "V")) + assert_equal(0x0102030405060708, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q>")) + assert_equal(0x0807060504030201, m.extract_item_members([1, 2, 3, 4, 5, 6, 7, 8].pack("c*"), "Q<")) + end + + def test_rb_memory_view_extract_item_members_float + m = MemoryViewTestUtils + packed = [1.23].pack("f") + assert_equal(packed.unpack("f")[0], m.extract_item_members(packed, "f")) + end + + def test_rb_memory_view_extract_item_members_float_endianness + m = MemoryViewTestUtils + hi, lo = [1.23].pack("f").unpack("L")[0].divmod(0x10000) + packed = [lo, hi].pack("S*") + assert_equal(packed.unpack("e")[0], m.extract_item_members(packed, "e")) + packed = [hi, lo].pack("S*") + assert_equal(packed.unpack("g")[0], m.extract_item_members(packed, "g")) + end + + def test_rb_memory_view_extract_item_members_doble + m = MemoryViewTestUtils + packed = [1.23].pack("d") + assert_equal(1.23, m.extract_item_members(packed, "d")) + end + + def test_rb_memory_view_extract_item_members_doble_endianness + m = MemoryViewTestUtils + hi, lo = [1.23].pack("d").unpack("Q")[0].divmod(0x10000) + packed = [lo, hi].pack("L*") + assert_equal(packed.unpack("E")[0], m.extract_item_members(packed, "E")) + packed = [hi, lo].pack("L*") + assert_equal(packed.unpack("G")[0], m.extract_item_members(packed, "G")) + end + + def test_rb_memory_view_available_p + es = MemoryViewTestUtils::ExportableString.new("ruby") + assert_equal(true, MemoryViewTestUtils.available?(es)) + es = MemoryViewTestUtils::ExportableString.new(nil) + assert_equal(false, MemoryViewTestUtils.available?(es)) + end + + def test_ref_count_with_exported_object + es = MemoryViewTestUtils::ExportableString.new("ruby") + assert_equal(1, MemoryViewTestUtils.ref_count_while_exporting(es, 1)) + assert_equal(2, MemoryViewTestUtils.ref_count_while_exporting(es, 2)) + assert_equal(10, MemoryViewTestUtils.ref_count_while_exporting(es, 10)) + assert_nil(MemoryViewTestUtils.ref_count_while_exporting(es, 0)) + end + + def test_rb_memory_view_init_as_byte_array + # ExportableString's memory view is initialized by rb_memory_view_init_as_byte_array + es = MemoryViewTestUtils::ExportableString.new("ruby") + memory_view_info = MemoryViewTestUtils.get_memory_view_info(es) + assert_equal({ + obj: es, + byte_size: 4, + readonly: true, + format: nil, + item_size: 1, + ndim: 1, + shape: nil, + strides: nil, + sub_offsets: nil + }, + memory_view_info) + end + + def test_rb_memory_view_get_with_memory_view_unavailable_object + es = MemoryViewTestUtils::ExportableString.new(nil) + memory_view_info = MemoryViewTestUtils.get_memory_view_info(es) + assert_nil(memory_view_info) + end + + def test_rb_memory_view_fill_contiguous_strides + row_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], true) + assert_equal([96, 32, 8], + row_major_strides) + + column_major_strides = MemoryViewTestUtils.fill_contiguous_strides(3, 8, [2, 3, 4], false) + assert_equal([8, 16, 48], + column_major_strides) + end + + def test_rb_memory_view_get_item_pointer_single_member + buf = [ 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12 ].pack("l!*") + shape = [3, 4] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "l!", shape, nil) + assert_equal(1, mv[[0, 0]]) + assert_equal(4, mv[[0, 3]]) + assert_equal(6, mv[[1, 1]]) + assert_equal(10, mv[[2, 1]]) + end + + def test_rb_memory_view_get_item_pointer_multiple_members + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + -1, -2, -3, -4, -5, -6, -7, -8].pack("s*") + shape = [2, 4] + strides = [4*sizeof(:short)*2, sizeof(:short)*2] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides) + assert_equal([1, 2], mv[[0, 0]]) + assert_equal([5, 6], mv[[0, 2]]) + assert_equal([-1, -2], mv[[1, 0]]) + assert_equal([-7, -8], mv[[1, 3]]) + end + + def test_ractor + assert_in_out_err([], <<-"end;", ["[5, 6]", "[-7, -8]"], []) + require "-test-/memory_view" + require "rbconfig/sizeof" + $VERBOSE = nil + r = Ractor.new RbConfig::SIZEOF["short"] do |sizeof_short| + buf = [ 1, 2, 3, 4, 5, 6, 7, 8, + -1, -2, -3, -4, -5, -6, -7, -8].pack("s*") + shape = [2, 4] + strides = [4*sizeof_short*2, sizeof_short*2] + mv = MemoryViewTestUtils::MultiDimensionalView.new(buf, "ss", shape, strides) + p mv[[0, 2]] + mv[[1, 3]] + end + p r.value + end; + end +end diff --git a/test/ruby/test_metaclass.rb b/test/ruby/test_metaclass.rb new file mode 100644 index 0000000000..8c1990a78c --- /dev/null +++ b/test/ruby/test_metaclass.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestMetaclass < Test::Unit::TestCase + class Foo; end + class Bar < Foo; end + class Baz; end + + def setup + Object.class_eval do + def method_o; end + end + Module.class_eval do + def method_m; end + end + Class.class_eval do + def method_c; end + end + end + def teardown + Object.class_eval do + remove_method :method_o rescue nil + end + Module.class_eval do + remove_method :method_m rescue nil + end + Class.class_eval do + remove_method :method_c rescue nil + end + Object.class_eval do + class << self + remove_method :class_method_o rescue nil + end + end + Module.class_eval do + class << self + remove_method :class_method_m rescue nil + end + end + Class.class_eval do + class << self + remove_method :class_method_c rescue nil + end + end + Object.class_eval do + class << self + class << self + remove_method :metaclass_method_o rescue nil + end + end + end + Module.class_eval do + class << self + class << self + remove_method :metaclass_method_m rescue nil + end + end + end + Class.class_eval do + class << self + class << self + remove_method :metaclass_method_c rescue nil + end + end + end + end + + def test_metaclass + class << Object + def class_method_o; end + end + class << Foo + def class_method_f; end + end + class << Baz + def class_method_b; end + end + assert_nothing_raised{ Bar.method_o } + assert_nothing_raised{ Bar.method_m } + assert_nothing_raised{ Bar.method_c } + assert_nothing_raised{ Bar.class_method_o } + assert_nothing_raised{ Bar.class_method_f } + assert_raise(NoMethodError){ Bar.class_method_b } + + class << Module + def class_method_m; end + end + class << Class + def class_method_c; end + end + class << Object + class << self + def metaclass_method_o; end + end + end + class << Foo + class << self + def metaclass_method_f; end + end + end + class << Baz + class << self + def metaclass_method_b; end + end + end + metaclass_of_bar = class << Bar; self end + assert_nothing_raised{ metaclass_of_bar.method_o } + assert_nothing_raised{ metaclass_of_bar.method_m } + assert_nothing_raised{ metaclass_of_bar.method_c } + assert_nothing_raised{ metaclass_of_bar.class_method_o } + assert_raise(NoMethodError){ metaclass_of_bar.class_method_f } + assert_raise(NoMethodError){ metaclass_of_bar.class_method_b } + assert_nothing_raised{ metaclass_of_bar.class_method_m } + assert_nothing_raised{ metaclass_of_bar.class_method_c } + assert_nothing_raised{ metaclass_of_bar.metaclass_method_o } + assert_nothing_raised{ metaclass_of_bar.metaclass_method_f } + assert_raise(NoMethodError){ metaclass_of_bar.metaclass_method_b } + + class << Module + class << self + def metaclass_method_m; end + end + end + class << Class + class << self + def metaclass_method_c; end + end + end + class << Object + class << self + class << self + def metametaclass_method_o; end + end + end + end + class << Foo + class << self + class << self + def metametaclass_method_f; end + end + end + end + class << Baz + class << self + class << self + def metametaclass_method_b; end + end + end + end + metametaclass_of_bar = class << metaclass_of_bar; self end + assert_nothing_raised{ metametaclass_of_bar.method_o } + assert_nothing_raised{ metametaclass_of_bar.method_m } + assert_nothing_raised{ metametaclass_of_bar.method_c } + assert_nothing_raised{ metametaclass_of_bar.class_method_o } + assert_raise(NoMethodError){ metametaclass_of_bar.class_method_f } + assert_raise(NoMethodError){ metametaclass_of_bar.class_method_b } + assert_nothing_raised{ metametaclass_of_bar.class_method_m } + assert_nothing_raised{ metametaclass_of_bar.class_method_c } + assert_nothing_raised{ metametaclass_of_bar.metaclass_method_o } + assert_raise(NoMethodError){ metametaclass_of_bar.metaclass_method_f } + assert_raise(NoMethodError){ metametaclass_of_bar.metaclass_method_b } + assert_nothing_raised{ metametaclass_of_bar.metaclass_method_m } + assert_nothing_raised{ metametaclass_of_bar.metaclass_method_c } + assert_nothing_raised{ metametaclass_of_bar.metametaclass_method_o } + assert_nothing_raised{ metametaclass_of_bar.metametaclass_method_f } + assert_raise(NoMethodError){ metametaclass_of_bar.metaclass_method_b } + end +end diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb new file mode 100644 index 0000000000..8561f841a8 --- /dev/null +++ b/test/ruby/test_method.rb @@ -0,0 +1,1893 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestMethod < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $VERBOSE = @verbose + end + + def m0() end + def m1(a) end + def m2(a, b) end + def mo1(a = nil, &b) end + def mo2(a, b = nil) end + def mo3(*a) end + def mo4(a, *b, &c) end + def mo5(a, *b, c) end + def mo6(a, *b, c, &d) end + def mo7(a, b = nil, *c, d, &e) end + def mo8(a, b = nil, *, d, &e) end + def ma1((a), &b) nil && a end + def mk1(**) end + def mk2(**o) nil && o end + 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 + def mk8(a, b = nil, *c, d, e:, f: nil, **o) nil && o end + def mnk(**nil) end + def mf(...) end + + class Base + def foo() :base end + end + class Derived < Base + def foo() :derived end + 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; :meth end + end + + def mv1() end + def mv2() end + private :mv2 + def mv3() end + protected :mv3 + + class Visibility + def mv1() end + def mv2() end + private :mv2 + def mv3() end + protected :mv3 + end + + def test_arity + assert_equal(0, method(:m0).arity) + assert_equal(1, method(:m1).arity) + assert_equal(2, method(:m2).arity) + assert_equal(-1, method(:mo1).arity) + assert_equal(-2, method(:mo2).arity) + assert_equal(-1, method(:mo3).arity) + 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 + assert_equal(-1, method(:__send__).arity) + end + + def test_unbind + assert_equal(:derived, Derived.new.foo) + um = Derived.new.method(:foo).unbind + assert_instance_of(UnboundMethod, um) + Derived.class_eval do + def foo() :changed end + end + assert_equal(:changed, Derived.new.foo) + assert_equal(:derived, um.bind(Derived.new).call) + assert_raise(TypeError) do + um.bind(Base.new) + end + + # cleanup + Derived.class_eval do + remove_method :foo + def foo() :derived; end + end + end + + def test_callee + assert_equal(:test_callee, __method__) + assert_equal(:m, Class.new {def m; __method__; end}.new.m) + assert_equal(:m, Class.new {def m; tap{return __method__}; end}.new.m) + 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 + o = Object.new + 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 + c1 = Class.new + c1.class_eval { def foo; :foo; end } + c2 = Class.new(c1) + c2.class_eval { private :foo } + o = c2.new + o.extend(Module.new) + assert_raise(NameError) { o.method(:bar) } + assert_raise(NameError) { o.public_method(:foo) } + assert_equal(:foo, o.method(:foo).call) + end + + def test_eq + o = Object.new + class << o + def foo; end + alias bar foo + def baz; end + end + assert_not_equal(o.method(:foo), nil) + m = o.method(:foo) + def m.foo; end + assert_not_equal(o.method(:foo), m) + assert_equal(o.method(:foo), o.method(:foo)) + assert_equal(o.method(:foo), o.method(:bar)) + assert_not_equal(o.method(:foo), o.method(:baz)) + end + + def test_hash + 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_hash_does_not_change_after_compaction + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + # iseq backed method + assert_separately([], <<~RUBY) + def a; end + + # Need this method here because otherwise the iseq may be on the C stack + # which would get pinned and not move during compaction + def get_hash + method(:a).hash + end + + hash = get_hash + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(hash, get_hash) + RUBY + end + + def test_owner + 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 + o = Object.new + def o.foo; end + m = o.method(:foo) + assert_equal(o, m.receiver) + assert_equal(:foo, m.name) + 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 + c = Class.new + c.class_eval do + def foo; :foo; end + private :foo + end + o = c.new + o.method(:foo).unbind + assert_raise(NoMethodError) { o.foo } + c.instance_method(:foo).bind(o) + assert_equal(:foo, o.instance_eval { foo }) + assert_raise(NameError) { c.public_instance_method(:foo) } + def o.bar; end + m = o.method(:bar).unbind + assert_raise(TypeError) { m.bind(Object.new) } + + cx = EnvUtil.labeled_class("X\u{1f431}") + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /X\u{1f431}/) do + o.method(cx) + end + 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 + c = Class.new + c.class_eval { def foo; :foo; end } + o = c.new + def o.bar; :bar; end + assert_raise(TypeError) do + c.class_eval { define_method(:foo, :foo) } + end + assert_raise(ArgumentError) do + c.class_eval { define_method } + end + c2 = Class.new(c) + c2.class_eval { define_method(:baz, o.method(:foo)) } + assert_equal(:foo, c2.new.baz) + assert_raise(TypeError) do + Class.new.class_eval { define_method(:foo, o.method(:foo)) } + end + assert_raise(TypeError) do + Class.new.class_eval { define_method(:bar, o.method(:bar)) } + end + + cx = EnvUtil.labeled_class("X\u{1f431}") + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /X\u{1F431}/) do + Class.new {define_method(cx) {}} + end + 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 + 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 + + PUBLIC_SINGLETON_TEST = Object.new + class << PUBLIC_SINGLETON_TEST + private + PUBLIC_SINGLETON_TEST.define_singleton_method(:dsm){} + def PUBLIC_SINGLETON_TEST.def; end + end + def test_define_singleton_method_public + assert_nil(PUBLIC_SINGLETON_TEST.dsm) + assert_nil(PUBLIC_SINGLETON_TEST.def) + end + + def test_define_singleton_method_no_proc + o = Object.new + assert_raise(ArgumentError) { + 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 + o = Object.new + def o.foo; :foo; end + m = o.method(:foo) + def m.bar; :bar; end + assert_equal(:foo, m.clone.call) + assert_equal(:bar, m.clone.bar) + end + + def test_clone_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + o = Object.new + def o.foo; :foo; end + m = o.method(:foo) + def m.bar; :bar; end + assert_equal(:foo, m.clone.call) + assert_equal(:bar, m.clone.bar) + end + end + + def test_inspect + o = Object.new + def o.foo; end; line_no = __LINE__ + m = o.method(:foo) + assert_equal("#<Method: #{ o.inspect }.foo() #{__FILE__}:#{line_no}>", m.inspect) + m = o.method(:foo) + assert_match("#<UnboundMethod: #{ class << o; self; end.inspect }#foo() #{__FILE__}:#{line_no}", m.unbind.inspect) + + c = Class.new + c.class_eval { def foo; end; }; line_no = __LINE__ + m = c.new.method(:foo) + assert_equal("#<Method: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect) + m = c.instance_method(:foo) + assert_equal("#<UnboundMethod: #{ c.inspect }#foo() #{__FILE__}:#{line_no}>", m.inspect) + + c2 = Class.new(c) + c2.class_eval { private :foo } + m2 = c2.new.method(:foo) + assert_equal("#<Method: #{ c2.inspect }(#{ c.inspect })#foo() #{__FILE__}:#{line_no}>", m2.inspect) + + bug7806 = '[ruby-core:52048] [Bug #7806]' + c3 = Class.new(c) + c3.class_eval { alias bar foo } + m3 = c3.new.method(:bar) + assert_equal("#<Method: #{c3.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m3.inspect, bug7806) + + bug15608 = '[ruby-core:91570] [Bug #15608]' + c4 = Class.new(c) + c4.class_eval { alias bar foo } + o = c4.new + o.singleton_class + m4 = o.method(:bar) + assert_equal("#<Method: #{c4.inspect}(#{c.inspect})#bar(foo)() #{__FILE__}:#{line_no}>", m4.inspect, bug15608) + + bug17428 = '[ruby-core:101635] [Bug #17428]' + assert_equal("#<Method: #<Class:String>(Module)#prepend(*)>", String.method(:prepend).inspect, bug17428) + + c5 = Class.new(String) + m = Module.new{def prepend; end; alias prep prepend}; line_no = __LINE__ + c5.extend(m) + c6 = Class.new(c5) + assert_equal("#<Method: #<Class:#{c6.inspect}>(#{m.inspect})#prep(prepend)() #{__FILE__}:#{line_no}>", c6.method(:prep).inspect, bug17428) + end + + def test_callee_top_level + assert_in_out_err([], "p __callee__", %w(nil), []) + end + + def test_caller_top_level + assert_in_out_err([], "p caller", %w([]), []) + end + + def test_caller_negative_level + assert_raise(ArgumentError) { caller(-1) } + end + + def test_attrset_ivar + c = Class.new + c.class_eval { attr_accessor :foo } + o = c.new + o.method(:foo=).call(42) + assert_equal(42, o.foo) + assert_raise(ArgumentError) { o.method(:foo=).call(1, 2, 3) } + assert_raise(ArgumentError) { o.method(:foo).call(1) } + end + + def test_default_accessibility + 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 + + def test_respond_to_missing_argument + obj = Struct.new(:mid).new + def obj.respond_to_missing?(id, *) + self.mid = id + true + end + assert_kind_of(Method, obj.method("bug15640")) + assert_kind_of(Symbol, obj.mid) + assert_equal("bug15640", obj.mid.to_s) + + arg = Struct.new(:to_str).new("bug15640_2") + assert_kind_of(Method, obj.method(arg)) + assert_kind_of(Symbol, obj.mid) + assert_equal("bug15640_2", obj.mid.to_s) + end + + define_method(:pm0) {||} + define_method(:pm1) {|a|} + define_method(:pm2) {|a, b|} + define_method(:pmo1) {|a = nil, &b|} + define_method(:pmo2) {|a, b = nil|} + define_method(:pmo3) {|*a|} + define_method(:pmo4) {|a, *b, &c|} + 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| 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|} + define_method(:pmk8) {|a, b = nil, *c, d, e:, f: nil, **o|} + define_method(:pmnk) {|**nil|} + + def test_bound_parameters + assert_equal([], method(:m0).parameters) + assert_equal([[:req, :a]], method(:m1).parameters) + assert_equal([[:req, :a], [:req, :b]], method(:m2).parameters) + assert_equal([[:opt, :a], [:block, :b]], method(:mo1).parameters) + assert_equal([[:req, :a], [:opt, :b]], method(:mo2).parameters) + assert_equal([[:rest, :a]], method(:mo3).parameters) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], method(:mo4).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:mo5).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:mo6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:mo7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], method(:mo8).parameters) + assert_equal([[:req], [:block, :b]], method(:ma1).parameters) + assert_equal([[:keyrest, :**]], method(:mk1).parameters) + assert_equal([[:keyrest, :o]], method(:mk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], method(:mk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], method(:mk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], method(:mk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], method(:mk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], method(:mk7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:mk8).parameters) + assert_equal([[:nokey]], method(:mnk).parameters) + # pending + assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], method(:mf).parameters) + end + + def test_unbound_parameters + assert_equal([], self.class.instance_method(:m0).parameters) + assert_equal([[:req, :a]], self.class.instance_method(:m1).parameters) + assert_equal([[:req, :a], [:req, :b]], self.class.instance_method(:m2).parameters) + assert_equal([[:opt, :a], [:block, :b]], self.class.instance_method(:mo1).parameters) + assert_equal([[:req, :a], [:opt, :b]], self.class.instance_method(:mo2).parameters) + assert_equal([[:rest, :a]], self.class.instance_method(:mo3).parameters) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], self.class.instance_method(:mo4).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:mo5).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], self.class.instance_method(:mo6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], self.class.instance_method(:mo7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :*], [:req, :d], [:block, :e]], self.class.instance_method(:mo8).parameters) + assert_equal([[:req], [:block, :b]], self.class.instance_method(:ma1).parameters) + assert_equal([[:keyrest, :**]], self.class.instance_method(:mk1).parameters) + assert_equal([[:keyrest, :o]], self.class.instance_method(:mk2).parameters) + assert_equal([[:req, :a], [:keyrest, :o]], self.class.instance_method(:mk3).parameters) + assert_equal([[:opt, :a], [:keyrest, :o]], self.class.instance_method(:mk4).parameters) + assert_equal([[:req, :a], [:opt, :b], [:keyrest, :o]], self.class.instance_method(:mk5).parameters) + assert_equal([[:req, :a], [:opt, :b], [:req, :c], [:keyrest, :o]], self.class.instance_method(:mk6).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyrest, :o]], self.class.instance_method(:mk7).parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:mk8).parameters) + assert_equal([[:nokey]], self.class.instance_method(:mnk).parameters) + # pending + assert_equal([[:rest, :*], [:keyrest, :**], [:block, :&]], self.class.instance_method(:mf).parameters) + end + + def test_bmethod_bound_parameters + assert_equal([], method(:pm0).parameters) + assert_equal([[:req, :a]], method(:pm1).parameters) + assert_equal([[:req, :a], [:req, :b]], method(:pm2).parameters) + assert_equal([[:opt, :a], [:block, :b]], method(:pmo1).parameters) + assert_equal([[:req, :a], [:opt, :b]], method(:pmo2).parameters) + assert_equal([[:rest, :a]], method(:pmo3).parameters) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], method(:pmo4).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:pmo5).parameters) + 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) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], method(:pmk8).parameters) + assert_equal([[:nokey]], method(:pmnk).parameters) + end + + def test_bmethod_unbound_parameters + assert_equal([], self.class.instance_method(:pm0).parameters) + assert_equal([[:req, :a]], self.class.instance_method(:pm1).parameters) + assert_equal([[:req, :a], [:req, :b]], self.class.instance_method(:pm2).parameters) + assert_equal([[:opt, :a], [:block, :b]], self.class.instance_method(:pmo1).parameters) + assert_equal([[:req, :a], [:opt, :b]], self.class.instance_method(:pmo2).parameters) + assert_equal([[:rest, :a]], self.class.instance_method(:pmo3).parameters) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], self.class.instance_method(:pmo4).parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], self.class.instance_method(:pmo5).parameters) + 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) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:key, :f], [:keyrest, :o]], self.class.instance_method(:pmk8).parameters) + assert_equal([[:nokey]], self.class.instance_method(:pmnk).parameters) + end + + def test_hidden_parameters + instance_eval("def m((_)"+",(_)"*256+");end") + assert_empty(method(:m).parameters.map{|_,n|n}.compact) + end + + def test_method_parameters_inspect + assert_include(method(:m0).inspect, "()") + assert_include(method(:m1).inspect, "(a)") + assert_include(method(:m2).inspect, "(a, b)") + assert_include(method(:mo1).inspect, "(a=..., &b)") + assert_include(method(:mo2).inspect, "(a, b=...)") + assert_include(method(:mo3).inspect, "(*a)") + assert_include(method(:mo4).inspect, "(a, *b, &c)") + assert_include(method(:mo5).inspect, "(a, *b, c)") + assert_include(method(:mo6).inspect, "(a, *b, c, &d)") + assert_include(method(:mo7).inspect, "(a, b=..., *c, d, &e)") + assert_include(method(:mo8).inspect, "(a, b=..., *, d, &e)") + assert_include(method(:ma1).inspect, "(_, &b)") + assert_include(method(:mk1).inspect, "(**)") + assert_include(method(:mk2).inspect, "(**o)") + assert_include(method(:mk3).inspect, "(a, **o)") + assert_include(method(:mk4).inspect, "(a=..., **o)") + assert_include(method(:mk5).inspect, "(a, b=..., **o)") + assert_include(method(:mk6).inspect, "(a, b=..., c, **o)") + assert_include(method(:mk7).inspect, "(a, b=..., *c, d, **o)") + assert_include(method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)") + assert_include(method(:mnk).inspect, "(**nil)") + assert_include(method(:mf).inspect, "(...)") + end + + def test_unbound_method_parameters_inspect + assert_include(self.class.instance_method(:m0).inspect, "()") + assert_include(self.class.instance_method(:m1).inspect, "(a)") + assert_include(self.class.instance_method(:m2).inspect, "(a, b)") + assert_include(self.class.instance_method(:mo1).inspect, "(a=..., &b)") + assert_include(self.class.instance_method(:mo2).inspect, "(a, b=...)") + assert_include(self.class.instance_method(:mo3).inspect, "(*a)") + assert_include(self.class.instance_method(:mo4).inspect, "(a, *b, &c)") + assert_include(self.class.instance_method(:mo5).inspect, "(a, *b, c)") + assert_include(self.class.instance_method(:mo6).inspect, "(a, *b, c, &d)") + assert_include(self.class.instance_method(:mo7).inspect, "(a, b=..., *c, d, &e)") + assert_include(self.class.instance_method(:mo8).inspect, "(a, b=..., *, d, &e)") + assert_include(self.class.instance_method(:ma1).inspect, "(_, &b)") + assert_include(self.class.instance_method(:mk1).inspect, "(**)") + assert_include(self.class.instance_method(:mk2).inspect, "(**o)") + assert_include(self.class.instance_method(:mk3).inspect, "(a, **o)") + assert_include(self.class.instance_method(:mk4).inspect, "(a=..., **o)") + assert_include(self.class.instance_method(:mk5).inspect, "(a, b=..., **o)") + assert_include(self.class.instance_method(:mk6).inspect, "(a, b=..., c, **o)") + assert_include(self.class.instance_method(:mk7).inspect, "(a, b=..., *c, d, **o)") + assert_include(self.class.instance_method(:mk8).inspect, "(a, b=..., *c, d, e:, f: ..., **o)") + assert_include(self.class.instance_method(:mnk).inspect, "(**nil)") + assert_include(self.class.instance_method(:mf).inspect, "(...)") + end + + def test_public_method_with_zsuper_method + c = Class.new + c.class_eval do + def foo + :ok + end + private :foo + end + d = Class.new(c) + d.class_eval do + public :foo + end + assert_equal(:ok, d.new.public_method(:foo).call) + end + + def test_public_methods_with_extended + m = Module.new do def m1; end end + a = Class.new do def a; end end + bug = '[ruby-dev:41553]' + obj = a.new + assert_equal([:a], obj.public_methods(false), bug) + obj.extend(m) + assert_equal([:m1, :a], obj.public_methods(false), bug) + end + + def test_visibility + assert_equal('method', defined?(mv1)) + assert_equal('method', defined?(mv2)) + assert_equal('method', defined?(mv3)) + + assert_equal('method', defined?(self.mv1)) + assert_equal(nil, defined?(self.mv2)) + assert_equal('method', defined?(self.mv3)) + + assert_equal(true, respond_to?(:mv1)) + assert_equal(false, respond_to?(:mv2)) + assert_equal(false, respond_to?(:mv3)) + + assert_equal(true, respond_to?(:mv1, true)) + assert_equal(true, respond_to?(:mv2, true)) + assert_equal(true, respond_to?(:mv3, true)) + + assert_nothing_raised { mv1 } + assert_nothing_raised { mv2 } + assert_nothing_raised { mv3 } + + assert_nothing_raised { self.mv1 } + assert_nothing_raised { self.mv2 } + assert_raise(NoMethodError) { (self).mv2 } + assert_nothing_raised { self.mv3 } + + class << (obj = Object.new) + private def [](x) x end + def mv1(x) self[x] end + def mv2(x) (self)[x] end + end + assert_nothing_raised { obj.mv1(0) } + assert_raise(NoMethodError) { obj.mv2(0) } + + v = Visibility.new + + assert_equal('method', defined?(v.mv1)) + assert_equal(nil, defined?(v.mv2)) + assert_equal(nil, defined?(v.mv3)) + + assert_equal(true, v.respond_to?(:mv1)) + assert_equal(false, v.respond_to?(:mv2)) + assert_equal(false, v.respond_to?(:mv3)) + + assert_equal(true, v.respond_to?(:mv1, true)) + assert_equal(true, v.respond_to?(:mv2, true)) + assert_equal(true, v.respond_to?(:mv3, true)) + + assert_nothing_raised { v.mv1 } + assert_raise(NoMethodError) { v.mv2 } + assert_raise(NoMethodError) { v.mv3 } + + assert_nothing_raised { v.__send__(:mv1) } + assert_nothing_raised { v.__send__(:mv2) } + assert_nothing_raised { v.__send__(:mv3) } + + assert_nothing_raised { v.instance_eval { mv1 } } + 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]' + file, line = *binding.source_location + file = File.realpath(file) + assert_equal(__dir__, eval("__dir__", binding, file, line), bug8436) + bug8662 = '[ruby-core:56099] [Bug #8662]' + assert_equal("arbitrary", eval("__dir__", binding, "arbitrary/file.rb"), bug8662) + assert_equal("arbitrary", Object.new.instance_eval("__dir__", "arbitrary/file.rb"), bug8662) + 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_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 + + def test_singleton_method_included_or_prepended_bug_20620 + m = Module.new do + extend self + def foo = :foo + end + assert_equal(:foo, m.singleton_method(:foo).call) + assert_raise(NameError) {m.singleton_method(:puts)} + + sc = Class.new do + def t = :t + end + c = Class.new(sc) do + singleton_class.prepend(Module.new do + def bar = :bar + end) + extend(Module.new do + def quux = :quux + end) + def self.baz = :baz + end + assert_equal(:bar, c.singleton_method(:bar).call) + assert_equal(:baz, c.singleton_method(:baz).call) + assert_equal(:quux, c.singleton_method(:quux).call) + + assert_raise(NameError) {c.singleton_method(:t)} + + c2 = Class.new(c) + assert_raise(NameError) {c2.singleton_method(:bar)} + assert_raise(NameError) {c2.singleton_method(:baz)} + assert_raise(NameError) {c2.singleton_method(:quux)} + 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_bind_unbind_clone + bug15629_m1 = Module.new do + def foo; end + end + + bug15629_m2 = Module.new do + def foo; end + end + + bug15629_c = Class.new do + include bug15629_m1 + include bug15629_m2 + end + + o = bug15629_c.new + m = o.method(:foo) + sm = m.super_method + im = bug15629_c.instance_method(:foo) + sim = im.super_method + + assert_equal(sm, m.clone.super_method) + assert_equal(sim, m.unbind.super_method) + assert_equal(sim, m.unbind.clone.super_method) + assert_equal(sim, im.clone.super_method) + assert_equal(sm, m.unbind.bind(o).super_method) + assert_equal(sm, m.unbind.clone.bind(o).super_method) + assert_equal(sm, im.bind(o).super_method) + assert_equal(sm, im.clone.bind(o).super_method) + end + + def test_super_method_removed_public + c1 = Class.new {private def foo; end} + c2 = Class.new(c1) {public :foo} + c3 = Class.new(c2) {def foo; end} + 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_super_method_removed_regular + c1 = Class.new { def foo; end } + c2 = Class.new(c1) { def foo; end } + assert_equal c1.instance_method(:foo), c2.instance_method(:foo).super_method + c1.remove_method :foo + assert_equal nil, c2.instance_method(:foo).super_method + end + + def test_prepended_public_zsuper + mod = EnvUtil.labeled_module("Mod") {private def foo; [:ok] end} + obj = Object.new.extend(mod) + + class << obj + public :foo + end + + mod1 = EnvUtil.labeled_module("Mod1") {def foo; [:mod1] + super end} + obj.singleton_class.prepend(mod1) + + mod2 = EnvUtil.labeled_module("Mod2") {def foo; [:mod2] + super end} + obj.singleton_class.prepend(mod2) + + m = obj.method(:foo) + assert_equal mod2, m.owner + assert_equal mod1, m.super_method.owner + assert_equal obj.singleton_class, m.super_method.super_method.owner + assert_equal nil, m.super_method.super_method.super_method + + assert_equal [:mod2, :mod1, :ok], obj.foo + end + + def test_super_method_with_prepended_module + 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 + + # Bug 18435 + def test_instance_methods_owner_consistency + a = Module.new { def method1; end } + + b = Class.new do + include a + protected :method1 + end + + assert_equal [:method1], b.instance_methods(false) + assert_equal b, b.instance_method(:method1).owner + end + + def test_zsuper_method_removed + a = EnvUtil.labeled_class('A') do + private + def foo(arg = nil) + 1 + end + end + line = __LINE__ - 4 + + b = EnvUtil.labeled_class('B', a) do + public :foo + end + + unbound = b.instance_method(:foo) + + assert_equal unbound, b.public_instance_method(:foo) + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + + a.remove_method(:foo) + + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + + obj = b.new + assert_equal 1, unbound.bind_call(obj) + + assert_include b.instance_methods(false), :foo + link = 'https://github.com/ruby/ruby/pull/6467#issuecomment-1262159088' + assert_raise(NameError, link) { b.instance_method(:foo) } + # For #test_method_list below, otherwise we get the same error as just above + b.remove_method(:foo) + end + + def test_zsuper_method_removed_higher_method + a0 = EnvUtil.labeled_class('A0') do + def foo(arg1 = nil, arg2 = nil) + 0 + end + end + line0 = __LINE__ - 4 + a0_foo = a0.instance_method(:foo) + + a = EnvUtil.labeled_class('A', a0) do + private + def foo(arg = nil) + 1 + end + end + line = __LINE__ - 4 + + b = EnvUtil.labeled_class('B', a) do + public :foo + end + + unbound = b.instance_method(:foo) + + assert_equal a0_foo, unbound.super_method + + a.remove_method(:foo) + + assert_equal "#<UnboundMethod: A#foo(arg=...) #{__FILE__}:#{line}>", unbound.inspect + assert_equal [[:opt, :arg]], unbound.parameters + assert_equal a0_foo, unbound.super_method + + obj = b.new + assert_equal 1, unbound.bind_call(obj) + + assert_include b.instance_methods(false), :foo + assert_equal "#<UnboundMethod: A0#foo(arg1=..., arg2=...) #{__FILE__}:#{line0}>", b.instance_method(:foo).inspect + end + + def test_zsuper_method_redefined_bind_call + c0 = EnvUtil.labeled_class('C0') do + def foo + [:foo] + end + end + + c1 = EnvUtil.labeled_class('C1', c0) do + def foo + super + [:bar] + end + end + m1 = c1.instance_method(:foo) + + c2 = EnvUtil.labeled_class('C2', c1) do + private :foo + end + + assert_equal [:foo], c2.private_instance_methods(false) + m2 = c2.instance_method(:foo) + + c1.class_exec do + remove_method :foo + def foo + [:bar2] + end + end + + m3 = c2.instance_method(:foo) + c = c2.new + assert_equal [:foo, :bar], m1.bind_call(c) + assert_equal c1, m1.owner + assert_equal [:foo, :bar], m2.bind_call(c) + assert_equal c2, m2.owner + assert_equal [:bar2], m3.bind_call(c) + assert_equal c2, m3.owner + end + + # Bug #18751 + def method_equality_visbility_alias + c = Class.new do + class << self + alias_method :n, :new + private :new + end + end + + assert_equal c.method(:n), c.method(:new) + + assert_not_equal c.method(:n), Class.method(:new) + assert_equal c.method(:n) == Class.instance_method(:new).bind(c) + + assert_not_equal c.method(:new), Class.method(:new) + assert_equal c.method(:new), Class.instance_method(:new).bind(c) + end + + def rest_parameter(*rest) + rest + end + + def test_splat_long_array + if File.exist?('/etc/os-release') && File.read('/etc/os-release').include?('openSUSE Leap') + # For RubyCI's openSUSE machine http://rubyci.s3.amazonaws.com/opensuseleap/ruby-trunk/recent.html, which tends to die with NoMemoryError here. + omit 'do not exhaust memory on RubyCI openSUSE Leap machine' + end + n = 10_000_000 + assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]' + end + + class C + D = "Const_D" + def foo + a = b = c = a = b = c = 12345 + end + + def binding_noarg + a = a = 12345 + binding + end + + def binding_one_arg(x) + a = a = 12345 + binding + end + + def binding_optargs(x, y=42) + a = a = 12345 + binding + end + + def binding_anyargs(*x) + a = a = 12345 + binding + end + + def binding_keywords(x: 42) + a = a = 12345 + binding + end + + def binding_anykeywords(**x) + a = a = 12345 + binding + end + + def binding_forwarding(...) + a = a = 12345 + binding + end + + def binding_forwarding1(x, ...) + a = a = 12345 + binding + 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 + + def test_method_binding + c = C.new + + b = c.binding_noarg + assert_equal(12345, b.local_variable_get(:a)) + + b = c.binding_one_arg(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + + b = c.binding_anyargs() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([], b.local_variable_get(:x)) + b = c.binding_anyargs(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([0], b.local_variable_get(:x)) + b = c.binding_anyargs(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal([0, 1], b.local_variable_get(:x)) + + b = c.binding_optargs(0) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + assert_equal(42, b.local_variable_get(:y)) + b = c.binding_optargs(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(0, b.local_variable_get(:x)) + assert_equal(1, b.local_variable_get(:y)) + + b = c.binding_keywords() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(42, b.local_variable_get(:x)) + b = c.binding_keywords(x: 102) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(102, b.local_variable_get(:x)) + + b = c.binding_anykeywords() + assert_equal(12345, b.local_variable_get(:a)) + assert_equal({}, b.local_variable_get(:x)) + b = c.binding_anykeywords(foo: 999) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal({foo: 999}, b.local_variable_get(:x)) + + b = c.binding_forwarding() + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(0) + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(0, 1) + assert_equal(12345, b.local_variable_get(:a)) + b = c.binding_forwarding(foo: 42) + assert_equal(12345, b.local_variable_get(:a)) + + b = c.binding_forwarding1(987) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(987, b.local_variable_get(:x)) + b = c.binding_forwarding1(987, 654) + assert_equal(12345, b.local_variable_get(:a)) + assert_equal(987, b.local_variable_get(:x)) + end + + MethodInMethodClass_Setup = -> do + remove_const :MethodInMethodClass if defined? MethodInMethodClass + + class MethodInMethodClass + def m1 + def m2 + end + self.class.send(:define_method, :m3){} # [Bug #11754] + end + private + end + end + + def test_method_in_method_visibility_should_be_public + MethodInMethodClass_Setup.call + + assert_equal([:m1].sort, MethodInMethodClass.public_instance_methods(false).sort) + assert_equal([].sort, MethodInMethodClass.private_instance_methods(false).sort) + + 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', nil, "main.rb" + $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 "main.rb:#{$line_lambda}:in 'block in <main>'", e.backtrace.first + end + begin + foo(1) + rescue ArgumentError => e + assert_equal "main.rb:#{$line_method}:in 'Object#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_zsuper_private_override_instance_method + assert_separately([], <<-'end;', timeout: 30) + # Bug #16942 [ruby-core:98691] + module M + def x + end + end + + module M2 + prepend Module.new + include M + private :x + end + + ::Object.prepend(M2) + + m = Object.instance_method(:x) + assert_equal M2, m.owner + end; + end + + def test_override_optimized_method_on_class_using_prepend + assert_separately([], <<-'end;', timeout: 30) + # Bug #17725 [ruby-core:102884] + $VERBOSE = nil + String.prepend(Module.new) + class String + def + other + 'blah blah' + end + end + + assert_equal('blah blah', 'a' + 'b') + end; + end + + def test_eqq + assert_operator(0.method(:<), :===, 5) + assert_not_operator(0.method(:<), :===, -5) + end + + def test_compose_with_method + c = Class.new { + def f(x) x * 2 end + def g(x) x + 1 end + } + f = c.new.method(:f) + g = c.new.method(:g) + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_proc + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_callable + c = Class.new { + def f(x) x * 2 end + } + c2 = Class.new { + def call(x) x + 1 end + } + f = c.new.method(:f) + g = c2.new + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + end + + def test_compose_with_noncallable + c = Class.new { + def f(x) x * 2 end + } + f = c.new.method(:f) + + assert_raise(TypeError) { + f << 5 + } + assert_raise(TypeError) { + f >> 5 + } + end + + def test_umethod_bind_call + foo = Base.instance_method(:foo) + assert_equal(:base, foo.bind_call(Base.new)) + assert_equal(:base, foo.bind_call(Derived.new)) + + plus = Integer.instance_method(:+) + assert_equal(3, plus.bind_call(1, 2)) + end + + def test_method_list + # chkbuild lists all methods. + # The following code emulate this listing. + + # use_symbol = Object.instance_methods[0].is_a?(Symbol) + nummodule = nummethod = 0 + mods = [] + ObjectSpace.each_object(Module) {|m| mods << m if String === m.name } + mods = mods.sort_by {|m| m.name } + mods.each {|mod| + nummodule += 1 + mc = mod.kind_of?(Class) ? "class" : "module" + puts_line = "#{mc} #{mod.name} #{(mod.ancestors - [mod]).inspect}" + puts_line = puts_line # prevent unused var warning + mod.singleton_methods(false).sort.each {|methname| + nummethod += 1 + meth = mod.method(methname) + line = "#{mod.name}.#{methname} #{meth.arity}" + line << " not-implemented" if !mod.respond_to?(methname) + # puts line + } + ms = mod.instance_methods(false) + if true or use_symbol + ms << :initialize if mod.private_instance_methods(false).include? :initialize + else + ms << "initialize" if mod.private_instance_methods(false).include? "initialize" + end + + ms.sort.each {|methname| + nummethod += 1 + meth = mod.instance_method(methname) + line = "#{mod.name}\##{methname} #{meth.arity}" + line << " not-implemented" if /\(not-implemented\)/ =~ meth.inspect + # puts line + } + } + # puts "#{nummodule} modules, #{nummethod} methods" + + assert_operator nummodule, :>, 0 + assert_operator nummethod, :>, 0 + end + + def test_invalidating_CC_ASAN + assert_ruby_status(['-e', 'using Module.new']) + end + + def test_kwarg_eval_memory_leak + assert_no_memory_leak([], "", <<~RUBY, rss: true, limit: 1.2) + obj = Object.new + def obj.test(**kwargs) = nil + + 100_000.times do + eval("obj.test(foo: 123)") + end + RUBY + end + + def test_super_with_splat + c = Class.new { + attr_reader :x + + def initialize(*args) + @x, _ = args + end + } + b = Class.new(c) { def initialize(...) = super } + a = Class.new(b) { def initialize(*args) = super } + obj = a.new(1, 2, 3) + assert_equal 1, obj.x + end + + def test_warn_unused_block + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + foo{} # warn + send(:foo){} # don't warn because it uses send + b = Proc.new{} + foo(&b) # warn + RUBY + errstr = err.join("\n") + assert_equal 2, err.size, errstr + + assert_match(/-:2: warning/, errstr) + assert_match(/-:5: warning/, errstr) + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + 10.times{foo{}} # warn once + RUBY + assert_equal 1, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil; b = nil + foo(&b) # no warning + 1.object_id{} # no warning because it is written in C + + class C + def initialize + end + end + C.new{} # no warning + + RUBY + assert_equal 0, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + class C0 + def f1 = nil + def f2 = nil + def f3 = nil + def f4 = nil + def f5 = nil + def f6 = nil + end + + class C1 < C0 + def f1 = super # zsuper / use + def f2 = super() # super / use + def f3(&_) = super(&_) # super / use + def f4 = super(&nil) # super / unuse + def f5 = super(){} # super / unuse + def f6 = super{} # zsuper / unuse + end + + C1.new.f1{} # no warning + C1.new.f2{} # no warning + C1.new.f3{} # no warning + C1.new.f4{} # warning + C1.new.f5{} # warning + C1.new.f6{} # warning + RUBY + assert_equal 3, err.size, err.join("\n") + assert_match(/-:22: warning.+f4/, err.join) + assert_match(/-:23: warning.+f5/, err.join) + assert_match(/-:24: warning.+f6/, err.join) + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + class C0 + def f = yield + end + + class C1 < C0 + def f = nil + end + + C1.new.f{} # do not warn on duck typing + RUBY + assert_equal 0, err.size, err.join("\n") + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo(*, &block) = block + def bar(buz, ...) = foo(buz, ...) + bar(:test) {} # do not warn because of forwarding + RUBY + assert_equal 0, err.size, err.join("\n") + end + end +end diff --git a/test/ruby/test_method_cache.rb b/test/ruby/test_method_cache.rb new file mode 100644 index 0000000000..2ed89e47bf --- /dev/null +++ b/test/ruby/test_method_cache.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestMethodCache < Test::Unit::TestCase + def test_undef + # clear same + c0 = Class.new do + def foo; end + undef foo + end + + assert_raise(NoMethodError) do + c0.new.foo + end + + c0.class_eval do + def foo; :ok; end + end + + assert_equal :ok, c0.new.foo + end + + def test_undef_with_subclasses + # with subclasses + c0 = Class.new do + def foo; end + undef foo + end + + _c1 = Class.new(c0) + + assert_raise(NoMethodError) do + c0.new.foo + end + + c0.class_eval do + def foo; :ok; end + end + + assert_equal :ok, c0.new.foo + end + + def test_undef_with_subclasses_complicated + c0 = Class.new{ def foo; end } + c1 = Class.new(c0){ undef foo } + c2 = Class.new(c1) + c3 = Class.new(c2) + _c4 = Class.new(c3) + + assert_raise(NoMethodError) do + c3.new.foo + end + + c2.class_eval do + def foo; :c2; end + end + + assert_raise(NoMethodError) do + c1.new.foo + end + + assert_equal :c2, c3.new.foo + end + + def test_negative_cache_with_and_without_subclasses + c0 = Class.new{} + c1 = Class.new(c0){} + c0.new.foo rescue nil + c1.new.foo rescue nil + c1.module_eval{ def foo = :c1 } + c0.module_eval{ def foo = :c0 } + + assert_equal :c0, c0.new.foo + end +end + diff --git a/test/ruby/test_mixed_unicode_escapes.rb b/test/ruby/test_mixed_unicode_escapes.rb new file mode 100644 index 0000000000..a30b5c19f5 --- /dev/null +++ b/test/ruby/test_mixed_unicode_escapes.rb @@ -0,0 +1,30 @@ +# -*- coding: cp932 -*- +# 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' + +class TestMixedUnicodeEscape < Test::Unit::TestCase + def test_basic + # Unicode escapes do work in an sjis encoded file, but only + # if they don't contain other multi-byte chars + assert_equal("A", "\u0041") + # 8-bit character escapes are okay. + assert_equal("B\xFF", "\u0042\xFF") + + # 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(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(/#{"\u1234"}#{"é"}/)} + 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 new file mode 100644 index 0000000000..9ed6c1e321 --- /dev/null +++ b/test/ruby/test_module.rb @@ -0,0 +1,3423 @@ +# frozen_string_literal: false +require 'test/unit' +require 'pp' + +$m0 = Module.nesting + +class TestModule < Test::Unit::TestCase + def _wrap_assertion + yield + end + + def assert_method_defined?(klass, (mid, *args), message="") + message = build_message(message, "#{klass}\##{mid} expected to be defined.") + _wrap_assertion do + klass.method_defined?(mid, *args) or + raise Test::Unit::AssertionFailedError, message, caller(3) + end + end + + def assert_method_not_defined?(klass, (mid, *args), message="") + message = build_message(message, "#{klass}\##{mid} expected to not be defined.") + _wrap_assertion do + klass.method_defined?(mid, *args) and + raise Test::Unit::AssertionFailedError, message, caller(3) + end + end + + def setup + @verbose = $VERBOSE + @deprecated = Warning[:deprecated] + Warning[:deprecated] = true + end + + def teardown + $VERBOSE = @verbose + Warning[:deprecated] = @deprecated + end + + def test_LT_0 + assert_equal true, String < Object + assert_equal false, Object < String + assert_nil String < Array + assert_equal true, Array < Enumerable + assert_equal false, Enumerable < Array + assert_nil Proc < Comparable + assert_nil Comparable < Proc + end + + def test_GT_0 + assert_equal false, String > Object + assert_equal true, Object > String + assert_nil String > Array + assert_equal false, Array > Enumerable + assert_equal true, Enumerable > Array + assert_nil Comparable > Proc + assert_nil Proc > Comparable + end + + def test_CMP_0 + assert_equal(-1, (String <=> Object)) + assert_equal 1, (Object <=> String) + assert_nil(Array <=> String) + end + + ExpectedException = NoMethodError + + # Support stuff + + module Mixin + MIXIN = 1 + def mixin + end + end + + module User + USER = 2 + include Mixin + def user + end + + def user2 + end + protected :user2 + + def user3 + end + private :user3 + end + + OtherSetup = -> do + remove_const :Other if defined? ::TestModule::Other + module Other + def other + end + end + end + + class AClass + def AClass.cm1 + "cm1" + end + def AClass.cm2 + cm1 + "cm2" + cm3 + end + def AClass.cm3 + "cm3" + end + + private_class_method :cm1, "cm3" + + def aClass + :aClass + end + + def aClass1 + :aClass1 + end + + def aClass2 + :aClass2 + end + + private :aClass1 + protected :aClass2 + end + + class BClass < AClass + def bClass1 + :bClass1 + end + + private + + def bClass2 + :bClass2 + end + + protected + def bClass3 + :bClass3 + end + end + + class CClass < BClass + def self.cClass + end + end + + MyClass = AClass.clone + class MyClass + public_class_method :cm1 + end + + # ----------------------------------------------------------- + + def test_CMP # '<=>' + assert_equal( 0, Mixin <=> Mixin) + assert_equal(-1, User <=> Mixin) + assert_equal( 1, Mixin <=> User) + + assert_equal( 0, Object <=> Object) + assert_equal(-1, String <=> Object) + assert_equal( 1, Object <=> String) + end + + def test_GE # '>=' + assert_operator(Mixin, :>=, User) + assert_operator(Mixin, :>=, Mixin) + assert_not_operator(User, :>=, Mixin) + + assert_operator(Object, :>=, String) + assert_operator(String, :>=, String) + assert_not_operator(String, :>=, Object) + end + + def test_GT # '>' + assert_operator(Mixin, :>, User) + assert_not_operator(Mixin, :>, Mixin) + assert_not_operator(User, :>, Mixin) + + assert_operator(Object, :>, String) + assert_not_operator(String, :>, String) + assert_not_operator(String, :>, Object) + end + + def test_LE # '<=' + assert_operator(User, :<=, Mixin) + assert_operator(Mixin, :<=, Mixin) + assert_not_operator(Mixin, :<=, User) + + assert_operator(String, :<=, Object) + assert_operator(String, :<=, String) + assert_not_operator(Object, :<=, String) + end + + def test_LT # '<' + assert_operator(User, :<, Mixin) + assert_not_operator(Mixin, :<, Mixin) + assert_not_operator(Mixin, :<, User) + + assert_operator(String, :<, Object) + assert_not_operator(String, :<, String) + assert_not_operator(Object, :<, String) + end + + def test_VERY_EQUAL # '===' + 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) + + 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 + @@class_eval = 'b' + + def test_class_eval + OtherSetup.call + + Other.class_eval("CLASS_EVAL = 1") + assert_equal(1, Other::CLASS_EVAL) + assert_include(Other.constants, :CLASS_EVAL) + assert_equal(2, Other.class_eval { CLASS_EVAL }) + + Other.class_eval("@@class_eval = 'a'") + assert_equal('a', Other.class_variable_get(:@@class_eval)) + assert_equal('b', Other.class_eval { @@class_eval }) + + Other.class_eval do + module_function + + def class_eval_test + "foo" + end + end + assert_equal("foo", Other.class_eval_test) + + assert_equal([Other], Other.class_eval { |*args| args }) + end + + def test_const_defined? + 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") + + # Test invalid symbol name + # [Bug #20245] + EnvUtil.under_gc_stress do + assert_raise(EncodingError) do + Math.const_defined?("\xC3") + end + end + end + + def each_bad_constants(m, &b) + [ + "#<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, Regexp.compile(Regexp.quote(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 + OtherSetup.call + + assert_equal Other, Object.const_get([self.class, 'Other'].join('::')) + assert_equal User::USER, self.class.const_get([User, 'USER'].join('::')) + assert_raise(NameError) { + Object.const_get([self.class.name, 'String'].join('::')) + } + end + + def test_nested_get_symbol + OtherSetup.call + + const = [self.class, Other].join('::').to_sym + assert_raise(NameError) {Object.const_get(const)} + + 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 + OtherSetup.call + + assert_send([Object, :const_defined?, [self.class.name, 'Other'].join('::')]) + assert_send([self.class, :const_defined?, 'User::USER']) + assert_not_send([self.class, :const_defined?, 'User::Foo']) + assert_not_send([Object, :const_defined?, [self.class.name, 'String'].join('::')]) + end + + def test_nested_defined_symbol + OtherSetup.call + + const = [self.class, Other].join('::').to_sym + assert_raise(NameError) {Object.const_defined?(const)} + + 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 + OtherSetup.call + + assert_not_operator(Other, :const_defined?, :KOALA) + Other.const_set(:KOALA, 99) + 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 + assert_equal([:MIXIN], Mixin.constants) + assert_equal([:MIXIN, :USER], User.constants.sort) + end + + def test_initialize_copy_empty + 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 = Class.new(Module) do + def initialize_copy(other) + # leave uninitialized + end + end.new.dup + c = Class.new + assert_operator(c.include(m), :<, m) + cp = Module.instance_method(:initialize_copy) + assert_raise(TypeError) do + cp.bind_call(m, Module.new) + end + end + + class Bug18185 < Module + module InstanceMethods + end + attr_reader :ancestor_list + def initialize + @ancestor_list = ancestors + include InstanceMethods + end + class Foo + attr_reader :key + def initialize(key:) + @key = key + end + end + end + + def test_module_subclass_initialize + mod = Bug18185.new + c = Class.new(Bug18185::Foo) do + include mod + end + anc = c.ancestors + assert_include(anc, mod) + assert_equal(1, anc.count(BasicObject), ->{anc.inspect}) + b = c.new(key: 1) + assert_equal(1, b.key) + assert_not_include(mod.ancestor_list, BasicObject) + end + + def test_module_collected_extended_object + m1 = labeled_module("m1") + m2 = labeled_module("m2") + Object.new.extend(m1) + GC.start + m1.include(m2) + assert_equal([m1, m2], m1.ancestors) + end + + def test_dup + OtherSetup.call + + bug6454 = '[ruby-core:45132]' + + a = Module.new + 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_include_before_initialize + m = Class.new(Module) do + def initialize(...) + include Enumerable + super + end + end.new + assert_operator(m, :<, Enumerable) + end + + def test_prepend_self + m = Module.new + assert_equal([m], m.ancestors) + m.prepend(m) rescue nil + assert_equal([m], m.ancestors) + end + + def test_bug17590 + m = Module.new + c = Class.new + c.prepend(m) + c.include(m) + m.prepend(m) rescue nil + m2 = Module.new + m2.prepend(m) + c.include(m2) + + assert_equal([m, c, m2] + Object.ancestors, c.ancestors) + end + + def test_prepend_works_with_duped_classes + m = Module.new + a = Class.new do + def b; 2 end + prepend m + end + a2 = a.dup.new + a.class_eval do + alias _b b + def b; 1 end + end + assert_equal(2, a2.b) + end + + def test_ancestry_of_duped_classes + m = Module.new + sc = Class.new + a = Class.new(sc) do + def b; 2 end + prepend m + end + + a2 = a.dup.new + + assert_kind_of Object, a2 + assert_kind_of sc, a2 + refute_kind_of a, a2 + assert_kind_of m, a2 + + assert_kind_of Class, a2.class + assert_kind_of sc.singleton_class, a2.class + assert_same sc, a2.class.superclass + end + + def test_gc_prepend_chain + assert_ruby_status([], <<-EOS) + 10000.times { |i| + m1 = Module.new do + def foo; end + end + m2 = Module.new do + prepend m1 + def bar; end + end + m3 = Module.new do + def baz; end + prepend m2 + end + Class.new do + prepend m3 + end + } + GC.start + EOS + end + + def test_refine_module_then_include + assert_separately([], "#{<<~"end;"}\n") + module M + end + class C + include M + end + module RefinementBug + refine M do + def refined_method + :rm + end + end + end + using RefinementBug + + class A + include M + end + + assert_equal(:rm, C.new.refined_method) + end; + end + + def test_include_into_module_already_included + c = Class.new{def foo; [:c] end} + modules = lambda do || + sub = Class.new(c){def foo; [:sc] + super end} + [ + Module.new{def foo; [:m1] + super end}, + Module.new{def foo; [:m2] + super end}, + Module.new{def foo; [:m3] + super end}, + sub, + sub.new + ] + end + + m1, m2, m3, sc, o = modules.call + assert_equal([:sc, :c], o.foo) + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + m1.include m2 + assert_equal([:sc, :m1, :m2, :c], o.foo) + m2.include m3 + assert_equal([:sc, :m1, :m2, :m3, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.prepend m1 + assert_equal([:m1, :sc, :c], o.foo) + m1.include m2 + assert_equal([:m1, :m2, :sc, :c], o.foo) + m2.include m3 + assert_equal([:m1, :m2, :m3, :sc, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.include m2 + assert_equal([:sc, :m2, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :m2, :c], o.foo) + m1.include m2 + assert_equal([:m1, :sc, :m2, :c], o.foo) + m1.include m3 + assert_equal([:m1, :m3, :sc, :m2, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + sc.include m3 + sc.include m2 + assert_equal([:sc, :m2, :m3, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :m2, :m3, :c], o.foo) + m1.include m2 + m1.include m3 + assert_equal([:m1, :sc, :m2, :m3, :c], o.foo) + + m1, m2, m3, sc, o = modules.call + assert_equal([:sc, :c], o.foo) + sc.prepend m1 + assert_equal([:m1, :sc, :c], o.foo) + m1.prepend m2 + assert_equal([:m2, :m1, :sc, :c], o.foo) + m2.prepend m3 + assert_equal([:m3, :m2, :m1, :sc, :c], o.foo) + m1, m2, m3, sc, o = modules.call + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + sc.prepend m2 + assert_equal([:m2, :sc, :m1, :c], o.foo) + sc.prepend m3 + assert_equal([:m3, :m2, :sc, :m1, :c], o.foo) + m1, m2, m3, sc, o = modules.call + sc.include m1 + assert_equal([:sc, :m1, :c], o.foo) + m2.prepend m3 + m1.include m2 + assert_equal([:sc, :m1, :m3, :m2, :c], o.foo) + end + + def test_included_modules + assert_equal([], Mixin.included_modules) + assert_equal([Mixin], User.included_modules) + + 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_included_modules_with_prepend + m1 = Module.new + m2 = Module.new + m3 = Module.new + + m2.prepend m1 + m3.include m2 + assert_equal([m1, m2], m3.included_modules) + end + + def test_include_with_prepend + c = Class.new{def m; [:c] end} + p = Module.new{def m; [:p] + super end} + q = Module.new{def m; [:q] + super end; include p} + r = Module.new{def m; [:r] + super end; prepend q} + s = Module.new{def m; [:s] + super end; include r} + a = Class.new(c){def m; [:a] + super end; prepend p; include s} + assert_equal([:p, :a, :s, :q, :r, :c], a.new.m) + end + + def test_prepend_after_include + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m = Module.new{def m; [:m] + super end} + sc.include m + sc.prepend m + sc.prepend m + assert_equal([:m, :sc, :m, :c], sc.new.m) + + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m0 = Module.new{def m; [:m0] + super end} + m1 = Module.new{def m; [:m1] + super end} + m1.prepend m0 + sc.include m1 + sc.prepend m1 + assert_equal([:m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + sc.prepend m + assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + sc.prepend m1 + assert_equal([:m, :m0, :m1, :sc, :m0, :m1, :c], sc.new.m) + + + c = Class.new{def m; [:c] end} + sc = Class.new(c){def m; [:sc] + super end} + m0 = Module.new{def m; [:m0] + super end} + m1 = Module.new{def m; [:m1] + super end} + m1.include m0 + sc.include m1 + sc.prepend m + sc.prepend m1 + sc.prepend m1 + assert_equal([:m1, :m0, :m, :sc, :m1, :m0, :c], sc.new.m) + end + + def test_include_into_module_after_prepend_bug_20871 + bar = Module.new{def bar; 'bar'; end} + foo = Module.new{def foo; 'foo'; end} + m = Module.new + c = Class.new{include m} + m.prepend bar + Class.new{include m} + m.include foo + assert_include c.ancestors, foo + assert_equal "foo", c.new.foo + end + + def test_protected_include_into_included_module + m1 = Module.new do + def other_foo(other) + other.foo + end + + protected + def foo + :ok + end + end + m2 = Module.new + c1 = Class.new { include m2 } + c2 = Class.new { include m2 } + m2.include(m1) + + assert_equal :ok, c1.new.other_foo(c2.new) + end + + def test_instance_methods + assert_equal([:user, :user2], User.instance_methods(false).sort) + assert_equal([:user, :user2, :mixin].sort, User.instance_methods(true).sort) + 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)) + 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? + [User, Class.new{include User}, Class.new{prepend User}].each do |klass| + [[], [true]].each do |args| + assert_method_not_defined?(klass, [:wombat, *args]) + assert_method_defined?(klass, [:mixin, *args]) + assert_method_defined?(klass, [:user, *args]) + assert_method_defined?(klass, [:user2, *args]) + assert_method_not_defined?(klass, [:user3, *args]) + + assert_method_not_defined?(klass, ["wombat", *args]) + assert_method_defined?(klass, ["mixin", *args]) + assert_method_defined?(klass, ["user", *args]) + assert_method_defined?(klass, ["user2", *args]) + assert_method_not_defined?(klass, ["user3", *args]) + end + end + end + + def test_method_defined_without_include_super + assert_method_defined?(User, [:user, false]) + assert_method_not_defined?(User, [:mixin, false]) + assert_method_defined?(Mixin, [:mixin, false]) + + User.const_set(:FOO, c = Class.new) + + c.prepend(User) + assert_method_not_defined?(c, [:user, false]) + c.define_method(:user){} + assert_method_defined?(c, [:user, false]) + + assert_method_not_defined?(c, [:mixin, false]) + c.define_method(:mixin){} + assert_method_defined?(c, [:mixin, false]) + + assert_method_not_defined?(c, [:userx, false]) + c.define_method(:userx){} + assert_method_defined?(c, [:userx, false]) + + # cleanup + User.class_eval do + remove_const :FOO + end + end + + def module_exec_aux + Proc.new do + def dynamically_added_method_3; end + end + end + def module_exec_aux_2(&block) + User.module_exec(&block) + end + + def test_module_exec + User.module_exec do + def dynamically_added_method_1; end + end + assert_method_defined?(User, :dynamically_added_method_1) + + block = Proc.new do + def dynamically_added_method_2; end + end + User.module_exec(&block) + assert_method_defined?(User, :dynamically_added_method_2) + + User.module_exec(&module_exec_aux) + assert_method_defined?(User, :dynamically_added_method_3) + + module_exec_aux_2 do + def dynamically_added_method_4; end + end + assert_method_defined?(User, :dynamically_added_method_4) + + # cleanup + User.class_eval do + remove_method :dynamically_added_method_1 + remove_method :dynamically_added_method_2 + remove_method :dynamically_added_method_3 + remove_method :dynamically_added_method_4 + end + end + + def test_module_eval + User.module_eval("MODULE_EVAL = 1") + assert_equal(1, User::MODULE_EVAL) + assert_include(User.constants, :MODULE_EVAL) + User.instance_eval("remove_const(:MODULE_EVAL)") + assert_not_include(User.constants, :MODULE_EVAL) + end + + def test_name + assert_equal("Integer", Integer.name) + assert_equal("TestModule::Mixin", Mixin.name) + assert_equal("TestModule::User", User.name) + + assert_predicate Integer.name, :frozen? + assert_predicate Mixin.name, :frozen? + assert_predicate User.name, :frozen? + end + + def test_accidental_singleton_naming_with_module + o = Object.new + assert_nil(o.singleton_class.name) + class << o + module Hi; end + end + assert_nil(o.singleton_class.name) + end + + def test_accidental_singleton_naming_with_class + o = Object.new + assert_nil(o.singleton_class.name) + class << o + class Hi; end + end + assert_nil(o.singleton_class.name) + end + + def test_classpath + m = Module.new + n = Module.new + m.const_set(:N, n) + assert_nil(m.name) + assert_match(/::N$/, n.name) + assert_equal([:N], m.constants) + m.module_eval("module O end") + assert_equal([:N, :O], m.constants.sort) + m.module_eval("class C; end") + assert_equal([:C, :N, :O], m.constants.sort) + assert_match(/::N$/, m::N.name) + assert_match(/\A#<Module:.*>::O\z/, m::O.name) + assert_match(/\A#<Module:.*>::C\z/, m::C.name) + self.class.const_set(:M, m) + 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) + c = m.class_eval("Bug15891 = Class.new.freeze") + assert_equal(prefix+"Bug15891", c.name) + ensure + self.class.class_eval {remove_const(:M)} + end + + def test_private_class_method + assert_raise(ExpectedException) { AClass.cm1 } + assert_raise(ExpectedException) { AClass.cm3 } + assert_equal("cm1cm2cm3", AClass.cm2) + + c = Class.new(AClass) + c.class_eval {private_class_method [:cm1, :cm2]} + assert_raise(NoMethodError, /private method/) {c.cm1} + assert_raise(NoMethodError, /private method/) {c.cm2} + end + + def test_private_instance_methods + assert_equal([:aClass1], AClass.private_instance_methods(false)) + assert_equal([:bClass2], BClass.private_instance_methods(false)) + assert_equal([:aClass1, :bClass2], + (BClass.private_instance_methods(true) - + Object.private_instance_methods(true)).sort) + end + + def test_protected_instance_methods + assert_equal([:aClass2], AClass.protected_instance_methods) + assert_equal([:bClass3], BClass.protected_instance_methods(false)) + assert_equal([:bClass3, :aClass2].sort, + (BClass.protected_instance_methods(true) - + Object.protected_instance_methods(true)).sort) + end + + def test_public_class_method + assert_equal("cm1", MyClass.cm1) + assert_equal("cm1cm2cm3", MyClass.cm2) + assert_raise(ExpectedException) { eval "MyClass.cm3" } + + c = Class.new(AClass) + c.class_eval {public_class_method [:cm1, :cm2]} + assert_equal("cm1", c.cm1) + assert_equal("cm1cm2cm3", c.cm2) + end + + def test_public_instance_methods + assert_equal([:aClass], AClass.public_instance_methods(false)) + assert_equal([:bClass1], BClass.public_instance_methods(false)) + end + + def test_undefined_instance_methods + assert_equal([], AClass.undefined_instance_methods) + assert_equal([], BClass.undefined_instance_methods) + c = Class.new(AClass) {undef aClass} + assert_equal([:aClass], c.undefined_instance_methods) + c = Class.new(c) + assert_equal([], c.undefined_instance_methods) + end + + def test_s_public + o = (c = Class.new(AClass)).new + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + c.class_eval {public :aClass1} + assert_equal(:aClass1, o.aClass1) + + o = (c = Class.new(AClass)).new + c.class_eval {public :aClass1, :aClass2} + assert_equal(:aClass1, o.aClass1) + assert_equal(:aClass2, o.aClass2) + + o = (c = Class.new(AClass)).new + c.class_eval {public [:aClass1, :aClass2]} + assert_equal(:aClass1, o.aClass1) + assert_equal(:aClass2, o.aClass2) + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + end + + def test_s_private + o = (c = Class.new(AClass)).new + assert_equal(:aClass, o.aClass) + c.class_eval {private :aClass} + assert_raise(NoMethodError, /private method/) {o.aClass} + + o = (c = Class.new(AClass)).new + c.class_eval {private :aClass, :aClass2} + assert_raise(NoMethodError, /private method/) {o.aClass} + assert_raise(NoMethodError, /private method/) {o.aClass2} + + o = (c = Class.new(AClass)).new + c.class_eval {private [:aClass, :aClass2]} + assert_raise(NoMethodError, /private method/) {o.aClass} + assert_raise(NoMethodError, /private method/) {o.aClass2} + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + end + + def test_s_protected + aclass = Class.new(AClass) do + def _aClass(o) o.aClass; end + def _aClass1(o) o.aClass1; end + def _aClass2(o) o.aClass2; end + end + + o = (c = Class.new(aclass)).new + assert_equal(:aClass, o.aClass) + c.class_eval {protected :aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_equal(:aClass, c.new._aClass(o)) + + o = (c = Class.new(aclass)).new + c.class_eval {protected :aClass, :aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass1} + assert_equal(:aClass, c.new._aClass(o)) + assert_equal(:aClass1, c.new._aClass1(o)) + + o = (c = Class.new(aclass)).new + c.class_eval {protected [:aClass, :aClass1]} + assert_raise(NoMethodError, /protected method/) {o.aClass} + assert_raise(NoMethodError, /protected method/) {o.aClass1} + assert_equal(:aClass, c.new._aClass(o)) + assert_equal(:aClass1, c.new._aClass1(o)) + + o = AClass.new + assert_equal(:aClass, o.aClass) + assert_raise(NoMethodError, /private method/) {o.aClass1} + assert_raise(NoMethodError, /protected method/) {o.aClass2} + end + + def test_visibility_method_return_value + no_arg_results = nil + c = Module.new do + singleton_class.send(:public, :public, :private, :protected, :module_function) + def foo; end + def bar; end + no_arg_results = [public, private, protected, module_function] + end + + assert_equal([nil]*4, no_arg_results) + + assert_equal(:foo, c.private(:foo)) + assert_equal(:foo, c.public(:foo)) + assert_equal(:foo, c.protected(:foo)) + assert_equal(:foo, c.module_function(:foo)) + + assert_equal([:foo, :bar], c.private(:foo, :bar)) + assert_equal([:foo, :bar], c.public(:foo, :bar)) + assert_equal([:foo, :bar], c.protected(:foo, :bar)) + assert_equal([:foo, :bar], c.module_function(:foo, :bar)) + end + + def test_s_constants + c1 = Module.constants + Object.module_eval "WALTER = 99" + c2 = Module.constants + assert_equal([:WALTER], c2 - c1) + + Object.class_eval do + remove_const :WALTER + end + + assert_equal([], Module.constants(true)) + assert_equal([], Module.constants(false)) + + src = <<-INPUT + ary = Module.constants + module M + WALTER = 99 + end + class Module + include M + end + 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 + $m1 = Module.nesting + module M2 + $m2 = Module.nesting + end + end + + def test_s_nesting + assert_equal([], $m0) + assert_equal([TestModule::M1, TestModule], $m1) + assert_equal([TestModule::M1::M2, + TestModule::M1, TestModule], $m2) + end + + def test_s_new + m = Module.new + assert_instance_of(Module, m) + end + + def test_freeze + m = Module.new do + def self.baz; end + def bar; end + end + m.freeze + 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 + c = Class.new do + extend Test::Unit::Assertions + extend Test::Unit::CoreAssertions + def initialize + @foo = :foo + @bar = :bar + end + assert_deprecated_warning(/optional boolean argument/) do + attr :foo, true + end + assert_deprecated_warning(/optional boolean argument/) do + attr :bar, false + end + end + o = c.new + assert_equal(true, o.respond_to?(:foo)) + assert_equal(true, o.respond_to?(:foo=)) + assert_equal(true, o.respond_to?(:bar)) + assert_equal(false, o.respond_to?(:bar=)) + end + + def test_attr_public_at_toplevel + s = Object.new + TOPLEVEL_BINDING.eval(<<-END).call(s.singleton_class) + proc do |c| + c.send(:attr_accessor, :x) + c.send(:attr, :y) + c.send(:attr_reader, :z) + c.send(:attr_writer, :w) + end + END + assert_nil s.x + s.x = 1 + assert_equal 1, s.x + + assert_nil s.y + s.instance_variable_set(:@y, 2) + assert_equal 2, s.y + + assert_nil s.z + s.instance_variable_set(:@z, 3) + assert_equal 3, s.z + + s.w = 4 + assert_equal 4, s.instance_variable_get(:@w) + end + + def test_const_get_evaled + c1 = Class.new + c2 = Class.new(c1) + + eval("c1::Foo = :foo") + assert_equal(:foo, c1::Foo) + assert_equal(:foo, c2::Foo) + assert_equal(:foo, c2.const_get(:Foo)) + assert_raise(NameError) { c2.const_get(:Foo, false) } + + c1.__send__(:remove_const, :Foo) + eval("c1::Foo = :foo") + assert_raise(NameError) { c1::Bar } + assert_raise(NameError) { c2::Bar } + 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) + x + end + end + + assert_equal(:Bar, c1::Bar) + 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_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}") + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /X\u{3042}/) { c1.const_set(cx, :foo) } + end + end + + def test_const_get_invalid_name + c1 = Class.new + assert_raise(NameError) { c1.const_get(:foo) } + bug5084 = '[ruby-dev:44200]' + assert_raise(TypeError, bug5084) { c1.const_get(1) } + end + + 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) } + 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 + 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 + c = Class.new + 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 + c = Class.new + 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 + c = Class.new + 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 + m = Module.new + assert_raise(NameError) do + m.instance_eval { public(:foo) } + end + end + + def test_attr + assert_in_out_err([], <<-INPUT, %w(nil)) + $VERBOSE = true + c = Class.new + c.instance_eval do + private + attr_reader :foo + end + o = c.new + p(o.instance_eval { foo }) + INPUT + + c = Class.new + assert_raise(NameError) do + c.instance_eval { attr_reader :"." } + end + + c = Class.new + assert_equal([:a], c.class_eval { attr :a }) + assert_equal([:b, :c], c.class_eval { attr :b, :c }) + assert_equal([:d], c.class_eval { attr_reader :d }) + assert_equal([:e, :f], c.class_eval { attr_reader :e, :f }) + assert_equal([:g=], c.class_eval { attr_writer :g }) + assert_equal([:h=, :i=], c.class_eval { attr_writer :h, :i }) + assert_equal([:j, :j=], c.class_eval { attr_accessor :j }) + assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor :k, :l }) + + c = Class.new + assert_equal([:a], c.class_eval { attr "a" }) + assert_equal([:b, :c], c.class_eval { attr "b", "c" }) + assert_equal([:d], c.class_eval { attr_reader "d" }) + assert_equal([:e, :f], c.class_eval { attr_reader "e", "f" }) + assert_equal([:g=], c.class_eval { attr_writer "g" }) + assert_equal([:h=, :i=], c.class_eval { attr_writer "h", "i" }) + assert_equal([:j, :j=], c.class_eval { attr_accessor "j" }) + assert_equal([:k, :k=, :l, :l=], c.class_eval { attr_accessor "k", "l" }) + end + + def test_alias_method + c = Class.new do + def foo; :foo end + end + o = c.new + assert_respond_to(o, :foo) + assert_not_respond_to(o, :bar) + r = c.class_eval {alias_method :bar, :foo} + assert_respond_to(o, :bar) + assert_equal(:foo, o.bar) + assert_equal(:bar, r) + end + + def test_undef + c = Class.new + assert_raise(NameError) do + c.instance_eval { undef_method(:foo) } + end + + m = Module.new + assert_raise(NameError) do + m.instance_eval { undef_method(:foo) } + end + + o = Object.new + assert_raise(NameError) do + class << o; self; end.instance_eval { undef_method(:foo) } + end + + %w(object_id __id__ __send__ initialize).each do |n| + assert_in_out_err([], <<-INPUT, [], %r"warning: undefining '#{n}' may cause serious problems$") + $VERBOSE = false + Class.new.instance_eval { undef_method(:#{n}) } + INPUT + end + end + + def test_alias + m = Module.new + assert_raise(NameError) do + m.class_eval { alias foo bar } + end + + assert_in_out_err([], <<-INPUT, %w(2), /discarding old foo$/) + $VERBOSE = true + c = Class.new + c.class_eval do + def foo; 1; end + def bar; 2; end + end + c.class_eval { alias foo bar } + p c.new.foo + INPUT + end + + def test_mod_constants + m = Module.new + m.const_set(:Foo, :foo) + assert_equal([:Foo], m.constants(true)) + assert_equal([:Foo], m.constants(false)) + m.instance_eval { remove_const(:Foo) } + end + + 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(FrozenError) do + m.instance_eval { undef_method(:foo) } + end + end + + def test_frozen_class + c = Class.new + c.freeze + assert_raise(FrozenError) do + c.instance_eval { undef_method(:foo) } + end + end + + def test_frozen_singleton_class + klass = Class.new + o = klass.new + c = class << o; self; end + c.freeze + 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 + cl = Class.new + def_methods = proc do + def foo; end + def bar; end + def baz; end + public :foo + protected :bar + private :baz + end + cl.class_eval(&def_methods) + sc = Class.new(cl) + mod = Module.new(&def_methods) + only_prepend = Class.new{prepend(mod)} + empty_prepend = cl.clone + empty_prepend.prepend(Module.new) + overlap_prepend = cl.clone + overlap_prepend.prepend(mod) + + [[], [true], [false]].each do |args| + [cl, sc, only_prepend, empty_prepend, overlap_prepend].each do |c| + always_false = [sc, only_prepend].include?(c) && args == [false] + + assert_equal(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 + + def test_top_public_private + assert_in_out_err([], <<-INPUT, %w([:foo] [:bar] [:bar,\ :foo] [] [:bar,\ :foo] []), []) + private + def foo; :foo; end + public + def bar; :bar; end + p self.private_methods.grep(/^foo$|^bar$/) + p self.methods.grep(/^foo$|^bar$/) + + private :foo, :bar + p self.private_methods.grep(/^foo$|^bar$/).sort + + public :foo, :bar + p self.private_methods.grep(/^foo$|^bar$/).sort + + private [:foo, :bar] + p self.private_methods.grep(/^foo$|^bar$/).sort + + public [:foo, :bar] + p self.private_methods.grep(/^foo$|^bar$/).sort + INPUT + end + + def test_append_features + t = nil + m = Module.new + m.module_eval do + def foo; :foo; end + end + class << m; self; end.class_eval do + define_method(:append_features) do |mod| + t = mod + super(mod) + end + end + + m2 = Module.new + m2.module_eval { include(m) } + assert_equal(m2, t) + + o = Object.new + o.extend(m2) + assert_equal(true, o.respond_to?(:foo)) + end + + def test_append_features_raise + m = Module.new + m.module_eval do + def foo; :foo; end + end + class << m; self; end.class_eval do + define_method(:append_features) {|mod| raise } + end + + m2 = Module.new + assert_raise(RuntimeError) do + m2.module_eval { include(m) } + end + + o = Object.new + o.extend(m2) + assert_equal(false, o.respond_to?(:foo)) + end + + def test_append_features_type_error + assert_raise(TypeError) do + Module.new.instance_eval { append_features(1) } + end + end + + def test_included + m = Module.new + m.module_eval do + def foo; :foo; end + end + class << m; self; end.class_eval do + define_method(:included) {|mod| raise } + end + + m2 = Module.new + assert_raise(RuntimeError) do + m2.module_eval { include(m) } + end + + o = Object.new + o.extend(m2) + assert_equal(true, o.respond_to?(:foo)) + end + + def test_cyclic_include + m1 = Module.new + m2 = Module.new + m1.instance_eval { include(m2) } + assert_raise(ArgumentError) do + m2.instance_eval { include(m1) } + end + end + + def test_include_p + m = Module.new + c1 = Class.new + c1.instance_eval { include(m) } + c2 = Class.new(c1) + assert_equal(true, c1.include?(m)) + assert_equal(true, c2.include?(m)) + assert_equal(false, m.include?(m)) + end + + def test_send + a = AClass.new + assert_equal(:aClass, a.__send__(:aClass)) + assert_equal(:aClass1, a.__send__(:aClass1)) + assert_equal(:aClass2, a.__send__(:aClass2)) + b = BClass.new + assert_equal(:aClass, b.__send__(:aClass)) + assert_equal(:aClass1, b.__send__(:aClass1)) + assert_equal(:aClass2, b.__send__(:aClass2)) + assert_equal(:bClass1, b.__send__(:bClass1)) + assert_equal(:bClass2, b.__send__(:bClass2)) + assert_equal(:bClass3, b.__send__(:bClass3)) + end + + + def test_nonascii_name + c = eval("class ::C\u{df}; self; end") + 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) + ensure + Object.send(:remove_const, "C\u{df}") + end + + + def test_const_added + eval(<<~RUBY) + module TestConstAdded + @memo = [] + class << self + attr_accessor :memo + + def const_added(sym) + memo << sym + end + end + CONST = 1 + module SubModule + end + + class SubClass + end + end + TestConstAdded::OUTSIDE_CONST = 2 + module TestConstAdded::OutsideSubModule; end + class TestConstAdded::OutsideSubClass; end + RUBY + TestConstAdded.const_set(:CONST_SET, 3) + assert_equal [ + :CONST, + :SubModule, + :SubClass, + :OUTSIDE_CONST, + :OutsideSubModule, + :OutsideSubClass, + :CONST_SET, + ], TestConstAdded.memo + ensure + if self.class.const_defined? :TestConstAdded + self.class.send(:remove_const, :TestConstAdded) + end + end + + def test_method_added + memo = [] + mod = Module.new do + mod = self + (class << self ; self ; end).class_eval do + define_method :method_added do |sym| + memo << sym + memo << mod.instance_methods(false) + memo << (mod.instance_method(sym) rescue nil) + end + end + def f + end + alias g f + attr_reader :a + attr_writer :a + end + assert_equal :f, memo.shift + 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].sort, memo.shift.sort + assert_equal mod.instance_method(:f), memo.shift + assert_equal :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=].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]' + + line = __LINE__+4 + stderr = EnvUtil.verbose_warning do + Module.new do + def foo; end + def foo; end + end + end + assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) + assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) + + assert_warning '' do + Module.new do + def foo; end + alias bar foo + def foo; end + end + end + + assert_warning '' do + Module.new do + def foo; end + alias bar foo + alias bar foo + end + end + + line = __LINE__+4 + stderr = EnvUtil.verbose_warning do + Module.new do + define_method(:foo) do end + def foo; end + end + end + assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) + assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) + + assert_warning '' do + Module.new do + define_method(:foo) do end + alias bar foo + alias bar foo + end + end + + assert_warning('', '[ruby-dev:39397]') do + Module.new do + module_function + def foo; end + module_function :foo + end + end + + assert_warning '' do + Module.new do + def foo; end + undef foo + end + end + + stderr = EnvUtil.verbose_warning do + Module.new do + def foo; end + mod = self + c = Class.new do + include mod + end + c.new.foo + def foo; end + end + end + assert_match(/: warning: method redefined; discarding old foo/, stderr) + assert_match(/: warning: previous definition of foo/, stderr) + end + + def test_module_function_inside_method + assert_warn(/calling module_function without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Module.new do + def self.foo + module_function + end + foo + end + end + end + + def test_protected_singleton_method + klass = Class.new + x = klass.new + class << x + protected + + def foo + end + end + assert_raise(NoMethodError) do + x.foo + end + klass.send(:define_method, :bar) do + x.foo + end + assert_nothing_raised do + x.bar + end + y = klass.new + assert_raise(NoMethodError) do + y.bar + end + end + + def test_uninitialized_toplevel_constant + bug3123 = '[ruby-dev:40951]' + e = assert_raise(NameError) {eval("Bug3123", TOPLEVEL_BINDING)} + assert_not_match(/Object::/, e.message, bug3123) + end + + def test_attr_inherited_visibility + bug3406 = '[ruby-core:30638]' + c = Class.new do + class << self + private + def attr_accessor(*); super; end + end + attr_accessor :x + end.new + 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/) do + Warning[:deprecated] = true + c::FOO + end + assert_warn(/#{c}::FOO is deprecated/) do + Warning[:deprecated] = true + Class.new(c)::FOO + end + bug12382 = '[ruby-core:75505] [Bug #12382]' + assert_warn(/deprecated/, bug12382) do + Warning[:deprecated] = true + c.class_eval "FOO" + end + assert_warn('') do + Warning[:deprecated] = false + c::FOO + end + assert_warn('') do + Warning[:deprecated] = false + Class.new(c)::FOO + end + assert_warn(/deprecated/) do + c.class_eval {remove_const "FOO"} + end + 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_frozen_prepend_remove_method + [Module, Class].each do |klass| + mod = klass.new do + prepend(Module.new) + def foo; end + end + mod.freeze + assert_raise(FrozenError, '[Bug #19166]') { mod.send(:remove_method, :foo) } + assert_equal([:foo], mod.instance_methods(false)) + end + end + + def test_prepend_class_ancestors + bug6658 = '[ruby-core:45919]' + m = labeled_module("m") + 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([m0, m1, m3, m0, m1], m3.ancestors) + m3 = labeled_module("m3") {prepend m1; include m1} + assert_equal([m0, m1, m3], m3.ancestors) + m3 = labeled_module("m3") {prepend m1; prepend m1} + 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_visibility_after_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + Module.new do + refine sc do + def x; :z end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_prepend_visibility + bug8005 = '[ruby-core:53106] [Bug #8005]' + c = Class.new do + 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_prepended_module_with_super_and_alias + bug16736 = '[Bug #16736]' + + a = labeled_class("A") do + def m; "A"; end + end + m = labeled_module("M") do + prepend Module.new + + def self.included(base) + base.alias_method :base_m, :m + end + + def m + super + "M" + end + + def m2 + base_m + end + end + b = labeled_class("B", a) do + include m + end + assert_equal("AM", b.new.m2, bug16736) + end + + def test_prepend_super_in_alias + bug7842 = '[Bug #7842]' + + 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 + + def test_class_variable_in_dup_class + a = Class.new do + @@a = 'A' + def a=(x) + @@a = x + end + def a + @@a + end + end + + b = a.dup + b.new.a = 'B' + assert_equal 'A', a.new.a, '[ruby-core:17019]' + end + + Bug6891 = '[ruby-core:47241]' + + def test_extend_module_with_protected_method + 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 + $foo + \u3042$ + ].each do |name| + e = assert_raise(NameError) do + Module.new { attr_accessor name.to_sym } + end + assert_equal(name, e.name.to_s) + end + end + + class AttrTest + class << self + attr_accessor :cattr + def reset + self.cattr = nil + end + end + attr_accessor :iattr + def ivar + @ivar + end + end + + def test_uninitialized_instance_variable + a = AttrTest.new + assert_warning('') 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('') 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 + + AttrTest.reset + 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_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_ruby_status [], %{ + 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_prepend_constant_lookup + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + prepend m + end + sc = Class.new(c) + # Situation from [Bug #17887] + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + assert_equal(:c, c::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Same ancestors, built with include instead of prepend + m = Module.new do + const_set(:C, :m) + end + c = Class.new do + const_set(:C, :c) + end + sc = Class.new(c) do + include m + end + + assert_equal(sc.ancestors.take(3), [sc, m, c]) + assert_equal(:m, sc.const_get(:C)) + assert_equal(:m, sc::C) + + m.send(:remove_const, :C) + assert_equal(:c, sc.const_get(:C)) + assert_equal(:c, sc::C) + + # Situation from [Bug #17887], but with modules + m = Module.new do + const_set(:C, :m) + end + m2 = Module.new do + const_set(:C, :m2) + prepend m + end + c = Class.new do + include m2 + end + assert_equal(c.ancestors.take(3), [c, m, m2]) + assert_equal(:m, c.const_get(:C)) + assert_equal(:m, c::C) + end + + def test_inspect_segfault + bug_10282 = '[ruby-core:65214] [Bug #10282]' + assert_separately [], "#{<<~"begin;"}\n#{<<~'end;'}" + bug_10282 = "#{bug_10282}" + begin; + line = __LINE__ + 2 + 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)() -:#{line}>" + assert_equal expect, A.new.method(:inspect).inspect, bug_10282 + end; + end + + def test_define_method_changes_visibility_with_existing_method_bug_19749 + c = Class.new do + def a; end + private def b; end + + define_method(:b, instance_method(:b)) + private + define_method(:a, instance_method(:a)) + end + assert_equal([:b], c.public_instance_methods(false)) + assert_equal([:a], c.private_instance_methods(false)) + 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", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /is not a module/) { + m.module_eval "module A; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + n = "M\u{1f5ff}" + m.module_eval "#{n} = 42", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /#{n} is not a module/) { + m.module_eval "module #{n}; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + + assert_separately([], <<-"end;") + Etc = (class C\u{1f5ff}; self; end).new + 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 + + ConstLocation = [__FILE__, __LINE__] + + def test_const_source_location + assert_equal(ConstLocation, self.class.const_source_location(:ConstLocation)) + assert_equal(ConstLocation, self.class.const_source_location("ConstLocation")) + assert_equal(ConstLocation, Object.const_source_location("#{self.class.name}::ConstLocation")) + assert_raise(TypeError) { + self.class.const_source_location(nil) + } + assert_raise_with_message(NameError, /wrong constant name/) { + self.class.const_source_location("xxx") + } + assert_raise_with_message(TypeError, %r'does not refer to class/module') { + self.class.const_source_location("ConstLocation::FILE") + } + end + + module CloneTestM_simple + C = 1 + def self.m; C; end + end + + module CloneTestM0 + def foo; TEST; end + end + + CloneTestM1 = CloneTestM0.clone + CloneTestM2 = CloneTestM0.clone + module CloneTestM1 + TEST = :M1 + end + module CloneTestM2 + TEST = :M2 + end + class CloneTestC1 + include CloneTestM1 + end + class CloneTestC2 + include CloneTestM2 + end + + def test_constant_access_from_method_in_cloned_module + m = CloneTestM_simple.dup + assert_equal 1, m::C, '[ruby-core:47834]' + assert_equal 1, m.m, '[ruby-core:47834]' + + assert_equal :M1, CloneTestC1.new.foo, '[Bug #15877]' + assert_equal :M2, CloneTestC2.new.foo, '[Bug #15877]' + end + + def test_clone_freeze + m = Module.new.freeze + assert_predicate m.clone, :frozen? + assert_not_predicate m.clone(freeze: false), :frozen? + end + + def test_module_name_in_singleton_method + s = Object.new.singleton_class + mod = s.const_set(:Foo, Module.new) + assert_match(/::Foo$/, mod.name, '[Bug #14895]') + end + + def test_iclass_memory_leak + # [Bug #19550] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + mod = Module.new + Class.new do + include mod + end + end + 1_000.times(&code) + PREP + 3_000_000.times(&code) + CODE + end + + def test_complemented_method_entry_memory_leak + # [Bug #19894] [Bug #19896] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + $c = Class.new do + def foo; end + end + + $m = Module.new do + refine $c do + def foo; end + end + end + + Class.new do + using $m + + def initialize + o = $c.new + o.method(:foo).unbind + end + end.new + end + 1_000.times(&code) + PREP + 300_000.times(&code) + CODE + end + + def test_module_clone_memory_leak + # [Bug #19901] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + Module.new.clone + end + 1_000.times(&code) + PREP + 1_000_000.times(&code) + CODE + end + + def test_set_temporary_name + m = Module.new + assert_nil m.name + + m.const_set(:N, Module.new) + + assert_match(/\A#<Module:0x\h+>::N\z/, m::N.name) + assert_same m::N, m::N.set_temporary_name(name = "fake_name_under_M") + name.upcase! + assert_equal("fake_name_under_M", m::N.name) + assert_raise(FrozenError) {m::N.name.upcase!} + assert_same m::N, m::N.set_temporary_name(nil) + assert_nil(m::N.name) + + m::N.const_set(:O, Module.new) + m.const_set(:Recursive, m) + m::N.const_set(:Recursive, m) + m.const_set(:A, 42) + + assert_same m, m.set_temporary_name(name = "fake_name") + name.upcase! + assert_equal("fake_name", m.name) + assert_raise(FrozenError) {m.name.upcase!} + assert_equal("fake_name::N", m::N.name) + assert_equal("fake_name::N::O", m::N::O.name) + + assert_same m, m.set_temporary_name(nil) + assert_nil m.name + assert_nil m::N.name + assert_nil m::N::O.name + + assert_raise_with_message(ArgumentError, "empty class/module name") do + m.set_temporary_name("") + end + %w[A A::B ::A ::A::B].each do |name| + assert_raise_with_message(ArgumentError, /must not be a constant path/) do + m.set_temporary_name(name) + end + end + + [Object, User, AClass].each do |mod| + assert_raise_with_message(RuntimeError, /permanent name/) do + mod.set_temporary_name("fake_name") + end + end + 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 /) { + recv = self + recv.#{method} + } + } + end +end diff --git a/test/ruby/test_name_error.rb b/test/ruby/test_name_error.rb new file mode 100644 index 0000000000..f0402de4b9 --- /dev/null +++ b/test/ruby/test_name_error.rb @@ -0,0 +1,156 @@ +require 'test/unit' + +class TestNameError < Test::Unit::TestCase + def test_new_default + error = NameError.new + assert_equal("NameError", error.message) + end + + def test_new_message + error = NameError.new("Message") + assert_equal("Message", error.message) + end + + def test_new_name + error = NameError.new("Message") + assert_nil(error.name) + + error = NameError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_new_receiver + receiver = Object.new + + error = NameError.new + assert_raise(ArgumentError) {error.receiver} + assert_equal("NameError", error.message) + + error = NameError.new(receiver: receiver) + assert_equal(["NameError", receiver], + [error.message, error.receiver]) + + error = NameError.new("Message", :foo, receiver: receiver) + assert_equal(["Message", receiver, :foo], + [error.message, error.receiver, error.name]) + end + + PrettyObject = + Class.new(BasicObject) do + alias object_id __id__ + def pretty_inspect; "`obj'"; end + alias inspect pretty_inspect + end + + def test_info_const + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval("Object") + } + assert_equal(:Object, e.name) + + e = assert_raise(NameError) { + BasicObject::X + } + assert_same(BasicObject, e.receiver) + assert_equal(:X, e.name) + end + + def test_info_const_name + mod = Module.new do + def self.name + "ModuleName" + end + + def self.inspect + raise "<unusable info>" + end + end + assert_raise_with_message(NameError, /ModuleName/) {mod::DOES_NOT_EXIST} + end + + def test_info_method + obj = PrettyObject.new + + e = assert_raise(NameError) { + obj.instance_eval {foo} + } + assert_equal(:foo, e.name) + assert_same(obj, e.receiver) + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + + def test_info_local_variables + obj = PrettyObject.new + def obj.test(a, b=nil, *c, &d) + e = a + 1.times {|f| g = foo; g} + e + end + + e = assert_raise(NameError) { + obj.test(3) + } + assert_equal(:foo, e.name) + assert_same(obj, e.receiver) + assert_equal(%i[a b c d e f g], e.local_variables.sort) + end + + def test_info_method_missing + obj = PrettyObject.new + def obj.method_missing(*) + super + end + + e = assert_raise(NoMethodError) { + obj.foo(1, 2) + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_not_predicate(e, :private_call?) + + e = assert_raise(NoMethodError) { + obj.instance_eval {foo(1, 2)} + } + assert_equal(:foo, e.name) + assert_equal([1, 2], e.args) + assert_same(obj, e.receiver) + assert_predicate(e, :private_call?) + end + + def test_info_parent_iseq_mark + assert_separately(['-', File.join(__dir__, 'bug-11928.rb')], <<-'end;') + -> {require ARGV[0]}.call + end; + end + + def test_large_receiver_inspect + receiver = Class.new do + def self.inspect + 'A' * 120 + end + end + + error = assert_raise(NameError) do + receiver::FOO + end + assert_match(/\Auninitialized constant #{'A' * 120}::FOO$/, error.message) + end +end diff --git a/test/ruby/test_nomethod_error.rb b/test/ruby/test_nomethod_error.rb new file mode 100644 index 0000000000..6abd20cc81 --- /dev/null +++ b/test/ruby/test_nomethod_error.rb @@ -0,0 +1,137 @@ +require 'test/unit' + +class TestNoMethodError < Test::Unit::TestCase + def test_new_default + error = NoMethodError.new + assert_equal("NoMethodError", error.message) + end + + def test_new_message + error = NoMethodError.new("Message") + assert_equal("Message", error.message) + end + + def test_new_name + error = NoMethodError.new("Message") + assert_nil(error.name) + + error = NoMethodError.new("Message", :foo) + assert_equal(:foo, error.name) + end + + def test_new_name_args + error = NoMethodError.new("Message", :foo) + assert_nil(error.args) + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_equal([:foo, [1, 2]], [error.name, error.args]) + end + + def test_new_name_args_priv + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_not_predicate(error, :private_call?) + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_equal([:foo, [1, 2], true], + [error.name, error.args, error.private_call?]) + end + + def test_new_receiver + receiver = Object.new + + error = NoMethodError.new + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new(receiver: receiver) + assert_equal(receiver, error.receiver) + + error = NoMethodError.new("Message") + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", receiver: receiver) + assert_equal(["Message", receiver], + [error.message, error.receiver]) + + error = NoMethodError.new("Message", :foo) + assert_raise(ArgumentError) {error.receiver} + + msg = "Message" + + error = NoMethodError.new("Message", :foo, receiver: receiver) + assert_match msg, error.message + assert_equal :foo, error.name + assert_equal receiver, error.receiver + + error = NoMethodError.new("Message", :foo, [1, 2]) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], receiver: receiver) + assert_match msg, error.message + assert_equal :foo, error.name + assert_equal [1, 2], error.args + assert_equal receiver, error.receiver + + error = NoMethodError.new("Message", :foo, [1, 2], true) + assert_raise(ArgumentError) {error.receiver} + + error = NoMethodError.new("Message", :foo, [1, 2], true, receiver: receiver) + assert_equal :foo, error.name + assert_equal [1, 2], error.args + assert_equal receiver, error.receiver + assert_predicate error, :private_call? + end + + def test_message_encoding + bug3237 = '[ruby-core:29948]' + str = "\u2600" + id = :"\u2604" + msg = "undefined method '#{id}' for an instance of String" + assert_raise_with_message(NoMethodError, Regexp.compile(Regexp.quote(msg)), bug3237) do + str.__send__(id) + end + end + + def test_to_s + pre = Module.new do + def name + BasicObject.new + end + end + mod = Module.new + mod.singleton_class.prepend(pre) + + err = assert_raise(NoMethodError) do + mod.this_method_does_not_exist + end + + assert_match(/undefined method.+this_method_does_not_exist.+for.+Module/, err.to_s) + end + + def test_send_forward_raises + t = EnvUtil.labeled_class("Test") do + def foo(...) + forward(...) + end + end + obj = t.new + assert_raise(NoMethodError) do + obj.foo + end + end + + # [Bug #21535] + def test_send_forward_raises_when_called_through_vcall + t = EnvUtil.labeled_class("Test") do + def foo(...) + forward(...) + end + def foo_indirect + foo # vcall + end + end + obj = t.new + assert_raise(NoMethodError) do + obj.foo_indirect + end + 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_numeric.rb b/test/ruby/test_numeric.rb new file mode 100644 index 0000000000..35496ac875 --- /dev/null +++ b/test/ruby/test_numeric.rb @@ -0,0 +1,498 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestNumeric < Test::Unit::TestCase + def test_coerce + a, b = 1.coerce(2) + 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} + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /:\u{3042}/) {1+:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {1&:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {1|:"\u{3042}"} + assert_raise_with_message(TypeError, /:\u{3042}/) {1^:"\u{3042}"} + + assert_raise_with_message(TypeError, /:\u{3044}/) {1+"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1&"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1|"\u{3044}".to_sym} + assert_raise_with_message(TypeError, /:\u{3044}/) {1^"\u{3044}".to_sym} + end + + EnvUtil.with_default_internal(Encoding::US_ASCII) do + assert_raise_with_message(TypeError, /:"\\u3042"/) {1+:"\u{3042}"} + assert_raise_with_message(TypeError, /:"\\u3042"/) {1&:"\u{3042}"} + assert_raise_with_message(TypeError, /:"\\u3042"/) {1|:"\u{3042}"} + assert_raise_with_message(TypeError, /:"\\u3042"/) {1^:"\u{3042}"} + end + + 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 = Class.new(Numeric) do + def coerce(x); nil; end + end.new + assert_raise(TypeError) { -a } + assert_nil(1 <=> a) + assert_raise(ArgumentError) { 1 <= a } + + a = Class.new(Numeric) do + def coerce(x); 1.coerce(x); end + end.new + assert_equal(2, 1 + a) + assert_equal(0, 1 <=> a) + assert_operator(1, :<=, a) + + a = Class.new(Numeric) do + def coerce(x); [x, 1]; end + end.new + assert_equal(-1, -a) + + a = Class.new(Numeric) do + def coerce(x); raise StandardError, "my error"; end + end.new + assert_raise_with_message(StandardError, "my error") { 1 + a } + assert_raise_with_message(StandardError, "my error") { 1 < a } + + a = Class.new(Numeric) do + def coerce(x); :bad_return_value; end + end.new + assert_raise_with_message(TypeError, "coerce must return [x, y]") { 1 + a } + assert_raise_with_message(TypeError, "coerce must return [x, y]") { 1 < a } + end + + def test_singleton_method + a = Numeric.new + assert_raise_with_message(TypeError, /foo/) { def a.foo; end } + assert_raise_with_message(TypeError, /\u3042/) { eval("def a.\u3042; end") } + end + + def test_dup + a = Numeric.new + assert_same a, a.dup + end + + def test_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 + 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 + x = Class.new(Numeric) do + def /(x); 42.0; end + def %(x); :mod; end + end.new + + 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]') + end + + def test_real_p + assert_predicate(Numeric.new, :real?) + end + + def test_integer_p + assert_not_predicate(Numeric.new, :integer?) + end + + def test_abs + a = Class.new(Numeric) do + def -@; :ok; end + def <(x); true; end + end.new + + assert_equal(:ok, a.abs) + + a = Class.new(Numeric) do + def <(x); false; end + end.new + + assert_equal(a, a.abs) + end + + def test_zero_p + a = Class.new(Numeric) do + def ==(x); true; end + end.new + + assert_predicate(a, :zero?) + 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 + a = Class.new(Numeric) do + def to_i; :ok; end + end.new + + assert_equal(:ok, a.to_int) + end + + def test_cmp + a = Numeric.new + assert_equal(0, a <=> a) + assert_nil(a <=> :foo) + end + + def test_float_round_ndigits + bug14635 = "[ruby-core:86323]" + f = 0.5 + 31.times do |i| + assert_equal(0.5, f.round(i+1), bug14635 + " (argument: #{i+1})") + end + end + + def test_floor_ceil_round_truncate + a = Class.new(Numeric) do + def to_f; 1.5; end + end.new + + assert_equal(1, a.floor) + assert_equal(2, a.ceil) + assert_equal(2, a.round) + assert_equal(1, a.truncate) + + a = Class.new(Numeric) do + def to_f; 1.4; end + end.new + + assert_equal(1, a.floor) + assert_equal(2, a.ceil) + assert_equal(1, a.round) + assert_equal(1, a.truncate) + + a = Class.new(Numeric) do + def to_f; -1.5; end + end.new + + assert_equal(-2, a.floor) + assert_equal(-1, a.ceil) + assert_equal(-2, a.round) + assert_equal(-1, a.truncate) + end + + def test_floor_ceil_ndigits + bug17183 = "[ruby-core:100090]" + f = 291.4 + 31.times do |i| + assert_equal(291.4, f.floor(i+1), bug17183) + assert_equal(291.4, f.ceil(i+1), bug17183) + end + end + + def assert_step(expected, (from, *args), inf: false) + kw = args.last.is_a?(Hash) ? args.pop : {} + enum = from.step(*args, **kw) + size = enum.size + xsize = expected.size + + if inf + assert_send [size, :infinite?], "step size: +infinity" + assert_send [size, :>, 0], "step size: +infinity" + + a = [] + from.step(*args, **kw) { |x| a << x; break if a.size == xsize } + assert_equal expected, a, "step" + + 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 = [] + from.step(*args, **kw) { |x| a << x } + assert_equal expected, a, "step" + + a = [] + enum.each { |x| a << x } + assert_equal expected, a, "step enumerator" + end + end + + 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, nil).size } + assert_nothing_raised { 1.step(by: nil) } + assert_nothing_raised { 1.step(by: nil).size } + + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: nil)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: 10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: -1)) + + bug9811 = '[ruby-dev:48177] [Bug #9811]' + assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} } + assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size } + 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 } + + feature15573 = "[ruby-core:91324] [Feature #15573]" + assert_raise(ArgumentError, feature15573) { 1.step(10, 0) } + assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) } + assert_raise(ArgumentError, feature15573) { 1.step(10, 0) { break } } + assert_raise(ArgumentError, feature15573) { 1.step(10, by: 0) { break } } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: -Float::INFINITY) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0, to: 42.5) } + assert_raise(ArgumentError, feature15573) { 4.2.step(by: 0.0) } + assert_raise(ArgumentError, feature15573) { 4.2.step(by: -0.0) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 44) } + assert_raise(ArgumentError, feature15573) { 42.step(by: 0.0, to: 0) } + assert_raise(ArgumentError, feature15573) { 42.step(by: -0.0, to: 44) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0.0) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: bignum+1) } + assert_raise(ArgumentError, feature15573) { bignum.step(by: 0, to: 0) } + + e = 1.step(10, {by: "1"}) + assert_raise(TypeError) {e.next} + assert_raise(TypeError) {e.size} + e = 1.step(to: "10") + assert_raise(ArgumentError) {e.next} + assert_raise(ArgumentError) {e.size} + + assert_equal(bignum*2+1, (-bignum).step(bignum, 1).size) + assert_equal(bignum*2, (-bignum).step(bignum-1, 1).size) + + 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 [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 + end + + def test_step_bug15537 + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, to: 1, by: -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10.0, 1, -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, to: 1.0, by: -2] + assert_step [10.0, 8.0, 6.0, 4.0, 2.0], [10, 1.0, -2] + + assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10, by: -1.0, to: nil], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10, nil, -1.0], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10.0, by: -1], inf: true + assert_step [10.0, 9.0, 8.0, 7.0], [10.0, nil, -1], inf: true + end + + def test_num2long + assert_raise(TypeError) { 1 & nil } + assert_raise(TypeError) { 1 & 1.0 } + assert_raise(TypeError) { 1 & 2147483648.0 } + assert_raise(TypeError) { 1 & 9223372036854777856.0 } + o = Object.new + def o.to_int; 1; end + assert_raise(TypeError) { assert_equal(1, 1 & o) } + end + + def test_eql + 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_remainder_infinity + assert_equal(4, 4.remainder(Float::INFINITY)) + assert_equal(4, 4.remainder(-Float::INFINITY)) + assert_equal(-4, -4.remainder(Float::INFINITY)) + assert_equal(-4, -4.remainder(-Float::INFINITY)) + + assert_equal(4.2, 4.2.remainder(Float::INFINITY)) + assert_equal(4.2, 4.2.remainder(-Float::INFINITY)) + assert_equal(-4.2, -4.2.remainder(Float::INFINITY)) + assert_equal(-4.2, -4.2.remainder(-Float::INFINITY)) + end + + def test_comparison_comparable + bug12864 = '[ruby-core:77713] [Bug #12864]' + + 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]') + + integers = [-2, -1, 0, 1, 2, 3, 6, 1234567890123456789] + integers.each do |i| + assert_equal(0, i.pow(0, 1), '[Bug #17257]') + assert_equal(1, i.pow(0, 2)) + assert_equal(1, i.pow(0, 3)) + assert_equal(1, i.pow(0, 6)) + assert_equal(1, i.pow(0, 1234567890123456789)) + + assert_equal(0, i.pow(0, -1)) + assert_equal(-1, i.pow(0, -2)) + assert_equal(-2, i.pow(0, -3)) + assert_equal(-5, i.pow(0, -6)) + assert_equal(-1234567890123456788, i.pow(0, -1234567890123456789)) + end + + assert_equal(0, 0.pow(2, 1)) + assert_equal(0, 0.pow(3, 1)) + assert_equal(0, 2.pow(3, 1)) + assert_equal(0, -2.pow(3, 1)) + + min, max = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX") + assert_equal(0, 0.pow(2, min)) + assert_equal(0, Integer.sqrt(max+1).pow(2, min)) + end + +end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb new file mode 100644 index 0000000000..f4dfe2251b --- /dev/null +++ b/test/ruby/test_object.rb @@ -0,0 +1,1105 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' + +class TestObject < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $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_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 + + c = a.clone + assert_equal(false, c.frozen?) + assert_equal(false, a.frozen?) + assert_equal(2, c.b) + + c = a.clone(freeze: true) + assert_equal(true, c.frozen?) + assert_equal(false, a.frozen?) + assert_equal(2, c.b) + + a.freeze + c = a.clone + assert_equal(true, c.frozen?) + assert_equal(true, a.frozen?) + assert_equal(2, c.b) + + assert_raise(ArgumentError) {a.clone(freeze: [])} + d = a.clone(freeze: false) + def d.e; 3; end + assert_equal(false, d.frozen?) + assert_equal(true, a.frozen?) + assert_equal(2, d.b) + assert_equal(3, d.e) + + 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 + + c = Class.new do + attr_reader :f + end + o = c.new + def o.initialize_clone(_, freeze: true) + @f = freeze + super + end + clone = o.clone + assert_kind_of c, clone + assert_equal true, clone.f + clone = o.clone(freeze: false) + assert_kind_of c, clone + assert_equal false, clone.f + + class << o + remove_method(:initialize_clone) + end + def o.initialize_clone(_) + super + end + assert_kind_of c, o.clone + assert_raise(ArgumentError) { o.clone(freeze: false) } + end + + def test_init_dupclone + 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 + + def test_kind_of + assert_raise(TypeError) { 1.kind_of?(1) } + end + + def test_freeze_immediate + assert_equal(true, 1.frozen?) + 1.freeze + assert_equal(true, 1.frozen?) + assert_equal(true, 2.frozen?) + assert_equal(true, true.frozen?) + assert_equal(true, false.frozen?) + assert_equal(true, nil.frozen?) + end + + def test_frozen_error_message + name = "C\u{30c6 30b9 30c8}" + klass = EnvUtil.labeled_class(name) { + attr_accessor :foo + } + obj = klass.new.freeze + assert_raise_with_message(FrozenError, /#{name}/) { + obj.foo = 1 + } + end + + def test_nil_to_f + assert_equal(0.0, nil.to_f) + end + + def test_nil_to_s + str = nil.to_s + assert_equal("", str) + assert_predicate(str, :frozen?) + assert_same(str, nil.to_s) + end + + def test_true_to_s + str = true.to_s + assert_equal("true", str) + assert_predicate(str, :frozen?) + assert_same(str, true.to_s) + end + + def test_false_to_s + str = false.to_s + assert_equal("false", str) + assert_predicate(str, :frozen?) + assert_same(str, false.to_s) + end + + def test_not + assert_equal(false, Object.new.send(:!)) + assert_equal(true, nil.send(:!)) + end + + def test_true_and + assert_equal(true, true & true) + assert_equal(true, true & 1) + assert_equal(false, true & false) + assert_equal(false, true & nil) + end + + def test_true_or + assert_equal(true, true | true) + assert_equal(true, true | 1) + assert_equal(true, true | false) + assert_equal(true, true | nil) + end + + def test_true_xor + assert_equal(false, true ^ true) + assert_equal(false, true ^ 1) + assert_equal(true, true ^ false) + assert_equal(true, true ^ nil) + end + + def test_false_and + assert_equal(false, false & true) + assert_equal(false, false & 1) + assert_equal(false, false & false) + assert_equal(false, false & nil) + end + + def test_false_or + assert_equal(true, false | true) + assert_equal(true, false | 1) + assert_equal(false, false | false) + assert_equal(false, false | nil) + end + + def test_false_xor + assert_equal(true, false ^ true) + assert_equal(true, false ^ 1) + assert_equal(false, false ^ false) + assert_equal(false, false ^ nil) + end + + def test_methods + o = Object.new + a1 = o.methods + a2 = o.methods(false) + + def o.foo; end + + assert_equal([:foo], o.methods(true) - a1) + assert_equal([:foo], o.methods(false) - a2) + end + + def test_methods2 + c0 = Class.new + c1 = Class.new(c0) + c1.module_eval do + public ; def foo; end + protected; def bar; end + private ; def baz; end + end + c2 = Class.new(c1) + c2.module_eval do + public ; def foo2; end + protected; def bar2; end + private ; def baz2; end + end + + o0 = c0.new + o2 = c2.new + + assert_equal([:baz, :baz2], (o2.private_methods - o0.private_methods).sort) + assert_equal([:baz2], (o2.private_methods(false) - o0.private_methods(false)).sort) + + assert_equal([:bar, :bar2], (o2.protected_methods - o0.protected_methods).sort) + assert_equal([:bar2], (o2.protected_methods(false) - o0.protected_methods(false)).sort) + + assert_equal([:foo, :foo2], (o2.public_methods - o0.public_methods).sort) + 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_methods_prepend_singleton + c = Class.new(Module) {private def foo; end} + k = c.new + k.singleton_class + c.module_eval {prepend(Module.new)} + assert_equal([:foo], k.private_methods(false)) + end + + class ToStrCounter + def initialize(str = "@foo") @str = str; @count = 0; end + def to_str; @count += 1; @str; end + def count; @count; end + 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 = ToStrCounter.new + 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 = ToStrCounter.new + o.instance_variable_set(n, :bar) + assert_equal(:bar, o.instance_eval { @foo }) + assert_equal(1, n.count) + end + + def test_instance_variable_defined + o = Object.new + 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 = ToStrCounter.new + assert_equal(true, o.instance_variable_defined?(n)) + assert_equal(1, n.count) + end + + def test_remove_instance_variable + { '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_remove_instance_variable_re_embed + assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + c = Class.new do + attr_reader :a, :b, :c + + def initialize + @a = nil + @b = nil + @c = nil + end + end + + o1 = c.new + o2 = c.new + + o1.instance_variable_set(:@foo, 5) + o1.instance_variable_set(:@a, 0) + o1.instance_variable_set(:@b, 1) + o1.instance_variable_set(:@c, 2) + refute_includes ObjectSpace.dump(o1), '"embedded":true' + o1.remove_instance_variable(:@foo) + assert_includes ObjectSpace.dump(o1), '"embedded":true' + + o2.instance_variable_set(:@a, 0) + o2.instance_variable_set(:@b, 1) + o2.instance_variable_set(:@c, 2) + assert_includes ObjectSpace.dump(o2), '"embedded":true' + + assert_equal(0, o1.a) + assert_equal(1, o1.b) + assert_equal(2, o1.c) + assert_equal(0, o2.a) + assert_equal(1, o2.b) + assert_equal(2, o2.c) + end; + end + + def test_convert_string + o = Object.new + def o.to_s; 1; end + assert_raise(TypeError) { String(o) } + o.singleton_class.remove_method(:to_s) + def o.to_s; "o"; end + assert_equal("o", String(o)) + def o.to_str; "O"; end + assert_equal("O", String(o)) + def o.respond_to?(*) false; end + assert_raise(TypeError) { String(o) } + end + + def test_convert_array + o = Object.new + def o.to_a; 1; end + assert_raise(TypeError) { Array(o) } + o.singleton_class.remove_method(:to_a) + def o.to_a; [1]; end + assert_equal([1], Array(o)) + def o.to_ary; [2]; end + 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)) + o.singleton_class.remove_method(:to_hash) + 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) } + o.singleton_class.remove_method(:to_i) + 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 + def initialize(n); @num = n; end + def to_int; @num; end + def <=>(n); @num <=> n.to_int; end + def <=(n); @num <= n.to_int; end + def +(n); MyInteger.new(@num + n.to_int); end + end + + def test_check_to_integer + o1 = MyInteger.new(1) + o9 = MyInteger.new(9) + n = 0 + Range.new(o1, o9).step(2) {|x| n += x.to_int } + assert_equal(1+3+5+7+9, n) + end + + def test_max_shape_variation_with_performance_warnings + assert_in_out_err([], <<-INPUT, %w(), /The class Foo reached 8 shape variations, instance variables accesses will be slower and memory usage increased/) + $VERBOSE = false + Warning[:performance] = true + + class Foo; end + 10.times do |i| + Foo.new.instance_variable_set(:"@a\#{i}", nil) + end + INPUT + end + + def test_redefine_method_under_verbose + assert_in_out_err([], <<-INPUT, %w(2), /warning: method redefined; discarding old foo$/) + $VERBOSE = true + o = Object.new + def o.foo; 1; end + def o.foo; 2; end + p o.foo + INPUT + end + + def test_redefine_method_which_may_case_serious_problem + %w(object_id __id__ __send__).each do |m| + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$") + $VERBOSE = false + def (Object.new).#{m}; end + INPUT + + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$") + $VERBOSE = false + Class.new.define_method(:#{m}) {} + INPUT + + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$") + $VERBOSE = false + Class.new.attr_reader(:#{m}) + INPUT + + assert_in_out_err([], <<-INPUT, [], %r"warning: redefining '#{m}' may cause serious problems$") + $VERBOSE = false + Class.new do + def foo; end + alias #{m} foo + end + INPUT + end + + 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 + c = Class.new + c.freeze + assert_raise(FrozenError) do + c.instance_eval { remove_method(:foo) } + end + + c = Class.new do + def meth1; "meth" end + end + d = Class.new(c) do + alias meth2 meth1 + end + o1 = c.new + assert_respond_to(o1, :meth1) + assert_equal("meth", o1.meth1) + o2 = d.new + assert_respond_to(o2, :meth1) + assert_equal("meth", o2.meth1) + assert_respond_to(o2, :meth2) + assert_equal("meth", o2.meth2) + d.class_eval do + remove_method :meth2 + end + bug2202 = '[ruby-core:26074]' + assert_raise(NoMethodError, bug2202) {o2.meth2} + + %w(object_id __id__ __send__ initialize).each do |m| + 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}) } + rescue NameError + p :ok + 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 + assert_raise(ArgumentError) do + 1.instance_eval { method_missing } + end + + c = Class.new + c.class_eval do + protected + def foo; end + end + assert_raise(NoMethodError) do + c.new.foo + end + + assert_raise(NoMethodError) do + 1.instance_eval { method_missing(:method_missing) } + end + + c.class_eval do + undef_method(:method_missing) + end + + assert_raise(ArgumentError) do + c.new.method_missing + end + + bug2494 = '[ruby-core:27219]' + c = Class.new do + def method_missing(meth, *args) + super + end + end + b = c.new + foo rescue nil + 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) + if id == :foobar + true + else + false + end + end + def method_missing(id, *args) + if id == :foobar + return [:foo, *args] + else + super + end + end + end + + foo = c.new + 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_respond_to(foo, :foobar) + assert_not_respond_to(foo, :foobarbaz) + assert_raise(NoMethodError) do + foo.foobarbaz + end + + foobar = foo.method(:foobar) + assert_equal(-1, foobar.arity); + assert_equal([:foo], foobar.call); + assert_equal([:foo, 1], foobar.call(1)); + assert_equal([:foo, 1, 2, 3, 4, 5], foobar.call(1, 2, 3, 4, 5)); + assert_equal(foobar, foo.method(:foobar)) + assert_not_equal(foobar, c.new.method(:foobar)) + + c = Class.new(c) + assert_equal(false, c.method_defined?(:foobar)) + assert_raise(NameError, '[ruby-core:25748]') do + c.instance_method(:foobar) + end + + m = Module.new + assert_equal(false, m.method_defined?(:foobar)) + assert_raise(NameError, '[ruby-core:25748]') do + m.instance_method(:foobar) + 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, priv = false| + 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]' + + o = Object.new + e = assert_raise(NoMethodError) { + o.method(:__send__).call(:never_defined_test_no_superclass_method) + } + m1 = e.message + assert_no_match(/no superclass method/, m1, bug2312) + e = assert_raise(NoMethodError) { + o.method(:__send__).call(:never_defined_test_no_superclass_method) + } + assert_equal(m1, e.message, bug2312) + e = assert_raise(NoMethodError) { + o.never_defined_test_no_superclass_method + } + assert_equal(m1.lines.first, e.message.lines.first, bug2312) + end + + def test_superclass_method + bug2312 = '[ruby-dev:39581]' + assert_in_out_err(["-e", "module Enumerable;undef min;end; (1..2).min{}"], + "", [], /no superclass method/, bug2312) + end + + def test_specific_eval_with_wrong_arguments + assert_raise(ArgumentError) do + 1.instance_eval("foo") { foo } + end + + assert_raise(ArgumentError) do + 1.instance_eval + end + + assert_raise(ArgumentError) do + 1.instance_eval("", 1, 1, 1) + end + end + + class InstanceExec + INSTANCE_EXEC = 123 + end + + def test_instance_exec + x = 1.instance_exec(42) {|a| self + a } + assert_equal(43, x) + + x = "foo".instance_exec("bar") {|a| self + a } + assert_equal("foobar", x) + + assert_raise(NameError) do + InstanceExec.new.instance_exec { INSTANCE_EXEC } + end + end + + def test_extend + assert_raise(ArgumentError) do + 1.extend + end + end + + def test_to_s + x = eval(<<-EOS) + class ToS\u{3042} + new.to_s + end + EOS + assert_match(/\bToS\u{3042}:/, x) + + name = "X".freeze + x = Object.new + class<<x;self;end.class_eval {define_method(:to_s) {name}} + assert_same(name, x.to_s) + assert_equal("X", [x].join("")) + end + + def test_inspect + x = Object.new + assert_match(/\A#<Object:0x\h+>\z/, x.inspect) + + x.instance_variable_set(:@ivar, :value) + assert_match(/\A#<Object:0x\h+ @ivar=:value>\z/, x.inspect) + + x = Object.new + x.instance_variable_set(:@recur, x) + assert_match(/\A#<Object:0x\h+ @recur=#<Object:0x\h+ \.\.\.>>\z/, x.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) + + # Bug: [ruby-core:19167] + x = Object.new + x.instance_variable_set(:@foo, NilClass) + assert_match(/\A#<Object:0x\h+ @foo=NilClass>\z/, x.inspect) + x.instance_variable_set(:@foo, TrueClass) + assert_match(/\A#<Object:0x\h+ @foo=TrueClass>\z/, x.inspect) + x.instance_variable_set(:@foo, FalseClass) + assert_match(/\A#<Object:0x\h+ @foo=FalseClass>\z/, x.inspect) + + # #inspect does not call #to_s anymore + feature6130 = '[ruby-core:43238]' + x = Object.new + def x.to_s + "to_s" + end + assert_match(/\A#<Object:0x\h+>\z/, x.inspect, feature6130) + + x = eval(<<-EOS) + class Inspect\u{3042} + new.inspect + end + EOS + assert_match(/\bInspect\u{3042}:/, x) + + x = eval(<<-EOS) + class Inspect\u{3042} + def initialize + @\u{3044} = 42 + end + new + end + 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) + + x = Object.new + x.singleton_class.class_eval do + private def instance_variables_to_inspect = [:@host, :@user] + end + + x.instance_variable_set(:@host, "localhost") + x.instance_variable_set(:@user, "root") + x.instance_variable_set(:@password, "hunter2") + s = x.inspect + assert_include(s, "@host=\"localhost\"") + assert_include(s, "@user=\"root\"") + assert_not_include(s, "@password=") + end + + def test_singleton_methods + assert_equal([], Object.new.singleton_methods) + assert_equal([], Object.new.singleton_methods(false)) + c = Class.new + def c.foo; end + assert_equal([:foo], c.singleton_methods - [:yaml_tag]) + assert_equal([:foo], c.singleton_methods(false)) + assert_equal([], c.singleton_class.singleton_methods(false)) + c.singleton_class.singleton_class + assert_equal([], c.singleton_class.singleton_methods(false)) + + o = c.new.singleton_class + assert_equal([:foo], o.singleton_methods - [:yaml_tag]) + assert_equal([], o.singleton_methods(false)) + o.singleton_class + assert_equal([:foo], o.singleton_methods - [:yaml_tag]) + assert_equal([], o.singleton_methods(false)) + + c.extend(Module.new{def bar; end}) + assert_equal([:bar, :foo], c.singleton_methods.sort - [:yaml_tag]) + assert_equal([:foo], c.singleton_methods(false)) + end + + def test_singleton_class + x = Object.new + xs = class << x; self; end + assert_equal(xs, x.singleton_class) + + y = Object.new + ys = y.singleton_class + assert_equal(class << y; self; end, ys) + + assert_equal(NilClass, nil.singleton_class) + assert_equal(TrueClass, true.singleton_class) + assert_equal(FalseClass, false.singleton_class) + + assert_raise(TypeError) do + 123.singleton_class + end + assert_raise(TypeError) do + :foo.singleton_class + end + end + + def test_singleton_class_freeze + x = Object.new + xs = x.singleton_class + x.freeze + assert_predicate(xs, :frozen?) + + y = Object.new + ys = y.singleton_class + ys.prepend(Module.new) + y.freeze + assert_predicate(ys, :frozen?, '[Bug #19169]') + end + + def test_redef_method_missing + bug5473 = '[ruby-core:40287]' + ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code| + exc = code[/\A[A-Z]\w+/] || 'RuntimeError' + assert_separately([], <<-SRC) + $VERBOSE = nil + class ::Object + def method_missing(m, *a, &b) + raise #{code} + 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 + [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 + + def test_frozen_inspect + obj = Object.new + obj.instance_variable_set(:@a, "a") + ins = obj.inspect + obj.freeze + + assert_equal(ins, obj.inspect) + end +end diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb new file mode 100644 index 0000000000..adb819febc --- /dev/null +++ b/test/ruby/test_object_id.rb @@ -0,0 +1,303 @@ +require 'test/unit' +require "securerandom" + +class TestObjectId < Test::Unit::TestCase + def setup + @obj = Object.new + end + + def test_dup_new_id + id = @obj.object_id + refute_equal id, @obj.dup.object_id + end + + def test_dup_with_ivar_and_id + id = @obj.object_id + @obj.instance_variable_set(:@foo, 42) + + copy = @obj.dup + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_dup_with_id_and_ivar + @obj.instance_variable_set(:@foo, 42) + id = @obj.object_id + + copy = @obj.dup + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_dup_with_id_and_ivar_and_frozen + @obj.instance_variable_set(:@foo, 42) + @obj.freeze + id = @obj.object_id + + copy = @obj.dup + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + refute_predicate copy, :frozen? + end + + def test_clone_new_id + id = @obj.object_id + refute_equal id, @obj.clone.object_id + end + + def test_clone_with_ivar_and_id + id = @obj.object_id + @obj.instance_variable_set(:@foo, 42) + + copy = @obj.clone + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_clone_with_id_and_ivar + @obj.instance_variable_set(:@foo, 42) + id = @obj.object_id + + copy = @obj.clone + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_clone_with_id_and_ivar_and_frozen + @obj.instance_variable_set(:@foo, 42) + @obj.freeze + id = @obj.object_id + + copy = @obj.clone + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + assert_predicate copy, :frozen? + end + + def test_marshal_new_id + return pass if @obj.is_a?(Module) + + id = @obj.object_id + refute_equal id, Marshal.load(Marshal.dump(@obj)).object_id + end + + def test_marshal_with_ivar_and_id + return pass if @obj.is_a?(Module) + + id = @obj.object_id + @obj.instance_variable_set(:@foo, 42) + + copy = Marshal.load(Marshal.dump(@obj)) + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_marshal_with_id_and_ivar + return pass if @obj.is_a?(Module) + + @obj.instance_variable_set(:@foo, 42) + id = @obj.object_id + + copy = Marshal.load(Marshal.dump(@obj)) + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + end + + def test_marshal_with_id_and_ivar_and_frozen + return pass if @obj.is_a?(Module) + + @obj.instance_variable_set(:@foo, 42) + @obj.freeze + id = @obj.object_id + + copy = Marshal.load(Marshal.dump(@obj)) + refute_equal id, copy.object_id + assert_equal 42, copy.instance_variable_get(:@foo) + refute_predicate copy, :frozen? + end + + def test_object_id_need_resize + (3 - @obj.instance_variables.size).times do |i| + @obj.instance_variable_set("@a_#{i}", "[Bug #21445]") + end + @obj.object_id + GC.start + end +end + +class TestObjectIdClass < TestObjectId + def setup + @obj = Class.new + end +end + +class TestObjectIdGeneric < TestObjectId + def setup + @obj = Array.new + end +end + +class TestObjectIdTooComplex < TestObjectId + class TooComplex + def initialize + @too_complex_obj_id_test = 1 + end + end + + def setup + if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS) + assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + 8.times do |i| + TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1) + end + @obj = TooComplex.new + @obj.instance_variable_set("@a#{rand(10_000)}", 1) + + if defined?(RubyVM::Shape) + assert_predicate(RubyVM::Shape.of(@obj), :too_complex?) + end + end +end + +class TestObjectIdTooComplexClass < TestObjectId + class TooComplex < Module + end + + def setup + if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS) + assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + + @obj = TooComplex.new + + @obj.instance_variable_set("@___#{SecureRandom.hex}", 1) + + 8.times do |i| + @obj.instance_variable_set("@TestObjectIdTooComplexClass#{i}", 1) + @obj.remove_instance_variable("@TestObjectIdTooComplexClass#{i}") + end + + @obj.instance_variable_set("@test", 1) + + if defined?(RubyVM::Shape) + assert_predicate(RubyVM::Shape.of(@obj), :too_complex?) + end + end +end + +class TestObjectIdTooComplexGeneric < TestObjectId + class TooComplex < Array + end + + def setup + if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS) + assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + 8.times do |i| + TooComplex.new.instance_variable_set("@TestObjectIdTooComplexGeneric#{i}", 1) + end + @obj = TooComplex.new + @obj.instance_variable_set("@a#{rand(10_000)}", 1) + @obj.instance_variable_set("@a#{rand(10_000)}", 1) + + if defined?(RubyVM::Shape) + assert_predicate(RubyVM::Shape.of(@obj), :too_complex?) + end + end +end + +class TestObjectIdRactor < Test::Unit::TestCase + def test_object_id_race_free + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + N = 10_000 + objs = Ractor.make_shareable(N.times.map { MyClass.new }) + results = 4.times.map{ + Ractor.new(objs) { |objs| + vars = [] + ids = [] + objs.each do |obj| + vars << obj.a << obj.b << obj.c + ids << obj.object_id + end + [vars, ids] + } + }.map(&:value) + assert_equal 1, results.uniq.size + end; + end + + def test_external_object_id_ractor_move + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + class MyClass + attr_reader :a, :b, :c + def initialize + @a = @b = @c = nil + end + end + obj = Ractor.make_shareable(MyClass.new) + object_id = obj.object_id + obj = Ractor.new { Ractor.receive }.send(obj, move: true).value + assert_equal object_id, obj.object_id + end; + end +end + +class TestObjectIdStruct < TestObjectId + EmbeddedStruct = Struct.new(:embedded_field) + + def setup + @obj = EmbeddedStruct.new + end +end + +class TestObjectIdStructGenIvar < TestObjectId + GenIvarStruct = Struct.new(:a, :b, :c) + + def setup + @obj = GenIvarStruct.new + end +end + +class TestObjectIdStructNotEmbed < TestObjectId + MANY_IVS = 80 + + StructNotEmbed = Struct.new(*MANY_IVS.times.map { |i| :"field_#{i}" }) + + def setup + @obj = StructNotEmbed.new + end +end + +class TestObjectIdStructTooComplex < TestObjectId + StructTooComplex = Struct.new(:a) do + def initialize + @too_complex_obj_id_test = 1 + end + end + + def setup + if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS) + assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + 8.times do |i| + StructTooComplex.new.instance_variable_set("@TestObjectIdStructTooComplex#{i}", 1) + end + @obj = StructTooComplex.new + @obj.instance_variable_set("@a#{rand(10_000)}", 1) + + if defined?(RubyVM::Shape) + assert_predicate(RubyVM::Shape.of(@obj), :too_complex?) + end + end +end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb new file mode 100644 index 0000000000..a479547599 --- /dev/null +++ b/test/ruby/test_objectspace.rb @@ -0,0 +1,331 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestObjectSpace < Test::Unit::TestCase + def self.deftest_id2ref(obj) + /:(\d+)/ =~ caller[0] + file = $` + line = $1.to_i + code = <<"End" + define_method("test_id2ref_#{line}") {\ + o = EnvUtil.suppress_warning { ObjectSpace._id2ref(obj.object_id) } + assert_same(obj, o, "didn't round trip: \#{obj.inspect}");\ + } +End + eval code, binding, file, line + end + + deftest_id2ref(-0x4000000000000001) + deftest_id2ref(-0x4000000000000000) + deftest_id2ref(-0x40000001) + deftest_id2ref(-0x40000000) + deftest_id2ref(-1) + deftest_id2ref(0) + deftest_id2ref(1) + deftest_id2ref(0x3fffffff) + deftest_id2ref(0x40000000) + deftest_id2ref(0x3fffffffffffffff) + deftest_id2ref(0x4000000000000000) + deftest_id2ref(:a) + deftest_id2ref(:abcdefghijilkjl) + deftest_id2ref(:==) + deftest_id2ref(Object.new) + deftest_id2ref(self) + deftest_id2ref(true) + deftest_id2ref(false) + deftest_id2ref(nil) + + def test_id2ref_liveness + assert_normal_exit <<-EOS + ids = [] + 10.times{ + 1_000.times{ + ids << 'hello'.object_id + } + objs = ids.map{|id| + begin + ObjectSpace._id2ref(id) + rescue RangeError + nil + end + } + GC.start + objs.each{|e| e.inspect} + } + EOS + end + + def test_id2ref_invalid_argument + msg = /no implicit conversion/ + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(nil) } } + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(false) } } + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(true) } } + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a) } } + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref("0") } } + assert_raise_with_message(TypeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(Object.new) } } + end + + def test_id2ref_invalid_symbol_id + # RB_STATIC_SYM_P checks for static symbols by checking that the bottom + # 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make + # sure that the bottom 8 bits remain unchanged. + msg = /is not a symbol id value/ + assert_raise_with_message(RangeError, msg) { EnvUtil.suppress_warning { ObjectSpace._id2ref(:a.object_id + 256) } } + end + + def test_count_objects + h = {} + ObjectSpace.count_objects(h) + assert_kind_of(Hash, h) + 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_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) } + + h0 = {:T_FOO=>1000} + h = ObjectSpace.count_objects(h0) + assert_same(h0, h) + assert_equal(0, h0[:T_FOO]) + end + + def test_finalizer + assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok), []) + a = [] + ObjectSpace.define_finalizer(a) { p :ok } + b = a.dup + ObjectSpace.define_finalizer(a) { p :ok } + !b + END + + assert_in_out_err(["-e", <<~RUBY], "", %w(:ok :ok), [], timeout: 60) + a = Object.new + ObjectSpace.define_finalizer(a) { p :ok } + + 1_000_000.times do + o = Object.new + ObjectSpace.define_finalizer(o) { } + end + + b = Object.new + ObjectSpace.define_finalizer(b) { p :ok } + RUBY + + 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_copy + assert_in_out_err(["-e", <<~'RUBY'], "", %w(:ok), []) + def fin + ids = Set.new + ->(id) { puts "object_id (#{id}) reused" unless ids.add?(id) } + end + + OBJ = Object.new + ObjectSpace.define_finalizer(OBJ, fin) + OBJ.freeze + + 10.times do + OBJ.clone + end + + p :ok + RUBY + 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_exception_in_finalizer + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], /finalizing \(RuntimeError\)/) + begin; + ObjectSpace.define_finalizer(Object.new) {raise "finalizing"} + end; + end + + def test_finalizer_thread_raise + EnvUtil.without_gc do + fzer = proc do |id| + sleep 0.2 + end + 2.times do + o = Object.new + ObjectSpace.define_finalizer(o, fzer) + end + + my_error = Class.new(RuntimeError) + begin + main_th = Thread.current + Thread.new do + sleep 0.1 + main_th.raise(my_error) + end + GC.start + sleep(10) + assert(false) + rescue my_error + end + 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_garbage + 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_id2ref_table_build + assert_separately([], <<-End) + 10.times do + Object.new.object_id + end + + GC.start(immediate_mark: false) + + obj = Object.new + EnvUtil.suppress_warning do + assert_equal obj, ObjectSpace._id2ref(obj.object_id) + end + 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 + + def test_each_object_with_allocation + assert_normal_exit(<<-End) + list = [] + ObjectSpace.each_object { |o| list << Object.new } + End + end +end diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb new file mode 100644 index 0000000000..5d16984eef --- /dev/null +++ b/test/ruby/test_optimization.rb @@ -0,0 +1,1278 @@ +# frozen_string_literal: false +require 'test/unit' +require 'objspace' + +class TestRubyOptimization < Test::Unit::TestCase + def assert_redefine_method(klass, method, code, msg = nil) + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + class #{klass} + undef #{method} + def #{method}(*args) + args[0] + end + end + #{code} + end; + end + + def assert_performance_warning(klass, method) + assert_in_out_err([], "#{<<-"begin;"}\n#{<<~"end;"}", [], ["-:4: warning: Redefining '#{klass}##{method}' disables interpreter and JIT optimizations"]) + begin; + Warning[:performance] = true + class #{klass} + undef #{method} + def #{method} + end + end + end; + end + + def disasm(name) + RubyVM::InstructionSequence.of(method(name)).disasm + end + + def test_fixnum_plus + assert_equal 21, 10 + 11 + assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11') + assert_performance_warning('Integer', '+') + end + + def test_fixnum_minus + assert_equal 5, 8 - 3 + assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3') + assert_performance_warning('Integer', '-') + end + + def test_fixnum_mul + assert_equal 15, 3 * 5 + assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5') + assert_performance_warning('Integer', '*') + end + + def test_fixnum_div + assert_equal 3, 15 / 5 + assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5') + assert_performance_warning('Integer', '/') + end + + def test_fixnum_mod + assert_equal 1, 8 % 7 + assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7') + assert_performance_warning('Integer', '%') + end + + def test_fixnum_lt + assert_equal true, 1 < 2 + assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2') + assert_performance_warning('Integer', '<') + end + + def test_fixnum_le + assert_equal true, 1 <= 2 + assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2') + assert_performance_warning('Integer', '<=') + end + + def test_fixnum_gt + assert_equal false, 1 > 2 + assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2') + assert_performance_warning('Integer', '>') + end + + def test_fixnum_ge + assert_equal false, 1 >= 2 + assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2') + assert_performance_warning('Integer', '>=') + end + + def test_float_plus + assert_equal 4.0, 2.0 + 2.0 + assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + assert_performance_warning('Float', '+') + end + + def test_float_minus + assert_equal 4.0, 2.0 + 2.0 + assert_redefine_method('Float', '-', 'assert_equal 2.0, 4.0 - 2.0') + assert_performance_warning('Float', '-') + 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') + assert_performance_warning('Float', '*') + 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]") + assert_performance_warning('Float', '/') + end + + def test_float_lt + assert_equal true, 1.1 < 2.2 + assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2') + assert_performance_warning('Float', '<') + end + + def test_float_le + assert_equal true, 1.1 <= 2.2 + assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2') + assert_performance_warning('Float', '<=') + end + + def test_float_gt + assert_equal false, 1.1 > 2.2 + assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2') + assert_performance_warning('Float', '>') + end + + def test_float_ge + assert_equal false, 1.1 >= 2.2 + assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2') + assert_performance_warning('Float', '>=') + end + + def test_string_length + assert_equal 6, "string".length + assert_redefine_method('String', 'length', 'assert_nil "string".length') + assert_performance_warning('String', 'length') + end + + def test_string_size + assert_equal 6, "string".size + assert_redefine_method('String', 'size', 'assert_nil "string".size') + assert_performance_warning('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?') + assert_performance_warning('String', 'empty?') + end + + def test_string_plus + assert_equal "", "" + "" + assert_equal "x", "x" + "" + assert_equal "x", "" + "x" + assert_equal "ab", "a" + "b" + assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"') + assert_performance_warning('String', '+') + end + + def test_string_succ + assert_equal 'b', 'a'.succ + assert_equal 'B', 'A'.succ + assert_performance_warning('String', 'succ') + end + + def test_string_format + assert_equal '2', '%d' % 2 + assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2') + assert_performance_warning('String', '%') + 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') + assert_performance_warning('String', 'freeze') + end + + def test_string_uminus + assert_same "foo".freeze, -"foo" + assert_redefine_method('String', '-@', 'assert_nil(-"foo")') + assert_performance_warning('String', '-@') + end + + def test_array_min + assert_equal 1, [1, 2, 4].min + assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)') + assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)') + assert_performance_warning('Array', 'min') + end + + def test_array_max + assert_equal 4, [1, 2, 4].max + assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)') + assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)') + assert_performance_warning('Array', 'max') + end + + def test_array_hash + assert_performance_warning('Array', 'hash') + end + + def test_trace_optimized_methods + bug14870 = "[ruby-core:87638]" + expected = [:-@, :max, :min, :+, :-, :*, :/, :%, :==, :<, :<=, :>, :>=, :<<, + :&, :|, :[], :[]=, :length, :empty?, :nil?, :succ, :!, :=~] + [:c_call, :c_return].each do |type| + methods = [] + tp = TracePoint.new(type) { |tp| methods << tp.method_id } + tp.enable do + x = "a"; x = -x + [1].max + [1].min + x = 42 + 2 + x = 42 - 2 + x = 42 * 2 + x = 42 / 2 + x = 42 % 2 + y = x == 42 + y = x < 42 + y = x <= 42 + y = x > 42 + y = x >= 42 + x = x << 1 + x = x & 1 + x = x | 1 + x = []; x[1] + x[1] = 2 + x.length + x.empty? + x.nil? + x = 1; x.succ + !x + x = 'a'; x =~ /a/ + x = y + end + assert_equal(expected, methods, bug14870) + end + + methods = [] + tp = TracePoint.new(:c_call, :c_return) { |tp| methods << tp.method_id } + tp.enable do + x = 1 + x != 42 + end + assert_equal([:!=, :==, :==, :!=], methods, bug14870) + end + + def test_string_freeze_saves_memory + n = 16384 + data = '.'.freeze + 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 + + assert_performance_warning('String', '==') + 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"') + assert_performance_warning('String', '<<') + end + + def test_fixnum_and + assert_equal 1, 1&3 + assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3') + assert_performance_warning('Integer', '&') + end + + def test_fixnum_or + assert_equal 3, 1|3 + assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1') + assert_performance_warning('Integer', '|') + end + + def test_array_plus + assert_equal [1,2], [1]+[2] + assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') + assert_performance_warning('Array', '+') + end + + def test_array_minus + assert_equal [2], [1,2] - [1] + assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]') + assert_performance_warning('Array', '-') + 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)') + assert_performance_warning('Array', '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?)') + assert_performance_warning('Array', '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)') + assert_performance_warning('Hash', '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?)') + assert_performance_warning('Hash', '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; + assert_performance_warning('Hash', '[]') + 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; + assert_performance_warning('Hash', '[]=') + end + + class MyObj + def ==(other) + true + end + end + + def test_eq + assert_equal true, nil == nil + assert_equal true, 1 == 1 + assert_equal true, 'string' == 'string' + assert_equal true, 1 == MyObj.new + assert_equal false, nil == MyObj.new + assert_equal true, MyObj.new == 1 + 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 + 1 + 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]' + + EnvUtil.suppress_warning {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_c_func_with_sp_offset_under_tailcall + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def calc_one_plus_two + 1 + 2.abs + end + + def one_plus_two + calc_one_plus_two + end + end; + assert_equal(3, one_plus_two) + end + + def test_tailcall_and_post_arg + tailcall(<<~RUBY) + def ret_const = :ok + + def post_arg(_a = 1, _b) = ret_const + RUBY + + # YJIT probably uses a fallback on the call to post_arg + assert_equal(:ok, post_arg(0)) + 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;"}", __FILE__, nil, __LINE__+1 + 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 + + def test_tailcall_not_to_grow_stack + bug16161 = '[ruby-core:94881]' + + tailcall("#{<<-"begin;"}\n#{<<~"end;"}") + begin; + def foo(n) + return :ok if n < 1 + foo(n - 1) + end + end; + assert_nothing_raised(SystemStackError, bug16161) do + assert_equal(:ok, foo(1_000_000), bug16161) + end + end + + class Bug10557 + def [](_, &) + block_given? + 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)") + assert_performance_warning(k, '===') + 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_peephole_array_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [1].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [1].freeze + RUBY + end + + def test_peephole_array_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + [].freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_ary_freeze/, insn) + assert_no_match(/duparray/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Array + prepend Module.new { + def freeze + :ok + end + } + end + p [].freeze + RUBY + end + + def test_peephole_hash_freeze + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {a:1}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([1].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({a:1}.freeze) + RUBY + end + + def test_peephole_hash_freeze_empty + code = "#{<<~'begin;'}\n#{<<~'end;'}" + begin; + {}.freeze + end; + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_hash_freeze/, insn) + assert_no_match(/duphash/, insn) + assert_no_match(/send/, insn) + assert_predicate([].freeze, :frozen?) + assert_in_out_err([], <<~RUBY, [":ok"]) + class Hash + prepend Module.new { + def freeze + :ok + end + } + end + p({}.freeze) + RUBY + 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) # rehearsal + GC.start; GC.disable # to disable GC while foo{} + ObjectSpace.count_objects(h1) + foo{} + ObjectSpace.count_objects(h2) + + assert_equal 0, h2[:T_DATA] - h1[:T_DATA] # Proc is T_DATA + END + end + + def test_peephole_optimization_without_trace + assert_ruby_status [], <<-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_ruby_status [], <<-END, timeout: 60 + script = <<-EOS + if true + else + foo(k1:1) + end + EOS + GC.stress = true + 30.times{ + RubyVM::InstructionSequence.compile(script) + } + END + end + + def test_callinfo_unreachable_path + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + iseq = RubyVM::InstructionSequence.compile("if false; foo(bar: :baz); else :ok end") + bin = iseq.to_binary + iseq = RubyVM::InstructionSequence.load_from_binary(bin) + assert_instance_of(RubyVM::InstructionSequence, iseq) + assert_equal(:ok, iseq.eval) + end; + end + + def test_side_effect_in_popped_splat + bug = '[ruby-core:84340] [Bug #14201]' + eval("{**(bug = nil; {})};42") + assert_nil(bug) + + bug = '[ruby-core:85486] [Bug #14459]' + h = {} + assert_equal(bug, eval('{ok: 42, **h}; bug')) + assert_equal(:ok, eval('{ok: bug = :ok, **h}; bug')) + assert_empty(h) + end + + def test_overwritten_blockparam + obj = Object.new + def obj.a(&block) + block = 1 + return :ok if block + :ng + end + assert_equal(:ok, obj.a()) + end + + def test_blockparam_in_rescue + obj = Object.new + def obj.foo(&b) + raise + rescue + b.call + end + result = nil + assert_equal(42, obj.foo {result = 42}) + assert_equal(42, result) + end + + def test_unconditional_branch_to_leave_block + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + tap {true || tap {}} + end; + end + + def test_jump_elimination_with_optimized_out_block + x = Object.new + def x.bug(obj) + if obj || obj + obj = obj + else + raise "[ruby-core:87830] [Bug #14897]" + end + obj + end + assert_equal(:ok, x.bug(:ok)) + end + + def test_jump_elimination_with_optimized_out_block_2 + x = Object.new + def x.bug + a = "aaa" + ok = :NG + if a == "bbb" || a == "ccc" then + a = a + else + ok = :ok + end + ok + end + assert_equal(:ok, x.bug) + end + + def test_peephole_jump_after_newarray + i = 0 + %w(1) || 2 while (i += 1) < 100 + assert_equal(100, i) + end + + def test_optimized_empty_ensure + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}", timeout: 10) + begin; + assert_raise(RuntimeError) { + begin raise ensure nil if nil end + } + end; + end + + def test_optimized_rescue + assert_in_out_err("", "#{<<~"begin;"}\n#{<<~'end;'}", [], /END \(RuntimeError\)/) + begin; + if false + begin + require "some_mad_stuff" + rescue LoadError + puts "no mad stuff loaded" + end + end + + raise "END" + end; + end + + class Objtostring + end + + def test_objtostring_immediate + assert_raise(NoMethodError){"#{BasicObject.new}"} + assert_redefine_method('Symbol', 'to_s', <<-'end') + assert_match %r{\A#<Symbol:0x[0-9a-f]+>\z}, "#{:foo}" + end + assert_redefine_method('NilClass', 'to_s', <<-'end') + assert_match %r{\A#<NilClass:0x[0-9a-f]+>\z}, "#{nil}" + end + assert_redefine_method('TrueClass', 'to_s', <<-'end') + assert_match %r{\A#<TrueClass:0x[0-9a-f]+>\z}, "#{true}" + end + assert_redefine_method('FalseClass', 'to_s', <<-'end') + assert_match %r{\A#<FalseClass:0x[0-9a-f]+>\z}, "#{false}" + end + end + + def test_objtostring_fixnum + assert_redefine_method('Integer', 'to_s', <<-'end') + (-1..10).each { |i| + assert_match %r{\A#<Integer:0x[0-9a-f]+>\z}, "#{i}" + } + end + end + + def test_objtostring + assert_equal "TestRubyOptimization::Objtostring", "#{Objtostring}" + assert_match %r{\A#<Class:0x[0-9a-f]+>\z}, "#{Class.new}" + assert_match %r{\A#<Module:0x[0-9a-f]+>\z}, "#{Module.new}" + o = Object.new + def o.to_s; 1; end + assert_match %r{\A#<Object:0x[0-9a-f]+>\z}, "#{o}" + end + + def test_opt_duparray_send_include_p + [ + 'x = :b; [:a, :b].include?(x)', + '@c = :b; [:a, :b].include?(@c)', + '@c = "b"; %i[a b].include?(@c.to_sym)', + '[:a, :b].include?(self) == false', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_duparray_send/, insn) + assert_no_match(/\bduparray\b/, insn) + assert_equal(true, eval(code)) + end + + x, y = :b, :c + assert_equal(true, [:a, :b].include?(x)) + assert_equal(false, [:a, :b].include?(y)) + + assert_in_out_err([], <<~RUBY, ["1,2", "3,3", "1,2", "4,4"]) + class Array + prepend(Module.new do + def include?(i) + puts self.join(",") + # Modify self to prove that we are operating on a copy. + map! { i } + puts self.join(",") + end + end) + end + def x(i) + [1, 2].include?(i) + end + x(3) + x(4) + RUBY + + # Ensure raises happen correctly. + assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"]) + class Integer + undef_method :== + def == x + raise "int \#{self} not \#{x}" + end + end + x = 3 + puts "will raise" + begin + p [1, 2].include?(x) + rescue + puts $! + end + RUBY + end + + def test_opt_newarray_send_include_p + [ + 'b = :b; [:a, b].include?(:b)', + # Use Object.new to ensure that we get newarray rather than duparray. + 'value = 1; [Object.new, true, "true", 1].include?(value)', + 'value = 1; [Object.new, "1"].include?(value.to_s)', + '[Object.new, "1"].include?(self) == false', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_newarray_send/, insn) + assert_no_match(/\bnewarray\b/, insn) + assert_equal(true, eval(code)) + end + + x, y = :b, :c + assert_equal(true, [:a, x].include?(x)) + assert_equal(false, [:a, x].include?(y)) + + assert_in_out_err([], <<~RUBY, ["1,3", "3,3", "1,4", "4,4"]) + class Array + prepend(Module.new do + def include?(i) + puts self.join(",") + # Modify self to prove that we are operating on a copy. + map! { i } + puts self.join(",") + end + end) + end + def x(i) + [1, i].include?(i) + end + x(3) + x(4) + RUBY + + # Ensure raises happen correctly. + assert_in_out_err([], <<~RUBY, ["will raise", "int 1 not 3"]) + class Integer + undef_method :== + def == x + raise "int \#{self} not \#{x}" + end + end + x = 3 + puts "will raise" + begin + p [1, x].include?(x) + rescue + puts $! + end + RUBY + end + + def test_opt_new_with_safe_navigation + payload = nil + assert_nil payload&.new + end + + def test_opt_new + pos_initialize = " + def initialize a, b + @a = a + @b = b + end + " + kw_initialize = " + def initialize a:, b: + @a = a + @b = b + end + " + kw_hash_initialize = " + def initialize a, **kw + @a = a + @b = kw[:b] + end + " + pos_prelude = "class OptNewFoo; #{pos_initialize}; end;" + kw_prelude = "class OptNewFoo; #{kw_initialize}; end;" + kw_hash_prelude = "class OptNewFoo; #{kw_hash_initialize}; end;" + [ + "#{pos_prelude} OptNewFoo.new 1, 2", + "#{pos_prelude} a = 1; b = 2; OptNewFoo.new a, b", + "#{pos_prelude} def optnew_foo(a, b) = OptNewFoo.new(a, b); optnew_foo 1, 2", + "#{pos_prelude} def optnew_foo(*a) = OptNewFoo.new(*a); optnew_foo 1, 2", + "#{pos_prelude} def optnew_foo(...) = OptNewFoo.new(...); optnew_foo 1, 2", + "#{kw_prelude} def optnew_foo(**a) = OptNewFoo.new(**a); optnew_foo a: 1, b: 2", + "#{kw_hash_prelude} def optnew_foo(*a, **b) = OptNewFoo.new(*a, **b); optnew_foo 1, b: 2", + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_match(/opt_new/, insn) + assert_match(/OptNewFoo:.+@a=1, @b=2/, iseq.eval.inspect) + # clean up to avoid warnings + Object.send :remove_const, :OptNewFoo + Object.remove_method :optnew_foo if defined?(optnew_foo) + end + [ + 'def optnew_foo(&) = OptNewFoo.new(&)', + 'def optnew_foo(a, ...) = OptNewFoo.new(a, ...)', + ].each do |code| + iseq = RubyVM::InstructionSequence.compile(code) + insn = iseq.disasm + assert_no_match(/opt_new/, insn) + end + end +end diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb new file mode 100644 index 0000000000..ca089f09c3 --- /dev/null +++ b/test/ruby/test_pack.rb @@ -0,0 +1,939 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' +require 'rbconfig' +require 'rbconfig/sizeof' + +class TestPack < Test::Unit::TestCase + # Note: the size of intptr_t and uintptr_t should be equal. + J_SIZE = RbConfig::SIZEOF['uintptr_t'] + + def test_pack + format = "c2x5CCxsdils_l_a6"; + # Need the expression in here to force ary[5] to be numeric. This avoids + # test2 failing because ary2 goes str->numeric->str and ary does not. + ary = [1,-100,127,128,32767,987.654321098 / 100.0,12345,123456,-32767,-123456,"abcdef"] + x = ary.pack(format) + ary2 = x.unpack(format) + + assert_equal(ary.length, ary2.length) + assert_equal(ary.join(':'), ary2.join(':')) + assert_match(/def/, x) + + x = [-1073741825] + assert_equal(x, x.pack("q").unpack("q")) + + x = [-1] + assert_equal(x, x.pack("l").unpack("l")) + end + + def test_ascii_incompatible + assert_raise(Encoding::CompatibilityError) do + ["foo"].pack("u".encode("UTF-32BE")) + end + + assert_raise(Encoding::CompatibilityError) do + "foo".unpack("C".encode("UTF-32BE")) + end + + assert_raise(Encoding::CompatibilityError) do + "foo".unpack1("C".encode("UTF-32BE")) + end + end + + def test_pack_n + assert_equal "\000\000", [0].pack('n') + assert_equal "\000\001", [1].pack('n') + assert_equal "\000\002", [2].pack('n') + assert_equal "\000\003", [3].pack('n') + assert_equal "\377\376", [65534].pack('n') + assert_equal "\377\377", [65535].pack('n') + + assert_equal "\200\000", [2**15].pack('n') + assert_equal "\177\377", [-2**15-1].pack('n') + assert_equal "\377\377", [-1].pack('n') + + assert_equal "\000\001\000\001", [1,1].pack('n*') + assert_equal "\000\001\000\001\000\001", [1,1,1].pack('n*') + end + + def test_unpack_n + assert_equal 1, "\000\001".unpack('n')[0] + assert_equal 2, "\000\002".unpack('n')[0] + assert_equal 3, "\000\003".unpack('n')[0] + assert_equal 65535, "\377\377".unpack('n')[0] + assert_equal [1,1], "\000\001\000\001".unpack('n*') + assert_equal [1,1,1], "\000\001\000\001\000\001".unpack('n*') + end + + def test_pack_N + assert_equal "\000\000\000\000", [0].pack('N') + assert_equal "\000\000\000\001", [1].pack('N') + assert_equal "\000\000\000\002", [2].pack('N') + assert_equal "\000\000\000\003", [3].pack('N') + assert_equal "\377\377\377\376", [4294967294].pack('N') + assert_equal "\377\377\377\377", [4294967295].pack('N') + + assert_equal "\200\000\000\000", [2**31].pack('N') + assert_equal "\177\377\377\377", [-2**31-1].pack('N') + assert_equal "\377\377\377\377", [-1].pack('N') + + assert_equal "\000\000\000\001\000\000\000\001", [1,1].pack('N*') + assert_equal "\000\000\000\001\000\000\000\001\000\000\000\001", [1,1,1].pack('N*') + end + + def test_unpack_N + assert_equal 1, "\000\000\000\001".unpack('N')[0] + assert_equal 2, "\000\000\000\002".unpack('N')[0] + assert_equal 3, "\000\000\000\003".unpack('N')[0] + assert_equal 4294967295, "\377\377\377\377".unpack('N')[0] + assert_equal [1,1], "\000\000\000\001\000\000\000\001".unpack('N*') + 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)) + case J_SIZE + when 4 + assert_equal("\x01\x02\x03\x04", [0x01020304].pack("j"+mod)) + assert_equal("\x01\x02\x03\x04", [0x01020304].pack("J"+mod)) + when 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)) + case J_SIZE + when 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)) + when 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)) + case J_SIZE + when 4 + assert_equal("\x04\x03\x02\x01", [0x01020304].pack("j"+mod)) + assert_equal("\x04\x03\x02\x01", [0x01020304].pack("J"+mod)) + when 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)) + case J_SIZE + when 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)) + when 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_include(["\0\1", "\1\0"], s) + if s == "\0\1" + _integer_big_endian() + else + _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 + assert_raise(RangeError) { [-0x40000001].pack("U") } + assert_raise(RangeError) { [-0x40000000].pack("U") } + assert_raise(RangeError) { [-1].pack("U") } + assert_equal "\000", [0].pack("U") + assert_equal "\374\277\277\277\277\277".force_encoding(Encoding::UTF_8), [0x3fffffff].pack("U") + assert_equal "\375\200\200\200\200\200".force_encoding(Encoding::UTF_8), [0x40000000].pack("U") + assert_equal "\375\277\277\277\277\277".force_encoding(Encoding::UTF_8), [0x7fffffff].pack("U") + assert_raise(RangeError) { [0x80000000].pack("U") } + assert_raise(RangeError) { [0x100000000].pack("U") } + end + + def test_pack_P + a = ["abc"] + assert_equal a, a.pack("P").unpack("P*") + assert_equal "a", a.pack("P").unpack("P")[0] + assert_equal a, a.pack("P").freeze.unpack("P*") + assert_raise(ArgumentError) { (a.pack("P") + "").unpack("P*") } + end + + def test_pack_p + a = ["abc"] + assert_equal a, a.pack("p").unpack("p*") + 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 + fmt = "CC" + o = Object.new + class << o; self; end.class_eval do + define_method(:to_int) { fmt.clear; 0 } + end + assert_raise(RuntimeError) do + [o, o].pack(fmt) + end + end + + def test_comment + assert_equal("\0\1", [0,1].pack(" C #foo \n C ")) + assert_equal([0,1], "\0\1".unpack(" C #foo \n C ")) + end + + def test_illegal_bang + assert_raise(ArgumentError) { [].pack("a!") } + assert_raise(ArgumentError) { "".unpack("a!") } + end + + def test_pack_unpack_aA + assert_equal("f", ["foo"].pack("A")) + assert_equal("f", ["foo"].pack("a")) + assert_equal("foo", ["foo"].pack("A*")) + assert_equal("foo", ["foo"].pack("a*")) + assert_equal("fo", ["foo"].pack("A2")) + assert_equal("fo", ["foo"].pack("a2")) + assert_equal("foo ", ["foo"].pack("A4")) + assert_equal("foo\0", ["foo"].pack("a4")) + assert_equal(" ", [nil].pack("A")) + assert_equal("\0", [nil].pack("a")) + assert_equal("", [nil].pack("A*")) + assert_equal("", [nil].pack("a*")) + assert_equal(" ", [nil].pack("A2")) + assert_equal("\0\0", [nil].pack("a2")) + + assert_equal("foo" + "\0" * 27, ["foo"].pack("a30")) + + assert_equal(["f"], "foo\0".unpack("A")) + assert_equal(["f"], "foo\0".unpack("a")) + assert_equal(["foo"], "foo\0".unpack("A4")) + assert_equal(["foo\0"], "foo\0".unpack("a4")) + assert_equal(["foo"], "foo ".unpack("A4")) + assert_equal(["foo "], "foo ".unpack("a4")) + assert_equal(["foo"], "foo".unpack("A4")) + assert_equal(["foo"], "foo".unpack("a4")) + end + + def test_pack_unpack_Z + assert_equal("f", ["foo"].pack("Z")) + assert_equal("foo\0", ["foo"].pack("Z*")) + assert_equal("fo", ["foo"].pack("Z2")) + assert_equal("foo\0\0", ["foo"].pack("Z5")) + assert_equal("\0", [nil].pack("Z")) + assert_equal("\0", [nil].pack("Z*")) + assert_equal("\0\0", [nil].pack("Z2")) + + assert_equal(["f"], "foo\0".unpack("Z")) + assert_equal(["foo"], "foo".unpack("Z*")) + assert_equal(["foo"], "foo\0".unpack("Z*")) + assert_equal(["foo"], "foo".unpack("Z5")) + end + + def test_pack_unpack_bB + assert_equal("\xff\x00", ["1111111100000000"].pack("b*")) + assert_equal("\x01\x02", ["1000000001000000"].pack("b*")) + assert_equal("", ["1"].pack("b0")) + assert_equal("\x01", ["1"].pack("b1")) + assert_equal("\x01\x00", ["1"].pack("b2")) + assert_equal("\x01\x00", ["1"].pack("b3")) + assert_equal("\x01\x00\x00", ["1"].pack("b4")) + assert_equal("\x01\x00\x00", ["1"].pack("b5")) + assert_equal("\x01\x00\x00\x00", ["1"].pack("b6")) + + assert_equal("\xff\x00", ["1111111100000000"].pack("B*")) + assert_equal("\x01\x02", ["0000000100000010"].pack("B*")) + assert_equal("", ["1"].pack("B0")) + assert_equal("\x80", ["1"].pack("B1")) + assert_equal("\x80\x00", ["1"].pack("B2")) + assert_equal("\x80\x00", ["1"].pack("B3")) + assert_equal("\x80\x00\x00", ["1"].pack("B4")) + assert_equal("\x80\x00\x00", ["1"].pack("B5")) + assert_equal("\x80\x00\x00\x00", ["1"].pack("B6")) + + assert_equal(["1111111100000000"], "\xff\x00".unpack("b*")) + assert_equal(["1000000001000000"], "\x01\x02".unpack("b*")) + assert_equal([""], "".unpack("b0")) + assert_equal(["1"], "\x01".unpack("b1")) + assert_equal(["10"], "\x01".unpack("b2")) + assert_equal(["100"], "\x01".unpack("b3")) + + assert_equal(["1111111100000000"], "\xff\x00".unpack("B*")) + assert_equal(["0000000100000010"], "\x01\x02".unpack("B*")) + assert_equal([""], "".unpack("B0")) + 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 + assert_equal("\x01\xfe", ["10ef"].pack("h*")) + assert_equal("", ["10ef"].pack("h0")) + assert_equal("\x01\x0e", ["10ef"].pack("h3")) + assert_equal("\x01\xfe\x0", ["10ef"].pack("h5")) + assert_equal("\xff\x0f", ["fff"].pack("h3")) + assert_equal("\xff\x0f", ["fff"].pack("h4")) + assert_equal("\xff\x0f\0", ["fff"].pack("h5")) + assert_equal("\xff\x0f\0", ["fff"].pack("h6")) + assert_equal("\xff\x0f\0\0", ["fff"].pack("h7")) + assert_equal("\xff\x0f\0\0", ["fff"].pack("h8")) + + assert_equal("\x10\xef", ["10ef"].pack("H*")) + assert_equal("", ["10ef"].pack("H0")) + assert_equal("\x10\xe0", ["10ef"].pack("H3")) + assert_equal("\x10\xef\x0", ["10ef"].pack("H5")) + assert_equal("\xff\xf0", ["fff"].pack("H3")) + assert_equal("\xff\xf0", ["fff"].pack("H4")) + assert_equal("\xff\xf0\0", ["fff"].pack("H5")) + assert_equal("\xff\xf0\0", ["fff"].pack("H6")) + assert_equal("\xff\xf0\0\0", ["fff"].pack("H7")) + assert_equal("\xff\xf0\0\0", ["fff"].pack("H8")) + + assert_equal(["10ef"], "\x01\xfe".unpack("h*")) + assert_equal([""], "\x01\xfe".unpack("h0")) + assert_equal(["1"], "\x01\xfe".unpack("h1")) + assert_equal(["10"], "\x01\xfe".unpack("h2")) + assert_equal(["10e"], "\x01\xfe".unpack("h3")) + assert_equal(["10ef"], "\x01\xfe".unpack("h4")) + assert_equal(["10ef"], "\x01\xfe".unpack("h5")) + + assert_equal(["10ef"], "\x10\xef".unpack("H*")) + assert_equal([""], "\x10\xef".unpack("H0")) + assert_equal(["1"], "\x10\xef".unpack("H1")) + assert_equal(["10"], "\x10\xef".unpack("H2")) + 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 + assert_equal("\0\1\xff", [0, 1, 255].pack("c*")) + assert_equal("\0\1\xff", [0, 1, -1].pack("c*")) + + assert_equal("\0\1\xff", [0, 1, 255].pack("C*")) + assert_equal("\0\1\xff", [0, 1, -1].pack("C*")) + + assert_equal([0, 1, -1], "\0\1\xff".unpack("c*")) + + assert_equal([0, 1, 255], "\0\1\xff".unpack("C*")) + end + + def test_pack_unpack_sS + s1 = [513, -514].pack("s*") + s2 = [513, 65022].pack("S*") + assert_equal(s1, s2) + assert_equal([513, -514], s2.unpack("s*")) + assert_equal([513, 65022], s1.unpack("S*")) + + s1 = [513, -514].pack("s!*") + s2 = [513, 65022].pack("S!*") + assert_equal(s1, s2) + assert_equal([513, -514], s2.unpack("s!*")) + assert_equal([513, 65022], s1.unpack("S!*")) + + assert_equal(2, [1].pack("s").bytesize) + assert_equal(2, [1].pack("S").bytesize) + assert_operator(2, :<=, [1].pack("s!").bytesize) + assert_operator(2, :<=, [1].pack("S!").bytesize) + end + + def test_pack_unpack_iI + s1 = [67305985, -50462977].pack("i*") + s2 = [67305985, 4244504319].pack("I*") + assert_equal(s1, s2) + assert_equal([67305985, -50462977], s2.unpack("i*")) + assert_equal([67305985, 4244504319], s1.unpack("I*")) + + s1 = [67305985, -50462977].pack("i!*") + s2 = [67305985, 4244504319].pack("I!*") + assert_equal([67305985, -50462977], s1.unpack("i!*")) + assert_equal([67305985, 4244504319], s2.unpack("I!*")) + + assert_operator(4, :<=, [1].pack("i").bytesize) + assert_operator(4, :<=, [1].pack("I").bytesize) + assert_operator(4, :<=, [1].pack("i!").bytesize) + assert_operator(4, :<=, [1].pack("I!").bytesize) + end + + def test_pack_unpack_lL + s1 = [67305985, -50462977].pack("l*") + s2 = [67305985, 4244504319].pack("L*") + assert_equal(s1, s2) + assert_equal([67305985, -50462977], s2.unpack("l*")) + assert_equal([67305985, 4244504319], s1.unpack("L*")) + + s1 = [67305985, -50462977].pack("l!*") + s2 = [67305985, 4244504319].pack("L!*") + assert_equal([67305985, -50462977], s1.unpack("l!*")) + assert_equal([67305985, 4244504319], s2.unpack("L!*")) + + assert_equal(4, [1].pack("l").bytesize) + assert_equal(4, [1].pack("L").bytesize) + assert_operator(4, :<=, [1].pack("l!").bytesize) + assert_operator(4, :<=, [1].pack("L!").bytesize) + end + + def test_pack_unpack_qQ + s1 = [578437695752307201, -506097522914230529].pack("q*") + s2 = [578437695752307201, 17940646550795321087].pack("Q*") + assert_equal(s1, s2) + 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 + case J_SIZE + when 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) + when 8 + s1 = [578437695752307201, -506097522914230529].pack("j*") + s2 = [578437695752307201, 17940646550795321087].pack("J*") + assert_equal(s1, s2) + assert_equal([578437695752307201, -506097522914230529], s2.unpack("j*")) + assert_equal([578437695752307201, 17940646550795321087], s1.unpack("J*")) + + s1 = [578437695752307201, -506097522914230529].pack("j!*") + s2 = [578437695752307201, 17940646550795321087].pack("J!*") + assert_equal([578437695752307201, -506097522914230529], s2.unpack("j!*")) + assert_equal([578437695752307201, 17940646550795321087], s1.unpack("J!*")) + + assert_equal(8, [1].pack("j").bytesize) + assert_equal(8, [1].pack("J").bytesize) + else + assert false, "we don't know such platform now." + end + end + + def test_pack_unpack_nN + assert_equal("\000\000\000\001\377\377\177\377\200\000\377\377", [0,1,-1,32767,-32768,65535].pack("n*")) + assert_equal("\000\000\000\000\000\000\000\001\377\377\377\377", [0,1,-1].pack("N*")) + + assert_equal([0,1,65535,32767,32768,65535], "\000\000\000\001\377\377\177\377\200\000\377\377".unpack("n*")) + assert_equal([0,1,4294967295], "\000\000\000\000\000\000\000\001\377\377\377\377".unpack("N*")) + + assert_equal(2, [1].pack("n").bytesize) + assert_equal(4, [1].pack("N").bytesize) + end + + def test_pack_unpack_vV + assert_equal("\000\000\001\000\377\377\377\177\000\200\377\377", [0,1,-1,32767,-32768,65535].pack("v*")) + assert_equal("\000\000\000\000\001\000\000\000\377\377\377\377", [0,1,-1].pack("V*")) + + assert_equal([0,1,65535,32767,32768,65535], "\000\000\001\000\377\377\377\177\000\200\377\377".unpack("v*")) + assert_equal([0,1,4294967295], "\000\000\000\000\001\000\000\000\377\377\377\377".unpack("V*")) + + assert_equal(2, [1].pack("v").bytesize) + assert_equal(4, [1].pack("V").bytesize) + end + + def test_pack_unpack_fdeEgG + inf = 1.0/0.0 + nan = inf/inf + [0.0, 1.0, 3.0, inf, -inf, nan].each do |x| + %w(f d e E g G).each do |f| + v = [x].pack(f).unpack(f) + if x.nan? + assert_predicate(v.first, :nan?) + else + assert_equal([x], v) + end + end + end + end + + def test_pack_unpack_x + assert_equal("", [].pack("x0")) + assert_equal("\0", [].pack("x")) + assert_equal("\0" * 30, [].pack("x30")) + + assert_equal([0, 2], "\x00\x00\x02".unpack("CxC")) + assert_raise(ArgumentError) { "".unpack("x") } + end + + def test_pack_unpack_X + assert_equal("\x00\x02", [0, 1, 2].pack("CCXC")) + assert_equal("\x02", [0, 1, 2].pack("CCX2C")) + assert_raise(ArgumentError) { [].pack("X") } + + assert_equal([0, 2, 2], "\x00\x02".unpack("CCXC")) + assert_raise(ArgumentError) { "".unpack("X") } + end + + def test_pack_unpack_atmark + assert_equal("\x01\x00\x00\x02", [1, 2].pack("C@3C")) + assert_equal("\x02", [1, 2].pack("C@0C")) + assert_equal("\x01\x02", [1, 2].pack("C@1C")) + + 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 + assert_raise(ArgumentError) { [].pack("%") } + assert_raise(ArgumentError) { "".unpack("%") } + end + + def test_pack_unpack_U + assert_equal([0], [0].pack("U").unpack("U")) + assert_equal([0x80], [0x80].pack("U").unpack("U")) + assert_equal([0x800], [0x800].pack("U").unpack("U")) + assert_equal([0x10000], [0x10000].pack("U").unpack("U")) + assert_equal([0x400000], [0x400000].pack("U").unpack("U")) + + assert_raise(ArgumentError) { "\x80".unpack("U") } + assert_raise(ArgumentError) { "\xff".unpack("U") } + assert_raise(ArgumentError) { "\xfc\x00".unpack("U") } + assert_raise(ArgumentError) { "\xc0\xc0".unpack("U") } + assert_raise(ArgumentError) { "\xe0\x80\x80".unpack("U") } + end + + def test_pack_unpack_u + assert_equal("", [""].pack("u")) + assert_equal("!80``\n", ["a"].pack("u")) + assert_equal("#86)C\n", ["abc"].pack("u")) + assert_equal("$86)C9```\n", ["abcd"].pack("u")) + assert_equal("M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n", ["a"*45].pack("u")) + assert_equal("M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n!80``\n", ["a"*46].pack("u")) + assert_equal("&86)C9&5F\n#9VAI\n", ["abcdefghi"].pack("u6")) + + 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")) + assert_equal(["abc"], "#86)C\n".unpack("u")) + assert_equal(["abcd"], "$86)C9```\n".unpack("u")) + assert_equal(["a"*45], "M86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A86%A\n".unpack("u")) + 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 + + def test_pack_unpack_m + assert_equal("", [""].pack("m")) + assert_equal("AA==\n", ["\0"].pack("m")) + assert_equal("AAA=\n", ["\0\0"].pack("m")) + assert_equal("AAAA\n", ["\0\0\0"].pack("m")) + assert_equal("/w==\n", ["\377"].pack("m")) + assert_equal("//8=\n", ["\377\377"].pack("m")) + assert_equal("////\n", ["\377\377\377"].pack("m")) + + assert_equal([""], "".unpack("m")) + assert_equal(["\0"], "AA==\n".unpack("m")) + assert_equal(["\0\0"], "AAA=\n".unpack("m")) + assert_equal(["\0\0\0"], "AAAA\n".unpack("m")) + 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_bug_18343 + bug18343 = '[ruby-core:106096] [Bug #18343]' + assert_separately(%W[- #{bug18343}], <<-'end;') + bug = ARGV.shift + assert_raise(ArgumentError, bug){[0].pack('c', {})} + end; + end + + def test_pack_unpack_m0 + assert_equal("", [""].pack("m0")) + assert_equal("AA==", ["\0"].pack("m0")) + assert_equal("AAA=", ["\0\0"].pack("m0")) + assert_equal("AAAA", ["\0\0\0"].pack("m0")) + assert_equal("/w==", ["\377"].pack("m0")) + assert_equal("//8=", ["\377\377"].pack("m0")) + assert_equal("////", ["\377\377\377"].pack("m0")) + + assert_equal([""], "".unpack("m0")) + assert_equal(["\0"], "AA==".unpack("m0")) + assert_equal(["\0\0"], "AAA=".unpack("m0")) + assert_equal(["\0\0\0"], "AAAA".unpack("m0")) + assert_equal(["\377"], "/w==".unpack("m0")) + assert_equal(["\377\377"], "//8=".unpack("m0")) + assert_equal(["\377\377\377"], "////".unpack("m0")) + + assert_raise(ArgumentError) { "^".unpack("m0") } + assert_raise(ArgumentError) { "A".unpack("m0") } + assert_raise(ArgumentError) { "A^".unpack("m0") } + assert_raise(ArgumentError) { "AA".unpack("m0") } + assert_raise(ArgumentError) { "AA=".unpack("m0") } + assert_raise(ArgumentError) { "AA===".unpack("m0") } + assert_raise(ArgumentError) { "AA=x".unpack("m0") } + assert_raise(ArgumentError) { "AAA".unpack("m0") } + assert_raise(ArgumentError) { "AAA^".unpack("m0") } + assert_raise(ArgumentError) { "AB==".unpack("m0") } + assert_raise(ArgumentError) { "AAB=".unpack("m0") } + end + + def test_pack_unpack_M + assert_equal("a b c\td =\n\ne=\n", ["a b c\td \ne"].pack("M")) + assert_equal(["a b c\td \ne"], "a b c\td =\n\ne=\n".unpack("M")) + assert_equal("=00=\n", ["\0"].pack("M")) + assert_equal("a"*73+"=\na=\n", ["a"*74].pack("M")) + assert_equal(("a"*73+"=\n")*14+"a=\n", ["a"*1023].pack("M")) + assert_equal(["\0"], "=00=\n".unpack("M")) + assert_equal(["a"*74], ("a"*73+"=\na=\n").unpack("M")) + 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"], "=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 + assert_raise(ArgumentError) { ["abc"].pack("P4") } + assert_raise(ArgumentError) { [""].pack("P") } + + assert_equal([], ".".unpack("P")) + assert_equal([], ".".unpack("p")) + assert_equal([nil], ("\0" * 1024).unpack("P")) + end + + def test_pack_p2 + assert_match(/\A\0*\Z/, [nil].pack("p")) + end + + def test_pack_unpack_w + assert_equal("\000", [0].pack("w")) + assert_equal("\001", [1].pack("w")) + assert_equal("\177", [127].pack("w")) + assert_equal("\201\000", [128].pack("w")) + assert_equal("\377\177", [0x3fff].pack("w")) + assert_equal("\201\200\000", [0x4000].pack("w")) + assert_equal("\203\377\377\377\177", [0x3fffffff].pack("w")) + assert_equal("\204\200\200\200\000", [0x40000000].pack("w")) + assert_equal("\217\377\377\377\177", [0xffffffff].pack("w")) + assert_equal("\220\200\200\200\000", [0x100000000].pack("w")) + assert_raise(ArgumentError) { [-1].pack("w") } + + assert_equal([0], "\000".unpack("w")) + assert_equal([1], "\001".unpack("w"), [1]) + assert_equal([127], "\177".unpack("w"), [127]) + assert_equal([128], "\201\000".unpack("w"), [128]) + assert_equal([0x3fff], "\377\177".unpack("w"), [0x3fff]) + assert_equal([0x4000], "\201\200\000".unpack("w"), [0x4000]) + assert_equal([0x3fffffff], "\203\377\377\377\177".unpack("w"), [0x3fffffff]) + assert_equal([0x40000000], "\204\200\200\200\000".unpack("w"), [0x40000000]) + assert_equal([0xffffffff], "\217\377\377\377\177".unpack("w"), [0xffffffff]) + assert_equal([0x100000000], "\220\200\200\200\000".unpack("w"), [0x100000000]) + end + + def test_length_too_big + assert_raise(RangeError) { [].pack("C100000000000000000000") } + end + + def test_short_string + %w[n N v V s S i I l L q Q s! S! i! I! l! l!].each {|fmt| + str = [1].pack(fmt) + 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 + assert_raise(ArgumentError, %r%unknown pack directive '\*' in '\*U'$%) do + assert_equal "\000", [0].pack("*U") + end + end + + def test_unpack_garbage + assert_raise(ArgumentError, %r%unknown unpack directive '\*' in '\*U'$%) do + assert_equal [0], "\000".unpack("*U") + end + end + + def test_invalid_warning + assert_raise(ArgumentError, /unknown pack directive ',' in ','/) { + [].pack(",") + } + assert_raise(ArgumentError, /\A[ -~]+\Z/) { + [].pack("\x7f") + } + assert_raise(ArgumentError, /\A(.* in '\u{3042}'\n)+\z/) { + [].pack("\u{3042}") + } + + assert_raise(ArgumentError, /\A.* in '.*U'\Z/) { + assert_equal "\000", [0].pack("\0U") + } + assert_raise(ArgumentError, /\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_unpack1_offset + assert_equal 65, "ZA".unpack1("C", offset: 1) + assert_equal "01000001", "YZA".unpack1("B*", offset: 2) + assert_nil "abc".unpack1("C", offset: 3) + assert_raise_with_message(ArgumentError, /offset can't be negative/) { + "a".unpack1("C", offset: -1) + } + assert_raise_with_message(ArgumentError, /offset outside of string/) { + "a".unpack1("C", offset: 2) + } + assert_nil "a".unpack1("C", offset: 1) + end + + def test_unpack_offset + assert_equal [65], "ZA".unpack("C", offset: 1) + assert_equal ["01000001"], "YZA".unpack("B*", offset: 2) + assert_equal [nil, nil, nil], "abc".unpack("CCC", offset: 3) + assert_raise_with_message(ArgumentError, /offset can't be negative/) { + "a".unpack("C", offset: -1) + } + assert_raise_with_message(ArgumentError, /offset outside of string/) { + "a".unpack("C", offset: 2) + } + assert_equal [nil], "a".unpack("C", offset: 1) + end + + def test_monkey_pack + assert_separately([], <<-'end;') + $-w = false + class Array + alias :old_pack :pack + def pack _; "oh no"; end + end + + v = [2 ** 15].pack('n') + + class Array + alias :pack :old_pack + end + + assert_equal "oh no", v + end; + end + + def test_monkey_pack_buffer + assert_separately([], <<-'end;') + $-w = false + class Array + alias :old_pack :pack + def pack _, buffer:; buffer << " no"; end + end + + def test + b = +"oh" + [2 ** 15].pack('n', buffer: b) + end + + v = test + + class Array + alias :pack :old_pack + end + + assert_equal "oh no", v + end; + end +end diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb new file mode 100644 index 0000000000..def41d6017 --- /dev/null +++ b/test/ruby/test_parse.rb @@ -0,0 +1,1836 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' +require 'stringio' +require_relative '../lib/parser_support' + +class TestParse < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $VERBOSE = @verbose + end + + def test_error_line + assert_syntax_error('------,,', /\n\z/, 'Message to pipe should end with a newline') + assert_syntax_error("{hello\n world}", /hello/) + end + + def test_else_without_rescue + assert_syntax_error(<<-END, %r"(:#{__LINE__+2}:|#{__LINE__+2} \|.+?\n.+?\^~.+?;) else without rescue"o, [__FILE__, __LINE__+1]) + begin + else + 42 + end + END + end + + def test_alias_backref + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /can't make alias/) do + begin; + alias $foo $1 + end; + end + end + + def test_command_call + t = Object.new + def t.foo(x); x; end + + a = false + b = c = d = true + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a &&= t.foo 42 + b &&= t.foo 42 + c &&= t.foo nil + d &&= t.foo false + END + end + assert_equal([false, 42, nil, false], [a, b, c, d]) + + a = 3 + assert_nothing_raised { eval("a &= t.foo 5") } + assert_equal(1, a) + + a = [nil, nil, true, true] + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a[0] ||= t.foo 42 + a[1] &&= t.foo 42 + a[2] ||= t.foo 42 + a[3] &&= t.foo 42 + END + end + assert_equal([42, nil, true, 42], a) + + o = Object.new + class << o + attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux + end + o.foo = o.Foo = o::baz = nil + o.bar = o.Bar = o::qux = 1 + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + o.foo ||= t.foo 42 + o.bar &&= t.foo 42 + o.Foo ||= t.foo 42 + o.Bar &&= t.foo 42 + o::baz ||= t.foo 42 + o::qux &&= t.foo 42 + END + end + assert_equal([42, 42], [o.foo, o.bar]) + assert_equal([42, 42], [o.Foo, o.Bar]) + assert_equal([42, 42], [o::baz, o::qux]) + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; + $1 ||= t.foo 42 + end; + end + + def t.bar(x); x + yield; end + + a = b = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = t.bar "foo" do + "bar" + end.gsub "ob", "OB" + b = t.bar "foo" do + "bar" + end::gsub "ob", "OB" + END + end + assert_equal("foOBar", a) + assert_equal("foOBar", b) + + a = nil + assert_nothing_raised do + t.instance_eval <<-END, __FILE__, __LINE__+1 + a = bar "foo" do "bar" end + END + end + assert_equal("foobar", a) + + a = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = t::bar "foo" do "bar" end + END + end + assert_equal("foobar", a) + + def t.baz(*r) + @baz = r + (block_given? ? [yield] : []) + end + + assert_nothing_raised do + t.instance_eval "baz (1), 2" + end + assert_equal([1, 2], t.instance_eval { @baz }) + end + + def test_mlhs_node + c = Class.new + class << c + attr_accessor :foo, :bar, :Foo, :Bar + FOO = BAR = nil + end + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + c::foo, c::bar = 1, 2 + c.Foo, c.Bar = 1, 2 + c::FOO, c::BAR = 1, 2 + END + end + assert_equal([1, 2], [c::foo, c::bar]) + assert_equal([1, 2], [c.Foo, c.Bar]) + assert_equal([1, 2], [c::FOO, c::BAR]) + end + + def test_dynamic_constant_assignment + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do + begin; + def foo + self::FOO, self::BAR = 1, 2 + ::FOO, ::BAR = 1, 2 + end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; + $1, $2 = 1, 2 + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /dynamic constant/) do + begin; + def foo + ::FOO = 1 + end + end; + end + + c = Class.new + c.freeze + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do + begin; + c::FOO &= 1 + ::FOO &= 1 + end; + end + + c = Class.new + c.freeze + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do + begin; + c::FOO &= p 1 + ::FOO &= p 1 + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /Can't set variable/) do + begin; + $1 &= 1 + end; + end + end + + def test_class_module + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /must be CONSTANT/) do + begin; + class foo; end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /in method body/) do + begin; + def foo + class Foo; end + module Bar; end + end + end; + end + + assert_valid_syntax("#{<<~"begin;"}\n#{<<~'end;'}") do + begin; + class Foo 1; end + end; + end + end + + def test_op_name + o = Object.new + def o.>(x); x; end + def o./(x); x; end + + assert_nothing_raised do + o.instance_eval <<-END, __FILE__, __LINE__+1 + undef >, / + END + end + end + + def test_arg + o = Object.new + class << o + attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux + end + o.foo = o.Foo = o::baz = nil + o.bar = o.Bar = o::qux = 1 + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + o.foo ||= 42 + o.bar &&= 42 + o.Foo ||= 42 + o.Bar &&= 42 + o::baz ||= 42 + o::qux &&= 42 + END + end + assert_equal([42, 42], [o.foo, o.bar]) + assert_equal([42, 42], [o.Foo, o.Bar]) + assert_equal([42, 42], [o::baz, o::qux]) + + a = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = -2.0 ** 2 + END + end + assert_equal(-4.0, a) + end + + def test_block_variable + o = Object.new + def o.foo(*r); yield(*r); end + + a = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + o.foo 1 do|; a| a = 42 end + END + end + assert_nil(a) + end + + def test_bad_arg + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a constant/) do + begin; + def foo(FOO); end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do + begin; + def foo(@foo); end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a global variable/) do + begin; + def foo($foo); end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be a class variable/) do + begin; + def foo(@@foo); end + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /cannot be an instance variable/) do + begin; + o.foo {|; @a| @a = 42 } + end; + end + end + + def test_do_lambda + a = b = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = -> do + b = 42 + end + END + end + a.call + assert_equal(42, b) + end + + def test_block_call_colon2 + o = Object.new + def o.foo(x); x + yield; end + + a = b = nil + assert_nothing_raised do + o.instance_eval <<-END, __FILE__, __LINE__+1 + a = foo 1 do 42 end.to_s + b = foo 1 do 42 end::to_s + END + end + assert_equal("43", a) + assert_equal("43", b) + end + + def test_call_method + a = b = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = proc {|x| x + "bar" }.("foo") + b = proc {|x| x + "bar" }::("foo") + END + end + assert_equal("foobar", a) + assert_equal("foobar", b) + end + + def test_call_command + a = b = nil + o = Object.new + def o.m(*arg); proc {|a| arg.join + a }; end + + assert_nothing_raised do + o.instance_eval <<-END, __FILE__, __LINE__+1 + a = o.m "foo", "bar" do end.("buz") + b = o.m "foo", "bar" do end::("buz") + END + end + assert_equal("foobarbuz", a) + assert_equal("foobarbuz", b) + end + + def test_xstring + assert_raise(Errno::ENOENT) do + eval("``") + end + end + + def test_words + assert_equal([], %W( )) + assert_syntax_error('%w[abc', /unterminated list/) + end + + def test_dstr + @@foo = 1 + assert_equal("foo 1 bar", "foo #@@foo bar") + "1" =~ /(.)/ + assert_equal("foo 1 bar", "foo #$1 bar") + assert_equal('foo #@1 bar', eval('"foo #@1 bar"')) + end + + def test_dstr_disallowed_variable + bug8375 = '[ruby-core:54885] [Bug #8375]' + %w[@ @. @@ @@1 @@. $ $%].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) + noname.each do |name| + assert_syntax_error("proc{a = #{name} }", "'#{noname[0]}' without identifiers is not allowed as #{type} variable name") + end + invalid.each do |name| + assert_syntax_error("proc {a = #{name} }", "'#{name}' is not allowed as #{type} variable name") + end + end + + def test_disallowed_instance_variable + assert_disallowed_variable("an instance", %w[@ @.], %w[]) + end + + def test_disallowed_class_variable + assert_disallowed_variable("a class", %w[@@ @@.], %w[@@1]) + end + + def test_disallowed_gloal_variable + assert_disallowed_variable("a global", %w[$], %w[$%]) + end + + def test_arg2 + o = Object.new + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end + END + end + assert_equal(-1405, o.foo(1,2,3,4) {|x| -x }) + assert_equal(-1302, o.foo(1,2,3) {|x| -x }) + assert_equal(-1200, o.foo(1,2) {|x| -x }) + assert_equal(-42100, o.foo(1) {|x| -x }) + assert_raise(ArgumentError) { o.foo() } + + o = Object.new + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + def o.foo(a=42,z,&b); b.call(a*1000+z*100); end + END + end + assert_equal(-1200, o.foo(1,2) {|x| -x } ) + assert_equal(-42100, o.foo(1) {|x| -x } ) + assert_raise(ArgumentError) { o.foo() } + + o = Object.new + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end + END + end + assert_equal(-303, o.foo(1,2,3) {|x| -x } ) + assert_equal(-201, o.foo(1,2) {|x| -x } ) + assert_equal(-100, o.foo(1) {|x| -x } ) + assert_raise(ArgumentError) { o.foo() } + end + + def test_duplicate_argument + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", '') do + begin; + 1.times {|&b?| } + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do + begin; + 1.times {|a, a|} + end; + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /duplicated argument/) do + begin; + def foo(a, a); end + end; + end + end + + def test_define_singleton_error + msg = /singleton method for literals/ + assert_parse_error(%q[def ("foo").foo; end], msg) + assert_parse_error(%q[def (1).foo; end], msg) + assert_parse_error(%q[def ((1;1)).foo; end], msg) + assert_parse_error(%q[def ((;1)).foo; end], msg) + assert_parse_error(%q[def ((1+1;1)).foo; end], msg) + assert_parse_error(%q[def ((%s();1)).foo; end], msg) + assert_parse_error(%q[def ((%w();1)).foo; end], msg) + assert_parse_error(%q[def ("#{42}").foo; end], msg) + assert_parse_error(%q[def (:"#{42}").foo; end], msg) + assert_parse_error(%q[def ([]).foo; end], msg) + assert_parse_error(%q[def ([1]).foo; end], msg) + assert_parse_error(%q[def (__FILE__).foo; end], msg) + assert_parse_error(%q[def (__LINE__).foo; end], msg) + assert_parse_error(%q[def (__ENCODING__).foo; end], msg) + assert_parse_error(%q[def __FILE__.foo; end], msg) + assert_parse_error(%q[def __LINE__.foo; end], msg) + assert_parse_error(%q[def __ENCODING__.foo; end], msg) + end + + def test_flip_flop + all_assertions_foreach(nil, + ['(cond1..cond2)', true], + ['((cond1..cond2))', true], + + # '(;;;cond1..cond2)', # don't care + + '(1; cond1..cond2)', + '(%s(); cond1..cond2)', + '(%w(); cond1..cond2)', + '(1; (2; (3; 4; cond1..cond2)))', + '(1+1; cond1..cond2)', + ) do |code, pass| + code = code.sub("cond1", "n==4").sub("cond2", "n==5") + if pass + assert_equal([4,5], eval("(1..9).select {|n| true if #{code}}")) + else + assert_raise_with_message(ArgumentError, /bad value for range/, code) { + verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context" + begin + eval("[4].each {|n| true if #{code}}") + ensure + $VERBOSE = verbose_bak + end + } + end + end + end + + def test_op_asgn1_with_block + t = Object.new + a = [] + blk = proc {|x| a << x } + + # Prevent an "assigned but unused variable" warning + _ = blk + + def t.[](_) + yield(:aref) + nil + end + def t.[]=(_, _) + yield(:aset) + end + def t.dummy(_) + end + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/) + begin; + t[42, &blk] ||= 42 + end; + + assert_syntax_error("#{<<~"begin;"}\n#{<<~'end;'}", /block arg given in index assignment/) + begin; + t[42, &blk] ||= t.dummy 42 # command_asgn test + end; + end + + def test_backquote + t = Object.new + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + def t.`(x); "foo" + x + "bar"; end + END + end + a = b = c = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + a = t.` "zzz" + 1.times {|;z| t.` ("zzz") } + END + t.instance_eval <<-END, __FILE__, __LINE__+1 + b = `zzz` + c = %x(ccc) + END + end + assert_equal("foozzzbar", a) + assert_equal("foozzzbar", b) + assert_equal("foocccbar", c) + end + + def test_carrige_return + assert_equal(2, eval("1 +\r\n1")) + end + + def test_string + mesg = 'from the backslash through the invalid char' + + e = assert_syntax_error('"\xg1"', /hex escape/) + assert_match(/(^|\| ) \^~(?!~)/, e.message.lines.last, mesg) + + e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape') + assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg) + + e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape') + assert_match(/(^|\| ) \^(?!~)/, e.message.lines.last, mesg) + + e = assert_syntax_error('"\u{xxxx', 'Unicode escape') + if e.message.lines.first == "#{__FILE__}:#{__LINE__ - 1}: syntax errors found\n" + assert_pattern_list([ + /\s+\| \^ unterminated string;.+\n/, + /\s+\| \^ unterminated Unicode escape\n/, + /\s+\| \^ invalid Unicode escape sequence\n/, + ], e.message.lines[2..-1].join) + else + assert_pattern_list([ + /.*: invalid Unicode escape\n.*\n/, + / \^/, + /\n/, + /.*: unterminated Unicode escape\n.*\n/, + / \^/, + /\n/, + /.*: unterminated string.*\n.*\n/, + / \^\n/, + ], e.message) + end + + e = assert_syntax_error('"\M1"', /escape character syntax/) + assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg) + + e = assert_syntax_error('"\C1"', /escape character syntax/) + assert_match(/(^|\| ) \^~~(?!~)/, e.message.lines.last, mesg) + + src = '"\xD0\u{90'"\n""000000000000000000000000" + assert_syntax_error(src, /(:#{__LINE__}:|> #{__LINE__} \|.+) unterminated/om) + + assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/) + assert_equal("", eval('"\u{}"')) + assert_equal("", eval('"\u{ }"')) + + assert_equal("\x81", eval('"\C-\M-a"')) + assert_equal("\177", eval('"\c?"')) + + assert_warning(/use \\C-\\s/) {assert_equal("\x00", eval('"\C- "'))} + assert_warning(/use \\M-\\s/) {assert_equal("\xa0", eval('"\M- "'))} + assert_warning(/use \\M-\\C-\\s/) {assert_equal("\x80", eval('"\M-\C- "'))} + assert_warning(/use \\C-\\M-\\s/) {assert_equal("\x80", eval('"\C-\M- "'))} + assert_warning(/use \\t/) {assert_equal("\x09", eval("\"\\C-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\M-\\C-\t\""))} + assert_warning(/use \\M-\\t/) {assert_equal("\x89", eval("\"\\C-\\M-\t\""))} + assert_syntax_error("\"\\C-\x01\"", 'Invalid escape character syntax') + assert_syntax_error("\"\\M-\x01\"", 'Invalid escape character syntax') + assert_syntax_error("\"\\M-\\C-\x01\"", 'Invalid escape character syntax') + assert_syntax_error("\"\\C-\\M-\x01\"", 'Invalid escape character syntax') + + e = assert_syntax_error('"\c\u0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last) + e = assert_syntax_error('"\c\U0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~(?!~)/, e.message.lines.last) + + e = assert_syntax_error('"\C-\u0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last) + e = assert_syntax_error('"\C-\U0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last) + + e = assert_syntax_error('"\M-\u0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last) + e = assert_syntax_error('"\M-\U0000"', 'Invalid escape character syntax') + assert_match(/(^|\| ) \^~~~~(?!~)/, e.message.lines.last) + + e = assert_syntax_error(%["\\C-\u3042"], 'Invalid escape character syntax') + assert_match(/(^|\|\s)\s \^(?# \\ ) ~(?# C ) ~(?# - ) ~+(?# U+3042 )($|\s)/x, e.message.lines.last) + assert_not_include(e.message, "invalid multibyte char") + end + + def test_question + assert_syntax_error('?', /incomplete/) + assert_syntax_error('? ', /unexpected/) + assert_syntax_error("?\n", /unexpected/) + assert_syntax_error("?\t", /unexpected/) + assert_syntax_error("?\v", /unexpected/) + assert_syntax_error("?\r", /unexpected/) + assert_syntax_error("?\f", /unexpected/) + assert_syntax_error(" ?a\x8a".force_encoding("utf-8"), /invalid multibyte/) + assert_equal("\u{1234}", eval("?\u{1234}")) + assert_equal("\u{1234}", eval('?\u{1234}')) + assert_equal("\u{1234}", eval('?\u1234')) + assert_syntax_error('?\u{41 42}', 'Multiple codepoints at single character literal') + assert_syntax_error("?and", /unexpected '\?'/) + assert_syntax_error("?\u1234and", /unexpected '\?'/) + e = assert_syntax_error('"#{?\u123}"', 'invalid Unicode escape') + assert_not_match(/end-of-input/, e.message) + + assert_warning(/use ?\\C-\\s/) {assert_equal("\x00", eval('?\C- '))} + assert_warning(/use ?\\M-\\s/) {assert_equal("\xa0", eval('?\M- '))} + assert_warning(/use ?\\M-\\C-\\s/) {assert_equal("\x80", eval('?\M-\C- '))} + assert_warning(/use ?\\C-\\M-\\s/) {assert_equal("\x80", eval('?\C-\M- '))} + assert_warning(/use ?\\t/) {assert_equal("\x09", eval("?\\C-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\M-\\C-\t"))} + assert_warning(/use ?\\M-\\t/) {assert_equal("\x89", eval("?\\C-\\M-\t"))} + assert_syntax_error("?\\C-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\M-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\M-\\C-\x01", 'Invalid escape character syntax') + assert_syntax_error("?\\C-\\M-\x01", 'Invalid escape character syntax') + + assert_equal("\xff", eval("# encoding: ascii-8bit\n""?\\\xFF")) + end + + def test_percent + assert_equal(:foo, eval('%s(foo)')) + assert_syntax_error('%s', /unterminated quoted string/) + assert_syntax_error('%ss', /unknown type/) + assert_syntax_error('%z()', /unknown type/) + assert_syntax_error("%\u3042", /unknown type/) + assert_syntax_error("%q\u3042", /unknown type/) + assert_syntax_error("%", /unterminated quoted string/) + end + + def test_symbol + bug = '[ruby-dev:41447]' + sym = "foo\0bar".to_sym + assert_nothing_raised(SyntaxError, bug) do + assert_equal(sym, eval(":'foo\0bar'")) + end + assert_nothing_raised(SyntaxError, bug) do + assert_equal(sym, eval(':"foo\u0000bar"')) + end + assert_nothing_raised(SyntaxError, bug) do + assert_equal(sym, eval(':"foo\u{0}bar"')) + end + assert_nothing_raised(SyntaxError) do + assert_equal(:foobar, eval(':"foo\u{}bar"')) + assert_equal(:foobar, eval(':"foo\u{ }bar"')) + end + + assert_syntax_error(':@@', /is not allowed/) + assert_syntax_error(':@@1', /is not allowed/) + assert_syntax_error(':@', /is not allowed/) + assert_syntax_error(':@1', /is not allowed/) + assert_syntax_error(':$01234', /is not allowed/) + end + + def test_parse_string + assert_syntax_error("/\n", /unterminated/) + end + + def test_here_document + x = nil + + assert_syntax_error("<\<FOO\n", /can't find string "FOO"/) + + assert_nothing_raised(SyntaxError) do + x = eval %q( +<<FOO +#$ +FOO + ) + end + assert_equal "\#$\n", x + + assert_syntax_error("<\<\"\n", /unterminated here document identifier/) + + assert_syntax_error("<<``\n", /can't find string ""/) + + assert_syntax_error("<<--\n", /unexpected <</) + + 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" + end + assert_equal("1\n", x) + + assert_nothing_raised do + x = eval "<<' FOO'\n""[Bug #19539]\n"" FOO\n" + end + assert_equal("[Bug #19539]\n", x) + + assert_nothing_raised do + x = eval "<<-' FOO'\n""[Bug #19539]\n"" FOO\n" + end + assert_equal("[Bug #19539]\n", x) + end + + def test_magic_comment + x = nil + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding: utf-8 +x = __ENCODING__ + END + end + assert_equal(Encoding.find("UTF-8"), x) + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding = utf-8 +x = __ENCODING__ + END + end + assert_equal(Encoding.find("UTF-8"), x) + + assert_raise(ArgumentError) do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding = foobarbazquxquux_dummy_enconding +x = __ENCODING__ + END + end + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 +# xxxx : coding sjis +x = __ENCODING__ + END + end + assert_equal(__ENCODING__, x) + + assert_raise(ArgumentError) do + EnvUtil.with_default_external(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1} +# coding = external +x = __ENCODING__ + END + end + + assert_raise(ArgumentError) do + EnvUtil.with_default_internal(Encoding::US_ASCII) {eval <<-END, nil, __FILE__, __LINE__+1} +# coding = internal +x = __ENCODING__ + END + end + + assert_raise(ArgumentError) do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding = filesystem +x = __ENCODING__ + END + end + + assert_raise(ArgumentError) do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding = locale +x = __ENCODING__ + END + end + + e = assert_raise(ArgumentError) do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding: foo + END + end + + message = e.message.gsub(/\033\[.*?m/, "") + assert_include(message, "# coding: foo\n") + assert_include(message, " ^") + + e = assert_raise(ArgumentError) do + eval <<-END, nil, __FILE__, __LINE__+1 +# coding = foo + END + end + + message = e.message.gsub(/\033\[.*?m/, "") + assert_include(message, "# coding = foo\n") + assert_include(message, " ^") + end + + def test_utf8_bom + x = nil + assert_nothing_raised do + eval "\xef\xbb\xbf x = __ENCODING__" + end + assert_equal(Encoding.find("UTF-8"), x) + assert_raise(NameError) { eval "\xef" } + end + + def test_dot_in_next_line + x = nil + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + x = 1 + .to_s + END + end + assert_equal("1", x) + end + + def test_pow_asgn + x = 3 + assert_nothing_raised { eval("x **= 2") } + assert_equal(9, x) + end + + def test_embedded_rd + assert_valid_syntax("=begin\n""=end") + assert_valid_syntax("=begin\n""=end\0") + assert_valid_syntax("=begin\n""=end\C-d") + assert_valid_syntax("=begin\n""=end\C-z") + end + + def test_embedded_rd_error + error = 'embedded document meets end of file' + assert_syntax_error("=begin\n", error) + assert_syntax_error("=begin", error) + end + + def test_float + assert_predicate(assert_warning(/out of range/) {eval("1e10000")}, :infinite?) + assert_syntax_error('1_E', /trailing '_'/) + assert_syntax_error('1E1E1', /unexpected constant/) + end + + def test_global_variable + assert_equal(nil, assert_warning(/not initialized/) {eval('$-x')}) + assert_equal(nil, eval('alias $preserve_last_match $&')) + assert_equal(nil, eval('alias $& $test_parse_foobarbazqux')) + $test_parse_foobarbazqux = nil + assert_equal(nil, $&) + assert_equal(nil, eval('alias $& $preserve_last_match')) + assert_syntax_error('a = $#', /as a global variable name/) + assert_syntax_error('a = $#', /a = \$\#\n(^|.+?\| ) \^~(?!~)/) + end + + def test_invalid_instance_variable + pattern = /without identifiers is not allowed as an instance variable name/ + assert_syntax_error('@%', pattern) + assert_syntax_error('@', pattern) + end + + def test_invalid_class_variable + pattern = /without identifiers is not allowed as a class variable name/ + assert_syntax_error('@@%', pattern) + assert_syntax_error('@@', pattern) + end + + def test_invalid_char + bug10117 = '[ruby-core:64243] [Bug #10117]' + invalid_char = /Invalid char '\\x01'/ + x = 1 + 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 + assert_syntax_error(%q(self = 1), /Can't change the value of self/) + assert_syntax_error(%q(nil = 1), /Can't assign to nil/) + assert_syntax_error(%q(true = 1), /Can't assign to true/) + assert_syntax_error(%q(false = 1), /Can't assign to false/) + assert_syntax_error(%q(__FILE__ = 1), /Can't assign to __FILE__/) + assert_syntax_error(%q(__LINE__ = 1), /Can't assign to __LINE__/) + assert_syntax_error(%q(__ENCODING__ = 1), /Can't assign to __ENCODING__/) + assert_syntax_error("def foo; FOO = 1; end", /dynamic constant assignment/) + assert_syntax_error("x, true", /Can't assign to true/) + end + + def test_block_dup + assert_syntax_error("foo(&proc{}) {}", /both block arg and actual block/) + end + + def test_set_backref + assert_syntax_error("$& = 1", /Can't set variable/) + end + + def test_void_expr_stmts_value + x = 1 + useless_use = /useless use/ + assert_nil assert_warning(useless_use) {eval("x; nil")} + assert_nil assert_warning(useless_use) {eval("1+1; nil")} + assert_nil assert_warning('') {eval("1.+(1); nil")} + assert_nil assert_warning(useless_use) {eval("TestParse; nil")} + assert_nil assert_warning(useless_use) {eval("::TestParse; nil")} + assert_nil assert_warning(useless_use) {eval("x..x; nil")} + assert_nil assert_warning(useless_use) {eval("x...x; nil")} + assert_nil assert_warning(useless_use) {eval("self; nil")} + assert_nil assert_warning(useless_use) {eval("nil; nil")} + assert_nil assert_warning(useless_use) {eval("true; nil")} + assert_nil assert_warning(useless_use) {eval("false; nil")} + assert_nil assert_warning(useless_use) {eval("defined?(1); nil")} + assert_nil assert_warning(useless_use) {eval("begin; ensure; x; end")} + assert_equal 1, x + + assert_syntax_error("1; next; 2", /Invalid next/) + end + + def test_assign_in_conditional + # multiple assignment + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + (x, y = 1, 2) ? 1 : 2 + END + end + + # instance variable assignment + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + if @x = true + 1 + else + 2 + end + END + end + + # local variable assignment + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + def m + if x = true + 1 + else + 2 + end + end + END + end + + # global variable assignment + assert_separately([], <<-RUBY) + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + if $x = true + 1 + else + 2 + end + END + end + RUBY + + # dynamic variable assignment + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + y = 1 + + 1.times do + if y = true + 1 + else + 2 + end + end + END + end + + # class variable assignment + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + c = Class.new + class << c + if @@a = 1 + end + end + END + end + + # constant declaration + assert_separately([], <<-RUBY) + assert_warning(/'= literal' in conditional/) do + eval <<-END, nil, __FILE__, __LINE__+1 + if Const = true + 1 + else + 2 + end + END + end + RUBY + end + + def test_literal_in_conditional + assert_warning(/string literal in condition/) do + eval <<-END, nil, __FILE__, __LINE__+1 + "foo" ? 1 : 2 + END + end + + assert_warning(/regex literal in condition/) do + x = "bar" + eval <<-END, nil, __FILE__, __LINE__+1 + /foo#{x}baz/ ? 1 : 2 + END + end + + assert_nothing_raised do + eval <<-END, nil, __FILE__, __LINE__+1 + (true..false) ? 1 : 2 + END + end + + assert_warning(/string literal in flip-flop/) do + eval <<-END, nil, __FILE__, __LINE__+1 + ("foo".."bar") ? 1 : 2 + END + end + + assert_warning(/literal in condition/) do + x = "bar" + eval <<-END, nil, __FILE__, __LINE__+1 + :"foo#{"x"}baz" ? 1 : 2 + END + assert_equal "bar", x + end + end + + def test_no_blockarg + assert_syntax_error("yield(&:+)", /block argument should not be given/) + end + + 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_shadowing_private_local_variable + assert_equal 1, eval("_ = 1; [[2]].each{ |(_)| }; _") + end + + def test_unused_variable + o = Object.new + assert_warning(/assigned but unused variable/) {o.instance_eval("def foo; a=1; nil; end")} + 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 foo0; #{a}=1; nil; end")} + assert_warning(/assigned but unused variable/) {o.instance_eval("def foo1; tap {a=1; a()}; end")} + assert_warning('') {o.instance_eval("def bar1; a=a=1; nil; end")} + assert_warning(/assigned but unused variable/) {o.instance_eval("def bar2; a, = 1, 2; end")} + assert_warning('') {o.instance_eval("def marg1(a); nil; end")} + assert_warning('') {o.instance_eval("def marg2((a)); nil; end")} + end + + def test_parsing_begin_statement_inside_method_definition + assert_equal :bug_20234, eval("def (begin;end).bug_20234; end") + NilClass.remove_method(:bug_20234) + assert_equal :bug_20234, eval("def (begin;rescue;end).bug_20234; end") + NilClass.remove_method(:bug_20234) + assert_equal :bug_20234, eval("def (begin;ensure;end).bug_20234; end") + NilClass.remove_method(:bug_20234) + assert_equal :bug_20234, eval("def (begin;rescue;else;end).bug_20234; end") + NilClass.remove_method(:bug_20234) + + assert_raise(SyntaxError) { eval("def (begin;else;end).bug_20234; end") } + assert_raise(SyntaxError) { eval("def (begin;ensure;else;end).bug_20234; end") } + end + + def test_named_capture_conflict + a = 1 + assert_warning('') {eval("a = 1; /(?<a>)/ =~ ''")} + a = "\u{3042}" + assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")} + end + + def test_named_capture_in_block + all_assertions_foreach(nil, + '(/(?<a>.*)/)', + '(;/(?<a>.*)/)', + '(%s();/(?<a>.*)/)', + '(%w();/(?<a>.*)/)', + '(1; (2; 3; (4; /(?<a>.*)/)))', + '(1+1; /(?<a>.*)/)', + '/#{""}(?<a>.*)/', + ) do |code, pass| + token = Random.bytes(4).unpack1("H*") + if pass + assert_equal(token, eval("#{code} =~ #{token.dump}; a")) + else + verbose_bak, $VERBOSE = $VERBOSE, nil # disable "warning: possibly useless use of a literal in void context" + begin + assert_nil(eval("#{code} =~ #{token.dump}; defined?(a)"), code) + ensure + $VERBOSE = verbose_bak + end + end + end + 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_unclosed_unicode_escape_at_eol_bug_19750 + assert_separately([], "#{<<-"begin;"}\n#{<<~'end;'}") + begin; + assert_syntax_error("/\\u", /too short escape sequence/) + assert_syntax_error("/\\u{", /unterminated regexp meets end of file/) + assert_syntax_error("/\\u{\\n", /invalid Unicode list/) + assert_syntax_error("/a#\\u{\\n/", /invalid Unicode list/) + re = eval("/a#\\u{\n$/x") + assert_match(re, 'a') + assert_not_match(re, 'a#') + re = eval("/a#\\u\n$/x") + assert_match(re, 'a') + assert_not_match(re, 'a#') + end; + end + + def test_error_def_in_argument + assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") + begin; + 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(&0)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_heredoc_interpolation + var = 1 + + v1 = <<~HEREDOC + something + #{"/#{var}"} + HEREDOC + + v2 = <<~HEREDOC + something + #{_other = "/#{var}"} + HEREDOC + + v3 = <<~HEREDOC + something + #{("/#{var}")} + HEREDOC + + assert_equal "something\n/1\n", v1 + assert_equal "something\n/1\n", v2 + assert_equal "something\n/1\n", v3 + assert_equal v1, v2 + assert_equal v2, v3 + assert_equal v1, v3 + end + + def test_heredoc_unterminated_interpolation + code = <<~'HEREDOC' + <<A+1 + #{ + HEREDOC + + assert_syntax_error(code, /can't find string "A"/) + end + + def test_unexpected_token_error + assert_syntax_error('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', /unexpected/) + end + + def test_unexpected_token_after_numeric + assert_syntax_error('0000xyz', /(^|\| ) \^~~(?!~)/) + assert_syntax_error('1.2i1.1', /(^|\| ) \^~~(?!~)/) + assert_syntax_error('1.2.3', /(^|\| ) \^~(?!~)/) + assert_syntax_error('1.', /unexpected end-of-input/) + assert_syntax_error('1e', /expecting end-of-input/) + end + + def test_truncated_source_line + lineno = __LINE__ + 1 + e = assert_syntax_error("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 123456789012345678901234567890123456789", + /unexpected local variable or method/) + + line = e.message.lines[1] + line.delete_prefix!("> #{lineno} | ") if line.start_with?(">") + + assert_operator(line, :start_with?, "...") + assert_operator(line, :end_with?, "...\n") + end + + def test_unterminated_regexp_error + e = assert_syntax_error("/x", /unterminated regexp meets end of file/) + assert_not_match(/unexpected tSTRING_END/, e.message) + 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.i(nil) + o.instance_eval {i = 0; i (-1.3).abs; i} + 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") + x + end + end + + def test_eof + assert_equal(42, eval("42\0""end")) + assert_equal(42, eval("42\C-d""end")) + assert_equal(42, eval("42\C-z""end")) + end + + def test_eof_in_def + assert_syntax_error("def m\n\0""end", /unexpected/) + assert_syntax_error("def m\n\C-d""end", /unexpected/) + assert_syntax_error("def m\n\C-z""end", /unexpected/) + end + + def test_unexpected_eof + assert_syntax_error('unless', /(^|\| ) \^(?!~)/) + end + + def test_location_of_invalid_token + assert_syntax_error('class xxx end', /(^|\| ) \^~~(?!~)/) + end + + def test_whitespace_warning + assert_syntax_error("\\foo", /backslash/) + assert_syntax_error("\\ ", /escaped space/) + assert_syntax_error("\\\t", /escaped horizontal tab/) + assert_syntax_error("\\\f", /escaped form feed/) + assert_syntax_error("\\\r", /escaped carriage return/) + assert_warn(/middle of line/) {eval(" \r ")} + assert_syntax_error("\\\v", /escaped vertical tab/) + end + + def test_command_def_cmdarg + assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}") + begin; + m def x(); end + 1.tap do end + end; + end + + NONASCII_CONSTANTS = [ + *%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]}, + "\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}", + *%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]}, + ] + + def assert_nonascii_const + assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n| + m = Module.new + assert_not_operator(m, :const_defined?, n) + assert_raise_with_message(NameError, /uninitialized/) do + m.const_get(n) + end + assert_nil(eval("defined?(m::#{n})")) + + v = yield m, n + + assert_operator(m, :const_defined?, n) + assert_equal("constant", eval("defined?(m::#{n})")) + assert_same(v, m.const_get(n)) + + m.__send__(:remove_const, n) + assert_not_operator(m, :const_defined?, n) + assert_nil(eval("defined?(m::#{n})")) + end + end + + def test_nonascii_const_set + assert_nonascii_const do |m, n| + m.const_set(n, 42) + end + end + + def test_nonascii_constant + assert_nonascii_const do |m, n| + m.module_eval("class #{n}; self; end") + end + end + + def test_cdmarg_after_command_args_and_tlbrace_arg + assert_valid_syntax('let () { m(a) do; end }') + end + + def test_void_value_in_rhs + w = "void value expression" + [ + "x = return 1", "x = return, 1", "x = 1, return", "x, y = return", + "x = begin return ensure end", + "x = begin ensure return end", + "x = begin return ensure return end", + "x = begin return; rescue; return end", + "x = begin return; rescue; return; else return end", + ].each do |code| + ex = assert_syntax_error(code, w) + assert_equal(1, ex.message.scan(w).size, ->{"same #{w.inspect} warning should be just once\n#{w.message}"}) + end + [ + "x = begin return; rescue; end", + "x = begin return; rescue; return; else end", + ].each do |code| + assert_valid_syntax(code) + end + end + + def eval_separately(code) + Class.new.class_eval(code) + end + + def assert_raise_separately(error, message, code) + assert_raise_with_message(error, message) do + eval_separately(code) + end + end + + def assert_ractor_shareable(obj) + assert Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} to be ractor shareable"} + end + + def assert_not_ractor_shareable(obj) + assert !Ractor.shareable?(obj), ->{"Expected #{mu_pp(obj)} not to be ractor shareable"} + end + + def test_shareable_constant_value_invalid + assert_warning(/invalid value/) do + assert_valid_syntax("# shareable_constant_value: invalid-option", verbose: true) + end + end + + def test_shareable_constant_value_ignored + assert_warning(/ignored/) do + assert_valid_syntax("nil # shareable_constant_value: true", verbose: true) + end + end + + def test_shareable_constant_value_simple + obj = [['unshareable_value']] + a, b, c = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + A = [[1]] + # shareable_constant_value: none + B = [[2]] + # shareable_constant_value: literal + C = [["shareable", "constant#{nil}"]] + D = A + + [A, B, C] + end; + assert_ractor_shareable(a) + assert_not_ractor_shareable(b) + assert_ractor_shareable(c) + assert_equal([1], a[0]) + assert_ractor_shareable(a[0]) + + a, obj = eval_separately(<<~'end;') + # shareable_constant_value: experimental_copy + obj = [["unshareable"]] + A = obj + [A, obj] + end; + + assert_ractor_shareable(a) + assert_not_ractor_shareable(obj) + assert_equal obj, a + assert_not_same obj, a + + bug_20339 = '[ruby-core:117186] [Bug #20339]' + bug_20341 = '[ruby-core:117197] [Bug #20341]' + a, b = eval_separately(<<~'end;') + # shareable_constant_value: literal + foo = 1 + bar = 2 + A = { foo => bar } + B = [foo, bar] + [A, B] + end; + + assert_ractor_shareable(a) + assert_ractor_shareable(b) + assert_equal([1], a.keys, bug_20339) + assert_equal([2], a.values, bug_20339) + assert_equal(1, b[0], bug_20341) + assert_equal(2, b[1], bug_20341) + end + + def test_shareable_constant_value_literal_const_refs + a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + # [Bug #20668] + SOME_CONST = { + 'Object' => Object, + 'String' => String, + 'Array' => Array, + } + SOME_CONST + end; + assert_ractor_shareable(a) + end + + def test_shareable_constant_value_nested + a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: none + class X + # shareable_constant_value: experimental_everything + var = [[1]] + A = var + end + B = [] + [X::A, B] + end; + assert_ractor_shareable(a) + assert_not_ractor_shareable(b) + assert_equal([1], a[0]) + assert_ractor_shareable(a[0]) + end + + def test_shareable_constant_value_hash_with_keyword_splat + a, b = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + # [Bug #20927] + x = { x: {} } + y = { y: {} } + A = { **x } + B = { x: {}, **y } + [A, B] + end; + assert_ractor_shareable(a) + assert_ractor_shareable(b) + assert_equal({ x: {}}, a) + assert_equal({ x: {}, y: {}}, b) + end + + def test_shareable_constant_value_unshareable_literal + assert_raise_separately(Ractor::IsolationError, /unshareable object to C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + C = ["Not " + "shareable"] + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + B::C = ["Not " + "shareable"] + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do + # shareable_constant_value: literal + ::C = ["Not " + "shareable"] + end + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do + # shareable_constant_value: literal + ::B = Class.new + ::B::C = ["Not " + "shareable"] + end + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::C/) do + # shareable_constant_value: literal + ::C ||= ["Not " + "shareable"] + end + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to B::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + B::C ||= ["Not " + "shareable"] + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_raise_with_message(Ractor::IsolationError, /unshareable object to ::B::C/) do + # shareable_constant_value: literal + ::B = Class.new + ::B::C ||= ["Not " + "shareable"] + end + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable object to ...::C/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + B = Class.new + def self.expr; B; end + expr::C ||= ["Not " + "shareable"] + end; + end + + def test_shareable_constant_value_nonliteral + assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + var = [:not_frozen] + C = var + end; + + assert_raise_separately(Ractor::IsolationError, /unshareable/, "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: literal + D = begin [] end + end; + end + + def test_shareable_constant_value_unfrozen + assert_raise_separately(Ractor::Error, /does not freeze object correctly/, + "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + o = Object.new + def o.freeze; self; end + C = [o] + end; + end + + def test_shareable_constant_value_massign + a = eval_separately("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # shareable_constant_value: experimental_everything + A, = 1 + end; + assert_equal(1, a) + end + + def test_if_after_class + assert_valid_syntax('module if true; Object end::Kernel; end') + assert_valid_syntax('module while true; break Object end::Kernel; end') + assert_valid_syntax('class if true; Object end::Kernel; end') + assert_valid_syntax('class while true; break Object end::Kernel; end') + end + + def test_escaped_space + assert_syntax_error('x = \ 42', /escaped space/) + end + + def test_label + expected = {:foo => 1} + + code = '{"foo": 1}' + assert_valid_syntax(code) + assert_equal(expected, eval(code)) + + code = '{foo: 1}' + assert_valid_syntax(code) + assert_equal(expected, eval(code)) + + class << (obj = Object.new) + attr_reader :arg + def set(arg) + @arg = arg + end + end + + assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}") + do; + obj.set foo: + 1 + end; + assert_equal(expected, eval(code)) + assert_equal(expected, obj.arg) + + assert_valid_syntax(code = "#{<<~"do;"}\n#{<<~'end;'}") + do; + obj.set "foo": + 1 + end; + assert_equal(expected, eval(code)) + assert_equal(expected, obj.arg) + end + + def test_ungettable_gvar + assert_syntax_error('$01234', /not allowed/) + assert_syntax_error('"#$01234"', /not allowed/) + end + +=begin + def test_past_scope_variable + assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}} + end +=end + + def assert_parse(code) + assert_kind_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.parse(code)) + end + + def assert_parse_error(code, message) + assert_raise_with_message(SyntaxError, message) do + $VERBOSE, verbose_bak = nil, $VERBOSE + begin + RubyVM::AbstractSyntaxTree.parse(code) + ensure + $VERBOSE = verbose_bak + end + end + end +end diff --git a/test/ruby/test_path.rb b/test/ruby/test_path.rb new file mode 100644 index 0000000000..b35e942a2a --- /dev/null +++ b/test/ruby/test_path.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestPath < Test::Unit::TestCase + def test_path + assert_equal("a", File.basename("a")) + assert_equal("b", File.basename("a/b")) + assert_equal("b", File.basename("a/b/")) + assert_equal("/", File.basename("/")) + assert_equal("/", File.basename("//")) + assert_equal("/", File.basename("///")) + assert_equal("b", File.basename("a/b////")) + assert_equal("a", File.basename("a.rb", ".rb")) + assert_equal("a", File.basename("a.rb///", ".rb")) + assert_equal("a", File.basename("a.rb///", ".*")) + assert_equal("a.rb", File.basename("a.rb///", ".c")) + assert_equal(".", File.dirname("a")) + assert_equal("/", File.dirname("/")) + assert_equal("/", File.dirname("/a")) + assert_equal("a", File.dirname("a/b")) + assert_equal("a/b", File.dirname("a/b/c")) + assert_equal("/a/b", File.dirname("/a/b/c")) + assert_equal("/a", File.dirname("/a/b/")) + assert_equal("/a", File.dirname("/a/b///")) + case Dir.pwd + when %r'\A\w:' + assert_match(/\A\w:\/\z/, File.expand_path(".", "/")) + assert_match(/\A\w:\/a\z/, File.expand_path("a", "/")) + dosish = true + when %r'\A//' + assert_match(%r'\A//[^/]+/[^/]+\z', File.expand_path(".", "/")) + assert_match(%r'\A//[^/]+/[^/]+/a\z', File.expand_path(".", "/")) + dosish = true + else + assert_equal("/", File.expand_path(".", "/")) + assert_equal("/sub", File.expand_path("sub", "/")) + end + if dosish + assert_equal("//127.0.0.1/share", File.expand_path("/", "//127.0.0.1/share/sub")) + assert_equal("//127.0.0.1/share/dir", File.expand_path("/dir", "//127.0.0.1/share/sub")) + assert_equal("z:/", File.expand_path("/", "z:/sub")) + assert_equal("z:/dir", File.expand_path("/dir", "z:/sub")) + end + assert_equal("//", File.expand_path(".", "//")) + assert_equal("//sub", File.expand_path("sub", "//")) + + assert_equal("//127.0.0.1/\u3042", File.expand_path("\u3042", "//127.0.0.1")) + end + + def test_dirname + if /(bcc|ms)win\d|mingw|cygwin|emx/ =~ RUBY_PLATFORM + # DOSISH_DRIVE_LETTER + assert_equal('C:.', File.dirname('C:')) + assert_equal('C:.', File.dirname('C:a')) + assert_equal('C:.', File.dirname('C:a/')) + assert_equal('C:a', File.dirname('C:a/b'), "[ruby-dev:27738]") + + assert_equal('C:/', File.dirname('C:/')) + assert_equal('C:/', File.dirname('C:/a')) + assert_equal('C:/', File.dirname('C:/a/')) + assert_equal('C:/a', File.dirname('C:/a/b')) + + assert_equal('C:/', File.dirname('C://')) + assert_equal('C:/', File.dirname('C://a')) + assert_equal('C:/', File.dirname('C://a/')) + assert_equal('C:/a', File.dirname('C://a/b')) + + assert_equal('C:/', File.dirname('C:///'), "[ruby-dev:27738]") + assert_equal('C:/', File.dirname('C:///a')) + assert_equal('C:/', File.dirname('C:///a/')) + assert_equal('C:/a', File.dirname('C:///a/b')) + else + # others + assert_equal('.', File.dirname('C:')) + assert_equal('.', File.dirname('C:a')) + assert_equal('.', File.dirname('C:a/')) + assert_equal('C:a', File.dirname('C:a/b')) + + assert_equal('.', File.dirname('C:/')) + assert_equal('C:', File.dirname('C:/a')) + assert_equal('C:', File.dirname('C:/a/')) + assert_equal('C:/a', File.dirname('C:/a/b')) + + assert_equal('.', File.dirname('C://')) + assert_equal('C:', File.dirname('C://a')) + assert_equal('C:', File.dirname('C://a/')) + # not spec. + #assert_equal('C://a', File.dirname('C://a/b')) + + assert_equal('.', File.dirname('C:///')) + assert_equal('C:', File.dirname('C:///a')) + assert_equal('C:', File.dirname('C:///a/')) + # not spec. + #assert_equal('C:///a', File.dirname('C:///a/b')) + end + + assert_equal('.', File.dirname('')) + assert_equal('.', File.dirname('a')) + assert_equal('.', File.dirname('a/')) + assert_equal('a', File.dirname('a/b')) + + assert_equal('/', File.dirname('/')) + assert_equal('/', File.dirname('/a')) + assert_equal('/', File.dirname('/a/')) + assert_equal('/a', File.dirname('/a/b')) + + if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM + # DOSISH_UNC + assert_equal('//', File.dirname('//')) + assert_equal('//a', File.dirname('//a')) + assert_equal('//a', File.dirname('//a/')) + assert_equal('//a/b', File.dirname('//a/b')) + assert_equal('//a/b', File.dirname('//a/b/')) + assert_equal('//a/b', File.dirname('//a/b/c')) + + assert_equal('//', File.dirname('///')) + assert_equal('//a', File.dirname('///a')) + assert_equal('//a', File.dirname('///a/')) + assert_equal('//a/b', File.dirname('///a/b')) + assert_equal('//a/b', File.dirname('///a/b/')) + assert_equal('//a/b', File.dirname('///a/b/c')) + else + # others + assert_equal('/', File.dirname('//')) + assert_equal('/', File.dirname('//a')) + assert_equal('/', File.dirname('//a/')) + assert_equal('/a', File.dirname('//a/b')) + assert_equal('/a', File.dirname('//a/b/')) + assert_equal('/a/b', File.dirname('//a/b/c')) + + assert_equal('/', File.dirname('///')) + assert_equal('/', File.dirname('///a')) + assert_equal('/', File.dirname('///a/')) + assert_equal('/a', File.dirname('///a/b')) + assert_equal('/a', File.dirname('///a/b/')) + assert_equal('/a/b', File.dirname('///a/b/c')) + end + end + + def test_basename + if /(bcc|ms)win\d|mingw|cygwin|emx/ =~ RUBY_PLATFORM + # DOSISH_DRIVE_LETTER + assert_equal('', File.basename('C:')) + assert_equal('a', File.basename('C:a')) + assert_equal('a', File.basename('C:a/')) + assert_equal('b', File.basename('C:a/b')) + + assert_equal('/', File.basename('C:/')) + assert_equal('a', File.basename('C:/a')) + assert_equal('a', File.basename('C:/a/')) + assert_equal('b', File.basename('C:/a/b')) + + assert_equal('/', File.basename('C://')) + assert_equal('a', File.basename('C://a')) + assert_equal('a', File.basename('C://a/')) + assert_equal('b', File.basename('C://a/b')) + + assert_equal('/', File.basename('C:///')) + assert_equal('a', File.basename('C:///a')) + assert_equal('a', File.basename('C:///a/')) + assert_equal('b', File.basename('C:///a/b')) + else + # others + assert_equal('C:', File.basename('C:')) + assert_equal('C:a', File.basename('C:a')) + assert_equal('C:a', File.basename('C:a/')) + assert_equal('b', File.basename('C:a/b')) + + assert_equal('C:', File.basename('C:/')) + assert_equal('a', File.basename('C:/a')) + assert_equal('a', File.basename('C:/a/')) + assert_equal('b', File.basename('C:/a/b')) + + assert_equal('C:', File.basename('C://')) + assert_equal('a', File.basename('C://a')) + assert_equal('a', File.basename('C://a/')) + assert_equal('b', File.basename('C://a/b')) + + assert_equal('C:', File.basename('C:///')) + assert_equal('a', File.basename('C:///a')) + assert_equal('a', File.basename('C:///a/')) + assert_equal('b', File.basename('C:///a/b')) + end + + assert_equal('', File.basename('')) + assert_equal('a', File.basename('a')) + assert_equal('a', File.basename('a/')) + assert_equal('b', File.basename('a/b')) + + assert_equal('/', File.basename('/')) + assert_equal('a', File.basename('/a')) + assert_equal('a', File.basename('/a/')) + assert_equal('b', File.basename('/a/b')) + + assert_equal("..", File.basename("..", ".*")) + + if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM + # DOSISH_UNC + assert_equal('/', File.basename('//')) + assert_equal('/', File.basename('//a')) + assert_equal('/', File.basename('//a/')) + assert_equal('/', File.basename('//a/b'), "[ruby-dev:27776]") + assert_equal('/', File.basename('//a/b/')) + assert_equal('c', File.basename('//a/b/c')) + + assert_equal('/', File.basename('///')) + assert_equal('/', File.basename('///a')) + assert_equal('/', File.basename('///a/')) + assert_equal('/', File.basename('///a/b')) + assert_equal('/', File.basename('///a/b/')) + assert_equal('c', File.basename('///a/b/c')) + else + # others + assert_equal('/', File.basename('//')) + assert_equal('a', File.basename('//a')) + assert_equal('a', File.basename('//a/')) + assert_equal('b', File.basename('//a/b')) + assert_equal('b', File.basename('//a/b/')) + assert_equal('c', File.basename('//a/b/c')) + + assert_equal('/', File.basename('///')) + assert_equal('a', File.basename('///a')) + assert_equal('a', File.basename('///a/')) + assert_equal('b', File.basename('///a/b')) + assert_equal('b', File.basename('///a/b/')) + assert_equal('c', File.basename('///a/b/c')) + end + end + + def test_extname + 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 = '.' + end + assert_equal(ext, File.extname('a.rb.')) + if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + # trailing spaces and dots are ignored on NTFS. + ext = '' + end + assert_equal(ext, File.extname('a.')) + assert_equal('', File.extname('.x')) + assert_equal('', File.extname('..x')) + end + + 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_pattern_matching.rb b/test/ruby/test_pattern_matching.rb new file mode 100644 index 0000000000..96aa2a7fd6 --- /dev/null +++ b/test/ruby/test_pattern_matching.rb @@ -0,0 +1,1763 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestPatternMatching < Test::Unit::TestCase + class NullFormatter + def message_for(corrections) + "" + end + end + + def setup + if defined?(DidYouMean.formatter=nil) + @original_formatter = DidYouMean.formatter + DidYouMean.formatter = NullFormatter.new + end + end + + def teardown + if defined?(DidYouMean.formatter=nil) + DidYouMean.formatter = @original_formatter + end + end + + class C + class << self + attr_accessor :keys + end + + def initialize(obj) + @obj = obj + end + + def deconstruct + @obj + end + + def deconstruct_keys(keys) + C.keys = keys + @obj + end + end + + def test_basic + assert_block do + case 0 + in 0 + true + else + false + end + end + + assert_block do + case 0 + in 1 + false + else + true + end + end + + assert_raise(NoMatchingPatternError) do + case 0 + in 1 + false + end + end + + begin + o = [0] + case o + in 1 + false + end + rescue => e + assert_match o.inspect, e.message + end + + assert_block do + begin + true + ensure + case 0 + in 0 + false + end + end + end + + assert_block do + begin + true + ensure + case 0 + in 1 + else + false + end + end + end + + assert_raise(NoMatchingPatternError) do + begin + ensure + case 0 + in 1 + end + end + end + + assert_block do + eval(%q{ + case true + in a + a + end + }) + end + + assert_block do + tap do |a| + tap do + case true + in a + a + end + end + end + end + + assert_raise(NoMatchingPatternError) do + o = BasicObject.new + def o.match + case 0 + in 1 + end + end + o.match + end + end + + def test_modifier + assert_block do + case 0 + in a if a == 0 + true + end + end + + assert_block do + case 0 + in a if a != 0 + else + true + end + end + + assert_block do + case 0 + in a unless a != 0 + true + end + end + + assert_block do + case 0 + in a unless a == 0 + else + true + end + end + end + + def test_as_pattern + assert_block do + case 0 + in 0 => a + a == 0 + end + end + end + + def test_alternative_pattern + assert_block do + [0, 1].all? do |i| + case i + in 0 | 1 + true + end + end + end + + assert_block do + case 0 + in _ | _a + true + end + end + + assert_valid_syntax(%{ + case 0 + in [ :a | :b, x] + true + end + }) + + assert_in_out_err(['-c'], %q{ + case 0 + in a | 0 + end + }, [], /alternative pattern/, + success: false) + + assert_in_out_err(['-c'], %q{ + case 0 + in 0 | a + end + }, [], /alternative pattern/, + success: false) + end + + def test_alternative_pattern_nested + assert_in_out_err(['-c'], %q{ + case 0 + in [a] | 1 + end + }, [], /alternative pattern/, + success: false) + + assert_in_out_err(['-c'], %q{ + case 0 + in { a: b } | 1 + end + }, [], /alternative pattern/, + success: false) + + assert_in_out_err(['-c'], %q{ + case 0 + in [{ a: [{ b: [{ c: }] }] }] | 1 + end + }, [], /alternative pattern/, + success: false) + end + + def test_var_pattern + # NODE_DASGN_CURR + assert_block do + case 0 + in a + a == 0 + end + end + + # NODE_DASGN + b = 0 + assert_block do + case 1 + in b + b == 1 + end + end + + # NODE_LASGN + case 0 + in c + assert_equal(0, c) + else + flunk + end + + assert_syntax_error(%q{ + case 0 + in ^a + end + }, /no such local variable/) + + assert_syntax_error(%q{ + case 0 + in a, a + end + }, /duplicated variable name/) + + assert_block do + case [0, 1, 2, 3] + in _, _, _a, _a + true + end + end + + assert_syntax_error(%q{ + case 0 + in a, {a:} + end + }, /duplicated variable name/) + + assert_syntax_error(%q{ + case 0 + in a, {"a":} + end + }, /duplicated variable name/) + + assert_block do + case [0, "1"] + in a, "#{case 1; in a; a; end}" + true + end + end + + assert_syntax_error(%q{ + case [0, "1"] + in a, "#{case 1; in a; a; end}", a + end + }, /duplicated variable name/) + + assert_block do + case 0 + in a + assert_equal(0, a) + true + in a + flunk + end + end + + assert_syntax_error(%q{ + 0 => [a, a] + }, /duplicated variable name/) + end + + def test_literal_value_pattern + assert_block do + case [nil, self, true, false] + in [nil, self, true, false] + true + end + end + + assert_block do + case [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + in [0d170, 0D170, 0xaa, 0xAa, 0xAA, 0Xaa, 0XAa, 0XaA, 0252, 0o252, 0O252] + true + end + + case [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + in [0b10101010, 0B10101010, 12r, 12.3r, 1i, 12.3ri] + true + end + end + + assert_block do + x = 'x' + case ['a', 'a', x] + in ['a', "a", "#{x}"] + true + end + end + + assert_block do + case ["a\n"] + in [<<END] +a +END + true + end + end + + assert_block do + case [:a, :"a"] + in [:a, :"a"] + true + end + end + + assert_block do + case [0, 1, 2, 3, 4, 5] + in [0..1, 0...2, 0.., 0..., (...5), (..5)] + true + end + end + + assert_syntax_error(%q{ + case 0 + in a..b + end + }, /unexpected/) + + assert_block do + case 'abc' + in /a/ + true + end + end + + assert_block do + a = "abc" + case 'abc' + in /#{a}/o + true + end + end + + assert_block do + case 0 + in ->(i) { i == 0 } + true + end + end + + assert_block do + case [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), 'a'] + in [%(a), %q(a), %Q(a), %w(a), %W(a), %i(a), %I(a), %s(a), %x(echo a), %(), %q(), %Q(), %w(), %W(), %i(), %I(), %s(), %r(a)] + true + end + end + + assert_block do + case [__FILE__, __LINE__ + 1, __ENCODING__] + in [__FILE__, __LINE__, __ENCODING__] + true + end + end + end + + def test_constant_value_pattern + assert_block do + case 0 + in Integer + true + end + end + + assert_block do + case 0 + in Object::Integer + true + end + end + + assert_block do + case 0 + in ::Object::Integer + true + end + end + end + + def test_pin_operator_value_pattern + assert_block do + a = /a/ + case 'abc' + in ^a + true + end + end + + assert_block do + case [0, 0] + in a, ^a + a == 0 + end + end + + assert_block do + @a = /a/ + case 'abc' + in ^@a + true + end + end + + assert_block do + @@TestPatternMatching = /a/ + case 'abc' + in ^@@TestPatternMatching + true + end + end + + assert_block do + $TestPatternMatching = /a/ + case 'abc' + in ^$TestPatternMatching + true + end + end + end + + def test_pin_operator_expr_pattern + assert_block do + case 'abc' + in ^(/a/) + true + end + end + + assert_block do + case {name: '2.6', released_at: Time.new(2018, 12, 25)} + in {released_at: ^(Time.new(2010)..Time.new(2020))} + true + end + end + + assert_block do + case 0 + in ^(0+0) + true + end + end + + assert_valid_syntax("1 in ^(1\n)") + end + + def test_array_pattern + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0,; + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in 0,; + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in 0, 1 + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in 0, 1 + else + true + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *a + a == [] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a + a == [0] + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *a, 0, 1 + raise a # suppress "unused variable: a" warning + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *a, 0, 1 + a == [] + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *a, 1, 2 + a == [0] + end + end + end + + assert_block do + [[], C.new([])].all? do |i| + case i + in *; + true + end + end + end + + assert_block do + [[0], C.new([0])].all? do |i| + case i + in *, 0, 1 + else + true + end + end + end + + assert_block do + [[0, 1], C.new([0, 1])].all? do |i| + case i + in *, 0, 1 + true + end + end + end + + assert_block do + [[0, 1, 2], C.new([0, 1, 2])].all? do |i| + case i + in *, 1, 2 + true + end + end + end + + assert_block do + case C.new([0]) + in C(0) + true + end + end + + assert_block do + case C.new([0]) + in Array(0) + else + true + end + end + + assert_block do + case C.new([]) + in C() + true + end + end + + assert_block do + case C.new([]) + in Array() + else + true + end + end + + assert_block do + case C.new([0]) + in C[0] + true + end + end + + assert_block do + case C.new([0]) + in Array[0] + else + true + end + end + + assert_block do + case C.new([]) + in C[] + true + end + end + + assert_block do + case C.new([]) + in Array[] + else + true + end + end + + assert_block do + case [] + in [] + true + end + end + + assert_block do + case C.new([]) + in [] + true + end + end + + assert_block do + case [0] + in [0] + true + end + end + + assert_block do + case C.new([0]) + in [0] + true + end + end + + assert_block do + case [0] + in [0,] + true + end + end + + assert_block do + case [0, 1] + in [0,] + true + end + end + + assert_block do + case [] + in [0, *a] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0] + in [0, *a, 1] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0, 1] + in [0, *a, 1] + a == [] + end + end + + assert_block do + case [0, 1, 2] + in [0, *a, 2] + a == [1] + end + end + + assert_block do + case [] + in [0, *] + else + true + end + end + + assert_block do + case [0] + in [0, *] + true + end + end + + assert_block do + case [0, 1] + in [0, *] + true + end + end + + assert_block do + case [] + in [0, *a] + raise a # suppress "unused variable: a" warning + else + true + end + end + + assert_block do + case [0] + in [0, *a] + a == [] + end + end + + assert_block do + case [0, 1] + in [0, *a] + a == [1] + end + end + + assert_block do + case [0] + in [0, *, 1] + else + true + end + end + + assert_block do + case [0, 1] + in [0, *, 1] + true + end + end + + assert_syntax_error(%q{ + 0 => [a, *a] + }, /duplicated variable name/) + end + + def test_find_pattern + [0, 1, 2] => [*, 1 => a, *] + assert_equal(1, a) + + [0, 1, 2] => [*a, 1 => b, *c] + assert_equal([0], a) + assert_equal(1, b) + assert_equal([2], c) + + assert_block do + case [0, 1, 2] + in [*, 9, *] + false + else + true + end + end + + assert_block do + case [0, 1, 2] + in [*, Integer, String, *] + false + else + true + end + end + + [0, 1, 2] => [*a, 1 => b, 2 => c, *d] + assert_equal([0], a) + assert_equal(1, b) + assert_equal(2, c) + assert_equal([], d) + + case [0, 1, 2] + in *, 1 => a, *; + assert_equal(1, a) + end + + assert_block do + case [0, 1, 2] + in String(*, 1, *) + false + in Array(*, 1, *) + true + end + end + + assert_block do + case [0, 1, 2] + in String[*, 1, *] + false + in Array[*, 1, *] + true + end + end + + # https://bugs.ruby-lang.org/issues/17534 + assert_block do + case [0, 1, 2] + in x + x = x # avoid a warning "assigned but unused variable - x" + true + in [*, 2, *] + false + end + end + + assert_syntax_error(%q{ + 0 => [*a, a, b, *b] + }, /duplicated variable name/) + end + + def test_hash_pattern + assert_block do + [{}, C.new({})].all? do |i| + case i + in a: 0 + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: 0, b: 1 + else + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{a: 0, b: 1, c: 2}, C.new({a: 0, b: 1, c: 2})].all? do |i| + case i + in a: 0, b: 1 + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in a: + raise a # suppress "unused variable: a" warning + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a: + a == 0 + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a": 0 + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in "a":; + a == 0 + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **a + a == {} + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **a + a == {a: 0} + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **; + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in a:, **b + raise a # suppress "unused variable: a" warning + raise b # suppress "unused variable: b" warning + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **b + a == 0 && b == {} + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a:, **b + a == 0 && b == {b: 1} + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in **nil + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in **nil + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in a:, **nil + assert_equal(0, a) + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in a:, **nil + assert_equal(0, a) + else + true + end + end + end + + assert_block do + case C.new({a: 0}) + in C(a: 0) + true + end + end + + assert_block do + case {a: 0} + in C(a: 0) + else + true + end + end + + assert_block do + case C.new({a: 0}) + in C[a: 0] + true + end + end + + assert_block do + case {a: 0} + in C[a: 0] + else + true + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {a: 0} + else + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{a: 0, b: 1}, C.new({a: 0, b: 1})].all? do |i| + case i + in {a: 0} + true + end + end + end + + assert_block do + [{}, C.new({})].all? do |i| + case i + in {} + true + end + end + end + + assert_block do + [{a: 0}, C.new({a: 0})].all? do |i| + case i + in {} + else + true + end + end + end + + bug18890 = assert_warning(/(?:.*:[47]: warning: possibly useless use of a literal in void context\n){2}/) do + eval("#{<<~';;;'}") + proc do |i| + case i + in a: + 0 # line 4 + a + in "b": + 0 # line 7 + b + else + false + end + end + ;;; + end + [{a: 42}, {b: 42}].each do |i| + assert_block('newline should be significant after pattern label') do + bug18890.call(i) + end + end + + assert_syntax_error(%q{ + case _ + in a:, a: + end + }, /duplicated key name/) + + assert_syntax_error(%q{ + case _ + in a?: + end + }, /key must be valid as local variables/) + + assert_block do + case {a?: true} + in a?: true + true + end + end + + assert_block do + case {a: 0, b: 1} + in {a: 1,} + false + in {a:,} + _a = a + true + end + end + + assert_block do + case {a: 0} + in {a: 1 + } + false + in {a: + 2} + false + in a: {b:}, c: + _b = b + p c + in {a: + } + _a = a + true + end + end + + assert_syntax_error(%q{ + case _ + in "a-b": + end + }, /key must be valid as local variables/) + + assert_block do + case {"a-b": true} + in "a-b": true + true + end + end + + assert_syntax_error(%q{ + case _ + in "#{a}": a + end + }, /symbol literal with interpolation is not allowed/) + + assert_syntax_error(%q{ + case _ + in "#{a}": + end + }, /symbol literal with interpolation is not allowed/) + end + + def test_paren + assert_block do + case 0 + in (0) + true + end + end + end + + def test_nomatchingpatternerror + assert_equal(StandardError, NoMatchingPatternError.superclass) + end + + def test_invalid_syntax + assert_syntax_error(%q{ + case 0 + in a, b: + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in [a:] + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {a} + end + }, /unexpected/) + + assert_syntax_error(%q{ + case 0 + in {0 => a} + end + }, /unexpected/) + end + + ################################################################ + + class CTypeError + def deconstruct + nil + end + + def deconstruct_keys(keys) + nil + end + end + + def test_deconstruct + assert_raise(TypeError) do + case CTypeError.new + in [] + end + end + end + + def test_deconstruct_keys + assert_raise(TypeError) do + case CTypeError.new + in {} + end + end + + assert_block do + case C.new({}) + in {} + C.keys == nil + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:} + assert_equal(0, b) + C.keys == [:a, :b] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:, **} + assert_equal(0, b) + C.keys == [:a, :b] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {a: 0, b:, **r} + assert_equal(0, b) + assert_equal({c: 0}, r) + C.keys == nil + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {**} + C.keys == [] + end + end + + assert_block do + case C.new({a: 0, b: 0, c: 0}) + in {**r} + assert_equal({a: 0, b: 0, c: 0}, r) + C.keys == nil + end + end + end + + ################################################################ + + class CDeconstructCache + def initialize(v) + @v = v + end + + def deconstruct + @v.shift + end + end + + def test_deconstruct_cache + assert_block do + case CDeconstructCache.new([[0]]) + in [1] + in [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0, 1]]) + in [1,] + in [0,] + true + end + end + + assert_block do + case CDeconstructCache.new([[[0]]]) + in [[1]] + in [[*a]] + a == [0] + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [x] if x > 0 + in [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [] + in [1] | [0] + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in [1] => _ + in [0] => _ + true + end + end + + assert_block do + case CDeconstructCache.new([[0]]) + in C[0] + in CDeconstructCache[0] + true + end + end + + assert_block do + case [CDeconstructCache.new([[0], [1]])] + in [[1]] + false + in [[1]] + true + end + end + + assert_block do + case CDeconstructCache.new([[0, :a, 1]]) + in [*, String => x, *] + false + in [*, Symbol => x, *] + x == :a + end + end + end + + ################################################################ + + class TestPatternMatchingRefinements < Test::Unit::TestCase + class C1 + def deconstruct + [:C1] + end + end + + class C2 + end + + module M + refine Array do + def deconstruct + [0] + end + end + + refine Hash do + def deconstruct_keys(_) + {a: 0} + end + end + + refine C2.singleton_class do + def ===(obj) + obj.kind_of?(C1) + end + end + end + + using M + + def test_refinements + assert_block do + case [] + in [0] + true + end + end + + assert_block do + case {} + in {a: 0} + true + end + end + + assert_block do + case C1.new + in C2(:C1) + true + end + end + end + end + + ################################################################ + + def test_struct + assert_block do + s = Struct.new(:a, :b) + case s[0, 1] + in 0, 1 + true + end + end + + s = Struct.new(:a, :b, keyword_init: true) + assert_block do + case s[a: 0, b: 1] + in **r + r == {a: 0, b: 1} + end + end + assert_block do + s = Struct.new(:a, :b, keyword_init: true) + case s[a: 0, b: 1] + in a:, b: + a == 0 && b == 1 + end + end + assert_block do + s = Struct.new(:a, :b, keyword_init: true) + case s[a: 0, b: 1] + in a:, c: + raise a # suppress "unused variable: a" warning + raise c # suppress "unused variable: c" warning + flunk + in a:, b:, c: + flunk + in b: + b == 1 + end + end + end + + ################################################################ + + def test_one_line + 1 => a + assert_equal 1, a + assert_raise(NoMatchingPatternError) do + {a: 1} => {a: 0} + end + + [1, 2] => a, b + assert_equal 1, a + assert_equal 2, b + + {a: 1} => a: + assert_equal 1, a + + assert_equal true, (1 in 1) + assert_equal false, (1 in 2) + end + + def test_bug18990 + {a: 0} => a: + assert_equal 0, a + {a: 0} => a: + assert_equal 0, a + + {a: 0} in a: + assert_equal 0, a + {a: 0} in a: + assert_equal 0, a + end + + ################################################################ + + def test_single_pattern_error_value_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 1 === 0 does not return true") do + 0 => 1 + end + end + + def test_single_pattern_error_array_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] length mismatch (given 1, expected 0)") do + [0] => [] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [_, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [0, 1] + end + + assert_raise_with_message(NoMatchingPatternError, "[0, 0]: 1 === 0 does not return true") do + [0, 0] => [*, 0, 1] + end + end + + def test_single_pattern_error_find_pattern + assert_raise_with_message(NoMatchingPatternError, "[]: Hash === [] does not return true") do + [] => Hash[*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct") do + 0 => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[]: [] length mismatch (given 0, expected 1+)") do + [] => [*, _, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, 1, *] + end + + assert_raise_with_message(NoMatchingPatternError, "[0]: [0] does not match to find pattern") do + [0] => [*, {a:}, *] + raise a # suppress "unused variable: a" warning + end + end + + def test_single_pattern_error_hash_pattern + assert_raise_with_message(NoMatchingPatternError, "{}: Array === {} does not return true") do + {} => Array[a:] + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternError, "0: 0 does not respond to #deconstruct_keys") do + 0 => {a:} + raise a # suppress "unused variable: a" warning + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{a: 0}: key not found: :aa") do + {a: 0} => {aa:} + raise aa # suppress "unused variable: aa" warning + rescue NoMatchingPatternKeyError => e + assert_equal({a: 0}, e.matchee) + assert_equal(:aa, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternKeyError, "{a: {b: 0}}: key not found: :bb") do + {a: {b: 0}} => {a: {bb:}} + raise bb # suppress "unused variable: bb" warning + rescue NoMatchingPatternKeyError => e + assert_equal({b: 0}, e.matchee) + assert_equal(:bb, e.key) + raise e + end + + assert_raise_with_message(NoMatchingPatternError, "{a: 0}: 1 === 0 does not return true") do + {a: 0} => {a: 1} + end + + assert_raise_with_message(NoMatchingPatternError, "{a: 0}: {a: 0} is not empty") do + {a: 0} => {} + end + + assert_raise_with_message(NoMatchingPatternError, "[{a: 0}]: rest of {a: 0} is not empty") do + [{a: 0}] => [{**nil}] + end + end + + def test_single_pattern_error_as_pattern + assert_raise_with_message(NoMatchingPatternError, "[0]: 1 === 0 does not return true") do + case [0] + in [1] => _ + end + end + end + + def test_single_pattern_error_alternative_pattern + assert_raise_with_message(NoMatchingPatternError, "0: 2 === 0 does not return true") do + 0 => 1 | 2 + end + end + + def test_single_pattern_error_guard_clause + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ if false + end + end + + assert_raise_with_message(NoMatchingPatternError, "0: guard clause does not return true") do + case 0 + in _ unless true + end + end + end +end diff --git a/test/ruby/test_pipe.rb b/test/ruby/test_pipe.rb new file mode 100644 index 0000000000..9fa42fd375 --- /dev/null +++ b/test/ruby/test_pipe.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: false +require 'test/unit' +require_relative 'ut_eof' + +class TestPipe < Test::Unit::TestCase + include TestEOF + def open_file(content) + r, w = IO.pipe + w << content + w.close + begin + yield r + ensure + 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 new file mode 100644 index 0000000000..f1db934000 --- /dev/null +++ b/test/ruby/test_primitive.rb @@ -0,0 +1,452 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRubyPrimitive < Test::Unit::TestCase + + def test_not + assert_equal false, !true + assert_equal true, !false + assert_equal true, !nil + assert_equal false, !(1+1) + assert_equal false, !!nil + assert_equal true, !!1 + end + + def test_lvar + a = 1 + assert_equal 1, a + b = 2 + assert_equal 1, a + a = b = 3 + assert_equal 3, a + assert_equal 3, b + a = b = c = 4 + assert_equal 4, a + assert_equal 4, b + assert_equal 4, c + end + + C_Setup = -> do + remove_const :C if defined? ::TestRubyPrimitive::C + remove_const :A if defined? ::TestRubyPrimitive::A + + C = 1 + class A + Const = 1 + class B + Const = 2 + class C + Const = 3 + def const + Const + end + end + end + end + (1..2).map { + A::B::C::Const + } + end + + def test_constant + C_Setup.call + + assert_equal 1, C + assert_equal 1, C + assert_equal 1, A::Const + assert_equal 2, A::B::Const + assert_equal 3, A::B::C::Const + assert_equal 3, A::B::C.new.const + assert_equal 1, ::TestRubyPrimitive::A::Const + A::B::C.send(:remove_const, :Const) + assert_equal 2, A::B::C.new.const + assert_raise(TypeError) { + C::CONST + } + end + + class A2 + class B2 + class C2 + C = 7 + end + end + end + + def test_constant_cache + i = 0 + while i < 3 + r = A2::B2::C2::C + i += 1 + end + assert_equal 7, r + end + + class A3 + class B3 + C = 99 + end + end + i = 0 + while i < 3 + r = r = A3::B3::C # cache + class A3::B3 + remove_const :C + end + A3::B3::C = i ** i + i += 1 + end + + def test_constant_cache2 + assert_equal 4, A3::B3::C + end + + class A4 + Const = 7 + (1..3).map { + $test_ruby_primitive_constant_cache3 = self::Const + } + end + + def test_constant_cache3 + assert_equal 7, $test_ruby_primitive_constant_cache3 + end + + class A5 + Const = 8 + (1..3).map { + $test_ruby_primitive_constant_cache4 = eval('self')::Const + } + end + + def test_constatant_cache4 + assert_equal 8, $test_ruby_primitive_constant_cache4 + end + + class A6 + Const = 0 + def self.foo + self::Const + end + end + class B6 < A6 + Const = 1 + end + class C6 < B6 + Const = 2 + end + $test_ruby_primitive_constant_cache5 = [A6.foo, B6.foo, C6.foo] + + def test_constant_cache5 + assert_equal [0, 1, 2], $test_ruby_primitive_constant_cache5 + end + + def test_gvar + $test_ruby_primitive_gvar = 7 + assert_equal 7, $test_ruby_primitive_gvar + assert_equal 7, $test_ruby_primitive_gvar + $test_ruby_primitive_gvar = 88 + assert_equal 88, $test_ruby_primitive_gvar + assert_equal 88, $test_ruby_primitive_gvar + assert_equal 7, ($test_ruby_primitive_gvar = 7) + assert_equal 7, ($test_ruby_primitive_gvar = 7) + end + + A7_Setup = -> do + remove_const :A7 if defined? TestRubyPrimitive::A7 + + class A7 + @@c = 1 + def m + @@c += 1 + end + end + end + + def test_cvar_from_instance_method + A7_Setup.call + + assert_equal 2, A7.new.m + assert_equal 3, A7.new.m + assert_equal 4, A7.new.m + end + + A8_Setup = -> do + remove_const :A8 if defined? TestRubyPrimitive::A8 + + class A8 + @@c = 1 + class << self + def m + @@c += 1 + end + end + end + end + + def test_cvar_from_singleton_method + A8_Setup.call + + assert_equal 2, A8.m + assert_equal 3, A8.m + assert_equal 4, A8.m + end + + A9_Setup = -> do + remove_const :A8 if defined? TestRubyPrimitive::A8 + + class A9 + @@c = 1 + def self.m + @@c += 1 + end + end + end + + def test_cvar_from_singleton_method2 + A9_Setup.call + + assert_equal 2, A9.m + assert_equal 3, A9.m + assert_equal 4, A9.m + end + + class A10 + attr_accessor :a + end + + def test_opassign + i = 0 + i += 1 + assert_equal 1, i + + @iv = 2 + @iv += 2 + assert_equal 4, @iv + + # init @@cv + @@cv = nil + + @@cv ||= 1 + assert_equal 1, @@cv + @@cv &&= 2 + assert_equal 2, @@cv + @@cv ||= 99 + assert_equal 2, @@cv + + $gv = 3 + $gv += 4 + assert_equal 7, $gv + + obj = A10.new + obj.a = 9 + obj.a &&= 7 + assert_equal 7, obj.a + + obj.a = nil + obj.a ||= 2 + assert_equal 2, obj.a + + obj.a &&= 3 + assert_equal 3, obj.a + + a = [] + a[0] ||= 3 + assert_equal 3, a[0] + a[0] &&= 7 + assert_equal 7, a[0] + a[0] ||= 3 + assert_equal 7, a[0] + + a = [0, 1, nil, 3, 4] + a[*[2]] ||= :foo + assert_equal [0, 1, :foo, 3, 4], a + a[*[1,3]] &&= [:bar] + assert_equal [0, :bar, 4], a + end + + def test_opassign_and_or + a = 1 + a ||= 2 + assert_equal 1, a + a = nil + a ||= 2 + assert_equal 2, a + a = 1 + a &&= 3 + assert_equal 3, a + a = nil + a &&= 4 + assert_nil a + + h = {} + h[0] ||= 1 + assert_equal 1, h[0] + h = {} + h[0] &&= 1 + assert_nil h[0] + h = {0 => 7} + h[0] ||= 1 + assert_equal 7, h[0] + h = {0 => 7} + h[0] &&= 1 + assert_equal 1, h[0] + end + + def test_backref + /a(b)(c)d/ =~ 'xyzabcdefgabcdefg' + assert_equal 'b', $1 + assert_equal 'c', $2 + assert_nil $3 + assert_instance_of MatchData, $~ + assert_equal 'abcd', $& + assert_equal 'xyz', $` + assert_equal 'efgabcdefg', $' + assert_equal 'c', $+ + + /(?!)/ =~ 'xyzabcdefgabcdefg' + assert_nil $1 + assert_nil $2 + assert_nil $3 + assert_nil $~ + assert_nil $& + assert_nil $` + assert_nil $' + assert_nil $+ + end + + def test_fact + assert_equal 306057512216440636035370461297268629388588804173576999416776741259476533176716867465515291422477573349939147888701726368864263907759003154226842927906974559841225476930271954604008012215776252176854255965356903506788725264321896264299365204576448830388909753943489625436053225980776521270822437639449120128678675368305712293681943649956460498166450227716500185176546469340112226034729724066333258583506870150169794168850353752137554910289126407157154830282284937952636580145235233156936482233436799254594095276820608062232812387383880817049600000000000000000000000000000000000000000000000000000000000000000000000000, fact(300) + end + + def fact(n) + if n > 1 + n * fact(n - 1) + else + 1 + end + end + + def test_mul + assert_equal 0, 2 * 0 + assert_equal 0, 0 * 2 + assert_equal 4, 2 * 2 + end + + class MyNum + def /(a) + a * 100 + end + end + + def test_div + assert_equal 1, 3 / 2 + assert_equal 1.5, 3.0 / 2.0 + assert_equal 300, MyNum.new / 3 + end + + class MyArr + def length + 'string' + end + end + + def test_length + assert_equal 0, [].length + assert_equal 1, [1].length + assert_equal 2, [1,2].length + assert_equal 0, {}.length + assert_equal 1, {1=>1}.length + assert_equal 2, {1=>1, 2=>2}.length + assert_equal 'string', MyArr.new.length + end + + class MyNum2 + def %(a) + a * 100 + end + end + + def test_mod + assert_equal 2, 5 % 3 + assert_equal 1.0, 3.0 % 2.0 + assert_equal 300, MyNum2.new % 3 + end + + class MyObj + def [](*args) + args + end + + def []=(*args) + args + end + end + + def test_aref + a = [0,1] + assert_equal 0, a[0] + assert_equal 1, a[1] + assert_nil a[2] + h = {0=>0, 1=>1} + obj = MyObj.new + assert_equal 0, h[0] + assert_equal 1, h[1] + assert_nil h[2] + assert_equal [0], obj[0] + assert_equal [0,1], obj[0,1] + assert_equal [0,1,2], obj[0,1,2] + end + + def test_aset + obj = MyObj.new + assert_equal 7, (obj[0] = 7) + assert_equal 7, (obj[0,1] = 7) + assert_equal 7, (obj[0,1,2] = 7) + end + + class MyObj2 + def attr=(*args) + args + end + end + + def test_attr_setter + obj = MyObj2.new + assert_equal 1, (obj.attr = 1) + end + + def test_list_expand + a = [] + assert_equal [0], [0, *a] + a = [1] + assert_equal [0,1], [0, *a] + a = [1,2] + assert_equal [0,1,2], [0, *a] + a = [1,2,3] + assert_equal [0,1,2,3], [0, *a] + #a = [1,2,3] + #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 new file mode 100644 index 0000000000..959ea87f25 --- /dev/null +++ b/test/ruby/test_proc.rb @@ -0,0 +1,2219 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestProc < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $VERBOSE = @verbose + end + + def test_proc + p1 = proc{|i| i} + assert_equal(2, p1.call(2)) + assert_equal(3, p1.call(3)) + + p1 = proc{|i| i*2} + assert_equal(4, p1.call(2)) + assert_equal(6, p1.call(3)) + + p2 = nil + x=0 + + proc{ + iii=5 # nested local variable + p1 = proc{|i| + iii = i + } + p2 = proc { + x = iii # nested variables shared by procs + } + # scope of nested variables + assert(defined?(iii)) + }.call + assert(!defined?(iii)) # out of scope + + loop{iii=iii=5; assert(eval("defined? iii")); break} + loop { + iii=iii = 10 + def self.dyna_var_check + loop { + assert(!defined?(iii)) + break + } + end + dyna_var_check + break + } + p1.call(5) + p2.call + assert_equal(5, x) + end + + def assert_arity(n, &block) + meta = class << self; self; end + b = Proc.new(&block) + meta.class_eval { + remove_method(:foo_arity) if method_defined?(:foo_arity) + define_method(:foo_arity, b) + } + assert_equal(n, method(:foo_arity).arity) + end + + def test_arity + 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) {||} + assert_arity(1) {|x|} + assert_arity(2) {|x, y|} + assert_arity(-2) {|x, *y|} + 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) + lambda { x } + end + + def m_nest0(&block) + block + end + + def m_nest(&block) + [m_nest0(&block), m_nest0(&block)] + end + + def test_eq + a = m(1) + b = m(2) + assert_not_equal(a, b, "[ruby-dev:22592]") + assert_not_equal(a.call, b.call, "[ruby-dev:22592]") + + assert_not_equal(proc {||}, proc {|x,y|}, "[ruby-dev:22599]") + + a = lambda {|x| lambda {} }.call(1) + b = lambda {} + assert_not_equal(a, b, "[ruby-dev:22601]") + + assert_equal(*m_nest{}, "[ruby-core:84583] Feature #14627") + end + + def test_hash_equal + # iseq backed proc + p1 = proc {} + p2 = p1.dup + + assert_equal p1.hash, p2.hash + + # ifunc backed proc + p1 = {}.to_proc + p2 = p1.dup + + assert_equal p1.hash, p2.hash + + # symbol backed proc + p1 = :hello.to_proc + p2 = :hello.to_proc + + assert_equal p1.hash, p2.hash + end + + def test_hash_uniqueness + def self.capture(&block) + block + end + + procs = Array.new(1000){capture{:foo }} + assert_operator(procs.map(&:hash).uniq.size, :>=, 500) + + # iseq backed proc + unique_hashes = 1000.times.map { proc {}.hash }.uniq + assert_operator(unique_hashes.size, :>=, 500) + + # ifunc backed proc + unique_hashes = 1000.times.map { {}.to_proc.hash }.uniq + assert_operator(unique_hashes.size, :>=, 500) + + # symbol backed proc + unique_hashes = 1000.times.map { |i| :"test#{i}".to_proc.hash }.uniq + assert_operator(unique_hashes.size, :>=, 500) + end + + def test_hash_does_not_change_after_compaction + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + # [Bug #20853] + [ + "proc {}", # iseq backed proc + "{}.to_proc", # ifunc backed proc + ":hello.to_proc", # symbol backed proc + ].each do |proc| + assert_separately([], <<~RUBY) + p1 = #{proc} + hash = p1.hash + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(hash, p1.hash, "proc is `#{proc}`") + RUBY + end + end + + def test_block_par + assert_equal(10, Proc.new{|&b| b.call(10)}.call {|x| x}) + assert_equal(12, Proc.new{|a,&b| b.call(a)}.call(12) {|x| x}) + end + + def m2 + "OK" + end + + def block + 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 + 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 + verbose_bak, $VERBOSE = $VERBOSE, nil + m = method(:m_block_given?) + assert(!m.call, "without block") + assert(m.call {}, "with block") + assert(!m.call, "without block second") + ensure + $VERBOSE = verbose_bak + end + + def test_block_given_method_to_proc + verbose_bak, $VERBOSE = $VERBOSE, nil + 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") + ensure + $VERBOSE = verbose_bak + 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_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 = assert_warning(/given block not used/) {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, 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 helper_test_warn_lambda_with_passed_block &b + lambda(&b) + end + + def test_lambda_warning_pass_proc + assert_raise(ArgumentError) do + b = proc{} + lambda(&b) + end + end + + def test_lambda_warning_pass_block + assert_raise(ArgumentError) do + helper_test_warn_lambda_with_passed_block{} + end + end + + def test_curry_ski_fib + s = proc {|f, g, x| f[x][g[x]] }.curry + k = proc {|x, y| x }.curry + i = proc {|x| x }.curry + + fib = [] + inc = proc {|x| fib[-1] += 1; x }.curry + ret = proc {|x| throw :end if fib.size > 10; fib << 0; x }.curry + + catch(:end) do + s[ + s[s[i][i]][k[i]] + ][ + k[inc] + ][ + s[ + s[ + k[s] + ][ + s[k[s[k[s]]] + ][ + s[s[k[s]][s[k[s[k[ret]]]][s[k[s[i]]][k]]]][k]] + ] + ][ + k[s[k[s]][k]] + ] + ] + end + + assert_equal(fib, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]) + end + + def test_curry_passed_block + a = lambda {|x, y, &b| b } + b = a.curry[1] + + 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 + # iseq backed proc + b = proc {|x| x + "bar" } + class << b; attr_accessor :foo; end + + bd = b.dup + assert_equal("foobar", bd.call("foo")) + assert_raise(NoMethodError) { bd.foo = :foo } + assert_raise(NoMethodError) { bd.foo } + + bc = b.clone + assert_equal("foobar", bc.call("foo")) + bc.foo = :foo + assert_equal(:foo, bc.foo) + + # ifunc backed proc + b = {foo: "bar"}.to_proc + + bd = b.dup + assert_equal("bar", bd.call(:foo)) + + bc = b.clone + assert_equal("bar", bc.call(:foo)) + + # symbol backed proc + b = :to_s.to_proc + + bd = b.dup + assert_equal("testing", bd.call(:testing)) + + bc = b.clone + assert_equal("testing", bc.call(:testing)) + end + + def test_dup_subclass + c1 = Class.new(Proc) + assert_equal c1, c1.new{}.dup.class, '[Bug #17545]' + c1 = Class.new(Proc) {def initialize_dup(*) throw :initialize_dup; end} + assert_throw(:initialize_dup) {c1.new{}.dup} + end + + def test_dup_ifunc_proc_bug_20950 + assert_normal_exit(<<~RUBY, "[Bug #20950]") + p = { a: 1 }.to_proc + 100.times do + p = p.dup + GC.start + p.call + rescue ArgumentError + end + RUBY + end + + def test_clone_subclass + c1 = Class.new(Proc) + assert_equal c1, c1.new{}.clone.class, '[Bug #17545]' + c1 = Class.new(Proc) {def initialize_clone(*) throw :initialize_clone; end} + assert_throw(:initialize_clone) {c1.new{}.clone} + end + + def test_binding + b = proc {|x, y, z| proc {}.binding }.call(1, 2, 3) + class << b; attr_accessor :foo; end + + bd = b.dup + assert_equal([1, 2, 3], bd.eval("[x, y, z]")) + assert_raise(NoMethodError) { bd.foo = :foo } + assert_raise(NoMethodError) { bd.foo } + + bc = b.clone + assert_equal([1, 2, 3], bc.eval("[x, y, z]")) + bc.foo = :foo + assert_equal(:foo, bc.foo) + + b = nil + 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[0], lineno, 'Bug #2427') + end + + def test_binding_error_unless_ruby_frame + define_singleton_method :binding_from_c!, method(:binding).to_proc >> ->(bndg) {bndg} + assert_raise(RuntimeError) { binding_from_c! } + end + + def test_proc_lambda + assert_raise(ArgumentError) { proc } + assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} } + + o = Object.new + def o.foo + b = nil + 1.times { b = lambda } + b + end + assert_raise(ArgumentError) do + assert_deprecated_warning {o.foo { :foo }}.call + end + + def o.bar(&b) + b = nil + 1.times { b = lambda } + b + end + assert_raise(ArgumentError) do + assert_deprecated_warning {o.bar { :foo }}.call + end + end + + def test_arity2 + assert_equal(0, method(:proc).to_proc.arity) + assert_equal(-1, proc {}.curry.arity) + + c = Class.new + c.class_eval { attr_accessor :foo } + assert_equal(1, c.new.method(:foo=).to_proc.arity) + end + + def test_proc_location + t = Thread.new { sleep } + assert_raise(ThreadError) { t.instance_eval { initialize { } } } + t.kill + t.join + end + + def test_to_proc + b = proc { :foo } + assert_equal(:foo, b.to_proc.call) + end + + def test_localjump_error + o = o = Object.new + def foo; yield; end + exc = foo rescue $! + assert_nil(exc.exit_value) + assert_equal(:noreason, exc.reason) + end + + def test_curry_binding + assert_raise(ArgumentError) { proc {}.curry.binding } + end + + def test_proc_args_plain + pr = proc {|a,b,c,d,e| + [a,b,c,d,e] + } + assert_equal [nil,nil,nil,nil,nil], pr.call() + assert_equal [1,nil,nil,nil,nil], pr.call(1) + assert_equal [1,2,nil,nil,nil], pr.call(1,2) + assert_equal [1,2,3,nil,nil], pr.call(1,2,3) + assert_equal [1,2,3,4,nil], 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 [nil,nil,nil,nil,nil], pr.call([]) + assert_equal [1,nil,nil,nil,nil], pr.call([1]) + assert_equal [1,2,nil,nil,nil], pr.call([1,2]) + assert_equal [1,2,3,nil,nil], pr.call([1,2,3]) + assert_equal [1,2,3,4,nil], 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]) + + r = proc{|a| a}.call([1,2,3]) + assert_equal [1,2,3], r + + r = proc{|a,| a}.call([1,2,3]) + assert_equal 1, r + + r = proc{|a,| a}.call([]) + assert_equal nil, r + end + + + def test_proc_args_rest + pr = proc {|a,b,c,*d| + [a,b,c,d] + } + assert_equal [nil,nil,nil,[]], pr.call() + assert_equal [1,nil,nil,[]], pr.call(1) + assert_equal [1,2,nil,[]], 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 [nil,nil,nil,[]], pr.call([]) + assert_equal [1,nil,nil,[]], pr.call([1]) + assert_equal [1,2,nil,[]], 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]) + + r = proc{|*a| a}.call([1,2,3]) + assert_equal [[1,2,3]], r + end + + def test_proc_args_pos_rest_post + pr = proc {|a,b,*c,d,e| + [a,b,c,d,e] + } + assert_equal [nil, nil, [], nil, nil], pr.call() + assert_equal [1, nil, [], nil, nil], pr.call(1) + assert_equal [1, 2, [], nil, nil], pr.call(1,2) + assert_equal [1, 2, [], 3, nil], 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, [], nil, nil], pr.call([]) + assert_equal [1, nil, [], nil, nil], pr.call([1]) + assert_equal [1, 2, [], nil, nil], pr.call([1,2]) + assert_equal [1, 2, [], 3, nil], 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_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] + } + assert_equal [nil, nil, :c], pr.call() + assert_equal [1, nil, :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 [nil, nil, :c], pr.call([]) + assert_equal [1, nil, :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 + 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] + } + assert_equal [nil, nil, :c, nil, nil], pr.call() + assert_equal [1, nil, :c, nil, nil], pr.call(1) + assert_equal [1, 2, :c, nil, nil], pr.call(1,2) + assert_equal [1, 2, :c, 3, nil], 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 [nil, nil, :c, nil, nil], pr.call([]) + assert_equal [1, nil, :c, nil, nil], pr.call([1]) + assert_equal [1, 2, :c, nil, nil], pr.call([1,2]) + assert_equal [1, 2, :c, 3, nil], 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_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] + } + assert_equal [nil, nil, :c, []], pr.call() + assert_equal [1, nil, :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 [nil, nil, :c, []], pr.call([]) + assert_equal [1, nil, :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_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] + } + assert_equal [nil, nil, :c, [], nil], pr.call() + assert_equal [1, nil, :c, [], nil], pr.call(1) + assert_equal [1, 2, :c, [], nil], 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 [nil, nil, :c, [], nil], pr.call([]) + assert_equal [1, nil, :c, [], nil], pr.call([1]) + assert_equal [1, 2, :c, [], nil], 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_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)] + } + 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, 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 }) + assert_equal [1, 2, Proc, :proc], (pr.call(1, 2, 3){ :proc }) + assert_equal [1, 2, 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, Proc, :x], (pr.call(1, 2, 3){|x| x}) + assert_equal [1, 2, Proc, :x], (pr.call(1, 2, 3, 4){|x| x}) + end + + def test_proc_args_pos_rest_block + pr = proc {|a,b,*c,&d| + [a, b, c, d.class, d&&d.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_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_single_kw_no_autosplat + pr = proc {|c, a: 1| [c, a] } + assert_equal [nil, 1], pr.call() + assert_equal [1, 1], pr.call(1) + assert_equal [[1], 1], pr.call([1]) + assert_equal [1, 1], pr.call(1,2) + assert_equal [[1, 2], 1], pr.call([1,2]) + + assert_equal [nil, 3], pr.call(a: 3) + assert_equal [1, 3], pr.call(1, a: 3) + assert_equal [[1], 3], pr.call([1], a: 3) + assert_equal [1, 3], pr.call(1,2, a: 3) + assert_equal [[1, 2], 3], pr.call([1,2], a: 3) + end + + def test_proc_args_single_kwsplat_no_autosplat + pr = proc {|c, **kw| [c, kw] } + assert_equal [nil, {}], pr.call() + assert_equal [1, {}], pr.call(1) + assert_equal [[1], {}], pr.call([1]) + assert_equal [1, {}], pr.call(1,2) + assert_equal [[1, 2], {}], pr.call([1,2]) + + assert_equal [nil, {a: 3}], pr.call(a: 3) + assert_equal [1, {a: 3}], pr.call(1, a: 3) + assert_equal [[1], {a: 3}], pr.call([1], a: 3) + assert_equal [1, {a: 3}], pr.call(1,2, a: 3) + assert_equal [[1, 2], {a: 3}], pr.call([1,2], a: 3) + end + + def test_proc_args_multiple_kw_autosplat + pr = proc {|c, b, a: 1| [c, b, a] } + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c=nil, b=nil, a: 1| [c, b, a] } + assert_equal [nil, nil, 1], pr.call([]) + assert_equal [1, nil, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c, b=nil, a: 1| [c, b, a] } + assert_equal [1, nil, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c=nil, b, a: 1| [c, b, a] } + assert_equal [nil, 1, 1], pr.call([1]) + assert_equal [1, 2, 1], pr.call([1,2]) + + pr = proc {|c, *b, a: 1| [c, b, a] } + assert_equal [1, [], 1], pr.call([1]) + assert_equal [1, [2], 1], pr.call([1,2]) + + pr = proc {|*c, b, a: 1| [c, b, a] } + assert_equal [[], 1, 1], pr.call([1]) + assert_equal [[1], 2, 1], pr.call([1,2]) + end + + def test_proc_args_multiple_kwsplat_autosplat + pr = proc {|c, b, **kw| [c, b, kw] } + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c=nil, b=nil, **kw| [c, b, kw] } + assert_equal [nil, nil, {}], pr.call([]) + assert_equal [1, nil, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c, b=nil, **kw| [c, b, kw] } + assert_equal [1, nil, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c=nil, b, **kw| [c, b, kw] } + assert_equal [nil, 1, {}], pr.call([1]) + assert_equal [1, 2, {}], pr.call([1,2]) + + pr = proc {|c, *b, **kw| [c, b, kw] } + assert_equal [1, [], {}], pr.call([1]) + assert_equal [1, [2], {}], pr.call([1,2]) + + pr = proc {|*c, b, **kw| [c, b, kw] } + assert_equal [[], 1, {}], pr.call([1]) + assert_equal [[1], 2, {}], pr.call([1,2]) + end + + def test_proc_args_only_rest + pr = proc {|*c| c } + assert_equal [], pr.call() + assert_equal [1], pr.call(1) + assert_equal [[1]], pr.call([1]) + assert_equal [1, 2], pr.call(1,2) + assert_equal [[1, 2]], pr.call([1,2]) + end + + def test_proc_args_rest_kw + pr = proc {|*c, a: 1| [c, a] } + assert_equal [[], 1], pr.call() + assert_equal [[1], 1], pr.call(1) + assert_equal [[[1]], 1], pr.call([1]) + assert_equal [[1, 2], 1], pr.call(1,2) + assert_equal [[[1, 2]], 1], pr.call([1,2]) + end + + def test_proc_args_rest_kwsplat + pr = proc {|*c, **kw| [c, kw] } + assert_equal [[], {}], pr.call() + assert_equal [[1], {}], pr.call(1) + assert_equal [[[1]], {}], pr.call([1]) + assert_equal [[1, 2], {}], pr.call(1,2) + assert_equal [[[1, 2]], {}], pr.call([1,2]) + end + + def test_proc_args_pos_rest_post_block + pr = proc {|a,b,*c,d,e,&f| + [a, b, c, d, e, f.class, f&&f.call(:x)] + } + assert_equal [nil, nil, [], nil, nil, NilClass, nil], pr.call() + assert_equal [1, nil, [], nil, nil, NilClass, nil], pr.call(1) + assert_equal [1, 2, [], nil, nil, NilClass, nil], pr.call(1,2) + assert_equal [1, 2, [], 3, nil, 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 [nil, nil, [], nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, nil, [], nil, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [1, 2, [], nil, nil, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, 2, [], 3, nil, 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 [nil, nil, [], nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [1, nil, [], nil, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [1, 2, [], nil, nil, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, 2, [], 3, nil, 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_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)] + } + assert_equal [nil, nil, :c, :d, NilClass, nil], pr.call() + assert_equal [1, nil, :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 [nil, nil, :c, :d, Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, nil, :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 [nil, nil, :c, :d, Proc, :x], (pr.call(){|x| x}) + assert_equal [1, nil, :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_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)] + } + assert_equal [nil, nil, :c, :d, nil, nil, NilClass, nil], pr.call() + assert_equal [1, nil, :c, :d, nil, nil, NilClass, nil], pr.call(1) + assert_equal [1, 2, :c, :d, nil, nil, NilClass, nil], pr.call(1,2) + assert_equal [1, 2, :c, :d, 3, nil, 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 [nil, nil, :c, :d, nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, nil, :c, :d, nil, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [1, 2, :c, :d, nil, nil, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, 2, :c, :d, 3, nil, 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 [nil, nil, :c, :d, nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [1, nil, :c, :d, nil, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [1, 2, :c, :d, nil, nil, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, 2, :c, :d, 3, nil, 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_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)] + } + assert_equal [nil, nil, :c, :d, [], NilClass, nil], pr.call() + assert_equal [1, nil, :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 [nil, nil, :c, :d, [], Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, nil, :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 [nil, nil, :c, :d, [], Proc, :x], (pr.call(){|x| x}) + assert_equal [1, nil, :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_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)] + } + assert_equal [nil, nil, :c, :d, [], nil, nil, NilClass, nil], pr.call() + assert_equal [1, nil, :c, :d, [], nil, nil, NilClass, nil], pr.call(1) + assert_equal [1, 2, :c, :d, [], nil, nil, NilClass, nil], pr.call(1,2) + assert_equal [1, 2, :c, :d, [], 3, nil, 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 [nil, nil, :c, :d, [], nil, nil, Proc, :proc], (pr.call(){ :proc }) + assert_equal [1, nil, :c, :d, [], nil, nil, Proc, :proc], (pr.call(1){ :proc }) + assert_equal [1, 2, :c, :d, [], nil, nil, Proc, :proc], (pr.call(1, 2){ :proc }) + assert_equal [1, 2, :c, :d, [], 3, nil, 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 [nil, nil, :c, :d, [], nil, nil, Proc, :x], (pr.call(){|x| x}) + assert_equal [1, nil, :c, :d, [], nil, nil, Proc, :x], (pr.call(1){|x| x}) + assert_equal [1, 2, :c, :d, [], nil, nil, Proc, :x], (pr.call(1, 2){|x| x}) + assert_equal [1, 2, :c, :d, [], 3, nil, 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_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) + assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]") + end + + def test_proc_autosplat + def self.a(arg, kw) + yield arg + yield arg, **kw + yield arg, kw + end + + arr = [] + a([1,2,3], {}) do |arg1, arg2=0| + arr << [arg1, arg2] + end + assert_equal([[1, 2], [[1, 2, 3], 0], [[1, 2, 3], {}]], arr) + + arr = [] + a([1,2,3], a: 1) do |arg1, arg2=0| + arr << [arg1, arg2] + end + assert_equal([[1, 2], [[1, 2, 3], {a: 1}], [[1, 2, 3], {a: 1}]], arr) + end + + def test_proc_single_arg_with_keywords_accepted_and_yielded + def self.a + yield [], **{a: 1} + end + res = a do |arg, **opts| + [arg, opts] + end + assert_equal([[], {a: 1}], res) + end + + def test_parameters + assert_equal([], proc {}.parameters) + assert_equal([], proc {||}.parameters) + assert_equal([[:opt, :a]], proc {|a|}.parameters) + assert_equal([[:opt, :a], [:opt, :b]], proc {|a, b|}.parameters) + assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters) + assert_equal([[:opt, :a], [:opt, :b]], proc {|a, b=:b|}.parameters) + assert_equal([[:rest, :a]], proc {|*a|}.parameters) + assert_equal([[:opt, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters) + 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], [:block, :b]], proc {|(a), &b|a}.parameters) + assert_equal([[:opt], [:rest, :_], [:opt]], proc {|(a_), *_, (b_)|}.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) + + assert_equal([[:opt]], proc { it }.parameters) + end + + def test_proc_autosplat_with_multiple_args_with_ruby2_keywords_splat_bug_19759 + def self.yielder_ab(splat) + yield([:a, :b], *splat) + end + + res = yielder_ab([[:aa, :bb], Hash.ruby2_keywords_hash({k: :k})]) do |a, b, k:| + [a, b, k] + end + assert_equal([[:a, :b], [:aa, :bb], :k], res) + + def self.yielder(splat) + yield(*splat) + end + res = yielder([ [:a, :b] ]){|a, b, **| [a, b]} + assert_equal([:a, :b], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **| [a, b]} + assert_equal([[:a, :b], nil], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({c: 1}) ]){|a, b, **| [a, b]} + assert_equal([[:a, :b], nil], res) + + res = yielder([ [:a, :b], Hash.ruby2_keywords_hash({}) ]){|a, b, **nil| [a, b]} + assert_equal([[:a, :b], nil], res) + end + + def test_parameters_lambda + assert_equal([], proc {}.parameters(lambda: true)) + assert_equal([], proc {||}.parameters(lambda: true)) + assert_equal([[:req, :a]], proc {|a|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:req, :b]], proc {|a, b|}.parameters(lambda: true)) + assert_equal([[:opt, :a], [:block, :b]], proc {|a=:a, &b|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:opt, :b]], proc {|a, b=:b|}.parameters(lambda: true)) + assert_equal([[:rest, :a]], proc {|*a|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], proc {|a, *b, &c|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], proc {|a, *b, c|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], proc {|a, *b, c, &d|}.parameters(lambda: true)) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], proc {|a, b=:b, *c, d, &e|}.parameters(lambda: true)) + assert_equal([[:req], [:block, :b]], proc {|(a), &b|a}.parameters(lambda: true)) + assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], proc {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: true)) + + pr = eval("proc{|"+"(_),"*30+"|}") + assert_empty(pr.parameters(lambda: true).map{|_,n|n}.compact) + + assert_equal([[:opt, :a]], lambda {|a|}.parameters(lambda: false)) + assert_equal([[:opt, :a], [:opt, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:opt, :f], [:opt, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters(lambda: false)) + + assert_equal([[:req]], proc { it }.parameters(lambda: true)) + assert_equal([[:opt]], lambda { it }.parameters(lambda: false)) + end + + def pm0() end + def pm1(a) end + def pm2(a, b) end + def pmo1(a = :a, &b) end + def pmo2(a, b = :b) end + def pmo3(*a) end + def pmo4(a, *b, &c) end + 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) 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 + assert_equal([], method(:pm0).to_proc.parameters) + assert_equal([[:req, :a]], method(:pm1).to_proc.parameters) + assert_equal([[:req, :a], [:req, :b]], method(:pm2).to_proc.parameters) + assert_equal([[:opt, :a], [:block, :b]], method(:pmo1).to_proc.parameters) + assert_equal([[:req, :a], [:opt, :b]], method(:pmo2).to_proc.parameters) + assert_equal([[:rest, :a]], method(:pmo3).to_proc.parameters) + assert_equal([[:req, :a], [:rest, :b], [:block, :c]], method(:pmo4).to_proc.parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c]], method(:pmo5).to_proc.parameters) + assert_equal([[:req, :a], [:rest, :b], [:req, :c], [:block, :d]], method(:pmo6).to_proc.parameters) + assert_equal([[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:block, :e]], method(:pmo7).to_proc.parameters) + assert_equal([[:req], [:block, :b]], method(:pma1).to_proc.parameters) + assert_equal([[:keyrest, :**]], method(:pmk1).to_proc.parameters) + assert_equal([[:keyrest, :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 + + def test_to_s + assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+>$/, proc {}.to_s) + assert_match(/^#<Proc:0x\h+ #{ Regexp.quote(__FILE__) }:\d+ \(lambda\)>$/, lambda {}.to_s) + assert_match(/^#<Proc:0x\h+ \(lambda\)>$/, method(:p).to_proc.to_s) + name = "Proc\u{1f37b}" + assert_include(EnvUtil.labeled_class(name, Proc).new {}.to_s, name) + end + + @@line_of_source_location_test = [__LINE__ + 1, 2, __LINE__ + 3, 5] + def source_location_test a=1, + b=2 + end + + def test_source_location + file, *loc = method(:source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') + + file, *loc = self.class.instance_method(:source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_source_location_test, loc, 'Bug #2427') + end + + @@line_of_attr_reader_source_location_test = __LINE__ + 3 + @@line_of_attr_writer_source_location_test = __LINE__ + 3 + @@line_of_attr_accessor_source_location_test = __LINE__ + 3 + attr_reader :attr_reader_source_location_test + attr_writer :attr_writer_source_location_test + attr_accessor :attr_accessor_source_location_test + + def test_attr_source_location + file, lineno = method(:attr_reader_source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_attr_reader_source_location_test, lineno) + + file, lineno = method(:attr_writer_source_location_test=).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_attr_writer_source_location_test, lineno) + + file, lineno = method(:attr_accessor_source_location_test).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(@@line_of_attr_accessor_source_location_test, lineno) + + file, lineno = method(:attr_accessor_source_location_test=).source_location + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + 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_loc = [__LINE__ + 3, 49, __LINE__ + 4, 49] + file, *loc = block_source_location_test(1, + 2, + 3) do + end + assert_match(/^#{ Regexp.quote(__FILE__) }$/, file) + assert_equal(exp_loc, loc) + end + + def test_splat_without_respond_to + 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 = x; 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)) + + assert_raise_with_message(NameError, /local variable \Wdefault\W/) { + binding.local_variable_get(:default) + } + 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_numparam_is_not_local_variables + "foo".tap do + _9 and flunk + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + "bar".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + + "foo".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + "bar".tap do + _9 and flunk + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:_9) } + assert_raise(NameError) { binding.local_variable_set(:_9, 1) } + assert_raise(NameError) { binding.local_variable_defined?(:_9) } + end + end + + def test_implicit_parameters_for_numparams + x = x = 1 + assert_raise(NameError) { binding.implicit_parameter_get(:x) } + assert_raise(NameError) { binding.implicit_parameter_defined?(:x) } + + "foo".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + assert_equal(false, binding.implicit_parameter_defined?(:it)) + end + end + + def test_it_is_not_local_variable + "foo".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + + "foo".tap do + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + "bar".tap do + it + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + end + + def test_implicit_parameters_for_it + "foo".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + + "foo".tap do + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + end + end + + def test_implicit_parameters_for_it_complex + "foo".tap do + it = it = "bar" + + assert_equal([], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([], binding.local_variables) + assert_raise(NameError) { binding.local_variable_get(:it) } + assert_equal(false, binding.local_variable_defined?(:it)) + end + + "foo".tap do + it or flunk + it = it = "bar" + + assert_equal([:it], binding.implicit_parameters) + assert_equal("foo", binding.implicit_parameter_get(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:it)) + + assert_equal([:it], binding.local_variables) + assert_equal("bar", binding.local_variable_get(:it)) + assert_equal(true, binding.local_variable_defined?(:it)) + end + end + + def test_implicit_parameters_for_it_and_numparams + "foo".tap do + it or flunk + "bar".tap do + _5 and flunk + assert_equal([:_1, :_2, :_3, :_4, :_5], binding.implicit_parameters) + assert_raise(NameError) { binding.implicit_parameter_get(:it) } + assert_equal("bar", binding.implicit_parameter_get(:_1)) + assert_equal(nil, binding.implicit_parameter_get(:_5)) + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(false, binding.implicit_parameter_defined?(:it)) + assert_equal(true, binding.implicit_parameter_defined?(:_1)) + assert_equal(true, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + + "foo".tap do + _5 and flunk + "bar".tap do + it or flunk + assert_equal([:it], binding.implicit_parameters) + assert_equal("bar", binding.implicit_parameter_get(:it)) + assert_raise(NameError) { binding.implicit_parameter_get(:_1) } + assert_raise(NameError) { binding.implicit_parameter_get(:_5) } + assert_raise(NameError) { binding.implicit_parameter_get(:_6) } + assert_equal(true, binding.implicit_parameter_defined?(:it)) + assert_equal(false, binding.implicit_parameter_defined?(:_1)) + assert_equal(false, binding.implicit_parameter_defined?(:_5)) + assert_equal(false, binding.implicit_parameter_defined?(:_6)) + end + end + end + + def test_implicit_parameter_invalid_name + message_pattern = /is not an implicit parameter/ + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get(:foo) } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_defined?("wrong_implicit_parameter_name_#{rand(10000)}") } + assert_raise_with_message(NameError, message_pattern) { binding.implicit_parameter_get("wrong_implicit_parameter_name_#{rand(10000)}") } + 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 test_compose + f = proc {|x| x * 2} + g = proc {|x| x + 1} + + assert_equal(6, (f << g).call(2)) + assert_equal(6, (g >> f).call(2)) + end + + def test_compose_with_multiple_args + f = proc {|x| x * 2} + g = proc {|x, y| x + y} + + assert_equal(6, (f << g).call(1, 2)) + assert_equal(6, (g >> f).call(1, 2)) + end + + def test_compose_with_block + f = proc {|x| x * 2} + g = proc {|&blk| blk.call(1) } + + assert_equal(8, (f << g).call { |x| x + 3 }) + assert_equal(8, (g >> f).call { |x| x + 3 }) + end + + def test_compose_with_lambda + f = lambda {|x| x * 2} + g = lambda {|x| x} + not_lambda = proc {|x| x} + + assert_predicate((f << g), :lambda?) + assert_predicate((g >> f), :lambda?) + assert_predicate((not_lambda << f), :lambda?) + assert_not_predicate((f << not_lambda), :lambda?) + assert_not_predicate((not_lambda >> f), :lambda?) + end + + def test_compose_with_method + f = proc {|x| x * 2} + c = Class.new { + def g(x) x + 1 end + } + g = c.new.method(:g) + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + assert_predicate((f << g), :lambda?) + end + + def test_compose_with_callable + f = proc {|x| x * 2} + c = Class.new { + def call(x) x + 1 end + } + g = c.new + + assert_equal(6, (f << g).call(2)) + assert_equal(5, (f >> g).call(2)) + assert_predicate((f << g), :lambda?) + end + + def test_compose_with_noncallable + f = proc {|x| x * 2} + + assert_raise(TypeError) { + (f << 5).call(2) + } + assert_raise(TypeError) { + (f >> 5).call(2) + } + end + + def test_orphan_return + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { return 42 } end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1 { return 42 }.call end }.m2) + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { return 42 } end }.m2.call } + end + + def test_orphan_break + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { break 42 } end }.m2 ) + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { break 42 }.call end }.m2 } + assert_raise(LocalJumpError) { Module.new { extend self + def m1(&b) b end; def m2(); m1 { break 42 } end }.m2.call } + end + + def test_not_orphan_next + assert_equal(42, Module.new { extend self + def m1(&b) b.call end; def m2(); m1 { next 42 } end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1 { next 42 }.call end }.m2) + assert_equal(42, Module.new { extend self + def m1(&b) b end; def m2(); m1 { next 42 } end }.m2.call) + end + + def test_isolate + assert_raise_with_message ArgumentError, /\(a\)/ do + a = :a + Proc.new{p a}.isolate + end + + assert_raise_with_message ArgumentError, /\(a\)/ do + a = :a + 1.times{ + Proc.new{p a}.isolate + } + end + + assert_raise_with_message ArgumentError, /yield/ do + Proc.new{yield}.isolate + end + + + name = "\u{2603 26a1}" + assert_raise_with_message(ArgumentError, /\(#{name}\)/) do + eval("#{name} = :#{name}; Proc.new {p #{name}}").isolate + end + + # binding + + :a.tap{|a| + :b.tap{|b| + Proc.new{ + :c.tap{|c| + assert_equal :c, eval('c') + + assert_raise_with_message SyntaxError, /\`a\'/ do + eval('p a') + end + + assert_raise_with_message SyntaxError, /\`b\'/ do + eval('p b') + end + + assert_raise_with_message SyntaxError, /can not yield from isolated Proc/ do + eval('p yield') + end + + assert_equal :c, binding.local_variable_get(:c) + + assert_raise_with_message NameError, /local variable \`a\' is not defined/ do + binding.local_variable_get(:a) + end + + assert_equal [:c], local_variables + assert_equal [:c], binding.local_variables + } + }.isolate.call + } + } + end if proc{}.respond_to? :isolate +end + +class TestProcKeywords < Test::Unit::TestCase + def test_compose_keywords + f = ->(**kw) { kw.merge(:a=>1) } + g = ->(kw) { kw.merge(:a=>2) } + + assert_equal(2, (f >> g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (f >> g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } + assert_equal(2, (g << f).call(a: 3)[:a]) + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } + assert_equal(2, (f >> g).call(**{})[:a]) + end + + def test_compose_keywords_method + f = ->(**kw) { kw.merge(:a=>1) }.method(:call) + g = ->(kw) { kw.merge(:a=>2) }.method(:call) + + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (f >> g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } + assert_equal(2, (g << f).call(a: 3)[:a]) + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } + assert_equal(2, (f >> g).call(**{})[:a]) + end + + def test_compose_keywords_non_proc + f = ->(**kw) { kw.merge(:a=>1) } + g = Object.new + def g.call(kw) kw.merge(:a=>2) end + def g.to_proc; method(:call).to_proc; end + def g.<<(f) to_proc << f end + def g.>>(f) to_proc >> f end + + assert_raise(ArgumentError) { (f << g).call(a: 3)[:a] } + assert_equal(2, (f >> g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } + assert_equal(2, (g << f).call(a: 3)[:a]) + assert_raise(ArgumentError) { (g >> f).call(a: 3)[:a] } + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f << g).call(**{})[:a] } + assert_equal(2, (f >> g).call(**{})[:a]) + + f = ->(kw) { kw.merge(:a=>1) } + g = Object.new + def g.call(**kw) kw.merge(:a=>2) end + def g.to_proc; method(:call).to_proc; end + def g.<<(f) to_proc << f end + def g.>>(f) to_proc >> f end + + assert_equal(1, (f << g).call(a: 3)[:a]) + assert_raise(ArgumentError) { (f >> g).call(a: 3)[:a] } + assert_raise(ArgumentError) { (f << g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (f >> g).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g << f).call(a: 3)[:a] } + assert_equal(1, (g >> f).call(a: 3)[:a]) + assert_raise(ArgumentError) { (g << f).call({a: 3})[:a] } + assert_raise(ArgumentError) { (g >> f).call({a: 3})[:a] } + assert_equal(1, (f << g).call(**{})[:a]) + assert_raise(ArgumentError) { (f >> g).call(**{})[:a] } + end +end diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb new file mode 100644 index 0000000000..b3a88b664c --- /dev/null +++ b/test/ruby/test_process.rb @@ -0,0 +1,2864 @@ +# coding: utf-8 +# frozen_string_literal: false +require 'test/unit' +require 'tempfile' +require 'timeout' +require 'rbconfig' + +class TestProcess < Test::Unit::TestCase + RUBY = EnvUtil.rubybin + + def setup + Process.waitall + end + + def teardown + Process.waitall + end + + def windows? + self.class.windows? + end + def self.windows? + return /mswin|mingw|bccwin/ =~ RUBY_PLATFORM + end + + def with_tmpchdir + Dir.mktmpdir {|d| + d = File.realpath(d) + Dir.chdir(d) { + yield d + } + } + end + + def run_in_child(str) # should be called in a temporary directory + File.write("test-script", str) + Process.wait spawn(RUBY, "test-script") + $? + end + + def test_rlimit_availability + begin + Process.getrlimit(nil) + rescue NotImplementedError + assert_raise(NotImplementedError) { Process.setrlimit } + rescue TypeError + assert_raise(ArgumentError) { Process.setrlimit } + end + end + + def rlimit_exist? + Process.getrlimit(nil) + rescue NotImplementedError + return false + rescue TypeError + return true + end + + def test_rlimit_nofile + return unless rlimit_exist? + omit "LSAN needs to open proc file" if Test::Sanitizers.lsan_enabled? + + with_tmpchdir { + File.write '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, limit) + rescue Errno::EINVAL + result = 0 + end + if result == 1 + begin + IO.pipe + rescue Errno::EMFILE + result = 0 + end + end + exit result + End + pid = spawn RUBY, "s" + Process.wait pid + assert_equal(0, $?.to_i, "#{$?}") + } + end + + def test_rlimit_name + return unless rlimit_exist? + [ + :AS, "AS", + :CORE, "CORE", + :CPU, "CPU", + :DATA, "DATA", + :FSIZE, "FSIZE", + :MEMLOCK, "MEMLOCK", + :MSGQUEUE, "MSGQUEUE", + :NICE, "NICE", + :NOFILE, "NOFILE", + :NPROC, "NPROC", + :RSS, "RSS", + :RTPRIO, "RTPRIO", + :RTTIME, "RTTIME", + :SBSIZE, "SBSIZE", + :SIGPENDING, "SIGPENDING", + :STACK, "STACK", + ].each {|name| + if Process.const_defined? "RLIMIT_#{name}" + assert_nothing_raised { Process.getrlimit(name) } + else + assert_raise(ArgumentError) { Process.getrlimit(name) } + end + } + assert_raise(ArgumentError) { Process.getrlimit(:FOO) } + assert_raise(ArgumentError) { Process.getrlimit("FOO") } + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.getrlimit("\u{30eb 30d3 30fc}") } + end + end + + def test_rlimit_value + return unless rlimit_exist? + assert_raise(ArgumentError) { Process.setrlimit(:FOO, 0) } + assert_raise(ArgumentError) { Process.setrlimit(:CORE, :FOO) } + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.setrlimit("\u{30eb 30d3 30fc}", 0) } + end + 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) + Process.setrlimit(:NOFILE, [max-10, cur].min) + begin + Process.setrlimit(:NOFILE, :INFINITY) + rescue Errno::EPERM + exit false + end + End + assert_not_predicate(s, :success?) + s = run_in_child(<<-'End') + cur, max = Process.getrlimit(:NOFILE) + Process.setrlimit(:NOFILE, [max-10, cur].min) + begin + Process.setrlimit(:NOFILE, "INFINITY") + rescue Errno::EPERM + exit false + end + End + assert_not_predicate(s, :success?) + end + end + + TRUECOMMAND = [RUBY, '-e', ''] + + def test_execopts_opts + assert_nothing_raised { + Process.wait Process.spawn(*TRUECOMMAND, {}) + } + assert_raise(ArgumentError) { + Process.wait Process.spawn(*TRUECOMMAND, :foo => 100) + } + assert_raise(ArgumentError) { + Process.wait Process.spawn(*TRUECOMMAND, Process => 100) + } + end + + def test_execopts_pgroup + omit "system(:pgroup) is not supported" if windows? + assert_nothing_raised { system(*TRUECOMMAND, :pgroup=>false) } + + io = IO.popen([RUBY, "-e", "print Process.getpgrp"]) + assert_equal(Process.getpgrp.to_s, io.read) + io.close + + io = IO.popen([RUBY, "-e", "print Process.getpgrp", :pgroup=>true]) + assert_equal(io.pid.to_s, io.read) + io.close + + assert_raise(ArgumentError) { system(*TRUECOMMAND, :pgroup=>-1) } + 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]) + assert_equal(io1.pid.to_s, io1.read) + assert_equal(io1.pid.to_s, io2.read) + Process.wait io1.pid + Process.wait io2.pid + io1.close + io2.close + end + + def test_execopts_rlimit + return unless rlimit_exist? + assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_foo=>0) } + assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_NOFILE=>0) } + assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[]) } + assert_raise(ArgumentError) { system(*TRUECOMMAND, :rlimit_nofile=>[1,2,3]) } + + max = Process.getrlimit(:CORE).last + + # When running under ASAN, we need to set disable_coredump=0 for this test; by default + # the ASAN runtime library sets RLIMIT_CORE to 0, "to avoid dumping a 16T+ core file", and + # that inteferes with this test. + asan_options = ENV['ASAN_OPTIONS'] || '' + asan_options << ':' unless asan_options.empty? + env = { + 'ASAN_OPTIONS' => "#{asan_options}disable_coredump=0" + } + + n = max + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| + assert_equal("#{n}\n#{n}\n", io.read) + } + + n = 0 + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| + assert_equal("#{n}\n#{n}\n", io.read) + } + + n = max + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io| + assert_equal("#{n}\n#{n}\n", io.read) + } + + m, n = 0, max + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| + assert_equal("#{m}\n#{n}\n", io.read) + } + + m, n = 0, 0 + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| + assert_equal("#{m}\n#{n}\n", io.read) + } + + n = max + IO.popen([env, RUBY, "-e", + "puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)", + :rlimit_core=>n, :rlimit_cpu=>3600]) {|io| + assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read) + } + + assert_raise(ArgumentError) do + system(env, RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) + end + assert_separately([env],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) + BUG = "[ruby-core:82033] [Bug #13744]" + begin; + assert_equal([3600,3600], Process.getrlimit(:CPU), BUG) + end; + + assert_raise_with_message(ArgumentError, /bogus/) do + system(env, RUBY, '-e', 'exit', :rlimit_bogus => 123) + end + + assert_raise_with_message(ArgumentError, /rlimit_cpu/) { + system(env, RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) + } + end + + def test_overwrite_ENV + assert_separately([],"#{<<~"begin;"}\n#{<<~"end;"}") + BUG = "[ruby-core:105223] [Bug #18164]" + begin; + $VERBOSE = nil + ENV = {} + pid = spawn({}, *#{TRUECOMMAND.inspect}) + ENV.replace({}) + assert_kind_of(Integer, pid, BUG) + end; + end + + MANDATORY_ENVS = %w[RUBYLIB GEM_HOME GEM_PATH RUBY_FREE_AT_EXIT] + if e = RbConfig::CONFIG['LIBPATHENV'] + MANDATORY_ENVS << e + end + if e = RbConfig::CONFIG['PRELOADENV'] and !e.empty? + MANDATORY_ENVS << e + end + case RbConfig::CONFIG['target_os'] + when /mswin|mingw/ + MANDATORY_ENVS.concat(%w[HOME USER TMPDIR PROCESSOR_ARCHITECTURE]) + when /darwin/ + MANDATORY_ENVS.concat(%w[TMPDIR], ENV.keys.grep(/\A__CF_/)) + # IO.popen([ENV.keys.to_h {|e| [e, nil]}, + # RUBY, "-e", %q[print ENV.keys.join(?\0)]], + # &:read).split(?\0) + 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) + + def test_execopts_env + assert_raise(ArgumentError) { + system({"F=O"=>"BAR"}, *TRUECOMMAND) + } + + with_tmpchdir {|d| + prog = "#{d}/notexist" + e = assert_raise(Errno::ENOENT) { + Process.wait Process.spawn({"FOO"=>"BAR"}, prog) + } + assert_equal(prog, e.message.sub(/.* - /, '')) + e = assert_raise(Errno::ENOENT) { + Process.wait Process.spawn({"FOO"=>"BAR"}, [prog, "blar"]) + } + assert_equal(prog, e.message.sub(/.* - /, '')) + } + h = {} + cmd = [h, RUBY] + (ENV.keys + MANDATORY_ENVS).each do |k| + case k + when /\APATH\z/i + when *MANDATORY_ENVS + cmd << '-e' << "ENV.delete('#{k}')" + else + h[k] = nil + end + end + cmd << '-e' << 'puts ENV.keys.map{|e|e.upcase}' + IO.popen(cmd) {|io| + assert_equal("PATH\n", io.read) + } + + IO.popen([{"FOO"=>"BAR"}, *ENVCOMMAND]) {|io| + assert_match(/^FOO=BAR$/, io.read) + } + + with_tmpchdir {|d| + system({"fofo"=>"haha"}, *ENVCOMMAND, STDOUT=>"out") + assert_match(/^fofo=haha$/, File.read("out").chomp) + } + + old = ENV["hmm"] + begin + ENV["hmm"] = "fufu" + IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=fufu$/, io.read) } + IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) } + IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) } + ENV["hmm"] = "" + IO.popen(ENVCOMMAND) {|io| assert_match(/^hmm=$/, io.read) } + IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) } + IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) } + ENV["hmm"] = nil + IO.popen(ENVCOMMAND) {|io| assert_not_match(/^hmm=/, io.read) } + IO.popen([{"hmm"=>""}, *ENVCOMMAND]) {|io| assert_match(/^hmm=$/, io.read) } + IO.popen([{"hmm"=>nil}, *ENVCOMMAND]) {|io| assert_not_match(/^hmm=/, io.read) } + ensure + ENV["hmm"] = old + end + + assert_raise_with_message(ArgumentError, /fo=fo/) { + system({"fo=fo"=>"ha"}, *ENVCOMMAND) + } + assert_raise_with_message(ArgumentError, /\u{30c0}=\u{30e1}/) { + system({"\u{30c0}=\u{30e1}"=>"ha"}, *ENVCOMMAND) + } + end + + def test_execopt_env_path + bug8004 = '[ruby-core:53103] [Bug #8004]' + Dir.mktmpdir do |d| + File.write("#{d}/tmp_script.cmd", ": ;\n", perm: 0o755) + 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| + File.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| + File.write 's', <<-"End" + ENV["mgg"] = nil + prog = "./nonexistent" + begin + Process.exec({"mgg" => "mggoo"}, [prog, prog]) + rescue Errno::ENOENT + end + File.write('out', 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| + File.write("test_execopts_env_single_word.rb", "print ENV['hgga']\n") + 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} + IO.popen([h, *ENVCOMMAND, :unsetenv_others=>true]) {|io| + assert_equal("", io.read) + } + IO.popen([h.merge("A"=>"B"), *ENVCOMMAND, :unsetenv_others=>true]) {|io| + assert_equal("A=B\n", io.read) + } + end + + PWD = [RUBY, '-e', 'puts Dir.pwd'] + + def test_execopts_chdir + with_tmpchdir {|d| + IO.popen([*PWD, :chdir => d]) {|io| + assert_equal(d, io.read.chomp) + } + 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 + omit "umask is not supported" if windows? + IO.popen([*UMASK, :umask => 0]) {|io| + assert_equal("0000", io.read.chomp) + } + IO.popen([*UMASK, :umask => 0777]) {|io| + assert_equal("0777", io.read.chomp) + } + end + + def with_pipe + begin + r, w = IO.pipe + yield r, w + ensure + r.close unless r.closed? + w.close unless w.closed? + end + end + + def with_pipes(n) + ary = [] + begin + n.times { + ary << IO.pipe + } + yield ary + ensure + ary.each {|r, w| + r.close unless r.closed? + w.close unless w.closed? + } + end + end + + ECHO = lambda {|arg| [RUBY, '-e', "puts #{arg.dump}; STDOUT.flush"] } + SORT = [RUBY, '-e', "puts ARGF.readlines.sort"] + CAT = [RUBY, '-e', "IO.copy_stream STDIN, STDOUT"] + + 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 windows? + # currently telling to child the file modes is not supported. + File.write("out", "0\n", mode: "a") + else + Process.wait Process.spawn(*ECHO["0"], STDOUT=>["out", File::WRONLY|File::CREAT|File::APPEND, 0644]) + assert_equal("a\n0\n", File.read("out")) + end + Process.wait Process.spawn(*SORT, STDIN=>["out", File::RDONLY, 0644], + STDOUT=>["out2", File::WRONLY|File::CREAT|File::TRUNC, 0644]) + assert_equal("0\na\n", File.read("out2")) + Process.wait Process.spawn(*ECHO["b"], [STDOUT, STDERR]=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644]) + assert_equal("b", File.read("out").chomp) + # 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_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"], STDOUT=>f) + assert_equal("d", File.read("out").chomp) + } + 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) + 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) + unless windows? + # passing non-stdio fds is not supported on Windows + File.open("out", "w") {|f| + h = {STDOUT=>f, f=>STDOUT} + 3.upto(30) {|i| h[i] = STDOUT if f.fileno != i } + Process.wait Process.spawn(*ECHO["f"], h) + assert_equal("f", File.read("out").chomp) + } + end + assert_raise(ArgumentError) { + Process.wait Process.spawn(*ECHO["f"], 1=>Process) + } + assert_raise(ArgumentError) { + Process.wait Process.spawn(*ECHO["f"], [Process]=>1) + } + assert_raise(ArgumentError) { + Process.wait Process.spawn(*ECHO["f"], [1, STDOUT]=>2) + } + assert_raise(ArgumentError) { + Process.wait Process.spawn(*ECHO["f"], -1=>2) + } + Process.wait Process.spawn(*ECHO["hhh\nggg\n"], STDOUT=>"out") + assert_equal("hhh\nggg\n", File.read("out")) + Process.wait Process.spawn(*SORT, STDIN=>"out", STDOUT=>"out2") + assert_equal("ggg\nhhh\n", File.read("out2")) + + 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]) + } + assert_equal("", File.read("err")) + end + + system(*ECHO["bb\naa\n"], STDOUT=>["out", "w"]) + 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_file.pipe?("fifo") + 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 + pid = nil + 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 + pid = io.pid + assert_equal("start\n", io.gets) + sleep 0.5 + Process.kill(:USR1, io.pid) + assert_equal("ok\n", io.read) + } + assert_equal(pid, $?.pid) + assert_predicate($?, :success?) + } + ensure + assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)} if pid + end unless windows? # does not support fifo + + def test_execopts_redirect_open_fifo_interrupt_print + pid = nil + 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 + pid = io.pid + 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)) + sleep 0.2 # wait for the child to return to opening "fifo". + # On arm64-darwin22, often deadlocks while the child is + # opening "fifo". Not sure to where "ok" line being written + # at the next has gone. + File.write("fifo", "ok\n") + assert_equal("ok\n", io.read) + } + assert_equal(pid, $?.pid) + assert_predicate($?, :success?) + } + ensure + if pid + assert_raise(Errno::ESRCH) {Process.kill(:KILL, pid)} + end + 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) + } + } + + 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; + } + + 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 + } + + 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") + assert_equal("henya\n", File.read("out")) + IO.popen([*CAT, :in=>"out"]) {|io| + assert_equal("henya\n", io.read) + } + } + 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'", + STDOUT=>"out", STDERR=>[:child, STDOUT]) + assert_equal("errout", File.read("out")) + + Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", + STDERR=>"out", STDOUT=>[:child, STDERR]) + assert_equal("errout", File.read("out")) + + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + Process.wait spawn(RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", + STDOUT=>"out", + STDERR=>[:child, 3], + 3=>[:child, 4], + 4=>[:child, STDOUT] + ) + assert_equal("errout", File.read("out")) + + IO.popen([RUBY, "-e", "STDERR.print 'err'; STDOUT.print 'out'", STDERR=>[:child, STDOUT]]) {|io| + assert_equal("errout", io.read) + } + + assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, STDOUT]) } + assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 3]) } + assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, 3=>[:child, 4], 4=>[:child, 5], 5=>[:child, 3]) } + assert_raise(ArgumentError) { Process.wait spawn(*TRUECOMMAND, STDOUT=>[:child, 3]) } + } + end + + def test_execopts_exec + with_tmpchdir {|d| + File.write("s", 'exec "echo aaa", STDOUT=>"foo"') + pid = spawn RUBY, 's' + Process.wait pid + assert_equal("aaa\n", File.read("foo")) + } + end + + def test_execopts_popen + with_tmpchdir {|d| + 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| } + } + IO.popen([*ECHO["hoge"], STDERR=>STDOUT]) {|io| + assert_equal("hoge\n", io.read) + } + assert_raise(ArgumentError) { + IO.popen([*ECHO["fuga"], STDOUT=>"out"]) {|io| } + } + } + end + + def test_execopts_popen_extra_fd + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + with_tmpchdir {|d| + with_pipe {|r, w| + IO.popen([RUBY, '-e', 'IO.new(3, "w").puts("a"); puts "b"', 3=>w]) {|io| + assert_equal("b\n", io.read) + } + w.close + assert_equal("a\n", r.read) + } + IO.popen([RUBY, '-e', "IO.new(9, 'w').puts(:b)", + 9=>["out2", File::WRONLY|File::CREAT|File::TRUNC]]) {|io| + assert_equal("", io.read) + } + assert_equal("b\n", File.read("out2")) + } + end + + if Process.respond_to?(:fork) + def test_popen_fork + IO.popen("-") do |io| + if !io + puts "fooo" + else + assert_equal("fooo\n", io.read) + end + end + end + + def test_popen_fork_ensure + IO.popen("-") do |io| + if !io + STDERR.reopen(STDOUT) + raise "fooo" + else + assert_empty io.read + end + end + rescue RuntimeError + abort "[Bug #20995] should not reach here" + end + end + + def test_fd_inheritance + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + with_pipe {|r, w| + system(RUBY, '-e', 'IO.new(ARGV[0].to_i, "w").puts(:ba)', w.fileno.to_s, w=>w) + w.close + assert_equal("ba\n", r.read) + } + with_pipe {|r, w| + Process.wait spawn(RUBY, '-e', + 'IO.new(ARGV[0].to_i, "w").puts("bi") rescue nil', + w.fileno.to_s) + w.close + assert_equal("", r.read) + } + with_pipe {|r, w| + with_tmpchdir {|d| + File.write("s", <<-"End") + exec(#{RUBY.dump}, '-e', + 'IO.new(ARGV[0].to_i, "w").puts("bu") rescue nil', + #{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) + } + } + with_pipe {|r, w| + io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')"]) + 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)"` + w.close + assert_equal("", r.read) + assert_not_equal("", errmsg) + } + end + + def test_execopts_close_others + omit "inheritance of fd other than stdin,stdout and stderr is not supported" if windows? + with_tmpchdir {|d| + with_pipe {|r, w| + system(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("ma")', w.fileno.to_s, :close_others=>true) + w.close + assert_equal("", r.read) + assert_not_equal("", File.read("err")) + File.unlink("err") + } + with_pipe {|r, w| + Process.wait spawn(RUBY, '-e', 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mi")', w.fileno.to_s, :close_others=>true) + w.close + assert_equal("", r.read) + assert_not_equal("", File.read("err")) + 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) + } + with_pipe {|r, w| + File.write("s", <<-"End") + exec(#{RUBY.dump}, '-e', + 'STDERR.reopen("err", "w"); IO.new(ARGV[0].to_i, "w").puts("mu")', + #{w.fileno.to_s.dump}, + :close_others=>true) + End + Process.wait spawn(RUBY, "s", :close_others=>false) + w.close + assert_equal("", r.read) + assert_not_equal("", File.read("err")) + File.unlink("err") + } + with_pipe {|r, w| + io = IO.popen([RUBY, "-e", "STDERR.reopen(STDOUT); IO.new(#{w.fileno}, 'w').puts('me')", :close_others=>true]) + 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]) + 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]) + 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| + w << "haha\n" + w.close + r.close_on_exec = true + IO.popen([RUBY, "-e", "print IO.new(#{r.fileno}, 'r').read", r.fileno=>r.fileno, :close_others=>false]) {|io| + assert_equal("haha\n", io.read) + } + } + rescue NotImplementedError + omit "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 + IO.popen("#{RUBY} -e ''", "r+") {|duplex| + assert_raise(ArgumentError) { system("#{RUBY} -e ''", duplex=>STDOUT) } + assert_raise(ArgumentError) { system("#{RUBY} -e ''", STDOUT=>duplex) } + } + end + + def test_execopts_modification + h = {} + Process.wait spawn(*TRUECOMMAND, h) + assert_equal({}, h) + + h = {} + system(*TRUECOMMAND, h) + assert_equal({}, h) + + h = {} + io = IO.popen([*TRUECOMMAND, h]) + io.close + assert_equal({}, h) + end + + def test_system_noshell + str = "echo non existing command name which contains spaces" + assert_nil(system([str, str])) + end + + def test_spawn_noshell + str = "echo non existing command name which contains spaces" + assert_raise(Errno::ENOENT) { spawn([str, str]) } + end + + def test_popen_noshell + str = "echo non existing command name which contains spaces" + assert_raise(Errno::ENOENT) { IO.popen([str, str]) } + end + + def test_exec_noshell + with_tmpchdir {|d| + File.write("s", <<-"End") + str = "echo non existing command name which contains spaces" + STDERR.reopen(STDOUT) + begin + exec [str, str] + rescue Errno::ENOENT + print "Errno::ENOENT success" + end + End + r = IO.popen([RUBY, "s", :close_others=>false], "r") {|f| f.read} + assert_equal("Errno::ENOENT success", r) + } + end + + def test_system_wordsplit + with_tmpchdir {|d| + File.write("script", <<-'End') + File.open("result", "w") {|t| t << "haha pid=#{$$} ppid=#{Process.ppid}" } + exit 5 + End + str = "#{RUBY} script" + ret = system(str) + status = $? + assert_equal(false, ret) + assert_predicate(status, :exited?) + assert_equal(5, status.exitstatus) + assert_equal("haha pid=#{status.pid} ppid=#{$$}", File.read("result")) + } + end + + def test_spawn_wordsplit + with_tmpchdir {|d| + File.write("script", <<-'End') + File.open("result", "w") {|t| t << "hihi pid=#{$$} ppid=#{Process.ppid}" } + exit 6 + End + str = "#{RUBY} script" + pid = spawn(str) + Process.wait pid + status = $? + assert_equal(pid, status.pid) + assert_predicate(status, :exited?) + assert_equal(6, status.exitstatus) + assert_equal("hihi pid=#{status.pid} ppid=#{$$}", File.read("result")) + } + end + + def test_popen_wordsplit + with_tmpchdir {|d| + File.write("script", <<-'End') + print "fufu 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("fufu pid=#{status.pid} ppid=#{$$}", result) + } + end + + def test_popen_wordsplit_beginning_and_trailing_spaces + with_tmpchdir {|d| + File.write("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| + File.write("script", <<-'End') + File.open("result", "w") {|t| + if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + t << "hehe ppid=#{Process.ppid}" + else + t << "hehe pid=#{$$} ppid=#{Process.ppid}" + end + } + exit 6 + End + File.write("s", <<-"End") + ruby = #{RUBY.dump} + exec "\#{ruby} script" + End + pid = spawn(RUBY, "s") + Process.wait pid + status = $? + assert_equal(pid, status.pid) + assert_predicate(status, :exited?) + assert_equal(6, status.exitstatus) + if windows? + expected = "hehe ppid=#{status.pid}" + else + expected = "hehe pid=#{status.pid} ppid=#{$$}" + end + assert_equal(expected, File.read("result")) + } + end + + def test_system_shell + with_tmpchdir {|d| + File.write("script1", <<-'End') + File.open("result1", "w") {|t| t << "taka pid=#{$$} ppid=#{Process.ppid}" } + exit 7 + End + File.write("script2", <<-'End') + File.open("result2", "w") {|t| t << "taki pid=#{$$} ppid=#{Process.ppid}" } + exit 8 + End + ret = system("#{RUBY} script1 || #{RUBY} script2") + status = $? + assert_equal(false, ret) + 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 windows? + Dir.mkdir(path = "path with space") + File.write(bat = path + "/bat test.bat", "@echo %1>out") + system(bat, "foo 'bar'") + assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') + system(%[#{bat.dump} "foo 'bar'"]) + assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') + end + } + end + + def test_spawn_shell + with_tmpchdir {|d| + File.write("script1", <<-'End') + File.open("result1", "w") {|t| t << "taku pid=#{$$} ppid=#{Process.ppid}" } + exit 7 + End + File.write("script2", <<-'End') + File.open("result2", "w") {|t| t << "take pid=#{$$} ppid=#{Process.ppid}" } + exit 8 + End + pid = spawn("#{RUBY} script1 || #{RUBY} script2") + Process.wait pid + status = $? + 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 windows? + Dir.mkdir(path = "path with space") + File.write(bat = path + "/bat test.bat", "@echo %1>out") + pid = spawn(bat, "foo 'bar'") + Process.wait pid + status = $? + 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_predicate(status, :exited?) + assert_predicate(status, :success?) + assert_equal(%["foo 'bar'"\n], File.read("out"), '[ruby-core:22960]') + end + } + end + + def test_popen_shell + with_tmpchdir {|d| + File.write("script1", <<-'End') + puts "tako pid=#{$$} ppid=#{Process.ppid}" + exit 7 + End + File.write("script2", <<-'End') + puts "tika pid=#{$$} ppid=#{Process.ppid}" + exit 8 + End + io = IO.popen("#{RUBY} script1 || #{RUBY} script2") + result = io.read + io.close + status = $? + 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 windows? + Dir.mkdir(path = "path with space") + File.write(bat = path + "/bat test.bat", "@echo %1") + r = IO.popen([bat, "foo 'bar'"]) {|f| f.read} + assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]') + r = IO.popen(%[#{bat.dump} "foo 'bar'"]) {|f| f.read} + assert_equal(%["foo 'bar'"\n], r, '[ruby-core:22960]') + end + } + end + + def test_exec_shell + with_tmpchdir {|d| + File.write("script1", <<-'End') + File.open("result1", "w") {|t| t << "tiki pid=#{$$} ppid=#{Process.ppid}" } + exit 7 + End + File.write("script2", <<-'End') + File.open("result2", "w") {|t| t << "tiku pid=#{$$} ppid=#{Process.ppid}" } + exit 8 + End + File.write("s", <<-"End") + ruby = #{RUBY.dump} + exec("\#{ruby} script1 || \#{ruby} script2") + End + pid = spawn RUBY, "s" + Process.wait pid + status = $? + 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) + assert_match(/\Atiku pid=\d+ ppid=\d+\z/, result2) + assert_not_equal(result1[/\d+/].to_i, status.pid) + } + end + + def test_argv0 + with_tmpchdir {|d| + assert_equal(false, system([RUBY, "asdfg"], "-e", "exit false")) + assert_equal(true, system([RUBY, "zxcvb"], "-e", "exit true")) + + Process.wait spawn([RUBY, "poiu"], "-e", "exit 4") + assert_equal(4, $?.exitstatus) + + assert_equal("1", IO.popen([[RUBY, "qwerty"], "-e", "print 1"]) {|f| f.read }) + + File.write("s", <<-"End") + exec([#{RUBY.dump}, "lkjh"], "-e", "exit 5") + End + pid = spawn RUBY, "s" + Process.wait pid + assert_equal(5, $?.exitstatus) + } + end + + def with_stdin(filename) + File.open(filename) {|f| + begin + old = STDIN.dup + begin + STDIN.reopen(filename) + yield + ensure + STDIN.reopen(old) + end + ensure + old.close + end + } + end + + def test_argv0_noarg + with_tmpchdir {|d| + File.write("t", "exit true") + File.write("f", "exit false") + + with_stdin("t") { assert_equal(true, system([RUBY, "qaz"])) } + with_stdin("f") { assert_equal(false, system([RUBY, "wsx"])) } + + with_stdin("t") { Process.wait spawn([RUBY, "edc"]) } + assert_predicate($?, :success?) + with_stdin("f") { Process.wait spawn([RUBY, "rfv"]) } + assert_not_predicate($?, :success?) + + with_stdin("t") { IO.popen([[RUBY, "tgb"]]) {|io| assert_equal("", io.read) } } + assert_predicate($?, :success?) + with_stdin("f") { IO.popen([[RUBY, "yhn"]]) {|io| assert_equal("", io.read) } } + assert_not_predicate($?, :success?) + + status = run_in_child "STDIN.reopen('t'); exec([#{RUBY.dump}, 'ujm'])" + assert_predicate(status, :success?) + status = run_in_child "STDIN.reopen('f'); exec([#{RUBY.dump}, 'ik,'])" + 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_argv0_frozen + assert_predicate Process.argv0, :frozen? + assert_predicate $0, :frozen? + end + + def test_status + with_tmpchdir do + s = run_in_child("exit 1") + assert_equal("#<Process::Status: pid #{ s.pid } exit #{ s.exitstatus }>", s.inspect) + + assert_equal(s, s) + assert_equal(s, s.to_i) + + assert_equal(false, s.stopped?) + assert_equal(nil, s.stopsig) + + assert_equal(s, Marshal.load(Marshal.dump(s))) + end + end + + def test_status_kill + return unless Process.respond_to?(:kill) + 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 + File.write("foo", "Process.kill(:KILL, $$); exit(42)") + system(RUBY, "foo") + s = $? + assert_equal(expected, + [s.exited?, s.signaled?, s.stopped?, s.success?], + "[s.exited?, s.signaled?, s.stopped?, s.success?]") + + assert_equal(s, Marshal.load(Marshal.dump(s))) + end + end + + 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)/, '')) + + assert_equal(s, Marshal.load(Marshal.dump(s))) + end + end + + def test_status_fail + ret = Process::Status.wait($$) + assert_instance_of(Process::Status, ret) + assert_equal(-1, ret.pid) + end + + + def test_status_wait + IO.popen([RUBY, "-e", "gets"], "w") do |io| + pid = io.pid + assert_nil(Process::Status.wait(pid, Process::WNOHANG)) + io.puts + ret = Process::Status.wait(pid) + assert_instance_of(Process::Status, ret) + assert_equal(pid, ret.pid) + assert_predicate(ret, :exited?) + end + end + + def test_wait_without_arg + with_tmpchdir do + File.write("foo", "sleep 0.1") + pid = spawn(RUBY, "foo") + assert_equal(pid, Process.wait) + end + end + + def test_wait2 + with_tmpchdir do + File.write("foo", "sleep 0.1") + pid = spawn(RUBY, "foo") + assert_equal([pid, 0], Process.wait2) + end + end + + def test_waitall + with_tmpchdir do + File.write("foo", "sleep 0.1") + ps = (0...3).map { spawn(RUBY, "foo") }.sort + ss = Process.waitall.sort + ps.zip(ss) do |p1, (p2, s)| + assert_equal(p1, p2) + assert_equal(p1, s.pid) + end + 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 + rescue Errno::EPIPE + omit "child process exited already in #{diff} seconds" + end + end + + def test_abort + with_tmpchdir do + s = run_in_child("abort") + assert_not_predicate(s, :success?) + File.write("test-script", "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + STDERR.reopen(STDOUT) + begin + raise "[Bug #16424]" + rescue + abort + end + end; + assert_include(IO.popen([RUBY, "test-script"], &:read), "[Bug #16424]") + end + end + + def test_sleep + assert_raise(ArgumentError) { sleep(1, 1) } + [-1, -1.0, -1r].each do |sec| + assert_raise_with_message(ArgumentError, /not.*negative/) { sleep(sec) } + end + end + + def test_getpgid + assert_kind_of(Integer, Process.getpgid(Process.ppid)) + rescue NotImplementedError + end + + def test_getpriority + assert_kind_of(Integer, Process.getpriority(Process::PRIO_PROCESS, $$)) + rescue NameError, NotImplementedError + end + + def test_setpriority + if defined? Process::PRIO_USER + assert_nothing_raised do + pr = Process.getpriority(Process::PRIO_PROCESS, $$) + Process.setpriority(Process::PRIO_PROCESS, $$, pr) + end + end + end + + def test_getuid + assert_kind_of(Integer, Process.uid) + end + + def test_groups + gs = Process.groups + assert_instance_of(Array, gs) + gs.each {|g| assert_kind_of(Integer, g) } + rescue NotImplementedError + end + + def test_maxgroups + max = Process.maxgroups + rescue NotImplementedError + else + assert_kind_of(Integer, max) + assert_predicate(max, :positive?) + omit "not limited to NGROUPS_MAX" if /darwin/ =~ RUBY_PLATFORM + gs = Process.groups + assert_operator(gs.size, :<=, max) + gs[0] ||= 0 + assert_raise(ArgumentError) {Process.groups = gs * (max / gs.size + 1)} + end + + def test_geteuid + 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 + omit "root can use Process.egid on Android platform" if RUBY_PLATFORM =~ /android/ + assert_nothing_raised(TypeError) {Process.egid += 0} + rescue NotImplementedError + end + + if Process::UID.respond_to?(:from_name) + def test_uid_from_name + if u = Etc.getpwuid(Process.uid) + assert_equal(Process.uid, Process::UID.from_name(u.name), u.name) + end + exc = assert_raise_kind_of(ArgumentError, SystemCallError) { + Process::UID.from_name("\u{4e0d 5b58 5728}") + } + assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) + end + end + + if Process::GID.respond_to?(:from_name) && !RUBY_PLATFORM.include?("android") + def test_gid_from_name + if g = Etc.getgrgid(Process.gid) + assert_equal(Process.gid, Process::GID.from_name(g.name), g.name) + end + exc = assert_raise_kind_of(ArgumentError, SystemCallError) do + Process::GID.from_name("\u{4e0d 5b58 5728}") # fu son zai ("absent" in Kanji) + end + assert_match(/\u{4e0d 5b58 5728}/, exc.message) if exc.is_a?(ArgumentError) + end + end + + def test_uid_re_exchangeable_p + r = Process::UID.re_exchangeable? + assert_include([true, false], r) + end + + def test_gid_re_exchangeable_p + r = Process::GID.re_exchangeable? + assert_include([true, false], r) + end + + def test_uid_sid_available? + r = Process::UID.sid_available? + assert_include([true, false], r) + end + + def test_gid_sid_available? + r = Process::GID.sid_available? + assert_include([true, false], r) + end + + def test_pst_inspect + assert_nothing_raised { Process::Status.allocate.inspect } + end + + def test_wait_and_sigchild + signal_received = [] + IO.pipe do |sig_r, sig_w| + Signal.trap(:CHLD) do + signal_received << true + sig_w.write('?') + end + pid = nil + th = nil + IO.pipe do |r, w| + pid = fork { r.read(1); exit } + th = Thread.start { + Thread.current.report_on_exception = false + raise + } + w.puts + end + Process.wait pid + begin + th.join + rescue Exception + end + assert_send [sig_r, :wait_readable, 5], 'self-pipe not readable' + end + assert_equal [true], signal_received, "[ruby-core:19744]" + rescue NotImplementedError, ArgumentError + ensure + begin + Signal.trap(:CHLD, 'DEFAULT') + rescue ArgumentError + end + end + + def test_no_curdir + with_tmpchdir {|d| + Dir.mkdir("vd") + status = nil + Dir.chdir("vd") { + dir = "#{d}/vd" + # Windows cannot remove the current directory with permission issues. + 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_predicate(status, :success?, "[ruby-dev:38105]") + } + end + + def test_fallback_to_sh + feature = '[ruby-core:32745]' + with_tmpchdir do |d| + File.write("tmp_script.#{$$}", ": ;\n", perm: 0o755) + assert_not_nil(pid = Process.spawn("./tmp_script.#{$$}"), feature) + wpid, st = Process.waitpid2(pid) + assert_equal([pid, true], [wpid, st.success?], feature) + + File.write("tmp_script.#{$$}", "echo $#: $@", perm: 0o755) + result = IO.popen(["./tmp_script.#{$$}", "a b", "c"]) {|f| f.read} + assert_equal("2: a b c\n", result, feature) + + File.write("tmp_script.#{$$}", "echo $hghg", perm: 0o755) + 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 + 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::EINVAL if windows? + exs << Errno::E2BIG if defined?(Errno::E2BIG) + opts = {[STDOUT, STDERR]=>File::NULL} + if defined?(Process::RLIMIT_NPROC) + opts[:rlimit_nproc] = /openbsd/i =~ RUBY_PLATFORM ? 64 : 128 + end + EnvUtil.suppress_warning do + assert_raise(*exs, mesg) do + begin + loop do + Process.spawn(cmds.join(sep), opts) + min = [cmds.size, min].max + begin + cmds *= 100 + rescue ArgumentError + raise NoMemoryError + end + 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 + EnvUtil.timeout(10) 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 + + def test_daemon_detached + IO.popen("-", "r+") do |f| + if f + assert_equal(f.pid, Process.wait(f.pid)) + + dpid, ppid, dsid = 3.times.map {Integer(f.gets)} + + message = "daemon #{dpid} should be detached" + assert_not_equal($$, ppid, message) # would be 1 almost always + assert_raise(Errno::ECHILD, message) {Process.wait(dpid)} + assert_kind_of(Integer, Process.kill(0, dpid), message) + assert_equal(dpid, dsid) + + break # close f, and let the daemon resume and exit + end + Process.setsid rescue nil + Process.daemon(false, true) + puts $$, Process.ppid, Process.getsid + $stdin.gets # wait for the above assertions using signals + end + end + + if File.directory?("/proc/self/task") && /netbsd[a-z]*[1-6]/ !~ RUBY_PLATFORM + def test_daemon_no_threads + pid, data = IO.popen("-", "r+") do |f| + 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 = EnvUtil.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_ruby_status([], "#{<<~"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 + omit "root can use uid option of Kernel#system on Android platform" if RUBY_PLATFORM =~ /android/ + feature6975 = '[ruby-core:47414]' + + [30000, [Process.uid, ENV["USER"]]].each do |uid, user| + if user + assert_nothing_raised(feature6975) do + begin + system(*TRUECOMMAND, uid: user, exception: true) + rescue Errno::EPERM, Errno::EACCES, NotImplementedError + end + end + end + + assert_nothing_raised(feature6975) do + begin + system(*TRUECOMMAND, uid: uid, exception: true) + rescue Errno::EPERM, Errno::EACCES, 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, Errno::EACCES, NotImplementedError + end + end + end + end + + def test_execopts_gid + omit "Process.groups not implemented on Windows platform" if windows? + omit "root can use Process.groups on Android platform" if RUBY_PLATFORM =~ /android/ + feature6975 = '[ruby-core:47414]' + + groups = Process.groups.map do |g| + 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.local_charmap) + 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_with_message(Errno::EINVAL, /:foo/) do + Process.clock_gettime(:foo) + end + 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_with_message(Errno::EINVAL, /:foo/) do + Process.clock_getres(:foo) + end + 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_ruby_status(%W(- #{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) + 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_predicate status, :success? + 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" + q = Thread::Queue.new + th = Thread.start {system(prog);q.push(nil);sleep} + q.pop + 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 + EnvUtil.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 + EnvUtil.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 = Thread::Queue.new + th1 = Thread.new { i = 0; i += 1 while q.empty?; i } + th2 = Thread.new { j = 0; j += 1 while q.empty? && Thread.pass.nil?; j } + sleep 0.5 + q << true + w.syswrite "#{th1.value} #{th2.value}\n" + end + w.close + assert_equal "exec failed\n", r.gets + vals = r.gets.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 + omit "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 + + def test_last_status_failure + assert_nil system("sad") + assert_not_predicate $?, :success? + assert_equal $?.exitstatus, 127 + end + + def test_exec_failure_leaves_no_child + assert_raise(Errno::ENOENT) do + spawn('inexistent_command') + end + assert_empty(Process.waitall) + end + + def test__fork + r, w = IO.pipe + pid = Process._fork + if pid == 0 + begin + r.close + w << "ok: #$$" + w.close + ensure + exit! + end + else + w.close + assert_equal("ok: #{pid}", r.read) + r.close + Process.waitpid(pid) + end + end if Process.respond_to?(:_fork) + + def test__fork_pid_cache + _parent_pid = Process.pid + r, w = IO.pipe + pid = Process._fork + if pid == 0 + begin + r.close + w << "ok: #{Process.pid}" + w.close + ensure + exit! + end + else + w.close + assert_equal("ok: #{pid}", r.read) + r.close + Process.waitpid(pid) + end + end if Process.respond_to?(:_fork) + + def test__fork_hook + %w(fork Process.fork).each do |method| + feature17795 = '[ruby-core:103400] [Feature #17795]' + assert_in_out_err([], <<-"end;", [], [], feature17795, timeout: 60) do |r, e| + module ForkHook + def _fork + p :before + ret = super + p :after + ret + end + end + + Process.singleton_class.prepend(ForkHook) + + pid = #{ method } + p pid + Process.waitpid(pid) if pid + end; + assert_equal([], e) + assert_equal(":before", r.shift) + assert_equal(":after", r.shift) + s = r.map {|s| s.chomp }.sort #=> [pid, ":after", "nil"] + assert_match(/^\d+$/, s[0]) # pid + assert_equal(":after", s[1]) + assert_equal("nil", s[2]) + end + end + end if Process.respond_to?(:_fork) + + def test__fork_hook_popen + feature17795 = '[ruby-core:103400] [Feature #17795]' + assert_in_out_err([], <<-"end;", %w(:before :after :after foo bar), [], feature17795, timeout: 60) + module ForkHook + def _fork + p :before + ret = super + p :after + ret + end + end + + Process.singleton_class.prepend(ForkHook) + + IO.popen("-") {|io| + if !io + puts "foo" + else + puts io.read + "bar" + end + } + end; + end if Process.respond_to?(:_fork) + + def test__fork_wrong_type_hook + feature17795 = '[ruby-core:103400] [Feature #17795]' + assert_in_out_err([], <<-"end;", ["OK"], [], feature17795, timeout: 60) + module ForkHook + def _fork + "BOO" + end + end + + Process.singleton_class.prepend(ForkHook) + + begin + fork + rescue TypeError + puts "OK" + end + end; + end if Process.respond_to?(:_fork) + + def test_warmup_promote_all_objects_to_oldgen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + require 'objspace' + begin; + obj = Object.new + + assert_not_include(ObjectSpace.dump(obj), '"old":true') + Process.warmup + assert_include(ObjectSpace.dump(obj), '"old":true') + end; + end + + def test_warmup_run_major_gc_and_compact + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + # Run a GC to ensure that we are not in the middle of a GC run + GC.start + + major_gc_count = GC.stat(:major_gc_count) + compact_count = GC.stat(:compact_count) + Process.warmup + assert_equal major_gc_count + 1, GC.stat(:major_gc_count) + assert_equal compact_count + 1, GC.stat(:compact_count) + end; + end + + def test_warmup_precompute_string_coderange + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + require 'objspace' + begin; + obj = "a" * 12 + obj.force_encoding(Encoding::UTF_16LE) + obj.force_encoding(Encoding::BINARY) + assert_include(ObjectSpace.dump(obj), '"coderange":"unknown"') + Process.warmup + assert_include(ObjectSpace.dump(obj), '"coderange":"7bit"') + end; + end + + def test_warmup_frees_pages + assert_separately([{"RUBY_GC_HEAP_FREE_SLOTS_MAX_RATIO" => "1.0"}, "-W0"], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + GC.start + + TIMES = 100_000 + ary = Array.new(TIMES) + TIMES.times do |i| + ary[i] = Object.new + end + ary.clear + ary = nil + + # Disable GC so we can make sure GC only runs in Process.warmup + GC.disable + + total_slots_before = GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots) + + Process.warmup + + # TODO: flaky + # assert_equal(total_slots_before, GC.stat(:heap_available_slots) + GC.stat(:heap_allocatable_slots)) + + assert_equal(0, GC.stat(:heap_empty_pages)) + assert_operator(GC.stat(:total_freed_pages), :>, 0) + end; + end + + def test_concurrent_group_and_pid_wait + # Use a pair of pipes that will make long_pid exit when this test exits, to avoid + # leaking temp processes. + long_rpipe, long_wpipe = IO.pipe + short_rpipe, short_wpipe = IO.pipe + # This process should run forever + long_pid = fork do + [short_rpipe, short_wpipe, long_wpipe].each(&:close) + long_rpipe.read + end + # This process will exit + short_pid = fork do + [long_rpipe, long_wpipe, short_wpipe].each(&:close) + short_rpipe.read + end + t1, t2, t3 = nil + EnvUtil.timeout(5) do + t1 = Thread.new do + Process.waitpid long_pid + end + # Wait for us to be blocking in a call to waitpid2 + Thread.pass until t1.stop? + short_wpipe.close # Make short_pid exit + + # The short pid has exited, so -1 should pick that up. + assert_equal short_pid, Process.waitpid(-1) + + # Terminate t1 for the next phase of the test. + t1.kill + t1.join + + t2 = Thread.new do + Process.waitpid(-1) + rescue Errno::ECHILD + nil + end + Thread.pass until t2.stop? + t3 = Thread.new do + Process.waitpid long_pid + rescue Errno::ECHILD + nil + end + Thread.pass until t3.stop? + + # it's actually nondeterministic which of t2 or t3 will receive the wait (this + # nondeterminism comes from the behaviour of the underlying system calls) + long_wpipe.close + assert_equal [long_pid], [t2, t3].map(&:value).compact + end + ensure + [t1, t2, t3].each { _1&.kill rescue nil } + [t1, t2, t3].each { _1&.join rescue nil } + [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil } + end if defined?(fork) + + def test_handle_interrupt_with_fork + Thread.handle_interrupt(RuntimeError => :never) do + Thread.current.raise(RuntimeError, "Queued error") + + assert_predicate Thread, :pending_interrupt? + + pid = Process.fork do + if Thread.pending_interrupt? + exit 1 + end + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + + assert_predicate Thread, :pending_interrupt? + end + rescue RuntimeError + # Ignore. + end if defined?(fork) +end diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb new file mode 100644 index 0000000000..6ae511217a --- /dev/null +++ b/test/ruby/test_ractor.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRactor < Test::Unit::TestCase + def test_shareability_of_iseq_proc + assert_raise Ractor::IsolationError do + foo = [] + Ractor.shareable_proc{ foo } + end + end + + def test_shareability_of_method_proc + # TODO: fix with Ractor.shareable_proc/lambda +=begin + str = +"" + + x = str.instance_exec { proc { to_s } } + assert_unshareable(x, /Proc\'s self is not shareable/) + + x = str.instance_exec { method(:to_s) } + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) + + x = str.instance_exec { method(:to_s).to_proc } + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) + + x = str.instance_exec { method(:itself).to_proc } + assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error) + + str.freeze + + x = str.instance_exec { proc { to_s } } + assert_make_shareable(x) + + x = str.instance_exec { method(:to_s) } + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) + + x = str.instance_exec { method(:to_s).to_proc } + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) + + x = str.instance_exec { method(:itself).to_proc } + assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error) +=end + end + + def test_shareability_error_uses_inspect + x = (+"").instance_exec { method(:to_s) } + def x.to_s + raise "this should not be called" + end + assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()> because it refers unshareable objects", exception: Ractor::Error) + end + + def test_default_thread_group + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:experimental] = false + + main_ractor_id = Thread.current.group.object_id + ractor_id = Ractor.new { Thread.current.group.object_id }.value + refute_equal main_ractor_id, ractor_id + end; + end + + def test_class_instance_variables + assert_ractor(<<~'RUBY') + # Once we're in multi-ractor mode, the codepaths + # for class instance variables are a bit different. + Ractor.new {}.value + + class TestClass + @a = 1 + @b = 2 + @c = 3 + @d = 4 + end + + assert_equal 4, TestClass.remove_instance_variable(:@d) + assert_nil TestClass.instance_variable_get(:@d) + assert_equal 4, TestClass.instance_variable_set(:@d, 4) + assert_equal 4, TestClass.instance_variable_get(:@d) + RUBY + end + + def test_struct_instance_variables + assert_ractor(<<~'RUBY') + StructIvar = Struct.new(:member) do + def initialize(*) + super + @ivar = "ivar" + end + attr_reader :ivar + end + obj = StructIvar.new("member") + obj_copy = Ractor.new { Ractor.receive }.send(obj).value + assert_equal obj.ivar, obj_copy.ivar + refute_same obj.ivar, obj_copy.ivar + assert_equal obj.member, obj_copy.member + refute_same obj.member, obj_copy.member + RUBY + end + + def test_move_nested_hash_during_gc_with_yjit + assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }]) + GC.stress = true + hash = { foo: { bar: "hello" }, baz: { qux: "there" } } + result = Ractor.new { Ractor.receive }.send(hash, move: true).value + assert_equal "hello", result[:foo][:bar] + assert_equal "there", result[:baz][:qux] + RUBY + end + + def test_fork_raise_isolation_error + assert_ractor(<<~'RUBY') + ractor = Ractor.new do + Process.fork + rescue Ractor::IsolationError => e + e + end + assert_equal Ractor::IsolationError, ractor.value.class + RUBY + end if Process.respond_to?(:fork) + + def test_require_raises_and_no_ractor_belonging_issue + assert_ractor(<<~'RUBY') + require "tempfile" + f = Tempfile.new(["file_to_require_from_ractor", ".rb"]) + f.write("raise 'uh oh'") + f.flush + err_msg = Ractor.new(f.path) do |path| + begin + require path + rescue RuntimeError => e + e.message # had confirm belonging issue here + else + nil + end + end.value + assert_equal "uh oh", err_msg + RUBY + end + + def test_require_non_string + assert_ractor(<<~'RUBY') + require "tempfile" + require "pathname" + f = Tempfile.new(["file_to_require_from_ractor", ".rb"]) + f.write("") + f.flush + result = Ractor.new(f.path) do |path| + require Pathname.new(path) + "success" + end.value + assert_equal "success", result + RUBY + end + + # [Bug #21398] + def test_port_receive_dnt_with_port_send + omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/ + assert_ractor(<<~'RUBY', timeout: 90) + THREADS = 10 + JOBS_PER_THREAD = 50 + ARRAY_SIZE = 20_000 + def ractor_job(job_count, array_size) + port = Ractor::Port.new + workers = (1..4).map do |i| + Ractor.new(port) do |job_port| + while job = Ractor.receive + result = job.map { |x| x * 2 }.sum + job_port.send result + end + end + end + jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } } + jobs.each_with_index do |job, i| + w_idx = i % 4 + workers[w_idx].send(job) + end + results = [] + jobs.size.times do + result = port.receive # dnt receive + results << result + end + results + end + threads = [] + # creates 40 ractors (THREADSx4) + THREADS.times do + threads << Thread.new do + ractor_job(JOBS_PER_THREAD, ARRAY_SIZE) + end + end + threads.each(&:join) + RUBY + end + + # [Bug #20146] + def test_max_cpu_1 + assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }]) + assert_equal :ok, Ractor.new { :ok }.value + RUBY + end + + def test_symbol_proc_is_shareable + pr = :symbol.to_proc + assert_make_shareable(pr) + end + + # [Bug #21775] + def test_ifunc_proc_not_shareable + h = Hash.new { self } + pr = h.to_proc + assert_unshareable(pr, /not supported yet/, exception: RuntimeError) + end + + def assert_make_shareable(obj) + refute Ractor.shareable?(obj), "object was already shareable" + Ractor.make_shareable(obj) + assert Ractor.shareable?(obj), "object didn't become shareable" + end + + def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError) + refute Ractor.shareable?(obj), "object is already shareable" + assert_raise_with_message(exception, msg) do + Ractor.make_shareable(obj) + end + refute Ractor.shareable?(obj), "despite raising, object became shareable" + end +end diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb new file mode 100644 index 0000000000..f177664943 --- /dev/null +++ b/test/ruby/test_rand.rb @@ -0,0 +1,442 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestRand < Test::Unit::TestCase + def assert_random_int(m, init = 0, iterate: 5) + srand(init) + rnds = [Random.new(init)] + rnds2 = [rnds[0].dup] + rnds3 = [rnds[0].dup] + iterate.times do |i| + w = rand(m) + rnds.each do |rnd| + assert_equal(w, rnd.rand(m)) + end + rnds2.each do |rnd| + r=rnd.rand(i...(m+i)) + assert_equal(w+i, r) + end + rnds3.each do |rnd| + r=rnd.rand(i..(m+i-1)) + assert_equal(w+i, r) + end + rnds << Marshal.load(Marshal.dump(rnds[-1])) + rnds2 << Marshal.load(Marshal.dump(rnds2[-1])) + end + end + + def test_mt + assert_random_int(0x100000000, 0x00000456_00000345_00000234_00000123) + end + + def test_0x3fffffff + assert_random_int(0x3fffffff) + end + + def test_0x40000000 + assert_random_int(0x40000000) + end + + def test_0x40000001 + assert_random_int(0x40000001) + end + + def test_0xffffffff + assert_random_int(0xffffffff) + end + + def test_0x100000000 + assert_random_int(0x100000000) + end + + def test_0x100000001 + assert_random_int(0x100000001) + end + + def test_rand_0x100000000 + assert_random_int(0x100000001, 311702798) + end + + def test_0x1000000000000 + assert_random_int(0x1000000000000) + end + + def test_0x1000000000001 + assert_random_int(0x1000000000001) + end + + def test_0x3fffffffffffffff + assert_random_int(0x3fffffffffffffff) + end + + def test_0x4000000000000000 + assert_random_int(0x4000000000000000) + end + + def test_0x4000000000000001 + assert_random_int(0x4000000000000001) + end + + def test_0x10000000000 + assert_random_int(0x10000000000, 3) + end + + def test_0x10000 + assert_random_int(0x10000) + end + + def assert_same_numbers(type, *nums) + nums.each do |n| + assert_instance_of(type, n) + end + x = nums.shift + nums.each do |n| + assert_equal(x, n) + end + x + end + + def test_types + o = Object.new + class << o + def to_int; 100; end + def class; Integer; end + end + + srand(0) + nums = [100.0, (2**100).to_f, (2**100), o, o, o].map do |m| + k = Integer + assert_kind_of(k, x = rand(m), m.inspect) + [m, k, x] + end + assert_kind_of(Integer, rand(-(2**100).to_f)) + + srand(0) + rnd = Random.new(0) + rnd2 = Random.new(0) + nums.each do |m, k, x| + assert_same_numbers(m.class, Random.rand(m), rnd.rand(m), rnd2.rand(m)) + end + end + + def test_srand + srand + assert_kind_of(Integer, rand(2)) + assert_kind_of(Integer, Random.new.rand(2)) + + srand(2**100) + rnd = Random.new(2**100) + r = 3.times.map do + assert_same_numbers(Integer, rand(0x100000000), rnd.rand(0x100000000)) + end + srand(2**100) + r.each do |n| + assert_same_numbers(Integer, n, rand(0x100000000)) + end + end + + def test_shuffle + srand(0) + 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(0x100000000, 2**1000000-1) + end + + def test_random_gc + r = Random.new(0) + 3.times do + assert_kind_of(Integer, r.rand(0x100000000)) + end + GC.start + 3.times do + assert_kind_of(Integer, r.rand(0x100000000)) + end + end + + def test_random_type_error + assert_raise(TypeError) { Random.new(Object.new) } + assert_raise(TypeError) { Random.new(0).rand(Object.new) } + end + + def test_random_argument_error + r = Random.new(0) + assert_raise(ArgumentError) { r.rand(0, 0) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(-1.0) } + assert_raise(ArgumentError, '[ruby-core:24677]') { r.rand(0) } + assert_equal(0, r.rand(1), '[ruby-dev:39166]') + assert_equal(0, r.rand(0...1), '[ruby-dev:39166]') + assert_equal(0, r.rand(0..0), '[ruby-dev:39166]') + assert_equal(0.0, r.rand(0.0..0.0), '[ruby-dev:39166]') + assert_raise(ArgumentError, '[ruby-dev:39166]') { r.rand(0...0) } + 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) } + bug3027 = '[ruby-core:29075]' + assert_raise(ArgumentError, bug3027) { r.rand(nil) } + end + + def test_random_seed + assert_equal(0, Random.new(0).seed) + assert_equal(0x100000000, Random.new(0x100000000).seed) + assert_equal(2**100, Random.new(2**100).seed) + end + + def test_random_dup + r1 = Random.new(0) + r2 = r1.dup + 3.times do + assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000)) + end + r2 = r1.dup + 3.times do + assert_same_numbers(Integer, r1.rand(0x100000000), r2.rand(0x100000000)) + end + end + + def test_random_bytes + srand(0) + r = Random.new(0) + + assert_equal("", r.bytes(0)) + assert_equal("", Random.bytes(0)) + + x = r.bytes(1) + assert_equal(1, x.bytesize) + assert_equal(x, Random.bytes(1)) + + x = r.bytes(10) + assert_equal(10, x.bytesize) + assert_equal(x, Random.bytes(10)) + end + + def test_random_range + srand(0) + r = Random.new(0) + now = Time.now + [5..9, -1000..1000, 2**100+5..2**100+9, 3.1..4, now..(now+2)].each do |range| + 3.times do + x = rand(range) + assert_instance_of(range.first.class, x) + assert_equal(x, r.rand(range)) + assert_include(range, x) + end + end + end + + def test_random_float + r = Random.new(0) + 3.times do + assert_include(0...1.0, r.rand) + end + [2.0, (2**100).to_f].each do |x| + range = 0...x + 3.times do + assert_include(range, r.rand(x), "rand(#{x})") + end + end + + assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(1.0 / 0.0) } + assert_raise(Errno::EDOM, Errno::ERANGE) { r.rand(0.0 / 0.0) } + assert_raise(Errno::EDOM) {r.rand(1..)} + assert_raise(Errno::EDOM) {r.rand(..1)} + + r = Random.new(0) + [1.0...2.0, 1.0...11.0, 2.0...4.0].each do |range| + 3.times do + assert_include(range, r.rand(range), "[ruby-core:24655] rand(#{range})") + end + end + + assert_nothing_raised {r.rand(-Float::MAX..Float::MAX)} + end + + def test_random_equal + r = Random.new(0) + assert_equal(r, r) + assert_equal(r, r.dup) + r1 = r.dup + r2 = r.dup + r1.rand(0x100) + assert_not_equal(r1, r2) + r2.rand(0x100) + 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 + end + _, 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.rand(4) + end + rescue NotImplementedError + end + + def test_seed + bug3104 = '[ruby-core:29292]' + rand_1 = Random.new(-1).rand + assert_not_equal(rand_1, Random.new((1 << 31) -1).rand, "#{bug3104} (2)") + assert_not_equal(rand_1, Random.new((1 << 63) -1).rand, "#{bug3104} (2)") + + [-1, -2**10, -2**40].each {|n| + b = (2**64).coerce(n)[0] + r1 = Random.new(n).rand + r2 = Random.new(b).rand + assert_equal(r1, r2) + } + end + + def test_seed_leading_zero_guard + guard = 1<<32 + range = 0...(1<<32) + all_assertions_foreach(nil, 0, 1, 2) do |i| + assert_not_equal(Random.new(i).rand(range), Random.new(i+guard).rand(range)) + end + end + + def test_marshal + bug3656 = '[ruby-core:31622]' + assert_raise(TypeError, bug3656) { + 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([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + verbose, $VERBOSE = $VERBOSE, nil + seed = Random.seed + rand1 = Random.rand + $VERBOSE = verbose + rand2 = Random.new(seed).rand + assert_equal(rand1, rand2) + + srand seed + rand3 = rand + assert_equal(rand1, rand3) + end; + end + + 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 + + def test_broken_marshal + assert_raise(ArgumentError) { Marshal.load("\x04\bU:\vRandom" + Marshal.dump([1,0,1])[2..]) } + assert_raise(ArgumentError) { Marshal.load("\x04\bU:\vRandom" + Marshal.dump([1,-1,1])[2..]) } + end +end diff --git a/test/ruby/test_random_formatter.rb b/test/ruby/test_random_formatter.rb new file mode 100644 index 0000000000..784fbfc2b8 --- /dev/null +++ b/test/ruby/test_random_formatter.rb @@ -0,0 +1,183 @@ +require 'test/unit' +require 'random/formatter' + +module Random::Formatter + module FormatterTest + def test_random_bytes + assert_equal(16, @it.random_bytes.size) + assert_equal(Encoding::ASCII_8BIT, @it.random_bytes.encoding) + 65.times do |idx| + assert_equal(idx, @it.random_bytes(idx).size) + end + end + + def test_hex + s = @it.hex + assert_equal(16 * 2, s.size) + assert_match(/\A\h+\z/, s) + 33.times do |idx| + s = @it.hex(idx) + assert_equal(idx * 2, s.size) + assert_match(/\A\h*\z/, s) + end + end + + def test_hex_encoding + assert_equal(Encoding::US_ASCII, @it.hex.encoding) + end + + def test_base64 + assert_equal(16, @it.base64.unpack1('m*').size) + 17.times do |idx| + assert_equal(idx, @it.base64(idx).unpack1('m*').size) + end + end + + def test_urlsafe_base64 + safe = /[\n+\/]/ + 65.times do |idx| + assert_not_match(safe, @it.urlsafe_base64(idx)) + end + # base64 can include unsafe byte + assert((0..10000).any? {|idx| safe =~ @it.base64(idx)}, "None of base64(0..10000) is url-safe") + end + + def test_random_number_float + 101.times do + v = @it.random_number + assert_in_range(0.0...1.0, v) + end + end + + def test_random_number_float_by_zero + 101.times do + v = @it.random_number(0) + assert_in_range(0.0...1.0, v) + end + end + + def test_random_number_int + 101.times do |idx| + next if idx.zero? + v = @it.random_number(idx) + assert_in_range(0...idx, v) + end + end + + def test_uuid + uuid = @it.uuid + assert_equal(36, uuid.size) + + # Check time_hi_and_version and clock_seq_hi_res bits (RFC 4122 4.4) + assert_equal('4', uuid[14]) + assert_include(%w'8 9 a b', uuid[19]) + + assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid) + end + + def assert_uuid_v7(**opts) + t1 = current_uuid7_time(**opts) + uuid = @it.uuid_v7(**opts) + t3 = current_uuid7_time(**opts) + + assert_match(/\A\h{8}-\h{4}-7\h{3}-[89ab]\h{3}-\h{12}\z/, uuid) + + t2 = get_uuid7_time(uuid, **opts) + assert_operator(t1, :<=, t2) + assert_operator(t2, :<=, t3) + end + + def test_uuid_v7 + assert_uuid_v7 + 0.upto(12) do |extra_timestamp_bits| + assert_uuid_v7 extra_timestamp_bits: extra_timestamp_bits + end + end + + # It would be nice to simply use Time#floor here. But that is problematic + # due to the difference between decimal vs binary fractions. + def current_uuid7_time(extra_timestamp_bits: 0) + denominator = (1 << extra_timestamp_bits).to_r + Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) + .then {|ns| ((ns / 1_000_000r) * denominator).floor / denominator } + .then {|ms| Time.at(ms / 1000r, in: "+00:00") } + end + + def get_uuid7_time(uuid, extra_timestamp_bits: 0) + denominator = (1 << extra_timestamp_bits) * 1000r + extra_chars = extra_timestamp_bits / 4 + last_char_bits = extra_timestamp_bits % 4 + extra_chars += 1 if last_char_bits != 0 + timestamp_re = /\A(\h{8})-(\h{4})-7(\h{#{extra_chars}})/ + timestamp_chars = uuid.match(timestamp_re).captures.join + timestamp = timestamp_chars.to_i(16) + timestamp >>= 4 - last_char_bits unless last_char_bits == 0 + timestamp /= denominator + Time.at timestamp, in: "+00:00" + end + + def test_alphanumeric + 65.times do |n| + an = @it.alphanumeric(n) + assert_match(/\A[0-9a-zA-Z]*\z/, an) + assert_equal(n, an.length) + end + end + + def test_alphanumeric_chars + [ + [[*"0".."9"], /\A\d*\z/], + [[*"a".."t"], /\A[a-t]*\z/], + ["一二三四五å…七八ä¹å".chars, /\A[一二三四五å…七八ä¹å]*\z/], + ].each do |chars, pattern| + 10.times do |n| + an = @it.alphanumeric(n, chars: chars) + assert_match(pattern, an) + assert_equal(n, an.length) + end + end + end + + def assert_in_range(range, result, mesg = nil) + assert(range.cover?(result), build_message(mesg, "Expected #{result} to be in #{range}")) + end + end + + module NotDefaultTest + def test_random_number_not_default + msg = "random_number should not be affected by srand" + seed = srand(0) + x = @it.random_number(1000) + 10.times do|i| + srand(0) + return unless @it.random_number(1000) == x + end + srand(0) + assert_not_equal(x, @it.random_number(1000), msg) + ensure + srand(seed) if seed + end + end + + class TestClassMethods < Test::Unit::TestCase + include FormatterTest + + def setup + @it = Random + end + + def test_alphanumeric_frozen + assert_predicate @it::Formatter::ALPHANUMERIC, :frozen? + assert @it::Formatter::ALPHANUMERIC.all?(&:frozen?) + end + end + + class TestInstanceMethods < Test::Unit::TestCase + include FormatterTest + include NotDefaultTest + + def setup + @it = Random.new + end + end +end diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb new file mode 100644 index 0000000000..ff17dca69e --- /dev/null +++ b/test/ruby/test_range.rb @@ -0,0 +1,1551 @@ +# frozen_string_literal: false +require 'test/unit' +require 'delegate' +require 'timeout' +require 'date' +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)) + assert_equal([*"a".."z"], eval("('a' || 'b')..'z'").to_a) + 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 + + def test_range_symbol + assert_equal([:a, :b], (:a .. :b).to_a) + end + + def test_evaluation_order + arr = [1,2] + r = (arr.shift)..(arr.shift) + assert_equal(1..2, r, "[ruby-dev:26383]") + end + + class DuckRange + def initialize(b,e,excl=false) + @begin = b + @end = e + @excl = excl + end + attr_reader :begin, :end + + def exclude_end? + @excl + end + end + + def test_duckrange + assert_equal("bc", "abcd"[DuckRange.new(1,2)]) + end + + def test_min + assert_equal(1, (1..2).min) + assert_equal(nil, (2..1).min) + assert_equal(1, (1...2).min) + assert_equal(1, (1..).min) + assert_raise(RangeError) { (..1).min } + assert_raise(RangeError) { (...1).min } + + assert_equal(1.0, (1.0..2.0).min) + assert_equal(nil, (2.0..1.0).min) + assert_equal(1, (1.0...2.0).min) + assert_equal(1, (1.0..).min) + + assert_equal(0, (0..0).min) + assert_equal(nil, (0...0).min) + + assert_equal([0,1,2], (0..10).min(3)) + assert_equal([0,1], (0..1).min(3)) + assert_equal([0,1,2], (0..).min(3)) + assert_raise(RangeError) { (..1).min(3) } + assert_raise(RangeError) { (...1).min(3) } + + assert_raise(RangeError) { (0..).min {|a, b| a <=> b } } + end + + def test_max + assert_equal(2, (1..2).max) + assert_equal(nil, (2..1).max) + assert_equal(1, (1...2).max) + assert_raise(RangeError) { (1..).max } + assert_raise(RangeError) { (1...).max } + + assert_equal(2.0, (1.0..2.0).max) + assert_equal(nil, (2.0..1.0).max) + 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_equal([10,9,8], (..10).max(3)) + assert_equal([9,8,7], (...10).max(3)) + assert_raise(RangeError) { (1..).max(3) } + assert_raise(RangeError) { (1...).max(3) } + + assert_raise(RangeError) { (..0).min {|a, b| a <=> b } } + + assert_equal(2, (..2).max) + assert_equal(1, (...2).max) + assert_raise(TypeError) { (...2.0).max } + + assert_equal(Float::INFINITY, (1..Float::INFINITY).max) + assert_nil((1..-Float::INFINITY).max) + end + + def test_minmax + assert_equal([1, 2], (1..2).minmax) + assert_equal([nil, nil], (2..1).minmax) + assert_equal([1, 1], (1...2).minmax) + assert_raise(RangeError) { (1..).minmax } + assert_raise(RangeError) { (1...).minmax } + + assert_equal([1.0, 2.0], (1.0..2.0).minmax) + assert_equal([nil, nil], (2.0..1.0).minmax) + assert_raise(TypeError) { (1.0...2.0).minmax } + assert_raise(TypeError) { (1...1.5).minmax } + assert_raise(TypeError) { (1.5...2).minmax } + + assert_equal([-0x80000002, -0x80000002], ((-0x80000002)...(-0x80000001)).minmax) + + assert_equal([0, 0], (0..0).minmax) + assert_equal([nil, nil], (0...0).minmax) + + assert_equal([2, 1], (1..2).minmax{|a, b| b <=> a}) + + assert_equal(['a', 'c'], ('a'..'c').minmax) + assert_equal(['a', 'b'], ('a'...'c').minmax) + + assert_equal([1, Float::INFINITY], (1..Float::INFINITY).minmax) + assert_equal([nil, nil], (1..-Float::INFINITY).minmax) + end + + def test_initialize_twice + r = eval("1..2") + assert_raise(FrozenError) { r.instance_eval { initialize 3, 4 } } + assert_raise(FrozenError) { r.instance_eval { initialize_copy 3..4 } } + end + + def test_uninitialized_range + r = Range.allocate + s = Marshal.dump(r) + r = Marshal.load(s) + 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_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_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_equal(r, subclass.new(0,nil)) + end + + def test_eql + r = (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_operator(r, :eql?, subclass.new(0,1)) + + r = (0..nil) + assert_operator(r, :eql?, r) + assert_operator(r, :eql?, 0..nil) + assert_not_operator(r, :eql?, 0) + assert_not_operator(r, :eql?, 0...nil) + subclass = Class.new(Range) + assert_operator(r, :eql?, subclass.new(0,nil)) + end + + def test_hash + assert_kind_of(Integer, (0..1).hash) + assert_equal((0..1).hash, (0..1).hash) + assert_not_equal((0..1).hash, (0...1).hash) + assert_equal((0..nil).hash, (0..nil).hash) + assert_not_equal((0..nil).hash, (0...nil).hash) + assert_kind_of(String, (0..1).hash.to_s) + end + + def test_step_numeric_range + # Fixnums, floats and all other numbers (like rationals) should behave exactly the same, + # but the behavior is implemented independently in 3 different branches of code, + # so we need to test each of them. + %i[to_i to_r to_f].each do |type| + conv = type.to_proc + + from = conv.(0) + to = conv.(10) + step = conv.(2) + + # finite + a = [] + (from..to).step(step) {|x| a << x } + assert_equal([0, 2, 4, 6, 8, 10].map(&conv), a) + + a = [] + (from...to).step(step) {|x| a << x } + assert_equal([0, 2, 4, 6, 8].map(&conv), a) + + # Note: ArithmeticSequence behavior tested in its own test, but we also put it here + # to demonstrate the result is the same + assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step(step)) + assert_equal([0, 2, 4, 6, 8, 10].map(&conv), (from..to).step(step).to_a) + assert_kind_of(Enumerator::ArithmeticSequence, (from...to).step(step)) + assert_equal([0, 2, 4, 6, 8].map(&conv), (from...to).step(step).to_a) + + # endless + a = [] + (from..).step(step) {|x| a << x; break if a.size == 5 } + assert_equal([0, 2, 4, 6, 8].map(&conv), a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from..).step(step)) + assert_equal([0, 2, 4, 6, 8].map(&conv), (from..).step(step).take(5)) + + # beginless + assert_raise(ArgumentError) { (..to).step(step) {} } + assert_kind_of(Enumerator::ArithmeticSequence, (..to).step(step)) + # This is inconsistent, but so it is implemented by ArithmeticSequence + assert_raise(TypeError) { (..to).step(step).to_a } + + # negative step + + a = [] + (from..to).step(-step) {|x| a << x } + assert_equal([], a) + + a = [] + (from..-to).step(-step) {|x| a << x } + assert_equal([0, -2, -4, -6, -8, -10].map(&conv), a) + + a = [] + (from...-to).step(-step) {|x| a << x } + assert_equal([0, -2, -4, -6, -8].map(&conv), a) + + a = [] + (from...).step(-step) {|x| a << x; break if a.size == 5 } + assert_equal([0, -2, -4, -6, -8].map(&conv), a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step(-step)) + assert_equal([], (from..to).step(-step).to_a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from..-to).step(-step)) + assert_equal([0, -2, -4, -6, -8, -10].map(&conv), (from..-to).step(-step).to_a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from...-to).step(-step)) + assert_equal([0, -2, -4, -6, -8].map(&conv), (from...-to).step(-step).to_a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from...).step(-step)) + assert_equal([0, -2, -4, -6, -8].map(&conv), (from...).step(-step).take(5)) + + # zero step + + assert_raise(ArgumentError) { (from..to).step(0) {} } + assert_raise(ArgumentError) { (from..to).step(0) } + + # default step + + a = [] + (from..to).step {|x| a << x } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&conv), a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from..to).step) + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&conv), (from..to).step.to_a) + + # default + endless range + a = [] + (from..).step {|x| a << x; break if a.size == 5 } + assert_equal([0, 1, 2, 3, 4].map(&conv), a) + + assert_kind_of(Enumerator::ArithmeticSequence, (from..).step) + assert_equal([0, 1, 2, 3, 4].map(&conv), (from..).step.take(5)) + + # default + beginless range + assert_kind_of(Enumerator::ArithmeticSequence, (..to).step) + + # step is not numeric + + to = conv.(5) + + val = Struct.new(:val) + + a = [] + assert_raise(TypeError) { (from..to).step(val.new(step)) {|x| a << x } } + assert_kind_of(Enumerator, (from..to).step(val.new(step))) + assert_raise(TypeError) { (from..to).step(val.new(step)).to_a } + + # step is not numeric, but coercible + val = Struct.new(:val) do + def coerce(num) = [self.class.new(num), self] + def +(other) = self.class.new(val + other.val) + def <=>(other) = other.is_a?(self.class) ? val <=> other.val : val <=> other + end + + a = [] + (from..to).step(val.new(step)) {|x| a << x } + assert_equal([from, val.new(conv.(2)), val.new(conv.(4))], a) + + assert_kind_of(Enumerator, (from..to).step(val.new(step))) + assert_equal([from, val.new(conv.(2)), val.new(conv.(4))], (from..to).step(val.new(step)).to_a) + end + end + + def test_step_numeric_fixnum_boundary + a = [] + (2**32-1 .. 2**32+1).step(2) {|x| a << x } + assert_equal([4294967295, 4294967297], a) + + zero = (2**32).coerce(0).first + assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) } + assert_raise(ArgumentError) { (2**32-1 .. 2**32+1).step(zero) { } } + + a = [] + (2**32-1 .. ).step(2) {|x| a << x; break if a.size == 2 } + assert_equal([4294967295, 4294967297], a) + + max = RbConfig::LIMITS["FIXNUM_MAX"] + a = [] + (max..).step {|x| a << x; break if a.size == 2 } + assert_equal([max, max+1], a) + + a = [] + (max..).step(max) {|x| a << x; break if a.size == 4 } + assert_equal([max, 2*max, 3*max, 4*max], a) + end + + def test_step_big_float + a = [] + (0x40000000..0x40000002).step(0.5) {|x| a << x } + assert_equal([1073741824, 1073741824.5, 1073741825.0, 1073741825.5, 1073741826], a) + end + + def test_step_non_numeric_range + # finite + a = [] + ('a'..'aaaa').step('a') { a << _1 } + assert_equal(%w[a aa aaa aaaa], a) + + assert_kind_of(Enumerator, ('a'..'aaaa').step('a')) + assert_equal(%w[a aa aaa aaaa], ('a'..'aaaa').step('a').to_a) + + a = [] + ('a'...'aaaa').step('a') { a << _1 } + assert_equal(%w[a aa aaa], a) + + assert_kind_of(Enumerator, ('a'...'aaaa').step('a')) + assert_equal(%w[a aa aaa], ('a'...'aaaa').step('a').to_a) + + # endless + a = [] + ('a'...).step('a') { a << _1; break if a.size == 3 } + assert_equal(%w[a aa aaa], a) + + assert_kind_of(Enumerator, ('a'...).step('a')) + assert_equal(%w[a aa aaa], ('a'...).step('a').take(3)) + + # beginless + assert_raise(ArgumentError) { (...'aaa').step('a') {} } + assert_raise(ArgumentError) { (...'aaa').step('a') } + + # step is not provided + assert_raise(ArgumentError) { (Time.new(2022)...Time.new(2023)).step } + + # step is incompatible + assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a') {} } + assert_raise(TypeError) { (Time.new(2022)...Time.new(2023)).step('a').to_a } + + # step is compatible, but shouldn't convert into numeric domain: + a = [] + (Time.utc(2022, 2, 24)...).step(1) { a << _1; break if a.size == 2 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a) + + a = [] + (Time.utc(2022, 2, 24)...).step(1.0) { a << _1; break if a.size == 2 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a) + + a = [] + (Time.utc(2022, 2, 24)...).step(1r) { a << _1; break if a.size == 2 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 24, 0, 0, 1)], a) + + # step decreases the value + a = [] + (Time.utc(2022, 2, 24)...).step(-1) { a << _1; break if a.size == 2 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59)], a) + + a = [] + (Time.utc(2022, 2, 24)...Time.utc(2022, 2, 23, 23, 59, 57)).step(-1) { a << _1 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59), + Time.utc(2022, 2, 23, 23, 59, 58)], a) + + a = [] + (Time.utc(2022, 2, 24)..Time.utc(2022, 2, 23, 23, 59, 57)).step(-1) { a << _1 } + assert_equal([Time.utc(2022, 2, 24), Time.utc(2022, 2, 23, 23, 59, 59), + Time.utc(2022, 2, 23, 23, 59, 58), Time.utc(2022, 2, 23, 23, 59, 57)], a) + + # step decreases, but the range is forward-directed: + a = [] + (Time.utc(2022, 2, 24)...Time.utc(2022, 2, 24, 01, 01, 03)).step(-1) { a << _1 } + assert_equal([], a) + end + + def test_step_string_legacy + # finite + a = [] + ('a'..'g').step(2) { a << _1 } + assert_equal(%w[a c e g], a) + + assert_kind_of(Enumerator, ('a'..'g').step(2)) + assert_equal(%w[a c e g], ('a'..'g').step(2).to_a) + + a = [] + ('a'...'g').step(2) { a << _1 } + assert_equal(%w[a c e], a) + + assert_kind_of(Enumerator, ('a'...'g').step(2)) + assert_equal(%w[a c e], ('a'...'g').step(2).to_a) + + # endless + a = [] + ('a'...).step(2) { a << _1; break if a.size == 3 } + assert_equal(%w[a c e], a) + + assert_kind_of(Enumerator, ('a'...).step(2)) + assert_equal(%w[a c e], ('a'...).step(2).take(3)) + + # beginless + assert_raise(ArgumentError) { (...'g').step(2) {} } + assert_raise(ArgumentError) { (...'g').step(2) } + + # step is not provided + a = [] + ('a'..'d').step { a << _1 } + assert_equal(%w[a b c d], a) + + assert_kind_of(Enumerator, ('a'..'d').step) + assert_equal(%w[a b c d], ('a'..'d').step.to_a) + + a = [] + ('a'...'d').step { a << _1 } + assert_equal(%w[a b c], a) + + assert_kind_of(Enumerator, ('a'...'d').step) + assert_equal(%w[a b c], ('a'...'d').step.to_a) + + # endless + a = [] + ('a'...).step { a << _1; break if a.size == 3 } + assert_equal(%w[a b c], a) + + assert_kind_of(Enumerator, ('a'...).step) + assert_equal(%w[a b c], ('a'...).step.take(3)) + end + + def test_step_symbol_legacy + # finite + a = [] + (:a..:g).step(2) { a << _1 } + assert_equal(%i[a c e g], a) + + assert_kind_of(Enumerator, (:a..:g).step(2)) + assert_equal(%i[a c e g], (:a..:g).step(2).to_a) + + a = [] + (:a...:g).step(2) { a << _1 } + assert_equal(%i[a c e], a) + + assert_kind_of(Enumerator, (:a...:g).step(2)) + assert_equal(%i[a c e], (:a...:g).step(2).to_a) + + # endless + a = [] + (:a...).step(2) { a << _1; break if a.size == 3 } + assert_equal(%i[a c e], a) + + assert_kind_of(Enumerator, (:a...).step(2)) + assert_equal(%i[a c e], (:a...).step(2).take(3)) + + # beginless + assert_raise(ArgumentError) { (...:g).step(2) {} } + assert_raise(ArgumentError) { (...:g).step(2) } + + # step is not provided + a = [] + (:a..:d).step { a << _1 } + assert_equal(%i[a b c d], a) + + assert_kind_of(Enumerator, (:a..:d).step) + assert_equal(%i[a b c d], (:a..:d).step.to_a) + + a = [] + (:a...:d).step { a << _1 } + assert_equal(%i[a b c], a) + + assert_kind_of(Enumerator, (:a...:d).step) + assert_equal(%i[a b c], (:a...:d).step.to_a) + + # endless + a = [] + (:a...).step { a << _1; break if a.size == 3 } + assert_equal(%i[a b c], a) + + assert_kind_of(Enumerator, (:a...).step) + assert_equal(%i[a b c], (:a...).step.take(3)) + end + + def test_step_bug15537 + assert_equal([10.0, 9.0, 8.0, 7.0], (10 ..).step(-1.0).take(4)) + assert_equal([10.0, 9.0, 8.0, 7.0], (10.0 ..).step(-1).take(4)) + end + + def test_percent_step + aseq = (1..10) % 2 + assert_equal(Enumerator::ArithmeticSequence, aseq.class) + assert_equal(1, aseq.begin) + assert_equal(10, aseq.end) + assert_equal(2, aseq.step) + assert_equal([1, 3, 5, 7, 9], aseq.to_a) + end + + def test_step_ruby_core_35753 + 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_step_with_nonnumeric_endpoint + num = Data.define(:value) do + def coerce(o); [o, 100]; end + def <=>(o) value<=>o; end + def +(o) with(value: value + o) end + end + i = num.new(100) + + assert_equal([100], (100..100).step(10).to_a) + assert_equal([], (100...100).step(10).to_a) + assert_equal([100], (100..i).step(10).to_a) + assert_equal([i], (i..100).step(10).to_a) + assert_equal([], (100...i).step(10).to_a) + assert_equal([], (i...100).step(10).to_a) + end + + def test_each + a = [] + (0..10).each {|x| a << x } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + + a = [] + (0..).each {|x| a << x; break if a.size == 10 } + assert_equal([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], a) + + o1 = Object.new + o2 = Object.new + def o1.setcmp(v) @cmpresult = v end + o1.setcmp(-1) + def o1.<=>(x); @cmpresult; end + def o2.setcmp(v) @cmpresult = v end + o2.setcmp(0) + def o2.<=>(x); @cmpresult; end + class << o1; self; end.class_eval do + define_method(:succ) { o2 } + end + + r1 = (o1..o2) + r2 = (o1...o2) + + a = [] + r1.each {|x| a << x } + assert_equal([o1, o2], a) + + a = [] + r2.each {|x| a << x } + assert_equal([o1], a) + + o2.setcmp(1) + + a = [] + r1.each {|x| a << x } + assert_equal([o1], a) + + o2.setcmp(nil) + + a = [] + r1.each {|x| a << x } + assert_equal([o1], a) + + o1.setcmp(nil) + + a = [] + r2.each {|x| a << x } + assert_equal([], a) + + o = Object.new + class << o + def to_str() "a" end + def <=>(other) to_str <=> other end + end + + a = [] + (o.."c").each {|x| a << x} + assert_equal(["a", "b", "c"], a) + a = [] + (o..).each {|x| a << x; break if a.size >= 3} + assert_equal(["a", "b", "c"], a) + end + + def test_each_with_succ + c = Struct.new(:i) do + def succ; self.class.new(i+1); end + def <=>(other) i <=> other.i;end + end.new(0) + + result = [] + (c..c.succ).each do |d| + result << d.i + end + assert_equal([0, 1], result) + + result = [] + (c..).each do |d| + result << d.i + break if d.i >= 4 + end + assert_equal([0, 1, 2, 3, 4], result) + end + + def test_reverse_each + a = [] + (1..3).reverse_each {|x| a << x } + assert_equal([3, 2, 1], a) + + a = [] + (1...3).reverse_each {|x| a << x } + assert_equal([2, 1], a) + + fmax = RbConfig::LIMITS['FIXNUM_MAX'] + fmin = RbConfig::LIMITS['FIXNUM_MIN'] + + a = [] + (fmax+1..fmax+3).reverse_each {|x| a << x } + assert_equal([fmax+3, fmax+2, fmax+1], a) + + a = [] + (fmax+1...fmax+3).reverse_each {|x| a << x } + assert_equal([fmax+2, fmax+1], a) + + a = [] + (fmax-1..fmax+1).reverse_each {|x| a << x } + assert_equal([fmax+1, fmax, fmax-1], a) + + a = [] + (fmax-1...fmax+1).reverse_each {|x| a << x } + assert_equal([fmax, fmax-1], a) + + a = [] + (fmin-1..fmin+1).reverse_each{|x| a << x } + assert_equal([fmin+1, fmin, fmin-1], a) + + a = [] + (fmin-1...fmin+1).reverse_each{|x| a << x } + assert_equal([fmin, fmin-1], a) + + a = [] + (fmin-3..fmin-1).reverse_each{|x| a << x } + assert_equal([fmin-1, fmin-2, fmin-3], a) + + a = [] + (fmin-3...fmin-1).reverse_each{|x| a << x } + assert_equal([fmin-2, fmin-3], a) + + a = [] + ("a".."c").reverse_each {|x| a << x } + assert_equal(["c", "b", "a"], a) + end + + def test_reverse_each_for_beginless_range + fmax = RbConfig::LIMITS['FIXNUM_MAX'] + fmin = RbConfig::LIMITS['FIXNUM_MIN'] + + a = [] + (..3).reverse_each {|x| a << x; break if x <= 0 } + assert_equal([3, 2, 1, 0], a) + + a = [] + (...3).reverse_each {|x| a << x; break if x <= 0 } + assert_equal([2, 1, 0], a) + + a = [] + (..fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 } + assert_equal([fmax+1, fmax, fmax-1], a) + + a = [] + (...fmax+1).reverse_each {|x| a << x; break if x <= fmax-1 } + assert_equal([fmax, fmax-1], a) + + a = [] + (..fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 } + assert_equal([fmin+1, fmin, fmin-1], a) + + a = [] + (...fmin+1).reverse_each {|x| a << x; break if x <= fmin-1 } + assert_equal([fmin, fmin-1], a) + + a = [] + (..fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 } + assert_equal([fmin-1, fmin-2, fmin-3], a) + + a = [] + (...fmin-1).reverse_each {|x| a << x; break if x <= fmin-3 } + assert_equal([fmin-2, fmin-3], a) + end + + def test_reverse_each_for_endless_range + assert_raise(TypeError) { (1..).reverse_each {} } + + enum = nil + assert_nothing_raised { enum = (1..).reverse_each } + assert_raise(TypeError) { enum.each {} } + end + + def test_reverse_each_for_single_point_range + fmin = RbConfig::LIMITS['FIXNUM_MIN'] + fmax = RbConfig::LIMITS['FIXNUM_MAX'] + + values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2] + + values.each do |b| + r = b..b + a = [] + r.reverse_each {|x| a << x } + assert_equal([b], a, "failed on #{r}") + + r = b...b+1 + a = [] + r.reverse_each {|x| a << x } + assert_equal([b], a, "failed on #{r}") + end + end + + def test_reverse_each_for_empty_range + fmin = RbConfig::LIMITS['FIXNUM_MIN'] + fmax = RbConfig::LIMITS['FIXNUM_MAX'] + + values = [fmin*2, fmin-1, fmin, 0, fmax, fmax+1, fmax*2] + + values.each do |b| + r = b..b-1 + a = [] + r.reverse_each {|x| a << x } + assert_equal([], a, "failed on #{r}") + end + + values.repeated_permutation(2).to_a.product([true, false]).each do |(b, e), excl| + next unless b > e || (b == e && excl) + + r = Range.new(b, e, excl) + a = [] + r.reverse_each {|x| a << x } + assert_equal([], a, "failed on #{r}") + end + end + + def test_reverse_each_with_no_block + enum = (1..5).reverse_each + assert_equal 5, enum.size + + a = [] + enum.each {|x| a << x } + assert_equal [5, 4, 3, 2, 1], a + end + + def test_reverse_each_size + assert_equal(3, (1..3).reverse_each.size) + assert_equal(3, (1..3.3).reverse_each.size) + assert_raise(TypeError) { (1..nil).reverse_each.size } + assert_raise(TypeError) { (1.1..3).reverse_each.size } + assert_raise(TypeError) { (1.1..3.3).reverse_each.size } + assert_raise(TypeError) { (1.1..nil).reverse_each.size } + assert_equal(Float::INFINITY, (..3).reverse_each.size) + assert_raise(TypeError) { (nil..3.3).reverse_each.size } + assert_raise(TypeError) { (nil..nil).reverse_each.size } + + assert_equal(2, (1...3).reverse_each.size) + assert_equal(3, (1...3.3).reverse_each.size) + + assert_equal(nil, ('a'..'z').reverse_each.size) + assert_raise(TypeError) { ('a'..).reverse_each.size } + assert_raise(TypeError) { (..'z').reverse_each.size } + 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([8, 9, 10], (nil..10).last(3)) + assert_equal(0, (0..10).first) + assert_equal(10, (0..10).last) + assert_equal(10, (nil..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([7, 8, 9], (nil...10).last(3)) + assert_equal(0, (0...10).first) + assert_equal(10, (0...10).last) + assert_equal(10, (nil...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) } + assert_raise(RangeError) { (nil..0).first } + assert_raise(RangeError) { (nil..0).first(3) } + + assert_equal([0, 1, 2], (0..10).first(3.0)) + assert_equal([8, 9, 10], (0..10).last(3.0)) + assert_raise(TypeError) { (0..10).first("3") } + assert_raise(TypeError) { (0..10).last("3") } + class << (o = Object.new) + def to_int; 3; end + end + assert_equal([0, 1, 2], (0..10).first(o)) + assert_equal([8, 9, 10], (0..10).last(o)) + + assert_raise(ArgumentError) { (0..10).first(-1) } + assert_raise(ArgumentError) { (0..10).last(-1) } + end + + def test_last_with_redefine_each + assert_in_out_err([], <<-'end;', ['true'], []) + class Range + remove_method :each + def each(&b) + [1, 2, 3, 4, 5].each(&b) + end + end + puts [3, 4, 5] == (1..10).last(3) + end; + end + + def test_to_s + assert_equal("0..1", (0..1).to_s) + assert_equal("0...1", (0...1).to_s) + assert_equal("0..", (0..nil).to_s) + assert_equal("0...", (0...nil).to_s) + end + + def test_inspect + assert_equal("0..1", (0..1).inspect) + assert_equal("0...1", (0...1).inspect) + assert_equal("0..", (0..nil).inspect) + assert_equal("0...", (0...nil).inspect) + assert_equal("..1", (nil..1).inspect) + assert_equal("...1", (nil...1).inspect) + assert_equal("nil..nil", (nil..nil).inspect) + assert_equal("nil...nil", (nil...nil).inspect) + end + + def test_eqq + assert_operator(0..10, :===, 5) + assert_not_operator(0..10, :===, 11) + assert_operator(5..nil, :===, 11) + assert_not_operator(5..nil, :===, 0) + assert_operator(nil..10, :===, 0) + assert_operator(nil..nil, :===, 0) + assert_operator(nil..nil, :===, Object.new) + assert_not_operator(0..10, :===, 0..10) + end + + def test_eqq_string + assert_operator('A'..'Z', :===, 'ANA') + assert_not_operator('A'..'Z', :===, 'ana') + assert_operator('A'.., :===, 'ANA') + assert_operator(..'Z', :===, 'ANA') + assert_operator(nil..nil, :===, 'ANA') + 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_unbounded_ruby_bug_19864 + t1 = Date.today + t2 = t1 + 1 + assert_equal(true, (..t1) === t1) + assert_equal(false, (..t1) === t2) + assert_equal(true, (..t2) === t1) + assert_equal(true, (..t2) === t2) + assert_equal(false, (...t1) === t1) + assert_equal(false, (...t1) === t2) + assert_equal(true, (...t2) === t1) + assert_equal(false, (...t2) === t2) + + assert_equal(true, (t1..) === t1) + assert_equal(true, (t1..) === t2) + assert_equal(false, (t2..) === t1) + assert_equal(true, (t2..) === t2) + assert_equal(true, (t1...) === t1) + assert_equal(true, (t1...) === t2) + assert_equal(false, (t2...) === t1) + assert_equal(true, (t2...) === t2) + end + + def test_eqq_non_iteratable + k = Class.new do + include Comparable + attr_reader :i + def initialize(i) @i = i; end + def <=>(o); i <=> o.i; end + end + assert_operator(k.new(0)..k.new(2), :===, k.new(1)) + end + + def test_include + assert_include("a".."z", "c") + assert_not_include("a".."z", "5") + assert_include("a"..."z", "y") + assert_not_include("a"..."z", "z") + assert_not_include("a".."z", "cc") + assert_raise(TypeError) {("a"..).include?("c")} + assert_raise(TypeError) {("a"..).include?("5")} + + assert_include(0...10, 5) + assert_include(5..., 10) + assert_not_include(5..., 0) + assert_raise(TypeError) {(.."z").include?("z")} + assert_raise(TypeError) {(..."z").include?("z")} + assert_include(..10, 10) + assert_not_include(...10, 10) + end + + def test_cover + 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, :cover?, ..2) + assert_operator(..2, :cover?, ..1) + assert_operator(..2, :cover?, 0..1) + assert_operator(2.0..5.0, :cover?, 2..3) + assert_operator(2..5, :cover?, 2.0..3.0) + assert_operator(2..5, :cover?, 2.0...3.0) + assert_operator(2..5, :cover?, 2.0...5.0) + assert_operator(2.0..5.0, :cover?, 2.0...3.0) + assert_operator(2.0..5.0, :cover?, 2.0...5.0) + assert_operator('aa'..'zz', :cover?, 'aa'...'bb') + + assert_not_operator(2..5, :cover?, 1..5) + assert_not_operator(2...6, :cover?, 1..5) + assert_not_operator(2..5, :cover?, 1...6) + assert_not_operator(1..3, :cover?, 1...6) + assert_not_operator(2..5, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2..6) + assert_not_operator(2...6, :cover?, 2...7) + assert_not_operator(2..3, :cover?, 1..4) + assert_not_operator(1..2, :cover?, 1.0..3.0) + assert_not_operator(1.0..2.9, :cover?, 1.0..3.0) + assert_not_operator(1..2, :cover?, 4..3) + assert_not_operator(2..1, :cover?, 1..2) + assert_not_operator(1...2, :cover?, 1...3) + assert_not_operator(2.., :cover?, 1..) + assert_not_operator(2.., :cover?, 1..10) + assert_not_operator(2.., :cover?, ..10) + assert_not_operator(1..10, :cover?, 1..) + assert_not_operator(1..10, :cover?, ..1) + assert_not_operator(1..5, :cover?, 3..2) + assert_not_operator(1..10, :cover?, 3...2) + assert_not_operator(1..10, :cover?, 3...3) + assert_not_operator('aa'..'zz', :cover?, 'aa'...'zzz') + assert_not_operator(1..10, :cover?, 1...10.1) + + assert_operator(..2, :cover?, 1) + assert_operator(..2, :cover?, 2) + assert_not_operator(..2, :cover?, 3) + assert_not_operator(...2, :cover?, 2) + assert_not_operator(..2, :cover?, "2") + assert_operator(..2, :cover?, ..2) + assert_operator(..2, :cover?, ...2) + assert_not_operator(..2, :cover?, .."2") + assert_not_operator(...2, :cover?, ..2) + + assert_not_operator(2.., :cover?, 1) + assert_operator(2.., :cover?, 2) + assert_operator(2..., :cover?, 3) + assert_operator(2.., :cover?, 2) + assert_not_operator(2.., :cover?, "2") + assert_operator(2.., :cover?, 2..) + assert_operator(2.., :cover?, 2...) + assert_not_operator(2.., :cover?, "2"..) + assert_not_operator(2..., :cover?, 2..) + assert_operator(2..., :cover?, 3...) + assert_not_operator(2..., :cover?, 3..) + assert_not_operator(3.., :cover?, 2..) + + assert_operator(nil..., :cover?, Object.new) + assert_operator(nil..., :cover?, nil...) + assert_operator(nil.., :cover?, nil...) + assert_not_operator(nil..., :cover?, nil..) + assert_not_operator(nil..., :cover?, 1..) + end + + def test_beg_len + o = Object.new + assert_raise(TypeError) { [][o] } + class << o; attr_accessor :begin end + o.begin = -10 + assert_raise(TypeError) { [][o] } + class << o; attr_accessor :end end + o.end = 0 + 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]) + o.begin = 0 + assert_equal([0], [0][o]) + o.begin = 2 + o.end = 0 + assert_equal([], [0, 1, 2][o]) + end + + class CyclicRange < Range + def <=>(other); true; end + end + def test_cyclic_range_inspect + o = CyclicRange.allocate + o.instance_eval { initialize(o, 1) } + assert_equal("(... .. ...)..1", o.inspect) + end + + def test_comparison_when_recursive + x = CyclicRange.allocate; x.send(:initialize, x, 1) + y = CyclicRange.allocate; y.send(:initialize, y, 1) + Timeout.timeout(1) { + assert_equal x, y + assert_operator x, :eql?, y + } + + z = CyclicRange.allocate; z.send(:initialize, z, :another) + Timeout.timeout(1) { + assert_not_equal x, z + assert_not_operator x, :eql?, z + } + + x = CyclicRange.allocate + y = CyclicRange.allocate + x.send(:initialize, y, 1) + y.send(:initialize, x, 1) + Timeout.timeout(1) { + assert_equal x, y + assert_operator x, :eql?, y + } + + x = CyclicRange.allocate + z = CyclicRange.allocate + x.send(:initialize, z, 1) + z.send(:initialize, x, :other) + Timeout.timeout(1) { + assert_not_equal x, z + assert_not_operator x, :eql?, z + } + end + + def test_size + Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end| + r = Range.new(beg.send(m1), ende.send(m2), exclude_end) + iterable = true + yielded = [] + begin + r.each { yielded << _1 } + rescue TypeError + iterable = false + end + + if iterable + assert_equal(yielded.size, r.size, "failed on #{r}") + assert_equal(yielded.size, r.each.size, "failed on #{r}") + else + assert_raise(TypeError, "failed on #{r}") { r.size } + assert_raise(TypeError, "failed on #{r}") { r.each.size } + end + end + + assert_nil ("a"..."z").size + + assert_equal Float::INFINITY, (1..).size + assert_raise(TypeError) { (1.0..).size } + assert_raise(TypeError) { (1r..).size } + assert_nil ("a"..).size + + assert_raise(TypeError) { (..1).size } + assert_raise(TypeError) { (..1.0).size } + assert_raise(TypeError) { (..1r).size } + assert_raise(TypeError) { (..'z').size } + + assert_raise(TypeError) { (nil...nil).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 + } + 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 }) + assert_equal( -999_999, (...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 }) + 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, (0...).bsearch {|i| i > bignum * 2 }) + assert_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 }) + assert_equal(-bignum * 2 + 1, (...0).bsearch {|i| i > -bignum * 2 }) + 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 + + def test_to_set + assert_equal(Set[1,2,3,4,5], (1..5).to_set) + assert_equal(Set[1,2,3,4], (1...5).to_set) + assert_raise(RangeError) { (1..).to_set } + end + + def test_beginless_range_iteration + assert_raise(TypeError) { (..1).each { } } + end + + def test_count + assert_equal 42, (1..42).count + assert_equal 41, (1...42).count + assert_equal 0, (42..1).count + assert_equal 0, (42...1).count + assert_equal 2**100, (1..2**100).count + assert_equal 6, (1...6.3).count + assert_equal 4, ('a'..'d').count + assert_equal 3, ('a'...'d').count + + assert_equal(Float::INFINITY, (1..).count) + assert_equal(Float::INFINITY, (..1).count) + end + + def test_overlap? + assert_not_operator(0..2, :overlap?, -2..-1) + assert_not_operator(0..2, :overlap?, -2...0) + assert_operator(0..2, :overlap?, -1..0) + assert_operator(0..2, :overlap?, 1..2) + assert_operator(0..2, :overlap?, 2..3) + assert_not_operator(0..2, :overlap?, 3..4) + assert_not_operator(0...2, :overlap?, 2..3) + + assert_operator(..0, :overlap?, -1..0) + assert_operator(...0, :overlap?, -1..0) + assert_operator(..0, :overlap?, 0..1) + assert_operator(..0, :overlap?, ..1) + assert_not_operator(..0, :overlap?, 1..2) + assert_not_operator(...0, :overlap?, 0..1) + + assert_not_operator(0.., :overlap?, -2..-1) + assert_not_operator(0.., :overlap?, ...0) + assert_operator(0.., :overlap?, -1..0) + assert_operator(0.., :overlap?, ..0) + assert_operator(0.., :overlap?, 0..1) + assert_operator(0.., :overlap?, 1..2) + assert_operator(0.., :overlap?, 1..) + + assert_not_operator((1..3), :overlap?, ('a'..'d')) + assert_not_operator((1..), :overlap?, ('a'..)) + assert_not_operator((..1), :overlap?, (..'a')) + + assert_raise(TypeError) { (0..).overlap?(1) } + assert_raise(TypeError) { (0..).overlap?(nil) } + + assert_operator((1..3), :overlap?, (2..4)) + assert_operator((1...3), :overlap?, (2..3)) + assert_operator((2..3), :overlap?, (1..2)) + assert_operator((..3), :overlap?, (3..)) + assert_operator((nil..nil), :overlap?, (3..)) + assert_operator((nil...nil), :overlap?, (nil..)) + assert_operator((nil..nil), :overlap?, (..3)) + assert_operator((..3), :overlap?, (nil..nil)) + + assert_raise(TypeError) { (1..3).overlap?(1) } + + assert_not_operator((1..2), :overlap?, (2...2)) + assert_not_operator((2...2), :overlap?, (1..2)) + + assert_not_operator((4..1), :overlap?, (2..3)) + assert_not_operator((4..1), :overlap?, (..3)) + assert_not_operator((4..1), :overlap?, (2..)) + + assert_not_operator((1..4), :overlap?, (3..2)) + assert_not_operator((..4), :overlap?, (3..2)) + assert_not_operator((1..), :overlap?, (3..2)) + + assert_not_operator((4..5), :overlap?, (2..3)) + assert_not_operator((4..5), :overlap?, (2...4)) + + assert_not_operator((1..2), :overlap?, (3..4)) + assert_not_operator((1...3), :overlap?, (3..4)) + + assert_not_operator((4..5), :overlap?, (2..3)) + assert_not_operator((4..5), :overlap?, (2...4)) + + assert_not_operator((1..2), :overlap?, (3..4)) + assert_not_operator((1...3), :overlap?, (3..4)) + assert_not_operator((...3), :overlap?, (3..)) + end +end diff --git a/test/ruby/test_rational.rb b/test/ruby/test_rational.rb new file mode 100644 index 0000000000..e0edbde463 --- /dev/null +++ b/test/ruby/test_rational.rb @@ -0,0 +1,1101 @@ +# frozen_string_literal: false +require 'test/unit' + +class RationalSub < Rational; end + +class Rational_Test < Test::Unit::TestCase + + def test_ratsub + c = RationalSub.__send__(:convert, 1) + + assert_kind_of(Numeric, c) + + assert_instance_of(RationalSub, c) + + c2 = c + 1 + assert_instance_of(RationalSub, c2) + c2 = c - 1 + assert_instance_of(RationalSub, c2) + + 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) + + c1 = Rational(1) + assert_equal(c1.hash, c.hash, '[ruby-dev:38850]') + assert_equal([true, true], [c.eql?(c1), c1.eql?(c)]) + end + + def test_eql_p + c = Rational(0) + c2 = Rational(0) + c3 = Rational(1) + + assert_operator(c, :eql?, c2) + assert_not_operator(c, :eql?, c3) + + assert_not_operator(c, :eql?, 0) + end + + def test_hash + h = Rational(1,2).hash + assert_kind_of(Integer, h) + assert_nothing_raised {h.to_s} + + h = {} + h[Rational(0)] = 0 + h[Rational(1,1)] = 1 + h[Rational(2,1)] = 2 + h[Rational(3,1)] = 3 + + assert_equal(4, h.size) + assert_equal(2, h[Rational(2,1)]) + + h[Rational(0,1)] = 9 + assert_equal(4, h.size) + end + + def test_freeze + c = Rational(1) + assert_predicate(c, :frozen?) + assert_instance_of(String, c.to_s) + end + + def test_conv + c = Rational(0,1) + assert_equal(Rational(0,1), c) + + c = Rational(2**32, 2**32) + assert_equal(Rational(2**32,2**32), c) + assert_equal([1,1], [c.numerator,c.denominator]) + + c = Rational(-2**32, 2**32) + assert_equal(Rational(-2**32,2**32), c) + assert_equal([-1,1], [c.numerator,c.denominator]) + + c = Rational(2**32, -2**32) + assert_equal(Rational(2**32,-2**32), c) + assert_equal([-1,1], [c.numerator,c.denominator]) + + c = Rational(-2**32, -2**32) + assert_equal(Rational(-2**32,-2**32), c) + assert_equal([1,1], [c.numerator,c.denominator]) + + c = Rational(Rational(1,2),2) + assert_equal(Rational(1,4), c) + + c = Rational(2,Rational(1,2)) + assert_equal(Rational(4), c) + + c = Rational(Rational(1,2),Rational(1,2)) + assert_equal(Rational(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(Complex(1,2),Complex(1,2)) + assert_equal(Rational(1), c) + + assert_equal(Rational(3),Rational(3)) + assert_equal(Rational(1),Rational(3,3)) + assert_equal(3.3.to_r,Rational(3.3)) + assert_equal(1,Rational(3.3,3.3)) + 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('')} + + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(ArgumentError, /\u{221a 2668}/) { + Rational("\u{221a 2668}") + } + end + + assert_warning('') { + assert_predicate(Rational('1e-99999999999999999999'), :zero?) + } + + assert_raise(TypeError){Rational(Object.new)} + assert_raise(TypeError){Rational(Object.new, Object.new)} + assert_raise(TypeError){Rational(1, Object.new)} + + bug12485 = '[ruby-core:75995] [Bug #12485]' + o = Object.new + def o.to_int; 1; end + assert_equal(1, Rational(o, 1), bug12485) + assert_equal(1, Rational(1, o), bug12485) + assert_equal(1, Rational(o, o), bug12485) + + o = Object.new + def o.to_r; 1/42r; end + assert_equal(1/42r, Rational(o)) + assert_equal(1/84r, Rational(o, 2)) + assert_equal(42, Rational(1, o)) + assert_equal(1, Rational(o, o)) + + o = Object.new + def o.to_r; nil; end + assert_raise(TypeError) { Rational(o) } + assert_raise(TypeError) { Rational(o, 2) } + assert_raise(TypeError) { Rational(1, o) } + assert_raise(TypeError) { Rational(o, o) } + + o = Object.new + def o.to_r; raise; end + assert_raise(RuntimeError) { Rational(o) } + assert_raise(RuntimeError) { Rational(o, 2) } + assert_raise(RuntimeError) { Rational(1, o) } + assert_raise(RuntimeError) { Rational(o, o) } + + assert_raise(ArgumentError){Rational()} + assert_raise(ArgumentError){Rational(1,2,3)} + + if (0.0/0).nan? + assert_raise(FloatDomainError){Rational(0.0/0)} + end + if (1.0/0).infinite? + assert_raise(FloatDomainError){Rational(1.0/0)} + end + + bug16518 = "[ruby-core:96942] [Bug #16518]" + cls = Class.new(Numeric) do + def /(y); 42; end + def to_r; 1r; end + def to_int; 1; end + end + assert_equal(1/2r, Rational(cls.new, 2), bug16518) + end + + def test_attr + c = Rational(4) + + assert_equal(4, c.numerator) + assert_equal(1, c.denominator) + + c = Rational(4,5) + + assert_equal(4, c.numerator) + assert_equal(5, c.denominator) + + c = Rational(4) + + assert_equal(4, c.numerator) + assert_equal(1, c.denominator) + + c = Rational(4,5) + + assert_equal(4, c.numerator) + assert_equal(5, c.denominator) + + c = Rational(4) + + assert_equal(4, c.numerator) + assert_equal(1, c.denominator) + + c = Rational(4,5) + + assert_equal(4, c.numerator) + assert_equal(5, c.denominator) + end + + def test_attr2 + c = Rational(1) + + 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?) + + assert_nil(Rational(0).nonzero?) + assert_nil(Rational(0,1).nonzero?) + assert_equal(Rational(1,1), Rational(1,1).nonzero?) + end + + def test_uplus + assert_equal(Rational(1), +Rational(1)) + assert_equal(Rational(-1), +Rational(-1)) + assert_equal(Rational(1,1), +Rational(1,1)) + assert_equal(Rational(-1,1), +Rational(-1,1)) + assert_equal(Rational(-1,1), +Rational(1,-1)) + assert_equal(Rational(1,1), +Rational(-1,-1)) + end + + def test_negate + assert_equal(Rational(-1), -Rational(1)) + assert_equal(Rational(1), -Rational(-1)) + assert_equal(Rational(-1,1), -Rational(1,1)) + assert_equal(Rational(1,1), -Rational(-1,1)) + assert_equal(Rational(1,1), -Rational(1,-1)) + assert_equal(Rational(-1,1), -Rational(-1,-1)) + end + + def test_add + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(Rational(7,6), c + c2) + + assert_equal(Rational(5,2), c + 2) + assert_equal(2.5, c + 2.0) + end + + def test_sub + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(Rational(-1,6), c - c2) + + assert_equal(Rational(-3,2), c - 2) + assert_equal(-1.5, c - 2.0) + end + + def test_mul + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(Rational(1,3), c * c2) + + assert_equal(Rational(1,1), c * 2) + assert_equal(1.0, c * 2.0) + end + + def test_div + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(Rational(3,4), c / c2) + + assert_equal(Rational(1,4), c / 2) + assert_equal(0.25, c / 2.0) + + 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) + unless Array === exp + exp = [exp] + end + unless Array === act + act = [act] + end + exp.zip(act).each do |e, a| + na = [e, a] + args + assert_equal(*na) + na = [e.class, a] + args + assert_instance_of(*na) + end + end + + def test_idiv + c = Rational(1,2) + c2 = Rational(2,3) + + assert_eql(0, c.div(c2)) + assert_eql(0, c.div(2)) + assert_eql(0, c.div(2.0)) + + c = Rational(301,100) + c2 = Rational(7,5) + + assert_equal(2, c.div(c2)) + assert_equal(-3, c.div(-c2)) + assert_equal(-3, (-c).div(c2)) + assert_equal(2, (-c).div(-c2)) + + c = Rational(301,100) + c2 = Rational(2) + + assert_equal(1, c.div(c2)) + assert_equal(-2, c.div(-c2)) + assert_equal(-2, (-c).div(c2)) + assert_equal(1, (-c).div(-c2)) + + 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 + + def test_modulo + c = Rational(1,2) + c2 = Rational(2,3) + + assert_eql(Rational(1,2), c.modulo(c2)) + assert_eql(Rational(1,2), c.modulo(2)) + assert_eql(0.5, c.modulo(2.0)) + + c = Rational(301,100) + c2 = Rational(7,5) + + assert_equal(Rational(21,100), c.modulo(c2)) + assert_equal(Rational(-119,100), c.modulo(-c2)) + assert_equal(Rational(119,100), (-c).modulo(c2)) + assert_equal(Rational(-21,100), (-c).modulo(-c2)) + + c = Rational(301,100) + c2 = Rational(2) + + assert_equal(Rational(101,100), c.modulo(c2)) + assert_equal(Rational(-99,100), c.modulo(-c2)) + assert_equal(Rational(99,100), (-c).modulo(c2)) + assert_equal(Rational(-101,100), (-c).modulo(-c2)) + + 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 + + def test_divmod + c = Rational(1,2) + c2 = Rational(2,3) + + assert_eql([0, Rational(1,2)], c.divmod(c2)) + assert_eql([0, Rational(1,2)], c.divmod(2)) + assert_eql([0, 0.5], c.divmod(2.0)) + + c = Rational(301,100) + c2 = Rational(7,5) + + assert_equal([2, Rational(21,100)], c.divmod(c2)) + assert_equal([-3, Rational(-119,100)], c.divmod(-c2)) + assert_equal([-3, Rational(119,100)], (-c).divmod(c2)) + assert_equal([2, Rational(-21,100)], (-c).divmod(-c2)) + + c = Rational(301,100) + c2 = Rational(2) + + assert_equal([1, Rational(101,100)], c.divmod(c2)) + assert_equal([-2, Rational(-99,100)], c.divmod(-c2)) + assert_equal([-2, Rational(99,100)], (-c).divmod(c2)) + assert_equal([1, Rational(-101,100)], (-c).divmod(-c2)) + + 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 + + def test_remainder + c = Rational(1,2) + c2 = Rational(2,3) + + assert_eql(Rational(1,2), c.remainder(c2)) + assert_eql(Rational(1,2), c.remainder(2)) + assert_eql(0.5, c.remainder(2.0)) + + c = Rational(301,100) + c2 = Rational(7,5) + + assert_equal(Rational(21,100), c.remainder(c2)) + assert_equal(Rational(21,100), c.remainder(-c2)) + assert_equal(Rational(-21,100), (-c).remainder(c2)) + assert_equal(Rational(-21,100), (-c).remainder(-c2)) + + c = Rational(301,100) + c2 = Rational(2) + + assert_equal(Rational(101,100), c.remainder(c2)) + assert_equal(Rational(101,100), c.remainder(-c2)) + assert_equal(Rational(-101,100), (-c).remainder(c2)) + assert_equal(Rational(-101,100), (-c).remainder(-c2)) + + 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 + + def test_quo + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(Rational(3,4), c.quo(c2)) + + assert_equal(Rational(1,4), c.quo(2)) + assert_equal(0.25, c.quo(2.0)) + end + + def test_fdiv + c = Rational(1,2) + c2 = Rational(2,3) + + assert_equal(0.75, c.fdiv(c2)) + + 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 + c = Rational(1,2) + c2 = Rational(2,3) + + r = c ** c2 + assert_in_delta(0.6299, r, 0.001) + + assert_equal(Rational(1,4), c ** 2) + assert_equal(Rational(4), c ** -2) + assert_equal(Rational(1,4), (-c) ** 2) + assert_equal(Rational(4), (-c) ** -2) + + assert_equal(0.25, c ** 2.0) + assert_equal(4.0, c ** -2.0) + + assert_equal(Rational(1,4), c ** Rational(2)) + assert_equal(Rational(4), c ** Rational(-2)) + + assert_equal(Rational(1), 0 ** Rational(0)) + assert_equal(Rational(1), Rational(0) ** 0) + assert_equal(Rational(1), Rational(0) ** Rational(0)) + + # p ** p + x = 2 ** Rational(2) + assert_equal(Rational(4), x) + assert_instance_of(Rational, x) + assert_equal(4, x.numerator) + assert_equal(1, x.denominator) + + x = Rational(2) ** 2 + assert_equal(Rational(4), x) + 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) + 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) + assert_instance_of(Rational, x) + assert_equal(4, x.numerator) + assert_equal(1, x.denominator) + + x = Rational(-2) ** 2 + assert_equal(Rational(4), x) + 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) + assert_instance_of(Rational, x) + assert_equal(4, x.numerator) + assert_equal(1, x.denominator) + + # p ** -p + x = 2 ** Rational(-2) + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + x = Rational(2) ** -2 + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + x = Rational(2) ** Rational(-2) + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + # -p ** -p + x = (-2) ** Rational(-2) + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + x = Rational(-2) ** -2 + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + x = Rational(-2) ** Rational(-2) + assert_equal(Rational(1,4), x) + assert_instance_of(Rational, x) + assert_equal(1, x.numerator) + assert_equal(4, x.denominator) + + assert_raise(ZeroDivisionError){0 ** -1} + end + + def test_cmp + assert_equal(-1, Rational(-1) <=> Rational(0)) + assert_equal(0, Rational(0) <=> Rational(0)) + assert_equal(+1, Rational(+1) <=> Rational(0)) + + assert_equal(-1, Rational(-1) <=> 0) + assert_equal(0, Rational(0) <=> 0) + assert_equal(+1, Rational(+1) <=> 0) + + assert_equal(-1, Rational(-1) <=> 0.0) + assert_equal(0, Rational(0) <=> 0.0) + assert_equal(+1, Rational(+1) <=> 0.0) + + assert_equal(-1, Rational(1,2) <=> Rational(2,3)) + assert_equal(0, Rational(2,3) <=> Rational(2,3)) + assert_equal(+1, Rational(2,3) <=> Rational(1,2)) + + f = 2**30-1 + b = 2**30 + + assert_equal(0, Rational(f) <=> Rational(f)) + assert_equal(-1, Rational(f) <=> Rational(b)) + assert_equal(+1, Rational(b) <=> Rational(f)) + assert_equal(0, Rational(b) <=> Rational(b)) + + assert_equal(-1, Rational(f-1) <=> Rational(f)) + assert_equal(+1, Rational(f) <=> Rational(f-1)) + assert_equal(-1, Rational(b-1) <=> Rational(b)) + assert_equal(+1, Rational(b) <=> Rational(b-1)) + + 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_nil(Rational(0) <=> nil) + assert_nil(Rational(0) <=> 'foo') + end + + def test_eqeq + assert_equal(Rational(1,1), Rational(1)) + assert_equal(Rational(-1,1), Rational(-1)) + + assert_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 + + 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) + 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, 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| + 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 + + def test_to_s + c = Rational(1,2) + + assert_instance_of(String, c.to_s) + assert_equal('1/2', c.to_s) + + 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) + assert_equal('-1/2', Rational(1,-2).to_s) + assert_equal('1/2', Rational(-1,-2).to_s) + end + + def test_inspect + c = Rational(1,2) + + assert_instance_of(String, c.inspect) + assert_equal('(1/2)', c.inspect) + end + + def test_marshal + c = Rational(1,2) + + s = Marshal.dump(c) + c2 = Marshal.load(s) + assert_equal(c, c2) + 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 + 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'] + + ng[5, 1, '5/-3'] + end + + def test_parse_zero_denominator + assert_raise(ZeroDivisionError) {"1/0".to_r} + assert_raise(ZeroDivisionError) {Rational("1/0")} + end + + def test_Rational_with_invalid_exception + assert_raise(ArgumentError) { + Rational("1/1", exception: 1) + } + end + + def test_Rational_without_exception + assert_nothing_raised(ArgumentError) { + assert_equal(nil, Rational("5/3x", exception: false)) + } + assert_nothing_raised(ZeroDivisionError) { + assert_equal(nil, Rational("1/0", exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(Object.new, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, nil, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, Object.new, exception: false)) + } + + bug12485 = '[ruby-core:75995] [Bug #12485]' + assert_nothing_raised(RuntimeError, bug12485) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(RuntimeError, bug12485) { + o = Object.new + def o.to_int; raise; end + assert_equal(nil, Rational(1, o, exception: false)) + } + + o = Object.new; + def o.to_r; raise; end + assert_nothing_raised(RuntimeError) { + assert_equal(nil, Rational(o, exception: false)) + } + assert_nothing_raised(TypeError) { + assert_equal(nil, Rational(1, o, exception: false)) + } + end + + def test_to_i + assert_equal(1, Rational(3,2).to_i) + assert_equal(1, Integer(Rational(3,2))) + end + + 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 + 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 + c = nil.to_r + assert_equal([0,1], [c.numerator, c.denominator]) + + c = 0.to_r + assert_equal([0,1], [c.numerator, c.denominator]) + + c = 1.to_r + assert_equal([1,1], [c.numerator, c.denominator]) + + c = 1.1.to_r + assert_equal([2476979795053773, 2251799813685248], + [c.numerator, c.denominator]) + + c = Rational(1,2).to_r + assert_equal([1,2], [c.numerator, c.denominator]) + + assert_raise(RangeError){Complex(1,2).to_r} + + if (0.0/0).nan? + assert_raise(FloatDomainError){(0.0/0).to_r} + end + if (1.0/0).infinite? + assert_raise(FloatDomainError){(1.0/0).to_r} + end + end + + def test_rationalize + c = nil.rationalize + assert_equal([0,1], [c.numerator, c.denominator]) + + c = 0.rationalize + assert_equal([0,1], [c.numerator, c.denominator]) + + c = 1.rationalize + assert_equal([1,1], [c.numerator, c.denominator]) + + c = 1.1.rationalize + assert_equal([11, 10], [c.numerator, c.denominator]) + + c = Rational(1,2).rationalize + assert_equal([1,2], [c.numerator, c.denominator]) + + assert_equal(nil.rationalize(Rational(1,10)), Rational(0)) + assert_equal(0.rationalize(Rational(1,10)), Rational(0)) + assert_equal(10.rationalize(Rational(1,10)), Rational(10)) + + r = 0.3333 + assert_equal(r.rationalize, Rational(3333, 10000)) + assert_equal(r.rationalize(Rational(1,10)), Rational(1,3)) + assert_equal(r.rationalize(Rational(-1,10)), Rational(1,3)) + + r = Rational(5404319552844595,18014398509481984) + assert_equal(r.rationalize, r) + assert_equal(r.rationalize(Rational(1,10)), Rational(1,3)) + assert_equal(r.rationalize(Rational(-1,10)), Rational(1,3)) + + r = -0.3333 + assert_equal(r.rationalize, Rational(-3333, 10000)) + assert_equal(r.rationalize(Rational(1,10)), Rational(-1,3)) + assert_equal(r.rationalize(Rational(-1,10)), Rational(-1,3)) + + r = Rational(-5404319552844595,18014398509481984) + assert_equal(r.rationalize, r) + assert_equal(r.rationalize(Rational(1,10)), Rational(-1,3)) + assert_equal(r.rationalize(Rational(-1,10)), Rational(-1,3)) + + assert_raise(RangeError){Complex(1,2).rationalize} + + if (0.0/0).nan? + assert_raise(FloatDomainError){(0.0/0).rationalize} + end + if (1.0/0).infinite? + assert_raise(FloatDomainError){(1.0/0).rationalize} + end + end + + def test_gcdlcm + assert_equal(7, 91.gcd(-49)) + assert_equal(5, 5.gcd(0)) + assert_equal(5, 0.gcd(5)) + assert_equal(70, 14.lcm(35)) + assert_equal(0, 5.lcm(0)) + assert_equal(0, 0.lcm(5)) + assert_equal([5,0], 0.gcdlcm(5)) + assert_equal([5,0], 5.gcdlcm(0)) + + assert_equal(1, 1073741827.gcd(1073741789)) + assert_equal(1152921470247108503, 1073741827.lcm(1073741789)) + + assert_equal(1, 1073741789.gcd(1073741827)) + 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_predicate(1, :real?) + assert_predicate(1.1, :real?) + + assert_equal(1, 1.numerator) + assert_equal(9, 9.numerator) + assert_equal(1, 1.denominator) + assert_equal(1, 9.denominator) + + assert_equal(1.0, 1.0.numerator) + assert_equal(9.0, 9.0.numerator) + assert_equal(1.0, 1.0.denominator) + assert_equal(1.0, 9.0.denominator) + + 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 + assert_raise(NoMethodError){ Rational.new(1) } + assert_raise(NoMethodError){ Rational.new!(1) } + end + + def test_fixed_bug + n = Float::MAX.to_i * 2 + 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 + assert_raise(ArgumentError) { 4r**400000000000000000000 } + exp = 4**40000000 + assert_equal exp, 4r**40000000 + assert_equal 1r/exp, (1/4r)**40000000 + 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 new file mode 100644 index 0000000000..4e96bf621c --- /dev/null +++ b/test/ruby/test_rational2.rb @@ -0,0 +1,1387 @@ +# frozen_string_literal: false +require 'test/unit' + +class Rational_Test2 < Test::Unit::TestCase + + def test_kumi + assert_equal(Rational(1, 1), +Rational(1, 1)) + assert_equal(Rational(-1, 1), -Rational(1, 1)) + assert_equal(Rational(2, 1), + Rational(1, 1) + Rational(1, 1)) + assert_equal(Rational(0, 1), + Rational(1, 1) - Rational(1, 1)) + assert_equal(Rational(1, 1), + Rational(1, 1) * Rational(1, 1)) + assert_equal(Rational(1, 1), + Rational(1, 1) / Rational(1, 1)) + assert_equal(Rational(3, 1), + Rational(1, 1) + Rational(2, 1)) + assert_equal(Rational(-1, 1), + Rational(1, 1) - Rational(2, 1)) + assert_equal(Rational(2, 1), + Rational(1, 1) * Rational(2, 1)) + assert_equal(Rational(1, 2), + Rational(1, 1) / Rational(2, 1)) + assert_equal(Rational(4, 1), + Rational(1, 1) + Rational(3, 1)) + assert_equal(Rational(-2, 1), + Rational(1, 1) - Rational(3, 1)) + assert_equal(Rational(3, 1), + Rational(1, 1) * Rational(3, 1)) + assert_equal(Rational(1, 3), + Rational(1, 1) / Rational(3, 1)) + assert_equal(Rational(1073741790, 1), + Rational(1, 1) + Rational(1073741789, 1)) + assert_equal(Rational(-1073741788, 1), + Rational(1, 1) - Rational(1073741789, 1)) + assert_equal(Rational(1073741789, 1), + Rational(1, 1) * Rational(1073741789, 1)) + assert_equal(Rational(1, 1073741789), + Rational(1, 1) / Rational(1073741789, 1)) + assert_equal(Rational(1073741828, 1), + Rational(1, 1) + Rational(1073741827, 1)) + assert_equal(Rational(-1073741826, 1), + Rational(1, 1) - Rational(1073741827, 1)) + assert_equal(Rational(1073741827, 1), + Rational(1, 1) * Rational(1073741827, 1)) + assert_equal(Rational(1, 1073741827), + Rational(1, 1) / Rational(1073741827, 1)) + assert_equal(Rational(5, 3), + Rational(1, 1) + Rational(2, 3)) + assert_equal(Rational(1, 3), + Rational(1, 1) - Rational(2, 3)) + assert_equal(Rational(2, 3), + Rational(1, 1) * Rational(2, 3)) + assert_equal(Rational(3, 2), + Rational(1, 1) / Rational(2, 3)) + assert_equal(Rational(5, 2), + Rational(1, 1) + Rational(3, 2)) + assert_equal(Rational(-1, 2), + Rational(1, 1) - Rational(3, 2)) + assert_equal(Rational(3, 2), + Rational(1, 1) * Rational(3, 2)) + assert_equal(Rational(2, 3), + Rational(1, 1) / Rational(3, 2)) + assert_equal(Rational(1073741792, 1073741789), + Rational(1, 1) + Rational(3, 1073741789)) + assert_equal(Rational(1073741786, 1073741789), + Rational(1, 1) - Rational(3, 1073741789)) + assert_equal(Rational(3, 1073741789), + Rational(1, 1) * Rational(3, 1073741789)) + assert_equal(Rational(1073741789, 3), + Rational(1, 1) / Rational(3, 1073741789)) + assert_equal(Rational(1073741792, 3), + Rational(1, 1) + Rational(1073741789, 3)) + assert_equal(Rational(-1073741786, 3), + Rational(1, 1) - Rational(1073741789, 3)) + assert_equal(Rational(1073741789, 3), + Rational(1, 1) * Rational(1073741789, 3)) + assert_equal(Rational(3, 1073741789), + Rational(1, 1) / Rational(1073741789, 3)) + assert_equal(Rational(1073741830, 1073741827), + Rational(1, 1) + Rational(3, 1073741827)) + assert_equal(Rational(1073741824, 1073741827), + Rational(1, 1) - Rational(3, 1073741827)) + assert_equal(Rational(3, 1073741827), + Rational(1, 1) * Rational(3, 1073741827)) + assert_equal(Rational(1073741827, 3), + Rational(1, 1) / Rational(3, 1073741827)) + assert_equal(Rational(1073741830, 3), + Rational(1, 1) + Rational(1073741827, 3)) + assert_equal(Rational(-1073741824, 3), + Rational(1, 1) - Rational(1073741827, 3)) + assert_equal(Rational(1073741827, 3), + Rational(1, 1) * Rational(1073741827, 3)) + assert_equal(Rational(3, 1073741827), + Rational(1, 1) / Rational(1073741827, 3)) + assert_equal(Rational(2147483616, 1073741827), + Rational(1, 1) + Rational(1073741789, 1073741827)) + assert_equal(Rational(38, 1073741827), + Rational(1, 1) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1, 1) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1, 1) / Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483616, 1073741789), + Rational(1, 1) + Rational(1073741827, 1073741789)) + assert_equal(Rational(-38, 1073741789), + Rational(1, 1) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1, 1) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1, 1) / Rational(1073741827, 1073741789)) + assert_equal(Rational(2, 1), +Rational(2, 1)) + assert_equal(Rational(-2, 1), -Rational(2, 1)) + assert_equal(Rational(3, 1), + Rational(2, 1) + Rational(1, 1)) + assert_equal(Rational(1, 1), + Rational(2, 1) - Rational(1, 1)) + assert_equal(Rational(2, 1), + Rational(2, 1) * Rational(1, 1)) + assert_equal(Rational(2, 1), + Rational(2, 1) / Rational(1, 1)) + assert_equal(Rational(4, 1), + Rational(2, 1) + Rational(2, 1)) + assert_equal(Rational(0, 1), + Rational(2, 1) - Rational(2, 1)) + assert_equal(Rational(4, 1), + Rational(2, 1) * Rational(2, 1)) + assert_equal(Rational(1, 1), + Rational(2, 1) / Rational(2, 1)) + assert_equal(Rational(5, 1), + Rational(2, 1) + Rational(3, 1)) + assert_equal(Rational(-1, 1), + Rational(2, 1) - Rational(3, 1)) + assert_equal(Rational(6, 1), + Rational(2, 1) * Rational(3, 1)) + assert_equal(Rational(2, 3), + Rational(2, 1) / Rational(3, 1)) + assert_equal(Rational(1073741791, 1), + Rational(2, 1) + Rational(1073741789, 1)) + assert_equal(Rational(-1073741787, 1), + Rational(2, 1) - Rational(1073741789, 1)) + assert_equal(Rational(2147483578, 1), + Rational(2, 1) * Rational(1073741789, 1)) + assert_equal(Rational(2, 1073741789), + Rational(2, 1) / Rational(1073741789, 1)) + assert_equal(Rational(1073741829, 1), + Rational(2, 1) + Rational(1073741827, 1)) + assert_equal(Rational(-1073741825, 1), + Rational(2, 1) - Rational(1073741827, 1)) + assert_equal(Rational(2147483654, 1), + Rational(2, 1) * Rational(1073741827, 1)) + assert_equal(Rational(2, 1073741827), + Rational(2, 1) / Rational(1073741827, 1)) + assert_equal(Rational(8, 3), + Rational(2, 1) + Rational(2, 3)) + assert_equal(Rational(4, 3), + Rational(2, 1) - Rational(2, 3)) + assert_equal(Rational(4, 3), + Rational(2, 1) * Rational(2, 3)) + assert_equal(Rational(3, 1), + Rational(2, 1) / Rational(2, 3)) + assert_equal(Rational(7, 2), + Rational(2, 1) + Rational(3, 2)) + assert_equal(Rational(1, 2), + Rational(2, 1) - Rational(3, 2)) + assert_equal(Rational(3, 1), + Rational(2, 1) * Rational(3, 2)) + assert_equal(Rational(4, 3), + Rational(2, 1) / Rational(3, 2)) + assert_equal(Rational(2147483581, 1073741789), + Rational(2, 1) + Rational(3, 1073741789)) + assert_equal(Rational(2147483575, 1073741789), + Rational(2, 1) - Rational(3, 1073741789)) + assert_equal(Rational(6, 1073741789), + Rational(2, 1) * Rational(3, 1073741789)) + assert_equal(Rational(2147483578, 3), + Rational(2, 1) / Rational(3, 1073741789)) + assert_equal(Rational(1073741795, 3), + Rational(2, 1) + Rational(1073741789, 3)) + assert_equal(Rational(-1073741783, 3), + Rational(2, 1) - Rational(1073741789, 3)) + assert_equal(Rational(2147483578, 3), + Rational(2, 1) * Rational(1073741789, 3)) + assert_equal(Rational(6, 1073741789), + Rational(2, 1) / Rational(1073741789, 3)) + assert_equal(Rational(2147483657, 1073741827), + Rational(2, 1) + Rational(3, 1073741827)) + assert_equal(Rational(2147483651, 1073741827), + Rational(2, 1) - Rational(3, 1073741827)) + assert_equal(Rational(6, 1073741827), + Rational(2, 1) * Rational(3, 1073741827)) + assert_equal(Rational(2147483654, 3), + Rational(2, 1) / Rational(3, 1073741827)) + assert_equal(Rational(1073741833, 3), + Rational(2, 1) + Rational(1073741827, 3)) + assert_equal(Rational(-1073741821, 3), + Rational(2, 1) - Rational(1073741827, 3)) + assert_equal(Rational(2147483654, 3), + Rational(2, 1) * Rational(1073741827, 3)) + assert_equal(Rational(6, 1073741827), + Rational(2, 1) / Rational(1073741827, 3)) + assert_equal(Rational(3221225443, 1073741827), + Rational(2, 1) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741865, 1073741827), + Rational(2, 1) - Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483578, 1073741827), + Rational(2, 1) * Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483654, 1073741789), + Rational(2, 1) / Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225405, 1073741789), + Rational(2, 1) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741751, 1073741789), + Rational(2, 1) - Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483654, 1073741789), + Rational(2, 1) * Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483578, 1073741827), + Rational(2, 1) / Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 1), +Rational(3, 1)) + assert_equal(Rational(-3, 1), -Rational(3, 1)) + assert_equal(Rational(4, 1), + Rational(3, 1) + Rational(1, 1)) + assert_equal(Rational(2, 1), + Rational(3, 1) - Rational(1, 1)) + assert_equal(Rational(3, 1), + Rational(3, 1) * Rational(1, 1)) + assert_equal(Rational(3, 1), + Rational(3, 1) / Rational(1, 1)) + assert_equal(Rational(5, 1), + Rational(3, 1) + Rational(2, 1)) + assert_equal(Rational(1, 1), + Rational(3, 1) - Rational(2, 1)) + assert_equal(Rational(6, 1), + Rational(3, 1) * Rational(2, 1)) + assert_equal(Rational(3, 2), + Rational(3, 1) / Rational(2, 1)) + assert_equal(Rational(6, 1), + Rational(3, 1) + Rational(3, 1)) + assert_equal(Rational(0, 1), + Rational(3, 1) - Rational(3, 1)) + assert_equal(Rational(9, 1), + Rational(3, 1) * Rational(3, 1)) + assert_equal(Rational(1, 1), + Rational(3, 1) / Rational(3, 1)) + assert_equal(Rational(1073741792, 1), + Rational(3, 1) + Rational(1073741789, 1)) + assert_equal(Rational(-1073741786, 1), + Rational(3, 1) - Rational(1073741789, 1)) + assert_equal(Rational(3221225367, 1), + Rational(3, 1) * Rational(1073741789, 1)) + assert_equal(Rational(3, 1073741789), + Rational(3, 1) / Rational(1073741789, 1)) + assert_equal(Rational(1073741830, 1), + Rational(3, 1) + Rational(1073741827, 1)) + assert_equal(Rational(-1073741824, 1), + Rational(3, 1) - Rational(1073741827, 1)) + assert_equal(Rational(3221225481, 1), + Rational(3, 1) * Rational(1073741827, 1)) + assert_equal(Rational(3, 1073741827), + Rational(3, 1) / Rational(1073741827, 1)) + assert_equal(Rational(11, 3), + Rational(3, 1) + Rational(2, 3)) + assert_equal(Rational(7, 3), + Rational(3, 1) - Rational(2, 3)) + assert_equal(Rational(2, 1), + Rational(3, 1) * Rational(2, 3)) + assert_equal(Rational(9, 2), + Rational(3, 1) / Rational(2, 3)) + assert_equal(Rational(9, 2), + Rational(3, 1) + Rational(3, 2)) + assert_equal(Rational(3, 2), + Rational(3, 1) - Rational(3, 2)) + assert_equal(Rational(9, 2), + Rational(3, 1) * Rational(3, 2)) + assert_equal(Rational(2, 1), + Rational(3, 1) / Rational(3, 2)) + assert_equal(Rational(3221225370, 1073741789), + Rational(3, 1) + Rational(3, 1073741789)) + assert_equal(Rational(3221225364, 1073741789), + Rational(3, 1) - Rational(3, 1073741789)) + assert_equal(Rational(9, 1073741789), + Rational(3, 1) * Rational(3, 1073741789)) + assert_equal(Rational(1073741789, 1), + Rational(3, 1) / Rational(3, 1073741789)) + assert_equal(Rational(1073741798, 3), + Rational(3, 1) + Rational(1073741789, 3)) + assert_equal(Rational(-1073741780, 3), + Rational(3, 1) - Rational(1073741789, 3)) + assert_equal(Rational(1073741789, 1), + Rational(3, 1) * Rational(1073741789, 3)) + assert_equal(Rational(9, 1073741789), + Rational(3, 1) / Rational(1073741789, 3)) + assert_equal(Rational(3221225484, 1073741827), + Rational(3, 1) + Rational(3, 1073741827)) + assert_equal(Rational(3221225478, 1073741827), + Rational(3, 1) - Rational(3, 1073741827)) + assert_equal(Rational(9, 1073741827), + Rational(3, 1) * Rational(3, 1073741827)) + assert_equal(Rational(1073741827, 1), + Rational(3, 1) / Rational(3, 1073741827)) + assert_equal(Rational(1073741836, 3), + Rational(3, 1) + Rational(1073741827, 3)) + assert_equal(Rational(-1073741818, 3), + Rational(3, 1) - Rational(1073741827, 3)) + assert_equal(Rational(1073741827, 1), + Rational(3, 1) * Rational(1073741827, 3)) + assert_equal(Rational(9, 1073741827), + Rational(3, 1) / Rational(1073741827, 3)) + assert_equal(Rational(4294967270, 1073741827), + Rational(3, 1) + Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483692, 1073741827), + Rational(3, 1) - Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225367, 1073741827), + Rational(3, 1) * Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225481, 1073741789), + Rational(3, 1) / Rational(1073741789, 1073741827)) + assert_equal(Rational(4294967194, 1073741789), + Rational(3, 1) + Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483540, 1073741789), + Rational(3, 1) - Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225481, 1073741789), + Rational(3, 1) * Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225367, 1073741827), + Rational(3, 1) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 1), +Rational(1073741789, 1)) + assert_equal(Rational(-1073741789, 1), -Rational(1073741789, 1)) + assert_equal(Rational(1073741790, 1), + Rational(1073741789, 1) + Rational(1, 1)) + assert_equal(Rational(1073741788, 1), + Rational(1073741789, 1) - Rational(1, 1)) + assert_equal(Rational(1073741789, 1), + Rational(1073741789, 1) * Rational(1, 1)) + assert_equal(Rational(1073741789, 1), + Rational(1073741789, 1) / Rational(1, 1)) + assert_equal(Rational(1073741791, 1), + Rational(1073741789, 1) + Rational(2, 1)) + assert_equal(Rational(1073741787, 1), + Rational(1073741789, 1) - Rational(2, 1)) + assert_equal(Rational(2147483578, 1), + Rational(1073741789, 1) * Rational(2, 1)) + assert_equal(Rational(1073741789, 2), + Rational(1073741789, 1) / Rational(2, 1)) + assert_equal(Rational(1073741792, 1), + Rational(1073741789, 1) + Rational(3, 1)) + assert_equal(Rational(1073741786, 1), + Rational(1073741789, 1) - Rational(3, 1)) + assert_equal(Rational(3221225367, 1), + Rational(1073741789, 1) * Rational(3, 1)) + assert_equal(Rational(1073741789, 3), + Rational(1073741789, 1) / Rational(3, 1)) + assert_equal(Rational(2147483578, 1), + Rational(1073741789, 1) + Rational(1073741789, 1)) + assert_equal(Rational(0, 1), + Rational(1073741789, 1) - Rational(1073741789, 1)) + assert_equal(Rational(1152921429444920521, 1), + Rational(1073741789, 1) * Rational(1073741789, 1)) + assert_equal(Rational(1, 1), + Rational(1073741789, 1) / Rational(1073741789, 1)) + assert_equal(Rational(2147483616, 1), + Rational(1073741789, 1) + Rational(1073741827, 1)) + assert_equal(Rational(-38, 1), + Rational(1073741789, 1) - Rational(1073741827, 1)) + assert_equal(Rational(1152921470247108503, 1), + Rational(1073741789, 1) * Rational(1073741827, 1)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1073741789, 1) / Rational(1073741827, 1)) + assert_equal(Rational(3221225369, 3), + Rational(1073741789, 1) + Rational(2, 3)) + assert_equal(Rational(3221225365, 3), + Rational(1073741789, 1) - Rational(2, 3)) + assert_equal(Rational(2147483578, 3), + Rational(1073741789, 1) * Rational(2, 3)) + assert_equal(Rational(3221225367, 2), + Rational(1073741789, 1) / Rational(2, 3)) + assert_equal(Rational(2147483581, 2), + Rational(1073741789, 1) + Rational(3, 2)) + assert_equal(Rational(2147483575, 2), + Rational(1073741789, 1) - Rational(3, 2)) + assert_equal(Rational(3221225367, 2), + Rational(1073741789, 1) * Rational(3, 2)) + assert_equal(Rational(2147483578, 3), + Rational(1073741789, 1) / Rational(3, 2)) + assert_equal(Rational(1152921429444920524, 1073741789), + Rational(1073741789, 1) + Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920518, 1073741789), + Rational(1073741789, 1) - Rational(3, 1073741789)) + assert_equal(Rational(3, 1), + Rational(1073741789, 1) * Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920521, 3), + Rational(1073741789, 1) / Rational(3, 1073741789)) + assert_equal(Rational(4294967156, 3), + Rational(1073741789, 1) + Rational(1073741789, 3)) + assert_equal(Rational(2147483578, 3), + Rational(1073741789, 1) - Rational(1073741789, 3)) + assert_equal(Rational(1152921429444920521, 3), + Rational(1073741789, 1) * Rational(1073741789, 3)) + assert_equal(Rational(3, 1), + Rational(1073741789, 1) / Rational(1073741789, 3)) + assert_equal(Rational(1152921470247108506, 1073741827), + Rational(1073741789, 1) + Rational(3, 1073741827)) + assert_equal(Rational(1152921470247108500, 1073741827), + Rational(1073741789, 1) - Rational(3, 1073741827)) + assert_equal(Rational(3221225367, 1073741827), + Rational(1073741789, 1) * Rational(3, 1073741827)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741789, 1) / Rational(3, 1073741827)) + assert_equal(Rational(4294967194, 3), + Rational(1073741789, 1) + Rational(1073741827, 3)) + assert_equal(Rational(2147483540, 3), + Rational(1073741789, 1) - Rational(1073741827, 3)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741789, 1) * Rational(1073741827, 3)) + assert_equal(Rational(3221225367, 1073741827), + Rational(1073741789, 1) / Rational(1073741827, 3)) + assert_equal(Rational(1152921471320850292, 1073741827), + Rational(1073741789, 1) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921469173366714, 1073741827), + Rational(1073741789, 1) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921429444920521, 1073741827), + Rational(1073741789, 1) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741827, 1), + Rational(1073741789, 1) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921430518662348, 1073741789), + Rational(1073741789, 1) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921428371178694, 1073741789), + Rational(1073741789, 1) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 1), + Rational(1073741789, 1) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921429444920521, 1073741827), + Rational(1073741789, 1) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 1), +Rational(1073741827, 1)) + assert_equal(Rational(-1073741827, 1), -Rational(1073741827, 1)) + assert_equal(Rational(1073741828, 1), + Rational(1073741827, 1) + Rational(1, 1)) + assert_equal(Rational(1073741826, 1), + Rational(1073741827, 1) - Rational(1, 1)) + assert_equal(Rational(1073741827, 1), + Rational(1073741827, 1) * Rational(1, 1)) + assert_equal(Rational(1073741827, 1), + Rational(1073741827, 1) / Rational(1, 1)) + assert_equal(Rational(1073741829, 1), + Rational(1073741827, 1) + Rational(2, 1)) + assert_equal(Rational(1073741825, 1), + Rational(1073741827, 1) - Rational(2, 1)) + assert_equal(Rational(2147483654, 1), + Rational(1073741827, 1) * Rational(2, 1)) + assert_equal(Rational(1073741827, 2), + Rational(1073741827, 1) / Rational(2, 1)) + assert_equal(Rational(1073741830, 1), + Rational(1073741827, 1) + Rational(3, 1)) + assert_equal(Rational(1073741824, 1), + Rational(1073741827, 1) - Rational(3, 1)) + assert_equal(Rational(3221225481, 1), + Rational(1073741827, 1) * Rational(3, 1)) + assert_equal(Rational(1073741827, 3), + Rational(1073741827, 1) / Rational(3, 1)) + assert_equal(Rational(2147483616, 1), + Rational(1073741827, 1) + Rational(1073741789, 1)) + assert_equal(Rational(38, 1), + Rational(1073741827, 1) - Rational(1073741789, 1)) + assert_equal(Rational(1152921470247108503, 1), + Rational(1073741827, 1) * Rational(1073741789, 1)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1073741827, 1) / Rational(1073741789, 1)) + assert_equal(Rational(2147483654, 1), + Rational(1073741827, 1) + Rational(1073741827, 1)) + assert_equal(Rational(0, 1), + Rational(1073741827, 1) - Rational(1073741827, 1)) + assert_equal(Rational(1152921511049297929, 1), + Rational(1073741827, 1) * Rational(1073741827, 1)) + assert_equal(Rational(1, 1), + Rational(1073741827, 1) / Rational(1073741827, 1)) + assert_equal(Rational(3221225483, 3), + Rational(1073741827, 1) + Rational(2, 3)) + assert_equal(Rational(3221225479, 3), + Rational(1073741827, 1) - Rational(2, 3)) + assert_equal(Rational(2147483654, 3), + Rational(1073741827, 1) * Rational(2, 3)) + assert_equal(Rational(3221225481, 2), + Rational(1073741827, 1) / Rational(2, 3)) + assert_equal(Rational(2147483657, 2), + Rational(1073741827, 1) + Rational(3, 2)) + assert_equal(Rational(2147483651, 2), + Rational(1073741827, 1) - Rational(3, 2)) + assert_equal(Rational(3221225481, 2), + Rational(1073741827, 1) * Rational(3, 2)) + assert_equal(Rational(2147483654, 3), + Rational(1073741827, 1) / Rational(3, 2)) + assert_equal(Rational(1152921470247108506, 1073741789), + Rational(1073741827, 1) + Rational(3, 1073741789)) + assert_equal(Rational(1152921470247108500, 1073741789), + Rational(1073741827, 1) - Rational(3, 1073741789)) + assert_equal(Rational(3221225481, 1073741789), + Rational(1073741827, 1) * Rational(3, 1073741789)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741827, 1) / Rational(3, 1073741789)) + assert_equal(Rational(4294967270, 3), + Rational(1073741827, 1) + Rational(1073741789, 3)) + assert_equal(Rational(2147483692, 3), + Rational(1073741827, 1) - Rational(1073741789, 3)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741827, 1) * Rational(1073741789, 3)) + assert_equal(Rational(3221225481, 1073741789), + Rational(1073741827, 1) / Rational(1073741789, 3)) + assert_equal(Rational(1152921511049297932, 1073741827), + Rational(1073741827, 1) + Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297926, 1073741827), + Rational(1073741827, 1) - Rational(3, 1073741827)) + assert_equal(Rational(3, 1), + Rational(1073741827, 1) * Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297929, 3), + Rational(1073741827, 1) / Rational(3, 1073741827)) + assert_equal(Rational(4294967308, 3), + Rational(1073741827, 1) + Rational(1073741827, 3)) + assert_equal(Rational(2147483654, 3), + Rational(1073741827, 1) - Rational(1073741827, 3)) + assert_equal(Rational(1152921511049297929, 3), + Rational(1073741827, 1) * Rational(1073741827, 3)) + assert_equal(Rational(3, 1), + Rational(1073741827, 1) / Rational(1073741827, 3)) + assert_equal(Rational(1152921512123039718, 1073741827), + Rational(1073741827, 1) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921509975556140, 1073741827), + Rational(1073741827, 1) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741789, 1), + Rational(1073741827, 1) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921511049297929, 1073741789), + Rational(1073741827, 1) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921471320850330, 1073741789), + Rational(1073741827, 1) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921469173366676, 1073741789), + Rational(1073741827, 1) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921511049297929, 1073741789), + Rational(1073741827, 1) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 1), + Rational(1073741827, 1) / Rational(1073741827, 1073741789)) + assert_equal(Rational(2, 3), +Rational(2, 3)) + assert_equal(Rational(-2, 3), -Rational(2, 3)) + assert_equal(Rational(5, 3), + Rational(2, 3) + Rational(1, 1)) + assert_equal(Rational(-1, 3), + Rational(2, 3) - Rational(1, 1)) + assert_equal(Rational(2, 3), + Rational(2, 3) * Rational(1, 1)) + assert_equal(Rational(2, 3), + Rational(2, 3) / Rational(1, 1)) + assert_equal(Rational(8, 3), + Rational(2, 3) + Rational(2, 1)) + assert_equal(Rational(-4, 3), + Rational(2, 3) - Rational(2, 1)) + assert_equal(Rational(4, 3), + Rational(2, 3) * Rational(2, 1)) + assert_equal(Rational(1, 3), + Rational(2, 3) / Rational(2, 1)) + assert_equal(Rational(11, 3), + Rational(2, 3) + Rational(3, 1)) + assert_equal(Rational(-7, 3), + Rational(2, 3) - Rational(3, 1)) + assert_equal(Rational(2, 1), + Rational(2, 3) * Rational(3, 1)) + assert_equal(Rational(2, 9), + Rational(2, 3) / Rational(3, 1)) + assert_equal(Rational(3221225369, 3), + Rational(2, 3) + Rational(1073741789, 1)) + assert_equal(Rational(-3221225365, 3), + Rational(2, 3) - Rational(1073741789, 1)) + assert_equal(Rational(2147483578, 3), + Rational(2, 3) * Rational(1073741789, 1)) + assert_equal(Rational(2, 3221225367), + Rational(2, 3) / Rational(1073741789, 1)) + assert_equal(Rational(3221225483, 3), + Rational(2, 3) + Rational(1073741827, 1)) + assert_equal(Rational(-3221225479, 3), + Rational(2, 3) - Rational(1073741827, 1)) + assert_equal(Rational(2147483654, 3), + Rational(2, 3) * Rational(1073741827, 1)) + assert_equal(Rational(2, 3221225481), + Rational(2, 3) / Rational(1073741827, 1)) + assert_equal(Rational(4, 3), + Rational(2, 3) + Rational(2, 3)) + assert_equal(Rational(0, 1), + Rational(2, 3) - Rational(2, 3)) + assert_equal(Rational(4, 9), + Rational(2, 3) * Rational(2, 3)) + assert_equal(Rational(1, 1), + Rational(2, 3) / Rational(2, 3)) + assert_equal(Rational(13, 6), + Rational(2, 3) + Rational(3, 2)) + assert_equal(Rational(-5, 6), + Rational(2, 3) - Rational(3, 2)) + assert_equal(Rational(1, 1), + Rational(2, 3) * Rational(3, 2)) + assert_equal(Rational(4, 9), + Rational(2, 3) / Rational(3, 2)) + assert_equal(Rational(2147483587, 3221225367), + Rational(2, 3) + Rational(3, 1073741789)) + assert_equal(Rational(2147483569, 3221225367), + Rational(2, 3) - Rational(3, 1073741789)) + assert_equal(Rational(2, 1073741789), + Rational(2, 3) * Rational(3, 1073741789)) + assert_equal(Rational(2147483578, 9), + Rational(2, 3) / Rational(3, 1073741789)) + assert_equal(Rational(1073741791, 3), + Rational(2, 3) + Rational(1073741789, 3)) + assert_equal(Rational(-357913929, 1), + Rational(2, 3) - Rational(1073741789, 3)) + assert_equal(Rational(2147483578, 9), + Rational(2, 3) * Rational(1073741789, 3)) + assert_equal(Rational(2, 1073741789), + Rational(2, 3) / Rational(1073741789, 3)) + assert_equal(Rational(2147483663, 3221225481), + Rational(2, 3) + Rational(3, 1073741827)) + assert_equal(Rational(2147483645, 3221225481), + Rational(2, 3) - Rational(3, 1073741827)) + assert_equal(Rational(2, 1073741827), + Rational(2, 3) * Rational(3, 1073741827)) + assert_equal(Rational(2147483654, 9), + Rational(2, 3) / Rational(3, 1073741827)) + assert_equal(Rational(357913943, 1), + Rational(2, 3) + Rational(1073741827, 3)) + assert_equal(Rational(-1073741825, 3), + Rational(2, 3) - Rational(1073741827, 3)) + assert_equal(Rational(2147483654, 9), + Rational(2, 3) * Rational(1073741827, 3)) + assert_equal(Rational(2, 1073741827), + Rational(2, 3) / Rational(1073741827, 3)) + assert_equal(Rational(5368709021, 3221225481), + Rational(2, 3) + Rational(1073741789, 1073741827)) + assert_equal(Rational(-1073741713, 3221225481), + Rational(2, 3) - Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483578, 3221225481), + Rational(2, 3) * Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483654, 3221225367), + Rational(2, 3) / Rational(1073741789, 1073741827)) + assert_equal(Rational(5368709059, 3221225367), + Rational(2, 3) + Rational(1073741827, 1073741789)) + assert_equal(Rational(-1073741903, 3221225367), + Rational(2, 3) - Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483654, 3221225367), + Rational(2, 3) * Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483578, 3221225481), + Rational(2, 3) / Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 2), +Rational(3, 2)) + assert_equal(Rational(-3, 2), -Rational(3, 2)) + assert_equal(Rational(5, 2), + Rational(3, 2) + Rational(1, 1)) + assert_equal(Rational(1, 2), + Rational(3, 2) - Rational(1, 1)) + assert_equal(Rational(3, 2), + Rational(3, 2) * Rational(1, 1)) + assert_equal(Rational(3, 2), + Rational(3, 2) / Rational(1, 1)) + assert_equal(Rational(7, 2), + Rational(3, 2) + Rational(2, 1)) + assert_equal(Rational(-1, 2), + Rational(3, 2) - Rational(2, 1)) + assert_equal(Rational(3, 1), + Rational(3, 2) * Rational(2, 1)) + assert_equal(Rational(3, 4), + Rational(3, 2) / Rational(2, 1)) + assert_equal(Rational(9, 2), + Rational(3, 2) + Rational(3, 1)) + assert_equal(Rational(-3, 2), + Rational(3, 2) - Rational(3, 1)) + assert_equal(Rational(9, 2), + Rational(3, 2) * Rational(3, 1)) + assert_equal(Rational(1, 2), + Rational(3, 2) / Rational(3, 1)) + assert_equal(Rational(2147483581, 2), + Rational(3, 2) + Rational(1073741789, 1)) + assert_equal(Rational(-2147483575, 2), + Rational(3, 2) - Rational(1073741789, 1)) + assert_equal(Rational(3221225367, 2), + Rational(3, 2) * Rational(1073741789, 1)) + assert_equal(Rational(3, 2147483578), + Rational(3, 2) / Rational(1073741789, 1)) + assert_equal(Rational(2147483657, 2), + Rational(3, 2) + Rational(1073741827, 1)) + assert_equal(Rational(-2147483651, 2), + Rational(3, 2) - Rational(1073741827, 1)) + assert_equal(Rational(3221225481, 2), + Rational(3, 2) * Rational(1073741827, 1)) + assert_equal(Rational(3, 2147483654), + Rational(3, 2) / Rational(1073741827, 1)) + assert_equal(Rational(13, 6), + Rational(3, 2) + Rational(2, 3)) + assert_equal(Rational(5, 6), + Rational(3, 2) - Rational(2, 3)) + assert_equal(Rational(1, 1), + Rational(3, 2) * Rational(2, 3)) + assert_equal(Rational(9, 4), + Rational(3, 2) / Rational(2, 3)) + assert_equal(Rational(3, 1), + Rational(3, 2) + Rational(3, 2)) + assert_equal(Rational(0, 1), + Rational(3, 2) - Rational(3, 2)) + assert_equal(Rational(9, 4), + Rational(3, 2) * Rational(3, 2)) + assert_equal(Rational(1, 1), + Rational(3, 2) / Rational(3, 2)) + assert_equal(Rational(3221225373, 2147483578), + Rational(3, 2) + Rational(3, 1073741789)) + assert_equal(Rational(3221225361, 2147483578), + Rational(3, 2) - Rational(3, 1073741789)) + assert_equal(Rational(9, 2147483578), + Rational(3, 2) * Rational(3, 1073741789)) + assert_equal(Rational(1073741789, 2), + Rational(3, 2) / Rational(3, 1073741789)) + assert_equal(Rational(2147483587, 6), + Rational(3, 2) + Rational(1073741789, 3)) + assert_equal(Rational(-2147483569, 6), + Rational(3, 2) - Rational(1073741789, 3)) + assert_equal(Rational(1073741789, 2), + Rational(3, 2) * Rational(1073741789, 3)) + assert_equal(Rational(9, 2147483578), + Rational(3, 2) / Rational(1073741789, 3)) + assert_equal(Rational(3221225487, 2147483654), + Rational(3, 2) + Rational(3, 1073741827)) + assert_equal(Rational(3221225475, 2147483654), + Rational(3, 2) - Rational(3, 1073741827)) + assert_equal(Rational(9, 2147483654), + Rational(3, 2) * Rational(3, 1073741827)) + assert_equal(Rational(1073741827, 2), + Rational(3, 2) / Rational(3, 1073741827)) + assert_equal(Rational(2147483663, 6), + Rational(3, 2) + Rational(1073741827, 3)) + assert_equal(Rational(-2147483645, 6), + Rational(3, 2) - Rational(1073741827, 3)) + assert_equal(Rational(1073741827, 2), + Rational(3, 2) * Rational(1073741827, 3)) + assert_equal(Rational(9, 2147483654), + Rational(3, 2) / Rational(1073741827, 3)) + assert_equal(Rational(5368709059, 2147483654), + Rational(3, 2) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741903, 2147483654), + Rational(3, 2) - Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225367, 2147483654), + Rational(3, 2) * Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225481, 2147483578), + Rational(3, 2) / Rational(1073741789, 1073741827)) + assert_equal(Rational(5368709021, 2147483578), + Rational(3, 2) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741713, 2147483578), + Rational(3, 2) - Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225481, 2147483578), + Rational(3, 2) * Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225367, 2147483654), + Rational(3, 2) / Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 1073741789), +Rational(3, 1073741789)) + assert_equal(Rational(-3, 1073741789), -Rational(3, 1073741789)) + assert_equal(Rational(1073741792, 1073741789), + Rational(3, 1073741789) + Rational(1, 1)) + assert_equal(Rational(-1073741786, 1073741789), + Rational(3, 1073741789) - Rational(1, 1)) + assert_equal(Rational(3, 1073741789), + Rational(3, 1073741789) * Rational(1, 1)) + assert_equal(Rational(3, 1073741789), + Rational(3, 1073741789) / Rational(1, 1)) + assert_equal(Rational(2147483581, 1073741789), + Rational(3, 1073741789) + Rational(2, 1)) + assert_equal(Rational(-2147483575, 1073741789), + Rational(3, 1073741789) - Rational(2, 1)) + assert_equal(Rational(6, 1073741789), + Rational(3, 1073741789) * Rational(2, 1)) + assert_equal(Rational(3, 2147483578), + Rational(3, 1073741789) / Rational(2, 1)) + assert_equal(Rational(3221225370, 1073741789), + Rational(3, 1073741789) + Rational(3, 1)) + assert_equal(Rational(-3221225364, 1073741789), + Rational(3, 1073741789) - Rational(3, 1)) + assert_equal(Rational(9, 1073741789), + Rational(3, 1073741789) * Rational(3, 1)) + assert_equal(Rational(1, 1073741789), + Rational(3, 1073741789) / Rational(3, 1)) + assert_equal(Rational(1152921429444920524, 1073741789), + Rational(3, 1073741789) + Rational(1073741789, 1)) + assert_equal(Rational(-1152921429444920518, 1073741789), + Rational(3, 1073741789) - Rational(1073741789, 1)) + assert_equal(Rational(3, 1), + Rational(3, 1073741789) * Rational(1073741789, 1)) + assert_equal(Rational(3, 1152921429444920521), + Rational(3, 1073741789) / Rational(1073741789, 1)) + assert_equal(Rational(1152921470247108506, 1073741789), + Rational(3, 1073741789) + Rational(1073741827, 1)) + assert_equal(Rational(-1152921470247108500, 1073741789), + Rational(3, 1073741789) - Rational(1073741827, 1)) + assert_equal(Rational(3221225481, 1073741789), + Rational(3, 1073741789) * Rational(1073741827, 1)) + assert_equal(Rational(3, 1152921470247108503), + Rational(3, 1073741789) / Rational(1073741827, 1)) + assert_equal(Rational(2147483587, 3221225367), + Rational(3, 1073741789) + Rational(2, 3)) + assert_equal(Rational(-2147483569, 3221225367), + Rational(3, 1073741789) - Rational(2, 3)) + assert_equal(Rational(2, 1073741789), + Rational(3, 1073741789) * Rational(2, 3)) + assert_equal(Rational(9, 2147483578), + Rational(3, 1073741789) / Rational(2, 3)) + assert_equal(Rational(3221225373, 2147483578), + Rational(3, 1073741789) + Rational(3, 2)) + assert_equal(Rational(-3221225361, 2147483578), + Rational(3, 1073741789) - Rational(3, 2)) + assert_equal(Rational(9, 2147483578), + Rational(3, 1073741789) * Rational(3, 2)) + assert_equal(Rational(2, 1073741789), + Rational(3, 1073741789) / Rational(3, 2)) + assert_equal(Rational(6, 1073741789), + Rational(3, 1073741789) + Rational(3, 1073741789)) + assert_equal(Rational(0, 1), + Rational(3, 1073741789) - Rational(3, 1073741789)) + assert_equal(Rational(9, 1152921429444920521), + Rational(3, 1073741789) * Rational(3, 1073741789)) + assert_equal(Rational(1, 1), + Rational(3, 1073741789) / Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920530, 3221225367), + Rational(3, 1073741789) + Rational(1073741789, 3)) + assert_equal(Rational(-1152921429444920512, 3221225367), + Rational(3, 1073741789) - Rational(1073741789, 3)) + assert_equal(Rational(1, 1), + Rational(3, 1073741789) * Rational(1073741789, 3)) + assert_equal(Rational(9, 1152921429444920521), + Rational(3, 1073741789) / Rational(1073741789, 3)) + assert_equal(Rational(6442450848, 1152921470247108503), + Rational(3, 1073741789) + Rational(3, 1073741827)) + assert_equal(Rational(114, 1152921470247108503), + Rational(3, 1073741789) - Rational(3, 1073741827)) + assert_equal(Rational(9, 1152921470247108503), + Rational(3, 1073741789) * Rational(3, 1073741827)) + assert_equal(Rational(1073741827, 1073741789), + Rational(3, 1073741789) / Rational(3, 1073741827)) + assert_equal(Rational(1152921470247108512, 3221225367), + Rational(3, 1073741789) + Rational(1073741827, 3)) + assert_equal(Rational(-1152921470247108494, 3221225367), + Rational(3, 1073741789) - Rational(1073741827, 3)) + assert_equal(Rational(1073741827, 1073741789), + Rational(3, 1073741789) * Rational(1073741827, 3)) + assert_equal(Rational(9, 1152921470247108503), + Rational(3, 1073741789) / Rational(1073741827, 3)) + assert_equal(Rational(1152921432666146002, 1152921470247108503), + Rational(3, 1073741789) + Rational(1073741789, 1073741827)) + assert_equal(Rational(-1152921426223695040, 1152921470247108503), + Rational(3, 1073741789) - Rational(1073741789, 1073741827)) + assert_equal(Rational(3, 1073741827), + Rational(3, 1073741789) * Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225481, 1152921429444920521), + Rational(3, 1073741789) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741830, 1073741789), + Rational(3, 1073741789) + Rational(1073741827, 1073741789)) + assert_equal(Rational(-1073741824, 1073741789), + Rational(3, 1073741789) - Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225481, 1152921429444920521), + Rational(3, 1073741789) * Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 1073741827), + Rational(3, 1073741789) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 3), +Rational(1073741789, 3)) + assert_equal(Rational(-1073741789, 3), -Rational(1073741789, 3)) + assert_equal(Rational(1073741792, 3), + Rational(1073741789, 3) + Rational(1, 1)) + assert_equal(Rational(1073741786, 3), + Rational(1073741789, 3) - Rational(1, 1)) + assert_equal(Rational(1073741789, 3), + Rational(1073741789, 3) * Rational(1, 1)) + assert_equal(Rational(1073741789, 3), + Rational(1073741789, 3) / Rational(1, 1)) + assert_equal(Rational(1073741795, 3), + Rational(1073741789, 3) + Rational(2, 1)) + assert_equal(Rational(1073741783, 3), + Rational(1073741789, 3) - Rational(2, 1)) + assert_equal(Rational(2147483578, 3), + Rational(1073741789, 3) * Rational(2, 1)) + assert_equal(Rational(1073741789, 6), + Rational(1073741789, 3) / Rational(2, 1)) + assert_equal(Rational(1073741798, 3), + Rational(1073741789, 3) + Rational(3, 1)) + assert_equal(Rational(1073741780, 3), + Rational(1073741789, 3) - Rational(3, 1)) + assert_equal(Rational(1073741789, 1), + Rational(1073741789, 3) * Rational(3, 1)) + assert_equal(Rational(1073741789, 9), + Rational(1073741789, 3) / Rational(3, 1)) + assert_equal(Rational(4294967156, 3), + Rational(1073741789, 3) + Rational(1073741789, 1)) + assert_equal(Rational(-2147483578, 3), + Rational(1073741789, 3) - Rational(1073741789, 1)) + assert_equal(Rational(1152921429444920521, 3), + Rational(1073741789, 3) * Rational(1073741789, 1)) + assert_equal(Rational(1, 3), + Rational(1073741789, 3) / Rational(1073741789, 1)) + assert_equal(Rational(4294967270, 3), + Rational(1073741789, 3) + Rational(1073741827, 1)) + assert_equal(Rational(-2147483692, 3), + Rational(1073741789, 3) - Rational(1073741827, 1)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741789, 3) * Rational(1073741827, 1)) + assert_equal(Rational(1073741789, 3221225481), + Rational(1073741789, 3) / Rational(1073741827, 1)) + assert_equal(Rational(1073741791, 3), + Rational(1073741789, 3) + Rational(2, 3)) + assert_equal(Rational(357913929, 1), + Rational(1073741789, 3) - Rational(2, 3)) + assert_equal(Rational(2147483578, 9), + Rational(1073741789, 3) * Rational(2, 3)) + assert_equal(Rational(1073741789, 2), + Rational(1073741789, 3) / Rational(2, 3)) + assert_equal(Rational(2147483587, 6), + Rational(1073741789, 3) + Rational(3, 2)) + assert_equal(Rational(2147483569, 6), + Rational(1073741789, 3) - Rational(3, 2)) + assert_equal(Rational(1073741789, 2), + Rational(1073741789, 3) * Rational(3, 2)) + assert_equal(Rational(2147483578, 9), + Rational(1073741789, 3) / Rational(3, 2)) + assert_equal(Rational(1152921429444920530, 3221225367), + Rational(1073741789, 3) + Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920512, 3221225367), + Rational(1073741789, 3) - Rational(3, 1073741789)) + assert_equal(Rational(1, 1), + Rational(1073741789, 3) * Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920521, 9), + Rational(1073741789, 3) / Rational(3, 1073741789)) + assert_equal(Rational(2147483578, 3), + Rational(1073741789, 3) + Rational(1073741789, 3)) + assert_equal(Rational(0, 1), + Rational(1073741789, 3) - Rational(1073741789, 3)) + assert_equal(Rational(1152921429444920521, 9), + Rational(1073741789, 3) * Rational(1073741789, 3)) + assert_equal(Rational(1, 1), + Rational(1073741789, 3) / Rational(1073741789, 3)) + assert_equal(Rational(1152921470247108512, 3221225481), + Rational(1073741789, 3) + Rational(3, 1073741827)) + assert_equal(Rational(1152921470247108494, 3221225481), + Rational(1073741789, 3) - Rational(3, 1073741827)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1073741789, 3) * Rational(3, 1073741827)) + assert_equal(Rational(1152921470247108503, 9), + Rational(1073741789, 3) / Rational(3, 1073741827)) + assert_equal(Rational(715827872, 1), + Rational(1073741789, 3) + Rational(1073741827, 3)) + assert_equal(Rational(-38, 3), + Rational(1073741789, 3) - Rational(1073741827, 3)) + assert_equal(Rational(1152921470247108503, 9), + Rational(1073741789, 3) * Rational(1073741827, 3)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1073741789, 3) / Rational(1073741827, 3)) + assert_equal(Rational(1152921473468333870, 3221225481), + Rational(1073741789, 3) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921467025883136, 3221225481), + Rational(1073741789, 3) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921429444920521, 3221225481), + Rational(1073741789, 3) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741827, 3), + Rational(1073741789, 3) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921432666146002, 3221225367), + Rational(1073741789, 3) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921426223695040, 3221225367), + Rational(1073741789, 3) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 3), + Rational(1073741789, 3) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921429444920521, 3221225481), + Rational(1073741789, 3) / Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 1073741827), +Rational(3, 1073741827)) + assert_equal(Rational(-3, 1073741827), -Rational(3, 1073741827)) + assert_equal(Rational(1073741830, 1073741827), + Rational(3, 1073741827) + Rational(1, 1)) + assert_equal(Rational(-1073741824, 1073741827), + Rational(3, 1073741827) - Rational(1, 1)) + assert_equal(Rational(3, 1073741827), + Rational(3, 1073741827) * Rational(1, 1)) + assert_equal(Rational(3, 1073741827), + Rational(3, 1073741827) / Rational(1, 1)) + assert_equal(Rational(2147483657, 1073741827), + Rational(3, 1073741827) + Rational(2, 1)) + assert_equal(Rational(-2147483651, 1073741827), + Rational(3, 1073741827) - Rational(2, 1)) + assert_equal(Rational(6, 1073741827), + Rational(3, 1073741827) * Rational(2, 1)) + assert_equal(Rational(3, 2147483654), + Rational(3, 1073741827) / Rational(2, 1)) + assert_equal(Rational(3221225484, 1073741827), + Rational(3, 1073741827) + Rational(3, 1)) + assert_equal(Rational(-3221225478, 1073741827), + Rational(3, 1073741827) - Rational(3, 1)) + assert_equal(Rational(9, 1073741827), + Rational(3, 1073741827) * Rational(3, 1)) + assert_equal(Rational(1, 1073741827), + Rational(3, 1073741827) / Rational(3, 1)) + assert_equal(Rational(1152921470247108506, 1073741827), + Rational(3, 1073741827) + Rational(1073741789, 1)) + assert_equal(Rational(-1152921470247108500, 1073741827), + Rational(3, 1073741827) - Rational(1073741789, 1)) + assert_equal(Rational(3221225367, 1073741827), + Rational(3, 1073741827) * Rational(1073741789, 1)) + assert_equal(Rational(3, 1152921470247108503), + Rational(3, 1073741827) / Rational(1073741789, 1)) + assert_equal(Rational(1152921511049297932, 1073741827), + Rational(3, 1073741827) + Rational(1073741827, 1)) + assert_equal(Rational(-1152921511049297926, 1073741827), + Rational(3, 1073741827) - Rational(1073741827, 1)) + assert_equal(Rational(3, 1), + Rational(3, 1073741827) * Rational(1073741827, 1)) + assert_equal(Rational(3, 1152921511049297929), + Rational(3, 1073741827) / Rational(1073741827, 1)) + assert_equal(Rational(2147483663, 3221225481), + Rational(3, 1073741827) + Rational(2, 3)) + assert_equal(Rational(-2147483645, 3221225481), + Rational(3, 1073741827) - Rational(2, 3)) + assert_equal(Rational(2, 1073741827), + Rational(3, 1073741827) * Rational(2, 3)) + assert_equal(Rational(9, 2147483654), + Rational(3, 1073741827) / Rational(2, 3)) + assert_equal(Rational(3221225487, 2147483654), + Rational(3, 1073741827) + Rational(3, 2)) + assert_equal(Rational(-3221225475, 2147483654), + Rational(3, 1073741827) - Rational(3, 2)) + assert_equal(Rational(9, 2147483654), + Rational(3, 1073741827) * Rational(3, 2)) + assert_equal(Rational(2, 1073741827), + Rational(3, 1073741827) / Rational(3, 2)) + assert_equal(Rational(6442450848, 1152921470247108503), + Rational(3, 1073741827) + Rational(3, 1073741789)) + assert_equal(Rational(-114, 1152921470247108503), + Rational(3, 1073741827) - Rational(3, 1073741789)) + assert_equal(Rational(9, 1152921470247108503), + Rational(3, 1073741827) * Rational(3, 1073741789)) + assert_equal(Rational(1073741789, 1073741827), + Rational(3, 1073741827) / Rational(3, 1073741789)) + assert_equal(Rational(1152921470247108512, 3221225481), + Rational(3, 1073741827) + Rational(1073741789, 3)) + assert_equal(Rational(-1152921470247108494, 3221225481), + Rational(3, 1073741827) - Rational(1073741789, 3)) + assert_equal(Rational(1073741789, 1073741827), + Rational(3, 1073741827) * Rational(1073741789, 3)) + assert_equal(Rational(9, 1152921470247108503), + Rational(3, 1073741827) / Rational(1073741789, 3)) + assert_equal(Rational(6, 1073741827), + Rational(3, 1073741827) + Rational(3, 1073741827)) + assert_equal(Rational(0, 1), + Rational(3, 1073741827) - Rational(3, 1073741827)) + assert_equal(Rational(9, 1152921511049297929), + Rational(3, 1073741827) * Rational(3, 1073741827)) + assert_equal(Rational(1, 1), + Rational(3, 1073741827) / Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297938, 3221225481), + Rational(3, 1073741827) + Rational(1073741827, 3)) + assert_equal(Rational(-1152921511049297920, 3221225481), + Rational(3, 1073741827) - Rational(1073741827, 3)) + assert_equal(Rational(1, 1), + Rational(3, 1073741827) * Rational(1073741827, 3)) + assert_equal(Rational(9, 1152921511049297929), + Rational(3, 1073741827) / Rational(1073741827, 3)) + assert_equal(Rational(1073741792, 1073741827), + Rational(3, 1073741827) + Rational(1073741789, 1073741827)) + assert_equal(Rational(-1073741786, 1073741827), + Rational(3, 1073741827) - Rational(1073741789, 1073741827)) + assert_equal(Rational(3221225367, 1152921511049297929), + Rational(3, 1073741827) * Rational(1073741789, 1073741827)) + assert_equal(Rational(3, 1073741789), + Rational(3, 1073741827) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921514270523296, 1152921470247108503), + Rational(3, 1073741827) + Rational(1073741827, 1073741789)) + assert_equal(Rational(-1152921507828072562, 1152921470247108503), + Rational(3, 1073741827) - Rational(1073741827, 1073741789)) + assert_equal(Rational(3, 1073741789), + Rational(3, 1073741827) * Rational(1073741827, 1073741789)) + assert_equal(Rational(3221225367, 1152921511049297929), + Rational(3, 1073741827) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 3), +Rational(1073741827, 3)) + assert_equal(Rational(-1073741827, 3), -Rational(1073741827, 3)) + assert_equal(Rational(1073741830, 3), + Rational(1073741827, 3) + Rational(1, 1)) + assert_equal(Rational(1073741824, 3), + Rational(1073741827, 3) - Rational(1, 1)) + assert_equal(Rational(1073741827, 3), + Rational(1073741827, 3) * Rational(1, 1)) + assert_equal(Rational(1073741827, 3), + Rational(1073741827, 3) / Rational(1, 1)) + assert_equal(Rational(1073741833, 3), + Rational(1073741827, 3) + Rational(2, 1)) + assert_equal(Rational(1073741821, 3), + Rational(1073741827, 3) - Rational(2, 1)) + assert_equal(Rational(2147483654, 3), + Rational(1073741827, 3) * Rational(2, 1)) + assert_equal(Rational(1073741827, 6), + Rational(1073741827, 3) / Rational(2, 1)) + assert_equal(Rational(1073741836, 3), + Rational(1073741827, 3) + Rational(3, 1)) + assert_equal(Rational(1073741818, 3), + Rational(1073741827, 3) - Rational(3, 1)) + assert_equal(Rational(1073741827, 1), + Rational(1073741827, 3) * Rational(3, 1)) + assert_equal(Rational(1073741827, 9), + Rational(1073741827, 3) / Rational(3, 1)) + assert_equal(Rational(4294967194, 3), + Rational(1073741827, 3) + Rational(1073741789, 1)) + assert_equal(Rational(-2147483540, 3), + Rational(1073741827, 3) - Rational(1073741789, 1)) + assert_equal(Rational(1152921470247108503, 3), + Rational(1073741827, 3) * Rational(1073741789, 1)) + assert_equal(Rational(1073741827, 3221225367), + Rational(1073741827, 3) / Rational(1073741789, 1)) + assert_equal(Rational(4294967308, 3), + Rational(1073741827, 3) + Rational(1073741827, 1)) + assert_equal(Rational(-2147483654, 3), + Rational(1073741827, 3) - Rational(1073741827, 1)) + assert_equal(Rational(1152921511049297929, 3), + Rational(1073741827, 3) * Rational(1073741827, 1)) + assert_equal(Rational(1, 3), + Rational(1073741827, 3) / Rational(1073741827, 1)) + assert_equal(Rational(357913943, 1), + Rational(1073741827, 3) + Rational(2, 3)) + assert_equal(Rational(1073741825, 3), + Rational(1073741827, 3) - Rational(2, 3)) + assert_equal(Rational(2147483654, 9), + Rational(1073741827, 3) * Rational(2, 3)) + assert_equal(Rational(1073741827, 2), + Rational(1073741827, 3) / Rational(2, 3)) + assert_equal(Rational(2147483663, 6), + Rational(1073741827, 3) + Rational(3, 2)) + assert_equal(Rational(2147483645, 6), + Rational(1073741827, 3) - Rational(3, 2)) + assert_equal(Rational(1073741827, 2), + Rational(1073741827, 3) * Rational(3, 2)) + assert_equal(Rational(2147483654, 9), + Rational(1073741827, 3) / Rational(3, 2)) + assert_equal(Rational(1152921470247108512, 3221225367), + Rational(1073741827, 3) + Rational(3, 1073741789)) + assert_equal(Rational(1152921470247108494, 3221225367), + Rational(1073741827, 3) - Rational(3, 1073741789)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1073741827, 3) * Rational(3, 1073741789)) + assert_equal(Rational(1152921470247108503, 9), + Rational(1073741827, 3) / Rational(3, 1073741789)) + assert_equal(Rational(715827872, 1), + Rational(1073741827, 3) + Rational(1073741789, 3)) + assert_equal(Rational(38, 3), + Rational(1073741827, 3) - Rational(1073741789, 3)) + assert_equal(Rational(1152921470247108503, 9), + Rational(1073741827, 3) * Rational(1073741789, 3)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1073741827, 3) / Rational(1073741789, 3)) + assert_equal(Rational(1152921511049297938, 3221225481), + Rational(1073741827, 3) + Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297920, 3221225481), + Rational(1073741827, 3) - Rational(3, 1073741827)) + assert_equal(Rational(1, 1), + Rational(1073741827, 3) * Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297929, 9), + Rational(1073741827, 3) / Rational(3, 1073741827)) + assert_equal(Rational(2147483654, 3), + Rational(1073741827, 3) + Rational(1073741827, 3)) + assert_equal(Rational(0, 1), + Rational(1073741827, 3) - Rational(1073741827, 3)) + assert_equal(Rational(1152921511049297929, 9), + Rational(1073741827, 3) * Rational(1073741827, 3)) + assert_equal(Rational(1, 1), + Rational(1073741827, 3) / Rational(1073741827, 3)) + assert_equal(Rational(1152921514270523296, 3221225481), + Rational(1073741827, 3) + Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921507828072562, 3221225481), + Rational(1073741827, 3) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1073741789, 3), + Rational(1073741827, 3) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921511049297929, 3221225367), + Rational(1073741827, 3) / Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921473468333984, 3221225367), + Rational(1073741827, 3) + Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921467025883022, 3221225367), + Rational(1073741827, 3) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921511049297929, 3221225367), + Rational(1073741827, 3) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 3), + Rational(1073741827, 3) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741789, 1073741827), +Rational(1073741789, 1073741827)) + assert_equal(Rational(-1073741789, 1073741827), -Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483616, 1073741827), + Rational(1073741789, 1073741827) + Rational(1, 1)) + assert_equal(Rational(-38, 1073741827), + Rational(1073741789, 1073741827) - Rational(1, 1)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1073741789, 1073741827) * Rational(1, 1)) + assert_equal(Rational(1073741789, 1073741827), + Rational(1073741789, 1073741827) / Rational(1, 1)) + assert_equal(Rational(3221225443, 1073741827), + Rational(1073741789, 1073741827) + Rational(2, 1)) + assert_equal(Rational(-1073741865, 1073741827), + Rational(1073741789, 1073741827) - Rational(2, 1)) + assert_equal(Rational(2147483578, 1073741827), + Rational(1073741789, 1073741827) * Rational(2, 1)) + assert_equal(Rational(1073741789, 2147483654), + Rational(1073741789, 1073741827) / Rational(2, 1)) + assert_equal(Rational(4294967270, 1073741827), + Rational(1073741789, 1073741827) + Rational(3, 1)) + assert_equal(Rational(-2147483692, 1073741827), + Rational(1073741789, 1073741827) - Rational(3, 1)) + assert_equal(Rational(3221225367, 1073741827), + Rational(1073741789, 1073741827) * Rational(3, 1)) + assert_equal(Rational(1073741789, 3221225481), + Rational(1073741789, 1073741827) / Rational(3, 1)) + assert_equal(Rational(1152921471320850292, 1073741827), + Rational(1073741789, 1073741827) + Rational(1073741789, 1)) + assert_equal(Rational(-1152921469173366714, 1073741827), + Rational(1073741789, 1073741827) - Rational(1073741789, 1)) + assert_equal(Rational(1152921429444920521, 1073741827), + Rational(1073741789, 1073741827) * Rational(1073741789, 1)) + assert_equal(Rational(1, 1073741827), + Rational(1073741789, 1073741827) / Rational(1073741789, 1)) + assert_equal(Rational(1152921512123039718, 1073741827), + Rational(1073741789, 1073741827) + Rational(1073741827, 1)) + assert_equal(Rational(-1152921509975556140, 1073741827), + Rational(1073741789, 1073741827) - Rational(1073741827, 1)) + assert_equal(Rational(1073741789, 1), + Rational(1073741789, 1073741827) * Rational(1073741827, 1)) + assert_equal(Rational(1073741789, 1152921511049297929), + Rational(1073741789, 1073741827) / Rational(1073741827, 1)) + assert_equal(Rational(5368709021, 3221225481), + Rational(1073741789, 1073741827) + Rational(2, 3)) + assert_equal(Rational(1073741713, 3221225481), + Rational(1073741789, 1073741827) - Rational(2, 3)) + assert_equal(Rational(2147483578, 3221225481), + Rational(1073741789, 1073741827) * Rational(2, 3)) + assert_equal(Rational(3221225367, 2147483654), + Rational(1073741789, 1073741827) / Rational(2, 3)) + assert_equal(Rational(5368709059, 2147483654), + Rational(1073741789, 1073741827) + Rational(3, 2)) + assert_equal(Rational(-1073741903, 2147483654), + Rational(1073741789, 1073741827) - Rational(3, 2)) + assert_equal(Rational(3221225367, 2147483654), + Rational(1073741789, 1073741827) * Rational(3, 2)) + assert_equal(Rational(2147483578, 3221225481), + Rational(1073741789, 1073741827) / Rational(3, 2)) + assert_equal(Rational(1152921432666146002, 1152921470247108503), + Rational(1073741789, 1073741827) + Rational(3, 1073741789)) + assert_equal(Rational(1152921426223695040, 1152921470247108503), + Rational(1073741789, 1073741827) - Rational(3, 1073741789)) + assert_equal(Rational(3, 1073741827), + Rational(1073741789, 1073741827) * Rational(3, 1073741789)) + assert_equal(Rational(1152921429444920521, 3221225481), + Rational(1073741789, 1073741827) / Rational(3, 1073741789)) + assert_equal(Rational(1152921473468333870, 3221225481), + Rational(1073741789, 1073741827) + Rational(1073741789, 3)) + assert_equal(Rational(-1152921467025883136, 3221225481), + Rational(1073741789, 1073741827) - Rational(1073741789, 3)) + assert_equal(Rational(1152921429444920521, 3221225481), + Rational(1073741789, 1073741827) * Rational(1073741789, 3)) + assert_equal(Rational(3, 1073741827), + Rational(1073741789, 1073741827) / Rational(1073741789, 3)) + assert_equal(Rational(1073741792, 1073741827), + Rational(1073741789, 1073741827) + Rational(3, 1073741827)) + assert_equal(Rational(1073741786, 1073741827), + Rational(1073741789, 1073741827) - Rational(3, 1073741827)) + assert_equal(Rational(3221225367, 1152921511049297929), + Rational(1073741789, 1073741827) * Rational(3, 1073741827)) + assert_equal(Rational(1073741789, 3), + Rational(1073741789, 1073741827) / Rational(3, 1073741827)) + assert_equal(Rational(1152921514270523296, 3221225481), + Rational(1073741789, 1073741827) + Rational(1073741827, 3)) + assert_equal(Rational(-1152921507828072562, 3221225481), + Rational(1073741789, 1073741827) - Rational(1073741827, 3)) + assert_equal(Rational(1073741789, 3), + Rational(1073741789, 1073741827) * Rational(1073741827, 3)) + assert_equal(Rational(3221225367, 1152921511049297929), + Rational(1073741789, 1073741827) / Rational(1073741827, 3)) + assert_equal(Rational(2147483578, 1073741827), + Rational(1073741789, 1073741827) + Rational(1073741789, 1073741827)) + assert_equal(Rational(0, 1), + Rational(1073741789, 1073741827) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921429444920521, 1152921511049297929), + Rational(1073741789, 1073741827) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1, 1), + Rational(1073741789, 1073741827) / Rational(1073741789, 1073741827)) + assert_equal(Rational(2305842940494218450, 1152921470247108503), + Rational(1073741789, 1073741827) + Rational(1073741827, 1073741789)) + assert_equal(Rational(-81604377408, 1152921470247108503), + Rational(1073741789, 1073741827) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1, 1), + Rational(1073741789, 1073741827) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921429444920521, 1152921511049297929), + Rational(1073741789, 1073741827) / Rational(1073741827, 1073741789)) + assert_equal(Rational(1073741827, 1073741789), +Rational(1073741827, 1073741789)) + assert_equal(Rational(-1073741827, 1073741789), -Rational(1073741827, 1073741789)) + assert_equal(Rational(2147483616, 1073741789), + Rational(1073741827, 1073741789) + Rational(1, 1)) + assert_equal(Rational(38, 1073741789), + Rational(1073741827, 1073741789) - Rational(1, 1)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1073741827, 1073741789) * Rational(1, 1)) + assert_equal(Rational(1073741827, 1073741789), + Rational(1073741827, 1073741789) / Rational(1, 1)) + assert_equal(Rational(3221225405, 1073741789), + Rational(1073741827, 1073741789) + Rational(2, 1)) + assert_equal(Rational(-1073741751, 1073741789), + Rational(1073741827, 1073741789) - Rational(2, 1)) + assert_equal(Rational(2147483654, 1073741789), + Rational(1073741827, 1073741789) * Rational(2, 1)) + assert_equal(Rational(1073741827, 2147483578), + Rational(1073741827, 1073741789) / Rational(2, 1)) + assert_equal(Rational(4294967194, 1073741789), + Rational(1073741827, 1073741789) + Rational(3, 1)) + assert_equal(Rational(-2147483540, 1073741789), + Rational(1073741827, 1073741789) - Rational(3, 1)) + assert_equal(Rational(3221225481, 1073741789), + Rational(1073741827, 1073741789) * Rational(3, 1)) + assert_equal(Rational(1073741827, 3221225367), + Rational(1073741827, 1073741789) / Rational(3, 1)) + assert_equal(Rational(1152921430518662348, 1073741789), + Rational(1073741827, 1073741789) + Rational(1073741789, 1)) + assert_equal(Rational(-1152921428371178694, 1073741789), + Rational(1073741827, 1073741789) - Rational(1073741789, 1)) + assert_equal(Rational(1073741827, 1), + Rational(1073741827, 1073741789) * Rational(1073741789, 1)) + assert_equal(Rational(1073741827, 1152921429444920521), + Rational(1073741827, 1073741789) / Rational(1073741789, 1)) + assert_equal(Rational(1152921471320850330, 1073741789), + Rational(1073741827, 1073741789) + Rational(1073741827, 1)) + assert_equal(Rational(-1152921469173366676, 1073741789), + Rational(1073741827, 1073741789) - Rational(1073741827, 1)) + assert_equal(Rational(1152921511049297929, 1073741789), + Rational(1073741827, 1073741789) * Rational(1073741827, 1)) + assert_equal(Rational(1, 1073741789), + Rational(1073741827, 1073741789) / Rational(1073741827, 1)) + assert_equal(Rational(5368709059, 3221225367), + Rational(1073741827, 1073741789) + Rational(2, 3)) + assert_equal(Rational(1073741903, 3221225367), + Rational(1073741827, 1073741789) - Rational(2, 3)) + assert_equal(Rational(2147483654, 3221225367), + Rational(1073741827, 1073741789) * Rational(2, 3)) + assert_equal(Rational(3221225481, 2147483578), + Rational(1073741827, 1073741789) / Rational(2, 3)) + assert_equal(Rational(5368709021, 2147483578), + Rational(1073741827, 1073741789) + Rational(3, 2)) + assert_equal(Rational(-1073741713, 2147483578), + Rational(1073741827, 1073741789) - Rational(3, 2)) + assert_equal(Rational(3221225481, 2147483578), + Rational(1073741827, 1073741789) * Rational(3, 2)) + assert_equal(Rational(2147483654, 3221225367), + Rational(1073741827, 1073741789) / Rational(3, 2)) + assert_equal(Rational(1073741830, 1073741789), + Rational(1073741827, 1073741789) + Rational(3, 1073741789)) + assert_equal(Rational(1073741824, 1073741789), + Rational(1073741827, 1073741789) - Rational(3, 1073741789)) + assert_equal(Rational(3221225481, 1152921429444920521), + Rational(1073741827, 1073741789) * Rational(3, 1073741789)) + assert_equal(Rational(1073741827, 3), + Rational(1073741827, 1073741789) / Rational(3, 1073741789)) + assert_equal(Rational(1152921432666146002, 3221225367), + Rational(1073741827, 1073741789) + Rational(1073741789, 3)) + assert_equal(Rational(-1152921426223695040, 3221225367), + Rational(1073741827, 1073741789) - Rational(1073741789, 3)) + assert_equal(Rational(1073741827, 3), + Rational(1073741827, 1073741789) * Rational(1073741789, 3)) + assert_equal(Rational(3221225481, 1152921429444920521), + Rational(1073741827, 1073741789) / Rational(1073741789, 3)) + assert_equal(Rational(1152921514270523296, 1152921470247108503), + Rational(1073741827, 1073741789) + Rational(3, 1073741827)) + assert_equal(Rational(1152921507828072562, 1152921470247108503), + Rational(1073741827, 1073741789) - Rational(3, 1073741827)) + assert_equal(Rational(3, 1073741789), + Rational(1073741827, 1073741789) * Rational(3, 1073741827)) + assert_equal(Rational(1152921511049297929, 3221225367), + Rational(1073741827, 1073741789) / Rational(3, 1073741827)) + assert_equal(Rational(1152921473468333984, 3221225367), + Rational(1073741827, 1073741789) + Rational(1073741827, 3)) + assert_equal(Rational(-1152921467025883022, 3221225367), + Rational(1073741827, 1073741789) - Rational(1073741827, 3)) + assert_equal(Rational(1152921511049297929, 3221225367), + Rational(1073741827, 1073741789) * Rational(1073741827, 3)) + assert_equal(Rational(3, 1073741789), + Rational(1073741827, 1073741789) / Rational(1073741827, 3)) + assert_equal(Rational(2305842940494218450, 1152921470247108503), + Rational(1073741827, 1073741789) + Rational(1073741789, 1073741827)) + assert_equal(Rational(81604377408, 1152921470247108503), + Rational(1073741827, 1073741789) - Rational(1073741789, 1073741827)) + assert_equal(Rational(1, 1), + Rational(1073741827, 1073741789) * Rational(1073741789, 1073741827)) + assert_equal(Rational(1152921511049297929, 1152921429444920521), + Rational(1073741827, 1073741789) / Rational(1073741789, 1073741827)) + assert_equal(Rational(2147483654, 1073741789), + Rational(1073741827, 1073741789) + Rational(1073741827, 1073741789)) + assert_equal(Rational(0, 1), + Rational(1073741827, 1073741789) - Rational(1073741827, 1073741789)) + assert_equal(Rational(1152921511049297929, 1152921429444920521), + Rational(1073741827, 1073741789) * Rational(1073741827, 1073741789)) + assert_equal(Rational(1, 1), + Rational(1073741827, 1073741789) / Rational(1073741827, 1073741789)) + end + +end diff --git a/test/ruby/test_readpartial.rb b/test/ruby/test_readpartial.rb new file mode 100644 index 0000000000..bc22556cd4 --- /dev/null +++ b/test/ruby/test_readpartial.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: false +require 'test/unit' +require 'timeout' +require 'fcntl' + +class TestReadPartial < Test::Unit::TestCase + def make_pipe + r, w = IO.pipe + r.binmode + w.binmode + begin + yield r, w + ensure + r.close unless r.closed? + w.close unless w.closed? + end + end + + def pipe + make_pipe {|r, w| + yield r, w + } + return unless defined?(Fcntl::F_SETFL) + return unless defined?(Fcntl::F_GETFL) + return unless defined?(Fcntl::O_NONBLOCK) + make_pipe {|r, w| + r.fcntl(Fcntl::F_SETFL, r.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) + yield r, w + } + end + + def test_length_zero + pipe {|r, w| + assert_equal('', r.readpartial(0)) + } + end + + def test_closed_pipe + pipe {|r, w| + w << 'abc' + w.close + assert_equal('ab', r.readpartial(2)) + assert_equal('c', r.readpartial(2)) + assert_raise(EOFError) { r.readpartial(2) } + assert_raise(EOFError) { r.readpartial(2) } + } + end + + def test_open_pipe + pipe {|r, w| + w << 'abc' + assert_equal('ab', r.readpartial(2)) + assert_equal('c', r.readpartial(2)) + assert_raise(Timeout::Error) { + Timeout.timeout(0.1) { r.readpartial(2) } + } + } + end + + def test_with_stdio + pipe {|r, w| + w << "abc\ndef\n" + assert_equal("abc\n", r.gets) + w << "ghi\n" + assert_equal("de", r.readpartial(2)) + assert_equal("f\n", r.readpartial(4096)) + assert_equal("ghi\n", r.readpartial(4096)) + assert_raise(Timeout::Error) { + Timeout.timeout(0.1) { r.readpartial(2) } + } + } + end +end diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb new file mode 100644 index 0000000000..209e55294b --- /dev/null +++ b/test/ruby/test_refinement.rb @@ -0,0 +1,2767 @@ +# 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.instance_method_z(foo) + return foo.class.instance_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 + + module MethodIntegerPowEx + refine Integer do + def pow(*) + :refine_pow + end + end + end + def test_method_should_use_refinements + omit if Test::Unit::Runner.current_repeat_count > 0 + + foo = Foo.new + assert_raise(NameError) { foo.method(:z) } + assert_equal("FooExt#z", FooExtClient.method_z(foo).call) + assert_raise(NameError) { foo.method(:z) } + assert_equal(8, eval(<<~EOS, Sandbox::BINDING)) + meth = 2.method(:pow) + using MethodIntegerPowEx + meth.call(3) + EOS + assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.pow(3)")) + assert_equal(:refine_pow, eval_using(MethodIntegerPowEx, "2.method(:pow).(3)")) + end + + module InstanceMethodIntegerPowEx + refine Integer do + def abs + :refine_abs + end + end + end + def test_instance_method_should_use_refinements + omit if Test::Unit::Runner.current_repeat_count > 0 + + foo = Foo.new + assert_raise(NameError) { Foo.instance_method(:z) } + assert_equal("FooExt#z", FooExtClient.instance_method_z(foo).bind(foo).call) + assert_raise(NameError) { Foo.instance_method(:z) } + assert_equal(4, eval(<<~EOS, Sandbox::BINDING)) + meth = Integer.instance_method(:abs) + using InstanceMethodIntegerPowEx + meth.bind(-4).call + EOS + assert_equal(:refine_abs, eval_using(InstanceMethodIntegerPowEx, "Integer.instance_method(:abs).bind(-4).call")) + end + + def test_no_local_rebinding + 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("recv = self; recv.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 + + def self.suppress_verbose + verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = verbose + end + + def test_include_into_refinement + assert_raise(TypeError) do + c = Class.new + mixin = Module.new + + Module.new do + refine c do + include mixin + end + end + end + end + + def test_prepend_into_refinement + assert_raise(TypeError) do + c = Class.new + mixin = Module.new + + Module.new do + refine c do + prepend mixin + end + end + end + 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 + omit "too slow cpu" + end + assert_normal_exit %Q{ + GC.stress = true + 10.times{ + #{PrependAfterRefine_CODE} + Object.send(:remove_const, :PrependAfterRefine) + } + }, timeout: 60 + 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!'.dup.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_refined_protected_methods + assert_separately([], <<-"end;") + bug18806 = '[ruby-core:108705] [Bug #18806]' + class C; end + + module R + refine C do + def refined_call_foo = foo + def refined_call_foo_on(other) = other.foo + + protected + + def foo = :foo + end + end + + class C + using R + + def call_foo = foo + def call_foo_on(other) = other.foo + end + + c = C.new + assert_equal :foo, c.call_foo, bug18806 + assert_equal :foo, c.call_foo_on(c), bug18806 + assert_equal :foo, c.call_foo_on(C.new), bug18806 + + using R + assert_equal :foo, c.refined_call_foo, bug18806 + assert_equal :foo, c.refined_call_foo_on(c), bug18806 + assert_equal :foo, c.refined_call_foo_on(C.new), bug18806 + end; + end + + def test_refine_basic_object + assert_separately([], <<-"end;") + bug10106 = '[ruby-core:64166] [Bug #10106]' + 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 + ext + 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;") + class C + end + + module R + refine C do + def m + :foo + end + end + end + + using R + def m + C.new.m + end + + assert_equal(:foo, C.new.m) + assert_equal(:foo, m) + + module R + refine C do + + assert_equal(:foo, C.new.m) + assert_equal(:foo, m) + + alias m m + + assert_equal(:foo, C.new.m) + assert_equal(:foo, m) + + def m + :bar + end + + assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]") + assert_equal(:bar, m, "[Bug #20285]") + end + end + + assert_equal(:bar, C.new.m, "[ruby-core:71423] [Bug #11672]") + assert_equal(:bar, m, "[Bug #20285]") + 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 + + RefA.const_set(:REF, self) + end + end + + module RefB + refine Object do + def in_ref_b + end + + RefB.const_set(:REF, self) + end + end + + module RefC + using RefA + + refine Object do + def in_ref_c + end + + RefC.const_set(:REF, self) + end + end + + module Foo + using RefB + USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements + end + + module Bar + using RefC + USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements + end + + module Combined + using RefA + using RefB + USED_MODS = Module.used_modules + USED_REFS = Module.used_refinements + end + end + + 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_used_refinements + ref = VisibleRefinements + assert_equal [], Module.used_refinements + assert_equal [ref::RefB::REF], ref::Foo::USED_REFS + assert_equal [ref::RefC::REF], ref::Bar::USED_REFS + assert_equal [ref::RefB::REF, ref::RefA::REF], ref::Combined::USED_REFS + end + + def test_refinements + int_refinement = nil + str_refinement = nil + m = Module.new { + refine Integer do + int_refinement = self + end + + refine String do + str_refinement = self + end + } + assert_equal([int_refinement, str_refinement], m.refinements) + end + + def test_target + refinements = Module.new { + refine Integer do + end + + refine String do + end + }.refinements + assert_equal(Integer, refinements[0].target) + assert_equal(String, refinements[1].target) + 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 + + def test_public_in_refine_for_method_in_superclass + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + bug21446 = '[ruby-core:122558] [Bug #21446]' + + class CowSuper + private + def moo() "Moo"; end + end + class Cow < CowSuper + end + + module PublicCows + refine(Cow) { + public :moo + } + end + + using PublicCows + assert_equal("Moo", Cow.new.moo, bug21446) + 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(TypeError, /refinement/, bug) do + m.module_eval {include r} + end + assert_raise_with_message(TypeError, /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) + 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]') + d + end + + class RefineInUsing + module M1 + refine RefineInUsing do + def foo + :ok + end + end + end + + module M2 + using M1 + refine RefineInUsing do + def call_foo + RefineInUsing.new.foo + end + end + end + + using M2 + def self.test + new.call_foo + end + end + + def test_refine_in_using + assert_equal(:ok, RefineInUsing.test) + end + + class Bug16242 + module OtherM + end + + module M + prepend OtherM + + refine M do + def refine_method + "refine_method" + end + end + using M + + def hoge + refine_method + end + end + + class X + include M + end + end + + def test_refine_prepended_module + assert_equal("refine_method", Bug16242::X.new.hoge) + end + + module Bug13446 + module Enumerable + def sum(*args) + i = 0 + args.each { |arg| i += a } + i + end + end + + using Module.new { + refine Enumerable do + alias :orig_sum :sum + end + } + + module Enumerable + def sum(*args) + orig_sum(*args) + end + end + + class GenericEnumerable + include Enumerable + end + + Enumerable.prepend(Module.new) + end + + def test_prepend_refined_module + assert_equal(0, Bug13446::GenericEnumerable.new.sum) + end + + def test_unbound_refine_method + a = EnvUtil.labeled_class("A") do + def foo + self.class + end + end + b = EnvUtil.labeled_class("B") + bar = EnvUtil.labeled_module("R") do + break refine a do + def foo + super + end + end + end + assert_raise(TypeError) do + bar.instance_method(:foo).bind(b.new) + end + end + + def test_refine_frozen_class + verbose_bak, $VERBOSE = $VERBOSE, nil + singleton_class.instance_variable_set(:@x, self) + class << self + c = Class.new do + def foo + :cfoo + end + end + foo = Module.new do + refine c do + def foo + :rfoo + end + end + end + using foo + @x.assert_equal(:rfoo, c.new.foo) + c.freeze + foo.module_eval do + refine c do + def foo + :rfoo2 + end + def bar + :rbar + end + end + end + @x.assert_equal(:rfoo2, c.new.foo) + @x.assert_equal(:rbar, c.new.bar, '[ruby-core:71391] [Bug #11669]') + end + ensure + $VERBOSE = verbose_bak + end + + # [Bug #17386] + def test_prepended_with_method_cache + foo = Class.new do + def foo + :Foo + end + end + + code = Module.new do + def foo + :Code + end + end + + _ext = Module.new do + refine foo do + def foo; end + end + end + + obj = foo.new + + assert_equal :Foo, obj.foo + foo.prepend code + assert_equal :Code, obj.foo + end + + # [Bug #17417] + def test_prepended_with_method_cache_17417 + assert_normal_exit %q{ + module M + def hoge; end + end + + module R + refine Hash do + def except *args; end + end + end + + h = {} + h.method(:except) # put it on pCMC + Hash.prepend(M) + h.method(:except) + } + end + + def test_defining_after_cached + klass = Class.new + _refinement = Module.new { refine(klass) { def foo; end } } + klass.new.foo rescue nil # cache the refinement method entry + klass.define_method(:foo) { 42 } + assert_equal(42, klass.new.foo) + end + + # [Bug #17806] + def test_two_refinements_for_prepended_class + assert_normal_exit %q{ + module R1 + refine Hash do + def foo; :r1; end + end + end + + class Hash + prepend(Module.new) + end + + class Hash + def foo; end + end + + {}.method(:foo) # put it on pCMC + + module R2 + refine Hash do + def foo; :r2; end + end + end + + {}.foo + } + end + + # [Bug #17806] + def test_redefining_refined_for_prepended_class + klass = Class.new { def foo; end } + _refinement = Module.new do + refine(klass) { def foo; :refined; end } + end + klass.prepend(Module.new) + klass.new.foo # cache foo + klass.define_method(:foo) { :second } + assert_equal(:second, klass.new.foo) + end + + class Bug18180 + module M + refine Array do + def min; :min; end + def max; :max; end + end + end + + using M + + def t + [[1+0, 2, 4].min, [1, 2, 4].min, [1+0, 2, 4].max, [1, 2, 4].max] + end + end + + def test_refine_array_min_max + assert_equal([:min, :min, :max, :max], Bug18180.new.t) + end + + class Bug17822 + module Ext + refine(Bug17822) do + def foo = :refined + end + end + + private(def foo = :not_refined) + + module Client + using Ext + def self.call_foo + Bug17822.new.foo + end + end + end + + # [Bug #17822] + def test_privatizing_refined_method + assert_equal(:refined, Bug17822::Client.call_foo) + end + + def test_ancestors + refinement = nil + as = nil + Module.new do + refine Array do + refinement = self + as = ancestors + end + end + assert_equal([refinement], as, "[ruby-core:86949] [Bug #14744]") + end + + module TestImport + class A + def foo + "original" + end + end + + module B + BAR = "bar" + + def bar + "#{foo}:#{BAR}" + end + end + + module C + refine A do + import_methods B + + def foo + "refined" + end + end + end + + module UsingC + using C + + def self.call_bar + A.new.bar + end + end + end + + def test_import_methods + assert_equal("refined:bar", TestImport::UsingC.call_bar) + + assert_raise(ArgumentError) do + Module.new do + refine Integer do + import_methods Enumerable + end + end + end + end + + def test_inherit_singleton_methods_of_module + assert_equal([], Refinement.used_modules) + end + + def test_inlinecache + assert_separately([], <<-"end;") + module R + refine String do + def to_s = :R + end + end + + 2.times{|i| + s = ''.to_s + assert_equal '', s if i == 0 + assert_equal :R, s if i == 1 + using R if i == 0 + assert_equal :R, ''.to_s + } + end; + end + + def test_inline_cache_invalidation + klass = Class.new do + def cached_foo_callsite = foo + + def foo = :v1 + + host = self + @refinement = Module.new do + refine(host) do + def foo = :unused + end + end + end + + obj = klass.new + obj.cached_foo_callsite # prime cache + klass.class_eval do + def foo = :v2 # invalidate + end + assert_equal(:v2, obj.cached_foo_callsite) + end + + # [Bug #20302] + def test_multiple_refinements_for_same_module + assert_in_out_err([], <<-INPUT, %w(:f2 :f1), []) + module M1 + refine(Kernel) do + def f1 = :f1 + end + end + + module M2 + refine(Kernel) do + def f2 = :f2 + end + end + + class Foo + using M1 + using M2 + + def test + p f2 + p f1 + end + end + + Foo.new.test + INPUT + end + + def test_refined_module_method + m = Module.new { + x = Module.new {def qux;end} + refine(x) {def qux;end} + break x + } + extend m + meth = method(:qux) + assert_equal m, meth.owner + assert_equal :qux, meth.name + end + + def test_symbol_proc_from_using_scope + # assert_separately to contain the side effects of refining Kernel + assert_separately([], <<~RUBY) + class RefinedScope + using(Module.new { refine(Kernel) { def itself = 0 } }) + ITSELF = :itself.to_proc + end + + assert_equal(1, RefinedScope::ITSELF[1], "[Bug #21265]") + RUBY + 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 new file mode 100644 index 0000000000..9feababa53 --- /dev/null +++ b/test/ruby/test_regexp.rb @@ -0,0 +1,2318 @@ +# coding: US-ASCII +# frozen_string_literal: false +require 'test/unit' + +class TestRegexp < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $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") + end + + def test_ruby_core_27247 + assert_match(/(a){2}z/, "aaz") + end + + def test_ruby_dev_24643 + assert_nothing_raised("[ruby-dev:24643]") { + /(?:(?:[a]*[a])?b)*a*$/ =~ "aabaaca" + } + end + + def test_ruby_talk_116455 + assert_match(/^(\w{2,}).* ([A-Za-z\xa2\xc0-\xff]{2,}?)$/n, "Hallo Welt") + end + + def test_ruby_dev_24887 + assert_equal("a".gsub(/a\Z/, ""), "") + end + + def test_ruby_dev_31309 + assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-')) + end + + def test_premature_end_char_property + ["\\p{", + "\\p{".dup.force_encoding("UTF-8"), + "\\p{".dup.force_encoding("US-ASCII") + ].each do |string| + assert_raise(RegexpError) do + Regexp.new(string) + end + end + end + + def test_assert_normal_exit + # moved from knownbug. It caused core. + Regexp.union("a", "a") + end + + 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_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + 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 + 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_extended_comment_invalid_escape_bug_18294 + assert_separately([], <<-RUBY) + re = / C:\\\\[a-z]{5} # e.g. C:\\users /x + assert_match(re, 'C:\\users') + assert_not_match(re, 'C:\\user') + + re = / + foo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[#o]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[[:alnum:]#]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f(?# \\M-ca)oo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# \\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /[-(?# fca)]oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# ca\0\\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + RUBY + + assert_raise(SyntaxError) {eval "/\\users/x"} + assert_raise(SyntaxError) {eval "/[\\users]/x"} + assert_raise(SyntaxError) {eval "/(?<\\users)/x"} + assert_raise(SyntaxError) {eval "/# \\users/"} + end + + def test_nonextended_section_of_extended_regexp_bug_19379 + assert_separately([], <<-'RUBY') + re = /(?-x:#)/x + assert_match(re, '#') + assert_not_match(re, '-') + + re = /(?xi:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?mix:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?x-im:# + y)/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + y))/x + assert_match(re, 'y') + assert_not_match(re, '-') + + re = /(?x)# + y/ + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?mx-i)# + y/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + (?-x)y#))/x + assert_match(re, 'Y#') + assert_not_match(re, '-#') + + re = /(?imx:# + (?-xim:#(?im)#(?x)# + )# + (?x)# + y)/ + assert_match(re, '###Y') + assert_not_match(re, '###-') + + re = %r{#c-\w+/comment/[\w-]+} + re = %r{https?://[^/]+#{re}}x + assert_match(re, 'http://foo#c-x/comment/bar') + assert_not_match(re, 'http://foo#cx/comment/bar') + RUBY + end + + def test_utf8_comment_in_usascii_extended_regexp_bug_19455 + assert_separately([], <<-RUBY) + assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding) + assert_equal(Encoding::UTF_8, /#\u1000/x.encoding) + RUBY + end + + def test_union + assert_equal :ok, begin + Regexp.union( + "a", + Regexp.new("\xc2\xa1".force_encoding("euc-jp")), + Regexp.new("\xc2\xa1".force_encoding("utf-8"))) + :ng + 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 + m = /&(?<foo>.*?);/.match("aaa & yyy") + assert_equal("amp", m["foo"]) + assert_equal("amp", m[:foo]) + assert_equal(5, m.begin(:foo)) + assert_equal(8, m.end(:foo)) + assert_equal([5,8], m.offset(:foo)) + + assert_equal("aaa [amp] yyy", + "aaa & yyy".sub(/&(?<foo>.*?);/, '[\k<foo>]')) + + assert_equal('#<MatchData "& y" foo:"amp">', + /&(?<foo>.*?); (y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" 1:"amp" 2:"y">', + /&(.*?); (y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" foo:"amp" bar:"y">', + /&(?<foo>.*?); (?<bar>y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" foo:"amp" foo:"y">', + /&(?<foo>.*?); (?<foo>y)/.match("aaa & yyy").inspect) + + /(?<_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) + assert_equal({"foo"=>[1, 2]}, + /(?<foo>.)(?<foo>.)/.named_captures) + assert_equal({}, /(.)(.)/.named_captures) + + 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: '1', b: '2', c: ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: true)) + assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: false)) + + 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"') } + assert_nil(eval('/(?<Foo>.)/ =~ "a"; defined? Foo')) + end + + def test_assign_named_capture_to_reserved_word + /(?<nil>.)/ =~ "a" + assert_not_include(local_variables, :nil, "[ruby-dev:32675]") + + def (obj = Object.new).test(s, nil: :ng) + /(?<nil>.)/ =~ s + binding.local_variable_get(:nil) + end + assert_equal("b", obj.test("b")) + + tap do |nil: :ng| + /(?<nil>.)/ =~ "c" + assert_equal("c", binding.local_variable_get(:nil)) + end + end + + def test_assign_named_capture_to_const + %W[C \u{1d402}].each do |name| + assert_equal(:ok, Class.new.class_eval("#{name} = :ok; /(?<#{name}>.*)/ =~ 'ng'; #{name}")) + end + end + + def test_assign_named_capture_trace + bug = '[ruby-core:79940] [Bug #13287]' + assert_normal_exit("#{<<-"begin;"}\n#{<<-"end;"}", bug) + begin; + / (?<foo>.*)/ =~ "bar" && + true + end; + end + + def test_match_regexp + r = /./ + m = r.match("a") + assert_equal(r, m.regexp) + re = /foo/ + assert_equal(re, re.match("foo").regexp) + end + + def test_match_lambda_multithread + bug17507 = "[ruby-core:101901]" + str = "a-x-foo-bar-baz-z-b" + + worker = lambda do + m = /foo-([A-Za-z0-9_\.]+)-baz/.match(str) + assert_equal("bar", m[1], bug17507) + + # These two lines are needed to trigger the bug + File.exist? "/tmp" + str.gsub(/foo-bar-baz/, "foo-abc-baz") + end + + def self. threaded_test(worker) + 6.times.map {Thread.new {10_000.times {worker.call}}}.each(&:join) + end + + # The bug only occurs in a method calling a block/proc/lambda + threaded_test(worker) + end + + def test_source + bug5484 = '[ruby-core:40364]' + assert_equal('', //.source) + 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 + assert_equal('//', //.inspect) + assert_equal('//i', //i.inspect) + assert_equal('/\//i', /\//i.inspect) + assert_equal('/\//i', %r"#{'/'}"i.inspect) + assert_equal('/\/x/i', /\/x/i.inspect) + assert_equal('/\x00/i', /#{"\0"}/i.inspect) + assert_equal("/\n/i", /#{"\n"}/i.inspect) + s = [0xf1, 0xf2, 0xf3].pack("C*") + assert_equal('/\/\xF1\xF2\xF3/i', /\/#{s}/i.inspect) + end + + def test_inspect_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + assert_equal('/(?-mix:\\/)|/', Regexp.union(/\//, "").inspect) + end + end + + def test_char_to_option + assert_equal("BAR", "FOOBARBAZ"[/b../i]) + assert_equal("bar", "foobarbaz"[/ b . . /x]) + assert_equal("bar\n", "foo\nbar\nbaz"[/b.../m]) + assert_raise(SyntaxError) { eval('//z') } + end + + def test_char_to_option_kcode + assert_equal("bar", "foobarbaz"[/b../s]) + assert_equal("bar", "foobarbaz"[/b../e]) + assert_equal("bar", "foobarbaz"[/b../u]) + end + + def test_to_s2 + assert_equal('(?-mix:foo)', /(?:foo)/.to_s) + assert_equal('(?m-ix:foo)', /(?:foo)/m.to_s) + assert_equal('(?mi-x:foo)', /(?:foo)/mi.to_s) + assert_equal('(?mix:foo)', /(?:foo)/mix.to_s) + assert_equal('(?m-ix:foo)', /(?m-ix:foo)/.to_s) + assert_equal('(?mi-x:foo)', /(?mi-x:foo)/.to_s) + assert_equal('(?mix:foo)', /(?mix:foo)/.to_s) + assert_equal('(?mix:)', /(?mix)/.to_s) + assert_equal('(?-mix:(?mix:foo) )', /(?mix:foo) /.to_s) + end + + def test_casefold_p + assert_equal(false, /a/.casefold?) + assert_equal(true, /a/i.casefold?) + assert_equal(false, /(?i:a)/.casefold?) + end + + def test_options + assert_equal(Regexp::IGNORECASE, /a/i.options) + assert_equal(Regexp::EXTENDED, /a/x.options) + assert_equal(Regexp::MULTILINE, /a/m.options) + end + + def test_match_init_copy + m = /foo/.match("foo") + assert_equal(/foo/, m.dup.regexp) + assert_raise(TypeError) do + m.instance_eval { initialize_copy(nil) } + end + assert_equal([0, 3], m.offset(0)) + assert_equal(/foo/, m.dup.regexp) + end + + def test_match_size + m = /(.)(.)(\d+)(\d)/.match("THX1138.") + assert_equal(5, m.size) + end + + def test_match_offset_begin_end + m = /(?<x>b..)/.match("foobarbaz") + assert_equal([3, 6], m.offset("x")) + assert_equal(3, m.begin("x")) + assert_equal(6, m.end("x")) + assert_raise(IndexError) { m.offset("y") } + assert_raise(IndexError) { m.offset(2) } + assert_raise(IndexError) { m.begin(2) } + assert_raise(IndexError) { m.end(2) } + + m = /(?<x>q..)?/.match("foobarbaz") + assert_equal([nil, nil], m.offset("x")) + assert_equal(nil, m.begin("x")) + assert_equal(nil, m.end("x")) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal([1, 2], m.offset(1)) + assert_equal([nil, nil], m.offset(2)) + assert_equal([2, 3], m.offset(3)) + end + + def test_match_byteoffset_begin_end + m = /(?<x>b..)/.match("foobarbaz") + assert_equal([3, 6], m.byteoffset("x")) + assert_equal(3, m.begin("x")) + assert_equal(6, m.end("x")) + assert_raise(IndexError) { m.byteoffset("y") } + assert_raise(IndexError) { m.byteoffset(2) } + assert_raise(IndexError) { m.begin(2) } + assert_raise(IndexError) { m.end(2) } + assert_raise(IndexError) { m.bytebegin(2) } + assert_raise(IndexError) { m.byteend(2) } + + m = /(?<x>q..)?/.match("foobarbaz") + assert_equal([nil, nil], m.byteoffset("x")) + assert_equal(nil, m.begin("x")) + assert_equal(nil, m.end("x")) + assert_equal(nil, m.bytebegin("x")) + assert_equal(nil, m.byteend("x")) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal([3, 6], m.byteoffset(1)) + assert_equal(3, m.bytebegin(1)) + assert_equal(6, m.byteend(1)) + assert_equal([nil, nil], m.byteoffset(2)) + assert_equal(nil, m.bytebegin(2)) + assert_equal(nil, m.byteend(2)) + assert_equal([6, 9], m.byteoffset(3)) + assert_equal(6, m.bytebegin(3)) + assert_equal(9, m.byteend(3)) + end + + def test_match_to_s + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("bar", m.to_s) + end + + def test_match_pre_post + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("foo", m.pre_match) + assert_equal("baz", m.post_match) + end + + def test_match_array + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal(["foobarbaz", "foo", "bar", "baz", nil], m.to_a) + end + + def test_match_captures + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal(["foo", "bar", "baz", nil], m.captures) + end + + 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] } + assert_equal(["baz", nil], m[-2, 3]) + 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 + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("foobarbaz", m.string) + end + + def test_match_matchsubstring + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal("HX1138", m.match(0)) + assert_equal("8", m.match(4)) + assert_nil(m.match(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal("\u3043", m.match(1)) + assert_nil(m.match(2)) + assert_equal("\u3044", m.match(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal("h", m.match(:foo)) + assert_nil(m.match(:n)) + assert_equal("oge\u3042", m.match(:bar)) + end + + def test_match_match_length + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal(6, m.match_length(0)) + assert_equal(1, m.match_length(4)) + assert_nil(m.match_length(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal(1, m.match_length(1)) + assert_nil(m.match_length(2)) + assert_equal(1, m.match_length(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal(1, m.match_length(:foo)) + assert_nil(m.match_length(:n)) + assert_equal(4, m.match_length(:bar)) + end + + def test_match_inspect + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect) + end + + def test_match_data_deconstruct + m = /foo.+/.match("foobarbaz") + assert_equal([], m.deconstruct) + + m = /(foo).+(baz)/.match("foobarbaz") + assert_equal(["foo", "baz"], m.deconstruct) + + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal(["foo", "bar", "baz", nil], m.deconstruct) + end + + def test_match_data_deconstruct_keys + m = /foo.+/.match("foobarbaz") + assert_equal({}, m.deconstruct_keys([:a])) + + m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz") + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil)) + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b])) + assert_equal({b: "baz"}, m.deconstruct_keys([:b])) + assert_equal({}, m.deconstruct_keys([:c, :a])) + assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c])) + assert_equal({}, m.deconstruct_keys([:a, :b, :c])) + + assert_raise(TypeError) { + m.deconstruct_keys(0) + } + + assert_raise(TypeError) { + m.deconstruct_keys(["a", "b"]) + } + end + + def test_match_no_match_no_matchdata + EnvUtil.without_gc do + h = {} + ObjectSpace.count_objects(h) + prev_matches = h[:T_MATCH] || 0 + _md = /[A-Z]/.match('1') # no match + ObjectSpace.count_objects(h) + new_matches = h[:T_MATCH] || 0 + assert_equal prev_matches, new_matches, "Bug [#20104]" + end + end + + def test_initialize + assert_raise(ArgumentError) { Regexp.new } + assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)}) + + arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value + + assert_deprecated_warning('') do + assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding) + assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)]) + assert_equal(//, Regexp.new("")) + assert_equal(//, Regexp.new("", timeout: 1)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1)) + + assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options) + + assert_nil(Regexp.new("").timeout) + assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout) + assert_nil(Regexp.compile("").timeout) + assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout) + end + + 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_initialize_from_regex_memory_corruption + assert_ruby_status([], <<-'end;') + 10_000.times { Regexp.new(Regexp.new("(?<name>)")) } + end; + end + + def test_initialize_bool_warning + assert_warning(/expected true or false as ignorecase/) do + Regexp.new("foo", :i) + end + end + + def test_initialize_option + assert_equal(//i, Regexp.new("", "i")) + assert_equal(//m, Regexp.new("", "m")) + assert_equal(//x, Regexp.new("", "x")) + assert_equal(//imx, Regexp.new("", "imx")) + assert_equal(//, Regexp.new("", "")) + assert_equal(//imx, Regexp.new("", "mimix")) + + assert_raise(ArgumentError) { Regexp.new("", "e") } + assert_raise(ArgumentError) { Regexp.new("", "n") } + assert_raise(ArgumentError) { Regexp.new("", "s") } + assert_raise(ArgumentError) { Regexp.new("", "u") } + assert_raise(ArgumentError) { Regexp.new("", "o") } + assert_raise(ArgumentError) { Regexp.new("", "j") } + assert_raise(ArgumentError) { Regexp.new("", "xmen") } + end + + def test_match_control_meta_escape + assert_equal(0, /\c\xFF/ =~ "\c\xFF") + assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF") + assert_equal(0, /\C-\xFF/ =~ "\C-\xFF") + assert_equal(0, /\C-\M-\xFF/ =~ "\C-\M-\xFF") + assert_equal(0, /\M-\xFF/ =~ "\M-\xFF") + assert_equal(0, /\M-\C-\xFF/ =~ "\M-\C-\xFF") + assert_equal(0, /\M-\c\xFF/ =~ "\M-\c\xFF") + + assert_nil(/\c\xFE/ =~ "\c\xFF") + assert_nil(/\c\M-\xFE/ =~ "\c\M-\xFF") + assert_nil(/\C-\xFE/ =~ "\C-\xFF") + assert_nil(/\C-\M-\xFE/ =~ "\C-\M-\xFF") + assert_nil(/\M-\xFE/ =~ "\M-\xFF") + assert_nil(/\M-\C-\xFE/ =~ "\M-\C-\xFF") + assert_nil(/\M-\c\xFE/ =~ "\M-\c\xFF") + end + + def test_unescape + assert_raise(ArgumentError) { s = '\\'; /#{ s }/ } + assert_equal(/\xFF/n, /#{ s="\\xFF" }/n) + assert_equal(/\177/, (s = '\177'; /#{ s }/)) + assert_raise(ArgumentError) { s = '\u'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffffffff }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffffff }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffff X }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ }'; /#{ s }/ } + assert_equal("b", "abc"[(s = '\u{0062}'; /#{ s }/)]) + assert_equal("b", "abc"[(s = '\u0062'; /#{ s }/)]) + assert_raise(ArgumentError) { s = '\u0'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u000X'; /#{ s }/ } + assert_raise(ArgumentError) { s = "\xff" + '\u3042'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u3042' + [0xff].pack("C"); /#{ s }/ } + assert_raise(SyntaxError) { s = ''; eval(%q(/\u#{ s }/)) } + + 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}/)); [s1, s2] } + + assert_raise(ArgumentError) { s = '\x'; /#{ s }/ } + + assert_equal("\xe1", [0x00, 0xe1, 0xff].pack("C*")[/\M-a/]) + assert_equal("\xdc", [0x00, 0xdc, 0xff].pack("C*")[/\M-\\/]) + assert_equal("\x8a", [0x00, 0x8a, 0xff].pack("C*")[/\M-\n/]) + assert_equal("\x89", [0x00, 0x89, 0xff].pack("C*")[/\M-\t/]) + assert_equal("\x8d", [0x00, 0x8d, 0xff].pack("C*")[/\M-\r/]) + assert_equal("\x8c", [0x00, 0x8c, 0xff].pack("C*")[/\M-\f/]) + assert_equal("\x8b", [0x00, 0x8b, 0xff].pack("C*")[/\M-\v/]) + assert_equal("\x87", [0x00, 0x87, 0xff].pack("C*")[/\M-\a/]) + assert_equal("\x9b", [0x00, 0x9b, 0xff].pack("C*")[/\M-\e/]) + assert_equal("\x01", [0x00, 0x01, 0xff].pack("C*")[/\C-a/]) + + assert_raise(ArgumentError) { s = '\M'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\M-a'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\\'; /#{ s }/ } + + assert_raise(ArgumentError) { s = '\C'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\c'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\C-\C-a'; /#{ s }/ } + + assert_raise(ArgumentError) { s = '\M-\z'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\777'; /#{ s }/ } + + assert_equal("\u3042\u3042", "\u3042\u3042"[(s = "\u3042" + %q(\xe3\x81\x82); /#{s}/)]) + assert_raise(ArgumentError) { s = "\u3042" + %q(\xe3); /#{s}/ } + assert_raise(ArgumentError) { s = "\u3042" + %q(\xe3\xe3); /#{s}/ } + assert_raise(ArgumentError) { s = '\u3042' + [0xff].pack("C"); /#{s}/ } + + assert_raise(SyntaxError) { eval("/\u3042/n") } + + s = ".........." + 5.times { s.sub!(".", "") } + assert_equal(".....", s) + + assert_equal("\\\u{3042}", Regexp.new("\\\u{3042}").source) + end + + def test_equal + 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 + assert_nil(//.match(nil)) + assert_equal("abc", /.../.match(:abc)[0]) + assert_raise(TypeError) { /.../.match(Object.new)[0] } + assert_equal("bc", /../.match('abc', 1)[0]) + assert_equal("bc", /../.match('abc', -2)[0]) + assert_nil(/../.match("abc", -4)) + assert_nil(/../.match("abc", 4)) + + # use eval because only one warning is shown for the same regexp literal + pat = eval('/../n') + assert_equal('\x', assert_warning(/binary regexp/) {pat.match("\u3042" + '\x', 1)}[0]) + + r = nil + /.../.match("abc") {|m| r = m[0] } + assert_equal("abc", r) + + $_ = "abc"; assert_equal(1, ~/bc/) + $_ = "abc"; assert_nil(~/d/) + $_ = nil; assert_nil(~/./) + end + + def test_match_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal("h", m.match(:foo)) + end + 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 + + def test_quote + assert_equal("\xff", Regexp.quote([0xff].pack("C"))) + assert_equal("\\ ", Regexp.quote("\ ")) + assert_equal("\\t", Regexp.quote("\t")) + assert_equal("\\n", Regexp.quote("\n")) + assert_equal("\\r", Regexp.quote("\r")) + assert_equal("\\f", Regexp.quote("\f")) + 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 + assert_equal(/re/, Regexp.try_convert(/re/)) + assert_nil(Regexp.try_convert("re")) + + o = Object.new + assert_nil(Regexp.try_convert(o)) + def o.to_regexp() /foo/ end + assert_equal(/foo/, Regexp.try_convert(o)) + end + + def test_union2 + assert_equal(/(?!)/, Regexp.union) + assert_equal(/foo/, Regexp.union(/foo/)) + assert_equal(/foo/, Regexp.union([/foo/])) + assert_equal(/\t/, Regexp.union("\t")) + assert_equal(/(?-mix:\u3042)|(?-mix:\u3042)/, Regexp.union(/\u3042/, /\u3042/)) + assert_equal("\u3041", "\u3041"[Regexp.union(/\u3042/, "\u3041")]) + end + + def test_dup + assert_equal(//, //.dup) + assert_raise(TypeError) { //.dup.instance_eval { initialize_copy(nil) } } + end + + def test_regsub + assert_equal("fooXXXbaz", "foobarbaz".sub!(/bar/, "XXX")) + s = [0xff].pack("C") + assert_equal(s, "X".sub!(/./, s)) + assert_equal('\\' + s, "X".sub!(/./, '\\' + s)) + assert_equal('\k', "foo".sub!(/.../, '\k')) + assert_raise(RuntimeError) { "foo".sub!(/(?<x>o)/, '\k<x') } + assert_equal('foo[bar]baz', "foobarbaz".sub!(/(b..)/, '[\0]')) + assert_equal('foo[foo]baz', "foobarbaz".sub!(/(b..)/, '[\`]')) + assert_equal('foo[baz]baz', "foobarbaz".sub!(/(b..)/, '[\\\']')) + assert_equal('foo[r]baz', "foobarbaz".sub!(/(b)(.)(.)/, '[\+]')) + assert_equal('foo[\\]baz', "foobarbaz".sub!(/(b..)/, '[\\\\]')) + 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_regsub_no_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true) + code = proc do + "aaaaaaaaaaa".gsub(/a/, "") + end + + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_ignorecase + v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= } + assert_equal(false, v) + assert_deprecated_warning(/variable \$= is no longer effective; ignored/) { $= = nil } + end + + def test_match_setter + /foo/ =~ "foo" + m = $~ + /bar/ =~ "bar" + $~ = m + 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]' + bug18160 = '[Bug #18160]' + test.call {|m| assert_raise_with_message(IndexError, /foo/, bug10877) {m["foo"]} } + key = "\u{3042}" + [Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc| + idx = key.encode(enc) + pat = /#{idx}/ + EnvUtil.with_default_internal(enc) do + test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} } + end + 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)) + assert_equal("foo", Regexp.last_match(1)) + assert_nil(Regexp.last_match(5)) + assert_nil(Regexp.last_match(-1)) + end + + def test_getter + alias $__REGEXP_TEST_LASTMATCH__ $& + alias $__REGEXP_TEST_PREMATCH__ $` + alias $__REGEXP_TEST_POSTMATCH__ $' + alias $__REGEXP_TEST_LASTPARENMATCH__ $+ + /(b)(.)(.)/.match("foobarbaz") + assert_equal("bar", $__REGEXP_TEST_LASTMATCH__) + assert_equal("foo", $__REGEXP_TEST_PREMATCH__) + assert_equal("baz", $__REGEXP_TEST_POSTMATCH__) + assert_equal("r", $__REGEXP_TEST_LASTPARENMATCH__) + + /(...)(...)(...)/.match("foobarbaz") + assert_equal("baz", $+) + end + + def test_rindex_regexp + # use eval because only one warning is shown for the same regexp literal + pat = eval('/b../n') + assert_equal(3, assert_warning(/binary regexp/) {"foobarbaz\u3042".rindex(pat, 5)}) + end + + def assert_regexp(re, ss, fs = [], msg = nil) + re = EnvUtil.suppress_warning {Regexp.new(re)} unless re.is_a?(Regexp) + ss = [ss] unless ss.is_a?(Array) + ss.each do |e, s| + s ||= e + assert_match(re, s, msg) + m = re.match(s) + assert_equal(e, m[0], msg) + end + fs = [fs] unless fs.is_a?(Array) + fs.each {|s| assert_no_match(re, s, msg) } + end + alias check assert_regexp + + def assert_fail(re) + assert_raise(RegexpError) { %r"#{ re }" } + end + alias failcheck assert_fail + + def test_parse + check(/\*\+\?\{\}\|\(\)\<\>\`\'/, "*+?{}|()<>`'") + check(/\A\w\W\z/, %w(a. b!), %w(.. ab)) + check(/\A.\b.\b.\B.\B.\z/, %w(a.aaa .a...), %w(aaaaa .....)) + check(/\A\s\S\z/, [' a', "\n."], [' ', "\n\n", 'a ']) + check(/\A\d\D\z/, '0a', %w(00 aa)) + check(/\A\h\H\z/, %w(0g ag BH), %w(a0 af GG)) + check(/\Afoo\Z\s\z/, "foo\n", ["foo", "foo\nbar"]) + assert_equal(%w(a b c), "abc def".scan(/\G\w/)) + check(/\A\u3042\z/, "\u3042", ["", "\u3043", "a"]) + check(/\A(..)\1\z/, %w(abab ....), %w(abba aba)) + failcheck('\1') + check(/\A\80\z/, "80", ["\100", ""]) + check(/\A\77\z/, "?") + check(/\A\78\z/, "\7" + '8', ["\100", ""]) + check(assert_warning(/Unknown escape/) {eval('/\A\Qfoo\E\z/')}, "QfooE") + check(/\Aa++\z/, "aaa") + check('\Ax]\z', "x]") + check(/x#foo/x, "x", "#foo") + check(/\Ax#foo#{ "\n" }x\z/x, "xx", ["x", "x#foo\nx"]) + check(/\A\p{Alpha}\z/, ["a", "z"], [".", "", ".."]) + check(/\A\p{^Alpha}\z/, [".", "!"], ["!a", ""]) + check(/\A\n\z/, "\n") + check(/\A\t\z/, "\t") + check(/\A\r\z/, "\r") + check(/\A\f\z/, "\f") + check(/\A\a\z/, "\007") + check(/\A\e\z/, "\033") + check(/\A\v\z/, "\v") + failcheck('(') + failcheck('(?foo)') + failcheck('/\p{foobarbazqux}/') + failcheck('/\p{foobarbazqux' + 'a' * 1000 + '}/') + failcheck('/[1-\w]/') + end + + def test_exec + check(/A*B/, %w(B AB AAB AAAB), %w(A)) + check(/\w*!/, %w(! a! ab! abc!), %w(abc)) + check(/\w*\W/, %w(! a" ab# abc$), %w(abc)) + check(/\w*\w/, %w(z az abz abcz), %w(!)) + check(/[a-z]*\w/, %w(z az abz abcz), %w(!)) + check(/[a-z]*\W/, %w(! a" ab# abc$), %w(A)) + check(/((a|bb|ccc|dddd)(1|22|333|4444))/i, %w(a1 bb1 a22), %w(a2 b1)) + check(/\u0080/, (1..4).map {|i| ["\u0080", "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080/, (2..4).map {|i| ["\u0080" * 2, "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080\u0080/, (3..4).map {|i| ["\u0080" * 3, "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080\u0080\u0080/, (4..4).map {|i| ["\u0080" * 4, "\u0080" * i] }, ["\u0081"]) + check(/[^\u3042\u3043\u3044]/, %W(a b \u0080 \u3041 \u3045), %W(\u3042 \u3043 \u3044)) + check(/a.+/m, %W(a\u0080 a\u0080\u0080 a\u0080\u0080\u0080), %W(a)) + check(/a.+z/m, %W(a\u0080z a\u0080\u0080z a\u0080\u0080\u0080z), %W(az)) + check(/abc\B.\Bxyz/, %w(abcXxyz abc0xyz), %w(abc|xyz abc-xyz)) + check(/\Bxyz/, [%w(xyz abcXxyz), %w(xyz abc0xyz)], %w(abc xyz abc-xyz)) + check(/abc\B/, [%w(abc abcXxyz), %w(abc abc0xyz)], %w(abc xyz abc-xyz)) + failcheck('(?<foo>abc)\1') + check(/^(A+|B+)(?>\g<1>)*[BC]$/, %w(AC BC ABC BAC AABBC), %w(AABB)) + check(/^(A+|B(?>\g<1>)*)[AC]$/, %w(AAAC BBBAAAAC), %w(BBBAAA)) + check(/^()(?>\g<1>)*$/, "", "a") + check(/^(?>(?=a)(#{ "a" * 1000 }|))++$/, ["a" * 1000, "a" * 2000, "a" * 3000], ["", "a" * 500, "b" * 1000]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a?)?$/')}, ["", "a"], ["aa"]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a+)?$/')}, ["", "a", "aa"], ["ab"]) + check(/^(?:a?)+?$/, ["", "a", "aa"], ["ab"]) + check(/^a??[ab]/, [["a", "a"], ["a", "aa"], ["b", "b"], ["a", "ab"]], ["c"]) + check(/^(?:a*){3,5}$/, ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"], ["b"]) + check(/^(?:a+){3,5}$/, ["aaa", "aaaa", "aaaaa", "aaaaaa"], ["", "a", "aa", "b"]) + end + + def test_parse_look_behind + check(/(?<=A)B(?=C)/, [%w(B ABC)], %w(aBC ABc aBc)) + check(/(?<!A)B(?!C)/, [%w(B aBc)], %w(ABC aBC ABc)) + failcheck('(?<=.*)') + 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 + check(/\A(.)(.)\k<1>(.)\z/, %w(abac abab ....), %w(abcd aaba xxx)) + check(/\A(.)(.)\k<-1>(.)\z/, %w(abbc abba ....), %w(abcd aaba xxx)) + check(/\A(?<n>.)(?<x>\g<n>){0}(?<y>\k<n+0>){0}\g<x>\g<y>\z/, "aba", "abb") + check(/\A(?<n>.)(?<x>\g<n>){0}(?<y>\k<n+1>){0}\g<x>\g<y>\z/, "abb", "aba") + check(/\A(?<x>..)\k<x>\z/, %w(abab ....), %w(abac abba xxx)) + check(/\A(.)(..)\g<-1>\z/, "abcde", %w(.... ......)) + failcheck('\k<x>') + failcheck('\k<') + failcheck('\k<>') + failcheck('\k<.>') + failcheck('\k<x.>') + failcheck('\k<1.>') + failcheck('\k<x') + failcheck('\k<x+') + failcheck('()\k<-2>') + failcheck('()\g<-2>') + check(/\A(?<x>.)(?<x>.)\k<x>\z/, %w(aba abb), %w(abc .. ....)) + check(/\A(?<x>.)(?<x>.)\k<x>\z/i, %w(aba ABa abb ABb), %w(abc .. ....)) + check('\k\g', "kg") + failcheck('(.\g<1>)') + failcheck('(.\g<2>)') + failcheck('(?=\g<1>)') + failcheck('((?=\g<1>))') + failcheck('(\g<1>|.)') + failcheck('(.|\g<1>)') + check(/(!)(?<=(a)|\g<1>)/, ["!"], %w(a)) + check(/^(a|b\g<1>c)$/, %w(a bac bbacc bbbaccc), %w(bbac bacc)) + check(/^(a|b\g<2>c)(B\g<1>C){0}$/, %w(a bBaCc bBbBaCcCc bBbBbBaCcCcCc), %w(bBbBaCcC BbBaCcCc)) + check(/\A(?<n>.|X\g<n>)(?<x>\g<n>){0}(?<y>\k<n+0>){0}\g<x>\g<y>\z/, "XXaXbXXa", %w(XXabXa abb)) + check(/\A(?<n>.|X\g<n>)(?<x>\g<n>){0}(?<y>\k<n+1>){0}\g<x>\g<y>\z/, "XaXXbXXb", %w(aXXbXb aba)) + failcheck('(?<x>)(?<x>)(\g<x>)') + check(/^(?<x>foo)(bar)\k<x>/, %w(foobarfoo), %w(foobar barfoo)) + check(/^(?<a>f)(?<a>o)(?<a>o)(?<a>b)(?<a>a)(?<a>r)(?<a>b)(?<a>a)(?<a>z)\k<a>{9}$/, %w(foobarbazfoobarbaz foobarbazbazbarfoo foobarbazzabraboof), %w(foobar barfoo)) + end + + def test_parse_curly_brace + check(/\A{/, ["{", ["{", "{x"]]) + check(/\A{ /, ["{ ", ["{ ", "{ x"]]) + check(/\A{,}\z/, "{,}") + check(/\A{}\z/, "{}") + check(/\Aa{0}+\z/, "", %w(a aa aab)) + check(/\Aa{1}+\z/, %w(a aa), ["", "aab"]) + check(/\Aa{1,2}b{1,2}\z/, %w(ab aab abb aabb), ["", "aaabb", "abbb"]) + check(/(?!x){0,1}/, [ ['', 'ab'], ['', ''] ]) + check(/c\z{0,1}/, [ ['c', 'abc'], ['c', 'cab']], ['abd']) + check(/\A{0,1}a/, [ ['a', 'abc'], ['a', '____abc']], ['bcd']) + failcheck('.{100001}') + failcheck('.{0,100001}') + failcheck('.{1,0}') + failcheck('{0}') + end + + def test_parse_comment + check(/\A(?#foo\)bar)\z/, "", "a") + failcheck('(?#') + end + + def test_char_type + check(/\u3042\d/, ["\u30421", "\u30422"]) + + # CClassTable cache test + assert_match(/\u3042\d/, "\u30421") + assert_match(/\u3042\d/, "\u30422") + end + + def test_char_class + failcheck('[]') + failcheck('[x') + check('\A[]]\z', "]", "") + check('\A[]\.]+\z', %w(] . ]..]), ["", "["]) + check(/\A[\u3042]\z/, "\u3042", "\u3042aa") + check(/\A[\u3042\x61]+\z/, ["aa\u3042aa", "\u3042\u3042", "a"], ["", "b"]) + check(/\A[\u3042\x61\x62]+\z/, "abab\u3042abab\u3042") + check(/\A[abc]+\z/, "abcba", ["", "ada"]) + check(/\A[\w][\W]\z/, %w(a. b!), %w(.. ab)) + check(/\A[\s][\S]\z/, [' a', "\n."], [' ', "\n\n", 'a ']) + check(/\A[\d][\D]\z/, '0a', %w(00 aa)) + check(/\A[\h][\H]\z/, %w(0g ag BH), %w(a0 af GG)) + check(/\A[\p{Alpha}]\z/, ["a", "z"], [".", "", ".."]) + check(/\A[\p{^Alpha}]\z/, [".", "!"], ["!a", ""]) + check(/\A[\xff]\z/, "\xff", ["", "\xfe"]) + check(/\A[\80]+\z/, "8008", ["\\80", "\100", "\1000"]) + check(/\A[\77]+\z/, "???") + check(/\A[\78]+\z/, "\788\7") + check(/\A[\0]\z/, "\0") + check(/\A[[:0]]\z/, [":", "0"], ["", ":0"]) + check(/\A[0-]\z/, ["0", "-"], "0-") + check('\A[a-&&\w]\z', "a", "-") + check('\A[--0]\z', ["-", "/", "0"], ["", "1"]) + check('\A[\'--0]\z', %w(* + \( \) 0 ,), ["", ".", "1"]) + 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)) + 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"]) + failcheck('[9-1]') + + assert_match(/\A\d+\z/, "0123456789") + assert_no_match(/\d/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19") + assert_match(/\A\w+\z/, "09azAZ_") + assert_no_match(/\w/, "\uff10\uff19\uff41\uff5a\uff21\uff3a") + assert_match(/\A\s+\z/, "\r\n\v\f\r\s") + assert_no_match(/\s/, "\u0085") + end + + def test_posix_bracket + check(/\A[[:alpha:]0]\z/, %w(0 a), %w(1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:^alpha:]0]\z/')}, %w(0 1 .), "a") + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha\:]]\z/')}, %w(a l p h a :), %w(b 0 1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha:foo]0]\z/')}, %w(0 a), %w(1 .)) + check(/\A[[:xdigit:]&&[:alpha:]]\z/, "a", %w(g 0)) + check('\A[[:abcdefghijklmnopqrstu:]]+\z', "[]") + failcheck('[[:alpha') + assert_warning(/duplicated range/) {failcheck('[[:alpha:')} + failcheck('[[:alp:]]') + + assert_match(/\A[[:digit:]]+\z/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19") + assert_match(/\A[[:alnum:]]+\z/, "\uff10\uff19\uff41\uff5a\uff21\uff3a") + assert_match(/\A[[:space:]]+\z/, "\r\n\v\f\r\s\u0085") + assert_match(/\A[[:ascii:]]+\z/, "\x00\x7F") + assert_no_match(/[[:ascii:]]/, "\x80\xFF") + + assert_match(/[[:word:]]/, "\u{200C}") + assert_match(/[[:word:]]/, "\u{200D}") + end + + def test_cclass_R + assert_match(/\A\R\z/, "\r") + assert_match(/\A\R\z/, "\n") + assert_match(/\A\R\z/, "\f") + assert_match(/\A\R\z/, "\v") + assert_match(/\A\R\z/, "\r\n") + assert_match(/\A\R\z/, "\u0085") + assert_match(/\A\R\z/, "\u2028") + assert_match(/\A\R\z/, "\u2029") + end + + def test_cclass_X + assert_match(/\A\X\z/, "\u{20 200d}") + assert_match(/\A\X\z/, "\u{600 600}") + assert_match(/\A\X\z/, "\u{600 20}") + assert_match(/\A\X\z/, "\u{261d 1F3FB}") + assert_match(/\A\X\z/, "\u{1f600}") + assert_match(/\A\X\z/, "\u{20 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)) + assert_equal(3, ("foo" + "bar" * 1000).rindex(/#{"bar"*1000}/)) + assert_equal(4, ("foo\nbar\nbaz\n").rindex(/bar/i)) + end + + def test_uninitialized + assert_raise(TypeError) { Regexp.allocate.hash } + assert_raise(TypeError) { Regexp.allocate.eql? Regexp.allocate } + assert_raise(TypeError) { Regexp.allocate == Regexp.allocate } + assert_raise(TypeError) { Regexp.allocate =~ "" } + assert_equal(false, Regexp.allocate === Regexp.allocate) + assert_nil(~Regexp.allocate) + assert_raise(TypeError) { Regexp.allocate.match("") } + assert_raise(TypeError) { Regexp.allocate.to_s } + assert_match(/^#<Regexp:.*>$/, Regexp.allocate.inspect) + assert_raise(TypeError) { Regexp.allocate.source } + assert_raise(TypeError) { Regexp.allocate.casefold? } + assert_raise(TypeError) { Regexp.allocate.options } + assert_equal(Encoding.find("ASCII-8BIT"), Regexp.allocate.encoding) + assert_equal(false, Regexp.allocate.fixed_encoding?) + assert_raise(TypeError) { Regexp.allocate.names } + assert_raise(TypeError) { Regexp.allocate.named_captures } + + assert_not_respond_to(MatchData, :allocate) +=begin + assert_raise(TypeError) { MatchData.allocate.hash } + assert_raise(TypeError) { MatchData.allocate.regexp } + assert_raise(TypeError) { MatchData.allocate.names } + assert_raise(TypeError) { MatchData.allocate.size } + assert_raise(TypeError) { MatchData.allocate.length } + assert_raise(TypeError) { MatchData.allocate.offset(0) } + assert_raise(TypeError) { MatchData.allocate.begin(0) } + assert_raise(TypeError) { MatchData.allocate.end(0) } + assert_raise(TypeError) { MatchData.allocate.to_a } + assert_raise(TypeError) { MatchData.allocate[:foo] } + assert_raise(TypeError) { MatchData.allocate.captures } + assert_raise(TypeError) { MatchData.allocate.values_at } + assert_raise(TypeError) { MatchData.allocate.pre_match } + assert_raise(TypeError) { MatchData.allocate.post_match } + assert_raise(TypeError) { MatchData.allocate.to_s } + assert_match(/^#<MatchData:.*>$/, MatchData.allocate.inspect) + assert_raise(TypeError) { MatchData.allocate.string } + $~ = MatchData.allocate + assert_raise(TypeError) { $& } + assert_raise(TypeError) { $` } + assert_raise(TypeError) { $' } + assert_raise(TypeError) { $+ } +=end + end + + def test_unicode + assert_match(/^\u3042{0}\p{Any}$/, "a") + assert_match(/^\u3042{0}\p{Any}$/, "\u3041") + assert_match(/^\u3042{0}\p{Any}$/, "\0") + assert_match(/^\p{Lo}{4}$/u, "\u3401\u4E01\u{20001}\u{2A701}") + assert_no_match(/^\u3042{0}\p{Any}$/, "\0\0") + assert_no_match(/^\u3042{0}\p{Any}$/, "") + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{' + "\u3042" + '}$/') } + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{' + 'a' * 1000 + '}$/') } + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{foobarbazqux}$/') } + assert_match(/^(\uff21)(a)\1\2$/i, "\uff21A\uff41a") + assert_no_match(/^(\uff21)\1$/i, "\uff21A") + assert_no_match(/^(\uff41)\1$/i, "\uff41a") + assert_match(/^\u00df$/i, "\u00df") + assert_match(/^\u00df$/i, "ss") + #assert_match(/^(\u00df)\1$/i, "\u00dfss") # this must be bug... + assert_match(/^\u00df{2}$/i, "\u00dfss") + assert_match(/^\u00c5$/i, "\u00c5") + assert_match(/^\u00c5$/i, "\u00e5") + assert_match(/^\u00c5$/i, "\u212b") + assert_match(/^(\u00c5)\1\1$/i, "\u00c5\u00e5\u212b") + assert_match(/^\u0149$/i, "\u0149") + assert_match(/^\u0149$/i, "\u02bcn") + #assert_match(/^(\u0149)\1$/i, "\u0149\u02bcn") # this must be bug... + assert_match(/^\u0149{2}$/i, "\u0149\u02bcn") + assert_match(/^\u0390$/i, "\u0390") + assert_match(/^\u0390$/i, "\u03b9\u0308\u0301") + #assert_match(/^(\u0390)\1$/i, "\u0390\u03b9\u0308\u0301") # this must be bug... + assert_match(/^\u0390{2}$/i, "\u0390\u03b9\u0308\u0301") + assert_match(/^\ufb05$/i, "\ufb05") + assert_match(/^\ufb05$/i, "\ufb06") + assert_match(/^\ufb05$/i, "st") + #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") + end + + def test_unicode_age + assert_unicode_age("\u261c", matches: %w"6.0 1.1", unmatches: []) + + assert_unicode_age("\u31f6", matches: %w"6.0 3.2", unmatches: %w"3.1 3.0 1.1") + assert_unicode_age("\u2754", matches: %w"6.0", unmatches: %w"5.0 4.0 3.0 2.0 1.1") + + assert_unicode_age("\u32FF", matches: %w"12.1", unmatches: %w"12.0") + end + + def test_unicode_age_14_0 + @matches = %w"14.0" + @unmatches = %w"13.0" + + assert_unicode_age("\u{10570}") + assert_unicode_age("\u9FFF") + assert_unicode_age("\u{2A6DF}") + assert_unicode_age("\u{2B738}") + end + + def test_unicode_age_15_0 + @matches = %w"15.0" + @unmatches = %w"14.0" + + assert_unicode_age("\u{0CF3}", + "KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT") + assert_unicode_age("\u{0ECE}", "LAO YAMAKKAN") + assert_unicode_age("\u{10EFD}".."\u{10EFF}", + "ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA") + assert_unicode_age("\u{1123F}".."\u{11241}", + "KHOJKI LETTER QA..KHOJKI VOWEL SIGN VOCALIC R") + assert_unicode_age("\u{11B00}".."\u{11B09}", + "DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU") + assert_unicode_age("\u{11F00}".."\u{11F10}", + "KAWI SIGN CANDRABINDU..KAWI LETTER O") + assert_unicode_age("\u{11F12}".."\u{11F3A}", + "KAWI LETTER KA..KAWI VOWEL SIGN VOCALIC R") + assert_unicode_age("\u{11F3E}".."\u{11F59}", + "KAWI VOWEL SIGN E..KAWI DIGIT NINE") + assert_unicode_age("\u{1342F}", + "EGYPTIAN HIEROGLYPH V011D") + assert_unicode_age("\u{13439}".."\u{1343F}", + "EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE") + assert_unicode_age("\u{13440}".."\u{13455}", + "EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED") + assert_unicode_age("\u{1B132}", "HIRAGANA LETTER SMALL KO") + assert_unicode_age("\u{1B155}", "KATAKANA LETTER SMALL KO") + assert_unicode_age("\u{1D2C0}".."\u{1D2D3}", + "KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN") + assert_unicode_age("\u{1DF25}".."\u{1DF2A}", + "LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK") + assert_unicode_age("\u{1E030}".."\u{1E06D}", + "MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE") + assert_unicode_age("\u{1E08F}", + "COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I") + assert_unicode_age("\u{1E4D0}".."\u{1E4F9}", + "NAG MUNDARI LETTER O..NAG MUNDARI DIGIT NINE") + assert_unicode_age("\u{1F6DC}", "WIRELESS") + assert_unicode_age("\u{1F774}".."\u{1F776}", + "LOT OF FORTUNE..LUNAR ECLIPSE") + assert_unicode_age("\u{1F77B}".."\u{1F77F}", + "HAUMEA..ORCUS") + assert_unicode_age("\u{1F7D9}", "NINE POINTED WHITE STAR") + assert_unicode_age("\u{1FA75}".."\u{1FA77}", + "LIGHT BLUE HEART..PINK HEART") + assert_unicode_age("\u{1FA87}".."\u{1FA88}", + "MARACAS..FLUTE") + assert_unicode_age("\u{1FAAD}".."\u{1FAAF}", + "FOLDING HAND FAN..KHANDA") + assert_unicode_age("\u{1FABB}".."\u{1FABD}", + "HYACINTH..WING") + assert_unicode_age("\u{1FABF}", "GOOSE") + assert_unicode_age("\u{1FACE}".."\u{1FACF}", + "MOOSE..DONKEY") + assert_unicode_age("\u{1FADA}".."\u{1FADB}", + "GINGER ROOT..PEA POD") + assert_unicode_age("\u{1FAE8}", "SHAKING FACE") + assert_unicode_age("\u{1FAF7}".."\u{1FAF8}", + "LEFTWARDS PUSHING HAND..RIGHTWARDS PUSHING HAND") + assert_unicode_age("\u{2B739}", + "CJK UNIFIED IDEOGRAPH-2B739") + assert_unicode_age("\u{31350}".."\u{323AF}", + "CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF") + end + + def test_unicode_age_15_1 + @matches = %w"15.1" + @unmatches = %w"15.0" + + # https://www.unicode.org/Public/15.1.0/ucd/DerivedAge.txt + assert_unicode_age("\u{2FFC}".."\u{2FFF}", + "IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION") + assert_unicode_age("\u{31EF}", + "IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION") + assert_unicode_age("\u{2EBF0}".."\u{2EE5D}", + "CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D") + end + + def test_unicode_age_16_0 + @matches = %w"16.0" + @unmatches = %w"15.1" + + # https://www.unicode.org/Public/16.0.0/ucd/DerivedAge.txt + assert_unicode_age("\u{0897}", + "ARABIC PEPET") + assert_unicode_age("\u{1B4E}".."\u{1B4F}", + "BALINESE INVERTED CARIK SIKI..BALINESE INVERTED CARIK PAREREN") + assert_unicode_age("\u{1B7F}", + "BALINESE PANTI BAWAK") + assert_unicode_age("\u{1C89}".."\u{1C8A}", + "CYRILLIC CAPITAL LETTER TJE..CYRILLIC SMALL LETTER TJE") + assert_unicode_age("\u{2427}".."\u{2429}", + "SYMBOL FOR DELETE SQUARE CHECKER BOARD FORM..SYMBOL FOR DELETE MEDIUM SHADE FORM") + assert_unicode_age("\u{31E4}".."\u{31E5}", + "CJK STROKE HXG..CJK STROKE SZP") + assert_unicode_age("\u{A7CB}".."\u{A7CD}", + "LATIN CAPITAL LETTER RAMS HORN..LATIN SMALL LETTER S WITH DIAGONAL STROKE") + assert_unicode_age("\u{A7DA}".."\u{A7DC}", + "LATIN CAPITAL LETTER LAMBDA..LATIN CAPITAL LETTER LAMBDA WITH STROKE") + assert_unicode_age("\u{105C0}".."\u{105F3}", + "TODHRI LETTER A..TODHRI LETTER OO") + assert_unicode_age("\u{10D40}".."\u{10D65}", + "GARAY DIGIT ZERO..GARAY CAPITAL LETTER OLD NA") + assert_unicode_age("\u{10D69}".."\u{10D85}", + "GARAY VOWEL SIGN E..GARAY SMALL LETTER OLD NA") + assert_unicode_age("\u{10D8E}".."\u{10D8F}", + "GARAY PLUS SIGN..GARAY MINUS SIGN") + assert_unicode_age("\u{10EC2}".."\u{10EC4}", + "ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW") + assert_unicode_age("\u{10EFC}", + "ARABIC COMBINING ALEF OVERLAY") + assert_unicode_age("\u{11380}".."\u{11389}", + "TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL") + assert_unicode_age("\u{1138B}", + "TULU-TIGALARI LETTER EE") + assert_unicode_age("\u{1138E}", + "TULU-TIGALARI LETTER AI") + assert_unicode_age("\u{11390}".."\u{113B5}", + "TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA") + assert_unicode_age("\u{113B7}".."\u{113C0}", + "TULU-TIGALARI SIGN AVAGRAHA..TULU-TIGALARI VOWEL SIGN VOCALIC LL") + assert_unicode_age("\u{113C2}", + "TULU-TIGALARI VOWEL SIGN EE") + assert_unicode_age("\u{113C5}", + "TULU-TIGALARI VOWEL SIGN AI") + assert_unicode_age("\u{113C7}".."\u{113CA}", + "TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA") + assert_unicode_age("\u{113CC}".."\u{113D5}", + "TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI DOUBLE DANDA") + assert_unicode_age("\u{113D7}".."\u{113D8}", + "TULU-TIGALARI SIGN OM PUSHPIKA..TULU-TIGALARI SIGN SHRII PUSHPIKA") + assert_unicode_age("\u{113E1}".."\u{113E2}", + "TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA") + assert_unicode_age("\u{116D0}".."\u{116E3}", + "MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE") + assert_unicode_age("\u{11BC0}".."\u{11BE1}", + "SUNUWAR LETTER DEVI..SUNUWAR SIGN PVO") + assert_unicode_age("\u{11BF0}".."\u{11BF9}", + "SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE") + assert_unicode_age("\u{11F5A}", + "KAWI SIGN NUKTA") + assert_unicode_age("\u{13460}".."\u{143FA}", + "EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA") + assert_unicode_age("\u{16100}".."\u{16139}", + "GURUNG KHEMA LETTER A..GURUNG KHEMA DIGIT NINE") + assert_unicode_age("\u{16D40}".."\u{16D79}", + "KIRAT RAI SIGN ANUSVARA..KIRAT RAI DIGIT NINE") + assert_unicode_age("\u{18CFF}", + "KHITAN SMALL SCRIPT CHARACTER-18CFF") + assert_unicode_age("\u{1CC00}".."\u{1CCF9}", + "UP-POINTING GO-KART..OUTLINED DIGIT NINE") + assert_unicode_age("\u{1CD00}".."\u{1CEB3}", + "BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET") + assert_unicode_age("\u{1E5D0}".."\u{1E5FA}", + "OL ONAL LETTER O..OL ONAL DIGIT NINE") + assert_unicode_age("\u{1E5FF}", + "OL ONAL ABBREVIATION SIGN") + assert_unicode_age("\u{1F8B2}".."\u{1F8BB}", + "RIGHTWARDS ARROW WITH LOWER HOOK..SOUTH WEST ARROW FROM BAR") + assert_unicode_age("\u{1F8C0}".."\u{1F8C1}", + "LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW") + assert_unicode_age("\u{1FA89}", + "HARP") + assert_unicode_age("\u{1FA8F}", + "SHOVEL") + assert_unicode_age("\u{1FABE}", + "LEAFLESS TREE") + assert_unicode_age("\u{1FAC6}", + "FINGERPRINT") + assert_unicode_age("\u{1FADC}", + "ROOT VEGETABLE") + assert_unicode_age("\u{1FADF}", + "SPLATTER") + assert_unicode_age("\u{1FAE9}", + "FACE WITH BAGS UNDER EYES") + assert_unicode_age("\u{1FBCB}".."\u{1FBEF}", + "WHITE CROSS MARK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE") + end + + UnicodeAgeRegexps = Hash.new do |h, age| + h[age] = [/\A\p{age=#{age}}+\z/u, /\A\P{age=#{age}}+\z/u].freeze + end + + def assert_unicode_age(char, mesg = nil, matches: @matches, unmatches: @unmatches) + if Range === char + char = char.to_a.join("") + end + + matches.each do |age| + pos, neg = UnicodeAgeRegexps[age] + assert_match(pos, char, mesg) + assert_not_match(neg, char, mesg) + end + + unmatches.each do |age| + pos, neg = UnicodeAgeRegexps[age] + assert_not_match(pos, char, mesg) + assert_match(neg, char, mesg) + end + 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]') +=begin + assert_match(/#<TestRegexp::MatchData_\u{3042}:/, MatchData_A.allocate.inspect) +=end + + h = /^(?<@time>\d+): (?<body>.*)/.match("123456: hoge fuga") + assert_equal("123456", h["@time"]) + assert_equal("hoge fuga", h["body"]) + end + + def test_matchdata_large_capture_groups_stack + env = {"RUBY_THREAD_MACHINE_STACK_SIZE" => (256 * 1024).to_s} + assert_separately([env], <<~'RUBY') + n = 20000 + require "rbconfig/sizeof" + stack = RubyVM::DEFAULT_PARAMS[:thread_machine_stack_size] + size = RbConfig::SIZEOF["long"] + required = (n + 1) * 4 * size + if !stack || stack == 0 || stack >= required + omit "thread machine stack size not reduced (#{stack}:#{required})" + end + + inspect = Thread.new do + str = "\u{3042}" * n + m = Regexp.new("(.)" * n).match(str) + assert_not_nil(m) + assert_equal([n - 1, n], m.offset(n)) + m.inspect + end.value + + assert_include(inspect, "MatchData") + RUBY + end + + def test_regexp_popped + EnvUtil.suppress_warning do + assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } + assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } + end + end + + def test_invalid_fragment + bug2547 = '[ruby-core:27374]' + assert_raise(SyntaxError, bug2547) do + assert_warning(/ignored/) {eval('/#{"\\\\"}y/')} + end + end + + def test_dup_warn + 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 } + + assert_warning(%r[/.*/\Z]) { Regexp.new("[\n\n]") } + 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. + re = assert_warning(/without escape/) {Regexp.new('[0-1-\\s]')} + check(re, [' ', '-'], ['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_quick_search + assert_match_at('(?i) *TOOKY', 'Mozilla/5.0 (Linux; Android 4.0.3; TOOKY', [[34, 40]]) # Issue #120 + end + + def test_ss_in_look_behind + assert_match_at("(?i:ss)", "ss", [[0, 2]]) + assert_match_at("(?i:ss)", "Ss", [[0, 2]]) + assert_match_at("(?i:ss)", "SS", [[0, 2]]) + assert_match_at("(?i:ss)", "\u017fS", [[0, 2]]) # LATIN SMALL LETTER LONG S + assert_match_at("(?i:ss)", "s\u017f", [[0, 2]]) + assert_match_at("(?i:ss)", "\u00df", [[0, 1]]) # LATIN SMALL LETTER SHARP S + assert_match_at("(?i:ss)", "\u1e9e", [[0, 1]]) # LATIN CAPITAL LETTER SHARP S + assert_match_at("(?i:xssy)", "xssy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xSsy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xSSy", [[0, 4]]) + assert_match_at("(?i:xssy)", "x\u017fSy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xs\u017fy", [[0, 4]]) + assert_match_at("(?i:xssy)", "x\u00dfy", [[0, 3]]) + assert_match_at("(?i:xssy)", "x\u1e9ey", [[0, 3]]) + assert_match_at("(?i:\u00df)", "ss", [[0, 2]]) + assert_match_at("(?i:\u00df)", "SS", [[0, 2]]) + assert_match_at("(?i:[\u00df])", "ss", [[0, 2]]) + assert_match_at("(?i:[\u00df])", "SS", [[0, 2]]) + assert_match_at("(?i)(?<!ss)\u2728", "qq\u2728", [[2, 3]]) # Issue #92 + assert_match_at("(?i)(?<!xss)\u2728", "qq\u2728", [[2, 3]]) + 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_quantifier_reduction + assert_equal('aa', eval('/(a+?)*/').match('aa')[0]) + assert_equal('aa', eval('/(?:a+?)*/').match('aa')[0]) + + quantifiers = %w'? * + ?? *? +?' + quantifiers.product(quantifiers) do |q1, q2| + EnvUtil.suppress_warning do + r1 = eval("/(a#{q1})#{q2}/").match('aa')[0] + r2 = eval("/(?:a#{q1})#{q2}/").match('aa')[0] + assert_equal(r1, r2) + end + end + end + + def test_once + pr1 = proc{|i| /#{i}/o} + assert_equal(/0/, pr1.call(0)) + 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) + + assert_equal(0, /(?~(a))/ =~ "") + assert_nil($1) + end + + def test_backref_overrun + assert_raise_with_message(SyntaxError, /invalid backref number/) do + eval(%["".match(/(())(?<X>)((?(90000)))/)]) + end + end + + def test_bug18631 + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaab") + end + + def test_invalid_group + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_raise_with_message(RegexpError, /invalid conditional pattern/) do + Regexp.new("((?(1)x|x|)x)+") + end + end; + end + + def test_too_big_number_for_repeat_range + assert_raise_with_message(SyntaxError, /too big number for repeat range/) do + eval(%[/|{1000000}/]) + end + end + + # This assertion is for porting x2() tests in testpy.py of Onigmo. + def assert_match_at(re, str, positions, msg = nil) + re = Regexp.new(re) unless re.is_a?(Regexp) + + 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 + + def test_s_timeout + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect } + begin; + Regexp.timeout = timeout + assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON) + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + # A typical ReDoS case + /^(a*)*\1$/ =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_operator(timeout, :<=, [timeout * 1.5, 1].max) + end; + end + + def test_s_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(Regexp.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + Regexp.timeout = Float::INFINITY + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + Regexp.timeout = 1e300 + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + assert_raise(ArgumentError) { Regexp.timeout = 0 } + assert_raise(ArgumentError) { Regexp.timeout = -1 } + + Regexp.timeout = nil + assert_nil(Regexp.timeout) + end; + end + + def test_s_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", "[Bug #20228]", rss: true) + Regexp.timeout = 0.001 + regex = /^(a*)*$/ + str = "a" * 1000000 + "x" + + code = proc do + regex =~ str + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + + def test_bug_20453 + re = Regexp.new("^(a*)x$", timeout: 0.001) + + assert_raise(Regexp::TimeoutError) do + re =~ "a" * 1000000 + "x" + end + end + + def test_bug_20886 + re = Regexp.new("d()*+|a*a*bc", timeout: 0.02) + assert_raise(Regexp::TimeoutError) do + re === "b" + "a" * 1000 + end + end + + def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 60) + global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect } + per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect } + expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect } + begin; + Regexp.timeout = global_timeout + + re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout) + if per_instance_timeout + assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON) + else + assert_nil(re.timeout) + end + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + re =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4) + end; + end + + def test_timeout_shorter_than_global + omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/ + per_instance_redos_test(10, 0.5, 0.5) + end + + def test_timeout_longer_than_global + omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/ + per_instance_redos_test(0.01, 0.5, 0.5) + end + + def test_timeout_nil + per_instance_redos_test(0.5, nil, 0.5) + end + + def test_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(//.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout) + + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout) + + assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) } + assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) } + end; + end + + def test_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20650]", timeout: 100, rss: true) + regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001) + str = "a" * 1_000_000 + "x" + + code = proc do + regex =~ str + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + + def test_match_cache_exponential + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_square + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_atomic + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?>a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_atomic_complex + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/a*(?>a*)ab/ =~ "a" * 1000000 + "b") + end; + end + + def test_match_cache_positive_look_ahead + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30) + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?=a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_positive_look_ahead_complex + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30) + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_equal(/(?:(?=a*)a)*/ =~ "a" * 1000000, 0) + end; + end + + def test_match_cache_negative_look_ahead + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?!a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_positive_look_behind + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/(?<=abc|def)(a|a)*$/ =~ "abc" + "a" * 1000000 + "x") + end; + end + + def test_match_cache_negative_look_behind + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/(?<!x)(a|a)*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_with_peek_optimization + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/a+z/ =~ "a" * 1000000 + "xz") + end; + end + + def test_cache_opcodes_initialize + str = 'test1-test2-test3-test4-test_5' + re = '^([0-9a-zA-Z\-/]*){1,256}$' + 100.times do + assert !Regexp.new(re).match?(str) + end + end + + def test_bug_19273 # [Bug #19273] + pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/ + assert_equal("10:0:0".match(pattern)[0], "10:0:0") + end + + def test_bug_19467 # [Bug #19467] + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + + assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y") + end; + end + + def test_bug_19476 # [Bug #19476] + assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789") + assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789") + end + + def test_encoding_flags_are_preserved_when_initialized_with_another_regexp + re = Regexp.new("\u2018hello\u2019".encode("UTF-8")) + str = "".encode("US-ASCII") + + assert_nothing_raised do + str.match?(re) + str.match?(Regexp.new(re)) + end + end + + def test_bug_19537 # [Bug #19537] + str = 'aac' + re = '^([ab]{1,3})(a?)*$' + 100.times do + assert !Regexp.new(re).match?(str) + end + end + + def test_bug_20083 # [Bug #20083] + re = /([\s]*ABC)$/i + (1..100).each do |n| + text = "#{"0" * n}ABC" + assert text.match?(re) + end + end + + def test_bug_20098 # [Bug #20098] + assert(/a((.|.)|bc){,4}z/.match? 'abcbcbcbcz') + assert(/a(b+?c*){4,5}z/.match? 'abbbccbbbccbcbcz') + assert(/a(b+?(.|.)){2,3}z/.match? 'abbbcbbbcbbbcz') + assert(/a(b*?(.|.)[bc]){2,5}z/.match? 'abcbbbcbcccbcz') + assert(/^(?:.+){2,4}?b|b/.match? "aaaabaa") + end + + def test_bug_20207 # [Bug #20207] + assert(!'clan'.match?(/(?=.*a)(?!.*n)/)) + end + + def test_bug_20212 # [Bug #20212] + regex = Regexp.new( + /\A((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+)\Z/x + ) + string = "www.google.com" + 100.times.each { assert(regex.match?(string)) } + end + + def test_bug_20246 # [Bug #20246] + assert_equal '1.2.3', '1.2.3'[/(\d+)(\.\g<1>){2}/] + assert_equal '1.2.3', '1.2.3'[/((?:\d|foo|bar)+)(\.\g<1>){2}/] + end + + def test_linear_time_p + assert_send [Regexp, :linear_time?, /a/] + assert_send [Regexp, :linear_time?, 'a'] + assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE] + assert_not_send [Regexp, :linear_time?, /(a)\1/] + assert_not_send [Regexp, :linear_time?, "(a)\\1"] + + assert_not_send [Regexp, :linear_time?, /(?=(a))/] + assert_not_send [Regexp, :linear_time?, /(?!(a))/] + + assert_raise(TypeError) {Regexp.linear_time?(nil)} + assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)} + end + + def test_linear_performance + pre = ->(n) {[Regexp.new("a?" * n + "a" * n), "a" * n]} + assert_linear_performance([10, 29], pre: pre) do |re, s| + re =~ s + end + end + + def test_bug_16145_and_bug_21176_caseinsensitive_small # [Bug#16145] [Bug#21176] + encodings = [Encoding::UTF_8, Encoding::ISO_8859_1] + encodings.each do |enc| + o_acute_lower = "\u00F3".encode(enc) + o_acute_upper = "\u00D3".encode(enc) + assert_match(/[x#{o_acute_lower}]/i, "abc#{o_acute_upper}", "should match o acute case insensitive") + + e_acute_lower = "\u00E9".encode(enc) + e_acute_upper = "\u00C9".encode(enc) + assert_match(/[x#{e_acute_lower}]/i, "CAF#{e_acute_upper}", "should match e acute case insensitive") + end + end +end diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb new file mode 100644 index 0000000000..0067a49700 --- /dev/null +++ b/test/ruby/test_require.rb @@ -0,0 +1,1058 @@ +# frozen_string_literal: false +require 'test/unit' + +require 'tempfile' +require 'tmpdir' + +class TestRequire < Test::Unit::TestCase + def test_load_error_path + Tempfile.create(["should_not_exist", ".rb"]) {|t| + filename = t.path + t.close + File.unlink(filename) + + error = assert_raise(LoadError) do + require filename + end + assert_equal filename, error.path + + # with --disable=gems + assert_separately(["-", filename], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + filename = ARGV[0] + path = Struct.new(:to_path).new(filename) + error = assert_raise(LoadError) do + require path + end + assert_equal filename, error.path + end; + } + end + + def test_require_invalid_shared_object + 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_separately(["RUBYOPT"=>nil], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + $:.replace([IO::NULL]) + assert_raise(LoadError) do + require '#{ "foo/" * 10000 }foo' + end + end; + + begin + assert_in_out_err(["-S", "-w", "foo/" * 1024 + "foo"], "") do |r, e| + assert_equal([], r) + assert_operator(2, :<=, e.size) + assert_match(/warning: openpath: pathname too long \(ignored\)/, e.first) + assert_match(/\(LoadError\)/, e.last) + end + rescue Errno::EINVAL + # too long commandline may be blocked by OS. + end + end + + def test_require_nonascii + bug3758 = '[ruby-core:31915]' + ["\u{221e}", "\x82\xa0".force_encoding("cp932")].each do |path| + e = assert_raise(LoadError, bug3758) {require path} + assert_operator(e.message, :end_with?, path, bug3758) + 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_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_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 + + 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 + + def prepare_require_path(dir, encoding) + require 'enc/trans/single_byte' + Dir.mktmpdir {|tmp| + begin + require_path = File.join(tmp, dir, 'foo.rb').encode(encoding) + rescue + omit "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 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" * 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 + + 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 -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 + + 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 + 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$') + omit "local drive #$1: is not shared" unless File.exist?(path) + args = ['--disable-gems', "-I#{File.dirname(path)}"] + assert_in_out_err(args, "#{<<~"END;"}", [path], []) + begin + 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([], <<-INPUT, %w(:ok), []) + require "#{req}" + require "#{req}" + INPUT + end + end + + def assert_syntax_error_backtrace + loaded_features = $LOADED_FEATURES.dup + Dir.mktmpdir do |tmp| + req = File.join(tmp, "test.rb") + File.write(req, ",\n") + e = assert_raise_with_message(SyntaxError, /unexpected/) { + yield req + } + assert_not_nil(bt = e.backtrace, "no backtrace") + assert_not_empty(bt.find_all {|b| b.start_with? __FILE__}, proc {bt.inspect}) + end + ensure + $LOADED_FEATURES.replace loaded_features + end + + def test_require_syntax_error + assert_syntax_error_backtrace {|req| require req} + end + + def test_require_syntax_error_rescued + assert_syntax_error_backtrace do |req| + assert_raise_with_message(SyntaxError, /unexpected/) {require req} + require req + end + end + + def test_load_syntax_error + assert_syntax_error_backtrace {|req| load req} + end + + def test_define_class + begin + require "socket" + rescue LoadError + return + end + + assert_separately([], <<-INPUT) + BasicSocket = 1 + assert_raise(TypeError) do + require 'socket' + end + INPUT + + assert_separately([], <<-INPUT) + class BasicSocket; end + assert_raise(TypeError) do + require 'socket' + end + INPUT + + assert_separately([], <<-INPUT) + class BasicSocket < IO; end + assert_nothing_raised do + require 'socket' + end + INPUT + end + + def test_define_class_under + begin + require "zlib" + rescue LoadError + return + end + + assert_separately([], <<-INPUT) + module Zlib; end + Zlib::Error = 1 + assert_raise(TypeError) do + require 'zlib' + end + INPUT + + assert_separately([], <<-INPUT) + module Zlib; end + class Zlib::Error; end + assert_raise(TypeError) do + require 'zlib' + end + INPUT + + assert_separately([], <<-INPUT) + module Zlib; end + class Zlib::Error < StandardError; end + assert_nothing_raised do + require 'zlib' + end + INPUT + end + + def test_define_module + begin + require "zlib" + rescue LoadError + return + end + + assert_separately([], <<-INPUT) + Zlib = 1 + assert_raise(TypeError) do + require 'zlib' + end + INPUT + end + + def test_define_module_under + begin + require "socket" + rescue LoadError + return + end + + assert_separately([], <<-INPUT) + class BasicSocket < IO; end + class Socket < BasicSocket; end + Socket::Constants = 1 + assert_raise(TypeError) do + require 'socket' + end + INPUT + end + + def test_load + 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_require_in_wrapped_load + Dir.mktmpdir do |tmp| + File.write("#{tmp}/1.rb", "require_relative '2'\n") + File.write("#{tmp}/2.rb", "class Foo\n""end\n") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + path = ""#{tmp.dump}"/1.rb" + begin; + load path, true + assert_instance_of(Class, Foo) + end; + end + end + + def test_public_in_wrapped_load + Tempfile.create(["test_public_in_wrapped_load", ".rb"]) do |t| + t.puts "def foo; end", "public :foo" + t.close + assert_warning(/main\.public/) do + assert load(t.path, true) + end + end + end + + def test_private_in_wrapped_load + Tempfile.create(["test_private_in_wrapped_load", ".rb"]) do |t| + t.puts "def foo; end", "private :foo" + t.close + assert_warning(/main\.private/) do + assert load(t.path, true) + end + end + end + + def test_load_scope + bug1982 = '[ruby-core:25039] [Bug #1982]' + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + 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_load_into_module + Tempfile.create(["test_ruby_test_require", ".rb"]) {|t| + t.puts "def b; 1 end" + t.puts "class Foo" + t.puts " def c; 2 end" + t.puts "end" + t.close + + m = Module.new + load(t.path, m) + assert_equal([:b], m.private_instance_methods(false)) + c = Class.new do + include m + public :b + end + assert_equal(1, c.new.b) + assert_equal(2, m::Foo.new.c) + } + end + + def test_load_wrap_nil + Dir.mktmpdir do |tmp| + File.write("#{tmp}/1.rb", "class LoadWrapNil; end\n") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + path = ""#{tmp.dump}"/1.rb" + begin; + load path, nil + assert_instance_of(Class, LoadWrapNil) + end; + end + end + + def test_load_ospath + bug = '[ruby-list:49994] path in ospath' + base = "test_load\u{3042 3044 3046 3048 304a}".encode(Encoding::Windows_31J) + path = nil + Dir.mktmpdir do |dir| + path = File.join(dir, base+".rb") + assert_raise_with_message(LoadError, /#{base}/) { + load(File.join(dir, base)) + } + + File.open(path, "w+b") do |t| + t.puts "warn 'ok'" + end + assert_include(path, base) + assert_warn("ok\n", bug) { + assert_nothing_raised(LoadError, bug) { + load(path) + } + } + end + end + + def test_relative + load_path = $:.dup + loaded_featrures = $LOADED_FEATURES.dup + + $:.delete(".") + Dir.mktmpdir do |tmp| + Dir.chdir(tmp) do + Dir.mkdir('x') + File.open('x/t.rb', 'wb') {} + File.open('x/a.rb', 'wb') {|f| f.puts("require_relative('t.rb')")} + assert require('./x/t.rb') + assert !require(File.expand_path('x/t.rb')) + assert_nothing_raised(LoadError) {require('./x/a.rb')} + assert_raise(LoadError) {require('x/t.rb')} + File.unlink(*Dir.glob('x/*')) + Dir.rmdir("#{tmp}/x") + $:.replace(load_path) + load_path = nil + assert(!require('tmpdir')) + end + end + ensure + $:.replace(load_path) if load_path + $LOADED_FEATURES.replace loaded_featrures + end + + def test_relative_symlink + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + Dir.mkdir "a" + Dir.mkdir "b" + File.open("a/lib.rb", "w") {|f| f.puts 'puts "a/lib.rb"' } + File.open("b/lib.rb", "w") {|f| f.puts 'puts "b/lib.rb"' } + 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) + assert_equal("a/lib.rb\n", result, "[ruby-dev:40040]") + rescue NotImplementedError, Errno::EACCES + omit "File.symlink is not implemented" + end + } + } + end + + def test_relative_symlink_realpath + Dir.mktmpdir {|tmp| + Dir.chdir(tmp) { + Dir.mkdir "a" + File.open("a/a.rb", "w") {|f| f.puts 'require_relative "b"' } + File.open("a/b.rb", "w") {|f| f.puts '$t += 1' } + Dir.mkdir "b" + File.binwrite("c.rb", <<~RUBY) + $t = 0 + $:.unshift(File.expand_path('../b', __FILE__)) + require "b" + require "a" + print $t + RUBY + begin + File.symlink("../a/a.rb", "b/a.rb") + File.symlink("../a/b.rb", "b/b.rb") + result = IO.popen([EnvUtil.rubybin, "c.rb"], &:read) + assert_equal("1", result, "bug17885 [ruby-core:104010]") + rescue NotImplementedError, Errno::EACCES + omit "File.symlink is not implemented" + end + } + } + end + + def test_frozen_loaded_features + bug3756 = '[ruby-core:31913]' + assert_in_out_err(['-e', '$LOADED_FEATURES.freeze; require "erb"'], "", + [], /\$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 + Kernel.send(:require, path) + rescue RuntimeError + end + + t1_res = require(path) + end + + t2 = Thread.new do + Thread.pass until scratch[0] + t2_res = Kernel.send(: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) + } + 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_default_loaded_features_encoding + Dir.mktmpdir {|tmp| + Dir.mkdir("#{tmp}/1") + Dir.mkdir("#{tmp}/2") + File.write("#{tmp}/1/bug18191-1.rb", "") + File.write("#{tmp}/2/bug18191-2.rb", "") + assert_separately(%W[-Eutf-8 -I#{tmp}/1 -], "#{<<~"begin;"}\n#{<<~'end;'}") + tmp = #{tmp.dump}"/2" + begin; + $:.unshift(tmp) + require "bug18191-1" + require "bug18191-2" + encs = [Encoding::US_ASCII, Encoding.find("filesystem")] + message = -> { + require "pp" + {filesystem: encs[1], **$".group_by(&:encoding)}.pretty_inspect + } + assert($".all? {|n| encs.include?(n.encoding)}, message) + end; + } + end + + def test_require_changed_current_dir + bug7158 = '[ruby-core:47970]' + Dir.mktmpdir {|tmp| + 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}], "#{<<~"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}], "#{<<~"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([], "#{<<~"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 = 30 + ITERATIONS_PER_THREAD = 300 + + 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; + } + + # [Bug #21567] + assert_ruby_status(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class MyString + def initialize(path) + @path = path + end + + def to_str + $LOADED_FEATURES.clear + @path + end + + def to_path = @path + end + + FILES = [] + + def create_ruby_file + file = Tempfile.open(["test", ".rb"]) + FILES << file + file.path + end + + require MyString.new(create_ruby_file) + $LOADED_FEATURES.unshift(create_ruby_file) + $LOADED_FEATURES << MyString.new(create_ruby_file) + require create_ruby_file + 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: 10) + 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 + omit "[Bug #18613]" if /freebsd/=~ RUBY_PLATFORM + + Tempfile.create(%w'fifo .rb') {|f| + f.close + File.unlink(f.path) + File.mkfifo(f.path) + + assert_separately(["-", f.path], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) + 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 + omit if RUBY_PLATFORM =~ /android/ # https://rubyci.org/logs/rubyci.s3.amazonaws.com/android29-x86_64/ruby-master/log/20200419T124100Z.fail.html.gz + + Tempfile.create(%w'fifo .rb') {|f| + f.close + File.unlink(f.path) + 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 + omit "File.symlink is not implemented" + end + File.write(File.join(tmp, "real/test_symlink_load_path.rb"), "print __FILE__") + result = IO.popen([EnvUtil.rubybin, "-I#{tmp}/symlink", "-e", "require 'test_symlink_load_path.rb'"], &:read) + assert_operator(result, :end_with?, "/real/test_symlink_load_path.rb") + } + end + + def test_provide_in_required_file + paths, loaded = $:.dup, $".dup + Dir.mktmpdir do |tmp| + provide = File.realdirpath("provide.rb", tmp) + File.write(File.join(tmp, "target.rb"), "raise __FILE__\n") + File.write(provide, '$" << '"'target.rb'\n") + $:.replace([tmp]) + assert(require("provide")) + assert(!require("target")) + assert_equal($".pop, provide) + assert_equal($".pop, "target.rb") + end + ensure + $:.replace(paths) + $".replace(loaded) + end + + if defined?($LOAD_PATH.resolve_feature_path) + def test_resolve_feature_path + paths, loaded = $:.dup, $".dup + Dir.mktmpdir do |tmp| + Tempfile.create(%w[feature .rb], tmp) do |file| + file.close + path = File.realpath(file.path) + dir, base = File.split(path) + $:.unshift(dir) + assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base)) + $".push(path) + assert_equal([:rb, path], $LOAD_PATH.resolve_feature_path(base)) + end + end + ensure + $:.replace(paths) + $".replace(loaded) + end + + def test_resolve_feature_path_with_missing_feature + assert_nil($LOAD_PATH.resolve_feature_path("superkalifragilisticoespialidoso")) + end + end + + def test_require_with_public_method_missing + # [Bug #19793] + assert_ruby_status(["-W0", "-rtempfile"], <<~RUBY, timeout: 60) + GC.stress = true + + class Object + public :method_missing + end + + Tempfile.create(["empty", ".rb"]) do |file| + require file.path + end + RUBY + end + + def test_bug_21568 + load_path = $LOAD_PATH.dup + loaded_featrures = $LOADED_FEATURES.dup + + $LOAD_PATH.clear + $LOADED_FEATURES.replace(["foo.so", "a/foo.rb", "b/foo.rb"]) + + assert_nothing_raised(LoadError) { require "foo" } + + ensure + $LOAD_PATH.replace(load_path) if load_path + $LOADED_FEATURES.replace loaded_featrures + end +end diff --git a/test/ruby/test_require_lib.rb b/test/ruby/test_require_lib.rb new file mode 100644 index 0000000000..44dfbcf9ec --- /dev/null +++ b/test/ruby/test_require_lib.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +require 'test/unit' + +class TestRequireLib < Test::Unit::TestCase + libdir = __dir__ + '/../../lib' + + # .rb files at lib + scripts = Dir.glob('*.rb', base: libdir).map {|f| f.chomp('.rb')} + + # .rb files in subdirectories of lib without same name script + dirs = Dir.glob('*/', base: libdir).map {|d| d.chomp('/')} + dirs -= scripts + scripts.concat(Dir.glob(dirs.map {|d| d + '/*.rb'}, base: libdir).map {|f| f.chomp('.rb')}) + + # skip some problems + scripts -= %w[bundler bundled_gems rubygems mkmf set/sorted_set] + + scripts.each do |lib| + define_method "test_thread_size:#{lib}" do + assert_separately(['-W0'], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60) + begin; + n = Thread.list.size + require #{lib.dump} + assert_equal n, Thread.list.size + end; + end + end +end diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb new file mode 100644 index 0000000000..cd2dd5d3ff --- /dev/null +++ b/test/ruby/test_rubyoptions.rb @@ -0,0 +1,1326 @@ +# -*- coding: us-ascii -*- +require 'test/unit' + +require 'timeout' +require 'tmpdir' +require 'tempfile' +require_relative '../lib/jit_support' +require_relative '../lib/parser_support' + +class TestRubyOptions < Test::Unit::TestCase + # Here we're defining our own RUBY_DESCRIPTION without "+PRISM". We do this + # here so that the various tests that reference RUBY_DESCRIPTION don't have to + # worry about it. The flag itself is tested in its own test. + RUBY_DESCRIPTION = + if ParserSupport.prism_enabled_in_subprocess? + ::RUBY_DESCRIPTION + else + ::RUBY_DESCRIPTION.sub(/\+PRISM /, '') + end + + NO_JIT_DESCRIPTION = + case + when JITSupport.yjit_enabled? + RUBY_DESCRIPTION.sub(/\+YJIT( \w+)? /, '') + when JITSupport.zjit_enabled? + RUBY_DESCRIPTION.sub(/\+ZJIT( \w+)? /, '') + 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 + + # This constant enforces the traditional 80x25 terminal size standard + TRADITIONAL_TERM_COLS = 80 # DO NOT MODIFY! + TRADITIONAL_TERM_ROWS = 25 # DO NOT MODIFY! + + def test_usage + # This test checks if the output of `ruby -h` fits in 80x25 + assert_in_out_err(%w(-h)) do |r, e| + assert_operator(r.size, :<=, TRADITIONAL_TERM_ROWS) + longer = r[1..-1].select {|x| x.size >= TRADITIONAL_TERM_COLS} + assert_equal([], longer) + assert_equal([], e) + end + end + + def test_usage_long + assert_in_out_err(%w(--help)) do |r, e| + longer = r[1..-1].select {|x| x.size > 80} + assert_equal([], longer) + assert_equal([], e) + end + end + + def test_option_variables + assert_in_out_err(["-e", 'p [$-p, $-l, $-a]']) do |r, e| + assert_equal(["[false, false, false]"], r) + assert_equal([], e) + end + + assert_in_out_err(%w(-p -l -a -e) + ['p [$-p, $-l, $-a]'], + "foo\nbar\nbaz") do |r, e| + assert_equal( + [ '[true, true, true]', 'foo', + '[true, true, true]', 'bar', + '[true, true, true]', 'baz' ], r) + assert_equal([], e) + end + end + + def test_backtrace_limit + assert_in_out_err(%w(--backtrace-limit), "", [], /missing argument for --backtrace-limit/) + assert_in_out_err(%w(--backtrace-limit= 1), "", [], /missing argument for --backtrace-limit/) + assert_in_out_err(%w(--backtrace-limit=-2), "", [], /wrong limit for backtrace length/) + code = 'def f(n);n > 0 ? f(n-1) : raise;end;f(5)' + assert_in_out_err(%w(--backtrace-limit=1), code, [], + [/.*unhandled exception\n/, /^\tfrom .*\n/, + /^\t \.{3} \d+ levels\.{3}\n/]) + assert_in_out_err(%w(--backtrace-limit=3), code, [], + [/.*unhandled exception\n/, *[/^\tfrom .*\n/]*3, + /^\t \.{3} \d+ levels\.{3}\n/]) + assert_kind_of(Integer, Thread::Backtrace.limit) + assert_in_out_err(%w(--backtrace-limit=1), "p Thread::Backtrace.limit", ['1'], []) + assert_in_out_err(%w(--backtrace-limit 1), "p Thread::Backtrace.limit", ['1'], []) + env = {"RUBYOPT" => "--backtrace-limit=5"} + assert_in_out_err([env], "p Thread::Backtrace.limit", ['5'], []) + assert_in_out_err([env, "--backtrace-limit=1"], "p Thread::Backtrace.limit", ['1'], []) + assert_in_out_err([env, "--backtrace-limit=-1"], "p Thread::Backtrace.limit", ['-1'], []) + assert_in_out_err([env, "--backtrace-limit=3", "--backtrace-limit=1"], + "p Thread::Backtrace.limit", ['1'], []) + assert_in_out_err([{"RUBYOPT" => "--backtrace-limit=5 --backtrace-limit=3"}], + "p Thread::Backtrace.limit", ['3'], []) + long_max = RbConfig::LIMITS["LONG_MAX"] + assert_in_out_err(%W(--backtrace-limit=#{long_max}), "p Thread::Backtrace.limit", + ["#{long_max}"], []) + end + + def test_warning + save_rubyopt = ENV.delete('RUBYOPT') + assert_in_out_err(%w(-W0 -e) + ['p $-W'], "", %w(0), []) + assert_in_out_err(%w(-W1 -e) + ['p $-W'], "", %w(1), []) + assert_in_out_err(%w(-Wx -e) + ['p $-W'], "", %w(2), []) + assert_in_out_err(%w(-W -e) + ['p $-W'], "", %w(2), []) + assert_in_out_err(%w(-We) + ['p $-W'], "", %w(2), []) + assert_in_out_err(%w(-w -W0 -e) + ['p $-W'], "", %w(0), []) + + categories = {deprecated: 1, experimental: 0, performance: 2, strict_unused_block: 3} + assert_equal categories.keys.sort, Warning.categories.sort + + categories.each do |category, level| + assert_in_out_err(["-W:#{category}", "-e", "p Warning[:#{category}]"], "", %w(true), []) + assert_in_out_err(["-W:no-#{category}", "-e", "p Warning[:#{category}]"], "", %w(false), []) + assert_in_out_err(["-e", "p Warning[:#{category}]"], "", level > 0 ? %w(false) : %w(true), []) + assert_in_out_err(["-w", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), []) + assert_in_out_err(["-W", "-e", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), []) + assert_in_out_err(["-We", "p Warning[:#{category}]"], "", level > 1 ? %w(false) : %w(true), []) + end + assert_in_out_err(%w(-W:qux), "", [], /unknown warning category: 'qux'/) + + def categories.expected(lev = 1, **warnings) + [ + (lev > 1).to_s, + *map {|category, level| warnings.fetch(category, lev > level).to_s} + ].join(':') + end + code = ['#{$VERBOSE}', *categories.map {|category, | "\#{Warning[:#{category}]}"}].join(':') + code = %[puts "#{code}"] + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) do |t| + t.puts code + t.close + assert_in_out_err(["-r#{t.path}", '-e', code], "", [categories.expected(1)]*2, []) + assert_in_out_err(["-r#{t.path}", '-w', '-e', code], "", [categories.expected(2)]*2, []) + categories.each do |category, | + assert_in_out_err(["-r#{t.path}", "-W:#{category}", '-e', code], "", [categories.expected(category => 'true')]*2, []) + assert_in_out_err(["-r#{t.path}", "-W:no-#{category}", '-e', code], "", [categories.expected(category => 'false')]*2, []) + end + end + ensure + ENV['RUBYOPT'] = save_rubyopt + end + + def test_debug + assert_in_out_err(["-de", "p $DEBUG"], "", %w(true), []) + + assert_in_out_err(["--debug", "-e", "p $DEBUG"], + "", %w(true), []) + + assert_in_out_err(["--debug-", "-e", "p $DEBUG"], "", %w(), /invalid option --debug-/) + 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).*? (\+PRISM )?\[#{q[RUBY_PLATFORM]}\]$/ + end + private_constant :VERSION_PATTERN + + def test_verbose + assert_in_out_err([{'RUBY_YJIT_ENABLE' => nil}, "-vve", ""]) do |r, e| + assert_match(VERSION_PATTERN, r[0]) + if (JITSupport.yjit_enabled? && !JITSupport.yjit_force_enabled?) || JITSupport.zjit_enabled? + assert_equal(NO_JIT_DESCRIPTION, r[0]) + else + assert_equal(RUBY_DESCRIPTION, r[0]) + end + assert_equal([], e) + end + + assert_in_out_err(%w(--verbose -e) + ["p $VERBOSE"], "", %w(true), []) + + assert_in_out_err(%w(--verbose), "", [], []) + end + + def test_copyright + assert_in_out_err(%w(--copyright), "", + /^ruby - Copyright \(C\) 1993-\d+ Yukihiro Matsumoto$/, []) + + assert_in_out_err(%w(--verbose -e) + ["p $VERBOSE"], "", %w(true), []) + end + + def test_enable + if JITSupport.yjit_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/) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: true) + assert_in_out_err(%w(-e) + ['p defined? Gem'], "", %w["constant"], [], gems: nil) + end + + def test_disable + assert_in_out_err(%w(--disable all -e) + [""], "", [], []) + assert_in_out_err(%w(--disable-all -e) + [""], "", [], []) + assert_in_out_err(%w(--disable=all -e) + [""], "", [], []) + 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(-e) + ['p defined? Gem'], "", ["nil"], [], gems: false) + assert_in_out_err(%w(--disable-did_you_mean -e) + ['p defined? DidYouMean'], "", ["nil"], []) + assert_in_out_err(%w(-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 + 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 + env = { 'RUBY_YJIT_ENABLE' => nil } # unset in children + assert_in_out_err([env, '--version']) do |r, e| + assert_match(VERSION_PATTERN, r[0]) + if ENV['RUBY_YJIT_ENABLE'] == '1' + assert_equal(NO_JIT_DESCRIPTION, r[0]) + elsif JITSupport.yjit_enabled? || JITSupport.zjit_enabled? # checking -DYJIT_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 + end + + def test_enabled_gc + omit unless /linux|darwin/ =~ RUBY_PLATFORM + + if RbConfig::CONFIG['modular_gc_dir'].length > 0 + assert_match(/\+GC/, RUBY_DESCRIPTION) + else + assert_no_match(/\+GC/, RUBY_DESCRIPTION) + end + end + + def test_parser_flag + omit if ENV["RUBYOPT"]&.include?("--parser=") + + assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), []) + assert_in_out_err(%w(--parser=prism --dump=parsetree -e _=:hi), "", /"hi"/, []) + + assert_in_out_err(%w(--parser=parse.y -e) + ["puts :hi"], "", %w(hi), []) + assert_norun_with_rflag('--parser=parse.y', '--version', "") + + assert_in_out_err(%w(--parser=notreal -e) + ["puts :hi"], "", [], /unknown parser notreal/) + + assert_in_out_err(%w(--parser=prism --version), "", /\+PRISM/, []) + end + + def test_eval + assert_in_out_err(%w(-e), "", [], /no code specified for -e \(RuntimeError\)/) + end + + def test_require + 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 + + def test_include + d = Dir.tmpdir + assert_in_out_err(["-I" + d, "-e", ""], "", [], []) + assert_in_out_err(["-I", d, "-e", ""], "", [], []) + end + + def test_separator + assert_in_out_err(%w(-000 -e) + ["print gets"], "foo\nbar\0baz", %W(foo bar\0baz), []) + + 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 + assert_in_out_err(%w(-W0 -an -F: -e) + ["p $F"], "foo:bar:baz\nqux:quux:quuux\n", + ['["foo", "bar", "baz\n"]', '["qux", "quux", "quuux\n"]'], []) + end + + def test_chdir + assert_in_out_err(%w(-C), "", [], /Can't chdir/) + + assert_in_out_err(%w(-C test_ruby_test_rubyoptions_foobarbazqux), "", [], /Can't chdir/) + + d = Dir.tmpdir + assert_in_out_err(["-C", d, "-e", "puts Dir.pwd"]) do |r, e| + assert_file.identical?(r.join(''), d) + assert_equal([], e) + end + + Dir.mktmpdir(nil, d) do |base| + # "test" in Japanese and N'Ko + test = base + "/\u{30c6 30b9 30c8}_\u{7e1 7ca 7dd 7cc 7df 7cd 7eb}" + Dir.mkdir(test) + assert_in_out_err(["-C", base, "-C", File.basename(test), "-e", "puts Dir.pwd"]) do |r, e| + assert_file.identical?(r.join(''), test) + assert_equal([], e) + end + Dir.rmdir(test) + end + end + + def test_yydebug + omit if ParserSupport.prism_enabled_in_subprocess? + + assert_in_out_err(["-ye", ""]) do |r, e| + assert_not_equal([], r) + assert_equal([], e) + end + + assert_in_out_err(%w(--yydebug -e) + [""]) do |r, e| + assert_not_equal([], r) + assert_equal([], e) + end + end + + def test_encoding + 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(-Eutf-8), 'puts Encoding::default_external', ["UTF-8"]) + assert_in_out_err(%w(-Ecesu-8), 'puts Encoding::default_external', ["CESU-8"]) + assert_in_out_err(%w(--encoding utf-8), 'puts Encoding::default_external', ["UTF-8"]) + assert_in_out_err(%w(--encoding cesu-8), 'puts Encoding::default_external', ["CESU-8"]) + end + + def test_syntax_check + assert_in_out_err(%w(-cw -e a=1+1 -e !a), "", ["Syntax OK"], []) + assert_in_out_err(%w(-cw -e break), "", [], [:*, /(-e:1:|~) Invalid break/, :*]) + assert_in_out_err(%w(-cw -e next), "", [], [:*, /(-e:1:|~) Invalid next/, :*]) + assert_in_out_err(%w(-cw -e redo), "", [], [:*, /(-e:1:|~) Invalid redo/, :*]) + assert_in_out_err(%w(-cw -e retry), "", [], [:*, /(-e:1:|~) Invalid retry/, :*]) + assert_in_out_err(%w(-cw -e yield), "", [], [:*, /(-e:1:|~) Invalid yield/, :*]) + assert_in_out_err(%w(-cw -e begin -e break -e end), "", [], [:*, /(-e:2:|~) Invalid break/, :*]) + assert_in_out_err(%w(-cw -e begin -e next -e end), "", [], [:*, /(-e:2:|~) Invalid next/, :*]) + assert_in_out_err(%w(-cw -e begin -e redo -e end), "", [], [:*, /(-e:2:|~) Invalid redo/, :*]) + assert_in_out_err(%w(-cw -e begin -e retry -e end), "", [], [:*, /(-e:2:|~) Invalid retry/, :*]) + assert_in_out_err(%w(-cw -e begin -e yield -e end), "", [], [:*, /(-e:2:|~) Invalid yield/, :*]) + assert_in_out_err(%w(-cw -e !defined?(break)), "", ["Syntax OK"], []) + assert_in_out_err(%w(-cw -e !defined?(next)), "", ["Syntax OK"], []) + assert_in_out_err(%w(-cw -e !defined?(redo)), "", ["Syntax OK"], []) + assert_in_out_err(%w(-cw -e !defined?(retry)), "", ["Syntax OK"], []) + assert_in_out_err(%w(-cw -e !defined?(yield)), "", ["Syntax OK"], []) + assert_in_out_err(%w(-n -cw -e break), "", ["Syntax OK"], []) + assert_in_out_err(%w(-n -cw -e next), "", ["Syntax OK"], []) + assert_in_out_err(%w(-n -cw -e redo), "", ["Syntax OK"], []) + end + + def test_invalid_option + assert_in_out_err(%w(--foobarbazqux), "", [], /invalid option --foobarbazqux/) + + assert_in_out_err(%W(-\r -e) + [""], "", [], []) + + assert_in_out_err(%W(-\rx), "", [], /invalid option -\\r \(-h will show valid options\) \(RuntimeError\)/) + + assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/) + + assert_in_out_err(%w(-Z), "", [], /invalid option -Z \(-h will show valid options\) \(RuntimeError\)/) + + assert_in_out_err(%W(-\u{1f608}), "", [], + /invalid option -(\\xf0|\u{1f608}) \(-h will show valid options\) \(RuntimeError\)/, + encoding: Encoding::UTF_8) + end + + def test_rubyopt + rubyopt_orig = ENV['RUBYOPT'] + + ENV['RUBYOPT'] = ' - -' + assert_in_out_err([], "", [], []) + + ENV['RUBYOPT'] = '-e "p 1"' + assert_in_out_err([], "", [], /invalid switch in RUBYOPT: -e \(RuntimeError\)/) + + ENV['RUBYOPT'] = '-Eus-ascii -KN' + assert_in_out_err(%w(-Eutf-8 -KU), "p '\u3042'") do |r, e| + assert_equal("\"\u3042\"", r.join.force_encoding(Encoding::UTF_8)) + 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"]) + assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"]) + assert_in_out_err(%w(-W0), "p Warning[:deprecated]", ["false"]) + assert_in_out_err(%w(-W1), "p Warning[:deprecated]", ["false"]) + assert_in_out_err(%w(-W2), "p Warning[:deprecated]", ["true"]) + ENV['RUBYOPT'] = '-W:deprecated' + assert_in_out_err(%w(), "p Warning[:deprecated]", ["true"]) + ENV['RUBYOPT'] = '-W:no-deprecated' + assert_in_out_err(%w(), "p Warning[:deprecated]", ["false"]) + ENV['RUBYOPT'] = '-W:experimental' + assert_in_out_err(%w(), "p Warning[:experimental]", ["true"]) + ENV['RUBYOPT'] = '-W:no-experimental' + assert_in_out_err(%w(), "p Warning[:experimental]", ["false"]) + ENV['RUBYOPT'] = '-W:qux' + assert_in_out_err(%w(), "", [], /unknown warning category: 'qux'/) + + ENV['RUBYOPT'] = 'w' + assert_in_out_err(%w(), "p $VERBOSE", ["true"]) + ensure + ENV['RUBYOPT'] = rubyopt_orig + end + + def test_search + rubypath_orig = ENV['RUBYPATH'] + path_orig = ENV['PATH'] + + Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t| + t.puts "p 1" + t.close + + @verbose = $VERBOSE + $VERBOSE = nil + + path, name = File.split(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 + + ENV['RUBYPATH'] = path + assert_in_out_err(%w(-S) + [name], "", %w(1), []) + } + + ensure + if rubypath_orig + ENV['RUBYPATH'] = rubypath_orig + else + ENV.delete('RUBYPATH') + end + if path_orig + ENV['PATH'] = path_orig + else + ENV.delete('PATH') + end + $VERBOSE = @verbose + end + + def test_shebang + assert_in_out_err([], "#! /test_r_u_b_y_test_r_u_b_y_options_foobarbazqux\r\np 1\r\n", + [], /: no Ruby script found in input/) + + 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/) + + warning = /mswin|mingw/ =~ RUBY_PLATFORM ? [] : /shebang line ending with \\r/ + assert_in_out_err([{'RUBYOPT' => nil}], "#!ruby -KU -Eutf-8\r\np \"\u3042\"\r\n", + ["\"\u3042\""], warning, + encoding: Encoding::UTF_8) + + bug4118 = '[ruby-dev:42680]' + assert_in_out_err(%w[], "#!/bin/sh\n""#!shebang\n""#!ruby\n""puts __LINE__\n", + %w[4], [], bug4118) + assert_in_out_err(%w[-x], "#!/bin/sh\n""#!shebang\n""#!ruby\n""puts __LINE__\n", + %w[4], [], bug4118) + + assert_ruby_status(%w[], "#! ruby -- /", '[ruby-core:82267] [Bug #13786]') + + assert_ruby_status(%w[], "#!") + assert_in_out_err(%w[-c], "#!", ["Syntax OK"]) + end + + def test_flag_in_shebang + Tempfile.create(%w"pflag .rb") do |script| + code = "#!ruby -p" + script.puts(code) + script.close + assert_in_out_err([script.path, script.path], '', [code]) + end + Tempfile.create(%w"sflag .rb") do |script| + script.puts("#!ruby -s") + script.puts("p $abc") + script.close + assert_in_out_err([script.path, "-abc=foo"], '', ['"foo"']) + end + end + + def test_sflag + assert_in_out_err(%w(- -abc -def=foo -ghi-jkl -- -xyz), + "#!ruby -s\np [$abc, $def, $ghi_jkl, defined?($xyz)]\n", + ['[true, "foo", true, nil]'], []) + + assert_in_out_err(%w(- -#), "#!ruby -s\n", [], + /invalid name for global variable - -# \(NameError\)/) + + assert_in_out_err(%w(- -#=foo), "#!ruby -s\n", [], + /invalid name for global variable - -# \(NameError\)/) + + assert_in_out_err(['-s', '-e', 'GC.start; p $DEBUG', '--', '-DEBUG=x'], "", ['"x"']) + end + + def test_option_missing_argument + assert_in_out_err(%w(-0 --enable), "", [], /missing argument for --enable/) + assert_in_out_err(%w(-0 --disable), "", [], /missing argument for --disable/) + assert_in_out_err(%w(-0 --dump), "", [], /missing argument for --dump/) + assert_in_out_err(%w(-0 --encoding), "", [], /missing argument for --encoding/) + assert_in_out_err(%w(-0 --external-encoding), "", [], /missing argument for --external-encoding/) + assert_in_out_err(%w(-0 --internal-encoding), "", [], /missing argument for --internal-encoding/) + assert_in_out_err(%w(-0 --backtrace-limit), "", [], /missing argument for --backtrace-limit/) + end + + def test_assignment_in_conditional + 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.puts "if a = :sym; end" + t.flush + err = ["#{t.path}:1:#{warning}", + "#{t.path}:2:#{warning}", + "#{t.path}:3:#{warning}", + "#{t.path}:5:#{warning}", + "#{t.path}:6:#{warning}", + "#{t.path}:8:#{warning}", + ] + feature4299 = '[ruby-dev:43083]' + assert_in_out_err(["-w", t.path], "", [], err, feature4299) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err, feature4299) + + t.rewind + t.truncate(0) + t.puts "if a = __LINE__; end" + t.puts "if a = __FILE__; end" + t.flush + err = ["#{t.path}:1:#{warning}", + "#{t.path}:2:#{warning}", + ] + assert_in_out_err(["-w", t.path], "", [], err) + assert_in_out_err(["-wr", t.path, "-e", ""], "", [], err) + } + end + + def test_indentation_check + 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"], + ["if false;", "end", "if true\nelse ", "end"], + ["else", " end", "_ = if true\n"], + ["begin\n def f() = nil", "end"], + ["begin\n def self.f() = nil", "end"], + ].each do + |b, e = 'end', pre = nil, post = nil| + src = ["#{pre}#{b}\n", " #{e}\n#{post}"] + k = b[/\A\s*(\S+)/, 1] + e = e[/\A\s*(\S+)/, 1] + n = 1 + src[0].count("\n") + n1 = 1 + (pre ? pre.count("\n") : 0) + + a.for("no directives with #{src}") do + err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"] + t.rewind + t.truncate(0) + t.puts src + 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 #{src}") 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 #{src}") do + err = ["#{t.path}:#{n+2}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1+2}"] + t.rewind + t.truncate(0) + t.puts "# -*- warn-indent: false -*-" + 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 #{src}") 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 #{src}") do + err = ["#{t.path}:#{n}: warning: mismatched indentations at '#{e}' with '#{k}' at #{n1}"] + 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" + 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_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 + ruby = EnvUtil.rubybin + IO.popen([ruby, '-e', 'print $0']) {|f| + assert_equal('-e', f.read) + } + IO.popen([ruby, '-'], 'r+') {|f| + f << 'print $0' + f.close_write + assert_equal('-', f.read) + } + Dir.mktmpdir {|d| + n1 = File.join(d, 't1') + open(n1, 'w') {|f| f << 'print $0' } + IO.popen([ruby, n1]) {|f| + assert_equal(n1, f.read) + } + if File.respond_to? :symlink + n2 = File.join(d, 't2') + 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' + open(n3, 'w') {|f| f << 'print $0' } + IO.popen([ruby, '--', n3]) {|f| + assert_equal(n3, f.read) + } + n4 = '-' + IO.popen([ruby, '--', n4], 'r+') {|f| + f << 'print $0' + f.close_write + assert_equal(n4, f.read) + } + } + } + end + + 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 + omit "platform dependent feature" unless defined?(PSCMD) and PSCMD + + with_tmpchdir do + write_file("test-script", "$0 = 'hello world'; /test-script/ =~ Process.argv0 or $0 = 'Process.argv0 changed!'; sleep 60") + + pid = spawn(EnvUtil.rubybin, "test-script") + ps = nil + 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 + EnvUtil.timeout(5) { Process.wait(pid) } + end + end + + def test_setproctitle + omit "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 = {} + unless /mswin|mingw/ =~ RUBY_PLATFORM + opts[:rlimit_core] = 0 + end + opts[:failed] = proc do |status, message = "", out = ""| + if (sig = status.termsig) && Signal.list["SEGV"] == sig + out = "" + end + Test::Unit::CoreAssertions::FailDesc[status, message] + end + ExecOptions = opts.freeze + + # The regexp list that should match the entire stderr output. + # see assert_pattern_list + ExpectedStderrList = [ + %r( + (?:-e:(?:1:)?\s)?\[BUG\]\sSegmentation\sfault.*\n + )x, + %r( + #{ Regexp.quote(RUBY_DESCRIPTION) }\n\n + )x, + %r( + (?:--\s(?:.+\n)*\n)? + --\sControl\sframe\sinformation\s-+\n + (?:(?:c:.*\n)|(?:^\s+.+\n))* + \n + )x, + %r( + (?: + --\sRuby\slevel\sbacktrace\sinformation\s----------------------------------------\n + (?:-e:1:in\s\'(?:block\sin\s)?<main>\'\n)* + -e:1:in\s\'kill\'\n + \n + )? + )x, + %r( + (?:--\sThreading(?:.+\n)*\n)? + )x, + %r( + (?:--\sMachine(?:.+\n)*\n)? + )x, + %r( + (?: + --\sC\slevel\sbacktrace\sinformation\s-------------------------------------------\n + (?:Un(?:expected|supported|known)\s.*\n)* + (?:(?:.*\s)?\[0x\h+\].*\n|.*:\d+\n)*\n + )? + )x, + %r( + (?:--\sOther\sruntime\sinformation\s-+\n + (?:.*\n)* + )? + )x, + ] + + KILL_SELF = "Process.kill :SEGV, $$" + end + + def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block) + # We want YJIT to be enabled in the subprocess if it's enabled for us + # so that the Ruby description matches. + env = Hash === args.first ? args.shift : {} + args.unshift("--yjit") if JITSupport.yjit_enabled? + args.unshift("--zjit") if JITSupport.zjit_enabled? + env.update({'RUBY_ON_BUG' => nil}) + env['RUBY_CRASH_REPORT'] ||= nil # default to not passing down parent setting + # ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when + # catching sigsegv; we don't expect that output, so suppress it. + env.update({'ASAN_OPTIONS' => 'handle_segv=0', 'LSAN_OPTIONS' => 'handle_segv=0'}) + args.unshift(env) + + test_stdin = "" + if !block + tests = [//, list, message] + elsif message + tests = [[], [], message] + end + + assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT", + **SEGVTest::ExecOptions, **opt, &block) + end + + def test_segv_test + assert_segv(["--disable-gems", "-e", SEGVTest::KILL_SELF]) + end + + def test_segv_loaded_features + bug7402 = '[ruby-core:49573]' + + assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}", + '-e', 'class Bogus; def to_str; exit true; end; end', + '-e', '$".clear', + '-e', '$".unshift Bogus.new', + '-e', '(p $"; abort) unless $".size == 1', + ], bug7402, success: false) + 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]; #{SEGVTest::KILL_SELF}", t.path], bug7597) + } + end + + def assert_crash_report(path, cmd = nil, &block) + Dir.mktmpdir("ruby_crash_report") do |dir| + list = SEGVTest::ExpectedStderrList + if cmd + FileUtils.mkpath(File.join(dir, File.dirname(cmd))) + File.write(File.join(dir, cmd), SEGVTest::KILL_SELF+"\n") + c = Regexp.quote(cmd) + list = list.map {|re| Regexp.new(re.source.gsub(/^\s*(\(\?:)?\K-e(?=:)/) {c}, re.options)} + else + cmd = ['-e', SEGVTest::KILL_SELF] + end + status = assert_segv([{"RUBY_CRASH_REPORT"=>path}, *cmd], list: [], chdir: dir, &block) + next if block + reports = Dir.glob("*.log", File::FNM_DOTMATCH, base: dir) + assert_equal(1, reports.size) + assert_pattern_list(list, File.read(File.join(dir, reports.first))) + break status, reports.first + end + end + + def test_crash_report + status, report = assert_crash_report("%e.%f.%p.log") + assert_equal("#{File.basename(EnvUtil.rubybin)}.-e.#{status.pid}.log", report) + end + + def test_crash_report_script + status, report = assert_crash_report("%e.%f.%p.log", "bug.rb") + assert_equal("#{File.basename(EnvUtil.rubybin)}.bug.rb.#{status.pid}.log", report) + end + + def test_crash_report_executable_path + omit if EnvUtil.rubybin.size > 245 + status, report = assert_crash_report("%E.%p.log") + path = EnvUtil.rubybin.sub(/\A\w\K:[\/\\]/, '!').tr_s('/', '!') + assert_equal("#{path}.#{status.pid}.log", report) + end + + def test_crash_report_script_path + status, report = assert_crash_report("%F.%p.log", "test/bug.rb") + assert_equal("test!bug.rb.#{status.pid}.log", report) + end + + def test_crash_report_pipe + if File.executable?(echo = "/bin/echo") + elsif /mswin|ming/ =~ RUBY_PLATFORM + echo = "echo" + else + omit "/bin/echo not found" + end + assert_crash_report("| #{echo} %e:%f:%p") do |stdin, stdout, status| + assert_equal(["#{File.basename(EnvUtil.rubybin)}:-e:#{status.pid}"], stdin) + end + end + + def test_crash_report_pipe_script + omit "only runs on Linux" unless RUBY_PLATFORM.include?("linux") + + Tempfile.create(["script", ".sh"]) do |script| + Tempfile.create("crash_report") do |crash_report| + script.write(<<~BASH) + #!/usr/bin/env bash + + cat > #{crash_report.path} + BASH + script.close + + FileUtils.chmod("+x", script) + + assert_crash_report("| #{script.path}") do + assert_include(File.read(crash_report.path), "[BUG] Segmentation fault at") + end + end + end + end + + def test_DATA + 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"], "", [], [], 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 + begin + require 'pty' + require 'io/console' + rescue LoadError + return + end + require 'timeout' + result = nil + 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 = EnvUtil.timeout(10) {r.read} + end + Process.wait pid + } + rescue RuntimeError + omit $! + end + } + assert_equal("", result, '[ruby-dev:37798]') + 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("external") rescue "?.txt" + with_tmpchdir do |dir| + open(name, "w") {} + assert_in_out_err(["-e", "puts ARGV", "?.txt"], "", [expected], [], + bug10555, encoding: "external") + 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 + } + omit "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("external", 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, test_stderr: []) + 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) + File.write(script, "abort ':run'\n") + opts = ['-C', dir, '-r', "./#{base}", *opt] + _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr) + stderr.concat(e) if e + stderr << "---" + _, e = assert_in_out_err([*opts, base], "", //, test_stderr) + 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') + assert_norun_with_rflag('--dump=parsetree+error_tolerant') + assert_norun_with_rflag('--dump=parse+error_tolerant') + end + + def test_dump_parsetree_error_tolerant + omit if ParserSupport.prism_enabled_in_subprocess? + + assert_in_out_err(['--dump=parse', '-e', 'begin'], + "", [], /unexpected end-of-input/, success: false) + assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'], + "", /^# @/, /unexpected end-of-input/, success: true) + assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'], + "", [], /unexpected end-of-input/, success: false) + 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}" + negopt = exp == "true" ? "disable" : "enable" + env = {"RUBYOPT"=>"--#{negopt}=#{arg}"} + a.for(key) do + assert_in_out_err([env, "--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: "\w+" \(FrozenError\)\n\z/ + frozen = [ + ["--enable-frozen-string-literal", true], + ["--disable-frozen-string-literal", 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"', []], + ['+"foo#{123}bar" << "bar"', []], + ['-"foo#{123}bar" << "bar"', wo_debug_pat], + ].each do |code, expected| + assert_in_out_err(opt, code, [], expected, "#{opt} #{code}") + end + end + end + + def test_frozen_string_literal_debug_chilled_strings + code = <<~RUBY + "foo" << "bar" + RUBY + assert_in_out_err(["-W:deprecated"], code, [], ["-:1: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)"]) + assert_in_out_err(["-W:deprecated", "--debug-frozen-string-literal"], code, [], ["-:1: warning: literal string will be frozen in the future", "-:1: info: the string was created here"]) + assert_in_out_err(["-W:deprecated", "--disable-frozen-string-literal", "--debug-frozen-string-literal"], code, [], []) + assert_in_out_err(["-W:deprecated", "--enable-frozen-string-literal", "--debug-frozen-string-literal"], code, [], ["-:1:in '<main>': can't modify frozen String: \"foo\", created at -:1 (FrozenError)"]) + 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_rubylib_invalid_encoding + env = {"RUBYLIB"=>"\xFF", "LOCALE"=>"en_US.UTF-8", "LC_ALL"=>"en_US.UTF-8"} + assert_ruby_status([env, "-e;"]) + end + + def test_null_script + omit "#{IO::NULL} is not a character device" unless File.chardev?(IO::NULL) + assert_in_out_err([IO::NULL], success: true) + end + + def test_free_at_exit_env_var + env = {"RUBY_FREE_AT_EXIT"=>"1"} + assert_ruby_status([env, "-e;"]) + assert_in_out_err([env, "-W"], "", [], /Free at exit is experimental and may be unstable/) + end + + def test_toplevel_ruby + assert_instance_of Module, ::Ruby + end + + def test_ruby_patchlevel + # We stopped bumping RUBY_PATCHLEVEL at Ruby 4.0.0. + # Released versions have RUBY_PATCHLEVEL 0, and un-released versions have -1. + assert_include [-1, 0], RUBY_PATCHLEVEL + end +end diff --git a/test/ruby/test_rubyvm.rb b/test/ruby/test_rubyvm.rb new file mode 100644 index 0000000000..225cb45f33 --- /dev/null +++ b/test/ruby/test_rubyvm.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: false +require 'test/unit' +require_relative '../lib/parser_support' + +class TestRubyVM < Test::Unit::TestCase + def test_stat + assert_kind_of Hash, RubyVM.stat + + RubyVM.stat(stat = {}) + assert_not_empty stat + end + + def test_stat_unknown + assert_raise(ArgumentError){ RubyVM.stat(:unknown) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {RubyVM.stat(:"\u{30eb 30d3 30fc}")} + end + + def parse_and_compile + script = <<~RUBY + _a = 1 + def foo + _b = 2 + end + 1.times{ + _c = 3 + } + RUBY + + ast = RubyVM::AbstractSyntaxTree.parse(script) + iseq = RubyVM::InstructionSequence.compile(script) + + [ast, iseq] + end + + def test_keep_script_lines + omit if ParserSupport.prism_enabled? + pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO + + prev_conf = RubyVM.keep_script_lines + + # keep + RubyVM.keep_script_lines = true + + ast, iseq = *parse_and_compile + + lines = ast.script_lines + assert_equal Array, lines.class + + lines = iseq.script_lines + assert_equal Array, lines.class + iseq.each_child{|child| + assert_equal lines, child.script_lines + } + assert lines.frozen? + + # don't keep + RubyVM.keep_script_lines = false + + ast, iseq = *parse_and_compile + + lines = ast.script_lines + assert_equal nil, lines + + lines = iseq.script_lines + assert_equal nil, lines + iseq.each_child{|child| + assert_equal lines, child.script_lines + } + + ensure + RubyVM.keep_script_lines = prev_conf + end +end diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb new file mode 100644 index 0000000000..70a61aa3b5 --- /dev/null +++ b/test/ruby/test_set.rb @@ -0,0 +1,1052 @@ +# frozen_string_literal: false +require 'test/unit' +require 'set' + +class TC_Set < Test::Unit::TestCase + class SetSubclass < Set + end + class CoreSetSubclass < Set::CoreSet + end + ALL_SET_CLASSES = [Set, SetSubclass, CoreSetSubclass].freeze + + def test_marshal + set = Set[1, 2, 3] + mset = Marshal.load(Marshal.dump(set)) + assert_equal(set, mset) + assert_equal(set.compare_by_identity?, mset.compare_by_identity?) + + set.compare_by_identity + mset = Marshal.load(Marshal.dump(set)) + assert_equal(set, mset) + assert_equal(set.compare_by_identity?, mset.compare_by_identity?) + + set.instance_variable_set(:@a, 1) + mset = Marshal.load(Marshal.dump(set)) + assert_equal(set, mset) + assert_equal(set.compare_by_identity?, mset.compare_by_identity?) + assert_equal(1, mset.instance_variable_get(:@a)) + + old_stdlib_set_data = "\x04\bo:\bSet\x06:\n@hash}\bi\x06Ti\aTi\bTF".b + set = Marshal.load(old_stdlib_set_data) + assert_equal(Set[1, 2, 3], set) + + old_stdlib_set_cbi_data = "\x04\bo:\bSet\x06:\n@hashC:\tHash}\ai\x06Ti\aTF".b + set = Marshal.load(old_stdlib_set_cbi_data) + assert_equal(Set[1, 2].compare_by_identity, set) + end + + def test_aref + assert_nothing_raised { + Set[] + Set[nil] + Set[1,2,3] + } + + assert_equal(0, Set[].size) + assert_equal(1, Set[nil].size) + assert_equal(1, Set[[]].size) + assert_equal(1, Set[[nil]].size) + + set = Set[2,4,6,4] + assert_equal(Set.new([2,4,6]), set) + end + + def test_s_new + assert_nothing_raised { + Set.new() + Set.new(nil) + Set.new([]) + Set.new([1,2]) + Set.new('a'..'c') + } + assert_raise(ArgumentError) { + Set.new(false) + } + assert_raise(ArgumentError) { + Set.new(1) + } + assert_raise(ArgumentError) { + Set.new(1,2) + } + + assert_equal(0, Set.new().size) + assert_equal(0, Set.new(nil).size) + assert_equal(0, Set.new([]).size) + assert_equal(1, Set.new([nil]).size) + + ary = [2,4,6,4] + set = Set.new(ary) + ary.clear + assert_equal(false, set.empty?) + assert_equal(3, set.size) + + ary = [1,2,3] + + s = Set.new(ary) { |o| o * 2 } + assert_equal([2,4,6], s.sort) + end + + def test_clone + set1 = Set.new + set2 = set1.clone + set1 << 'abc' + assert_equal(Set.new, set2) + end + + def test_dup + set1 = Set[1,2] + set2 = set1.dup + + assert_not_same(set1, set2) + + assert_equal(set1, set2) + + set1.add(3) + + assert_not_equal(set1, set2) + end + + def test_size + assert_equal(0, Set[].size) + assert_equal(2, Set[1,2].size) + assert_equal(2, Set[1,2,1].size) + end + + def test_empty? + assert_equal(true, Set[].empty?) + assert_equal(false, Set[1, 2].empty?) + end + + def test_clear + set = Set[1,2] + ret = set.clear + + assert_same(set, ret) + assert_equal(true, set.empty?) + end + + def test_replace + set = Set[1,2] + ret = set.replace('a'..'c') + + assert_same(set, ret) + assert_equal(Set['a','b','c'], set) + + set = Set[1,2] + ret = set.replace(Set.new('a'..'c')) + + assert_same(set, ret) + assert_equal(Set['a','b','c'], set) + + set = Set[1,2] + assert_raise(ArgumentError) { + set.replace(3) + } + assert_equal(Set[1,2], set) + end + + def test_to_a + set = Set[1,2,3,2] + ary = set.to_a + + assert_equal([1,2,3], ary.sort) + end + + def test_flatten + # test1 + set1 = Set[ + 1, + Set[ + 5, + Set[7, + Set[0] + ], + Set[6,2], + 1 + ], + 3, + Set[3,4] + ] + + set2 = set1.flatten + set3 = Set.new(0..7) + + assert_not_same(set2, set1) + assert_equal(set3, set2) + + # test2; destructive + orig_set1 = set1 + set1.flatten! + + assert_same(orig_set1, set1) + assert_equal(set3, set1) + + # test3; multiple occurrences of a set in an set + set1 = Set[1, 2] + set2 = Set[set1, Set[set1, 4], 3] + + assert_nothing_raised { + set2.flatten! + } + + assert_equal(Set.new(1..4), set2) + + # test4; recursion + set2 = Set[] + set1 = Set[1, set2] + set2.add(set1) + + assert_raise(ArgumentError) { + set1.flatten! + } + + # test5; miscellaneous + empty = Set[] + set = Set[Set[empty, "a"],Set[empty, "b"]] + + assert_nothing_raised { + set.flatten + } + + set1 = empty.merge(Set["no_more", set]) + + assert_nil(Set.new(0..31).flatten!) + + x = Set[Set[],Set[1,2]].flatten! + y = Set[1,2] + + assert_equal(x, y) + end + + def test_include? + set = Set[1,2,3] + + assert_equal(true, set.include?(1)) + assert_equal(true, set.include?(2)) + assert_equal(true, set.include?(3)) + assert_equal(false, set.include?(0)) + assert_equal(false, set.include?(nil)) + + set = Set["1",nil,"2",nil,"0","1",false] + assert_equal(true, set.include?(nil)) + assert_equal(true, set.include?(false)) + assert_equal(true, set.include?("1")) + assert_equal(false, set.include?(0)) + assert_equal(false, set.include?(true)) + end + + def test_eqq + set = Set[1,2,3] + + assert_equal(true, set === 1) + assert_equal(true, set === 2) + assert_equal(true, set === 3) + assert_equal(false, set === 0) + assert_equal(false, set === nil) + + set = Set["1",nil,"2",nil,"0","1",false] + assert_equal(true, set === nil) + assert_equal(true, set === false) + assert_equal(true, set === "1") + assert_equal(false, set === 0) + assert_equal(false, set === true) + end + + def test_superset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.superset?() + } + + assert_raise(ArgumentError) { + set.superset?(2) + } + + assert_raise(ArgumentError) { + set.superset?([2]) + } + + ALL_SET_CLASSES.each { |klass| + assert_equal(true, set.superset?(klass[]), klass.name) + assert_equal(true, set.superset?(klass[1,2]), klass.name) + assert_equal(true, set.superset?(klass[1,2,3]), klass.name) + assert_equal(false, set.superset?(klass[1,2,3,4]), klass.name) + assert_equal(false, set.superset?(klass[1,4]), klass.name) + + assert_equal(true, set >= klass[1,2,3], klass.name) + assert_equal(true, set >= klass[1,2], klass.name) + + assert_equal(true, Set[].superset?(klass[]), klass.name) + } + end + + def test_proper_superset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.proper_superset?() + } + + assert_raise(ArgumentError) { + set.proper_superset?(2) + } + + assert_raise(ArgumentError) { + set.proper_superset?([2]) + } + + ALL_SET_CLASSES.each { |klass| + assert_equal(true, set.proper_superset?(klass[]), klass.name) + assert_equal(true, set.proper_superset?(klass[1,2]), klass.name) + assert_equal(false, set.proper_superset?(klass[1,2,3]), klass.name) + assert_equal(false, set.proper_superset?(klass[1,2,3,4]), klass.name) + assert_equal(false, set.proper_superset?(klass[1,4]), klass.name) + + assert_equal(false, set > klass[1,2,3], klass.name) + assert_equal(true, set > klass[1,2], klass.name) + + assert_equal(false, Set[].proper_superset?(klass[]), klass.name) + } + end + + def test_subset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.subset?() + } + + assert_raise(ArgumentError) { + set.subset?(2) + } + + assert_raise(ArgumentError) { + set.subset?([2]) + } + + ALL_SET_CLASSES.each { |klass| + assert_equal(true, set.subset?(klass[1,2,3,4]), klass.name) + assert_equal(true, set.subset?(klass[1,2,3]), klass.name) + assert_equal(false, set.subset?(klass[1,2]), klass.name) + assert_equal(false, set.subset?(klass[]), klass.name) + + assert_equal(true, set <= klass[1,2,3], klass.name) + assert_equal(true, set <= klass[1,2,3,4], klass.name) + + assert_equal(true, Set[].subset?(klass[1]), klass.name) + assert_equal(true, Set[].subset?(klass[]), klass.name) + } + end + + def test_proper_subset? + set = Set[1,2,3] + + assert_raise(ArgumentError) { + set.proper_subset?() + } + + assert_raise(ArgumentError) { + set.proper_subset?(2) + } + + assert_raise(ArgumentError) { + set.proper_subset?([2]) + } + + ALL_SET_CLASSES.each { |klass| + assert_equal(true, set.proper_subset?(klass[1,2,3,4]), klass.name) + assert_equal(false, set.proper_subset?(klass[1,2,3]), klass.name) + assert_equal(false, set.proper_subset?(klass[1,2]), klass.name) + assert_equal(false, set.proper_subset?(klass[]), klass.name) + + assert_equal(false, set < klass[1,2,3], klass.name) + assert_equal(true, set < klass[1,2,3,4], klass.name) + + assert_equal(false, Set[].proper_subset?(klass[]), klass.name) + } + end + + def test_spacecraft_operator + set = Set[1,2,3] + + assert_nil(set <=> 2) + + assert_nil(set <=> set.to_a) + + ALL_SET_CLASSES.each { |klass| + assert_equal(-1, set <=> klass[1,2,3,4], klass.name) + assert_equal( 0, set <=> klass[3,2,1] , klass.name) + assert_equal(nil, set <=> klass[1,2,4] , klass.name) + assert_equal(+1, set <=> klass[2,3] , klass.name) + assert_equal(+1, set <=> klass[] , klass.name) + + assert_equal(0, Set[] <=> klass[], klass.name) + } + end + + def assert_intersect(expected, set, other) + case expected + when true + assert_send([set, :intersect?, other]) + assert_send([set, :intersect?, other.to_a]) + assert_send([other, :intersect?, set]) + assert_not_send([set, :disjoint?, other]) + assert_not_send([set, :disjoint?, other.to_a]) + assert_not_send([other, :disjoint?, set]) + when false + assert_not_send([set, :intersect?, other]) + assert_not_send([set, :intersect?, other.to_a]) + assert_not_send([other, :intersect?, set]) + assert_send([set, :disjoint?, other]) + assert_send([set, :disjoint?, other.to_a]) + assert_send([other, :disjoint?, set]) + when Class + assert_raise(expected) { + set.intersect?(other) + } + assert_raise(expected) { + set.disjoint?(other) + } + else + raise ArgumentError, "%s: unsupported expected value: %s" % [__method__, expected.inspect] + end + end + + def test_intersect? + set = Set[3,4,5] + + assert_intersect(ArgumentError, set, 3) + assert_intersect(true, set, Set[2,4,6]) + + assert_intersect(true, set, set) + assert_intersect(true, set, Set[2,4]) + assert_intersect(true, set, Set[5,6,7]) + assert_intersect(true, set, Set[1,2,6,8,4]) + + assert_intersect(false, set, Set[]) + assert_intersect(false, set, Set[0,2]) + assert_intersect(false, set, Set[0,2,6]) + assert_intersect(false, set, Set[0,2,6,8,10]) + + # Make sure set hasn't changed + assert_equal(Set[3,4,5], set) + end + + def test_each + ary = [1,3,5,7,10,20] + set = Set.new(ary) + + ret = set.each { |o| } + assert_same(set, ret) + + e = set.each + assert_instance_of(Enumerator, e) + + assert_nothing_raised { + set.each { |o| + ary.delete(o) or raise "unexpected element: #{o}" + } + + ary.empty? or raise "forgotten elements: #{ary.join(', ')}" + } + + assert_equal(6, e.size) + set << 42 + assert_equal(7, e.size) + end + + def test_add + set = Set[1,2,3] + + ret = set.add(2) + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + ret = set.add?(2) + assert_nil(ret) + assert_equal(Set[1,2,3], set) + + ret = set.add(4) + assert_same(set, ret) + assert_equal(Set[1,2,3,4], set) + + ret = set.add?(5) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,5], set) + end + + def test_delete + set = Set[1,2,3] + + ret = set.delete(4) + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + ret = set.delete?(4) + assert_nil(ret) + assert_equal(Set[1,2,3], set) + + ret = set.delete(2) + assert_equal(set, ret) + assert_equal(Set[1,3], set) + + ret = set.delete?(1) + assert_equal(set, ret) + assert_equal(Set[3], set) + end + + def test_delete_if + set = Set.new(1..10) + ret = set.delete_if { |i| i > 10 } + assert_same(set, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.delete_if { |i| i % 3 == 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.delete_if + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 == 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_keep_if + set = Set.new(1..10) + ret = set.keep_if { |i| i <= 10 } + assert_same(set, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.keep_if { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.keep_if + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 != 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_collect! + set = Set[1,2,3,'a','b','c',-1..1,2..4] + + ret = set.collect! { |i| + case i + when Numeric + i * 2 + when String + i.upcase + else + nil + end + } + + assert_same(set, ret) + assert_equal(Set[2,4,6,'A','B','C',nil], set) + + set = Set[1,2,3,'a','b','c',-1..1,2..4] + enum = set.collect! + + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| + case i + when Numeric + i * 2 + when String + i.upcase + else + nil + end + }) + assert_equal(Set[2,4,6,'A','B','C',nil], set) + end + + def test_reject! + set = Set.new(1..10) + + ret = set.reject! { |i| i > 10 } + assert_nil(ret) + assert_equal(Set.new(1..10), set) + + ret = set.reject! { |i| i % 3 == 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.reject! + assert_equal(set.size, enum.size) + assert_same(set, enum.each { |i| i % 3 == 0 }) + assert_equal(Set[1,2,4,5,7,8,10], set) + end + + def test_select! + set = Set.new(1..10) + ret = set.select! { |i| i <= 10 } + assert_equal(nil, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.select! { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.select! + assert_equal(set.size, enum.size) + assert_equal(nil, enum.each { |i| i <= 10 }) + assert_equal(Set.new(1..10), set) + end + + def test_filter! + set = Set.new(1..10) + ret = set.filter! { |i| i <= 10 } + assert_equal(nil, ret) + assert_equal(Set.new(1..10), set) + + set = Set.new(1..10) + ret = set.filter! { |i| i % 3 != 0 } + assert_same(set, ret) + assert_equal(Set[1,2,4,5,7,8,10], set) + + set = Set.new(1..10) + enum = set.filter! + assert_equal(set.size, enum.size) + assert_equal(nil, enum.each { |i| i <= 10 }) + assert_equal(Set.new(1..10), set) + end + + def test_merge + set = Set[1,2,3] + ret = set.merge([2,4,6]) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,6], set) + + set = Set[1,2,3] + ret = set.merge() + assert_same(set, ret) + assert_equal(Set[1,2,3], set) + + set = Set[1,2,3] + ret = set.merge([2,4,6], Set[4,5,6]) + assert_same(set, ret) + assert_equal(Set[1,2,3,4,5,6], set) + + assert_raise(ArgumentError) { + Set[].merge(a: 1) + } + end + + def test_merge_mutating_hash_bug_21305 + a = (1..100).to_a + o = Object.new + o.define_singleton_method(:hash) do + a.clear + 0 + end + a.unshift o + assert_equal([o], Set.new.merge(a).to_a) + end + + def test_initialize_mutating_array_bug_21306 + a = (1..100).to_a + assert_equal(Set[0], Set.new(a){a.clear; 0}) + end + + def test_subtract + set = Set[1,2,3] + + ret = set.subtract([2,4,6]) + assert_same(set, ret) + assert_equal(Set[1,3], set) + end + + def test_plus + set = Set[1,2,3] + + ret = set + [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[1,2,3,4,6], ret) + end + + def test_minus + set = Set[1,2,3] + + ret = set - [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[1,3], ret) + end + + def test_and + set = Set[1,2,3,4] + + ret = set & [2,4,6] + assert_not_same(set, ret) + assert_equal(Set[2,4], ret) + end + + def test_xor + ALL_SET_CLASSES.each { |klass| + set = klass[1,2,3,4] + ret = set ^ [2,4,5,5] + assert_not_same(set, ret) + assert_equal(klass[1,3,5], ret) + + set2 = klass[1,2,3,4] + ret2 = set2 ^ [2,4,5,5] + assert_instance_of(klass, ret2) + assert_equal(klass[1,3,5], ret2) + } + end + + def test_xor_does_not_mutate_other_set + a = Set[1] + b = Set[1, 2] + original_b = b.dup + + result = a ^ b + + assert_equal(original_b, b) + assert_equal(Set[2], result) + end + + def test_eq + set1 = Set[2,3,1] + set2 = Set[1,2,3] + + assert_equal(set1, set1) + assert_equal(set1, set2) + assert_not_equal(Set[1], [1]) + + set1 = Class.new(Set)["a", "b"] + set1.add(set1).reset # Make recursive + set2 = Set["a", "b", Set["a", "b", set1]] + + assert_equal(set1, set2) + + assert_not_equal(Set[Exception.new,nil], Set[Exception.new,Exception.new], "[ruby-dev:26127]") + end + + def test_classify + set = Set.new(1..10) + ret = set.classify { |i| i % 3 } + + assert_equal(3, ret.size) + assert_instance_of(Hash, ret) + ret.each_value { |value| assert_instance_of(Set, value) } + assert_equal(Set[3,6,9], ret[0]) + assert_equal(Set[1,4,7,10], ret[1]) + assert_equal(Set[2,5,8], ret[2]) + + set = Set.new(1..10) + enum = set.classify + + assert_equal(set.size, enum.size) + ret = enum.each { |i| i % 3 } + assert_equal(3, ret.size) + assert_instance_of(Hash, ret) + ret.each_value { |value| assert_instance_of(Set, value) } + assert_equal(Set[3,6,9], ret[0]) + assert_equal(Set[1,4,7,10], ret[1]) + assert_equal(Set[2,5,8], ret[2]) + end + + def test_divide + set = Set.new(1..10) + ret = set.divide { |i| i % 3 } + + assert_equal(3, ret.size) + n = 0 + ret.each { |s| n += s.size } + assert_equal(set.size, n) + assert_equal(set, ret.flatten) + + set = Set[7,10,5,11,1,3,4,9,0] + ret = set.divide { |a,b| (a - b).abs == 1 } + + assert_equal(4, ret.size) + n = 0 + ret.each { |s| n += s.size } + assert_equal(set.size, n) + assert_equal(set, ret.flatten) + ret.each { |s| + if s.include?(0) + assert_equal(Set[0,1], s) + elsif s.include?(3) + assert_equal(Set[3,4,5], s) + elsif s.include?(7) + assert_equal(Set[7], s) + elsif s.include?(9) + assert_equal(Set[9,10,11], s) + else + raise "unexpected group: #{s.inspect}" + end + } + + set = Set.new(1..10) + enum = set.divide + ret = enum.each { |i| i % 3 } + + assert_equal(set.size, enum.size) + assert_equal(3, ret.size) + n = 0 + ret.each { |s| n += s.size } + assert_equal(set.size, n) + assert_equal(set, ret.flatten) + + set = Set[2,12,9,11,13,4,10,15,3,8,5,0,1,7,14] + ret = set.divide { |a,b| (a - b).abs == 1 } + assert_equal(2, ret.size) + end + + def test_freeze + orig = set = Set[1,2,3] + assert_equal false, set.frozen? + set << 4 + assert_same orig, set.freeze + assert_equal true, set.frozen? + assert_raise(FrozenError) { + set << 5 + } + assert_equal 4, set.size + end + + def test_freeze_dup + set1 = Set[1,2,3] + set1.freeze + set2 = set1.dup + + assert_not_predicate set2, :frozen? + assert_nothing_raised { + set2.add 4 + } + end + + def test_freeze_clone + set1 = Set[1,2,3] + set1.freeze + set2 = set1.clone + + assert_predicate set2, :frozen? + assert_raise(FrozenError) { + set2.add 5 + } + end + + def test_freeze_clone_false + set1 = Set[1,2,3] + set1.freeze + set2 = set1.clone(freeze: false) + + assert_not_predicate set2, :frozen? + set2.add 5 + assert_equal Set[1,2,3,5], set2 + assert_equal Set[1,2,3], set1 + end if Kernel.instance_method(:initialize_clone).arity != 1 + + def test_join + assert_equal('123', Set[1, 2, 3].join) + assert_equal('1 & 2 & 3', Set[1, 2, 3].join(' & ')) + end + + def test_inspect + set1 = Set[1, 2] + assert_equal('Set[1, 2]', set1.inspect) + + set2 = Set[Set[0], 1, 2, set1] + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.inspect) + + set1.add(set2) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.inspect) + + c = Class.new(Set::CoreSet) + c.set_temporary_name("_MySet") + assert_equal('_MySet[1, 2]', c[1, 2].inspect) + + c = Class.new(Set) + c.set_temporary_name("_MySet") + assert_equal('#<_MySet: {1, 2}>', c[1, 2].inspect) + end + + def test_to_s + set1 = Set[1, 2] + assert_equal('Set[1, 2]', set1.to_s) + + set2 = Set[Set[0], 1, 2, set1] + assert_equal('Set[Set[0], 1, 2, Set[1, 2]]', set2.to_s) + + set1.add(set2) + assert_equal('Set[Set[0], 1, 2, Set[1, 2, Set[...]]]', set2.to_s) + end + + def test_compare_by_identity + a1, a2 = "a", "a" + b1, b2 = "b", "b" + c = "c" + array = [a1, b1, c, a2, b2] + + iset = Set.new.compare_by_identity + assert_send([iset, :compare_by_identity?]) + iset.merge(array) + assert_equal(5, iset.size) + assert_equal(array.map(&:object_id).sort, iset.map(&:object_id).sort) + + set = Set.new + assert_not_send([set, :compare_by_identity?]) + set.merge(array) + assert_equal(3, set.size) + assert_equal(array.uniq.sort, set.sort) + end + + def test_reset + [Set, Class.new(Set)].each { |klass| + a = [1, 2] + b = [1] + set = klass.new([a, b]) + + b << 2 + set.reset + + assert_equal(klass.new([a]), set, klass.name) + } + end + + def test_set_gc_compact_does_not_allocate + assert_in_out_err([], <<-"end;", [], []) + def x + s = Set.new + s << Object.new + s + end + + x + begin + GC.compact + rescue NotImplementedError + end + end; + end + + def test_larger_sets + set = Set.new + 10_000.times do |i| + set << i + end + set = set.dup + + 10_000.times do |i| + assert_includes set, i + end + end + + def test_subclass_new_calls_add + c = Class.new(Set) do + def add(o) + super + super(o+1) + end + end + assert_equal([1, 2], c.new([1]).to_a) + end + + def test_subclass_aref_calls_initialize + c = Class.new(Set) do + def initialize(enum) + super + add(1) + end + end + assert_equal([2, 1], c[2].to_a) + end + +end + +class TC_Enumerable < Test::Unit::TestCase + def test_to_set + ary = [2,5,4,3,2,1,3] + + set = ary.to_set + assert_instance_of(Set, set) + assert_equal([1,2,3,4,5], set.sort) + + set = ary.to_set { |o| o * -2 } + assert_instance_of(Set, set) + assert_equal([-10,-8,-6,-4,-2], set.sort) + + assert_same set, set.to_set + assert_not_same set, set.to_set { |o| o } + end + + class MyEnum + include Enumerable + + def initialize(array) + @array = array + end + + def each(&block) + @array.each(&block) + end + + def size + raise "should not be called" + end + end + + def test_to_set_not_calling_size + enum = MyEnum.new([1,2,3]) + + set = assert_nothing_raised { enum.to_set } + assert(set.is_a?(Set)) + assert_equal(Set[1,2,3], set) + + enumerator = enum.to_enum + + set = assert_nothing_raised { enumerator.to_set } + assert(set.is_a?(Set)) + assert_equal(Set[1,2,3], set) + end +end + +class TC_Set_Builtin < Test::Unit::TestCase + private def should_omit? + (RUBY_VERSION.scan(/\d+/).map(&:to_i) <=> [3, 2]) < 0 || + !File.exist?(File.expand_path('../prelude.rb', __dir__)) + end + + def test_Set + omit "skipping the test for the builtin Set" if should_omit? + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = Set.new([1, 2]) + assert_equal('Set', set.class.name) + end + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = Set[1, 2] + assert_equal('Set', set.class.name) + end + end; + end + + def test_to_set + omit "skipping the test for the builtin Enumerable#to_set" if should_omit? + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + assert_nothing_raised do + set = [1, 2].to_set + assert_equal('Set', set.class.name) + end + end; + end +end diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb new file mode 100644 index 0000000000..d3b2441e21 --- /dev/null +++ b/test/ruby/test_settracefunc.rb @@ -0,0 +1,3166 @@ +# frozen_string_literal: false +require 'test/unit' +EnvUtil.suppress_warning {require 'continuation'} + +class TestSetTraceFunc < Test::Unit::TestCase + def setup + if defined?(RubyVM) + @original_compile_option = RubyVM::InstructionSequence.compile_option + RubyVM::InstructionSequence.compile_option = { + :trace_instruction => true, + :specialized_instruction => false + } + end + @target_thread = Thread.current + end + + def teardown + set_trace_func(nil) + if defined?(RubyVM) + RubyVM::InstructionSequence.compile_option = @original_compile_option + end + @target_thread = nil + end + + def target_thread? + Thread.current == @target_thread + end + + def test_c_call + events = [] + 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] if file == name + 3: }) + 4: x = 1 + 1 + 5: set_trace_func(nil) + EOF + 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, :+, Integer], + events.shift) + assert_equal(["c-return", 4, :+, Integer], + events.shift) + assert_equal(["line", 5, __method__, self.class], + events.shift) + assert_equal(["c-call", 5, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + end + + def test_c_return_no_binding + binding = :none + TracePoint.new(:c_return){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_no_binding + binding = :none + TracePoint.new(:c_call){|tp| + binding = tp.binding + }.enable{ + 1.object_id + } + assert_nil(binding) + end + + def test_c_call_removed_method + # [Bug #19305] + klass = Class.new do + attr_writer :bar + alias_method :set_bar, :bar= + remove_method :bar= + end + + obj = klass.new + method_id = nil + parameters = nil + + TracePoint.new(:c_call) { |tp| + method_id = tp.method_id + parameters = tp.parameters + }.enable { + obj.set_bar(1) + } + + assert_equal(:bar=, method_id) + assert_equal([[:req]], parameters) + end + + def test_c_call_aliased_method + # [Bug #20915] + klass = Class.new do + alias_method :new_method, :method + end + + instance = klass.new + parameters = nil + + TracePoint.new(:c_call) do |tp| + parameters = tp.parameters + end.enable { instance.new_method(:to_s) } + + assert_equal([[:req]], parameters) + end + + def test_call + events = [] + name = "#{self.class}\##{__method__}" + eval <<-EOF.gsub(/^.*?: /, ""), nil, name + 1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass| + 2: events << [event, lineno, mid, klass] if file == name + 3: }) + 4: def add(x, y) + 5: x + y + 6: end + 7: x = add(1, 1) + 8: set_trace_func(nil) + EOF + 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, self.class], + events.shift) + assert_equal(["c-return", 4, :method_added, self.class], + events.shift) + assert_equal(["line", 7, __method__, self.class], + events.shift) + assert_equal(["call", 4, :add, self.class], + events.shift) + assert_equal(["line", 5, :add, self.class], + events.shift) + assert_equal(["c-call", 5, :+, Integer], + events.shift) + assert_equal(["c-return", 5, :+, Integer], + events.shift) + assert_equal(["return", 6, :add, self.class], + events.shift) + assert_equal(["line", 8, __method__, self.class], + events.shift) + assert_equal(["c-call", 8, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + end + + def test_class + events = [] + 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] if file == name + 3: }) + 4: class Foo + 5: def bar + 6: end + 7: end + 8: x = Foo.new.bar + 9: set_trace_func(nil) + EOF + 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, :const_added, Module], + events.shift) + assert_equal(["c-return", 4, :const_added, Module], + events.shift) + assert_equal(["c-call", 4, :inherited, Class], + events.shift) + assert_equal(["c-return", 4, :inherited, Class], + events.shift) + assert_equal(["class", 4, nil, nil], + events.shift) + assert_equal(["line", 5, nil, nil], + events.shift) + assert_equal(["c-call", 5, :method_added, Module], + events.shift) + assert_equal(["c-return", 5, :method_added, Module], + events.shift) + assert_equal(["end", 7, nil, nil], + events.shift) + assert_equal(["line", 8, __method__, self.class], + events.shift) + assert_equal(["c-call", 8, :new, Class], + events.shift) + assert_equal(["c-call", 8, :initialize, BasicObject], + events.shift) + assert_equal(["c-return", 8, :initialize, BasicObject], + events.shift) + assert_equal(["c-return", 8, :new, Class], + events.shift) + assert_equal(["call", 5, :bar, Foo], + events.shift) + assert_equal(["return", 6, :bar, Foo], + events.shift) + assert_equal(["line", 9, __method__, self.class], + events.shift) + assert_equal(["c-call", 9, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + + self.class.class_eval do + remove_const :Foo + end + end + + def test_return # [ruby-dev:38701] + events = [] + 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] if file == name + 3: }) + 4: def meth_return(a) + 5: return if a + 6: return + 7: end + 8: meth_return(true) + 9: meth_return(false) + 10: set_trace_func(nil) + EOF + 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, self.class], + events.shift) + assert_equal(["c-return", 4, :method_added, self.class], + events.shift) + assert_equal(["line", 8, __method__, self.class], + events.shift) + assert_equal(["call", 4, :meth_return, self.class], + events.shift) + assert_equal(["line", 5, :meth_return, self.class], + events.shift) + assert_equal(["return", 5, :meth_return, self.class], + events.shift) + assert_equal(["line", 9, :test_return, self.class], + events.shift) + assert_equal(["call", 4, :meth_return, self.class], + events.shift) + assert_equal(["line", 5, :meth_return, self.class], + events.shift) + assert_equal(["line", 6, :meth_return, self.class], + events.shift) + assert_equal(["return", 6, :meth_return, self.class], + events.shift) + assert_equal(["line", 10, :test_return, self.class], + events.shift) + assert_equal(["c-call", 10, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + end + + def test_return2 # [ruby-core:24463] + events = [] + 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] if file == name + 3: }) + 4: def meth_return2 + 5: a = 5 + 6: return a + 7: end + 8: meth_return2 + 9: set_trace_func(nil) + EOF + 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, self.class], + events.shift) + assert_equal(["c-return", 4, :method_added, self.class], + events.shift) + assert_equal(["line", 8, __method__, self.class], + events.shift) + assert_equal(["call", 4, :meth_return2, self.class], + events.shift) + assert_equal(["line", 5, :meth_return2, self.class], + events.shift) + assert_equal(["line", 6, :meth_return2, self.class], + events.shift) + assert_equal(["return", 6, :meth_return2, self.class], + events.shift) + assert_equal(["line", 9, :test_return2, self.class], + events.shift) + assert_equal(["c-call", 9, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + end + + def test_raise + events = [] + 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] if file == name + 3: }) + 4: begin + 5: raise TypeError, "error" + 6: rescue TypeError + 7: end + 8: set_trace_func(nil) + EOF + assert_equal(["c-return", 1, :set_trace_func, Kernel], + events.shift) + assert_equal(["line", 5, __method__, self.class], + events.shift) + assert_equal(["c-call", 5, :raise, Kernel], + events.shift) + assert_equal(["c-call", 5, :exception, Exception], + events.shift) + assert_equal(["c-call", 5, :initialize, Exception], + events.shift) + assert_equal(["c-return", 5, :initialize, Exception], + 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(["raise", 5, :test_raise, TestSetTraceFunc], + events.shift) + assert_equal(["c-call", 6, :===, Module], + events.shift) + assert_equal(["c-return", 6, :===, Module], + events.shift) + assert_equal(["line", 8, __method__, self.class], + events.shift) + assert_equal(["c-call", 8, :set_trace_func, Kernel], + events.shift) + assert_equal([], events) + end + + def test_break # [ruby-core:27606] [Bug #2610] + events = [] + 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] if file == name + 3: }) + 4: [1,2,3].any? {|n| n} + 8: set_trace_func(nil) + EOF + + [["c-return", 1, :set_trace_func, Kernel], + ["line", 4, __method__, self.class], + ["c-call", 4, :any?, Array], + ["line", 4, __method__, self.class], + ["c-return", 4, :any?, Array], + ["line", 5, __method__, self.class], + ["c-call", 5, :set_trace_func, Kernel]].each.with_index{|e, i| + assert_equal(e, events.shift, "mismatch on #{i}th trace") + } + end + + def test_invalid_proc + assert_raise(TypeError) { set_trace_func(1) } + end + + def test_raise_in_trace + set_trace_func proc {raise rescue nil} + assert_equal(42, (raise rescue 42), '[ruby-core:24118]') + end + + def test_thread_trace + events = {:set => [], :add => []} + name = "#{self.class}\##{__method__}" + prc = Proc.new { |event, file, lineno, mid, binding, klass| + events[:set] << [event, lineno, mid, klass, :set] if file == name + } + prc = prc # suppress warning + prc2 = Proc.new { |event, file, lineno, mid, binding, klass| + events[:add] << [event, lineno, mid, klass, :add] if file == name + } + prc2 = prc2 # suppress warning + + th = Thread.new do + th = Thread.current + 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 + 6: end + 7: end + 8: ThreadTraceInnerClass.new.foo + 9: th.set_trace_func(nil) + EOF + end + th.join + + [["c-return", 1, :set_trace_func, Thread, :set], + ["line", 2, __method__, self.class, :set], + ["c-call", 2, :add_trace_func, Thread, :set]].each do |e| + assert_equal(e, events[:set].shift) + end + + [["c-return", 2, :add_trace_func, Thread], + ["line", 3, __method__, self.class], + ["c-call", 3, :const_added, Module], + ["c-return", 3, :const_added, Module], + ["c-call", 3, :inherited, Class], + ["c-return", 3, :inherited, Class], + ["class", 3, nil, nil], + ["line", 4, nil, nil], + ["c-call", 4, :method_added, Module], + ["c-return", 4, :method_added, Module], + ["end", 7, nil, nil], + ["line", 8, __method__, self.class], + ["c-call", 8, :new, Class], + ["c-call", 8, :initialize, BasicObject], + ["c-return", 8, :initialize, BasicObject], + ["c-return", 8, :new, Class], + ["call", 4, :foo, ThreadTraceInnerClass], + ["line", 5, :foo, ThreadTraceInnerClass], + ["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| + [:set, :add].each do |type| + assert_equal(e + [type], events[type].shift) + end + end + assert_equal([], events[:set]) + assert_equal([], events[:add]) + + # cleanup + self.class.class_eval do + remove_const :ThreadTraceInnerClass + end + end + + def test_trace_defined_method + 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", 1, :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| + # In parallel testing, unexpected events like IO operations may be traced, + # so we filter out events here. + next unless f == __FILE__ + 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].reverse_each{|;_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 = [ + # + [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 4, 'xyzzy', Array, :reverse_each, [1], nil, :nothing], + [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], + [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], + [:c_return, 4, "xyzzy", Array, :reverse_each, [1], nil, [1]], + [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, :nothing], + [:c_return, 7, "xyzzy", Module, :const_added, TestSetTraceFunc, nil, nil], + [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, :nothing], + [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil], + [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, :nothing], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, :nothing], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, :nothing], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, xyzzy], + [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing], + [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], + [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], + [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], + [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, :nothing], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, raised_exc], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, raised_exc], + [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, :nothing], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], + [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, :nothing], + [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, true], + [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + ] + + return events, answer_events + end + + def test_tracepoint + events1, answer_events = *trace_by_tracepoint(:line, :class, :end, :call, :return, :c_call, :c_return, :raise) + + ms = [events1, answer_events].map{|evs| + evs.map{|e| + "#{e[0]} - #{e[2]}:#{e[1]} id: #{e[4]}" + } + } + + if false # show all events + printf(" %-60s | %-60s\n", "actual", "expected") + ms[0].zip(ms[1]){|a, b| + printf("%s%-60s | %-60s\n", a==b ? ' ' : '!', a, b) + } + end + + mesg = ms[0].zip(ms[1]).map{|a, b| + if a != b + "actual: #{a} <-> expected: #{b}" + end + }.compact.join("\n") + + answer_events.zip(events1){|answer, event| + assert_equal answer, event, mesg + } + + [:line, :class, :end, :call, :return, :c_call, :c_return, :raise].each{|event| + events1, answer_events = *trace_by_tracepoint(event) + answer_events.find_all{|e| e[0] == event}.zip(events1){|answer_line, event_line| + assert_equal answer_line, event_line + } + } + end + + # Bug #18264 + def test_tracepoint_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { TracePoint.new(:line) { } } +1_000.times(&code) +PREP +1_000_000.times(&code) +CODE + end + + def test_tracepoint_bmethod_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #20194]", rss: true) + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + tp = TracePoint.new(:return) {} + begin; + 1_000_000.times do + tp.enable(target: bmethod) {} + end + end; + end + + def trace_by_set_trace_func + events = [] + trace = nil + trace = trace + xyzzy = nil + xyzzy = xyzzy + _local_var = :outer + method = :trace_by_set_trace_func + raised_exc = nil + + eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy' + 1: set_trace_func(lambda{|event, file, line, id, binding, klass| + 2: events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy' + 3: }) + 4: [1].map!{|;_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)} + + answer_events = [ + # + [:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, nil, nil], + [:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 4, 'xyzzy', Integer, :times, 1, nil, nil], + [:line, 4, 'xyzzy', self.class, method, self, nil, :nothing], + [:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing], + [:c_return, 4, "xyzzy", Integer, :times, 1, nil, nil], + [:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing], + [:c_call, 7, "xyzzy", Class, :inherited, Object, nil, nil], + [:c_return, 7, "xyzzy", Class, :inherited, Object, nil, nil], + [:c_call, 7, "xyzzy", Class, :const_added, Object, nil, nil], + [:c_return, 7, "xyzzy", Class, :const_added, Object, nil, nil], + [:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing], + [:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, nil, nil], + [:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing], + [:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 18, "xyzzy", Class, :new, xyzzy.class, nil, nil], + [:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, nil, nil], + [:c_return,18, "xyzzy", Class, :new, xyzzy.class, nil, nil], + [:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing], + [:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing], + [:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing], + [:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing], + [:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy], + [:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy], + [:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, nil, nil], + [:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :initialize, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :exception, RuntimeError, nil, nil], + [:c_return,20, "xyzzy", Kernel, :raise, self, nil, nil], + [:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], + [:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, nil, nil], + [:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc], + [:c_call, 20, "xyzzy", Module, :===, RuntimeError, nil, nil], + [:c_return,20, "xyzzy", Module, :===, RuntimeError, nil, nil], + [:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing], + [:c_call, 21, "xyzzy", TracePoint, :disable, trace, nil, nil], + ] + return events, answer_events + end + + def test_set_trace_func_curry_argument_error + b = lambda {|x, y, z| (x||0) + (y||0) + (z||0) }.curry[1, 2] + set_trace_func(proc {}) + assert_raise(ArgumentError) {b[3, 4]} + end + + def test_set_trace_func + actual_events, expected_events = trace_by_set_trace_func + expected_events.zip(actual_events){|e, a| + a[0] = a[0].to_s.sub('-', '_').to_sym + assert_equal e[0..2], a[0..2], a.inspect + + # event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var") + assert_equal e[3].nil?, a[3].nil? # klass + assert_equal e[4].nil?, a[4].nil? # id + assert_equal e[6], a[6] # _local_var + } + end + + 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 + begin + trace = TracePoint.new(:call){|tp| + next if !target_thread? + ary << tp.method_id + } + foo + trace.enable(target_thread: nil){|*a| + args = a + foo + } + foo + assert_equal([:foo], ary) + assert_equal([], args) + ensure + trace&.disable + end + + trace = TracePoint.new{} + begin + assert_equal(false, trace.enable) + assert_equal(true, trace.enable) + trace.enable(target_thread: nil){} + trace.disable + assert_equal(false, trace.enable) + ensure + trace.disable + end + end + + def test_tracepoint_disable + ary = [] + args = nil + trace = TracePoint.trace(:call){|tp| + next if !target_thread? + # In parallel testing, unexpected events like IO operations may be traced, + # so we filter out events here. + next unless [TracePoint, TestSetTraceFunc].include?(tp.defined_class) + ary << tp.method_id + } + foo + trace.disable{|*a| + args = a + foo + } + foo + trace.disable + assert_equal([:foo, :disable, :foo, :disable], 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 + + def test_tracepoint_attr + c = Class.new do + attr_accessor :x + alias y x + alias y= x= + end + obj = c.new + + ar_meth = obj.method(:x) + aw_meth = obj.method(:x=) + aar_meth = obj.method(:y) + aaw_meth = obj.method(:y=) + events = [] + trace = TracePoint.new(:c_call, :c_return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + next if tp.method_id == :call + case tp.event + when :c_call + assert_raise(RuntimeError) {tp.return_value} + events << [tp.event, tp.method_id, tp.callee_id] + when :c_return + events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] + end + } + test_proc = proc do + obj.x = 1 + obj.x + obj.y = 2 + obj.y + aw_meth.call(1) + ar_meth.call + aaw_meth.call(2) + aar_meth.call + end + test_proc.call # populate call caches + trace.enable(&test_proc) + expected = [ + [:c_call, :x=, :x=], + [:c_return, :x=, :x=, 1], + [:c_call, :x, :x], + [:c_return, :x, :x, 1], + [:c_call, :x=, :y=], + [:c_return, :x=, :y=, 2], + [:c_call, :x, :y], + [:c_return, :x, :y, 2], + ] + assert_equal(expected*2, events) + end + + def test_tracepoint_struct + c = Struct.new(:x) do + alias y x + alias y= x= + end + obj = c.new + + ar_meth = obj.method(:x) + aw_meth = obj.method(:x=) + aar_meth = obj.method(:y) + aaw_meth = obj.method(:y=) + events = [] + trace = TracePoint.new(:c_call, :c_return){|tp| + next if !target_thread? + next if tp.path != __FILE__ + next if tp.method_id == :call + case tp.event + when :c_call + assert_raise(RuntimeError) {tp.return_value} + events << [tp.event, tp.method_id, tp.callee_id] + when :c_return + events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] + end + } + test_proc = proc do + obj.x = 1 + obj.x + obj.y = 2 + obj.y + aw_meth.call(1) + ar_meth.call + aaw_meth.call(2) + aar_meth.call + end + test_proc.call # populate call caches + trace.enable(&test_proc) + expected = [ + [:c_call, :x=, :x=], + [:c_return, :x=, :x=, 1], + [:c_call, :x, :x], + [:c_return, :x, :x, 1], + [:c_call, :x=, :y=], + [:c_return, :x=, :y=, 2], + [:c_call, :x, :y], + [:c_return, :x, :y, 2], + ] + assert_equal(expected*2, events) + end + + class XYZZYException < Exception; end + def method_test_tracepoint_raised_exception err + raise err + end + + def test_tracepoint_raised_exception + trace = TracePoint.new(:call, :return, :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].map!{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + # pp events + # expected_events = + [[:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], + [:c_call, :map!, Array, Array, nil], + [:b_call, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, nil], + [:b_return, :test_tracepoint_block, TestSetTraceFunc, TestSetTraceFunc, 3], + [:c_return, :map!, Array, Array, [3]], + [: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(target_thread: nil){ + 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(:line){ + 10.times{ + Thread.pass + } + }.enable do + (1..10).map{ + Thread.new{ + 1_000.times{|i| + _a = i + } + } + }.each{|th| + th.join + } + end + _a = 1 + _b = 2 + _c = 3 # to make sure the deletion of unused TracePoints + end + end + + 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 + if respond_to?(:callcc) + 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 + end + + # 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_return + } # 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 = [] + log = [] + TracePoint.new(:a_call){|tp| + next if !target_thread? + events << tp.event + log << "| event:#{ tp.event } method_id:#{ tp.method_id } #{ tp.path }:#{ tp.lineno }" + }.enable{ + [1].map!{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + assert_equal([ + :b_call, + :c_call, + :b_call, + :call, + :b_call, + ], events, "TracePoint log:\n#{ log.join("\n") }\n") + end + + def test_a_return + events = [] + log = [] + TracePoint.new(:a_return){|tp| + next if !target_thread? + events << tp.event + log << "| event:#{ tp.event } method_id:#{ tp.method_id } #{ tp.path }:#{ tp.lineno }" + }.enable{ + [1].map!{ + 3 + } + method_for_test_tracepoint_block{ + 4 + } + } + assert_equal([ + :b_return, + :c_return, + :b_return, + :return, + :b_return + ], events, "TracePoint log:\n#{ log.join("\n") }\n") + 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 + evs = [] + + TracePoint.new(:call, :return){|tp| + next unless target_thread? + evs << tp.event + }.enable{ + Bug10724.new + } + + assert_equal([:call, :call, :return, :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 raise into resumable fiber + evs = [] + f = nil + TracePoint.new(:raise, :fiber_switch){|tp| + next unless target_thread? + evs << [tp.event, Fiber.current] + }.enable{ + f = Fiber.new{ + Fiber.yield # will raise + Fiber.yield # unreachable + } + begin + f.resume + f.raise StopIteration + rescue StopIteration + evs << :rescued + end + } + assert_equal [:fiber_switch, f], evs[0], "initial resume" + assert_equal [:fiber_switch, Fiber.current], evs[1], "Fiber.yield" + assert_equal [:fiber_switch, f], evs[2], "fiber.raise" + assert_equal [:raise, f], evs[3], "fiber.raise" + assert_equal [:fiber_switch, Fiber.current], evs[4], "terminated with raise" + assert_equal [:raise, Fiber.current], evs[5], "terminated with raise" + assert_equal :rescued, evs[6] + assert_equal 7, evs.size + + # test for transfer + evs = [] + TracePoint.new(:fiber_switch){|tp| + 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 + } + + # test for raise and from transferring fibers + evs = [] + f1 = f2 = nil + TracePoint.new(:raise, :fiber_switch){|tp| + next unless target_thread? + evs << [tp.event, Fiber.current] + }.enable{ + f1 = Fiber.new{ + f2.transfer + f2.raise ScriptError + Fiber.yield :ok + } + f2 = Fiber.new{ + f1.transfer + f1.transfer + } + begin + f1.resume + rescue ScriptError + evs << :rescued + end + } + assert_equal [:fiber_switch, f1], evs[0], "initial resume" + assert_equal [:fiber_switch, f2], evs[1], "f2.transfer" + assert_equal [:fiber_switch, f1], evs[2], "f1.transfer" + assert_equal [:fiber_switch, f2], evs[3], "f2.raise ScriptError" + assert_equal [:raise, f2], evs[4], "f2.raise ScriptError" + assert_equal [:fiber_switch, f1], evs[5], "f2 unhandled exception" + assert_equal [:raise, f1], evs[6], "f2 unhandled exception" + assert_equal [:fiber_switch, Fiber.current], evs[7], "f1 unhandled exception" + assert_equal [:raise, Fiber.current], evs[8], "f1 unhandled exception" + assert_equal :rescued, evs[9], "rescued everything" + assert_equal 10, evs.size + + end + + def test_tracepoint_callee_id + 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, :tap, :tap], [: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_singleton_class singleton_class + define_method(:m){alias_singleton_class} + }.new + TracePoint.new(:c_return, &capture_events).enable{ + o.m + } + assert_equal [[:c_return, :singleton_class, :alias_singleton_class]], 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[0] + 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? + next if tp.path != __FILE__ + ary << [tp.event, tp.method_id, tp.return_value] + }.enable{ + send mid + } + ary.pop # last b_return event is not required. + ary + end + + def test_single_raise_inside_load + events = [] + tmpdir = Dir.mktmpdir + path = "#{tmpdir}/hola.rb" + File.open(path, "w") { |f| f.write("raise") } + tp = TracePoint.new(:raise) {|tp| events << [tp.event] if target_thread?} + tp.enable{ + load path rescue nil + } + assert_equal [[:raise]], events + events.clear + tp.enable{ + require path rescue nil + } + assert_equal [[:raise]], events + ensure + FileUtils.rmtree(tmpdir) + end + + def f_raise + raise + rescue + 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 + break :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, :f_return_defined], + [:return, :f_return_defined, :f_return_defined]], + tp_return_value(:f_return_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_break_defined, :f_break_defined], + [:return, :f_break_defined, :f_break_defined]], + tp_return_value(:f_break_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_raise_defined, f_raise_defined], + [:return, :f_raise_defined, f_raise_defined]], + tp_return_value(:f_raise_defined), + '[Bug #13369]' + + assert_equal [[:b_return, :f_break_in_rescue_defined, f_break_in_rescue_defined], + [:return, :f_break_in_rescue_defined, f_break_in_rescue_defined]], + tp_return_value(:f_break_in_rescue_defined), + '[Bug #13369]' + end + + define_method(:just_yield) do |&block| + block.call + end + + define_method(:unwind_multiple_bmethods) do + just_yield { return :unwind_multiple_bmethods } + end + + def test_non_local_return_across_multiple_define_methods + assert_equal [[:b_return, :unwind_multiple_bmethods, nil], + [:b_return, :just_yield, nil], + [:return, :just_yield, nil], + [:b_return, :unwind_multiple_bmethods, :unwind_multiple_bmethods], + [:return, :unwind_multiple_bmethods, :unwind_multiple_bmethods]], + tp_return_value(:unwind_multiple_bmethods) + end + + def f_iter + yield + end + + 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 = [] + t = Thread.new{ + Thread.current.add_trace_func proc{|ev, file, line, *args| + events << [ev, line] if file == __FILE__ + } # do not stop trace. They will be stopped at Thread termination. + q.push 1 + _x = 1 + method_for_test_thread_add_trace_func + _y = 2 + } + q.pop + method_for_test_thread_add_trace_func + t.join + assert_equal ["c-return", base_line + 3], events[0] + assert_equal ["line", base_line + 6], events[1] + assert_equal ["c-call", base_line + 6], events[2] + assert_equal ["c-return", base_line + 6], events[3] + assert_equal ["line", base_line + 7], events[4] + assert_equal ["line", base_line + 8], events[5] + assert_equal ["call", base_line + -6], events[6] + assert_equal ["return", base_line + -4], events[7] + assert_equal ["line", base_line + 9], events[8] + assert_equal nil, events[9] + + # other thread + events = [] + m2t_q = Thread::Queue.new + + t = Thread.new{ + Thread.current.abort_on_exception = true + assert_equal 1, m2t_q.pop + _x = 1 + method_for_test_thread_add_trace_func + _y = 2 + Thread.current.set_trace_func(nil) + method_for_test_thread_add_trace_func + } + # it is dirty hack. usually we shouldn't use such technique + Thread.pass until t.status == 'sleep' + + t.add_trace_func proc{|ev, file, line, *args| + if file == __FILE__ + events << [ev, line] + end + } + + method_for_test_thread_add_trace_func + + m2t_q.push 1 + t.join + + assert_equal ["line", base_line + 32], events[0] + assert_equal ["line", base_line + 33], events[1] + assert_equal ["call", base_line + -6], events[2] + assert_equal ["return", base_line + -4], events[3] + assert_equal ["line", base_line + 34], events[4] + assert_equal ["line", base_line + 35], events[5] + assert_equal ["c-call", base_line + 35], events[6] # Thread.current + assert_equal ["c-return", base_line + 35], events[7] # Thread.current + assert_equal ["c-call", base_line + 35], events[8] # Thread#set_trace_func + assert_equal nil, events[9] + end + + def test_lineno_in_optimized_insn + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $loc = nil + class String + undef -@ + def -@ + $loc = caller_locations(1, 1)[0].lineno + end + end + + assert_predicate(-"", :frozen?) + assert_equal(__LINE__-1, $loc, '[Bug #14809]') + end; + end + + def method_for_enable_target1 + a = 1 + b = 2 + 1.times{|i| + _x = i + } + _c = a + b + end + + def method_for_enable_target2 + a = 1 + b = 2 + 1.times{|i| + _x = i + } + _c = a + b + end + + def check_with_events(trace_point_events, expected_events = trace_point_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_point_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.keep_if { |(ev)| expected_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]) + + # No arguments passed into TracePoint.new enables all ISEQ_TRACE_EVENTS + check_with_events([], [:line, :class, :end, :call, :return, :c_call, :c_return, :b_call, :b_return, :rescue]) + + # Raise event should be ignored + check_with_events([:line, :raise]) + end + + def test_tracepoint_nested_enabled_with_target + code1 = proc{ + _a = 1 + } + code2 = proc{ + _b = 2 + } + + ## error + + # targeted TP and targeted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # global TP and targeted TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target_thread: nil){ + tp.enable(target: code2){} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # targeted TP and global TP + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.enable{} + } + end + assert_equal "can't nest-enable a targeting TracePoint", ex.message + + # targeted TP and disable + ex = assert_raise(ArgumentError) do + tp = TracePoint.new(:line){} + tp.enable(target: code1){ + tp.disable{} + } + end + assert_equal "can't disable a targeting TracePoint in a block", ex.message + + ## success with two nesting targeting tracepoints + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp1, :___], events + + # success with two tracepoints (global/targeting) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable do + tp2.enable(target: code1) do + code1.call + events << :___ + end + end + assert_equal [:tp1, :tp1, :tp1, :tp1, :tp2, :tp1, :___], events + + # success with two tracepoints (targeting/global) + events = [] + tp1 = TracePoint.new(:line){|tp| events << :tp1} + tp2 = TracePoint.new(:line){|tp| events << :tp2} + tp1.enable(target: code1) do + tp2.enable do + code1.call + events << :___ + end + end + assert_equal [:tp2, :tp2, :tp1, :tp2, :___], events + end + + def test_tracepoint_enable_with_target_line + events = [] + line_0 = __LINE__ + code1 = proc{ + events << 1 + events << 2 + events << 3 + } + tp = TracePoint.new(:line) do |tp| + events << :tp + end + tp.enable(target: code1, target_line: line_0 + 3) do + code1.call + end + assert_equal [1, :tp, 2, 3], events + + + e = assert_raise(ArgumentError) do + TracePoint.new(:line){}.enable(target_line: 10){} + end + assert_equal 'only target_line is specified', e.message + + e = assert_raise(ArgumentError) do + TracePoint.new(:call){}.enable(target: code1, target_line: 10){} + end + assert_equal 'target_line is specified, but line event is not specified', e.message + end + + def test_tracepoint_enable_with_target_line_two_times + events = [] + line_0 = __LINE__ + code1 = proc{ + events << 1 # tp1 + events << 2 + events << 3 # tp2 + } + + tp1 = TracePoint.new(:line) do |tp| + events << :tp1 + end + tp2 = TracePoint.new(:line) do |tp| + events << :tp2 + end + + tp1.enable(target: code1, target_line: line_0 + 2) do + tp2.enable(target: code1, target_line: line_0 + 4) do + # two hooks + code1.call + end + end + assert_equal [:tp1, 1, 2, :tp2, 3], events + end + + def test_multiple_enable + ary = [] + trace = TracePoint.new(:call) do |tp| + ary << tp.method_id + end + trace.enable + trace.enable + foo + trace.disable + assert_equal(1, ary.count(:foo), '[Bug #19114]') + end + + def test_multiple_tracepoints_same_bmethod + events = [] + tp1 = TracePoint.new(:return) do |tp| + events << :tp1 + end + tp2 = TracePoint.new(:return) do |tp| + events << :tp2 + end + + obj = Object.new + obj.define_singleton_method(:foo) {} + bmethod = obj.method(:foo) + + tp1.enable(target: bmethod) do + tp2.enable(target: bmethod) do + obj.foo + end + end + + assert_equal([:tp2, :tp1], events, '[Bug #18031]') + end + + def test_script_compiled + events = [] + tp = TracePoint.new(:script_compiled){|tp| + next unless target_thread? + events << [tp.instruction_sequence.path, + tp.eval_script] + } + + eval_script = 'a = 1' + tp.enable{ + eval(eval_script, nil, __FILE__+"/eval") + nil.instance_eval(eval_script, __FILE__+"/instance_eval") + Object.class_eval(eval_script, __FILE__+"/class_eval") + } + assert_equal [[__FILE__+"/eval", eval_script], + [__FILE__+"/instance_eval", eval_script], + [__FILE__+"/class_eval", eval_script], + ], events + + events.clear + tp.enable{ + begin + eval('a=') + rescue SyntaxError + end + } + assert_equal [], events, 'script_compiled event should not be invoked on compile error' + + omit "TODO: test for requires" + + events.clear + tp.enable{ + require '' + require_relative '' + load '' + } + assert_equal [], events + end + + def test_enable_target_thread + events = [] + TracePoint.new(:line) do |tp| + events << Thread.current + end.enable(target_thread: Thread.current) do + _a = 1 + Thread.new{ + _b = 2 + _c = 3 + }.join + _d = 4 + end + assert_equal Array.new(3){Thread.current}, events + + events = [] + tp = TracePoint.new(:line) do |tp| + events << Thread.current + end + + q1 = Thread::Queue.new + q2 = Thread::Queue.new + + th = Thread.new{ + q1 << :ok; q2.pop + _t1 = 1 + _t2 = 2 + } + q1.pop + tp.enable(target_thread: th) do + q2 << 1 + _a = 1 + _b = 2 + th.join + end + + assert_equal Array.new(2){th}, events + end + + def test_return_bmethod_location + bug13392 = "[ruby-core:80515] incorrect bmethod return location" + actual = nil + obj = Object.new + expected = __LINE__ + 1 + obj.define_singleton_method(:t){} + tp = TracePoint.new(:return) do + next unless target_thread? + actual = tp.lineno + end + tp.enable {obj.t} + assert_equal(expected, actual, bug13392) + end + + def test_b_tracepoints_going_away + # test that call and return TracePoints continue to work + # when b_call and b_return TracePoints stop + events = [] + record_events = ->(tp) do + next unless target_thread? + events << [tp.event, tp.method_id] + end + + call_ret_tp = TracePoint.new(:call, :return, &record_events) + block_call_ret_tp = TracePoint.new(:b_call, :b_return, &record_events) + + obj = Object.new + obj.define_singleton_method(:foo) {} # a bmethod + + foo = obj.method(:foo) + call_ret_tp.enable(target: foo) do + block_call_ret_tp.enable(target: foo) do + obj.foo + end + obj.foo + end + + assert_equal( + [ + [:call, :foo], + [:b_call, :foo], + [:b_return, :foo], + [:return, :foo], + [:call, :foo], + [:return, :foo], + ], + events, + ) + end + + def test_target_different_bmethod_same_iseq + # make two bmethods that share the same block iseq + block = Proc.new {} + obj = Object.new + obj.define_singleton_method(:one, &block) + obj.define_singleton_method(:two, &block) + + events = [] + record_events = ->(tp) do + next unless target_thread? + events << [tp.event, tp.method_id] + end + tp_one = TracePoint.new(:call, :return, &record_events) + tp_two = TracePoint.new(:call, :return, &record_events) + + tp_one.enable(target: obj.method(:one)) do + obj.one + obj.two # not targeted + end + assert_equal([[:call, :one], [:return, :one]], events) + events.clear + + tp_one.enable(target: obj.method(:one)) do + obj.one + tp_two.enable(target: obj.method(:two)) do + obj.two + end + obj.two + obj.one + end + assert_equal( + [ + [:call, :one], + [:return, :one], + [:call, :two], + [:return, :two], + [:call, :one], + [:return, :one], + ], + events + ) + end + + def test_return_event_with_rescue + obj = Object.new + def obj.example + 1 if 1 == 1 + rescue + end + ok = false + tp = TracePoint.new(:return) {ok = true} + tp.enable {obj.example} + assert ok, "return event should be emitted" + end + + def test_disable_local_tracepoint_in_trace + assert_normal_exit(<<-EOS, timeout: 60) + def foo + trace = TracePoint.new(:b_return){|tp| + tp.disable + } + trace.enable(target: method(:bar)) + end + def bar + 100.times{|i| + foo; foo + } + end + bar + EOS + + assert_normal_exit(<<-EOS, 'Bug #18730') + def bar + 42 + end + tp_line = TracePoint.new(:line) do |tp0| + tp_multi1 = TracePoint.new(:return, :b_return, :line) do |tp| + tp0.disable + end + tp_multi1.enable + end + tp_line.enable(target: method(:bar)) + bar + EOS + end + + def test_stat_exists + assert_instance_of Hash, TracePoint.stat + end + + def test_tracepoint_opt_invokebuiltin_delegate_leave + code = 'puts RubyVM::InstructionSequence.of("\x00".method(:unpack)).disasm' + out = EnvUtil.invoke_ruby(['-e', code], '', true).first + assert_match(/^0000 opt_invokebuiltin_delegate_leave /, out) + + event = eval(EnvUtil.invoke_ruby(['-e', <<~'EOS'], '', true).first) + TracePoint.new(:return) do |tp| + p [tp.event, tp.method_id] + end.enable do + "\x00".unpack("c") + end + EOS + assert_equal [:return, :unpack], event + end + + def test_while_in_while + lines = [] + + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + n = 3 + while n > 0 + n -= 1 while n > 0 + end + } + assert_equal [__LINE__ - 5, __LINE__ - 4, __LINE__ - 3], lines, 'Bug #17868' + end + + def test_allow_reentry + event_lines = [] + _l1 = _l2 = _l3 = _l4 = nil + TracePoint.new(:line) do |tp| + next unless target_thread? + + event_lines << tp.lineno + next if (__LINE__ + 2 .. __LINE__ + 4).cover?(tp.lineno) + TracePoint.allow_reentry do + _a = 1; _l3 = __LINE__ + _b = 2; _l4 = __LINE__ + end + end.enable do + _c = 3; _l1 = __LINE__ + _d = 4; _l2 = __LINE__ + end + + assert_equal [_l1, _l3, _l4, _l2, _l3, _l4], event_lines + + assert_raise RuntimeError do + TracePoint.allow_reentry{} + end + end + + def test_raising_from_b_return_tp_tracing_bmethod + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.trace(:b_return) do |tp| + raise + end + + Foo.foo + RUBY + + # Same thing but with a target + assert_normal_exit(<<~RUBY, '[Bug #18060]', timeout: 3) + class Foo + define_singleton_method(:foo) { return } # a bmethod + end + + TracePoint.new(:b_return) do |tp| + raise + end.enable(target: Foo.method(:foo)) + + Foo.foo + RUBY + end + + def helper_cant_rescue + begin + raise SyntaxError + rescue + cant_rescue + end + end + + def test_tp_rescue + lines = [] + TracePoint.new(:line){|tp| + next unless target_thread? + lines << tp.lineno + }.enable{ + begin + helper_cant_rescue + rescue SyntaxError + end + } + _call_line = lines.shift + _raise_line = lines.shift + assert_equal [], lines + end + + def helper_can_rescue + begin + raise __LINE__.to_s + rescue SyntaxError + :ng + rescue + :ok + end + end + + def helper_can_rescue_empty_body + begin + raise __LINE__.to_s + rescue SyntaxError + :ng + rescue + end + end + + def test_tp_rescue_event + lines = [] + TracePoint.new(:rescue){|tp| + next unless target_thread? + lines << [tp.lineno, tp.raised_exception] + }.enable{ + helper_can_rescue + } + + line, err, = lines.pop + assert_equal [], lines + assert err.kind_of?(RuntimeError) + assert_equal err.message.to_i + 4, line + + lines = [] + TracePoint.new(:rescue){|tp| + next unless target_thread? + lines << [tp.lineno, tp.raised_exception] + }.enable{ + helper_can_rescue_empty_body + } + + line, err, = lines.pop + assert_equal [], lines + assert err.kind_of?(RuntimeError) + assert_equal err.message.to_i + 3, line + end + + def test_tracepoint_thread_begin + target_thread = nil + + trace = TracePoint.new(:thread_begin) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + Thread.new{}.join + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_thread_end_with_exception + target_thread = nil + + trace = TracePoint.new(:thread_end) do |tp| + target_thread = tp.self + end + + trace.enable(target_thread: nil) do + thread = Thread.new do + Thread.current.report_on_exception = false + raise + end + + # Ignore the exception raised by the thread: + thread.join rescue nil + end + + assert_kind_of(Thread, target_thread) + end + + def test_tracepoint_garbage_collected_when_disable + before_count_stat = 0 + before_count_objspace = 0 + TracePoint.stat.each do + before_count_stat += 1 + end + ObjectSpace.each_object(TracePoint) do + before_count_objspace += 1 + end + tp = TracePoint.new(:c_call, :c_return) do + end + tp.enable + Class.inspect # c_call, c_return invoked + tp.disable + tp_id = tp.object_id + tp = nil + + gc_times = 0 + gc_max_retries = 10 + EnvUtil.suppress_warning do + until (ObjectSpace._id2ref(tp_id) rescue nil).nil? + GC.start + gc_times += 1 + if gc_times == gc_max_retries + break + end + end + end + return if gc_times == gc_max_retries + + after_count_stat = 0 + TracePoint.stat.each do |v| + after_count_stat += 1 + end + assert after_count_stat <= before_count_stat + after_count_objspace = 0 + ObjectSpace.each_object(TracePoint) do + after_count_objspace += 1 + end + assert after_count_objspace <= before_count_objspace + end + + def test_tp_ractor_local_untargeted + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + r = Ractor.new do + results = [] + tp = TracePoint.new(:line) { |tp| results << tp.path } + tp.enable + Ractor.main << :continue + Ractor.receive + tp.disable + results + end + outer_results = [] + outer_tp = TracePoint.new(:line) { |tp| outer_results << tp.path } + outer_tp.enable + Ractor.receive + GC.start # so I can check <internal:gc> path + r << :continue + inner_results = r.value + outer_tp.disable + assert_equal 1, outer_results.select { |path| path.match?(/internal:gc/) }.size + assert_equal 0, inner_results.select { |path| path.match?(/internal:gc/) }.size + end; + end + + def test_tp_targeted_ractor_local_bmethod + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + mname = :foo + prok = Ractor.shareable_proc do + end + klass = EnvUtil.labeled_class(:Klass) do + define_method(mname, &prok) + end + outer_results = 0 + _outer_tp = TracePoint.new(:call) do + outer_results += 1 + end # not enabled + rs = 10.times.map do + Ractor.new(mname, klass) do |mname, klass0| + inner_results = 0 + tp = TracePoint.new(:call) { |tp| inner_results += 1 } + target = klass0.instance_method(mname) + tp.enable(target: target) + obj = klass0.new + 10.times { obj.send(mname) } + tp.disable + inner_results + end + end + inner_results = rs.map(&:value).sum + obj = klass.new + 10.times { obj.send(mname) } + assert_equal 100, inner_results + assert_equal 0, outer_results + end; + end + + def test_tp_targeted_ractor_local_method + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def foo + end + outer_results = 0 + _outer_tp = TracePoint.new(:call) do + outer_results += 1 + end # not enabled + + rs = 10.times.map do + Ractor.new do + inner_results = 0 + tp = TracePoint.new(:call) do + inner_results += 1 + end + tp.enable(target: method(:foo)) + 10.times { foo } + tp.disable + inner_results + end + end + + inner_results = rs.map(&:value).sum + 10.times { foo } + assert_equal 100, inner_results + assert_equal 0, outer_results + end; + end + + def test_tracepoints_not_disabled_by_ractor_gc + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $-w = nil # uses ObjectSpace._id2ref + def hi = "hi" + greetings = 0 + tp_target = TracePoint.new(:call) do |tp| + greetings += 1 + end + tp_target.enable(target: method(:hi)) + + raises = 0 + tp_global = TracePoint.new(:raise) do |tp| + raises += 1 + end + tp_global.enable + + r = Ractor.new { 10 } + r.join + ractor_id = r.object_id + r = nil # allow gc for ractor + gc_max_retries = 15 + gc_times = 0 + # force GC of ractor (or try, because we have a conservative GC) + until (ObjectSpace._id2ref(ractor_id) rescue nil).nil? + GC.start + gc_times += 1 + if gc_times == gc_max_retries + break + end + end + + # tracepoints should still be enabled after GC of `r` + 5.times { + hi + } + 6.times { + raise "uh oh" rescue nil + } + tp_target.disable + tp_global.disable + assert_equal 5, greetings + if gc_times == gc_max_retries # _id2ref never raised + assert_equal 6, raises + else + assert_equal 7, raises + end + end; + end + + def test_lots_of_enabled_tracepoints_ractor_gc + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + def foo; end + sum = 8.times.map do + Ractor.new do + called = 0 + TracePoint.new(:call) do |tp| + next if tp.callee_id != :foo + called += 1 + end.enable + 200.times do + TracePoint.new(:line) { + # all these allocations shouldn't GC these tracepoints while the ractor is alive. + Object.new + }.enable + end + 100.times { foo } + called + end + end.map(&:value).sum + assert_equal 800, sum + 4.times { GC.start } # Now the tracepoints can be GC'd because the ractors can be GC'd + end; + end +end diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb new file mode 100644 index 0000000000..67e2c543a3 --- /dev/null +++ b/test/ruby/test_shapes.rb @@ -0,0 +1,1212 @@ +# frozen_string_literal: false +require 'test/unit' +require 'objspace' +require 'json' +require 'securerandom' + +# These test the functionality of object shapes +class TestShapes < Test::Unit::TestCase + MANY_IVS = 80 + + class IVOrder + def expected_ivs + %w{ @a @b @c @d @e @f @g @h @i @j @k } + end + + def set_ivs + expected_ivs.each { instance_variable_set(_1, 1) } + self + end + end + + class ShapeOrder + def initialize + @b = :b # 5 => 6 + end + + def set_b + @b = :b # 5 => 6 + end + + def set_c + @c = :c # 5 => 7 + end + end + + class OrderedAlloc + def add_ivars + 10.times do |i| + instance_variable_set("@foo" + i.to_s, 0) + end + end + end + + class Example + def initialize + @a = 1 + end + end + + class RemoveAndAdd + def add_foo + @foo = 1 + end + + def remove_foo + remove_instance_variable(:@foo) + end + + def add_bar + @bar = 1 + end + end + + class TooComplex + attr_reader :hopefully_unique_name, :b + + def initialize + @hopefully_unique_name = "a" + @b = "b" + end + + # Make enough lazily defined accessors to allow us to force + # polymorphism + class_eval (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map { + "def a#{_1}_m; @a#{_1} ||= #{_1}; end" + }.join(" ; ") + + class_eval "attr_accessor " + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times.map { + ":a#{_1}" + }.join(", ") + + def iv_not_defined; @not_defined; end + + def write_iv_method + self.a3 = 12345 + end + + def write_iv + @a3 = 12345 + end + end + + # RubyVM::Shape.of returns new instances of shape objects for + # each call. This helper method allows us to define equality for + # shapes + def assert_shape_equal(e, a) + assert_equal( + {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) + end + + def refute_shape_equal(e, a) + refute_equal( + {id: e.id, parent_id: e.parent_id, depth: e.depth, type: e.type}, + {id: a.id, parent_id: a.parent_id, depth: a.depth, type: a.type}, + ) + end + + def test_iv_order_correct_on_complex_objects + (RubyVM::Shape::SHAPE_MAX_VARIATIONS + 1).times { + IVOrder.new.instance_variable_set("@a#{_1}", 1) + } + + obj = IVOrder.new + iv_list = obj.set_ivs.instance_variables + assert_equal obj.expected_ivs, iv_list.map(&:to_s) + end + + def test_too_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + end + + def test_ordered_alloc_is_not_complex + 5.times { OrderedAlloc.new.add_ivars } + obj = JSON.parse(ObjectSpace.dump(OrderedAlloc)) + assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS + end + + def test_too_many_ivs_on_obj + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + RubyVM::Shape.exhaust_shapes(2) + + obj = Hi.new + obj.instance_variable_set(:@b, 1) + obj.instance_variable_set(:@c, 1) + obj.instance_variable_set(:@d, 1) + + assert_predicate RubyVM::Shape.of(obj), :too_complex? + end; + end + + def test_too_many_ivs_on_class + obj = Class.new + + obj.instance_variable_set(:@test_too_many_ivs_on_class, 1) + refute_predicate RubyVM::Shape.of(obj), :too_complex? + + MANY_IVS.times do + obj.instance_variable_set(:"@a#{_1}", 1) + end + + refute_predicate RubyVM::Shape.of(obj), :too_complex? + end + + def test_removing_when_too_many_ivs_on_class + obj = Class.new + + (MANY_IVS + 2).times do + obj.instance_variable_set(:"@a#{_1}", 1) + end + (MANY_IVS + 2).times do + obj.remove_instance_variable(:"@a#{_1}") + end + + assert_empty obj.instance_variables + end + + def test_removing_when_too_many_ivs_on_module + obj = Module.new + + (MANY_IVS + 2).times do + obj.instance_variable_set(:"@a#{_1}", 1) + end + (MANY_IVS + 2).times do + obj.remove_instance_variable(:"@a#{_1}") + end + + assert_empty obj.instance_variables + end + + def test_too_complex_geniv + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class TooComplex < Hash + attr_reader :very_unique + end + + RubyVM::Shape.exhaust_shapes + + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", 1) + end + + tc = TooComplex.new + tc.instance_variable_set(:@very_unique, 3) + tc.instance_variable_set(:@very_unique2, 4) + assert_equal 3, tc.instance_variable_get(:@very_unique) + assert_equal 4, tc.instance_variable_get(:@very_unique2) + + assert_equal [:@very_unique, :@very_unique2], tc.instance_variables + end; + end + + def test_use_all_shapes_then_freeze + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + RubyVM::Shape.exhaust_shapes(3) + + obj = Hi.new + i = 0 + while RubyVM::Shape.shapes_available > 0 + obj.instance_variable_set(:"@b#{i}", 1) + i += 1 + end + obj.freeze + + assert obj.frozen? + end; + end + + def test_run_out_of_shape_for_object + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + def initialize + @a = 1 + end + end + RubyVM::Shape.exhaust_shapes + + A.new + end; + end + + def test_run_out_of_shape_for_class_ivar + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + RubyVM::Shape.exhaust_shapes + + c = Class.new + c.instance_variable_set(:@a, 1) + assert_equal(1, c.instance_variable_get(:@a)) + + c.remove_instance_variable(:@a) + assert_nil(c.instance_variable_get(:@a)) + + assert_raise(NameError) do + c.remove_instance_variable(:@a) + end + end; + end + + def test_evacuate_class_ivar_and_compaction + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + count = 20 + + c = Class.new + count.times do |ivar| + c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}") + end + + RubyVM::Shape.exhaust_shapes + + GC.auto_compact = true + GC.stress = true + # Cause evacuation + c.instance_variable_set(:@a, o = Object.new) + assert_equal(o, c.instance_variable_get(:@a)) + GC.stress = false + + count.times do |ivar| + assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}") + end + end; + end + + def test_evacuate_generic_ivar_and_compaction + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + count = 20 + + c = Hash.new + count.times do |ivar| + c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}") + end + + RubyVM::Shape.exhaust_shapes + + GC.auto_compact = true + GC.stress = true + + # Cause evacuation + c.instance_variable_set(:@a, o = Object.new) + assert_equal(o, c.instance_variable_get(:@a)) + + GC.stress = false + + count.times do |ivar| + assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}") + end + end; + end + + def test_evacuate_object_ivar_and_compaction + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + count = 20 + + c = Object.new + count.times do |ivar| + c.instance_variable_set("@i#{ivar}", "ivar-#{ivar}") + end + + RubyVM::Shape.exhaust_shapes + + GC.auto_compact = true + GC.stress = true + + # Cause evacuation + c.instance_variable_set(:@a, o = Object.new) + assert_equal(o, c.instance_variable_get(:@a)) + + GC.stress = false + + count.times do |ivar| + assert_equal "ivar-#{ivar}", c.instance_variable_get("@i#{ivar}") + end + end; + end + + def test_gc_stress_during_evacuate_generic_ivar + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + [].instance_variable_set(:@a, 1) + + RubyVM::Shape.exhaust_shapes + + ary = 10.times.map { [] } + + GC.stress = true + ary.each do |o| + o.instance_variable_set(:@a, 1) + o.instance_variable_set(:@b, 1) + end + end; + end + + def test_run_out_of_shape_for_module_ivar + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + RubyVM::Shape.exhaust_shapes + + module Foo + @a = 1 + @b = 2 + assert_equal 1, @a + assert_equal 2, @b + end + end; + end + + def test_run_out_of_shape_for_class_cvar + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + RubyVM::Shape.exhaust_shapes + + c = Class.new + + c.class_variable_set(:@@a, 1) + assert_equal(1, c.class_variable_get(:@@a)) + + c.class_eval { remove_class_variable(:@@a) } + assert_false(c.class_variable_defined?(:@@a)) + + assert_raise(NameError) do + c.class_eval { remove_class_variable(:@@a) } + end + end; + end + + def test_run_out_of_shape_generic_instance_variable_set + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class TooComplex < Hash + end + + RubyVM::Shape.exhaust_shapes + + tc = TooComplex.new + tc.instance_variable_set(:@a, 1) + tc.instance_variable_set(:@b, 2) + + tc.remove_instance_variable(:@a) + assert_nil(tc.instance_variable_get(:@a)) + + assert_raise(NameError) do + tc.remove_instance_variable(:@a) + end + end; + end + + def test_run_out_of_shape_generic_ivar_set + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi < String + def initialize + 8.times do |i| + instance_variable_set("@ivar_#{i}", i) + end + end + + def transition + @hi_transition ||= 1 + end + end + + a = Hi.new + + # Try to run out of shapes + RubyVM::Shape.exhaust_shapes + + assert_equal 1, a.transition + assert_equal 1, a.transition + end; + end + + def test_run_out_of_shape_instance_variable_defined + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + attr_reader :a, :b, :c, :d + def initialize + @a = @b = @c = @d = 1 + end + end + + RubyVM::Shape.exhaust_shapes + + a = A.new + assert_equal true, a.instance_variable_defined?(:@a) + end; + end + + def test_run_out_of_shape_instance_variable_defined_on_module + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + RubyVM::Shape.exhaust_shapes + + module A + @a = @b = @c = @d = 1 + end + + assert_equal true, A.instance_variable_defined?(:@a) + end; + end + + def test_run_out_of_shape_during_remove_instance_variable + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + o = Object.new + 10.times { |i| o.instance_variable_set(:"@a#{i}", i) } + + RubyVM::Shape.exhaust_shapes + + o.remove_instance_variable(:@a0) + (1...10).each do |i| + assert_equal(i, o.instance_variable_get(:"@a#{i}")) + end + end; + end + + def test_run_out_of_shape_remove_instance_variable + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + attr_reader :a, :b, :c, :d + def initialize + @a = @b = @c = @d = 1 + end + end + + a = A.new + + RubyVM::Shape.exhaust_shapes + + a.remove_instance_variable(:@b) + assert_nil a.b + + a.remove_instance_variable(:@a) + assert_nil a.a + + a.remove_instance_variable(:@c) + assert_nil a.c + + assert_equal 1, a.d + end; + end + + def test_run_out_of_shape_rb_obj_copy_ivar + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class A + def initialize + init # Avoid right sizing + end + + def init + @a = @b = @c = @d = @e = @f = 1 + end + end + + a = A.new + + RubyVM::Shape.exhaust_shapes + + a.dup + end; + end + + def test_evacuate_generic_ivar_memory_leak + assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", rss: true) + o = [] + o.instance_variable_set(:@a, 1) + + RubyVM::Shape.exhaust_shapes + + ary = 1_000_000.times.map { [] } + begin; + ary.each do |o| + o.instance_variable_set(:@a, 1) + o.instance_variable_set(:@b, 1) + end + ary.clear + ary = nil + GC.start + end; + end + + def test_use_all_shapes_module + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + RubyVM::Shape.exhaust_shapes(2) + + obj = Module.new + 3.times do + obj.instance_variable_set(:"@a#{_1}", _1) + end + + ivs = 3.times.map do + obj.instance_variable_get(:"@a#{_1}") + end + + assert_equal [0, 1, 2], ivs + end; + end + + def test_complex_freeze_after_clone + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + + RubyVM::Shape.exhaust_shapes(2) + + obj = Object.new + i = 0 + while RubyVM::Shape.shapes_available > 0 + obj.instance_variable_set(:"@b#{i}", i) + i += 1 + end + + v = obj.clone(freeze: true) + assert_predicate v, :frozen? + assert_equal 0, v.instance_variable_get(:@b0) + end; + end + + def test_too_complex_ractor + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.new(tc) { |x| x.very_unique }.value + assert_equal tc.instance_variables.sort, Ractor.new(tc) { |x| x.instance_variables }.value.sort + end; + end + + def test_too_complex_ractor_shareable + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_too_complex_and_frozen + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + shape = RubyVM::Shape.of(tc) + assert_predicate shape, :too_complex? + refute_predicate shape, :shape_frozen? + tc.freeze + frozen_shape = RubyVM::Shape.of(tc) + refute_equal shape.id, frozen_shape.id + assert_predicate frozen_shape, :too_complex? + assert_predicate frozen_shape, :shape_frozen? + + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_object_id_transition_too_complex + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + obj = Object.new + obj.instance_variable_set(:@a, 1) + RubyVM::Shape.exhaust_shapes + assert_equal obj.object_id, obj.object_id + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class Hi; end + obj = Hi.new + obj.instance_variable_set(:@a, 1) + obj.instance_variable_set(:@b, 2) + old_id = obj.object_id + + RubyVM::Shape.exhaust_shapes + obj.remove_instance_variable(:@a) + + assert_equal old_id, obj.object_id + end; + end + + def test_too_complex_and_frozen_and_object_id + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + class TooComplex + attr_reader :very_unique + end + + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + TooComplex.new.instance_variable_set(:"@unique_#{_1}", Object.new) + end + + tc = TooComplex.new + tc.instance_variable_set(:"@very_unique", 3) + + shape = RubyVM::Shape.of(tc) + assert_predicate shape, :too_complex? + refute_predicate shape, :shape_frozen? + tc.freeze + frozen_shape = RubyVM::Shape.of(tc) + refute_equal shape.id, frozen_shape.id + assert_predicate frozen_shape, :too_complex? + assert_predicate frozen_shape, :shape_frozen? + refute_predicate frozen_shape, :has_object_id? + + assert_equal tc.object_id, tc.object_id + + id_shape = RubyVM::Shape.of(tc) + refute_equal frozen_shape.id, id_shape.id + assert_predicate id_shape, :too_complex? + assert_predicate id_shape, :has_object_id? + assert_predicate id_shape, :shape_frozen? + + assert_equal 3, tc.very_unique + assert_equal 3, Ractor.make_shareable(tc).very_unique + end; + end + + def test_too_complex_obj_ivar_ractor_share + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + + RubyVM::Shape.exhaust_shapes + + r = Ractor.new do + o = Object.new + o.instance_variable_set(:@a, "hello") + o + end + + o = r.value + assert_equal "hello", o.instance_variable_get(:@a) + end; + end + + def test_too_complex_generic_ivar_ractor_share + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $VERBOSE = nil + + RubyVM::Shape.exhaust_shapes + + r = Ractor.new do + o = [] + o.instance_variable_set(:@a, "hello") + o + end + + o = r.value + assert_equal "hello", o.instance_variable_get(:@a) + end; + end + + def test_read_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + end + + def test_read_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + assert_equal 3, tc.a3 + end + + def test_write_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.write_iv_method + tc.write_iv_method + assert_equal 12345, tc.a3_m + assert_equal 12345, tc.a3 + end + + def test_write_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.write_iv + tc.write_iv + assert_equal 12345, tc.a3_m + assert_equal 12345, tc.a3 + end + + def test_iv_read_via_method_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal 3, tc.a3_m + assert_equal 3, tc.instance_variable_get(:@a3) + end + + def test_delete_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.remove_instance_variable(:@a3) + refute tc.instance_variable_defined?(:@a3) + assert_nil tc.a3 + end + + def test_delete_iv_after_complex_and_object_id + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.object_id + tc.remove_instance_variable(:@a3) + refute tc.instance_variable_defined?(:@a3) + assert_nil tc.a3 + end + + def test_delete_iv_after_complex_and_freeze + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + assert_equal 3, tc.a3_m # make sure IV is initialized + assert tc.instance_variable_defined?(:@a3) + tc.freeze + assert_raise FrozenError do + tc.remove_instance_variable(:@a3) + end + assert tc.instance_variable_defined?(:@a3) + assert_equal 3, tc.a3 + end + + def test_delete_undefined_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + + refute tc.instance_variable_defined?(:@a3) + assert_raise(NameError) do + tc.remove_instance_variable(:@a3) + end + assert_nil tc.a3 + end + + def test_remove_instance_variable + ivars_count = 5 + object = Object.new + ivars_count.times do |i| + object.instance_variable_set("@ivar_#{i}", i) + end + + ivars = ivars_count.times.map do |i| + object.instance_variable_get("@ivar_#{i}") + end + assert_equal [0, 1, 2, 3, 4], ivars + + object.remove_instance_variable(:@ivar_2) + + ivars = ivars_count.times.map do |i| + object.instance_variable_get("@ivar_#{i}") + end + assert_equal [0, 1, nil, 3, 4], ivars + end + + def test_remove_instance_variable_when_out_of_shapes + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + ivars_count = 5 + object = Object.new + ivars_count.times do |i| + object.instance_variable_set("@ivar_#{i}", i) + end + + ivars = ivars_count.times.map do |i| + object.instance_variable_get("@ivar_#{i}") + end + assert_equal [0, 1, 2, 3, 4], ivars + + RubyVM::Shape.exhaust_shapes + + object.remove_instance_variable(:@ivar_2) + + ivars = ivars_count.times.map do |i| + object.instance_variable_get("@ivar_#{i}") + end + assert_equal [0, 1, nil, 3, 4], ivars + end; + end + + def test_remove_instance_variable_capacity_transition + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + + # a does not transition in capacity + a = Class.new.new + root_shape = RubyVM::Shape.of(a) + + assert_equal(RubyVM::Shape::SHAPE_ROOT, root_shape.type) + initial_capacity = root_shape.capacity + refute_equal(0, initial_capacity) + + initial_capacity.times do |i| + a.instance_variable_set(:"@ivar#{i + 1}", i) + end + + # b transitions in capacity + b = Class.new.new + (initial_capacity + 1).times do |i| + b.instance_variable_set(:"@ivar#{i}", i) + end + + assert_operator(RubyVM::Shape.of(a).capacity, :<, RubyVM::Shape.of(b).capacity) + + # b will now have the same tree as a + b.remove_instance_variable(:@ivar0) + + a.instance_variable_set(:@foo, 1) + a.instance_variable_set(:@bar, 1) + + # Check that there is no heap corruption + GC.verify_internal_consistency + end; + end + + def test_freeze_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + tc.freeze + assert_raise(FrozenError) { tc.a3_m } + # doesn't transition to frozen shape in this case + assert_predicate RubyVM::Shape.of(tc), :too_complex? + end + + def test_read_undefined_iv_after_complex + ensure_complex + + tc = TooComplex.new + tc.send("a#{RubyVM::Shape::SHAPE_MAX_VARIATIONS}_m") + assert_predicate RubyVM::Shape.of(tc), :too_complex? + assert_equal nil, tc.iv_not_defined + assert_predicate RubyVM::Shape.of(tc), :too_complex? + end + + def test_shape_order + bar = ShapeOrder.new # 0 => 1 + bar.set_c # 1 => 2 + bar.set_b # 2 => 2 + + foo = ShapeOrder.new # 0 => 1 + shape_id = RubyVM::Shape.of(foo).id + foo.set_b # should not transition + assert_equal shape_id, RubyVM::Shape.of(foo).id + end + + def test_iv_index + example = RemoveAndAdd.new + initial_shape = RubyVM::Shape.of(example) + assert_equal 0, initial_shape.next_field_index + + example.add_foo # makes a transition + add_foo_shape = RubyVM::Shape.of(example) + assert_equal([:@foo], example.instance_variables) + assert_equal(initial_shape.raw_id, add_foo_shape.parent.raw_id) + assert_equal(1, add_foo_shape.next_field_index) + + example.remove_foo # makes a transition + remove_foo_shape = RubyVM::Shape.of(example) + assert_equal([], example.instance_variables) + assert_shape_equal(initial_shape, remove_foo_shape) + + example.add_bar # makes a transition + bar_shape = RubyVM::Shape.of(example) + assert_equal([:@bar], example.instance_variables) + assert_equal(initial_shape.raw_id, bar_shape.parent_id) + assert_equal(1, bar_shape.next_field_index) + end + + def test_remove_then_add_again + example = RemoveAndAdd.new + _initial_shape = RubyVM::Shape.of(example) + + example.add_foo # makes a transition + add_foo_shape = RubyVM::Shape.of(example) + example.remove_foo # makes a transition + example.add_foo # makes a transition + assert_shape_equal(add_foo_shape, RubyVM::Shape.of(example)) + end + + class TestObject; end + + def test_new_obj_has_t_object_shape + obj = TestObject.new + shape = RubyVM::Shape.of(obj) + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type + assert_nil shape.parent + end + + def test_str_has_root_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of("")) + end + + def test_array_has_root_shape + assert_shape_equal(RubyVM::Shape.root_shape, RubyVM::Shape.of([])) + end + + def test_raise_on_special_consts + assert_raise ArgumentError do + RubyVM::Shape.of(true) + end + assert_raise ArgumentError do + RubyVM::Shape.of(false) + end + assert_raise ArgumentError do + RubyVM::Shape.of(nil) + end + assert_raise ArgumentError do + RubyVM::Shape.of(0) + end + # 32-bit platforms don't have flonums or static symbols as special + # constants + # TODO(max): Add ArgumentError tests for symbol and flonum, skipping if + # RUBY_PLATFORM =~ /i686/ + end + + def test_root_shape_frozen + frozen_root_shape = RubyVM::Shape.of([].freeze) + assert_predicate(frozen_root_shape, :frozen?) + assert_equal(RubyVM::Shape.root_shape.id, frozen_root_shape.raw_id) + end + + def test_basic_shape_transition + obj = Example.new + shape = RubyVM::Shape.of(obj) + refute_equal(RubyVM::Shape.root_shape, shape) + assert_equal :@a, shape.edge_name + assert_equal RubyVM::Shape::SHAPE_IVAR, shape.type + + shape = shape.parent + assert_equal RubyVM::Shape::SHAPE_ROOT, shape.type + assert_nil shape.parent + + assert_equal(1, obj.instance_variable_get(:@a)) + end + + def test_different_objects_make_same_transition + obj = [] + obj2 = "" + obj.instance_variable_set(:@a, 1) + obj2.instance_variable_set(:@a, 1) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_duplicating_objects + obj = Example.new + obj2 = obj.dup + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_duplicating_too_complex_objects_memory_leak + assert_no_memory_leak([], "#{<<~'begin;'}", "#{<<~'end;'}", "[Bug #20162]", rss: true) + RubyVM::Shape.exhaust_shapes + + o = Object.new + o.instance_variable_set(:@a, 0) + begin; + 1_000_000.times do + o.dup + end + end; + end + + def test_freezing_and_duplicating_object + obj = Object.new.freeze + assert_predicate(RubyVM::Shape.of(obj), :shape_frozen?) + + # dup'd objects shouldn't be frozen + obj2 = obj.dup + refute_predicate(obj2, :frozen?) + refute_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) + end + + def test_freezing_and_duplicating_object_with_ivars + obj = Example.new.freeze + obj2 = obj.dup + refute_predicate(obj2, :frozen?) + refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_equal(obj2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_duplicating_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + str2 = str.dup + refute_predicate(str2, :frozen?) + + refute_equal(RubyVM::Shape.of(str).id, RubyVM::Shape.of(str2).id) + assert_equal(str2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_cloning_objects + obj = Object.new.freeze + obj2 = obj.clone(freeze: true) + assert_predicate(obj2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + end + + def test_cloning_with_freeze_option + obj = Object.new + obj2 = obj.clone(freeze: true) + assert_predicate(obj2, :frozen?) + refute_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_predicate(RubyVM::Shape.of(obj2), :shape_frozen?) + end + + def test_freezing_and_cloning_object_with_ivars + obj = Example.new.freeze + obj2 = obj.clone(freeze: true) + assert_predicate(obj2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(obj), RubyVM::Shape.of(obj2)) + assert_equal(obj2.instance_variable_get(:@a), 1) + end + + def test_freezing_and_cloning_string + str = ("str" + "str").freeze + str2 = str.clone(freeze: true) + assert_predicate(str2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) + end + + def test_freezing_and_cloning_string_with_ivars + str = "str" + str.instance_variable_set(:@a, 1) + str.freeze + str2 = str.clone(freeze: true) + assert_predicate(str2, :frozen?) + assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) + assert_equal(str2.instance_variable_get(:@a), 1) + end + + def test_out_of_bounds_shape + assert_raise ArgumentError do + RubyVM::Shape.find_by_id(RubyVM.stat[:next_shape_id]) + end + assert_raise ArgumentError do + RubyVM::Shape.find_by_id(-1) + end + end + + def ensure_complex + RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do + tc = TooComplex.new + tc.send("a#{_1}_m") + end + end + + def assert_too_complex_during_delete(obj) + obj.instance_variable_set("@___#{SecureRandom.hex}", 1) + + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i| + obj.instance_variable_set("@ivar#{i}", i) + end + + refute_predicate RubyVM::Shape.of(obj), :too_complex? + (RubyVM::Shape::SHAPE_MAX_VARIATIONS * 2).times do |i| + obj.remove_instance_variable("@ivar#{i}") + end + assert_predicate RubyVM::Shape.of(obj), :too_complex? + end + + def test_object_too_complex_during_delete + assert_too_complex_during_delete(Class.new.new) + end + + def test_class_too_complex_during_delete + assert_too_complex_during_delete(Module.new) + end + + def test_generic_too_complex_during_delete + assert_too_complex_during_delete(Class.new(Array).new) + end +end if defined?(RubyVM::Shape) diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb new file mode 100644 index 0000000000..091a66d5da --- /dev/null +++ b/test/ruby/test_signal.rb @@ -0,0 +1,353 @@ +# frozen_string_literal: false +require 'test/unit' +require 'timeout' +require 'tempfile' + +class TestSignal < Test::Unit::TestCase + def test_signal + begin + x = 0 + oldtrap = Signal.trap(:INT) {|sig| x = 2 } + Process.kill :INT, Process.pid + 10.times do + break if 2 == x + sleep 0.1 + end + assert_equal 2, x + + Signal.trap(:INT) { raise "Interrupt" } + assert_raise_with_message(RuntimeError, /Interrupt/) { + Process.kill :INT, Process.pid + sleep 0.1 + } + ensure + Signal.trap :INT, oldtrap if oldtrap + 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 + 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) } + STDIN.sysread(4096) + End + pid = io.pid + io.sysread(1) + sleep 0.1 + assert_nothing_raised("[ruby-dev:26128]") { + Process.kill(term, pid) + begin + Timeout.timeout(3) { + Process.waitpid pid + } + rescue Timeout::Error + if term + Process.kill(term, pid) + term = (:KILL if term != :KILL) + retry + end + raise + end + } + end + end if Process.respond_to?(:kill) + + def test_invalid_signal_name + assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { Process.kill("\u{30eb 30d3 30fc}", $$) } + end if Process.respond_to?(:kill) + + def test_signal_exception + assert_raise(ArgumentError) { SignalException.new } + assert_raise(ArgumentError) { SignalException.new(-1) } + assert_raise(ArgumentError) { SignalException.new(:XXXXXXXXXX) } + assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) { SignalException.new("\u{30eb 30d3 30fc}") } + Signal.list.each do |signm, signo| + next if signm == "EXIT" + assert_equal(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 + assert_raise(Interrupt) { raise Interrupt.new } + end + + def test_signal2 + begin + x = false + oldtrap = Signal.trap(:INT) {|sig| x = true } + GC.start + + assert_raise(ArgumentError) { Process.kill } + + Timeout.timeout(10) do + x = false + Process.kill(SignalException.new(:INT).signo, $$) + sleep(0.01) until x + + x = false + Process.kill("INT", $$) + sleep(0.01) until x + + x = false + Process.kill("SIGINT", $$) + sleep(0.01) until x + + x = false + o = Object.new + def o.to_str; "SIGINT"; end + Process.kill(o, $$) + sleep(0.01) until x + end + + assert_raise(ArgumentError) { Process.kill(Object.new, $$) } + + ensure + Signal.trap(:INT, oldtrap) if oldtrap + end + end if Process.respond_to?(:kill) + + def test_trap + begin + oldtrap = Signal.trap(:INT) {|sig| } + + assert_raise(ArgumentError) { Signal.trap } + + # FIXME! + Signal.trap(:INT, nil) + Signal.trap(:INT, "") + Signal.trap(:INT, "SIG_IGN") + Signal.trap(:INT, "IGNORE") + + Signal.trap(:INT, "SIG_DFL") + Signal.trap(:INT, "SYSTEM_DEFAULT") + + Signal.trap(:INT, "EXIT") + + Signal.trap(:INT, "xxxxxx") + Signal.trap(:INT, "xxxx") + + Signal.trap(SignalException.new(:INT).signo, "SIG_DFL") + + assert_raise(ArgumentError) { Signal.trap(-1, "xxxx") } + + o = Object.new + def o.to_str; "SIGINT"; end + Signal.trap(o, "SIG_DFL") + + 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 + 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 + + 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]' + no_core = "Process.setrlimit(Process::RLIMIT_CORE, 0); " if defined?(Process.setrlimit) && defined?(Process::RLIMIT_CORE) + status = assert_in_out_err(['-e', "#{no_core}Process.kill(:TRAP, $$)"]) + assert_predicate(status, :signaled?, bug9820) + assert_equal(trap, status.termsig, bug9820) + end + + 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 + omit unless Process.respond_to?(:fork) + omit unless defined?(Process::WUNTRACED) + + # Make a process that stops itself + child_pid = fork do + Process.kill(:STOP, Process.pid) + end + + # The parent should be notified about the stop + _, status = Process.waitpid2(child_pid, Process::WUNTRACED) + assert_predicate status, :stopped? + + # It can be continued + Process.kill(:CONT, child_pid) + + # And the child then runs to completion + _, status = Process.waitpid2(child_pid) + assert_predicate status, :exited? + assert_predicate status, :success? + end + + def test_sigwait_fd_unused + t = EnvUtil.apply_timeout_scale(0.1) + assert_ruby_status([], <<-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 new file mode 100644 index 0000000000..7ef962db4a --- /dev/null +++ b/test/ruby/test_sleep.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: false +require 'test/unit' +require 'etc' +require 'timeout' + +class TestSleep < Test::Unit::TestCase + def test_sleep_5sec + EnvUtil.without_gc do + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + sleep 5 + 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") + end + end + + def test_sleep_forever_not_woken_by_sigchld + begin + t = Thread.new do + sleep 0.5 + `echo hello` + end + + assert_raise Timeout::Error do + Timeout.timeout 2 do + sleep # Should block forever + end + end + ensure + t.join + end + end +end diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb new file mode 100644 index 0000000000..1c7e89c265 --- /dev/null +++ b/test/ruby/test_sprintf.rb @@ -0,0 +1,557 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestSprintf < Test::Unit::TestCase + def test_positional + assert_equal(" 00001", sprintf("%*1$.*2$3$d", 10, 5, 1)) + end + + def test_binary + assert_equal("0", sprintf("%b", 0)) + assert_equal("1", sprintf("%b", 1)) + assert_equal("10", sprintf("%b", 2)) + assert_equal("..1", sprintf("%b", -1)) + + assert_equal(" 0", sprintf("%4b", 0)) + assert_equal(" 1", sprintf("%4b", 1)) + assert_equal(" 10", sprintf("%4b", 2)) + assert_equal(" ..1", sprintf("%4b", -1)) + + assert_equal("0000", sprintf("%04b", 0)) + assert_equal("0001", sprintf("%04b", 1)) + assert_equal("0010", sprintf("%04b", 2)) + assert_equal("..11", sprintf("%04b", -1)) + + assert_equal("0000", sprintf("%.4b", 0)) + assert_equal("0001", sprintf("%.4b", 1)) + assert_equal("0010", sprintf("%.4b", 2)) + assert_equal("..11", sprintf("%.4b", -1)) + + assert_equal(" 0000", sprintf("%6.4b", 0)) + assert_equal(" 0001", sprintf("%6.4b", 1)) + assert_equal(" 0010", sprintf("%6.4b", 2)) + assert_equal(" ..11", sprintf("%6.4b", -1)) + + assert_equal(" 0", sprintf("%#4b", 0)) + assert_equal(" 0b1", sprintf("%#4b", 1)) + assert_equal("0b10", sprintf("%#4b", 2)) + assert_equal("0b..1", sprintf("%#4b", -1)) + + assert_equal("0000", sprintf("%#04b", 0)) + assert_equal("0b01", sprintf("%#04b", 1)) + assert_equal("0b10", sprintf("%#04b", 2)) + assert_equal("0b..1", sprintf("%#04b", -1)) + + assert_equal("0000", sprintf("%#.4b", 0)) + assert_equal("0b0001", sprintf("%#.4b", 1)) + assert_equal("0b0010", sprintf("%#.4b", 2)) + assert_equal("0b..11", sprintf("%#.4b", -1)) + + assert_equal(" 0000", sprintf("%#6.4b", 0)) + assert_equal("0b0001", sprintf("%#6.4b", 1)) + assert_equal("0b0010", sprintf("%#6.4b", 2)) + assert_equal("0b..11", sprintf("%#6.4b", -1)) + + assert_equal("+0", sprintf("%+b", 0)) + assert_equal("+1", sprintf("%+b", 1)) + assert_equal("+10", sprintf("%+b", 2)) + assert_equal("-1", sprintf("%+b", -1)) + + assert_equal(" +0", sprintf("%+4b", 0)) + assert_equal(" +1", sprintf("%+4b", 1)) + assert_equal(" +10", sprintf("%+4b", 2)) + assert_equal(" -1", sprintf("%+4b", -1)) + + assert_equal("+000", sprintf("%+04b", 0)) + assert_equal("+001", sprintf("%+04b", 1)) + assert_equal("+010", sprintf("%+04b", 2)) + assert_equal("-001", sprintf("%+04b", -1)) + + assert_equal("+0000", sprintf("%+.4b", 0)) + assert_equal("+0001", sprintf("%+.4b", 1)) + assert_equal("+0010", sprintf("%+.4b", 2)) + assert_equal("-0001", sprintf("%+.4b", -1)) + + assert_equal(" +0000", sprintf("%+6.4b", 0)) + assert_equal(" +0001", sprintf("%+6.4b", 1)) + assert_equal(" +0010", sprintf("%+6.4b", 2)) + assert_equal(" -0001", sprintf("%+6.4b", -1)) + end + + def test_nan + nan = 0.0 / 0.0 + assert_equal("NaN", sprintf("%f", nan)) + 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)) + + assert_equal(" NaN", sprintf("%08f", nan)) + assert_equal("NaN ", sprintf("%-08f", nan)) + assert_equal(" +NaN", sprintf("%+08f", nan)) + + assert_equal(" NaN", sprintf("% 8f", nan)) + assert_equal(" NaN ", sprintf("%- 8f", nan)) + assert_equal(" +NaN", sprintf("%+ 8f", nan)) + + assert_equal(" NaN", sprintf("% 08f", nan)) + assert_equal(" NaN ", sprintf("%- 08f", nan)) + assert_equal(" +NaN", sprintf("%+ 08f", nan)) + end + + def test_inf + inf = 1.0 / 0.0 + 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("%+ 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)) + + assert_equal(" Inf", sprintf("%08f", inf)) + assert_equal("Inf ", sprintf("%-08f", inf)) + assert_equal(" +Inf", sprintf("%+08f", inf)) + + assert_equal(" Inf", sprintf("% 8f", inf)) + assert_equal(" Inf ", sprintf("%- 8f", inf)) + assert_equal(" +Inf", sprintf("%+ 8f", inf)) + + assert_equal(" Inf", sprintf("% 08f", inf)) + assert_equal(" Inf ", sprintf("%- 08f", inf)) + assert_equal(" +Inf", sprintf("%+ 08f", 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("%- 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)) + + assert_equal(" -Inf", sprintf("%08f", -inf)) + assert_equal("-Inf ", sprintf("%-08f", -inf)) + assert_equal(" -Inf", sprintf("%+08f", -inf)) + + assert_equal(" -Inf", sprintf("% 8f", -inf)) + assert_equal("-Inf ", sprintf("%- 8f", -inf)) + assert_equal(" -Inf", sprintf("%+ 8f", -inf)) + + assert_equal(" -Inf", sprintf("% 08f", -inf)) + assert_equal("-Inf ", sprintf("%- 08f", -inf)) + assert_equal(" -Inf", sprintf("%+ 08f", -inf)) + assert_equal('..f00000000', + sprintf("%x", -2**32), '[ruby-dev:32351]') + assert_equal("..101111111111111111111111111111111", + sprintf("%b", -2147483649), '[ruby-dev:32365]') + 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) + + require 'rbconfig/sizeof' + fmin, fmax = RbConfig::LIMITS.values_at("FIXNUM_MIN", "FIXNUM_MAX") + assert_match(/\A-\d+\.\d+\z/, sprintf("%f", Rational(fmin, fmax))) + end + + def test_rational_precision + assert_match(/\A0\.\d{600}\z/, sprintf("%.600f", 600**~60)) + end + + def test_hash + options = {:capture=>/\d+/} + assert_equal("with options #{options.inspect}", 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)} + + # Precision before flags and width: + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%.5+05d", 5)} + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%.5 5d", 5)} + + # Overriding a star width with a numeric one: + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%*1s", 5, 1)} + + # Width before flags: + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%5+0d", 1)} + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%5 0d", 1)} + + # Specifying width multiple times: + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%50+30+20+10+5d", 5)} + assert_raise(ArgumentError, "[ruby-core:11569]") {sprintf("%50 30 20 10 5d", 5)} + + # Specifying the precision multiple times with negative star arguments: + assert_raise(ArgumentError, "[ruby-core:11570]") {sprintf("%.*.*.*.*f", -1, -1, -1, 5, 1)} + + assert_raise(ArgumentError) {sprintf("%\0x hello")} + assert_raise(ArgumentError) {sprintf("%\nx hello")} + + assert_raise(ArgumentError, "[ruby-core:11573]") {sprintf("%.25555555555555555555555555555555555555s", "hello")} + + assert_raise(ArgumentError) { sprintf("%\1", 1) } + 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) } + + assert_warning(/too many arguments/) do + sprintf("", 1) + end + end + + def test_float + 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 + assert_equal("-0x0p+0", sprintf("%a", -0.0)) + assert_equal("0x0p+0", sprintf("%a", 0.0)) + assert_equal("0x1p-1", sprintf("%a", 0.5)) + assert_equal("0x1p+0", sprintf("%a", 1.0)) + assert_equal("0x1p+1", sprintf("%a", 2.0)) + assert_equal("0x1p+10", sprintf("%a", 1024)) + assert_equal("0x1.23456p+789", sprintf("%a", 3.704450999893983e+237)) + assert_equal("0x1p-1074", sprintf("%a", 4.9e-324)) + assert_equal("Inf", sprintf("%e", Float::INFINITY)) + 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 + + def test_skip + assert_equal(" " * BSIZ + "1", sprintf(" " * BSIZ + "%d", 1)) + end + + def test_char + assert_equal("a", sprintf("%c", 97)) + assert_equal("a", sprintf("%c", ?a)) + assert_equal("a", sprintf("%c", "a")) + assert_equal("a", sprintf("%c", sprintf("%c%c", ?a, ?a))) + assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%c", ?a)) + assert_equal(" " * (BSIZ - 1) + "a", sprintf(" " * (BSIZ - 1) + "%-1c", ?a)) + assert_equal(" " * BSIZ + "a", sprintf("%#{ BSIZ + 1 }c", ?a)) + assert_equal("a" + " " * BSIZ, sprintf("%-#{ BSIZ + 1 }c", ?a)) + assert_raise(ArgumentError) { sprintf("%c", -1) } + s = sprintf("%c".encode(Encoding::US_ASCII), 0x80) + assert_equal("\x80".b, s) + assert_predicate(s, :valid_encoding?) + end + + def test_string + assert_equal("foo", sprintf("%s", "foo")) + assert_equal("fo", sprintf("%.2s", "foo")) + assert_equal(" " * BSIZ, sprintf("%s", " " * BSIZ)) + assert_equal(" " * (BSIZ - 1) + "foo", sprintf("%#{ BSIZ - 1 + 3 }s", "foo")) + assert_equal(" " * BSIZ + "foo", sprintf("%#{ BSIZ + 3 }s", "foo")) + assert_equal("foo" + " " * BSIZ, sprintf("%-#{ BSIZ + 3 }s", "foo")) + end + + def test_integer + assert_equal("01", sprintf("%#o", 1)) + assert_equal("0x1", sprintf("%#x", 1)) + assert_equal("0X1", sprintf("%#X", 1)) + assert_equal("0b1", sprintf("%#b", 1)) + assert_equal("0B1", sprintf("%#B", 1)) + assert_equal("1", sprintf("%d", 1.0)) + assert_equal("4294967296", sprintf("%d", (2**32).to_f)) + assert_equal("-2147483648", sprintf("%d", -(2**31).to_f)) + assert_equal("18446744073709551616", sprintf("%d", (2**64).to_f)) + assert_equal("-9223372036854775808", sprintf("%d", -(2**63).to_f)) + assert_equal("1", sprintf("%d", "1")) + o = Object.new; def o.to_int; 1; end + assert_equal("1", sprintf("%d", o)) + assert_equal("+1", sprintf("%+d", 1)) + assert_equal(" 1", sprintf("% d", 1)) + assert_equal("..f", sprintf("%x", -1)) + assert_equal("..7", sprintf("%o", -1)) + one = (2**32).coerce(1).first + mone = (2**32).coerce(-1).first + assert_equal("+1", sprintf("%+d", one)) + assert_equal(" 1", sprintf("% d", one)) + assert_equal("..f", sprintf("%x", mone)) + assert_equal("..7", sprintf("%o", mone)) + assert_equal(" " * BSIZ + "1", sprintf("%#{ BSIZ + 1 }d", one)) + assert_equal(" " * (BSIZ - 1) + "1", sprintf(" " * (BSIZ - 1) + "%d", 1)) + end + + def test_float2 + inf = 1.0 / 0.0 + assert_equal(" " * BSIZ + "Inf", sprintf("%#{ BSIZ + 3 }.1f", inf)) + assert_equal("+Inf", sprintf("%+-f", inf)) + assert_equal(" " * BSIZ + "1.0", sprintf("%#{ BSIZ + 3 }.1f", 1.0)) + end + + class T012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + end + + 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) + end + + def test_negative_hex + s1 = sprintf("%0x", -0x40000000) + s2 = sprintf("%0x", -0x40000001) + b1 = (/\.\./ =~ s1) != nil + b2 = (/\.\./ =~ s2) != nil + assert_equal(b1, b2, "[ruby-dev:33224]") + end + + def test_named_untyped + assert_equal("value", sprintf("%<key>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_coderange + format_str = "wrong constant name %s" + interpolated_str = "\u3042" + assert_predicate format_str, :ascii_only? + refute_predicate interpolated_str, :ascii_only? + + str = format_str % interpolated_str + refute_predicate str, :ascii_only? + end + + def test_named_default + h = Hash.new('world') + assert_equal("hello world", "hello %{location}" % h) + 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_binary_format_coderange + 1.upto(500) do |i| + str = sprintf("%*s".b, i, "\xe2".b) + refute_predicate str, :ascii_only? + assert_equal i, str.bytesize + end + end +end diff --git a/test/ruby/test_sprintf_comb.rb b/test/ruby/test_sprintf_comb.rb new file mode 100644 index 0000000000..4113113030 --- /dev/null +++ b/test/ruby/test_sprintf_comb.rb @@ -0,0 +1,554 @@ +# frozen_string_literal: false +require 'test/unit' +require_relative 'allpairs' + +class TestSprintfComb < Test::Unit::TestCase + VS = [ + #-0x1000000000000000000000000000000000000000000000002, + #-0x1000000000000000000000000000000000000000000000001, + #-0x1000000000000000000000000000000000000000000000000, + #-0xffffffffffffffffffffffffffffffffffffffffffffffff, + #-0x1000000000000000000000002, + #-0x1000000000000000000000001, + #-0x1000000000000000000000000, + #-0xffffffffffffffffffffffff, + -0x10000000000000002, + -0x10000000000000001, + -0x10000000000000000, + -0xffffffffffffffff, + -0x4000000000000002, + -0x4000000000000001, + -0x4000000000000000, + -0x3fffffffffffffff, + -0x100000002, + -0x100000001, + -0x100000000, + -0xffffffff, + #-0xc717a08d, # 0xc717a08d * 0x524b2245 = 0x4000000000000001 + -0x80000002, + -0x80000001, + -0x80000000, + -0x7fffffff, + #-0x524b2245, + -0x40000002, + -0x40000001, + -0x40000000, + -0x3fffffff, + #-0x10002, + #-0x10001, + #-0x10000, + #-0xffff, + #-0x8101, # 0x8101 * 0x7f01 = 0x40000001 + #-0x8002, + #-0x8001, + #-0x8000, + #-0x7fff, + #-0x7f01, + #-65, + #-64, + #-63, + #-62, + #-33, + #-32, + #-31, + #-30, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + #30, + #31, + #32, + #33, + #62, + #63, + #64, + #65, + #0x7f01, + #0x7ffe, + #0x7fff, + #0x8000, + #0x8001, + #0x8101, + #0xfffe, + #0xffff, + #0x10000, + #0x10001, + 0x3ffffffe, + 0x3fffffff, + 0x40000000, + 0x40000001, + #0x524b2245, + 0x7ffffffe, + 0x7fffffff, + 0x80000000, + 0x80000001, + #0xc717a08d, + 0xfffffffe, + 0xffffffff, + 0x100000000, + 0x100000001, + 0x3ffffffffffffffe, + 0x3fffffffffffffff, + 0x4000000000000000, + 0x4000000000000001, + 0xfffffffffffffffe, + 0xffffffffffffffff, + 0x10000000000000000, + 0x10000000000000001, + #0xffffffffffffffffffffffff, + #0x1000000000000000000000000, + #0x1000000000000000000000001, + #0xffffffffffffffffffffffffffffffffffffffffffffffff, + #0x1000000000000000000000000000000000000000000000000, + #0x1000000000000000000000000000000000000000000000001 + ] + VS.reverse! + + FLAGS = [['', ' '], ['', '#'], ['', '+'], ['', '-'], ['', '0']] + + def self.combination(*args, &b) + #AllPairs.exhaustive_each(*args, &b) + AllPairs.each(*args, &b) + end + + def emu_int(format, v) + /\A%( )?(\#)?(\+)?(-)?(0)?(\d+)?(?:\.(\d*))?(.)\z/ =~ format + sp = $1 + hs = $2 + pl = $3 + mi = $4 + zr = $5 + width = $6 + precision = $7 + type = $8 + width = width.to_i if width + precision = precision.to_i if precision + prefix = '' + + zr = nil if precision + + zr = nil if mi && zr + + case type + when 'B' + radix = 2 + digitmap = {0 => '0', 1 => '1'} + complement = !pl && !sp + prefix = '0B' if hs && v != 0 + when 'b' + radix = 2 + digitmap = {0 => '0', 1 => '1'} + complement = !pl && !sp + prefix = '0b' if hs && v != 0 + when 'd' + radix = 10 + digitmap = {} + 10.times {|i| digitmap[i] = i.to_s } + complement = false + when 'o' + radix = 8 + digitmap = {} + 8.times {|i| digitmap[i] = i.to_s } + complement = !pl && !sp + when 'X' + radix = 16 + digitmap = {} + 16.times {|i| digitmap[i] = i.to_s(16).upcase } + complement = !pl && !sp + prefix = '0X' if hs && v != 0 + when 'x' + radix = 16 + digitmap = {} + 16.times {|i| digitmap[i] = i.to_s(16) } + complement = !pl && !sp + prefix = '0x' if hs && v != 0 + else + raise "unexpected type: #{type.inspect}" + end + + digits = [] + abs = v.abs + sign = '' + while 0 < abs + digits << (abs % radix) + abs /= radix + end + + if v < 0 + if complement + digits.map! {|d| radix-1 - d } + carry = 1 + digits.each_index {|i| + digits[i] += carry + carry = 0 + if radix <= digits[i] + digits[i] -= radix + carry = 1 + end + } + if digits.last != radix-1 + digits << (radix-1) + end + sign = '..' + else + sign = '-' + end + else + if pl + sign = '+' + elsif sp + sign = ' ' + end + end + + dlen = digits.length + dlen += 2 if sign == '..' + + if v < 0 && complement + d = radix - 1 + else + d = 0 + end + if precision + if dlen < precision + (precision - dlen).times { + digits << d + } + end + else + if dlen == 0 + digits << d + end + end + if type == 'o' && hs + if digits.empty? || digits.last != d + digits << d + end + end + + digits.reverse! + + str = digits.map {|digit| digitmap[digit] }.join + + pad = '' + nlen = prefix.length + sign.length + str.length + if width && nlen < width + len = width - nlen + if zr + if complement && v < 0 + pad = digitmap[radix-1] * len + else + pad = '0' * len + end + else + pad = ' ' * len + end + end + + if / / =~ pad + if sign == '..' + str = prefix + sign + str + else + str = sign + prefix + str + end + if mi + str = str + pad + else + str = pad + str + end + else + if sign == '..' + str = prefix + sign + pad + str + else + str = sign + prefix + pad + str + end + end + + str + end + + def self.assertions_format_integer(format) + proc { + VS.each {|v| + r = sprintf format, v + e = emu_int format, v + if true + assert_equal(e, r, "sprintf(#{format.dump}, #{v})") + else + if e != r + puts "#{e.dump}\t#{r.dump}\tsprintf(#{format.dump}, #{v})" + end + end + } + } + 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, + -1.0, + -0.0, + 0.0, + 0.01, + 1/3.0, + 2/3.0, + 1.0, + 2.0, + 9.99999999, + 123456789.0, + 1e100, + Float::MAX, + Float::MIN, + Float::EPSILON, + 1+Float::EPSILON, + #1-Float::EPSILON/2, + 10 + Float::EPSILON*10, + 10 - Float::EPSILON*5, + 1.0/0.0, + -1.0/0.0, + 0.0/0.0, + ] + + def split_float10(v) + if v == 0 + if 1/v < 0 + sign = -1 + v = -v + else + sign = 1 + end + else + if v < 0 + sign = -1 + v = -v + else + sign = 1 + end + end + exp = 0 + int = v.floor + v -= int + while v != 0 + v *= 2 + int *= 2 + i = v.floor + v -= i + int += i + exp -= 1 + end + int *= 5 ** (-exp) + [sign, int, exp] + end + + def emu_e(sp, hs, pl, mi, zr, width, precision, type, v, sign, int, exp) + precision = 6 unless precision + if int == 0 + if precision == 0 && !hs + result = "0#{type}+00" + else + result = "0." + "0" * precision + "#{type}+00" + end + else + if int < 10**precision + int *= 10**precision + exp -= precision + end + digits = int.to_s.length + discard = digits - (precision+1) + if discard != 0 + q, r = int.divmod(10**discard) + if r < 10**discard / 2 + int = q + exp += discard + elsif (q+1).to_s.length == q.to_s.length + int = q+1 + exp += discard + else + discard += 1 + q, r = int.divmod(10**discard) + int = q+1 + exp += discard + end + end + ints = int.to_s + frac = ints[1..-1] + result = ints[0,1] + e = exp + frac.length + if precision != 0 || hs + result << "." + if precision != 0 + result << frac + end + end + result << type + if e == 0 + if v.abs < 1 + result << '-00' # glibc 2.7 causes '+00' + else + result << '+00' + end + else + result << sprintf("%+03d", e) + end + result + end + result + end + + def emu_f(sp, hs, pl, mi, zr, width, precision, type, sign, int, exp) + precision = 6 unless precision + if int == 0 + if precision == 0 && !hs + result = '0' + else + result = '0.' + '0' * precision + end + else + if -precision < exp + int *= 10 ** (precision+exp) + exp = -precision + end + if exp < -precision + discard = -exp - precision + q, r = int.divmod(10**discard) + if 10**discard / 2 <= r + q += 1 + end + int = q + exp += discard + end + result = int.to_s + if result.length <= precision + result = '0' * (precision+1 - result.length) + result + end + if precision != 0 || hs + if precision == 0 + result << '.' + else + result[-precision,0] = '.' + end + end + end + result + end + + def emu_float(format, v) + /\A%( )?(\#)?(\+)?(-)?(0)?(\d+)?(?:\.(\d*))?(.)\z/ =~ format + sp = $1 + hs = $2 + pl = $3 + mi = $4 + zr = $5 + width = $6 + precision = $7 + type = $8 + width = width.to_i if width + precision = precision.to_i if precision + + zr = nil if mi && zr + + if v.infinite? + sign = v < 0 ? -1 : 1 + int = :inf + hs = zr = nil + elsif v.nan? + sign = 1 + int = :nan + hs = zr = nil + else + sign, int, exp = split_float10(v) + end + + if sign < 0 + sign = '-' + elsif sign == 0 + sign = '' + elsif pl + sign = '+' + elsif sp + sign = ' ' + else + sign = '' + end + + if v.nan? + result = 'NaN' + elsif v.infinite? + result = 'Inf' + else + case type + when /[eE]/ + result = emu_e(sp, hs, pl, mi, zr, width, precision, type, v, sign, int, exp) + when /f/ + result = emu_f(sp, hs, pl, mi, zr, width, precision, type, sign, int, exp) + when /[gG]/ + precision = 6 unless precision + precision = 1 if precision == 0 + r = emu_e(sp, hs, pl, mi, zr, width, precision-1, type.tr('gG', 'eE'), v, sign, int, exp) + /[eE]([+-]\d+)/ =~ r + e = $1.to_i + if e < -4 || precision <= e + result = r + else + result = emu_f(sp, hs, pl, mi, zr, width, precision-1-e, type, sign, int, exp) + end + result.sub!(/\.[0-9]*/) { $&.sub(/\.?0*\z/, '') } if !hs + else + raise "unexpected type: #{type}" + end + end + + pad = '' + if width && sign.length + result.length < width + if zr + pad = '0' * (width - sign.length - result.length) + else + pad = ' ' * (width - sign.length - result.length) + end + end + if mi + sign + result + pad + elsif zr + sign + pad + result + else + pad + sign + result + end + + end + + def self.assertions_format_float(format) + proc { + FLOAT_VALUES.each {|v| + r = sprintf format, v + e = emu_float format, v + if true + assert_equal(e, r, "sprintf(#{format.dump}, #{'%.20g' % v})") + else + if e != r + puts "#{e.dump}\t#{r.dump}\tsprintf(#{format.dump}, #{'%.20g' % v})" + end + end + } + } + 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_stack.rb b/test/ruby/test_stack.rb new file mode 100644 index 0000000000..8a78848322 --- /dev/null +++ b/test/ruby/test_stack.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestStack < Test::Unit::TestCase + LARGE_VM_STACK_SIZE = 1024*1024*5 + LARGE_MACHINE_STACK_SIZE = 1024*1024*10 + + def initialize(*) + super + + @h_default = nil + @h_0 = nil + @h_large = nil + end + + def invoke_ruby script, vm_stack_size: nil, machine_stack_size: nil + env = {} + env['RUBY_FIBER_VM_STACK_SIZE'] = vm_stack_size.to_s if vm_stack_size + env['RUBY_FIBER_MACHINE_STACK_SIZE'] = machine_stack_size.to_s if machine_stack_size + + stdout, stderr, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true, timeout: 30) + assert(!status.signaled?, FailDesc[status, nil, stderr]) + + return stdout + end + + def h_default + @h_default ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS')) + end + + def h_0 + @h_0 ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS', + vm_stack_size: 0, + machine_stack_size: 0 + )) + end + + def h_large + @h_large ||= eval(invoke_ruby('p RubyVM::DEFAULT_PARAMS', + vm_stack_size: LARGE_VM_STACK_SIZE, + machine_stack_size: LARGE_MACHINE_STACK_SIZE + )) + end + + def test_relative_stack_sizes + assert_operator(h_default[:fiber_vm_stack_size], :>, h_0[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_vm_stack_size], :<, h_large[:fiber_vm_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :>=, h_0[:fiber_machine_stack_size]) + assert_operator(h_default[:fiber_machine_stack_size], :<=, h_large[:fiber_machine_stack_size]) + end + + def test_vm_stack_size + script = '$stdout.sync=true; def rec; print "."; rec; end; Fiber.new{rec}.resume' + + size_default = invoke_ruby(script).bytesize + assert_operator(size_default, :>, 0) + + size_0 = invoke_ruby(script, vm_stack_size: 0).bytesize + assert_operator(size_default, :>, size_0) + + size_large = invoke_ruby(script, vm_stack_size: LARGE_VM_STACK_SIZE).bytesize + assert_operator(size_default, :<, size_large) + end + + # Depending on OS, machine stack size may not change size. + def test_machine_stack_size + return if /mswin|mingw/ =~ RUBY_PLATFORM + + script = '$stdout.sync=true; def rec; print "."; 1.times{1.times{1.times{rec}}}; end; Fiber.new{rec}.resume' + + vm_stack_size = 1024 * 1024 + size_default = invoke_ruby(script, vm_stack_size: vm_stack_size).bytesize + + size_0 = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: 0).bytesize + assert_operator(size_default, :>=, size_0) + + size_large = invoke_ruby(script, vm_stack_size: vm_stack_size, machine_stack_size: LARGE_MACHINE_STACK_SIZE).bytesize + assert_operator(size_default, :<=, size_large) + end +end diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb new file mode 100644 index 0000000000..2458d38ef4 --- /dev/null +++ b/test/ruby/test_string.rb @@ -0,0 +1,4045 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestString < Test::Unit::TestCase + WIDE_ENCODINGS = [ + Encoding::UTF_16BE, Encoding::UTF_16LE, + Encoding::UTF_32BE, Encoding::UTF_32LE, + ] + + def initialize(*args) + @cls = String + super + end + + def S(*args, **kw) + @cls.new(*args, **kw) + end + + def test_s_new + 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)) + + if @cls == String + 100.times { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa". + __send__(:initialize, capacity: -1) + } + end + end + + def test_initialize_shared + S(str = "mystring" * 10).__send__(:initialize, capacity: str.bytesize) + assert_equal("mystring", str[0, 8]) + end + + def test_initialize_nonstring + assert_raise(TypeError) { + S(1) + } + assert_raise(TypeError) { + S(1, capacity: 1000) + } + end + + def test_initialize_memory_leak + return unless @cls == String + + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {('x'*100_000).__send__(:initialize, '')} +1_000.times(&code) +PREP +100_000.times(&code) +CODE + end + + # Bug #18154 + def test_initialize_nofree_memory_leak + return unless @cls == String + + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc {0.to_s.__send__(:initialize, capacity: 100_000)} +1_000.times(&code) +PREP +100_000.times(&code) +CODE + end + + def test_AREF # '[]' + assert_equal("A", S("AooBar")[0]) + assert_equal("B", S("FooBaB")[-1]) + assert_equal(nil, S("FooBar")[6]) + assert_equal(nil, S("FooBar")[-7]) + + assert_equal(S("Foo"), S("FooBar")[0,3]) + assert_equal(S("Bar"), S("FooBar")[-3,3]) + assert_equal(S(""), S("FooBar")[6,2]) + assert_equal(nil, S("FooBar")[-7,10]) + + assert_equal(S("Foo"), S("FooBar")[0..2]) + assert_equal(S("Foo"), S("FooBar")[0...3]) + assert_equal(S("Bar"), S("FooBar")[-3..-1]) + assert_equal(S(""), S("FooBar")[6..2]) + assert_equal(nil, S("FooBar")[-10..-7]) + + assert_equal(S("Foo"), S("FooBar")[/^F../]) + assert_equal(S("Bar"), S("FooBar")[/..r$/]) + assert_equal(nil, S("FooBar")[/xyzzy/]) + assert_equal(nil, S("FooBar")[/plugh/]) + + assert_equal(S("Foo"), S("FooBar")[S("Foo")]) + assert_equal(S("Bar"), S("FooBar")[S("Bar")]) + assert_equal(nil, S("FooBar")[S("xyzzy")]) + assert_equal(nil, S("FooBar")[S("plugh")]) + + assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 1]) + assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, 2]) + assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, 3]) + assert_equal(S("Bar"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -1]) + assert_equal(S("Foo"), S("FooBar")[/([A-Z]..)([A-Z]..)/, -2]) + assert_equal(nil, S("FooBar")[/([A-Z]..)([A-Z]..)/, -3]) + + o = Object.new + def o.to_int; 2; end + assert_equal("o", "foo"[o]) + + assert_raise(ArgumentError) { "foo"[] } + end + + def test_AREF_underflow + require "rbconfig/sizeof" + assert_equal(nil, S("\u{3042 3044 3046}")[RbConfig::LIMITS["LONG_MIN"], 1]) + end + + def test_AREF_invalid_encoding + assert_equal(S("\x80"), S("A"*39+"\x80")[-1, 1]) + end + + def test_ASET # '[]=' + s = S("FooBar") + s[0] = S('A') + assert_equal(S("AooBar"), s) + + s[-1]= S('B') + assert_equal(S("AooBaB"), s) + assert_raise(IndexError) { s[-7] = S("xyz") } + assert_equal(S("AooBaB"), s) + s[0] = S("ABC") + assert_equal(S("ABCooBaB"), s) + + s = S("FooBar") + s[0,3] = S("A") + assert_equal(S("ABar"),s) + s[0] = S("Foo") + assert_equal(S("FooBar"), s) + s[-3,3] = S("Foo") + assert_equal(S("FooFoo"), s) + assert_raise(IndexError) { s[7,3] = S("Bar") } + assert_raise(IndexError) { s[-7,3] = S("Bar") } + + s = S("FooBar") + s[0..2] = S("A") + assert_equal(S("ABar"), s) + s[1..3] = S("Foo") + assert_equal(S("AFoo"), s) + s[-4..-4] = S("Foo") + assert_equal(S("FooFoo"), s) + assert_raise(RangeError) { s[7..10] = S("Bar") } + assert_raise(RangeError) { s[-7..-10] = S("Bar") } + + s = S("FooBar") + s[/^F../]= S("Bar") + assert_equal(S("BarBar"), s) + s[/..r$/] = S("Foo") + assert_equal(S("BarFoo"), s) + assert_raise(IndexError) { s[/xyzzy/] = S("None") } + + s[/([A-Z]..)([A-Z]..)/, 1] = S("Foo") + assert_equal(S("FooFoo"), s) + s[/([A-Z]..)([A-Z]..)/, 2] = S("Bar") + assert_equal(S("FooBar"), s) + assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, 3] = "None" } + s[/([A-Z]..)([A-Z]..)/, -1] = S("Foo") + assert_equal(S("FooFoo"), s) + s[/([A-Z]..)([A-Z]..)/, -2] = S("Bar") + assert_equal(S("BarFoo"), s) + assert_raise(IndexError) { s[/([A-Z]..)([A-Z]..)/, -3] = "None" } + + s = S("FooBar") + s[S("Foo")] = S("Bar") + assert_equal(S("BarBar"), s) + + s = S("a string") + s[0..s.size] = S("another string") + assert_equal(S("another string"), s) + + o = Object.new + def o.to_int; 2; end + s = "foo" + s[o] = "bar" + assert_equal("fobar", s) + + assert_raise(ArgumentError) { "foo"[1, 2, 3] = "" } + + assert_raise(IndexError) {"foo"[RbConfig::LIMITS["LONG_MIN"]] = "l"} + end + + def test_CMP # '<=>' + assert_equal(1, S("abcdef") <=> S("abcde")) + assert_equal(0, S("abcdef") <=> S("abcdef")) + assert_equal(-1, S("abcde") <=> S("abcdef")) + + assert_equal(-1, S("ABCDEF") <=> S("abcdef")) + + assert_nil(S("foo") <=> Object.new) + + o = Object.new + def o.to_str; "bar"; end + assert_equal(1, S("foo") <=> o) + + class << o;remove_method :to_str;end + def o.<=>(x); nil; end + assert_nil(S("foo") <=> o) + + class << o;remove_method :<=>;end + def o.<=>(x); 1; end + assert_equal(-1, S("foo") <=> o) + + class << o;remove_method :<=>;end + def o.<=>(x); 2**100; end + assert_equal(-1, S("foo") <=> o) + end + + def test_EQUAL # '==' + assert_not_equal(:foo, S("foo")) + assert_equal(S("abcdef"), S("abcdef")) + + assert_not_equal(S("CAT"), S('cat')) + assert_not_equal(S("CaT"), S('cAt')) + assert_not_equal(S("cat\0""dog"), S("cat\0")) + + o = Object.new + def o.to_str; end + def o.==(x); false; end + assert_equal(false, S("foo") == o) + class << o;remove_method :==;end + def o.==(x); true; end + assert_equal(true, S("foo") == o) + end + + def test_LSHIFT # '<<' + assert_equal(S("world!"), S("world") << 33) + assert_equal(S("world!"), S("world") << S("!")) + + s = "a" + 10.times {|i| + s << s + assert_equal("a" * (2 << i), s) + } + + s = ["foo"].pack("p") + l = s.size + s << "bar" + assert_equal(l + 3, s.size) + + bug = '[ruby-core:27583]' + assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -3} + assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -2} + assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << -1} + assert_raise(RangeError, bug) {S("a".force_encoding(Encoding::UTF_8)) << 0x81308130} + assert_nothing_raised {S("a".force_encoding(Encoding::GB18030)) << 0x81308130} + + s = "\x95".force_encoding(Encoding::SJIS).tap(&:valid_encoding?) + assert_predicate(s << 0x5c, :valid_encoding?) + end + + def test_MATCH # '=~' + assert_equal(10, S("FeeFieFoo-Fum") =~ /Fum$/) + assert_equal(nil, S("FeeFieFoo-Fum") =~ /FUM$/) + + o = Object.new + def o.=~(x); x + "bar"; end + assert_equal("foobar", S("foo") =~ o) + + assert_raise(TypeError) { S("foo") =~ "foo" } + end + + def test_MOD # '%' + assert_equal(S("00123"), S("%05d") % 123) + assert_equal(S("123 |00000001"), S("%-5s|%08x") % [123, 1]) + x = S("%3s %-4s%%foo %.0s%5d %#x%c%3.1f %b %x %X %#b %#x %#X") % + [S("hi"), + 123, + S("never seen"), + 456, + 0, + ?A, + 3.0999, + 11, + 171, + 171, + 11, + 171, + 171] + + assert_equal(S(' hi 123 %foo 456 0A3.1 1011 ab AB 0b1011 0xab 0XAB'), x) + end + + def test_MUL # '*' + assert_equal(S("XXX"), S("X") * 3) + assert_equal(S("HOHO"), S("HO") * 2) + end + + def test_PLUS # '+' + assert_equal(S("Yodel"), S("Yo") + S("del")) + end + + def casetest(a, b, rev=false) + msg = proc {"#{a} should#{' not' if rev} match #{b}"} + case a + when b + assert(!rev, msg) + else + assert(rev, msg) + end + end + + def test_VERY_EQUAL # '===' + # assert_equal(true, S("foo") === :foo) + casetest(S("abcdef"), S("abcdef")) + + 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 + + def test_capitalize + assert_equal(S("Hello"), S("hello").capitalize) + assert_equal(S("Hello"), S("hELLO").capitalize) + assert_equal(S("123abc"), S("123ABC").capitalize) + end + + def test_capitalize! + a = S("hello"); a.capitalize! + assert_equal(S("Hello"), a) + + a = S("hELLO"); a.capitalize! + assert_equal(S("Hello"), a) + + a = S("123ABC"); a.capitalize! + assert_equal(S("123abc"), a) + + assert_equal(nil, S("123abc").capitalize!) + assert_equal(S("123abc"), S("123ABC").capitalize!) + assert_equal(S("Abc"), S("ABC").capitalize!) + assert_equal(S("Abc"), S("abc").capitalize!) + assert_equal(nil, S("Abc").capitalize!) + + a = S("hello") + b = a.dup + assert_equal(S("Hello"), a.capitalize!) + assert_equal(S("hello"), b) + + end + + Bug2463 = '[ruby-dev:39856]' + def test_center + assert_equal(S("hello"), S("hello").center(4)) + assert_equal(S(" hello "), S("hello").center(11)) + assert_equal(S("ababaababa"), S("").center(10, "ab"), Bug2463) + assert_equal(S("ababaababab"), S("").center(11, "ab"), Bug2463) + end + + def test_chomp + verbose, $VERBOSE = $VERBOSE, nil + + assert_equal(S("hello"), S("hello").chomp("\n")) + assert_equal(S("hello"), S("hello\n").chomp("\n")) + save = $/ + + $/ = "\n" + + assert_equal(S("hello"), S("hello").chomp) + assert_equal(S("hello"), S("hello\n").chomp) + + $/ = "!" + assert_equal(S("hello"), S("hello").chomp) + assert_equal(S("hello"), S("hello!").chomp) + $/ = 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 + $VERBOSE = verbose + end + + def test_chomp! + verbose, $VERBOSE = $VERBOSE, nil + + a = S("hello") + a.chomp!(S("\n")) + + assert_equal(S("hello"), a) + assert_equal(nil, a.chomp!(S("\n"))) + + a = S("hello\n") + a.chomp!(S("\n")) + assert_equal(S("hello"), a) + save = $/ + + $/ = "\n" + a = S("hello") + a.chomp! + assert_equal(S("hello"), a) + + a = S("hello\n") + a.chomp! + assert_equal(S("hello"), a) + + $/ = "!" + a = S("hello") + a.chomp! + assert_equal(S("hello"), a) + + a="hello!" + a.chomp! + assert_equal(S("hello"), a) + + $/ = save + + a = S("hello\n") + b = a.dup + assert_equal(S("hello"), a.chomp!) + assert_equal(S("hello\n"), b) + + s = "foo\r\n" + s.chomp! + assert_equal("foo", s) + + s = "foo\r" + s.chomp! + assert_equal("foo", s) + + s = "foo\r\n" + s.chomp!("") + assert_equal("foo", s) + + s = "foo\r" + s.chomp!("") + 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!} + $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state + + 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)} + $VERBOSE = nil # EnvUtil.suppress_warning resets $VERBOSE to the original state + + 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")) + + assert_raise(ArgumentError) {String.new.chomp!("", "")} + ensure + $/ = save + $VERBOSE = verbose + end + + def test_chop + assert_equal(S("hell"), S("hello").chop) + assert_equal(S("hello"), S("hello\r\n").chop) + assert_equal(S("hello\n"), S("hello\n\r").chop) + assert_equal(S(""), S("\r\n").chop) + assert_equal(S(""), S("").chop) + assert_equal(S("a").hash, S("a\u00d8").chop.hash) + end + + def test_chop! + a = S("hello").chop! + assert_equal(S("hell"), a) + + a = S("hello\r\n").chop! + assert_equal(S("hello"), a) + + a = S("hello\n\r").chop! + assert_equal(S("hello\n"), a) + + a = S("\r\n").chop! + assert_equal(S(""), a) + + a = S("").chop! + assert_nil(a) + + a = S("a\u00d8") + a.chop! + assert_equal(S("a").hash, a.hash) + + a = S("hello\n") + b = a.dup + assert_equal(S("hello"), a.chop!) + assert_equal(S("hello\n"), b) + end + + def test_clone + for frozen in [ false, true ] + a = S("Cool") + a.freeze if frozen + b = a.clone + + assert_equal(a, b) + assert_not_same(a, b) + assert_equal(a.frozen?, b.frozen?) + end + + assert_equal("", File.read(IO::NULL).clone, '[ruby-dev:32819] reported by Kazuhiro NISHIYAMA') + 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) { S('foo') << :foo } + assert_raise(FrozenError) { S('foo').freeze.concat('bar') } + end + + def test_concat_literals + s=S("." * 50) + assert_equal(Encoding::UTF_8, "#{s}x".encoding) + end + + def test_string_interpolations_across_heaps_get_embedded + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 + + require 'objspace' + base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + small_obj_size = (base_slot_size / 2) + large_obj_size = base_slot_size * 2 + + a = "a" * small_obj_size + b = "a" * large_obj_size + + res = "#{a}, #{b}" + dump_res = ObjectSpace.dump(res) + dump_orig = ObjectSpace.dump(a) + new_slot_size = Integer(dump_res.match(/"slot_size":(\d+)/)[1]) + orig_slot_size = Integer(dump_orig.match(/"slot_size":(\d+)/)[1]) + + assert_match(/"embedded":true/, dump_res) + assert_operator(new_slot_size, :>, orig_slot_size) + end + + def test_count + a = S("hello world") + assert_equal(5, a.count(S("lo"))) + assert_equal(2, a.count(S("lo"), S("o"))) + 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, S("abc\u{3042 3044 3046}").count("^a")) + assert_equal(1, S("abc\u{3042 3044 3046}").count("\u3042")) + assert_equal(5, S("abc\u{3042 3044 3046}").count("^\u3042")) + assert_equal(2, S("abc\u{3042 3044 3046}").count("a-z", "^a")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("a", "\u3042")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "a")) + assert_equal(0, S("abc\u{3042 3044 3046}").count("\u3042", "\u3044")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^a", "^\u3044")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3044", "^a")) + assert_equal(4, S("abc\u{3042 3044 3046}").count("^\u3042", "^\u3044")) + + assert_raise(ArgumentError) { S("foo").count } + end + + def crypt_supports_des_crypt? + /openbsd/ !~ RUBY_PLATFORM + end + + def test_crypt + if crypt_supports_des_crypt? + pass = "aaGUC/JkO9/Sc" + good_salt = "aa" + bad_salt = "ab" + else + pass = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWuBQGbkjzgtSc3gJbmdy0GAGMa45MFM2." + good_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHWu" + bad_salt = "$2a$04$0WVaz0pV3jzfZ5G5tpmHXu" + end + assert_equal(S(pass), S("mypassword").crypt(S(good_salt))) + assert_not_equal(S(pass), S("mypassword").crypt(S(bad_salt))) + assert_raise(ArgumentError) {S("mypassword").crypt(S(""))} + assert_raise(ArgumentError) {S("mypassword").crypt(S("\0a"))} + assert_raise(ArgumentError) {S("mypassword").crypt(S("a\0"))} + assert_raise(ArgumentError) {S("poison\u0000null").crypt(S("aa"))} + 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 = ''; salt_proc = proc{#{(crypt_supports_des_crypt? ? '..' : good_salt).inspect}}", "#{<<~"begin;"}\n#{<<~'end;'}") + + begin; + 1000.times { s.crypt(-salt_proc.call).clear } + end; + end + + def test_delete + assert_equal(S("heo"), S("hello").delete(S("l"), S("lo"))) + assert_equal(S("he"), S("hello").delete(S("lo"))) + assert_equal(S("hell"), S("hello").delete(S("aeiou"), S("^e"))) + assert_equal(S("ho"), S("hello").delete(S("ej-m"))) + + assert_equal(S("a").hash, S("a\u0101").delete("\u0101").hash, '[ruby-talk:329267]') + assert_equal(true, S("a\u0101").delete("\u0101").ascii_only?) + assert_equal(true, S("a\u3041").delete("\u3041").ascii_only?) + assert_equal(false, S("a\u3041\u3042").delete("\u3041").ascii_only?) + + assert_equal("a", S("abc\u{3042 3044 3046}").delete("^a")) + assert_equal("bc\u{3042 3044 3046}", S("abc\u{3042 3044 3046}").delete("a")) + assert_equal("\u3042", S("abc\u{3042 3044 3046}").delete("^\u3042")) + + bug6160 = '[ruby-dev:45374]' + assert_equal("", S('\\').delete('\\'), bug6160) + end + + def test_delete! + a = S("hello") + a.delete!(S("l"), S("lo")) + assert_equal(S("heo"), a) + + a = S("hello") + a.delete!(S("lo")) + assert_equal(S("he"), a) + + a = S("hello") + a.delete!(S("aeiou"), S("^e")) + assert_equal(S("hell"), a) + + a = S("hello") + a.delete!(S("ej-m")) + assert_equal(S("ho"), a) + + a = S("hello") + assert_nil(a.delete!(S("z"))) + + a = S("hello") + b = a.dup + a.delete!(S("lo")) + assert_equal(S("he"), a) + assert_equal(S("hello"), b) + + a = S("hello") + a.delete!(S("^el")) + assert_equal(S("ell"), a) + + assert_raise(ArgumentError) { S("foo").delete! } + end + + + def test_downcase + assert_equal(S("hello"), S("helLO").downcase) + assert_equal(S("hello"), S("hello").downcase) + assert_equal(S("hello"), S("HELLO").downcase) + assert_equal(S("abc hello 123"), S("abc HELLO 123").downcase) + assert_equal(S("h\0""ello"), S("h\0""ELLO").downcase) + end + + def test_downcase! + a = S("helLO") + b = a.dup + assert_equal(S("hello"), a.downcase!) + assert_equal(S("hello"), a) + assert_equal(S("helLO"), b) + + a=S("hello") + assert_nil(a.downcase!) + assert_equal(S("hello"), a) + + a = S("h\0""ELLO") + b = a.dup + assert_equal(S("h\0""ello"), a.downcase!) + assert_equal(S("h\0""ello"), a) + assert_equal(S("h\0""ELLO"), b) + end + + def test_dump + 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), + S('"a\x00b\x00c\x00".force_encoding("UTF-16LE")').undump) + + assert_equal('\#', S('"\\\\#"').undump) + assert_equal('\#{', S('"\\\\\#{"').undump) + + assert_undump("\0\u{ABCD}") + assert_undump(S('"\x00\u3042"'.force_encoding("SJIS"))) + assert_undump(S('"\u3042\x7E"'.force_encoding("SJIS"))) + + assert_raise(RuntimeError) { S('\u3042').undump } + assert_raise(RuntimeError) { S('"\x82\xA0\u3042"'.force_encoding("SJIS")).undump } + assert_raise(RuntimeError) { S('"\u3042\x82\xA0"'.force_encoding("SJIS")).undump } + assert_raise(RuntimeError) { S('"".force_encoding()').undump } + assert_raise(RuntimeError) { S('"".force_encoding("').undump } + assert_raise(RuntimeError) { S('"".force_encoding("UNKNOWN")').undump } + assert_raise(RuntimeError) { S('"\u3042".force_encoding("UTF-16LE")').undump } + assert_raise(RuntimeError) { S('"\x00\x00".force_encoding("UTF-16LE")"').undump } + assert_raise(RuntimeError) { S('"\x00\x00".force_encoding("'+("a"*9999999)+'")"').undump } + assert_raise(RuntimeError) { S(%("\u00E4")).undump } + assert_raise(RuntimeError) { S('"').undump } + assert_raise(RuntimeError) { S('"""').undump } + assert_raise(RuntimeError) { S('""""').undump } + + assert_raise(RuntimeError) { S('"a').undump } + assert_raise(RuntimeError) { S('"\u"').undump } + assert_raise(RuntimeError) { S('"\u{"').undump } + assert_raise(RuntimeError) { S('"\u304"').undump } + assert_raise(RuntimeError) { S('"\u304Z"').undump } + assert_raise(RuntimeError) { S('"\udfff"').undump } + assert_raise(RuntimeError) { S('"\u{dfff}"').undump } + assert_raise(RuntimeError) { S('"\u{3042"').undump } + assert_raise(RuntimeError) { S('"\u{3042 "').undump } + assert_raise(RuntimeError) { S('"\u{110000}"').undump } + assert_raise(RuntimeError) { S('"\u{1234567}"').undump } + assert_raise(RuntimeError) { S('"\x"').undump } + assert_raise(RuntimeError) { S('"\xA"').undump } + assert_raise(RuntimeError) { S('"\\"').undump } + assert_raise(RuntimeError) { S(%("\0")).undump } + assert_raise_with_message(RuntimeError, /invalid/) { + S('"\\u{007F}".xxxxxx').undump + } + end + + def test_undump_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + a = S("Test") << 1 << 2 << 3 << 9 << 13 << 10 + EnvUtil.under_gc_compact_stress do + assert_equal(a, S('"Test\\x01\\x02\\x03\\t\\r\\n"').undump) + end + + EnvUtil.under_gc_compact_stress do + assert_equal(S("\u{ABCDE 10ABCD}"), S('"\\u{ABCDE 10ABCD}"').undump) + end + end + + def test_dup + for frozen in [ false, true ] + a = S("hello") + a.freeze if frozen + b = a.dup + + assert_equal(a, b) + assert_not_same(a, b) + assert_not_predicate(b, :frozen?) + end + end + + class StringWithIVSet < String + def set_iv + @foo = 1 + end + end + + def test_ivar_set_after_frozen_dup + str = StringWithIVSet.new.freeze + str.dup.set_iv + assert_raise(FrozenError) { str.set_iv } + end + + def test_each + verbose, $VERBOSE = $VERBOSE, nil + + save = $/ + $/ = "\n" + res=[] + S("hello\nworld").lines.each {|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"), 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 + $VERBOSE = verbose + end + + def test_each_byte + s = S("ABC") + + res = [] + 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 + + res = [] + assert_equal s.object_id, s.bytes {|x| res << x }.object_id + assert_equal(65, res[0]) + assert_equal(66, res[1]) + assert_equal(67, res[2]) + s = S("ABC") + res = [] + assert_same s, s.bytes {|x| res << x } + assert_equal [65, 66, 67], res + end + + def test_each_codepoint + # 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 + + res = [] + assert_equal s.object_id, s.codepoints {|x| res << x }.object_id + assert_equal(0x3042, res[0]) + assert_equal(0x3044, res[1]) + assert_equal(0x3046, res[2]) + s = S("ABC") + res = [] + assert_same s, s.codepoints {|x| res << x } + assert_equal [65, 66, 67], res + end + + def test_each_char + 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 + + res = [] + assert_equal s.object_id, s.chars {|x| res << x }.object_id + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) + end + + def test_each_grapheme_cluster + [ + "\u{0D 0A}", + "\u{20 200d}", + "\u{600 600}", + "\u{600 20}", + "\u{261d 1F3FB}", + "\u{1f600}", + "\u{20 308}", + "\u{1F477 1F3FF 200D 2640 FE0F}", + "\u{1F468 200D 1F393}", + "\u{1F46F 200D 2642 FE0F}", + "\u{1f469 200d 2764 fe0f 200d 1f469}", + ].each do |g| + assert_equal [g], g.each_grapheme_cluster.to_a + assert_equal 1, g.each_grapheme_cluster.size + end + + [ + ["\u{a 324}", ["\u000A", "\u0324"]], + ["\u{d 324}", ["\u000D", "\u0324"]], + ["abc", ["a", "b", "c"]], + ].each do |str, grapheme_clusters| + assert_equal grapheme_clusters, str.each_grapheme_cluster.to_a + assert_equal grapheme_clusters.size, str.each_grapheme_cluster.size + end + + 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 + 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"], S("abc").b.grapheme_clusters + + s = S("ABC").b + res = [] + assert_same s, s.grapheme_clusters {|x| res << x } + assert_equal(3, res.size) + assert_equal("A", res[0]) + assert_equal("B", res[1]) + assert_equal("C", res[2]) + end + + def test_grapheme_clusters_memory_leak + assert_no_memory_leak([], "", "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #todo]", rss: true) + begin; + str = "hello world".encode(Encoding::UTF_32LE) + + 10_000.times do + str.grapheme_clusters + end + end; + end + + def test_byteslice_grapheme_clusters + string = "안녕" + assert_equal(["안"], string.byteslice(0,4).grapheme_clusters) + end + + def test_each_line + verbose, $VERBOSE = $VERBOSE, nil + + save = $/ + $/ = "\n" + res=[] + 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").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").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 + S("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 + S("\n\u0100").each_line("\n") {} + end + ensure + $/ = save + $VERBOSE = verbose + 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\n").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world\n"), res[1]) + + res = [] + S("hello\r\n\r\nworld\r\n").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world\r\n"), res[1]) + + res = [] + S("hello\r\n\n\nworld").each_line(S(''), chomp: true) {|x| res << x} + assert_equal(S("hello"), res[0]) + assert_equal(S("world"), res[1]) + + res = [] + S("hello!world").each_line(S('!'), chomp: true) {|x| res << x} + 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 + S("foo\nbar").each_line(nil, chomp: true) {|s2| s = s2 } + assert_equal("foo\nbar", s) + + assert_equal "hello", S("hello\nworld").each_line(chomp: true).next + 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) + + res = [] + assert_equal s.object_id, s.lines {|x| res << x }.object_id + assert_equal(S("hello\n"), res[0]) + assert_equal(S("world"), res[1]) + end + + def test_empty? + 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")]) + assert_send([S("hello"), :end_with?, S("")]) + assert_not_send([S("hello"), :end_with?]) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {S("str").end_with? :not_convertible_to_string} + end + + def test_eql? + a = S("hello") + assert_operator(a, :eql?, S("hello")) + assert_operator(a, :eql?, a) + end + + def test_gsub + assert_equal(S("h*ll*"), S("hello").gsub(/[aeiou]/, S('*'))) + assert_equal(S("h<e>ll<o>"), S("hello").gsub(/([aeiou])/, S('<\1>'))) + assert_equal(S("h e l l o "), + 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>'))) + + assert_equal("z", S("abc").gsub(/./, "a" => "z"), "moved from btest/knownbug") + + assert_raise(ArgumentError) { S("foo").gsub } + end + + def test_gsub_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress { assert_equal(S("h<e>ll<o>"), S("hello").gsub(/([aeiou])/, S('<\1>'))) } + 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 + a.gsub!(/[aeiou]/, S('*')) + assert_equal(S("h*ll*"), a) + assert_equal(S("hello"), b) + + a = S("hello") + a.gsub!(/([aeiou])/, S('<\1>')) + assert_equal(S("h<e>ll<o>"), a) + + a = S("hello") + a.gsub!(/./) { |s| s[0].to_s + S(' ')} + assert_equal(S("h e l l o "), a) + + a = S("hello") + a.gsub!(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 } + assert_equal(S("HELL-o"), a) + + a = S("hello") + assert_nil(a.sub!(S('X'), S('Y'))) + end + + def test_gsub_bang_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + a = S("hello") + a.gsub!(/([aeiou])/, S('<\1>')) + assert_equal(S("h<e>ll<o>"), a) + end + end + + def test_sub_hash + assert_equal('azc', S('abc').sub(/b/, "b" => "z")) + assert_equal('ac', S('abc').sub(/b/, {})) + assert_equal('a1c', S('abc').sub(/b/, "b" => 1)) + assert_equal('aBc', S('abc').sub(/b/, Hash.new {|h, k| k.upcase })) + assert_equal('a[\&]c', S('abc').sub(/b/, "b" => '[\&]')) + assert_equal('aBcabc', S('abcabc').sub(/b/, Hash.new {|h, k| h[k] = k.upcase })) + assert_equal('aBcdef', S('abcdef').sub(/de|b/, "b" => "B", "de" => "DE")) + end + + def test_gsub_hash + assert_equal('azc', S('abc').gsub(/b/, "b" => "z")) + assert_equal('ac', S('abc').gsub(/b/, {})) + assert_equal('a1c', S('abc').gsub(/b/, "b" => 1)) + assert_equal('aBc', S('abc').gsub(/b/, Hash.new {|h, k| k.upcase })) + assert_equal('a[\&]c', S('abc').gsub(/b/, "b" => '[\&]')) + assert_equal('aBcaBc', S('abcabc').gsub(/b/, Hash.new {|h, k| h[k] = k.upcase })) + assert_equal('aBcDEf', S('abcdef').gsub(/de|b/, "b" => "B", "de" => "DE")) + end + + def test_hash + assert_equal(S("hello").hash, S("hello").hash) + 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) + assert_equal(S("").hash, S("".encode(Encoding::UTF_32BE)).hash) + h1, h2 = ["\x80", "\x81"].map {|c| c.b.hash ^ c.hash} + assert_not_equal(h1, h2) + end + + def test_hex + assert_equal(255, S("0xff").hex) + assert_equal(-255, S("-0xff").hex) + assert_equal(255, S("ff").hex) + assert_equal(-255, S("-ff").hex) + assert_equal(0, S("-ralph").hex) + assert_equal(-15, S("-fred").hex) + assert_equal(15, S("fred").hex) + end + + def test_include? + 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 + assert_index(0, S("hello"), ?h) + assert_index(1, S("hello"), S("ell")) + assert_index(2, S("hello"), /ll./) + + assert_index(3, S("hello"), ?l, 3) + assert_index(3, S("hello"), S("l"), 3) + assert_index(3, S("hello"), /l./, 3) + + assert_index(nil, S("hello"), ?z, 3) + assert_index(nil, S("hello"), S("z"), 3) + assert_index(nil, S("hello"), /z./, 3) + + assert_index(nil, S("hello"), ?z) + assert_index(nil, S("hello"), S("z")) + assert_index(nil, S("hello"), /z./) + + assert_index(0, S(""), S("")) + assert_index(0, S(""), //) + assert_index(nil, S(""), S("hello")) + assert_index(nil, S(""), /hello/) + assert_index(0, S("hello"), S("")) + assert_index(0, S("hello"), //) + + s = S("long") * 1000 << "x" + assert_index(nil, s, S("y")) + assert_index(4 * 1000, s, S("x")) + s << "yx" + assert_index(4 * 1000, s, S("x")) + assert_index(4 * 1000, s, S("xyx")) + + o = Object.new + def o.to_str; "bar"; end + assert_index(3, S("foobarbarbaz"), o) + assert_raise(TypeError) { S("foo").index(Object.new) } + + assert_index(nil, S("foo"), //, -100) + assert_index(nil, S("foo"), //, 4) + + assert_index(2, S("abcdbce"), /b\Kc/) + + assert_index(0, S("ã“ã‚“ã«ã¡ã¯"), ?ã“) + assert_index(1, S("ã“ã‚“ã«ã¡ã¯"), S("ã‚“ã«ã¡")) + assert_index(2, S("ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./) + + assert_index(0, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 0) + assert_index(2, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 1) + assert_index(2, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 2) + assert_index(nil, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 3) + end + + def test_insert + assert_equal("Xabcd", S("abcd").insert(0, 'X')) + assert_equal("abcXd", S("abcd").insert(3, 'X')) + assert_equal("abcdX", S("abcd").insert(4, 'X')) + assert_equal("abXcd", S("abcd").insert(-3, 'X')) + assert_equal("abcdX", S("abcd").insert(-1, 'X')) + end + + def test_intern + assert_equal(:koala, S("koala").intern) + assert_not_equal(:koala, S("Koala").intern) + end + + def test_length + assert_equal(0, S("").length) + assert_equal(4, S("1234").length) + assert_equal(6, S("1234\r\n").length) + assert_equal(7, S("\0011234\r\n").length) + end + + def test_ljust + assert_equal(S("hello"), S("hello").ljust(4)) + assert_equal(S("hello "), S("hello").ljust(11)) + assert_equal(S("ababababab"), S("").ljust(10, "ab"), Bug2463) + assert_equal(S("abababababa"), S("").ljust(11, "ab"), Bug2463) + end + + def test_next + assert_equal(S("abd"), S("abc").next) + assert_equal(S("z"), S("y").next) + assert_equal(S("aaa"), S("zz").next) + + assert_equal(S("124"), S("123").next) + assert_equal(S("1000"), S("999").next) + + assert_equal(S("2000aaa"), S("1999zzz").next) + 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! + a = S("abc") + b = a.dup + assert_equal(S("abd"), a.next!) + assert_equal(S("abd"), a) + assert_equal(S("abc"), b) + + a = S("y") + assert_equal(S("z"), a.next!) + assert_equal(S("z"), a) + + a = S("zz") + assert_equal(S("aaa"), a.next!) + assert_equal(S("aaa"), a) + + a = S("123") + assert_equal(S("124"), a.next!) + assert_equal(S("124"), a) + + a = S("999") + assert_equal(S("1000"), a.next!) + assert_equal(S("1000"), a) + + a = S("1999zzz") + assert_equal(S("2000aaa"), a.next!) + assert_equal(S("2000aaa"), a) + + a = S("ZZZZ999") + assert_equal(S("AAAAA000"), a.next!) + assert_equal(S("AAAAA000"), a) + + 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 + assert_equal(255, S("0377").oct) + assert_equal(255, S("377").oct) + assert_equal(-255, S("-0377").oct) + assert_equal(-255, S("-377").oct) + assert_equal(0, S("OO").oct) + assert_equal(24, S("030OO").oct) + end + + def test_replace + a = S("foo") + assert_equal(S("f"), a.replace(S("f"))) + + a = S("foo") + assert_equal(S("foobar"), a.replace(S("foobar"))) + + a = S("foo") + b = a.replace(S("xyz")) + assert_equal(S("xyz"), b) + + s = S("foo") * 100 + s2 = ("bar" * 100).dup + s.replace(s2) + assert_equal(s2, s) + + s2 = [S("foo")].pack("p") + s.replace(s2) + assert_equal(s2, s) + + fs = S("").freeze + assert_raise(FrozenError) { fs.replace("a") } + assert_raise(FrozenError) { fs.replace(fs) } + assert_raise(ArgumentError) { fs.replace() } + assert_raise(FrozenError) { fs.replace(42) } + end + + def test_reverse + assert_equal(S("beta"), S("ateb").reverse) + assert_equal(S("madamImadam"), S("madamImadam").reverse) + + a=S("beta") + assert_equal(S("ateb"), a.reverse) + assert_equal(S("beta"), a) + end + + def test_reverse! + a = S("beta") + b = a.dup + assert_equal(S("ateb"), a.reverse!) + assert_equal(S("ateb"), a) + assert_equal(S("beta"), b) + + assert_equal(S("madamImadam"), S("madamImadam").reverse!) + + a = S("madamImadam") + assert_equal(S("madamImadam"), a.reverse!) # ?? + assert_equal(S("madamImadam"), a) + end + + def test_rindex + assert_rindex(3, S("hello"), ?l) + assert_rindex(6, S("ell, hello"), S("ell")) + assert_rindex(7, S("ell, hello"), /ll./) + + assert_rindex(3, S("hello,lo"), ?l, 3) + assert_rindex(3, S("hello,lo"), S("l"), 3) + assert_rindex(3, S("hello,lo"), /l./, 3) + + assert_rindex(nil, S("hello"), ?z, 3) + assert_rindex(nil, S("hello"), S("z"), 3) + assert_rindex(nil, S("hello"), /z./, 3) + + assert_rindex(nil, S("hello"), ?z) + assert_rindex(nil, S("hello"), S("z")) + assert_rindex(nil, S("hello"), /z./) + + assert_rindex(5, S("hello"), S("")) + assert_rindex(5, S("hello"), S(""), 5) + assert_rindex(4, S("hello"), S(""), 4) + assert_rindex(0, S("hello"), S(""), 0) + + o = Object.new + def o.to_str; "bar"; end + assert_rindex(6, S("foobarbarbaz"), o) + assert_raise(TypeError) { S("foo").rindex(Object.new) } + + assert_rindex(nil, S("foo"), //, -100) + + m = assert_rindex(3, S("foo"), //) + assert_equal([3, 3], m.offset(0)) + assert_rindex(3, S("foo"), //, 4) + + assert_rindex(5, S("abcdbce"), /b\Kc/) + + assert_rindex(2, S("ã“ã‚“ã«ã¡ã¯"), ?ã«) + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯")) + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./) + + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 7) + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -2) + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 6) + assert_rindex(6, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -3) + assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 5) + assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -4) + assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 1) + assert_rindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 0) + + assert_rindex(0, S("ã“ã‚“ã«ã¡ã¯"), S("ã“ã‚“ã«ã¡ã¯")) + assert_rindex(nil, S("ã“ã‚“ã«ã¡"), S("ã“ã‚“ã«ã¡ã¯")) + assert_rindex(nil, S("ã“"), S("ã“ã‚“ã«ã¡ã¯")) + assert_rindex(nil, S(""), S("ã“ã‚“ã«ã¡ã¯")) + end + + def test_rjust + assert_equal(S("hello"), S("hello").rjust(4)) + assert_equal(S(" hello"), S("hello").rjust(11)) + assert_equal(S("ababababab"), S("").rjust(10, "ab"), Bug2463) + assert_equal(S("abababababa"), S("").rjust(11, "ab"), Bug2463) + end + + def test_scan + a = S("cruel world") + assert_equal([S("cruel"), S("world")],a.scan(/\w+/)) + assert_equal([S("cru"), S("el "), S("wor")],a.scan(/.../)) + assert_equal([[S("cru")], [S("el ")], [S("wor")]],a.scan(/(...)/)) + + res = [] + a.scan(/\w+/) { |w| res << w } + assert_equal([S("cruel"), S("world") ],res) + + res = [] + a.scan(/.../) { |w| res << w } + assert_equal([S("cru"), S("el "), S("wor")],res) + + res = [] + a.scan(/(...)/) { |w| res << w } + assert_equal([[S("cru")], [S("el ")], [S("wor")]],res) + + /h/ =~ a + a.scan(/x/) + assert_nil($~) + + /h/ =~ a + a.scan('x') + assert_nil($~) + + assert_equal(%w[1 2 3], S("a1 a2 a3").scan(/a\K./)) + end + + def test_scan_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress { assert_equal([["1a"], ["2b"], ["3c"]], S("1a2b3c").scan(/(\d.)/)) } + end + + def test_scan_segv + bug19159 = '[Bug #19159]' + assert_nothing_raised(Exception, bug19159) do + ObjectSpace.each_object(MatchData).to_a + "".scan(//) + ObjectSpace.each_object(MatchData).to_a.inspect + end + end + + def test_size + assert_equal(0, S("").size) + assert_equal(4, S("1234").size) + assert_equal(6, S("1234\r\n").size) + assert_equal(7, S("\0011234\r\n").size) + end + + def test_slice + assert_equal(?A, S("AooBar").slice(0)) + assert_equal(?B, S("FooBaB").slice(-1)) + assert_nil(S("FooBar").slice(6)) + assert_nil(S("FooBar").slice(-7)) + + assert_equal(S("Foo"), S("FooBar").slice(0,3)) + assert_equal(S(S("Bar")), S("FooBar").slice(-3,3)) + assert_nil(S("FooBar").slice(7,2)) # Maybe should be six? + assert_nil(S("FooBar").slice(-7,10)) + + assert_equal(S("Foo"), S("FooBar").slice(0..2)) + assert_equal(S("Bar"), S("FooBar").slice(-3..-1)) + assert_equal(S(""), S("FooBar").slice(6..2)) + assert_nil(S("FooBar").slice(-10..-7)) + + assert_equal(S("Foo"), S("FooBar").slice(/^F../)) + assert_equal(S("Bar"), S("FooBar").slice(/..r$/)) + assert_nil(S("FooBar").slice(/xyzzy/)) + assert_nil(S("FooBar").slice(/plugh/)) + + assert_equal(S("Foo"), S("FooBar").slice(S("Foo"))) + 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! + a = S("AooBar") + b = a.dup + assert_equal(?A, a.slice!(0)) + assert_equal(S("ooBar"), a) + assert_equal(S("AooBar"), b) + + a = S("FooBar") + assert_equal(?r,a.slice!(-1)) + assert_equal(S("FooBa"), a) + + a = S("FooBar") + assert_nil( a.slice!(6) ) + assert_nil( a.slice!(6r) ) + assert_equal(S("FooBar"), a) + + assert_nil( a.slice!(-7) ) + assert_equal(S("FooBar"), a) + + a = S("FooBar") + assert_equal(S("Foo"), a.slice!(0,3)) + assert_equal(S("Bar"), a) + + a = S("FooBar") + assert_equal(S("Bar"), a.slice!(-3,3)) + assert_equal(S("Foo"), a) + + a=S("FooBar") + assert_nil(a.slice!(7,2)) # Maybe should be six? + assert_equal(S("FooBar"), a) + assert_nil(a.slice!(-7,10)) + assert_equal(S("FooBar"), a) + + a=S("FooBar") + assert_equal(S("Foo"), a.slice!(0..2)) + assert_equal(S("Bar"), a) + + a=S("FooBar") + assert_equal(S("Bar"), a.slice!(-3..-1)) + assert_equal(S("Foo"), a) + + a=S("FooBar") + assert_equal(S(""), a.slice!(6..2)) + assert_equal(S("FooBar"), a) + assert_nil(a.slice!(-10..-7)) + assert_equal(S("FooBar"), a) + + a=S("FooBar") + assert_equal(S("Foo"), a.slice!(/^F../)) + assert_equal(S("Bar"), a) + + a=S("FooBar") + assert_equal(S("Bar"), a.slice!(/..r$/)) + assert_equal(S("Foo"), a) + + a=S("FooBar") + assert_nil(a.slice!(/xyzzy/)) + assert_equal(S("FooBar"), a) + assert_nil(a.slice!(/plugh/)) + assert_equal(S("FooBar"), a) + + a=S("FooBar") + assert_equal(S("Foo"), a.slice!(S("Foo"))) + assert_equal(S("Bar"), a) + + a=S("FooBar") + assert_equal(S("Bar"), a.slice!(S("Bar"))) + assert_equal(S("Foo"), a) + + a = S("foo") + assert_raise(ArgumentError) { a.slice! } + end + + def test_split + 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(" "))) + + assert_equal([S(" a "), S(" b "), S(" c ")], S(" a | b | c ").split(S("|"))) + + assert_equal([S("a"), S("b"), S("c")], S("aXXbXXcXX").split(/X./)) + + assert_equal([S("a"), S("b"), S("c")], S("abc").split(//)) + + assert_equal([S("a|b|c")], S("a|b|c").split(S('|'), 1)) + + assert_equal([S("a"), S("b|c")], S("a|b|c").split(S('|'), 2)) + assert_equal([S("a"), S("b"), S("c")], S("a|b|c").split(S('|'), 3)) + + assert_equal([S("a"), S("b"), S("c"), S("")], S("a|b|c|").split(S('|'), -1)) + assert_equal([S("a"), S("b"), S("c"), S(""), S("")], S("a|b|c||").split(S('|'), -1)) + + assert_equal([S("a"), S(""), S("b"), S("c")], S("a||b|c|").split(S('|'))) + assert_equal([S("a"), S(""), S("b"), S("c"), S("")], S("a||b|c|").split(S('|'), -1)) + + assert_equal([], S("").split(//, 1)) + ensure + EnvUtil.suppress_warning {$; = 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 = []; S("").split(//, 1) {|s| result << s} + assert_equal([], result) + + result = []; S("aaa,bbb,ccc,ddd").split(/,/) {|s| result << s.gsub(/./, "A")} + assert_equal(["AAA"]*4, result) + + s = S("abc ") * 20 + assert_raise(RuntimeError) { + 10.times do + s.split {s.prepend("xxx" * 100)} + end + } + ensure + EnvUtil.suppress_warning {$; = fs} + end + + def test_fs + return unless @cls == String + + begin + fs = $; + assert_deprecated_warning(/non-nil '\$;'/) {$; = "x"} + assert_raise_with_message(TypeError, /\$;/) {$; = []} + ensure + EnvUtil.suppress_warning {$; = fs} + end + name = "\u{5206 5217}" + assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") + do; + alias $#{name} $; + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } + assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } + end; + end + + def test_fs_gc + return unless @cls == String + + assert_separately(%W[-W0], "#{<<~"begin;"}\n#{<<~'end;'}") + bug = '[ruby-core:79582] $; must not be GCed' + begin; + $; = " " + $a = nil + alias $; $a + alias $-F $a + GC.start + assert_equal([], "".split, bug) + end; + end + + def test_split_encoding + bug6206 = '[ruby-dev:45441]' + Encoding.list.each do |enc| + next unless enc.ascii_compatible? + s = S("a:".force_encoding(enc)) + assert_equal([enc]*2, s.split(":", 2).map(&:encoding), bug6206) + end + end + + def test_split_wchar + bug8642 = '[ruby-core:56036] [Bug #8642]' + 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_split_lookbehind + assert_equal([S("ab"), S("d")], S("abcd").split(/(?<=b)c/)) + assert_equal([S("ab"), S("d")], S("abcd").split(/b\Kc/)) + end + + def test_squeeze + assert_equal(S("abc"), S("aaabbbbccc").squeeze) + assert_equal(S("aa bb cc"), S("aa bb cc").squeeze(S(" "))) + assert_equal(S("BxTyWz"), S("BxxxTyyyWzzzzz").squeeze(S("a-z"))) + end + + def test_squeeze! + a = S("aaabbbbccc") + b = a.dup + assert_equal(S("abc"), a.squeeze!) + assert_equal(S("abc"), a) + assert_equal(S("aaabbbbccc"), b) + + a = S("aa bb cc") + assert_equal(S("aa bb cc"), a.squeeze!(S(" "))) + assert_equal(S("aa bb cc"), a) + + a = S("BxxxTyyyWzzzzz") + assert_equal(S("BxTyWz"), a.squeeze!(S("a-z"))) + assert_equal(S("BxTyWz"), a) + + a=S("The quick brown fox") + 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")]) + assert_send([S("\xFF\xFE"), :start_with?, S("\xFF")]) + assert_send([S("hello\xBE"), :start_with?, S("hello")]) + assert_not_send([S("\u{c4}"), :start_with?, S("\xC3")]) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {S("str").start_with? :not_convertible_to_string} + end + + def test_start_with_regexp + assert_equal(true, S("hello").start_with?(/hel/)) + assert_equal("hel", $&) + assert_equal(false, S("hello").start_with?(/el/)) + assert_nil($&) + end + + def test_start_with_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20653]", rss: true) + regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001) + str = "a" * 1_000_000 + "x" + + code = proc do + str.start_with?(regex) + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + + def test_strip + assert_equal(S("x"), S(" x ").strip) + assert_equal(S("x"), S(" \n\r\t x \t\r\n\n ").strip) + assert_equal(S("x"), S("\x00x\x00").strip) + + assert_equal("0b0 ".force_encoding("UTF-16BE"), + S("\x00 0b0 ").force_encoding("UTF-16BE").strip) + assert_equal("0\x000b0 ".force_encoding("UTF-16BE"), + S("0\x000b0 ").force_encoding("UTF-16BE").strip) + end + + def test_strip! + a = S(" x ") + b = a.dup + assert_equal(S("x") ,a.strip!) + assert_equal(S("x") ,a) + assert_equal(S(" x "), b) + + a = S(" \n\r\t x \t\r\n\n ") + assert_equal(S("x"), a.strip!) + assert_equal(S("x"), a) + + a = S("\x00x\x00") + assert_equal(S("x"), a.strip!) + assert_equal(S("x"), a) + + a = S("x") + assert_nil(a.strip!) + assert_equal(S("x") ,a) + end + + def test_strip_with_selectors + assert_equal(S("abc"), S("---abc+++").strip("-+")) + assert_equal(S("abc"), S("+++abc---").strip("-+")) + assert_equal(S("abc"), S("+-+abc-+-").strip("-+")) + assert_equal(S(""), S("---+++").strip("-+")) + assert_equal(S("abc "), S("---abc ").strip("-")) + assert_equal(S(" abc"), S(" abc+++").strip("+")) + + # Test with multibyte characters + assert_equal(S("abc"), S("ã‚ã‚ã‚abcã„ã„ã„").strip("ã‚ã„")) + assert_equal(S("abc"), S("ã„ã„ã„abcã‚ã‚ã‚").strip("ã‚ã„")) + + # Test with NUL characters + assert_equal(S("abc\0"), S("---abc\0--").strip("-")) + assert_equal(S("\0abc"), S("--\0abc---").strip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").strip("-+")) + assert_equal(S("abc"), S("abc").strip("")) + + # Test with range + assert_equal(S("abc"), S("012abc345").strip("0-9")) + assert_equal(S("abc"), S("012abc345").strip("^a-z")) + + # Test with multiple selectors + assert_equal(S("4abc56"), S("01234abc56789").strip("0-9", "^4-6")) + end + + def test_strip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("+++abc---") + assert_equal(S("abc"), a.strip!("-+")) + assert_equal(S("abc"), a) + + a = S("abc") + assert_nil(a.strip!("-+")) + assert_equal(S("abc"), a) + + # Test with multibyte characters + a = S("ã‚ã‚ã‚abcã„ã„ã„") + assert_equal(S("abc"), a.strip!("ã‚ã„")) + assert_equal(S("abc"), a) + end + + def test_lstrip_with_selectors + assert_equal(S("abc+++"), S("---abc+++").lstrip("-")) + assert_equal(S("abc---"), S("+++abc---").lstrip("+")) + assert_equal(S("abc"), S("---abc").lstrip("-")) + assert_equal(S(""), S("---").lstrip("-")) + + # Test with multibyte characters + assert_equal(S("abcã„ã„ã„"), S("ã‚ã‚ã‚abcã„ã„ã„").lstrip("ã‚")) + + # Test with NUL characters + assert_equal(S("\0abc+++"), S("--\0abc+++").lstrip("-")) + + # Test without modification + assert_equal(S("abc"), S("abc").lstrip("-")) + + # Test with range + assert_equal(S("abc345"), S("012abc345").lstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("4abc56789"), S("01234abc56789").lstrip("0-9", "^4-6")) + end + + def test_lstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("abc+++"), a.lstrip!("-")) + assert_equal(S("abc+++"), a) + + a = S("abc") + assert_nil(a.lstrip!("-")) + assert_equal(S("abc"), a) + end + + def test_rstrip_with_selectors + assert_equal(S("---abc"), S("---abc+++").rstrip("+")) + assert_equal(S("+++abc"), S("+++abc---").rstrip("-")) + assert_equal(S("abc"), S("abc+++").rstrip("+")) + assert_equal(S(""), S("+++").rstrip("+")) + + # Test with multibyte characters + assert_equal(S("ã‚ã‚ã‚abc"), S("ã‚ã‚ã‚abcã„ã„ã„").rstrip("ã„")) + + # Test with NUL characters + assert_equal(S("---abc\0"), S("---abc\0++").rstrip("+")) + + # Test without modification + assert_equal(S("abc"), S("abc").rstrip("-")) + + # Test with range + assert_equal(S("012abc"), S("012abc345").rstrip("0-9")) + + # Test with multiple selectors + assert_equal(S("01234abc56"), S("01234abc56789").rstrip("0-9", "^4-6")) + end + + def test_rstrip_bang_with_chars + a = S("---abc+++") + assert_equal(S("---abc"), a.rstrip!("+")) + assert_equal(S("---abc"), a) + + a = S("abc") + assert_nil(a.rstrip!("+")) + assert_equal(S("abc"), a) + end + + def test_sub + assert_equal(S("h*llo"), S("hello").sub(/[aeiou]/, S('*'))) + assert_equal(S("h<e>llo"), S("hello").sub(/([aeiou])/, S('<\1>'))) + assert_equal(S("h ello"), S("hello").sub(/./) { + |s| s[0].to_s + S(' ')}) + 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\\')) + assert_equal(S("ababa"), S("ababa").sub(/(b)/, '\1')) + assert_equal(S("ababa"), S("ababa").sub(/(b)/, '\\1')) + assert_equal(S("a\\1aba"), S("ababa").sub(/(b)/, '\\\1')) + assert_equal(S("a\\1aba"), S("ababa").sub(/(b)/, '\\\\1')) + assert_equal(S("a\\baba"), S("ababa").sub(/(b)/, '\\\\\1')) + + assert_equal(S("a--ababababababababab"), + S("abababababababababab").sub(/(b)/, '-\9-')) + assert_equal(S("1-b-0"), + S("1b2b3b4b5b6b7b8b9b0"). + sub(/(b).(b).(b).(b).(b).(b).(b).(b).(b)/, '-\9-')) + assert_equal(S("1-b-0"), + S("1b2b3b4b5b6b7b8b9b0"). + sub(/(b).(b).(b).(b).(b).(b).(b).(b).(b)/, '-\\9-')) + assert_equal(S("1-\\9-0"), + S("1b2b3b4b5b6b7b8b9b0"). + sub(/(b).(b).(b).(b).(b).(b).(b).(b).(b)/, '-\\\9-')) + assert_equal(S("k"), + S("1a2b3c4d5e6f7g8h9iAjBk"). + sub(/.(.).(.).(.).(.).(.).(.).(.).(.).(.).(.).(.)/, '\+')) + + assert_equal(S("ab\\aba"), S("ababa").sub(/b/, '\&\\')) + assert_equal(S("ababa"), S("ababa").sub(/b/, '\&')) + assert_equal(S("ababa"), S("ababa").sub(/b/, '\\&')) + assert_equal(S("a\\&aba"), S("ababa").sub(/b/, '\\\&')) + assert_equal(S("a\\&aba"), S("ababa").sub(/b/, '\\\\&')) + assert_equal(S("a\\baba"), S("ababa").sub(/b/, '\\\\\&')) + + o = Object.new + def o.to_str; "bar"; end + assert_equal("fooBARbaz", S("foobarbaz").sub(o, "BAR")) + + assert_raise(TypeError) { S("foo").sub(Object.new, "") } + + assert_raise(ArgumentError) { S("foo").sub } + + assert_raise(IndexError) { "foo"[/(?:(o$)|(x))/, 2] = 'bar' } + + o = Object.new + def o.to_s; self; end + assert_match(/^foo#<Object:0x.*>baz$/, S("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) { + S('hello').gsub('hello', '\k<oops>') + } + end + + def test_sub_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + m = /&(?<foo>.*?);/.match(S("aaa & yyy")) + assert_equal("amp", m["foo"]) + + assert_equal("aaa [amp] yyy", S("aaa & yyy").sub(/&(?<foo>.*?);/, S('[\k<foo>]'))) + end + end + + def test_sub! + a = S("hello") + b = a.dup + a.sub!(/[aeiou]/, S('*')) + assert_equal(S("h*llo"), a) + assert_equal(S("hello"), b) + + a = S("hello") + a.sub!(/([aeiou])/, S('<\1>')) + assert_equal(S("h<e>llo"), a) + + a = S("hello") + a.sub!(/./) { |s| s[0].to_s + S(' ')} + assert_equal(S("h ello"), a) + + a = S("hello") + a.sub!(/(hell)(.)/) { |s| $1.upcase + S('-') + $2 } + assert_equal(S("HELL-o"), a) + + a=S("hello") + assert_nil(a.sub!(/X/, S('Y'))) + + 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 + assert_equal(S("abd"), S("abc").succ) + assert_equal(S("z"), S("y").succ) + assert_equal(S("aaa"), S("zz").succ) + + assert_equal(S("124"), S("123").succ) + assert_equal(S("1000"), S("999").succ) + assert_equal(S("2.000"), S("1.999").succ) + + assert_equal(S("No.10"), S("No.9").succ) + assert_equal(S("2000aaa"), S("1999zzz").succ) + assert_equal(S("AAAAA000"), S("ZZZZ999").succ) + assert_equal(S("*+"), S("**").succ) + + assert_equal("abce", S("abcd").succ) + assert_equal("THX1139", S("THX1138").succ) + assert_equal("<\<koalb>>", S("<\<koala>>").succ) + assert_equal("2000aaa", S("1999zzz").succ) + assert_equal("AAAA0000", S("ZZZ9999").succ) + assert_equal("**+", S("***").succ) + + assert_equal("!", S(" ").succ) + assert_equal("", S("").succ) + + bug = '[ruby-core:83062] [Bug #13952]' + s = S("\xff").b + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.succ, :ascii_only?, bug) + end + + def test_succ! + a = S("abc") + b = a.dup + assert_equal(S("abd"), a.succ!) + assert_equal(S("abd"), a) + assert_equal(S("abc"), b) + + a = S("y") + assert_equal(S("z"), a.succ!) + assert_equal(S("z"), a) + + a = S("zz") + assert_equal(S("aaa"), a.succ!) + assert_equal(S("aaa"), a) + + a = S("123") + assert_equal(S("124"), a.succ!) + assert_equal(S("124"), a) + + a = S("999") + assert_equal(S("1000"), a.succ!) + assert_equal(S("1000"), a) + + a = S("1999zzz") + assert_equal(S("2000aaa"), a.succ!) + assert_equal(S("2000aaa"), a) + + a = S("ZZZZ999") + assert_equal(S("AAAAA000"), a.succ!) + assert_equal(S("AAAAA000"), a) + + a = S("**") + assert_equal(S("*+"), a.succ!) + assert_equal(S("*+"), a) + + a = S("No.9") + 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", S("zzzzzzzzzzz").succ!) + assert_equal("aaaaaaaaaaaaaaaaaaaaaaaa", S("zzzzzzzzzzzzzzzzzzzzzzz").succ!) + end + + def test_sum + n = S("\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001") + assert_equal(15, n.sum) + n += S("\001") + assert_equal(16, n.sum(17)) + n[0] = 2.chr + assert_not_equal(15, n.sum) + assert_equal(17, n.sum(0)) + assert_equal(17, n.sum(-1)) + end + + def check_sum(str, bits=16) + sum = 0 + str.each_byte {|c| sum += c} + sum = sum & ((1 << bits) - 1) if bits != 0 + assert_equal(sum, str.sum(bits)) + end + + def test_sum_2 + assert_equal(0, S("").sum) + assert_equal(294, S("abc").sum) + check_sum("abc") + check_sum("\x80") + -3.upto(70) {|bits| + check_sum("xyz", bits) + } + end + + def test_sum_long + s8421505 = "\xff" * 8421505 + assert_equal(127, s8421505.sum(31)) + assert_equal(2147483775, s8421505.sum(0)) + s16843010 = ("\xff" * 16843010) + assert_equal(254, s16843010.sum(32)) + assert_equal(4294967550, s16843010.sum(0)) + end + + def test_swapcase + assert_equal(S("hi&LOW"), S("HI&low").swapcase) + s = S("") + assert_not_same(s, s.swapcase) + end + + def test_swapcase! + a = S("hi&LOW") + b = a.dup + assert_equal(S("HI&low"), a.swapcase!) + assert_equal(S("HI&low"), a) + assert_equal(S("hi&LOW"), b) + + a = S("$^#^%$#!!") + assert_nil(a.swapcase!) + assert_equal(S("$^#^%$#!!"), a) + end + + def test_to_f + assert_equal(344.3, S("344.3").to_f) + assert_equal(5.9742e24, S("5.9742e24").to_f) + assert_equal(98.6, S("98.6 degrees").to_f) + assert_equal(0.0, S("degrees 100.0").to_f) + assert_equal([ 0.0].pack('G'), [S(" 0.0").to_f].pack('G')) + assert_equal([-0.0].pack('G'), [S("-0.0").to_f].pack('G')) + end + + def test_to_i + assert_equal(1480, S("1480ft/sec").to_i) + assert_equal(0, S("speed of sound in water @20C = 1480ft/sec)").to_i) + assert_equal(0, S(" 0").to_i) + assert_equal(0, S("+0").to_i) + assert_equal(0, S("-0").to_i) + assert_equal(0, S("--0").to_i) + assert_equal(16, S("0x10").to_i(0)) + assert_equal(16, S("0X10").to_i(0)) + assert_equal(2, S("0b10").to_i(0)) + assert_equal(2, S("0B10").to_i(0)) + assert_equal(8, S("0o10").to_i(0)) + assert_equal(8, S("0O10").to_i(0)) + assert_equal(10, S("0d10").to_i(0)) + assert_equal(10, S("0D10").to_i(0)) + assert_equal(8, S("010").to_i(0)) + assert_raise(ArgumentError) { S("010").to_i(-10) } + 2.upto(36) {|radix| + assert_equal(radix, S("10").to_i(radix)) + assert_equal(radix**2, S("100").to_i(radix)) + } + assert_raise(ArgumentError) { S("0").to_i(1) } + assert_raise(ArgumentError) { S("0").to_i(37) } + assert_equal(0, S("z").to_i(10)) + assert_equal(12, S("1_2").to_i(10)) + assert_equal(0x40000000, S("1073741824").to_i(10)) + assert_equal(0x4000000000000000, S("4611686018427387904").to_i(10)) + assert_equal(1, S("1__2").to_i(10)) + assert_equal(1, S("1_z").to_i(10)) + + bug6192 = '[ruby-core:43566]' + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16be")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-16le")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32be")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("utf-32le")).to_i} + assert_raise(Encoding::CompatibilityError, bug6192) {S("0".encode("iso-2022-jp")).to_i} + end + + def test_to_s + a = S("me") + assert_equal("me", a.to_s) + assert_equal(a.__id__, a.to_s.__id__) if @cls == String + end + + def test_to_str + a = S("me") + assert_equal("me", a.to_s) + assert_equal(a.__id__, a.to_s.__id__) if @cls == String + + o = Object.new + def o.to_str + "at" + end + assert_equal("meat", a.concat(o)) + + o = Object.new + def o.to_str + foo_bar() + end + assert_match(/foo_bar/, assert_raise(NoMethodError) {a.concat(o)}.message) + end + + def test_tr + assert_equal(S("hippo"), S("hello").tr(S("el"), S("ip"))) + assert_equal(S("*e**o"), S("hello").tr(S("^aeiou"), S("*"))) + assert_equal(S("hal"), S("ibm").tr(S("b-z"), S("a-z"))) + + a = S("abc".force_encoding(Encoding::US_ASCII)) + assert_equal(Encoding::US_ASCII, a.tr(S("z"), S("\u0101")).encoding, '[ruby-core:22326]') + + assert_equal("a".hash, S("a").tr("a", "\u0101").tr("\u0101", "a").hash, '[ruby-core:22328]') + assert_equal(true, S("\u0101").tr("\u0101", "a").ascii_only?) + assert_equal(true, S("\u3041").tr("\u3041", "a").ascii_only?) + assert_equal(false, S("\u3041\u3042").tr("\u3041", "a").ascii_only?) + + bug6156 = '[ruby-core:43335]' + bug13950 = '[ruby-core:83056] [Bug #13950]' + 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) + + assert_equal(S("XYC"), S("ABC").tr("A-AB", "XY")) + end + + def test_tr! + a = S("hello") + b = a.dup + assert_equal(S("hippo"), a.tr!(S("el"), S("ip"))) + assert_equal(S("hippo"), a) + assert_equal(S("hello"),b) + + a = S("hello") + assert_equal(S("*e**o"), a.tr!(S("^aeiou"), S("*"))) + assert_equal(S("*e**o"), a) + + a = S("IBM") + assert_equal(S("HAL"), a.tr!(S("B-Z"), S("A-Z"))) + assert_equal(S("HAL"), a) + + a = S("ibm") + assert_nil(a.tr!(S("B-Z"), S("A-Z"))) + assert_equal(S("ibm"), a) + + a = S("abc".force_encoding(Encoding::US_ASCII)) + assert_nil(a.tr!(S("z"), S("\u0101")), '[ruby-core:22326]') + assert_equal(Encoding::US_ASCII, a.encoding, '[ruby-core:22326]') + + assert_equal(S("XYC"), S("ABC").tr!("A-AB", "XY")) + end + + def test_tr_s + assert_equal(S("hypo"), S("hello").tr_s(S("el"), S("yp"))) + assert_equal(S("h*o"), S("hello").tr_s(S("el"), S("*"))) + assert_equal("a".hash, S("\u0101\u0101").tr_s("\u0101", "a").hash) + assert_equal(true, S("\u3041\u3041").tr("\u3041", "a").ascii_only?) + + assert_equal(S("XYC"), S("ABC").tr_s("A-AB", "XY")) + end + + def test_tr_s! + a = S("hello") + b = a.dup + assert_equal(S("hypo"), a.tr_s!(S("el"), S("yp"))) + assert_equal(S("hypo"), a) + assert_equal(S("hello"), b) + + a = S("hello") + assert_equal(S("h*o"), a.tr_s!(S("el"), S("*"))) + assert_equal(S("h*o"), a) + + assert_equal(S("XYC"), S("ABC").tr_s!("A-AB", "XY")) + end + + def test_unpack + a = [S("cat"), S("wom"), S("x"), S("yy")] + assert_equal(a, S("catwomx yy ").unpack(S("A3A3A3A3"))) + + assert_equal([S("cat")], S("cat \000\000").unpack(S("A*"))) + assert_equal([S("cwx"), S("wx"), S("x"), S("yy")], + S("cwx yy ").unpack(S("A3@1A3@2A3A3"))) + assert_equal([S("cat"), S("wom"), S("x\000\000"), S("yy\000")], + S("catwomx\000\000yy\000").unpack(S("a3a3a3a3"))) + assert_equal([S("cat \000\000")], S("cat \000\000").unpack(S("a*"))) + assert_equal([S("ca")], S("catdog").unpack(S("a2"))) + + assert_equal([S("cat\000\000")], + S("cat\000\000\000\000\000dog").unpack(S("a5"))) + + assert_equal([S("01100001")], S("\x61").unpack(S("B8"))) + assert_equal([S("01100001")], S("\x61").unpack(S("B*"))) + assert_equal([S("0110000100110111")], S("\x61\x37").unpack(S("B16"))) + assert_equal([S("01100001"), S("00110111")], S("\x61\x37").unpack(S("B8B8"))) + assert_equal([S("0110")], S("\x60").unpack(S("B4"))) + + assert_equal([S("01")], S("\x40").unpack(S("B2"))) + + assert_equal([S("01100001")], S("\x86").unpack(S("b8"))) + assert_equal([S("01100001")], S("\x86").unpack(S("b*"))) + + assert_equal([S("0110000100110111")], S("\x86\xec").unpack(S("b16"))) + assert_equal([S("01100001"), S("00110111")], S("\x86\xec").unpack(S("b8b8"))) + + assert_equal([S("0110")], S("\x06").unpack(S("b4"))) + assert_equal([S("01")], S("\x02").unpack(S("b2"))) + + assert_equal([ 65, 66, 67 ], S("ABC").unpack(S("C3"))) + assert_equal([ 255, 66, 67 ], S("\377BC").unpack("C*")) + assert_equal([ 65, 66, 67 ], S("ABC").unpack("c3")) + assert_equal([ -1, 66, 67 ], S("\377BC").unpack("c*")) + + + assert_equal([S("4142"), S("0a"), S("1")], S("AB\n\x10").unpack(S("H4H2H1"))) + assert_equal([S("1424"), S("a0"), S("2")], S("AB\n\x02").unpack(S("h4h2h1"))) + + assert_equal([S("abc\002defcat\001"), S(""), S("")], + S("abc=02def=\ncat=\n=01=\n").unpack(S("M9M3M4"))) + + assert_equal([S("hello\n")], S("aGVsbG8K\n").unpack(S("m"))) + + assert_equal([S("hello\nhello\n")], S(",:&5L;&\\*:&5L;&\\*\n").unpack(S("u"))) + + assert_equal([0xa9, 0x42, 0x2260], S("\xc2\xa9B\xe2\x89\xa0").unpack(S("U*"))) + + # more comprehensive tests are in test_pack.rb + end + + def test_upcase + assert_equal(S("HELLO"), S("hello").upcase) + assert_equal(S("HELLO"), S("hello").upcase) + assert_equal(S("HELLO"), S("HELLO").upcase) + assert_equal(S("ABC HELLO 123"), S("abc HELLO 123").upcase) + assert_equal(S("H\0""ELLO"), S("H\0""ello").upcase) + assert_equal(S("\u{10574}"), S("\u{1059B}").upcase) + end + + def test_upcase! + a = S("hello") + b = a.dup + assert_equal(S("HELLO"), a.upcase!) + assert_equal(S("HELLO"), a) + assert_equal(S("hello"), b) + + a = S("HELLO") + assert_nil(a.upcase!) + assert_equal(S("HELLO"), a) + + a = S("H\0""ello") + b = a.dup + assert_equal(S("H\0""ELLO"), a.upcase!) + assert_equal(S("H\0""ELLO"), a) + assert_equal(S("H\0""ello"), b) + end + + def test_upto + a = S("aa") + start = S("aa") + count = 0 + assert_equal(S("aa"), a.upto(S("zz")) {|s| + assert_equal(start, s) + start.succ! + count += 1 + }) + assert_equal(676, count) + end + + def test_upto_numeric + a = S("00") + start = S("00") + count = 0 + assert_equal(S("00"), a.upto(S("23")) {|s| + assert_equal(start, s, "[ruby-dev:39361]") + assert_equal(Encoding::US_ASCII, s.encoding) + start.succ! + count += 1 + }) + assert_equal(24, count, "[ruby-dev:39361]") + end + + def test_upto_nonalnum + first = S("\u3041") + last = S("\u3093") + count = 0 + assert_equal(first, first.upto(last) {|s| + count += 1 + s.replace(last) + }) + assert_equal(83, count, "[ruby-dev:39626]") + end + + def test_mod_check + assert_raise(RuntimeError) { + s = "" + s.sub!(/\A/) { s.replace "z" * 2000; "zzz" } + } + end + + def test_frozen_check + assert_raise(FrozenError) { + s = "" + s.sub!(/\A/) { s.freeze; "zzz" } + } + end + + class S2 < String + end + def test_str_new4 + return unless @cls == String + + s = (0..54).to_a.join # length = 100 + s2 = S2.new(s[10,90]) + s3 = s2[10,80] + assert_equal((10..54).to_a.to_a.join, s2) + assert_equal((15..54).to_a.to_a.join, s3) + end + + def test_rb_str_new4 + s = S("a" * 100) + s2 = s[10,90] + assert_equal("a" * 90, s2) + s3 = s2[10,80] + assert_equal("a" * 80, s3) + end + + class StringLike + def initialize(str) + @str = str + end + + def to_str + @str + end + end + + def test_rb_str_to_str + assert_equal("ab", S("a") + StringLike.new("b")) + end + + def test_rb_str_shared_replace + s = S("a" * 100) + s.succ! + assert_equal("a" * 99 + "b", s) + s = "" + s.succ! + assert_equal("", s) + end + + def test_times + assert_raise(ArgumentError) { "a" * (-1) } + end + + def test_splice! + l = S("1234\n234\n34\n4\n") + assert_equal(S("1234\n"), l.slice!(/\A.*\n/), "[ruby-dev:31665]") + assert_equal(S("234\n"), l.slice!(/\A.*\n/), "[ruby-dev:31665]") + assert_equal(S("34\n"), l.slice!(/\A.*\n/), "[ruby-dev:31665]") + assert_equal(S("4\n"), l.slice!(/\A.*\n/), "[ruby-dev:31665]") + assert_nil(l.slice!(/\A.*\n/), "[ruby-dev:31665]") + end + + def test_times2 + s1 = '' + 100.times {|n| + s2 = S("a") * n + assert_equal(s1, s2) + s1 << 'a' + } + + assert_raise(ArgumentError) { S("foo") * (-1) } + end + + def test_respond_to + o = Object.new + def o.respond_to?(arg) [:to_str].include?(arg) ? nil : super end + def o.to_str() "" end + def o.==(other) "" == other end + assert_equal(false, S("") == o) + end + + def test_match_method + assert_equal("bar", S("foobarbaz").match(/bar/).to_s) + + o = Regexp.new('foo') + def o.match(x, y, z); x + y + z; end + assert_equal("foobarbaz", S("foo").match(o, "bar", "baz")) + x = nil + S("foo").match(o, "bar", "baz") {|y| x = y } + assert_equal("foobarbaz", x) + + assert_raise(ArgumentError) { S("foo").match } + end + + def test_match_p_regexp + /backref/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal(true, S("").match?(//)) + assert_equal(true, :abc.match?(/.../)) + assert_equal(true, S('abc').match?(/b/)) + assert_equal(true, S('abc').match?(/b/, 1)) + assert_equal(true, S('abc').match?(/../, 1)) + assert_equal(true, S('abc').match?(/../, -2)) + assert_equal(false, S('abc').match?(/../, -4)) + assert_equal(false, S('abc').match?(/../, 4)) + assert_equal(true, S("\u3042xx").match?(/../, 1)) + assert_equal(false, S("\u3042x").match?(/../, 1)) + assert_equal(true, S('').match?(/\z/)) + assert_equal(true, S('abc').match?(/\z/)) + assert_equal(true, S('Ruby').match?(/R.../)) + assert_equal(false, S('Ruby').match?(/R.../, 1)) + assert_equal(false, S('Ruby').match?(/P.../)) + assert_equal('backref', $&) + end + + 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, S("").match?('')) + assert_equal(true, :abc.match?('...')) + assert_equal(true, S('abc').match?('b')) + assert_equal(true, S('abc').match?('b', 1)) + assert_equal(true, S('abc').match?('..', 1)) + assert_equal(true, S('abc').match?('..', -2)) + assert_equal(false, S('abc').match?('..', -4)) + assert_equal(false, S('abc').match?('..', 4)) + assert_equal(true, S("\u3042xx").match?('..', 1)) + assert_equal(false, S("\u3042x").match?('..', 1)) + assert_equal(true, S('').match?('\z')) + assert_equal(true, S('abc').match?('\z')) + assert_equal(true, S('Ruby').match?('R...')) + assert_equal(false, S('Ruby').match?('R...', 1)) + assert_equal(false, S('Ruby').match?('P...')) + assert_equal('backref', $&) + end + + def test_clear + s = "foo" * 100 + s.clear + assert_equal("", s) + end + + def test_to_s_2 + c = Class.new(String) + s = c.new + s.replace("foo") + assert_equal("foo", s.to_s) + assert_instance_of(String, s.to_s) + end + + def test_inspect_nul + bug8290 = '[ruby-core:54458]' + s = S("\0") + "12" + assert_equal '"\u000012"', s.inspect, bug8290 + s = S("\0".b) + "12" + assert_equal '"\x0012"', s.inspect, bug8290 + end + + def test_inspect_next_line + bug16842 = '[ruby-core:98231]' + assert_equal '"\\u0085"', 0x85.chr(Encoding::UTF_8).inspect, bug16842 + end + + def test_partition + assert_equal(%w(he l lo), S("hello").partition(/l/)) + assert_equal(%w(he l lo), S("hello").partition("l")) + assert_raise(TypeError) { S("hello").partition(1) } + def (hyphen = Object.new).to_str; "-"; end + assert_equal(%w(foo - bar), S("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"], + S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").partition(/[@.]/)) + + bug = '[ruby-core:82911]' + hello = S("hello") + hello.partition("hi").map(&:upcase!) + assert_equal("hello", hello, bug) + + assert_equal(["", "", "foo"], S("foo").partition(/^=*/)) + + assert_equal([S("ab"), S("c"), S("dbce")], S("abcdbce").partition(/b\Kc/)) + end + + def test_rpartition + assert_equal(%w(hel l o), S("hello").rpartition(/l/)) + assert_equal(%w(hel l o), S("hello").rpartition("l")) + assert_raise(TypeError) { S("hello").rpartition(1) } + def (hyphen = Object.new).to_str; "-"; end + assert_equal(%w(foo - bar), S("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"], + S("\u30E6\u30FC\u30B6@\u30C9\u30E1.\u30A4\u30F3").rpartition(/[@.]/), bug8138) + + bug = '[ruby-core:82911]' + hello = "hello" + hello.rpartition("hi").map(&:upcase!) + assert_equal("hello", hello, bug) + + assert_equal([S("abcdb"), S("c"), S("e")], S("abcdbce").rpartition(/b\Kc/)) + end + + def test_rs + return unless @cls == String + + begin + rs = $/ + assert_deprecated_warning(/non-nil '\$\/'/) { $/ = "" } + assert_raise(TypeError) { $/ = 1 } + ensure + EnvUtil.suppress_warning { $/ = rs } + end + name = "\u{5206 884c}" + assert_separately([], "#{<<~"do;"}\n#{<<~"end;"}") + do; + alias $#{name} $/ + assert_deprecated_warning(/\\$#{name}/) { $#{name} = "" } + assert_raise_with_message(TypeError, /\\$#{name}/) { $#{name} = 1 } + end; + end + + def test_to_id + c = Class.new + c.class_eval do + def initialize + @foo = :foo + end + end + + assert_raise(TypeError) do + c.class_eval { attr 1 } + end + + o = Object.new + def o.to_str; :foo; end + assert_raise(TypeError) do + 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 } + end + assert_equal(:foo, c.new.foo) + end + + def test_gsub_enumerator + e = S("abc").gsub(/./) + assert_equal("a", e.next, "[ruby-dev:34828]") + assert_equal("b", e.next) + assert_equal("c", e.next) + end + + def test_clear_nonasciicompat + assert_equal("", S("\u3042".encode("ISO-2022-JP")).clear) + end + + def test_try_convert + assert_equal(nil, @cls.try_convert(1)) + assert_equal("foo", @cls.try_convert("foo")) + end + + def test_substr_negative_begin + assert_equal("\u3042", ("\u3042" * 100)[-1]) + end + + def test_compare_different_encoding_string + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_equal([-1, 1], [s1 <=> s2, s2 <=> s1].sort) + + s3 = S("ã‚".force_encoding("UTF-16LE")) + s4 = S("a".force_encoding("IBM437")) + assert_equal([-1, 1], [s3 <=> s4, s4 <=> s3].sort) + end + + def test_casecmp + assert_equal(0, S("FoO").casecmp("fOO")) + assert_equal(1, S("FoO").casecmp("BaR")) + assert_equal(-1, S("foo").casecmp("FOOBAR")) + assert_equal(-1, S("baR").casecmp("FoO")) + assert_equal(1, S("\u3042B").casecmp("\u3042a")) + assert_equal(-1, S("foo").casecmp("foo\0")) + assert_equal(1, S("FOOBAR").casecmp("foo")) + assert_equal(0, S("foo\0bar").casecmp("FOO\0BAR")) + + assert_nil(S("foo").casecmp(:foo)) + assert_nil(S("foo").casecmp(Object.new)) + + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + + o = Object.new + def o.to_str; "fOO"; end + assert_equal(0, S("FoO").casecmp(o)) + + assert_equal(0, S("#" * 128 + "A" * 256 + "b").casecmp("#" * 128 + "a" * 256 + "B")) + assert_equal(0, S("a" * 256 + "B").casecmp("A" * 256 + "b")) + + assert_equal(-1, S("@").casecmp("`")) + assert_equal(0, S("hello\u00E9X").casecmp("HELLO\u00E9x")) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp(s2)) + end + + def test_casecmp? + assert_equal(true, S('FoO').casecmp?('fOO')) + assert_equal(false, S('FoO').casecmp?('BaR')) + assert_equal(false, S('baR').casecmp?('FoO')) + assert_equal(true, S('äöü').casecmp?('ÄÖÜ')) + assert_equal(false, S("foo").casecmp?("foo\0")) + + assert_nil(S("foo").casecmp?(:foo)) + assert_nil(S("foo").casecmp?(Object.new)) + + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + + o = Object.new + def o.to_str; "fOO"; end + assert_equal(true, S("FoO").casecmp?(o)) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp?(s2)) + end + + def test_upcase2 + assert_equal("\u3042AB", S("\u3042aB").upcase) + end + + def test_downcase2 + assert_equal("\u3042ab", S("\u3042aB").downcase) + end + + def test_rstrip + assert_equal(" hello", S(" hello ").rstrip) + assert_equal("\u3042", S("\u3042 ").rstrip) + assert_equal("\u3042", S("\u3042\u0000").rstrip) + assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip } + end + + def test_rstrip_bang + 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) + + s5 = S("\u3042\u0000") + assert_equal("\u3042", s5.rstrip!) + assert_equal("\u3042", s5) + + assert_raise(Encoding::CompatibilityError) { S("\u3042".encode("ISO-2022-JP")).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc \x80 ".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc\x80 ".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("abc \x80".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S("\x80".force_encoding('UTF-8')).rstrip! } + assert_raise(Encoding::CompatibilityError) { S(" \x80 ".force_encoding('UTF-8')).rstrip! } + end + + def test_lstrip + assert_equal("hello ", S(" hello ").lstrip) + assert_equal("\u3042", S(" \u3042").lstrip) + assert_equal("hello ", S("\x00hello ").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) + + s5 = S("\u0000\u3042") + assert_equal("\u3042", s5.lstrip!) + assert_equal("\u3042", s5) + end + + def test_delete_prefix_type_error + assert_raise(TypeError) { S('hello').delete_prefix(nil) } + assert_raise(TypeError) { S('hello').delete_prefix(1) } + assert_raise(TypeError) { S('hello').delete_prefix(/hel/) } + end + + def test_delete_prefix + 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) + end + + def test_delete_prefix_broken_encoding + 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) + + assert_equal("\xFE", S("\xFF\xFE").delete_prefix("\xFF")) + assert_equal("\xBE", S("hello\xBE").delete_prefix("hello")) + assert_equal("\xBE", S("\xFFhello\xBE").delete_prefix("\xFFhello")) + end + + def test_delete_prefix_clear_coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix("\u{3053 3093}"), :ascii_only?) + end + + def test_delete_prefix_argument_conversion + 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_type_error + assert_raise(TypeError) { S('hello').delete_prefix!(nil) } + assert_raise(TypeError) { S('hello').delete_prefix!(1) } + assert_raise(TypeError) { S('hello').delete_prefix!(/hel/) } + end + + def test_delete_prefix_bang + 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) + end + + def test_delete_prefix_bang_broken_encoding + s = S("\xe3\x81\x82") + assert_equal(nil, s.delete_prefix!("\xe3")) + assert_equal("\xe3\x81\x82", s) + + s = S("\xFF\xFE") + assert_equal("\xFE", s.delete_prefix!("\xFF")) + assert_equal("\xFE", s) + end + + def test_delete_prefix_bang_clear_coderange + s = S("\u{3053 3093}hello") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_prefix!("\u{3053 3093}"), :ascii_only?) + end + + def test_delete_prefix_bang_argument_conversion + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("bba", s.delete_prefix!(klass.new)) + assert_equal("bba", s) + end + + def test_delete_prefix_bang_frozen_error + 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_type_error + assert_raise(TypeError) { S('hello').delete_suffix(nil) } + assert_raise(TypeError) { S('hello').delete_suffix(1) } + assert_raise(TypeError) { S('hello').delete_suffix(/hel/) } + end + + def test_delete_suffix + 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) + end + + def test_delete_suffix_broken_encoding + s = S("\xe3\x81\x82") + assert_equal("\xe3\x81\x82", s.delete_suffix("\x82")) + assert_equal("\xe3\x81\x82", s) + end + + def test_delete_suffix_clear_coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_suffix("\u{3053 3093}"), :ascii_only?) + end + + def test_delete_suffix_argument_conversion + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.delete_suffix(klass.new)) + assert_equal("abba", s) + end + + def test_delete_suffix_newline + # 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_type_error + assert_raise(TypeError) { S('hello').delete_suffix!(nil) } + assert_raise(TypeError) { S('hello').delete_suffix!(1) } + assert_raise(TypeError) { S('hello').delete_suffix!(/hel/) } + end + + def test_delete_suffix_bang_frozen_error + 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)} + end + + def test_delete_suffix_bang + 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) + end + + def test_delete_suffix_bang_broken_encoding + 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) + end + + def test_delete_suffix_bang_clear_coderange + s = S("hello\u{3053 3093}") + assert_not_predicate(s, :ascii_only?) + assert_predicate(s.delete_suffix!("\u{3053 3093}"), :ascii_only?) + end + + def test_delete_suffix_bang_argument_conversion + klass = Class.new { def to_str; 'a'; end } + s = S("abba") + assert_equal("abb", s.delete_suffix!(klass.new)) + assert_equal("abb", s) + end + + def test_delete_suffix_bang_newline + # chomp removes any of "\n", "\r\n", "\r" when "\n" is specified, + # but delete_suffix does not + s = "foo\n" + assert_equal("foo", s.delete_suffix!("\n")) + s = "foo\r\n" + assert_equal("foo\r", s.delete_suffix!("\n")) + s = "foo\r" + assert_equal(nil, s.delete_suffix!("\n")) + end + +=begin + def test_symbol_table_overflow + assert_in_out_err([], <<-INPUT, [], /symbol table overflow \(symbol [a-z]{8}\) \(RuntimeError\)/) + ("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 = S("\u{3066}\u{3059}\u{3068}").gsub(//, '') + h = {} + h[s] = nil + k = h.keys[0] + assert_equal(s, k, '[ruby-dev:39068]') + assert_equal(Encoding::UTF_8, k.encoding, '[ruby-dev:39068]') + s.dup.force_encoding(Encoding::ASCII_8BIT).gsub(//, '') + k = h.keys[0] + assert_equal(s, k, '[ruby-dev:39068]') + assert_equal(Encoding::UTF_8, k.encoding, '[ruby-dev:39068]') + end + + def test_ascii_incomat_inspect + bug4081 = '[ruby-core:33283]' + WIDE_ENCODINGS.each do |e| + assert_equal('"abc"', S("abc".encode(e)).inspect) + assert_equal('"\\u3042\\u3044\\u3046"', S("\u3042\u3044\u3046".encode(e)).inspect) + assert_equal('"ab\\"c"', S("ab\"c".encode(e)).inspect, bug4081) + end + + EnvUtil.with_default_external(Encoding::US_ASCII) do + i = S("abc\"\\".force_encoding("utf-8")).inspect + + assert_equal('"abc\\"\\\\"', i, bug4081) + end + end + + def test_dummy_inspect + assert_equal('"\e\x24\x42\x22\x4C\x22\x68\e\x28\x42"', + S("\u{ffe2}\u{2235}".encode("cp50220")).inspect) + end + + def test_prepend + assert_equal(S("hello world!"), S("!").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"), S("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", S("hello").byteslice(0)) + assert_equal(nil, S("hello").byteslice(5)) + assert_equal("o", S("hello").byteslice(-1)) + assert_equal(nil, S("hello").byteslice(-6)) + + assert_equal("", S("hello").byteslice(0, 0)) + assert_equal("hello", S("hello").byteslice(0, 6)) + assert_equal("hello", S("hello").byteslice(0, 6)) + assert_equal("", S("hello").byteslice(5, 1)) + assert_equal("o", S("hello").byteslice(-1, 6)) + assert_equal(nil, S("hello").byteslice(-6, 1)) + assert_equal(nil, S("hello").byteslice(0, -1)) + + assert_equal("h", S("hello").byteslice(0..0)) + assert_equal("", S("hello").byteslice(5..0)) + assert_equal("o", S("hello").byteslice(4..5)) + assert_equal(nil, S("hello").byteslice(6..0)) + assert_equal("", S("hello").byteslice(-1..0)) + assert_equal("llo", S("hello").byteslice(-3..5)) + + assert_equal(u("\x81"), S("\u3042").byteslice(1)) + assert_equal(u("\x81\x82"), S("\u3042").byteslice(1, 2)) + assert_equal(u("\x81\x82"), S("\u3042").byteslice(1..2)) + + assert_equal(u("\x82")+("\u3042"*9), S("\u3042"*10).byteslice(2, 28)) + + assert_equal("\xE3", S("ã“ã‚“ã«ã¡ã¯").byteslice(0)) + assert_equal("ã“ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯").byteslice(0, 15)) + assert_equal("ã“", S("ã“ã‚“ã«ã¡ã¯").byteslice(0, 3)) + assert_equal("ã¯", S("ã“ã‚“ã«ã¡ã¯").byteslice(12, 15)) + + bug7954 = '[ruby-dev:47108]' + assert_equal(false, S("\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 + return unless @cls == String + + 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(S("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 + return unless @cls == String + + 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) + + require 'objspace' + + str = "test_uplus_minus_str".freeze + assert_includes ObjectSpace.dump(str), '"fstring":true' + + assert_predicate(str, :frozen?) + assert_not_predicate(+str, :frozen?) + assert_predicate(-str, :frozen?) + + assert_not_same(str, +str) + assert_same(str, -str) + + bar = -%w(test uplus minus str).join('_') + assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}") + end + + def test_uminus_dedup_in_place + dynamic = "this string is unique and frozen #{rand}".freeze + assert_same dynamic, -dynamic + assert_same dynamic, -dynamic.dup + end + + def test_uminus_frozen + return unless @cls == String + + # embedded + str1 = ("foobar" * 3).freeze + str2 = ("foobar" * 3).freeze + assert_not_same str1, str2 + assert_same str1, -str1 + assert_same str1, -str2 + + # regular + str1 = ("foobar" * 4).freeze + str2 = ("foobar" * 4).freeze + assert_not_same str1, str2 + assert_same str1, -str1 + assert_same str1, -str2 + end + + def test_uminus_no_freeze_not_bare + str = S("foo") + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + + str = S("foo") + str.instance_variable_set(:@iv, 1) + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + assert_equal(1, str.instance_variable_get(:@iv)) + + str = S("foo") + assert_instance_of(@cls, -str) + assert_equal(false, str.frozen?) + end + + def test_uminus_no_embed_gc + pad = "a"*2048 + File.open(IO::NULL, "w") do |dev_null| + ("aa".."zz").each do |c| + fstr = -(c + pad).freeze + dev_null.write(fstr) + end + end + GC.start + end + + def test_ord + assert_equal(97, S("a").ord) + assert_equal(97, S("abc").ord) + assert_equal(0x3042, S("\u3042\u3043").ord) + assert_raise(ArgumentError) { S("").ord } + end + + def test_chr + assert_equal("a", S("abcde").chr) + assert_equal("a", S("a").chr) + assert_equal("\u3042", S("\u3042\u3043").chr) + assert_equal('', S('').chr) + end + + def test_substr_code_range + data = S("\xff" + "a"*200) + assert_not_predicate(data, :valid_encoding?) + assert_predicate(data[100..-1], :valid_encoding?) + end + + def test_byteindex + assert_byteindex(0, S("hello"), ?h) + assert_byteindex(1, S("hello"), S("ell")) + assert_byteindex(2, S("hello"), /ll./) + + assert_byteindex(3, S("hello"), ?l, 3) + assert_byteindex(3, S("hello"), S("l"), 3) + assert_byteindex(3, S("hello"), /l./, 3) + + assert_byteindex(nil, S("hello"), ?z, 3) + assert_byteindex(nil, S("hello"), S("z"), 3) + assert_byteindex(nil, S("hello"), /z./, 3) + + assert_byteindex(nil, S("hello"), ?z) + assert_byteindex(nil, S("hello"), S("z")) + assert_byteindex(nil, S("hello"), /z./) + + assert_byteindex(0, S(""), S("")) + assert_byteindex(0, S(""), //) + assert_byteindex(nil, S(""), S("hello")) + assert_byteindex(nil, S(""), /hello/) + assert_byteindex(0, S("hello"), S("")) + assert_byteindex(0, S("hello"), //) + + s = S("long") * 1000 << "x" + assert_byteindex(nil, s, S("y")) + assert_byteindex(4 * 1000, s, S("x")) + s << "yx" + assert_byteindex(4 * 1000, s, S("x")) + assert_byteindex(4 * 1000, s, S("xyx")) + + o = Object.new + def o.to_str; "bar"; end + assert_byteindex(3, S("foobarbarbaz"), o) + assert_raise(TypeError) { S("foo").byteindex(Object.new) } + + assert_byteindex(nil, S("foo"), //, -100) + assert_byteindex(nil, S("foo"), //, -4) + + assert_byteindex(2, S("abcdbce"), /b\Kc/) + + assert_byteindex(0, S("ã“ã‚“ã«ã¡ã¯"), ?ã“) + assert_byteindex(3, S("ã“ã‚“ã«ã¡ã¯"), S("ã‚“ã«ã¡")) + assert_byteindex(6, S("ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./) + + assert_byteindex(0, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 0) + assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 1) } + assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 5) } + assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), ?ã«, 6) + assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), S("ã«"), 6) + assert_byteindex(6, S("ã«ã‚“ã«ã¡ã¯"), /ã«./, 6) + assert_raise(IndexError) { S("ã«ã‚“ã«ã¡ã¯").byteindex(?ã«, 7) } + + s = S("foobarbarbaz") + assert !1000.times.any? {s.byteindex("", 100_000_000)} + end + + def test_byterindex + assert_byterindex(3, S("hello"), ?l) + assert_byterindex(6, S("ell, hello"), S("ell")) + assert_byterindex(7, S("ell, hello"), /ll./) + + assert_byterindex(3, S("hello,lo"), ?l, 3) + assert_byterindex(3, S("hello,lo"), S("l"), 3) + assert_byterindex(3, S("hello,lo"), /l./, 3) + + assert_byterindex(nil, S("hello"), ?z, 3) + assert_byterindex(nil, S("hello"), S("z"), 3) + assert_byterindex(nil, S("hello"), /z./, 3) + + assert_byterindex(nil, S("hello"), ?z) + assert_byterindex(nil, S("hello"), S("z")) + assert_byterindex(nil, S("hello"), /z./) + + assert_byterindex(5, S("hello"), S("")) + assert_byterindex(5, S("hello"), S(""), 5) + assert_byterindex(4, S("hello"), S(""), 4) + assert_byterindex(0, S("hello"), S(""), 0) + + o = Object.new + def o.to_str; "bar"; end + assert_byterindex(6, S("foobarbarbaz"), o) + assert_raise(TypeError) { S("foo").byterindex(Object.new) } + + assert_byterindex(nil, S("foo"), //, -100) + + m = assert_byterindex(3, S("foo"), //) + assert_equal([3, 3], m.offset(0)) + assert_byterindex(3, S("foo"), //, 4) + + assert_byterindex(5, S("abcdbce"), /b\Kc/) + + assert_byterindex(6, S("ã“ã‚“ã«ã¡ã¯"), ?ã«) + assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯")) + assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), /ã«ã¡./) + + assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 19) } + assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), -2) } + assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 18) + assert_byterindex(18, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), -3) + assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 17) } + assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), -4) } + assert_raise(IndexError) { S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯").byterindex(S("ã«ã¡ã¯"), 1) } + assert_byterindex(0, S("ã«ã¡ã¯ã€ã“ã‚“ã«ã¡ã¯"), S("ã«ã¡ã¯"), 0) + + assert_byterindex(0, S("ã“ã‚“ã«ã¡ã¯"), S("ã“ã‚“ã«ã¡ã¯")) + assert_byterindex(nil, S("ã“ã‚“ã«ã¡"), S("ã“ã‚“ã«ã¡ã¯")) + assert_byterindex(nil, S("ã“"), S("ã“ã‚“ã«ã¡ã¯")) + assert_byterindex(nil, S(""), S("ã“ã‚“ã«ã¡ã¯")) + end + + def test_bytesplice + assert_bytesplice_raise(IndexError, S("hello"), -6, 0, "bye") + assert_bytesplice_result("byehello", S("hello"), -5, 0, "bye") + assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye") + assert_bytesplice_result("byeello", S("hello"), 0, 1, "bye") + assert_bytesplice_result("bye", S("hello"), 0, 5, "bye") + assert_bytesplice_result("bye", S("hello"), 0, 6, "bye") + + assert_bytesplice_raise(IndexError, S("hello"), -5, 0, "bye", -4, 0) + assert_bytesplice_result("byehello", S("hello"), 0, 0, "bye", 0, 3) + assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 3) + assert_bytesplice_result("yehello", S("hello"), 0, 0, "bye", 1, 2) + assert_bytesplice_result("ehello", S("hello"), 0, 0, "bye", 2, 1) + assert_bytesplice_result("hello", S("hello"), 0, 0, "bye", 3, 0) + assert_bytesplice_result("hello", s = S("hello"), 0, 5, s, 0, 5) + assert_bytesplice_result("elloo", s = S("hello"), 0, 4, s, 1, 4) + assert_bytesplice_result("llolo", s = S("hello"), 0, 3, s, 2, 3) + assert_bytesplice_result("lollo", s = S("hello"), 0, 2, s, 3, 2) + assert_bytesplice_result("oello", s = S("hello"), 0, 1, s, 4, 1) + assert_bytesplice_result("hhell", s = S("hello"), 1, 4, s, 0, 4) + assert_bytesplice_result("hehel", s = S("hello"), 2, 3, s, 0, 3) + assert_bytesplice_result("helhe", s = S("hello"), 3, 2, s, 0, 2) + assert_bytesplice_result("hellh", s = S("hello"), 4, 1, s, 0, 1) + + assert_bytesplice_raise(RangeError, S("hello"), -6...-6, "bye") + assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye") + assert_bytesplice_result("byehello", S("hello"), 0...0, "bye") + assert_bytesplice_result("byeello", S("hello"), 0..0, "bye") + assert_bytesplice_result("byeello", S("hello"), 0...1, "bye") + assert_bytesplice_result("byello", S("hello"), 0..1, "bye") + assert_bytesplice_result("bye", S("hello"), 0..-1, "bye") + assert_bytesplice_result("bye", S("hello"), 0...5, "bye") + assert_bytesplice_result("bye", S("hello"), 0...6, "bye") + assert_bytesplice_result("llolo", s = S("hello"), 0..2, s, 2..4) + + assert_bytesplice_raise(RangeError, S("hello"), -5...-5, "bye", -6...-6) + assert_bytesplice_result("byehello", S("hello"), -5...-5, "bye", 0..-1) + assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..-1) + assert_bytesplice_result("bhello", S("hello"), 0...0, "bye", 0..0) + assert_bytesplice_result("byhello", S("hello"), 0...0, "bye", 0..1) + assert_bytesplice_result("byehello", S("hello"), 0...0, "bye", 0..2) + assert_bytesplice_result("yehello", S("hello"), 0...0, "bye", 1..2) + + assert_bytesplice_raise(TypeError, S("hello"), 0, "bye") + + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), -16, 0, "bye") + assert_bytesplice_result("byeã“ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), -15, 0, "bye") + assert_bytesplice_result("byeã“ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 0, "bye") + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 1, 0, "bye") + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 1, "bye") + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 2, "bye") + assert_bytesplice_result("byeã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 3, "bye") + assert_bytesplice_result("ã“ã‚“ã«ã¡ã¯bye", S("ã“ã‚“ã«ã¡ã¯"), 15, 0, "bye") + + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 0, "ã•よã†ãªã‚‰", -16, 0) + assert_bytesplice_result("ã“ã‚“ã«ã¡ã¯ã•よã†ãªã‚‰", S("ã“ã‚“ã«ã¡ã¯"), 15, 0, "ã•よã†ãªã‚‰", 0, 15) + assert_bytesplice_result("ã•よã†ãªã‚‰", S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 15) + assert_bytesplice_result("ã•ã‚“ã«ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 3, "ã•よã†ãªã‚‰", 0, 3) + assert_bytesplice_result("ã•よã†ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 0, 9) + assert_bytesplice_result("よã†ãªã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 9) + assert_bytesplice_result("よã†ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 6) + assert_bytesplice_result("よã†ãªã‚‰ã¡ã¯", S("ã“ã‚“ã«ã¡ã¯"), 0, 9, "ã•よã†ãªã‚‰", 3, 12) + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", -16, 0) + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 1, 0) + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 2, 0) + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 1) + assert_bytesplice_raise(IndexError, S("ã“ã‚“ã«ã¡ã¯"), 0, 15, "ã•よã†ãªã‚‰", 0, 2) + assert_bytesplice_result("ã«ã¡ã¯ã¡ã¯", s = S("ã“ã‚“ã«ã¡ã¯"), 0, 9, s, 6, 9) + + assert_bytesplice_result("", S(""), 0, 0, "") + assert_bytesplice_result("xxx", S(""), 0, 0, "xxx") + + assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0) + assert_bytesplice_raise(ArgumentError, S("hello"), 0, 5, "bye", 0..-1) + assert_bytesplice_raise(ArgumentError, S("hello"), 0..-1, "bye", 0, 3) + end + + def test_append_bytes_into_binary + buf = S("".b) + assert_equal Encoding::BINARY, buf.encoding + + buf.append_as_bytes(S("hello")) + assert_equal "hello".b, buf + assert_equal Encoding::BINARY, buf.encoding + + buf.append_as_bytes(S("ã“ã‚“ã«ã¡ã¯")) + assert_equal S("helloã“ã‚“ã«ã¡ã¯".b), buf + assert_equal Encoding::BINARY, buf.encoding + end + + def test_append_bytes_into_utf8 + buf = S("") + assert_equal Encoding::UTF_8, buf.encoding + + buf.append_as_bytes(S("hello")) + assert_equal S("hello"), buf + assert_equal Encoding::UTF_8, buf.encoding + assert_predicate buf, :ascii_only? + assert_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("ã“ã‚“ã«ã¡ã¯")) + assert_equal S("helloã“ã‚“ã«ã¡ã¯"), buf + assert_equal Encoding::UTF_8, buf.encoding + refute_predicate buf, :ascii_only? + assert_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("\xE2\x82".b)) + assert_equal S("helloã“ã‚“ã«ã¡ã¯\xE2\x82"), buf + assert_equal Encoding::UTF_8, buf.encoding + refute_predicate buf, :valid_encoding? + + buf.append_as_bytes(S("\xAC".b)) + assert_equal S("helloã“ã‚“ã«ã¡ã¯â‚¬"), buf + assert_equal Encoding::UTF_8, buf.encoding + assert_predicate buf, :valid_encoding? + end + + def test_append_bytes_into_utf32 + buf = S("abc".encode(Encoding::UTF_32LE)) + assert_equal Encoding::UTF_32LE, buf.encoding + + buf.append_as_bytes("def") + assert_equal Encoding::UTF_32LE, buf.encoding + refute_predicate buf, :valid_encoding? + end + + def test_chilled_string + chilled_string = eval('"chilled"') + + assert_not_predicate chilled_string, :frozen? + + assert_not_predicate chilled_string.dup, :frozen? + assert_not_predicate chilled_string.clone, :frozen? + + # @+ treat the original string as frozen + assert_not_predicate(+chilled_string, :frozen?) + assert_not_same chilled_string, +chilled_string + + # @- treat the original string as mutable + assert_predicate(-chilled_string, :frozen?) + assert_not_same chilled_string, -chilled_string + end + + def test_chilled_string_setivar + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + + String.class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def setivar! + @ivar = 42 + @ivar + end + RUBY + chilled_string = eval('"chilled"') + begin + assert_equal 42, chilled_string.setivar! + ensure + String.undef_method(:setivar!) + end + ensure + Warning[:deprecated] = deprecated + end + + def test_chilled_string_substring + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + chilled_string = eval('"a chilled string."') + substring = chilled_string[0..-1] + assert_equal("a chilled string.", substring) + chilled_string[0..-1] = "This string is defrosted." + assert_equal("a chilled string.", substring) + ensure + Warning[:deprecated] = deprecated + end + + def test_encode_fallback_raise_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { raise MyError } + RUBY + "proc" => <<~RUBY, + fallback = proc { raise MyError } + RUBY + "method" => <<~RUBY, + def my_method(_str) = raise MyError + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = raise MyError + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue MyError + end + RUBY + end + end + + def test_encode_fallback_too_big_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { "\\uffee" } + RUBY + "proc" => <<~RUBY, + fallback = proc { "\\uffee" } + RUBY + "method" => <<~RUBY, + def my_method(_str) = "\\uffee" + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = "\\uffee" + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue ArgumentError + end + RUBY + end + end + + def test_encode_fallback_not_string_memory_leak + { + "hash" => <<~RUBY, + fallback = Hash.new { Object.new } + RUBY + "proc" => <<~RUBY, + fallback = proc { Object.new } + RUBY + "method" => <<~RUBY, + def my_method(_str) = Object.new + fallback = method(:my_method) + RUBY + "aref" => <<~RUBY, + fallback = Object.new + def fallback.[](_str) = Object.new + RUBY + }.each do |type, code| + assert_no_memory_leak([], '', <<~RUBY, "fallback type is #{type}", rss: true) + class MyError < StandardError; end + + #{code} + + 100_000.times do |i| + "\\ufffd".encode(Encoding::US_ASCII, fallback:) + rescue TypeError + end + RUBY + end + end + + private + + def assert_bytesplice_result(expected, s, *args) + assert_equal(expected, s.send(:bytesplice, *args)) + assert_equal(expected, s) + end + + def assert_bytesplice_raise(e, s, *args) + assert_raise(e) { s.send(:bytesplice, *args) } + end + + def assert_index_like(method, expected, string, match, *rest) + message = "#{method} with string does not affect $~" + /.*/ =~ message + md_before = $~ + assert_equal(expected, string.__send__(method, match, *rest)) + md_after = $~ + case match + when Regexp + if expected + assert_not_nil(md_after) + assert_not_same(md_before, md_after) + else + assert_nil(md_after) + end + else + assert_same(md_before, md_after) + end + md_after + end + + def assert_index(expected, string, match, *rest) + assert_index_like(:index, expected, string, match, *rest) + end + + def assert_rindex(expected, string, match, *rest) + assert_index_like(:rindex, expected, string, match, *rest) + end + + def assert_byteindex(expected, string, match, *rest) + assert_index_like(:byteindex, expected, string, match, *rest) + end + + def assert_byterindex(expected, string, match, *rest) + assert_index_like(:byterindex, expected, string, match, *rest) + end + + def assert_undump(str, *rest) + assert_equal(str, str.dump.undump, *rest) + end +end + +class TestString2 < TestString + def initialize(*args) + super + @cls = S2 + end +end diff --git a/test/ruby/test_string_memory.rb b/test/ruby/test_string_memory.rb new file mode 100644 index 0000000000..a93a3bd54a --- /dev/null +++ b/test/ruby/test_string_memory.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: false +require 'test/unit' +require 'objspace' + +class TestStringMemory < Test::Unit::TestCase + def capture_allocations(klass) + allocations = [] + + EnvUtil.without_gc do + GC.start + generation = GC.count + + ObjectSpace.trace_object_allocations do + yield + + ObjectSpace.each_object(klass) do |instance| + allocations << instance if ObjectSpace.allocation_generation(instance) == generation + end + end + + return allocations.map do |instance| + [ + ObjectSpace.allocation_sourcefile(instance), + ObjectSpace.allocation_sourceline(instance), + instance.class, + instance, + ] + end.select do |path,| + # drop strings not created in this file + # (the parallel testing framework may create strings in a separate thread) + path == __FILE__ + end + end + end + + def test_byteslice_prefix + string = ("a" * 100_000).freeze + + allocations = capture_allocations(String) do + string.byteslice(0, 50_000) + end + + assert_equal 1, allocations.size, "One object allocation is expected, but allocated: #{ allocations.inspect }" + end + + def test_byteslice_postfix + string = ("a" * 100_000).freeze + + allocations = capture_allocations(String) do + string.byteslice(50_000, 100_000) + end + + assert_equal 1, allocations.size, "One object allocation is expected, but allocated: #{ allocations.inspect }" + end + + def test_byteslice_postfix_twice + string = ("a" * 100_000).freeze + + allocations = capture_allocations(String) do + string.byteslice(50_000, 100_000).byteslice(25_000, 50_000) + end + + assert_equal 2, allocations.size, "Two object allocations are expected, but allocated: #{ allocations.inspect }" + end +end diff --git a/test/ruby/test_stringchar.rb b/test/ruby/test_stringchar.rb new file mode 100644 index 0000000000..e13beef69c --- /dev/null +++ b/test/ruby/test_stringchar.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestStringchar < Test::Unit::TestCase + def test_string + assert_equal("abcd", "abcd") + assert_match(/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_operator("abc", :!~, /^$/) + assert_operator("abc\n", :!~, /^$/) + assert_operator("abc", :!~, /^d*$/) + assert_equal(3, ("abc" =~ /d*$/)) + assert("" =~ /^$/) + assert("\n" =~ /^$/) + assert("a\n\n" =~ /^$/) + assert("abcabc" =~ /.*a/); assert_equal("abca", $&) + assert("abcabc" =~ /.*c/); assert_equal("abcabc", $&) + assert("abcabc" =~ /.*?a/); assert_equal("a", $&) + assert("abcabc" =~ /.*?c/); assert_equal("abc", $&) + assert(/(.|\n)*?\n(b|\n)/ =~ "a\nb\n\n"); assert_equal("a\nb", $&) + + assert(/^(ab+)+b/ =~ "ababb"); assert_equal("ababb", $&) + assert(/^(?:ab+)+b/ =~ "ababb"); assert_equal("ababb", $&) + assert(/^(ab+)+/ =~ "ababb"); assert_equal("ababb", $&) + assert(/^(?:ab+)+/ =~ "ababb"); assert_equal("ababb", $&) + + assert(/(\s+\d+){2}/ =~ " 1 2"); assert_equal(" 1 2", $&) + assert(/(?:\s+\d+){2}/ =~ " 1 2"); assert_equal(" 1 2", $&) + + x = <<END; +ABCD +ABCD +END + 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") + + $foo = "abc" + assert_equal("abc = abc", "#$foo = abc") + assert_equal("abc = abc", "#{$foo} = abc") + + foo = "abc" + assert_equal("abc = abc", "#{foo} = abc") + + assert_equal('-----', '-' * 5) + assert_equal('-', '-' * 1) + assert_equal('', '-' * 0) + + foo = '-' + assert_equal('-----', foo * 5) + 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(/.*\.([^\.]+)$/, '<\&>')) + end + + def test_char + # character constants(assumes ASCII) + assert_equal(?a, "a"[0]) + assert_equal(?a, ?a) + assert_equal("\1", ?\C-a) + assert_equal("\341", ?\M-a) + assert_equal("\201", ?\M-\C-a) + assert_equal(?A, "a".upcase![0]) + assert_equal(?a, "A".downcase![0]) + assert_equal("ABC", "abc".tr!("a-z", "A-Z")) + assert_equal("ABC", "aabbcccc".tr_s!("a-z", "A-Z")) + 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 + break + end + } + assert(!bad) + + s = "a string" + s[0..s.size]="another string" + assert_equal("another string", s) + + s = <<EOS +#{ +[1,2,3].join(",") +} +EOS + assert_equal("1,2,3\n", s) + assert_equal(926381, "Just".to_i(36)) + assert_equal(-23200231779, "-another".to_i(36)) + assert_equal("ruby", 1299022.to_s(36)) + assert_equal("-hacker", -1045307475.to_s(36)) + assert_equal(265419172580680477752431643787347, "Just_another_Ruby_hacker".to_i(36)) + assert_equal("-justanotherrubyhacker", -265419172580680477752431643787347.to_s(36)) + + a = [] + (0..255).each {|n| + ch = [n].pack("C") + a.push ch if /a#{Regexp.quote ch}b/x =~ "ab" + } + assert_equal(0, a.size) + end + + def test_bang + s = "aBc" + s.upcase + assert_equal("aBc", s) + s.upcase! + assert_equal("ABC", s) + + s = "aBc" + s.downcase + assert_equal("aBc", s) + s.downcase! + assert_equal("abc", s) + + s = "aBc" + s.swapcase + assert_equal("aBc", s) + s.swapcase! + assert_equal("AbC", s) + + s = "aBc" + s.capitalize + assert_equal("aBc", s) + s.capitalize! + assert_equal("Abc", s) + + s = "aBc" + s.tr("a-z", "A-Z") + assert_equal("aBc", s) + s.tr!("a-z", "A-Z") + assert_equal("ABC", s) + + s = "aaBBcc" + s.tr_s("a-z", "A-Z") + assert_equal("aaBBcc", s) + s.tr_s!("a-z", "A-Z") + assert_equal("ABBC", s) + + s = "aaBBcc" + s.squeeze("a-z") + assert_equal("aaBBcc", s) + s.squeeze!("a-z") + assert_equal("aBBc", s) + + s = "aaBBcc" + s.delete("a-z") + assert_equal("aaBBcc", s) + 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 new file mode 100644 index 0000000000..01e5cc68f6 --- /dev/null +++ b/test/ruby/test_struct.rb @@ -0,0 +1,577 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' +require 'timeout' + +module TestStruct + def test_struct + struct_test = @Struct.new("Test", :foo, :bar) + assert_equal(@Struct::Test, struct_test) + + test = struct_test.new(1, 2) + assert_equal(1, test.foo) + assert_equal(2, test.bar) + assert_equal(1, test[0]) + assert_equal(2, test[1]) + + a, b = test.to_a + assert_equal(1, a) + assert_equal(2, b) + + test[0] = 22 + assert_equal(22, test.foo) + + test.bar = 47 + assert_equal(47, test.bar) + + @Struct.class_eval do + remove_const :Test + end + end + + # [ruby-dev:26247] more than 10 struct members causes segmentation fault + 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 + list.each do |ch| + c.__send__(ch) + end + list.pop + end + end + + def test_larger_than_largest_pool + count = (GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] / RbConfig::SIZEOF["void*"]) + 1 + list = Array(0..count) + klass = @Struct.new(*list.map { |i| :"a_#{i}"}) + struct = klass.new(*list) + assert_equal 0, struct.a_0 + end + + def test_small_structs + names = [:a, :b, :c, :d] + 1.upto(4) {|n| + fields = names[0, n] + klass = @Struct.new(*fields) + o = klass.new(*(0...n).to_a) + fields.each_with_index {|name, i| + assert_equal(i, o[name]) + } + o = klass.new(*(0...n).to_a.reverse) + fields.each_with_index {|name, i| + assert_equal(n-i-1, o[name]) + } + } + end + + def test_inherit + klass = @Struct.new(:a) + klass2 = Class.new(klass) + o = klass2.new(1) + assert_equal(1, o.a) + end + + def test_attrset_id + assert_raise(ArgumentError) { Struct.new(:x=) } + end + + def test_members + klass = @Struct.new(:a) + o = klass.new(1) + assert_equal([:a], klass.members) + assert_equal([:a], o.members) + end + + def test_ref + klass = @Struct.new(:a) + o = klass.new(1) + assert_equal(1, o[:a]) + assert_raise(NameError) { o[:b] } + end + + def test_set + klass = @Struct.new(:a) + o = klass.new(1) + o[:a] = 2 + assert_equal(2, o[:a]) + assert_raise(NameError) { o[:b] = 3 } + 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_equal([:utime, :stime, :cutime, :cstime], Process.times.members) + end + + def test_struct_new_with_hash + assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {})} + assert_raise_with_message(TypeError, /not a symbol/) {Struct.new(:a, {name: "b"})} + end + + def test_struct_new_with_keyword_init + @Struct.new("KeywordInitTrue", :a, :b, keyword_init: true) + @Struct.new("KeywordInitFalse", :a, :b, keyword_init: false) + + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new({a: 100}, 2) } + assert_nothing_raised { @Struct::KeywordInitFalse.new(1, 2) } + assert_nothing_raised { @Struct::KeywordInitTrue.new(a: 1, b: 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(1, b: 2) } + assert_raise(ArgumentError) { @Struct::KeywordInitTrue.new(a: 1, b: 2, c: 3) } + assert_equal @Struct::KeywordInitTrue.new(a: 1, b: 2).values, @Struct::KeywordInitFalse.new(1, 2).values + assert_equal "#{@Struct}::KeywordInitFalse", @Struct::KeywordInitFalse.inspect + assert_equal "#{@Struct}::KeywordInitTrue(keyword_init: true)", @Struct::KeywordInitTrue.inspect + # eval is needed to prevent the warning duplication filter + k = Class.new(@Struct::KeywordInitTrue) {def initialize(b, options); super(a: options, b: b); end} + o = assert_warn('') { k.new(42, {foo: 1, bar: 2}) } + assert_equal(1, o.a[:foo]) + + @Struct.instance_eval do + remove_const(:KeywordInitTrue) + remove_const(:KeywordInitFalse) + end + end + + def test_struct_new_with_keyword_init_and_block + struct = @Struct.new(:a, :b, keyword_init: true) do + def c + a + b + end + end + + assert_equal(3, struct.new(a: 1, b: 2).c) + end + + def test_struct_keyword_init_p + struct = @Struct.new(:a, :b, keyword_init: true) + assert_equal(true, struct.keyword_init?) + + struct = @Struct.new(:a, :b, keyword_init: false) + assert_equal(false, struct.keyword_init?) + + struct = @Struct.new(:a, :b) + assert_nil(struct.keyword_init?) + end + + def test_initialize + klass = @Struct.new(:a) + assert_raise(ArgumentError) { klass.new(1, 2) } + 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_initialize_with_kw + klass = @Struct.new(:foo, :options) do + def initialize(foo, **options) + super(foo, options) + end + end + assert_equal({}, klass.new(42, **Hash.new).options) + x = assert_warn('') { klass.new(1, bar: 2) } + assert_equal 2, x.options[:bar] + end + + def test_each + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + assert_equal([1, 2], o.each.to_a) + end + + def test_each_pair + 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) + 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) } + + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + assert_equal("#<struct a=1, b=2>", o.inspect) + + 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) + o = klass.new(1) + assert_equal(o, o.dup) + end + + def test_aref + klass = @Struct.new(:a) + o = klass.new(1) + assert_equal(1, o[0]) + 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) + o = klass.new(1) + o[0] = 2 + assert_equal(2, o[:a]) + 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) + 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) + 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) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_equal(o1, o2) + assert_not_equal(o1, o3) + end + + def test_hash + klass = @Struct.new(:a) + o = klass.new(1) + assert_kind_of(Integer, o.hash) + assert_kind_of(String, o.hash.to_s) + end + + def test_eql + klass1 = @Struct.new(:a) + klass2 = @Struct.new(:a, :b) + o1 = klass1.new(1) + o2 = klass1.new(1) + o3 = klass2.new(1) + assert_operator(o1, :eql?, o2) + assert_not_operator(o1, :eql?, o3) + end + + def test_size + klass = @Struct.new(:a) + o = klass.new(1) + assert_equal(1, o.size) + end + + def test_error + assert_raise(TypeError){ + @Struct.new(0) + } + end + + def test_redefinition_warning + @Struct.new(name = "RedefinitionWarning") + e = EnvUtil.verbose_warning do + @Struct.new("RedefinitionWarning") + end + assert_match(/redefining constant #@Struct::RedefinitionWarning/, e) + + @Struct.class_eval do + remove_const name + end + end + + def test_keyword_args_warning + assert_warn('') { assert_equal(1, @Struct.new(:a).new(a: 1).a) } + assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: nil).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, :b).new(1, a: 1).b) } + assert_warn('') { assert_equal(1, @Struct.new(:a, keyword_init: true).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: nil).new({a: 1}).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new(a: 1).a) } + assert_warn('') { assert_equal({a: 1}, @Struct.new(:a, keyword_init: false).new({a: 1}).a) } + end + + def test_nonascii + struct_test = @Struct.new(name = "R\u{e9}sum\u{e9}", :"r\u{e9}sum\u{e9}") + assert_equal(@Struct.const_get("R\u{e9}sum\u{e9}"), struct_test, '[ruby-core:24849]') + a = struct_test.new(42) + assert_equal("#<struct #@Struct::R\u{e9}sum\u{e9} r\u{e9}sum\u{e9}=42>", a.inspect, '[ruby-core:24849]') + 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 + + @Struct.class_eval do + remove_const name + 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) + + x = klass1.new(1, 2, nil); x.c = x + y = klass1.new(1, 2, nil); y.c = y + Timeout.timeout(1) { + assert_equal x, y + assert_operator x, :eql?, y + } + + z = klass1.new(:something, :other, nil); z.c = z + Timeout.timeout(1) { + assert_not_equal x, z + assert_not_operator x, :eql?, z + } + + x.c = y; y.c = x + Timeout.timeout(1) { + assert_equal x, y + assert_operator x, :eql?, y + } + + x.c = z; z.c = x + Timeout.timeout(1) { + 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_duplicate + bug12291 = '[ruby-core:74971] [Bug #12291]' + assert_raise_with_message(ArgumentError, /duplicate member/, bug12291) { + @Struct.new(:a, :a) + } + end + + def test_deconstruct_keys + klass = @Struct.new(:a, :b) + o = klass.new(1, 2) + assert_equal({a: 1, b: 2}, o.deconstruct_keys(nil)) + assert_equal({a: 1, b: 2}, o.deconstruct_keys([:b, :a])) + assert_equal({a: 1}, o.deconstruct_keys([:a])) + assert_not_send([o.deconstruct_keys([:a, :c]), :key?, :c]) + assert_raise(TypeError) { + o.deconstruct_keys(0) + } + end + + def test_public_send + klass = @Struct.new(:a) + x = klass.new(1) + assert_equal(1, x.public_send("a")) + assert_equal(42, x.public_send("a=", 42)) + assert_equal(42, x.public_send("a")) + end + + def test_arity + klass = @Struct.new(:a) + assert_equal 0, klass.instance_method(:a).arity + assert_equal 1, klass.instance_method(:a=).arity + + klass.module_eval do + define_method(:b=, instance_method(:a=)) + alias c= a= + end + + assert_equal 1, klass.instance_method(:b=).arity + assert_equal 1, klass.instance_method(:c=).arity + end + + def test_parameters + klass = @Struct.new(:a) + assert_equal [], klass.instance_method(:a).parameters + # NOTE: :_ may not be a spec. + assert_equal [[:req, :_]], klass.instance_method(:a=).parameters + + klass.module_eval do + define_method(:b=, instance_method(:a=)) + alias c= a= + end + + assert_equal [[:req, :_]], klass.instance_method(:b=).parameters + assert_equal [[:req, :_]], klass.instance_method(:c=).parameters + end + + def test_named_structs_are_not_rooted + omit 'skip on riscv64-linux CI machine. See https://github.com/ruby/ruby/pull/13422' if ENV['RUBY_DEBUG'] == 'ci' && /riscv64-linux/ =~ RUBY_DESCRIPTION + + # [Bug #20311] + assert_no_memory_leak([], <<~PREP, <<~CODE, rss: true) + code = proc do + Struct.new("A") + Struct.send(:remove_const, :A) + end + + 10_000.times(&code) + PREP + 50_000.times(&code) + CODE + end + + def test_frozen_subclass + test = Class.new(@Struct.new(:a)).freeze.new(a: 0) + assert_kind_of(@Struct, test) + assert_equal([:a], test.members) + 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 new file mode 100644 index 0000000000..25bad2242a --- /dev/null +++ b/test/ruby/test_super.rb @@ -0,0 +1,777 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestSuper < Test::Unit::TestCase + class Base + def single(a) a end + def double(a, b) [a,b] end + def array(*a) a end + def optional(a = 0) a end + def keyword(**a) a end + def forward(*a) a end + end + class Single1 < Base + def single(*) super end + end + class Single2 < Base + def single(a,*) super end + end + class Double1 < Base + def double(*) super end + end + class Double2 < Base + def double(a,*) super end + end + class Double3 < Base + def double(a,b,*) super end + end + class Array1 < Base + def array(*) super end + end + class Array2 < Base + def array(a,*) super end + end + class Array3 < Base + def array(a,b,*) super end + end + class Array4 < Base + def array(a,b,c,*) super end + end + class Optional1 < Base + def optional(a = 1) super end + end + class Optional2 < Base + def optional(a, b = 1) super end + end + class Optional3 < Base + def single(a = 1) super end + end + class Optional4 < Base + def array(a = 1, *) super end + end + 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 + class Forward < Base + def forward(...) + w = super() + x = super + y = super(...) + a = 1 + z = super(a, ...) + [w, x, y, z] + end + end + + def test_single1 + assert_equal(1, Single1.new.single(1)) + end + def test_single2 + assert_equal(1, Single2.new.single(1)) + end + def test_double1 + assert_equal([1, 2], Double1.new.double(1, 2)) + end + def test_double2 + assert_equal([1, 2], Double2.new.double(1, 2)) + end + def test_double3 + assert_equal([1, 2], Double3.new.double(1, 2)) + end + def test_array1 + assert_equal([], Array1.new.array()) + assert_equal([1], Array1.new.array(1)) + end + def test_array2 + assert_equal([1], Array2.new.array(1)) + assert_equal([1,2], Array2.new.array(1, 2)) + end + def test_array3 + assert_equal([1,2], Array3.new.array(1, 2)) + assert_equal([1,2,3], Array3.new.array(1, 2, 3)) + end + def test_array4 + assert_equal([1,2,3], Array4.new.array(1, 2, 3)) + assert_equal([1,2,3,4], Array4.new.array(1, 2, 3, 4)) + end + def test_optional1 + assert_equal(9, Optional1.new.optional(9)) + assert_equal(1, Optional1.new.optional) + end + def test_optional2 + assert_raise(ArgumentError) do + # call Base#optional with 2 arguments; the 2nd arg is supplied + Optional2.new.optional(9) + end + assert_raise(ArgumentError) do + # call Base#optional with 2 arguments + Optional2.new.optional(9, 2) + end + end + def test_optional3 + assert_equal(9, Optional3.new.single(9)) + # call Base#single with 1 argument; the arg is supplied + assert_equal(1, Optional3.new.single) + end + def test_optional4 + assert_equal([1], Optional4.new.array) + assert_equal([9], Optional4.new.array(9)) + assert_equal([9, 8], Optional4.new.array(9, 8)) + end + def test_optional5 + assert_equal([1, 2], Optional5.new.array) + assert_equal([9, 2], Optional5.new.array(9)) + 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 + def test_forwardable(...) + assert_equal([[],[],[],[1]], Forward.new.forward()) + assert_equal([[],[1,2],[1,2],[1,1,2]], Forward.new.forward(1,2)) + assert_equal([[],[:test],[:test],[1,:test]], Forward.new.forward(:test, ...)) + end + + class A + def tt(aa) + "A#tt" + end + + def uu(a) + class << self + define_method(:tt) do |sym| + super(sym) + end + end + end + end + + def test_define_method + a = A.new + a.uu(12) + assert_equal("A#tt", a.tt(12), "[ruby-core:3856]") + assert_raise_with_message(RuntimeError, /implicit argument passing of super from method defined by define_method/, "[ruby-core:24244]") { + lambda { + Class.new { + define_method(:a) {super} + }.new.a + }.call + } + 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_instance_eval_in_module + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + return [:super, self] + end + } + mod = EnvUtil.labeled_module("Mod\u{30af 30e9 30b9}") { + def foo + x = Object.new + x.instance_eval do + super() + end + end + } + sub_class = EnvUtil.labeled_class("Sub\u{30af 30e9 30b9}", super_class) { + include mod + } + obj = sub_class.new + assert_raise_with_message(TypeError, /Sub\u{30af 30e9 30b9}/) do + obj.foo + end + end + + def test_super_in_orphan_block + super_class = EnvUtil.labeled_class("Super\u{30af 30e9 30b9}") { + def foo + 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 + + # [Bug #18329] + def test_super_missing_prepended_module + a = Module.new do + def probe(*methods) + prepend(probing_module(methods)) + end + + def probing_module(methods) + Module.new do + methods.each do |method| + define_method(method) do |*args, **kwargs, &block| + super(*args, **kwargs, &block) + end + end + end + end + end + + b = Class.new do + extend a + + probe :danger!, :missing + + def danger!; end + end + + o = b.new + o.danger! + begin + original_gc_stress = GC.stress + GC.stress = true + 2.times { o.missing rescue NoMethodError } + ensure + GC.stress = original_gc_stress + end + end + + def test_zsuper_kw_splat_not_mutable + extend(Module.new{def a(**k) k[:a] = 1 end}) + extend(Module.new do + def a(**k) + before = k.dup + super + [before, k] + end + end) + assert_equal(*a) + 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 + + def test_super_with_included_prepended_module_method_caching_bug_20716 + a = Module.new do + def test(*args) + super + end + end + + b = Module.new do + def test(a) + a + end + end + + c = Class.new + + b.prepend(a) + c.include(b) + + assert_equal(1, c.new.test(1)) + + b.class_eval do + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + def test + :test + end + ensure + $VERBOSE = verbose_bak + end + end + + assert_equal(:test, c.new.test) + end + + class TestFor_super_with_modified_rest_parameter_base + def foo *args + args + end + end + + class TestFor_super_with_modified_rest_parameter < TestFor_super_with_modified_rest_parameter_base + def foo *args + args = 13 + super + end + end + def test_super_with_modified_rest_parameter + assert_equal [13], TestFor_super_with_modified_rest_parameter.new.foo + end + + def test_super_with_define_method + superklass1 = Class.new do + def foo; :foo; end + def bar; :bar; end + def boo; :boo; end + end + superklass2 = Class.new(superklass1) do + alias baz boo + def boo; :boo2; end + end + subklass = Class.new(superklass2) + [:foo, :bar, :baz, :boo].each do |sym| + subklass.define_method(sym){ super() } + end + assert_equal :foo, subklass.new.foo + assert_equal :bar, subklass.new.bar + assert_equal :boo, subklass.new.baz + assert_equal :boo2, subklass.new.boo + end + + def test_super_attr_writer # [Bug #16785] + writer_class = Class.new do + attr_writer :test + end + superwriter_class = Class.new(writer_class) do + def initialize + @test = 1 # index: 1 + end + + def test=(test) + super(test) + end + end + inherited_class = Class.new(superwriter_class) do + def initialize + @a = nil + @test = 2 # index: 2 + end + end + + superwriter = superwriter_class.new + superwriter.test = 3 # set ic->index of superwriter_class#test= to 1 + + inherited = inherited_class.new + inherited.test = 4 # it may set 4 to index=1 while it should be index=2 + + assert_equal 3, superwriter.instance_variable_get(:@test) + assert_equal 4, inherited.instance_variable_get(:@test) + end + + def test_super_attr_reader + reader_class = Class.new do + attr_reader :test + end + superreader_class = Class.new(reader_class) do + def initialize + @test = 1 # index: 1 + end + + def test + super + end + end + inherited_class = Class.new(superreader_class) do + def initialize + @a = nil + @test = 2 # index: 2 + end + end + + superreader = superreader_class.new + assert_equal 1, superreader.test # set ic->index of superreader_class#test to 1 + + inherited = inherited_class.new + assert_equal 2, inherited.test # it may read index=1 while it should be index=2 + end + + def test_super_in_basic_object + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + class ::BasicObject + def no_super + super() + rescue ::NameError + :ok + end + end + + assert_equal :ok, "[Bug #21694]".no_super + end; + end +end diff --git a/test/ruby/test_symbol.rb b/test/ruby/test_symbol.rb new file mode 100644 index 0000000000..c50febf5d1 --- /dev/null +++ b/test/ruby/test_symbol.rb @@ -0,0 +1,632 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestSymbol < Test::Unit::TestCase + # [ruby-core:3573] + + 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(:"=", false) + assert_eval_inspected(:"0", false) + assert_eval_inspected(:"$1") + assert_eval_inspected(:"@1", false) + assert_eval_inspected(:"@@1", false) + assert_eval_inspected(:"@", false) + assert_eval_inspected(:"@@", false) + assert_eval_inspected(:"[]=") + assert_eval_inspected(:"[][]", false) + assert_eval_inspected(:"[][]=", false) + assert_eval_inspected(:"@=", false) + assert_eval_inspected(:"@@=", false) + assert_eval_inspected(:"@x=", false) + assert_eval_inspected(:"@@x=", false) + assert_eval_inspected(:"$$=", false) + assert_eval_inspected(:"$==", false) + assert_eval_inspected(:"$x=", false) + assert_eval_inspected(:"$$$=", false) + assert_eval_inspected(:"foo?=", false) + assert_eval_inspected(:"foo!=", false) + end + + def assert_inspect_evaled(n) + assert_nothing_raised(SyntaxError) {assert_equal(n, eval(n).inspect)} + end + + def test_inspect_suboptimal + # 3) Symbol#inspect sometimes returns suboptimal symbol representations: + 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(':=~') + assert_inspect_evaled(':==') + assert_inspect_evaled(':===') + assert_raise(SyntaxError) {eval ':='} + assert_inspect_evaled(':*') + assert_inspect_evaled(':**') + assert_raise(SyntaxError) {eval ':***'} + assert_inspect_evaled(':+') + assert_inspect_evaled(':-') + assert_inspect_evaled(':+@') + assert_inspect_evaled(':-@') + assert_inspect_evaled(':|') + assert_inspect_evaled(':^') + assert_inspect_evaled(':&') + assert_inspect_evaled(':/') + assert_inspect_evaled(':%') + assert_inspect_evaled(':~') + assert_inspect_evaled(':`') + assert_inspect_evaled(':[]') + assert_inspect_evaled(':[]=') + assert_raise(SyntaxError) {eval ':||'} + assert_raise(SyntaxError) {eval ':&&'} + assert_raise(SyntaxError) {eval ':['} + end + + def test_inspect_dollar + verbose_bak, $VERBOSE = $VERBOSE, nil + # 4) :$- always treats next character literally: + assert_raise(SyntaxError) {eval ':$-'} + assert_raise(SyntaxError) {eval ":$-\n"} + assert_raise(SyntaxError) {eval ":$- "} + assert_raise(SyntaxError) {eval ":$-#"} + assert_raise(SyntaxError) {eval ':$-('} + ensure + $VERBOSE = verbose_bak + end + + def test_inspect_number + # 5) Inconsistency between :$0 and :$1? The first one is valid, but the + # latter isn't. + assert_inspect_evaled(':$0') + 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_inspect_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + + EnvUtil.under_gc_compact_stress do + assert_inspect_evaled(':testing') + end + end + + def test_name + assert_equal("foo", :foo.name) + assert_same(:foo.name, :foo.name) + assert_predicate(:foo.name, :frozen?) + end + + def test_to_proc + assert_equal %w(1 2 3), (1..3).map(&:to_s) + [ + [], + [1], + [1, 2], + [1, [2, 3]], + ].each do |ary| + ary_id = ary.object_id + assert_equal ary_id, :object_id.to_proc.call(ary) + ary_ids = ary.collect{|x| x.object_id } + assert_equal ary_ids, ary.collect(&:object_id) + 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_lambda? + assert_predicate(:itself.to_proc, :lambda?) + end + + def test_to_proc_arity + assert_equal(-2, :itself.to_proc.arity) + end + + def test_to_proc_call_with_symbol_proc + first = 1 + bug11594 = "[ruby-core:71088] [Bug #11594] corrupted the first local variable" + # 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 + def _test_to_proc_with_refinements_call(&block) + block + end + using Module.new { + refine TestToPRocArgWithRefinements do + def hoge + :hoge + end + end + } + def test_to_proc_arg_with_refinements + assert_equal(:hoge, _test_to_proc_arg_with_refinements_call(&:hoge)) + end + + def test_to_proc_lambda_with_refinements + assert_predicate(_test_to_proc_with_refinements_call(&:hoge), :lambda?) + end + + def test_to_proc_arity_with_refinements + assert_equal(-2, _test_to_proc_with_refinements_call(&:hoge).arity) + end + + def self._test_to_proc_arg_with_refinements_call(&block) + block.call TestToPRocArgWithRefinements.new + end + _test_to_proc_arg_with_refinements_call(&:hoge) + using Module.new { + refine TestToPRocArgWithRefinements do + def hoge + :hogehoge + end + end + } + def test_to_proc_arg_with_refinements_override + assert_equal(:hogehoge, _test_to_proc_arg_with_refinements_call(&:hoge)) + end + + def test_to_proc_arg_with_refinements_undefined + assert_raise(NoMethodError) do + _test_to_proc_arg_with_refinements_call(&:foo) + end + end + + private def return_from_proc + Proc.new { return 1 }.tap(&:call) + end + + 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([[:req], [:rest]], :class.to_proc.parameters, bug11845) + c = Class.new {define_method(:klass, :class.to_proc)} + m = c.instance_method(:klass) + assert_nil(m.source_location, bug11845) + assert_equal([[:req], [: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 + + assert_equal(3, :foo.to_proc.call(o, 1, 2)) + 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 + + def test_cmp + assert_equal(0, :FoO <=> :FoO) + assert_equal(-1, :FoO <=> :fOO) + assert_equal(1, :fOO <=> :FoO) + assert_nil(:foo <=> "foo") + end + + def test_casecmp + 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 + assert_equal(3, :FoO.length) + assert_equal(3, :FoO.size) + end + + def test_empty + assert_equal(false, :FoO.empty?) + assert_equal(true, :"".empty?) + end + + def test_case + assert_equal(:FOO, :FoO.upcase) + assert_equal(:foo, :FoO.downcase) + assert_equal(:Foo, :foo.capitalize) + assert_equal(:fOo, :FoO.swapcase) + end + + 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 + + def test_ascii_incomat_inspect + [Encoding::UTF_16LE, Encoding::UTF_16BE, + Encoding::UTF_32LE, Encoding::UTF_32BE].each do |e| + assert_equal(':"abc"', "abc".encode(e).to_sym.inspect) + 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_memory_leak + bug10686 = '[ruby-core:67268] [Bug #10686]' + assert_no_memory_leak([], "#{<<~"begin;"}\n#{<<~'else;'}", "#{<<~'end;'}", bug10686, limit: 1.71, rss: true, timeout: 20) + begin; + n = 100_000 + n.times { |i| i.to_s.to_sym } + else; + (2 * n).times { |i| (i + n).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_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 + + def test_start_with? + assert_equal(true, :hello.start_with?("hel")) + assert_equal(false, :hello.start_with?("el")) + assert_equal(true, :hello.start_with?("el", "he")) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {:str.start_with? :not_convertible_to_string} + + assert_equal(true, :hello.start_with?(/hel/)) + assert_equal("hel", $&) + assert_equal(false, :hello.start_with?(/el/)) + assert_nil($&) + end + + def test_end_with? + assert_equal(true, :hello.end_with?("llo")) + assert_equal(false, :hello.end_with?("ll")) + assert_equal(true, :hello.end_with?("el", "lo")) + + bug5536 = '[ruby-core:40623]' + assert_raise(TypeError, bug5536) {:str.end_with? :not_convertible_to_string} + end +end diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb new file mode 100644 index 0000000000..b355128a73 --- /dev/null +++ b/test/ruby/test_syntax.rb @@ -0,0 +1,2402 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestSyntax < Test::Unit::TestCase + using Module.new { + refine(Object) do + def `(s) #` + s + end + end + } + + def assert_syntax_files(test) + srcdir = File.expand_path("../../..", __FILE__) + srcdir = File.join(srcdir, test) + assert_separately(%W[- #{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_script_lines_encoding + require 'tmpdir' + Dir.mktmpdir do |dir| + File.write(File.join(dir, "script_lines.rb"), "SCRIPT_LINES__ = {}\n") + assert_in_out_err(%w"-r./script_lines -w -Ke", "puts __ENCODING__.name", + %w"EUC-JP", /-K is specified/, chdir: dir) + end + end + + def test_anonymous_block_forwarding + assert_syntax_error("def b; c(&); end", /no anonymous block parameter/) + assert_syntax_error("def b(&) ->(&) {c(&)} end", /anonymous block parameter is also used/) + assert_valid_syntax("def b(&) ->() {c(&)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(&); c(&) end + def c(&); yield 1 end + a = nil + b{|c| a = c} + assert_equal(1, a) + + def inner + yield + end + + def block_only(&) + inner(&) + end + assert_equal(1, block_only{1}) + + def pos(arg1, &) + inner(&) + end + assert_equal(2, pos(nil){2}) + + def pos_kwrest(arg1, **kw, &) + inner(&) + end + assert_equal(3, pos_kwrest(nil){3}) + + def no_kw(arg1, **nil, &) + inner(&) + end + assert_equal(4, no_kw(nil){4}) + + def rest_kw(*a, kwarg: 1, &) + inner(&) + end + assert_equal(5, rest_kw{5}) + + def kw(kwarg:1, &) + inner(&) + end + assert_equal(6, kw{6}) + + def pos_kw_kwrest(arg1, kwarg:1, **kw, &) + inner(&) + end + assert_equal(7, pos_kw_kwrest(nil){7}) + + def pos_rkw(arg1, kwarg1:, &) + inner(&) + end + assert_equal(8, pos_rkw(nil, kwarg1: nil){8}) + + def all(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, &) + inner(&) + end + assert_equal(9, all(nil, nil, nil, nil, okw1: nil, okw2: nil){9}) + + def all_kwrest(arg1, arg2, *rest, post1, post2, kw1: 1, kw2: 2, okw1:, okw2:, **kw, &) + inner(&) + end + assert_equal(10, all_kwrest(nil, nil, nil, nil, okw1: nil, okw2: nil){10}) + + def evaled(&) + eval("inner(&)") + end + assert_equal(1, evaled{1}) + end; + end + + def test_anonymous_rest_forwarding + assert_syntax_error("def b; c(*); end", /no anonymous rest parameter/) + assert_syntax_error("def b; c(1, *); end", /no anonymous rest parameter/) + assert_syntax_error("def b(*) ->(*) {c(*)} end", /anonymous rest parameter is also used/) + assert_syntax_error("def b(a, *) ->(*) {c(1, *)} end", /anonymous rest parameter is also used/) + assert_syntax_error("def b(*) ->(a, *) {c(*)} end", /anonymous rest parameter is also used/) + assert_valid_syntax("def b(*) ->() {c(*)} end") + assert_valid_syntax("def b(a, *) ->() {c(1, *)} end") + assert_valid_syntax("def b(*) ->(a) {c(*)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(*); c(*) end + def c(*a); a end + def d(*); b(*, *) end + def e(*); eval("b(*)") end + assert_equal([1, 2], b(1, 2)) + assert_equal([1, 2, 1, 2], d(1, 2)) + assert_equal([1, 2], e(1, 2)) + end; + end + + def test_anonymous_keyword_rest_forwarding + assert_syntax_error("def b; c(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b; c(k: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def b(**) ->(**) {c(**)} end", /anonymous keyword rest parameter is also used/) + assert_syntax_error("def b(k:, **) ->(**) {c(k: 1, **)} end", /anonymous keyword rest parameter is also used/) + assert_syntax_error("def b(**) ->(k:, **) {c(**)} end", /anonymous keyword rest parameter is also used/) + assert_valid_syntax("def b(**) ->() {c(**)} end") + assert_valid_syntax("def b(k:, **) ->() {c(k: 1, **)} end") + assert_valid_syntax("def b(**) ->(k:) {c(**)} end") + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + def b(**); c(**) end + def c(**kw); kw end + def d(**); b(k: 1, **) end + def e(**); b(**, k: 1) end + def f(a: nil, **); b(**) end + def g(**); eval("b(**)") end + assert_equal({a: 1, k: 3}, b(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, d(a: 1, k: 3)) + assert_equal({a: 1, k: 1}, e(a: 1, k: 3)) + assert_equal({k: 3}, f(a: 1, k: 3)) + assert_equal({a: 1, k: 3}, g(a: 1, k: 3)) + end; + end + + def test_argument_forwarding_with_anon_rest_kwrest_and_block + assert_syntax_error("def f(*, **, &); g(...); end", /unexpected \.\.\./) + assert_syntax_error("def f(...); g(*); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/) + assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/) + 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 n3 == '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_hash_kwsplat_hash + kw = {} + h = {a: 1} + assert_equal({}, {**{}}) + assert_equal({}, {**kw}) + assert_equal(h, {**h}) + assert_equal(false, {**{}}.frozen?) + assert_equal(false, {**kw}.equal?(kw)) + assert_equal(false, {**h}.equal?(h)) + end + + def test_array_kwsplat_hash + kw = {} + h = {a: 1} + a = [] + assert_equal([], [**{}]) + assert_equal([], [**kw]) + assert_equal([h], [**h]) + assert_equal([{}], [{}]) + assert_equal([kw], [kw]) + assert_equal([h], [h]) + + assert_equal([1], [1, **{}]) + assert_equal([1], [1, **kw]) + assert_equal([1, h], [1, **h]) + assert_equal([1, {}], [1, {}]) + assert_equal([1, kw], [1, kw]) + assert_equal([1, h], [1, h]) + + assert_equal([], [*a, **{}]) + assert_equal([], [*a, **kw]) + assert_equal([h], [*a, **h]) + assert_equal([{}], [*a, {}]) + assert_equal([kw], [*a, kw]) + assert_equal([h], [*a, h]) + + assert_equal([1], [1, *a, **{}]) + assert_equal([1], [1, *a, **kw]) + assert_equal([1, h], [1, *a, **h]) + assert_equal([1, {}], [1, *a, {}]) + assert_equal([1, kw], [1, *a, kw]) + assert_equal([1, h], [1, *a, h]) + + assert_equal([], [**kw, **kw]) + assert_equal([], [**kw, **{}, **kw]) + assert_equal([1], [1, **kw, **{}, **kw]) + + assert_equal([{}], [{}, **kw, **kw]) + assert_equal([kw], [kw, **kw, **kw]) + assert_equal([h], [h, **kw, **kw]) + assert_equal([h, h], [h, **kw, **kw, **h]) + + assert_equal([h, {:a=>2}], [h, **{}, **h, a: 2]) + assert_equal([h, h], [h, **{}, a: 2, **h]) + assert_equal([h, h], [h, a: 2, **{}, **h]) + assert_equal([h, h], [h, a: 2, **h, **{}]) + assert_equal([h, {:a=>2}], [h, **h, a: 2, **{}]) + assert_equal([h, {:a=>2}], [h, **h, **{}, a: 2]) + end + + def test_normal_argument + assert_valid_syntax('def foo(x) end') + assert_syntax_error('def foo(X) end', /constant/) + 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 + begin + verbose_bak, $VERBOSE = $VERBOSE, nil + assert_equal([23, 2], eval("o.kw(**{k1: 22}, **{k1: 23})"), bug10315) + ensure + $VERBOSE = verbose_bak + end + + h = {k3: 31} + assert_raise(ArgumentError) {o.kw(**h)} + h = {"k1"=>11, k2: 12} + assert_raise(ArgumentError) {o.kw(**h)} + end + + def test_keyword_duplicated + bug10315 = '[ruby-core:65625] [Bug #10315]' + a = [] + def a.add(x) push(x); x; end + b = a.clone + def a.f(k:, **) k; end + def b.f(k:) k; end + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("b.f(k: b.add(1), k: b.add(2))")} + assert_equal(2, r) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4))")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) + a.clear + r = nil + assert_warn(/duplicated/) {r = eval("b.f(**{k: b.add(1), k: b.add(2)})")} + assert_equal(2, r) + assert_equal([1, 2], b, bug10315) + b.clear + r = nil + assert_warn(/duplicated/) {r = eval("a.f(**{k: a.add(1), j: a.add(2), k: a.add(3), k: a.add(4)})")} + assert_equal(4, r) + assert_equal([1, 2, 3, 4], a) + a.clear + r = nil + _z = {} + assert_warn(/duplicated/) {r = eval("a.f(k: a.add(1), **_z, k: a.add(2))")} + assert_equal(2, r) + assert_equal([1, 2], a) + 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 + assert_valid_syntax("def foo(var: defined?(var)) var end") + assert_valid_syntax("def foo(var: var) var end") + assert_valid_syntax("def foo(var: bar(var)) var end") + assert_valid_syntax("def foo(var: bar {var}) var end") + assert_valid_syntax("def foo(var: (1 in ^var)); 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: bar {| | var}) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var: bar {|| var}) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var: def bar(var) var; end) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("proc {|var: 1| var}") + end + + o = Object.new + assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo")) + end + + def test_keyword_invalid_name + bug11663 = '[ruby-core:71356] [Bug #11663]' + + assert_syntax_error('def foo(arg1?:) end', /arg1\?/, bug11663) + assert_syntax_error('def foo(arg1?:, arg2:) end', /arg1\?/, bug11663) + assert_syntax_error('proc {|arg1?:|}', /arg1\?/, bug11663) + assert_syntax_error('proc {|arg1?:, arg2:|}', /arg1\?/, bug11663) + + bug10545 = '[ruby-dev:48742] [Bug #10545]' + assert_syntax_error('def foo(FOO: a) end', /constant/, bug10545) + assert_syntax_error('def foo(@foo: a) end', /instance variable/) + assert_syntax_error('def foo(@@foo: a) end', /class variable/) + end + + def test_keywords_specified_and_not_accepted + assert_syntax_error('def foo(a:, **nil) end', /unexpected/) + assert_syntax_error('def foo(a:, **nil, &b) end', /unexpected/) + assert_syntax_error('def foo(**a, **nil) end', /unexpected/) + assert_syntax_error('def foo(**a, **nil, &b) end', /unexpected/) + assert_syntax_error('def foo(**nil, **a) end', /unexpected/) + assert_syntax_error('def foo(**nil, **a, &b) end', /unexpected/) + + assert_syntax_error('proc do |a:, **nil| end', /unexpected/) + assert_syntax_error('proc do |a:, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil| end', /unexpected/) + assert_syntax_error('proc do |**a, **nil, &b| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a| end', /unexpected/) + assert_syntax_error('proc do |**nil, **a, &b| end', /unexpected/) + end + + def test_optional_self_reference + assert_valid_syntax("def foo(var = defined?(var)) var end") + assert_valid_syntax("def foo(var = var) var end") + assert_valid_syntax("def foo(var = bar(var)) var end") + assert_valid_syntax("def foo(var = bar {var}) var end") + assert_valid_syntax("def foo(var = (def bar;end; var)) var end") + assert_valid_syntax("def foo(var = (def self.bar;end; var)) var end") + assert_valid_syntax("def foo(var = (1 in ^var)); 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 = bar {| | var}) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var = bar {|| var}) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("def foo(var = def bar(var) var; end) var end") + end + + o = Object.new + assert_warn("") do + o.instance_eval("proc {|var = 1| var}") + end + + o = Object.new + assert_nil(o.instance_eval("def foo(bar: bar) = bar; foo")) + 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 + [ + [:**, "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 + warning = /'#{Regexp.escape(op)}' after local variable or literal is interpreted as binary operator.+?even though it seems like #{syn}/m + + assert_warning(warning, 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)) + ensure + self.class.remove_method(:foo) + 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 = ->(line) { "warning: 'when' clause on line #{line} duplicates 'when' clause on line 3 and is ignored" } + assert_warning(/#{w[3]}.+#{w[4]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) { + eval %q{ + case 1 + when 1, 1 + when 1, 1 + when 1, 1 + end + } + } + assert_warning(/#{w[3]}.+#{w[4]}.+#{w[5]}.+#{w[5]}/m) { + a = a = 1 + eval %q{ + case 1 + when 1, 1 + when 1, a + when 1, 1 + end + } + } + assert_warning(/#{w[3]}/) { + eval %q{ + case 1 + when __LINE__, __LINE__ + when 3, 3 + when 3, 3 + end + } + } + assert_warning(/#{w[3]}/) { + eval %q{ + case 1 + when __FILE__, __FILE__ + when "filename", "filename" + when "filename", "filename" + end + }, binding, "filename" + } + end + + def test_duplicated_when_check_option + w = /'when' clause on line 4 duplicates 'when' clause on line 3 and is ignored/ + assert_in_out_err(%[-wc], "#{<<~"begin;"}\n#{<<~'end;'}", ["Syntax OK"], w) + begin; + case 1 + when 1 + when 1 + end + end; + end + + def test_invalid_break + assert_syntax_error("def m; break; end", /Invalid break/) + assert_syntax_error('/#{break}/', /Invalid break/) + assert_syntax_error('/#{break}/o', /Invalid break/) + end + + def test_invalid_next + assert_syntax_error("def m; next; end", /Invalid next/) + assert_syntax_error('/#{next}/', /Invalid next/) + assert_syntax_error('/#{next}/o', /Invalid next/) + end + + def test_lambda_with_space + feature6390 = '[ruby-dev:45605]' + assert_valid_syntax("-> (x, y) {}", __FILE__, feature6390) + 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) + + assert_valid_syntax('->(opt = (foo.[] bar)) {}') + assert_valid_syntax('->(opt = (foo.[]= bar)) {}') + assert_valid_syntax('->(opt = (foo.[] bar)) do end') + assert_valid_syntax('->(opt = (foo.[]= bar)) do end') + end + + def test_reserved_method_no_args + 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_syntax_error("#{kwd} = nil", /Can't .* #{kwd}$/) + 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_leading_blank_line + # the blank line has six leading spaces + result = " \n" \ + " b\n" + expect = " \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; + + assert_equal(" TEXT\n", eval("<<~eos\n" " \\\n" "TEXT\n" "eos\n")) + end + + def test_lineno_after_heredoc + bug7559 = '[ruby-dev:46737]' + expected, _, actual = __LINE__, <<eom, __LINE__ + 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 + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d\u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \xe9\x9d + \u1234 + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234\xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + + e = assert_syntax_error(<<-'HEREDOC', 'UTF-8 mixed within Windows-31J source') + #encoding: cp932 + <<-TEXT + \u1234 + \xe9\x9d + TEXT + HEREDOC + assert_not_match(/end-of-input/, e.message) + end + + def test_invalid_regexp + bug20295 = '[ruby-core:116913] [Bug #20295]' + + assert_syntax_error("/[/=~s", /premature end of char-class/, bug20295) + assert_syntax_error("/(?<>)/=~s", /group name is empty/, bug20295) + assert_syntax_error("/(?<a>[)/=~s", /premature end of char-class/, bug20295) + 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 + 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 + #{"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_no_terminator + assert_syntax_error("puts <<""A\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "A" anywhere before EOF/) + assert_syntax_error("puts <<""A + <<""B\n", /can't find string "B" anywhere before EOF/) + end + + def test_unterminated_heredoc + assert_syntax_error("<<\"EOS\n\nEOS\n", /unterminated/) + assert_syntax_error("<<\"EOS\n\"\nEOS\n", /unterminated/) + end + + def test_unterminated_heredoc_cr + %W[\r\n \n].each do |nl| + assert_syntax_error("<<\"\r\"#{nl}\r#{nl}", /unterminated/, nil, "CR with #{nl.inspect}") + end + end + + def test__END___cr + assert_syntax_error("__END__\r<<<<<\n", /unexpected <</) + end + + def test_warning_for_cr + feature8699 = '[ruby-core:56240] [Feature #8699]' + s = assert_warning(/encountered \\r/, feature8699) do + eval("'\r'\r") + end + assert_equal("\r", s) + s = assert_warning('') do + eval("'\r'\r\n") + end + assert_equal("\r", s) + end + + def test_unexpected_fraction + 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..%q.', /unterminated string/, bug10957) + assert_syntax_error('0...%q.', /unterminated string/, bug10957) + end + + def test_range_at_eol + assert_warn(/\.\.\. at EOL/) {eval("1...\n2")} + assert_warn('') {eval("(1...)")} + assert_warn('') {eval("(1...\n2)")} + assert_warn('') {eval("{a: 1...\n2}")} + + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[]= ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_valid_syntax('foo.[] ...', verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[]= bar, ...', /unexpected/, verbose: true) + end + assert_warn(/\.\.\. at EOL/) do + assert_syntax_error('foo.[] bar, ...', /unexpected/, verbose: true) + end + end + + def test_too_big_nth_ref + 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_invalid_literal_message + assert_syntax_error("def :foo", /unexpected symbol literal/) + assert_syntax_error("def 'foo'", /unexpected string literal/) + end + + def test_fluent_dot + assert_valid_syntax("a\n.foo") + assert_valid_syntax("a\n&.foo") + assert_valid_syntax("a #\n#\n.foo\n") + assert_valid_syntax("a #\n#\n&.foo\n") + end + + def test_fluent_and + assert_valid_syntax("a\n" "&& foo") + assert_valid_syntax("a\n" "and foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + && (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = true + if a + and (a = :ok; true) + a + end + end; + end + + def test_fluent_or + assert_valid_syntax("a\n" "|| foo") + assert_valid_syntax("a\n" "or foo") + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + || (a = :ok; true) + a + end + end; + + assert_equal(:ok, eval("#{<<~"begin;"}\n#{<<~'end;'}")) + begin; + a = false + if a + or (a = :ok; true) + a + end + end; + end + + def test_safe_call_in_massign_lhs + assert_syntax_error("*a&.x=0", /multiple assignment destination/) + assert_syntax_error("a&.x,=0", /multiple assignment destination/) + end + + def test_safe_call_in_for_variable + assert_valid_syntax("for x&.bar in []; end") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + foo = nil + for foo&.bar in [1]; end + assert_nil(foo) + + foo = Struct.new(:bar).new + for foo&.bar in [1]; end + assert_equal(1, foo.bar) + end; + end + + def test_no_warning_logop_literal + 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(/string literal in condition/) do + eval('1 if ""') + end + assert_warning(/string literal in condition/) do + eval('1 if __FILE__') + end + assert_warn(/regex literal in condition/) do + eval('1 if //') + end + assert_warning(/literal in condition/) do + eval('1 if 1') + end + assert_warning(/literal in condition/) do + eval('1 if __LINE__') + end + assert_warning(/symbol literal in condition/) do + eval('1 if :foo') + end + assert_warning(/symbol 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_warning_literal_in_flip_flop + assert_warn(/literal in flip-flop/) do + eval('1 if ""..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo..false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"..false') + end + assert_warn(/literal in flip-flop/) do + eval('1 if ""...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :foo...false') + end + assert_warning(/literal in flip-flop/) do + eval('1 if :"#{"foo".upcase}"...false') + end + end + + def test_alias_symbol + bug8851 = '[ruby-dev:47681] [Bug #8851]' + formats = ['%s', ":'%s'", ':"%s"', '%%s(%s)'] + 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_invalid_encoding_symbol + assert_syntax_error('{"\xC3": 1}', "invalid symbol") + end + + def test_invalid_symbol_in_hash_memory_leak + assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true) + str = '{"\xC3": 1}'.force_encoding("UTF-8") + code = proc do + eval(str) + raise "unreachable" + rescue SyntaxError + end + + 1_000.times(&code) + begin; + 1_000_000.times(&code) + end; + 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 + nil&defined?0--begin e=no_method_error(); return; 0;end + return puts('ignored') #=> ignored + BEGIN {return} + END {return if false} + end; + .split(/\n/).map {|s|[(line+=1), *s.split(/#=> /, 2)]} + failed = proc do |n, s| + 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_eval_return_toplevel + feature4840 = '[ruby-core:36785] [Feature #4840]' + line = __LINE__+2 + code = "#{<<~"begin;"}#{<<~'end;'}" + begin; + eval "return"; raise + begin eval "return"; rescue SystemExit; exit false; end + begin eval "return"; ensure puts "ensured"; end #=> ensured + begin ensure eval "return"; end + begin raise; ensure; eval "return"; end + begin raise; rescue; eval "return"; end + eval "return false"; raise + eval "return 1"; raise + "#{eval "return"}" + raise((eval "return"; "should not raise")) + begin raise; ensure eval "return"; end; self + begin raise; ensure eval "return"; end and self + eval "return puts('ignored')" #=> ignored + BEGIN {eval "return"} + 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, ex, /return/, proc {failed[n, s]}, success: false) + else + File.write(lib, s) + assert_in_out_err(args, "", ex, /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_return_toplevel_with_argument + assert_warn(/argument of top-level return is ignored/) {eval("return 1")} + end + + def test_return_in_proc_in_class + assert_in_out_err(['-e', 'class TestSyntax; proc{ return }.call; end'], "", [], /^-e:1:.*unexpected return \(LocalJumpError\)/) + end + + def test_return_in_END + assert_normal_exit('END {return}') + end + + def test_return_in_BEGIN_in_eval + # `BEGIN` in `eval` is allowed, even inside a method, and `return` + # from that block exits from that method without `LocalJumpError`. + obj = Object.new + def obj.ok + eval("BEGIN {return :ok}") + end + assert_equal :ok, assert_nothing_raised(LocalJumpError) {obj.ok} + 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_syntax_error_at_newline + expected = /(\n|\| ) \^/ + assert_syntax_error("%[abcdef", expected) + assert_syntax_error("%[abcdef\n", expected) + end + + def test_invalid_jump + assert_in_out_err(%w[-e redo], "", [], /^-e:1: |~ Invalid redo/) + 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_endless + assert_valid_syntax('private def foo = 42') + assert_valid_syntax('private def foo() = 42') + assert_valid_syntax('private def inc(x) = x + 1') + assert_valid_syntax('private def obj.foo = 42') + assert_valid_syntax('private def obj.foo() = 42') + assert_valid_syntax('private def obj.inc(x) = x + 1') + k = Class.new do + class_eval('def rescued(x) = raise("to be caught") rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise("to be caught") rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + error = /setter method cannot be defined in an endless method definition/ + assert_syntax_error('def foo=() = 42', error) + assert_syntax_error('def obj.foo=() = 42', error) + assert_syntax_error('def foo=() = 42 rescue nil', error) + assert_syntax_error('def obj.foo=() = 42 rescue nil', error) + end + + def test_methoddef_endless_command + assert_valid_syntax('def foo = puts "Hello"') + assert_valid_syntax('def foo() = puts "Hello"') + assert_valid_syntax('def foo(x) = puts x') + assert_valid_syntax('def obj.foo = puts "Hello"') + assert_valid_syntax('def obj.foo() = puts "Hello"') + assert_valid_syntax('def obj.foo(x) = puts x') + k = Class.new do + class_eval('def rescued(x) = raise "to be caught" rescue "instance #{x}"') + class_eval('def self.rescued(x) = raise "to be caught" rescue "class #{x}"') + end + assert_equal("class ok", k.rescued("ok")) + assert_equal("instance ok", k.new.rescued("ok")) + + assert_valid_syntax('private def foo = puts "Hello"') + assert_valid_syntax('private def foo() = puts "Hello"') + assert_valid_syntax('private def foo(x) = puts x') + assert_valid_syntax('private def obj.foo = puts "Hello"') + assert_valid_syntax('private def obj.foo() = puts "Hello"') + assert_valid_syntax('private def obj.foo(x) = puts x') + 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) {}') + assert_valid_syntax('bar {}') + assert_valid_syntax('Bar {}') + assert_valid_syntax('bar() {}') + assert_valid_syntax('Bar() {}') + assert_valid_syntax('Foo::bar {}') + assert_valid_syntax('Foo::Bar {}') + assert_valid_syntax('Foo::bar() {}') + assert_valid_syntax('Foo::Bar() {}') + end + + def test_command_newline_in_tlparen_args + assert_valid_syntax("p (1\n2\n),(3),(4)") + assert_valid_syntax("p (\n),(),()") + assert_valid_syntax("a.b (1\n2\n),(3),(4)") + assert_valid_syntax("a.b (\n),(),()") + end + + def test_command_semicolon_in_tlparen_at_the_first_arg + bug19281 = '[ruby-core:111499] [Bug #19281]' + assert_valid_syntax('p (1;2),(3),(4)', bug19281) + assert_valid_syntax('p (;),(),()', bug19281) + assert_valid_syntax('a.b (1;2),(3),(4)', bug19281) + assert_valid_syntax('a.b (;),(),()', bug19281) + end + + def test_command_do_block_call_with_empty_args_brace_block + assert_valid_syntax('cmd 1, 2 do end.m() { blk_body }') + end + + def test_numbered_parameter + assert_valid_syntax('proc {_1}') + assert_equal(3, eval('[1,2].then {_1+_2}')) + assert_equal("12", eval('[1,2].then {"#{_1}#{_2}"}')) + assert_equal([1, 2], eval('[1,2].then {_1}')) + assert_equal(3, eval('->{_1+_2}.call(1,2)')) + assert_equal(4, eval('->(a=->{_1}){a}.call.call(4)')) + assert_equal(5, eval('-> a: ->{_1} {a}.call.call(5)')) + assert_syntax_error('proc {|| _1}', /ordinary parameter is defined/) + assert_syntax_error('proc {|;a| _1}', /ordinary parameter is defined/) + assert_syntax_error("proc {|\n| _1}", /ordinary parameter is defined/) + assert_syntax_error('proc {|x| _1}', /ordinary parameter is defined/) + assert_syntax_error('proc {_1; proc {_2}}', /numbered parameter is already used/) + assert_syntax_error('proc {proc {_1}; _2}', /numbered parameter is already used/) + assert_syntax_error('->(){_1}', /ordinary parameter is defined/) + assert_syntax_error('->(x){_1}', /ordinary parameter is defined/) + assert_syntax_error('->x{_1}', /ordinary parameter is defined/) + assert_syntax_error('->x:_2{}', /ordinary parameter is defined/) + assert_syntax_error('->x=_1{}', /ordinary parameter is defined/) + assert_syntax_error('-> {_1; -> {_2}}', /numbered parameter is already used/) + assert_syntax_error('-> {-> {_1}; _2}', /numbered parameter is already used/) + assert_syntax_error('proc {_1; _1 = nil}', /Can't assign to numbered parameter _1/) + assert_syntax_error('proc {_1 = nil}', /_1 is reserved for numbered parameter/) + assert_syntax_error('_2=1', /_2 is reserved for numbered parameter/) + assert_syntax_error('proc {|_3|}', /_3 is reserved for numbered parameter/) + assert_syntax_error('def x(_4) end', /_4 is reserved for numbered parameter/) + assert_syntax_error('def _5; end', /_5 is reserved for numbered parameter/) + assert_syntax_error('def self._6; end', /_6 is reserved for numbered parameter/) + assert_raise_with_message(NameError, /undefined local variable or method '_1'/) { + eval('_1') + } + ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c| + assert_valid_syntax("->{#{c};->{_1};end;_1}\n") + assert_valid_syntax("->{_1;#{c};->{_1};end}\n") + end + + 1.times { + [ + _1, + assert_equal([:a], eval("[:a].map{_1}")), + assert_raise(NameError) {eval("_1")}, + ] + } + + assert_valid_syntax("proc {def foo(_);end;_1}") + assert_valid_syntax("p { [_1 **2] }") + assert_valid_syntax("proc {_1;def foo();end;_1}") + end + + def test_it + assert_valid_syntax('proc {it}') + assert_syntax_error('[1,2].then {it+_2}', /'it' is already used/) + assert_syntax_error('[1,2].then {_2+it}', /numbered parameter is already used/) + assert_equal([1, 2], eval('[1,2].then {it}')) + assert_syntax_error('[1,2].then {"#{it}#{_2}"}', /'it' is already used/) + assert_syntax_error('[1,2].then {"#{_2}#{it}"}', /numbered parameter is already used/) + assert_syntax_error('->{it+_2}.call(1,2)', /'it' is already used/) + assert_syntax_error('->{_2+it}.call(1,2)', /numbered parameter is already used/) + assert_equal(4, eval('->(a=->{it}){a}.call.call(4)')) + assert_equal(5, eval('-> a: ->{it} {a}.call.call(5)')) + assert_syntax_error('proc {|| it}', /ordinary parameter is defined/) + assert_syntax_error('proc {|;a| it}', /ordinary parameter is defined/) + assert_syntax_error("proc {|\n| it}", /ordinary parameter is defined/) + assert_syntax_error('proc {|x| it}', /ordinary parameter is defined/) + assert_equal([1, 2], eval('1.then {[it, 2.then {_1}]}')) + assert_equal([2, 1], eval('1.then {[2.then {_1}, it]}')) + assert_syntax_error('->(){it}', /ordinary parameter is defined/) + assert_syntax_error('->(x){it}', /ordinary parameter is defined/) + assert_syntax_error('->x{it}', /ordinary parameter is defined/) + assert_syntax_error('->x:_1{}', /ordinary parameter is defined/) + assert_syntax_error('->x=it{}', /ordinary parameter is defined/) + assert_valid_syntax('-> {it; -> {_2}}') + assert_valid_syntax('-> {-> {it}; _2}') + assert_equal([1, nil], eval('proc {that=it; it=nil; [that, it]}.call(1)')) + assert_equal(1, eval('proc {it = 1}.call')) + assert_warning(/1: warning: assigned but unused variable - it/) { + assert_equal(2, eval('a=Object.new; def a.foo; it = 2; end; a.foo')) + } + assert_equal(3, eval('proc {|it| it}.call(3)')) + assert_equal(4, eval('a=Object.new; def a.foo(it); it; end; a.foo(4)')) + assert_equal(5, eval('a=Object.new; def a.it; 5; end; a.it')) + assert_equal(6, eval('a=Class.new; a.class_eval{ def it; 6; end }; a.new.it')) + assert_equal([7, 8], eval('a=[]; [7].each { a << it; [8].each { a << it } }; a')) + assert_raise_with_message(NameError, /undefined local variable or method 'it'/) do + eval('it') + end + ['class C', 'class << C', 'module M', 'def m', 'def o.m'].each do |c| + assert_valid_syntax("->{#{c};->{it};end;it}\n") + assert_valid_syntax("->{it;#{c};->{it};end}\n") + end + 1.times do + [ + assert_equal(0, it), + assert_equal([:a], eval('[:a].map{it}')), + assert_raise(NameError) {eval('it')}, + ] + end + assert_valid_syntax('proc {def foo(_);end;it}') + assert_syntax_error('p { [it **2] }', /unexpected \*\*/) + assert_equal(1, eval('1.then { raise rescue it }')) + assert_equal(2, eval('1.then { 2.then { raise rescue it } }')) + assert_equal(3, eval('3.then { begin; raise; rescue; it; end }')) + assert_equal(4, eval('4.tap { begin; raise ; rescue; raise rescue it; end; }')) + assert_equal(5, eval('a = 0; 5.then { begin; nil; ensure; a = it; end }; a')) + assert_equal(6, eval('a = 0; 6.then { begin; nil; rescue; ensure; a = it; end }; a')) + assert_equal(7, eval('a = 0; 7.then { begin; raise; ensure; a = it; end } rescue a')) + assert_equal(8, eval('a = 0; 8.then { begin; raise; rescue; ensure; a = it; end }; a')) + assert_equal(/9/, eval('9.then { /#{it}/o }')) + end + + def test_it_with_splat_super_method + bug21256 = '[ruby-core:121592] [Bug #21256]' + + a = Class.new do + define_method(:foo) { it } + end + b = Class.new(a) do + def foo(*args) = super + end + + assert_equal(1, b.new.foo(1), bug21256) + end + + def test_value_expr_in_condition + mesg = /void value expression/ + assert_syntax_error("tap {a = (true ? next : break)}", mesg) + assert_valid_syntax("tap {a = (true ? true : break)}") + assert_valid_syntax("tap {a = (break if false)}") + assert_valid_syntax("tap {a = (break unless true)}") + end + + def test_value_expr_in_singleton + mesg = /void value expression/ + assert_syntax_error("class << (return); end", mesg) + end + + def test_tautological_condition + assert_valid_syntax("def f() return if false and invalid; nil end") + assert_valid_syntax("def f() return unless true or invalid; nil end") + end + + def test_argument_forwarding + assert_valid_syntax('def foo(...) bar(...) end') + assert_valid_syntax('def foo(...) end') + assert_valid_syntax('def foo(a, ...) bar(...) end') + assert_valid_syntax("def foo ...\n bar(...)\nend") + assert_valid_syntax("def foo a, ...\n bar(...)\nend") + assert_valid_syntax("def foo b = 1, ...\n bar(...)\nend") + assert_valid_syntax("def foo ...; bar(...); end") + assert_valid_syntax("def foo a, ...; bar(...); end") + assert_valid_syntax("def foo b = 1, ...; bar(...); end") + assert_valid_syntax("(def foo ...\n bar(...)\nend)") + assert_valid_syntax("(def foo ...; bar(...); end)") + assert_valid_syntax("def (1...).foo ...; bar(...); end") + assert_valid_syntax("def (tap{1...}).foo ...; bar(...); end") + assert_valid_syntax('def ==(...) end') + assert_valid_syntax('def [](...) end') + assert_valid_syntax('def nil(...) end') + assert_valid_syntax('def true(...) end') + assert_valid_syntax('def false(...) end') + assert_valid_syntax('->a=1...{}') + unexpected = /unexpected \.{3}/ + assert_syntax_error('iter do |...| end', /unexpected/) + assert_syntax_error('iter {|...|}', /unexpected/) + assert_syntax_error('->... {}', unexpected) + assert_syntax_error('->(...) {}', unexpected) + assert_syntax_error('->a,... {}', unexpected) + assert_syntax_error('->(a,...) {}', unexpected) + assert_syntax_error('->a=1,... {}', unexpected) + assert_syntax_error('->(a=1,...) {}', unexpected) + assert_syntax_error('def foo(x, y, z) bar(...); end', /unexpected/) + assert_syntax_error('def foo(x, y, z) super(...); end', /unexpected/) + assert_syntax_error('def foo(...) yield(...); end', /unexpected/) + assert_syntax_error('def foo(...) return(...); end', /unexpected/) + assert_syntax_error('def foo(...) a = (...); end', /unexpected/) + assert_syntax_error('def foo(...) [...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...]; end', /unexpected/) + assert_syntax_error('def foo(...) foo[...] = x; end', /unexpected/) + assert_syntax_error('def foo(...) foo(...) { }; end', /both block arg and actual block given/) + assert_syntax_error('def foo(...) defined?(...); end', /unexpected/) + assert_syntax_error('def foo(*rest, ...) end', '... after rest argument') + assert_syntax_error('def foo(*, ...) end', '... after rest argument') + + obj1 = Object.new + def obj1.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + obj4 = obj1.clone + obj5 = obj1.clone + obj6 = obj1.clone + obj1.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) + obj4.instance_eval("def foo ...\n bar(...)\n""end", __FILE__, __LINE__) + obj5.instance_eval("def foo ...; bar(...); end", __FILE__, __LINE__) + obj6.instance_eval('def foo(...) eval("bar(...)") end', __FILE__, __LINE__) + + klass = Class.new { + def foo(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + } + obj2 = klass.new + obj2.instance_eval('def foo(...) super(...) end', __FILE__, __LINE__) + + obj3 = Object.new + def obj3.bar(*args, &block) + if kws = Hash.try_convert(args.last) + args.pop + else + kws = {} + end + if block + block.call(args, kws) + else + [args, kws] + end + end + obj3.instance_eval('def foo(...) bar(...) end', __FILE__, __LINE__) + + [obj1, obj2, obj3, obj4, obj5, obj6].each do |obj| + assert_warning('') { + assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5) {|*x| x}) + } + assert_warning('') { + assert_equal([[1, 2, 3], {k1: 4, k2: 5}], obj.foo(1, 2, 3, k1: 4, k2: 5)) + } + array = obj == obj3 ? [] : [{}] + assert_warning('') { + assert_equal([array, {}], obj.foo({}) {|*x| x}) + } + assert_warning('') { + assert_equal([array, {}], obj.foo({})) + } + assert_equal(-1, obj.method(:foo).arity) + parameters = obj.method(:foo).parameters + assert_equal(:rest, parameters.dig(0, 0)) + assert_equal(:keyrest, parameters.dig(1, 0)) + assert_equal(:block, parameters.dig(2, 0)) + end + end + + def test_argument_forwarding_with_leading_arguments + obj = Object.new + def obj.bar(*args, **kws, &block) + if block + block.call(args, kws) + else + [args, kws] + end + end + obj.instance_eval('def foo(_a, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(1) + assert_equal [[2], {}], obj.foo(1, 2) + assert_equal [[2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[], {a: 1}], obj.foo(1, a: 1) + assert_equal [[2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo + assert_equal [[1, 1], {}], obj.foo(1) + assert_equal [[1, 1, 2], {}], obj.foo(1, 2) + assert_equal [[1, 1, 2, 3], {}], obj.foo(1, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(a: 1) + assert_equal [[1, 1], {a: 1}], obj.foo(1, a: 1) + assert_equal [[1, 1, 2], {a: 1}], obj.foo(1, 2, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1) + assert_equal [[1, 1, 2, 3], {a: 1}], obj.foo(1, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4) + assert_equal [[1, 2], {}], obj.foo(4, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(...) end', __FILE__, __LINE__) + assert_equal [[], {}], obj.foo(4, 5) + assert_equal [[2], {}], obj.foo(4, 5, 2) + assert_equal [[2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, _b, ...) bar(1, ...) end', __FILE__, __LINE__) + assert_equal [[1], {}], obj.foo(4, 5) + assert_equal [[1, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(_a, ...) bar(1, 2, ...) end', __FILE__, __LINE__) + assert_equal [[1, 2], {}], obj.foo(5) + assert_equal [[1, 2, 5], {}], obj.foo(4, 5) + assert_equal [[1, 2, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[1, 2, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[1, 2, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[1, 2, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[1, 2, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, b, ...) bar(b, a, ...) end', __FILE__, __LINE__) + assert_equal [[5, 4], {}], obj.foo(4, 5) + assert_equal [[5, 4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[5, 4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[5, 4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[5, 4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[5, 4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, _b, ...) bar(a, ...) end', __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4, 5) + assert_equal [[4, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval('def foo(a, ...) bar(a, 1, ...) end', __FILE__, __LINE__) + assert_equal [[4, 1], {}], obj.foo(4) + assert_equal [[4, 1, 5], {}], obj.foo(4, 5) + assert_equal [[4, 1, 5, 2], {}], obj.foo(4, 5, 2) + assert_equal [[4, 1, 5, 2, 3], {}], obj.foo(4, 5, 2, 3) + assert_equal [[4, 1, 5], {a: 1}], obj.foo(4, 5, a: 1) + assert_equal [[4, 1, 5, 2], {a: 1}], obj.foo(4, 5, 2, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1) + assert_equal [[4, 1, 5, 2, 3], {a: 1}], obj.foo(4, 5, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval("def foo a, ...\n bar(a, ...)\n"" end", __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + obj.singleton_class.send(:remove_method, :foo) + obj.instance_eval("def foo a, ...; bar(a, ...); end", __FILE__, __LINE__) + assert_equal [[4], {}], obj.foo(4) + assert_equal [[4, 2], {}], obj.foo(4, 2) + assert_equal [[4, 2, 3], {}], obj.foo(4, 2, 3) + assert_equal [[4], {a: 1}], obj.foo(4, a: 1) + assert_equal [[4, 2], {a: 1}], obj.foo(4, 2, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1) + assert_equal [[4, 2, 3], {a: 1}], obj.foo(4, 2, 3, a: 1){|args, kws| [args, kws]} + + exp = eval("-> (a: nil) {a...1}") + assert_equal 0...1, exp.call(a: 0) + end + + def test_argument_forwarding_with_super + assert_valid_syntax('def foo(...) super {}; end') + assert_valid_syntax('def foo(...) super() {}; end') + assert_syntax_error('def foo(...) super(...) {}; end', /both block arg and actual block/) + assert_syntax_error('def foo(...) super(1, ...) {}; end', /both block arg and actual block/) + end + + def test_argument_forwarding_with_super_memory_leak + assert_no_memory_leak([], "#{<<-'begin;'}", "#{<<-'end;'}", rss: true) + code = proc do + eval("def foo(...) super(...) {}; end") + raise "unreachable" + rescue SyntaxError + end + + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_class_module_Object_ancestors + assert_ruby_status([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + class Bug18832; end + RUBY + assert_ruby_status([], <<-RUBY) + m = Module.new + m::Bug18832 = 1 + include m + module Bug18832; end + RUBY + end + + def test_cdhash + assert_separately([], <<-RUBY) + n = case 1 when 2r then false else true end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 3/2r when 1.5r then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + assert_separately([], <<-RUBY) + n = case 1i when 1i then true else false end + assert_equal(n, true, '[ruby-core:103759] [Bug #17854]') + RUBY + end + + def test_defined_in_short_circuit_if_condition + bug = '[ruby-core:20501]' + conds = [ + "false && defined?(Some::CONSTANT)", + "true || defined?(Some::CONSTANT)", + "(false && defined?(Some::CONSTANT))", # parens exercise different code path + "(true || defined?(Some::CONSTANT))", + "@val && false && defined?(Some::CONSTANT)", + "@val || true || defined?(Some::CONSTANT)" + ] + + conds.each do |cond| + code = %Q{ + def my_method + var = "there" + if #{cond} + var = "here" + end + raise var + end + begin + my_method + rescue + print 'ok' + else + print 'ng' + end + } + # Invoke in a subprocess because the bug caused a segfault using the parse.y compiler. + # Don't use assert_separately because the bug was best reproducible in a clean slate, + # without test env loaded. + out, _err, status = EnvUtil.invoke_ruby(["--disable-gems"], code, true, false) + assert_predicate(status, :success?, bug) + assert_equal 'ok', out + end + 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 new file mode 100644 index 0000000000..3fcdaa6aad --- /dev/null +++ b/test/ruby/test_system.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestSystem < Test::Unit::TestCase + def test_system + ruby = EnvUtil.rubybin + assert_equal("foobar\n", `echo foobar`) + assert_equal('foobar', `#{ruby} -e 'print "foobar"'`) + + Dir.mktmpdir("ruby_script_tmp") {|tmpdir| + tmpfilename = "#{tmpdir}/ruby_script_tmp.#{$$}" + + tmp = open(tmpfilename, "w") + tmp.print "print $zzz\n"; + tmp.close + + assert_equal('true', `#{ruby} -s #{tmpfilename} -zzz`) + assert_equal('555', `#{ruby} -s #{tmpfilename} -zzz=555`) + + tmp = open(tmpfilename, "w") + tmp.print "#! /usr/local/bin/ruby -s\n"; + tmp.print "print $zzz\n"; + tmp.close + + assert_equal('678', `#{ruby} #{tmpfilename} -zzz=678`) + + tmp = open(tmpfilename, "w") + tmp.print "this is a leading junk\n"; + tmp.print "#! /usr/local/bin/ruby -s\n"; + tmp.print "print $zzz if defined? $zzz\n"; + tmp.print "__END__\n"; + tmp.print "this is a trailing junk\n"; + tmp.close + + assert_equal('', `#{ruby} -x #{tmpfilename}`) + assert_equal('555', `#{ruby} -x #{tmpfilename} -zzz=555`) + + tmp = open(tmpfilename, "w") + tmp.print "#! /non/exist\\interpreter?/./to|be:ignored\n"; + tmp.print "this is a leading junk\n"; + tmp.print "#! /usr/local/bin/ruby -s\n"; + tmp.print "print $zzz if defined? $zzz\n"; + tmp.print "__END__\n"; + tmp.print "this is a trailing junk\n"; + tmp.close + + assert_equal('', `#{ruby} #{tmpfilename}`) + assert_equal('555', `#{ruby} #{tmpfilename} -zzz=555`) + + tmp = open(tmpfilename, "w") + for i in 1..5 + tmp.print i, "\n" + end + tmp.close + + `#{ruby} -i.bak -pe '$_.sub!(/^[0-9]+$/){$&.to_i * 5}' #{tmpfilename}` + tmp = open(tmpfilename, "r") + while tmp.gets + assert_equal(0, $_.to_i % 5) + end + tmp.close + + File.unlink tmpfilename or `/bin/rm -f "#{tmpfilename}"` + File.unlink "#{tmpfilename}.bak" or `/bin/rm -f "#{tmpfilename}.bak"` + + if /mswin|mingw/ =~ RUBY_PLATFORM + testname = '[ruby-dev:38588]' + batch = "batch_tmp.#{$$}" + tmpfilename = "#{tmpdir}/#{batch}.bat" + open(tmpfilename, "wb") {|f| f.print "\r\n"} + assert(system(tmpfilename), testname) + assert(system("#{tmpdir}/#{batch}"), testname) + assert(system(tmpfilename, "1"), testname) + assert(system("#{tmpdir}/#{batch}", "1"), testname) + begin + path = ENV["PATH"] + ENV["PATH"] = "#{tmpdir.tr(File::SEPARATOR, File::ALT_SEPARATOR)}#{File::PATH_SEPARATOR + path if path}" + assert(system("#{batch}.bat"), testname) + assert(system(batch), testname) + assert(system("#{batch}.bat", "1"), testname) + assert(system(batch, "1"), testname) + ensure + 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_system_closed + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + ios = [] + ObjectSpace.each_object(IO) {|io| ios << io} + `echo` + ObjectSpace.each_object(IO) do |io| + next if ios.include?(io) + assert_nothing_raised {io.close} + 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/ + assert_raise_with_message(Errno::ENOENT, message) do + system(tmpfilename, exception: true) + end + open(tmpfilename, "w") {|f| + f.print "@" if /mingw|mswin/ =~ RUBY_PLATFORM + f.puts "exit 127" + f.chmod(0755) + } + assert_raise_with_message(RuntimeError, message) do + system(tmpfilename, exception: true) + end + end + end +end diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb new file mode 100644 index 0000000000..2a61fc3450 --- /dev/null +++ b/test/ruby/test_thread.rb @@ -0,0 +1,1655 @@ +# -*- coding: us-ascii -*- +# frozen_string_literal: false +require 'test/unit' +require "rbconfig/sizeof" +require "timeout" + +class TestThread < Test::Unit::TestCase + class Thread < ::Thread + Threads = [] + def self.new(*) + th = super + Threads << th + th + end + end + + def setup + Thread::Threads.clear + end + + def teardown + Thread::Threads.each do |t| + t.kill if t.alive? + begin + t.join + rescue Exception + end + end + end + + def test_inspect + m = Thread::Mutex.new + m.lock + line = __LINE__+1 + th = Module.new {break module_eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")}.start do + m.synchronize {} + end + Thread.pass until th.stop? + s = th.inspect + assert_include(s, "::C\u{30b9 30ec 30c3 30c9}:") + assert_include(s, " #{__FILE__}:#{line} ") + assert_equal(s, th.to_s) + ensure + m.unlock + th.join + end + + 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 = Thread::Mutex.new + r = 0 + num_threads = 10 + loop=100 + (1..num_threads).map{ + Thread.new{ + loop.times{ + m.synchronize{ + tmp = r + # empty and waste loop for making thread preemption + 100.times { + } + r = tmp + 1 + } + } + } + }.each{|e| + e.join + } + assert_equal(num_threads*loop, r) + end + + 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') + $:.shift + 3.times { + `#{EnvUtil.rubybin} #{lbtest}` + assert_not_predicate($?, :coredump?, '[ruby-dev:30653]') + } + end + + def test_priority + c1 = c2 = 0 + run = true + t1 = Thread.new { c1 += 1 while run } + t1.priority = 3 + t2 = Thread.new { c2 += 1 while run } + t2.priority = -3 + assert_equal(3, t1.priority) + assert_equal(-3, t2.priority) + sleep 0.5 + 5.times do + assert_not_predicate(t1, :stop?) + assert_not_predicate(t2, :stop?) + break if c1 > c2 + sleep 0.1 + end + run = false + t1.kill + t2.kill + assert_operator(c1, :>, c2, "[ruby-dev:33124]") # not guaranteed + t1.join + t2.join + end + + def test_new + assert_raise(ThreadError) do + Thread.new + end + + t1 = Thread.new { sleep } + assert_raise(ThreadError) do + t1.instance_eval { initialize { } } + end + + t2 = Thread.new(&method(:sleep).to_proc) + assert_raise(ThreadError) do + t2.instance_eval { initialize { } } + end + + ensure + 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.05)) + + ensure + t&.kill&.join + end + + def test_join2 + ok = false + t1 = Thread.new { ok = true; sleep } + Thread.pass until ok + Thread.pass until t1.stop? + t2 = Thread.new do + Thread.pass while ok + t1.join(0.01) + end + t3 = Thread.new do + ok = false + t1.join + end + assert_nil(t2.value) + t1.wakeup + assert_equal(t1, t3.value) + + ensure + t1&.kill&.join + t2&.kill&.join + t3&.kill&.join + end + + def test_join_argument_conversion + t = Thread.new {} + + # Make sure that the thread terminates + Thread.pass while t.status + + assert_raise(TypeError) {t.join(:foo)} + + limit = Struct.new(:to_f, :count).new(0.05) + assert_same(t, t.join(limit)) + end + + { 'FIXNUM_MAX' => RbConfig::LIMITS['FIXNUM_MAX'], + 'UINT64_MAX' => RbConfig::LIMITS['UINT64_MAX'], + 'INFINITY' => Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_#{name}") do + t = Thread.new {} + assert_same t, t.join(limit), "limit=#{limit.inspect}" + end + end + + { 'minus_1' => -1, + 'minus_0_1' => -0.1, + 'FIXNUM_MIN' => RbConfig::LIMITS['FIXNUM_MIN'], + 'INT64_MIN' => RbConfig::LIMITS['INT64_MIN'], + 'minus_INFINITY' => -Float::INFINITY + }.each do |name, limit| + define_method("test_join_limit_negative_#{name}") do + t = Thread.new { sleep } + begin + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(30) do + assert_nil t.join(limit), "limit=#{limit.inspect}" + end + end + ensure + t.kill + end + end + end + + def test_kill_main_thread + assert_in_out_err([], <<-INPUT, %w(1), []) + p 1 + Thread.kill Thread.current + p 2 + 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 + s += 1 + Thread.exit + s += 2 + end.join + assert_equal(1, s) + end + + def test_wakeup + s = 0 + t = Thread.new do + s += 1 + Thread.stop + s += 1 + end + Thread.pass until t.stop? + assert_equal(1, s) + t.wakeup + Thread.pass while t.alive? + assert_equal(2, s) + assert_raise(ThreadError) { t.wakeup } + ensure + t&.kill&.join + end + + def test_stop + assert_in_out_err([], <<-INPUT, %w(2), []) + begin + Thread.stop + p 1 + rescue ThreadError + p 2 + end + INPUT + end + + def test_list + assert_in_out_err([], <<-INPUT) do |r, e| + t1 = Thread.new { sleep } + Thread.pass + 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 + INPUT + assert_equal(r.first, r.last) + assert_equal([], e) + end + end + + def test_main + assert_in_out_err([], <<-INPUT, %w(true false), []) + p Thread.main == Thread.current + Thread.new { p Thread.main == Thread.current }.join + INPUT + end + + def test_abort_on_exception + assert_in_out_err([], <<-INPUT, %w(false 1), []) + p Thread.abort_on_exception + begin + t = Thread.new { + Thread.current.report_on_exception = false + raise + } + Thread.pass until t.stop? + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(true 2), []) + Thread.abort_on_exception = true + p Thread.abort_on_exception + begin + Thread.new { + Thread.current.report_on_exception = false + raise + } + sleep 0.5 + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err(%w(-d), <<-INPUT, %w(false 2), %r".+") + p Thread.abort_on_exception + begin + t = Thread.new { raise } + Thread.pass until t.stop? + p 1 + rescue + p 2 + end + INPUT + + assert_in_out_err([], <<-INPUT, %w(false true 2), []) + p Thread.abort_on_exception + begin + 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 + p 2 + end + 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_ignore_deadlock + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "can't trap a signal from another process on Windows" + end + assert_in_out_err([], <<-INPUT, %w(false :sig), [], :signal=>:INT, timeout: 1, timeout_error: nil) + p Thread.ignore_deadlock + q = Thread::Queue.new + trap(:INT){q.push :sig} + Thread.ignore_deadlock = true + p q.pop + INPUT + end + + def test_status_and_stop_p + a = ::Thread.new { + Thread.current.report_on_exception = false + raise("die now") + } + b = Thread.new { Thread.stop } + c = Thread.new { Thread.exit } + e = Thread.current + Thread.pass while a.alive? or !b.stop? or c.alive? + + assert_equal(nil, a.status) + assert_predicate(a, :stop?) + + assert_equal("sleep", b.status) + assert_predicate(b, :stop?) + + assert_equal(false, c.status) + assert_match(/^#<TestThread::Thread:.* dead>$/, c.inspect) + assert_predicate(c, :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 + waiter&.kill&.join + end + + def test_thread_local + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + + assert_equal(true, t.key?(:foo)) + assert_equal(true, t.key?("foo")) + assert_equal(false, t.key?(:qux)) + assert_equal(false, t.key?("qux")) + + assert_equal([:foo, :bar, :baz].sort, t.keys.sort) + + ensure + t&.kill&.join + end + + def test_thread_local_fetch + t = Thread.new { sleep } + + assert_equal(false, t.key?(:foo)) + + t["foo"] = "foo" + t["bar"] = "bar" + t["baz"] = "baz" + + x = nil + assert_equal("foo", t.fetch(:foo, 0)) + assert_equal("foo", t.fetch(:foo) {x = true}) + assert_nil(x) + assert_equal("foo", t.fetch("foo", 0)) + assert_equal("foo", t.fetch("foo") {x = true}) + assert_nil(x) + + x = nil + assert_equal(0, t.fetch(:qux, 0)) + assert_equal(1, t.fetch(:qux) {x = 1}) + assert_equal(1, x) + assert_equal(2, t.fetch("qux", 2)) + assert_equal(3, t.fetch("qux") {x = 3}) + assert_equal(3, x) + + e = assert_raise(KeyError) {t.fetch(:qux)} + assert_equal(:qux, e.key) + assert_equal(t, e.receiver) + ensure + t&.kill&.join + end + + def test_thread_local_security + Thread.new do + Thread.current[:foo] = :bar + Thread.current.freeze + assert_raise(FrozenError) do + Thread.current[:foo] = :baz + 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, 0.001)) + t = Thread.new do + IO.select(nil, nil, nil, nil) + end + Thread.pass until t.stop? + assert_predicate(t, :alive?) + ensure + t&.kill&.join + end + + def test_mutex_deadlock + m = Thread::Mutex.new + m.synchronize do + assert_raise(ThreadError) do + m.synchronize do + assert(false) + end + end + end + end + + def test_mutex_interrupt + m = Thread::Mutex.new + m.lock + t = Thread.new do + m.lock + :foo + end + Thread.pass until t.stop? + t.kill + assert_nil(t.value) + end + + def test_mutex_illegal_unlock + m = Thread::Mutex.new + m.lock + Thread.new do + assert_raise(ThreadError) do + m.unlock + end + end.join + end + + def test_mutex_fifo_like_lock + m1 = Thread::Mutex.new + m2 = Thread::Mutex.new + m1.lock + m2.lock + m1.unlock + m2.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + + m3 = Thread::Mutex.new + m1.lock + m2.lock + m3.lock + m1.unlock + m2.unlock + m3.unlock + assert_equal(false, m1.locked?) + assert_equal(false, m2.locked?) + assert_equal(false, m3.locked?) + end + + def test_mutex_trylock + m = Thread::Mutex.new + assert_equal(true, m.try_lock) + assert_equal(false, m.try_lock, '[ruby-core:20943]') + + Thread.new{ + assert_equal(false, m.try_lock) + }.join + + m.unlock + end + + def test_recursive_outer + arr = [] + obj = Struct.new(:foo, :visited).new(arr, false) + arr << obj + def obj.hash + self[:visited] = true + super + raise "recursive_outer should short circuit intermediate calls" + end + assert_nothing_raised {arr.hash} + assert(obj[:visited], "obj.hash was not called") + end + + 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_no_valid_cfp + omit 'with win32ole, cannot run this testcase because win32ole redefines Thread#initialize' if defined?(WIN32OLE) + bug5083 = '[ruby-dev:44208]' + assert_equal([], Thread.new(&Module.method(:nesting)).value, bug5083) + assert_instance_of(Thread, Thread.new(:to_s, &Class.new.method(:undef_method)).join, bug5083) + end + + def make_handle_interrupt_test_thread1 flag + r = [] + ready_q = Thread::Queue.new + done_q = Thread::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 + } + ready_q.pop + th.raise + begin + done_q << true + th.join + rescue + r << :c3 + end + r + end + + 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 + + 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 + _dummy_for_check_ints=nil + end + end + + def test_handle_interrupt_blocking + r = nil + q = Thread::Queue.new + e = Class.new(Exception) + th_s = Thread.current + th = Thread.start { + assert_raise(RuntimeError) { + Thread.handle_interrupt(Object => :on_blocking){ + begin + q.pop + Thread.current.raise RuntimeError, "will raise in sleep" + r = :ok + sleep + ensure + th_s.raise e, "raise from ensure", $@ + end + } + } + } + assert_raise(e) {q << true; th.join} + assert_equal(:ok, r) + end + + def test_handle_interrupt_and_io + assert_in_out_err([], <<-INPUT, %w(ok), []) + th_waiting = true + q = Thread::Queue.new + + t = Thread.new { + Thread.current.report_on_exception = false + Thread.handle_interrupt(RuntimeError => :on_blocking) { + q << true + nil while th_waiting + # async interrupt should be raised _before_ writing puts arguments + puts "ng" + } + } + + q.pop + 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 + q.push :ng1 + end + begin + Thread.handle_interrupt(Object => :immediate){} if Thread.pending_interrupt? + rescue RuntimeError + q.push :ok + end + rescue + 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 + 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) {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 + Thread.new{ + assert_equal(Array, Thread.main.backtrace.class) + }.join + + t = Thread.new{} + t.join + assert_equal(nil, t.backtrace) + end + + def test_thread_timer_and_interrupt + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + bug5757 = '[ruby-dev:44985]' + pid = nil + cmd = 'Signal.trap(:INT, "DEFAULT"); pipe=IO.pipe; Thread.start {Thread.pass until Thread.main.stop?; puts; STDOUT.flush}; pipe[0].read' + 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) + begin + Timeout.timeout(10) { Process.wait(pid) } + rescue Timeout::Error + EnvUtil.terminate(pid) + raise + end + t1 = Time.now.to_f + [$?, t1 - t0, err_p.read] + end + 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 + 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&.join + 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, err, status = EnvUtil.invoke_ruby([env, '-e', script], '', true, true) + assert_not_predicate(status, :signaled?, err) + + 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") + assert_equal("ok", invoke_rec('print :ok', 1024 * 1024 * 100, nil, false)) + 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 + mutex.lock + + th = Thread.new do + mutex.synchronize do + 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_value + bug18902 = "[Bug #18902]" + th = Thread.start { sleep 2 } + begin + pid = fork do + th.value + end + _, status = Process.wait2(pid) + assert_predicate(status, :success?, bug18902) + ensure + th.kill + th.join + end + end if Process.respond_to?(:fork) + + def test_fork_while_locked + m = Thread::Mutex.new + thrs = [] + 3.times do |i| + thrs << Thread.new { m.synchronize { Process.waitpid2(fork{})[1] } } + end + thrs.each do |t| + assert_predicate t.value, :success?, '[ruby-core:85940] [Bug #14578]' + end + end if Process.respond_to?(:fork) + + def test_fork_while_parent_locked + omit 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + nr = 1 + thrs = [] + m.synchronize do + thrs = nr.times.map { Thread.new { m.synchronize {} } } + thrs.each { Thread.pass } + pid = fork do + m.locked? or exit!(2) + thrs = nr.times.map { Thread.new { m.synchronize {} } } + m.unlock + thrs.each { |t| t.join(1) == t or exit!(1) } + exit!(0) + end + _, st = Process.waitpid2(pid) + assert_predicate st, :success?, '[ruby-core:90312] [Bug #15383]' + end + thrs.each { |t| assert_same t, t.join(1) } + end + + def test_fork_while_mutex_locked_by_forker + omit 'needs fork' unless Process.respond_to?(:fork) + m = Thread::Mutex.new + m.synchronize do + pid = fork do + exit!(2) unless m.locked? + m.unlock rescue exit!(3) + m.synchronize {} rescue exit!(4) + exit!(0) + end + _, st = Timeout.timeout(30) { Process.waitpid2(pid) } + assert_predicate st, :success?, '[ruby-core:90595] [Bug #15430]' + end + end + + def test_subclass_no_initialize + t = Module.new do + break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end") + 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_yield_across_thread_through_enum + bug18649 = '[ruby-core:107980] [Bug #18649]' + @log = [] + + def self.p(arg) + @log << arg + end + + def self.synchronize + yield + end + + def self.execute(task) + success = true + value = reason = nil + end_sync = false + + synchronize do + begin + p :before + value = task.call + p :never_reached + success = true + rescue StandardError => ex + ex = ex.class + p [:rescue, ex] + reason = ex + success = false + end + + end_sync = true + p :end_sync + end + + p :should_not_reach_here! unless end_sync + [success, value, reason] + end + + def self.foo + Thread.new do + result = execute(-> { yield 42 }) + p [:result, result] + end.join + end + + value = to_enum(:foo).first + expected = [:before, + [:rescue, LocalJumpError], + :end_sync, + [:result, [false, nil, LocalJumpError]]] + + assert_equal(expected, @log, bug18649) + assert_equal(42, value, bug18649) + end + + def test_thread_setname_in_initialize + bug12290 = '[ruby-core:74963] [Bug #12290]' + c = Class.new(Thread) {def initialize() self.name = "foo"; super; end} + assert_equal("foo", c.new {Thread.current.name}.value, bug12290) + end + + def test_thread_native_thread_id + omit "don't support native_thread_id" unless Thread.method_defined?(:native_thread_id) + assert_instance_of Integer, Thread.main.native_thread_id + + th1 = Thread.start{sleep} + + # newly created thread which doesn't run yet returns nil or integer + assert_include [NilClass, Integer], th1.native_thread_id.class + + Thread.pass until th1.stop? + + # After a thread starts (and execute `sleep`), it returns native_thread_id + native_tid = th1.native_thread_id + assert_instance_of Integer, native_tid if native_tid # it can be nil + + th1.wakeup + Thread.pass while th1.alive? + + # dead thread returns nil + assert_nil th1.native_thread_id + end + + def test_thread_native_thread_id_across_fork_on_linux + begin + require '-test-/thread/id' + rescue LoadError + omit "this test is only for Linux" + else + extend Bug::ThreadID + end + + parent_thread_id = Thread.main.native_thread_id + real_parent_thread_id = gettid + + assert_equal real_parent_thread_id, parent_thread_id + + child_lines = nil + IO.popen('-') do |pipe| + if pipe + # parent + child_lines = pipe.read.lines + else + # child + puts Thread.main.native_thread_id + puts gettid + end + end + child_thread_id = child_lines[0].chomp.to_i + real_child_thread_id = child_lines[1].chomp.to_i + + assert_equal real_child_thread_id, child_thread_id + refute_equal parent_thread_id, child_thread_id + end + + def test_thread_interrupt_for_killed_thread + pend "hang-up" if /mswin|mingw/ =~ RUBY_PLATFORM + + opts = { timeout: 5, timeout_error: nil } + + assert_normal_exit(<<-_end, '[Bug #8996]', **opts) + Thread.report_on_exception = false + trap(:TERM){exit} + while true + t = Thread.new{sleep 0} + t.raise Interrupt + Thread.pass # allow t to finish + end + _end + end + + def test_signal_at_join + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "can't trap a signal from another process on Windows" + # opt = {new_pgroup: true} + end + + if /freebsd/ =~ RUBY_PLATFORM + omit "[Bug #18613]" + end + + assert_separately([], "#{<<~"{#"}\n#{<<~'};'}", timeout: 120) + {# + n = 1000 + sig = :INT + trap(sig) {} + IO.popen([EnvUtil.rubybin, "-e", "#{<<~"{#1"}\n#{<<~'};#1'}"], "r+") do |f| + tpid = #{$$} + sig = :#{sig} + {#1 + STDOUT.sync = true + while gets + puts + Process.kill(sig, tpid) + end + };#1 + assert_nothing_raised do + n.times do + w = Thread.start do + sleep 30 + end + begin + f.puts + f.gets + ensure + w.kill + w.join + end + end + end + n.times do + w = Thread.start { sleep 30 } + begin + f.puts + f.gets + ensure + w.kill + t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + w.join(30) + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + diff = t1 - t0 + assert_operator diff, :<=, 2 + end + end + end + }; + end + + def test_pending_interrupt? + t = Thread.handle_interrupt(Exception => :never) { Thread.new { Thread.stop } } + t.raise(StandardError) + assert_equal(true, t.pending_interrupt?) + assert_equal(true, t.pending_interrupt?(Exception)) + assert_equal(false, t.pending_interrupt?(ArgumentError)) + end + + def test_deadlock_backtrace + bug21127 = '[ruby-core:120930] [Bug #21127]' + + expected_stderr = [ + /-:12:in 'Thread#join': No live threads left. Deadlock\? \(fatal\)\n/, + /2 threads, 2 sleeps current:\w+ main thread:\w+\n/, + /\* #<Thread:\w+ sleep_forever>\n/, + :*, + /^\s*-:6:in 'Object#frame_for_deadlock_test_2'/, + :*, + /\* #<Thread:\w+ -:10 sleep_forever>\n/, + :*, + /^\s*-:2:in 'Object#frame_for_deadlock_test_1'/, + :*, + ] + + assert_in_out_err([], <<-INPUT, [], expected_stderr, bug21127) + def frame_for_deadlock_test_1 + yield + end + + def frame_for_deadlock_test_2 + yield + end + + q = Thread::Queue.new + t = Thread.new { frame_for_deadlock_test_1 { q.pop } } + + frame_for_deadlock_test_2 { t.join } + INPUT + end + + # [Bug #21342] + def test_unlock_locked_mutex_with_collected_fiber + bug21127 = '[ruby-core:120930] [Bug #21127]' + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + 5.times do + m = Mutex.new + Thread.new do + m.synchronize do + end + end.join + Fiber.new do + GC.start + m.lock + end.resume + end + end; + end + + def test_unlock_locked_mutex_with_collected_fiber2 + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + MUTEXES = [] + 5.times do + m = Mutex.new + Fiber.new do + GC.start + m.lock + end.resume + MUTEXES << m + end + 10.times do + MUTEXES.clear + GC.start + end + end; + end + + def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers + assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + mutexes = 1000.times.map do + Mutex.new + end + + mutexes.map do |m| + Fiber.new do + m.lock + end.resume + end + + GC.start + + 1000.times.map do + Fiber.new do + raise "FAILED!" if mutexes.any?(&:owned?) + end.resume + 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..81201f134f --- /dev/null +++ b/test/ruby/test_thread_cv.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestThreadConditionVariable < Test::Unit::TestCase + ConditionVariable = Thread::ConditionVariable + Mutex = Thread::Mutex + + def test_condvar_signal_and_wait + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + result = [] + woken = nil + mutex.synchronize do + t = Thread.new do + mutex.synchronize do + result << 1 + condvar.signal + end + end + + result << 0 + woken = condvar.wait(mutex) + result << 2 + t.join + end + assert_equal([0, 1, 2], result) + assert(woken) + end + + def test_condvar_wait_exception_handling + # Calling wait in the only thread running should raise a ThreadError of + # 'stopping only thread' + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + locked = false + thread = Thread.new do + Thread.current.abort_on_exception = false + mutex.synchronize do + assert_raise(Interrupt) { + condvar.wait(mutex) + } + locked = mutex.locked? + end + end + + until thread.stop? + sleep(0.1) + end + + thread.raise Interrupt, "interrupt a dead condition variable" + thread.join + assert(locked) + end + + def test_condvar_wait_and_broadcast + nr_threads = 3 + threads = Array.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + result = [] + + nr_threads.times do |i| + threads[i] = Thread.new do + mutex.synchronize do + result << "C1" + condvar.wait mutex + result << "C2" + end + end + end + sleep 0.1 + mutex.synchronize do + result << "P1" + condvar.broadcast + result << "P2" + end + Timeout.timeout(60) do + nr_threads.times do |i| + threads[i].join + end + end + + assert_equal ["C1", "C1", "C1", "P1", "P2", "C2", "C2", "C2"], result + ensure + threads.each(&:kill) + threads.each(&:join) + end + + def test_condvar_wait_deadlock + assert_in_out_err([], <<-INPUT, /\Afatal\nNo live threads left\. Deadlock/, []) + mutex = Thread::Mutex.new + cv = Thread::ConditionVariable.new + + klass = nil + mesg = nil + begin + mutex.lock + cv.wait mutex + mutex.unlock + rescue Exception => e + klass = e.class + mesg = e.message + end + puts klass + print mesg +INPUT + end + + def test_condvar_wait_deadlock_2 + nr_threads = 3 + threads = Array.new + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + nr_threads.times do |i| + if (i != 0) + mutex.unlock + end + threads[i] = Thread.new do + mutex.synchronize do + condvar.wait mutex + end + end + mutex.lock + end + + assert_raise(Timeout::Error) do + Timeout.timeout(0.1) { condvar.wait mutex } + end + mutex.unlock + threads.each(&:kill) + threads.each(&:join) + end + + def test_condvar_timed_wait + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + timeout = 0.3 + locked = false + woken = true + + t0 = Time.now + mutex.synchronize do + begin + woken = condvar.wait(mutex, timeout) + ensure + locked = mutex.locked? + end + end + t1 = Time.now + t = t1-t0 + + assert_operator(timeout*0.9, :<, t) + assert(locked) + assert_nil(woken) + end + + def test_condvar_nolock + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + assert_raise(ThreadError) {condvar.wait(mutex)} + end + + def test_condvar_nolock_2 + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex)} + end.join + end + + def test_condvar_nolock_3 + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + Thread.new do + assert_raise(ThreadError) {condvar.wait(mutex, 0.1)} + end.join + end + + def test_condvar_empty_signal + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + assert_nothing_raised(Exception) { mutex.synchronize {condvar.signal} } + end + + def test_condvar_empty_broadcast + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + + assert_nothing_raised(Exception) { mutex.synchronize {condvar.broadcast} } + end + + def test_dup + bug9440 = '[ruby-core:59961] [Bug #9440]' + condvar = Thread::ConditionVariable.new + assert_raise(NoMethodError, bug9440) do + condvar.dup + end + end + + (DumpableCV = ConditionVariable.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + condvar = Thread::ConditionVariable.new + assert_raise_with_message(TypeError, /#{ConditionVariable}/, bug9674) do + Marshal.dump(condvar) + end + + condvar = DumpableCV.new + assert_raise(TypeError, bug9674) do + Marshal.dump(condvar) + end + end + + def test_condvar_fork + mutex = Thread::Mutex.new + condvar = Thread::ConditionVariable.new + thrs = (1..10).map do + Thread.new { mutex.synchronize { condvar.wait(mutex) } } + end + thrs.each { 3.times { Thread.pass } } + pid = fork do + th = Thread.new do + mutex.synchronize { condvar.wait(mutex) } + :ok + end + until th.join(0.01) + mutex.synchronize { condvar.broadcast } + end + exit!(th.value == :ok ? 0 : 1) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + until thrs.empty? + mutex.synchronize { condvar.broadcast } + thrs.delete_if { |t| t.join(0.01) } + end + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_thread_queue.rb b/test/ruby/test_thread_queue.rb new file mode 100644 index 0000000000..9a41be8b1a --- /dev/null +++ b/test/ruby/test_thread_queue.rb @@ -0,0 +1,730 @@ +# 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_with_message(TypeError, /\bQueue.* not initialized/) { + Queue.allocate.push(nil) + } + end + + def test_sized_queue_initialized + assert_raise_with_message(TypeError, /\bSizedQueue.* not initialized/) { + SizedQueue.allocate.push(nil) + } + end + + def test_freeze + assert_raise(TypeError) { + Queue.new.freeze + } + assert_raise(TypeError) { + SizedQueue.new(5).freeze + } + end + + def test_queue + grind(5, 1000, 15, Queue) + end + + def test_sized_queue + grind(5, 1000, 15, SizedQueue, 1000) + end + + def grind(num_threads, num_objects, num_iterations, klass, *args) + from_workers = klass.new(*args) + to_workers = klass.new(*args) + + workers = (1..num_threads).map { + Thread.new { + while object = to_workers.pop + from_workers.push object + end + } + } + + Thread.new { + num_iterations.times { + num_objects.times { to_workers.push 99 } + num_objects.times { from_workers.pop } + } + }.join + + # close the queue the old way to test for backwards-compatibility + num_threads.times { to_workers.push nil } + workers.each { |t| t.join } + + assert_equal 0, from_workers.size + assert_equal 0, to_workers.size + end + + def test_queue_initialize + e = Class.new do + include Enumerable + def initialize(list) @list = list end + def each(&block) @list.each(&block) end + end + + all_assertions_foreach(nil, + [Array, "Array"], + [e, "Enumerable"], + [Struct.new(:to_a), "Array-like"], + ) do |a, type| + q = Thread::Queue.new(a.new([1,2,3])) + assert_equal(3, q.size, type) + assert_not_predicate(q, :empty?, type) + assert_equal(1, q.pop, type) + assert_equal(2, q.pop, type) + assert_equal(3, q.pop, type) + assert_predicate(q, :empty?, type) + end + end + + def test_sized_queue_initialize + q = Thread::SizedQueue.new(1) + assert_equal 1, q.max + assert_raise(ArgumentError) { Thread::SizedQueue.new(0) } + assert_raise(ArgumentError) { Thread::SizedQueue.new(-1) } + end + + def test_sized_queue_assign_max + q = Thread::SizedQueue.new(2) + assert_equal(2, q.max) + q.max = 1 + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = 0 } + assert_equal(1, q.max) + assert_raise(ArgumentError) { q.max = -1 } + assert_equal(1, q.max) + + before = q.max + q.max.times { q << 1 } + t1 = Thread.new { q << 1 } + sleep 0.01 until t1.stop? + q.max = q.max + 1 + assert_equal before + 1, q.max + ensure + t1.join if t1 + end + + def test_queue_pop_interrupt + q = Thread::Queue.new + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_queue_pop_timeout + q = Thread::Queue.new + q << 1 + assert_equal 1, q.pop(timeout: 1) + + t1 = Thread.new { q.pop(timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.pop(timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_queue_pop_non_block + q = Thread::Queue.new + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_pop_interrupt + q = Thread::SizedQueue.new(1) + t1 = Thread.new { q.pop } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_sized_queue_pop_timeout + q = Thread::SizedQueue.new(1) + + q << 1 + assert_equal 1, q.pop(timeout: 1) + + t1 = Thread.new { q.pop(timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.pop(timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_sized_queue_pop_non_block + q = Thread::SizedQueue.new(1) + assert_raise_with_message(ThreadError, /empty/) do + q.pop(true) + end + end + + def test_sized_queue_push_timeout + q = Thread::SizedQueue.new(1) + + q << 1 + assert_equal 1, q.size + + t1 = Thread.new { q.push(2, timeout: 1) } + assert_equal t1, t1.join(2) + assert_nil t1.value + + t2 = Thread.new { q.push(2, timeout: 0.1) } + assert_equal t2, t2.join(1) + assert_nil t2.value + ensure + t1&.kill&.join + t2&.kill&.join + end + + def test_sized_queue_push_interrupt + q = Thread::SizedQueue.new(1) + q.push(1) + assert_raise_with_message(ThreadError, /full/) do + q.push(2, true) + end + end + + def test_sized_queue_push_non_block + q = Thread::SizedQueue.new(1) + q.push(1) + t1 = Thread.new { q.push(2) } + sleep 0.01 until t1.stop? + t1.kill.join + assert_equal(0, q.num_waiting) + end + + def test_thr_kill + omit "[Bug #18613]" if /freebsd/ =~ RUBY_PLATFORM + + bug5343 = '[ruby-core:39634]' + Dir.mktmpdir {|d| + timeout = 60 + total_count = 250 + begin + assert_normal_exit(<<-"_eom", bug5343, timeout: timeout, chdir: d) + r, w = IO.pipe + #{total_count}.times do |i| + File.open("test_thr_kill_count", "w") {|f| f.puts i } + queue = Thread::Queue.new + th = Thread.start { + queue.push(nil) + r.read 1 + } + queue.pop + th.kill + th.join + end + _eom + rescue Timeout::Error + # record load average: + uptime = `uptime` rescue nil + if uptime && /(load average: [\d.]+),/ =~ uptime + la = " (#{$1})" + end + + count = File.read("#{d}/test_thr_kill_count").to_i + flunk "only #{count}/#{total_count} done in #{timeout} seconds.#{la}" + end + } + end + + def test_queue_push_return_value + q = Thread::Queue.new + retval = q.push(1) + assert_same q, retval + end + + def test_queue_clear_return_value + q = Thread::Queue.new + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_clear + # Fill queue, then test that Thread::SizedQueue#clear wakes up all waiting threads + sq = Thread::SizedQueue.new(2) + 2.times { sq << 1 } + + t1 = Thread.new do + sq << 1 + end + + t2 = Thread.new do + sq << 1 + end + + t3 = Thread.new do + Thread.pass + sq.clear + end + + [t3, t2, t1].each(&:join) + assert_equal sq.length, 2 + end + + def test_sized_queue_push_return_value + q = Thread::SizedQueue.new(1) + retval = q.push(1) + assert_same q, retval + end + + def test_sized_queue_clear_return_value + q = Thread::SizedQueue.new(1) + retval = q.clear + assert_same q, retval + end + + def test_sized_queue_throttle + q = Thread::SizedQueue.new(1) + i = 0 + consumer = Thread.new do + while q.pop + i += 1 + Thread.pass + end + end + nprod = 4 + npush = 100 + + producer = nprod.times.map do + Thread.new do + npush.times { q.push(true) } + end + end + producer.each(&:join) + q.push(nil) + consumer.join + assert_equal(nprod * npush, i) + end + + def test_queue_thread_raise + q = Thread::Queue.new + th1 = Thread.new do + begin + q.pop + rescue RuntimeError + sleep + end + end + th2 = Thread.new do + sleep 0.1 + q.pop + end + sleep 0.1 + th1.raise + sleep 0.1 + q << :s + assert_nothing_raised(Timeout::Error) do + Timeout.timeout(1) { th2.join } + end + ensure + [th1, th2].each do |th| + if th and th.alive? + th.wakeup + th.join + end + end + end + + def test_dup + bug9440 = '[ruby-core:59961] [Bug #9440]' + q = Thread::Queue.new + assert_raise(NoMethodError, bug9440) do + q.dup + end + end + + (DumpableQueue = Queue.dup).class_eval {remove_method :marshal_dump} + + def test_dump + bug9674 = '[ruby-core:61677] [Bug #9674]' + q = Thread::Queue.new + assert_raise_with_message(TypeError, /#{Queue}/, bug9674) do + Marshal.dump(q) + end + + sq = Thread::SizedQueue.new(1) + assert_raise_with_message(TypeError, /#{SizedQueue}/, bug9674) do + Marshal.dump(sq) + end + + q = DumpableQueue.new + assert_raise(TypeError, bug9674) do + Marshal.dump(q) + end + end + + def test_close + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate.call + assert_equal false, q.closed? + q << :something + assert_equal q, q.close + assert_predicate q, :closed? + assert_raise_with_message(ClosedQueueError, /closed/){q << :nothing} + assert_equal q.pop, :something + assert_nil q.pop + assert_nil q.pop + # non-blocking + assert_raise_with_message(ThreadError, /queue empty/){q.pop(non_block=true)} + end + end + + # test that waiting producers are woken up on close + def close_wakeup( num_items, num_threads, &qcreate ) + raise "This test won't work with num_items(#{num_items}) >= num_threads(#{num_threads})" if num_items >= num_threads + + # create the Queue + q = yield + threads = num_threads.times.map{Thread.new{q.pop}} + num_items.times{|i| q << i} + + # wait until queue empty + (Thread.pass; sleep 0.01) until q.size == 0 + + # close the queue so remaining threads will wake up + q.close + + # wait for them to go away + Thread.pass until threads.all?{|thr| thr.status == false} + + # check that they've gone away. Convert nil to -1 so we can sort and do the comparison + expected_values = [-1] * (num_threads - num_items) + num_items.times.to_a + assert_equal expected_values, threads.map{|thr| thr.value || -1 }.sort + end + + def test_queue_close_wakeup + close_wakeup(15, 18){Thread::Queue.new} + end + + def test_size_queue_close_wakeup + close_wakeup(5, 8){Thread::SizedQueue.new 9} + end + + def test_sized_queue_one_closed_interrupt + q = Thread::SizedQueue.new 1 + q << :one + t1 = Thread.new { + Thread.current.report_on_exception = false + q << :two + } + sleep 0.01 until t1.stop? + q.close + assert_raise(ClosedQueueError) {t1.join} + + assert_equal 1, q.size + assert_equal :one, q.pop + assert_empty q + end + + # make sure that shutdown state is handled properly by empty? for the non-blocking case + def test_empty_non_blocking + q = Thread::SizedQueue.new 3 + 3.times{|i| q << i} + + # these all block cos the queue is full + prod_threads = 4.times.map {|i| + Thread.new { + Thread.current.report_on_exception = false + q << 3+i + } + } + sleep 0.01 until prod_threads.all?{|thr| thr.stop?} + + items = [] + # sometimes empty? is false but pop will raise ThreadError('empty'), + # meaning a value is not immediately available but will be soon. + while prod_threads.any?(&:alive?) or !q.empty? + items << q.pop(true) rescue nil + end + assert_join_threads(prod_threads) + items.compact! + + assert_equal 7.times.to_a, items.sort + assert q.empty? + end + + def test_sized_queue_closed_push_non_blocking + q = Thread::SizedQueue.new 7 + q.close + assert_raise_with_message(ClosedQueueError, /queue closed/){q.push(non_block=true)} + end + + def test_blocked_pushers + q = Thread::SizedQueue.new 3 + prod_threads = 6.times.map do |i| + thr = Thread.new{ + Thread.current.report_on_exception = false + q << i + } + thr[:pc] = i + thr + end + + # wait until some producer threads have finished, and the other 3 are blocked + sleep 0.01 while prod_threads.reject{|t| t.status}.count < 3 + # this would ensure that all producer threads call push before close + # sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + # more than prod_threads + cons_threads = 10.times.map do |i| + thr = Thread.new{q.pop}; thr[:pc] = i; thr + end + + # values that came from the queue + popped_values = cons_threads.map &:value + + # wait untl all threads have finished + sleep 0.01 until prod_threads.find_all{|t| t.status}.count == 0 + + # pick only the producer threads that got in before close + successful_prod_threads = prod_threads.reject{|thr| thr.status == nil} + assert_nothing_raised{ successful_prod_threads.map(&:value) } + + # the producer threads that tried to push after q.close should all fail + unsuccessful_prod_threads = prod_threads - successful_prod_threads + unsuccessful_prod_threads.each do |thr| + assert_raise(ClosedQueueError){ thr.value } + end + + assert_equal cons_threads.size, popped_values.size + assert_equal 0, q.size + + # check that consumer threads with values match producers that called push before close + assert_equal successful_prod_threads.map{|thr| thr[:pc]}, popped_values.compact.sort + assert_nil q.pop + end + + def test_deny_pushers + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + synq = Thread::Queue.new + prod_threads = 20.times.map do |i| + Thread.new { + synq.pop + assert_raise(ClosedQueueError) { + q << i + } + } + end + q.close + synq.close # start producer threads + + prod_threads.each(&:join) + end + end + + # size should account for waiting pushers during shutdown + def sized_queue_size_close + q = Thread::SizedQueue.new 4 + 4.times{|i| q << i} + Thread.new{ q << 5 } + Thread.new{ q << 6 } + assert_equal 4, q.size + assert_equal 4, q.items + q.close + assert_equal 6, q.size + assert_equal 4, q.items + end + + def test_blocked_pushers_empty + q = Thread::SizedQueue.new 3 + prod_threads = 6.times.map do |i| + Thread.new{ + Thread.current.report_on_exception = false + q << i + } + end + + # this ensures that all producer threads call push before close + sleep 0.01 while prod_threads.select{|t| t.status == 'sleep'}.count < 3 + q.close + + ary = [] + until q.empty? + ary << q.pop + end + assert_equal 0, q.size + + assert_equal 3, ary.size + ary.each{|e| assert_include [0,1,2,3,4,5], e} + assert_nil q.pop + + prod_threads.each{|t| + begin + t.join + rescue + end + } + end + + # test thread wakeup on one-element SizedQueue with close + def test_one_element_sized_queue + q = Thread::SizedQueue.new 1 + t = Thread.new{ q.pop } + q.close + assert_nil t.value + end + + def test_close_twice + [->{Thread::Queue.new}, ->{Thread::SizedQueue.new 3}].each do |qcreate| + q = qcreate[] + q.close + assert_nothing_raised(ClosedQueueError){q.close} + end + end + + def test_queue_close_multi_multi + q = Thread::SizedQueue.new rand(800..1200) + + count_items = rand(3000..5000) + count_producers = rand(10..20) + + # ensure threads do not start running too soon and complete before we check status + mutex = Mutex.new + mutex.lock + + producers = count_producers.times.map do + Thread.new do + mutex.lock + mutex.unlock + 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.to_s =~ /\A(?:run|sleep)\z/} + + assert (consumers + producers).all?{|thr| thr.status.to_s =~ /\A(?:run|sleep)\z/}, 'no threads running' + + mutex.unlock + + # just exercising the concurrency of the support methods. + counter = Thread.new do + until q.closed? && q.empty? + raise if q.size > q.max + # otherwise this exercise causes too much contention on the lock + sleep 0.01 + end + end + + producers.each &:join + q.close + + # results not randomly distributed. Not sure why. + # consumers.map{|thr| thr.value}.each do |x| + # assert_not_equal 0, x + # end + + all_items_count = consumers.map{|thr| thr.value}.inject(:+) + assert_equal count_items * count_producers, all_items_count + + # don't leak this thread + assert_nothing_raised{counter.join} + end + + def test_queue_with_trap + if ENV['APPVEYOR'] == 'True' && RUBY_PLATFORM.match?(/mswin/) + omit 'This test fails too often on AppVeyor vs140' + end + if RUBY_PLATFORM.match?(/mingw/) + omit 'This test fails too often on MinGW' + end + + assert_in_out_err([], <<-INPUT, %w(INT INT exit), []) + q = Thread::Queue.new + trap(:INT){ + q.push 'INT' + } + Thread.new{ + loop{ + Process.kill :INT, $$ + sleep 0.1 + } + } + puts q.pop + puts q.pop + puts 'exit' + INPUT + end + + def test_fork_while_queue_waiting + q = Thread::Queue.new + sq = Thread::SizedQueue.new(1) + thq = Thread.new { q.pop } + thsq = Thread.new { sq.pop } + Thread.pass until thq.stop? && thsq.stop? + + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(6) unless q.empty? + exit!(7) unless sq.empty? + q.push :child_q + sq.push :child_sq + exit!(3) if q.pop != :child_q + exit!(4) if sq.pop != :child_sq + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + q.push :thq + sq.push :thsq + assert_equal :thq, thq.value + assert_equal :thsq, thsq.value + + sq.push(1) + th = Thread.new { q.pop; sq.pop } + thsq = Thread.new { sq.push(2) } + Thread.pass until th.stop? && thsq.stop? + pid = fork do + exit!(1) if q.num_waiting != 0 + exit!(2) if sq.num_waiting != 0 + exit!(3) unless q.empty? + exit!(4) if sq.empty? + exit!(5) if sq.pop != 1 + exit!(0) + end + _, s = Process.waitpid2(pid) + assert_predicate s, :success?, 'no segfault [ruby-core:86316] [Bug #14634]' + + assert_predicate thsq, :stop? + assert_equal 1, sq.pop + assert_same sq, thsq.value + q.push('restart th') + assert_equal 2, th.value + end if Process.respond_to?(:fork) +end diff --git a/test/ruby/test_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 new file mode 100644 index 0000000000..333edb8021 --- /dev/null +++ b/test/ruby/test_time.rb @@ -0,0 +1,1518 @@ +# frozen_string_literal: false +require 'test/unit' +require 'delegate' +require 'timeout' +require 'delegate' + +class TestTime < Test::Unit::TestCase + def setup + @verbose = $VERBOSE + end + + def teardown + $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.new(2000,1,1,0,0,0), Time.new(2000)) + assert_equal(Time.new(2000,2,1,0,0,0), Time.new("2000", "Feb")) + assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, 3600*11)) + assert_equal(Time.utc(2000,2,10), Time.new(2000,2,9, 13,0,0, -3600*11)) + assert_equal(Time.utc(2000,2,29,23,0,0), Time.new(2000, 3, 1, 0, 0, 0, 3600)) + assert_equal(Time.utc(2000,2,10), Time.new(2000,2,10, 11,0,0, "+11:00")) + assert_equal(Rational(1,2), Time.new(2000,2,10, 11,0,5.5, "+11:00").subsec) + bug4090 = '[ruby-dev:42631]' + 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") } + msg = /invalid value for Integer/ + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, 1, "+09:99") } + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, 1, "+09:99") } + assert_raise_with_message(ArgumentError, msg) { Time.new(2021, "+09:99") } + + assert_equal([0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], Time.new(2000, 1, 1, 0, 0, 0, "-00:00").to_a) + assert_equal([0, 0, 0, 2, 1, 2000, 0, 2, false, "UTC"], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a) + end + + def test_new_from_string + assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") } + + t = Time.utc(2020, 12, 24, 15, 56, 17) + assert_equal(t, Time.new("2020-12-24T15:56:17Z")) + assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00")) + assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30")) + assert_equal(t, Time.new("2020-12-25 00:56:17 +0900")) + assert_equal(t, Time.new("2020-12-25 00:57:47 +090130")) + assert_equal(t, Time.new("2020-12-25T00:56:17+09:00")) + assert_raise_with_message(ArgumentError, /missing sec part/) { + Time.new("2020-12-25 00:56 +09:00") + } + assert_raise_with_message(ArgumentError, /missing min part/) { + Time.new("2020-12-25 00 +09:00") + } + + assert_equal(Time.new(2021), Time.new("2021")) + assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00")) + assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00", in: "-01:00")) + + assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec) + assert_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec) + assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec) + assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec) + assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") { + Time.new("2020-12-25 00:56:17. +0900") + } + assert_raise_with_message(ArgumentError, /year must be 4 or more/) { + Time.new("021-12-25 00:00:00.123456 +09:00") + } + assert_raise_with_message(ArgumentError, /fraction min is.*56\./) { + Time.new("2020-12-25 00:56. +0900") + } + assert_raise_with_message(ArgumentError, /fraction hour is.*00\./) { + Time.new("2020-12-25 00. +0900") + } + assert_raise_with_message(ArgumentError, /two digits sec.*:017\b/) { + Time.new("2020-12-25 00:56:017 +0900") + } + assert_raise_with_message(ArgumentError, /two digits sec.*:9\b/) { + Time.new("2020-12-25 00:56:9 +0900") + } + assert_raise_with_message(ArgumentError, /sec out of range/) { + Time.new("2020-12-25 00:56:64 +0900") + } + assert_raise_with_message(ArgumentError, /two digits min.*:056\b/) { + Time.new("2020-12-25 00:056:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits min.*:5\b/) { + Time.new("2020-12-25 00:5:17 +0900") + } + assert_raise_with_message(ArgumentError, /min out of range/) { + Time.new("2020-12-25 00:64:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits hour.*\b000\b/) { + Time.new("2020-12-25 000:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits hour.*\b0\b/) { + Time.new("2020-12-25 0:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /hour out of range/) { + Time.new("2020-12-25 33:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mday.*\b025\b/) { + Time.new("2020-12-025 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mday.*\b5\b/) { + Time.new("2020-12-5 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /mday out of range/) { + Time.new("2020-12-33 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mon.*\b012\b/) { + Time.new("2020-012-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /two digits mon.*\b1\b/) { + Time.new("2020-1-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /mon out of range/) { + Time.new("2020-17-25 00:56:17 +0900") + } + assert_raise_with_message(ArgumentError, /no time information/) { + Time.new("2020-12") + } + assert_raise_with_message(ArgumentError, /no time information/) { + Time.new("2020-12-02") + } + assert_raise_with_message(ArgumentError, /can't parse/) { + Time.new(" 2020-12-02 00:00:00") + } + assert_raise_with_message(ArgumentError, /can't parse/) { + Time.new("2020-12-02 00:00:00 ") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +0960") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +09:60") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +090060") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +09:00:60") + } + end + + def test_time_add() + assert_equal(Time.utc(2000, 3, 21, 3, 30) + 3 * 3600, + Time.utc(2000, 3, 21, 6, 30)) + assert_equal(Time.utc(2000, 3, 21, 3, 30) + (-3 * 3600), + Time.utc(2000, 3, 21, 0, 30)) + assert_equal(0, (Time.at(1.1) + 0.9).usec) + + 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_not_predicate(t, :utc?) + assert_equal(3600, t.utc_offset) + t = Time.new(2000, 4, 1, 0, 0, 0, "+02:00") + 24 + assert_not_predicate(t, :utc?) + assert_equal(7200, t.utc_offset) + end + + def test_time_subt() + assert_equal(Time.utc(2000, 3, 21, 3, 30) - 3 * 3600, + Time.utc(2000, 3, 21, 0, 30)) + assert_equal(Time.utc(2000, 3, 21, 3, 30) - (-3 * 3600), + Time.utc(2000, 3, 21, 6, 30)) + assert_equal(900000, (Time.at(1.1) - 0.2).usec) + end + + def test_time_time() + assert_equal(Time.utc(2000, 3, 21, 3, 30) \ + -Time.utc(2000, 3, 21, 0, 30), 3*3600) + assert_equal(Time.utc(2000, 3, 21, 0, 30) \ + -Time.utc(2000, 3, 21, 3, 30), -3*3600) + end + + def negative_time_t? + begin + Time.at(-1) + true + rescue ArgumentError + false + end + end + + def test_timegm + if negative_time_t? + assert_equal(-0x80000000, Time.utc(1901, 12, 13, 20, 45, 52).tv_sec) + assert_equal(-2, Time.utc(1969, 12, 31, 23, 59, 58).tv_sec) + assert_equal(-1, Time.utc(1969, 12, 31, 23, 59, 59).tv_sec) + end + + assert_equal(0, Time.utc(1970, 1, 1, 0, 0, 0).tv_sec) # the Epoch + assert_equal(1, Time.utc(1970, 1, 1, 0, 0, 1).tv_sec) + assert_equal(31535999, Time.utc(1970, 12, 31, 23, 59, 59).tv_sec) + 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) + + 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) + + # Giveup to try 2nd test because some state is changed. + omit if Test::Unit::Runner.current_repeat_count > 0 + + assert_equal(0x7fffffff, Time.utc(2038, 1, 19, 3, 14, 7).tv_sec) + assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) + else + 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) + assert_equal(78796802, Time.utc(1972, 7, 1, 0, 0, 1).tv_sec) + assert_equal(946684822, Time.utc(2000, 1, 1, 0, 0, 0).tv_sec) + end + end + + def test_strtime + t = nil + assert_nothing_raised { t = Time.utc("2000", "1", "2" , "3", "4", "5") } + assert_equal(Time.utc(2000,1,2,3,4,5), t) + end + + def test_huge_difference + if negative_time_t? + assert_equal(Time.at(-0x80000000), Time.at(0x7fffffff) - 0xffffffff, "[ruby-dev:22619]") + assert_equal(Time.at(-0x80000000), Time.at(0x7fffffff) + (-0xffffffff)) + assert_equal(Time.at(0x7fffffff), Time.at(-0x80000000) + 0xffffffff, "[ruby-dev:22619]") + assert_equal(Time.at(0x7fffffff), Time.at(-0x80000000) - (-0xffffffff)) + end + end + + def test_big_minus + begin + bigtime0 = Time.at(2**60) + bigtime1 = Time.at(2**60+1) + rescue RangeError + return + end + assert_equal(1.0, bigtime1 - bigtime0) + end + + def test_at + assert_equal(100000, Time.at("0.1".to_r).usec) + assert_equal(10000, Time.at("0.01".to_r).usec) + assert_equal(1000, Time.at("0.001".to_r).usec) + assert_equal(100, Time.at("0.0001".to_r).usec) + assert_equal(10, Time.at("0.00001".to_r).usec) + assert_equal(1, Time.at("0.000001".to_r).usec) + assert_equal(100000000, Time.at("0.1".to_r).nsec) + assert_equal(10000000, Time.at("0.01".to_r).nsec) + assert_equal(1000000, Time.at("0.001".to_r).nsec) + assert_equal(100000, Time.at("0.0001".to_r).nsec) + assert_equal(10000, Time.at("0.00001".to_r).nsec) + assert_equal(1000, Time.at("0.000001".to_r).nsec) + assert_equal(100, Time.at("0.0000001".to_r).nsec) + assert_equal(10, Time.at("0.00000001".to_r).nsec) + assert_equal(1, Time.at("0.000000001".to_r).nsec) + assert_equal(100000, Time.at(0.1).usec) + assert_equal(10000, Time.at(0.01).usec) + assert_equal(1000, Time.at(0.001).usec) + assert_equal(100, Time.at(0.0001).usec) + assert_equal(10, Time.at(0.00001).usec) + assert_equal(3, Time.at(0.000003).usec) + assert_equal(100000000, Time.at(0.1).nsec) + assert_equal(10000000, Time.at(0.01).nsec) + assert_equal(1000000, Time.at(0.001).nsec) + 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_in_delta(200, Time.at(0.0000002).nsec, 1, "should be within FP error") + assert_equal(10, Time.at(0.00000001).nsec) + assert_equal(1, Time.at(0.000000001).nsec) + + assert_equal(0, Time.at(1e-10).nsec) + assert_equal(0, Time.at(4e-10).nsec) + assert_equal(0, Time.at(6e-10).nsec) + assert_equal(1, Time.at(14e-10).nsec) + assert_equal(1, Time.at(16e-10).nsec) + if negative_time_t? + assert_equal(999999999, Time.at(-1e-10).nsec) + assert_equal(999999999, Time.at(-4e-10).nsec) + assert_equal(999999999, Time.at(-6e-10).nsec) + assert_equal(999999998, Time.at(-14e-10).nsec) + assert_equal(999999998, Time.at(-16e-10).nsec) + end + + t = Time.at(-4611686019).utc + assert_equal(1823, t.year) + + t = Time.at(4611686018, 999999).utc + assert_equal(2116, t.year) + assert_equal("0.999999".to_r, t.subsec) + + 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) + assert_equal(-146138510344, t.year) + t = Time.at(-0x4000_0000_0000_0001) + assert_equal(-146138510344, t.year) + t = Time.at(-0x5000_0000_0000_0001) + assert_equal(-182673138422, t.year) + t = Time.at(-0x6000_0000_0000_0000) + assert_equal(-219207766501, t.year) + + t = Time.at(0).utc + assert_equal([1970,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-86400).utc + assert_equal([1969,12,31, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-86400 * (400 * 365 + 97)).utc + assert_equal([1970-400,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-86400 * (400 * 365 + 97)*1000).utc + assert_equal([1970-400*1000,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-86400 * (400 * 365 + 97)*2421).utc + assert_equal([1970-400*2421,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-86400 * (400 * 365 + 97)*1000000).utc + assert_equal([1970-400*1000000,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + + t = Time.at(-30613683110400).utc + assert_equal([-968139,1,1, 0,0,0], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + t = Time.at(-30613683110401).utc + assert_equal([-968140,12,31, 23,59,59], [t.year, t.mon, t.day, t.hour, t.min, t.sec]) + end + + def test_at2 + assert_equal(100, Time.at(0, 0.1).nsec) + assert_equal(10, Time.at(0, 0.01).nsec) + assert_equal(1, Time.at(0, 0.001).nsec) + end + + def test_at_splat + assert_equal(Time.at(1, 2), Time.at(*[1, 2])) + end + + def test_at_with_unit + assert_equal(123456789, Time.at(0, 123456789, :nanosecond).nsec) + assert_equal(123456789, Time.at(0, 123456789, :nsec).nsec) + 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) + end + + def test_utc_subsecond + assert_equal(500000, Time.utc(2007,1,1,0,0,1.5).usec) + assert_equal(100000, Time.utc(2007,1,1,0,0,Rational(11,10)).usec) + end + + def test_eq_nsec + assert_equal(Time.at(0, 0.123), Time.at(0, 0.123)) + assert_not_equal(Time.at(0, 0.123), Time.at(0, 0.124)) + end + + def assert_marshal_roundtrip(t) + iv_names = t.instance_variables + iv_vals1 = iv_names.map {|n| t.instance_variable_get n } + m = Marshal.dump(t) + t2 = Marshal.load(m) + iv_vals2 = iv_names.map {|n| t2.instance_variable_get n } + assert_equal(t, t2) + assert_equal(iv_vals1, iv_vals2) + t2 + end + + def test_marshal_nsec + assert_marshal_roundtrip(Time.at(0, 0.123)) + assert_marshal_roundtrip(Time.at(0, 0.120)) + end + + def test_marshal_nsec_191 + # generated by ruby 1.9.1p376 + m = "\x04\bIu:\tTime\r \x80\x11\x80@\xE2\x01\x00\x06:\rsubmicro\"\ax\x90" + t = Marshal.load(m) + assert_equal(Time.at(Rational(123456789, 1000000000)), t, "[ruby-dev:40133]") + end + + def test_marshal_rational + assert_marshal_roundtrip(Time.at(0, Rational(1,3))) + assert_not_match(/Rational/, Marshal.dump(Time.at(0, Rational(1,3)))) + end + + def test_marshal_ivar + t = Time.at(123456789, 987654.321) + t.instance_eval { @var = 135 } + assert_marshal_roundtrip(t) + assert_marshal_roundtrip(Marshal.load(Marshal.dump(t))) + end + + def test_marshal_timezone + bug = '[ruby-dev:40063]' + + t1 = Time.gm(2000) + m = Marshal.dump(t1.getlocal("-02:00")) + t2 = Marshal.load(m) + assert_equal(t1, t2) + assert_equal(-7200, t2.utc_offset, bug) + m = Marshal.dump(t1.getlocal("+08:15")) + t2 = Marshal.load(m) + assert_equal(t1, t2) + assert_equal(29700, t2.utc_offset, bug) + end + + 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([], <<-'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_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_marshal_broken_month + data = "\x04\x08u:\tTime\r\x20\x7c\x1e\xc0\x00\x00\x00\x00" + assert_equal(Time.utc(2022, 4, 1), Marshal.load(data)) + end + + def test_marshal_distant_past + assert_marshal_roundtrip(Time.utc(1890, 1, 1)) + assert_marshal_roundtrip(Time.utc(-4.5e9, 1, 1)) + end + + def test_marshal_distant_future + assert_marshal_roundtrip(Time.utc(30000, 1, 1)) + assert_marshal_roundtrip(Time.utc(5.67e9, 4, 8)) + end + + def test_at3 + t2000 = get_t2000 + assert_equal(t2000, Time.at(t2000)) +# assert_raise(RangeError) do +# Time.at(2**31-1, 1_000_000) +# Time.at(2**63-1, 1_000_000) +# end +# assert_raise(RangeError) do +# Time.at(-2**31, -1_000_000) +# Time.at(-2**63, -1_000_000) +# end + end + + def test_utc_or_local + 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 + Time.gm(2000, 1, 1, 0, 0, 10_000_000_000_000_000_001.0, :foo, :foo) + 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) } + class << o; remove_method(:to_r); end + def o.to_r; ""; end + assert_raise(TypeError) { Time.gm(2000, 1, 1, 0, 0, o, :foo, :foo) } + class << o; remove_method(:to_r); end + def o.to_r; Rational(11); end + assert_equal(11, Time.gm(2000, 1, 1, 0, 0, o).sec) + o = Object.new + def o.to_int; 10; end + assert_equal(10, Time.gm(2000, 1, 1, 0, 0, o).sec) + assert_raise(ArgumentError) { Time.gm(2000, 13) } + + t = Time.local(2000) + 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) + + assert_raise(ArgumentError) { Time.gm(2000, 1, -1) } + assert_raise(ArgumentError) { Time.gm(2000, 1, 2**30 + 1) } + assert_raise(ArgumentError) { Time.gm(2000, 1, -2**30 + 1) } + end + + def test_time_interval + 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 + 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 + 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 + t2000 = get_t2000 + assert_operator(t2000, :eql?, t2000) + assert_not_operator(t2000, :eql?, Time.gm(2001)) + end + + def test_utc_p + assert_predicate(Time.gm(2000), :gmt?) + assert_not_predicate(Time.local(2000), :gmt?) + assert_not_predicate(Time.at(0), :gmt?) + end + + def test_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 + t2000 = get_t2000 + assert_equal(t2000, t2000.dup) + assert_raise(TypeError) do + t2000.instance_eval { initialize_copy(nil) } + end + end + + def test_localtime_gmtime + assert_nothing_raised do + t = Time.gm(2000) + assert_predicate(t, :gmt?) + t.localtime + assert_not_predicate(t, :gmt?) + t.localtime + assert_not_predicate(t, :gmt?) + t.gmtime + assert_predicate(t, :gmt?) + t.gmtime + assert_predicate(t, :gmt?) + end + + t1 = Time.gm(2000) + t2 = t1.getlocal + assert_equal(t1, t2) + 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?) + assert_equal(t1, t3) + + t1 = Time.local(2000) + t2 = t1.getgm + assert_equal(t1, t2) + t3 = t1.getlocal("-02:00") + assert_equal(t1, t3) + assert_equal(-7200, t3.utc_offset) + t1.gmtime + assert_equal(t1, t2) + assert_equal(t1.gmt?, t2.gmt?) + assert_equal(t1, t3) + end + + def test_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 + 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 test_inspect + t2000 = get_t2000 + assert_equal("2000-01-01 00:00:00 UTC", t2000.inspect) + assert_equal(Encoding::US_ASCII, t2000.inspect.encoding) + assert_kind_of(String, Time.at(946684800).getlocal.inspect) + assert_equal(Time.at(946684800).getlocal.inspect, Time.at(946684800).inspect) + + t2000 = get_t2000 + 1/10r + assert_equal("2000-01-01 00:00:00.1 UTC", t2000.inspect) + t2000 = get_t2000 + 1/1000000000r + assert_equal("2000-01-01 00:00:00.000000001 UTC", t2000.inspect) + t2000 = get_t2000 + 1/10000000000r + assert_equal("2000-01-01 00:00:00 1/10000000000 UTC", t2000.inspect) + t2000 = get_t2000 + 0.1 + assert_equal("2000-01-01 00:00:00 3602879701896397/36028797018963968 UTC", t2000.inspect) + + t2000 = get_t2000 + t2000 = t2000.localtime(9*3600) + assert_equal("2000-01-01 09:00:00 +0900", t2000.inspect) + + t2000 = get_t2000.localtime(9*3600) + 1/10r + assert_equal("2000-01-01 09:00:00.1 +0900", t2000.inspect) + + t2000 = get_t2000 + assert_equal("2000-01-01 09:12:00 +0912", t2000.localtime(9*3600+12*60).inspect) + assert_equal("2000-01-01 09:12:34 +091234", t2000.localtime(9*3600+12*60+34).inspect) + end + + def assert_zone_encoding(time) + zone = time.zone + assert_predicate(zone, :valid_encoding?) + 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 + t2000 = get_t2000 + # assert_raise(RangeError) { t2000 + 10000000000 } + # assert_raise(RangeError) t2000 - 3094168449 } + # assert_raise(RangeError) { t2000 + 1200798848 } + assert_raise(TypeError) { t2000 + Time.now } + end + + def test_plus_type + t0 = Time.utc(2000,1,1) + n0 = t0.to_i + n1 = n0+1 + t1 = Time.at(n1) + assert_equal(t1, t0 + 1) + assert_equal(t1, t0 + 1.0) + assert_equal(t1, t0 + Rational(1,1)) + assert_equal(t1, t0 + SimpleDelegator.new(1)) + assert_equal(t1, t0 + SimpleDelegator.new(1.0)) + assert_equal(t1, t0 + SimpleDelegator.new(Rational(1,1))) + assert_raise(TypeError) { t0 + nil } + assert_raise(TypeError) { t0 + "1" } + assert_raise(TypeError) { t0 + SimpleDelegator.new("1") } + assert_equal(0.5, (t0 + 1.5).subsec) + assert_equal(Rational(1,3), (t0 + Rational(4,3)).subsec) + assert_equal(0.5, (t0 + SimpleDelegator.new(1.5)).subsec) + assert_equal(Rational(1,3), (t0 + SimpleDelegator.new(Rational(4,3))).subsec) + end + + def test_minus + t = Time.at(-4611686018).utc - 100 + assert_equal(1823, t.year) + end + + def test_readers + 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) + assert_equal(t.min, Time.at(946684800).min) + assert_equal(t.hour, Time.at(946684800).hour) + assert_equal(t.mday, Time.at(946684800).mday) + assert_equal(t.mon, Time.at(946684800).mon) + assert_equal(t.year, Time.at(946684800).year) + assert_equal(t.wday, Time.at(946684800).wday) + 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?) + assert_equal(t.tuesday?, Time.at(946684800).tuesday?) + assert_equal(t.wednesday?, Time.at(946684800).wednesday?) + assert_equal(t.thursday?, Time.at(946684800).thursday?) + assert_equal(t.friday?, Time.at(946684800).friday?) + assert_equal(t.saturday?, Time.at(946684800).saturday?) + assert_equal(t.to_a, Time.at(946684800).to_a) + 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("12:00:00 AM", t2000.strftime("%r")) + assert_equal("Sat 2000-01-01T00:00:00", t2000.strftime("%3a %FT%T")) + + assert_warning(/strftime called with empty format string/) do + assert_equal("", t2000.strftime("")) + end + assert_equal("foo\0bar\x0000\x0000\x0000", t2000.strftime("foo\0bar\0%H\0%M\0%S")) + assert_equal("foo" * 1000, t2000.strftime("foo" * 1000)) + + 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")) + assert_equal(" 1", t.strftime("%_d")) + assert_equal(" 1", t.strftime("%e")) + assert_equal("01", t.strftime("%0e")) + assert_equal(" 1", t.strftime("%_e")) + assert_equal("AM", t.strftime("%p")) + assert_equal("am", t.strftime("%#p")) + assert_equal("am", t.strftime("%P")) + assert_equal("AM", t.strftime("%#P")) + assert_equal("02", t.strftime("%H")) + assert_equal("02", t.strftime("%0H")) + assert_equal(" 2", t.strftime("%_H")) + assert_equal("02", t.strftime("%I")) + assert_equal("02", t.strftime("%0I")) + assert_equal(" 2", t.strftime("%_I")) + assert_equal(" 2", t.strftime("%k")) + assert_equal("02", t.strftime("%0k")) + assert_equal(" 2", t.strftime("%_k")) + assert_equal(" 2", t.strftime("%l")) + assert_equal("02", t.strftime("%0l")) + assert_equal(" 2", t.strftime("%_l")) + t = Time.mktime(2001, 10, 1, 14, 0, 0) + assert_equal("PM", t.strftime("%p")) + assert_equal("pm", t.strftime("%#p")) + assert_equal("pm", t.strftime("%P")) + assert_equal("PM", t.strftime("%#P")) + assert_equal("14", t.strftime("%H")) + assert_equal("14", t.strftime("%0H")) + assert_equal("14", t.strftime("%_H")) + assert_equal("02", t.strftime("%I")) + assert_equal("02", t.strftime("%0I")) + assert_equal(" 2", t.strftime("%_I")) + assert_equal("14", t.strftime("%k")) + assert_equal("14", t.strftime("%0k")) + assert_equal("14", t.strftime("%_k")) + 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] + 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] + 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")) + assert_equal("03/14/2000 6:53:58.9793238460", + t.strftime("%m/%d/%Y %l:%M:%S.%10N")) + assert_equal("03/14/2000 6:53:58.979323846", + t.strftime("%m/%d/%Y %l:%M:%S.%9N")) + assert_equal("03/14/2000 6:53:58.97932384", + t.strftime("%m/%d/%Y %l:%M:%S.%8N")) + + t = Time.utc(1592,3,14, 6,53,"58.97932384626433832795028841971".to_r) # Pi Day + assert_equal("03/14/1592 6:53:58.97932384626433832795028841971", + t.strftime("%m/%d/%Y %l:%M:%S.%29N")) + assert_equal("03/14/1592 6:53:58.9793238462", + t.strftime("%m/%d/%Y %l:%M:%S.%10N")) + assert_equal("03/14/1592 6:53:58.979323846", + t.strftime("%m/%d/%Y %l:%M:%S.%9N")) + assert_equal("03/14/1592 6:53:58.97932384", + 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")) + + assert_equal("+0000", t2000.strftime("%z")) + assert_equal("-0000", t2000.strftime("%-z")) + assert_equal("-00:00", t2000.strftime("%-:z")) + assert_equal("-00:00:00", t2000.strftime("%-::z")) + + t = t2000.getlocal("+00:00") + assert_equal("+0000", t.strftime("%z")) + assert_equal("+0000", t.strftime("%-z")) + assert_equal("+00:00", t.strftime("%-:z")) + assert_equal("+00:00:00", t.strftime("%-::z")) + end + + def test_strftime_padding + 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)) + assert_equal(-1, t1 <=> t2) + assert_equal(1, t2 <=> t1) + assert_equal(-1, d1 <=> d2) + assert_equal(1, d2 <=> d1) + end + + def test_to_r + assert_kind_of(Rational, Time.new(2000,1,1,0,0,Rational(4,3)).to_r) + assert_kind_of(Rational, Time.utc(1970).to_r) + end + + def test_round + t = Time.utc(1999,12,31, 23,59,59) + t2 = (t+0.4).round + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.49).round + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.5).round + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.4).round + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.49).round + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.5).round + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + + t2 = (t+0.123456789).round(4) + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(Rational(1235,10000), t2.subsec) + + off = 0.0 + 100.times {|i| + t2 = (t+off).round(1) + assert_equal(Rational(i % 10, 10), t2.subsec) + off += 0.1 + } + end + + def test_floor + t = Time.utc(1999,12,31, 23,59,59) + t2 = (t+0.4).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.49).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.5).floor + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.4).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.49).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.5).floor + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + + t2 = (t+0.123456789).floor(4) + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(Rational(1234,10000), t2.subsec) + end + + def test_ceil + t = Time.utc(1999,12,31, 23,59,59) + t2 = (t+0.4).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.49).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+0.5).ceil + assert_equal([0,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.4).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.49).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + t2 = (t+1.5).ceil + assert_equal([1,0,0, 1,1,2000, 6,1,false,"UTC"], t2.to_a) + assert_equal(0, t2.subsec) + + t2 = (t+0.123456789).ceil(4) + assert_equal([59,59,23, 31,12,1999, 5,365,false,"UTC"], t2.to_a) + assert_equal(Rational(1235,10000), t2.subsec) + + time = Time.utc(2016, 4, 23, 0, 0, 0.123456789r) + assert_equal(time, time.ceil(9)) + assert_equal(time, time.ceil(10)) + assert_equal(time, time.ceil(11)) + end + + def test_getlocal_dont_share_eigenclass + bug5012 = "[ruby-dev:44071]" + + 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 + # Giveup to try 2nd test because some state is changed. + omit if Test::Unit::Runner.current_repeat_count > 0 + + if no_leap_seconds? + assert_equal(0x80000000, Time.utc(2038, 1, 19, 3, 14, 8).tv_sec) + end + [ + [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 + t = Time.gm(2000) + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("UTC")).to_a[0, 6] + assert_predicate t1, :utc? + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("-0000")).to_a[0, 6] + assert_predicate t1, :utc? + assert_equal [00, 00, 00, 1, 1, 2000], (t1 = t.getlocal("+0000")).to_a[0, 6] + assert_not_predicate t1, :utc? + end + + def test_getlocal_utc_offset + t = Time.gm(2000) + assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-02:30").to_a[0, 6] + assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+09:00").to_a[0, 6] + assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-02:30:40").to_a[0, 6] + assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+09:10:35").to_a[0, 6] + assert_equal [00, 30, 21, 31, 12, 1999], t.getlocal("-0230").to_a[0, 6] + assert_equal [00, 00, 9, 1, 1, 2000], t.getlocal("+0900").to_a[0, 6] + assert_equal [20, 29, 21, 31, 12, 1999], t.getlocal("-023040").to_a[0, 6] + assert_equal [35, 10, 9, 1, 1, 2000], t.getlocal("+091035").to_a[0, 6] + assert_raise(ArgumentError) {t.getlocal("-02:3040")} + assert_raise(ArgumentError) {t.getlocal("+0910:35")} + end + + def test_getlocal_nil + now = Time.now + now2 = nil + 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_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 + omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM + omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] > 0 + omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit "Only run this test on 64-bit" if RbConfig::SIZEOF["void*"] != 8 + + require 'objspace' + t = Time.at(0) + sizeof_timew = + if RbConfig::SIZEOF.key?("uint64_t") && RbConfig::SIZEOF["long"] * 2 <= RbConfig::SIZEOF["uint64_t"] + RbConfig::SIZEOF["uint64_t"] + else + RbConfig::SIZEOF["void*"] # Same size as VALUE + end + sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8 + expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm + assert_operator ObjectSpace.memsize_of(t), :<=, expect + rescue LoadError => e + omit "failed to load objspace: #{e.message}" + end + + def test_deconstruct_keys + t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) } + assert_equal( + {year: 2022, month: 10, day: 16, wday: 0, yday: 289, + hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'}, + t.deconstruct_keys(nil) + ) + + assert_equal( + {year: 2022, month: 10, sec: 30}, + t.deconstruct_keys(%i[year month sec nonexistent]) + ) + end + + def test_parse_zero_bigint + assert_equal 0, Time.new("2020-10-28T16:48:07.000Z").nsec, '[Bug #19390]' + end + + def test_xmlschema_encode + [:xmlschema, :iso8601].each do |method| + bug6100 = '[ruby-core:42997]' + + t = Time.utc(2001, 4, 17, 19, 23, 17, 300000) + assert_equal("2001-04-17T19:23:17Z", t.__send__(method)) + assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1)) + assert_equal("2001-04-17T19:23:17.300000Z", t.__send__(method, 6)) + assert_equal("2001-04-17T19:23:17.3000000Z", t.__send__(method, 7)) + assert_equal("2001-04-17T19:23:17.3Z", t.__send__(method, 1.9), bug6100) + + t = Time.utc(2001, 4, 17, 19, 23, 17, 123456) + assert_equal("2001-04-17T19:23:17.1234560Z", t.__send__(method, 7)) + assert_equal("2001-04-17T19:23:17.123456Z", t.__send__(method, 6)) + assert_equal("2001-04-17T19:23:17.12345Z", t.__send__(method, 5)) + assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1)) + assert_equal("2001-04-17T19:23:17.1Z", t.__send__(method, 1.9), bug6100) + + t = Time.at(2.quo(3)).getlocal("+09:00") + assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3)) + assert_equal("1970-01-01T09:00:00.6666666666+09:00", t.__send__(method, 10)) + assert_equal("1970-01-01T09:00:00.66666666666666666666+09:00", t.__send__(method, 20)) + assert_equal("1970-01-01T09:00:00.6+09:00", t.__send__(method, 1.1), bug6100) + assert_equal("1970-01-01T09:00:00.666+09:00", t.__send__(method, 3.2), bug6100) + + t = Time.at(123456789.quo(9999999999)).getlocal("+09:00") + assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3)) + assert_equal("1970-01-01T09:00:00.012345678+09:00", t.__send__(method, 9)) + assert_equal("1970-01-01T09:00:00.0123456789+09:00", t.__send__(method, 10)) + assert_equal("1970-01-01T09:00:00.0123456789012345678+09:00", t.__send__(method, 19)) + assert_equal("1970-01-01T09:00:00.01234567890123456789+09:00", t.__send__(method, 20)) + assert_equal("1970-01-01T09:00:00.012+09:00", t.__send__(method, 3.8), bug6100) + + t = Time.utc(1) + assert_equal("0001-01-01T00:00:00Z", t.__send__(method)) + + begin + Time.at(-1) + rescue ArgumentError + # ignore + else + t = Time.utc(1960, 12, 31, 23, 0, 0, 123456) + assert_equal("1960-12-31T23:00:00.123456Z", t.__send__(method, 6)) + end + + t = get_t2000.getlocal("-09:30") # Pacific/Marquesas + assert_equal("1999-12-31T14:30:00-09:30", t.__send__(method)) + + assert_equal("10000-01-01T00:00:00Z", Time.utc(10000).__send__(method)) + assert_equal("9999-01-01T00:00:00Z", Time.utc(9999).__send__(method)) + assert_equal("0001-01-01T00:00:00Z", Time.utc(1).__send__(method)) # 1 AD + assert_equal("0000-01-01T00:00:00Z", Time.utc(0).__send__(method)) # 1 BC + assert_equal("-0001-01-01T00:00:00Z", Time.utc(-1).__send__(method)) # 2 BC + assert_equal("-0004-01-01T00:00:00Z", Time.utc(-4).__send__(method)) # 5 BC + assert_equal("-9999-01-01T00:00:00Z", Time.utc(-9999).__send__(method)) + assert_equal("-10000-01-01T00:00:00Z", Time.utc(-10000).__send__(method)) + end + end +end diff --git a/test/ruby/test_time_tz.rb b/test/ruby/test_time_tz.rb new file mode 100644 index 0000000000..473c3cabcb --- /dev/null +++ b/test/ruby/test_time_tz.rb @@ -0,0 +1,828 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestTimeTZ < Test::Unit::TestCase + has_right_tz = true + has_lisbon_tz = true + force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes" + case RUBY_PLATFORM + when /darwin|linux/ + force_tz_test = true + when /freebsd|openbsd/ + 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 + 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 = "-" + gmtoff = -gmtoff + else + expected = "+" + end + gmtoff /= 60 + expected << "%02d" % [gmtoff / 60] + expected << ":" if colon + expected << "%02d" % [gmtoff % 60] + expected + end + + def format_gmtoff2(gmtoff) + if gmtoff < 0 + expected = "-" + gmtoff = -gmtoff + else + expected = "+" + end + expected << "%02d:%02d:%02d" % [gmtoff / 3600, gmtoff % 3600 / 60, gmtoff % 60] + expected + end + + def group_by(e, &block) + if e.respond_to? :group_by + e.group_by(&block) + else + h = {} + e.each {|o| + (h[yield(o)] ||= []) << o + } + h + end + end + + end + + include Util + extend Util + + has_right_tz &&= have_tz_offset?("right/America/Los_Angeles") + has_lisbon_tz &&= have_tz_offset?("Europe/Lisbon") + CORRECT_TOKYO_DST_1951 = with_tz("Asia/Tokyo") { + if Time.local(1951, 5, 6, 12, 0, 0).dst? # noon, DST + if Time.local(1951, 5, 6, 1, 0, 0).dst? # DST with fixed tzdata + Time.local(1951, 9, 8, 23, 0, 0).dst? ? "2018f" : "2018e" + end + end + } + CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") { + Time.local(1994, 12, 31, 0, 0, 0).year == 1995 + } + CORRECT_SINGAPORE_1982 = with_tz("Asia/Singapore") { + "2022g" if Time.local(1981, 12, 31, 23, 59, 59).utc_offset == 8*3600 + } + + def time_to_s(t) + t.to_s + end + + + def assert_time_constructor(tz, expected, method, args, message=nil) + m = message ? "#{message}\n" : "" + m << "TZ=#{tz} Time.#{method}(#{args.map {|arg| arg.inspect }.join(', ')})" + real = time_to_s(Time.send(method, *args)) + assert_equal(expected, real, m) + end + + def test_localtime_zone + t = with_tz("America/Los_Angeles") { + Time.local(2000, 1, 1) + } + omit "force_tz_test is false on this environment" unless t + z1 = t.zone + z2 = with_tz(tz="Asia/Singapore") { + t.localtime.zone + } + assert_equal(z2, z1) + end + + def test_america_los_angeles + with_tz(tz="America/Los_Angeles") { + assert_time_constructor(tz, "2007-03-11 03:00:00 -0700", :local, [2007,3,11,2,0,0]) + assert_time_constructor(tz, "2007-03-11 03:59:59 -0700", :local, [2007,3,11,2,59,59]) + assert_equal("PST", Time.new(0x1_0000_0000_0000_0000, 1).zone) + assert_equal("PDT", Time.new(0x1_0000_0000_0000_0000, 8).zone) + assert_equal(false, Time.new(0x1_0000_0000_0000_0000, 1).isdst) + assert_equal(true, Time.new(0x1_0000_0000_0000_0000, 8).isdst) + } + end + + def test_america_managua + with_tz(tz="America/Managua") { + assert_time_constructor(tz, "1993-01-01 01:00:00 -0500", :local, [1993,1,1,0,0,0]) + assert_time_constructor(tz, "1993-01-01 01:59:59 -0500", :local, [1993,1,1,0,59,59]) + } + end + + def test_asia_singapore + with_tz(tz="Asia/Singapore") { + assert_time_constructor(tz, "1981-12-31 23:29:59 +0730", :local, [1981,12,31,23,29,59]) + if CORRECT_SINGAPORE_1982 + assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1981,12,31,23,30,00]) + assert_time_constructor(tz, "1982-01-01 00:00:00 +0800", :local, [1982,1,1,0,0,0]) + assert_time_constructor(tz, "1982-01-01 00:29:59 +0800", :local, [1982,1,1,0,29,59]) + end + assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0]) + } + end + + def test_asia_tokyo + with_tz(tz="Asia/Tokyo") { + h = CORRECT_TOKYO_DST_1951 ? 0 : 2 + assert_time_constructor(tz, "1951-05-06 0#{h+1}:00:00 +1000", :local, [1951,5,6,h,0,0]) + assert_time_constructor(tz, "1951-05-06 0#{h+1}:59:59 +1000", :local, [1951,5,6,h,59,59]) + assert_time_constructor(tz, "2010-06-10 06:13:28 +0900", :local, [2010,6,10,6,13,28]) + } + end + + def test_asia_kuala_lumpur + with_tz(tz="Asia/Kuala_Lumpur") { + assert_time_constructor(tz, "1933-01-01 00:20:00 +0720", :local, [1933]) + } + end + + def test_canada_newfoundland + with_tz(tz="America/St_Johns") { + assert_time_constructor(tz, "2007-11-03 23:00:59 -0230", :new, [2007,11,3,23,0,59,:dst]) + 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]) + assert_time_constructor(tz, "2007-11-04 00:00:00 -0230", :new, [2007,11,4,0,0,0,:dst]) + assert_time_constructor(tz, "2007-11-04 00:00:59 -0230", :new, [2007,11,4,0,0,59,:dst]) + assert_time_constructor(tz, "2007-11-03 23:01:00 -0330", :new, [2007,11,3,23,1,0,:std]) + assert_time_constructor(tz, "2007-11-03 23:59:59 -0330", :new, [2007,11,3,23,59,59,:std]) + assert_time_constructor(tz, "2007-11-04 00:00:59 -0330", :new, [2007,11,4,0,0,59,:std]) + assert_time_constructor(tz, "2007-11-04 00:01:00 -0330", :new, [2007,11,4,0,1,0,:std]) + } + end + + 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] [Bug #3411]") + assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,0,59,59]) + assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1,1,0,0]) + assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,1,59,59]) + } + end + + def test_europe_berlin + with_tz(tz="Europe/Berlin") { + assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [2011,10,30,2,0,0], "[ruby-core:67345] [Bug #10698]") + assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [0,0,2,30,10,2011,nil,nil,false,nil]) + assert_time_constructor(tz, "2011-10-30 02:00:00 +0200", :local, [0,0,2,30,10,2011,nil,nil,true,nil]) + } + end + + def test_europe_lisbon + with_tz("Europe/Lisbon") { + assert_include(%w"LMT CET", Time.new(-0x1_0000_0000_0000_0000).zone) + } + end if has_lisbon_tz + + def test_pacific_kiritimati + with_tz(tz="Pacific/Kiritimati") { + assert_time_constructor(tz, "1994-12-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") { + 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 if has_right_tz + + def test_right_utc_switching + with_tz("UTC") { # ensure no leap second timezone + assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + with_tz(tz="right/UTC") { + assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) + assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) + assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + with_tz("right/UTC") { + assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + with_tz(tz="UTC") { + assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0]) + assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0]) + assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i) + } + } + end if has_right_tz + + def test_right_america_los_angeles + with_tz(tz="right/America/Los_Angeles") { + assert_time_constructor(tz, "2008-12-31 15:59:59 -0800", :local, [2008,12,31,15,59,59]) + 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 if has_right_tz + + def test_utc_names + assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "UTC"), :utc?) + assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "utc"), :utc?) + assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "Z"), :utc?) + assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "-00:00"), :utc?) + assert_not_predicate(Time.new(2019, 1, 1, 0, 0, 0, "+00:00"), :utc?) + end + + def test_military_names + assert_equal( +1*3600, Time.new(2019, 1, 1, 0, 0, 0, "A").gmtoff) + assert_equal( +2*3600, Time.new(2019, 1, 1, 0, 0, 0, "B").gmtoff) + assert_equal( +3*3600, Time.new(2019, 1, 1, 0, 0, 0, "C").gmtoff) + assert_equal( +4*3600, Time.new(2019, 1, 1, 0, 0, 0, "D").gmtoff) + assert_equal( +5*3600, Time.new(2019, 1, 1, 0, 0, 0, "E").gmtoff) + assert_equal( +6*3600, Time.new(2019, 1, 1, 0, 0, 0, "F").gmtoff) + assert_equal( +7*3600, Time.new(2019, 1, 1, 0, 0, 0, "G").gmtoff) + assert_equal( +8*3600, Time.new(2019, 1, 1, 0, 0, 0, "H").gmtoff) + assert_equal( +9*3600, Time.new(2019, 1, 1, 0, 0, 0, "I").gmtoff) + assert_equal(+10*3600, Time.new(2019, 1, 1, 0, 0, 0, "K").gmtoff) + assert_equal(+11*3600, Time.new(2019, 1, 1, 0, 0, 0, "L").gmtoff) + assert_equal(+12*3600, Time.new(2019, 1, 1, 0, 0, 0, "M").gmtoff) + assert_equal( -1*3600, Time.new(2019, 1, 1, 0, 0, 0, "N").gmtoff) + assert_equal( -2*3600, Time.new(2019, 1, 1, 0, 0, 0, "O").gmtoff) + assert_equal( -3*3600, Time.new(2019, 1, 1, 0, 0, 0, "P").gmtoff) + assert_equal( -4*3600, Time.new(2019, 1, 1, 0, 0, 0, "Q").gmtoff) + assert_equal( -5*3600, Time.new(2019, 1, 1, 0, 0, 0, "R").gmtoff) + assert_equal( -6*3600, Time.new(2019, 1, 1, 0, 0, 0, "S").gmtoff) + assert_equal( -7*3600, Time.new(2019, 1, 1, 0, 0, 0, "T").gmtoff) + assert_equal( -8*3600, Time.new(2019, 1, 1, 0, 0, 0, "U").gmtoff) + assert_equal( -9*3600, Time.new(2019, 1, 1, 0, 0, 0, "V").gmtoff) + assert_equal(-10*3600, Time.new(2019, 1, 1, 0, 0, 0, "W").gmtoff) + assert_equal(-11*3600, Time.new(2019, 1, 1, 0, 0, 0, "X").gmtoff) + assert_equal(-12*3600, Time.new(2019, 1, 1, 0, 0, 0, "Y").gmtoff) + assert_equal( 0, Time.new(2019, 1, 1, 0, 0, 0, "Z").gmtoff) + + assert_equal( +1*3600, Time.at(0, in: "A").gmtoff) + assert_equal( +2*3600, Time.at(0, in: "B").gmtoff) + assert_equal( +3*3600, Time.at(0, in: "C").gmtoff) + assert_equal( +4*3600, Time.at(0, in: "D").gmtoff) + assert_equal( +5*3600, Time.at(0, in: "E").gmtoff) + assert_equal( +6*3600, Time.at(0, in: "F").gmtoff) + assert_equal( +7*3600, Time.at(0, in: "G").gmtoff) + assert_equal( +8*3600, Time.at(0, in: "H").gmtoff) + assert_equal( +9*3600, Time.at(0, in: "I").gmtoff) + assert_equal(+10*3600, Time.at(0, in: "K").gmtoff) + assert_equal(+11*3600, Time.at(0, in: "L").gmtoff) + assert_equal(+12*3600, Time.at(0, in: "M").gmtoff) + assert_equal( -1*3600, Time.at(0, in: "N").gmtoff) + assert_equal( -2*3600, Time.at(0, in: "O").gmtoff) + assert_equal( -3*3600, Time.at(0, in: "P").gmtoff) + assert_equal( -4*3600, Time.at(0, in: "Q").gmtoff) + assert_equal( -5*3600, Time.at(0, in: "R").gmtoff) + assert_equal( -6*3600, Time.at(0, in: "S").gmtoff) + assert_equal( -7*3600, Time.at(0, in: "T").gmtoff) + assert_equal( -8*3600, Time.at(0, in: "U").gmtoff) + assert_equal( -9*3600, Time.at(0, in: "V").gmtoff) + assert_equal(-10*3600, Time.at(0, in: "W").gmtoff) + assert_equal(-11*3600, Time.at(0, in: "X").gmtoff) + assert_equal(-12*3600, Time.at(0, in: "Y").gmtoff) + assert_equal( 0, Time.at(0, in: "Z").gmtoff) + end + + MON2NUM = { + "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6, + "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12 + } + + @testnum = 0 + def self.gen_test_name(hint) + @testnum += 1 + s = "test_gen_#{@testnum}" + s.sub(/gen_/) { "gen" + "_#{hint}_".gsub(/[^0-9A-Za-z]+/, '_') } + end + + 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 = [] + 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 + 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" + define_method(gen_test_name(tz)) { + 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 } + assert_equal(expected, time_to_s(t), mesg) + assert_equal(gmtoff, t.gmtoff) + 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 {|(_, _, 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 + if monotonic_to_past && monotonic_to_future + define_method(gen_test_name(tz)) { + with_tz(tz) { + assert_time_constructor(tz, expected, :local, l) + assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil]) + assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil]) + assert_time_constructor(tz, expected, :new, l) + assert_time_constructor(tz, expected, :new, l+[:std]) + assert_time_constructor(tz, expected, :new, l+[:dst]) + } + } + elsif monotonic_to_past && !monotonic_to_future + define_method(gen_test_name(tz)) { + with_tz(tz) { + assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil]) + assert_time_constructor(tz, expected, :new, l+[:dst]) + } + } + elsif !monotonic_to_past && monotonic_to_future + define_method(gen_test_name(tz)) { + with_tz(tz) { + assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil]) + assert_time_constructor(tz, expected, :new, l+[:std]) + } + } + else + define_method(gen_test_name(tz)) { + flunk("time in reverse order: TZ=#{tz} #{expected}") + } + end + } + } + 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 +America/Lima Sat Jan 1 05:00:00 1994 UTC = Sat Jan 1 01:00:00 1994 PEST isdst=1 gmtoff=-14400 +America/Lima Fri Apr 1 03:59:59 1994 UTC = Thu Mar 31 23:59:59 1994 PEST isdst=1 gmtoff=-14400 +America/Lima Fri Apr 1 04:00:00 1994 UTC = Thu Mar 31 23:00:00 1994 PET isdst=0 gmtoff=-18000 +America/Los_Angeles Sun Apr 2 09:59:59 2006 UTC = Sun Apr 2 01:59:59 2006 PST isdst=0 gmtoff=-28800 +America/Los_Angeles Sun Apr 2 10:00:00 2006 UTC = Sun Apr 2 03:00:00 2006 PDT isdst=1 gmtoff=-25200 +America/Los_Angeles Sun Oct 29 08:59:59 2006 UTC = Sun Oct 29 01:59:59 2006 PDT isdst=1 gmtoff=-25200 +America/Los_Angeles Sun Oct 29 09:00:00 2006 UTC = Sun Oct 29 01:00:00 2006 PST isdst=0 gmtoff=-28800 +America/Los_Angeles Sun Mar 11 09:59:59 2007 UTC = Sun Mar 11 01:59:59 2007 PST isdst=0 gmtoff=-28800 +America/Los_Angeles Sun Mar 11 10:00:00 2007 UTC = Sun Mar 11 03:00:00 2007 PDT isdst=1 gmtoff=-25200 +America/Los_Angeles Sun Nov 4 08:59:59 2007 UTC = Sun Nov 4 01:59:59 2007 PDT isdst=1 gmtoff=-25200 +America/Los_Angeles Sun Nov 4 09:00:00 2007 UTC = Sun Nov 4 01:00:00 2007 PST isdst=0 gmtoff=-28800 +America/Managua Thu Sep 24 04:59:59 1992 UTC = Wed Sep 23 23:59:59 1992 EST isdst=0 gmtoff=-18000 +America/Managua Thu Sep 24 05:00:00 1992 UTC = Wed Sep 23 23:00:00 1992 CST isdst=0 gmtoff=-21600 +America/Managua Fri Jan 1 05:59:59 1993 UTC = Thu Dec 31 23:59:59 1992 CST isdst=0 gmtoff=-21600 +America/Managua Fri Jan 1 06:00:00 1993 UTC = Fri Jan 1 01:00:00 1993 EST isdst=0 gmtoff=-18000 +America/Managua Wed Jan 1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000 +America/Managua Wed Jan 1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600 +Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0 gmtoff=27000 +Asia/Singapore Thu Dec 31 15:59:59 1981 UTC = Thu Dec 31 23:29:59 1981 SGT isdst=0 gmtoff=27000 +Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0 gmtoff=28800 +End + gen_zdump_test <<'End' if CORRECT_SINGAPORE_1982 +Asia/Singapore Thu Dec 31 16:00:00 1981 UTC = Fri Jan 1 00:00:00 1982 SGT isdst=0 gmtoff=28800 +End + gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End' +Asia/Tokyo Sat May 5 14:59:59 1951 UTC = Sat May 5 23:59:59 1951 JST isdst=0 gmtoff=32400 +Asia/Tokyo Sat May 5 15:00:00 1951 UTC = Sun May 6 01:00:00 1951 JDT isdst=1 gmtoff=36000 +End +Asia/Tokyo Sat Sep 8 13:59:59 1951 UTC = Sat Sep 8 23:59:59 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Sat Sep 8 14:00:00 1951 UTC = Sat Sep 8 23:00:00 1951 JST isdst=0 gmtoff=32400 +2018e +Asia/Tokyo Sat Sep 8 14:59:59 1951 UTC = Sun Sep 9 00:59:59 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Sat Sep 8 15:00:00 1951 UTC = Sun Sep 9 00:00:00 1951 JST isdst=0 gmtoff=32400 +2018f +Asia/Tokyo Sat May 5 16:59:59 1951 UTC = Sun May 6 01:59:59 1951 JST isdst=0 gmtoff=32400 +Asia/Tokyo Sat May 5 17:00:00 1951 UTC = Sun May 6 03:00:00 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Fri Sep 7 15:59:59 1951 UTC = Sat Sep 8 01:59:59 1951 JDT isdst=1 gmtoff=36000 +Asia/Tokyo Fri Sep 7 16:00:00 1951 UTC = Sat Sep 8 01:00:00 1951 JST isdst=0 gmtoff=32400 +End + gen_zdump_test <<'End' +America/St_Johns Sun Mar 11 03:30:59 2007 UTC = Sun Mar 11 00:00:59 2007 NST isdst=0 gmtoff=-12600 +America/St_Johns Sun Mar 11 03:31:00 2007 UTC = Sun Mar 11 01:01:00 2007 NDT isdst=1 gmtoff=-9000 +America/St_Johns Sun Nov 4 02:30:59 2007 UTC = Sun Nov 4 00:00:59 2007 NDT isdst=1 gmtoff=-9000 +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 +Europe/Brussels Sat Sep 30 23:00:00 1916 UTC = Sun Oct 1 00:00:00 1916 CET isdst=0 gmtoff=3600 +Europe/London Sun Mar 16 01:59:59 1947 UTC = Sun Mar 16 01:59:59 1947 GMT isdst=0 gmtoff=0 +Europe/London Sun Mar 16 02:00:00 1947 UTC = Sun Mar 16 03:00:00 1947 BST isdst=1 gmtoff=3600 +Europe/London Sun Apr 13 00:59:59 1947 UTC = Sun Apr 13 01:59:59 1947 BST isdst=1 gmtoff=3600 +Europe/London Sun Apr 13 01:00:00 1947 UTC = Sun Apr 13 03:00:00 1947 BDST isdst=1 gmtoff=7200 +Europe/London Sun Aug 10 00:59:59 1947 UTC = Sun Aug 10 02:59:59 1947 BDST isdst=1 gmtoff=7200 +Europe/London Sun Aug 10 01:00:00 1947 UTC = Sun Aug 10 02:00:00 1947 BST isdst=1 gmtoff=3600 +Europe/London Sun Nov 2 01:59:59 1947 UTC = Sun Nov 2 02:59:59 1947 BST isdst=1 gmtoff=3600 +Europe/London Sun Nov 2 02:00:00 1947 UTC = Sun Nov 2 02:00:00 1947 GMT isdst=0 gmtoff=0 +End + if CORRECT_KIRITIMATI_SKIP_1994 + gen_zdump_test <<'End' +Pacific/Kiritimati Sat Dec 31 09:59:59 1994 UTC = Fri Dec 30 23:59:59 1994 LINT isdst=0 gmtoff=-36000 +Pacific/Kiritimati Sat Dec 31 10:00:00 1994 UTC = Sun Jan 1 00:00:00 1995 LINT isdst=0 gmtoff=50400 +End + else + gen_zdump_test <<'End' +Pacific/Kiritimati Sun Jan 1 09:59:59 1995 UTC = Sat Dec 31 23:59:59 1994 LINT isdst=0 gmtoff=-36000 +Pacific/Kiritimati Sun Jan 1 10:00:00 1995 UTC = Mon Jan 2 00:00:00 1995 LINT isdst=0 gmtoff=50400 +End + end + gen_zdump_test <<'End' if has_right_tz +right/America/Los_Angeles Fri Jun 30 23:59:60 1972 UTC = Fri Jun 30 16:59:60 1972 PDT isdst=1 gmtoff=-25200 +right/America/Los_Angeles Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800 +#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 + + class TZ + attr_reader :name + + def initialize(name, abbr, offset, abbr2 = nil, offset2 = nil) + @name = name + @abbr = abbr + @offset = offset + @abbr2 = abbr2 + @offset2 = offset2 + end + + def dst?(t) + return false unless @offset2 + case t when Integer + return nil + end + case t.mon + when 4..9 + true + else + false + end + end + + def offset(t) + (dst?(t) ? @offset2 : @offset) + end + + def local_to_utc(t) + t - offset(t) + end + + def utc_to_local(t) + t + offset(t) + end + + def abbr(t) + dst?(t) ? @abbr2 : @abbr + end + + def ==(other) + @name == other.name and abbr(0) == other.abbr(0) and offset(0) == other.offset(0) + end + + def inspect + "#<TZ: #@name #@abbr #@offset>" + end + end +end + +module TestTimeTZ::WithTZ + def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + h, m = (-utc_offset / 60).divmod(60) + assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i) + assert_equal(6, t.wday) + assert_equal(244, t.yday) + assert_equal(t, time_class.new(2018, 9, 1, 12, in: tzarg)) + assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)} + end + + def subtest_hour24(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2000, 1, 1, 24, 0, 0, tzarg) + assert_equal([0, 0, 0, 2, 1, 2000], [t.sec, t.min, t.hour, t.mday, t.mon, t.year]) + end + + def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.now(in: tzarg) + assert_equal(tz, t.zone) + end + + def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + utc = time_class.utc(2018, 9, 1, 12, 0, 0) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc) + t = utc.getlocal(tzarg) + h, m = (utc_offset / 60).divmod(60) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t) + end + + def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + h, m = (utc_offset.abs / 60).divmod(60) + h = -h if utc_offset < 0 + assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z")) + assert_equal("34 35 35", t.strftime("%U %V %W")) + end + + def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000 + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t) + assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + m, s = (4000-utc_offset).divmod(60) + h, m = m.divmod(60) + assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t) + end + + def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset) + abbr, abbr2 = *abbr + utc_offset, utc_offset2 = *utc_offset + utc = time_class.utc(2018, 9, 1, 12, 0, 0) + utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc) + h, m = (utc_offset / 60).divmod(60) + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(utc.to_i, t.to_i) + utc = utc.to_i + t = time_class.at(utc, in: tzarg) + assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone]) + assert_equal(utc, t.to_i) + end + + def subtest_to_a(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + ary = t.to_a + assert_equal(ary, [t.sec, t.min, t.hour, t.mday, t.mon, t.year, t.wday, t.yday, t.isdst, t.zone]) + end + + def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + t2 = Marshal.load(Marshal.dump(t)) + assert_equal(t, t2) + assert_equal(t.utc_offset, t2.utc_offset) + assert_equal(t.utc_offset, (t2+1).utc_offset) + assert_instance_of(t.zone.class, t2.zone) + assert_equal(t.dst?, t2.dst?) + end + + def subtest_fractional_second(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2024, 1, 1, 23, 59, 59.9r, tzarg) + assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset) + t = time_class.new(2024, 7, 1, 23, 59, 59.9r, tzarg) + assert_equal(utc_offset[t.dst? ? 1 : 0], t.utc_offset) + end + + def test_invalid_zone + make_timezone("INVALID", "INV", 0) + rescue => e + assert_kind_of(StandardError, e) + else + assert false, "ArgumentError expected but nothing was raised." + end + + def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset) + data = [ + "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1], + "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b, + Marshal.dump({offset: utc_offset, zone: abbr})[3..-1], + ].join('') + t = Marshal.load(data) + assert_equal(utc_offset, t.utc_offset) + assert_equal(utc_offset, (t+1).utc_offset) + # t.zone may be a mere String or timezone object. + end + + ZONES = { + "Asia/Tokyo" => ["JST", +9*3600], + "America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600], + "Africa/Ndjamena" => ["WAT", +1*3600], + "Etc/UTC" => ["UTC", 0], + } + + def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil) + self.class::TIME_CLASS.find_timezone(tzname) + end + + def subtest_dst?(time_class, tz, tzarg, tzname, abbr, utc_offset) + t = time_class.new(2018, 6, 22, 12, 0, 0, tzarg) + return unless tz.dst?(t) + assert_predicate t, :dst? + t = time_class.new(2018, 12, 22, 12, 0, 0, tzarg) + assert_not_predicate t, :dst? + end + + instance_methods(false).grep(/\Asub(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset, abbr2, utc_offset2)| + define_method("#{test}@#{tzname}") do + tz = make_timezone(tzname, abbr, utc_offset, abbr2, utc_offset2) + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tz, tz, tzname, [abbr, abbr2], [utc_offset, utc_offset2]) + __send__(subtest, time_class, tz, tzname, tzname, [abbr, abbr2], [utc_offset, utc_offset2]) + end + end + end + + instance_methods(false).grep(/\Aname(?=test_)/) do |subtest| + test = $' + ZONES.each_pair do |tzname, (abbr, utc_offset)| + define_method("#{test}@#{tzname}") do + time_class = self.class::TIME_CLASS + __send__(subtest, time_class, tzname, abbr, utc_offset) + end + end + end +end + +class TestTimeTZ::DummyTZ < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + ZONES = TestTimeTZ::WithTZ::ZONES + def self.find_timezone(tzname) + tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}" + TestTimeTZ::TZ.new(tzname, *tz) + end + end + + def self.make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil) + TestTimeTZ::TZ.new(tzname, abbr, utc_offset, abbr2, utc_offset2) + end + + def test_fractional_second + x = Object.new + def x.local_to_utc(t); t + 8*3600; end + def x.utc_to_local(t); t - 8*3600; end + + t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00') + t2 = Time.new(2020,11,11,12,13,14.124r, x) + assert_equal(t1, t2) + end +end + +begin + require "tzinfo" +rescue LoadError +else + class TestTimeTZ::GemTZInfo < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(tzname) + TZInfo::Timezone.get(tzname) + end + end + + def tz + @tz ||= TZInfo::Timezone.get(tzname) + end + end +end + +begin + require "timezone" +rescue LoadError +else + class TestTimeTZ::GemTimezone < Test::Unit::TestCase + include TestTimeTZ::WithTZ + + class TIME_CLASS < ::Time + def self.find_timezone(name) + Timezone.fetch(name) + end + end + + def tz + @tz ||= Timezone[tzname] + end + end +end diff --git a/test/ruby/test_trace.rb b/test/ruby/test_trace.rb new file mode 100644 index 0000000000..5842f11aee --- /dev/null +++ b/test/ruby/test_trace.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestTrace < Test::Unit::TestCase + def test_trace + $x = 1234 + $y = 0 + trace_var :$x, proc{$y = $x} + $x = 40414 + assert_equal($x, $y) + + untrace_var :$x + $x = 19660208 + assert_not_equal($x, $y) + + trace_var :$x, proc{$x *= 2} + $x = 5 + assert_equal(10, $x) + + untrace_var :$x + end + + def test_trace_proc_that_raises_exception + $x = 1234 + trace_var :$x, proc { raise } + assert_raise(RuntimeError) { $x = 42 } + ensure + untrace_var :$x + end + + def test_trace_string + $x = 1234 + trace_var :$x, "$y = :bar" + $x = 42 + assert_equal(:bar, $y) + 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 new file mode 100644 index 0000000000..99b5ee8d43 --- /dev/null +++ b/test/ruby/test_transcode.rb @@ -0,0 +1,2439 @@ +# 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') } + assert_raise(Encoding::ConverterNotFoundError) { 'abc'.encode!('foo', 'bar') } + assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode('foo') } + assert_raise(Encoding::ConverterNotFoundError) { 'abc'.force_encoding('utf-8').encode!('foo') } + assert_undefined_in("\x80", 'ASCII-8BIT') + assert_invalid_in("\x80", 'US-ASCII') + assert_undefined_in("\xA5", 'iso-8859-3') + 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 + assert_equal('abc', 'abc'.force_encoding('utf-8').encode('iso-8859-1')) + # check that encoding is kept when no conversion is done + assert_equal('abc'.force_encoding('Shift_JIS'), 'abc'.force_encoding('Shift_JIS').encode('Shift_JIS')) + assert_equal('abc'.force_encoding('Shift_JIS'), 'abc'.force_encoding('Shift_JIS').encode!('Shift_JIS')) + # assert that encoding is correctly set + assert_equal("D\u00FCrst".encoding, "D\xFCrst".force_encoding('iso-8859-1').encode('utf-8').encoding) + # check that Encoding can be used as parameter + assert_equal("D\u00FCrst", "D\xFCrst".encode('utf-8', Encoding.find('ISO-8859-1'))) + assert_equal("D\u00FCrst", "D\xFCrst".encode(Encoding.find('utf-8'), 'ISO-8859-1')) + assert_equal("D\u00FCrst", "D\xFCrst".encode(Encoding.find('utf-8'), Encoding.find('ISO-8859-1'))) + end + + def test_noargument + 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 + assert_equal("\u20AC"*20, ("\xA4"*20).encode('utf-8', 'iso-8859-15')) + assert_equal("\u20AC"*20, ("\xA4"*20).encode!('utf-8', 'iso-8859-15')) + assert_equal("\u20AC"*2000, ("\xA4"*2000).encode('utf-8', 'iso-8859-15')) + assert_equal("\u20AC"*2000, ("\xA4"*2000).encode!('utf-8', 'iso-8859-15')) + assert_equal("\u20AC"*200000, ("\xA4"*200000).encode('utf-8', 'iso-8859-15')) + assert_equal("\u20AC"*200000, ("\xA4"*200000).encode!('utf-8', 'iso-8859-15')) + 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') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-4') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-9') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-10') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-13') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-14') + check_both_ways("D\u00FCrst", "D\xFCrst", 'iso-8859-15') + check_both_ways("r\u00E9sum\u00E9", "r\xE9sum\xE9", 'iso-8859-1') # résumé + check_both_ways("\u0065\u006C\u0151\u00ED\u0072\u00E1\u0073", "el\xF5\xEDr\xE1s", 'iso-8859-2') # elÅ‘Ãrás + check_both_ways("\u043F\u0435\u0440\u0435\u0432\u043E\u0434", + "\xDF\xD5\xE0\xD5\xD2\xDE\xD4", 'iso-8859-5') # перевод + 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", + "\xBE\xC6\xC1\x64\xC1\x64\x20\x8C\x63\xB9\xE6\xC4\x4F\xB4\xD4\x20\xBB\xE7\xB6\xFB\xC5\x42", 'cp949') # ì•„í–í– ë˜ ë°©íš½ë‹˜ 사랑휖 + assert_equal(Encoding::ISO_8859_1, "D\xFCrst".force_encoding('iso-8859-2').encode('iso-8859-1', 'iso-8859-1').encoding) + end + + def test_twostep + assert_equal("D\xFCrst".force_encoding('iso-8859-2'), "D\xFCrst".encode('iso-8859-2', 'iso-8859-1')) + end + + def test_encode_xml_multibyte + encodings = %w'UTF-8 UTF-16LE UTF-16BE UTF-32LE UTF-32BE' + encodings.each do |src_enc| + encodings.each do |dst_enc| + escaped = "<>".encode(src_enc).encode(dst_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).encode(dst_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + + escaped = "<>".encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:text) + assert_equal("<>", escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :text") + + escaped = '<">'.encode(src_enc).force_encoding("UTF-8").encode(dst_enc, src_enc, :xml=>:attr) + assert_equal('"<">"', escaped.encode('UTF-8'), "failed encoding #{src_enc} to #{dst_enc} with xml: :attr") + end + end + # regression test; U+6E7F (湿) uses the same bytes in ISO-2022-JP as "<>" + assert_equal( "<>\u6E7F", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:text).encode("UTF-8")) + assert_equal("\"<>\u6E7F\"", "<>\u6E7F".encode("ISO-2022-JP").encode("ISO-2022-JP", :xml=>:attr).encode("UTF-8")) + end + + def test_ascii_range + encodings = [ + 'US-ASCII', 'ASCII-8BIT', + 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', + 'ISO-8859-4', 'ISO-8859-5', 'ISO-8859-6', + 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', + 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-13', + 'ISO-8859-14', 'ISO-8859-15', + 'EUC-JP', 'SHIFT_JIS', 'EUC-KR' + ] + all_ascii = (0..127).to_a.pack 'C*' + encodings.each do |enc| + test_start = all_ascii + assert_equal(test_start, test_start.encode('UTF-8',enc).encode(enc).force_encoding('ASCII-8BIT')) + end + end + + def test_all_bytes + encodings_8859 = [ + 'ISO-8859-1', 'ISO-8859-2', + #'ISO-8859-3', # not all bytes used + 'ISO-8859-4', 'ISO-8859-5', + #'ISO-8859-6', # not all bytes used + #'ISO-8859-7', # not all bytes used + #'ISO-8859-8', # not all bytes used + 'ISO-8859-9', 'ISO-8859-10', + #'ISO-8859-11', # not all bytes used + #'ISO-8859-12', # not available + 'ISO-8859-13','ISO-8859-14','ISO-8859-15', + #'ISO-8859-16', # not available + ] + all_bytes = (0..255).to_a.pack 'C*' + encodings_8859.each do |enc| + test_start = all_bytes + assert_equal(test_start, test_start.encode('UTF-8',enc).encode(enc).force_encoding('ASCII-8BIT')) + end + end + + def test_windows_874 + check_both_ways("\u20AC", "\x80", 'windows-874') # € + assert_undefined_in("\x81", 'windows-874') + assert_undefined_in("\x84", 'windows-874') + check_both_ways("\u2026", "\x85", 'windows-874') # … + assert_undefined_in("\x86", 'windows-874') + assert_undefined_in("\x8F", 'windows-874') + assert_undefined_in("\x90", 'windows-874') + check_both_ways("\u2018", "\x91", 'windows-874') # ‘ + check_both_ways("\u2014", "\x97", 'windows-874') # — + assert_undefined_in("\x98", 'windows-874') + assert_undefined_in("\x9F", 'windows-874') + check_both_ways("\u00A0", "\xA0", 'windows-874') # non-breaking space + check_both_ways("\u0E0F", "\xAF", 'windows-874') # ภ+ check_both_ways("\u0E10", "\xB0", 'windows-874') # ภ+ check_both_ways("\u0E1F", "\xBF", 'windows-874') # ฟ + check_both_ways("\u0E20", "\xC0", 'windows-874') # ภ+ check_both_ways("\u0E2F", "\xCF", 'windows-874') # ฯ + check_both_ways("\u0E30", "\xD0", 'windows-874') # ะ + check_both_ways("\u0E3A", "\xDA", 'windows-874') # ฺ + assert_undefined_in("\xDB", 'windows-874') + assert_undefined_in("\xDE", 'windows-874') + check_both_ways("\u0E3F", "\xDF", 'windows-874') # ฿ + check_both_ways("\u0E40", "\xE0", 'windows-874') # เ + check_both_ways("\u0E4F", "\xEF", 'windows-874') # ๠+ check_both_ways("\u0E50", "\xF0", 'windows-874') # ๠+ check_both_ways("\u0E5B", "\xFB", 'windows-874') # ๛ + assert_undefined_in("\xFC", 'windows-874') + assert_undefined_in("\xFF", 'windows-874') + end + + def test_windows_1250 + check_both_ways("\u20AC", "\x80", 'windows-1250') # € + assert_undefined_in("\x81", 'windows-1250') + check_both_ways("\u201A", "\x82", 'windows-1250') # ‚ + assert_undefined_in("\x83", 'windows-1250') + check_both_ways("\u201E", "\x84", 'windows-1250') # „ + check_both_ways("\u2021", "\x87", 'windows-1250') # ‡ + assert_undefined_in("\x88", 'windows-1250') + check_both_ways("\u2030", "\x89", 'windows-1250') # ‰ + check_both_ways("\u0179", "\x8F", 'windows-1250') # Ź + assert_undefined_in("\x90", 'windows-1250') + check_both_ways("\u2018", "\x91", 'windows-1250') # ‘ + check_both_ways("\u2014", "\x97", 'windows-1250') # — + assert_undefined_in("\x98", 'windows-1250') + check_both_ways("\u2122", "\x99", 'windows-1250') # â„¢ + check_both_ways("\u00A0", "\xA0", 'windows-1250') # non-breaking space + check_both_ways("\u017B", "\xAF", 'windows-1250') # Å» + check_both_ways("\u00B0", "\xB0", 'windows-1250') # ° + check_both_ways("\u017C", "\xBF", 'windows-1250') # ż + check_both_ways("\u0154", "\xC0", 'windows-1250') # Å” + check_both_ways("\u010E", "\xCF", 'windows-1250') # ÄŽ + check_both_ways("\u0110", "\xD0", 'windows-1250') # Ä + check_both_ways("\u00DF", "\xDF", 'windows-1250') # ß + check_both_ways("\u0155", "\xE0", 'windows-1250') # Å• + check_both_ways("\u010F", "\xEF", 'windows-1250') # Ä + check_both_ways("\u0111", "\xF0", 'windows-1250') # Ä‘ + check_both_ways("\u02D9", "\xFF", 'windows-1250') # Ë™ + end + + def test_windows_1251 + check_both_ways("\u0402", "\x80", 'windows-1251') # Ђ + check_both_ways("\u20AC", "\x88", 'windows-1251') # € + check_both_ways("\u040F", "\x8F", 'windows-1251') # Ð + check_both_ways("\u0452", "\x90", 'windows-1251') # Ñ’ + assert_undefined_in("\x98", 'windows-1251') + check_both_ways("\u045F", "\x9F", 'windows-1251') # ÑŸ + check_both_ways("\u00A0", "\xA0", 'windows-1251') # non-breaking space + check_both_ways("\u0407", "\xAF", 'windows-1251') # Ї + check_both_ways("\u00B0", "\xB0", 'windows-1251') # ° + check_both_ways("\u0457", "\xBF", 'windows-1251') # Ñ— + check_both_ways("\u0410", "\xC0", 'windows-1251') # Ð + check_both_ways("\u041F", "\xCF", 'windows-1251') # П + check_both_ways("\u0420", "\xD0", 'windows-1251') # Ð + check_both_ways("\u042F", "\xDF", 'windows-1251') # Я + check_both_ways("\u0430", "\xE0", 'windows-1251') # а + check_both_ways("\u043F", "\xEF", 'windows-1251') # п + check_both_ways("\u0440", "\xF0", 'windows-1251') # Ñ€ + check_both_ways("\u044F", "\xFF", 'windows-1251') # Ñ + end + + def test_windows_1252 + check_both_ways("\u20AC", "\x80", 'windows-1252') # € + assert_undefined_in("\x81", 'windows-1252') + check_both_ways("\u201A", "\x82", 'windows-1252') # ‚ + check_both_ways("\u0152", "\x8C", 'windows-1252') # >Å’ + assert_undefined_in("\x8D", 'windows-1252') + check_both_ways("\u017D", "\x8E", 'windows-1252') # Ž + assert_undefined_in("\x8F", 'windows-1252') + assert_undefined_in("\x90", 'windows-1252') + check_both_ways("\u2018", "\x91", 'windows-1252') #‘ + check_both_ways("\u0153", "\x9C", 'windows-1252') # Å“ + assert_undefined_in("\x9D", 'windows-1252') + check_both_ways("\u017E", "\x9E", 'windows-1252') # ž + check_both_ways("\u00A0", "\xA0", 'windows-1252') # non-breaking space + check_both_ways("\u00AF", "\xAF", 'windows-1252') # ¯ + check_both_ways("\u00B0", "\xB0", 'windows-1252') # ° + check_both_ways("\u00BF", "\xBF", 'windows-1252') # ¿ + check_both_ways("\u00C0", "\xC0", 'windows-1252') # À + check_both_ways("\u00CF", "\xCF", 'windows-1252') # à + check_both_ways("\u00D0", "\xD0", 'windows-1252') # à + check_both_ways("\u00DF", "\xDF", 'windows-1252') # ß + check_both_ways("\u00E0", "\xE0", 'windows-1252') # à + check_both_ways("\u00EF", "\xEF", 'windows-1252') # ï + check_both_ways("\u00F0", "\xF0", 'windows-1252') # ð + check_both_ways("\u00FF", "\xFF", 'windows-1252') # ÿ + end + + def test_windows_1253 + check_both_ways("\u20AC", "\x80", 'windows-1253') # € + assert_undefined_in("\x81", 'windows-1253') + check_both_ways("\u201A", "\x82", 'windows-1253') # ‚ + check_both_ways("\u2021", "\x87", 'windows-1253') # ‡ + assert_undefined_in("\x88", 'windows-1253') + check_both_ways("\u2030", "\x89", 'windows-1253') # ‰ + assert_undefined_in("\x8A", 'windows-1253') + check_both_ways("\u2039", "\x8B", 'windows-1253') # ‹ + assert_undefined_in("\x8C", 'windows-1253') + assert_undefined_in("\x8F", 'windows-1253') + assert_undefined_in("\x90", 'windows-1253') + check_both_ways("\u2018", "\x91", 'windows-1253') # ‘ + check_both_ways("\u2014", "\x97", 'windows-1253') # — + assert_undefined_in("\x98", 'windows-1253') + check_both_ways("\u2122", "\x99", 'windows-1253') # â„¢ + assert_undefined_in("\x9A", 'windows-1253') + check_both_ways("\u203A", "\x9B", 'windows-1253') # › + assert_undefined_in("\x9C", 'windows-1253') + assert_undefined_in("\x9F", 'windows-1253') + check_both_ways("\u00A0", "\xA0", 'windows-1253') # non-breaking space + check_both_ways("\u2015", "\xAF", 'windows-1253') # ― + check_both_ways("\u00B0", "\xB0", 'windows-1253') # ° + check_both_ways("\u038F", "\xBF", 'windows-1253') # Î + check_both_ways("\u0390", "\xC0", 'windows-1253') # Î + check_both_ways("\u039F", "\xCF", 'windows-1253') # Ο + check_both_ways("\u03A0", "\xD0", 'windows-1253') # Î + check_both_ways("\u03A1", "\xD1", 'windows-1253') # Ρ + assert_undefined_in("\xD2", 'windows-1253') + check_both_ways("\u03A3", "\xD3", 'windows-1253') # Σ + check_both_ways("\u03AF", "\xDF", 'windows-1253') # ί + check_both_ways("\u03B0", "\xE0", 'windows-1253') # ΰ + check_both_ways("\u03BF", "\xEF", 'windows-1253') # ο + check_both_ways("\u03C0", "\xF0", 'windows-1253') # Ï€ + check_both_ways("\u03CE", "\xFE", 'windows-1253') # ÏŽ + assert_undefined_in("\xFF", 'windows-1253') + end + + def test_windows_1254 + check_both_ways("\u20AC", "\x80", 'windows-1254') # € + assert_undefined_in("\x81", 'windows-1254') + check_both_ways("\u201A", "\x82", 'windows-1254') # ‚ + check_both_ways("\u0152", "\x8C", 'windows-1254') # Å’ + assert_undefined_in("\x8D", 'windows-1254') + assert_undefined_in("\x8F", 'windows-1254') + assert_undefined_in("\x90", 'windows-1254') + check_both_ways("\u2018", "\x91", 'windows-1254') # ‘ + check_both_ways("\u0153", "\x9C", 'windows-1254') # Å“ + assert_undefined_in("\x9D", 'windows-1254') + assert_undefined_in("\x9E", 'windows-1254') + check_both_ways("\u0178", "\x9F", 'windows-1254') # Ÿ + check_both_ways("\u00A0", "\xA0", 'windows-1254') # non-breaking space + check_both_ways("\u00AF", "\xAF", 'windows-1254') # ¯ + check_both_ways("\u00B0", "\xB0", 'windows-1254') # ° + check_both_ways("\u00BF", "\xBF", 'windows-1254') # ¿ + check_both_ways("\u00C0", "\xC0", 'windows-1254') # À + check_both_ways("\u00CF", "\xCF", 'windows-1254') # à + check_both_ways("\u011E", "\xD0", 'windows-1254') # Äž + check_both_ways("\u00DF", "\xDF", 'windows-1254') # ß + check_both_ways("\u00E0", "\xE0", 'windows-1254') # à + check_both_ways("\u00EF", "\xEF", 'windows-1254') # ï + check_both_ways("\u011F", "\xF0", 'windows-1254') # ÄŸ + check_both_ways("\u00FF", "\xFF", 'windows-1254') # ÿ + end + + def test_windows_1255 + check_both_ways("\u20AC", "\x80", 'windows-1255') # € + assert_undefined_in("\x81", 'windows-1255') + check_both_ways("\u201A", "\x82", 'windows-1255') # ‚ + check_both_ways("\u2030", "\x89", 'windows-1255') # ‰ + assert_undefined_in("\x8A", 'windows-1255') + check_both_ways("\u2039", "\x8B", 'windows-1255') # ‹ + assert_undefined_in("\x8C", 'windows-1255') + assert_undefined_in("\x8F", 'windows-1255') + assert_undefined_in("\x90", 'windows-1255') + check_both_ways("\u2018", "\x91", 'windows-1255') # ‘ + check_both_ways("\u2122", "\x99", 'windows-1255') # â„¢ + assert_undefined_in("\x9A", 'windows-1255') + check_both_ways("\u203A", "\x9B", 'windows-1255') # › + assert_undefined_in("\x9C", 'windows-1255') + assert_undefined_in("\x9F", 'windows-1255') + check_both_ways("\u00A0", "\xA0", 'windows-1255') # non-breaking space + check_both_ways("\u00A1", "\xA1", 'windows-1255') # ¡ + check_both_ways("\u00D7", "\xAA", 'windows-1255') # × + check_both_ways("\u00AF", "\xAF", 'windows-1255') # ¯ + check_both_ways("\u00B0", "\xB0", 'windows-1255') # ° + check_both_ways("\u00B8", "\xB8", 'windows-1255') # ¸ + check_both_ways("\u00F7", "\xBA", 'windows-1255') # ÷ + check_both_ways("\u00BF", "\xBF", 'windows-1255') # ¿ + check_both_ways("\u05B0", "\xC0", 'windows-1255') # Ö° + check_both_ways("\u05B9", "\xC9", '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') # ×€ + check_both_ways("\u05F3", "\xD7", 'windows-1255') # ׳ + check_both_ways("\u05F4", "\xD8", 'windows-1255') # ×´ + assert_undefined_in("\xD9", 'windows-1255') + assert_undefined_in("\xDF", 'windows-1255') + check_both_ways("\u05D0", "\xE0", 'windows-1255') # × + check_both_ways("\u05DF", "\xEF", 'windows-1255') # ן + check_both_ways("\u05E0", "\xF0", 'windows-1255') # × + check_both_ways("\u05EA", "\xFA", 'windows-1255') # ת + assert_undefined_in("\xFB", 'windows-1255') + assert_undefined_in("\xFC", 'windows-1255') + check_both_ways("\u200E", "\xFD", 'windows-1255') # left-to-right mark + check_both_ways("\u200F", "\xFE", 'windows-1255') # right-to-left mark + assert_undefined_in("\xFF", 'windows-1255') + end + + def test_windows_1256 + check_both_ways("\u20AC", "\x80", 'windows-1256') # € + check_both_ways("\u0679", "\x8A", 'windows-1256') # Ù¹ + check_both_ways("\u0688", "\x8F", 'windows-1256') # Úˆ + check_both_ways("\u06AF", "\x90", 'windows-1256') # Ú¯ + check_both_ways("\u06A9", "\x98", 'windows-1256') # Ú© + check_both_ways("\u0691", "\x9A", 'windows-1256') # Ú‘ + check_both_ways("\u06BA", "\x9F", 'windows-1256') # Úº + check_both_ways("\u00A0", "\xA0", 'windows-1256') # non-breaking space + check_both_ways("\u06BE", "\xAA", 'windows-1256') # Ú¾ + check_both_ways("\u00AF", "\xAF", 'windows-1256') # ¯ + check_both_ways("\u00B0", "\xB0", 'windows-1256') # ° + check_both_ways("\u061F", "\xBF", 'windows-1256') # ØŸ + check_both_ways("\u06C1", "\xC0", 'windows-1256') # Û + check_both_ways("\u062F", "\xCF", 'windows-1256') # د + check_both_ways("\u0630", "\xD0", 'windows-1256') # ذ + check_both_ways("\u0643", "\xDF", 'windows-1256') # Ùƒ + check_both_ways("\u00E0", "\xE0", 'windows-1256') # à + check_both_ways("\u00EF", "\xEF", 'windows-1256') # ï + check_both_ways("\u064B", "\xF0", 'windows-1256') # ًً + check_both_ways("\u06D2", "\xFF", 'windows-1256') # Û’ + end + + def test_windows_1257 + check_both_ways("\u20AC", "\x80", 'windows-1257') # € + assert_undefined_in("\x81", 'windows-1257') + check_both_ways("\u201A", "\x82", 'windows-1257') # ‚ + assert_undefined_in("\x83", 'windows-1257') + check_both_ways("\u201E", "\x84", 'windows-1257') # „ + check_both_ways("\u2021", "\x87", 'windows-1257') # ‡ + assert_undefined_in("\x88", 'windows-1257') + check_both_ways("\u2030", "\x89", 'windows-1257') # ‰ + assert_undefined_in("\x8A", 'windows-1257') + check_both_ways("\u2039", "\x8B", 'windows-1257') # ‹ + assert_undefined_in("\x8C", 'windows-1257') + check_both_ways("\u00A8", "\x8D", 'windows-1257') # ¨ + check_both_ways("\u02C7", "\x8E", 'windows-1257') # ˇ + check_both_ways("\u00B8", "\x8F", 'windows-1257') # ¸ + assert_undefined_in("\x90", 'windows-1257') + check_both_ways("\u2018", "\x91", 'windows-1257') # ‘ + check_both_ways("\u2014", "\x97", 'windows-1257') # — + assert_undefined_in("\x98", 'windows-1257') + check_both_ways("\u2122", "\x99", 'windows-1257') # â„¢ + assert_undefined_in("\x9A", 'windows-1257') + check_both_ways("\u203A", "\x9B", 'windows-1257') # › + assert_undefined_in("\x9C", 'windows-1257') + check_both_ways("\u00AF", "\x9D", 'windows-1257') # ¯ + check_both_ways("\u02DB", "\x9E", 'windows-1257') # Ë› + assert_undefined_in("\x9F", 'windows-1257') + check_both_ways("\u00A0", "\xA0", 'windows-1257') # non-breaking space + assert_undefined_in("\xA1", 'windows-1257') + check_both_ways("\u00A2", "\xA2", 'windows-1257') # ¢ + check_both_ways("\u00A4", "\xA4", 'windows-1257') # ¤ + assert_undefined_in("\xA5", 'windows-1257') + check_both_ways("\u00A6", "\xA6", 'windows-1257') # ¦ + check_both_ways("\u00C6", "\xAF", 'windows-1257') # Æ + check_both_ways("\u00B0", "\xB0", 'windows-1257') # ° + check_both_ways("\u00E6", "\xBF", 'windows-1257') # æ + check_both_ways("\u0104", "\xC0", 'windows-1257') # Ä„ + check_both_ways("\u013B", "\xCF", 'windows-1257') # Ä» + check_both_ways("\u0160", "\xD0", 'windows-1257') # Å + check_both_ways("\u00DF", "\xDF", 'windows-1257') # ß + check_both_ways("\u0105", "\xE0", 'windows-1257') # Ä… + check_both_ways("\u013C", "\xEF", 'windows-1257') # ļ + check_both_ways("\u0161", "\xF0", 'windows-1257') # Å¡ + check_both_ways("\u02D9", "\xFF", 'windows-1257') # Ë™ + end + + def test_IBM437 + check_both_ways("\u00C7", "\x80", 'IBM437') # Ç + check_both_ways("\u00C5", "\x8F", 'IBM437') # Ã… + check_both_ways("\u00C9", "\x90", 'IBM437') # É + check_both_ways("\u0192", "\x9F", 'IBM437') # Æ’ + check_both_ways("\u00E1", "\xA0", 'IBM437') # á + check_both_ways("\u00BB", "\xAF", 'IBM437') # » + check_both_ways("\u2591", "\xB0", 'IBM437') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM437') # â” + check_both_ways("\u2514", "\xC0", 'IBM437') # â”” + check_both_ways("\u2567", "\xCF", 'IBM437') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM437') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM437') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM437') # α + check_both_ways("\u2229", "\xEF", 'IBM437') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM437') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM437') # non-breaking space + end + + def test_IBM720 + assert_undefined_in("\x80", 'IBM720') + assert_undefined_in("\x8F", 'IBM720') + assert_undefined_in("\x90", 'IBM720') + check_both_ways("\u0627", "\x9F", 'IBM720') # ا + check_both_ways("\u0628", "\xA0", 'IBM720') # ب + check_both_ways("\u00BB", "\xAF", 'IBM720') # » + check_both_ways("\u2591", "\xB0", 'IBM720') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM720') # â” + check_both_ways("\u2514", "\xC0", 'IBM720') # â”” + check_both_ways("\u2567", "\xCF", 'IBM720') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM720') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM720') # â–€ + check_both_ways("\u0636", "\xE0", 'IBM720') # ض + check_both_ways("\u064A", "\xEF", 'IBM720') # ÙŠ + check_both_ways("\u2261", "\xF0", 'IBM720') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM720') # non-breaking space + end + + def test_IBM775 + check_both_ways("\u0106", "\x80", 'IBM775') # Ć + check_both_ways("\u00C5", "\x8F", 'IBM775') # Ã… + check_both_ways("\u00C9", "\x90", 'IBM775') # É + check_both_ways("\u00A4", "\x9F", 'IBM775') # ¤ + check_both_ways("\u0100", "\xA0", 'IBM775') # Ä€ + check_both_ways("\u00BB", "\xAF", 'IBM775') # » + check_both_ways("\u2591", "\xB0", 'IBM775') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM775') # â” + check_both_ways("\u2514", "\xC0", 'IBM775') # â”” + check_both_ways("\u017D", "\xCF", 'IBM775') # Ž + check_both_ways("\u0105", "\xD0", 'IBM775') # Ä… + check_both_ways("\u2580", "\xDF", 'IBM775') # â–€ + check_both_ways("\u00D3", "\xE0", 'IBM775') # Ó + check_both_ways("\u2019", "\xEF", 'IBM775') # ’ + check_both_ways("\u00AD", "\xF0", 'IBM775') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'IBM775') # non-breaking space + end + + def test_IBM852 + check_both_ways("\u00C7", "\x80", 'IBM852') # Ç + check_both_ways("\u0106", "\x8F", 'IBM852') # Ć + check_both_ways("\u00C9", "\x90", 'IBM852') # É + check_both_ways("\u010D", "\x9F", 'IBM852') # Ä + check_both_ways("\u00E1", "\xA0", 'IBM852') # á + check_both_ways("\u00BB", "\xAF", 'IBM852') # » + check_both_ways("\u2591", "\xB0", 'IBM852') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM852') # â” + check_both_ways("\u2514", "\xC0", 'IBM852') # â”” + check_both_ways("\u00A4", "\xCF", 'IBM852') # ¤ + check_both_ways("\u0111", "\xD0", 'IBM852') # Ä‘ + check_both_ways("\u2580", "\xDF", 'IBM852') # â–€ + check_both_ways("\u00D3", "\xE0", 'IBM852') # Ó + check_both_ways("\u00B4", "\xEF", 'IBM852') # ´ + check_both_ways("\u00AD", "\xF0", 'IBM852') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'IBM852') # non-breaking space + end + + def test_IBM855 + check_both_ways("\u0452", "\x80", 'IBM855') # Ñ’ + check_both_ways("\u0408", "\x8F", 'IBM855') # Ј + check_both_ways("\u0459", "\x90", 'IBM855') # Ñ™ + check_both_ways("\u042A", "\x9F", 'IBM855') # Ъ + check_both_ways("\u0430", "\xA0", 'IBM855') # а + check_both_ways("\u00BB", "\xAF", 'IBM855') # » + check_both_ways("\u2591", "\xB0", 'IBM855') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM855') # â” + check_both_ways("\u2514", "\xC0", 'IBM855') # â”” + check_both_ways("\u00A4", "\xCF", 'IBM855') # ¤ + check_both_ways("\u043B", "\xD0", 'IBM855') # л + check_both_ways("\u2580", "\xDF", 'IBM855') # â–€ + check_both_ways("\u042F", "\xE0", 'IBM855') # Я + check_both_ways("\u2116", "\xEF", 'IBM855') # â„– + check_both_ways("\u00AD", "\xF0", 'IBM855') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'IBM855') # non-breaking space + end + + def test_IBM857 + check_both_ways("\u00C7", "\x80", 'IBM857') # Ç + check_both_ways("\u00C5", "\x8F", 'IBM857') # Ã… + check_both_ways("\u00C9", "\x90", 'IBM857') # É + check_both_ways("\u015F", "\x9F", 'IBM857') # ÅŸ + check_both_ways("\u00E1", "\xA0", 'IBM857') # á + check_both_ways("\u00BB", "\xAF", 'IBM857') # » + check_both_ways("\u2591", "\xB0", 'IBM857') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM857') # â” + check_both_ways("\u2514", "\xC0", 'IBM857') # â”” + check_both_ways("\u00A4", "\xCF", 'IBM857') # ¤ + check_both_ways("\u00BA", "\xD0", 'IBM857') # º + check_both_ways("\u00C8", "\xD4", 'IBM857') # È + assert_undefined_in("\xD5", 'IBM857') + check_both_ways("\u00CD", "\xD6", 'IBM857') # à + check_both_ways("\u2580", "\xDF", 'IBM857') # â–€ + check_both_ways("\u00D3", "\xE0", 'IBM857') # Ó + check_both_ways("\u00B5", "\xE6", 'IBM857') # µ + assert_undefined_in("\xE7", 'IBM857') + check_both_ways("\u00D7", "\xE8", 'IBM857') # × + check_both_ways("\u00B4", "\xEF", 'IBM857') # ´ + check_both_ways("\u00AD", "\xF0", 'IBM857') # soft hyphen + check_both_ways("\u00B1", "\xF1", 'IBM857') # ± + assert_undefined_in("\xF2", 'IBM857') + check_both_ways("\u00BE", "\xF3", 'IBM857') # ¾ + check_both_ways("\u00A0", "\xFF", 'IBM857') # non-breaking space + end + + def test_IBM860 + check_both_ways("\u00C7", "\x80", 'IBM860') # Ç + check_both_ways("\u00C2", "\x8F", 'IBM860') #  + check_both_ways("\u00C9", "\x90", 'IBM860') # É + check_both_ways("\u00D3", "\x9F", 'IBM860') # Ó + check_both_ways("\u00E1", "\xA0", 'IBM860') # á + check_both_ways("\u00BB", "\xAF", 'IBM860') # » + check_both_ways("\u2591", "\xB0", 'IBM860') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM860') # â” + check_both_ways("\u2514", "\xC0", 'IBM860') # â”” + check_both_ways("\u2567", "\xCF", 'IBM860') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM860') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM860') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM860') # α + check_both_ways("\u2229", "\xEF", 'IBM860') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM860') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM860') # non-breaking space + end + + def test_IBM861 + check_both_ways("\u00C7", "\x80", 'IBM861') # Ç + check_both_ways("\u00C5", "\x8F", 'IBM861') # Ã… + check_both_ways("\u00C9", "\x90", 'IBM861') # É + check_both_ways("\u0192", "\x9F", 'IBM861') # Æ’ + check_both_ways("\u00E1", "\xA0", 'IBM861') # á + check_both_ways("\u00BB", "\xAF", 'IBM861') # » + check_both_ways("\u2591", "\xB0", 'IBM861') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM861') # â” + check_both_ways("\u2514", "\xC0", 'IBM861') # â”” + check_both_ways("\u2567", "\xCF", 'IBM861') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM861') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM861') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM861') # α + check_both_ways("\u2229", "\xEF", 'IBM861') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM861') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM861') # non-breaking space + end + + def test_IBM862 + check_both_ways("\u05D0", "\x80", 'IBM862') # × + check_both_ways("\u05DF", "\x8F", 'IBM862') # ן + check_both_ways("\u05E0", "\x90", 'IBM862') # × + check_both_ways("\u0192", "\x9F", 'IBM862') # Æ’ + check_both_ways("\u00E1", "\xA0", 'IBM862') # á + check_both_ways("\u00BB", "\xAF", 'IBM862') # » + check_both_ways("\u2591", "\xB0", 'IBM862') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM862') # â” + check_both_ways("\u2514", "\xC0", 'IBM862') # â”” + check_both_ways("\u2567", "\xCF", 'IBM862') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM862') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM862') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM862') # α + check_both_ways("\u2229", "\xEF", 'IBM862') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM862') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM862') # non-breaking space + end + + def test_IBM863 + check_both_ways("\u00C7", "\x80", 'IBM863') # Ç + check_both_ways("\u00A7", "\x8F", 'IBM863') # § + check_both_ways("\u00C9", "\x90", 'IBM863') # É + check_both_ways("\u0192", "\x9F", 'IBM863') # Æ’ + check_both_ways("\u00A6", "\xA0", 'IBM863') # ¦ + check_both_ways("\u00BB", "\xAF", 'IBM863') # » + check_both_ways("\u2591", "\xB0", 'IBM863') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM863') # â” + check_both_ways("\u2514", "\xC0", 'IBM863') # â”” + check_both_ways("\u2567", "\xCF", 'IBM863') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM863') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM863') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM863') # α + check_both_ways("\u2229", "\xEF", 'IBM863') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM863') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM863') # non-breaking space + end + + def test_IBM864 + check_both_ways("\u00B0", "\x80", 'IBM864') # ° + check_both_ways("\u2518", "\x8F", 'IBM864') # ┘ + check_both_ways("\u03B2", "\x90", 'IBM864') # β + check_both_ways("\uFE73", "\x9F", 'IBM864') # ï¹³ + check_both_ways("\u00A0", "\xA0", 'IBM864') # non-breaking space + check_both_ways("\uFEA5", "\xAF", 'IBM864') # ﺥ + check_both_ways("\u0660", "\xB0", 'IBM864') # Ù + check_both_ways("\u061F", "\xBF", 'IBM864') # ØŸ + check_both_ways("\u00A2", "\xC0", 'IBM864') # ¢ + check_both_ways("\uFEA9", "\xCF", 'IBM864') # ﺩ + check_both_ways("\uFEAB", "\xD0", 'IBM864') # ﺫ + check_both_ways("\uFEC9", "\xDF", 'IBM864') # ﻉ + check_both_ways("\u0640", "\xE0", 'IBM864') # Ù€ + check_both_ways("\uFEE1", "\xEF", 'IBM864') # ﻡ + check_both_ways("\uFE7D", "\xF0", 'IBM864') # ï¹½ + check_both_ways("\u25A0", "\xFE", 'IBM864') # â– + end + + def test_IBM865 + check_both_ways("\u00C7", "\x80", 'IBM865') # Ç + check_both_ways("\u00C5", "\x8F", 'IBM865') # Ã… + check_both_ways("\u00C9", "\x90", 'IBM865') # É + check_both_ways("\u0192", "\x9F", 'IBM865') # Æ’ + check_both_ways("\u00E1", "\xA0", 'IBM865') # á + check_both_ways("\u00A4", "\xAF", 'IBM865') # ¤ + check_both_ways("\u2591", "\xB0", 'IBM865') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM865') # â” + check_both_ways("\u2514", "\xC0", 'IBM865') # â”” + check_both_ways("\u2567", "\xCF", 'IBM865') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM865') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM865') # â–€ + check_both_ways("\u03B1", "\xE0", 'IBM865') # α + check_both_ways("\u2229", "\xEF", 'IBM865') # ∩ + check_both_ways("\u2261", "\xF0", 'IBM865') # ≡ + check_both_ways("\u00A0", "\xFF", 'IBM865') # non-breaking space + end + + def test_IBM866 + check_both_ways("\u0410", "\x80", 'IBM866') # Ð + check_both_ways("\u041F", "\x8F", 'IBM866') # П + check_both_ways("\u0420", "\x90", 'IBM866') # Ð + check_both_ways("\u042F", "\x9F", 'IBM866') # Я + check_both_ways("\u0430", "\xA0", 'IBM866') # а + check_both_ways("\u043F", "\xAF", 'IBM866') # п + check_both_ways("\u2591", "\xB0", 'IBM866') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM866') # â” + check_both_ways("\u2514", "\xC0", 'IBM866') # â”” + check_both_ways("\u2567", "\xCF", 'IBM866') # â•§ + check_both_ways("\u2568", "\xD0", 'IBM866') # ╨ + check_both_ways("\u2580", "\xDF", 'IBM866') # â–€ + check_both_ways("\u0440", "\xE0", 'IBM866') # Ñ€ + check_both_ways("\u044F", "\xEF", 'IBM866') # Ñ + check_both_ways("\u0401", "\xF0", 'IBM866') # Ð + check_both_ways("\u00A0", "\xFF", 'IBM866') # non-breaking space + end + + def test_IBM869 + assert_undefined_in("\x80", 'IBM869') + assert_undefined_in("\x85", 'IBM869') + check_both_ways("\u0386", "\x86", 'IBM869') # Ά + assert_undefined_in("\x87", 'IBM869') + check_both_ways("\u00B7", "\x88", 'IBM869') # · + check_both_ways("\u0389", "\x8F", 'IBM869') # Ή + check_both_ways("\u038A", "\x90", 'IBM869') # Ί + check_both_ways("\u038C", "\x92", 'IBM869') # ÎŒ + assert_undefined_in("\x93", 'IBM869') + assert_undefined_in("\x94", 'IBM869') + check_both_ways("\u038E", "\x95", 'IBM869') # ÎŽ + check_both_ways("\u03AF", "\x9F", 'IBM869') # ί + check_both_ways("\u03CA", "\xA0", 'IBM869') # ÏŠ + check_both_ways("\u00BB", "\xAF", 'IBM869') # » + check_both_ways("\u2591", "\xB0", 'IBM869') # â–‘ + check_both_ways("\u2510", "\xBF", 'IBM869') # â” + check_both_ways("\u2514", "\xC0", 'IBM869') # â”” + check_both_ways("\u03A3", "\xCF", 'IBM869') # Σ + check_both_ways("\u03A4", "\xD0", 'IBM869') # Τ + check_both_ways("\u2580", "\xDF", 'IBM869') # â–€ + check_both_ways("\u03B6", "\xE0", 'IBM869') # ζ + check_both_ways("\u0384", "\xEF", 'IBM869') # ΄ + check_both_ways("\u00AD", "\xF0", 'IBM869') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'IBM869') # non-breaking space + end + + def test_macCroatian + check_both_ways("\u00C4", "\x80", 'macCroatian') # Ä + check_both_ways("\u00E8", "\x8F", 'macCroatian') # è + check_both_ways("\u00EA", "\x90", 'macCroatian') # ê + check_both_ways("\u00FC", "\x9F", 'macCroatian') # ü + check_both_ways("\u2020", "\xA0", 'macCroatian') # †+ check_both_ways("\u00D8", "\xAF", 'macCroatian') # Ø + check_both_ways("\u221E", "\xB0", 'macCroatian') # ∞ + check_both_ways("\u00F8", "\xBF", 'macCroatian') # ø + check_both_ways("\u00BF", "\xC0", 'macCroatian') # ¿ + check_both_ways("\u0153", "\xCF", 'macCroatian') # Å“ + check_both_ways("\u0110", "\xD0", 'macCroatian') # Ä + check_both_ways("\u00A9", "\xD9", 'macCroatian') # © + check_both_ways("\u2044", "\xDA", 'macCroatian') # â„ + check_both_ways("\u203A", "\xDD", 'macCroatian') # › + check_both_ways("\u00C6", "\xDE", 'macCroatian') # Æ + check_both_ways("\u00BB", "\xDF", 'macCroatian') # » + check_both_ways("\u2013", "\xE0", 'macCroatian') # – + check_both_ways("\u00B7", "\xE1", 'macCroatian') # · + check_both_ways("\u00C2", "\xE5", 'macCroatian') #  + check_both_ways("\u0107", "\xE6", 'macCroatian') # ć + check_both_ways("\u00C1", "\xE7", 'macCroatian') # à + check_both_ways("\u010D", "\xE8", 'macCroatian') # Ä + check_both_ways("\u00C8", "\xE9", 'macCroatian') # È + check_both_ways("\u00D4", "\xEF", 'macCroatian') # Ô + check_both_ways("\u0111", "\xF0", 'macCroatian') # Ä‘ + check_both_ways("\u00D2", "\xF1", 'macCroatian') # Ã’ + check_both_ways("\u00AF", "\xF8", 'macCroatian') # ¯ + check_both_ways("\u03C0", "\xF9", 'macCroatian') # Ï€ + check_both_ways("\u00CB", "\xFA", 'macCroatian') # Ë + check_both_ways("\u00CA", "\xFD", 'macCroatian') # Ê + check_both_ways("\u00E6", "\xFE", 'macCroatian') # æ + check_both_ways("\u02C7", "\xFF", 'macCroatian') # ˇ + end + + def test_macCyrillic + check_both_ways("\u0410", "\x80", 'macCyrillic') # Ð + check_both_ways("\u041F", "\x8F", 'macCyrillic') # П + check_both_ways("\u0420", "\x90", 'macCyrillic') # Ð + check_both_ways("\u042F", "\x9F", 'macCyrillic') # Я + check_both_ways("\u2020", "\xA0", 'macCyrillic') # †+ check_both_ways("\u0453", "\xAF", 'macCyrillic') # Ñ“ + check_both_ways("\u221E", "\xB0", 'macCyrillic') # ∞ + check_both_ways("\u045A", "\xBF", 'macCyrillic') # Ñš + check_both_ways("\u0458", "\xC0", 'macCyrillic') # ј + check_both_ways("\u0455", "\xCF", 'macCyrillic') # Ñ• + check_both_ways("\u2013", "\xD0", 'macCyrillic') # – + check_both_ways("\u044F", "\xDF", 'macCyrillic') # Ñ + check_both_ways("\u0430", "\xE0", 'macCyrillic') # а + check_both_ways("\u043F", "\xEF", 'macCyrillic') # п + check_both_ways("\u0440", "\xF0", 'macCyrillic') # Ñ€ + check_both_ways("\u00A4", "\xFF", 'macCyrillic') # ¤ + end + + def test_macGreek + check_both_ways("\u00C4", "\x80", 'macGreek') # Ä + check_both_ways("\u00E8", "\x8F", 'macGreek') # è + check_both_ways("\u00EA", "\x90", 'macGreek') # ê + check_both_ways("\u00FC", "\x9F", 'macGreek') # ü + check_both_ways("\u2020", "\xA0", 'macGreek') # †+ check_both_ways("\u0393", "\xA1", 'macGreek') # Γ + check_both_ways("\u0387", "\xAF", 'macGreek') # · + check_both_ways("\u0391", "\xB0", 'macGreek') # Α + check_both_ways("\u03A9", "\xBF", 'macGreek') # Ω + check_both_ways("\u03AC", "\xC0", 'macGreek') # ά + check_both_ways("\u0153", "\xCF", 'macGreek') # Å“ + check_both_ways("\u2013", "\xD0", 'macGreek') # – + check_both_ways("\u038F", "\xDF", 'macGreek') # Î + check_both_ways("\u03CD", "\xE0", 'macGreek') # Ï + check_both_ways("\u03BF", "\xEF", 'macGreek') # ο + check_both_ways("\u03C0", "\xF0", 'macGreek') # Ï€ + check_both_ways("\u03B0", "\xFE", 'macGreek') # ΰ + assert_undefined_in("\xFF", 'macGreek') + end + + def test_macIceland + check_both_ways("\u00C4", "\x80", 'macIceland') # Ä + check_both_ways("\u00E8", "\x8F", 'macIceland') # è + check_both_ways("\u00EA", "\x90", 'macIceland') # ê + check_both_ways("\u00FC", "\x9F", 'macIceland') # ü + check_both_ways("\u00DD", "\xA0", 'macIceland') # à + check_both_ways("\u00D8", "\xAF", 'macIceland') # Ø + check_both_ways("\u221E", "\xB0", 'macIceland') # ∞ + check_both_ways("\u00F8", "\xBF", 'macIceland') # ø + check_both_ways("\u00BF", "\xC0", 'macIceland') # ¿ + check_both_ways("\u0153", "\xCF", 'macIceland') # Å“ + check_both_ways("\u2013", "\xD0", 'macIceland') # – + check_both_ways("\u00FE", "\xDF", 'macIceland') # þ + check_both_ways("\u00FD", "\xE0", 'macIceland') # ý + check_both_ways("\u00D4", "\xEF", 'macIceland') # Ô + #check_both_ways("\uF8FF", "\xF0", 'macIceland') # Apple logo + check_both_ways("\u02C7", "\xFF", 'macIceland') # ˇ + end + + def test_macRoman + check_both_ways("\u00C4", "\x80", 'macRoman') # Ä + check_both_ways("\u00E8", "\x8F", 'macRoman') # è + check_both_ways("\u00EA", "\x90", 'macRoman') # ê + check_both_ways("\u00FC", "\x9F", 'macRoman') # ü + check_both_ways("\u2020", "\xA0", 'macRoman') # †+ #check_both_ways("\u00DB", "\xAF", 'macRoman') # Ø + check_both_ways("\u221E", "\xB0", 'macRoman') # ∞ + check_both_ways("\u00F8", "\xBF", 'macRoman') # ø + check_both_ways("\u00BF", "\xC0", 'macRoman') # ¿ + check_both_ways("\u0153", "\xCF", 'macRoman') # Å“ + check_both_ways("\u2013", "\xD0", 'macRoman') # – + check_both_ways("\u00A4", "\xDB", 'macRoman') # ¤ + check_both_ways("\uFB02", "\xDF", 'macRoman') # fl + check_both_ways("\u2021", "\xE0", 'macRoman') # ‡ + check_both_ways("\u00D4", "\xEF", 'macRoman') # Ô + #check_both_ways("\uF8FF", "\xF0", 'macRoman') # Apple logo + check_both_ways("\u02C7", "\xFF", 'macRoman') # ˇ + end + + def test_macRomania + check_both_ways("\u00C4", "\x80", 'macRomania') # Ä + check_both_ways("\u00E8", "\x8F", 'macRomania') # è + check_both_ways("\u00EA", "\x90", 'macRomania') # ê + check_both_ways("\u00FC", "\x9F", 'macRomania') # ü + check_both_ways("\u2020", "\xA0", 'macRomania') # †+ check_both_ways("\u015E", "\xAF", 'macRomania') # Åž + check_both_ways("\u221E", "\xB0", 'macRomania') # ∞ + check_both_ways("\u015F", "\xBF", 'macRomania') # ÅŸ + check_both_ways("\u00BF", "\xC0", 'macRomania') # ¿ + check_both_ways("\u0153", "\xCF", 'macRomania') # Å“ + check_both_ways("\u2013", "\xD0", 'macRomania') # – + check_both_ways("\u00A4", "\xDB", 'macRomania') # € + check_both_ways("\u0163", "\xDF", 'macRomania') # Å£ + check_both_ways("\u2021", "\xE0", 'macRomania') # ‡ + check_both_ways("\u00D4", "\xEF", 'macRomania') # Ô + #check_both_ways("\uF8FF", "\xF0", 'macRomania') # Apple logo + check_both_ways("\u02C7", "\xFF", 'macRomania') # ˇ + end + + def test_macTurkish + check_both_ways("\u00C4", "\x80", 'macTurkish') # Ä + check_both_ways("\u00E8", "\x8F", 'macTurkish') # è + check_both_ways("\u00EA", "\x90", 'macTurkish') # ê + check_both_ways("\u00FC", "\x9F", 'macTurkish') # ü + check_both_ways("\u2020", "\xA0", 'macTurkish') # †+ check_both_ways("\u00D8", "\xAF", 'macTurkish') # Ø + check_both_ways("\u221E", "\xB0", 'macTurkish') # ∞ + check_both_ways("\u00F8", "\xBF", 'macTurkish') # ø + check_both_ways("\u00BF", "\xC0", 'macTurkish') # ¿ + check_both_ways("\u0153", "\xCF", 'macTurkish') # Å“ + check_both_ways("\u2013", "\xD0", 'macTurkish') # – + check_both_ways("\u015F", "\xDF", 'macTurkish') # ÅŸ + check_both_ways("\u2021", "\xE0", 'macTurkish') # ‡ + check_both_ways("\u00D4", "\xEF", 'macTurkish') # Ô + #check_both_ways("\uF8FF", "\xF0", 'macTurkish') # Apple logo + check_both_ways("\u00D9", "\xF4", 'macTurkish') # Ù + assert_undefined_in("\xF5", 'macTurkish') + check_both_ways("\u02C6", "\xF6", 'macTurkish') # ˆ + check_both_ways("\u02C7", "\xFF", 'macTurkish') # ˇ + end + + def test_macUkraine + check_both_ways("\u0410", "\x80", 'macUkraine') # Ð + check_both_ways("\u041F", "\x8F", 'macUkraine') # П + check_both_ways("\u0420", "\x90", 'macUkraine') # Ð + check_both_ways("\u042F", "\x9F", 'macUkraine') # Я + check_both_ways("\u2020", "\xA0", 'macUkraine') # †+ check_both_ways("\u0453", "\xAF", 'macUkraine') # Ñ“ + check_both_ways("\u221E", "\xB0", 'macUkraine') # ∞ + check_both_ways("\u045A", "\xBF", 'macUkraine') # Ñš + check_both_ways("\u0458", "\xC0", 'macUkraine') # ј + check_both_ways("\u0455", "\xCF", 'macUkraine') # Ñ• + check_both_ways("\u2013", "\xD0", 'macUkraine') # – + check_both_ways("\u044F", "\xDF", 'macUkraine') # Ñ + check_both_ways("\u0430", "\xE0", 'macUkraine') # а + check_both_ways("\u043F", "\xEF", 'macUkraine') # п + check_both_ways("\u0440", "\xF0", 'macUkraine') # Ñ€ + check_both_ways("\u00A4", "\xFF", 'macUkraine') # ¤ + end + + def test_koi8_u + check_both_ways("\u2500", "\x80", 'KOI8-U') # ─ + check_both_ways("\u2590", "\x8F", 'KOI8-U') # â– + check_both_ways("\u2591", "\x90", 'KOI8-U') # â–‘ + check_both_ways("\u00F7", "\x9F", 'KOI8-U') # ÷ + check_both_ways("\u2550", "\xA0", 'KOI8-U') # â• + check_both_ways("\u0454", "\xA4", 'KOI8-U') # Ñ” + check_both_ways("\u0456", "\xA6", 'KOI8-U') # Ñ– + check_both_ways("\u0457", "\xA7", 'KOI8-U') # Ñ— + check_both_ways("\u0491", "\xAD", 'KOI8-U') # Ò‘ + check_both_ways("\u255E", "\xAF", 'KOI8-U') # ╞ + check_both_ways("\u255F", "\xB0", 'KOI8-U') # ╟ + check_both_ways("\u0404", "\xB4", 'KOI8-U') # Є + check_both_ways("\u0406", "\xB6", 'KOI8-U') # І + check_both_ways("\u0407", "\xB7", 'KOI8-U') # Ї + check_both_ways("\u0490", "\xBD", 'KOI8-U') # Ò + check_both_ways("\u00A9", "\xBF", 'KOI8-U') # © + check_both_ways("\u044E", "\xC0", 'KOI8-U') # ÑŽ + check_both_ways("\u043E", "\xCF", 'KOI8-U') # о + check_both_ways("\u043F", "\xD0", 'KOI8-U') # п + check_both_ways("\u044A", "\xDF", 'KOI8-U') # ÑŠ + check_both_ways("\u042E", "\xE0", 'KOI8-U') # Ю + check_both_ways("\u041E", "\xEF", 'KOI8-U') # О + check_both_ways("\u041F", "\xF0", 'KOI8-U') # П + check_both_ways("\u042A", "\xFF", 'KOI8-U') # Ъ + end + + def test_koi8_r + check_both_ways("\u2500", "\x80", 'KOI8-R') # ─ + check_both_ways("\u2590", "\x8F", 'KOI8-R') # â– + check_both_ways("\u2591", "\x90", 'KOI8-R') # â–‘ + check_both_ways("\u00F7", "\x9F", 'KOI8-R') # ÷ + check_both_ways("\u2550", "\xA0", 'KOI8-R') # â• + check_both_ways("\u255E", "\xAF", 'KOI8-R') # ╞ + check_both_ways("\u255F", "\xB0", 'KOI8-R') # ╟ + check_both_ways("\u00A9", "\xBF", 'KOI8-R') # © + check_both_ways("\u044E", "\xC0", 'KOI8-R') # ÑŽ + check_both_ways("\u043E", "\xCF", 'KOI8-R') # о + check_both_ways("\u043F", "\xD0", 'KOI8-R') # п + check_both_ways("\u044A", "\xDF", 'KOI8-R') # ÑŠ + check_both_ways("\u042E", "\xE0", 'KOI8-R') # Ю + check_both_ways("\u041E", "\xEF", 'KOI8-R') # О + check_both_ways("\u041F", "\xF0", 'KOI8-R') # П + check_both_ways("\u042A", "\xFF", 'KOI8-R') # Ъ + end + + def test_TIS_620 + assert_undefined_in("\x80", 'TIS-620') + assert_undefined_in("\x8F", 'TIS-620') + assert_undefined_in("\x90", 'TIS-620') + assert_undefined_in("\x9F", 'TIS-620') + assert_undefined_in("\xA0", 'TIS-620') + check_both_ways("\u0E01", "\xA1", 'TIS-620') # ภ+ check_both_ways("\u0E0F", "\xAF", 'TIS-620') # ภ+ check_both_ways("\u0E10", "\xB0", 'TIS-620') # ภ+ check_both_ways("\u0E1F", "\xBF", 'TIS-620') # ฟ + check_both_ways("\u0E20", "\xC0", 'TIS-620') # ภ+ check_both_ways("\u0E2F", "\xCF", 'TIS-620') # ฯ + check_both_ways("\u0E30", "\xD0", 'TIS-620') # ะ + check_both_ways("\u0E3A", "\xDA", 'TIS-620') # ฺ + assert_undefined_in("\xDB", 'TIS-620') + assert_undefined_in("\xDE", 'TIS-620') + check_both_ways("\u0E3F", "\xDF", 'TIS-620') # ฿ + check_both_ways("\u0E40", "\xE0", 'TIS-620') # เ + check_both_ways("\u0E4F", "\xEF", 'TIS-620') # ๠+ check_both_ways("\u0E50", "\xF0", 'TIS-620') # ๠+ check_both_ways("\u0E5B", "\xFB", 'TIS-620') # ๛ + assert_undefined_in("\xFC", 'TIS-620') + assert_undefined_in("\xFF", 'TIS-620') + end + + def test_CP850 + check_both_ways("\u00C7", "\x80", 'CP850') # Ç + check_both_ways("\u00C5", "\x8F", 'CP850') # Ã… + check_both_ways("\u00C9", "\x90", 'CP850') # É + check_both_ways("\u0192", "\x9F", 'CP850') # Æ’ + check_both_ways("\u00E1", "\xA0", 'CP850') # á + check_both_ways("\u00BB", "\xAF", 'CP850') # » + check_both_ways("\u2591", "\xB0", 'CP850') # â–‘ + check_both_ways("\u2510", "\xBF", 'CP850') # â” + check_both_ways("\u2514", "\xC0", 'CP850') # â”” + check_both_ways("\u00A4", "\xCF", 'CP850') # ¤ + check_both_ways("\u00F0", "\xD0", 'CP850') # ð + check_both_ways("\u2580", "\xDF", 'CP850') # â–€ + check_both_ways("\u00D3", "\xE0", 'CP850') # Ó + check_both_ways("\u00B4", "\xEF", 'CP850') # ´ + check_both_ways("\u00AD", "\xF0", 'CP850') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'CP850') # non-breaking space + end + + def test_CP852 + check_both_ways("\u00C7", "\x80", 'CP852') # Ç + check_both_ways("\u0106", "\x8F", 'CP852') # Ć + check_both_ways("\u00C9", "\x90", 'CP852') # É + check_both_ways("\u010D", "\x9F", 'CP852') # Ä + check_both_ways("\u00E1", "\xA0", 'CP852') # á + check_both_ways("\u00BB", "\xAF", 'CP852') # » + check_both_ways("\u2591", "\xB0", 'CP852') # â–‘ + check_both_ways("\u2510", "\xBF", 'CP852') # â” + check_both_ways("\u2514", "\xC0", 'CP852') # â”” + check_both_ways("\u00A4", "\xCF", 'CP852') # ¤ + check_both_ways("\u0111", "\xD0", 'CP852') # Ä‘ + check_both_ways("\u2580", "\xDF", 'CP852') # â–€ + check_both_ways("\u00D3", "\xE0", 'CP852') # Ó + check_both_ways("\u00B4", "\xEF", 'CP852') # ´ + check_both_ways("\u00AD", "\xF0", 'CP852') # soft hyphen + check_both_ways("\u00A0", "\xFF", 'CP852') # non-breaking space + end + + def test_CP855 + check_both_ways("\u0452", "\x80", 'CP855') # Ñ’ + check_both_ways("\u0408", "\x8F", 'CP855') # Ј + check_both_ways("\u0459", "\x90", 'CP855') # Ñ™ + check_both_ways("\u042A", "\x9F", 'CP855') # Ъ + check_both_ways("\u0430", "\xA0", 'CP855') # а + check_both_ways("\u00BB", "\xAF", 'CP855') # » + check_both_ways("\u2591", "\xB0", 'CP855') # â–‘ + check_both_ways("\u2510", "\xBF", 'CP855') # â” + check_both_ways("\u2514", "\xC0", 'CP855') # â”” + check_both_ways("\u00A4", "\xCF", 'CP855') # ¤ + check_both_ways("\u043B", "\xD0", 'CP855') # л + check_both_ways("\u2580", "\xDF", 'CP855') # â–€ + check_both_ways("\u042F", "\xE0", 'CP855') # Я + check_both_ways("\u2116", "\xEF", 'CP855') # â„– + check_both_ways("\u00AD", "\xF0", 'CP855') # soft hyphen + 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] } + check_both_ways(utf8, raw, 'utf-16be') + check_both_ways(utf8, copy, 'utf-16le') + end + + def test_utf_16 + check_utf_16_both_ways("abc", "\x00a\x00b\x00c") + check_utf_16_both_ways("\u00E9", "\x00\xE9"); + check_utf_16_both_ways("\u00E9\u0070\u00E9\u0065", "\x00\xE9\x00\x70\x00\xE9\x00\x65") # épée + check_utf_16_both_ways("\u677E\u672C\u884C\u5F18", "\x67\x7E\x67\x2C\x88\x4C\x5F\x18") # æ¾æœ¬è¡Œå¼˜ + check_utf_16_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\x97\x52\x5C\x71\x5B\x66\x96\x62\x59\x27\x5B\x66") # é’å±±å¦é™¢å¤§å¦ + check_utf_16_both_ways("Martin D\u00FCrst", "\x00M\x00a\x00r\x00t\x00i\x00n\x00 \x00D\x00\xFC\x00r\x00s\x00t") # Martin Dürst + # BMP + check_utf_16_both_ways("\u0000", "\x00\x00") + check_utf_16_both_ways("\u007F", "\x00\x7F") + check_utf_16_both_ways("\u0080", "\x00\x80") + check_utf_16_both_ways("\u0555", "\x05\x55") + check_utf_16_both_ways("\u04AA", "\x04\xAA") + check_utf_16_both_ways("\u0333", "\x03\x33") + check_utf_16_both_ways("\u04CC", "\x04\xCC") + check_utf_16_both_ways("\u00F0", "\x00\xF0") + check_utf_16_both_ways("\u070F", "\x07\x0F") + check_utf_16_both_ways("\u07FF", "\x07\xFF") + check_utf_16_both_ways("\u0800", "\x08\x00") + check_utf_16_both_ways("\uD7FF", "\xD7\xFF") + check_utf_16_both_ways("\uE000", "\xE0\x00") + check_utf_16_both_ways("\uFFFF", "\xFF\xFF") + check_utf_16_both_ways("\u5555", "\x55\x55") + check_utf_16_both_ways("\uAAAA", "\xAA\xAA") + check_utf_16_both_ways("\u3333", "\x33\x33") + check_utf_16_both_ways("\uCCCC", "\xCC\xCC") + check_utf_16_both_ways("\uF0F0", "\xF0\xF0") + check_utf_16_both_ways("\u0F0F", "\x0F\x0F") + check_utf_16_both_ways("\uFF00", "\xFF\x00") + check_utf_16_both_ways("\u00FF", "\x00\xFF") + # outer planes + check_utf_16_both_ways("\u{10000}", "\xD8\x00\xDC\x00") + check_utf_16_both_ways("\u{FFFFF}", "\xDB\xBF\xDF\xFF") + check_utf_16_both_ways("\u{100000}", "\xDB\xC0\xDC\x00") + check_utf_16_both_ways("\u{10FFFF}", "\xDB\xFF\xDF\xFF") + check_utf_16_both_ways("\u{105555}", "\xDB\xD5\xDD\x55") + check_utf_16_both_ways("\u{55555}", "\xD9\x15\xDD\x55") + check_utf_16_both_ways("\u{AAAAA}", "\xDA\x6A\xDE\xAA") + check_utf_16_both_ways("\u{33333}", "\xD8\x8C\xDF\x33") + check_utf_16_both_ways("\u{CCCCC}", "\xDA\xF3\xDC\xCC") + check_utf_16_both_ways("\u{8F0F0}", "\xD9\xFC\xDC\xF0") + check_utf_16_both_ways("\u{F0F0F}", "\xDB\x83\xDF\x0F") + check_utf_16_both_ways("\u{8FF00}", "\xD9\xFF\xDF\x00") + 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_invalid_in(%w/feffdfb7/.pack("H*"), "UTF-16") + assert_invalid_in(%w/fffeb7df/.pack("H*"), "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_invalid_in(%w/0000feff00110000/.pack("H*"), "UTF-32") + end + + def check_utf_32_both_ways(utf8, raw) + copy = raw.dup + 0.step(copy.length-1, 4) do |i| + copy[i+3], copy[i+2], copy[i+1], copy[i] = copy[i], copy[i+1], copy[i+2], copy[i+3] + end + check_both_ways(utf8, raw, 'utf-32be') + #check_both_ways(utf8, copy, 'utf-32le') + end + + def test_utf_32 + check_utf_32_both_ways("abc", "\x00\x00\x00a\x00\x00\x00b\x00\x00\x00c") + check_utf_32_both_ways("\u00E9", "\x00\x00\x00\xE9"); + check_utf_32_both_ways("\u00E9\u0070\u00E9\u0065", + "\x00\x00\x00\xE9\x00\x00\x00\x70\x00\x00\x00\xE9\x00\x00\x00\x65") # épée + check_utf_32_both_ways("\u677E\u672C\u884C\u5F18", + "\x00\x00\x67\x7E\x00\x00\x67\x2C\x00\x00\x88\x4C\x00\x00\x5F\x18") # æ¾æœ¬è¡Œå¼˜ + check_utf_32_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", + "\x00\x00\x97\x52\x00\x00\x5C\x71\x00\x00\x5B\x66\x00\x00\x96\x62\x00\x00\x59\x27\x00\x00\x5B\x66") # é’å±±å¦é™¢å¤§å¦ + check_utf_32_both_ways("Martin D\u00FCrst", + "\x00\x00\x00M\x00\x00\x00a\x00\x00\x00r\x00\x00\x00t\x00\x00\x00i\x00\x00\x00n\x00\x00\x00 \x00\x00\x00D\x00\x00\x00\xFC\x00\x00\x00r\x00\x00\x00s\x00\x00\x00t") # Martin Dürst + # BMP + check_utf_32_both_ways("\u0000", "\x00\x00\x00\x00") + check_utf_32_both_ways("\u007F", "\x00\x00\x00\x7F") + check_utf_32_both_ways("\u0080", "\x00\x00\x00\x80") + check_utf_32_both_ways("\u0555", "\x00\x00\x05\x55") + check_utf_32_both_ways("\u04AA", "\x00\x00\x04\xAA") + check_utf_32_both_ways("\u0333", "\x00\x00\x03\x33") + check_utf_32_both_ways("\u04CC", "\x00\x00\x04\xCC") + check_utf_32_both_ways("\u00F0", "\x00\x00\x00\xF0") + check_utf_32_both_ways("\u070F", "\x00\x00\x07\x0F") + check_utf_32_both_ways("\u07FF", "\x00\x00\x07\xFF") + check_utf_32_both_ways("\u0800", "\x00\x00\x08\x00") + check_utf_32_both_ways("\uD7FF", "\x00\x00\xD7\xFF") + check_utf_32_both_ways("\uE000", "\x00\x00\xE0\x00") + check_utf_32_both_ways("\uFFFF", "\x00\x00\xFF\xFF") + check_utf_32_both_ways("\u5555", "\x00\x00\x55\x55") + check_utf_32_both_ways("\uAAAA", "\x00\x00\xAA\xAA") + check_utf_32_both_ways("\u3333", "\x00\x00\x33\x33") + check_utf_32_both_ways("\uCCCC", "\x00\x00\xCC\xCC") + check_utf_32_both_ways("\uF0F0", "\x00\x00\xF0\xF0") + check_utf_32_both_ways("\u0F0F", "\x00\x00\x0F\x0F") + check_utf_32_both_ways("\uFF00", "\x00\x00\xFF\x00") + check_utf_32_both_ways("\u00FF", "\x00\x00\x00\xFF") + # outer planes + check_utf_32_both_ways("\u{10000}", "\x00\x01\x00\x00") + check_utf_32_both_ways("\u{FFFFF}", "\x00\x0F\xFF\xFF") + check_utf_32_both_ways("\u{100000}","\x00\x10\x00\x00") + check_utf_32_both_ways("\u{10FFFF}","\x00\x10\xFF\xFF") + check_utf_32_both_ways("\u{105555}","\x00\x10\x55\x55") + check_utf_32_both_ways("\u{55555}", "\x00\x05\x55\x55") + check_utf_32_both_ways("\u{AAAAA}", "\x00\x0A\xAA\xAA") + check_utf_32_both_ways("\u{33333}", "\x00\x03\x33\x33") + check_utf_32_both_ways("\u{CCCCC}", "\x00\x0C\xCC\xCC") + check_utf_32_both_ways("\u{8F0F0}", "\x00\x08\xF0\xF0") + check_utf_32_both_ways("\u{F0F0F}", "\x00\x0F\x0F\x0F") + check_utf_32_both_ways("\u{8FF00}", "\x00\x08\xFF\x00") + check_utf_32_both_ways("\u{F00FF}", "\x00\x0F\x00\xFF") + end + + def test_invalid_ignore + # arguments only + assert_nothing_raised { 'abc'.encode('utf-8', invalid: :replace, replace: "") } + # check handling of UTF-8 ill-formed subsequences + assert_equal("\x00\x41\x00\x3E\x00\x42".force_encoding('UTF-16BE'), + "\x41\xC2\x3E\x42".encode('UTF-16BE', 'UTF-8', invalid: :replace, replace: "")) + assert_equal("\x00\x41\x00\xF1\x00\x42".force_encoding('UTF-16BE'), + "\x41\xC2\xC3\xB1\x42".encode('UTF-16BE', 'UTF-8', invalid: :replace, replace: "")) + assert_equal("\x00\x42".force_encoding('UTF-16BE'), + "\xF0\x80\x80\x42".encode('UTF-16BE', 'UTF-8', invalid: :replace, replace: "")) + assert_equal(''.force_encoding('UTF-16BE'), + "\x82\xAB".encode('UTF-16BE', 'UTF-8', invalid: :replace, replace: "")) + + assert_equal("\e$B!!\e(B".force_encoding("ISO-2022-JP"), + "\xA1\xA1\xFF".encode("ISO-2022-JP", "EUC-JP", invalid: :replace, replace: "")) + assert_equal("\e$B\x24\x22\x24\x24\e(B".force_encoding("ISO-2022-JP"), + "\xA4\xA2\xFF\xA4\xA4".encode("ISO-2022-JP", "EUC-JP", invalid: :replace, replace: "")) + assert_equal("\e$B\x24\x22\x24\x24\e(B".force_encoding("ISO-2022-JP"), + "\xA4\xA2\xFF\xFF\xA4\xA4".encode("ISO-2022-JP", "EUC-JP", invalid: :replace, replace: "")) + end + + def test_invalid_replace + # arguments only + assert_nothing_raised { 'abc'.encode('UTF-8', invalid: :replace) } + assert_equal("\xEF\xBF\xBD".force_encoding("UTF-8"), + "\x80".encode("UTF-8", "UTF-16BE", invalid: :replace)) + assert_equal("\xFF\xFD".force_encoding("UTF-16BE"), + "\x80".encode("UTF-16BE", "UTF-8", invalid: :replace)) + assert_equal("\xFD\xFF".force_encoding("UTF-16LE"), + "\x80".encode("UTF-16LE", "UTF-8", invalid: :replace)) + assert_equal("\x00\x00\xFF\xFD".force_encoding("UTF-32BE"), + "\x80".encode("UTF-32BE", "UTF-8", invalid: :replace)) + assert_equal("\xFD\xFF\x00\x00".force_encoding("UTF-32LE"), + "\x80".encode("UTF-32LE", "UTF-8", invalid: :replace)) + + assert_equal("\uFFFD!", + "\xdc\x00\x00!".encode("utf-8", "utf-16be", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\xd8\x00\x00!".encode("utf-8", "utf-16be", :invalid=>:replace)) + + assert_equal("\uFFFD!", + "\x00\xdc!\x00".encode("utf-8", "utf-16le", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\x00\xd8!\x00".encode("utf-8", "utf-16le", :invalid=>:replace)) + + assert_equal("\uFFFD!", + "\x01\x00\x00\x00\x00\x00\x00!".encode("utf-8", "utf-32be", :invalid=>:replace), "[ruby-dev:35726]") + assert_equal("\uFFFD!", + "\x00\xff\x00\x00\x00\x00\x00!".encode("utf-8", "utf-32be", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\x00\x00\xd8\x00\x00\x00\x00!".encode("utf-8", "utf-32be", :invalid=>:replace)) + + assert_equal("\uFFFD!", + "\x00\x00\x00\xff!\x00\x00\x00".encode("utf-8", "utf-32le", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\x00\x00\xff\x00!\x00\x00\x00".encode("utf-8", "utf-32le", :invalid=>:replace)) + assert_equal("\uFFFD!", + "\x00\xd8\x00\x00!\x00\x00\x00".encode("utf-8", "utf-32le", :invalid=>:replace)) + + 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]") + assert_equal("ab?cd?ef", + "\0a\0b\xdc\x00\0c\0d\xdf\x00\0e\0f".encode("EUC-JP", "UTF-16BE", :invalid=>:replace)) + + assert_equal("\e$B!!\e(B?".force_encoding("ISO-2022-JP"), + "\xA1\xA1\xFF".encode("ISO-2022-JP", "EUC-JP", invalid: :replace)) + assert_equal("\e$B\x24\x22\e(B?\e$B\x24\x24\e(B".force_encoding("ISO-2022-JP"), + "\xA4\xA2\xFF\xA4\xA4".encode("ISO-2022-JP", "EUC-JP", invalid: :replace)) + assert_equal("\e$B\x24\x22\e(B??\e$B\x24\x24\e(B".force_encoding("ISO-2022-JP"), + "\xA4\xA2\xFF\xFF\xA4\xA4".encode("ISO-2022-JP", "EUC-JP", invalid: :replace)) + end + + 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 + assert_equal("?", "\u20AC".encode("EUC-JP", :undef=>:replace), "[ruby-dev:35709]") + end + + def test_undef_replace_string + assert_equal("a<x>A", "a\u3042A".encode("us-ascii", :undef=>:replace, :replace=>"<x>")) + end + + def test_shift_jis + check_both_ways("\u3000", "\x81\x40", 'shift_jis') # full-width space + check_both_ways("\u00D7", "\x81\x7E", 'shift_jis') # × + check_both_ways("\u00F7", "\x81\x80", 'shift_jis') # ÷ + check_both_ways("\u25C7", "\x81\x9E", 'shift_jis') # â—‡ + check_both_ways("\u25C6", "\x81\x9F", 'shift_jis') # â—† + check_both_ways("\u25EF", "\x81\xFC", 'shift_jis') # â—¯ + check_both_ways("\u6A97", "\x9F\x40", 'shift_jis') # 檗 + check_both_ways("\u6BEF", "\x9F\x7E", 'shift_jis') # 毯 + check_both_ways("\u9EBE", "\x9F\x80", 'shift_jis') # 麾 + check_both_ways("\u6CBE", "\x9F\x9E", 'shift_jis') # æ²¾ + check_both_ways("\u6CBA", "\x9F\x9F", 'shift_jis') # 沺 + check_both_ways("\u6ECC", "\x9F\xFC", 'shift_jis') # 滌 + check_both_ways("\u6F3E", "\xE0\x40", 'shift_jis') # æ¼¾ + check_both_ways("\u70DD", "\xE0\x7E", 'shift_jis') # çƒ + check_both_ways("\u70D9", "\xE0\x80", 'shift_jis') # 烙 + check_both_ways("\u71FC", "\xE0\x9E", 'shift_jis') # 燼 + check_both_ways("\u71F9", "\xE0\x9F", 'shift_jis') # 燹 + check_both_ways("\u73F1", "\xE0\xFC", 'shift_jis') # ç± + assert_undefined_in("\xEF\x40", 'shift_jis') + assert_undefined_in("\xEF\x7E", 'shift_jis') + assert_undefined_in("\xEF\x80", 'shift_jis') + assert_undefined_in("\xEF\x9E", 'shift_jis') + assert_undefined_in("\xEF\x9F", 'shift_jis') + assert_undefined_in("\xEF\xFC", 'shift_jis') + assert_undefined_in("\xF0\x40", 'shift_jis') + assert_undefined_in("\xF0\x7E", 'shift_jis') + assert_undefined_in("\xF0\x80", 'shift_jis') + assert_undefined_in("\xF0\x9E", 'shift_jis') + assert_undefined_in("\xF0\x9F", 'shift_jis') + assert_undefined_in("\xF0\xFC", 'shift_jis') + #check_both_ways("\u9ADC", "\xFC\x40", 'shift_jis') # 髜 (IBM extended) + assert_undefined_in("\xFC\x7E", 'shift_jis') + assert_undefined_in("\xFC\x80", 'shift_jis') + assert_undefined_in("\xFC\x9E", 'shift_jis') + assert_undefined_in("\xFC\x9F", 'shift_jis') + assert_undefined_in("\xFC\xFC", 'shift_jis') + check_both_ways("\u677E\u672C\u884C\u5F18", "\x8f\xbc\x96\x7b\x8d\x73\x8d\x4f", 'shift_jis') # æ¾æœ¬è¡Œå¼˜ + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\x90\xC2\x8E\x52\x8A\x77\x89\x40\x91\xE5\x8A\x77", 'shift_jis') # é’å±±å¦é™¢å¤§å¦ + check_both_ways("\u795E\u6797\u7FA9\u535A", "\x90\x5F\x97\xD1\x8B\x60\x94\x8E", 'shift_jis') # ç¥žæž—ç¾©åš + end + + def test_windows_31j + check_both_ways("\u222A", "\x81\xBE", 'Windows-31J') # Union + check_both_ways("\uFFE2", "\x81\xCA", 'Windows-31J') # Fullwidth Not Sign + check_both_ways("\u2235", "\x81\xE6", 'Windows-31J') # Because + check_both_ways("\u2160", "\x87\x54", 'Windows-31J') # Roman Numeral One + check_both_ways("\u2170", "\xFA\x40", 'Windows-31J') # Small Roman Numeral One + end + + def test_euc_jp + check_both_ways("\u3000", "\xA1\xA1", 'euc-jp') # full-width space + check_both_ways("\u00D7", "\xA1\xDF", 'euc-jp') # × + check_both_ways("\u00F7", "\xA1\xE0", 'euc-jp') # ÷ + check_both_ways("\u25C7", "\xA1\xFE", 'euc-jp') # â—‡ + check_both_ways("\u25C6", "\xA2\xA1", 'euc-jp') # â—† + assert_undefined_in("\xA2\xAF", 'euc-jp') + assert_undefined_in("\xA2\xB9", 'euc-jp') + assert_undefined_in("\xA2\xC2", 'euc-jp') + assert_undefined_in("\xA2\xC9", 'euc-jp') + assert_undefined_in("\xA2\xD1", 'euc-jp') + assert_undefined_in("\xA2\xDB", 'euc-jp') + assert_undefined_in("\xA2\xEB", 'euc-jp') + assert_undefined_in("\xA2\xF1", 'euc-jp') + assert_undefined_in("\xA2\xFA", 'euc-jp') + assert_undefined_in("\xA2\xFD", 'euc-jp') + check_both_ways("\u25EF", "\xA2\xFE", 'euc-jp') # â—¯ + assert_undefined_in("\xA3\xAF", 'euc-jp') + assert_undefined_in("\xA3\xBA", 'euc-jp') + assert_undefined_in("\xA3\xC0", 'euc-jp') + assert_undefined_in("\xA3\xDB", 'euc-jp') + assert_undefined_in("\xA3\xE0", 'euc-jp') + assert_undefined_in("\xA3\xFB", 'euc-jp') + assert_undefined_in("\xA4\xF4", 'euc-jp') + assert_undefined_in("\xA5\xF7", 'euc-jp') + assert_undefined_in("\xA6\xB9", 'euc-jp') + assert_undefined_in("\xA6\xC0", 'euc-jp') + assert_undefined_in("\xA6\xD9", 'euc-jp') + assert_undefined_in("\xA7\xC2", 'euc-jp') + assert_undefined_in("\xA7\xD0", 'euc-jp') + assert_undefined_in("\xA7\xF2", 'euc-jp') + assert_undefined_in("\xA8\xC1", 'euc-jp') + assert_undefined_in("\xCF\xD4", 'euc-jp') + assert_undefined_in("\xCF\xFE", 'euc-jp') + check_both_ways("\u6A97", "\xDD\xA1", 'euc-jp') # 檗 + check_both_ways("\u6BEF", "\xDD\xDF", 'euc-jp') # 毯 + check_both_ways("\u9EBE", "\xDD\xE0", 'euc-jp') # 麾 + check_both_ways("\u6CBE", "\xDD\xFE", 'euc-jp') # æ²¾ + check_both_ways("\u6CBA", "\xDE\xA1", 'euc-jp') # 沺 + check_both_ways("\u6ECC", "\xDE\xFE", 'euc-jp') # 滌 + check_both_ways("\u6F3E", "\xDF\xA1", 'euc-jp') # æ¼¾ + check_both_ways("\u70DD", "\xDF\xDF", 'euc-jp') # çƒ + check_both_ways("\u70D9", "\xDF\xE0", 'euc-jp') # 烙 + check_both_ways("\u71FC", "\xDF\xFE", 'euc-jp') # 燼 + check_both_ways("\u71F9", "\xE0\xA1", 'euc-jp') # 燹 + check_both_ways("\u73F1", "\xE0\xFE", 'euc-jp') # ç± + assert_undefined_in("\xF4\xA7", 'euc-jp') + #check_both_ways("\u9ADC", "\xFC\xE3", 'euc-jp') # 髜 (IBM extended) + + check_both_ways("\u677E\u672C\u884C\u5F18", "\xBE\xBE\xCB\xDC\xB9\xD4\xB9\xB0", 'euc-jp') # æ¾æœ¬è¡Œå¼˜ + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC0\xC4\xBB\xB3\xB3\xD8\xB1\xA1\xC2\xE7\xB3\xD8", 'euc-jp') # é’å±±å¦é™¢å¤§å¦ + 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') # ï½” + assert_undefined_in("\xA5\xF7", '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 + check_both_ways("\u3231", "\xAD\xEA", 'eucJP-ms') # PARENTHESIZED IDEOGRAPH STOCK + check_both_ways("\uFF5E", "\xA1\xC1", 'eucJP-ms') # WAVE DASH + end + + def test_eucjp_sjis + check_both_ways2("\xa1\xa1", "EUC-JP", "\x81\x40", "Shift_JIS") + check_both_ways2("\xa1\xdf", "EUC-JP", "\x81\x7e", "Shift_JIS") + check_both_ways2("\xa1\xe0", "EUC-JP", "\x81\x80", "Shift_JIS") + check_both_ways2("\xa1\xfe", "EUC-JP", "\x81\x9e", "Shift_JIS") + check_both_ways2("\xa2\xa1", "EUC-JP", "\x81\x9f", "Shift_JIS") + check_both_ways2("\xa2\xfe", "EUC-JP", "\x81\xfc", "Shift_JIS") + + check_both_ways2("\xdd\xa1", "EUC-JP", "\x9f\x40", "Shift_JIS") + check_both_ways2("\xdd\xdf", "EUC-JP", "\x9f\x7e", "Shift_JIS") + check_both_ways2("\xdd\xe0", "EUC-JP", "\x9f\x80", "Shift_JIS") + check_both_ways2("\xdd\xfe", "EUC-JP", "\x9f\x9e", "Shift_JIS") + check_both_ways2("\xde\xa1", "EUC-JP", "\x9f\x9f", "Shift_JIS") + check_both_ways2("\xde\xfe", "EUC-JP", "\x9f\xfc", "Shift_JIS") + + check_both_ways2("\xdf\xa1", "EUC-JP", "\xe0\x40", "Shift_JIS") + check_both_ways2("\xdf\xdf", "EUC-JP", "\xe0\x7e", "Shift_JIS") + check_both_ways2("\xdf\xe0", "EUC-JP", "\xe0\x80", "Shift_JIS") + check_both_ways2("\xdf\xfe", "EUC-JP", "\xe0\x9e", "Shift_JIS") + check_both_ways2("\xe0\xa1", "EUC-JP", "\xe0\x9f", "Shift_JIS") + check_both_ways2("\xe0\xfe", "EUC-JP", "\xe0\xfc", "Shift_JIS") + + check_both_ways2("\xf4\xa1", "EUC-JP", "\xea\x9f", "Shift_JIS") + check_both_ways2("\xf4\xa2", "EUC-JP", "\xea\xa0", "Shift_JIS") + check_both_ways2("\xf4\xa3", "EUC-JP", "\xea\xa1", "Shift_JIS") + check_both_ways2("\xf4\xa4", "EUC-JP", "\xea\xa2", "Shift_JIS") # end of JIS X 0208 1983 + check_both_ways2("\xf4\xa5", "EUC-JP", "\xea\xa3", "Shift_JIS") + check_both_ways2("\xf4\xa6", "EUC-JP", "\xea\xa4", "Shift_JIS") # end of JIS X 0208 1990 + + check_both_ways2("\x8e\xa1", "EUC-JP", "\xa1", "Shift_JIS") + check_both_ways2("\x8e\xdf", "EUC-JP", "\xdf", "Shift_JIS") + end + + def test_eucjp_sjis_unassigned + check_both_ways2("\xfd\xa1", "EUC-JP", "\xef\x40", "Shift_JIS") + check_both_ways2("\xfd\xa1", "EUC-JP", "\xef\x40", "Shift_JIS") + check_both_ways2("\xfd\xdf", "EUC-JP", "\xef\x7e", "Shift_JIS") + check_both_ways2("\xfd\xe0", "EUC-JP", "\xef\x80", "Shift_JIS") + check_both_ways2("\xfd\xfe", "EUC-JP", "\xef\x9e", "Shift_JIS") + check_both_ways2("\xfe\xa1", "EUC-JP", "\xef\x9f", "Shift_JIS") + check_both_ways2("\xfe\xfe", "EUC-JP", "\xef\xfc", "Shift_JIS") + end + + def test_eucjp_sjis_undef + assert_undefined_conversion("\x8e\xe0", "Shift_JIS", "EUC-JP") + assert_undefined_conversion("\x8e\xfe", "Shift_JIS", "EUC-JP") + assert_undefined_conversion("\x8f\xa1\xa1", "Shift_JIS", "EUC-JP") + assert_undefined_conversion("\x8f\xa1\xfe", "Shift_JIS", "EUC-JP") + assert_undefined_conversion("\x8f\xfe\xa1", "Shift_JIS", "EUC-JP") + assert_undefined_conversion("\x8f\xfe\xfe", "Shift_JIS", "EUC-JP") + + assert_undefined_conversion("\xf0\x40", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xf0\x7e", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xf0\x80", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xf0\xfc", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xfc\x40", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xfc\x7e", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xfc\x80", "EUC-JP", "Shift_JIS") + assert_undefined_conversion("\xfc\xfc", "EUC-JP", "Shift_JIS") + end + + def test_iso_2022_jp + assert_invalid_in("\x1b(A", "iso-2022-jp") + assert_invalid_in("\x1b$(A", "iso-2022-jp") + assert_invalid_in("\x1b$C", "iso-2022-jp") + assert_invalid_in("\x0e", "iso-2022-jp") + assert_invalid_in("\x80", "iso-2022-jp") + assert_invalid_in("\x1b$(Dd!\x1b(B", "iso-2022-jp") + assert_undefined_conversion("\u9299", "iso-2022-jp") + assert_undefined_conversion("\uff71\uff72\uff73\uff74\uff75", "iso-2022-jp") + assert_invalid_in("\x1b(I12345\x1b(B", "iso-2022-jp") + assert_equal("\xA1\xA1".force_encoding("euc-jp"), + "\e$B!!\e(B".encode("EUC-JP", "ISO-2022-JP")) + assert_equal("\e$B!!\e(B".force_encoding("ISO-2022-JP"), + "\xA1\xA1".encode("ISO-2022-JP", "EUC-JP")) + end + + 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")) + assert_equal("\uFF71", "\e(B\xB1".encode("utf-8", "cp50221")) + assert_equal("\uFF71", "\e(J\xB1".encode("utf-8", "cp50221")) + assert_equal("\uFF71", "\e(I\xB1".encode("utf-8", "cp50221")) + assert_equal("\uFF71", "\e(I\x31".encode("utf-8", "cp50221")) + assert_equal("\uFF71", "\x0E\xB1".encode("utf-8", "cp50221")) + assert_equal("\u3000", "\e$@\x21\x21".encode("utf-8", "cp50221")) + assert_equal("\u3000", "\e$B\x21\x21".encode("utf-8", "cp50221")) + assert_equal("\u2460", "\e$B\x2D\x21".encode("utf-8", "cp50221")) + assert_equal("\u7e8a", "\e$B\x79\x21".encode("utf-8", "cp50221")) + 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("\xA1\xDF".force_encoding("sjis"), + "\e(I!_\e(B".encode("sjis","cp50220")) + end + + 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"), + "\xB5\xDE\xB6\xDE\xC4\xDE\xC5\xDE\xC9\xDE\xCA\xDE\xCE\xDE\xCF\xDE\xCA\xDF\xCE\xDF\xB1". + encode("cp50220", "sjis")) + assert_equal("\e$B\x21\x23\e(I\x7E\e(B".force_encoding("cp50220"), + "\x8E\xA1\x8E\xFE".encode("cp50220", "cp51932")) + end + + def test_iso_2022_jp_1 + # check_both_ways("\u9299", "\x1b$(Dd!\x1b(B", "iso-2022-jp-1") # JIS X 0212 区68 点01 銙 + end + + def test_unicode_public_review_issue_121 # see http://www.unicode.org/review/pr-121.html + 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 + + # 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 + check_both_ways("\u005C", "\x5C", "Shift_JIS") + check_both_ways("\u005C", "\x5C", "Windows-31J") + check_both_ways("\u005C", "\x5C", "EUC-JP") + check_both_ways("\u005C", "\x5C", "eucJP-ms") + check_both_ways("\u005C", "\x5C", "CP51932") + check_both_ways("\u005C", "\x5C", "ISO-2022-JP") + assert_equal("\u005C", "\e(B\x5C\e(B".encode("UTF-8", "ISO-2022-JP")) + assert_equal("\u005C", "\e(J\x5C\e(B".encode("UTF-8", "ISO-2022-JP")) + assert_equal("\u005C", "\x5C".encode("stateless-ISO-2022-JP", "ISO-2022-JP")) + assert_equal("\u005C", "\e(J\x5C\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP")) + assert_undefined_conversion("\u00A5", "Shift_JIS") + assert_undefined_conversion("\u00A5", "Windows-31J") + assert_undefined_conversion("\u00A5", "EUC-JP") + assert_undefined_conversion("\u00A5", "eucJP-ms") + assert_undefined_conversion("\u00A5", "CP51932") + + # FULLWIDTH REVERSE SOLIDUS + check_both_ways("\uFF3C", "\x81\x5F", "Shift_JIS") + check_both_ways("\uFF3C", "\x81\x5F", "Windows-31J") + check_both_ways("\uFF3C", "\xA1\xC0", "EUC-JP") + check_both_ways("\uFF3C", "\xA1\xC0", "eucJP-ms") + check_both_ways("\uFF3C", "\xA1\xC0", "CP51932") + end + + def test_tilde_overline + check_both_ways("\u007E", "\x7E", "Shift_JIS") + check_both_ways("\u007E", "\x7E", "Windows-31J") + check_both_ways("\u007E", "\x7E", "EUC-JP") + check_both_ways("\u007E", "\x7E", "eucJP-ms") + check_both_ways("\u007E", "\x7E", "CP51932") + check_both_ways("\u007E", "\x7E", "ISO-2022-JP") + assert_equal("\u007E", "\e(B\x7E\e(B".encode("UTF-8", "ISO-2022-JP")) + assert_equal("\u007E", "\e(J\x7E\e(B".encode("UTF-8", "ISO-2022-JP")) + assert_equal("\u007E", "\x7E".encode("stateless-ISO-2022-JP", "ISO-2022-JP")) + assert_equal("\u007E", "\e(J\x7E\e(B".encode("stateless-ISO-2022-JP", "ISO-2022-JP")) + assert_undefined_conversion("\u203E", "Shift_JIS") + assert_undefined_conversion("\u203E", "Windows-31J") + assert_undefined_conversion("\u203E", "EUC-JP") + assert_undefined_conversion("\u203E", "eucJP-ms") + assert_undefined_conversion("\u203E", "CP51932") + end + + def test_gb2312 + check_both_ways("\u3000", "\xA1\xA1", 'GB2312') # full-width space + check_both_ways("\u3013", "\xA1\xFE", 'GB2312') # 〓 + assert_undefined_in("\xA2\xB0", 'GB2312') + check_both_ways("\u2488", "\xA2\xB1", 'GB2312') # â’ˆ + assert_undefined_in("\xA2\xE4", 'GB2312') + check_both_ways("\u3220", "\xA2\xE5", 'GB2312') # ㈠+ assert_undefined_in("\xA2\xF0", 'GB2312') + check_both_ways("\u2160", "\xA2\xF1", 'GB2312') # â… + check_both_ways("\uFF01", "\xA3\xA1", 'GB2312') # ï¼ + check_both_ways("\uFFE3", "\xA3\xFE", 'GB2312') # ï¿£ + check_both_ways("\u3041", "\xA4\xA1", 'GB2312') # ã + check_both_ways("\u30A1", "\xA5\xA1", 'GB2312') # ã‚¡ + check_both_ways("\u0391", "\xA6\xA1", 'GB2312') # Α + check_both_ways("\u03B1", "\xA6\xC1", 'GB2312') # α + check_both_ways("\u0410", "\xA7\xA1", 'GB2312') # Ð + check_both_ways("\u0430", "\xA7\xD1", 'GB2312') # а + check_both_ways("\u0101", "\xA8\xA1", 'GB2312') # Ä + assert_undefined_in("\xA8\xC4", 'GB2312') + check_both_ways("\u3105", "\xA8\xC5", 'GB2312') # ã„… + assert_undefined_in("\xA9\xA3", 'GB2312') + check_both_ways("\u2500", "\xA9\xA4", 'GB2312') # ─ + check_both_ways("\u554A", "\xB0\xA1", 'GB2312') # 啊 + check_both_ways("\u5265", "\xB0\xFE", 'GB2312') # 剥 + check_both_ways("\u4FCA", "\xBF\xA1", 'GB2312') # 俊 + check_both_ways("\u5080", "\xBF\xFE", 'GB2312') # å‚€ + check_both_ways("\u9988", "\xC0\xA1", 'GB2312') # 馈 + check_both_ways("\u4FD0", "\xC0\xFE", 'GB2312') # ä¿ + check_both_ways("\u7A00", "\xCF\xA1", 'GB2312') # 稀 + check_both_ways("\u6653", "\xCF\xFE", 'GB2312') # 晓 + check_both_ways("\u5C0F", "\xD0\xA1", 'GB2312') # å° + check_both_ways("\u7384", "\xD0\xFE", 'GB2312') # 玄 + check_both_ways("\u4F4F", "\xD7\xA1", 'GB2312') # ä½ + check_both_ways("\u5EA7", "\xD7\xF9", 'GB2312') # 座 + assert_undefined_in("\xD7\xFA", 'GB2312') + check_both_ways("\u647A", "\xDF\xA1", 'GB2312') # 摺 + check_both_ways("\u553C", "\xDF\xFE", 'GB2312') # 唼 + check_both_ways("\u5537", "\xE0\xA1", 'GB2312') # å”· + check_both_ways("\u5E3C", "\xE0\xFE", 'GB2312') # 帼 + check_both_ways("\u94E9", "\xEF\xA1", 'GB2312') # é“© + check_both_ways("\u7A14", "\xEF\xFE", 'GB2312') # 稔 + check_both_ways("\u7A39", "\xF0\xA1", 'GB2312') # 稹 + check_both_ways("\u7619", "\xF0\xFE", 'GB2312') # 瘙 + check_both_ways("\u9CCC", "\xF7\xA1", 'GB2312') # 鳌 + check_both_ways("\u9F44", "\xF7\xFE", 'GB2312') # 齄 + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GB2312') # é’å±±å¦é™¢å¤§å¦ + end + + def test_gbk + check_both_ways("\u4E02", "\x81\x40", 'GBK') # 丂 + check_both_ways("\u4E8A", "\x81\x7E", 'GBK') # 亊 + check_both_ways("\u4E90", "\x81\x80", 'GBK') # äº + check_both_ways("\u4FA2", "\x81\xFE", 'GBK') # ä¾¢ + check_both_ways("\u5EC6", "\x8F\x40", 'GBK') # 廆 + check_both_ways("\u5F24", "\x8F\x7E", 'GBK') # 弤 + check_both_ways("\u5F28", "\x8F\x80", 'GBK') # 弨 + check_both_ways("\u6007", "\x8F\xFE", 'GBK') # 怇 + check_both_ways("\u6008", "\x90\x40", 'GBK') # 怈 + check_both_ways("\u6080", "\x90\x7E", 'GBK') # æ‚€ + check_both_ways("\u6081", "\x90\x80", 'GBK') # æ‚ + check_both_ways("\u6146", "\x90\xFE", 'GBK') # æ…† + check_both_ways("\u70DC", "\x9F\x40", 'GBK') # 烜 + check_both_ways("\u7134", "\x9F\x7E", 'GBK') # ç„´ + check_both_ways("\u7135", "\x9F\x80", 'GBK') # 焵 + check_both_ways("\u71D3", "\x9F\xFE", 'GBK') # 燓 + check_both_ways("\u71D6", "\xA0\x40", 'GBK') # 燖 + check_both_ways("\u721A", "\xA0\x7E", 'GBK') # 爚 + check_both_ways("\u721B", "\xA0\x80", 'GBK') # 爛 + check_both_ways("\u72DB", "\xA0\xFE", 'GBK') # ç‹› + check_both_ways("\u3000", "\xA1\xA1", 'GBK') # full-width space + check_both_ways("\u3001", "\xA1\xA2", 'GBK') # 〠+ check_both_ways("\u3013", "\xA1\xFE", 'GBK') # 〓 + assert_undefined_in("\xA2\xA0", 'GBK') + check_both_ways("\u2170", "\xA2\xA1", 'GBK') # â…° + assert_undefined_in("\xA2\xB0", 'GBK') + check_both_ways("\u2488", "\xA2\xB1", 'GBK') # â’ˆ + assert_undefined_in("\xA2\xE4", 'GBK') + check_both_ways("\u3220", "\xA2\xE5", 'GBK') # ㈠+ assert_undefined_in("\xA2\xF0", 'GBK') + check_both_ways("\u2160", "\xA2\xF1", 'GBK') # â… + assert_undefined_in("\xA3\xA0", 'GBK') + check_both_ways("\uFF01", "\xA3\xA1", 'GBK') # ï¼ + check_both_ways("\uFFE3", "\xA3\xFE", 'GBK') # ï¿£ + assert_undefined_in("\xA4\xA0", 'GBK') + check_both_ways("\u3041", "\xA4\xA1", 'GBK') # ã + assert_undefined_in("\xA5\xA0", 'GBK') + check_both_ways("\u30A1", "\xA5\xA1", 'GBK') # ã‚¡ + check_both_ways("\u0391", "\xA6\xA1", 'GBK') # Α + check_both_ways("\u03B1", "\xA6\xC1", 'GBK') # α + assert_undefined_in("\xA6\xED", 'GBK') + check_both_ways("\uFE3B", "\xA6\xEE", 'GBK') # ︻ + check_both_ways("\u0410", "\xA7\xA1", 'GBK') # Ð + check_both_ways("\u0430", "\xA7\xD1", 'GBK') # а + check_both_ways("\u02CA", "\xA8\x40", 'GBK') # ËŠ + check_both_ways("\u2587", "\xA8\x7E", 'GBK') # â–‡ + assert_undefined_in("\xA8\x96", 'GBK') + check_both_ways("\u0101", "\xA8\xA1", 'GBK') # Ä + assert_undefined_in("\xA8\xBC", 'GBK') + assert_undefined_in("\xA8\xBF", 'GBK') + assert_undefined_in("\xA8\xC4", 'GBK') + check_both_ways("\u3105", "\xA8\xC5", 'GBK') # ã„… + check_both_ways("\u3021", "\xA9\x40", 'GBK') # 〡 + assert_undefined_in("\xA9\x58", 'GBK') + assert_undefined_in("\xA9\x5B", 'GBK') + assert_undefined_in("\xA9\x5D", 'GBK') + check_both_ways("\u3007", "\xA9\x96", 'GBK') # 〇 + assert_undefined_in("\xA9\xA3", 'GBK') + check_both_ways("\u2500", "\xA9\xA4", 'GBK') # ─ + assert_undefined_in("\xA9\xF0", 'GBK') + check_both_ways("\u7588", "\xAF\x40", 'GBK') # ç–ˆ + check_both_ways("\u7607", "\xAF\x7E", 'GBK') # 瘇 + check_both_ways("\u7608", "\xAF\x80", 'GBK') # 瘈 + check_both_ways("\u7644", "\xAF\xA0", 'GBK') # 癄 + assert_undefined_in("\xAF\xA1", 'GBK') + check_both_ways("\u7645", "\xB0\x40", 'GBK') # ç™… + check_both_ways("\u769B", "\xB0\x7E", 'GBK') # çš› + check_both_ways("\u769C", "\xB0\x80", 'GBK') # çšœ + check_both_ways("\u5265", "\xB0\xFE", 'GBK') # 剥 + check_both_ways("\u7DFB", "\xBF\x40", 'GBK') # ç·» + check_both_ways("\u7E39", "\xBF\x7E", 'GBK') # 縹 + check_both_ways("\u7E3A", "\xBF\x80", 'GBK') # 縺 + check_both_ways("\u5080", "\xBF\xFE", 'GBK') # å‚€ + check_both_ways("\u7E5E", "\xC0\x40", 'GBK') # 繞 + check_both_ways("\u7E9E", "\xC0\x7E", 'GBK') # 纞 + check_both_ways("\u7EAE", "\xC0\x80", 'GBK') # 纮 + check_both_ways("\u4FD0", "\xC0\xFE", 'GBK') # ä¿ + check_both_ways("\u87A5", "\xCF\x40", 'GBK') # 螥 + check_both_ways("\u87F8", "\xCF\x7E", 'GBK') # 蟸 + check_both_ways("\u87FA", "\xCF\x80", 'GBK') # 蟺 + check_both_ways("\u6653", "\xCF\xFE", 'GBK') # 晓 + check_both_ways("\u8824", "\xD0\x40", 'GBK') # è ¤ + check_both_ways("\u887A", "\xD0\x7E", 'GBK') # 衺 + check_both_ways("\u887B", "\xD0\x80", 'GBK') # è¡» + check_both_ways("\u7384", "\xD0\xFE", 'GBK') # 玄 + check_both_ways("\u9019", "\xDF\x40", 'GBK') # 這 + check_both_ways("\u9081", "\xDF\x7E", 'GBK') # é‚ + check_both_ways("\u9084", "\xDF\x80", 'GBK') # é‚„ + check_both_ways("\u553C", "\xDF\xFE", 'GBK') # 唼 + check_both_ways("\u90C2", "\xE0\x40", 'GBK') # 郂 + check_both_ways("\u911C", "\xE0\x7E", 'GBK') # 鄜 + check_both_ways("\u911D", "\xE0\x80", 'GBK') # é„ + check_both_ways("\u5E3C", "\xE0\xFE", 'GBK') # 帼 + check_both_ways("\u986F", "\xEF\x40", 'GBK') # 顯 + check_both_ways("\u98E4", "\xEF\x7E", 'GBK') # 飤 + check_both_ways("\u98E5", "\xEF\x80", 'GBK') # 飥 + check_both_ways("\u7A14", "\xEF\xFE", 'GBK') # 稔 + check_both_ways("\u9908", "\xF0\x40", 'GBK') # 餈 + check_both_ways("\u9949", "\xF0\x7E", 'GBK') # 饉 + check_both_ways("\u994A", "\xF0\x80", 'GBK') # 饊 + check_both_ways("\u7619", "\xF0\xFE", 'GBK') # 瘙 + check_both_ways("\u9F32", "\xFD\x40", 'GBK') # é¼² + check_both_ways("\u9F78", "\xFD\x7E", 'GBK') # 齸 + check_both_ways("\u9F79", "\xFD\x80", 'GBK') # é½¹ + check_both_ways("\uF9F1", "\xFD\xA0", 'GBK') # ï§± + assert_undefined_in("\xFD\xA1", 'GBK') + check_both_ways("\uFA0C", "\xFE\x40", 'GBK') # 兀 + check_both_ways("\uFA29", "\xFE\x4F", 'GBK') # 﨩 + assert_undefined_in("\xFE\x50", 'GBK') + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GBK') # é’å±±å¦é™¢å¤§å¦ + check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GBK') # ç¥žæž—ç¾©åš + end + + def test_gb18030 + # overall roundtrip test + all_unicode = (0x0..0xD7FF).to_a.pack 'U*' #è¿½åŠ + all_unicode << (0xE000..0xFFFF).to_a.pack("U*") #è¿½åŠ + + assert_equal(all_unicode, all_unicode.encode("gb18030").encode("UTF-8")) #è¿½åŠ + + # tests from GBK + check_both_ways("\u4E02", "\x81\x40", 'GB18030') # + check_both_ways("\u4E8A", "\x81\x7E", 'GB18030') # + check_both_ways("\u4E90", "\x81\x80", 'GB18030') # + check_both_ways("\u4FA2", "\x81\xFE", 'GB18030') # ä¾¢ + check_both_ways("\u5EC6", "\x8F\x40", 'GB18030') # + check_both_ways("\u5F24", "\x8F\x7E", 'GB18030') # 弤 + check_both_ways("\u5F28", "\x8F\x80", 'GB18030') # 弨 + check_both_ways("\u6007", "\x8F\xFE", 'GB18030') # + check_both_ways("\u6008", "\x90\x40", 'GB18030') # + check_both_ways("\u6080", "\x90\x7E", 'GB18030') # æ‚€ + check_both_ways("\u6081", "\x90\x80", 'GB18030') # + check_both_ways("\u6146", "\x90\xFE", 'GB18030') # + check_both_ways("\u70DC", "\x9F\x40", 'GB18030') # + check_both_ways("\u7134", "\x9F\x7E", 'GB18030') # ç„´ + check_both_ways("\u7135", "\x9F\x80", 'GB18030') # 焵 + check_both_ways("\u71D3", "\x9F\xFE", 'GB18030') # + check_both_ways("\u71D6", "\xA0\x40", 'GB18030') # + check_both_ways("\u721A", "\xA0\x7E", 'GB18030') # + check_both_ways("\u721B", "\xA0\x80", 'GB18030') # + check_both_ways("\u72DB", "\xA0\xFE", 'GB18030') # + check_both_ways("\u3000", "\xA1\xA1", 'GB18030') # full-width space + check_both_ways("\u3001", "\xA1\xA2", 'GB18030') # + check_both_ways("\u3013", "\xA1\xFE", 'GB18030') # + #assert_undefined_in("\xA2\xA0", 'GB18030') + check_both_ways("\u2170", "\xA2\xA1", 'GB18030') # â…° + #assert_undefined_in("\xA2\xB0", 'GB18030') + check_both_ways("\u2488", "\xA2\xB1", 'GB18030') # + #assert_undefined_in("\xA2\xE4", 'GB18030') + check_both_ways("\u3220", "\xA2\xE5", 'GB18030') # ㈠+ #assert_undefined_in("\xA2\xF0", 'GB18030') + check_both_ways("\u2160", "\xA2\xF1", 'GB18030') # â… + #assert_undefined_in("\xA3\xA0", 'GB18030') + check_both_ways("\uFF01", "\xA3\xA1", 'GB18030') # E + check_both_ways("\uFFE3", "\xA3\xFE", 'GB18030') # E + #assert_undefined_in("\xA4\xA0", 'GB18030') + check_both_ways("\u3041", "\xA4\xA1", 'GB18030') # + #assert_undefined_in("\xA5\xA0", 'GB18030') + check_both_ways("\u30A1", "\xA5\xA1", 'GB18030') # ã‚¡ + check_both_ways("\u0391", "\xA6\xA1", 'GB18030') # + check_both_ways("\u03B1", "\xA6\xC1", 'GB18030') # α + #assert_undefined_in("\xA6\xED", 'GB18030') + check_both_ways("\uFE3B", "\xA6\xEE", 'GB18030') # E + check_both_ways("\u0410", "\xA7\xA1", 'GB18030') # + check_both_ways("\u0430", "\xA7\xD1", 'GB18030') # а + check_both_ways("\u02CA", "\xA8\x40", 'GB18030') # + check_both_ways("\u2587", "\xA8\x7E", 'GB18030') # + #assert_undefined_in("\xA8\x96", 'GB18030') + check_both_ways("\u0101", "\xA8\xA1", 'GB18030') # + #assert_undefined_in("\xA8\xBC", 'GB18030') + #assert_undefined_in("\xA8\xBF", 'GB18030') + #assert_undefined_in("\xA8\xC4", 'GB18030') + check_both_ways("\u3105", "\xA8\xC5", 'GB18030') # + check_both_ways("\u3021", "\xA9\x40", 'GB18030') # 〡 + #assert_undefined_in("\xA9\x58", 'GB18030') + #assert_undefined_in("\xA9\x5B", 'GB18030') + #assert_undefined_in("\xA9\x5D", 'GB18030') + check_both_ways("\u3007", "\xA9\x96", 'GB18030') # + #assert_undefined_in("\xA9\xA3", 'GB18030') + check_both_ways("\u2500", "\xA9\xA4", 'GB18030') # ─ + #assert_undefined_in("\xA9\xF0", 'GB18030') + check_both_ways("\u7588", "\xAF\x40", 'GB18030') # + check_both_ways("\u7607", "\xAF\x7E", 'GB18030') # + check_both_ways("\u7608", "\xAF\x80", 'GB18030') # + check_both_ways("\u7644", "\xAF\xA0", 'GB18030') # + #assert_undefined_in("\xAF\xA1", 'GB18030') + check_both_ways("\u7645", "\xB0\x40", 'GB18030') # + check_both_ways("\u769B", "\xB0\x7E", 'GB18030') # + check_both_ways("\u769C", "\xB0\x80", 'GB18030') # + check_both_ways("\u5265", "\xB0\xFE", 'GB18030') # 剥 + check_both_ways("\u7DFB", "\xBF\x40", 'GB18030') # ç·» + check_both_ways("\u7E39", "\xBF\x7E", 'GB18030') # 縹 + check_both_ways("\u7E3A", "\xBF\x80", 'GB18030') # 縺 + check_both_ways("\u5080", "\xBF\xFE", 'GB18030') # å‚€ + check_both_ways("\u7E5E", "\xC0\x40", 'GB18030') # + check_both_ways("\u7E9E", "\xC0\x7E", 'GB18030') # + check_both_ways("\u7EAE", "\xC0\x80", 'GB18030') # 纮 + check_both_ways("\u4FD0", "\xC0\xFE", 'GB18030') # + check_both_ways("\u87A5", "\xCF\x40", 'GB18030') # 螥 + check_both_ways("\u87F8", "\xCF\x7E", 'GB18030') # 蟸 + check_both_ways("\u87FA", "\xCF\x80", 'GB18030') # 蟺 + check_both_ways("\u6653", "\xCF\xFE", 'GB18030') # + check_both_ways("\u8824", "\xD0\x40", 'GB18030') # è ¤ + check_both_ways("\u887A", "\xD0\x7E", 'GB18030') # 衺 + check_both_ways("\u887B", "\xD0\x80", 'GB18030') # è¡» + check_both_ways("\u7384", "\xD0\xFE", 'GB18030') # + check_both_ways("\u9019", "\xDF\x40", 'GB18030') # + check_both_ways("\u9081", "\xDF\x7E", 'GB18030') # + check_both_ways("\u9084", "\xDF\x80", 'GB18030') # + check_both_ways("\u553C", "\xDF\xFE", 'GB18030') # 唼 + check_both_ways("\u90C2", "\xE0\x40", 'GB18030') # + check_both_ways("\u911C", "\xE0\x7E", 'GB18030') # + check_both_ways("\u911D", "\xE0\x80", 'GB18030') # + check_both_ways("\u5E3C", "\xE0\xFE", 'GB18030') # 帼 + check_both_ways("\u986F", "\xEF\x40", 'GB18030') # 顯 + check_both_ways("\u98E4", "\xEF\x7E", 'GB18030') # 飤 + check_both_ways("\u98E5", "\xEF\x80", 'GB18030') # 飥 + check_both_ways("\u7A14", "\xEF\xFE", 'GB18030') # + check_both_ways("\u9908", "\xF0\x40", 'GB18030') # + check_both_ways("\u9949", "\xF0\x7E", 'GB18030') # + check_both_ways("\u994A", "\xF0\x80", 'GB18030') # + check_both_ways("\u7619", "\xF0\xFE", 'GB18030') # + check_both_ways("\u9F32", "\xFD\x40", 'GB18030') # é¼² + check_both_ways("\u9F78", "\xFD\x7E", 'GB18030') # 齸 + check_both_ways("\u9F79", "\xFD\x80", 'GB18030') # é½¹ + check_both_ways("\uF9F1", "\xFD\xA0", 'GB18030') # E + #assert_undefined_in("\xFD\xA1", 'GB18030') + check_both_ways("\uFA0C", "\xFE\x40", 'GB18030') # E + check_both_ways("\uFA29", "\xFE\x4F", 'GB18030') # E + #assert_undefined_in("\xFE\x50", 'GB18030') + check_both_ways("\u9752\u5C71\u5B66\u9662\u5927\u5B66", "\xC7\xE0\xC9\xBD\xD1\xA7\xD4\xBA\xB4\xF3\xD1\xA7", 'GB18030') # é’å±±å¦é™¢å¤§å¦ + check_both_ways("\u795E\u6797\u7FA9\u535A", "\xC9\xF1\xC1\xD6\xC1\x78\xB2\xA9", 'GB18030') # 神林義 + + # new tests for GB18030 + check_both_ways("\u9FA6", "\x82\x35\x8F\x33", 'GB18030') # 龦 + check_both_ways("\uD7FF", "\x83\x36\xC7\x38", 'GB18030') # No name () + + check_both_ways("\u0452", "\x81\x30\xD3\x30", 'GB18030') # + check_both_ways("\u200F", "\x81\x36\xA5\x31", 'GB18030') # RIGHT-TO-LEFT MARK + + check_both_ways("\uE865", "\x83\x36\xD0\x30", 'GB18030') # No name (Private Use Area) + check_both_ways("\uF92B", "\x84\x30\x85\x34", 'GB18030') # E + + check_both_ways("\u2643", "\x81\x37\xA8\x39", 'GB18030') # + check_both_ways("\u2E80", "\x81\x38\xFD\x38", 'GB18030') # ⺀ + + check_both_ways("\uFA2A", "\x84\x30\x9C\x38", 'GB18030') # E + check_both_ways("\uFE2F", "\x84\x31\x85\x37", 'GB18030') # No name (Combining Half Marks) + + check_both_ways("\u3CE1", "\x82\x31\xD4\x38", 'GB18030') # 㳡 + check_both_ways("\u4055", "\x82\x32\xAF\x32", 'GB18030') # + + check_both_ways("\u361B", "\x82\x30\xA6\x33", 'GB18030') # + check_both_ways("\u3917", "\x82\x30\xF2\x37", 'GB18030') # + + check_both_ways("\u49B8", "\x82\x34\xA1\x31", 'GB18030') # 䦸 + check_both_ways("\u4C76", "\x82\x34\xE7\x33", 'GB18030') # ä±¶ + + check_both_ways("\u4160", "\x82\x32\xC9\x37", 'GB18030') # ä… + check_both_ways("\u4336", "\x82\x32\xF8\x37", 'GB18030') # 䌶 + + check_both_ways("\u478E", "\x82\x33\xE8\x38", 'GB18030') # + check_both_ways("\u4946", "\x82\x34\x96\x38", 'GB18030') # + + check_both_ways("\u44D7", "\x82\x33\xA3\x39", 'GB18030') # + check_both_ways("\u464B", "\x82\x33\xC9\x31", 'GB18030') # + + check_both_ways("\uFFE6", "\x84\x31\xA2\x34", 'GB18030') # E + check_both_ways("\uFFFF", "\x84\x31\xA4\x39", 'GB18030') # not a character + + check_both_ways("\u{10000}", "\x90\x30\x81\x30", 'GB18030') # ð€€ + check_both_ways("\u{10FFFE}", "\xE3\x32\x9A\x34", 'GB18030') # No name (Not a character) + check_both_ways("\u{10FFFF}", "\xE3\x32\x9A\x35", 'GB18030') # No name (Not a character) + end + + def test_Big5 + check_both_ways("\u3000", "\xA1\x40", 'Big5') # full-width space + check_both_ways("\uFE5A", "\xA1\x7E", 'Big5') # ﹚ + check_both_ways("\uFE5B", "\xA1\xA1", 'Big5') # ï¹› + #check_both_ways("\uFF0F", "\xA1\xFE", 'Big5') # ï¼ + check_both_ways("\uFF57", "\xA3\x40", 'Big5') # ï½— + check_both_ways("\u310F", "\xA3\x7E", 'Big5') # ã„ + check_both_ways("\u3110", "\xA3\xA1", 'Big5') # ã„ + check_both_ways("\u02CB", "\xA3\xBF", 'Big5') # Ë‹ + assert_undefined_in("\xA3\xC0", 'Big5') + check_both_ways("\u6D6C", "\xAF\x40", 'Big5') # 浬 + check_both_ways("\u7837", "\xAF\x7E", 'Big5') # ç · + check_both_ways("\u7825", "\xAF\xA1", 'Big5') # ç ¥ + check_both_ways("\u8343", "\xAF\xFE", 'Big5') # èƒ + check_both_ways("\u8654", "\xB0\x40", 'Big5') # è™” + check_both_ways("\u9661", "\xB0\x7E", 'Big5') # 陡 + check_both_ways("\u965B", "\xB0\xA1", 'Big5') # é™› + check_both_ways("\u5A40", "\xB0\xFE", 'Big5') # å©€ + check_both_ways("\u6FC3", "\xBF\x40", 'Big5') # 濃 + check_both_ways("\u7E0A", "\xBF\x7E", 'Big5') # 縊 + check_both_ways("\u7E11", "\xBF\xA1", 'Big5') # 縑 + check_both_ways("\u931A", "\xBF\xFE", 'Big5') # 錚 + check_both_ways("\u9310", "\xC0\x40", 'Big5') # éŒ + check_both_ways("\u5687", "\xC0\x7E", 'Big5') # 嚇 + check_both_ways("\u568F", "\xC0\xA1", 'Big5') # åš + check_both_ways("\u77AC", "\xC0\xFE", 'Big5') # 瞬 + check_both_ways("\u8B96", "\xC6\x40", 'Big5') # è®– + check_both_ways("\u7C72", "\xC6\x7E", 'Big5') # ç±² + #assert_undefined_in("\xC6\xA1", 'Big5') + #assert_undefined_in("\xC7\x40", 'Big5') + #assert_undefined_in("\xC8\x40", 'Big5') + check_both_ways("\u4E42", "\xC9\x40", 'Big5') # 乂 + check_both_ways("\u6C15", "\xC9\x7E", 'Big5') # æ°• + check_both_ways("\u6C36", "\xC9\xA1", 'Big5') # æ°¶ + check_both_ways("\u6C4B", "\xC9\xFE", 'Big5') # 汋 + check_both_ways("\u67DC", "\xCF\x40", 'Big5') # 柜 + check_both_ways("\u6D42", "\xCF\x7E", 'Big5') # 浂 + check_both_ways("\u6D01", "\xCF\xA1", 'Big5') # æ´ + check_both_ways("\u7A80", "\xCF\xFE", 'Big5') # 窀 + check_both_ways("\u7A7E", "\xD0\x40", 'Big5') # 穾 + check_both_ways("\u82EA", "\xD0\x7E", 'Big5') # 苪 + check_both_ways("\u82E4", "\xD0\xA1", 'Big5') # 苤 + check_both_ways("\u54F1", "\xD0\xFE", 'Big5') # 哱 + check_both_ways("\u7A1B", "\xDF\x40", 'Big5') # 稛 + check_both_ways("\u816F", "\xDF\x7E", 'Big5') # è…¯ + check_both_ways("\u8144", "\xDF\xA1", 'Big5') # è…„ + check_both_ways("\u89E4", "\xDF\xFE", 'Big5') # 觤 + check_both_ways("\u89E1", "\xE0\x40", 'Big5') # è§¡ + check_both_ways("\u903F", "\xE0\x7E", 'Big5') # 逿 + check_both_ways("\u9044", "\xE0\xA1", 'Big5') # é„ + check_both_ways("\u50E0", "\xE0\xFE", 'Big5') # åƒ + check_both_ways("\u979E", "\xEF\x40", 'Big5') # éžž + check_both_ways("\u9D30", "\xEF\x7E", 'Big5') # é´° + check_both_ways("\u9D45", "\xEF\xA1", 'Big5') # éµ… + check_both_ways("\u7376", "\xEF\xFE", 'Big5') # ç¶ + check_both_ways("\u74B8", "\xF0\x40", 'Big5') # ç’¸ + check_both_ways("\u81D2", "\xF0\x7E", 'Big5') # 臒 + check_both_ways("\u81D0", "\xF0\xA1", 'Big5') # è‡ + check_both_ways("\u8E67", "\xF0\xFE", 'Big5') # è¹§ + check_both_ways("\u7E98", "\xF9\x40", 'Big5') # 纘 + check_both_ways("\u9F0A", "\xF9\x7E", 'Big5') # 鼊 + check_both_ways("\u9FA4", "\xF9\xA1", 'Big5') # 龤 + check_both_ways("\u9F98", "\xF9\xD5", 'Big5') # 龘 + #assert_undefined_in("\xF9\xD6", 'Big5') + check_both_ways("\u795E\u6797\u7FA9\u535A", "\xAF\xAB\xAA\x4C\xB8\x71\xB3\xD5", 'Big5') # ç¥žæž—ç¾©åš + end + + def test_Big5_Hkscs + check_both_ways("\u3000", "\xA1\x40", 'Big5-HKSCS') # full-width space + check_both_ways("\uFE5A", "\xA1\x7E", 'Big5-HKSCS') # ﹚ + check_both_ways("\uFE5B", "\xA1\xA1", 'Big5-HKSCS') # ï¹› + #check_both_ways("\uFF0F", "\xA1\xFE", 'Big5-HKSCS') # ï¼ + check_both_ways("\uFF57", "\xA3\x40", 'Big5-HKSCS') # ï½— + check_both_ways("\u310F", "\xA3\x7E", 'Big5-HKSCS') # ã„ + check_both_ways("\u3110", "\xA3\xA1", 'Big5-HKSCS') # ã„ + check_both_ways("\u02CB", "\xA3\xBF", 'Big5-HKSCS') # Ë‹ + #assert_undefined_in("\xA3\xC0", 'Big5-HKSCS') + check_both_ways("\u6D6C", "\xAF\x40", 'Big5-HKSCS') # 浬 + check_both_ways("\u7837", "\xAF\x7E", 'Big5-HKSCS') # ç · + check_both_ways("\u7825", "\xAF\xA1", 'Big5-HKSCS') # ç ¥ + check_both_ways("\u8343", "\xAF\xFE", 'Big5-HKSCS') # èƒ + check_both_ways("\u8654", "\xB0\x40", 'Big5-HKSCS') # è™” + check_both_ways("\u9661", "\xB0\x7E", 'Big5-HKSCS') # 陡 + check_both_ways("\u965B", "\xB0\xA1", 'Big5-HKSCS') # é™› + check_both_ways("\u5A40", "\xB0\xFE", 'Big5-HKSCS') # å©€ + check_both_ways("\u6FC3", "\xBF\x40", 'Big5-HKSCS') # 濃 + check_both_ways("\u7E0A", "\xBF\x7E", 'Big5-HKSCS') # 縊 + check_both_ways("\u7E11", "\xBF\xA1", 'Big5-HKSCS') # 縑 + check_both_ways("\u931A", "\xBF\xFE", 'Big5-HKSCS') # 錚 + check_both_ways("\u9310", "\xC0\x40", 'Big5-HKSCS') # éŒ + check_both_ways("\u5687", "\xC0\x7E", 'Big5-HKSCS') # 嚇 + check_both_ways("\u568F", "\xC0\xA1", 'Big5-HKSCS') # åš + 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_undefined_in("\xC6\xA1", 'Big5-HKSCS') + #assert_undefined_in("\xC7\x40", 'Big5-HKSCS') + #assert_undefined_in("\xC8\x40", 'Big5-HKSCS') + check_both_ways("\u4E42", "\xC9\x40", 'Big5-HKSCS') # 乂 + check_both_ways("\u6C15", "\xC9\x7E", 'Big5-HKSCS') # æ°• + check_both_ways("\u6C36", "\xC9\xA1", 'Big5-HKSCS') # æ°¶ + check_both_ways("\u6C4B", "\xC9\xFE", 'Big5-HKSCS') # 汋 + check_both_ways("\u67DC", "\xCF\x40", 'Big5-HKSCS') # 柜 + check_both_ways("\u6D42", "\xCF\x7E", 'Big5-HKSCS') # 浂 + check_both_ways("\u6D01", "\xCF\xA1", 'Big5-HKSCS') # æ´ + check_both_ways("\u7A80", "\xCF\xFE", 'Big5-HKSCS') # 窀 + check_both_ways("\u7A7E", "\xD0\x40", 'Big5-HKSCS') # 穾 + check_both_ways("\u82EA", "\xD0\x7E", 'Big5-HKSCS') # 苪 + check_both_ways("\u82E4", "\xD0\xA1", 'Big5-HKSCS') # 苤 + check_both_ways("\u54F1", "\xD0\xFE", 'Big5-HKSCS') # 哱 + check_both_ways("\u7A1B", "\xDF\x40", 'Big5-HKSCS') # 稛 + check_both_ways("\u816F", "\xDF\x7E", 'Big5-HKSCS') # è…¯ + check_both_ways("\u8144", "\xDF\xA1", 'Big5-HKSCS') # è…„ + check_both_ways("\u89E4", "\xDF\xFE", 'Big5-HKSCS') # 觤 + check_both_ways("\u89E1", "\xE0\x40", 'Big5-HKSCS') # è§¡ + check_both_ways("\u903F", "\xE0\x7E", 'Big5-HKSCS') # 逿 + check_both_ways("\u9044", "\xE0\xA1", 'Big5-HKSCS') # é„ + check_both_ways("\u50E0", "\xE0\xFE", 'Big5-HKSCS') # åƒ + check_both_ways("\u979E", "\xEF\x40", 'Big5-HKSCS') # éžž + check_both_ways("\u9D30", "\xEF\x7E", 'Big5-HKSCS') # é´° + check_both_ways("\u9D45", "\xEF\xA1", 'Big5-HKSCS') # éµ… + check_both_ways("\u7376", "\xEF\xFE", 'Big5-HKSCS') # ç¶ + check_both_ways("\u74B8", "\xF0\x40", 'Big5-HKSCS') # ç’¸ + check_both_ways("\u81D2", "\xF0\x7E", 'Big5-HKSCS') # 臒 + check_both_ways("\u81D0", "\xF0\xA1", 'Big5-HKSCS') # è‡ + check_both_ways("\u8E67", "\xF0\xFE", 'Big5-HKSCS') # è¹§ + check_both_ways("\u7E98", "\xF9\x40", 'Big5-HKSCS') # 纘 + 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') # 𣻗 + #assert_undefined_in("\xF9\xD6", '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_CESU_8 + check_both_ways("aijrszAIJRSZ09", "aijrszAIJRSZ09", 'CESU-8') # single bytes + + # check NULL explicitly + # this is different in CESU-8 and in Java modified UTF-8 strings + check_both_ways("\0", "\0", 'CESU-8') + + # U+0080 U+00FC U+00FF U+0100 U+0400 U+0700 U+07FF + two_byte_chars = "\xC2\x80\x20\xC3\xBC\x20\xC3\xBF\x20\xC4\x80\x20\xD0\x80\x20\xDC\x80\x20\xDF\xBF" + check_both_ways(two_byte_chars, two_byte_chars, 'CESU-8') + + # U+0800 U+2200 U+4E00 U+D7FF U+E000 U+FFFF + three_byte_chars = "\xE0\xA0\x80\x20\xE2\x88\x80\x20\xE4\xB8\x80\x20\xED\x9F\xBF\x20\xEE\x80\x80\x20\xEF\xBF\xBF" + check_both_ways(three_byte_chars, three_byte_chars, 'CESU-8') + + # characters outside BMP (double surrogates in CESU-8) + # U+10000 U+20000 U+50000 U+10FFFF + utf8 = "\xF0\x90\x80\x80 \xF0\xA0\x80\x80 \xF1\x90\x80\x80 \xF4\x8F\xBF\xBF" + cesu = "\xED\xA0\x80\xED\xB0\x80 \xED\xA1\x80\xED\xB0\x80 \xED\xA4\x80\xED\xB0\x80 \xED\xAF\xBF\xED\xBF\xBF" + check_both_ways(utf8, cesu, 'CESU-8') + end + + def test_nothing_changed + a = "James".force_encoding("US-ASCII") + b = a.encode("Shift_JIS") + assert_equal(Encoding::US_ASCII, a.encoding) + assert_equal(Encoding::Shift_JIS, b.encoding) + end + + def test_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\u0342\u0345", "\u{1ff7}".encode("UTF8-MAC").force_encoding("UTF-8")) + + check_both_ways("\u{e9 74 e8}", "e\u0301te\u0300", 'UTF8-MAC') + end + + def test_fallback + assert_equal("\u3042".encode("EUC-JP"), "\u{20000}".encode("EUC-JP", + fallback: {"\u{20000}" => "\u3042".encode("EUC-JP")})) + assert_equal("\u3042".encode("EUC-JP"), "\u{20000}".encode("EUC-JP", + fallback: {"\u{20000}" => "\u3042"})) + 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 + + def test_pseudo_encoding_inspect + s = 'aaa'.encode "UTF-16" + assert_equal '"\xFE\xFF\x00\x61\x00\x61\x00\x61"', s.inspect + + s = 'aaa'.encode "UTF-32" + assert_equal '"\x00\x00\xFE\xFF\x00\x00\x00\x61\x00\x00\x00\x61\x00\x00\x00\x61"', s.inspect + end + + def test_encode_with_invalid_chars + 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".dup.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_newline_options + bug11324 = '[ruby-core:69841] [Bug #11324]' + usascii = Encoding::US_ASCII + s = "A\nB\r\nC".force_encoding(usascii) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, universal_newline: true, undef: :replace, replace: ''), bug11324) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :universal, undef: :replace, replace: '')) + assert_equal("A\rB\r\rC", s.encode(usascii, cr_newline: true)) + assert_equal("A\rB\r\rC", s.encode(usascii, newline: :cr)) + assert_equal("A\r\nB\r\r\nC", s.encode(usascii, crlf_newline: true)) + assert_equal("A\r\nB\r\r\nC", s.encode(usascii, newline: :crlf)) + assert_equal("A\nB\nC", s.encode(usascii, lf_newline: true)) + assert_equal("A\nB\nC", s.encode(usascii, newline: :lf)) + end + + def test_ractor_lazy_load_encoding + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60) + begin; + rs = [] + autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze + 7.times do + rs << Ractor.new(autoload_encodings) do |encodings| + str = "\u0300" + encodings.each do |enc| + str.encode(enc) rescue Encoding::UndefinedConversionError + end + end + end + + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end + + def test_ractor_lazy_load_encoding_random + omit 'unstable on s390x and windows' if RUBY_PLATFORM =~ /s390x|mswin/ + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + rs = [] + 100.times do + rs << Ractor.new do + "\u0300".encode(Encoding.list.sample) rescue Encoding::UndefinedConversionError + end + end + + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end + + def test_ractor_asciicompat_encoding_exists + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + rs = [] + 7.times do + rs << Ractor.new do + string = "ISO-2022-JP" + encoding = Encoding.find(string) + 20_000.times do + Encoding::Converter.asciicompat_encoding(string) + Encoding::Converter.asciicompat_encoding(encoding) + end + end + end + + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end + + def test_ractor_asciicompat_encoding_doesnt_exist + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 60) + begin; + rs = [] + NO_EXIST = "I".freeze + 7.times do + rs << Ractor.new do + 50.times do + if (val = Encoding::Converter.asciicompat_encoding(NO_EXIST)) + raise "Got #{val}, expected nil" + end + end + end + end + + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert_empty rs + end; + end + + private + + def assert_conversion_both_ways_utf8(utf8, raw, encoding) + assert_conversion_both_ways(utf8, 'utf-8', raw, encoding) + end + alias check_both_ways assert_conversion_both_ways_utf8 + + def assert_conversion_both_ways(str1, enc1, str2, enc2) + message = str1.dump+str2.dump + assert_equal(str1.force_encoding(enc1), str2.encode(enc1, enc2), message) + assert_equal(str2.force_encoding(enc2), str1.encode(enc2, enc1), message) + end + alias check_both_ways2 assert_conversion_both_ways + + def assert_undefined_conversion(str, to, from = nil) + assert_raise(Encoding::UndefinedConversionError) { str.encode(to, from) } + end + + def assert_undefined_in(str, encoding) + assert_undefined_conversion(str, 'utf-8', encoding) + end + + def assert_invalid_byte_sequence(str, to, from = nil) + assert_raise(Encoding::InvalidByteSequenceError) { str.encode(to, from) } + end + + def assert_invalid_in(str, encoding) + assert_invalid_byte_sequence(str, 'utf-8', encoding) + end +end diff --git a/test/ruby/test_undef.rb b/test/ruby/test_undef.rb new file mode 100644 index 0000000000..074b92be55 --- /dev/null +++ b/test/ruby/test_undef.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestUndef < Test::Unit::TestCase + class Undef0 + def foo + "foo" + end + undef foo + end + + class Undef1 + def bar + "bar" + end + end + + class Undef2 < Undef1 + undef bar + end + + def test_undef + x = Undef0.new + assert_raise(NoMethodError) { x.foo } + y = Undef1.new + assert_equal "bar", y.bar + z = Undef2.new + assert_raise(NoMethodError) { z.bar } + end + + def test_special_const_undef + assert_raise(TypeError) do + 1.instance_eval do + undef to_s + end + end + end + + def test_singleton_undef + klass = Class.new do + def foo + :ok + end + end + + klass.new.foo + + klass.new.instance_eval do + undef foo + end + + klass.new.foo + end +end diff --git a/test/ruby/test_unicode_escape.rb b/test/ruby/test_unicode_escape.rb new file mode 100644 index 0000000000..5913bb0130 --- /dev/null +++ b/test/ruby/test_unicode_escape.rb @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require 'test/unit' + +class TestUnicodeEscape < Test::Unit::TestCase + def test_basic + assert_equal('Matz - æ¾æœ¬è¡Œå¼˜', + "Matz - \u677E\u672C\u884C\u5F18") + assert_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', + "Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D") + assert_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', + "Matz - \u{307E}\u{3064}\u{3082}\u{3068} \u{3086}\u{304D}\u{3072}\u{308D}") + assert_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', + "Matz - \u{307E 3064 3082 3068 20 3086 304D 3072 308D}") + assert_equal("Aoyama Gakuin University - \xE9\x9D\x92\xE5\xB1\xB1\xE5\xAD\xA6\xE9\x99\xA2\xE5\xA4\xA7\xE5\xAD\xA6", + "Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66") + assert_equal('Aoyama Gakuin University - é’å±±å¦é™¢å¤§å¦', + "Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66") + assert_equal('é’å±±å¦é™¢å¤§å¦', "\u9752\u5C71\u5B66\u9662\u5927\u5B66") + assert_equal("Martin D\xC3\xBCrst", "Martin D\u00FCrst") + assert_equal('Martin Dürst', "Martin D\u00FCrst") + assert_equal('ü', "\u00FC") + assert_equal("Martin D\xC3\xBCrst", "Martin D\u{FC}rst") + assert_equal('Martin Dürst', "Martin D\u{FC}rst") + assert_equal('ü', "\u{FC}") + assert_equal('ü', %Q|\u{FC}|) + assert_equal('ü', %W{\u{FC}}[0]) + + # \u escapes in here documents + assert_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', <<EOS.chop) +Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D +EOS + + assert_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', <<"EOS".chop) +Matz - \u{307E 3064 3082 3068} \u{3086 304D 3072 308D} +EOS + assert_not_equal('Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚', <<'EOS'.chop) +Matz - \u{307E 3064 3082 3068} \u{3086 304D 3072 308D} +EOS + + # single-quoted things don't expand \u + assert_not_equal('ü', '\u{FC}') + assert_not_equal('ü', %q|\u{FC}|) + assert_not_equal('ü', %w{\u{FC}}[0]) + assert_equal('\u00fc', "\\" + "u00fc") + + # \u in %x strings + assert_match(/^("?)A\1$/, `echo "\u0041"`) #" + assert_match(/^("?)A\1$/, %x{echo "\u0041"}) #" + assert_match(/^("?)ü\1$/, + `#{EnvUtil.rubybin} -e "#coding:utf-8\nputs \\"\\u{FC}\\""`.force_encoding("utf-8")) #" + + # \u in quoted symbols + assert_equal(:A, :"\u0041") + assert_equal(:a, :"\u0061") + assert_equal(:ã¾, :ã¾) + assert_equal(:ü, :ü) + assert_equal(:"\u{41}", :"\u0041") + assert_equal(:ü, :"\u{fc}") + + # the NUL character is allowed in symbols + bug = '[ruby-dev:41447]' + sym = "\0".to_sym + assert_nothing_raised(SyntaxError, bug) {assert_equal(sym, eval(%q(:"\u{0}")))} + assert_nothing_raised(SyntaxError, bug) {assert_equal(sym, eval(%q(:"\u0000")))} + assert_nothing_raised(SyntaxError, bug) {assert_equal("\u{fc}\0A".to_sym, eval(%q(:"\u{fc 0 0041}")))} + assert_nothing_raised(SyntaxError, bug) {assert_equal(sym, eval(%q(:"\x00")))} + assert_nothing_raised(SyntaxError, bug) {assert_equal(sym, eval(%q(:"\0")))} + end + + def test_regexp + + # Compare regexps to regexps + assert_not_equal(/Yukihiro Matsumoto - æ¾æœ¬è¡Œå¼˜/, + /Yukihiro Matsumoto - \u677E\u672C\u884C\u5F18/) + assert_not_equal(/Yukihiro Matsumoto - æ¾æœ¬è¡Œå¼˜/, + /Yukihiro Matsumoto - \u{677E 672C 884C 5F18}/) + assert_not_equal(/Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚/, + /Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D/) + assert_not_equal(/Aoyama Gakuin University - é’å±±å¦é™¢å¤§å¦/, + /Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66/) + assert_not_equal(/é’å±±å¦é™¢å¤§å¦/, /\u9752\u5C71\u5B66\u9662\u5927\u5B66/) + assert_not_equal(/Martin Dürst/, /Martin D\u00FCrst/) + assert_not_equal(/ü/, /\u00FC/) + assert_not_equal(/Martin Dürst/, /Martin D\u{FC}rst/) + assert_not_equal(/ü/, /\u{FC}/) + assert_not_equal(/ü/, %r{\u{FC}}) + assert_not_equal(/ü/i, %r{\u00FC}i) + + assert_equal('Yukihiro Matsumoto - \u677E\u672C\u884C\u5F18', + /Yukihiro Matsumoto - \u677E\u672C\u884C\u5F18/.source) + assert_equal('Yukihiro Matsumoto - \u{677E 672C 884C 5F18}', + /Yukihiro Matsumoto - \u{677E 672C 884C 5F18}/.source) + assert_equal('Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D', + /Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D/.source) + assert_equal('Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66', + /Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66/.source) + assert_equal('\u9752\u5C71\u5B66\u9662\u5927\u5B66', + /\u9752\u5C71\u5B66\u9662\u5927\u5B66/.source) + assert_equal('Martin D\u00FCrst', /Martin D\u00FCrst/.source) + assert_equal('\u00FC', /\u00FC/.source) + assert_equal('Martin D\u{FC}rst', /Martin D\u{FC}rst/.source) + assert_equal('\u{FC}', /\u{FC}/.source) + assert_equal('\u{FC}', %r{\u{FC}}.source) + assert_equal('\u00FC', %r{\u00FC}i.source) + + # match strings to regexps + assert_equal(0, "Yukihiro Matsumoto - æ¾æœ¬è¡Œå¼˜" =~ /Yukihiro Matsumoto - \u677E\u672C\u884C\u5F18/) + assert_equal(0, "Yukihiro Matsumoto - \u677E\u672C\u884C\u5F18" =~ /Yukihiro Matsumoto - \u677E\u672C\u884C/) + assert_equal(0, "Yukihiro Matsumoto - æ¾æœ¬è¡Œå¼˜" =~ /Yukihiro Matsumoto - \u{677E 672C 884C 5F18}/) + assert_equal(0, %Q{Yukihiro Matsumoto - \u{677E 672C 884C 5F18}} =~ /Yukihiro Matsumoto - \u{677E 672C 884C 5F18}/) + assert_equal(0, "Matz - ã¾ã¤ã‚‚㨠ゆãã²ã‚" =~ /Matz - \u307E\u3064\u3082\u3068 \u3086\u304D\u3072\u308D/) + assert_equal(0, "Aoyama Gakuin University - é’å±±å¦é™¢å¤§å¦" =~ /Aoyama Gakuin University - \u9752\u5C71\u5B66\u9662\u5927\u5B66/) + assert_equal(0, "é’å±±å¦é™¢å¤§å¦" =~ /\u9752\u5C71\u5B66\u9662\u5927\u5B66/) + assert_equal(0, "Martin Dürst" =~ /Martin D\u00FCrst/) + assert_equal(0, "ü" =~ /\u00FC/) + assert_equal(0, "Martin Dürst" =~ /Martin D\u{FC}rst/) + assert_equal(0, "ü" =~ %r{\u{FC}}) + assert_equal(0, "ü" =~ %r{\u00FC}i) + + # Flip order of the two operands + assert_equal(0, /Martin D\u00FCrst/ =~ "Martin Dürst") + assert_equal(4, /\u00FC/ =~ "testü") + assert_equal(3, /Martin D\u{FC}rst/ =~ "fooMartin Dürstbar") + assert_equal(3, %r{\u{FC}} =~ "fooübar") + + # Put \u in strings, literal character in regexp + assert_equal(0, "Martin D\u00FCrst" =~ /Martin Dürst/) + assert_equal(4, "test\u00FC" =~ /ü/) + assert_equal(3, "fooMartin D\u{FC}rstbar" =~ /Martin Dürst/) + assert_equal(3, %Q{foo\u{FC}bar} =~ %r<ü>) + + assert_match(eval('/\u{2a}/'), "*") + assert_raise(SyntaxError) { eval('/\u{6666}/n') } + assert_raise(SyntaxError) { eval('/\u{6666}/e') } + assert_raise(SyntaxError) { eval('/\u{6666}/s') } + assert_nothing_raised { eval('/\u{6666}/u') } + end + + def test_dynamic_regexp + assert_match(Regexp.new("Martin D\\u{FC}rst"), "Martin Dürst") + end + + def test_syntax_variants + # all hex digits + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89AB\uCDEF") + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89AB\uCDEF") + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89ab\ucdef") + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89ab\ucdef") + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89aB\uCdEf") + assert_equal("\xC4\xA3\xE4\x95\xA7\xE8\xA6\xAB\xEC\xB7\xAF", "\u0123\u4567\u89aB\ucDEF") + end + + def test_fulton + # examples from Hal Fulton's book (second edition), chapter 4 + # precomposed e'pe'e + assert_equal('épée', "\u00E9\u0070\u00E9\u0065") + assert_equal('épée', "\u00E9p\u00E9e") + assert_equal("\xC3\xA9\x70\xC3\xA9\x65", "\u00E9\u0070\u00E9\u0065") + assert_equal("\xC3\xA9\x70\xC3\xA9\x65", "\u00E9p\u00E9e") + # decomposed e'pe'e + assert_equal('eÌpeÌe', "\u0065\u0301\u0070\u0065\u0301\u0065") + assert_equal('eÌpeÌe', "e\u0301pe\u0301e") + assert_equal("\x65\xCC\x81\x70\x65\xCC\x81\x65", "\u0065\u0301\u0070\u0065\u0301\u0065") + assert_equal("\x65\xCC\x81\x70\x65\xCC\x81\x65", "e\u0301pe\u0301e") + # combinations of NFC/D, NFKC/D + assert_equal('öffnen', "\u00F6\u0066\u0066\u006E\u0065\u006E") + assert_equal("\xC3\xB6ffnen", "\u00F6\u0066\u0066\u006E\u0065\u006E") + assert_equal('öffnen', "\u00F6ffnen") + assert_equal("\xC3\xB6ffnen", "\u00F6ffnen") + assert_equal('öffnen', "\u006F\u0308\u0066\u0066\u006E\u0065\u006E") + assert_equal("\x6F\xCC\x88ffnen", "\u006F\u0308\u0066\u0066\u006E\u0065\u006E") + assert_equal('öffnen', "o\u0308ffnen") + assert_equal("\x6F\xCC\x88ffnen", "o\u0308ffnen") + assert_equal('öffnen', "\u00F6\uFB00\u006E\u0065\u006E") + assert_equal("\xC3\xB6\xEF\xAC\x80nen", "\u00F6\uFB00\u006E\u0065\u006E") + assert_equal('öffnen', "\u00F6\uFB00nen") + assert_equal("\xC3\xB6\xEF\xAC\x80nen", "\u00F6\uFB00nen") + assert_equal('öffnen', "\u006F\u0308\uFB00\u006E\u0065\u006E") + assert_equal("\x6F\xCC\x88\xEF\xAC\x80nen", "\u006F\u0308\uFB00\u006E\u0065\u006E") + assert_equal('öffnen', "o\u0308\uFB00nen") + assert_equal("\x6F\xCC\x88\xEF\xAC\x80nen", "o\u0308\uFB00nen") + # German sharp s (sz) + assert_equal('Straße', "\u0053\u0074\u0072\u0061\u00DF\u0065") + assert_equal("\x53\x74\x72\x61\xC3\x9F\x65", "\u0053\u0074\u0072\u0061\u00DF\u0065") + assert_equal('Straße', "Stra\u00DFe") + assert_equal("\x53\x74\x72\x61\xC3\x9F\x65", "Stra\u00DFe") + assert_equal('Straße', "\u{53}\u{74}\u{72}\u{61}\u{DF}\u{65}") + assert_equal("\x53\x74\x72\x61\xC3\x9F\x65", "\u{53}\u{74}\u{72}\u{61}\u{DF}\u{65}") + assert_equal("\x53\x74\x72\x61\xC3\x9F\x65", "\u{53 74 72 61 DF 65}") + assert_equal('Straße', "Stra\u{DF}e") + assert_equal("\x53\x74\x72\x61\xC3\x9F\x65", "Stra\u{DF}e") + end + + def test_edge_cases + # start and end of each outer plane + assert_equal("\xF4\x8F\xBF\xBF", "\u{10FFFF}") + assert_equal("\xF4\x80\x80\x80", "\u{100000}") + assert_equal("\xF3\xBF\xBF\xBF", "\u{FFFFF}") + assert_equal("\xF3\xB0\x80\x80", "\u{F0000}") + assert_equal("\xF3\xAF\xBF\xBF", "\u{EFFFF}") + assert_equal("\xF3\xA0\x80\x80", "\u{E0000}") + assert_equal("\xF3\x9F\xBF\xBF", "\u{DFFFF}") + assert_equal("\xF3\x90\x80\x80", "\u{D0000}") + assert_equal("\xF3\x8F\xBF\xBF", "\u{CFFFF}") + assert_equal("\xF3\x80\x80\x80", "\u{C0000}") + assert_equal("\xF2\xBF\xBF\xBF", "\u{BFFFF}") + assert_equal("\xF2\xB0\x80\x80", "\u{B0000}") + assert_equal("\xF2\xAF\xBF\xBF", "\u{AFFFF}") + assert_equal("\xF2\xA0\x80\x80", "\u{A0000}") + assert_equal("\xF2\x9F\xBF\xBF", "\u{9FFFF}") + assert_equal("\xF2\x90\x80\x80", "\u{90000}") + assert_equal("\xF2\x8F\xBF\xBF", "\u{8FFFF}") + assert_equal("\xF2\x80\x80\x80", "\u{80000}") + assert_equal("\xF1\xBF\xBF\xBF", "\u{7FFFF}") + assert_equal("\xF1\xB0\x80\x80", "\u{70000}") + assert_equal("\xF1\xAF\xBF\xBF", "\u{6FFFF}") + assert_equal("\xF1\xA0\x80\x80", "\u{60000}") + assert_equal("\xF1\x9F\xBF\xBF", "\u{5FFFF}") + assert_equal("\xF1\x90\x80\x80", "\u{50000}") + assert_equal("\xF1\x8F\xBF\xBF", "\u{4FFFF}") + assert_equal("\xF1\x80\x80\x80", "\u{40000}") + assert_equal("\xF0\xBF\xBF\xBF", "\u{3FFFF}") + assert_equal("\xF0\xB0\x80\x80", "\u{30000}") + assert_equal("\xF0\xAF\xBF\xBF", "\u{2FFFF}") + assert_equal("\xF0\xA0\x80\x80", "\u{20000}") + assert_equal("\xF0\x9F\xBF\xBF", "\u{1FFFF}") + assert_equal("\xF0\x90\x80\x80", "\u{10000}") + # BMP + assert_equal("\xEF\xBF\xBF", "\uFFFF") + assert_equal("\xEE\x80\x80", "\uE000") + assert_equal("\xED\x9F\xBF", "\uD7FF") + assert_equal("\xE0\xA0\x80", "\u0800") + assert_equal("\xDF\xBF", "\u07FF") + assert_equal("\xC2\x80", "\u0080") + assert_equal("\x7F", "\u007F") + assert_equal("\x00", "\u0000") + end + + def test_chars + assert_equal(?\u0041, ?A) + assert_equal(?\u{79}, ?\x79) + assert_equal(?\u{0}, ?\000) + assert_equal(?\u0000, ?\000) + end + + # Tests to make sure that disallowed cases fail + def test_fail + assert_raise(SyntaxError) { eval %q("\uabc") } # too short + assert_raise(SyntaxError) { eval %q("\uab") } # too short + assert_raise(SyntaxError) { eval %q("\ua") } # too short + assert_raise(SyntaxError) { eval %q("\u") } # too short + assert_raise(SyntaxError) { eval %q("\u{110000}") } # too high + assert_raise(SyntaxError) { eval %q("\u{abcdeff}") } # too long + assert_raise(SyntaxError) { eval %q("\ughij") } # bad hex digits + assert_raise(SyntaxError) { eval %q("\u{ghij}") } # bad hex digits + + assert_raise_with_message(SyntaxError, /invalid/) { + eval %q("\u{123.}") # bad char + } + + # 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 new file mode 100644 index 0000000000..13b8a7905f --- /dev/null +++ b/test/ruby/test_variable.rb @@ -0,0 +1,552 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestVariable < Test::Unit::TestCase + class Gods + @@rule = "Uranus" + def ruler0 + @@rule + end + + def self.ruler1 # <= per method definition style + @@rule + end + class << self # <= multiple method definition style + def ruler2 + @@rule + end + end + end + + module Olympians + @@rule ="Zeus" + def ruler3 + @@rule + end + end + + class Titans < Gods + @@rule = "Cronus" # modifies @@rule in Gods + include Olympians + def ruler4 + @@rule + end + end + + Athena = Gods.clone + + def test_cloned_classes_copy_cvar_cache + assert_equal "Cronus", Athena.new.ruler0 + end + + def test_setting_class_variable_on_module_through_inheritance + mod = Module.new + mod.class_variable_set(:@@foo, 1) + mod.freeze + c = Class.new { include(mod) } + assert_raise(FrozenError) { c.class_variable_set(:@@foo, 2) } + assert_raise(FrozenError) { c.class_eval("@@foo = 2") } + assert_equal(1, c.class_variable_get(:@@foo)) + end + + Zeus = Gods.clone + + def test_cloned_allows_setting_cvar + Zeus.class_variable_set(:@@rule, "Athena") + + god = Gods.new.ruler0 + zeus = Zeus.new.ruler0 + + assert_equal "Cronus", god + assert_equal "Athena", zeus + assert_not_equal god.object_id, zeus.object_id + end + + def test_singleton_class_included_class_variable + c = Class.new + c.extend(Olympians) + assert_empty(c.singleton_class.class_variables) + assert_raise(NameError){ c.singleton_class.class_variable_get(:@@rule) } + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo], c.singleton_class.class_variables) + assert_equal(1, c.singleton_class.class_variable_get(:@@foo)) + + c = Class.new + c.extend(Olympians) + sc = Class.new(c) + assert_empty(sc.singleton_class.class_variables) + assert_raise(NameError){ sc.singleton_class.class_variable_get(:@@rule) } + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo], sc.singleton_class.class_variables) + assert_equal(1, sc.singleton_class.class_variable_get(:@@foo)) + + c = Class.new + o = c.new + o.extend(Olympians) + assert_equal([:@@rule], o.singleton_class.class_variables) + assert_equal("Zeus", o.singleton_class.class_variable_get(:@@rule)) + c.class_variable_set(:@@foo, 1) + assert_equal([:@@foo, :@@rule], o.singleton_class.class_variables.sort) + assert_equal(1, o.singleton_class.class_variable_get(:@@foo)) + end + + def test_cvar_overtaken_by_parent_class + error = eval <<~EORB + class Parent + end + + class Child < Parent + @@cvar = 1 + + def self.cvar + @@cvar + end + end + + assert_equal 1, Child.cvar + + class Parent + @@cvar = 2 + end + + assert_raise RuntimeError do + Child.cvar + end + EORB + + assert_equal "class variable @@cvar of TestVariable::Child is overtaken by TestVariable::Parent", error.message + ensure + TestVariable.send(:remove_const, :Child) rescue nil + TestVariable.send(:remove_const, :Parent) rescue nil + end + + def test_cvar_overtaken_by_module + error = eval <<~EORB + class ParentForModule + @@cvar = 1 + + def self.cvar + @@cvar + end + end + + assert_equal 1, ParentForModule.cvar + + module Mixin + @@cvar = 2 + end + + class ParentForModule + include Mixin + end + + assert_raise RuntimeError do + ParentForModule.cvar + end + EORB + + assert_equal "class variable @@cvar of TestVariable::ParentForModule is overtaken by TestVariable::Mixin", error.message + ensure + TestVariable.send(:remove_const, :Mixin) rescue nil + TestVariable.send(:remove_const, :ParentForModule) rescue nil + end + + class IncludeRefinedModuleClassVariableNoWarning + module Mod + @@_test_include_refined_module_class_variable = true + end + + module Mod2 + refine Mod do + end + end + + include Mod + + def t + @@_test_include_refined_module_class_variable + end + end + + def test_include_refined_module_class_variable + assert_warning('') do + IncludeRefinedModuleClassVariableNoWarning.new.t + end + end + + def test_set_class_variable_on_frozen_object + set_cvar = EnvUtil.labeled_class("SetCVar") + set_cvar.class_eval "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + def self.set(val) + @@a = val # inline cache + end + end; + set_cvar.set(1) # fill write cache + set_cvar.freeze + assert_raise(FrozenError, "[Bug #19341]") do + set_cvar.set(2) # hit write cache, but should check frozen status + end + end + + def test_variable + assert_instance_of(Integer, $$) + + # read-only variable + assert_raise(NameError) do + $$ = 5 + end + assert_normal_exit("$*=0; $*", "[ruby-dev:36698]") + + foobar = "foobar" + $_ = foobar + assert_equal(foobar, $_) + + assert_equal("Cronus", Gods.new.ruler0) + assert_equal("Cronus", Gods.ruler1) + assert_equal("Cronus", Gods.ruler2) + assert_equal("Cronus", Titans.ruler1) + assert_equal("Cronus", Titans.ruler2) + atlas = Titans.new + assert_equal("Cronus", atlas.ruler0) + assert_equal("Zeus", atlas.ruler3) + assert_raise(RuntimeError) { 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 + x = 1 + proc do |y| + assert_equal([:x, :y], local_variables.sort) + end.call + x + end + + def test_local_variables3 + x = 1 + proc do |y| + 1.times do |z| + 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 prepare_klass_for_test_svar_with_ifunc + Class.new do + include Enumerable + def each(&b) + @b = b + end + + def check1 + check2.merge({check1: $1}) + end + + def check2 + @b.call('foo') + {check2: $1} + end + end + end + + def test_svar_with_ifunc + c = prepare_klass_for_test_svar_with_ifunc + + expected_check1_result = { + check1: nil, check2: nil + }.freeze + + obj = c.new + result = nil + obj.grep(/(f..)/){ + result = $1 + } + assert_equal nil, result + assert_equal nil, $1 + assert_equal expected_check1_result, obj.check1 + assert_equal 'foo', result + assert_equal 'foo', $1 + + # this frame was escaped so try it again + $~ = nil + obj = c.new + result = nil + obj.grep(/(f..)/){ + result = $1 + } + assert_equal nil, result + assert_equal nil, $1 + assert_equal expected_check1_result, obj.check1 + assert_equal 'foo', result + assert_equal 'foo', $1 + + # different context + result = nil + Fiber.new{ + obj = c.new + obj.grep(/(f..)/){ + result = $1 + } + }.resume # obj is created in antoher Fiber + assert_equal nil, result + assert_equal expected_check1_result, obj.check1 + assert_equal 'foo', result + assert_equal 'foo', $1 + + # different thread context + result = nil + Thread.new{ + obj = c.new + obj.grep(/(f..)/){ + result = $1 + } + }.join # obj is created in another Thread + + assert_equal nil, result + assert_equal expected_check1_result, obj.check1 + assert_equal 'foo', result + assert_equal 'foo', $1 + end + + + def test_global_variable_0 + assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, []) + end + + 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}: #{v.inspect}" + + assert_raise_with_message(FrozenError, msg) do + v.instance_variable_set(:@foo, :bar) + end + + assert_raise_with_message(FrozenError, msg, "[Bug #19339]") do + v.instance_eval do + @a = 1 + end + end + + assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)} + assert_not_send([v, :instance_variable_defined?, :@foo]) + + assert_raise_with_message(FrozenError, msg) do + v.remove_instance_variable(:@foo) + end + end + end + + class RemoveIvar + class << self + attr_reader :ivar + + def add_ivar + @ivar = 1 + end + end + + attr_reader :ivar + + def add_ivar + @ivar = 1 + end + end + + def add_and_remove_ivar(obj) + assert_nil obj.ivar + assert_equal 1, obj.add_ivar + assert_equal 1, obj.instance_variable_get(:@ivar) + assert_equal 1, obj.ivar + + obj.remove_instance_variable(:@ivar) + assert_nil obj.ivar + + assert_raise NameError do + obj.remove_instance_variable(:@ivar) + end + end + + def test_remove_instance_variables_object + obj = RemoveIvar.new + add_and_remove_ivar(obj) + add_and_remove_ivar(obj) + end + + def test_remove_instance_variables_class + add_and_remove_ivar(RemoveIvar) + add_and_remove_ivar(RemoveIvar) + end + + class RemoveIvarGeneric < Array + attr_reader :ivar + + def add_ivar + @ivar = 1 + end + end + + def test_remove_instance_variables_generic + obj = RemoveIvarGeneric.new + add_and_remove_ivar(obj) + add_and_remove_ivar(obj) + end + + class ExIvar < Hash + def initialize + @a = 1 + @b = 2 + @c = 3 + end + + def ivars + [@a, @b, @c] + end + end + + def test_external_ivars + 3.times{ + # check inline cache for external ivar access + assert_equal [1, 2, 3], ExIvar.new.ivars + } + end + + def test_exivar_resize_with_compaction_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + objs = 10_000.times.map do + ExIvar.new + end + EnvUtil.under_gc_compact_stress do + 10.times do + x = ExIvar.new + x.instance_variable_set(:@resize, 1) + x + end + end + objs or flunk + 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_many_instance_variables + objects = [Object.new, Hash.new, Module.new] + objects.each do |obj| + 1000.times do |i| + obj.instance_variable_set("@var#{i}", i) + end + 1000.times do |i| + assert_equal(i, obj.instance_variable_get("@var#{i}")) + end + end + end + + def test_local_variables_encoding + α = 1 or flunk + b = binding + b.eval("".encode("us-ascii")) + assert_equal(%i[α b], b.local_variables) + end + + def test_genivar_cache + bug21547 = '[Bug #21547]' + klass = Class.new(Array) + instance = klass.new + instance.instance_variable_set(:@a1, 1) + instance.instance_variable_set(:@a2, 2) + Fiber.new do + instance.instance_variable_set(:@a3, 3) + instance.instance_variable_set(:@a4, 4) + end.resume + assert_equal 4, instance.instance_variable_get(:@a4), bug21547 + end + + def test_genivar_cache_free + str = +"hello" + str.instance_variable_set(:@x, :old_value) + + str.instance_variable_get(:@x) # populate cache + + Fiber.new { + str.remove_instance_variable(:@x) + str.instance_variable_set(:@x, :new_value) + }.resume + + assert_equal :new_value, str.instance_variable_get(:@x) + end + + def test_genivar_cache_invalidated_by_gc + str = +"hello" + str.instance_variable_set(:@x, :old_value) + + str.instance_variable_get(:@x) # populate cache + + Fiber.new { + str.remove_instance_variable(:@x) + str.instance_variable_set(:@x, :new_value) + }.resume + + GC.start + + assert_equal :new_value, str.instance_variable_get(:@x) + end + + 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..d183e03391 --- /dev/null +++ b/test/ruby/test_vm_dump.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +require 'test/unit' + +return unless /darwin/ =~ RUBY_PLATFORM + +class TestVMDump < Test::Unit::TestCase + def assert_darwin_vm_dump_works(args, timeout=nil) + args.unshift({"RUBY_ON_BUG" => nil, "RUBY_CRASH_REPORT" => nil}) + assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300) + end + + def test_darwin_invalid_call + assert_darwin_vm_dump_works(['-r-test-/fatal', '-eBug.invalid_call(1)']) + 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(['-r-test-/fatal', '-eBug.invalid_access(100)']) + end +end diff --git a/test/ruby/test_warning.rb b/test/ruby/test_warning.rb new file mode 100644 index 0000000000..cd220ff00f --- /dev/null +++ b/test/ruby/test_warning.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'test/unit' + +class TestWarning < Test::Unit::TestCase + def test_warn_called_only_when_category_enabled + # Assert that warn is called when the category is enabled + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:deprecated] = true + $warnings = [] + def Warning.warn(msg, category:) + $warnings << [msg, category] + end + assert_equal(0, $warnings.length) + "" << 12 + assert_equal(1, $warnings.length) + end; + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + Warning[:deprecated] = false + $warnings = [] + def Warning.warn(msg, category:) + $warnings << [msg, category] + end + assert_equal(0, $warnings.length) + "" << 12 + assert_equal(0, $warnings.length, $warnings.join) + end; + end +end diff --git a/test/ruby/test_weakkeymap.rb b/test/ruby/test_weakkeymap.rb new file mode 100644 index 0000000000..91c1538076 --- /dev/null +++ b/test/ruby/test_weakkeymap.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestWeakKeyMap < Test::Unit::TestCase + def setup + @wm = ObjectSpace::WeakKeyMap.new + end + + def test_map + x = Object.new + k = "foo" + @wm[k] = x + assert_same(x, @wm[k]) + assert_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[2**128] = x } + assert_raise(ArgumentError) { @wm[1.23] = x } + assert_raise(ArgumentError) { @wm[:foo] = x } + assert_raise(ArgumentError) { @wm["foo#{rand}".to_sym] = x } + end + + def test_getkey + k = "foo" + @wm[k] = true + assert_same(k, @wm.getkey("FOO".downcase)) + end + + def test_key? + assert_weak_include(:key?, "foo") + assert_not_send([@wm, :key?, "bar"]) + end + + def test_delete + k1 = "foo" + x1 = Object.new + @wm[k1] = x1 + assert_equal x1, @wm[k1] + assert_equal x1, @wm.delete(k1) + assert_nil @wm[k1] + assert_nil @wm.delete(k1) + + fallback = @wm.delete(k1) do |key| + assert_equal k1, key + 42 + end + assert_equal 42, fallback + end + + def test_clear + k = "foo" + @wm[k] = true + assert @wm[k] + assert_same @wm, @wm.clear + refute @wm[k] + end + + def test_clear_bug_20691 + assert_normal_exit(<<~RUBY) + map = ObjectSpace::WeakKeyMap.new + + 1_000.times do + 1_000.times do + map[Object.new] = nil + end + + map.clear + end + RUBY + end + + def test_inspect + x = Object.new + k = Object.new + @wm[k] = x + assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect) + + 1000.times do |i| + @wm[i.to_s] = Object.new + @wm.inspect + end + assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect) + end + + def test_no_hash_method + k = BasicObject.new + assert_raise NoMethodError do + @wm[k] = 42 + end + end + + def test_frozen_object + o = Object.new.freeze + assert_nothing_raised(FrozenError) {@wm[o] = 'foo'} + assert_nothing_raised(FrozenError) {@wm['foo'] = o} + end + + def test_inconsistent_hash_key_memory_leak + assert_no_memory_leak [], '', <<~RUBY + class BadHash + def initialize + @hash = 0 + end + + def hash + @hash += 1 + end + end + + k = BadHash.new + wm = ObjectSpace::WeakKeyMap.new + + 100_000.times do |i| + wm[k] = i + end + RUBY + end + + def test_compaction + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + assert_separately(%w(-robjspace), <<-'end;') + wm = ObjectSpace::WeakKeyMap.new + key = Object.new + val = Object.new + wm[key] = val + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(val, wm[key]) + end; + end + + def test_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress { ObjectSpace::WeakKeyMap.new } + end + + private + + 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_send([@wm, m, "FOO".downcase]) + x = Object.new + end + end +end diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb new file mode 100644 index 0000000000..4f5823ecf4 --- /dev/null +++ b/test/ruby/test_weakmap.rb @@ -0,0 +1,291 @@ +# 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 + @wm[true] = x + assert_same(x, @wm[true]) + @wm[false] = x + assert_same(x, @wm[false]) + @wm[nil] = x + assert_same(x, @wm[nil]) + @wm[42] = x + assert_same(x, @wm[42]) + @wm[:foo] = x + assert_same(x, @wm[:foo]) + + @wm[x] = true + assert_same(true, @wm[x]) + @wm[x] = false + assert_same(false, @wm[x]) + @wm[x] = nil + assert_same(nil, @wm[x]) + @wm[x] = 42 + assert_same(42, @wm[x]) + @wm[x] = :foo + assert_same(:foo, @wm[x]) + end + + def 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 + pend('TODO: failure introduced from 837fd5e494731d7d44786f29e7d6e8c27029806f') + 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_inspect_garbage + 1000.times do |i| + @wm[i] = Object.new + @wm.inspect + end + assert_match(/\A\#<#{@wm.class.name}:0x[\da-f]+(?::(?: \d+ => \#<(?:Object|collected):0x[\da-f]+>,?)+)?>\z/, + @wm.inspect) + end + + def test_delete + k1 = "foo" + x1 = Object.new + @wm[k1] = x1 + assert_equal x1, @wm[k1] + assert_equal x1, @wm.delete(k1) + assert_nil @wm[k1] + assert_nil @wm.delete(k1) + + fallback = @wm.delete(k1) do |key| + assert_equal k1, key + 42 + end + assert_equal 42, fallback + 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 + + def test_frozen_object + o = Object.new.freeze + assert_nothing_raised(FrozenError) {@wm[o] = 'foo'} + assert_nothing_raised(FrozenError) {@wm['foo'] = o} + end + + def test_no_memory_leak + assert_no_memory_leak([], '', "#{<<~"begin;"}\n#{<<~'end;'}", "[Bug #19398]", rss: true, limit: 1.5, timeout: 60) + begin; + 1_000_000.times do + ObjectSpace::WeakMap.new + end + end; + end + + def test_compaction + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + # [Bug #19529] + obj = Object.new + 100.times do |i| + GC.compact + @wm[i] = obj + end + + assert_ruby_status([], <<-'end;') + wm = ObjectSpace::WeakMap.new + obj = Object.new + 100.times do + wm[Object.new] = obj + GC.start + end + GC.compact + end; + + assert_separately(%w(-robjspace), <<-'end;') + wm = ObjectSpace::WeakMap.new + key = Object.new + val = Object.new + wm[key] = val + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_equal(val, wm[key]) + end; + + assert_ruby_status(["-W0"], <<-'end;') + wm = ObjectSpace::WeakMap.new + + ary = 10_000.times.map do + o = Object.new + wm[o] = 1 + o + end + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + end; + end + + def test_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress { ObjectSpace::WeakMap.new } + end + + def test_replaced_values_bug_19531 + a = "A".dup + b = "B".dup + + @wm[1] = a + @wm[1] = a + @wm[1] = a + + @wm[1] = b + assert_equal b, @wm[1] + + a = nil + GC.start + + assert_equal b, @wm[1] + end + + def test_use_after_free_bug_20688 + assert_normal_exit(<<~RUBY) + weakmap = ObjectSpace::WeakMap.new + 10_000.times { weakmap[Object.new] = Object.new } + RUBY + end + + def test_generational_gc + EnvUtil.without_gc do + wmap = ObjectSpace::WeakMap.new + + (GC::INTERNAL_CONSTANTS[:RVALUE_OLD_AGE] - 1).times { GC.start } + + retain = [] + 50.times do + k = Object.new + wmap[k] = true + retain << k + end + + GC.start # WeakMap promoted, other objects still young + + retain.clear + + GC.start(full_mark: false) + + wmap.keys.each(&:itself) # call method on keys to cause crash + end + end +end diff --git a/test/ruby/test_whileuntil.rb b/test/ruby/test_whileuntil.rb new file mode 100644 index 0000000000..ff6d29ac4a --- /dev/null +++ b/test/ruby/test_whileuntil.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: false +require 'test/unit' +require 'tmpdir' + +class TestWhileuntil < Test::Unit::TestCase + def test_while + Dir.mktmpdir("ruby_while_tmp") {|tmpdir| + tmpfilename = "#{tmpdir}/ruby_while_tmp.#{$$}" + + tmp = open(tmpfilename, "w") + tmp.print "tvi925\n"; + tmp.print "tvi920\n"; + tmp.print "vt100\n"; + tmp.print "Amiga\n"; + tmp.print "paper\n"; + tmp.close + + tmp = open(tmpfilename, "r") + assert_instance_of(File, tmp) + + while line = tmp.gets() + break if /vt100/ =~ line + end + + assert_not_predicate(tmp, :eof?) + assert_match(/vt100/, line) + tmp.close + + tmp = open(tmpfilename, "r") + while line = tmp.gets() + next if /vt100/ =~ line + assert_no_match(/vt100/, line) + end + assert_predicate(tmp, :eof?) + assert_no_match(/vt100/, line) + tmp.close + + tmp = open(tmpfilename, "r") + while line = tmp.gets() + lastline = line + line = line.gsub(/vt100/, 'VT100') + if lastline != line + line.gsub!('VT100', 'Vt100') + redo + end + assert_no_match(/vt100/, line) + assert_no_match(/VT100/, line) + end + assert_predicate(tmp, :eof?) + tmp.close + + sum=0 + for i in 1..10 + sum += i + i -= 1 + if i > 0 + redo + end + end + assert_equal(220, sum) + + tmp = open(tmpfilename, "r") + while line = tmp.gets() + break if $. == 3 + assert_no_match(/vt100/, line) + assert_no_match(/Amiga/, line) + assert_no_match(/paper/, line) + end + tmp.close + + File.unlink tmpfilename or `/bin/rm -f "#{tmpfilename}"` + assert_file.not_exist?(tmpfilename) + } + end + + def test_begin_while + i = 0 + sum = 0 + begin + i += 1 + sum += i + end while i < 10 + assert_equal([10, 55], [i, sum]) + + i = 0 + sum = 0 + ( + i += 1 + sum += i + ) while false + assert_equal([0, 0], [i, sum]) + end + + def test_until + i = 0 + until i>4 + i+=1 + end + assert_operator(i, :>, 4) + end +end diff --git a/test/ruby/test_yield.rb b/test/ruby/test_yield.rb new file mode 100644 index 0000000000..e7e65fce9e --- /dev/null +++ b/test/ruby/test_yield.rb @@ -0,0 +1,425 @@ +# frozen_string_literal: false +require 'test/unit' +require 'stringio' + +class TestRubyYield < Test::Unit::TestCase + + def test_ary_each + ary = [1] + ary.each {|a, b, c, d| assert_equal [1,nil,nil,nil], [a,b,c,d] } + ary.each {|a, b, c| assert_equal [1,nil,nil], [a,b,c] } + ary.each {|a, b| assert_equal [1,nil], [a,b] } + ary.each {|a| assert_equal 1, a } + end + + def test_hash_each + h = {:a => 1} + h.each do |k, v| + assert_equal :a, k + assert_equal 1, v + end + h.each do |kv| + assert_equal [:a, 1], kv + end + end + + def test_yield_0 + assert_equal 1, iter0 { 1 } + assert_equal 2, iter0 { 2 } + end + + def iter0 + yield + end + + def test_yield_1 + iter1([]) {|a, b| assert_equal [nil,nil], [a, b] } + iter1([1]) {|a, b| assert_equal [1,nil], [a, b] } + iter1([1, 2]) {|a, b| assert_equal [1,2], [a,b] } + iter1([1, 2, 3]) {|a, b| assert_equal [1,2], [a,b] } + + iter1([]) {|a| assert_equal [], a } + iter1([1]) {|a| assert_equal [1], a } + iter1([1, 2]) {|a| assert_equal [1,2], a } + iter1([1, 2, 3]) {|a| assert_equal [1,2,3], a } + end + + def iter1(args) + yield args + end + + def test_yield2 + def iter2_1() yield 1, *[2, 3] end + iter2_1 {|a, b, c| assert_equal [1,2,3], [a,b,c] } + def iter2_2() yield 1, *[] end + iter2_2 {|a, b, c| assert_equal [1,nil,nil], [a,b,c] } + def iter2_3() yield 1, *[2] end + iter2_3 {|a, b, c| assert_equal [1,2,nil], [a,b,c] } + end + + def test_yield_nested + [[1, [2, 3]]].each {|a, (b, c)| + assert_equal [1,2,3], [a,b,c] + } + [[1, [2, 3]]].map {|a, (b, c)| + assert_equal [1,2,3], [a,b,c] + } + end + + def test_with_enum + obj = Object.new + def obj.each + yield(*[]) + end + obj.each{|*v| assert_equal([], [], '[ruby-dev:32392]')} + obj.to_enum.each{|*v| assert_equal([], [], '[ruby-dev:32392]')} + end + + def block_args_unleashed + yield(1,2,3,4,5) + end + + def test_block_args_unleashed + r = block_args_unleashed {|a,b=1,*c,d,e| + [a,b,c,d,e] + } + assert_equal([1,2,[3],4,5], r, "[ruby-core:19485]") + end +end + +require_relative 'sentence' +class TestRubyYieldGen < Test::Unit::TestCase + Syntax = { + :exp => [["0"], + ["nil"], + ["false"], + ["[]"], + ["[",:exps,"]"]], + :exps => [[:exp], + [:exp,",",:exps]], + :opt_block_param => [[], + [:block_param_def]], + :block_param_def => [['|', '|'], + ['|', :block_param, '|']], + :block_param => [[:f_arg, ",", :f_rest_arg, :opt_f_block_arg], + [:f_arg, ","], + [:f_arg, ',', :f_rest_arg, ",", :f_arg, :opt_f_block_arg], + [:f_arg, :opt_f_block_arg], + [:f_rest_arg, :opt_f_block_arg], + [:f_rest_arg, ',', :f_arg, :opt_f_block_arg], + [:f_block_arg]], + :f_arg => [[:f_arg_item], + [:f_arg, ',', :f_arg_item]], + :f_rest_arg => [['*', "var"], + ['*']], + :opt_f_block_arg => [[',', :f_block_arg], + []], + :f_block_arg => [['&', 'var']], + :f_arg_item => [[:f_norm_arg], + ['(', :f_margs, ')']], + :f_margs => [[:f_marg_list], + [:f_marg_list, ',', '*', :f_norm_arg], + [:f_marg_list, ',', '*', :f_norm_arg, ',', :f_marg_list], + [:f_marg_list, ',', '*'], + [:f_marg_list, ',', '*', ',', :f_marg_list], + [ '*', :f_norm_arg], + [ '*', :f_norm_arg, ',', :f_marg_list], + [ '*'], + [ '*', ',', :f_marg_list]], + :f_marg_list => [[:f_marg], + [:f_marg_list, ',', :f_marg]], + :f_marg => [[:f_norm_arg], + ['(', :f_margs, ')']], + :f_norm_arg => [['var']], + + :command_args => [[:open_args]], + :open_args => [[' ',:call_args], + ['(', ')'], + ['(', :call_args2, ')']], + :call_args => [[:command], + [ :args, :opt_block_arg], + [ :assocs, :opt_block_arg], + [ :args, ',', :assocs, :opt_block_arg], + [ :block_arg]], + :call_args2 => [[:arg, ',', :args, :opt_block_arg], + [:arg, ',', :block_arg], + [ :assocs, :opt_block_arg], + [:arg, ',', :assocs, :opt_block_arg], + [:arg, ',', :args, ',', :assocs, :opt_block_arg], + [ :block_arg]], + + :command_args_noblock => [[:open_args_noblock]], + :open_args_noblock => [[' ',:call_args_noblock], + ['(', ')'], + ['(', :call_args2_noblock, ')']], + :call_args_noblock => [[:command], + [ :args], + [ :assocs], + [ :args, ',', :assocs]], + :call_args2_noblock => [[:arg, ',', :args], + [ :assocs], + [:arg, ',', :assocs], + [:arg, ',', :args, ',', :assocs]], + + :command => [], + :args => [[:arg], + ["*",:arg], + [:args,",",:arg], + [:args,",","*",:arg]], + :arg => [[:exp]], + :assocs => [[:assoc], + [:assocs, ',', :assoc]], + :assoc => [[:arg, '=>', :arg], + ['label', ':', :arg]], + :opt_block_arg => [[',', :block_arg], + []], + :block_arg => [['&', :arg]], + #:test => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']] + :test_proc => [['def m() yield', :command_args_noblock, ' end; r = m {', :block_param_def, 'vars', '}; undef m; r']], + :test_lambda => [['def m() yield', :command_args_noblock, ' end; r = m(&lambda {', :block_param_def, 'vars', '}); undef m; r']], + :test_enum => [['o = Object.new; def o.each() yield', :command_args_noblock, ' end; r1 = r2 = nil; o.each {|*x| r1 = x }; o.to_enum.each {|*x| r2 = x }; [r1, r2]']] + } + + def rename_var(obj) + vars = [] + r = obj.subst('var') { + var = "v#{vars.length}" + vars << var + var + } + return r, vars + end + + def split_by_comma(ary) + return [] if ary.empty? + result = [[]] + ary.each {|e| + if e == ',' + result << [] + else + result.last << e + end + } + result + end + + def emu_return_args(*vs) + vs + end + + def emu_eval_args(args) + if args.last == [] + args = args[0...-1] + end + code = "emu_return_args(#{args.map {|a| a.join('') }.join(",")})" + eval code, nil, 'generated_code_in_emu_eval_args' + end + + def emu_bind_single(arg, param, result_binding) + #p [:emu_bind_single, arg, param] + if param.length == 1 && String === param[0] && /\A[a-z0-9]+\z/ =~ param[0] + result_binding[param[0]] = arg + elsif param.length == 1 && Array === param[0] && param[0][0] == '(' && param[0][-1] == ')' + arg = [arg] unless Array === arg + emu_bind_params(arg, split_by_comma(param[0][1...-1]), false, result_binding) + else + raise "unexpected param: #{param.inspect}" + end + result_binding + end + + def emu_bind_params(args, params, islambda, result_binding={}) + #p [:emu_bind_params, args, params] + if params.last == [] # extra comma + params.pop + end + + star_index = nil + params.each_with_index {|par, i| + star_index = i if par[0] == '*' + } + + if islambda + if star_index + if args.length < params.length - 1 + throw :emuerror, ArgumentError + end + else + if args.length != params.length + throw :emuerror, ArgumentError + end + end + end + + # TRICK #2 : adjust mismatch on number of arguments + if star_index + pre_params = params[0...star_index] + rest_param = params[star_index] + post_params = params[(star_index+1)..-1] + pre_params.each {|par| emu_bind_single(args.shift, par, result_binding) } + if post_params.length <= args.length + post_params.reverse_each {|par| emu_bind_single(args.pop, par, result_binding) } + else + post_params.each {|par| emu_bind_single(args.shift, par, result_binding) } + end + if rest_param != ['*'] + emu_bind_single(args, rest_param[1..-1], result_binding) + end + else + params.each_with_index {|par, i| + emu_bind_single(args[i], par, result_binding) + } + end + + #p [args, params, result_binding] + + result_binding + end + + def emu_bind(t, islambda) + #puts + #p t + command_args_noblock = t[1] + block_param_def = t[3] + command_args_noblock = command_args_noblock.expand {|a| !(a[0] == '[' && a[-1] == ']') } + block_param_def = block_param_def.expand {|a| !(a[0] == '(' && a[-1] == ')') } + + if command_args_noblock.to_a[0] == ' ' + args = command_args_noblock.to_a[1..-1] + elsif command_args_noblock.to_a[0] == '(' && command_args_noblock.to_a[-1] == ')' + args = command_args_noblock.to_a[1...-1] + else + raise "unexpected command_args_noblock: #{command_args_noblock.inspect}" + end + args = emu_eval_args(split_by_comma(args)) + + params = block_param_def.to_a[1...-1] + params = split_by_comma(params) + + #p [:emu0, args, params] + + result_binding = {} + + if params.last && params.last[0] == '&' + result_binding[params.last[1]] = nil + params.pop + end + + if !islambda + # TRICK #1 : single array argument is expanded if there are two or more params. + # * block parameter is not counted. + # * extra comma after single param forces the expansion. + if args.length == 1 && Array === args[0] && 1 < params.length + args = args[0] + end + end + + emu_bind_params(args, params, islambda, result_binding) + #p result_binding + result_binding + end + + def emu(t, vars, islambda) + catch(:emuerror) { + emu_binding = emu_bind(t, islambda) + vars.map {|var| emu_binding.fetch(var, "NOVAL") } + } + end + + def disable_stderr + begin + save_stderr = $stderr + $stderr = StringIO.new + yield + ensure + $stderr = save_stderr + end + end + + def check_nofork(t, islambda=false) + t, vars = rename_var(t) + 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 + o.instance_eval(s, 'generated_code_in_check_nofork') + rescue ArgumentError + ArgumentError + end + } + #success = emu_values == eval_values ? 'succ' : 'fail' + #puts "eval:#{vs_ev.inspect[1...-1].delete(' ')}\temu:#{vs_emu.inspect[1...-1].delete(' ')}\t#{success}" + 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 + assert_all_sentences(Syntax, :test_proc, 4) {|t| + check_nofork(t) + } + end + + def test_yield_lambda + assert_all_sentences(Syntax, :test_lambda, 4) {|t| + check_nofork(t, true) + } + end + + def test_yield_enum + 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') + } + assert_equal(r1, r2, "#{t}") + } + 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_ruby_status([], <<-"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/test_yjit.rb b/test/ruby/test_yjit.rb new file mode 100644 index 0000000000..d6b9b75648 --- /dev/null +++ b/test/ruby/test_yjit.rb @@ -0,0 +1,1985 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS='test/ruby/test_yjit.rb' + +require 'test/unit' +require 'envutil' +require 'tmpdir' +require_relative '../lib/jit_support' + +return unless JITSupport.yjit_supported? + +require 'stringio' + +# Tests for YJIT with assertions on compilation and side exits +# insipired by the RJIT tests in test/ruby/test_rjit.rb +class TestYJIT < Test::Unit::TestCase + running_with_yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? + + def test_yjit_in_ruby_description + assert_includes(RUBY_DESCRIPTION, '+YJIT') + end if running_with_yjit + + # Check that YJIT is in the version string + def test_yjit_in_version + [ + %w(--version --yjit), + %w(--version --disable-yjit --yjit), + %w(--version --disable-yjit --enable-yjit), + %w(--version --disable-yjit --enable=yjit), + %w(--version --disable=yjit --yjit), + %w(--version --disable=yjit --enable-yjit), + %w(--version --disable=yjit --enable=yjit), + %w(--version --jit), + %w(--version --disable-jit --jit), + %w(--version --disable-jit --enable-jit), + %w(--version --disable-jit --enable=jit), + %w(--version --disable=jit --yjit), + %w(--version --disable=jit --enable-jit), + %w(--version --disable=jit --enable=jit), + ].each do |version_args| + assert_in_out_err(version_args) do |stdout, stderr| + assert_equal(RUBY_DESCRIPTION, stdout.first) + assert_equal([], stderr) + end + end + end if running_with_yjit + + def test_command_line_switches + assert_in_out_err('--yjit-', '', [], /invalid option --yjit-/) + assert_in_out_err('--yjithello', '', [], /invalid option --yjithello/) + #assert_in_out_err('--yjit-call-threshold', '', [], /--yjit-call-threshold needs an argument/) + #assert_in_out_err('--yjit-call-threshold=', '', [], /--yjit-call-threshold needs an argument/) + end + + def test_yjit_enable + args = [] + args << "--disable=yjit" if RubyVM::YJIT.enabled? + assert_separately(args, <<~'RUBY') + refute_predicate RubyVM::YJIT, :enabled? + refute_includes RUBY_DESCRIPTION, "+YJIT" + + RubyVM::YJIT.enable + + assert_predicate RubyVM::YJIT, :enabled? + assert_includes RUBY_DESCRIPTION, "+YJIT" + RUBY + end + + def test_yjit_disable + assert_separately(["--yjit", "--yjit-disable"], <<~'RUBY') + refute_predicate RubyVM::YJIT, :enabled? + refute_includes RUBY_DESCRIPTION, "+YJIT" + + RubyVM::YJIT.enable + + assert_predicate RubyVM::YJIT, :enabled? + assert_includes RUBY_DESCRIPTION, "+YJIT" + RUBY + end + + def test_yjit_enable_stats_false + assert_separately(["--yjit-disable", "--yjit-stats"], <<~RUBY, ignore_stderr: true) + assert_false RubyVM::YJIT.enabled? + assert_nil RubyVM::YJIT.runtime_stats + + RubyVM::YJIT.enable + + assert_true RubyVM::YJIT.enabled? + assert_true RubyVM::YJIT.runtime_stats[:all_stats] + RUBY + end + + def test_yjit_enable_stats_true + args = [] + args << "--disable=yjit" if RubyVM::YJIT.enabled? + assert_separately(args, <<~RUBY, ignore_stderr: true) + assert_false RubyVM::YJIT.enabled? + assert_nil RubyVM::YJIT.runtime_stats + + RubyVM::YJIT.enable(stats: true) + + assert_true RubyVM::YJIT.enabled? + assert_true RubyVM::YJIT.runtime_stats[:all_stats] + RUBY + end + + def test_yjit_enable_stats_quiet + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: true)']) do |_stdout, stderr, _status| + assert_not_empty stderr + end + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(stats: :quiet)']) do |_stdout, stderr, _status| + assert_empty stderr + end + end + + def test_yjit_enable_with_call_threshold + assert_separately(%w[--yjit-disable --yjit-call-threshold=1], <<~RUBY) + def not_compiled = nil + def will_compile = nil + def compiled_counts = RubyVM::YJIT.runtime_stats&.dig(:compiled_iseq_count) + + not_compiled + assert_nil compiled_counts + assert_false RubyVM::YJIT.enabled? + + RubyVM::YJIT.enable + + will_compile + assert compiled_counts > 0 + assert_true RubyVM::YJIT.enabled? + RUBY + end + + def test_yjit_enable_with_monkey_patch + assert_ruby_status(%w[--yjit-disable], <<~RUBY) + # This lets rb_method_entry_at(rb_mKernel, ...) return NULL + Kernel.prepend(Module.new) + + # This must not crash with "undefined optimized method!" + RubyVM::YJIT.enable + RUBY + end + + def test_yjit_enable_with_valid_runtime_call_threshold_option + assert_in_out_err(['--yjit-disable', '-e', + 'RubyVM::YJIT.enable(call_threshold: 1); puts RubyVM::YJIT.enabled?']) do |stdout, stderr, _status| + assert_empty stderr + assert_include stdout.join, "true" + end + end + + def test_yjit_enable_with_invalid_runtime_call_threshold_option + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status| + assert_not_empty stderr + assert_match(/ArgumentError/, stderr.join) + assert_equal 1, status.exitstatus + end + end + + def test_yjit_enable_with_invalid_runtime_mem_size_option + assert_in_out_err(['--yjit-disable', '-e', 'RubyVM::YJIT.enable(mem_size: 0)']) do |stdout, stderr, status| + assert_not_empty stderr + assert_match(/ArgumentError/, stderr.join) + assert_equal 1, status.exitstatus + end + end + + if JITSupport.zjit_supported? + def test_yjit_enable_with_zjit_enabled + assert_in_out_err(['--zjit'], 'puts RubyVM::YJIT.enable', ['false'], ['Only one JIT can be enabled at the same time.']) + end + end + + def test_yjit_stats_and_v_no_error + _stdout, stderr, _status = invoke_ruby(%w(-v --yjit-stats), '', true, true) + refute_includes(stderr, "NoMethodError") + end + + def test_enable_from_env_var + yjit_child_env = {'RUBY_YJIT_ENABLE' => '1'} + assert_in_out_err([yjit_child_env, '--version'], '') do |stdout, stderr| + assert_equal(RUBY_DESCRIPTION, stdout.first) + assert_equal([], stderr) + end + assert_in_out_err([yjit_child_env, '-e puts RUBY_DESCRIPTION'], '', [RUBY_DESCRIPTION]) + assert_in_out_err([yjit_child_env, '-e p RubyVM::YJIT.enabled?'], '', ['true']) + end if running_with_yjit + + def test_compile_setclassvariable + script = 'class Foo; def self.foo; @@foo = 1; end; end; Foo.foo' + assert_compiles(script, insns: %i[setclassvariable], result: 1) + end + + def test_compile_getclassvariable + script = 'class Foo; @@foo = 1; def self.foo; @@foo; end; end; Foo.foo' + assert_compiles(script, insns: %i[getclassvariable], result: 1) + end + + def test_compile_putnil + assert_compiles('nil', insns: %i[putnil], result: nil) + end + + def test_compile_putobject + assert_compiles('true', insns: %i[putobject], result: true) + assert_compiles('123', insns: %i[putobject], result: 123) + assert_compiles(':foo', insns: %i[putobject], result: :foo) + end + + def test_compile_opt_succ + assert_compiles('1.succ', insns: %i[opt_succ], result: 2) + end + + def test_compile_opt_not + assert_compiles('!false', insns: %i[opt_not], result: true) + assert_compiles('!nil', insns: %i[opt_not], result: true) + assert_compiles('!true', insns: %i[opt_not], result: false) + assert_compiles('![]', insns: %i[opt_not], result: false) + end + + def test_compile_opt_newarray + assert_compiles('[]', insns: %i[newarray], result: []) + assert_compiles('[1+1]', insns: %i[newarray opt_plus], result: [2]) + assert_compiles('[1,1+1,3,4,5,6]', insns: %i[newarray opt_plus], result: [1, 2, 3, 4, 5, 6]) + end + + def test_compile_opt_duparray + assert_compiles('[1]', insns: %i[duparray], result: [1]) + assert_compiles('[1, 2, 3]', insns: %i[duparray], result: [1, 2, 3]) + end + + def test_compile_newrange + assert_compiles('s = 1; (s..5)', insns: %i[newrange], result: 1..5) + assert_compiles('s = 1; e = 5; (s..e)', insns: %i[newrange], result: 1..5) + assert_compiles('s = 1; (s...5)', insns: %i[newrange], result: 1...5) + assert_compiles('s = 1; (s..)', insns: %i[newrange], result: 1..) + assert_compiles('e = 5; (..e)', insns: %i[newrange], result: ..5) + end + + def test_compile_duphash + assert_compiles('{ two: 2 }', insns: %i[duphash], result: { two: 2 }) + end + + def test_compile_newhash + assert_compiles('{}', insns: %i[newhash], result: {}) + assert_compiles('{ two: 1 + 1 }', insns: %i[newhash], result: { two: 2 }) + assert_compiles('{ 1 + 1 => :two }', insns: %i[newhash], result: { 2 => :two }) + end + + def test_compile_opt_nil_p + assert_compiles('nil.nil?', insns: %i[opt_nil_p], result: true) + assert_compiles('false.nil?', insns: %i[opt_nil_p], result: false) + assert_compiles('true.nil?', insns: %i[opt_nil_p], result: false) + assert_compiles('(-"").nil?', insns: %i[opt_nil_p], result: false) + assert_compiles('123.nil?', insns: %i[opt_nil_p], result: false) + end + + def test_compile_eq_fixnum + assert_compiles('123 == 123', insns: %i[opt_eq], result: true) + assert_compiles('123 == 456', insns: %i[opt_eq], result: false) + end + + def test_compile_eq_string + assert_compiles('-"" == -""', insns: %i[opt_eq], result: true) + assert_compiles('-"foo" == -"foo"', insns: %i[opt_eq], result: true) + assert_compiles('-"foo" == -"bar"', insns: %i[opt_eq], result: false) + end + + def test_compile_eq_symbol + assert_compiles(':foo == :foo', insns: %i[opt_eq], result: true) + assert_compiles(':foo == :bar', insns: %i[opt_eq], result: false) + assert_compiles(':foo == "foo".to_sym', insns: %i[opt_eq], result: true) + end + + def test_compile_eq_object + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: false) + def eq(a, b) + a == b + end + + eq(Object.new, Object.new) + RUBY + + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: true) + def eq(a, b) + a == b + end + + obj = Object.new + eq(obj, obj) + RUBY + end + + def test_compile_eq_arbitrary_class + assert_compiles(<<~RUBY, insns: %i[opt_eq], result: "yes") + def eq(a, b) + a == b + end + + class Foo + def ==(other) + "yes" + end + end + + eq(Foo.new, Foo.new) + eq(Foo.new, Foo.new) + RUBY + end + + def test_compile_opt_lt + assert_compiles('1 < 2', insns: %i[opt_lt]) + assert_compiles('"a" < "b"', insns: %i[opt_lt]) + end + + def test_compile_opt_le + assert_compiles('1 <= 2', insns: %i[opt_le]) + assert_compiles('"a" <= "b"', insns: %i[opt_le]) + end + + def test_compile_opt_gt + assert_compiles('1 > 2', insns: %i[opt_gt]) + assert_compiles('"a" > "b"', insns: %i[opt_gt]) + end + + def test_compile_opt_ge + assert_compiles('1 >= 2', insns: %i[opt_ge]) + assert_compiles('"a" >= "b"', insns: %i[opt_ge]) + end + + def test_compile_opt_plus + assert_compiles('1 + 2', insns: %i[opt_plus]) + assert_compiles('"a" + "b"', insns: %i[opt_plus]) + assert_compiles('[:foo] + [:bar]', insns: %i[opt_plus]) + end + + def test_compile_opt_minus + assert_compiles('1 - 2', insns: %i[opt_minus]) + assert_compiles('[:foo, :bar] - [:bar]', insns: %i[opt_minus]) + end + + def test_compile_opt_or + assert_compiles('1 | 2', insns: %i[opt_or]) + assert_compiles('[:foo] | [:bar]', insns: %i[opt_or]) + end + + def test_compile_opt_and + assert_compiles('1 & 2', insns: %i[opt_and]) + assert_compiles('[:foo, :bar] & [:bar]', insns: %i[opt_and]) + end + + def test_compile_set_and_get_global + assert_compiles('$foo = 123; $foo', insns: %i[setglobal], result: 123) + end + + def test_compile_putspecialobject + assert_compiles('-> {}', insns: %i[putspecialobject]) + end + + def test_compile_tostring + assert_no_exits('"i am a string #{true}"') + end + + def test_compile_opt_aset + assert_compiles('[1,2,3][2] = 4', insns: %i[opt_aset], frozen_string_literal: false) + assert_compiles('{}[:foo] = :bar', insns: %i[opt_aset], frozen_string_literal: false) + assert_compiles('[1,2,3][0..-1] = []', insns: %i[opt_aset], frozen_string_literal: false) + assert_compiles('"foo"[3] = "d"', insns: %i[opt_aset], frozen_string_literal: false) + end + + def test_compile_attr_set + assert_no_exits(<<~EORB) + class Foo + attr_accessor :bar + end + + foo = Foo.new + foo.bar = 3 + foo.bar = 3 + foo.bar = 3 + foo.bar = 3 + EORB + end + + def test_compile_regexp + assert_no_exits('/#{true}/') + end + + def test_compile_dynamic_symbol + assert_compiles(':"#{"foo"}"', insns: %i[intern]) + assert_compiles('s = "bar"; :"foo#{s}"', insns: %i[intern]) + end + + def test_getlocal_with_level + assert_compiles(<<~RUBY, insns: %i[getlocal opt_plus], result: [[7]]) + def foo(foo, bar) + [1].map do |x| + [1].map do |y| + foo + bar + end + end + end + + foo(5, 2) + RUBY + end + + def test_setlocal_with_level + assert_no_exits(<<~RUBY) + def sum(arr) + sum = 0 + arr.each do |x| + sum += x + end + sum + end + + sum([1,2,3]) + RUBY + end + + def test_string_then_nil + assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: true) + def foo(val) + val.nil? + end + + foo("foo") + foo(nil) + RUBY + end + + def test_nil_then_string + assert_compiles(<<~RUBY, insns: %i[opt_nil_p], result: false) + def foo(val) + val.nil? + end + + foo(nil) + foo("foo") + RUBY + end + + def test_string_concat_utf8 + assert_compiles(<<~RUBY, frozen_string_literal: true, result: true) + def str_cat_utf8 + s = String.new + 10.times { s << "✅" } + s + end + + str_cat_utf8 == "✅" * 10 + RUBY + end + + def test_string_concat_ascii + # Constant-get for classes (e.g. String, Encoding) can cause a side-exit in getinlinecache. For now, ignore exits. + assert_compiles(<<~RUBY, exits: :any) + str_arg = "b".encode(Encoding::ASCII) + def str_cat_ascii(arg) + s = String.new(encoding: Encoding::ASCII) + 10.times { s << arg } + s + end + + str_cat_ascii(str_arg) == str_arg * 10 + RUBY + end + + def test_opt_length_in_method + assert_compiles(<<~RUBY, insns: %i[opt_length], result: 5) + def foo(str) + str.length + end + + foo("hello, ") + foo("world") + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles(<<~RUBY, insns: %i[opt_regexpmatch2], result: 0) + def foo(str) + str =~ /foo/ + end + + foo("foobar") + RUBY + end + + def test_expandarray + assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [1, 2]) + a, b = [1, 2] + RUBY + end + + def test_expandarray_nil + assert_compiles(<<~'RUBY', insns: %i[expandarray], result: [nil, nil]) + a, b = nil + [a, b] + RUBY + end + + def test_getspecial_backref + assert_compiles("'foo' =~ /(o)./; $&", insns: %i[getspecial], result: "oo") + assert_compiles("'foo' =~ /(o)./; $`", insns: %i[getspecial], result: "f") + assert_compiles("'foo' =~ /(o)./; $'", insns: %i[getspecial], result: "") + assert_compiles("'foo' =~ /(o)./; $+", insns: %i[getspecial], result: "o") + assert_compiles("'foo' =~ /(o)./; $1", insns: %i[getspecial], result: "o") + assert_compiles("'foo' =~ /(o)./; $2", insns: %i[getspecial], result: nil) + end + + def test_compile_getconstant + assert_compiles(<<~RUBY, insns: %i[getconstant], result: [], call_threshold: 1) + def get_argv(klass) + klass::ARGV + end + + get_argv(Object) + RUBY + end + + def test_compile_getconstant_with_sp_offset + assert_compiles(<<~RUBY, insns: %i[getconstant], result: 2, call_threshold: 1) + class Foo + Bar = 1 + end + + 2.times do + s = Foo # this opt_getconstant_path needs warmup, so 2.times is needed + Class.new(Foo).const_set(:Bar, s::Bar) + end + RUBY + end + + def test_compile_opt_getconstant_path + assert_compiles(<<~RUBY, insns: %i[opt_getconstant_path], result: 123, call_threshold: 2) + def get_foo + FOO + end + + FOO = 123 + + get_foo # warm inline cache + get_foo + RUBY + end + + def test_opt_getconstant_path_slowpath + assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2) + class A + FOO = 42 + class << self + def foo + _foo = nil + FOO + end + end + end + + result = [] + + result << A.foo + result << A.foo + + class << A + FOO = 1 + end + + result << A.foo + result << A.foo + + result + RUBY + end + + def test_opt_getconstant_path_general + assert_compiles(<<~RUBY, result: [1, 1]) + module Base + Const = 1 + end + + class Sub + def const + _const = nil # make a non-entry block for opt_getconstant_path + Const + end + + def self.const_missing(n) + Base.const_get(n) + end + end + + + sub = Sub.new + result = [] + result << sub.const # generate the general case + result << sub.const # const_missing does not invalidate the block + result + RUBY + end + + def test_string_interpolation + assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "foobar", call_threshold: 2) + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str("foo", "bar") + make_str("foo", "bar") + RUBY + end + + def test_string_interpolation_cast + assert_compiles(<<~'RUBY', insns: %i[objtostring anytostring concatstrings], result: "123") + def make_str(foo, bar) + "#{foo}#{bar}" + end + + make_str(1, 23) + RUBY + end + + def test_checkkeyword + assert_compiles(<<~'RUBY', insns: %i[checkkeyword], result: [2, 5]) + def foo(foo: 1+1) + foo + end + + [foo, foo(foo: 5)] + RUBY + end + + def test_struct_aref + assert_compiles(<<~RUBY) + def foo(obj) + obj.foo + obj.bar + end + + Foo = Struct.new(:foo, :bar) + foo(Foo.new(123)) + foo(Foo.new(123)) + RUBY + end + + def test_struct_aset + assert_compiles(<<~RUBY) + def foo(obj) + obj.foo = 123 + obj.bar = 123 + end + + Foo = Struct.new(:foo, :bar) + foo(Foo.new(123)) + foo(Foo.new(123)) + RUBY + end + + def test_struct_aset_guards_recv_is_not_frozen + assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 }) + def foo(obj) + obj.foo = 123 + end + + Foo = Struct.new(:foo) + obj = Foo.new(123) + 100.times do + foo(obj) + end + obj.freeze + begin + foo(obj) + rescue FrozenError + :ok + end + RUBY + end + + def test_getblockparam + assert_compiles(<<~'RUBY', insns: [:getblockparam]) + def foo &blk + 2.times do + blk + end + end + + foo {} + foo {} + RUBY + end + + def test_getblockparamproxy + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {}) + def foo &blk + p blk.call + p blk.call + end + + foo { 1 } + foo { 2 } + RUBY + end + + def test_ifunc_getblockparamproxy + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy], exits: {}) + class Foo + include Enumerable + + def each(&block) + block.call 1 + block.call 2 + block.call 3 + end + end + + foo = Foo.new + foo.map { _1 * 2 } + foo.map { _1 * 2 } + RUBY + end + + def test_send_blockarg + assert_compiles(<<~'RUBY', insns: [:getblockparamproxy, :send], exits: {}) + def bar + end + + def foo &blk + bar(&blk) + bar(&blk) + end + + foo + foo + + foo { } + foo { } + RUBY + end + + def test_send_splat + assert_compiles(<<~'RUBY', result: "3#1,2,3/P", exits: {}) + def internal_method(*args) + "#{args.size}##{args.join(",")}" + end + + def jit_method + send(:internal_method, *[1, 2, 3]) + "/P" + end + + jit_method + RUBY + end + + def test_send_multiarg + assert_compiles(<<~'RUBY', result: "3#1,2,3/Q") + def internal_method(*args) + "#{args.size}##{args.join(",")}" + end + + def jit_method + send(:internal_method, 1, 2, 3) + "/Q" + end + + jit_method + RUBY + end + + def test_send_kwargs + # For now, this side-exits when calls include keyword args + assert_compiles(<<~'RUBY', result: "2#a:1,b:2/A") + def internal_method(**kw) + "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}" + end + + def jit_method + send(:internal_method, a: 1, b: 2) + "/A" + end + jit_method + RUBY + end + + def test_send_kwargs_in_receiver_only + assert_compiles(<<~'RUBY', result: "0/RK", exits: {}) + def internal_method(**kw) + "#{kw.size}" + end + + def jit_method + send(:internal_method) + "/RK" + end + jit_method + RUBY + end + + def test_send_with_underscores + assert_compiles(<<~'RUBY', result: "0/RK", exits: {}) + def internal_method(**kw) + "#{kw.size}" + end + + def jit_method + __send__(:internal_method) + "/RK" + end + jit_method + RUBY + end + + def test_send_kwargs_splat + # For now, this side-exits when calling with a splat + assert_compiles(<<~'RUBY', result: "2#a:1,b:2/B") + def internal_method(**kw) + "#{kw.size}##{kw.keys.map { |k| "#{k}:#{kw[k]}" }.join(",")}" + end + + def jit_method + send(:internal_method, **{ a: 1, b: 2 }) + "/B" + end + jit_method + RUBY + end + + def test_send_block + # Setlocal_wc_0 sometimes side-exits on write barrier + assert_compiles(<<~'RUBY', result: "b:n/b:y/b:y/b:n") + def internal_method(&b) + "b:#{block_given? ? "y" : "n"}" + end + + def jit_method + b7 = proc { 7 } + [ + send(:internal_method), + send(:internal_method, &b7), + send(:internal_method) { 7 }, + send(:internal_method, &nil), + ].join("/") + end + jit_method + RUBY + end + + def test_send_block_calling + assert_compiles(<<~'RUBY', result: "1a2", exits: {}) + def internal_method + out = yield + "1" + out + "2" + end + + def jit_method + __send__(:internal_method) { "a" } + end + jit_method + RUBY + end + + def test_send_block_only_receiver + assert_compiles(<<~'RUBY', result: "b:n", exits: {}) + def internal_method(&b) + "b:#{block_given? ? "y" : "n"}" + end + + def jit_method + send(:internal_method) + end + jit_method + RUBY + end + + def test_send_block_only_sender + assert_compiles(<<~'RUBY', result: "Y/Y/Y/Y", exits: {}) + def internal_method + "Y" + end + + def jit_method + b7 = proc { 7 } + [ + send(:internal_method), + send(:internal_method, &b7), + send(:internal_method) { 7 }, + send(:internal_method, &nil), + ].join("/") + end + jit_method + RUBY + end + + def test_multisend + assert_compiles(<<~'RUBY', result: "77") + def internal_method + "7" + end + + def jit_method + send(:send, :internal_method) + send(:send, :send, :internal_method) + end + jit_method + RUBY + end + + def test_getivar_opt_plus + assert_no_exits(<<~RUBY) + class TheClass + def initialize + @levar = 1 + end + + def get_sum + sum = 0 + # The type of levar is unknown, + # but this still should not exit + sum += @levar + sum + end + end + + obj = TheClass.new + obj.get_sum + RUBY + end + + def test_super_iseq + assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15) + class A + def foo + 1 + 2 + end + end + + class B < A + def foo + super * 5 + end + end + + B.new.foo + RUBY + end + + def test_super_with_alias + assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15) + class A + def foo = 1 + 2 + end + + module M + def foo = super() * 5 + alias bar foo + + def foo = :bad + end + + A.prepend M + + A.new.bar + RUBY + end + + def test_super_cfunc + assert_compiles(<<~'RUBY', insns: %i[invokesuper], result: "Hello") + class Gnirts < String + def initialize + super(-"olleH") + end + + def to_s + super().reverse + end + end + + Gnirts.new.to_s + RUBY + end + + # Tests calling a variadic cfunc with many args + def test_build_large_struct + assert_compiles(<<~RUBY, insns: %i[opt_send_without_block], call_threshold: 2) + ::Foo = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h) + + def build_foo + ::Foo.new(:a, :b, :c, :d, :e, :f, :g, :h) + end + + build_foo + build_foo + RUBY + end + + def test_fib_recursion + assert_compiles(<<~'RUBY', insns: %i[opt_le opt_minus opt_plus opt_send_without_block], result: 34) + def fib(n) + return n if n <= 1 + fib(n-1) + fib(n-2) + end + + fib(9) + RUBY + end + + def test_optarg_and_kwarg + assert_no_exits(<<~'RUBY') + def opt_and_kwarg(a, b=nil, c: nil) + end + + 2.times do + opt_and_kwarg(1, 2, c: 3) + end + RUBY + end + + def test_cfunc_kwarg + assert_no_exits('{}.store(:value, foo: 123)') + assert_no_exits('{}.store(:value, foo: 123, bar: 456, baz: 789)') + assert_no_exits('{}.merge(foo: 123)') + assert_no_exits('{}.merge(foo: 123, bar: 456, baz: 789)') + end + + # regression test simplified from URI::Generic#hostname= + def test_ctx_different_mappings + assert_compiles(<<~'RUBY', frozen_string_literal: true) + def foo(v) + !(v&.start_with?('[')) && v&.index(':') + end + + foo(nil) + foo("example.com") + RUBY + end + + def test_no_excessive_opt_getinlinecache_invalidation + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + objects = [Object.new, Object.new] + + objects.each do |o| + class << o + def foo + Object + end + end + end + + 9000.times { + objects[0].foo + objects[1].foo + } + + stats = RubyVM::YJIT.runtime_stats + return :ok unless stats[:all_stats] + return :ok if stats[:invalidation_count] < 10 + + :fail + RUBY + end + + def test_int_equal + assert_compiles(<<~'RUBY', exits: :any, result: [true, false, true, false, true, false, true, false]) + def eq(a, b) + a == b + end + + def eqq(a, b) + a === b + end + + big1 = 2 ** 65 + big2 = big1 + 1 + [eq(1, 1), eq(1, 2), eq(big1, big1), eq(big1, big2), eqq(1, 1), eqq(1, 2), eqq(big1, big1), eqq(big1, big2)] + RUBY + end + + def test_opt_case_dispatch + assert_compiles(<<~'RUBY', exits: :any, result: [:"1", "2", 3]) + def case_dispatch(val) + case val + when 1 + :"#{val}" + when 2 + "#{val}" + else + val + end + end + + [case_dispatch(1), case_dispatch(2), case_dispatch(3)] + RUBY + end + + def test_code_gc + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + return :not_paged unless add_pages(100) # prepare freeable pages + RubyVM::YJIT.code_gc # first code GC + return :not_compiled1 unless compiles { nil } # should be JITable again + + RubyVM::YJIT.code_gc # second code GC + return :not_compiled2 unless compiles { nil } # should be JITable again + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 2 + + :ok + RUBY + end + + def test_on_stack_code_gc_call + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume != 0 # JIT the fiber + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page + return :broken_resume2 if fiber.resume != 0 # The code should be still callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 1 + + :ok + RUBY + end + + def test_on_stack_code_gc_twice + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while Fiber.yield(nil.to_i); end + } + + return :not_paged1 unless add_pages(400) # go to a page without initial ocb code + return :broken_resume1 if fiber.resume(true) != 0 # JIT the fiber + RubyVM::YJIT.code_gc # first code GC, which should not free the fiber page + + return :not_paged2 unless add_pages(300) # add some stuff to be freed + # Not calling fiber.resume here to test the case that the YJIT payload loses some + # information at the previous code GC. The payload should still be there, and + # thus we could know the fiber ISEQ is still on stack on this second code GC. + RubyVM::YJIT.code_gc # second code GC, which should still not free the fiber page + + return :not_paged3 unless add_pages(200) # attempt to overwrite the fiber page (it shouldn't) + return :broken_resume2 if fiber.resume(true) != 0 # The fiber code should be still fine + + return :broken_resume3 if fiber.resume(false) != nil # terminate the fiber + RubyVM::YJIT.code_gc # third code GC, freeing a page that used to be on stack + + return :not_paged4 unless add_pages(100) # check everything still works + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 3 + + :ok + RUBY + end + + def test_disable_code_gc_with_many_iseqs + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: false) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(250) # use some pages + return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well + + add_pages(2000) # use a whole lot of pages to run out of 1MiB + return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count != 0 + + :ok + RUBY + end + + def test_code_gc_with_many_iseqs + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok, mem_size: 1, code_gc: true) + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(250) # use some pages + return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well + + add_pages(2000) # use a whole lot of pages to run out of 1MiB + return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count == 0 + + :ok + RUBY + end + + def test_code_gc_with_auto_compact + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + assert_compiles((code_gc_helpers + <<~'RUBY'), exits: :any, result: :ok, mem_size: 1, code_gc: true) + # Test ISEQ moves in the middle of code GC + GC.auto_compact = true + + fiber = Fiber.new { + # Loop to call the same basic block again after Fiber.yield + while true + Fiber.yield(nil.to_i) + end + } + + return :not_paged1 unless add_pages(250) # use some pages + return :broken_resume1 if fiber.resume != 0 # leave an on-stack code as well + + add_pages(2000) # use a whole lot of pages to run out of 1MiB + return :broken_resume2 if fiber.resume != 0 # on-stack code should be callable + + code_gc_count = RubyVM::YJIT.runtime_stats[:code_gc_count] + return :"code_gc_#{code_gc_count}" if code_gc_count == 0 + + :ok + RUBY + end + + def test_code_gc_partial_last_page + # call_threshold: 2 to avoid JIT-ing code_gc itself. If code_gc were JITed right before + # code_gc is called, the last page would be on stack. + assert_compiles(<<~'RUBY', exits: :any, result: :ok, call_threshold: 2) + # Leave a bunch of off-stack pages + i = 0 + while i < 1000 + eval("x = proc { 1.to_s }; x.call; x.call") + i += 1 + end + + # On Linux, memory page size != code page size. So the last code page could be partially + # mapped. This call tests that assertions and other things work fine under the situation. + RubyVM::YJIT.code_gc + + :ok + RUBY + end + + def test_trace_script_compiled # not ISEQ_TRACE_EVENTS + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + @eval_counter = 0 + def eval_script + eval('@eval_counter += 1') + end + + @trace_counter = 0 + trace = TracePoint.new(:script_compiled) do |t| + @trace_counter += 1 + end + + eval_script # JIT without TracePoint + trace.enable + eval_script # call with TracePoint + trace.disable + + return :"eval_#{@eval_counter}" if @eval_counter != 2 + return :"trace_#{@trace_counter}" if @trace_counter != 1 + + :ok + RUBY + end + + def test_trace_b_call # ISEQ_TRACE_EVENTS + assert_compiles(<<~'RUBY', exits: :any, result: :ok) + @call_counter = 0 + def block_call + 1.times { @call_counter += 1 } + end + + @trace_counter = 0 + trace = TracePoint.new(:b_call) do |t| + @trace_counter += 1 + end + + block_call # JIT without TracePoint + trace.enable + block_call # call with TracePoint + trace.disable + + return :"call_#{@call_counter}" if @call_counter != 2 + return :"trace_#{@trace_counter}" if @trace_counter != 1 + + :ok + RUBY + end + + def test_send_to_call + assert_compiles(<<~'RUBY', result: :ok) + ->{ :ok }.send(:call) + RUBY + end + + def test_invokeblock_many_locals + # [Bug #19299] + assert_compiles(<<~'RUBY', result: :ok) + def foo + yield + end + + foo do + a1=a2=a3=a4=a5=a6=a7=a8=a9=a10=a11=a12=a13=a14=a15=a16=a17=a18=a19=a20=a21=a22=a23=a24=a25=a26=a27=a28=a29=a30 = :ok + a30 + end + RUBY + end + + def test_bug_19316 + n = 2 ** 64 + # foo's extra param and the splats are relevant + assert_compiles(<<~'RUBY', result: [[n, -n], [n, -n]], exits: :any) + def foo(_, a, b, c) + [a & b, ~c] + end + + n = 2 ** 64 + args = [0, -n, n, n-1] + + GC.stress = true + [foo(*args), foo(*args)] + RUBY + end + + def test_gc_compact_cyclic_branch + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + assert_compiles(<<~'RUBY', result: 2) + def foo + i = 0 + while i < 2 + i += 1 + end + i + end + + foo + GC.compact + foo + RUBY + end + + def test_invalidate_cyclic_branch + assert_compiles(<<~'RUBY', result: 2, exits: { opt_plus: 1 }) + def foo + i = 0 + while i < 2 + i += 1 + end + i + end + + foo + class Integer + def +(x) = self - -x + end + foo + RUBY + end + + def test_tracing_str_uplus + assert_compiles(<<~RUBY, frozen_string_literal: true, result: :ok, exits: { putspecialobject: 1 }) + def str_uplus + _ = 1 + _ = 2 + ret = [+"frfr", __LINE__] + _ = 3 + _ = 4 + + ret + end + + str_uplus + require 'objspace' + ObjectSpace.trace_object_allocations_start + + str, expected_line = str_uplus + alloc_line = ObjectSpace.allocation_sourceline(str) + + if expected_line == alloc_line + :ok + else + [expected_line, alloc_line] + end + RUBY + end + + def test_str_uplus_subclass + assert_compiles(<<~RUBY, frozen_string_literal: true, result: :subclass) + class S < String + def encoding + :subclass + end + end + + def test(str) + (+str).encoding + end + + test "" + test S.new + RUBY + end + + def test_return_to_invalidated_block + # [Bug #19463] + assert_compiles(<<~RUBY, result: [1, 1, :ugokanai], exits: { definesmethod: 1, getlocal_WC_0: 1 }) + klass = Class.new do + def self.lookup(hash, key) = hash[key] + + def self.foo(a, b) = [] + + def self.test(hash, key) + [lookup(hash, key), key, "".freeze] + # 05 opt_send_without_block :lookup + # 07 getlocal_WC_0 :hash + # 09 opt_str_freeze "" + # 12 newarray 3 + # 14 leave + # + # YJIT will put instructions (07..14) into a block. + # When String#freeze is redefined from within lookup(), + # the return address to the block is still on-stack. We rely + # on invalidation patching the code at the return address + # to service this situation correctly. + end + end + + # get YJIT to compile test() + hash = { 1 => [] } + 31.times { klass.test(hash, 1) } + + # inject invalidation into lookup() + evil_hash = Hash.new do |_, key| + class String + undef :freeze + def freeze = :ugokanai + end + + key + end + klass.test(evil_hash, 1) + RUBY + end + + def test_return_to_invalidated_frame + assert_compiles(code_gc_helpers + <<~RUBY, exits: :any, result: :ok) + def jump + [] # something not inlined + end + + def entry(code_gc) + jit_exception(code_gc) + jump # faulty jump after code GC. #jit_exception should not come back. + end + + def jit_exception(code_gc) + if code_gc + tap do + RubyVM::YJIT.code_gc + break # jit_exec_exception catches TAG_BREAK and re-enters JIT code + end + end + end + + add_pages(100) + jump # Compile #jump in a non-first page + add_pages(100) + entry(false) # Compile #entry and its call to #jump in another page + entry(true) # Free #jump but not #entry + + :ok + RUBY + end + + def test_setivar_on_class + # Bug in https://github.com/ruby/ruby/pull/8152 + assert_compiles(<<~RUBY, result: :ok) + class Base + def self.or_equal + @or_equal ||= Object.new + end + end + + Base.or_equal # ensure compiled + + class Child < Base + end + + 200.times do |iv| # Need to be more than MAX_IVAR + Child.instance_variable_set("@_iv_\#{iv}", Object.new) + end + + Child.or_equal + :ok + RUBY + end + + def test_nested_send + #[Bug #19464] + assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { defineclass: 1 }) + klass = Class.new do + class << self + alias_method :my_send, :send + + def bar = :ok + + def foo = bar + end + end + + with_break = -> { break klass.send(:my_send, :foo) } + wo_break = -> { klass.send(:my_send, :foo) } + + [with_break[], wo_break[]] + RUBY + end + + def test_str_concat_encoding_mismatch + assert_compiles(<<~'RUBY', result: "incompatible character encodings: BINARY (ASCII-8BIT) and EUC-JP") + def bar(a, b) + a << b + rescue => e + e.message + end + + def foo(a, b, h) + h[nil] + bar(a, b) # Ruby call, not set cfp->pc + end + + h = Hash.new { nil } + foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h) + foo("\x80".b, "\xA1A1".dup.force_encoding("EUC-JP"), h) + RUBY + end + + def test_io_reopen_clobbering_singleton_class + assert_compiles(<<~RUBY, result: [:ok, :ok], exits: { definesmethod: 1, opt_eq: 2 }) + def $stderr.to_i = :i + + def test = $stderr.to_i + + [test, test] + $stderr.reopen($stderr.dup) + [test, test].map { :ok unless _1 == :i } + RUBY + end + + def test_proc_block_arg + assert_compiles(<<~RUBY, result: [:proc, :no_block]) + def yield_if_given = block_given? ? yield : :no_block + + def call(block_arg = nil) = yield_if_given(&block_arg) + + [call(-> { :proc }), call] + RUBY + end + + def test_opt_mult_overflow + assert_no_exits('0xfff_ffff_ffff_ffff * 0x10') + end + + def test_disable_stats + assert_in_out_err(%w[--yjit-stats --yjit-disable]) + end + + def test_odd_calls_to_attr_reader + # Use of delegate from ActiveSupport use these kind of calls to getter methods. + assert_compiles(<<~RUBY, result: [1, 1, 1], no_send_fallbacks: true) + class One + attr_reader :one + def initialize + @one = 1 + end + end + + def calls(obj, empty, &) + [obj.one(*empty), obj.one(&), obj.one(*empty, &)] + end + + calls(One.new, []) + RUBY + end + + def test_kwrest + assert_compiles(<<~RUBY, result: true, no_send_fallbacks: true) + def req_rest(r1:, **kwrest) = [r1, kwrest] + def opt_rest(r1: 1.succ, **kwrest) = [r1, kwrest] + def kwrest(**kwrest) = kwrest + + def calls + [ + [1, {}] == req_rest(r1: 1), + [1, {:r2=>2, :r3=>3}] == req_rest(r1: 1, r2: 2, r3: 3), + [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r1:1, r3: 3), + [1, {:r2=>2, :r3=>3}] == req_rest(r2: 2, r3: 3, r1: 1), + + [2, {}] == opt_rest, + [2, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3), + [0, { r2: 2, r3: 3 }] == opt_rest(r1: 0, r3: 3, r2: 2), + [0, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r1: 0, r3: 3), + [1, { r2: 2, r3: 3 }] == opt_rest(r2: 2, r3: 3, r1: 1), + + {} == kwrest, + { r0: 88, r1: 99 } == kwrest(r0: 88, r1: 99), + ] + end + + calls.all? + RUBY + end + + def test_send_polymorphic_method_name + assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true) + mid = "dynamic_mid_#{rand(100..200)}" + mid_dsym = mid.to_sym + + define_method(mid) { :ok } + + define_method(:send_site) { send(_1) } + + [send_site(mid), send_site(mid_dsym)] + RUBY + end + + def test_kw_splat_nil + assert_compiles(<<~'RUBY', result: %i[ok ok], no_send_fallbacks: true) + def id(x) = x + def kw_fw(arg, **) = id(arg, **) + def use = [kw_fw(:ok), :ok.itself(**nil)] + + use + RUBY + end + + def test_empty_splat + assert_compiles(<<~'RUBY', result: :ok, no_send_fallbacks: true) + def foo = :ok + def use(empty) = foo(*empty) + + use([]) + RUBY + end + + def test_byteslice_sp_invalidation + assert_compiles(<<~'RUBY', result: 'ok', no_send_fallbacks: true) + "okng".itself.byteslice(0, 2) + RUBY + end + + def test_leaf_builtin + assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: 1) + before = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf] + return 1 if before.nil? + + def entry = self.class + entry + + after = RubyVM::YJIT.runtime_stats[:num_send_iseq_leaf] + after - before + RUBY + end + + def test_runtime_stats_types + assert_compiles(<<~'RUBY', exits: :any, result: true) + def test = :ok + 3.times { test } + + stats = RubyVM::YJIT.runtime_stats + return true unless stats[:all_stats] + + [ + stats[:object_shape_count].is_a?(Integer), + stats[:ratio_in_yjit].nil? || stats[:ratio_in_yjit].is_a?(Float), + ].all? + RUBY + end + + def test_runtime_stats_key_arg + assert_compiles(<<~'RUBY', exits: :any, result: true) + def test = :ok + 3.times { test } + + # Collect single stat. + stat = RubyVM::YJIT.runtime_stats(:yjit_alloc_size) + + # Ensure this invocation had stats. + return true unless RubyVM::YJIT.runtime_stats[:all_stats] + + stat > 0.0 + RUBY + end + + def test_runtime_stats_arg_error + assert_compiles(<<~'RUBY', exits: :any, result: true) + begin + RubyVM::YJIT.runtime_stats(Object.new) + :no_error + rescue TypeError => e + e.message == "non-symbol given" + end + RUBY + end + + def test_runtime_stats_unknown_key + assert_compiles(<<~'RUBY', exits: :any, result: true) + def test = :ok + 3.times { test } + + RubyVM::YJIT.runtime_stats(:some_key_unlikely_to_exist).nil? + RUBY + end + + def test_yjit_option_uses_array_each_in_ruby + assert_separately(["--yjit"], <<~'RUBY') + # Array#each should be implemented in Ruby for YJIT + assert_equal "<internal:array>", Array.instance_method(:each).source_location.first + + # The backtrace, however, should not be `from <internal:array>:XX:in 'Array#each'` + begin + [nil].each { raise } + rescue => e + assert_equal "-:11:in 'Array#each'", e.backtrace[1] + end + RUBY + end + + def test_yjit_enable_replaces_array_each + assert_separately([*("--disable=yjit" if RubyVM::YJIT.enabled?)], <<~'RUBY') + # Array#each should be implemented in C for the interpreter + assert_nil Array.instance_method(:each).source_location + + # The backtrace should not be `from <internal:array>:XX:in 'Array#each'` + begin + [nil].each { raise } + rescue => e + assert_equal "-:11:in 'Array#each'", e.backtrace[1] + end + + RubyVM::YJIT.enable + + # Array#each should be implemented in Ruby for YJIT + assert_equal "<internal:array>", Array.instance_method(:each).source_location.first + + # However, the backtrace should still not be `from <internal:array>:XX:in 'Array#each'` + begin + [nil].each { raise } + rescue => e + assert_equal "-:23:in 'Array#each'", e.backtrace[1] + end + RUBY + end + + def test_yjit_enable_preserves_array_each_monkey_patch + assert_separately([*("--disable=yjit" if RubyVM::YJIT.enabled?)], <<~'RUBY') + # Array#each should be implemented in C initially + assert_nil Array.instance_method(:each).source_location + + # Override Array#each + $called = false + Array.prepend(Module.new { + def each + $called = true + super + end + }) + + RubyVM::YJIT.enable + + # The monkey-patch should still be alive + [].each {} + assert_true $called + + # YJIT should not replace Array#each with the "<internal:array>" one + assert_equal "-", Array.instance_method(:each).source_location.first + RUBY + end + + def test_yield_kwargs + assert_compiles(<<~RUBY, result: 3, no_send_fallbacks: true) + def req2kws = yield a: 1, b: 2 + + req2kws { |a:, b:| a + b } + RUBY + end + + def test_proc_block_with_kwrest + # When the bug was present this required --yjit-stats to trigger. + assert_compiles(<<~RUBY, result: {extra: 5}) + def foo = bar(w: 1, x: 2, y: 3, z: 4, extra: 5, &proc { _1 }) + def bar(w:, x:, y:, z:, **kwrest) = yield kwrest + + GC.stress = true + foo + foo + RUBY + end + + def test_yjit_dump_insns + # Testing that this undocumented debugging feature doesn't crash + args = [ + '--yjit-call-threshold=1', + '--yjit-dump-insns', + '-e def foo(case:) = {case:}[:case]', + '-e foo(case:0)', + ] + _out, _err, status = invoke_ruby(args, '', true, true) + assert_not_predicate(status, :signaled?) + end + + private + + def code_gc_helpers + <<~'RUBY' + def compiles(&block) + failures = RubyVM::YJIT.runtime_stats[:compilation_failure] + block.call + failures == RubyVM::YJIT.runtime_stats[:compilation_failure] + end + + def add_pages(num_jits) + pages = RubyVM::YJIT.runtime_stats[:live_page_count] + num_jits.times { return false unless eval('compiles { nil.to_i }') } + pages.nil? || pages < RubyVM::YJIT.runtime_stats[:live_page_count] + end + RUBY + end + + def assert_no_exits(script) + assert_compiles(script) + end + + ANY = Object.new + def assert_compiles( + test_script, insns: [], + call_threshold: 1, + stdout: nil, + exits: {}, + result: ANY, + frozen_string_literal: nil, + mem_size: nil, + code_gc: false, + no_send_fallbacks: false + ) + reset_stats = <<~RUBY + RubyVM::YJIT.runtime_stats + RubyVM::YJIT.reset_stats! + RUBY + + write_results = <<~RUBY + stats = RubyVM::YJIT.runtime_stats + + def collect_insns(iseq) + insns = RubyVM::YJIT.insns_compiled(iseq) + iseq.each_child { |c| insns.concat collect_insns(c) } + insns + end + + iseq = RubyVM::InstructionSequence.of(_test_proc) + IO.open(3).write Marshal.dump({ + result: #{result == ANY ? "nil" : "result"}, + stats: stats, + insns: collect_insns(iseq), + disasm: iseq.disasm + }) + RUBY + + script = <<~RUBY + #{"# frozen_string_literal: " + frozen_string_literal.to_s unless frozen_string_literal.nil?} + _test_proc = -> { + #{test_script} + } + #{reset_stats} + result = _test_proc.call + #{write_results} + RUBY + + status, out, err, stats = eval_with_jit(script, call_threshold:, mem_size:, code_gc:) + + assert status.success?, "exited with status #{status.to_i}, stderr:\n#{err}" + + assert_equal stdout.chomp, out.chomp if stdout + + unless ANY.equal?(result) + assert_equal result, stats[:result] + end + + runtime_stats = stats[:stats] + insns_compiled = stats[:insns] + disasm = stats[:disasm] + + # Check that exit counts are as expected + # Full stats are only available when --enable-yjit=dev + if runtime_stats[:all_stats] + recorded_exits = runtime_stats.select { |k, v| k.to_s.start_with?("exit_") } + recorded_exits = recorded_exits.reject { |k, v| v == 0 } + + recorded_exits.transform_keys! { |k| k.to_s.gsub("exit_", "").to_sym } + # Exits can be specified as a hash of stat-name symbol to integer for exact exits. + # or stat-name symbol to range if the number of side exits might vary (e.g. write + # barriers, cache misses.) + if exits != :any && + exits != recorded_exits && + (exits.keys != recorded_exits.keys || !exits.all? { |k, v| v === recorded_exits[k] }) # triple-equal checks range membership or integer equality + stats_reasons = StringIO.new + ::RubyVM::YJIT.send(:_print_stats_reasons, runtime_stats, stats_reasons) + stats_reasons = stats_reasons.string + flunk <<~EOM + Expected #{exits.empty? ? "no" : exits.inspect} exits, but got: + #{recorded_exits.inspect} + Reasons: + #{stats_reasons} + EOM + end + end + + if no_send_fallbacks + assert_equal(0, runtime_stats[:num_send_dynamic], "Expected no use of fallback implementation") + end + + # Only available when --enable-yjit=dev + if runtime_stats[:all_stats] + missed_insns = insns.dup + + insns_compiled.each do |op| + if missed_insns.include?(op) + # This instruction was compiled + missed_insns.delete(op) + end + end + + unless missed_insns.empty? + flunk "Expected to compile instructions #{missed_insns.join(", ")} but didn't.\niseq:\n#{disasm}" + end + end + end + + def script_shell_encode(s) + # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants. + s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join + end + + def eval_with_jit(script, call_threshold: 1, timeout: 1000, mem_size: nil, code_gc: false) + args = [ + "--disable-gems", + "--yjit-call-threshold=#{call_threshold}", + "--yjit-stats=quiet" + ] + args << "--yjit-exec-mem-size=#{mem_size}" if mem_size + args << "--yjit-code-gc" if code_gc + args << "-e" << script_shell_encode(script) + stats_r, stats_w = IO.pipe + # Separate thread so we don't deadlock when + # the child ruby blocks writing the stats to fd 3 + stats = '' + stats_reader = Thread.new do + stats = stats_r.read + stats_r.close + end + out, err, status = invoke_ruby(args, '', true, true, timeout: timeout, ios: { 3 => stats_w }) + stats_w.close + stats_reader.join(timeout) + stats = Marshal.load(stats) if !stats.empty? + [status, out, err, stats] + ensure + stats_reader&.kill + stats_reader&.join(timeout) + stats_r&.close + stats_w&.close + end + + # A wrapper of EnvUtil.invoke_ruby that uses RbConfig.ruby instead of EnvUtil.ruby + # that might use a wrong Ruby depending on your environment. + def invoke_ruby(*args, **kwargs) + EnvUtil.invoke_ruby(*args, rubybin: RbConfig.ruby, **kwargs) + end +end diff --git a/test/ruby/test_yjit_exit_locations.rb b/test/ruby/test_yjit_exit_locations.rb new file mode 100644 index 0000000000..816ab457ce --- /dev/null +++ b/test/ruby/test_yjit_exit_locations.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS='test/ruby/test_yjit_exit_locations.rb' + +require 'test/unit' +require 'envutil' +require 'tmpdir' +require_relative '../lib/jit_support' + +return unless JITSupport.yjit_supported? + +# Tests for YJIT with assertions on tracing exits +# insipired by the RJIT tests in test/ruby/test_yjit.rb +class TestYJITExitLocations < Test::Unit::TestCase + def test_yjit_trace_exits_and_v_no_error + _stdout, stderr, _status = EnvUtil.invoke_ruby(%w(-v --yjit-trace-exits), '', true, true) + refute_includes(stderr, "NoMethodError") + end + + def test_trace_exits_expandarray_splat + assert_exit_locations('*arr = []') + end + + private + + def assert_exit_locations(test_script) + write_results = <<~RUBY + IO.open(3).write Marshal.dump({ + enabled: RubyVM::YJIT.trace_exit_locations_enabled?, + exit_locations: RubyVM::YJIT.exit_locations + }) + RUBY + + script = <<~RUBY + _test_proc = -> { + #{test_script} + } + result = _test_proc.call + #{write_results} + RUBY + + run_script = eval_with_jit(script) + # If stats are disabled when configuring, --yjit-exit-locations + # can't be true. We don't want to check if exit_locations hash + # is not empty because that could indicate a bug in the exit + # locations collection. + return unless run_script[:enabled] + exit_locations = run_script[:exit_locations] + + assert exit_locations.key?(:raw) + assert exit_locations.key?(:frames) + assert exit_locations.key?(:lines) + assert exit_locations.key?(:samples) + assert exit_locations.key?(:missed_samples) + assert exit_locations.key?(:gc_samples) + + assert_equal 0, exit_locations[:missed_samples] + assert_equal 0, exit_locations[:gc_samples] + + assert_not_empty exit_locations[:raw] + assert_not_empty exit_locations[:frames] + assert_not_empty exit_locations[:lines] + + exit_locations[:frames].each do |frame_id, frame| + assert frame.key?(:name) + assert frame.key?(:file) + assert frame.key?(:samples) + assert frame.key?(:total_samples) + assert frame.key?(:edges) + end + end + + def eval_with_jit(script) + args = [ + "--disable-gems", + "--yjit-call-threshold=1", + "--yjit-trace-exits" + ] + args << "-e" << script_shell_encode(script) + stats_r, stats_w = IO.pipe + _out, _err, _status = EnvUtil.invoke_ruby(args, + '', true, true, timeout: 1000, ios: { 3 => stats_w } + ) + stats_w.close + stats = stats_r.read + stats = Marshal.load(stats) if !stats.empty? + stats_r.close + stats + end + + def script_shell_encode(s) + # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants. + s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join + end +end diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb new file mode 100644 index 0000000000..e347986abc --- /dev/null +++ b/test/ruby/test_zjit.rb @@ -0,0 +1,4858 @@ +# frozen_string_literal: true +# +# This set of tests can be run with: +# make test-all TESTS=test/ruby/test_zjit.rb + +require 'test/unit' +require 'envutil' +require_relative '../lib/jit_support' +return unless JITSupport.zjit_supported? + +class TestZJIT < Test::Unit::TestCase + def test_enabled + assert_runs 'false', <<~RUBY, zjit: false + RubyVM::ZJIT.enabled? + RUBY + assert_runs 'true', <<~RUBY, zjit: true + RubyVM::ZJIT.enabled? + RUBY + end + + def test_stats_enabled + assert_runs 'false', <<~RUBY, stats: false + RubyVM::ZJIT.stats_enabled? + RUBY + assert_runs 'true', <<~RUBY, stats: true + RubyVM::ZJIT.stats_enabled? + RUBY + end + + def test_stats_quiet + # Test that --zjit-stats-quiet collects stats but doesn't print them + script = <<~RUBY + def test = 42 + test + test + puts RubyVM::ZJIT.stats_enabled? + RUBY + + stats_header = "***ZJIT: Printing ZJIT statistics on exit***" + + # With --zjit-stats, stats should be printed to stderr + out, err, status = eval_with_jit(script, stats: true) + assert_success(out, err, status) + assert_includes(err, stats_header) + assert_equal("true\n", out) + + # With --zjit-stats-quiet, stats should NOT be printed but still enabled + out, err, status = eval_with_jit(script, stats: :quiet) + assert_success(out, err, status) + refute_includes(err, stats_header) + assert_equal("true\n", out) + + # With --zjit-stats=<path>, stats should be printed to the path + Tempfile.create("zjit-stats-") {|tmp| + stats_file = tmp.path + tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...") + tmp.close + + out, err, status = eval_with_jit(script, stats: stats_file) + assert_success(out, err, status) + refute_includes(err, stats_header) + assert_equal("true\n", out) + assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten" + } + end + + def test_enable_through_env + child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'} + assert_in_out_err([child_env, '-v'], '') do |stdout, stderr| + assert_includes(stdout.first, '+ZJIT') + assert_equal([], stderr) + end + end + + def test_zjit_enable + # --disable-all is important in case the build/environment has YJIT enabled by + # default through e.g. -DYJIT_FORCE_ENABLE. Can't enable ZJIT when YJIT is on. + assert_separately(["--disable-all"], <<~'RUBY') + refute_predicate RubyVM::ZJIT, :enabled? + refute_predicate RubyVM::ZJIT, :stats_enabled? + refute_includes RUBY_DESCRIPTION, "+ZJIT" + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + refute_predicate RubyVM::ZJIT, :stats_enabled? + assert_includes RUBY_DESCRIPTION, "+ZJIT" + RUBY + end + + def test_zjit_disable + assert_separately(["--zjit", "--zjit-disable"], <<~'RUBY') + refute_predicate RubyVM::ZJIT, :enabled? + refute_includes RUBY_DESCRIPTION, "+ZJIT" + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + assert_includes RUBY_DESCRIPTION, "+ZJIT" + RUBY + end + + def test_zjit_enable_respects_existing_options + assert_separately(['--zjit-disable', '--zjit-stats-quiet'], <<~RUBY) + refute_predicate RubyVM::ZJIT, :enabled? + assert_predicate RubyVM::ZJIT, :stats_enabled? + + RubyVM::ZJIT.enable + + assert_predicate RubyVM::ZJIT, :enabled? + assert_predicate RubyVM::ZJIT, :stats_enabled? + RUBY + end + + def test_call_itself + assert_compiles '42', <<~RUBY, call_threshold: 2 + def test = 42.itself + test + test + RUBY + end + + def test_nil + assert_compiles 'nil', %q{ + def test = nil + test + } + end + + def test_putobject + assert_compiles '1', %q{ + def test = 1 + test + } + end + + def test_putstring + assert_compiles '""', %q{ + def test = "#{""}" + test + }, insns: [:putstring] + end + + def test_putchilldedstring + assert_compiles '""', %q{ + def test = "" + test + }, insns: [:putchilledstring] + end + + def test_leave_param + assert_compiles '5', %q{ + def test(n) = n + test(5) + } + end + + def test_getglobal_with_warning + assert_compiles('"rescued"', %q{ + Warning[:deprecated] = true + + module Warning + def warn(message) + raise + end + end + + def test + $= + rescue + "rescued" + end + + $VERBOSE = true + test + }, insns: [:getglobal]) + end + + def test_setglobal + assert_compiles '1', %q{ + def test + $a = 1 + $a + end + + test + }, insns: [:setglobal] + end + + def test_string_intern + assert_compiles ':foo123', %q{ + def test + :"foo#{123}" + end + + test + }, insns: [:intern] + end + + def test_duphash + assert_compiles '{a: 1}', %q{ + def test + {a: 1} + end + + test + }, insns: [:duphash] + end + + def test_pushtoarray + assert_compiles '[1, 2, 3]', %q{ + def test + [*[], 1, 2, 3] + end + test + }, insns: [:pushtoarray] + end + + def test_splatarray_new_array + assert_compiles '[1, 2, 3]', %q{ + def test a + [*a, 3] + end + test [1, 2] + }, insns: [:splatarray] + end + + def test_splatarray_existing_array + assert_compiles '[1, 2, 3]', %q{ + def foo v + [1, 2, v] + end + def test a + foo(*a) + end + test [3] + }, insns: [:splatarray] + end + + def test_concattoarray + assert_compiles '[1, 2, 3]', %q{ + def test(*a) + [1, 2, *a] + end + test 3 + }, insns: [:concattoarray] + end + + def test_definedivar + assert_compiles '[nil, "instance-variable", nil]', %q{ + def test + v0 = defined?(@a) + @a = nil + v1 = defined?(@a) + remove_instance_variable :@a + v2 = defined?(@a) + [v0, v1, v2] + end + test + }, insns: [:definedivar] + end + + def test_setglobal_with_trace_var_exception + assert_compiles '"rescued"', %q{ + def test + $a = 1 + rescue + "rescued" + end + + trace_var(:$a) { raise } + test + }, insns: [:setglobal] + end + + def test_toplevel_binding + # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`. + out, err, status = eval_with_jit(%q{ + a = 1 + b = 2 + TOPLEVEL_BINDING.local_variable_set(:b, 3) + c = 4 + print [a, b, c] + }) + assert_success(out, err, status) + assert_equal "[1, 3, 4]", out + end + + def test_toplevel_local_after_eval + # Not using assert_compiles, which doesn't use the toplevel frame for `test_script`. + out, err, status = eval_with_jit(%q{ + a = 1 + b = 2 + eval('b = 3') + c = 4 + print [a, b, c] + }) + assert_success(out, err, status) + assert_equal "[1, 3, 4]", out + end + + def test_getlocal_after_eval + assert_compiles '2', %q{ + def test + a = 1 + eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_instance_eval + assert_compiles '2', %q{ + def test + a = 1 + instance_eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_module_eval + assert_compiles '2', %q{ + def test + a = 1 + Kernel.module_eval('a = 2') + a + end + test + } + end + + def test_getlocal_after_class_eval + assert_compiles '2', %q{ + def test + a = 1 + Kernel.class_eval('a = 2') + a + end + test + } + end + + def test_setlocal + assert_compiles '3', %q{ + def test(n) + m = n + m + end + test(3) + } + end + + def test_return_nonparam_local + # Use dead code (if false) to create a local without initialization instructions. + assert_compiles 'nil', %q{ + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test + }, call_threshold: 2 + end + + def test_nonparam_local_nil_in_jit_call + # Non-parameter locals must be initialized to nil in JIT-to-JIT calls. + # Use dead code (if false) to create locals without initialization instructions. + # Then eval a string that accesses the uninitialized locals. + assert_compiles '["x", "x", "x", "x"]', %q{ + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + }, call_threshold: 2 + end + + def test_kwargs_with_exit_and_local_invalidation + assert_compiles ':ok', %q{ + def a(b:, c:) + if c == :b + return -> {} + end + Class # invalidate locals + + raise "c is :b!" if c == :b + end + + def test + # note opposite order of kwargs + a(c: :c, b: :b) + end + + 4.times { test } + :ok + }, call_threshold: 2 + end + + def test_kwargs_with_max_direct_send_arg_count + # Ensure that we only reorder the args when we _can_ use direct send (< 6 args). + assert_compiles '[[1, 2, 3, 4, 5, 6, 7, 8]]', %q{ + def kwargs(five, six, a:, b:, c:, d:, e:, f:) + [a, b, c, d, five, six, e, f] + end + + 5.times.flat_map do + [ + kwargs(5, 6, d: 4, c: 3, a: 1, b: 2, e: 7, f: 8), + kwargs(5, 6, d: 4, c: 3, b: 2, a: 1, e: 7, f: 8) + ] + end.uniq + }, call_threshold: 2 + end + + def test_setlocal_on_eval + assert_compiles '1', %q{ + @b = binding + eval('a = 1', @b) + eval('a', @b) + } + end + + def test_optional_arguments + assert_compiles '[[1, 2, 3], [10, 20, 3], [100, 200, 300]]', %q{ + def test(a, b = 2, c = 3) + [a, b, c] + end + [test(1), test(10, 20), test(100, 200, 300)] + } + end + + def test_optional_arguments_setlocal + assert_compiles '[[2, 2], [1, nil]]', %q{ + def test(a = (b = 2)) + [a, b] + end + [test, test(1)] + } + end + + def test_optional_arguments_cyclic + assert_compiles '[nil, 1]', %q{ + test = proc { |a=a| a } + [test.call, test.call(1)] + } + end + + def test_optional_arguments_side_exit + # This leads to FailedOptionalArguments, so not using assert_compiles + assert_runs '[:foo, nil, 1]', %q{ + def test(a = (def foo = nil)) = a + [test, (undef :foo), test(1)] + } + end + + def test_getblockparamproxy + assert_compiles '1', %q{ + def test(&block) + 0.then(&block) + end + test { 1 } + }, insns: [:getblockparamproxy] + end + + def test_optimized_method_call_proc_call + assert_compiles '2', %q{ + p = proc { |x| x * 2 } + def test(p) + p.call(1) + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_aref + assert_compiles '4', %q{ + p = proc { |x| x * 2 } + def test(p) + p[2] + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_optimized_method_call_proc_yield + assert_compiles '6', %q{ + p = proc { |x| x * 2 } + def test(p) + p.yield(3) + end + test(p) + test(p) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_kw_splat + assert_compiles '3', %q{ + p = proc { |**kw| kw[:a] + kw[:b] } + def test(p, h) + p.call(**h) + end + h = { a: 1, b: 2 } + test(p, h) + test(p, h) + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_optimized_method_call_proc_call_splat + assert_compiles '43', %q{ + p = proc { |x| x + 1 } + def test(p) + ary = [42] + p.call(*ary) + end + test(p) + test(p) + }, call_threshold: 2 + end + + def test_optimized_method_call_proc_call_kwarg + assert_compiles '1', %q{ + p = proc { |a:| a } + def test(p) + p.call(a: 1) + end + test(p) + test(p) + }, call_threshold: 2 + end + + def test_call_a_forwardable_method + assert_runs '[]', %q{ + def test_root = forwardable + def forwardable(...) = Array.[](...) + test_root + test_root + }, call_threshold: 2 + end + + def test_setlocal_on_eval_with_spill + assert_compiles '1', %q{ + @b = binding + eval('a = 1; itself', @b) + eval('a', @b) + } + end + + def test_nested_local_access + assert_compiles '[1, 2, 3]', %q{ + 1.times do |l2| + 1.times do |l1| + define_method(:test) do + l1 = 1 + l2 = 2 + l3 = 3 + [l1, l2, l3] + end + end + end + + test + test + test + }, call_threshold: 3, insns: [:getlocal, :setlocal, :getlocal_WC_0, :setlocal_WC_1] + end + + def test_send_with_local_written_by_blockiseq + assert_compiles '[1, 2]', %q{ + def test + l1 = nil + l2 = nil + tap do |_| + l1 = 1 + tap do |_| + l2 = 2 + end + end + + [l1, l2] + end + + test + test + }, call_threshold: 2 + end + + def test_send_without_block + assert_compiles '[1, 2, 3]', %q{ + def foo = 1 + def bar(a) = a - 1 + def baz(a, b) = a - b + + def test1 = foo + def test2 = bar(3) + def test3 = baz(4, 1) + + [test1, test2, test3] + } + end + + def test_send_with_six_args + assert_compiles '[1, 2, 3, 4, 5, 6]', %q{ + def foo(a1, a2, a3, a4, a5, a6) + [a1, a2, a3, a4, a5, a6] + end + + def test + foo(1, 2, 3, 4, 5, 6) + end + + test # profile send + test + }, call_threshold: 2 + end + + def test_send_on_heap_object_in_spilled_arg + # This leads to a register spill, so not using `assert_compiles` + assert_runs 'Hash', %q{ + def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9) + a9.itself.class + end + + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) # profile + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + }, call_threshold: 2 + end + + def test_send_exit_with_uninitialized_locals + assert_runs 'nil', %q{ + def entry(init) + function_stub_exit(init) + end + + def function_stub_exit(init) + uninitialized_local = 1 if init + uninitialized_local + end + + entry(true) # profile and set 1 to the local slot + entry(false) + }, call_threshold: 2, allowed_iseqs: 'entry@-e:2' + end + + def test_send_optional_arguments + assert_compiles '[[1, 2], [3, 4]]', %q{ + def test(a, b = 2) = [a, b] + def entry = [test(1), test(3, 4)] + entry + entry + }, call_threshold: 2 + end + + def test_send_nil_block_arg + assert_compiles 'false', %q{ + def test = block_given? + def entry = test(&nil) + test + } + end + + def test_send_symbol_block_arg + assert_compiles '["1", "2"]', %q{ + def test = [1, 2].map(&:to_s) + test + } + end + + def test_send_variadic_with_block + assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{ + A = [1, 2, 3] + B = ["a", "b", "c"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + + test; test + }, call_threshold: 2 + end + + def test_send_splat + assert_runs '[1, 2]', %q{ + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + } + end + + def test_send_kwarg + assert_runs '[1, 2]', %q{ + def test(a:, b:) = [a, b] + def entry = test(b: 2, a: 1) # change order + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional + assert_compiles '[1, 2]', %q{ + def test(a: 1, b: 2) = [a, b] + def entry = test + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_too_many + assert_compiles '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', %q{ + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] + def entry = test + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_required_and_optional + assert_compiles '[3, 2]', %q{ + def test(a:, b: 2) = [a, b] + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_to_hash + assert_compiles '{a: 3}', %q{ + def test(hash) = hash + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) = s.each_line(chomp: true).to_a + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_and_block_to_ccall + assert_compiles '["a", "b", "c"]', %q{ + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + def entry = test(%(a\nb\nc)) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_with_too_many_args_to_c_call + assert_compiles '"a b c d {kwargs: :e}"', %q{ + def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) + def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwsplat + assert_compiles '3', %q{ + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + }, call_threshold: 2 + end + + def test_send_kwrest + assert_compiles '{a: 3}', %q{ + def test(**kwargs) = kwargs + def entry = test(a: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_req_kwreq + assert_compiles '[1, 3]', %q{ + def test(a, c:) = [a, c] + def entry = test(1, c: 3) + entry + entry + }, call_threshold: 2 + end + + def test_send_req_opt_kwreq + assert_compiles '[[1, 2, 3], [-1, -2, -3]]', %q{ + def test(a, b = 2, c:) = [a, b, c] + def entry = [test(1, c: 3), test(-1, -2, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + + def test_send_req_opt_kwreq_kwopt + assert_compiles '[[1, 2, 3, 4], [-1, -2, -3, -4]]', %q{ + def test(a, b = 2, c:, d: 4) = [a, b, c, d] + def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] # specify all, change kw order + entry + entry + }, call_threshold: 2 + end + + def test_send_unexpected_keyword + assert_compiles ':error', %q{ + def test(a: 1) = a*2 + def entry + test(z: 2) + rescue ArgumentError + :error + end + + entry + entry + }, call_threshold: 2 + end + + def test_pos_optional_with_maybe_too_many_args + assert_compiles '[[1, 2, 3, 4, 5, 6], [10, 20, 30, 4, 5, 6], [10, 20, 30, 40, 50, 60]]', %q{ + def target(a = 1, b = 2, c = 3, d = 4, e = 5, f:) = [a, b, c, d, e, f] + def test = [target(f: 6), target(10, 20, 30, f: 6), target(10, 20, 30, 40, 50, f: 60)] + test + test + }, call_threshold: 2 + end + + def test_send_kwarg_partial_optional + assert_compiles '[[1, 2, 3], [1, 20, 3], [10, 2, 30]]', %q{ + def test(a: 1, b: 2, c: 3) = [a, b, c] + def entry = [test, test(b: 20), test(c: 30, a: 10)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_a_lot + assert_compiles '[[1, 2, 3, 4, 5, 6], [1, 2, 3, 7, 8, 9], [2, 4, 6, 8, 10, 12]]', %q{ + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6) = [a, b, c, d, e, f] + def entry = [test, test(d: 7, f: 9, e: 8), test(f: 12, e: 10, d: 8, c: 6, b: 4, a: 2)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_non_constant_default + assert_compiles '[[1, 2], [10, 2]]', %q{ + def make_default = 2 + def test(a: 1, b: make_default) = [a, b] + def entry = [test, test(a: 10)] + entry + entry + }, call_threshold: 2 + end + + def test_send_kwarg_optional_static_with_side_exit + # verify frame reconstruction with synthesized keyword defaults is correct + assert_compiles '[10, 2, 10]', %q{ + def callee(a: 1, b: 2) + # use binding to force side-exit + x = binding.local_variable_get(:a) + [a, b, x] + end + + def entry + callee(a: 10) # b should get default value + end + + entry + entry + }, call_threshold: 2 + end + + def test_send_all_arg_types + assert_compiles '[:req, :opt, :post, :kwr, :kwo, true]', %q{ + def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] + def entry = test(:req, :post, d: :kwr) {} + entry + entry + }, call_threshold: 2 + end + + def test_send_ccall_variadic_with_different_receiver_classes + assert_compiles '[true, true]', %q{ + def test(obj) = obj.start_with?("a") + [test("abc"), test(:abc)] + }, call_threshold: 2 + end + + def test_forwardable_iseq + assert_compiles '1', %q{ + def test(...) = 1 + test + } + end + + def test_sendforward + assert_compiles '[1, 2]', %q{ + def callee(a, b) = [a, b] + def test(...) = callee(...) + test(1, 2) + }, insns: [:sendforward] + end + + def test_iseq_with_optional_arguments + assert_compiles '[[1, 2], [3, 4]]', %q{ + def test(a, b = 2) = [a, b] + [test(1), test(3, 4)] + } + end + + def test_invokesuper + assert_compiles '[6, 60]', %q{ + class Foo + def foo(a) = a + 1 + def bar(a) = a + 10 + end + + class Bar < Foo + def foo(a) = super(a) + 2 + def bar(a) = super + 20 + end + + bar = Bar.new + [bar.foo(3), bar.bar(30)] + } + end + + def test_invokesuper_with_local_written_by_blockiseq + # Using `assert_runs` because we don't compile invokeblock yet + assert_runs '3', %q{ + class Foo + def test + yield + end + end + + class Bar < Foo + def test + a = 1 + super do + a += 2 + end + a + end + end + + Bar.new.test + } + end + + def test_invokesuper_to_iseq + assert_compiles '["B", "A"]', %q{ + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_args + assert_compiles '["B", 11]', %q{ + class A + def foo(x) + x * 2 + end + end + + class B < A + def foo(x) + ["B", super(x) + 1] + end + end + + def test + B.new.foo(5) + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test super with explicit args when callee has rest parameter. + # This should fall back to dynamic dispatch since we can't handle rest params yet. + def test_invokesuper_with_args_to_rest_param + assert_compiles '["B", "a", ["b", "c"]]', %q{ + class A + def foo(x, *rest) + [x, rest] + end + end + + class B < A + def foo(x, y, z) + ["B", *super(x, y, z)] + end + end + + def test + B.new.foo("a", "b", "c") + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_block + assert_compiles '["B", "from_block"]', %q{ + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super { "from_block" }] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_to_cfunc + assert_compiles '["MyArray", 3]', %q{ + class MyArray < Array + def length + ["MyArray", super] + end + end + + def test + MyArray.new([1, 2, 3]).length + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_multilevel + assert_compiles '["C", ["B", "A"]]', %q{ + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + class C < B + def foo + ["C", super] + end + end + + def test + C.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test implicit block forwarding - super without explicit block should forward caller's block + # Note: We call test twice to ensure ZJIT compiles it before the final call that we check + def test_invokesuper_forwards_block_implicitly + assert_compiles '["B", "forwarded_block"]', %q{ + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # should forward the block from caller + end + end + + def test + B.new.foo { "forwarded_block" } + end + + test # profile invokesuper + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test implicit block forwarding with explicit arguments + def test_invokesuper_forwards_block_implicitly_with_args + assert_compiles '["B", ["arg_value", "forwarded"]]', %q{ + class A + def foo(x) + [x, (block_given? ? yield : "no_block")] + end + end + + class B < A + def foo(x) + ["B", super(x)] # explicit args, but block should still be forwarded + end + end + + def test + B.new.foo("arg_value") { "forwarded" } + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test implicit block forwarding when no block is given (should not fail) + def test_invokesuper_forwards_block_implicitly_no_block_given + assert_compiles '["B", "no_block"]', %q{ + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # no block given by caller + end + end + + def test + B.new.foo # called without a block + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test implicit block forwarding through multiple inheritance levels + def test_invokesuper_forwards_block_implicitly_multilevel + assert_compiles '["C", ["B", "deep_block"]]', %q{ + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # forwards block to A + end + end + + class C < B + def foo + ["C", super] # forwards block to B, which forwards to A + end + end + + def test + C.new.foo { "deep_block" } + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test implicit block forwarding with block parameter syntax + def test_invokesuper_forwards_block_param + assert_compiles '["B", "block_param_forwarded"]', %q{ + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo(&block) + ["B", super] # should forward &block implicitly + end + end + + def test + B.new.foo { "block_param_forwarded" } + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_blockarg + assert_compiles '["B", "different block"]', %q{ + class A + def foo + block_given? ? yield : "no block" + end + end + + class B < A + def foo(&blk) + other_block = proc { "different block" } + ["B", super(&other_block)] + end + end + + def test + B.new.foo { "passed block" } + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_symbol_to_proc + assert_compiles '["B", [3, 5, 7]]', %q{ + class A + def foo(items, &blk) + items.map(&blk) + end + end + + class B < A + def foo(items) + ["B", super(items, &:succ)] + end + end + + def test + B.new.foo([2, 4, 6]) + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_splat + assert_compiles '["B", 6]', %q{ + class A + def foo(a, b, c) + a + b + c + end + end + + class B < A + def foo(*args) + ["B", super(*args)] + end + end + + def test + B.new.foo(1, 2, 3) + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_kwargs + assert_compiles '["B", "x=1, y=2"]', %q{ + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(x:, y:) + ["B", super(x: x, y: y)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + def test_invokesuper_with_kw_splat + assert_compiles '["B", "x=1, y=2"]', %q{ + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(**kwargs) + ["B", super(**kwargs)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + }, call_threshold: 2 + end + + # Test that including a module after compilation correctly changes the super target. + # The included module's method should be called, not the original super target. + def test_invokesuper_with_include + assert_compiles '["B", "M"]', %q{ + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now include a module in B that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + B.include(M) + + test # should call M#foo, not A#foo + }, call_threshold: 2 + end + + # Test that prepending a module after compilation correctly changes the super target. + # The prepended module's method should be called, not the original super target. + def test_invokesuper_with_prepend + assert_compiles '["B", "M"]', %q{ + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now prepend a module that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + A.prepend(M) + + test # should call M#foo, not A#foo + }, call_threshold: 2 + end + + # Test super with positional and keyword arguments (pattern from chunky_png) + def test_invokesuper_with_keyword_args + assert_compiles '{content: "image data"}', %q{ + class A + def foo(attributes = {}) + @attributes = attributes + end + end + + class B < A + def foo(content = '') + super(content: content) + end + end + + def test + B.new.foo("image data") + end + + test + test + }, call_threshold: 2 + end + + def test_invokesuper_with_optional_keyword_args + assert_compiles '[1, 2, 3]', %q{ + class Parent + def foo(a, b: 2, c: 3) = [a, b, c] + end + + class Child < Parent + def foo(a) = super(a) + end + + def test = Child.new.foo(1) + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default + assert_compiles '[[2, 4, 16], [10, 4, 16], [2, 20, 16], [2, 4, 30], [10, 20, 30]]', %q{ + def dbl(x = 1) = x * 2 + + def foo(a: dbl, b: dbl(2), c: dbl(2 ** 3)) + [a, b, c] + end + + def test + [ + foo, + foo(a: 10), + foo(b: 20), + foo(c: 30), + foo(a: 10, b: 20, c: 30) + ] + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_not_evaluated_when_provided + assert_compiles '[1, 2, 3]', %q{ + def foo(a: raise, b: raise, c: raise) + [a, b, c] + end + + def test + foo(a: 1, b: 2, c: 3) + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_evaluated_when_not_provided + assert_compiles '["a", "b", "c"]', %q{ + def raise_a = raise "a" + def raise_b = raise "b" + def raise_c = raise "c" + + def foo(a: raise_a, b: raise_b, c: raise_c) + [a, b, c] + end + + def test_a + foo(b: 2, c: 3) + rescue RuntimeError => e + e.message + end + + def test_b + foo(a: 1, c: 3) + rescue RuntimeError => e + e.message + end + + def test_c + foo(a: 1, b: 2) + rescue RuntimeError => e + e.message + end + + def test + [test_a, test_b, test_c] + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_jit_to_jit + # Test that kw_bits passing works correctly in JIT-to-JIT calls + assert_compiles '[2, 4, 6]', %q{ + def make_default(x) = x * 2 + + def callee(a: make_default(1), b: make_default(2), c: make_default(3)) + [a, b, c] + end + + def caller_method + callee + end + + # Warm up callee first so it gets JITted + callee + callee + + # Now warm up caller - this creates JIT-to-JIT call + caller_method + caller_method + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_side_exit + # Verify frame reconstruction includes correct values for non-constant defaults + assert_compiles '[10, 2, 30]', %q{ + def make_b = 2 + + def callee(a: 1, b: make_b, c: 3) + x = binding.local_variable_get(:a) + y = binding.local_variable_get(:b) + z = binding.local_variable_get(:c) + [x, y, z] + end + + def test + callee(a: 10, c: 30) + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_non_constant_keyword_default_evaluation_order + # Verify defaults are evaluated left-to-right and only when not provided + assert_compiles '[["a", "b", "c"], ["b", "c"], ["a", "c"], ["a", "b"]]', %q{ + def log(x) + $order << x + x + end + + def foo(a: log("a"), b: log("b"), c: log("c")) + [a, b, c] + end + + def test + results = [] + + $order = [] + foo + results << $order.dup + + $order = [] + foo(a: "A") + results << $order.dup + + $order = [] + foo(b: "B") + results << $order.dup + + $order = [] + foo(c: "C") + results << $order.dup + + results + end + + test + test + }, call_threshold: 2 + end + + def test_send_with_too_many_non_constant_keyword_defaults + assert_compiles '35', %q{ + def many_kwargs( k1: 1, k2: 2, k3: 3, k4: 4, k5: 5, k6: 6, k7: 7, k8: 8, k9: 9, k10: 10, k11: 11, k12: 12, k13: 13, k14: 14, k15: 15, k16: 16, k17: 17, k18: 18, k19: 19, k20: 20, k21: 21, k22: 22, k23: 23, k24: 24, k25: 25, k26: 26, k27: 27, k28: 28, k29: 29, k30: 30, k31: 31, k32: 32, k33: 33, k34: k33 + 1) = k1 + k34 + def t = many_kwargs + t + t + }, call_threshold: 2 + end + + def test_invokebuiltin + # Not using assert_compiles due to register spill + assert_runs '["."]', %q{ + def test = Dir.glob(".") + test + } + end + + def test_invokebuiltin_delegate + assert_compiles '[[], true]', %q{ + def test = [].clone(freeze: true) + r = test + [r, r.frozen?] + } + end + + def test_opt_plus_const + assert_compiles '3', %q{ + def test = 1 + 2 + test # profile opt_plus + test + }, call_threshold: 2 + end + + def test_opt_plus_fixnum + assert_compiles '3', %q{ + def test(a, b) = a + b + test(0, 1) # profile opt_plus + test(1, 2) + }, call_threshold: 2 + end + + def test_opt_plus_chain + assert_compiles '6', %q{ + def test(a, b, c) = a + b + c + test(0, 1, 2) # profile opt_plus + test(1, 2, 3) + }, call_threshold: 2 + end + + def test_opt_plus_left_imm + assert_compiles '3', %q{ + def test(a) = 1 + a + test(1) # profile opt_plus + test(2) + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_exit + assert_compiles '[3, 3.0]', %q{ + def test(a) = 1 + a + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_exit_with_locals + assert_compiles '[6, 6.0]', %q{ + def test(a) + local = 3 + 1 + a + local + end + test(1) # profile opt_plus + [test(2), test(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit + assert_compiles '[4, 4.0]', %q{ + def side_exit(n) = 1 + n + def jit_frame(n) = 1 + side_exit(n) + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + + def test_opt_plus_type_guard_nested_exit_with_locals + assert_compiles '[9, 9.0]', %q{ + def side_exit(n) + local = 2 + 1 + n + local + end + def jit_frame(n) + local = 3 + 1 + side_exit(n) + local + end + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + }, call_threshold: 2 + end + + # Test argument ordering + def test_opt_minus + assert_compiles '2', %q{ + def test(a, b) = a - b + test(2, 1) # profile opt_minus + test(6, 4) + }, call_threshold: 2 + end + + def test_opt_mult + assert_compiles '6', %q{ + def test(a, b) = a * b + test(1, 2) # profile opt_mult + test(2, 3) + }, call_threshold: 2 + end + + def test_opt_mult_overflow + assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{ + def test(a, b) + a * b + end + test(1, 1) # profile opt_mult + + r1 = test(2, 3) + r2 = test(2, -3) + r3 = test(2 << 40, 2 << 41) + r4 = test(2 << 40, -2 << 41) + r5 = test(1 << 62, 1 << 62) + + [r1, r2, r3, r4, r5] + }, call_threshold: 2 + end + + def test_opt_eq + assert_compiles '[true, false]', %q{ + def test(a, b) = a == b + test(0, 2) # profile opt_eq + [test(1, 1), test(0, 1)] + }, insns: [:opt_eq], call_threshold: 2 + end + + def test_opt_eq_with_minus_one + assert_compiles '[false, true]', %q{ + def test(a) = a == -1 + test(1) # profile opt_eq + [test(0), test(-1)] + }, insns: [:opt_eq], call_threshold: 2 + end + + def test_opt_neq_dynamic + # TODO(max): Don't split this test; instead, run all tests with and without + # profiling. + assert_compiles '[false, true]', %q{ + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + }, insns: [:opt_neq], call_threshold: 1 + end + + def test_opt_neq_fixnum + assert_compiles '[false, true]', %q{ + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + }, call_threshold: 2 + end + + def test_opt_lt + assert_compiles '[true, false, false]', %q{ + def test(a, b) = a < b + test(2, 3) # profile opt_lt + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_lt], call_threshold: 2 + end + + def test_opt_lt_with_literal_lhs + assert_compiles '[false, false, true]', %q{ + def test(n) = 2 < n + test(2) # profile opt_lt + [test(1), test(2), test(3)] + }, insns: [:opt_lt], call_threshold: 2 + end + + def test_opt_le + assert_compiles '[true, true, false]', %q{ + def test(a, b) = a <= b + test(2, 3) # profile opt_le + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_le], call_threshold: 2 + end + + def test_opt_gt + assert_compiles '[false, false, true]', %q{ + def test(a, b) = a > b + test(2, 3) # profile opt_gt + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_gt], call_threshold: 2 + end + + def test_opt_empty_p + assert_compiles('[false, false, true]', <<~RUBY, insns: [:opt_empty_p]) + def test(x) = x.empty? + return test([1]), test("1"), test({}) + RUBY + end + + def test_opt_succ + assert_compiles('[0, "B"]', <<~RUBY, insns: [:opt_succ]) + def test(obj) = obj.succ + return test(-1), test("A") + RUBY + end + + def test_opt_and + assert_compiles('[1, [3, 2, 1]]', <<~RUBY, insns: [:opt_and]) + def test(x, y) = x & y + return test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3]) + RUBY + end + + def test_opt_or + assert_compiles('[11, [3, 2, 1]]', <<~RUBY, insns: [:opt_or]) + def test(x, y) = x | y + return test(0b1000, 3), test([3, 2, 1], [1, 2, 3]) + RUBY + end + + def test_fixnum_and + assert_compiles '[1, 2, 4]', %q{ + def test(a, b) = a & b + [ + test(5, 3), + test(0b011, 0b110), + test(-0b011, 0b110) + ] + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_and_side_exit + assert_compiles '[2, 2, false]', %q{ + def test(a, b) = a & b + [ + test(2, 2), + test(0b011, 0b110), + test(true, false) + ] + }, call_threshold: 2, insns: [:opt_and] + end + + def test_fixnum_or + assert_compiles '[7, 3, -3]', %q{ + def test(a, b) = a | b + [ + test(5, 3), + test(1, 2), + test(1, -4) + ] + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_or_side_exit + assert_compiles '[3, 2, true]', %q{ + def test(a, b) = a | b + [ + test(1, 2), + test(2, 2), + test(true, false) + ] + }, call_threshold: 2, insns: [:opt_or] + end + + def test_fixnum_xor + assert_compiles '[6, -8, 3]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(-5, 3), + test(1, 2) + ] + }, call_threshold: 2 + end + + def test_fixnum_xor_side_exit + assert_compiles '[6, 6, true]', %q{ + def test(a, b) = a ^ b + [ + test(5, 3), + test(5, 3), + test(true, false) + ] + }, call_threshold: 2 + end + + def test_fixnum_mul + assert_compiles '12', %q{ + C = 3 + def test(n) = C * n + test(4) + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_mult] + end + + def test_fixnum_div + assert_compiles '12', %q{ + C = 48 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_floor + assert_compiles '0', %q{ + C = 3 + def test(n) = C / n + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_fixnum_div_zero + assert_runs '"divided by 0"', %q{ + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + + test(0) + test(0) + }, call_threshold: 2, insns: [:opt_div] + end + + def test_opt_not + assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) + def test(obj) = !obj + return test(nil), test(false), test(0) + RUBY + end + + def test_opt_regexpmatch2 + assert_compiles('[1, nil]', <<~RUBY, insns: [:opt_regexpmatch2]) + def test(haystack) = /needle/ =~ haystack + return test("kneedle"), test("") + RUBY + end + + def test_opt_ge + assert_compiles '[false, true, true]', %q{ + def test(a, b) = a >= b + test(2, 3) # profile opt_ge + [test(0, 1), test(0, 0), test(1, 0)] + }, insns: [:opt_ge], call_threshold: 2 + end + + def test_opt_new_does_not_push_frame + assert_compiles 'nil', %q{ + class Foo + attr_reader :backtrace + + def initialize + @backtrace = caller + end + end + def test = Foo.new + + foo = test + foo.backtrace.find do |frame| + frame.include?('Class#new') + end + }, insns: [:opt_new] + end + + def test_opt_new_with_redefined + assert_compiles '"foo"', %q{ + class Foo + def self.new = "foo" + + def initialize = raise("unreachable") + end + def test = Foo.new + + test + }, insns: [:opt_new] + end + + def test_opt_new_invalidate_new + assert_compiles '["Foo", "foo"]', %q{ + class Foo; end + def test = Foo.new + test; test + result = [test.class.name] + def Foo.new = "foo" + result << test + result + }, insns: [:opt_new], call_threshold: 2 + end + + def test_opt_new_with_custom_allocator + assert_compiles '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"', %q{ + require "digest" + def test = Digest::SHA256.new.hexdigest + test; test + }, insns: [:opt_new], call_threshold: 2 + end + + def test_opt_new_with_custom_allocator_raises + assert_compiles '[42, 42]', %q{ + require "digest" + class C < Digest::Base; end + def test + begin + Digest::Base.new + rescue NotImplementedError + 42 + end + end + [test, test] + }, insns: [:opt_new], call_threshold: 2 + end + + def test_opt_newarray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_include_p_redefined + assert_compiles '[:true, :false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + + def test(x) + [:y, 1, Object.new].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_duparray_send_include_p + assert_compiles '[true, false]', %q{ + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + + def test_opt_duparray_send_include_p_redefined + assert_compiles '[:true, :false]', %q{ + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + + def test(x) + [:y, 1].include?(x) + end + [test(1), test("n")] + }, insns: [:opt_duparray_send], call_threshold: 1 + end + + def test_opt_newarray_send_pack_buffer + assert_compiles '["ABC", "ABC", "ABC", "ABC"]', %q{ + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), test(66, buf), test(67, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_pack_buffer_redefined + assert_compiles '["b", "A"]', %q{ + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + old_pack(fmt, buffer: buffer) + "b" + end + end + + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + [test(65, buf), buf] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_hash + assert_compiles 'Integer', %q{ + def test(x) + [1, 2, x].hash + end + test(20).class + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_hash_redefined + assert_compiles '42', %q{ + Array.class_eval { def hash = 42 } + + def test(x) + [1, 2, x].hash + end + test(20) + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_max + assert_compiles '[20, 40]', %q{ + def test(a,b) = [a,b].max + [test(10, 20), test(40, 30)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_opt_newarray_send_max_redefined + assert_compiles '[60, 90]', %q{ + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + + def test(a,b) = [a,b].max + [test(15, 30), test(45, 35)] + }, insns: [:opt_newarray_send], call_threshold: 1 + end + + def test_new_hash_empty + assert_compiles '{}', %q{ + def test = {} + test + }, insns: [:newhash] + end + + def test_new_hash_nonempty + assert_compiles '{"key" => "value", 42 => 100}', %q{ + def test + key = "key" + value = "value" + num = 42 + result = 100 + {key => value, num => result} + end + test + }, insns: [:newhash] + end + + def test_new_hash_single_key_value + assert_compiles '{"key" => "value"}', %q{ + def test = {"key" => "value"} + test + }, insns: [:newhash] + end + + def test_new_hash_with_computation + assert_compiles '{"sum" => 5, "product" => 6}', %q{ + def test(a, b) + {"sum" => a + b, "product" => a * b} + end + test(2, 3) + }, insns: [:newhash] + end + + def test_new_hash_with_user_defined_hash_method + assert_runs 'true', %q{ + class CustomKey + attr_reader :val + + def initialize(val) + @val = val + end + + def hash + @val.hash + end + + def eql?(other) + other.is_a?(CustomKey) && @val == other.val + end + end + + def test + key = CustomKey.new("key") + hash = {key => "value"} + hash[key] == "value" + end + test + } + end + + def test_new_hash_with_user_hash_method_exception + assert_runs 'RuntimeError', %q{ + class BadKey + def hash + raise "Hash method failed!" + end + end + + def test + key = BadKey.new + {key => "value"} + end + + begin + test + rescue => e + e.class + end + } + end + + def test_new_hash_with_user_eql_method_exception + assert_runs 'RuntimeError', %q{ + class BadKey + def hash + 42 + end + + def eql?(other) + raise "Eql method failed!" + end + end + + def test + key1 = BadKey.new + key2 = BadKey.new + {key1 => "value1", key2 => "value2"} + end + + begin + test + rescue => e + e.class + end + } + end + + def test_opt_hash_freeze + assert_compiles "[{}, 5]", %q{ + def test = {}.freeze + result = [test] + class Hash + def freeze = 5 + end + result << test + }, insns: [:opt_hash_freeze], call_threshold: 1 + end + + def test_opt_hash_freeze_rewritten + assert_compiles "5", %q{ + class Hash + def freeze = 5 + end + def test = {}.freeze + test + }, insns: [:opt_hash_freeze], call_threshold: 1 + end + + def test_opt_aset_hash + assert_compiles '42', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_opt_aset_hash_returns_value + assert_compiles '100', %q{ + def test(h, k, v) + h[k] = v + end + test({}, :key, 100) + test({}, :key, 100) + }, call_threshold: 2 + end + + def test_opt_aset_hash_string_key + assert_compiles '"bar"', %q{ + def test(h, k, v) + h[k] = v + end + h = {} + test(h, "foo", "bar") + test(h, "foo", "bar") + h["foo"] + }, call_threshold: 2 + end + + def test_opt_aset_hash_subclass + assert_compiles '42', %q{ + class MyHash < Hash; end + def test(h, k, v) + h[k] = v + end + h = MyHash.new + test(h, :key, 42) + test(h, :key, 42) + h[:key] + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_few_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h.[]= 123 + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + + def test_opt_aset_hash_too_many_args + assert_compiles '"ArgumentError"', %q{ + def test(h) + h[:a, :b] = :c + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + }, call_threshold: 2 + end + + def test_opt_ary_freeze + assert_compiles "[[], 5]", %q{ + def test = [].freeze + result = [test] + class Array + def freeze = 5 + end + result << test + }, insns: [:opt_ary_freeze], call_threshold: 1 + end + + def test_opt_ary_freeze_rewritten + assert_compiles "5", %q{ + class Array + def freeze = 5 + end + def test = [].freeze + test + }, insns: [:opt_ary_freeze], call_threshold: 1 + end + + def test_opt_str_freeze + assert_compiles "[\"\", 5]", %q{ + def test = ''.freeze + result = [test] + class String + def freeze = 5 + end + result << test + }, insns: [:opt_str_freeze], call_threshold: 1 + end + + def test_opt_str_freeze_rewritten + assert_compiles "5", %q{ + class String + def freeze = 5 + end + def test = ''.freeze + test + }, insns: [:opt_str_freeze], call_threshold: 1 + end + + def test_opt_str_uminus + assert_compiles "[\"\", 5]", %q{ + def test = -'' + result = [test] + class String + def -@ = 5 + end + result << test + }, insns: [:opt_str_uminus], call_threshold: 1 + end + + def test_opt_str_uminus_rewritten + assert_compiles "5", %q{ + class String + def -@ = 5 + end + def test = -'' + test + }, insns: [:opt_str_uminus], call_threshold: 1 + end + + def test_new_array_empty + assert_compiles '[]', %q{ + def test = [] + test + }, insns: [:newarray] + end + + def test_new_array_nonempty + assert_compiles '[5]', %q{ + def a = 5 + def test = [a] + test + } + end + + def test_new_array_order + assert_compiles '[3, 2, 1]', %q{ + def a = 3 + def b = 2 + def c = 1 + def test = [a, b, c] + test + } + end + + def test_array_dup + assert_compiles '[1, 2, 3]', %q{ + def test = [1,2,3] + test + } + end + + def test_array_fixnum_aref + assert_compiles '3', %q{ + def test(x) = [1,2,3][x] + test(2) + test(2) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_array_fixnum_aref_negative_index + assert_compiles '3', %q{ + def test(x) = [1,2,3][x] + test(-1) + test(-1) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_array_fixnum_aref_out_of_bounds_positive + assert_compiles 'nil', %q{ + def test(x) = [1,2,3][x] + test(10) + test(10) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_array_fixnum_aref_out_of_bounds_negative + assert_compiles 'nil', %q{ + def test(x) = [1,2,3][x] + test(-10) + test(-10) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_array_fixnum_aref_array_subclass + assert_compiles '3', %q{ + class MyArray < Array; end + def test(arr, idx) = arr[idx] + arr = MyArray[1,2,3] + test(arr, 2) + arr = MyArray[1,2,3] + test(arr, 2) + }, call_threshold: 2, insns: [:opt_aref] + end + + def test_array_aref_non_fixnum_index + assert_compiles 'TypeError', %q{ + def test(arr, idx) = arr[idx] + test([1,2,3], 1) + test([1,2,3], 1) + begin + test([1,2,3], "1") + rescue => e + e.class + end + }, call_threshold: 2 + end + + def test_array_fixnum_aset + assert_compiles '[1, 2, 7]', %q{ + def test(arr, idx) + arr[idx] = 7 + end + arr = [1,2,3] + test(arr, 2) + arr = [1,2,3] + test(arr, 2) + arr + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_returns_value + assert_compiles '7', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + test([1,2,3], 2) + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_fixnum_aset_out_of_bounds + assert_compiles '[1, 2, 3, nil, nil, 7]', %q{ + def test(arr) + arr[5] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_negative_index + assert_compiles '[1, 2, 7]', %q{ + def test(arr) + arr[-1] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + }, call_threshold: 2 + end + + def test_array_fixnum_aset_shared + assert_compiles '[10, 999, -1, -2]', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = (0..50).to_a + test(arr, 0, -1) + test(arr, 1, -2) + shared = arr[10, 20] + test(shared, 0, 999) + [arr[10], shared[0], arr[0], arr[1]] + }, call_threshold: 2 + end + + def test_array_fixnum_aset_frozen + assert_compiles 'FrozenError', %q{ + def test(arr, idx, val) + arr[idx] = val + end + arr = [1,2,3] + test(arr, 1, 9) + test(arr, 1, 9) + arr.freeze + begin + test(arr, 1, 9) + rescue => e + e.class + end + }, call_threshold: 2 + end + + def test_array_fixnum_aset_array_subclass + assert_compiles '7', %q{ + class MyArray < Array; end + def test(arr, idx) + arr[idx] = 7 + end + arr = MyArray.new + test(arr, 0) + arr = MyArray.new + test(arr, 0) + arr[0] + }, call_threshold: 2, insns: [:opt_aset] + end + + def test_array_aset_non_fixnum_index + assert_compiles 'TypeError', %q{ + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 0) + test([1,2,3], 0) + begin + test([1,2,3], "0") + rescue => e + e.class + end + }, call_threshold: 2 + end + + def test_empty_array_pop + assert_compiles 'nil', %q{ + def test(arr) = arr.pop + test([]) + test([]) + }, call_threshold: 2 + end + + def test_array_pop_no_arg + assert_compiles '42', %q{ + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_array_pop_arg + assert_compiles '[33, 42]', %q{ + def test(arr) = arr.pop(2) + test([32, 33, 42]) + test([32, 33, 42]) + }, call_threshold: 2 + end + + def test_new_range_inclusive + assert_compiles '1..5', %q{ + def test(a, b) = a..b + test(1, 5) + } + end + + def test_new_range_exclusive + assert_compiles '1...5', %q{ + def test(a, b) = a...b + test(1, 5) + } + end + + def test_new_range_with_literal + assert_compiles '3..10', %q{ + def test(n) = n..10 + test(3) + } + end + + def test_new_range_fixnum_both_literals_inclusive + assert_compiles '1..2', %q{ + def test() + a = 2 + (1..a) + end + test; test + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_both_literals_exclusive + assert_compiles '1...2', %q{ + def test() + a = 2 + (1...a) + end + test; test + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_low_literal_inclusive + assert_compiles '1..3', %q{ + def test(a) + (1..a) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_low_literal_exclusive + assert_compiles '1...3', %q{ + def test(a) + (1...a) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_high_literal_inclusive + assert_compiles '3..10', %q{ + def test(a) + (a..10) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_new_range_fixnum_high_literal_exclusive + assert_compiles '3...10', %q{ + def test(a) + (a...10) + end + test(2); test(3) + }, call_threshold: 2, insns: [:newrange] + end + + def test_if + assert_compiles '[0, nil]', %q{ + def test(n) + if n < 5 + 0 + end + end + [test(3), test(7)] + } + end + + def test_if_else + assert_compiles '[0, 1]', %q{ + def test(n) + if n < 5 + 0 + else + 1 + end + end + [test(3), test(7)] + } + end + + def test_if_else_params + assert_compiles '[1, 20]', %q{ + def test(n, a, b) + if n < 5 + a + else + b + end + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_if_else_nested + assert_compiles '[3, 8, 9, 14]', %q{ + def test(a, b, c, d, e) + if 2 < a + if a < 4 + b + else + c + end + else + if a < 0 + d + else + e + end + end + end + [ + test(-1, 1, 2, 3, 4), + test( 0, 5, 6, 7, 8), + test( 3, 9, 10, 11, 12), + test( 5, 13, 14, 15, 16), + ] + } + end + + def test_if_else_chained + assert_compiles '[12, 11, 21]', %q{ + def test(a) + (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end) + end + [test(0), test(3), test(5)] + } + end + + def test_if_elsif_else + assert_compiles '[0, 2, 1]', %q{ + def test(n) + if n < 5 + 0 + elsif 8 < n + 1 + else + 2 + end + end + [test(3), test(7), test(9)] + } + end + + def test_ternary_operator + assert_compiles '[1, 20]', %q{ + def test(n, a, b) + n < 5 ? a : b + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_ternary_operator_nested + assert_compiles '[2, 21]', %q{ + def test(n, a, b) + (n < 5 ? a : b) + 1 + end + [test(3, 1, 2), test(7, 10, 20)] + } + end + + def test_while_loop + assert_compiles '10', %q{ + def test(n) + i = 0 + while i < n + i = i + 1 + end + i + end + test(10) + } + end + + def test_while_loop_chain + assert_compiles '[135, 270]', %q{ + def test(n) + i = 0 + while i < n + i = i + 1 + end + while i < n * 10 + i = i * 3 + end + i + end + [test(5), test(10)] + } + end + + def test_while_loop_nested + assert_compiles '[0, 4, 12]', %q{ + def test(n, m) + i = 0 + while i < n + j = 0 + while j < m + j += 2 + end + i += j + end + i + end + [test(0, 0), test(1, 3), test(10, 5)] + } + end + + def test_while_loop_if_else + assert_compiles '[9, -1]', %q{ + def test(n) + i = 0 + while i < n + if n >= 10 + return -1 + else + i = i + 1 + end + end + i + end + [test(9), test(10)] + } + end + + def test_if_while_loop + assert_compiles '[9, 12]', %q{ + def test(n) + i = 0 + if n < 10 + while i < n + i += 1 + end + else + while i < n + i += 3 + end + end + i + end + [test(9), test(10)] + } + end + + def test_live_reg_past_ccall + assert_compiles '2', %q{ + def callee = 1 + def test = callee + callee + test + } + end + + def test_method_call + assert_compiles '12', %q{ + def callee(a, b) + a - b + end + + def test + callee(4, 2) + 10 + end + + test # profile test + test + }, call_threshold: 2 + end + + def test_recursive_fact + assert_compiles '[1, 6, 720]', %q{ + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + [fact(0), fact(3), fact(6)] + } + end + + def test_profiled_fact + assert_compiles '[1, 6, 720]', %q{ + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(1) # profile fact + [fact(0), fact(3), fact(6)] + }, call_threshold: 3, num_profiles: 2 + end + + def test_recursive_fib + assert_compiles '[0, 2, 3]', %q{ + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + [fib(0), fib(3), fib(4)] + } + end + + def test_profiled_fib + assert_compiles '[0, 2, 3]', %q{ + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(3) # profile fib + [fib(0), fib(3), fib(4)] + }, call_threshold: 5, num_profiles: 3 + end + + def test_spilled_basic_block_args + assert_compiles '55', %q{ + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + } + end + + def test_spilled_method_args + assert_runs '55', %q{ + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + + test + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '1', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9 + a(2,0,0,0,0,0,0,0,-1) + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '0', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8 + a(1,1,1,1,1,1,1,0) + } + + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + # self param with spilled param + assert_runs '"main"', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = self + a(1,0,0,0,0,0,0,0).to_s + } + end + + def test_spilled_param_new_arary + # TODO(Shopify/ruby#716): Support spills and change to assert_compiles + assert_runs '[:ok]', %q{ + def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8] + a(0,0,0,0,0,0,0, :ok) + } + end + + def test_forty_param_method + # This used to a trigger a miscomp on A64 due + # to a memory displacement larger than 9 bits. + # Using assert_runs again due to register spill. + # TODO: It should be fixed by register spill support. + assert_runs '1', %Q{ + def foo(#{'_,' * 39} n40) = n40 + + foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1) + } + end + + def test_putself + assert_compiles '3', %q{ + class Integer + def minus(a) + self - a + end + end + 5.minus(2) + } + end + + def test_getinstancevariable + assert_compiles 'nil', %q{ + def test() = @foo + + test() + } + assert_compiles '3', %q{ + @foo = 3 + def test() = @foo + + test() + } + end + + def test_getinstancevariable_miss + assert_compiles '[1, 1, 4]', %q{ + class C + def foo + @foo + end + + def foo_then_bar + @foo = 1 + @bar = 2 + end + + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + + o1 = C.new + o1.foo_then_bar + result = [] + result << o1.foo + result << o1.foo + o2 = C.new + o2.bar_then_foo + result << o2.foo + result + } + end + + def test_setinstancevariable + assert_compiles '1', %q{ + def test() = @foo = 1 + + test() + @foo + } + end + + def test_getclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x + end + + Foo.class_variable_set(:@@x, 42) + Foo.test() + } + end + + def test_getclassvariable_raises + assert_compiles '"uninitialized class variable @@x in Foo"', %q{ + class Foo + def self.test = @@x + end + + begin + Foo.test + rescue NameError => e + e.message + end + } + end + + def test_setclassvariable + assert_compiles '42', %q{ + class Foo + def self.test = @@x = 42 + end + + Foo.test() + Foo.class_variable_get(:@@x) + } + end + + def test_setclassvariable_raises + assert_compiles '"can\'t modify frozen Class: Foo"', %q{ + class Foo + def self.test = @@x = 42 + freeze + end + + begin + Foo.test + rescue FrozenError => e + e.message + end + } + end + + def test_attr_reader + assert_compiles '[4, 4]', %q{ + class C + attr_reader :foo + + def initialize + @foo = 4 + end + end + + def test(c) = c.foo + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_accessor_getivar + assert_compiles '[4, 4]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) = c.foo + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_accessor_setivar + assert_compiles '[5, 5]', %q{ + class C + attr_accessor :foo + + def initialize + @foo = 4 + end + end + + def test(c) + c.foo = 5 + c.foo + end + + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_attr_writer + assert_compiles '[5, 5]', %q{ + class C + attr_writer :foo + + def initialize + @foo = 4 + end + + def get_foo = @foo + end + + def test(c) + c.foo = 5 + c.get_foo + end + c = C.new + [test(c), test(c)] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_uncached_getconstant_path + assert_compiles RUBY_COPYRIGHT.dump, %q{ + def test = RUBY_COPYRIGHT + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + + def test_expandarray_no_splat + assert_compiles '[3, 4]', %q{ + def test(o) + a, b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat + assert_compiles '[3, [4]]', %q{ + def test(o) + a, *b = o + [a, b] + end + test [3, 4] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_expandarray_splat_post + assert_compiles '[3, [4], 5]', %q{ + def test(o) + a, *b, c = o + [a, b, c] + end + test [3, 4, 5] + }, call_threshold: 1, insns: [:expandarray] + end + + def test_getconstant_path_autoload + # A constant-referencing expression can run arbitrary code through Kernel#autoload. + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb') + File.write(autoload_path, 'X = RUBY_COPYRIGHT') + + assert_compiles RUBY_COPYRIGHT.dump, %Q{ + Object.autoload(:X, #{File.realpath(autoload_path).inspect}) + def test = X + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + end + + def test_constant_invalidation + assert_compiles '123', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + class C; end + def test = C + test + test + + C = 123 + test + RUBY + end + + def test_constant_path_invalidation + assert_compiles '["Foo::C", "Foo::C", "Bar::C"]', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + module A + module B; end + end + + module Foo + C = "Foo::C" + end + + module Bar + C = "Bar::C" + end + + A::B = Foo + + def test = A::B::C + + result = [] + + result << test + result << test + + A::B = Bar + + result << test + result + RUBY + end + + def test_single_ractor_mode_invalidation + # Without invalidating the single-ractor mode, the test would crash + assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path] + C = Object.new + + def test + C + rescue Ractor::IsolationError + "errored but not crashed" + end + + test + test + + Ractor.new { + test + }.value + RUBY + end + + def test_dupn + assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn] + def test(array) = (array[1, 2] ||= :rhs) + + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + RUBY + end + + def test_send_backtrace + backtrace = [ + "-e:2:in 'Object#jit_frame1'", + "-e:3:in 'Object#entry'", + "-e:5:in 'block in <main>'", + "-e:6:in '<main>'", + ] + assert_compiles backtrace.inspect, %q{ + def jit_frame2 = caller # 1 + def jit_frame1 = jit_frame2 # 2 + def entry = jit_frame1 # 3 + entry # profile send # 4 + entry # 5 + }, call_threshold: 2 + end + + def test_bop_invalidation + assert_compiles '100', %q{ + def test + eval(<<~RUBY) + class Integer + def +(_) = 100 + end + RUBY + 1 + 2 + end + test + } + end + + def test_defined_with_defined_values + assert_compiles '["constant", "method", "global-variable"]', %q{ + class Foo; end + def bar; end + $ruby = 1 + + def test = return defined?(Foo), defined?(bar), defined?($ruby) + + test + }, insns: [:defined] + end + + def test_defined_with_undefined_values + assert_compiles '[nil, nil, nil]', %q{ + def test = return defined?(Foo), defined?(bar), defined?($ruby) + + test + }, insns: [:defined] + end + + def test_defined_with_method_call + assert_compiles '["method", nil]', %q{ + def test = return defined?("x".reverse(1)), defined?("x".reverse(1).reverse) + + test + }, insns: [:defined] + end + + def test_defined_method_raise + assert_compiles '[nil, nil, nil]', %q{ + class C + def assert_equal expected, actual + if expected != actual + raise "NO" + end + end + + def test_defined_method + assert_equal(nil, defined?("x".reverse(1).reverse)) + end + end + + c = C.new + result = [] + result << c.test_defined_method + result << c.test_defined_method + result << c.test_defined_method + result + } + end + + def test_defined_yield + assert_compiles "nil", "defined?(yield)" + assert_compiles '[nil, nil, "yield"]', %q{ + def test = defined?(yield) + [test, test, test{}] + }, call_threshold: 2, insns: [:defined] + end + + def test_defined_yield_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + assert_compiles '[nil, nil, "yield"]', %q{ + def test + yield_self { yield_self { defined?(yield) } } + end + + [test, test, test{}] + }, call_threshold: 2 + end + + def test_block_given_p + assert_compiles "false", "block_given?" + assert_compiles '[false, false, true]', %q{ + def test = block_given? + [test, test, test{}] + }, call_threshold: 2, insns: [:opt_send_without_block] + end + + def test_block_given_p_from_block + # This will do some EP hopping to find the local EP, + # so it's slightly different than doing it outside of a block. + + assert_compiles '[false, false, true]', %q{ + def test + yield_self { yield_self { block_given? } } + end + + [test, test, test{}] + }, call_threshold: 2 + end + + def test_invokeblock_without_block_after_jit_call + assert_compiles '"no block given (yield)"', %q{ + def test(*arr, &b) + arr.class + yield + end + begin + test + rescue => e + e.message + end + } + end + + def test_putspecialobject_vm_core_and_cbase + assert_compiles '10', %q{ + def test + alias bar test + 10 + end + + test + bar + }, insns: [:putspecialobject] + end + + def test_putspecialobject_const_base + assert_compiles '1', %q{ + Foo = 1 + + def test = Foo + + # First call: populates the constant cache + test + # Second call: triggers ZJIT compilation with warm cache + # RubyVM::ZJIT.assert_compiles will panic if this fails to compile + test + }, call_threshold: 2 + end + + def test_branchnil + assert_compiles '[2, nil]', %q{ + def test(x) + x&.succ + end + [test(1), test(nil)] + }, call_threshold: 1, insns: [:branchnil] + end + + def test_nil_nil + assert_compiles 'true', %q{ + def test = nil.nil? + test + }, insns: [:opt_nil_p] + end + + def test_non_nil_nil + assert_compiles 'false', %q{ + def test = 1.nil? + test + }, insns: [:opt_nil_p] + end + + def test_getspecial_last_match + assert_compiles '"hello"', %q{ + def test(str) + str =~ /hello/ + $& + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_pre + assert_compiles '"hello "', %q{ + def test(str) + str =~ /world/ + $` + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_post + assert_compiles '" world"', %q{ + def test(str) + str =~ /hello/ + $' + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_match_last_group + assert_compiles '"world"', %q{ + def test(str) + str =~ /(hello) (world)/ + $+ + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_1 + assert_compiles '"hello"', %q{ + def test(str) + str =~ /(hello) (world)/ + $1 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_2 + assert_compiles '"world"', %q{ + def test(str) + str =~ /(hello) (world)/ + $2 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_numbered_match_nonexistent + assert_compiles 'nil', %q{ + def test(str) + str =~ /(hello)/ + $2 + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_no_match + assert_compiles 'nil', %q{ + def test(str) + str =~ /xyz/ + $& + end + test("hello world") + }, insns: [:getspecial] + end + + def test_getspecial_complex_pattern + assert_compiles '"123"', %q{ + def test(str) + str =~ /(\d+)/ + $1 + end + test("abc123def") + }, insns: [:getspecial] + end + + def test_getspecial_multiple_groups + assert_compiles '"456"', %q{ + def test(str) + str =~ /(\d+)-(\d+)/ + $2 + end + test("123-456") + }, insns: [:getspecial] + end + + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and + # b) being reliably ordered after all the other instructions. + def test_instruction_order + insn_names = RubyVM::INSTRUCTION_NAMES + zjit, others = insn_names.map.with_index.partition { |name, _| name.start_with?('zjit_') } + zjit_indexes = zjit.map(&:last) + other_indexes = others.map(&:last) + zjit_indexes.product(other_indexes).each do |zjit_index, other_index| + assert zjit_index > other_index, "'#{insn_names[zjit_index]}' at #{zjit_index} "\ + "must be defined after '#{insn_names[other_index]}' at #{other_index}" + end + end + + def test_require_rubygems + assert_runs 'true', %q{ + require 'rubygems' + }, call_threshold: 2 + end + + def test_require_rubygems_with_auto_compact + omit("GC.auto_compact= support is required for this test") unless GC.respond_to?(:auto_compact=) + assert_runs 'true', %q{ + GC.auto_compact = true + require 'rubygems' + }, call_threshold: 2 + end + + def test_stats_availability + assert_runs '[true, true]', %q{ + def test = 1 + test + [ + RubyVM::ZJIT.stats[:zjit_insn_count] > 0, + RubyVM::ZJIT.stats(:zjit_insn_count) > 0, + ] + }, stats: true + end + + def test_stats_consistency + assert_runs '[]', %q{ + def test = 1 + test # increment some counters + + RubyVM::ZJIT.stats.to_a.filter_map do |key, value| + # The value may be incremented, but the class should stay the same + other_value = RubyVM::ZJIT.stats(key) + if value.class != other_value.class + [key, value, other_value] + end + end + }, stats: true + end + + def test_reset_stats + assert_runs 'true', %q{ + def test = 1 + 100.times { test } + + # Get initial stats and verify they're non-zero + initial_stats = RubyVM::ZJIT.stats + + # Reset the stats + RubyVM::ZJIT.reset_stats! + + # Get stats after reset + reset_stats = RubyVM::ZJIT.stats + + [ + # After reset, counters should be zero or at least much smaller + # (some instructions might execute between reset and reading stats) + :zjit_insn_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] }, + :compiled_iseq_count.then { |s| initial_stats[s] > 0 && reset_stats[s] < initial_stats[s] } + ].all? + }, stats: true + end + + def test_zjit_option_uses_array_each_in_ruby + omit 'ZJIT wrongly compiles Array#each, so it is disabled for now' + assert_runs '"<internal:array>"', %q{ + Array.instance_method(:each).source_location&.first + } + end + + def test_profile_under_nested_jit_call + assert_compiles '[nil, nil, 3]', %q{ + def profile + 1 + 2 + end + + def jit_call(flag) + if flag + profile + end + end + + def entry(flag) + jit_call(flag) + end + + [entry(false), entry(false), entry(true)] + }, call_threshold: 2 + end + + def test_bop_redefined + assert_runs '[3, :+, 100]', %q{ + def test + 1 + 2 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + def test_bop_redefined_with_adjacent_patch_points + assert_runs '[15, :+, 100]', %q{ + def test + 1 + 2 + 3 + 4 + 5 + end + + test # profile opt_plus + [test, Integer.class_eval { def +(_) = 100 }, test] + }, call_threshold: 2 + end + + # ZJIT currently only generates a MethodRedefined patch point when the method + # is called on the top-level self. + def test_method_redefined_with_top_self + assert_runs '["original", "redefined"]', %q{ + def foo + "original" + end + + def test = foo + + test; test + + result1 = test + + # Redefine the method + def foo + "redefined" + end + + result2 = test + + [result1, result2] + }, call_threshold: 2 + end + + def test_method_redefined_with_module + assert_runs '["original", "redefined"]', %q{ + module Foo + def self.foo = "original" + end + + def test = Foo.foo + test + result1 = test + + def Foo.foo = "redefined" + result2 = test + + [result1, result2] + }, call_threshold: 2 + end + + def test_module_name_with_guard_passes + assert_compiles '"Integer"', %q{ + def test(mod) + mod.name + end + + test(String) + test(Integer) + }, call_threshold: 2 + end + + def test_module_name_with_guard_side_exit + # This test demonstrates that the guard side exit works correctly + # In this case, when we call with a non-Class object, it should fall back to interpreter + assert_compiles '["String", "Integer", "Bar"]', %q{ + class MyClass + def name = "Bar" + end + + def test(mod) + mod.name + end + + results = [] + results << test(String) + results << test(Integer) + results << test(MyClass.new) + + results + }, call_threshold: 2 + end + + def test_objtostring_calls_to_s_on_non_strings + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class Foo + def to_s + "foo" + end + end + + def test(str) + "#{str}" + end + + results << test(Foo.new) + results << test(Foo.new) + + results + } + end + + def test_objtostring_rewrite_does_not_call_to_s_on_strings + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class String + def to_s + "bad" + end + end + + def test(foo) + "#{foo}" + end + + results << test("foo") + results << test("foo") + + results + } + end + + def test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses + assert_compiles '["foo", "foo"]', %q{ + results = [] + + class StringSubclass < String + def to_s + "bad" + end + end + + foo = StringSubclass.new("foo") + + def test(str) + "#{str}" + end + + results << test(foo) + results << test(foo) + + results + } + end + + def test_objtostring_profiled_string_fastpath + assert_compiles '"foo"', %q{ + def test(str) + "#{str}" + end + test('foo'); test('foo') # profile as string + }, call_threshold: 2 + end + + def test_objtostring_profiled_string_subclass_fastpath + assert_compiles '"foo"', %q{ + class MyString < String; end + + def test(str) + "#{str}" + end + + foo = MyString.new("foo") + test(foo); test(foo) # still profiles as string + }, call_threshold: 2 + end + + def test_objtostring_profiled_string_fastpath_exits_on_nonstring + assert_compiles '"1"', %q{ + def test(str) + "#{str}" + end + + test('foo') # profile as string + test(1) + }, call_threshold: 2 + end + + def test_objtostring_profiled_nonstring_calls_to_s + assert_compiles '"[1, 2, 3]"', %q{ + def test(str) + "#{str}" + end + + test([1,2,3]); # profile as nonstring + test([1,2,3]); + }, call_threshold: 2 + end + + def test_objtostring_profiled_nonstring_guard_exits_when_string + assert_compiles '"foo"', %q{ + def test(str) + "#{str}" + end + + test([1,2,3]); # profiles as nonstring + test('foo'); + }, call_threshold: 2 + end + + def test_string_bytesize_with_guard + assert_compiles '5', %q{ + def test(str) + str.bytesize + end + + test('hello') + test('world') + }, call_threshold: 2 + end + + def test_string_bytesize_multibyte + assert_compiles '4', %q{ + def test(s) + s.bytesize + end + + test("💎") + }, call_threshold: 2 + end + + def test_nil_value_nil_opt_with_guard + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_nil_value_nil_opt_with_guard_side_exit + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(nil) + test(nil) + test(1) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(true) + test(true) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_true_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(true) + test(true) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(false) + test(false) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_false_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(false) + test(false) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1) + test(2) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_integer_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1) + test(2) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_float_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(1.0) + test(2.0) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_symbol_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(:foo) + test(:bar) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_class_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(String) + test(Integer) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard + assert_compiles 'false', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_module_nil_opt_with_guard_side_exit + assert_compiles 'true', %q{ + def test(val) = val.nil? + + test(Enumerable) + test(Kernel) + test(nil) + }, call_threshold: 2, insns: [:opt_nil_p] + end + + def test_basic_object_guard_works_with_immediate + assert_compiles 'NilClass', %q{ + class Foo; end + + def test(val) = val.class + + test(Foo.new) + test(Foo.new) + test(nil) + }, call_threshold: 2 + end + + def test_basic_object_guard_works_with_false + assert_compiles 'FalseClass', %q{ + class Foo; end + + def test(val) = val.class + + test(Foo.new) + test(Foo.new) + test(false) + }, call_threshold: 2 + end + + def test_string_concat + assert_compiles '"123"', %q{ + def test = "#{1}#{2}#{3}" + + test + }, insns: [:concatstrings] + end + + def test_string_concat_empty + assert_compiles '""', %q{ + def test = "#{}" + + test + }, insns: [:concatstrings] + end + + def test_regexp_interpolation + assert_compiles '/123/', %q{ + def test = /#{1}#{2}#{3}/ + + test + }, insns: [:toregexp] + end + + def test_new_range_non_leaf + assert_compiles '(0/1)..1', %q{ + def jit_entry(v) = make_range_then_exit(v) + + def make_range_then_exit(v) + range = (v..1) + super rescue range # TODO(alan): replace super with side-exit intrinsic + end + + jit_entry(0) # profile + jit_entry(0) # compile + jit_entry(0/1r) # run without stub + }, call_threshold: 2 + end + + def test_raise_in_second_argument + assert_compiles '{ok: true}', %q{ + def write(hash, key) + hash[key] = raise rescue true + hash + end + + write({}, :ok) + } + end + + def test_ivar_attr_reader_optimization_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + class << self + attr_accessor :bar + + def get_bar + bar + rescue Ractor::IsolationError + 42 + end + end + end + + Foo.bar = [] # needs to be a ractor unshareable object + + def test + Foo.get_bar + end + + test + test + + Ractor.new { test }.value + }, call_threshold: 2 + end + + def test_ivar_get_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.set_bar + @bar = [] # needs to be a ractor unshareable object + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + }, call_threshold: 2 + end + + def test_ivar_get_with_already_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.set_bar + @bar = [] # needs to be a ractor unshareable object + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + r = Ractor.new { + Ractor.receive + Foo.bar + } + + Foo.bar + Foo.bar + + r << :go + r.value + }, call_threshold: 2 + end + + def test_ivar_set_with_multi_ractor_mode + assert_compiles '42', %q{ + class Foo + def self.bar + _foo = 1 + _bar = 2 + begin + @bar = _foo + _bar + rescue Ractor::IsolationError + 42 + end + end + end + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + } + end + + def test_struct_set + assert_compiles '[42, 42, :frozen_error]', %q{ + C = Struct.new(:foo).new(1) + + def test + C.foo = Object.new + 42 + end + + r = [test, test] + C.freeze + r << begin + test + rescue FrozenError + :frozen_error + end + }, call_threshold: 2 + end + + def test_global_tracepoint + assert_compiles 'true', %q{ + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |event| + if event.method_id == :foo + called = true + end + } + tp.enable do + foo + end + called + } + end + + def test_local_tracepoint + assert_compiles 'true', %q{ + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |_| called = true } + tp.enable(target: method(:foo)) do + foo + end + called + } + end + + def test_line_tracepoint_on_c_method + assert_compiles '"[[:line, true]]"', %q{ + events = [] + events.instance_variable_set( + :@tp, + TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ } + ) + def events.to_str + @tp.enable; '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + @tp.disable; __LINE__ + end + + line = events.compiled(events) + events[0][-1] = (events[0][-1] == line) + + events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped + } + end + + def test_targeted_line_tracepoint_in_c_method_call + assert_compiles '"[true]"', %q{ + events = [] + events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) + def events.to_str + @tp.enable(target: method(:compiled)) + '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + __LINE__ + end + + line = events.compiled(events) + events[0] = (events[0] == line) + + events.to_s # can't dump events as it's a singleton object AND it has a TracePoint instance variable, which also can't be dumped + } + end + + def test_opt_case_dispatch + assert_compiles '[true, false]', %q{ + def test(x) + case x + when :foo + true + else + false + end + end + + results = [] + results << test(:foo) + results << test(1) + results + }, insns: [:opt_case_dispatch] + end + + def test_stack_overflow + assert_compiles 'nil', %q{ + def recurse(n) + return if n == 0 + recurse(n-1) + nil # no tail call + end + + recurse(2) + recurse(2) + begin + recurse(20_000) + rescue SystemStackError + # Not asserting an exception is raised here since main + # thread stack size is environment-sensitive. Only + # that we don't crash or infinite loop. + end + }, call_threshold: 2 + end + + def test_invokeblock + assert_compiles '42', %q{ + def test + yield + end + test { 42 } + }, insns: [:invokeblock] + end + + def test_invokeblock_with_args + assert_compiles '3', %q{ + def test(x, y) + yield x, y + end + test(1, 2) { |a, b| a + b } + }, insns: [:invokeblock] + end + + def test_invokeblock_no_block_given + assert_compiles ':error', %q{ + def test + yield rescue :error + end + test + }, insns: [:invokeblock] + end + + def test_invokeblock_multiple_yields + assert_compiles "[1, 2, 3]", %q{ + results = [] + def test + yield 1 + yield 2 + yield 3 + end + test { |x| results << x } + results + }, insns: [:invokeblock] + end + + def test_ccall_variadic_with_multiple_args + assert_compiles "[1, 2, 3]", %q{ + def test + a = [] + a.push(1, 2, 3) + a + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_ccall_variadic_with_no_args + assert_compiles "[1]", %q{ + def test + a = [1] + a.push + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_ccall_variadic_with_no_args_causing_argument_error + assert_compiles ":error", %q{ + def test + format + rescue ArgumentError + :error + end + + test + test + }, insns: [:opt_send_without_block] + end + + def test_allocating_in_hir_c_method_is + assert_compiles ":k", %q{ + # Put opt_new in a frame JIT code sets up that doesn't set cfp->pc + def a(f) = test(f) + def test(f) = (f.new if f) + # A parallel couple methods that will set PC at the same stack height + def second = third + def third = nil + + a(nil) + a(nil) + + class Foo + def self.new = :k + end + + second + + a(Foo) + }, call_threshold: 2, insns: [:opt_new] + end + + def test_singleton_class_invalidation_annotated_ccall + assert_compiles '[false, true]', %q{ + def define_singleton(obj, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + + def test(define) + obj = BasicObject.new + # This == call gets compiled to a CCall + obj == define_singleton(obj, define) + end + + result = [] + result << test(false) # Compiles BasicObject#== + result << test(true) # Should use singleton#== now + result + }, call_threshold: 2 + end + + def test_singleton_class_invalidation_optimized_variadic_ccall + assert_compiles '[1, 1000]', %q{ + def define_singleton(arr, define) + if define + # Wrap in C method frame to avoid exiting JIT on defineclass + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) # This CCall should be invalidated if singleton was defined + arr[0] + end + + result = [] + result << test(false) # Compiles Array#push as CCall + result << test(true) # Singleton defined, CCall should be invalidated + result + }, call_threshold: 2 + end + + def test_regression_cfp_sp_set_correctly_before_leaf_gc_call + assert_compiles ':ok', %q{ + def check(l, r) + return 1 unless l + 1 + check(*l) + check(*r) + end + + def tree(depth) + # This duparray is our leaf-gc target. + return [nil, nil] unless depth > 0 + + # Modify the local and pass it to the following calls. + depth -= 1 + [tree(depth), tree(depth)] + end + + def test + GC.stress = true + 2.times do + t = tree(11) + check(*t) + end + :ok + end + + test + }, call_threshold: 14, num_profiles: 5 + end + + def test_is_a_string_special_case + assert_compiles '[true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(String) + end + test("foo") + [test("bar"), test(1), test(false), test(:foo), test([]), test({})] + } + end + + def test_is_a_array_special_case + assert_compiles '[true, true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(Array) + end + test([]) + [test([1,2,3]), test([]), test(1), test(false), test(:foo), test("foo"), test({})] + } + end + + def test_is_a_hash_special_case + assert_compiles '[true, true, false, false, false, false, false]', %q{ + def test(x) + x.is_a?(Hash) + end + test({}) + [test({:a => "b"}), test({}), test(1), test(false), test(:foo), test([]), test("foo")] + } + end + + def test_is_a_hash_subclass + assert_compiles 'true', %q{ + class MyHash < Hash + end + def test(x) + x.is_a?(Hash) + end + test({}) + test(MyHash.new) + } + end + + def test_is_a_normal_case + assert_compiles '[true, false]', %q{ + class MyClass + end + def test(x) + x.is_a?(MyClass) + end + test("a") + [test(MyClass.new), test("a")] + } + end + + def test_exit_tracing + # This is a very basic smoke test. The StackProf format + # this option generates is external to us. + Dir.mktmpdir("zjit_test_exit_tracing") do |tmp_dir| + assert_compiles('true', <<~RUBY, extra_args: ['-C', tmp_dir, '--zjit-trace-exits']) + def test(object) = object.itself + + # induce an exit just for good measure + array = [] + test(array) + test(array) + def array.itself = :not_itself + test(array) + + RubyVM::ZJIT.exit_locations.is_a?(Hash) + RUBY + dump_files = Dir.glob('zjit_exits_*.dump', base: tmp_dir) + assert_equal(1, dump_files.length) + refute(File.empty?(File.join(tmp_dir, dump_files.first))) + end + end + + private + + # Assert that every method call in `test_script` can be compiled by ZJIT + # at a given call_threshold + def assert_compiles(expected, test_script, insns: [], **opts) + assert_runs(expected, test_script, insns:, assert_compiles: true, **opts) + end + + # Assert that `test_script` runs successfully with ZJIT enabled. + # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)` + # allows ZJIT to skip compiling methods. + def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts) + pipe_fd = 3 + disasm_method = :test + + script = <<~RUBY + ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call + result = { + ret_val:, + #{ unless insns.empty? + "insns: RubyVM::InstructionSequence.of(method(#{disasm_method.inspect})).to_a" + end} + } + IO.open(#{pipe_fd}).write(Marshal.dump(result)) + RUBY + + out, err, status, result = eval_with_jit(script, pipe_fd:, **opts) + assert_success(out, err, status) + + result = Marshal.load(result) + assert_equal(expected, result.fetch(:ret_val).inspect) + + unless insns.empty? + iseq = result.fetch(:insns) + assert_equal( + "YARVInstructionSequence/SimpleDataFormat", + iseq.first, + "Failed to get ISEQ disassembly. " \ + "Make sure to put code directly under the '#{disasm_method}' method." + ) + iseq_insns = iseq.last + + expected_insns = Set.new(insns) + iseq_insns.each do + next unless it.is_a?(Array) + expected_insns.delete(it.first) + end + assert(expected_insns.empty?, -> { "Not present in ISeq: #{expected_insns.to_a}" }) + end + end + + # Run a Ruby process with ZJIT options and a pipe for writing test results + def eval_with_jit( + script, + call_threshold: 1, + num_profiles: 1, + zjit: true, + stats: false, + debug: true, + allowed_iseqs: nil, + extra_args: nil, + timeout: 1000, + pipe_fd: nil + ) + args = ["--disable-gems", *extra_args] + if zjit + args << "--zjit-call-threshold=#{call_threshold}" + args << "--zjit-num-profiles=#{num_profiles}" + case stats + when true + args << "--zjit-stats" + when :quiet + args << "--zjit-stats-quiet" + else + args << "--zjit-stats=#{stats}" if stats + end + args << "--zjit-debug" if debug + if allowed_iseqs + jitlist = Tempfile.new("jitlist") + jitlist.write(allowed_iseqs) + jitlist.close + args << "--zjit-allowed-iseqs=#{jitlist.path}" + end + end + args << "-e" << script_shell_encode(script) + ios = {} + if pipe_fd + pipe_r, pipe_w = IO.pipe + # Separate thread so we don't deadlock when + # the child ruby blocks writing the output to pipe_fd + pipe_out = nil + pipe_reader = Thread.new do + pipe_out = pipe_r.read + pipe_r.close + end + ios[pipe_fd] = pipe_w + end + result = EnvUtil.invoke_ruby(args, '', true, true, rubybin: RbConfig.ruby, timeout: timeout, ios:) + if pipe_fd + pipe_w.close + pipe_reader.join(timeout) + result << pipe_out + end + result + ensure + pipe_reader&.kill + pipe_reader&.join(timeout) + pipe_r&.close + pipe_w&.close + jitlist&.unlink + end + + def assert_success(out, err, status) + message = "exited with status #{status.to_i}" + message << "\nstdout:\n```\n#{out}```\n" unless out.empty? + message << "\nstderr:\n```\n#{err}```\n" unless err.empty? + assert status.success?, message + end + + def script_shell_encode(s) + # We can't pass utf-8-encoded characters directly in a shell arg. But we can use Ruby \u constants. + s.chars.map { |c| c.ascii_only? ? c : "\\u%x" % c.codepoints[0] }.join + end +end diff --git a/test/ruby/ut_eof.rb b/test/ruby/ut_eof.rb new file mode 100644 index 0000000000..fcd7a63988 --- /dev/null +++ b/test/ruby/ut_eof.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: false +require 'test/unit' + +module TestEOF + def test_eof_0 + open_file("") {|f| + assert_equal("", f.read(0)) + assert_equal("", f.read(0)) + assert_equal("", f.read) + assert_equal("", f.read(0)) + assert_equal("", f.read(0)) + } + open_file("") {|f| + assert_nil(f.read(1)) + assert_equal("", f.read) + assert_nil(f.read(1)) + } + open_file("") {|f| + s = "x" + assert_equal("", f.read(nil, s)) + assert_equal("", s) + } + open_file("") {|f| + s = "x" + assert_nil(f.read(10, s)) + assert_equal("", s) + } + end + + def test_eof_0_rw + return unless respond_to? :open_file_rw + open_file_rw("") {|f| + assert_equal("", f.read) + assert_equal("", f.read) + assert_equal(0, f.syswrite("")) + assert_equal("", f.read) + } + end + + def test_eof_1 + open_file("a") {|f| + assert_equal("", f.read(0)) + assert_equal("a", f.read(1)) + assert_equal("" , f.read(0)) + assert_equal("" , f.read(0)) + assert_equal("", f.read) + assert_equal("", f.read(0)) + assert_equal("", f.read(0)) + } + open_file("a") {|f| + assert_equal("a", f.read(1)) + assert_nil(f.read(1)) + } + open_file("a") {|f| + assert_equal("a", f.read(2)) + assert_nil(f.read(1)) + assert_equal("", f.read) + assert_nil(f.read(1)) + } + open_file("a") {|f| + assert_equal("a", f.read) + assert_nil(f.read(1)) + assert_equal("", f.read) + assert_nil(f.read(1)) + } + open_file("a") {|f| + assert_equal("a", f.read(2)) + assert_equal("", f.read) + assert_equal("", f.read) + } + open_file("a") {|f| + assert_equal("a", f.read) + assert_equal("", f.read(0)) + } + open_file("a") {|f| + s = "x" + assert_equal("a", f.read(nil, s)) + assert_equal("a", s) + } + open_file("a") {|f| + s = "x" + assert_equal("a", f.read(10, s)) + assert_equal("a", s) + } + end + + def test_eof_2 + open_file("") {|f| + assert_equal("", f.read) + assert_predicate(f, :eof?) + } + end + + def test_eof_3 + open_file("") {|f| + assert_predicate(f, :eof?) + } + end + + module Seek + def open_file_seek(content, pos) + open_file(content) do |f| + f.seek(pos) + yield f + end + end + + def test_eof_0_seek + open_file_seek("", 10) {|f| + assert_equal(10, f.pos) + assert_equal("", f.read(0)) + assert_equal("", f.read) + assert_equal("", f.read(0)) + assert_equal("", f.read) + } + end + + def test_eof_1_seek + open_file_seek("a", 10) {|f| + assert_equal("", f.read) + assert_equal("", f.read) + } + open_file_seek("a", 1) {|f| + assert_equal("", f.read) + assert_equal("", f.read) + } + end + end +end |
