diff --git a/array.rb b/array.rb index 7ee4e09a4c00eb..b2965589325635 100644 --- a/array.rb +++ b/array.rb @@ -285,23 +285,24 @@ def select # :nodoc: end end - if Primitive.rb_builtin_basic_definition_p(:find) - undef :find + # TODO: Temporarily disabled because it is not compatible and fails multiple spec/ruby/core/array specs + # if Primitive.rb_builtin_basic_definition_p(:find) + # undef :find - def find(if_none_proc = nil) # :nodoc: - Primitive.attr! :inline_block, :c_trace, :without_interrupts + # def find(if_none_proc = nil) # :nodoc: + # Primitive.attr! :inline_block, :c_trace, :without_interrupts - unless defined?(yield) - return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' - end - i = 0 - until Primitive.rb_jit_ary_at_end(i) - value = Primitive.rb_jit_ary_at(i) - return value if yield(value) - i = Primitive.rb_jit_fixnum_inc(i) - end - if_none_proc&.call - end - end + # unless defined?(yield) + # return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' + # end + # i = 0 + # until Primitive.rb_jit_ary_at_end(i) + # value = Primitive.rb_jit_ary_at(i) + # return value if yield(value) + # i = Primitive.rb_jit_fixnum_inc(i) + # end + # if_none_proc&.call + # end + # end end end diff --git a/spec/ruby/command_line/dash_r_spec.rb b/spec/ruby/command_line/dash_r_spec.rb index 0de9ba2e24e920..052ad5e638003b 100644 --- a/spec/ruby/command_line/dash_r_spec.rb +++ b/spec/ruby/command_line/dash_r_spec.rb @@ -25,4 +25,9 @@ out.should_not.include?("REQUIRED") out.should.include?("No such file or directory") end + + it "requires in order when given multiple times" do + ruby_exe("", options: "-r#{fixture(__FILE__, "test_file.rb")} -r#{fixture(__FILE__, "verbose.rb")}"). + should == "REQUIRED\nfalse\n" + end end diff --git a/spec/ruby/command_line/dash_upper_e_spec.rb b/spec/ruby/command_line/dash_upper_e_spec.rb index 5a83962583e9c3..e260a4ec4763cd 100644 --- a/spec/ruby/command_line/dash_upper_e_spec.rb +++ b/spec/ruby/command_line/dash_upper_e_spec.rb @@ -32,6 +32,11 @@ ruby_exe("p 1", options: '-Eascii:ascii -U', args: '2>&1', - exit_status: 1).should =~ /RuntimeError/ + exit_status: 1).should =~ /already set to ascii \(RuntimeError\)/ + end + + it "doesn't raise a RuntimeError if used with -U in RUBYOPT" do + ruby_exe("puts Encoding.default_external, Encoding.default_internal", options: '-E ascii:ascii', env: { 'RUBYOPT' => '-U' }). + should == "US-ASCII\nUS-ASCII\n" end end diff --git a/spec/ruby/command_line/dash_x_spec.rb b/spec/ruby/command_line/dash_x_spec.rb index 38f97a5ab1b2f7..ecd74e6d4a5ab0 100644 --- a/spec/ruby/command_line/dash_x_spec.rb +++ b/spec/ruby/command_line/dash_x_spec.rb @@ -7,6 +7,12 @@ result.should == "success\n" end + it "changes the working directory when given" do + dir = fixture __FILE__, "bin" + result = ruby_exe(nil, options: "-x#{dir} embedded_ruby_change_directory.txt") + result.should == "#{dir}\n" + end + it "fails when /\#!.*ruby.*/-ish line in target file is not found" do bad_embedded_ruby = fixture __FILE__, "bin/bad_embedded_ruby.txt" result = ruby_exe(bad_embedded_ruby, options: '-x', args: '2>&1', exit_status: 1) diff --git a/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt b/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt new file mode 100644 index 00000000000000..338f1491c72650 --- /dev/null +++ b/spec/ruby/command_line/fixtures/bin/embedded_ruby_change_directory.txt @@ -0,0 +1,3 @@ +@@@This line is not valid Ruby +#!ruby +puts Dir.pwd diff --git a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb index 3718899d614c97..8e7714572bece7 100644 --- a/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb +++ b/spec/ruby/command_line/fixtures/freeze_flag_one_literal.rb @@ -1,2 +1,2 @@ -ids = Array.new(2) { "abc".object_id } -p ids.first == ids.last +ids = Array.new(2) { "abc" } +p ids.first.equal? ids.last diff --git a/spec/ruby/command_line/rubyopt_spec.rb b/spec/ruby/command_line/rubyopt_spec.rb index eb297cd6fe37e5..490e6a809a310b 100644 --- a/spec/ruby/command_line/rubyopt_spec.rb +++ b/spec/ruby/command_line/rubyopt_spec.rb @@ -59,6 +59,11 @@ ruby_exe("p $VERBOSE").chomp.should == "true" end + it "is overwritten by a cli switch" do + ENV["RUBYOPT"] = "-W2" + ruby_exe("p $VERBOSE", options: "-W0").chomp.should == "nil" + end + it "suppresses deprecation warnings for '-W:no-deprecated'" do ENV["RUBYOPT"] = '-W:no-deprecated' result = ruby_exe('$; = ""', args: '2>&1') @@ -83,6 +88,12 @@ ruby_exe("0", args: '2>&1').should =~ /^rubyopt.rb required/ end + it "requires from CLI -r first and then from RUBYOPT -r" do + ENV["RUBYOPT"] = "-r#{fixture(__FILE__, "rubyopt.rb")}" + ruby_exe("", options: "-r#{fixture(__FILE__, "test_file.rb")}"). + should == "REQUIRED\nrubyopt.rb required\n" + end + it "raises a RuntimeError for '-a'" do ENV["RUBYOPT"] = '-a' ruby_exe("", args: '2>&1', exit_status: 1).should =~ /RuntimeError/ diff --git a/spec/ruby/core/array/detect_spec.rb b/spec/ruby/core/array/detect_spec.rb new file mode 100644 index 00000000000000..b8a98062ec3765 --- /dev/null +++ b/spec/ruby/core/array/detect_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + describe "Array#detect" do + it "is an alias of Array#find" do + Array.instance_method(:detect).should == Array.instance_method(:find) + end + end +end diff --git a/spec/ruby/core/array/find_spec.rb b/spec/ruby/core/array/find_spec.rb new file mode 100644 index 00000000000000..d9cb1a0e32ce09 --- /dev/null +++ b/spec/ruby/core/array/find_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +ruby_version_is "4.0" do + describe "Array#find" do + it "returns the first element for which the block is not false" do + [1, 2, 3, 4, 5].find { |x| x % 2 == 0 }.should == 2 + end + + it "returns nil when the block is false and there is no ifnone proc given" do + [1, 2, 3].find { |x| false }.should == nil + end + + it "returns the value of the ifnone proc if the block is false" do + fail_proc = -> { "cheeseburgers" } + [1, 2, 3].find(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "doesn't call the ifnone proc if an element is found" do + fail_proc = -> { raise "This shouldn't have been called" } + [1, 2, 3].find(fail_proc) { |x| x == 1 }.should == 1 + end + + it "calls the ifnone proc only once when the block is false" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + [1, 2, 3].find(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "calls the ifnone proc when there are no elements" do + fail_proc = -> { "yay" } + [].find(fail_proc) { |x| true }.should == "yay" + end + + it "ignores the ifnone argument when nil" do + [1, 2, 3].find(nil) { |x| false }.should == nil + end + + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { [1, 2, 3].find(42) { |x| false } }.should.raise(NoMethodError) + end + + it "iterates elements in forward order" do + visited = [] + [1, 2, 3].find { |element| visited << element; false } + visited.should == [1, 2, 3] + end + + it "passes through the values yielded by #each_with_index" do + ScratchPad.record [] + [:a, :b].each_with_index.to_a.find { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:a, 0], [:b, 1]] + end + + it "stops iterating as soon as an element is found" do + visited = [] + [1, 2, 3, 4, 5].find { |x| visited << x; x == 3 } + visited.should == [1, 2, 3] + end + + it "returns an enumerator when no block given" do + [1, 2, 3].find.should.instance_of?(Enumerator) + end + + it "passes the ifnone proc to the enumerator" do + fail_proc = -> { "cheeseburgers" } + enum = [1, 2, 3].find(fail_proc) + enum.each { |x| false }.should == "cheeseburgers" + end + + it "does not destructure elements" do + multi = [[1, 2], [3, 4, 5], [6, 7, 8, 9]] + multi.find { |e| e == [1, 2] }.should == [1, 2] + end + + it "rechecks the array size during iteration" do + ary = [4, 2, 1, 5, 1, 3] + seen = [] + ary.find { |x| seen << x; ary.clear; false } + seen.should == [4] + end + + it_behaves_like :enumeratorized_with_unknown_size, :find, [1, 2, 3] + end +end diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb index 05283c0f7447cb..7389cb98558640 100644 --- a/spec/ruby/core/array/fixtures/classes.rb +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -5,7 +5,7 @@ def pack_format(count=nil, repeat=nil) format = instance_variable_get(:@method) format += count.to_s unless format == 'P' || format == 'p' format *= repeat if repeat - format.dup # because it may then become tainted + format end end diff --git a/spec/ruby/core/array/pack/a_spec.rb b/spec/ruby/core/array/pack/a_spec.rb index 03bfd8214c8ee8..491b8af9c2c4b6 100644 --- a/spec/ruby/core/array/pack/a_spec.rb +++ b/spec/ruby/core/array/pack/a_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "Array#pack with format 'A'" do it_behaves_like :array_pack_basic, 'A' it_behaves_like :array_pack_basic_non_float, 'A' it_behaves_like :array_pack_no_platform, 'A' it_behaves_like :array_pack_string, 'A' - it_behaves_like :array_pack_taint, 'A' it "calls #to_str to convert an Object to a String" do obj = mock("pack A string") @@ -49,7 +47,6 @@ it_behaves_like :array_pack_basic_non_float, 'a' it_behaves_like :array_pack_no_platform, 'a' it_behaves_like :array_pack_string, 'a' - it_behaves_like :array_pack_taint, 'a' it "adds all the bytes to the output when passed the '*' modifier" do ["abc"].pack("a*").should == "abc" diff --git a/spec/ruby/core/array/pack/b_spec.rb b/spec/ruby/core/array/pack/b_spec.rb index f7576846ef9062..ab50bd5af8cfb0 100644 --- a/spec/ruby/core/array/pack/b_spec.rb +++ b/spec/ruby/core/array/pack/b_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' -require_relative 'shared/taint' describe "Array#pack with format 'B'" do it_behaves_like :array_pack_basic, 'B' it_behaves_like :array_pack_basic_non_float, 'B' it_behaves_like :array_pack_arguments, 'B' it_behaves_like :array_pack_hex, 'B' - it_behaves_like :array_pack_taint, 'B' it "calls #to_str to convert an Object to a String" do obj = mock("pack B string") @@ -66,7 +64,6 @@ it_behaves_like :array_pack_basic_non_float, 'b' it_behaves_like :array_pack_arguments, 'b' it_behaves_like :array_pack_hex, 'b' - it_behaves_like :array_pack_taint, 'b' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/h_spec.rb b/spec/ruby/core/array/pack/h_spec.rb index 1492d02b1fb2c6..389295cb0b043a 100644 --- a/spec/ruby/core/array/pack/h_spec.rb +++ b/spec/ruby/core/array/pack/h_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/encodings' -require_relative 'shared/taint' describe "Array#pack with format 'H'" do it_behaves_like :array_pack_basic, 'H' it_behaves_like :array_pack_basic_non_float, 'H' it_behaves_like :array_pack_arguments, 'H' it_behaves_like :array_pack_hex, 'H' - it_behaves_like :array_pack_taint, 'H' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") @@ -112,7 +110,6 @@ it_behaves_like :array_pack_basic_non_float, 'h' it_behaves_like :array_pack_arguments, 'h' it_behaves_like :array_pack_hex, 'h' - it_behaves_like :array_pack_taint, 'h' it "calls #to_str to convert an Object to a String" do obj = mock("pack H string") diff --git a/spec/ruby/core/array/pack/m_spec.rb b/spec/ruby/core/array/pack/m_spec.rb index fb670d120e2f14..090ae6a7021aa2 100644 --- a/spec/ruby/core/array/pack/m_spec.rb +++ b/spec/ruby/core/array/pack/m_spec.rb @@ -2,13 +2,11 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "Array#pack with format 'M'" do it_behaves_like :array_pack_basic, 'M' it_behaves_like :array_pack_basic_non_float, 'M' it_behaves_like :array_pack_arguments, 'M' - it_behaves_like :array_pack_taint, 'M' it "encodes an empty string as an empty string" do [""].pack("M").should == "" @@ -202,7 +200,6 @@ it_behaves_like :array_pack_basic, 'm' it_behaves_like :array_pack_basic_non_float, 'm' it_behaves_like :array_pack_arguments, 'm' - it_behaves_like :array_pack_taint, 'm' it "encodes an empty string as an empty string" do [""].pack("m").should == "" diff --git a/spec/ruby/core/array/pack/p_spec.rb b/spec/ruby/core/array/pack/p_spec.rb index b023bf91106829..c9ca5f7174451c 100644 --- a/spec/ruby/core/array/pack/p_spec.rb +++ b/spec/ruby/core/array/pack/p_spec.rb @@ -1,11 +1,9 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "Array#pack with format 'P'" do it_behaves_like :array_pack_basic_non_float, 'P' - it_behaves_like :array_pack_taint, 'P' it "produces as many bytes as there are in a pointer" do ["hello"].pack("P").size.should == [0].pack("J").size @@ -22,7 +20,6 @@ describe "Array#pack with format 'p'" do it_behaves_like :array_pack_basic_non_float, 'p' - it_behaves_like :array_pack_taint, 'p' it "produces as many bytes as there are in a pointer" do ["hello"].pack("p").size.should == [0].pack("J").size diff --git a/spec/ruby/core/array/pack/shared/taint.rb b/spec/ruby/core/array/pack/shared/taint.rb deleted file mode 100644 index 2c2b011c34120a..00000000000000 --- a/spec/ruby/core/array/pack/shared/taint.rb +++ /dev/null @@ -1,2 +0,0 @@ -describe :array_pack_taint, shared: true do -end diff --git a/spec/ruby/core/array/pack/u_spec.rb b/spec/ruby/core/array/pack/u_spec.rb index c6a0d77eb2d937..633410404d6f36 100644 --- a/spec/ruby/core/array/pack/u_spec.rb +++ b/spec/ruby/core/array/pack/u_spec.rb @@ -3,7 +3,6 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' -require_relative 'shared/taint' describe "Array#pack with format 'U'" do it_behaves_like :array_pack_basic, 'U' @@ -16,7 +15,6 @@ it_behaves_like :array_pack_basic, 'u' it_behaves_like :array_pack_basic_non_float, 'u' it_behaves_like :array_pack_arguments, 'u' - it_behaves_like :array_pack_taint, 'u' it "calls #to_str to convert an Object to a String" do obj = mock("pack u string") diff --git a/spec/ruby/core/array/pack/z_spec.rb b/spec/ruby/core/array/pack/z_spec.rb index 5cd084c8251212..8df5c42bef3e03 100644 --- a/spec/ruby/core/array/pack/z_spec.rb +++ b/spec/ruby/core/array/pack/z_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "Array#pack with format 'Z'" do it_behaves_like :array_pack_basic, 'Z' it_behaves_like :array_pack_basic_non_float, 'Z' it_behaves_like :array_pack_no_platform, 'Z' it_behaves_like :array_pack_string, 'Z' - it_behaves_like :array_pack_taint, 'Z' it "calls #to_str to convert an Object to a String" do obj = mock("pack Z string") diff --git a/spec/ruby/core/array/rfind_spec.rb b/spec/ruby/core/array/rfind_spec.rb new file mode 100644 index 00000000000000..cf5f3d8b01ec48 --- /dev/null +++ b/spec/ruby/core/array/rfind_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../enumerable/shared/enumeratorized' + +# Modifying a collection while the contents are being iterated +# gives undefined behavior. See +# https://blade.ruby-lang.org/ruby-core/23633 + +ruby_version_is "4.0" do + describe "Array#rfind" do + it "returns the last element for which the block is not false" do + [1, 2, 3, 4, 5].rfind { |x| x % 2 == 0 }.should == 4 + end + + it "returns nil when the block is false and there is no ifnone proc given" do + [1, 2, 3].rfind { |x| false }.should == nil + end + + it "returns the value of the ifnone proc if the block is false" do + fail_proc = -> { "cheeseburgers" } + [1, 2, 3].rfind(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "doesn't call the ifnone proc if an element is found" do + fail_proc = -> { raise "This shouldn't have been called" } + [1, 2, 3].rfind(fail_proc) { |x| x == 3 }.should == 3 + end + + it "calls the ifnone proc only once when the block is false" do + times = 0 + fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } + [1, 2, 3].rfind(fail_proc) { |x| false }.should == "cheeseburgers" + end + + it "calls the ifnone proc when there are no elements" do + fail_proc = -> { "yay" } + [].rfind(fail_proc) { |x| true }.should == "yay" + end + + it "ignores the ifnone argument when nil" do + [1, 2, 3].rfind(nil) { |x| false }.should == nil + end + + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { [1, 2, 3].rfind(42) { |x| false } }.should.raise(NoMethodError) + end + + it "iterates elements in reverse order" do + visited = [] + [1, 2, 3].rfind { |x| visited << x; false } + visited.should == [3, 2, 1] + end + + it "passes through the values yielded by #each_with_index" do + ScratchPad.record [] + [:a, :b].each_with_index.to_a.rfind { |x, i| ScratchPad << [x, i]; nil } + ScratchPad.recorded.should == [[:b, 1], [:a, 0]] + end + + it "stops iterating as soon as a matching element is found from the end" do + visited = [] + [1, 2, 3, 4, 5].rfind { |x| visited << x; x == 3 } + visited.should == [5, 4, 3] + end + + it "returns an enumerator when no block given" do + [1, 2, 3].rfind.should.instance_of?(Enumerator) + end + + it "passes the ifnone proc to the enumerator" do + fail_proc = -> { "cheeseburgers" } + enum = [1, 2, 3].rfind(fail_proc) + enum.each { |x| false }.should == "cheeseburgers" + end + + it "does not destructure elements" do + multi = [[1, 2], [3, 4, 5], [6, 7, 8, 9]] + multi.rfind { |e| e == [1, 2] }.should == [1, 2] + end + + it "rechecks the array size during iteration" do + ary = [4, 2, 1, 5, 1, 3] + seen = [] + ary.rfind { |x| seen << x; ary.clear; false } + seen.should == [3] + end + + it_behaves_like :enumeratorized_with_unknown_size, :rfind, [1, 2, 3] + end +end diff --git a/spec/ruby/core/binding/implicit_parameter_defined_spec.rb b/spec/ruby/core/binding/implicit_parameter_defined_spec.rb new file mode 100644 index 00000000000000..2b3844dff7b206 --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameter_defined_spec.rb @@ -0,0 +1,84 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameter_defined?' do + it 'returns false when a numbered parameters or "it" does not exist' do + binding.implicit_parameter_defined?(:it).should == false + binding.implicit_parameter_defined?(:_1).should == false + end + + it 'returns true when a numbered parameter exists' do + proc { _1; binding.implicit_parameter_defined?(:_1) }.call.should == true + proc { r = binding.implicit_parameter_defined?(:_1); _1; r }.call.should == true + end + + it 'returns true for all numbered parameters up to the maximum referenced one' do + _3 + binding.implicit_parameter_defined?(:_1).should == true + binding.implicit_parameter_defined?(:_2).should == true + binding.implicit_parameter_defined?(:_3).should == true + binding.implicit_parameter_defined?(:_4).should == false + end + + it 'returns true when "it" parameter exists' do + proc { it; binding.implicit_parameter_defined?(:it) }.call.should == true + proc { r = binding.implicit_parameter_defined?(:it); it; r }.call.should == true + end + + it 'returns false when a numbered parameter is defined in a parent scope' do + foo = _1 + -> { + binding.implicit_parameter_defined?(:_1) + }.call.should == false + end + + it 'returns false when "it" parameter is defined in a parent scope' do + foo = it + -> { + binding.implicit_parameter_defined?(:it) + }.call.should == false + end + + it 'returns false when a numbered parameter is defined in a nested scope' do + foo = -> { _1 } + binding.implicit_parameter_defined?(:_1).should == false + end + + it 'returns false when "it" parameter is defined in a nested scope' do + foo = -> { it } + binding.implicit_parameter_defined?(:it).should == false + end + + it 'allows usage of a String as a numbered parameter name' do + _1 + binding.implicit_parameter_defined?('_1').should == true + end + + it 'allows usage of a String as "it" parameter name' do + it + binding.implicit_parameter_defined?('it').should == true + end + + it 'allows usage of an object responding to #to_str as the variable name' do + foo = _1 + name = mock(:obj) + name.stub!(:to_str).and_return('_1') + + binding.implicit_parameter_defined?(name).should == true + end + + it 'raises a NameError when given neither a numbered parameter nor "it" parameter' do + -> { + binding.implicit_parameter_defined?(:a) + }.should.raise(NameError, "'a' is not an implicit parameter") + end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.implicit_parameter_defined?(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end + end + RUBY +end diff --git a/spec/ruby/core/binding/implicit_parameter_get_spec.rb b/spec/ruby/core/binding/implicit_parameter_get_spec.rb new file mode 100644 index 00000000000000..314746db2f85ad --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameter_get_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameter_get' do + it 'reads a numbered parameter value when it exists' do + -> { _1; binding.implicit_parameter_get(:_1) }.call(:a).should == :a + -> { r = binding.implicit_parameter_get(:_1); _1; r }.call(:a).should == :a + end + + it 'reads any numbered parameter value up to the maximum referenced one' do + proc { + _3 + [ + binding.implicit_parameter_get(:_1), + binding.implicit_parameter_get(:_2), + binding.implicit_parameter_get(:_3) + ] + }.call(:a, :b, :c, :d).should == [:a, :b, :c] + end + + it 'reads "it" parameter value when it exists' do + -> { it; binding.implicit_parameter_get(:it) }.call(:a).should == :a + -> { r = binding.implicit_parameter_get(:it); it; r }.call(:a).should == :a + end + + it 'raises a NameError for not existing numbered parameter' do + proc { binding.implicit_parameter_get(:_1) }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError for not existing "it" parameter' do + proc { binding.implicit_parameter_get(:it) }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'raises a NameError when a numbered parameter is defined in a parent scope' do + proc { + foo = _1 + proc { binding.implicit_parameter_get(:_1) }.call + }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError when "it" parameter is defined in a parent scope' do + proc { + foo = it + proc { binding.implicit_parameter_get(:it) }.call + }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'raises a NameError when a numbered parameter is defined in a nested scope' do + proc { + foo = -> { _1 } + binding.implicit_parameter_get(:_1) + }.should.raise(NameError, /implicit parameter '_1' is not defined for/) + end + + it 'raises a NameError when "it" parameter is defined in a nested scope' do + proc { + foo = -> { it } + binding.implicit_parameter_get(:it) + }.should.raise(NameError, /implicit parameter 'it' is not defined for/) + end + + it 'allows usage of a String as a numbered parameter name' do + -> { _1; binding.implicit_parameter_get('_1') }.call(:a).should == :a + end + + it 'allows usage of a String as "it" parameter name' do + -> { it; binding.implicit_parameter_get('it') }.call(:a).should == :a + end + + it 'allows usage of an object responding to #to_str as the variable name' do + name = mock(:obj) + name.stub!(:to_str).and_return('_1') + + -> { _1; binding.implicit_parameter_get(name) }.call(:a).should == :a + end + + it 'raises a NameError when given neither a numbered parameter nor "it" parameter' do + -> { binding.implicit_parameter_get(:a) }.should.raise(NameError, "'a' is not an implicit parameter") + end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.implicit_parameter_get(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end + end + RUBY +end diff --git a/spec/ruby/core/binding/implicit_parameters_spec.rb b/spec/ruby/core/binding/implicit_parameters_spec.rb new file mode 100644 index 00000000000000..5fd2ca0ac1edc2 --- /dev/null +++ b/spec/ruby/core/binding/implicit_parameters_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' + +ruby_version_is "4.0" do + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 + describe 'Binding#implicit_parameters' do + it 'returns an Array' do + binding.implicit_parameters.should.is_a?(Array) + end + + it 'includes "it" parameter when defined in the current scope' do + a = it + binding.implicit_parameters.should == [:it] + end + + it 'includes numbered parameters when defined in the current scope' do + a = _1 + binding.implicit_parameters.should == [:_1] + end + + it 'includes all the numbered parameter names up to the maximum referenced one' do + proc { _3; binding.implicit_parameters }.call(:a, :b, :c, :d).should == [:_1, :_2, :_3] + end + + it 'returns [] when neither "it" parameter nor numbered parameters are defined in the current scope' do + a = 1 + binding.implicit_parameters.should == [] + end + + it "includes implicit parameters defined after calling binding.implicit_parameters" do + proc { + r = binding.implicit_parameters + a = it + r + }.call.should == [:it] + + proc { + r = binding.implicit_parameters + a = _1 + r + }.call.should == [:_1] + end + + it 'ignores "it" parameter defined in a parent scope' do + foo = it + proc { binding.implicit_parameters }.call.should == [] + end + + it 'ignores numbered parameters defined in a parent scope' do + foo = _1 + proc { binding.implicit_parameters }.call.should == [] + end + + it 'ignores "it" parameter defined in a nested scope' do + foo = -> { it } + binding.implicit_parameters.should == [] + end + + it 'ignores numbered parameters defined in a nested scope' do + foo = -> { _1 } + binding.implicit_parameters.should == [] + end + end + RUBY +end diff --git a/spec/ruby/core/binding/local_variable_defined_spec.rb b/spec/ruby/core/binding/local_variable_defined_spec.rb index 2fc6504ee51699..15f565108c9c04 100644 --- a/spec/ruby/core/binding/local_variable_defined_spec.rb +++ b/spec/ruby/core/binding/local_variable_defined_spec.rb @@ -43,4 +43,10 @@ binding.local_variable_defined?(name).should == true end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_defined?(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/binding/local_variable_get_spec.rb b/spec/ruby/core/binding/local_variable_get_spec.rb index d97100deda3da6..9f5da405924a54 100644 --- a/spec/ruby/core/binding/local_variable_get_spec.rb +++ b/spec/ruby/core/binding/local_variable_get_spec.rb @@ -53,4 +53,10 @@ -> { bind.local_variable_get(:$~) }.should.raise(NameError) -> { bind.local_variable_get(:$_) }.should.raise(NameError) end + + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_get(1) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/binding/local_variable_set_spec.rb b/spec/ruby/core/binding/local_variable_set_spec.rb index 3e4f407fc3292c..06c8fea9483e42 100644 --- a/spec/ruby/core/binding/local_variable_set_spec.rb +++ b/spec/ruby/core/binding/local_variable_set_spec.rb @@ -68,4 +68,9 @@ -> { bind.local_variable_set(:$_, "") }.should.raise(NameError) end + it 'raises a TypeError when given non-String/Symbol as the variable name' do + -> { + binding.local_variable_set(1, 2) + }.should.raise(TypeError, '1 is not a symbol nor a string') + end end diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb index ceccffe4703779..ddd6dccf7d3fdd 100644 --- a/spec/ruby/core/complex/to_s_spec.rb +++ b/spec/ruby/core/complex/to_s_spec.rb @@ -16,11 +16,8 @@ Complex(1, -5).to_s.should == "1-5i" Complex(-2.5, -1.5).to_s.should == "-2.5-1.5i" - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Complex(1, 0).to_s.should == "1+0i" - Complex(1, -0).to_s.should == "1+0i" - end + Complex(1, 0).to_s.should == "1+0i" + Complex(1, -0).to_s.should == "1+0i" end it "returns 1+0.0i for Complex(1, 0.0)" do diff --git a/spec/ruby/core/enumerable/find_spec.rb b/spec/ruby/core/enumerable/find_spec.rb index 4ac4b75c4735de..d3b8cd9d73e9ed 100644 --- a/spec/ruby/core/enumerable/find_spec.rb +++ b/spec/ruby/core/enumerable/find_spec.rb @@ -10,25 +10,16 @@ @empty = [] end - it "passes each entry in enum to block while block when block is false" do - visited_elements = [] - @numerous.find do |element| - visited_elements << element - false + it "returns the first element for which the block is not false" do + @elements.each do |element| + @numerous.find {|e| e > element - 1 }.should == element end - visited_elements.should == @elements end it "returns nil when the block is false and there is no ifnone proc given" do @numerous.find {|e| false }.should == nil end - it "returns the first element for which the block is not false" do - @elements.each do |element| - @numerous.find {|e| e > element - 1 }.should == element - end - end - it "returns the value of the ifnone proc if the block is false" do fail_proc = -> { "cheeseburgers" } @numerous.find(fail_proc) {|e| false }.should == "cheeseburgers" @@ -54,19 +45,35 @@ @numerous.find(nil) {|e| false }.should == nil end + it "raises a NoMethodError if the ifnone argument does not respond to #call and no element is found" do + -> { @numerous.find(42) {|e| false } }.should.raise(NoMethodError) + end + + it "iterates elements in forward order" do + visited = [] + @numerous.find { |element| visited << element; false } + visited.should == @elements + end + it "passes through the values yielded by #each_with_index" do [:a, :b].each_with_index.find { |x, i| ScratchPad << [x, i]; nil } ScratchPad.recorded.should == [[:a, 0], [:b, 1]] end + it "stops iterating as soon as an element is found" do + visited = [] + @numerous.find { |x| visited << x; x == 6 } + visited.should == [2, 4, 6] + end + it "returns an enumerator when no block given" do @numerous.find.should.instance_of?(Enumerator) end it "passes the ifnone proc to the enumerator" do - times = 0 - fail_proc = -> { times += 1; raise if times > 1; "cheeseburgers" } - @numerous.find(fail_proc).each {|e| false }.should == "cheeseburgers" + fail_proc = -> { "cheeseburgers" } + enum = @numerous.find(fail_proc) + enum.each { |e| false }.should == "cheeseburgers" end it "gathers whole arrays as elements when each yields multiple" do diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb index 039fd7572c2fc5..f439970c308483 100644 --- a/spec/ruby/core/file/birthtime_spec.rb +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -34,6 +34,21 @@ rescue NotImplementedError => e e.message.should.start_with?(*not_implemented_messages) end + + platform_is :linux do + guard -> { File.directory?('/proc') } do + it "raises NotImplementedError for a filesystem that does not support birthtime" do + # check if birthtime works on a regular file first + begin + File.birthtime(__FILE__) + rescue NotImplementedError + skip + end + + -> { File.birthtime('/proc') }.should.raise(NotImplementedError, "birthtime is unimplemented on this filesystem") + end + end + end end describe "File#birthtime" do diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb index 728f6353971199..6cee468eab4057 100644 --- a/spec/ruby/core/file/stat/birthtime_spec.rb +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -22,6 +22,7 @@ st = File.stat(@file) st.birthtime.should.is_a?(Time) st.birthtime.should <= Time.now + st.birthtime.should > Time.now - 24*60*60 rescue NotImplementedError => e e.message.should.start_with?(*not_implemented_messages) end diff --git a/spec/ruby/core/hash/compare_by_identity_spec.rb b/spec/ruby/core/hash/compare_by_identity_spec.rb index 1abf9d5666222b..31a8ca1240b2d1 100644 --- a/spec/ruby/core/hash/compare_by_identity_spec.rb +++ b/spec/ruby/core/hash/compare_by_identity_spec.rb @@ -80,6 +80,16 @@ def o.hash; 123; end @h[o].should == :o end + it "regards two empty hashes equal, even if one isn't compare_by_identity" do + @h.should == @idh + end + + it "doesn't regard two hashes equal when they differ only by compare_by_identity" do + @h[1] = 2 + @idh[1] = 2 + @h.should_not == @idh + end + it "raises a FrozenError on frozen hashes" do @h = @h.freeze -> { @h.compare_by_identity }.should.raise(FrozenError) diff --git a/spec/ruby/core/io/advise_spec.rb b/spec/ruby/core/io/advise_spec.rb index b77ed53ad4d0dd..8253f641bea1cc 100644 --- a/spec/ruby/core/io/advise_spec.rb +++ b/spec/ruby/core/io/advise_spec.rb @@ -44,7 +44,7 @@ it "raises a NotImplementedError if advise is not recognized" do ->{ @io.advise(:foo) - }.should.raise(NotImplementedError) + }.should.raise(NotImplementedError, "Unsupported advice: :foo") end it "supports the normal advice type" do diff --git a/spec/ruby/core/io/binread_spec.rb b/spec/ruby/core/io/binread_spec.rb index 3023c7f177a7af..200fa05abfb853 100644 --- a/spec/ruby/core/io/binread_spec.rb +++ b/spec/ruby/core/io/binread_spec.rb @@ -48,10 +48,23 @@ ruby_version_is ""..."4.0" do # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.binread(cmd) + IO.binread("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.binread("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.binread("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end end diff --git a/spec/ruby/core/io/foreach_spec.rb b/spec/ruby/core/io/foreach_spec.rb index ccd2f255170485..9d28f080ff34c9 100644 --- a/spec/ruby/core/io/foreach_spec.rb +++ b/spec/ruby/core/io/foreach_spec.rb @@ -49,12 +49,25 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.foreach(cmd).to_a + IO.foreach("|echo ok").to_a }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.foreach("|echo ok").to_a }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.foreach("|echo ok").to_a }.should.raise(Errno::EINVAL) + end + end + end end describe "IO.foreach" do diff --git a/spec/ruby/core/io/read_spec.rb b/spec/ruby/core/io/read_spec.rb index 5be969e2801248..0c165814c74194 100644 --- a/spec/ruby/core/io/read_spec.rb +++ b/spec/ruby/core/io/read_spec.rb @@ -150,8 +150,8 @@ end end -ruby_version_is ""..."4.0" do - describe "IO.read from a pipe" do +describe "IO.read from a pipe" do + ruby_version_is ""..."4.0" do it "runs the rest as a subprocess and returns the standard output" do cmd = "|sh -c 'echo hello'" platform_is :windows do @@ -216,12 +216,25 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation" do - cmd = "|echo ok" -> { - IO.read(cmd) + IO.read("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.read("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.read("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end end describe "IO.read on an empty file" do diff --git a/spec/ruby/core/io/readlines_spec.rb b/spec/ruby/core/io/readlines_spec.rb index d41d24d7d17b95..d872fb6b99d709 100644 --- a/spec/ruby/core/io/readlines_spec.rb +++ b/spec/ruby/core/io/readlines_spec.rb @@ -209,13 +209,26 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - IO.readlines(cmd) + IO.readlines("|echo ok") }.should complain(/IO process creation with a leading '\|'/) end end + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { IO.readlines("|echo ok") }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { IO.readlines("|echo ok") }.should.raise(Errno::EINVAL) + end + end + end + it_behaves_like :io_readlines, :readlines it_behaves_like :io_readlines_options_19, :readlines end diff --git a/spec/ruby/core/io/seek_spec.rb b/spec/ruby/core/io/seek_spec.rb index d26629fb892988..d6e553bae2ce58 100644 --- a/spec/ruby/core/io/seek_spec.rb +++ b/spec/ruby/core/io/seek_spec.rb @@ -24,6 +24,11 @@ @io.readline.should == "une.\n" end + it "accepts the symbol :CUR for SEEK_CUR" do + @io.seek(10, :CUR) + @io.readline.should == "igne une.\n" + end + it "moves the read position relative to the start with SEEK_SET" do @io.seek(1) @io.pos.should == 1 @@ -34,6 +39,11 @@ @io.readline.should == " la ligne une.\n" end + it "accepts the symbol :SET for SEEK_SET" do + @io.seek(43, :SET) + @io.readline.should == "Aquí está la línea tres.\n" + end + it "moves the read position relative to the end with SEEK_END" do @io.seek(0, IO::SEEK_END) @io.tell.should == 137 @@ -41,6 +51,11 @@ @io.readline.should == "cinco.\n" end + it "accepts the symbol :END for SEEK_END" do + @io.seek(0, :END) + @io.tell.should == 137 + end + it "moves the read position and clears EOF with SEEK_SET" do value = @io.read @io.seek(0, IO::SEEK_SET) diff --git a/spec/ruby/core/io/shared/new.rb b/spec/ruby/core/io/shared/new.rb index 6f318ddee50951..4952041a6f7f86 100644 --- a/spec/ruby/core/io/shared/new.rb +++ b/spec/ruby/core/io/shared/new.rb @@ -127,15 +127,15 @@ it "ignores the :encoding option when the :external_encoding option is present" do -> { - @io = IO.send(@method, @fd, 'w', external_encoding: 'utf-8', encoding: 'iso-8859-1:iso-8859-1') - }.should complain(/Ignoring encoding parameter/) - @io.external_encoding.to_s.should == 'UTF-8' + @io = IO.send(@method, @fd, 'w', external_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1') + }.should complain(/Ignoring encoding parameter 'iso-8859-1:iso-8859-1': external_encoding is used/) + @io.external_encoding.to_s.should == 'IBM866' end it "ignores the :encoding option when the :internal_encoding option is present" do -> { @io = IO.send(@method, @fd, 'w', internal_encoding: 'ibm866', encoding: 'iso-8859-1:iso-8859-1') - }.should complain(/Ignoring encoding parameter/) + }.should complain(/Ignoring encoding parameter 'iso-8859-1:iso-8859-1': internal_encoding is used/) @io.internal_encoding.to_s.should == 'IBM866' end diff --git a/spec/ruby/core/io/sysread_spec.rb b/spec/ruby/core/io/sysread_spec.rb index 2d58db22504137..078e97934a7668 100644 --- a/spec/ruby/core/io/sysread_spec.rb +++ b/spec/ruby/core/io/sysread_spec.rb @@ -103,7 +103,7 @@ it "discards the existing buffer content upon error" do buffer = +"existing content" - @file.seek(0, :END) + @file.seek(0, IO::SEEK_END) -> { @file.sysread(1, buffer) }.should.raise(EOFError) buffer.should.empty? end diff --git a/spec/ruby/core/io/sysseek_spec.rb b/spec/ruby/core/io/sysseek_spec.rb index 2384895dc5d8fb..e513d189aec0ff 100644 --- a/spec/ruby/core/io/sysseek_spec.rb +++ b/spec/ruby/core/io/sysseek_spec.rb @@ -21,6 +21,11 @@ @io.readline.should == "igne une.\n" end + it "accepts the symbol :CUR for SEEK_CUR" do + @io.sysseek(10, :CUR) + @io.readline.should == "igne une.\n" + end + it "raises an error when called after buffered reads" do @io.readline -> { @io.sysseek(-5, IO::SEEK_CUR) }.should.raise(IOError) @@ -36,6 +41,11 @@ @io.readline.should == "Aquí está la línea tres.\n" end + it "accepts the symbol :SET for SEEK_SET" do + @io.sysseek(43, :SET) + @io.readline.should == "Aquí está la línea tres.\n" + end + it "moves the read position relative to the end with SEEK_END" do @io.sysseek(1, IO::SEEK_END) @@ -46,4 +56,9 @@ @io.sysseek(-25, IO::SEEK_END) @io.sysread(7).should == "cinco.\n" end + + it "accepts the symbol :END for SEEK_END" do + @io.sysseek(-25, :END) + @io.sysread(7).should == "cinco.\n" + end end diff --git a/spec/ruby/core/io/ungetc_spec.rb b/spec/ruby/core/io/ungetc_spec.rb index 4a9e67f126b505..413ea2aa71c65d 100644 --- a/spec/ruby/core/io/ungetc_spec.rb +++ b/spec/ruby/core/io/ungetc_spec.rb @@ -105,7 +105,7 @@ it "raises TypeError if passed nil" do @io.getc.should == ?V - proc{@io.ungetc(nil)}.should.raise(TypeError) + proc{@io.ungetc(nil)}.should raise_consistent_error(TypeError, /no implicit conversion of nil into String/) end it "puts one or more characters back in the stream" do diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 1a745ba0124fce..469f9a89ee316b 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -237,6 +237,19 @@ }.should complain(/IO process creation with a leading '\|'/) end end + + ruby_version_is "4.0" do + it "writes to that literal file when path starts with a pipe" do + Dir.chdir(tmp("")) do + begin + IO.write("|cat", "xxx") + File.read("|cat").should == "xxx" + ensure + File.unlink("|cat") + end + end + end + end end end diff --git a/spec/ruby/core/kernel/Array_spec.rb b/spec/ruby/core/kernel/Array_spec.rb index 063faf7097134d..ef54f57778731d 100644 --- a/spec/ruby/core/kernel/Array_spec.rb +++ b/spec/ruby/core/kernel/Array_spec.rb @@ -1,20 +1,18 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel" do - it "has private instance method Array()" do - Kernel.private_instance_methods(false).should.include?(:Array) - end -end - -describe :kernel_Array, shared: true do +describe "Kernel#Array" do before :each do @array = [1, 2, 3] end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Array) + end + it "does not call #to_ary on an Array" do @array.should_not_receive(:to_ary) - @object.send(@method, @array).should == @array + Array(@array).should == @array end it "calls #to_ary to convert the argument to an Array" do @@ -22,19 +20,19 @@ obj.should_receive(:to_ary).and_return(@array) obj.should_not_receive(:to_a) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "does not call #to_a on an Array" do @array.should_not_receive(:to_a) - @object.send(@method, @array).should == @array + Array(@array).should == @array end it "calls #to_a if the argument does not respond to #to_ary" do obj = mock("Array([1,2,3])") obj.should_receive(:to_a).and_return(@array) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "calls #to_a if #to_ary returns nil" do @@ -42,56 +40,54 @@ obj.should_receive(:to_ary).and_return(nil) obj.should_receive(:to_a).and_return(@array) - @object.send(@method, obj).should == @array + Array(obj).should == @array end it "returns an Array containing the argument if #to_a returns nil" do obj = mock("Array([1,2,3])") obj.should_receive(:to_a).and_return(nil) - @object.send(@method, obj).should == [obj] + Array(obj).should == [obj] end it "calls #to_ary first, even if it's private" do obj = KernelSpecs::PrivateToAry.new - @object.send(@method, obj).should == [1, 2] + Array(obj).should == [1, 2] end it "calls #to_a if #to_ary is not defined, even if it's private" do obj = KernelSpecs::PrivateToA.new - @object.send(@method, obj).should == [3, 4] + Array(obj).should == [3, 4] end it "returns an Array containing the argument if it responds to neither #to_ary nor #to_a" do obj = mock("Array(x)") - @object.send(@method, obj).should == [obj] + Array(obj).should == [obj] end it "returns an empty Array when passed nil" do - @object.send(@method, nil).should == [] + Array(nil).should == [] end it "raises a TypeError if #to_ary does not return an Array" do obj = mock("Array() string") obj.should_receive(:to_ary).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Array(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_a does not return an Array" do obj = mock("Array() string") obj.should_receive(:to_a).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Array(obj) }.should.raise(TypeError) end end describe "Kernel.Array" do - it_behaves_like :kernel_Array, :Array_method, KernelSpecs -end - -describe "Kernel#Array" do - it_behaves_like :kernel_Array, :Array_function, KernelSpecs + it "is a public method" do + Kernel.public_methods(false).should.include?(:Array) + end end diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb index 92ce183cc8bef0..a3887953de2a05 100644 --- a/spec/ruby/core/kernel/Complex_spec.rb +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -2,7 +2,11 @@ require_relative '../../shared/kernel/complex' require_relative 'fixtures/Complex' -describe "Kernel.Complex()" do +describe "Kernel#Complex" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Complex) + end + describe "when passed [Complex, Complex]" do it "returns a new Complex number based on the two given numbers" do Complex(Complex(3, 4), Complex(5, 6)).should == Complex(3 - 6, 4 + 5) @@ -39,24 +43,21 @@ describe "when passed [Integer/Float]" do it "returns a new Complex number with 0 as the imaginary component" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Complex(1).should.instance_of?(Complex) - Complex(1).imag.should == 0 - Complex(1).real.should == 1 - - Complex(-3).should.instance_of?(Complex) - Complex(-3).imag.should == 0 - Complex(-3).real.should == -3 - - Complex(-4.5).should.instance_of?(Complex) - Complex(-4.5).imag.should == 0 - Complex(-4.5).real.should == -4.5 - - Complex(bignum_value).should.instance_of?(Complex) - Complex(bignum_value).imag.should == 0 - Complex(bignum_value).real.should == bignum_value - end + Complex(1).should.instance_of?(Complex) + Complex(1).imag.should == 0 + Complex(1).real.should == 1 + + Complex(-3).should.instance_of?(Complex) + Complex(-3).imag.should == 0 + Complex(-3).real.should == -3 + + Complex(-4.5).should.instance_of?(Complex) + Complex(-4.5).imag.should == 0 + Complex(-4.5).real.should == -4.5 + + Complex(bignum_value).should.instance_of?(Complex) + Complex(bignum_value).imag.should == 0 + Complex(bignum_value).real.should == bignum_value end end @@ -274,3 +275,9 @@ Complex(1).frozen?.should == true end end + +describe "Kernel.Complex" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Complex) + end +end diff --git a/spec/ruby/core/kernel/Float_spec.rb b/spec/ruby/core/kernel/Float_spec.rb index f5566067ba8173..4c5d783095cfca 100644 --- a/spec/ruby/core/kernel/Float_spec.rb +++ b/spec/ruby/core/kernel/Float_spec.rb @@ -1,41 +1,45 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_float, shared: true do +describe "Kernel#Float" do + it "is a instance method" do + Kernel.private_instance_methods(false).should.include?(:Float) + end + it "returns the identical Float for numeric Floats" do float = 1.12 - float2 = @object.send(:Float, float) + float2 = Float(float) float2.should == float float2.should.equal? float end it "returns a Float for Fixnums" do - @object.send(:Float, 1).should == 1.0 + Float(1).should == 1.0 end it "returns a Float for Complex with only a real part" do - @object.send(:Float, Complex(1)).should == 1.0 + Float(Complex(1)).should == 1.0 end it "returns a Float for Bignums" do - @object.send(:Float, 1000000000000).should == 1000000000000.0 + Float(1000000000000).should == 1000000000000.0 end it "raises an ArgumentError for nil" do - -> { @object.send(:Float, nil) }.should.raise(TypeError) + -> { Float(nil) }.should.raise(TypeError) end it "returns the identical NaN for NaN" do nan = nan_value nan.nan?.should == true - nan2 = @object.send(:Float, nan) + nan2 = Float(nan) nan2.nan?.should == true nan2.should.equal?(nan) end it "returns the same Infinity for Infinity" do infinity = infinity_value - infinity2 = @object.send(:Float, infinity) + infinity2 = Float(infinity) infinity2.should == infinity_value infinity.should.equal?(infinity2) end @@ -43,38 +47,38 @@ it "converts Strings to floats without calling #to_f" do string = +"10" string.should_not_receive(:to_f) - @object.send(:Float, string).should == 10.0 + Float(string).should == 10.0 end it "converts Strings with decimal points into Floats" do - @object.send(:Float, "10.0").should == 10.0 + Float("10.0").should == 10.0 end it "raises an ArgumentError for a String of word characters" do - -> { @object.send(:Float, "float") }.should.raise(ArgumentError) + -> { Float("float") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with string in error message" do - -> { @object.send(:Float, "foo") }.should.raise(ArgumentError) { |e| + -> { Float("foo") }.should.raise(ArgumentError) { |e| e.message.should == 'invalid value for Float(): "foo"' } end it "raises an ArgumentError if there are two decimal points in the String" do - -> { @object.send(:Float, "10.0.0") }.should.raise(ArgumentError) + -> { Float("10.0.0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of numbers followed by word characters" do - -> { @object.send(:Float, "10D") }.should.raise(ArgumentError) + -> { Float("10D") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of word characters followed by numbers" do - -> { @object.send(:Float, "D10") }.should.raise(ArgumentError) + -> { Float("D10") }.should.raise(ArgumentError) end it "is strict about the string form even across newlines" do - -> { @object.send(:Float, "not a number\n10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "10\nnot a number") }.should.raise(ArgumentError) + -> { Float("not a number\n10") }.should.raise(ArgumentError) + -> { Float("10\nnot a number") }.should.raise(ArgumentError) end it "converts String subclasses to floats without calling #to_f" do @@ -82,268 +86,274 @@ def to_f() 1.2 end end - @object.send(:Float, my_string.new("10")).should == 10.0 + Float(my_string.new("10")).should == 10.0 end it "returns a positive Float if the string is prefixed with +" do - @object.send(:Float, "+10").should == 10.0 - @object.send(:Float, " +10").should == 10.0 + Float("+10").should == 10.0 + Float(" +10").should == 10.0 end it "returns a negative Float if the string is prefixed with +" do - @object.send(:Float, "-10").should == -10.0 - @object.send(:Float, " -10").should == -10.0 + Float("-10").should == -10.0 + Float(" -10").should == -10.0 end it "raises an ArgumentError if a + or - is embedded in a String" do - -> { @object.send(:Float, "1+1") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1-1") }.should.raise(ArgumentError) + -> { Float("1+1") }.should.raise(ArgumentError) + -> { Float("1-1") }.should.raise(ArgumentError) end it "raises an ArgumentError if a String has a trailing + or -" do - -> { @object.send(:Float, "11+") }.should.raise(ArgumentError) - -> { @object.send(:Float, "11-") }.should.raise(ArgumentError) + -> { Float("11+") }.should.raise(ArgumentError) + -> { Float("11-") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a leading _" do - -> { @object.send(:Float, "_1") }.should.raise(ArgumentError) + -> { Float("_1") }.should.raise(ArgumentError) end it "returns a value for a String with an embedded _" do - @object.send(:Float, "1_000").should == 1000.0 + Float("1_000").should == 1000.0 end it "raises an ArgumentError for a String with a trailing _" do - -> { @object.send(:Float, "10_") }.should.raise(ArgumentError) + -> { Float("10_") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String of \\0" do - -> { @object.send(:Float, "\0") }.should.raise(ArgumentError) + -> { Float("\0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a leading \\0" do - -> { @object.send(:Float, "\01") }.should.raise(ArgumentError) + -> { Float("\01") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with an embedded \\0" do - -> { @object.send(:Float, "1\01") }.should.raise(ArgumentError) + -> { Float("1\01") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String with a trailing \\0" do - -> { @object.send(:Float, "1\0") }.should.raise(ArgumentError) + -> { Float("1\0") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String that is just an empty space" do - -> { @object.send(:Float, " ") }.should.raise(ArgumentError) + -> { Float(" ") }.should.raise(ArgumentError) end it "raises an ArgumentError for a String that with an embedded space" do - -> { @object.send(:Float, "1 2") }.should.raise(ArgumentError) + -> { Float("1 2") }.should.raise(ArgumentError) end it "returns a value for a String with a leading space" do - @object.send(:Float, " 1").should == 1.0 + Float(" 1").should == 1.0 end it "returns a value for a String with a trailing space" do - @object.send(:Float, "1 ").should == 1.0 + Float("1 ").should == 1.0 end it "returns a value for a String with any leading whitespace" do - @object.send(:Float, "\t\n1").should == 1.0 + Float("\t\n1").should == 1.0 end it "returns a value for a String with any trailing whitespace" do - @object.send(:Float, "1\t\n").should == 1.0 + Float("1\t\n").should == 1.0 end ruby_version_is ""..."3.4" do it "raises ArgumentError if a fractional part is missing" do - -> { @object.send(:Float, "1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "+1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "-1.") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1.e+0") }.should.raise(ArgumentError) - -> { @object.send(:Float, "1.e-2") }.should.raise(ArgumentError) + -> { Float("1.") }.should.raise(ArgumentError) + -> { Float("+1.") }.should.raise(ArgumentError) + -> { Float("-1.") }.should.raise(ArgumentError) + -> { Float("1.e+0") }.should.raise(ArgumentError) + -> { Float("1.e-2") }.should.raise(ArgumentError) end end ruby_version_is "3.4" do it "allows String representation without a fractional part" do - @object.send(:Float, "1.").should == 1.0 - @object.send(:Float, "+1.").should == 1.0 - @object.send(:Float, "-1.").should == -1.0 - @object.send(:Float, "1.e+0").should == 1.0 - @object.send(:Float, "1.e-2").should be_close(0.01, TOLERANCE) + Float("1.").should == 1.0 + Float("+1.").should == 1.0 + Float("-1.").should == -1.0 + Float("1.e+0").should == 1.0 + Float("1.e-2").should be_close(0.01, TOLERANCE) end end %w(e E).each do |e| it "raises an ArgumentError if #{e} is the trailing character" do - -> { @object.send(:Float, "2#{e}") }.should.raise(ArgumentError) + -> { Float("2#{e}") }.should.raise(ArgumentError) end it "raises an ArgumentError if #{e} is the leading character" do - -> { @object.send(:Float, "#{e}2") }.should.raise(ArgumentError) + -> { Float("#{e}2") }.should.raise(ArgumentError) end it "returns Infinity for '2#{e}1000'" do - @object.send(:Float, "2#{e}1000").should == Float::INFINITY + Float("2#{e}1000").should == Float::INFINITY end it "returns 0 for '2#{e}-1000'" do - @object.send(:Float, "2#{e}-1000").should == 0 + Float("2#{e}-1000").should == 0 end it "allows embedded _ in a number on either side of the #{e}" do - @object.send(:Float, "2_0#{e}100").should == 20e100 - @object.send(:Float, "20#{e}1_00").should == 20e100 - @object.send(:Float, "2_0#{e}1_00").should == 20e100 + Float("2_0#{e}100").should == 20e100 + Float("20#{e}1_00").should == 20e100 + Float("2_0#{e}1_00").should == 20e100 end it "raises an exception if a space is embedded on either side of the '#{e}'" do - -> { @object.send(:Float, "2 0#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}1 00") }.should.raise(ArgumentError) + -> { Float("2 0#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}1 00") }.should.raise(ArgumentError) end it "raises an exception if there's a leading _ on either side of the '#{e}'" do - -> { @object.send(:Float, "_20#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}_100") }.should.raise(ArgumentError) + -> { Float("_20#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}_100") }.should.raise(ArgumentError) end it "raises an exception if there's a trailing _ on either side of the '#{e}'" do - -> { @object.send(:Float, "20_#{e}100") }.should.raise(ArgumentError) - -> { @object.send(:Float, "20#{e}100_") }.should.raise(ArgumentError) + -> { Float("20_#{e}100") }.should.raise(ArgumentError) + -> { Float("20#{e}100_") }.should.raise(ArgumentError) end it "allows decimal points on the left side of the '#{e}'" do - @object.send(:Float, "2.0#{e}2").should == 2e2 + Float("2.0#{e}2").should == 2e2 end it "raises an ArgumentError if there's a decimal point on the right side of the '#{e}'" do - -> { @object.send(:Float, "20#{e}2.0") }.should.raise(ArgumentError) + -> { Float("20#{e}2.0") }.should.raise(ArgumentError) + end + + it "raises an ArgumentError if there's a trailing _ after '#{e}'" do + -> { @object.send(:Float, "20#{e}_") }.should.raise(ArgumentError) + -> { @object.send(:Float, "20#{e}+_") }.should.raise(ArgumentError) + -> { @object.send(:Float, "20#{e}-_") }.should.raise(ArgumentError) end end context "for hexadecimal literals" do it "interprets the 0x prefix as hexadecimal" do - @object.send(:Float, "0x10").should == 16.0 - @object.send(:Float, "0x0F").should == 15.0 - @object.send(:Float, "0x0f").should == 15.0 + Float("0x10").should == 16.0 + Float("0x0F").should == 15.0 + Float("0x0f").should == 15.0 end it "interprets negative hex value" do - @object.send(:Float, "-0x10").should == -16.0 + Float("-0x10").should == -16.0 end it "accepts embedded _ if the number does not contain a-f" do - @object.send(:Float, "0x1_0").should == 16.0 + Float("0x1_0").should == 16.0 end ruby_version_is ""..."3.4.3" do it "does not accept embedded _ if the number contains a-f" do - -> { @object.send(:Float, "0x1_0a") }.should.raise(ArgumentError) - @object.send(:Float, "0x1_0a", exception: false).should == nil + -> { Float("0x1_0a") }.should.raise(ArgumentError) + Float("0x1_0a", exception: false).should == nil end end ruby_version_is "3.4.3" do it "accepts embedded _ if the number contains a-f" do - @object.send(:Float, "0x1_0a").should == 0x10a.to_f + Float("0x1_0a").should == 0x10a.to_f end end it "does not accept _ before, after or inside the 0x prefix" do - -> { @object.send(:Float, "_0x10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0_x10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x_10") }.should.raise(ArgumentError) - @object.send(:Float, "_0x10", exception: false).should == nil - @object.send(:Float, "0_x10", exception: false).should == nil - @object.send(:Float, "0x_10", exception: false).should == nil + -> { Float("_0x10") }.should.raise(ArgumentError) + -> { Float("0_x10") }.should.raise(ArgumentError) + -> { Float("0x_10") }.should.raise(ArgumentError) + Float("_0x10", exception: false).should == nil + Float("0_x10", exception: false).should == nil + Float("0x_10", exception: false).should == nil end it "parses negative hexadecimal string as negative float" do - @object.send(:Float, "-0x7b").should == -123.0 + Float("-0x7b").should == -123.0 end ruby_version_is "3.4" do it "accepts a fractional part" do - @object.send(:Float, "0x0.8").should == 0.5 + Float("0x0.8").should == 0.5 end end describe "with binary exponent" do %w(p P).each do |p| it "interprets the fractional part (on the left side of '#{p}') in hexadecimal" do - @object.send(:Float, "0x10#{p}0").should == 16.0 + Float("0x10#{p}0").should == 16.0 end it "interprets the exponent (on the right of '#{p}') in decimal" do - @object.send(:Float, "0x1#{p}10").should == 1024.0 + Float("0x1#{p}10").should == 1024.0 end it "raises an ArgumentError if #{p} is the trailing character" do - -> { @object.send(:Float, "0x1#{p}") }.should.raise(ArgumentError) + -> { Float("0x1#{p}") }.should.raise(ArgumentError) end it "raises an ArgumentError if #{p} is the leading character" do - -> { @object.send(:Float, "0x#{p}1") }.should.raise(ArgumentError) + -> { Float("0x#{p}1") }.should.raise(ArgumentError) end it "returns Infinity for '0x1#{p}10000'" do - @object.send(:Float, "0x1#{p}10000").should == Float::INFINITY + Float("0x1#{p}10000").should == Float::INFINITY end it "returns 0 for '0x1#{p}-10000'" do - @object.send(:Float, "0x1#{p}-10000").should == 0 + Float("0x1#{p}-10000").should == 0 end it "allows embedded _ in a number on either side of the #{p}" do - @object.send(:Float, "0x1_0#{p}10").should == 16384.0 - @object.send(:Float, "0x10#{p}1_0").should == 16384.0 - @object.send(:Float, "0x1_0#{p}1_0").should == 16384.0 + Float("0x1_0#{p}10").should == 16384.0 + Float("0x10#{p}1_0").should == 16384.0 + Float("0x1_0#{p}1_0").should == 16384.0 end it "raises an exception if a space is embedded on either side of the '#{p}'" do - -> { @object.send(:Float, "0x1 0#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}1 0") }.should.raise(ArgumentError) + -> { Float("0x1 0#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}1 0") }.should.raise(ArgumentError) end it "raises an exception if there's a leading _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x_10#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}_10") }.should.raise(ArgumentError) + -> { Float("0x_10#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}_10") }.should.raise(ArgumentError) end it "raises an exception if there's a trailing _ on either side of the '#{p}'" do - -> { @object.send(:Float, "0x10_#{p}10") }.should.raise(ArgumentError) - -> { @object.send(:Float, "0x10#{p}10_") }.should.raise(ArgumentError) + -> { Float("0x10_#{p}10") }.should.raise(ArgumentError) + -> { Float("0x10#{p}10_") }.should.raise(ArgumentError) end it "allows hexadecimal points on the left side of the '#{p}'" do - @object.send(:Float, "0x1.8#{p}0").should == 1.5 + Float("0x1.8#{p}0").should == 1.5 end it "raises an ArgumentError if there's a decimal point on the right side of the '#{p}'" do - -> { @object.send(:Float, "0x1#{p}1.0") }.should.raise(ArgumentError) + -> { Float("0x1#{p}1.0") }.should.raise(ArgumentError) end end end end it "returns a Float that can be a parameter to #Float again" do - float = @object.send(:Float, "10") - @object.send(:Float, float).should == 10.0 + float = Float("10") + Float(float).should == 10.0 end it "otherwise, converts the given argument to a Float by calling #to_f" do (obj = mock('1.2')).should_receive(:to_f).once.and_return(1.2) obj.should_not_receive(:to_i) - @object.send(:Float, obj).should == 1.2 + Float(obj).should == 1.2 end it "returns the identical NaN if to_f is called and it returns NaN" do nan = nan_value (nan_to_f = mock('NaN')).should_receive(:to_f).once.and_return(nan) - nan2 = @object.send(:Float, nan_to_f) + nan2 = Float(nan_to_f) nan2.nan?.should == true nan2.should.equal?(nan) end @@ -351,63 +361,55 @@ def to_f() 1.2 end it "returns the identical Infinity if #to_f is called and it returns Infinity" do infinity = infinity_value (infinity_to_f = mock('Infinity')).should_receive(:to_f).once.and_return(infinity) - infinity2 = @object.send(:Float, infinity_to_f) + infinity2 = Float(infinity_to_f) infinity2.should.equal?(infinity) end it "raises a TypeError if #to_f is not provided" do - -> { @object.send(:Float, mock('x')) }.should.raise(TypeError) + -> { Float(mock('x')) }.should.raise(TypeError) end it "raises a TypeError if #to_f returns a String" do (obj = mock('ha!')).should_receive(:to_f).once.and_return('ha!') - -> { @object.send(:Float, obj) }.should.raise(TypeError) + -> { Float(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_f returns an Integer" do (obj = mock('123')).should_receive(:to_f).once.and_return(123) - -> { @object.send(:Float, obj) }.should.raise(TypeError) + -> { Float(obj) }.should.raise(TypeError) end it "raises a RangeError when passed a Complex argument" do c = Complex(2, 3) - -> { @object.send(:Float, c) }.should.raise(RangeError) + -> { Float(c) }.should.raise(RangeError) end describe "when passed exception: false" do describe "and valid input" do it "returns a Float number" do - @object.send(:Float, 1, exception: false).should == 1.0 - @object.send(:Float, "1", exception: false).should == 1.0 - @object.send(:Float, "1.23", exception: false).should == 1.23 + Float(1, exception: false).should == 1.0 + Float("1", exception: false).should == 1.0 + Float("1.23", exception: false).should == 1.23 end end describe "and invalid input" do it "swallows an error" do - @object.send(:Float, "abc", exception: false).should == nil - @object.send(:Float, :sym, exception: false).should == nil + Float("abc", exception: false).should == nil + Float(:sym, exception: false).should == nil end end describe "and nil" do it "swallows it" do - @object.send(:Float, nil, exception: false).should == nil + Float(nil, exception: false).should == nil end end end end describe "Kernel.Float" do - it_behaves_like :kernel_float, :Float, Kernel -end - -describe "Kernel#Float" do - it_behaves_like :kernel_float, :Float, Object.new -end - -describe "Kernel#Float" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:Float) + it "is a public method" do + Kernel.public_methods(false).should.include?(:Float) end end diff --git a/spec/ruby/core/kernel/Hash_spec.rb b/spec/ruby/core/kernel/Hash_spec.rb index ee16f56a90e8d9..8d3319a98c93b1 100644 --- a/spec/ruby/core/kernel/Hash_spec.rb +++ b/spec/ruby/core/kernel/Hash_spec.rb @@ -11,53 +11,49 @@ end end -describe "Kernel" do - it "has private instance method Hash()" do - Kernel.private_instance_methods(false).should.include?(:Hash) - end -end - -describe :kernel_Hash, shared: true do +describe "Kernel#Hash" do before :each do @hash = { a: 1} end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Hash) + end + it "converts nil to a Hash" do - @object.send(@method, nil).should == {} + Hash(nil).should == {} end it "converts an empty array to a Hash" do - @object.send(@method, []).should == {} + Hash([]).should == {} end it "does not call #to_hash on an Hash" do @hash.should_not_receive(:to_hash) - @object.send(@method, @hash).should == @hash + Hash(@hash).should == @hash end it "calls #to_hash to convert the argument to an Hash" do obj = mock("Hash(a: 1)") obj.should_receive(:to_hash).and_return(@hash) - @object.send(@method, obj).should == @hash + Hash(obj).should == @hash end it "raises a TypeError if it doesn't respond to #to_hash" do - -> { @object.send(@method, mock("")) }.should.raise(TypeError) + -> { Hash(mock("")) }.should.raise(TypeError) end it "raises a TypeError if #to_hash does not return an Hash" do obj = mock("Hash() string") obj.should_receive(:to_hash).and_return("string") - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { Hash(obj) }.should.raise(TypeError) end end describe "Kernel.Hash" do - it_behaves_like :kernel_Hash, :Hash_method, KernelSpecs -end - -describe "Kernel#Hash" do - it_behaves_like :kernel_Hash, :Hash_function, KernelSpecs + it "is a public method" do + Kernel.public_methods(false).should.include?(:Hash) + end end diff --git a/spec/ruby/core/kernel/Integer_spec.rb b/spec/ruby/core/kernel/Integer_spec.rb index 978fd8ef08c427..1c216ec54fcdab 100644 --- a/spec/ruby/core/kernel/Integer_spec.rb +++ b/spec/ruby/core/kernel/Integer_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_integer, shared: true do +describe "Kernel#Integer" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Integer) + end + it "returns a Bignum for a Bignum" do Integer(2e100).should == 2e100 end @@ -174,9 +178,7 @@ end end end -end -describe :kernel_integer_string, shared: true do it "raises an ArgumentError if the String is a null byte" do -> { Integer("\0") }.should.raise(ArgumentError) end @@ -377,9 +379,7 @@ -> { Integer("0#{d}a") }.should.raise(ArgumentError) end end -end -describe :kernel_integer_string_base, shared: true do it "raises an ArgumentError if the String is a null byte" do -> { Integer("\0", 2) }.should.raise(ArgumentError) end @@ -638,208 +638,184 @@ end end end -end -describe :kernel_Integer, shared: true do it "raises an ArgumentError when the String contains digits out of range of radix 2" do str = "23456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 2) }.should.raise(ArgumentError) + -> { Integer(str, 2) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 3" do str = "3456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 3) }.should.raise(ArgumentError) + -> { Integer(str, 3) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 4" do str = "456789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 4) }.should.raise(ArgumentError) + -> { Integer(str, 4) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 5" do str = "56789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 5) }.should.raise(ArgumentError) + -> { Integer(str, 5) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 6" do str = "6789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 6) }.should.raise(ArgumentError) + -> { Integer(str, 6) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 7" do str = "789abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 7) }.should.raise(ArgumentError) + -> { Integer(str, 7) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 8" do str = "89abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 8) }.should.raise(ArgumentError) + -> { Integer(str, 8) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 9" do str = "9abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 9) }.should.raise(ArgumentError) + -> { Integer(str, 9) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 10" do str = "abcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 10) }.should.raise(ArgumentError) + -> { Integer(str, 10) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 11" do str = "bcdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 11) }.should.raise(ArgumentError) + -> { Integer(str, 11) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 12" do str = "cdefghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 12) }.should.raise(ArgumentError) + -> { Integer(str, 12) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 13" do str = "defghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 13) }.should.raise(ArgumentError) + -> { Integer(str, 13) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 14" do str = "efghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 14) }.should.raise(ArgumentError) + -> { Integer(str, 14) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 15" do str = "fghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 15) }.should.raise(ArgumentError) + -> { Integer(str, 15) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 16" do str = "ghijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 16) }.should.raise(ArgumentError) + -> { Integer(str, 16) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 17" do str = "hijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 17) }.should.raise(ArgumentError) + -> { Integer(str, 17) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 18" do str = "ijklmnopqrstuvwxyz" - -> { @object.send(@method, str, 18) }.should.raise(ArgumentError) + -> { Integer(str, 18) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 19" do str = "jklmnopqrstuvwxyz" - -> { @object.send(@method, str, 19) }.should.raise(ArgumentError) + -> { Integer(str, 19) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 20" do str = "klmnopqrstuvwxyz" - -> { @object.send(@method, str, 20) }.should.raise(ArgumentError) + -> { Integer(str, 20) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 21" do str = "lmnopqrstuvwxyz" - -> { @object.send(@method, str, 21) }.should.raise(ArgumentError) + -> { Integer(str, 21) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 22" do str = "mnopqrstuvwxyz" - -> { @object.send(@method, str, 22) }.should.raise(ArgumentError) + -> { Integer(str, 22) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 23" do str = "nopqrstuvwxyz" - -> { @object.send(@method, str, 23) }.should.raise(ArgumentError) + -> { Integer(str, 23) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 24" do str = "opqrstuvwxyz" - -> { @object.send(@method, str, 24) }.should.raise(ArgumentError) + -> { Integer(str, 24) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 25" do str = "pqrstuvwxyz" - -> { @object.send(@method, str, 25) }.should.raise(ArgumentError) + -> { Integer(str, 25) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 26" do str = "qrstuvwxyz" - -> { @object.send(@method, str, 26) }.should.raise(ArgumentError) + -> { Integer(str, 26) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 27" do str = "rstuvwxyz" - -> { @object.send(@method, str, 27) }.should.raise(ArgumentError) + -> { Integer(str, 27) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 28" do str = "stuvwxyz" - -> { @object.send(@method, str, 28) }.should.raise(ArgumentError) + -> { Integer(str, 28) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 29" do str = "tuvwxyz" - -> { @object.send(@method, str, 29) }.should.raise(ArgumentError) + -> { Integer(str, 29) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 30" do str = "uvwxyz" - -> { @object.send(@method, str, 30) }.should.raise(ArgumentError) + -> { Integer(str, 30) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 31" do str = "vwxyz" - -> { @object.send(@method, str, 31) }.should.raise(ArgumentError) + -> { Integer(str, 31) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 32" do str = "wxyz" - -> { @object.send(@method, str, 32) }.should.raise(ArgumentError) + -> { Integer(str, 32) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 33" do str = "xyz" - -> { @object.send(@method, str, 33) }.should.raise(ArgumentError) + -> { Integer(str, 33) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 34" do str = "yz" - -> { @object.send(@method, str, 34) }.should.raise(ArgumentError) + -> { Integer(str, 34) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 35" do str = "z" - -> { @object.send(@method, str, 35) }.should.raise(ArgumentError) + -> { Integer(str, 35) }.should.raise(ArgumentError) end it "raises an ArgumentError when the String contains digits out of range of radix 36" do - -> { @object.send(@method, "{", 36) }.should.raise(ArgumentError) + -> { Integer("{", 36) }.should.raise(ArgumentError) end end describe "Kernel.Integer" do - it_behaves_like :kernel_Integer, :Integer_method, KernelSpecs - - # TODO: fix these specs - it_behaves_like :kernel_integer, :Integer, Kernel - it_behaves_like :kernel_integer_string, :Integer - - it_behaves_like :kernel_integer_string_base, :Integer - it "is a public method" do - Kernel.Integer(10).should == 10 - end -end - -describe "Kernel#Integer" do - it_behaves_like :kernel_Integer, :Integer_function, KernelSpecs - - # TODO: fix these specs - it_behaves_like :kernel_integer, :Integer, Object.new - it_behaves_like :kernel_integer_string, :Integer - - it_behaves_like :kernel_integer_string_base, :Integer - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:Integer) + Kernel.public_methods(false).should.include?(:Integer) end end diff --git a/spec/ruby/core/kernel/Rational_spec.rb b/spec/ruby/core/kernel/Rational_spec.rb index b13ab4cf551f7c..7ffa314a535267 100644 --- a/spec/ruby/core/kernel/Rational_spec.rb +++ b/spec/ruby/core/kernel/Rational_spec.rb @@ -1,15 +1,16 @@ require_relative '../../spec_helper' require_relative '../rational/fixtures/rational' -describe "Kernel.Rational" do +describe "Kernel#Rational" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:Rational) + end + describe "passed Integer" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns a new Rational number with 1 as the denominator" do - Rational(1).should.eql?(Rational(1, 1)) - Rational(-3).should.eql?(Rational(-3, 1)) - Rational(bignum_value).should.eql?(Rational(bignum_value, 1)) - end + it "returns a new Rational number with 1 as the denominator" do + Rational(1).should.eql?(Rational(1, 1)) + Rational(-3).should.eql?(Rational(-3, 1)) + Rational(bignum_value).should.eql?(Rational(bignum_value, 1)) end end @@ -234,3 +235,9 @@ def obj.to_int; raise; end Rational(1).frozen?.should == true end end + +describe "Kernel.Rational" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Rational) + end +end diff --git a/spec/ruby/core/kernel/String_spec.rb b/spec/ruby/core/kernel/String_spec.rb index a9b0758ad70b3f..6c86745ee8e17f 100644 --- a/spec/ruby/core/kernel/String_spec.rb +++ b/spec/ruby/core/kernel/String_spec.rb @@ -1,29 +1,33 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_String, shared: true do +describe "Kernel#String" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:String) + end + it "converts nil to a String" do - @object.send(@method, nil).should == "" + String(nil).should == "" end it "converts a Float to a String" do - @object.send(@method, 1.12).should == "1.12" + String(1.12).should == "1.12" end it "converts a boolean to a String" do - @object.send(@method, false).should == "false" - @object.send(@method, true).should == "true" + String(false).should == "false" + String(true).should == "true" end it "converts a constant to a String" do - @object.send(@method, Object).should == "Object" + String(Object).should == "Object" end it "calls #to_s to convert an arbitrary object to a String" do obj = mock('test') obj.should_receive(:to_s).and_return("test") - @object.send(@method, obj).should == "test" + String(obj).should == "test" end it "raises a TypeError if #to_s does not exist" do @@ -32,7 +36,7 @@ class << obj undef_method :to_s end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end # #5158 @@ -44,7 +48,7 @@ def respond_to?(meth, include_private=false) end end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "raises a TypeError if #to_s is not defined, even though #respond_to?(:to_s) returns true" do @@ -57,7 +61,7 @@ def respond_to?(meth, include_private=false) end end - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "calls #to_s if #respond_to?(:to_s) returns true" do @@ -69,18 +73,18 @@ def method_missing(meth, *args) end end - @object.send(@method, obj).should == "test" + String(obj).should == "test" end it "raises a TypeError if #to_s does not return a String" do (obj = mock('123')).should_receive(:to_s).and_return(123) - -> { @object.send(@method, obj) }.should.raise(TypeError) + -> { String(obj) }.should.raise(TypeError) end it "returns the same object if it is already a String" do string = +"Hello" string.should_not_receive(:to_s) - string2 = @object.send(@method, string) + string2 = String(string) string.should.equal?(string2) end @@ -88,19 +92,13 @@ def method_missing(meth, *args) subklass = Class.new(String) string = subklass.new("Hello") string.should_not_receive(:to_s) - string2 = @object.send(@method, string) + string2 = String(string) string.should.equal?(string2) end end describe "Kernel.String" do - it_behaves_like :kernel_String, :String, Kernel -end - -describe "Kernel#String" do - it_behaves_like :kernel_String, :String, Object.new - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:String) + it "is a public method" do + Kernel.public_methods(false).should.include?(:String) end end diff --git a/spec/ruby/core/kernel/__callee___spec.rb b/spec/ruby/core/kernel/__callee___spec.rb index 3059ea8b5778b3..db104835f89cab 100644 --- a/spec/ruby/core/kernel/__callee___spec.rb +++ b/spec/ruby/core/kernel/__callee___spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/__callee__' -describe "Kernel.__callee__" do +describe "Kernel#__callee__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__callee__) + end + it "returns the current method, even when aliased" do KernelSpecs::CalleeTest.new.f.should == :f end @@ -46,3 +50,9 @@ def g; f end c.new.g.should == :f end end + +describe "Kernel.__callee__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__callee__) + end +end diff --git a/spec/ruby/core/kernel/__dir___spec.rb b/spec/ruby/core/kernel/__dir___spec.rb index 242adbf48b5a1c..87de578ca7193e 100644 --- a/spec/ruby/core/kernel/__dir___spec.rb +++ b/spec/ruby/core/kernel/__dir___spec.rb @@ -1,6 +1,10 @@ require_relative '../../spec_helper' describe "Kernel#__dir__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__dir__) + end + it "returns the real name of the directory containing the currently-executing file" do __dir__.should == File.realpath(File.dirname(__FILE__)) end @@ -25,3 +29,9 @@ end end end + +describe "Kernel.__dir__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__dir__) + end +end diff --git a/spec/ruby/core/kernel/__method___spec.rb b/spec/ruby/core/kernel/__method___spec.rb index 578d25640d2846..cc485a397c6ef9 100644 --- a/spec/ruby/core/kernel/__method___spec.rb +++ b/spec/ruby/core/kernel/__method___spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/__method__' -describe "Kernel.__method__" do +describe "Kernel#__method__" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:__method__) + end + it "returns the current method, even when aliased" do KernelSpecs::MethodTest.new.f.should == :f end @@ -38,3 +42,9 @@ __method__.should == nil end end + +describe "Kernel.__method__" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:__method__) + end +end diff --git a/spec/ruby/core/kernel/abort_spec.rb b/spec/ruby/core/kernel/abort_spec.rb index b75138182d43ab..2daa674a7eea28 100644 --- a/spec/ruby/core/kernel/abort_spec.rb +++ b/spec/ruby/core/kernel/abort_spec.rb @@ -11,5 +11,7 @@ end describe "Kernel.abort" do - it_behaves_like :process_abort, :abort, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:abort) + end end diff --git a/spec/ruby/core/kernel/at_exit_spec.rb b/spec/ruby/core/kernel/at_exit_spec.rb index fdb0eaf34c4c35..4f3bbe128f36de 100644 --- a/spec/ruby/core/kernel/at_exit_spec.rb +++ b/spec/ruby/core/kernel/at_exit_spec.rb @@ -2,7 +2,7 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/at_exit' -describe "Kernel.at_exit" do +describe "Kernel#at_exit" do it_behaves_like :kernel_at_exit, :at_exit it "is a private method" do @@ -14,6 +14,8 @@ end end -describe "Kernel#at_exit" do - it "needs to be reviewed for spec completeness" +describe "Kernel.at_exit" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:at_exit) + end end diff --git a/spec/ruby/core/kernel/autoload_relative_spec.rb b/spec/ruby/core/kernel/autoload_relative_spec.rb index 7d03da8a4073ae..88ea1c33db5682 100644 --- a/spec/ruby/core/kernel/autoload_relative_spec.rb +++ b/spec/ruby/core/kernel/autoload_relative_spec.rb @@ -1,8 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -# Specs for Kernel#autoload_relative - ruby_version_is "4.1" do describe "Kernel#autoload_relative" do before :each do @@ -111,4 +109,10 @@ end end end + + describe "Kernel.autoload_relative" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload_relative) + end + end end diff --git a/spec/ruby/core/kernel/autoload_spec.rb b/spec/ruby/core/kernel/autoload_spec.rb index 46783734c43a0a..c2ac7e556ac31e 100644 --- a/spec/ruby/core/kernel/autoload_spec.rb +++ b/spec/ruby/core/kernel/autoload_spec.rb @@ -131,6 +131,10 @@ def go $".replace @loaded_features end + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload) + end + it "registers a file to load the first time the toplevel constant is accessed" do Kernel.autoload :KSAutoloadAA, @non_existent Kernel.autoload?(:KSAutoloadAA).should == @non_existent @@ -167,6 +171,10 @@ def go end describe "Kernel.autoload?" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:autoload?) + end + it "returns the name of the file that will be autoloaded" do Kernel.autoload :KSAutoload, "autoload.rb" Kernel.autoload?(:KSAutoload).should == "autoload.rb" diff --git a/spec/ruby/core/kernel/binding_spec.rb b/spec/ruby/core/kernel/binding_spec.rb index 6f27b26f379ba7..56432338e3e6f1 100644 --- a/spec/ruby/core/kernel/binding_spec.rb +++ b/spec/ruby/core/kernel/binding_spec.rb @@ -1,12 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.binding" do - it "returns a binding for the caller" do - Kernel.binding.eval("self").should == self - end -end - describe "Kernel#binding" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:binding) @@ -49,3 +43,13 @@ ScratchPad.recorded.should == cls end end + +describe "Kernel.binding" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:binding) + end + + it "returns a binding for the caller" do + Kernel.binding.eval("self").should == self + end +end diff --git a/spec/ruby/core/kernel/block_given_spec.rb b/spec/ruby/core/kernel/block_given_spec.rb index 20bb90ae96d5cc..e4f17c6db91f16 100644 --- a/spec/ruby/core/kernel/block_given_spec.rb +++ b/spec/ruby/core/kernel/block_given_spec.rb @@ -1,43 +1,37 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_block_given, shared: true do - it "returns true if and only if a block is supplied" do - @object.accept_block {}.should == true - @object.accept_block_as_argument {}.should == true - @object.accept_block_inside_block {}.should == true - @object.accept_block_as_argument_inside_block {}.should == true +describe "Kernel#block_given?" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:block_given?) + end - @object.accept_block.should == false - @object.accept_block_as_argument.should == false - @object.accept_block_inside_block.should == false - @object.accept_block_as_argument_inside_block.should == false + it "returns true if and only if a block is supplied" do + KernelSpecs::BlockGiven.accept_block {}.should == true + KernelSpecs::BlockGiven.accept_block_as_argument {}.should == true + KernelSpecs::BlockGiven.accept_block_inside_block {}.should == true + KernelSpecs::BlockGiven.accept_block_as_argument_inside_block {}.should == true + + KernelSpecs::BlockGiven.accept_block.should == false + KernelSpecs::BlockGiven.accept_block_as_argument.should == false + KernelSpecs::BlockGiven.accept_block_inside_block.should == false + KernelSpecs::BlockGiven.accept_block_as_argument_inside_block.should == false end # Clarify: Based on http://www.ruby-forum.com/topic/137822 it appears # that Matz wanted this to be true in 1.9. it "returns false when a method defined by define_method is called with a block" do - @object.defined_block {}.should == false - @object.defined_block_inside_block {}.should == false + KernelSpecs::BlockGiven.defined_block {}.should == false + KernelSpecs::BlockGiven.defined_block_inside_block {}.should == false end -end - -describe "Kernel#block_given?" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::BlockGiven it "returns false outside of a method" do block_given?.should == false end - - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:block_given?) - end end describe "Kernel.block_given?" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::KernelBlockGiven -end - -describe "self.send(:block_given?)" do - it_behaves_like :kernel_block_given, :block_given?, KernelSpecs::SelfBlockGiven + it "is a public method" do + Kernel.public_methods(false).should.include?(:block_given?) + end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 04cf806ef4bdf8..99392ebfb6fd6d 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -22,6 +22,18 @@ locations.length.should == 1 end + it "raises for negative start" do + -> { caller_locations(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { caller_locations(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + caller_locations(0, nil).map(&:to_s).should == caller_locations(0).map(&:to_s) + end + it "can be called with a range" do locations1 = caller_locations(0) locations2 = caller_locations(2..4) @@ -70,6 +82,11 @@ caller_locations.map(&:to_s).should == caller_locations(1..-1).map(&:to_s) end + it "coerces the arguments to integers" do + caller_locations(1.1, 1.1).map(&:to_s).should == caller(1, 1).map(&:to_s) + caller_locations(1.1..1.1).map(&:to_s).should == caller(1..1).map(&:to_s) + end + guard -> { Kernel.instance_method(:tap).source_location } do ruby_version_is ""..."3.4" do it "includes core library methods defined in Ruby" do @@ -111,3 +128,9 @@ end end end + +describe "Kernel.caller_locations" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:caller_locations) + end +end diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index 225f6fc4582c4b..4491d7e8acd487 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -43,6 +43,18 @@ lines[1].should =~ /\A#{path}:2:in [`']block in
'\n\z/ end + it "raises for negative start" do + -> { caller(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { caller(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + caller(0, nil).should == caller(0) + end + it "can be called with a range" do locations1 = caller(0) locations2 = caller(2..4) @@ -83,6 +95,11 @@ caller.should == caller(1..-1) end + it "coerces the arguments to integers" do + caller(1.1, 1.1).should == caller(1, 1) + caller(1.1..1.1).should == caller(1..1) + end + guard -> { Kernel.instance_method(:tap).source_location } do ruby_version_is ""..."3.4" do it "includes core library methods defined in Ruby" do @@ -119,3 +136,9 @@ end end end + +describe "Kernel.caller" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:caller) + end +end diff --git a/spec/ruby/core/kernel/catch_spec.rb b/spec/ruby/core/kernel/catch_spec.rb index 9f023036782827..9f31429ce2fde8 100644 --- a/spec/ruby/core/kernel/catch_spec.rb +++ b/spec/ruby/core/kernel/catch_spec.rb @@ -1,11 +1,15 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.catch" do +describe "Kernel#catch" do before :each do ScratchPad.clear end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:catch) + end + it "executes its block and catches a thrown value matching its argument" do catch :thrown_key do ScratchPad.record :catch_block @@ -120,8 +124,8 @@ def self.catching_method end end -describe "Kernel#catch" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:catch) +describe "Kernel.catch" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:catch) end end diff --git a/spec/ruby/core/kernel/chomp_spec.rb b/spec/ruby/core/kernel/chomp_spec.rb index 434bf652f98c89..2de6690b11f10a 100644 --- a/spec/ruby/core/kernel/chomp_spec.rb +++ b/spec/ruby/core/kernel/chomp_spec.rb @@ -2,45 +2,34 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_chomp, shared: true do +describe "Kernel#chomp" do + it "is a private method only when -n is passed" do + Kernel.private_instance_methods(false).should_not.include?(:chomp) + KernelSpecs.private_instance_method_with_dash_n?(:chomp).should == true + end + it "removes the final newline of $_" do - KernelSpecs.chomp("abc\n", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\n").should == "abc" end it "removes the final carriage return of $_" do - KernelSpecs.chomp("abc\r", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\r").should == "abc" end it "removes the final carriage return, newline of $_" do - KernelSpecs.chomp("abc\r\n", @method).should == "abc" + KernelSpecs.chomp_with_dash_n("abc\r\n").should == "abc" end it "removes only the final newline of $_" do - KernelSpecs.chomp("abc\n\n", @method).should == "abc\n" + KernelSpecs.chomp_with_dash_n("abc\n\n").should == "abc\n" end it "removes the value of $/ from the end of $_" do - KernelSpecs.chomp("abcde", @method, "cde").should == "ab" - end -end - -describe :kernel_chomp_private, shared: true do - it "is a private method" do - KernelSpecs.has_private_method(@method).should == true + KernelSpecs.chomp_with_dash_n("abcde", "cde").should == "ab" end end -describe "Kernel.chomp" do - it_behaves_like :kernel_chomp, "Kernel.chomp" -end - describe "Kernel#chomp" do - it_behaves_like :kernel_chomp, "chomp" - - it_behaves_like :kernel_chomp_private, :chomp -end - -describe :kernel_chomp_encoded, shared: true do before :each do @external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 @@ -51,15 +40,14 @@ end it "removes the final carriage return, newline from a multi-byte $_" do - script = fixture __FILE__, "#{@method}.rb" + script = fixture __FILE__, "chomp.rb" KernelSpecs.run_with_dash_n(script).should == "あれ" end end describe "Kernel.chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp" -end - -describe "Kernel#chomp" do - it_behaves_like :kernel_chomp_encoded, "chomp_f" + it "is a public method only when -n is passed" do + Kernel.public_methods(false).should_not.include?(:chomp) + KernelSpecs.public_singleton_method_with_dash_n?(:chomp).should == true + end end diff --git a/spec/ruby/core/kernel/chop_spec.rb b/spec/ruby/core/kernel/chop_spec.rb index bbf3c3f7248c98..ae9e0c5572bfd1 100644 --- a/spec/ruby/core/kernel/chop_spec.rb +++ b/spec/ruby/core/kernel/chop_spec.rb @@ -2,33 +2,22 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_chop, shared: true do - it "removes the final character of $_" do - KernelSpecs.chop("abc", @method).should == "ab" +describe "Kernel#chop" do + it "is a private method only when -n is passed" do + Kernel.private_instance_methods(false).should_not.include?(:chop) + KernelSpecs.private_instance_method_with_dash_n?(:chop).should == true end - it "removes the final carriage return, newline of $_" do - KernelSpecs.chop("abc\r\n", @method).should == "abc" + it "removes the final character of $_" do + KernelSpecs.chop_with_dash_n("abc").should == "ab" end -end -describe :kernel_chop_private, shared: true do - it "is a private method" do - KernelSpecs.has_private_method(@method).should == true + it "removes the final carriage return, newline of $_" do + KernelSpecs.chop_with_dash_n("abc\r\n").should == "abc" end end -describe "Kernel.chop" do - it_behaves_like :kernel_chop, "Kernel.chop" -end - describe "Kernel#chop" do - it_behaves_like :kernel_chop_private, :chop - - it_behaves_like :kernel_chop, "chop" -end - -describe :kernel_chop_encoded, shared: true do before :each do @external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 @@ -39,15 +28,14 @@ end it "removes the final multi-byte character from $_" do - script = fixture __FILE__, "#{@method}.rb" + script = fixture __FILE__, "chop.rb" KernelSpecs.run_with_dash_n(script).should == "あ" end end describe "Kernel.chop" do - it_behaves_like :kernel_chop_encoded, "chop" -end - -describe "Kernel#chop" do - it_behaves_like :kernel_chop_encoded, "chop_f" + it "is a public method only when -n is passed" do + Kernel.public_methods(false).should_not.include?(:chop) + KernelSpecs.public_singleton_method_with_dash_n?(:chop).should == true + end end diff --git a/spec/ruby/core/kernel/eval_spec.rb b/spec/ruby/core/kernel/eval_spec.rb index f9ca47866e0fe1..fb021332acf49f 100644 --- a/spec/ruby/core/kernel/eval_spec.rb +++ b/spec/ruby/core/kernel/eval_spec.rb @@ -8,10 +8,6 @@ Kernel.private_instance_methods(false).should.include?(:eval) end - it "is a module function" do - Kernel.respond_to?(:eval).should == true - end - it "evaluates the code within" do eval("2 + 3").should == 5 end @@ -264,7 +260,6 @@ def foo(a, b:, &block) # See http://jira.codehaus.org/browse/JRUBY-5163 it "uses the receiver as self inside the eval" do eval("self").should.equal?(self) - Kernel.eval("self").should.equal?(Kernel) end it "does not pass the block to the method being eval'ed" do @@ -550,3 +545,13 @@ def foo end end end + +describe "Kernel.eval" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Array) + end + + it "uses the receiver as self inside the eval" do + Kernel.eval("self").should.equal?(Kernel) + end +end diff --git a/spec/ruby/core/kernel/exec_spec.rb b/spec/ruby/core/kernel/exec_spec.rb index fd8485791a3602..6b611fa806579b 100644 --- a/spec/ruby/core/kernel/exec_spec.rb +++ b/spec/ruby/core/kernel/exec_spec.rb @@ -12,7 +12,7 @@ end describe "Kernel.exec" do - it "runs the specified command, replacing current process" do - ruby_exe('Kernel.exec "echo hello"; puts "fail"').should == "hello\n" + it "is a public method" do + Kernel.public_methods(false).should.include?(:exec) end end diff --git a/spec/ruby/core/kernel/exit_spec.rb b/spec/ruby/core/kernel/exit_spec.rb index 864ff84449e104..8de9bff588b467 100644 --- a/spec/ruby/core/kernel/exit_spec.rb +++ b/spec/ruby/core/kernel/exit_spec.rb @@ -11,7 +11,9 @@ end describe "Kernel.exit" do - it_behaves_like :process_exit, :exit, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:exit) + end end describe "Kernel#exit!" do @@ -23,5 +25,7 @@ end describe "Kernel.exit!" do - it_behaves_like :process_exit!, :exit!, "Kernel" + it "is a public method" do + Kernel.public_methods(false).should.include?(:exit!) + end end diff --git a/spec/ruby/core/kernel/fixtures/chomp.rb b/spec/ruby/core/kernel/fixtures/chomp.rb index f08dbadce5f591..3c22cb21e75594 100644 --- a/spec/ruby/core/kernel/fixtures/chomp.rb +++ b/spec/ruby/core/kernel/fixtures/chomp.rb @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- $_ = "あれ\r\n" -print Kernel.chomp +print chomp diff --git a/spec/ruby/core/kernel/fixtures/chomp_f.rb b/spec/ruby/core/kernel/fixtures/chomp_f.rb deleted file mode 100644 index 3c22cb21e75594..00000000000000 --- a/spec/ruby/core/kernel/fixtures/chomp_f.rb +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- - -$_ = "あれ\r\n" -print chomp diff --git a/spec/ruby/core/kernel/fixtures/chop.rb b/spec/ruby/core/kernel/fixtures/chop.rb index dfd06267239ff7..4ec89eb9ec69a4 100644 --- a/spec/ruby/core/kernel/fixtures/chop.rb +++ b/spec/ruby/core/kernel/fixtures/chop.rb @@ -1,4 +1,4 @@ # -*- encoding: utf-8 -*- $_ = "あれ" -print Kernel.chop +print chop diff --git a/spec/ruby/core/kernel/fixtures/chop_f.rb b/spec/ruby/core/kernel/fixtures/chop_f.rb deleted file mode 100644 index 4ec89eb9ec69a4..00000000000000 --- a/spec/ruby/core/kernel/fixtures/chop_f.rb +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- - -$_ = "あれ" -print chop diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index ad82f3cef571e4..3559b0fbe538f4 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -1,54 +1,30 @@ module KernelSpecs - def self.Array_function(arg) - Array(arg) - end - - def self.Array_method(arg) - Kernel.Array(arg) - end - - def self.Hash_function(arg) - Hash(arg) - end - - def self.Hash_method(arg) - Kernel.Hash(arg) - end - - def self.Integer_function(arg) - Integer(arg) - end - - def self.Integer_method(arg) - Kernel.Integer(arg) - end - - def self.putc_function(arg) - putc arg - end - - def self.putc_method(arg) - Kernel.putc arg + def self.private_instance_method_with_dash_n?(name) + IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_instance_methods(false).include?(#{name.inspect})"], "r+") do |io| + io.puts + io.close_write + io.read + end == "true" end - def self.has_private_method(name) - IO.popen([*ruby_exe, "-n", "-e", "print Kernel.private_method_defined?(#{name.inspect})"], "r+") do |io| + def self.public_singleton_method_with_dash_n?(name) + IO.popen([*ruby_exe, "-n", "-e", "print Kernel.public_methods(false).include?(#{name.inspect})"], "r+") do |io| io.puts io.close_write io.read end == "true" end - def self.chop(str, method) - IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; #{method}; print $_"], "r+") do |io| + def self.chop_with_dash_n(str) + IO.popen([*ruby_exe, "-n", "-e", "$_ = #{str.inspect}; chop; print $_"], "r+") do |io| io.puts io.close_write io.read end end - def self.chomp(str, method, sep="\n") - code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; #{method}; print $_" + def self.chomp_with_dash_n(str, sep="\n") + code = "$_ = #{str.inspect}; $/ = #{sep.inspect}; chomp; print $_" IO.popen([*ruby_exe, "-W0", "-n", "-e", code], "r+") do |io| io.puts io.close_write @@ -263,74 +239,6 @@ class << self end end - module SelfBlockGiven - def self.accept_block - self.send(:block_given?) - end - - def self.accept_block_as_argument(&block) - self.send(:block_given?) - end - - def self.accept_block_inside_block - yield_self { - self.send(:block_given?) - } - end - - def self.accept_block_as_argument_inside_block(&block) - yield_self { - self.send(:block_given?) - } - end - - class << self - define_method(:defined_block) do - self.send(:block_given?) - end - - define_method(:defined_block_inside_block) do - yield_self { - self.send(:block_given?) - } - end - end - end - - module KernelBlockGiven - def self.accept_block - Kernel.block_given? - end - - def self.accept_block_as_argument(&block) - Kernel.block_given? - end - - def self.accept_block_inside_block - yield_self { - Kernel.block_given? - } - end - - def self.accept_block_as_argument_inside_block(&block) - yield_self { - Kernel.block_given? - } - end - - class << self - define_method(:defined_block) do - Kernel.block_given? - end - - define_method(:defined_block_inside_block) do - yield_self { - Kernel.block_given? - } - end - end - end - class EvalTest def self.eval_yield_with_binding eval("yield", binding) diff --git a/spec/ruby/core/kernel/fork_spec.rb b/spec/ruby/core/kernel/fork_spec.rb index 4a2c848202095a..f0a8f3729cc641 100644 --- a/spec/ruby/core/kernel/fork_spec.rb +++ b/spec/ruby/core/kernel/fork_spec.rb @@ -11,5 +11,7 @@ end describe "Kernel.fork" do - it_behaves_like :process_fork, :fork, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:fork) + end end diff --git a/spec/ruby/core/kernel/gets_spec.rb b/spec/ruby/core/kernel/gets_spec.rb index 1b2b36fbb96457..6a9764cfb5fb6c 100644 --- a/spec/ruby/core/kernel/gets_spec.rb +++ b/spec/ruby/core/kernel/gets_spec.rb @@ -13,5 +13,7 @@ end describe "Kernel.gets" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:gets) + end end diff --git a/spec/ruby/core/kernel/global_variables_spec.rb b/spec/ruby/core/kernel/global_variables_spec.rb index d41bdff49a42f7..3a7cff4b2af701 100644 --- a/spec/ruby/core/kernel/global_variables_spec.rb +++ b/spec/ruby/core/kernel/global_variables_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.global_variables" do +describe "Kernel#global_variables" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:global_variables) end @@ -21,6 +21,8 @@ end end -describe "Kernel#global_variables" do - it "needs to be reviewed for spec completeness" +describe "Kernel.global_variables" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:global_variables) + end end diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb index 6ab89c2bbb9e75..bfeaaf93f734af 100644 --- a/spec/ruby/core/kernel/lambda_spec.rb +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -4,7 +4,7 @@ # The functionality of lambdas is specified in core/proc -describe "Kernel.lambda" do +describe "Kernel#lambda" do it_behaves_like :kernel_lambda, :lambda it "is a private method" do @@ -108,3 +108,9 @@ def ret end end end + +describe "Kernel.lambda" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:lambda) + end +end diff --git a/spec/ruby/core/kernel/load_spec.rb b/spec/ruby/core/kernel/load_spec.rb index 890aab8c27b9fe..05433bfcf33650 100644 --- a/spec/ruby/core/kernel/load_spec.rb +++ b/spec/ruby/core/kernel/load_spec.rb @@ -24,17 +24,7 @@ end describe "Kernel.load" do - before :each do - CodeLoadingSpecs.spec_setup - end - - after :each do - CodeLoadingSpecs.spec_cleanup + it "is a public method" do + Kernel.public_methods(false).should.include?(:load) end - - it_behaves_like :kernel_require_basic, :load, Kernel -end - -describe "Kernel.load" do - it_behaves_like :kernel_load, :load, Kernel end diff --git a/spec/ruby/core/kernel/local_variables_spec.rb b/spec/ruby/core/kernel/local_variables_spec.rb index 40c343f7e46f41..8654081ea0e76a 100644 --- a/spec/ruby/core/kernel/local_variables_spec.rb +++ b/spec/ruby/core/kernel/local_variables_spec.rb @@ -43,3 +43,9 @@ def local_var_method local_var_method.should == [:a] end end + +describe "Kernel.local_variables" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:local_variables) + end +end diff --git a/spec/ruby/core/kernel/loop_spec.rb b/spec/ruby/core/kernel/loop_spec.rb index c2976e5cc5bc24..63589c0eab050b 100644 --- a/spec/ruby/core/kernel/loop_spec.rb +++ b/spec/ruby/core/kernel/loop_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.loop" do +describe "Kernel#loop" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:loop) end @@ -77,3 +77,9 @@ end end end + +describe "Kernel.loop" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:loop) + end +end diff --git a/spec/ruby/core/kernel/open_spec.rb b/spec/ruby/core/kernel/open_spec.rb index 14a3c43cad246b..4af4a52e502cbe 100644 --- a/spec/ruby/core/kernel/open_spec.rb +++ b/spec/ruby/core/kernel/open_spec.rb @@ -81,13 +81,26 @@ # https://bugs.ruby-lang.org/issues/19630 it "warns about deprecation given a path with a pipe" do - cmd = "|echo ok" -> { - open(cmd) { |f| f.read } + open("|echo ok") { |f| f.read } }.should complain(/Kernel#open with a leading '\|'/) end end + ruby_version_is "4.0" do + platform_is_not :windows do + it "raises Errno::ENOENT when path starts with a pipe" do + -> { open("|echo ok") { } }.should.raise(Errno::ENOENT) + end + end + + platform_is :windows do + it "raises Errno::EINVAL when path starts with a pipe" do + -> { open("|echo ok") { } }.should.raise(Errno::EINVAL) + end + end + end + it "raises an ArgumentError if not passed one argument" do -> { open }.should.raise(ArgumentError) end @@ -188,5 +201,7 @@ def obj.to_open(*args, **kw) end describe "Kernel.open" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:open) + end end diff --git a/spec/ruby/core/kernel/p_spec.rb b/spec/ruby/core/kernel/p_spec.rb index f89716899a847d..9abacbfdb32999 100644 --- a/spec/ruby/core/kernel/p_spec.rb +++ b/spec/ruby/core/kernel/p_spec.rb @@ -81,5 +81,7 @@ end describe "Kernel.p" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:p) + end end diff --git a/spec/ruby/core/kernel/print_spec.rb b/spec/ruby/core/kernel/print_spec.rb index 5473d22f71c8de..2bebbe75869998 100644 --- a/spec/ruby/core/kernel/print_spec.rb +++ b/spec/ruby/core/kernel/print_spec.rb @@ -20,5 +20,7 @@ end describe "Kernel.print" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:print) + end end diff --git a/spec/ruby/core/kernel/printf_spec.rb b/spec/ruby/core/kernel/printf_spec.rb index 50939b3794be0e..af916c3eb3463d 100644 --- a/spec/ruby/core/kernel/printf_spec.rb +++ b/spec/ruby/core/kernel/printf_spec.rb @@ -3,12 +3,6 @@ require_relative 'shared/sprintf' describe "Kernel#printf" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:printf) - end -end - -describe "Kernel.printf" do before :each do @stdout = $stdout @name = tmp("kernel_puts.txt") @@ -21,26 +15,30 @@ rm_r @name end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:printf) + end + it "writes to stdout when a string is the first argument" do $stdout.should_receive(:write).with("string") - Kernel.printf("%s", "string") + printf("%s", "string") end it "calls write on the first argument when it is not a string" do object = mock('io') object.should_receive(:write).with("string") - Kernel.printf(object, "%s", "string") + printf(object, "%s", "string") end it "calls #to_str to convert the format object to a String" do object = mock('format string') object.should_receive(:to_str).and_return("to_str: %i") $stdout.should_receive(:write).with("to_str: 42") - Kernel.printf($stdout, object, 42) + printf($stdout, object, 42) end end -describe "Kernel.printf" do +describe "Kernel#printf" do describe "formatting" do before :each do require "stringio" @@ -49,7 +47,7 @@ context "io is specified" do it_behaves_like :kernel_sprintf, -> format, *args { io = StringIO.new(+"") - Kernel.printf(io, format, *args) + printf(io, format, *args) io.string } end @@ -59,7 +57,7 @@ stdout = $stdout begin $stdout = io = StringIO.new(+"") - Kernel.printf(format, *args) + printf(format, *args) io.string ensure $stdout = stdout @@ -68,3 +66,9 @@ end end end + +describe "Kernel.printf" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:printf) + end +end diff --git a/spec/ruby/core/kernel/proc_spec.rb b/spec/ruby/core/kernel/proc_spec.rb index 1ba662177b2a17..eb7f98989ebd1f 100644 --- a/spec/ruby/core/kernel/proc_spec.rb +++ b/spec/ruby/core/kernel/proc_spec.rb @@ -4,7 +4,7 @@ # The functionality of Proc objects is specified in core/proc -describe "Kernel.proc" do +describe "Kernel#proc" do it "is a private method" do Kernel.private_instance_methods(false).should.include?(:proc) end @@ -46,3 +46,9 @@ def some_method }.should.raise(ArgumentError, 'tried to create Proc object without a block') end end + +describe "Kernel.proc" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:proc) + end +end diff --git a/spec/ruby/core/kernel/putc_spec.rb b/spec/ruby/core/kernel/putc_spec.rb index e6a20a9af184b7..622524cd31ad47 100644 --- a/spec/ruby/core/kernel/putc_spec.rb +++ b/spec/ruby/core/kernel/putc_spec.rb @@ -3,12 +3,6 @@ require_relative '../../shared/io/putc' describe "Kernel#putc" do - it "is a private instance method" do - Kernel.private_instance_methods(false).should.include?(:putc) - end -end - -describe "Kernel.putc" do before :each do @name = tmp("kernel_putc.txt") @io = new_io @name @@ -20,20 +14,15 @@ $stdout = @stdout end - it_behaves_like :io_putc, :putc_method, KernelSpecs -end - -describe "Kernel#putc" do - before :each do - @name = tmp("kernel_putc.txt") - @io = new_io @name - @io_object = @object - @stdout, $stdout = $stdout, @io + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:putc) end - after :each do - $stdout = @stdout - end + it_behaves_like :io_putc, :putc, Object.new +end - it_behaves_like :io_putc, :putc_function, KernelSpecs +describe "Kernel.putc" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:putc) + end end diff --git a/spec/ruby/core/kernel/puts_spec.rb b/spec/ruby/core/kernel/puts_spec.rb index eed18fcd50ecc0..7e02d356ee922e 100644 --- a/spec/ruby/core/kernel/puts_spec.rb +++ b/spec/ruby/core/kernel/puts_spec.rb @@ -25,5 +25,7 @@ end describe "Kernel.puts" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:puts) + end end diff --git a/spec/ruby/core/kernel/raise_spec.rb b/spec/ruby/core/kernel/raise_spec.rb index 6162677e170e4e..c1642d5b43163a 100644 --- a/spec/ruby/core/kernel/raise_spec.rb +++ b/spec/ruby/core/kernel/raise_spec.rb @@ -214,9 +214,6 @@ class << public_raiser describe "Kernel.raise" do it "is a public method" do - Kernel.singleton_class.should.public_method_defined?(:raise) + Kernel.public_methods(false).should.include?(:Array) end - - it_behaves_like :kernel_raise, :raise, Kernel - it_behaves_like :kernel_raise_with_cause, :raise, Kernel end diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb index 1bf5e225d9f9de..092883410f8515 100644 --- a/spec/ruby/core/kernel/rand_spec.rb +++ b/spec/ruby/core/kernel/rand_spec.rb @@ -193,5 +193,7 @@ end describe "Kernel.rand" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:rand) + end end diff --git a/spec/ruby/core/kernel/readline_spec.rb b/spec/ruby/core/kernel/readline_spec.rb index 86899d78d26639..b8ab3be02b9f4d 100644 --- a/spec/ruby/core/kernel/readline_spec.rb +++ b/spec/ruby/core/kernel/readline_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.readline" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:readline) + end end diff --git a/spec/ruby/core/kernel/readlines_spec.rb b/spec/ruby/core/kernel/readlines_spec.rb index c758014aabe884..51de0dff8ef3be 100644 --- a/spec/ruby/core/kernel/readlines_spec.rb +++ b/spec/ruby/core/kernel/readlines_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.readlines" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:readlines) + end end diff --git a/spec/ruby/core/kernel/require_relative_spec.rb b/spec/ruby/core/kernel/require_relative_spec.rb index b3ac1fc64c5eff..e5bb862e6e93c7 100644 --- a/spec/ruby/core/kernel/require_relative_spec.rb +++ b/spec/ruby/core/kernel/require_relative_spec.rb @@ -14,6 +14,10 @@ CodeLoadingSpecs.spec_cleanup end + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:require_relative) + end + platform_is_not :windows do describe "when file is a symlink" do before :each do @@ -435,3 +439,9 @@ end end end + +describe "Kernel.require_relative" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:require_relative) + end +end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index 28d94b0515121a..90b2e15a8b60fc 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -54,14 +54,7 @@ end describe "Kernel.require" do - before :each do - CodeLoadingSpecs.spec_setup + it "is a public method" do + Kernel.public_methods(false).should.include?(:require) end - - after :each do - CodeLoadingSpecs.spec_cleanup - end - - it_behaves_like :kernel_require_basic, :require, Kernel - it_behaves_like :kernel_require, :require, Kernel end diff --git a/spec/ruby/core/kernel/select_spec.rb b/spec/ruby/core/kernel/select_spec.rb index 58c963c1a4a974..d46e5fca68b19f 100644 --- a/spec/ruby/core/kernel/select_spec.rb +++ b/spec/ruby/core/kernel/select_spec.rb @@ -5,9 +5,7 @@ it "is a private method" do Kernel.private_instance_methods(false).should.include?(:select) end -end -describe "Kernel.select" do it 'does not block when timeout is 0' do IO.pipe do |read, write| select([read], [], [], 0).should == nil @@ -16,3 +14,9 @@ end end end + +describe "Kernel.select" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:select) + end +end diff --git a/spec/ruby/core/kernel/set_trace_func_spec.rb b/spec/ruby/core/kernel/set_trace_func_spec.rb index 5201812f2f5234..5da4d3f54a9b7a 100644 --- a/spec/ruby/core/kernel/set_trace_func_spec.rb +++ b/spec/ruby/core/kernel/set_trace_func_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.set_trace_func" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:set_trace_func) + end end diff --git a/spec/ruby/core/kernel/shared/sprintf.rb b/spec/ruby/core/kernel/shared/sprintf.rb index e57334c5abb4fd..955d8ca36ebdea 100644 --- a/spec/ruby/core/kernel/shared/sprintf.rb +++ b/spec/ruby/core/kernel/shared/sprintf.rb @@ -460,6 +460,51 @@ def obj.to_str }.should.raise(ArgumentError) end + ruby_version_is ""..."3.4" do + it "formats single % character before a newline as literal %" do + @method.call("%\n").should == "%\n" + @method.call("foo%\n").should == "foo%\n" + @method.call("%\n.3f").should == "%\n.3f" + end + + it "formats single % character before a NUL as literal %" do + @method.call("%\0").should == "%\0" + @method.call("foo%\0").should == "foo%\0" + @method.call("%\0.3f").should == "%\0.3f" + end + + it "raises an error if single % appears anywhere else" do + -> { @method.call(" % ") }.should.raise(ArgumentError) + -> { @method.call("foo%quux") }.should.raise(ArgumentError) + end + + it "raises an error if NULL or \\n appear anywhere else in the format string" do + begin + old_debug, $DEBUG = $DEBUG, false + + -> { @method.call("%.\n3f", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.3\nf", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.\03f", 1.2) }.should.raise(ArgumentError) + -> { @method.call("%.3\0f", 1.2) }.should.raise(ArgumentError) + ensure + $DEBUG = old_debug + end + end + end + + ruby_version_is "3.4" do + it "raises ArgumentError if not followed by a conversion specifier" do + -> { @method.call("%") }.should.raise(ArgumentError, /incomplete format specifier/) + -> { @method.call("%\n") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%\0") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call(" % ") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.\n3f") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.3\nf") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.\03f") }.should.raise(ArgumentError, /malformed format string/) + -> { @method.call("%.3\0f") }.should.raise(ArgumentError, /malformed format string/) + end + end + it "is escaped by %" do @method.call("%%").should == "%" @method.call("%%d").should == "%d" @@ -844,6 +889,18 @@ def obj.to_str @method.call("%*10d", 10, 112) }.should.raise(ArgumentError) end + + ruby_version_is ""..."3.4" do + it "replaces trailing absolute argument specifier without type with percent sign" do + @method.call("hello %1$", "foo").should == "hello %" + end + end + + ruby_version_is "3.4" do + it "raises ArgumentError if absolute argument specifier is followed by a conversion specifier" do + -> { @method.call("hello %1$", "foo") }.should.raise(ArgumentError, /malformed format string/) + end + end end end diff --git a/spec/ruby/core/kernel/sleep_spec.rb b/spec/ruby/core/kernel/sleep_spec.rb index 61d8cc23804ee0..1433d829e0b79c 100644 --- a/spec/ruby/core/kernel/sleep_spec.rb +++ b/spec/ruby/core/kernel/sleep_spec.rb @@ -78,7 +78,7 @@ def o.divmod(*); [0, 0.001]; end t.value.should == 5 end - context "Kernel.sleep with Fiber scheduler" do + context "Kernel#sleep with Fiber scheduler" do before :each do Fiber.set_scheduler(FiberSpecs::LoggingScheduler.new) end @@ -114,5 +114,7 @@ def o.divmod(*); [0, 0.001]; end end describe "Kernel.sleep" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:sleep) + end end diff --git a/spec/ruby/core/kernel/spawn_spec.rb b/spec/ruby/core/kernel/spawn_spec.rb index 3432fc31da7b45..a87f7735014f01 100644 --- a/spec/ruby/core/kernel/spawn_spec.rb +++ b/spec/ruby/core/kernel/spawn_spec.rb @@ -17,9 +17,7 @@ end describe "Kernel.spawn" do - it "executes the given command" do - -> { - Process.wait Kernel.spawn("echo spawn") - }.should output_to_fd("spawn\n") + it "is a public method" do + Kernel.public_methods(false).should.include?(:spawn) end end diff --git a/spec/ruby/core/kernel/sprintf_spec.rb b/spec/ruby/core/kernel/sprintf_spec.rb index 5a4a90ff7a2860..8faa30a814e96e 100644 --- a/spec/ruby/core/kernel/sprintf_spec.rb +++ b/spec/ruby/core/kernel/sprintf_spec.rb @@ -3,14 +3,6 @@ require_relative 'shared/sprintf' require_relative 'shared/sprintf_encoding' -describe :kernel_sprintf_to_str, shared: true do - it "calls #to_str to convert the format object to a String" do - obj = mock('format string') - obj.should_receive(:to_str).and_return("to_str: %i") - @method.call(obj, 42).should == "to_str: 42" - end -end - describe "Kernel#sprintf" do it_behaves_like :kernel_sprintf, -> format, *args { r = nil @@ -28,37 +20,15 @@ r } - it_behaves_like :kernel_sprintf_to_str, -> format, *args { - r = nil - -> { - r = sprintf(format, *args) - }.should_not complain(verbose: true) - r - } + it "calls #to_str to convert the format object to a String" do + obj = mock('format string') + obj.should_receive(:to_str).and_return("to_str: %i") + @method.call(obj, 42).should == "to_str: 42" + end end describe "Kernel.sprintf" do - it_behaves_like :kernel_sprintf, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } - - it_behaves_like :kernel_sprintf_encoding, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } - - it_behaves_like :kernel_sprintf_to_str, -> format, *args { - r = nil - -> { - r = Kernel.sprintf(format, *args) - }.should_not complain(verbose: true) - r - } + it "is a public method" do + Kernel.public_methods(false).should.include?(:sprintf) + end end diff --git a/spec/ruby/core/kernel/srand_spec.rb b/spec/ruby/core/kernel/srand_spec.rb index cbc3a7f4b8048b..a74e64a65ad770 100644 --- a/spec/ruby/core/kernel/srand_spec.rb +++ b/spec/ruby/core/kernel/srand_spec.rb @@ -69,5 +69,7 @@ end describe "Kernel.srand" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:srand) + end end diff --git a/spec/ruby/core/kernel/syscall_spec.rb b/spec/ruby/core/kernel/syscall_spec.rb index 63943cdad5d2b1..344e4b5b74482b 100644 --- a/spec/ruby/core/kernel/syscall_spec.rb +++ b/spec/ruby/core/kernel/syscall_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.syscall" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:syscall) + end end diff --git a/spec/ruby/core/kernel/system_spec.rb b/spec/ruby/core/kernel/system_spec.rb index b24956104ab2c4..0f55fc938fd4d0 100644 --- a/spec/ruby/core/kernel/system_spec.rb +++ b/spec/ruby/core/kernel/system_spec.rb @@ -1,16 +1,20 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe :kernel_system, shared: true do +describe "Kernel#system" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:system) + end + it "executes the specified command in a subprocess" do - -> { @object.system("echo a") }.should output_to_fd("a\n") + -> { system("echo a") }.should output_to_fd("a\n") $?.should.instance_of? Process::Status $?.should.success? end it "returns true when the command exits with a zero exit status" do - @object.system(ruby_cmd('exit 0')).should == true + system(ruby_cmd('exit 0')).should == true $?.should.instance_of? Process::Status $?.should.success? @@ -18,7 +22,7 @@ end it "returns false when the command exits with a non-zero exit status" do - @object.system(ruby_cmd('exit 1')).should == false + system(ruby_cmd('exit 1')).should == false $?.should.instance_of? Process::Status $?.should_not.success? @@ -26,15 +30,15 @@ end it "raises RuntimeError when `exception: true` is given and the command exits with a non-zero exit status" do - -> { @object.system(ruby_cmd('exit 1'), exception: true) }.should.raise(RuntimeError) + -> { system(ruby_cmd('exit 1'), exception: true) }.should.raise(RuntimeError) end it "raises Errno::ENOENT when `exception: true` is given and the specified command does not exist" do - -> { @object.system('feature_14386', exception: true) }.should.raise(Errno::ENOENT) + -> { system('feature_14386', exception: true) }.should.raise(Errno::ENOENT) end it "returns nil when command execution fails" do - @object.system("sad").should == nil + system("sad").should == nil $?.should.instance_of? Process::Status $?.pid.should.is_a?(Integer) @@ -42,7 +46,7 @@ end it "does not write to stderr when command execution fails" do - -> { @object.system("sad") }.should output_to_fd("", STDERR) + -> { system("sad") }.should output_to_fd("", STDERR) end platform_is_not :windows do @@ -55,12 +59,12 @@ end it "executes with `sh` if the command contains shell characters" do - -> { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { system("echo $0") }.should output_to_fd("sh\n") end it "ignores SHELL env var and always uses `sh`" do ENV['SHELL'] = "/bin/fakeshell" - -> { @object.system("echo $0") }.should output_to_fd("sh\n") + -> { system("echo $0") }.should output_to_fd("sh\n") end end @@ -77,7 +81,7 @@ end it "executes with `sh` if the command is executable but not binary and there is no shebang" do - -> { @object.system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") + -> { system(@shell_command) }.should output_to_fd(ENV['PATH'] + "\n") end end @@ -94,39 +98,33 @@ end it "expands shell variables when given a single string argument" do - -> { @object.system("echo #{@shell_var}") }.should output_to_fd("foo\n") + -> { system("echo #{@shell_var}") }.should output_to_fd("foo\n") end platform_is_not :windows do it "does not expand shell variables when given multiples arguments" do - -> { @object.system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") + -> { system("echo", @shell_var) }.should output_to_fd("#{@shell_var}\n") end end platform_is :windows do it "does expand shell variables when given multiples arguments" do # See https://bugs.ruby-lang.org/issues/12231 - -> { @object.system("echo", @shell_var) }.should output_to_fd("foo\n") + -> { system("echo", @shell_var) }.should output_to_fd("foo\n") end end platform_is :windows do it "runs commands starting with any number of @ using shell" do `#{ruby_cmd("p system 'does_not_exist'")} 2>NUL`.chomp.should == "nil" - @object.system('@does_not_exist 2>NUL').should == false - @object.system("@@@#{ruby_cmd('exit 0')}").should == true + system('@does_not_exist 2>NUL').should == false + system("@@@#{ruby_cmd('exit 0')}").should == true end end end -describe "Kernel#system" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:system) - end - - it_behaves_like :kernel_system, :system, KernelSpecs::Method.new -end - describe "Kernel.system" do - it_behaves_like :kernel_system, :system, Kernel + it "is a public method" do + Kernel.public_methods(false).should.include?(:system) + end end diff --git a/spec/ruby/core/kernel/test_spec.rb b/spec/ruby/core/kernel/test_spec.rb index 30717dfd98e352..d1e55920dd221c 100644 --- a/spec/ruby/core/kernel/test_spec.rb +++ b/spec/ruby/core/kernel/test_spec.rb @@ -12,15 +12,15 @@ end it "returns true when passed ?f if the argument is a regular file" do - Kernel.test(?f, @file).should == true + test(?f, @file).should == true end it "returns true when passed ?e if the argument is a file" do - Kernel.test(?e, @file).should == true + test(?e, @file).should == true end it "returns true when passed ?d if the argument is a directory" do - Kernel.test(?d, @dir).should == true + test(?d, @dir).should == true end platform_is_not :windows do @@ -28,7 +28,7 @@ link = tmp("file_symlink.lnk") File.symlink(@file, link) begin - Kernel.test(?l, link).should == true + test(?l, link).should == true ensure rm_r link end @@ -36,11 +36,11 @@ end it "returns true when passed ?r if the argument is readable by the effective uid" do - Kernel.test(?r, @file).should == true + test(?r, @file).should == true end it "returns true when passed ?R if the argument is readable by the real uid" do - Kernel.test(?R, @file).should == true + test(?R, @file).should == true end context "writable test" do @@ -54,11 +54,11 @@ end it "returns true when passed ?w if the argument is readable by the effective uid" do - Kernel.test(?w, @tmp_file).should == true + test(?w, @tmp_file).should == true end it "returns true when passed ?W if the argument is readable by the real uid" do - Kernel.test(?W, @tmp_file).should == true + test(?W, @tmp_file).should == true end end @@ -73,37 +73,39 @@ end it "returns the last access time for the provided file when passed ?A" do - Kernel.test(?A, @tmp_file).should == @tmp_file.atime + test(?A, @tmp_file).should == @tmp_file.atime end it "returns the time at which the file was created when passed ?C" do - Kernel.test(?C, @tmp_file).should == @tmp_file.ctime + test(?C, @tmp_file).should == @tmp_file.ctime end it "returns the time at which the file was modified when passed ?M" do - Kernel.test(?M, @tmp_file).should == @tmp_file.mtime + test(?M, @tmp_file).should == @tmp_file.mtime end end it "calls #to_path on second argument when passed ?f and a filename" do p = mock('path') p.should_receive(:to_path).and_return @file - Kernel.test(?f, p) + test(?f, p) end it "calls #to_path on second argument when passed ?e and a filename" do p = mock('path') p.should_receive(:to_path).and_return @file - Kernel.test(?e, p) + test(?e, p) end it "calls #to_path on second argument when passed ?d and a directory" do p = mock('path') p.should_receive(:to_path).and_return @dir - Kernel.test(?d, p) + test(?d, p) end end describe "Kernel.test" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:test) + end end diff --git a/spec/ruby/core/kernel/throw_spec.rb b/spec/ruby/core/kernel/throw_spec.rb index 1086126176fe93..5c14c6591e05b5 100644 --- a/spec/ruby/core/kernel/throw_spec.rb +++ b/spec/ruby/core/kernel/throw_spec.rb @@ -1,7 +1,11 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' -describe "Kernel.throw" do +describe "Kernel#throw" do + it "is a private method" do + Kernel.private_instance_methods(false).should.include?(:throw) + end + it "transfers control to the end of the active catch block waiting for symbol" do catch(:blah) do :value @@ -73,8 +77,8 @@ end end -describe "Kernel#throw" do - it "is a private method" do - Kernel.private_instance_methods(false).should.include?(:throw) +describe "Kernel.throw" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:throw) end end diff --git a/spec/ruby/core/kernel/trace_var_spec.rb b/spec/ruby/core/kernel/trace_var_spec.rb index 150c3da266106c..3396409b74062e 100644 --- a/spec/ruby/core/kernel/trace_var_spec.rb +++ b/spec/ruby/core/kernel/trace_var_spec.rb @@ -52,3 +52,9 @@ end.should.raise(ArgumentError) end end + +describe "Kernel.trace_var" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:trace_var) + end +end diff --git a/spec/ruby/core/kernel/trap_spec.rb b/spec/ruby/core/kernel/trap_spec.rb index 8f44f3af4b6b35..42277b6e20b183 100644 --- a/spec/ruby/core/kernel/trap_spec.rb +++ b/spec/ruby/core/kernel/trap_spec.rb @@ -7,3 +7,9 @@ # Behaviour is specified for Signal.trap end + +describe "Kernel.trap" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:trap) + end +end diff --git a/spec/ruby/core/kernel/untrace_var_spec.rb b/spec/ruby/core/kernel/untrace_var_spec.rb index 8b219801c84933..26aa166bfb33d5 100644 --- a/spec/ruby/core/kernel/untrace_var_spec.rb +++ b/spec/ruby/core/kernel/untrace_var_spec.rb @@ -8,5 +8,7 @@ end describe "Kernel.untrace_var" do - it "needs to be reviewed for spec completeness" + it "is a public method" do + Kernel.public_methods(false).should.include?(:untrace_var) + end end diff --git a/spec/ruby/core/kernel/warn_spec.rb b/spec/ruby/core/kernel/warn_spec.rb index 189129dd313778..5bb0fd3813c1d6 100644 --- a/spec/ruby/core/kernel/warn_spec.rb +++ b/spec/ruby/core/kernel/warn_spec.rb @@ -228,10 +228,10 @@ def warn(message) begin ScratchPad.clear - Kernel.warn("Chunky bacon!") + warn("Chunky bacon!") ScratchPad.recorded.should == "Chunky bacon!\n" - Kernel.warn("Deprecated bacon!", category: :deprecated) + warn("Deprecated bacon!", category: :deprecated) ScratchPad.recorded.should == "Deprecated bacon!\n" ensure class << Warning @@ -248,7 +248,7 @@ class << Warning verbose = $VERBOSE $VERBOSE = false begin - Kernel.warn("Chunky bacon!") + warn("Chunky bacon!") ensure $VERBOSE = verbose end @@ -259,7 +259,7 @@ class << Warning verbose = $VERBOSE $VERBOSE = false begin - Kernel.warn("Chunky bacon!", category: 'deprecated') + warn("Chunky bacon!", category: 'deprecated') ensure $VERBOSE = verbose end @@ -296,3 +296,9 @@ def warn(*args, **kwargs) $?.should.success? end end + +describe "Kernel.warn" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:warn) + end +end diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index ac8c6e35601da4..31cbe2fe217459 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -180,4 +180,20 @@ RUBY end end + + ruby_version_is ""..."4.0" do + it "regards a destructured parameter in proc as an optional one" do + proc { |(a)| }.parameters.should == [[:opt, nil]] + end + end + + ruby_version_is "4.0" do + it "regards a destructured parameter in proc as an optional one" do + proc { |(a)| }.parameters.should == [[:opt]] + end + end + + it "regards a destructured parameter in lambda as an required" do + -> ((a)) { }.parameters.should == [[:req]] # rubocop:disable Style/StabbyLambdaParentheses + end end diff --git a/spec/ruby/core/process/spawn_spec.rb b/spec/ruby/core/process/spawn_spec.rb index fb619dce4278d5..8f005f8dee1f78 100644 --- a/spec/ruby/core/process/spawn_spec.rb +++ b/spec/ruby/core/process/spawn_spec.rb @@ -413,6 +413,12 @@ Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: dir) end.should output_to_fd(@dir) end + + it "raises an Errno::ENOENT when a given path doesn't exist" do + -> do + Process.wait Process.spawn(ruby_cmd("print Dir.pwd"), chdir: "nonexistent") + end.should.raise(Errno::ENOENT, "No such file or directory - nonexistent") + end end # chdir diff --git a/spec/ruby/core/range/fixtures/classes.rb b/spec/ruby/core/range/fixtures/classes.rb index 3a1df010b2abfe..bdabfb0d1c8176 100644 --- a/spec/ruby/core/range/fixtures/classes.rb +++ b/spec/ruby/core/range/fixtures/classes.rb @@ -58,10 +58,81 @@ def inspect end def <=>(other) + return nil unless other.is_a?(WithoutSucc) @n <=> other.n end end + class WithSucc + attr_reader :value + + def initialize(value) + @value = value + end + + def <=>(other) + return nil unless other.is_a?(WithSucc) + @value <=> other.value + end + + def succ + WithSucc.new(@value + 1) + end + + def ==(other) + return false unless other.is_a?(WithSucc) + @value == other.value + end + end + + class Number < Numeric + attr_reader :value + + def initialize(value) + @value = value + end + + def <=>(other) + return nil unless other.is_a?(Number) + @value <=> other.value + end + + def +(other) + raise "supported Integer only" unless other.is_a?(Integer) + Number.new(@value + other) + end + + def ==(other) + return false unless other.is_a?(Number) + @value == other.value + end + + # to prevent type conversion + undef_method :coerce + end + + # supports only interface required by Range#cover? + #succ + class CoverElementWithSucc + attr_reader :n + + def initialize(n) + @n = n + end + + def <=>(other) + return nil unless other.is_a?(CoverElementWithSucc) + @n <=> other.n + end + + def succ + CoverElementWithSucc.new(@n + 1) + end + + def inspect + "CoverElementWithSucc(#{@n})" + end + end + class Xs < Custom # represent a string of 'x's def succ Xs.new(@length + 1) diff --git a/spec/ruby/core/range/include_spec.rb b/spec/ruby/core/range/include_spec.rb index e5cc0dc234f395..6e21eb76507778 100644 --- a/spec/ruby/core/range/include_spec.rb +++ b/spec/ruby/core/range/include_spec.rb @@ -16,6 +16,76 @@ ('a'..'c').include?('bc').should == false ('a'...'c').include?('bc').should == false end + + it "returns whether object is an element of self using #== to compare" do + range = 'a'..'ab' + range.include?('b').should == true + range.include?('aa').should == true + range.include?('ac').should == false + end + + it "ignores self.end if excluded end" do + range = 'a'...'aa' + range.include?('aa').should == false + end + + it "returns false if backward range" do + range = 'aa'..'a' + range.include?('b').should == false + end + + it "returns false if empty range" do + range = 'aa'...'aa' + range.include?('aa').should == false + end + + it "raises TypeError for beginningless ranges" do + -> { (..'aa').include?('a') }.should.raise(TypeError) + -> { (..'aa').include?(Object.new) }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + ('aa'..).include?(Object.new) + }.should.raise(TypeError) + -> { + ('aa'..).include?('a') + }.should.raise(TypeError) + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = 'a'..'aa' + range.include?(Object.new).should == false + end + + it "returns false if an argument is empty" do + range = 'a'..'aa' + range.include?('').should == false + end + + describe "argument conversion to String" do + it "converts the passed argument to a String using #to_str" do + range = 'a'..'aa' + object = Object.new + def object.to_str; 'b'; end + range.include?(object).should == true + end + + it "returns false if the passed argument does not respond to #to_str" do + range = 'a'..'aa' + range.include?(nil).should == false + range.include?([]).should == false + end + + it "raises a TypeError if the passed argument responds to #to_str but it returns non-String value" do + range = 'a'..'aa' + object = Object.new + def object.to_str; 1; end + -> { + range.include?(object) + }.should.raise(TypeError) + end + end end describe "with weird succ" do @@ -95,4 +165,228 @@ it "does not include U+9995 in the range U+0999..U+9999" do ("\u{999}".."\u{9999}").include?("\u{9995}").should == false end + + describe "with custom Comparable objects (with #succ)" do + it "returns whether object is an element of self using #== to compare" do + range = RangeSpecs::WithSucc.new(1)..RangeSpecs::WithSucc.new(4) + range.include?(RangeSpecs::WithSucc.new(2)).should == true + range.include?(RangeSpecs::WithSucc.new(5)).should == false + end + + it "ignores self.end if excluded end" do + range = RangeSpecs::WithSucc.new(1)...RangeSpecs::WithSucc.new(4) + range.include?(RangeSpecs::WithSucc.new(4)).should == false + end + + it "returns false if backward range" do + range = RangeSpecs::WithSucc.new(4)..RangeSpecs::WithSucc.new(1) + range.include?(RangeSpecs::WithSucc.new(2)).should == false + end + + it "returns false if empty range" do + range = RangeSpecs::WithSucc.new(1)...RangeSpecs::WithSucc.new(1) + range.include?(RangeSpecs::WithSucc.new(1)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = RangeSpecs::WithSucc.new(0)..RangeSpecs::WithSucc.new(6) + range.include?(Object.new).should == false + end + + it "raises TypeError for beginningless ranges" do + -> { + (..RangeSpecs::WithSucc.new(10)).include?(RangeSpecs::WithSucc.new(5)) + }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + (RangeSpecs::WithSucc.new(0)..).include?(RangeSpecs::WithSucc.new(5)) + }.should.raise(TypeError) + end + end + + describe "with custom Comparable objects (without #succ)" do + it "raises TypeError for beginningless ranges" do + -> { + (..RangeSpecs::WithoutSucc.new(10)).include?(RangeSpecs::WithoutSucc.new(5)) + }.should.raise(TypeError) + + -> { + (..RangeSpecs::WithoutSucc.new(10)).include?(Object.new) + }.should.raise(TypeError) + end + + it "raises TypeError for endless ranges" do + -> { + (RangeSpecs::WithoutSucc.new(0)..).include?(RangeSpecs::WithoutSucc.new(5)) + }.should.raise(TypeError) + + -> { + (RangeSpecs::WithoutSucc.new(0)..).include?(Object.new) + }.should.raise(TypeError) + end + + it "raises TypeError for (nil..nil)" do + -> { + (nil..nil).include?(Object.new) + }.should.raise(TypeError) + end + end + + describe "with Numeric subclass elements" do + it "returns true if object is between self.begin and self.end" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(5)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(-5)).should == false + end + + it "returns false if object is greater than self.end" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(10)).should == false + end + + it "ignores end if excluded end" do + range = RangeSpecs::Number.new(0)...RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(6)).should == false + end + + it "returns true if argument is a single element in the range" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(0) + range.include?(RangeSpecs::Number.new(0)).should == true + end + + it "returns false if range is empty" do + range = RangeSpecs::Number.new(0)...RangeSpecs::Number.new(0) + range.include?(RangeSpecs::Number.new(0)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + range.include?(Object.new).should == false + end + + describe "beginningless range" do + it "returns false if object is greater than self.end" do + range = ..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(10)).should == false + end + + it "returns true if object is smaller than self.end" do + range = ..RangeSpecs::Number.new(6) + range.include?(RangeSpecs::Number.new(0)).should == true + end + end + + describe "endless range" do + it "returns true if object is greater than self.begin" do + range = (RangeSpecs::Number.new(0)..) + range.include?(RangeSpecs::Number.new(10)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = (RangeSpecs::Number.new(0)..) + range.include?(RangeSpecs::Number.new(-10)).should == false + end + end + + it "returns false if object isn't comparable with self.begin and self.end (that's #<=> returns nil)" do + range = RangeSpecs::Number.new(0)..RangeSpecs::Number.new(6) + object = Object.new + def object.<=>(other); nil; end + range.include?(object).should == false + end + end + + describe "with Time elements" do + it "returns true if object is between self.begin and self.end" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 5)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 - 5)).should == false + end + + it "returns false if object is greater than self.end" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 10)).should == false + end + + it "ignores end if excluded end" do + range = Time.at(1_700_000_000)...Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 6)).should == false + end + + it "returns true if argument is a single element in the range" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000) + range.include?(Time.at(1_700_000_000)).should == true + end + + it "returns false if range is empty" do + range = Time.at(1_700_000_000)...Time.at(1_700_000_000) + range.include?(Time.at(1_700_000_000)).should == false + end + + it "returns false if an argument isn't comparable with range boundaries" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + range.include?(Object.new).should == false + end + + describe "beginningless range" do + it "returns false if object is greater than self.end" do + range = ..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000 + 10)).should == false + end + + it "returns true if object is smaller than self.end" do + range = ..Time.at(1_700_000_000 + 6) + range.include?(Time.at(1_700_000_000)).should == true + end + end + + describe "endless range" do + it "returns true if object is greater than self.begin" do + range = (Time.at(1_700_000_000)..) + range.include?(Time.at(1_700_000_000 + 10)).should == true + end + + it "returns false if object is smaller than self.begin" do + range = (Time.at(1_700_000_000 + 10)..) + range.include?(Time.at(1_700_000_000)).should == false + end + end + + it "returns false if object isn't comparable with self.begin and self.end (that's #<=> returns nil)" do + range = Time.at(1_700_000_000)..Time.at(1_700_000_000 + 6) + object = Object.new + def object.<=>(other); nil; end + range.include?(object).should == false + end + end + + describe 'with beginless and endless range' do + it "return true for any Numeric value" do + (nil..nil).include?(1).should == true + (nil..nil).include?(1.0).should == true + (nil..nil).include?(1r).should == true + (nil..nil).include?(Complex(1)).should == true + (nil..nil).include?(RangeSpecs::Number.new(1)).should == true + end + + it "return true for any Time value" do + (nil..nil).include?(Time.now).should == true + end + + it "raises TypeError for non-Numeric/Time values" do + -> { (nil..nil).include?("a") }.should.raise(TypeError) + -> { (nil..nil).include?(:a) }.should.raise(TypeError) + -> { (nil..nil).include?([]) }.should.raise(TypeError) + end + end end diff --git a/spec/ruby/core/range/max_spec.rb b/spec/ruby/core/range/max_spec.rb index 57714967ce0afe..93630c3d926a93 100644 --- a/spec/ruby/core/range/max_spec.rb +++ b/spec/ruby/core/range/max_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Range#max" do - it "returns the maximum value in the range when called with no arguments" do + it "returns the maximum value in the range" do (1..10).max.should == 10 (1...10).max.should == 9 (0...2**64).max.should == 18446744073709551615 @@ -9,7 +9,7 @@ ('a'...'f').max.should == 'e' end - it "returns the maximum value in the Float range when called with no arguments" do + it "returns the maximum value in the Float range" do (303.20..908.1111).max.should == 908.1111 end @@ -47,7 +47,7 @@ end it "raises RangeError when called on an endless range" do - -> { eval("(1..)").max }.should.raise(RangeError) + -> { (1..).max }.should.raise(RangeError) end it "returns the end point for beginless ranges" do @@ -76,6 +76,97 @@ end end +describe "Range#max given an integer argument" do + it "returns the n maximum values in the range" do + (1..10).max(2).should == [10, 9] + (1...10).max(2).should == [9, 8] + ('f'..'l').max(2).should == ['l', 'k'] + ('a'...'f').max(2).should == ['e', 'd'] + end + + ruby_version_is "4.0" do + it "returns the n maximum values in a very large Integer range" do + (0...2**64).max(2).should == [18446744073709551615, 18446744073709551615-1] + end + end + + it "raise a TypeError for non Integer ranges" do + -> { (303.20..908.1111).max(2) }.should.raise(TypeError) + -> { (303.20...908.1111).max(2) }.should.raise(TypeError) + + -> { (['a']..['f']).max(2) }.should.raise(TypeError) + -> { (['a']...['f']).max(2) }.should.raise(TypeError) + + -> { (Time.now..Time.now).max(2) }.should.raise(TypeError) + -> { (Time.now...Time.now).max(2) }.should.raise(TypeError) + end + + it "returns [] when the endpoint is less than the start point" do + (100..10).max(2).should == [] + ('z'..'l').max(2).should == [] + end + + it "returns [] when the endpoint equals the start point and the Integer range is exclusive" do + (5...5).max(2).should == [] + end + + it "returns the endpoint when the endpoint equals the start point and the Integer range is inclusive" do + (5..5).max(2).should == [5] + end + + it "returns all the elements in the range when given n larger that elements count" do + (1..3).max(10).should == [3, 2, 1] + (1...3).max(10).should == [2, 1] + end + + it "raises RangeError when called on an endless range" do + -> { (1..).max(2) }.should.raise(RangeError) + end + + ruby_version_is "4.0" do + it "returns the n maximum values for beginless Integer ranges" do + (..1).max(2).should == [1, 0] + end + + it "returns the n maximum values (except the end point) for exclusive beginless Integer ranges" do + (...1).max(2).should == [0, -1] + end + end + + ruby_version_is ""..."4.0" do + it "raises a RangeError for an beginless non-Integer range" do + -> { (..1.0).max(2) }.should.raise(RangeError) + -> { (..'f').max(2) }.should.raise(RangeError) + end + end + + ruby_version_is "4.0" do + it "raises a TypeError for an beginless non-Integer range" do + -> { (..1.0).max(2) }.should.raise(TypeError) + -> { (..'f').max(2) }.should.raise(TypeError) + end + end + + it "raises an ArgumentError when given negative value" do + -> { (0..2).max(-1) }.should.raise(ArgumentError) + end + + it "converts the passed argument to an Integer using #to_int'" do + obj = mock_int(2) + (1..10).max(obj).should == [10, 9] + end + + it "raises a TypeError if the passed argument does not respond to #to_int" do + -> { (0..2).max(Object.new) }.should.raise(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("1") + -> { (0..2).max(obj) }.should.raise(TypeError) + end +end + describe "Range#max given a block" do it "passes each pair of values in the range to the block" do acc = [] @@ -113,3 +204,9 @@ -> { (..1).max {|a, b| a} }.should.raise(RangeError) end end + +describe "Range#max given a block and an integer argument" do + it "returns n elements the block determines to be the maximum" do + (1..10).max(2) {|a,b| a <=> b }.should == [10, 9] + end +end diff --git a/spec/ruby/core/range/min_spec.rb b/spec/ruby/core/range/min_spec.rb index 9c83d3decaec71..b39bfa5e90fec7 100644 --- a/spec/ruby/core/range/min_spec.rb +++ b/spec/ruby/core/range/min_spec.rb @@ -1,12 +1,12 @@ require_relative '../../spec_helper' describe "Range#min" do - it "returns the minimum value in the range when called with no arguments" do + it "returns the minimum value in the range" do (1..10).min.should == 1 ('f'..'l').min.should == 'f' end - it "returns the minimum value in the Float range when called with no arguments" do + it "returns the minimum value in the Float range" do (303.20..908.1111).min.should == 303.20 end @@ -40,8 +40,8 @@ end it "returns the start point for endless ranges" do - eval("(1..)").min.should == 1 - eval("(1.0...)").min.should == 1.0 + (1..).min.should == 1 + (1.0...).min.should == 1.0 end it "raises RangeError when called on an beginless range" do @@ -49,6 +49,78 @@ end end +describe "Range#min given an integer argument" do + it "returns the n minimum values in the range" do + (1..10).min(2).should == [1, 2] + (1...10).min(2).should == [1, 2] + (0...2**64).min(2).should == [0, 1] + ('f'..'l').min(2).should == ['f', 'g'] + ('a'...'f').min(2).should == ['a', 'b'] + end + + it "raise a TypeError for non Integer ranges" do + -> { (303.20..908.1111).min(2) }.should.raise(TypeError) + -> { (303.20...908.1111).min(2) }.should.raise(TypeError) + + -> { (['a']..['f']).min(2) }.should.raise(TypeError) + -> { (['a']...['f']).min(2) }.should.raise(TypeError) + + -> { (Time.now..Time.now).min(2) }.should.raise(TypeError) + -> { (Time.now...Time.now).min(2) }.should.raise(TypeError) + end + + it "returns [] when the endpoint is less than the start point" do + (100..10).min(2).should == [] + ('z'..'l').min(2).should == [] + end + + it "returns [] when the endpoint equals the start point and the Integer range is exclusive" do + (5...5).min(2).should == [] + end + + it "returns the endpoint when the endpoint equals the start point and the Integer range is inclusive" do + (5..5).min(2).should == [5] + end + + it "returns all the elements in the range when given n larger that elements count" do + (1..3).min(10).should == [1, 2, 3] + (1...3).min(10).should == [1, 2] + end + + it "raises RangeError when called on a beginless range" do + -> { (..1).min(2) }.should.raise(RangeError) + -> { (...1).min(2) }.should.raise(RangeError) + end + + it "returns the n minimum values for endless Integer ranges" do + (1..).min(2).should == [1, 2] + (1...).min(2).should == [1, 2] + end + + it "raises a TypeError for an endless non-Integer range" do + -> { (1.0..).min(2) }.should.raise(TypeError) + end + + it "raises an ArgumentError when given negative value" do + -> { (0..2).min(-1) }.should.raise(ArgumentError) + end + + it "converts the passed argument to an Integer using #to_int" do + obj = mock_int(2) + (1..10).min(obj).should == [1, 2] + end + + it "raises a TypeError if the passed argument does not respond to #to_int" do + -> { (0..2).min(Object.new) }.should.raise(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("1") + -> { (0..2).min(obj) }.should.raise(TypeError) + end +end + describe "Range#min given a block" do it "passes each pair of values in the range to the block" do acc = [] @@ -83,6 +155,12 @@ end it "raises RangeError when called with custom comparison method on an endless range" do - -> { eval("(1..)").min {|a, b| a} }.should.raise(RangeError) + -> { (1..).min {|a, b| a} }.should.raise(RangeError) + end +end + +describe "Range#max given a block and an integer argument" do + it "returns n elements the block determines to be the minimum" do + (1..10).min(2) { |a,b| a <=> b }.should == [1, 2] end end diff --git a/spec/ruby/core/range/overlap_spec.rb b/spec/ruby/core/range/overlap_spec.rb index 201cd2b1ff7e0f..c86936597fa1fd 100644 --- a/spec/ruby/core/range/overlap_spec.rb +++ b/spec/ruby/core/range/overlap_spec.rb @@ -9,6 +9,9 @@ (1..2).overlap?(0..3).should == true ('a'..'c').overlap?('b'..'d').should == true + + (0..4).overlap?(4..10).should == true + (4..10).overlap?(0..10).should == true end it "returns false if other Range does not overlap self" do @@ -43,6 +46,20 @@ (0..).overlap?(2..).should == true (..0).overlap?(..2).should == true + + (nil..nil).overlap?(4..6).should == true + (4..6).overlap?(nil..nil).should == true + + ruby_version_is "4.0" do + (nil..nil).overlap?(..6).should == true + (..6).overlap?(nil..nil).should == true + end + + (nil..nil).overlap?(4..).should == true + (4..).overlap?(nil..nil).should == true + + (..10).overlap?(10..).should == true + (10..).overlap?(..10).should == true end it "returns false when beginningless and endless Ranges do not overlap" do @@ -51,6 +68,8 @@ (..-1).overlap?(0..2).should == false (3..).overlap?(0..2).should == false + + (..0).overlap?(10..).should == false end it "returns false when Ranges are not compatible" do @@ -83,5 +102,14 @@ ('a'...'c').overlap?('c'..'e').should == false ('c'..'e').overlap?('a'...'c').should == false + + (0...10).overlap?(6..10).should == true + (nil...6).overlap?(4..6).should == true + (nil...10).overlap?(nil..10).should == true + + (nil...4).overlap?(4..10).should == false + (nil...10).overlap?(10..).should == false + (10..).overlap?(nil...10).should == false + (6..).overlap?(0...6).should == false end end diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 189f3da4bf5185..1a5b197597b122 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -90,104 +90,390 @@ end end end -end -describe :range_cover_subrange, shared: true do - context "range argument" do - it "accepts range argument" do - (0..10).send(@method, (3..7)).should == true - (0..10).send(@method, (3..15)).should == false - (0..10).send(@method, (-2..7)).should == false + describe "with non-Range argument" do + it "returns false if the object is smaller than self.begin" do + (0..6).send(@method, -5).should == false + end - (1.1..7.9).send(@method, (2.5..6.5)).should == true - (1.1..7.9).send(@method, (2.5..8.5)).should == false - (1.1..7.9).send(@method, (0.5..6.5)).should == false + it "returns false if the object is greater than self.end" do + (0..6).send(@method, 10).should == false + end - ('c'..'i').send(@method, ('d'..'f')).should == true - ('c'..'i').send(@method, ('d'..'z')).should == false - ('c'..'i').send(@method, ('a'..'f')).should == false + it "returns false if the range is empty" do + (0...0).send(@method, 0).should == false + end - range_10_100 = RangeSpecs::TenfoldSucc.new(10)..RangeSpecs::TenfoldSucc.new(100) - range_20_90 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(90) - range_20_110 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(110) - range_0_90 = RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(90) + it "returns false if the argument is not comparable with the range elements (returns nil from <=>)" do + (0..6).send(@method, Object.new).should == false + end - range_10_100.send(@method, range_20_90).should == true - range_10_100.send(@method, range_20_110).should == false - range_10_100.send(@method, range_0_90).should == false + it "returns false if the argument is greater than self.end on a beginless range" do + (...6).send(@method, 10).should == false end - it "supports boundaries of different comparable types" do - (0..10).send(@method, (3.1..7.9)).should == true - (0..10).send(@method, (3.1..15.9)).should == false - (0..10).send(@method, (-2.1..7.9)).should == false + it "returns true if the argument is smaller than self.end on a beginless range" do + (...6).send(@method, 0).should == true end - it "returns false if types are not comparable" do - (0..10).send(@method, ('a'..'z')).should == false - (0..10).send(@method, (RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(100))).should == false + it "returns true if the argument is greater than self.begin on an endless range" do + (0..).send(@method, 10).should == true end - it "honors exclusion of right boundary (:exclude_end option)" do - # Integer - (0..10).send(@method, (0..10)).should == true - (0...10).send(@method, (0...10)).should == true + it "returns false if the argument is smaller than self.begin on an endless range" do + (0..).send(@method, -10).should == false + end - (0..10).send(@method, (0...10)).should == true - (0...10).send(@method, (0..10)).should == false + it "returns true on any value for (nil..nil)" do + (nil..nil).send(@method, Object.new).should == true + end - (0...11).send(@method, (0..10)).should == true - (0..10).send(@method, (0...11)).should == true + it "returns true on any value for (nil...nil)" do + (nil...nil).send(@method, Object.new).should == true + end + end +end - # Float - (0..10.1).send(@method, (0..10.1)).should == true - (0...10.1).send(@method, (0...10.1)).should == true +describe :range_cover_subrange, shared: true do + it "accepts range argument" do + (0..10).send(@method, (3..7)).should == true + (0..10).send(@method, (3..15)).should == false + (0..10).send(@method, (-2..7)).should == false + + (1.1..7.9).send(@method, (2.5..6.5)).should == true + (1.1..7.9).send(@method, (2.5..8.5)).should == false + (1.1..7.9).send(@method, (0.5..6.5)).should == false + + ('c'..'i').send(@method, ('d'..'f')).should == true + ('c'..'i').send(@method, ('d'..'z')).should == false + ('c'..'i').send(@method, ('a'..'f')).should == false + + range_10_100 = RangeSpecs::TenfoldSucc.new(10)..RangeSpecs::TenfoldSucc.new(100) + range_20_90 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(90) + range_20_110 = RangeSpecs::TenfoldSucc.new(20)..RangeSpecs::TenfoldSucc.new(110) + range_0_90 = RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(90) + + range_10_100.send(@method, range_20_90).should == true + range_10_100.send(@method, range_20_110).should == false + range_10_100.send(@method, range_0_90).should == false + end - (0..10.1).send(@method, (0...10.1)).should == true - (0...10.1).send(@method, (0..10.1)).should == false + it "supports boundaries of different comparable types" do + (0..10).send(@method, (3.1..7.9)).should == true + (0..10).send(@method, (3.1..15.9)).should == false + (0..10).send(@method, (-2.1..7.9)).should == false + end - (0...11.1).send(@method, (0..10.1)).should == true - (0..10.1).send(@method, (0...11.1)).should == false - end + it "returns false if types are not comparable" do + (0..10).send(@method, ('a'..'z')).should == false + (0..10).send(@method, (RangeSpecs::TenfoldSucc.new(0)..RangeSpecs::TenfoldSucc.new(100))).should == false end - it "allows self to be a beginless range" do - (...10).send(@method, (3..7)).should == true - (...10).send(@method, (3..15)).should == false + it "honors exclusion of right boundary (:exclude_end option)" do + # Integer + (0..10).send(@method, (0..10)).should == true + (0...10).send(@method, (0...10)).should == true - (..7.9).send(@method, (2.5..6.5)).should == true - (..7.9).send(@method, (2.5..8.5)).should == false + (0..10).send(@method, (0...10)).should == true + (0...10).send(@method, (0..10)).should == false - (..'i').send(@method, ('d'..'f')).should == true - (..'i').send(@method, ('d'..'z')).should == false - end + (0...11).send(@method, (0..10)).should == true + (0..10).send(@method, (0...11)).should == true - it "allows self to be a endless range" do - eval("(0...)").send(@method, (3..7)).should == true - eval("(5...)").send(@method, (3..15)).should == false + # Float + (0..10.1).send(@method, (0..10.1)).should == true + (0...10.1).send(@method, (0...10.1)).should == true - eval("(1.1..)").send(@method, (2.5..6.5)).should == true - eval("(3.3..)").send(@method, (2.5..8.5)).should == false + (0..10.1).send(@method, (0...10.1)).should == true + (0...10.1).send(@method, (0..10.1)).should == false - eval("('a'..)").send(@method, ('d'..'f')).should == true - eval("('p'..)").send(@method, ('d'..'z')).should == false + (0...11.1).send(@method, (0..10.1)).should == true + (0..10.1).send(@method, (0...11.1)).should == false end - it "accepts beginless range argument" do - (..10).send(@method, (...10)).should == true - (0..10).send(@method, (...10)).should == false + context "range argument with Integer boundaries" do + context "when other range is completely inside self" do + it "returns true if self.begin < other.begin and self.end > other.end" do + (0..10).send(@method, 4..6).should == true + end + + it "returns true if self.begin == other.begin and self.end > other.end" do + (0..10).send(@method, 0..6).should == true + end + + it "returns true if self.begin < other.begin and self.end == other.end" do + (0..10).send(@method, 4..10).should == true + end + + it "returns true if self.begin < other.begin and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + (0..10).send(@method, 4...11).should == true + end + + it "returns true if self.begin == other.begin and self.end == other.end" do + (0..10).send(@method, 0..10).should == true + end + + it "returns true if self.begin == other.begin and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (0...10).send(@method, 0...10).should == true + end + + it "returns true if self is beginless and self.end > other.end" do + (...10).send(@method, 4..6).should == true + end + + it "returns true if self is beginless and self.end == other.end" do + (..10).send(@method, 4..10).should == true + end + + it "returns true if self is beginless and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (...10).send(@method, 4...10).should == true + end + + it "returns true if self and other are beginless and self.end > other.end" do + (...10).send(@method, (...6)).should == true + end + + it "returns true if self and other are beginless and self.end == other.end and self.exclude_end? is true and other.exclude_end? is true" do + (...10).send(@method, (...10)).should == true + end + + it "returns true if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + (..10).send(@method, 4...11).should == true + end + + it "returns true if self is endless and self.begin < other.begin" do + (0..).send(@method, 4..6).should == true + end + + it "returns true if self is endless and self.begin == other.begin" do + (0..).send(@method, 0..6).should == true + end + + it "returns true if self and other are endless and self.begin < other.begin" do + (0..).send(@method, (4..)).should == true + end + + it "returns true if self and other are endless and self.begin == other.begin" do + (0..).send(@method, (0..)).should == true + end + + it "returns true if self and other are endless and self.begin < other.begin and self.exclude_end? is true and other.exclude_end? is true" do + (0...).send(@method, (4...)).should == true + end + + it "returns true if self and other are endless and self.begin == other.begin and self.exclude_end? is true and other.exclude_end? is true" do + (0...).send(@method, (0...)).should == true + end + + it "returns true if self is (nil..nil) and other is finite" do + (nil..nil).send(@method, 4..6).should == true + end + + it "returns true if self is (nil..nil) and other is beginless" do + (nil..nil).send(@method, (...6)).should == true + end + + it "returns true if self is (nil..nil) and other is endless" do + (nil..nil).send(@method, (4..)).should == true + end + + it "returns true if self is (nil...nil) and other is finite" do + (nil...nil).send(@method, 4..6).should == true + end + + it "returns true if self is (nil...nil) and other is beginless" do + (nil...nil).send(@method, (...6)).should == true + end + + it "returns true if self is (nil...nil) and other is endless and other.exclude_end? is true" do + (nil...nil).send(@method, (4...)).should == true + end + end + + context "when other range is partially interleaved" do + it "returns false if self.begin > other.begin, self.begin < other.end and self.end > other.end" do + (4..10).send(@method, 0..6).should == false + end + + it "returns false if self.begin > other.begin, self.begin < other.end and self.end == other.end" do + (4..10).send(@method, 0..10).should == false + end + + it "returns false if self.begin > other.begin, self.begin < other.end and self.end < other.end" do + (4..6).send(@method, 0..10).should == false + end + + it "returns false if self.begin < other.begin and self.end > other.begin, self.end < other.end" do + (0..6).send(@method, 4..10).should == false + end + + it "returns false if self.begin < other.begin and self.end == other.end and self.exclude_end? is true" do + (0...10).send(@method, 6..10).should == false + end + + it "returns false if self.begin < other.begin and self.end == other.begin" do + (0..4).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and self.end > other.begin but self.end < other.end" do + (...6).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and self.end == other.end but self.exclude_end? is true" do + (...6).send(@method, 4..6).should == false + end + + it "returns false if self and other are beginless but self.end < other.end" do + (...6).send(@method, (...10)).should == false + end + + it "returns false if self and other are beginless and self.end == other.end but self.exclude_end? is true" do + (...10).send(@method, (..10)).should == false + end + + it "returns false if self is endless and self.begin > other.begin" do + (4..).send(@method, 0..6).should == false + end + + it "returns false if self and other are endless and self.begin > other.begin" do + (4..).send(@method, (0..)).should == false + end + + it "returns false if self and other are endless and self.begin == other.begin but self.exclude_end? is true" do + (0...).send(@method, (0..)).should == false + end + + it "returns false if self and other are endless and self.begin < other.begin but self.exclude_end? is true" do + (0...).send(@method, (4..)).should == false + end + + it "returns false if self is (nil...nil) and other is endless" do + (nil...nil).send(@method, (4..)).should == false + end + + it "returns false if self is beginless and other is endless and self.end == other.begin" do + (..10).send(@method, (10..)).should == false + end + + it "returns false if other is beginless and self is endless and other.end == self.begin" do + (10..).send(@method, (..10)).should == false + end + + it "returns false if self is finite and other is beginless and they overlap" do + (0..10).send(@method, ...10).should == false + end + + it "returns false if self is finite and other is endless and they overlap" do + (0..10).send(@method, 0..).should == false + end + end + + context "when other range does not interleave" do + it "returns false if self.begin > other.end" do + (6..10).send(@method, 0..4).should == false + end + + it "returns false if self.end < other.begin" do + (0..4).send(@method, 6..10).should == false + end + + it "returns false if self is beginless but self.end < other.begin" do + (...4).send(@method, 6..10).should == false + end + + it "returns false if self is beginless and self.end == other.begin but self.exclude_end? is true" do + (...4).send(@method, 4..10).should == false + end + + it "returns false if self is beginless and other is endless but self.end < other.begin" do + (...0).send(@method, (10..)).should == false + end + + it "returns false if self is beginless and other is endless and self.end == other.begin but self.exclude_end? is true" do + (...10).send(@method, (10..)).should == false + end + + it "returns false if self is endless but self.begin > other.end" do + (10..).send(@method, 0..6).should == false + end + + it "returns false if self is endless but self.begin == other.end but other.exclude_end? is true" do + (6..).send(@method, (0...6)).should == false + end + + it "returns false if other is beginless but self.begin > other.end" do + (4..10).send(@method, (...0)).should == false + end + + it "returns false if other is beginless and self.begin == other.end but other.exclude_end? is true" do + (6..10).send(@method, (...6)).should == false + end + + it "returns false if other is beginless and self is endless and other.end == self.begin but other.exclude_end? is true" do + (10..).send(@method, (...10)).should == false + end + + it "returns false if other is endless but self.end < other.begin" do + (0..4).send(@method, (10..)).should == false + end + + it "returns false if other is endless and self.end == other.begin but self.exclude_end? is true" do + (0...10).send(@method, (10..)).should == false + end + end + + context "when comparing with backward ranges" do + it "returns false if other is backward and fits into self" do + (0..10).send(@method, 6..4).should == false + end + + it "returns false if self is backward and other fits into self" do + (10..0).send(@method, 4..6).should == false + end + end - (1.1..7.9).send(@method, (...10.5)).should == false + context "when comparing with empty ranges" do + it "returns false if other is empty and fits into self" do + (0..10).send(@method, 4...4).should == false + end - ('c'..'i').send(@method, (..'i')).should == false + it "returns false if self is empty and equals other" do + (0...0).send(@method, 0...0).should == false + end + end + + it "returns false if other boundaries are not comparable with self boundaries" do + (0..10).send(@method, ("a".."z")).should == false + end end - it "accepts endless range argument" do - eval("(0..)").send(@method, eval("(0...)")).should == true - (0..10).send(@method, eval("(0...)")).should == false + context "range argument with boundaries of generic type (that implements only #<=>)" do + context "when other range is completely inside self" do + context "when a range can be iterated (that's it responds to #succ)" do + it "returns true if self.begin < other.begin and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + a = RangeSpecs::CoverElementWithSucc.new(0)..RangeSpecs::CoverElementWithSucc.new(10) + b = RangeSpecs::CoverElementWithSucc.new(4)...RangeSpecs::CoverElementWithSucc.new(11) + (a).send(@method, b).should == true + end + + it "returns true if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value <= self.end" do + a = ..RangeSpecs::CoverElementWithSucc.new(10) + b = RangeSpecs::CoverElementWithSucc.new(4)...RangeSpecs::CoverElementWithSucc.new(11) + (a).send(@method, b).should == true + end + end + end - (1.1..7.9).send(@method, eval("(0.8...)")).should == false + context "when other range is partially interleaved" do + context "when a range cannot be iterated (that's it does not responds to #succ)" do + it "returns false if self.begin < other.begin, self.end < other.end but other.exclude_end? is true and the rightmost other value < self.end" do + (0..10.0).send(@method, 5...11.0).should == false + end - ('c'..'i').send(@method, eval("('a'..)")).should == false + it "returns false if self is beginless and self.end < other.end but other.exclude_end? is true and the rightmost other value < self.end" do + (..10.0).send(@method, ...11.0).should == false + end + end + end end end diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index 97721a7307304f..b68dcfeb6ff740 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -83,4 +83,14 @@ ('A'..'C').send(@method, 20.9).should == false ('A'...'C').send(@method, 'C').should == false end + + it "returns false if other is not an element of self for endless ranges" do + (1..).send(@method, 0.4).should == false + (0.5...).send(@method, 0.4).should == false + end + + it "returns false if other is not an element of self for beginless ranges" do + (..10).send(@method, 12.4).should == false + (...10.5).send(@method, 12.4).should == false + end end diff --git a/spec/ruby/core/rational/exponent_spec.rb b/spec/ruby/core/rational/exponent_spec.rb index 88b4a4379666ea..f8582f0aabdcb0 100644 --- a/spec/ruby/core/rational/exponent_spec.rb +++ b/spec/ruby/core/rational/exponent_spec.rb @@ -2,34 +2,31 @@ describe "Rational#**" do describe "when passed Rational" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns Rational(1) if the exponent is Rational(0)" do - (Rational(0) ** Rational(0)).should.eql?(Rational(1)) - (Rational(1) ** Rational(0)).should.eql?(Rational(1)) - (Rational(3, 4) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-1) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-3, 4) ** Rational(0)).should.eql?(Rational(1)) - (Rational(bignum_value) ** Rational(0)).should.eql?(Rational(1)) - (Rational(-bignum_value) ** Rational(0)).should.eql?(Rational(1)) - end + it "returns Rational(1) if the exponent is Rational(0)" do + (Rational(0) ** Rational(0)).should.eql?(Rational(1)) + (Rational(1) ** Rational(0)).should.eql?(Rational(1)) + (Rational(3, 4) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-1) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-3, 4) ** Rational(0)).should.eql?(Rational(1)) + (Rational(bignum_value) ** Rational(0)).should.eql?(Rational(1)) + (Rational(-bignum_value) ** Rational(0)).should.eql?(Rational(1)) + end - it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do - (Rational(3, 4) ** Rational(1, 1)).should.eql?(Rational(3, 4)) - (Rational(3, 4) ** Rational(2, 1)).should.eql?(Rational(9, 16)) - (Rational(3, 4) ** Rational(-1, 1)).should.eql?(Rational(4, 3)) - (Rational(3, 4) ** Rational(-2, 1)).should.eql?(Rational(16, 9)) - end + it "returns self raised to the argument as a Rational if the exponent's denominator is 1" do + (Rational(3, 4) ** Rational(1, 1)).should.eql?(Rational(3, 4)) + (Rational(3, 4) ** Rational(2, 1)).should.eql?(Rational(9, 16)) + (Rational(3, 4) ** Rational(-1, 1)).should.eql?(Rational(4, 3)) + (Rational(3, 4) ** Rational(-2, 1)).should.eql?(Rational(16, 9)) + end - it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do - (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) - (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) - (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) - end + it "returns self raised to the argument as a Float if the exponent's denominator is not 1" do + (Rational(3, 4) ** Rational(4, 3)).should be_close(0.681420222312052, TOLERANCE) + (Rational(3, 4) ** Rational(-4, 3)).should be_close(1.46752322173095, TOLERANCE) + (Rational(3, 4) ** Rational(4, -3)).should be_close(1.46752322173095, TOLERANCE) + end - it "returns a complex number when self is negative and the passed argument is not 0" do - (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) - end + it "returns a complex number when self is negative and the passed argument is not 0" do + (Rational(-3, 4) ** Rational(-4, 3)).should be_close(Complex(-0.7337616108654732, 1.2709123906625817), TOLERANCE) end end @@ -46,16 +43,13 @@ (Rational(3, -bignum_value) ** -4).should == Rational(115792089237316195423570985008687907853269984665640564039457584007913129639936, 81) end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns Rational(1, 1) when the passed argument is 0" do - (Rational(3, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(-3, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(3, -4) ** 0).should.eql?(Rational(1, 1)) + it "returns Rational(1, 1) when the passed argument is 0" do + (Rational(3, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(-3, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(3, -4) ** 0).should.eql?(Rational(1, 1)) - (Rational(bignum_value, 4) ** 0).should.eql?(Rational(1, 1)) - (Rational(3, -bignum_value) ** 0).should.eql?(Rational(1, 1)) - end + (Rational(bignum_value, 4) ** 0).should.eql?(Rational(1, 1)) + (Rational(3, -bignum_value) ** 0).should.eql?(Rational(1, 1)) end end diff --git a/spec/ruby/core/rational/inspect_spec.rb b/spec/ruby/core/rational/inspect_spec.rb index edc5cffee94887..40eeb7159d765f 100644 --- a/spec/ruby/core/rational/inspect_spec.rb +++ b/spec/ruby/core/rational/inspect_spec.rb @@ -6,9 +6,6 @@ Rational(-5, 8).inspect.should == "(-5/8)" Rational(-1, -2).inspect.should == "(1/2)" - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" - end + Rational(bignum_value, 1).inspect.should == "(#{bignum_value}/1)" end end diff --git a/spec/ruby/core/rational/integer_spec.rb b/spec/ruby/core/rational/integer_spec.rb index cd7fa97fcf4e1f..443940126a3a29 100644 --- a/spec/ruby/core/rational/integer_spec.rb +++ b/spec/ruby/core/rational/integer_spec.rb @@ -1,10 +1,7 @@ require_relative "../../spec_helper" describe "Rational#integer?" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns false for a rational with a numerator and no denominator" do - Rational(20).integer?.should == false - end + it "returns false for a rational with a numerator and no denominator" do + Rational(20).integer?.should == false end it "returns false for a rational with a numerator and a denominator" do diff --git a/spec/ruby/core/rational/round_spec.rb b/spec/ruby/core/rational/round_spec.rb index dd6f66f408a9ff..2b991aa2ec7a53 100644 --- a/spec/ruby/core/rational/round_spec.rb +++ b/spec/ruby/core/rational/round_spec.rb @@ -47,11 +47,8 @@ it "returns a Rational" do @rational.round(1).should.is_a?(Rational) @rational.round(2).should.is_a?(Rational) - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(0, 1).round(1).should.is_a?(Rational) - Rational(2, 1).round(1).should.is_a?(Rational) - end + Rational(0, 1).round(1).should.is_a?(Rational) + Rational(2, 1).round(1).should.is_a?(Rational) end it "moves the truncation point n decimal places right" do diff --git a/spec/ruby/core/rational/to_s_spec.rb b/spec/ruby/core/rational/to_s_spec.rb index 24e30778e5a6c5..0720a122189343 100644 --- a/spec/ruby/core/rational/to_s_spec.rb +++ b/spec/ruby/core/rational/to_s_spec.rb @@ -2,11 +2,8 @@ describe "Rational#to_s" do it "returns a string representation of self" do - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - Rational(1, 1).to_s.should == "1/1" - Rational(2, 1).to_s.should == "2/1" - end + Rational(1, 1).to_s.should == "1/1" + Rational(2, 1).to_s.should == "2/1" Rational(1, 2).to_s.should == "1/2" Rational(-1, 3).to_s.should == "-1/3" Rational(1, -3).to_s.should == "-1/3" diff --git a/spec/ruby/core/string/fixtures/classes.rb b/spec/ruby/core/string/fixtures/classes.rb index 26fcd51b5d5fce..edc8f3ee3e0096 100644 --- a/spec/ruby/core/string/fixtures/classes.rb +++ b/spec/ruby/core/string/fixtures/classes.rb @@ -4,7 +4,7 @@ class Object def unpack_format(count=nil, repeat=nil) format = "#{instance_variable_get(:@method)}#{count}" format *= repeat if repeat - format.dup # because it may then become tainted + format end end diff --git a/spec/ruby/core/string/modulo_spec.rb b/spec/ruby/core/string/modulo_spec.rb index f93ec4bcf8fbe3..45381fd92f5cb0 100644 --- a/spec/ruby/core/string/modulo_spec.rb +++ b/spec/ruby/core/string/modulo_spec.rb @@ -55,51 +55,6 @@ -> { ("foo%" % [])}.should.raise(ArgumentError) end - ruby_version_is ""..."3.4" do - it "formats single % character before a newline as literal %" do - ("%\n" % []).should == "%\n" - ("foo%\n" % []).should == "foo%\n" - ("%\n.3f" % 1.2).should == "%\n.3f" - end - - it "formats single % character before a NUL as literal %" do - ("%\0" % []).should == "%\0" - ("foo%\0" % []).should == "foo%\0" - ("%\0.3f" % 1.2).should == "%\0.3f" - end - - it "raises an error if single % appears anywhere else" do - -> { (" % " % []) }.should.raise(ArgumentError) - -> { ("foo%quux" % []) }.should.raise(ArgumentError) - end - - it "raises an error if NULL or \\n appear anywhere else in the format string" do - begin - old_debug, $DEBUG = $DEBUG, false - - -> { "%.\n3f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\nf" % 1.2 }.should.raise(ArgumentError) - -> { "%.\03f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\0f" % 1.2 }.should.raise(ArgumentError) - ensure - $DEBUG = old_debug - end - end - end - - ruby_version_is "3.4" do - it "raises an ArgumentError if % is not followed by a conversion specifier" do - -> { "%" % [] }.should.raise(ArgumentError) - -> { "%\n" % [] }.should.raise(ArgumentError) - -> { "%\0" % [] }.should.raise(ArgumentError) - -> { " % " % [] }.should.raise(ArgumentError) - -> { "%.\n3f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\nf" % 1.2 }.should.raise(ArgumentError) - -> { "%.\03f" % 1.2 }.should.raise(ArgumentError) - -> { "%.3\0f" % 1.2 }.should.raise(ArgumentError) - end - end - it "ignores unused arguments when $DEBUG is false" do begin old_debug = $DEBUG @@ -140,18 +95,6 @@ end end - ruby_version_is ""..."3.4" do - it "replaces trailing absolute argument specifier without type with percent sign" do - ("hello %1$" % "foo").should == "hello %" - end - end - - ruby_version_is "3.4" do - it "raises an ArgumentError if absolute argument specifier is followed by a conversion specifier" do - -> { "hello %1$" % "foo" }.should.raise(ArgumentError) - end - end - it "raises an ArgumentError when given invalid argument specifiers" do -> { "%1" % [] }.should.raise(ArgumentError) -> { "%+" % [] }.should.raise(ArgumentError) diff --git a/spec/ruby/core/string/to_f_spec.rb b/spec/ruby/core/string/to_f_spec.rb index bb09e4f2f31975..d3c6fa8e0c53e0 100644 --- a/spec/ruby/core/string/to_f_spec.rb +++ b/spec/ruby/core/string/to_f_spec.rb @@ -46,16 +46,39 @@ "_9".to_f.should == 0 end - it "stops if the underscore is not followed or preceded by a number" do + it "stops if the underscore is not followed by a number" do "1__2".to_f.should == 1.0 - "1_.2".to_f.should == 1.0 - "1._2".to_f.should == 1.0 - "1.2_e2".to_f.should == 1.2 - "1.2e_2".to_f.should == 1.2 "1_x2".to_f.should == 1.0 + + "1.2__3".to_f.should == 1.2 + "1.2_x3".to_f.should == 1.2 + + "1.2e__3".to_f.should == 1.2 + "1.2e_x3".to_f.should == 1.2 + end + + it "stops if the number contains invalid characters" do "1x_2".to_f.should == 1.0 + "1.2x_3".to_f.should == 1.2 + "1.2ex_3".to_f.should == 1.2 + end + + it "stops if +/- is not followed by a number" do "+_1".to_f.should == 0.0 "-_1".to_f.should == 0.0 + "+a1".to_f.should == 0.0 + + "1.1e+_1".to_f.should == 1.1 + "1.1e-_1".to_f.should == 1.1 + "1.1e-a1".to_f.should == 1.1 + end + + it "stops if the underscore appears at boundaries" do + "1_.2".to_f.should == 1.0 + "1._2".to_f.should == 1.0 + + "1.2_e3".to_f.should == 1.2 + "1.2e_3".to_f.should == 1.2 end it "does not allow prefixes to autodetect the base" do diff --git a/spec/ruby/core/string/unpack/a_spec.rb b/spec/ruby/core/string/unpack/a_spec.rb index a68e842e15200e..925962fddbabb2 100644 --- a/spec/ruby/core/string/unpack/a_spec.rb +++ b/spec/ruby/core/string/unpack/a_spec.rb @@ -3,14 +3,12 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "String#unpack with format 'A'" do it_behaves_like :string_unpack_basic, 'A' it_behaves_like :string_unpack_no_platform, 'A' it_behaves_like :string_unpack_string, 'A' it_behaves_like :string_unpack_Aa, 'A' - it_behaves_like :string_unpack_taint, 'A' it "removes trailing space and NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b", ""]], @@ -42,7 +40,6 @@ it_behaves_like :string_unpack_no_platform, 'a' it_behaves_like :string_unpack_string, 'a' it_behaves_like :string_unpack_Aa, 'a' - it_behaves_like :string_unpack_taint, 'a' it "does not remove trailing whitespace or NULL bytes from the decoded string" do [ ["a\x00 b \x00", ["a\x00 b \x00"]], diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index fac6ef5151ef9e..79ee6163e52e9d 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'B'" do it_behaves_like :string_unpack_basic, 'B' it_behaves_like :string_unpack_no_platform, 'B' - it_behaves_like :string_unpack_taint, 'B' it "decodes one bit from each byte for each format character starting with the most significant bit" do [ ["\x00", "B", ["0"]], @@ -105,7 +103,6 @@ describe "String#unpack with format 'b'" do it_behaves_like :string_unpack_basic, 'b' it_behaves_like :string_unpack_no_platform, 'b' - it_behaves_like :string_unpack_taint, 'b' it "decodes one bit from each byte for each format character starting with the least significant bit" do [ ["\x00", "b", ["0"]], diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index 0cf8d943a7f458..b8f7d9ca31a92a 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'H'" do it_behaves_like :string_unpack_basic, 'H' it_behaves_like :string_unpack_no_platform, 'H' - it_behaves_like :string_unpack_taint, 'H' it "decodes one nibble from each byte for each format character starting with the most significant bit" do [ ["\x8f", "H", ["8"]], @@ -74,7 +72,6 @@ describe "String#unpack with format 'h'" do it_behaves_like :string_unpack_basic, 'h' it_behaves_like :string_unpack_no_platform, 'h' - it_behaves_like :string_unpack_taint, 'h' it "decodes one nibble from each byte for each format character starting with the least significant bit" do [ ["\x8f", "h", ["f"]], diff --git a/spec/ruby/core/string/unpack/m_spec.rb b/spec/ruby/core/string/unpack/m_spec.rb index c1c1eea629c7c8..e4b26c764da349 100644 --- a/spec/ruby/core/string/unpack/m_spec.rb +++ b/spec/ruby/core/string/unpack/m_spec.rb @@ -2,12 +2,10 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'M'" do it_behaves_like :string_unpack_basic, 'M' it_behaves_like :string_unpack_no_platform, 'M' - it_behaves_like :string_unpack_taint, 'M' it "decodes an empty string" do "".unpack("M").should == [""] @@ -107,7 +105,6 @@ describe "String#unpack with format 'm'" do it_behaves_like :string_unpack_basic, 'm' it_behaves_like :string_unpack_no_platform, 'm' - it_behaves_like :string_unpack_taint, 'm' it "decodes an empty string" do "".unpack("m").should == [""] diff --git a/spec/ruby/core/string/unpack/p_spec.rb b/spec/ruby/core/string/unpack/p_spec.rb index 4103730269b6f5..98e10df6bb5c2c 100644 --- a/spec/ruby/core/string/unpack/p_spec.rb +++ b/spec/ruby/core/string/unpack/p_spec.rb @@ -1,11 +1,9 @@ require_relative '../../../spec_helper' require_relative '../fixtures/classes' require_relative 'shared/basic' -require_relative 'shared/taint' describe "String#unpack with format 'P'" do it_behaves_like :string_unpack_basic, 'P' - it_behaves_like :string_unpack_taint, 'P' it "round-trips a string through pack and unpack" do ["hello"].pack("P").unpack("P5").should == ["hello"] @@ -29,7 +27,6 @@ describe "String#unpack with format 'p'" do it_behaves_like :string_unpack_basic, 'p' - it_behaves_like :string_unpack_taint, 'p' it "round-trips a string through pack and unpack" do ["hello"].pack("p").unpack("p").should == ["hello"] diff --git a/spec/ruby/core/string/unpack/shared/taint.rb b/spec/ruby/core/string/unpack/shared/taint.rb deleted file mode 100644 index 79c7251f01af27..00000000000000 --- a/spec/ruby/core/string/unpack/shared/taint.rb +++ /dev/null @@ -1,2 +0,0 @@ -describe :string_unpack_taint, shared: true do -end diff --git a/spec/ruby/core/string/unpack/u_spec.rb b/spec/ruby/core/string/unpack/u_spec.rb index 720c1b85832687..d620b70b714ef5 100644 --- a/spec/ruby/core/string/unpack/u_spec.rb +++ b/spec/ruby/core/string/unpack/u_spec.rb @@ -3,13 +3,11 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/unicode' -require_relative 'shared/taint' describe "String#unpack with format 'U'" do it_behaves_like :string_unpack_basic, 'U' it_behaves_like :string_unpack_no_platform, 'U' it_behaves_like :string_unpack_unicode, 'U' - it_behaves_like :string_unpack_taint, 'U' it "raises ArgumentError on a malformed byte sequence" do -> { "\xE3".unpack('U') }.should.raise(ArgumentError) @@ -23,7 +21,6 @@ describe "String#unpack with format 'u'" do it_behaves_like :string_unpack_basic, 'u' it_behaves_like :string_unpack_no_platform, 'u' - it_behaves_like :string_unpack_taint, 'u' it "decodes an empty string as an empty string" do "".unpack("u").should == [""] diff --git a/spec/ruby/core/string/unpack/z_spec.rb b/spec/ruby/core/string/unpack/z_spec.rb index 10303905508704..7d24687b2d3816 100644 --- a/spec/ruby/core/string/unpack/z_spec.rb +++ b/spec/ruby/core/string/unpack/z_spec.rb @@ -3,13 +3,11 @@ require_relative '../fixtures/classes' require_relative 'shared/basic' require_relative 'shared/string' -require_relative 'shared/taint' describe "String#unpack with format 'Z'" do it_behaves_like :string_unpack_basic, 'Z' it_behaves_like :string_unpack_no_platform, 'Z' it_behaves_like :string_unpack_string, 'Z' - it_behaves_like :string_unpack_taint, 'Z' it "stops decoding at NULL bytes when passed the '*' modifier" do "a\x00\x00 b \x00c".unpack('Z*Z*Z*Z*').should == ["a", "", " b ", "c"] diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index 28a488f3116458..e7bec2b461437a 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -32,6 +32,18 @@ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s) end + it "raises for negative start" do + -> { Thread.current.backtrace_locations(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { Thread.current.backtrace_locations(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + it "can be called with `nil` length" do + Thread.current.backtrace_locations(0, nil).map(&:to_s).should == Thread.current.backtrace_locations(0).map(&:to_s) + end + it "can be called with a range" do locations1 = Thread.current.backtrace_locations locations2 = Thread.current.backtrace_locations(2..4) @@ -76,4 +88,9 @@ it "[1..-1] is the same as #caller_locations(0..-1) for Thread.current" do Thread.current.backtrace_locations(1..-1).map(&:to_s).should == caller_locations(0..-1).map(&:to_s) end + + it "coerces the arguments to integers" do + Thread.current.backtrace_locations(1.1, 1.1).map(&:to_s).should == Thread.current.backtrace_locations(1, 1).map(&:to_s) + Thread.current.backtrace_locations(1.1..1.1).map(&:to_s).should == Thread.current.backtrace_locations(1..1).map(&:to_s) + end end diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb index 770c300f06a9a6..4a04a371b17709 100644 --- a/spec/ruby/core/thread/backtrace_spec.rb +++ b/spec/ruby/core/thread/backtrace_spec.rb @@ -46,6 +46,19 @@ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s) end + it "raises for negative start" do + -> { Thread.current.backtrace(-1) }.should.raise(ArgumentError, "negative level (-1)") + end + + it "raises for negative length" do + -> { Thread.current.backtrace(0, -1) }.should.raise(ArgumentError, "negative size (-1)") + end + + + it "can be called with `nil` length" do + Thread.current.backtrace(0, nil).should == Thread.current.backtrace(0) + end + it "can be called with a range" do locations1 = Thread.current.backtrace locations2 = Thread.current.backtrace(2..4) @@ -66,4 +79,9 @@ omit = Thread.current.backtrace.length Thread.current.backtrace(omit).should == [] end + + it "coerces the arguments to integers" do + Thread.current.backtrace(1.1, 1.1).should == Thread.current.backtrace(1, 1) + Thread.current.backtrace(1.1..1.1).should == Thread.current.backtrace(1..1) + end end diff --git a/spec/ruby/core/thread/to_s_spec.rb b/spec/ruby/core/thread/to_s_spec.rb index 2aef426de86d2c..2830fb8f4114b4 100644 --- a/spec/ruby/core/thread/to_s_spec.rb +++ b/spec/ruby/core/thread/to_s_spec.rb @@ -8,8 +8,21 @@ thread.to_s.should =~ /^#$/ end + it "returns a description including a class name" do + thread = Thread.new { "hello" }.join + thread.to_s.should.include?("Thread") + end + + it "returns a description including a thread name if given any" do + thread = Thread.new { "hello" }.join + thread.name = "平仮名" + thread.to_s.should.include?("@平仮名") + thread.to_s.encoding.should == Encoding::UTF_8 + end + it "has a binary encoding" do - ThreadSpecs.status_of_current_thread.to_s.encoding.should == Encoding::BINARY + thread = Thread.new { "hello" }.join + thread.to_s.encoding.should == Encoding::BINARY end it "can check it's own status" do diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb index 24d5233299fc6a..fc7548a05486ec 100644 --- a/spec/ruby/core/unboundmethod/equal_value_spec.rb +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -129,6 +129,13 @@ def discard_1; :discard; end (@discard_1 == UnboundMethodSpecs::Methods.instance_method(:discard_1)).should == false end + it "returns false for two attr_reader methods" do + Class.new do + attr_reader :a, :b + instance_method(:a).should_not == instance_method(:b) + end + end + it "considers methods through aliasing equal" do c = Class.new do class << self diff --git a/spec/ruby/language/it_parameter_spec.rb b/spec/ruby/language/it_parameter_spec.rb index 6ba67cbb4f26a3..2542eb70fe8766 100644 --- a/spec/ruby/language/it_parameter_spec.rb +++ b/spec/ruby/language/it_parameter_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' ruby_version_is "3.4" do - eval <<-RUBY # use eval to avoid warnings on Ruby 3.3 + eval <<-RUBY, binding, __FILE__, __LINE__ + 1 # use eval to avoid warnings on Ruby 3.3 describe "The `it` parameter" do it "provides it in a block" do -> { it }.call("a").should == "a" @@ -86,6 +86,25 @@ def o.it -> { it; binding.local_variables }.call("a").should == [] end + it "does not affect binding local variables getting" do + proc { + a = it; binding.local_variable_get(:it) + }.should.raise(NameError, /local variable 'it' is not defined for/) + end + + it "does not affect binding local variables setting" do + -> { + a = it + binding.local_variable_set(:it, :b) + [a, it] + }.call(:a).should == [:a, :a] + end + + it "does not affect binding local variables definition check" do + a = it + binding.local_variable_defined?(:it).should == false + end + it "does not work in methods" do obj = Object.new def obj.foo; it; end diff --git a/spec/ruby/language/numbered_parameters_spec.rb b/spec/ruby/language/numbered_parameters_spec.rb index 23e89a14be9065..17258c11f89d4c 100644 --- a/spec/ruby/language/numbered_parameters_spec.rb +++ b/spec/ruby/language/numbered_parameters_spec.rb @@ -95,6 +95,18 @@ -> { _1; binding.local_variables }.call("a").should == [:_1] -> { _2; binding.local_variables }.call("a", "b").should == [:_1, :_2] end + + it "affects binding local variables getting" do + -> { _1; binding.local_variable_get(:_1) }.call("a").should == "a" + end + + it "affects binding local variables setting" do + -> { _1; binding.local_variable_set(:_1, "b"); _1 }.call("a").should == "b" + end + + it "affects binding local variables definition check" do + -> { _1; binding.local_variable_defined?(:_1) }.call("a").should == true + end end ruby_version_is "4.0" do @@ -102,6 +114,24 @@ -> { _1; binding.local_variables }.call("a").should == [] -> { _2; binding.local_variables }.call("a", "b").should == [] end + + it "does not affect binding local variables getting" do + proc { + _1; binding.local_variable_get(:_1) + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end + + it "does not affect binding local variables setting" do + proc { + _1; binding.local_variable_set(:_1, "b") + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end + + it "does not affect binding local variables definition check" do + proc { + _1; binding.local_variable_defined?(:_1) + }.should.raise(NameError, "numbered parameter '_1' is not a local variable") + end end it "does not work in methods" do @@ -110,4 +140,8 @@ def obj.foo; _1 end -> { obj.foo("a") }.should.raise(ArgumentError, /wrong number of arguments/) end + + it "cannot be accessed using eval()" do + -> { proc { binding.eval('_1') }.call(1) }.should.raise(NameError, /undefined local variable or method ._1'/) + end end diff --git a/spec/ruby/language/precedence_spec.rb b/spec/ruby/language/precedence_spec.rb index edb990525eef14..a4de9fac1740d7 100644 --- a/spec/ruby/language/precedence_spec.rb +++ b/spec/ruby/language/precedence_spec.rb @@ -131,12 +131,9 @@ class UnaryMinusTest; def -@; 50; end; end it "* / % are left-associative" do (2*1/2).should == (2*1)/2 - # Guard against the Mathn library # TODO: Make these specs not rely on specific behaviour / result values # by using mocks. - guard -> { !defined?(Math.rsqrt) } do - (2*1/2).should_not == 2*(1/2) - end + (2*1/2).should_not == 2*(1/2) (10/7/5).should == (10/7)/5 (10/7/5).should_not == 10/(7/5) diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index cf16d8f6f85718..673976f8bf58e5 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -98,6 +98,19 @@ def a ScratchPad.recorded.should == ["message"] end + it 'captures successfully in a begin/end block' do + ScratchPad.record [] + + begin + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + e.should.is_a?(RuntimeError) + end + it 'captures successfully in a class' do ScratchPad.record [] @@ -122,7 +135,7 @@ module RescueSpecs::M ScratchPad.recorded.should == ["message"] end - it 'captures sucpcessfully in a singleton class' do + it 'captures successfully in a singleton class' do ScratchPad.record [] class << Object.new diff --git a/spec/ruby/library/delegate/delegator/taint_spec.rb b/spec/ruby/library/delegate/delegator/taint_spec.rb deleted file mode 100644 index 6bf13bb73db3f2..00000000000000 --- a/spec/ruby/library/delegate/delegator/taint_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#taint" do - before :each do - @delegate = DelegateSpecs::Delegator.new("") - end -end diff --git a/spec/ruby/library/delegate/delegator/trust_spec.rb b/spec/ruby/library/delegate/delegator/trust_spec.rb deleted file mode 100644 index f1b81814c56907..00000000000000 --- a/spec/ruby/library/delegate/delegator/trust_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#trust" do - before :each do - @delegate = DelegateSpecs::Delegator.new([]) - end -end diff --git a/spec/ruby/library/delegate/delegator/untaint_spec.rb b/spec/ruby/library/delegate/delegator/untaint_spec.rb deleted file mode 100644 index 4051fd2629b1f3..00000000000000 --- a/spec/ruby/library/delegate/delegator/untaint_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#untaint" do - before :each do - @delegate = -> { DelegateSpecs::Delegator.new("") }.call - end -end diff --git a/spec/ruby/library/delegate/delegator/untrust_spec.rb b/spec/ruby/library/delegate/delegator/untrust_spec.rb deleted file mode 100644 index 4f7fa1e582c555..00000000000000 --- a/spec/ruby/library/delegate/delegator/untrust_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require_relative '../../../spec_helper' -require_relative '../fixtures/classes' - -describe "Delegator#untrust" do - before :each do - @delegate = DelegateSpecs::Delegator.new("") - end -end diff --git a/spec/ruby/library/matrix/divide_spec.rb b/spec/ruby/library/matrix/divide_spec.rb index 711a5189e44a2b..d4ffee3c86111d 100644 --- a/spec/ruby/library/matrix/divide_spec.rb +++ b/spec/ruby/library/matrix/divide_spec.rb @@ -14,15 +14,12 @@ (@a / @b).should be_close_to_matrix([[2.5, -1.5], [1.5, -0.5]]) end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns the result of dividing self by a Fixnum" do - (@a / 2).should == Matrix[ [0, 1], [1, 2] ] - end + it "returns the result of dividing self by a Fixnum" do + (@a / 2).should == Matrix[ [0, 1], [1, 2] ] + end - it "returns the result of dividing self by a Bignum" do - (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ] - end + it "returns the result of dividing self by a Bignum" do + (@a / bignum_value).should == Matrix[ [0, 0], [0, 0] ] end it "returns the result of dividing self by a Float" do diff --git a/spec/ruby/library/matrix/real_spec.rb b/spec/ruby/library/matrix/real_spec.rb index 4589dc22a5ffb9..60872ee64c981a 100644 --- a/spec/ruby/library/matrix/real_spec.rb +++ b/spec/ruby/library/matrix/real_spec.rb @@ -16,11 +16,8 @@ Matrix[ [Complex(1,1), 2], [3, 4] ].real?.should == false end - # Guard against the Mathn library - guard -> { !defined?(Math.rsqrt) } do - it "returns false if one element is a Complex whose imaginary part is 0" do - Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should == false - end + it "returns false if one element is a Complex whose imaginary part is 0" do + Matrix[ [Complex(1,0), 2], [3, 4] ].real?.should == false end end diff --git a/spec/ruby/library/pathname/pathname_spec.rb b/spec/ruby/library/pathname/pathname_spec.rb index 6fa6fd2bcba58d..31228de65ce022 100644 --- a/spec/ruby/library/pathname/pathname_spec.rb +++ b/spec/ruby/library/pathname/pathname_spec.rb @@ -2,14 +2,10 @@ require 'pathname' describe "Kernel#Pathname" do - it "is a private instance method" do + it "is a private method" do Kernel.private_instance_methods(false).should.include?(:Pathname) end - it "is also a public method" do - Kernel.should.respond_to?(:Pathname) - end - it "returns same argument when called with a pathname argument" do path = Pathname('foo') new_path = Pathname(path) @@ -17,3 +13,9 @@ path.should.equal?(new_path) end end + +describe "Kernel.Pathname" do + it "is a public method" do + Kernel.public_methods(false).should.include?(:Pathname) + end +end diff --git a/spec/ruby/library/socket/ipsocket/inspect_spec.rb b/spec/ruby/library/socket/ipsocket/inspect_spec.rb index 85780a16f6f35d..92f60e347266b3 100644 --- a/spec/ruby/library/socket/ipsocket/inspect_spec.rb +++ b/spec/ruby/library/socket/ipsocket/inspect_spec.rb @@ -21,4 +21,22 @@ ensure @socket&.close end + + it 'returns a String marking the socket as closed for a closed TCPSocket' do + @server = TCPServer.new("127.0.0.1", 0) + @socket = TCPSocket.new("127.0.0.1", @server.addr[1]) + @socket.close + + @socket.inspect.should == "#" + ensure + @server&.close + end + + it 'returns a String marking the socket as closed for a closed UDPSocket' do + @socket = UDPSocket.new + @socket.bind('127.0.0.1', 0) + @socket.close + + @socket.inspect.should == "#" + end end diff --git a/spec/ruby/library/stringio/ungetc_spec.rb b/spec/ruby/library/stringio/ungetc_spec.rb index 8753ac9666e55b..dbe1ce5ddd3931 100644 --- a/spec/ruby/library/stringio/ungetc_spec.rb +++ b/spec/ruby/library/stringio/ungetc_spec.rb @@ -12,6 +12,18 @@ @io.string.should == 'A234' end + it "writes the passed string before the current position" do + @io.pos = 3 + @io.ungetc("foo") + @io.string.should == 'foo4' + end + + it "writes the passed string at the start if the current position is 0" do + @io.pos = 0 + @io.ungetc("A") + @io.string.should == 'A1234' + end + it "returns nil" do @io.pos = 1 @io.ungetc(?A).should == nil @@ -23,10 +35,32 @@ @io.pos.should.eql?(1) end + it "decreases the current position by the size of a multibyte character" do + @io.pos = 2 + @io.ungetc("φ") + @io.pos.should == 0 + @io.string.should == "φ34" + end + + it "writes the given string completely when the current position does not have enough space" do + @io.pos = 2 + @io.ungetc("foo") + @io.pos.should == 0 + @io.string.should == "foo34" + end + it "pads with \\000 when the current position is after the end" do @io.pos = 15 @io.ungetc(?A) @io.string.should == "1234\000\000\000\000\000\000\000\000\000\000A" + @io.pos.should == 14 + end + + it "pads with \\000 when the current position is after the end for a multibyte character" do + @io.pos = 15 + @io.ungetc("φ") + @io.string.should == "1234\000\000\000\000\000\000\000\000\000φ" + @io.pos.should == 13 end it "tries to convert the passed argument to an String using #to_str" do diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb index 0b997f774ca470..ad0eb56741efdb 100644 --- a/spec/ruby/optional/capi/array_spec.rb +++ b/spec/ruby/optional/capi/array_spec.rb @@ -343,7 +343,7 @@ end end - ruby_version_is ""..."4.0" do + ruby_version_is ""..."4.1" do describe "rb_iterate" do it "calls an callback function as a block passed to an method" do s = [1,2,3,4] @@ -376,6 +376,10 @@ def o.each s2.should == [1,2,3,4] end + + it "passes non-Ruby pointers to the iteration and block callbacks" do + @s.rb_iterate_with_pointer.should == [14] + end end end diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 628c4df9d74a60..164c9063eed27f 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -196,7 +196,7 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { return rb_ary_push(new_ary, el); } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -218,7 +218,7 @@ static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { return rb_ary_push(holder, rb_ary_entry(el, 1)); } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE each_pair(VALUE obj) { return rb_funcall(obj, rb_intern("each_pair"), 0); } @@ -240,12 +240,52 @@ static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { return new_ary; } +#ifndef RUBY_VERSION_IS_4_1 +struct rb_iterate_pointer_data { + int magic; + VALUE array; +}; + +static VALUE pointer_iter(VALUE value) { + struct rb_iterate_pointer_data *data = (struct rb_iterate_pointer_data *)value; + VALUE array; + + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid iteration pointer"); + } + + if (rb_block_given_p()) { + rb_yield(INT2FIX(14)); + return Qnil; + } else { + array = rb_ary_new(); + rb_ary_push(array, INT2FIX(14)); + return rb_funcall(array, rb_intern("each"), 0); + } +} + +static VALUE pointer_block(RB_BLOCK_CALL_FUNC_ARGLIST(el, value)) { + struct rb_iterate_pointer_data *data = (struct rb_iterate_pointer_data *)value; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid block pointer"); + } + + return rb_ary_push(data->array, el); +} + +static VALUE array_spec_rb_iterate_with_pointer(VALUE self) { + struct rb_iterate_pointer_data data = { 0x1234, rb_ary_new() }; + rb_iterate(pointer_iter, (VALUE)&data, pointer_block, (VALUE)&data); + return data.array; +} +#endif + static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { rb_yield(el); return Qnil; } -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 static VALUE array_spec_rb_iterate_then_yield(VALUE self, VALUE obj) { rb_iterate(rb_each, obj, iter_yield, obj); return Qnil; @@ -314,10 +354,11 @@ void Init_array_spec(void) { rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2); rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2); rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2); -#ifndef RUBY_VERSION_IS_4_0 +#ifndef RUBY_VERSION_IS_4_1 rb_define_method(cls, "rb_iterate", array_spec_rb_iterate, 1); rb_define_method(cls, "rb_iterate_each_pair", array_spec_rb_iterate_each_pair, 1); rb_define_method(cls, "rb_iterate_then_yield", array_spec_rb_iterate_then_yield, 1); + rb_define_method(cls, "rb_iterate_with_pointer", array_spec_rb_iterate_with_pointer, 0); #endif rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1); rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1); diff --git a/spec/ruby/optional/capi/ext/fiber_spec.c b/spec/ruby/optional/capi/ext/fiber_spec.c index db54f7ad8c6c92..ef2dcbc35a6f15 100644 --- a/spec/ruby/optional/capi/ext/fiber_spec.c +++ b/spec/ruby/optional/capi/ext/fiber_spec.c @@ -44,6 +44,25 @@ VALUE fiber_spec_rb_fiber_new(VALUE self) { return rb_fiber_new(fiber_spec_rb_fiber_new_function, Qnil); } +struct rb_fiber_new_pointer_data { + int magic; +}; + +VALUE fiber_spec_rb_fiber_new_pointer_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, value)) { + struct rb_fiber_new_pointer_data *data = (struct rb_fiber_new_pointer_data *)value; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid fiber pointer"); + } + + return Qtrue; +} + +VALUE fiber_spec_rb_fiber_new_with_pointer(VALUE self) { + struct rb_fiber_new_pointer_data data = { 0x1234 }; + VALUE fiber = rb_fiber_new(fiber_spec_rb_fiber_new_pointer_function, (VALUE)&data); + return rb_fiber_resume(fiber, 0, NULL); +} + VALUE fiber_spec_rb_fiber_raise(int argc, VALUE *argv, VALUE self) { VALUE fiber = argv[0]; return rb_fiber_raise(fiber, argc-1, argv+1); @@ -56,6 +75,7 @@ void Init_fiber_spec(void) { rb_define_method(cls, "rb_fiber_resume", fiber_spec_rb_fiber_resume, 2); rb_define_method(cls, "rb_fiber_yield", fiber_spec_rb_fiber_yield, 1); rb_define_method(cls, "rb_fiber_new", fiber_spec_rb_fiber_new, 0); + rb_define_method(cls, "rb_fiber_new_with_pointer", fiber_spec_rb_fiber_new_with_pointer, 0); rb_define_method(cls, "rb_fiber_raise", fiber_spec_rb_fiber_raise, -1); } diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index fe31cffb495241..5cf96ecc0eb2d1 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -35,6 +35,10 @@ VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) { return INT2NUM(io_spec_get_fd(io)); } +VALUE io_spec_rb_io_get_io(VALUE self, VALUE io) { + return rb_io_get_io(io); +} + VALUE io_spec_rb_io_addstr(VALUE self, VALUE io, VALUE str) { return rb_io_addstr(io, str); } @@ -366,6 +370,10 @@ static VALUE io_spec_rb_io_open_descriptor_without_encoding(VALUE self, VALUE kl return rb_io_open_descriptor(klass, FIX2INT(descriptor), FIX2INT(mode), path, timeout, NULL); } +static VALUE io_spec_rb_eIOTimeoutError(VALUE self) { + return rb_eIOTimeoutError; +} + void Init_io_spec(void) { VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1); @@ -379,6 +387,7 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_check_readable", io_spec_rb_io_check_readable, 1); rb_define_method(cls, "rb_io_check_writable", io_spec_rb_io_check_writable, 1); rb_define_method(cls, "rb_io_check_closed", io_spec_rb_io_check_closed, 1); + rb_define_method(cls, "rb_io_get_io", io_spec_rb_io_get_io, 1); rb_define_method(cls, "rb_io_set_nonblock", io_spec_rb_io_set_nonblock, 1); rb_define_method(cls, "rb_io_taint_check", io_spec_rb_io_taint_check, 1); rb_define_method(cls, "rb_io_wait_readable", io_spec_rb_io_wait_readable, 2); @@ -404,6 +413,7 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_closed_p", io_spec_rb_io_closed_p, 1); rb_define_method(cls, "rb_io_open_descriptor", io_spec_rb_io_open_descriptor, 9); rb_define_method(cls, "rb_io_open_descriptor_without_encoding", io_spec_rb_io_open_descriptor_without_encoding, 5); + rb_define_method(cls, "rb_eIOTimeoutError", io_spec_rb_eIOTimeoutError, 0); rb_define_const(cls, "FMODE_READABLE", INT2FIX(FMODE_READABLE)); rb_define_const(cls, "FMODE_WRITABLE", INT2FIX(FMODE_WRITABLE)); rb_define_const(cls, "FMODE_BINMODE", INT2FIX(FMODE_BINMODE)); diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index eee324052d0936..6384aee36e221c 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -114,6 +114,24 @@ VALUE kernel_spec_rb_catch_obj(VALUE self, VALUE obj, VALUE main_proc) { return rb_catch_obj(obj, kernel_spec_call_proc_with_catch_obj, main_proc); } +struct rb_catch_obj_pointer_data { + int magic; +}; + +VALUE kernel_spec_catch_obj_pointer(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) { + struct rb_catch_obj_pointer_data *pointer_data = (struct rb_catch_obj_pointer_data *)data; + if (pointer_data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid catch pointer"); + } + + return Qtrue; +} + +VALUE kernel_spec_rb_catch_obj_with_pointer(VALUE self, VALUE obj) { + struct rb_catch_obj_pointer_data data = { 0x1234 }; + return rb_catch_obj(obj, kernel_spec_catch_obj_pointer, (VALUE)&data); +} + VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) { return rb_eval_string(RSTRING_PTR(str)); } @@ -329,6 +347,29 @@ static VALUE kernel_spec_rb_exec_recursive(VALUE self, VALUE obj) { return rb_exec_recursive(do_rec, obj, Qtrue); } +struct rb_exec_recursive_pointer_data { + int magic; +}; + +static VALUE do_rec_pointer(VALUE obj, VALUE arg, int is_rec) { + struct rb_exec_recursive_pointer_data *obj_data = (struct rb_exec_recursive_pointer_data *)obj; + struct rb_exec_recursive_pointer_data *arg_data = (struct rb_exec_recursive_pointer_data *)arg; + if (obj_data->magic != 0x1234 || arg_data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid recursive pointer"); + } + + if (is_rec) { + return Qtrue; + } else { + return rb_exec_recursive(do_rec_pointer, obj, arg); + } +} + +static VALUE kernel_spec_rb_exec_recursive_with_pointer(VALUE self) { + struct rb_exec_recursive_pointer_data data = { 0x1234 }; + return rb_exec_recursive(do_rec_pointer, (VALUE)&data, (VALUE)&data); +} + static void write_io(VALUE io) { rb_funcall(io, rb_intern("write"), 1, rb_str_new2("in write_io")); } @@ -432,6 +473,7 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_eval_string_protect", kernel_spec_rb_eval_string_protect, 2); rb_define_method(cls, "rb_catch", kernel_spec_rb_catch, 2); rb_define_method(cls, "rb_catch_obj", kernel_spec_rb_catch_obj, 2); + rb_define_method(cls, "rb_catch_obj_with_pointer", kernel_spec_rb_catch_obj_with_pointer, 1); rb_define_method(cls, "rb_sys_fail", kernel_spec_rb_sys_fail, 1); rb_define_method(cls, "rb_syserr_fail", kernel_spec_rb_syserr_fail, 2); rb_define_method(cls, "rb_syserr_fail_str", kernel_spec_rb_syserr_fail_str, 2); @@ -443,6 +485,7 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_yield_values2", kernel_spec_rb_yield_values2, 1); rb_define_method(cls, "rb_yield_splat", kernel_spec_rb_yield_splat, 1); rb_define_method(cls, "rb_exec_recursive", kernel_spec_rb_exec_recursive, 1); + rb_define_method(cls, "rb_exec_recursive_with_pointer", kernel_spec_rb_exec_recursive_with_pointer, 0); rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1); rb_define_method(cls, "ruby_vm_at_exit", kernel_spec_ruby_vm_at_exit, 0); rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1); diff --git a/spec/ruby/optional/capi/ext/proc_spec.c b/spec/ruby/optional/capi/ext/proc_spec.c index b7cd5d62620034..f836be59d94607 100644 --- a/spec/ruby/optional/capi/ext/proc_spec.c +++ b/spec/ruby/optional/capi/ext/proc_spec.c @@ -60,6 +60,25 @@ VALUE proc_spec_rb_proc_new_callback_arg(VALUE self, VALUE arg) { return rb_proc_new(proc_spec_rb_proc_new_function_callback_arg, arg); } +struct rb_proc_new_pointer_data { + int magic; +}; + +VALUE proc_spec_rb_proc_new_function_pointer(VALUE arg, VALUE callback_arg, int argc, const VALUE *argv, VALUE blockarg) { + struct rb_proc_new_pointer_data *data = (struct rb_proc_new_pointer_data *)callback_arg; + if (data->magic != 0x1234) { + rb_raise(rb_eRuntimeError, "invalid proc pointer"); + } + + return Qtrue; +} + +VALUE proc_spec_rb_proc_new_with_pointer(VALUE self) { + struct rb_proc_new_pointer_data data = { 0x1234 }; + VALUE proc = rb_proc_new(proc_spec_rb_proc_new_function_pointer, (VALUE)&data); + return rb_funcall(proc, rb_intern("call"), 0); +} + VALUE proc_spec_rb_proc_new_blockarg(VALUE self) { return rb_proc_new(proc_spec_rb_proc_new_function_blockarg, Qnil); } @@ -131,6 +150,7 @@ void Init_proc_spec(void) { rb_define_method(cls, "rb_proc_new_argc", proc_spec_rb_proc_new_argc, 0); rb_define_method(cls, "rb_proc_new_argv_n", proc_spec_rb_proc_new_argv_n, 0); rb_define_method(cls, "rb_proc_new_callback_arg", proc_spec_rb_proc_new_callback_arg, 1); + rb_define_method(cls, "rb_proc_new_with_pointer", proc_spec_rb_proc_new_with_pointer, 0); rb_define_method(cls, "rb_proc_new_blockarg", proc_spec_rb_proc_new_blockarg, 0); rb_define_method(cls, "rb_proc_new_block_given_p", proc_spec_rb_proc_new_block_given_p, 0); rb_define_method(cls, "rb_proc_arity", proc_spec_rb_proc_arity, 1); diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c index 5a95b92804dc2d..892d3258c0b539 100644 --- a/spec/ruby/optional/capi/ext/rbasic_spec.c +++ b/spec/ruby/optional/capi/ext/rbasic_spec.c @@ -13,13 +13,8 @@ extern "C" { #define RBASIC_SET_FLAGS(obj, flags_to_set) (RBASIC(obj)->flags = flags_to_set) #endif -#ifndef FL_SHAREABLE -static const VALUE VISIBLE_BITS = FL_TAINT | FL_FREEZE; -static const VALUE DATA_VISIBLE_BITS = FL_TAINT | FL_FREEZE | ~(FL_USER0 - 1); -#else static const VALUE VISIBLE_BITS = FL_FREEZE; static const VALUE DATA_VISIBLE_BITS = FL_FREEZE | ~(FL_USER0 - 1); -#endif #if SIZEOF_VALUE == SIZEOF_LONG #define VALUE2NUM(v) ULONG2NUM(v) diff --git a/spec/ruby/optional/capi/fiber_spec.rb b/spec/ruby/optional/capi/fiber_spec.rb index c820ba17c2f6ae..d19e85edd70d30 100644 --- a/spec/ruby/optional/capi/fiber_spec.rb +++ b/spec/ruby/optional/capi/fiber_spec.rb @@ -46,6 +46,10 @@ fiber.should.instance_of?(Fiber) fiber.resume(42).should == "42" end + + it "passes non-Ruby pointers to the fiber callback" do + @s.rb_fiber_new_with_pointer.should == true + end end describe "rb_fiber_raise" do diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index 459a32d954b3f2..b7517f7f322ff6 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -190,6 +190,45 @@ end end + describe "rb_io_get_io" do + it "returns the passed object and does not call #to_io if the object is already an IO" do + @io.should_not_receive(:to_io) + + @o.rb_io_get_io(@io).should.equal?(@io) + end + + it "returns the passed object and does not call #to_io if the object is a subclass of IO" do + file = File.open(@name) + + begin + file.should_not_receive(:to_io) + + @o.rb_io_get_io(file).should.equal?(file) + ensure + file.close + end + end + + it "calls #to_io to convert the object to an IO" do + wrapper = Object.new + io = @io + wrapper.define_singleton_method(:to_io) { io } + + @o.rb_io_get_io(wrapper).should.equal?(@io) + end + + it "raises a TypeError if #to_io does not return an IO" do + wrapper = Object.new + wrapper.define_singleton_method(:to_io) { Object.new } + + -> { @o.rb_io_get_io(wrapper) }.should.raise(TypeError) + end + + it "raises a TypeError if the object does not respond to #to_io" do + -> { @o.rb_io_get_io(Object.new) }.should.raise(TypeError) + end + end + describe "rb_io_binmode" do it "returns self" do @o.rb_io_binmode(@io).should == @io @@ -280,6 +319,36 @@ -> { @o.rb_io_maybe_wait_writable(0, IO.allocate, nil) }.should.raise(IOError, "uninitialized stream") end + ruby_version_is "3.4" do + platform_is_not :windows do + it "raises a IO::TimeoutError if the timeout elapses" do + IOSpec.exhaust_write_buffer(@w_io) + -> { @o.rb_io_maybe_wait_writable(Errno::EAGAIN::Errno, @w_io, 0) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become writable!") + end + end + + platform_is :windows do + # Windows select/poll wrapper (rb_w32_select) treats write descriptors of non-sockets + # (such as pipe writers) as always writable. Thus it immediately returns IO::WRITABLE + # instead of timing out. So use sockets instead. + it "raises a IO::TimeoutError if the timeout elapses" do + require 'socket' + r_sock, w_sock = Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0) + begin + r_sock.close_write + w_sock.close_read + IOSpec.exhaust_write_buffer(w_sock) + -> { @o.rb_io_maybe_wait_writable(Errno::EAGAIN::Errno, w_sock, 0) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become writable!") + ensure + r_sock.close unless r_sock.closed? + w_sock.close unless w_sock.closed? + end + end + end + end + it "can be interrupted" do IOSpec.exhaust_write_buffer(@w_io) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) @@ -392,6 +461,13 @@ it "raises an IOError if the IO is not initialized" do -> { @o.rb_io_maybe_wait_readable(0, IO.allocate, nil, false) }.should.raise(IOError, "uninitialized stream") end + + ruby_version_is "3.4" do + it "raises a IO::TimeoutError if the timeout elapses" do + -> { @o.rb_io_maybe_wait_readable(Errno::EAGAIN::Errno, @r_io, 0, false) }. + should.raise(IO::TimeoutError, "Timed out waiting for IO to become readable!") + end + end end end @@ -785,4 +861,8 @@ def path.to_str; "a.txt"; end @o.rb_io_mode_sync_flag(io).should == false } end + + specify "rb_eIOTimeoutError references the IO::TimeoutError class" do + @o.rb_eIOTimeoutError.should == IO::TimeoutError + end end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 62eb21448b24ca..2fee0c292098c8 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -525,6 +525,10 @@ def proc_caller @s.rb_catch_obj(@tag, -> { 1 }).should == 1 end + it "passes non-Ruby pointers to the catch callback" do + @s.rb_catch_obj_with_pointer(@tag).should == true + end + it "terminates the function at the point it was called" do proc = -> do ScratchPad << :before_throw @@ -695,6 +699,10 @@ def proc_caller s = "hello" @s.rb_exec_recursive(s).should == s end + + it "passes non-Ruby pointers to the recursive callback" do + @s.rb_exec_recursive_with_pointer.should == true + end end describe "rb_set_end_proc" do diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 8e907a5a8ca50b..298aad389138a4 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -652,15 +652,6 @@ def reach end end - describe "OBJ_TAINT" do - end - - describe "OBJ_TAINTED" do - end - - describe "OBJ_INFECT" do - end - describe "rb_obj_freeze" do it "freezes the object passed to it" do obj = "" @@ -701,9 +692,6 @@ def reach end end - describe "rb_obj_taint" do - end - describe "rb_check_frozen" do it "raises a FrozenError if the obj is frozen" do -> { @o.rb_check_frozen("".freeze) }.should.raise(FrozenError) diff --git a/spec/ruby/optional/capi/proc_spec.rb b/spec/ruby/optional/capi/proc_spec.rb index 1c250fee761167..c66af484ae5a33 100644 --- a/spec/ruby/optional/capi/proc_spec.rb +++ b/spec/ruby/optional/capi/proc_spec.rb @@ -39,6 +39,10 @@ -> { @prc2.call(3, :foo, :bar) }.should.raise(ArgumentError) end + it "passes non-Ruby pointers to the proc callback" do + @p.rb_proc_new_with_pointer.should == true + end + it "calls the C function with the block passed in blockarg" do a_block = :foo.to_proc @p.rb_proc_new_blockarg.call(&a_block).should == a_block diff --git a/spec/ruby/security/cve_2018_16396_spec.rb b/spec/ruby/security/cve_2018_16396_spec.rb deleted file mode 100644 index 6f8a5d2f5d6030..00000000000000 --- a/spec/ruby/security/cve_2018_16396_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require_relative '../spec_helper' - -describe "Array#pack" do -end - -describe "String#unpack" do -end diff --git a/spec/ruby/shared/basicobject/send.rb b/spec/ruby/shared/basicobject/send.rb index d38aea975e3be2..711848e68ae5b6 100644 --- a/spec/ruby/shared/basicobject/send.rb +++ b/spec/ruby/shared/basicobject/send.rb @@ -125,4 +125,10 @@ def foo(first = true) first end c2.new.send(@method, :foo, *[[]]).should == %i[m1 m2] end + + it "can be called reflectively" do + obj = Object.new + obj.method(@method).call(:itself).should.equal?(obj) + obj.method(@method).to_proc.call(:itself).should.equal?(obj) + end end diff --git a/spec/ruby/shared/kernel/fixtures/classes.rb b/spec/ruby/shared/kernel/fixtures/classes.rb new file mode 100644 index 00000000000000..81d6811ed656f8 --- /dev/null +++ b/spec/ruby/shared/kernel/fixtures/classes.rb @@ -0,0 +1,9 @@ +module KernelSpecs + module RaiseSpecs + class UniqueClass + def self.with_raise + yield + end + end + end +end diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 04eaa94e6aa476..cdbbf931a977e1 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -1,3 +1,5 @@ +require_relative 'fixtures/classes' + describe :kernel_raise, shared: true do before :each do ScratchPad.clear @@ -139,6 +141,42 @@ def e.exception raised_exception.should == exception end + it "re-raises a previously rescued exception with cleaned backtrace and sets a new backtrace" do + begin + raise "raised" + rescue => exception + # ignore + end + + exception.set_backtrace(nil) + + # To get a uniform backtrace check across Kernel, Thread, and Fiber: + # - Kernel#raise raises directly in the outer block. + # - Fiber/Thread wrappers (NewFiberToRaise, NewThreadToRaise) yield/sleep + # inside the inner block in their own context. + # In both cases, the backtrace contains the UniqueClass.with_raise frame. + begin + KernelSpecs::RaiseSpecs::UniqueClass.with_raise do + @object.raise(exception) do |&pause| + KernelSpecs::RaiseSpecs::UniqueClass.with_raise do + pause.call + end + end + end + rescue => reraised_exception + # ignore + end + + reraised_exception.should == exception + reraised_exception.backtrace.should.is_a?(Array) + + ruby_version_is "4.0" do + reraised_exception.backtrace.should.any? { |line| + line.include?("KernelSpecs::RaiseSpecs::UniqueClass.with_raise") + } + end + end + it "allows Exception, message, and backtrace parameters" do -> do @object.raise(ArgumentError, "message", caller) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 59dce175f13341..a7affd46cdf27b 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -678,7 +678,7 @@ def test_string_interpolations_across_heaps_get_embedded omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 require 'objspace' - base_slot_size = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] + base_slot_size = 64 small_obj_size = (base_slot_size / 2) large_obj_size = base_slot_size * 2 diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4df06236bbc0b3..a6239074a02ee9 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -23,7 +23,7 @@ use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Co use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, Target, asm_ccall, asm_comment}; use crate::hir::{iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; -use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendFallbackReason}; +use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason}; use crate::hir_type::{types, Type}; use crate::options::{get_option, InlineDepth, PerfMap, DEFAULT_MAX_VERSIONS}; use crate::cast::IntoUsize; @@ -644,10 +644,13 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::Send { cd, block: Some(BlockHandler::BlockIseq(blockiseq)), state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason), &Insn::Send { cd, block: Some(BlockHandler::BlockArg), state, reason, .. } => gen_send(jit, asm, cd, std::ptr::null(), &function.frame_state(state), reason), &Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason), - &Insn::SendDirect { cme, iseq, recv, ref args, kw_bits, block, state, .. } => gen_send_iseq_direct( - cb, jit, asm, cme, iseq, opnd!(recv), opnds!(args), - kw_bits, &function.frame_state(state), block, - ), + Insn::SendDirect(insn) => { + let SendDirectData { cme, iseq, recv, args, kw_bits, block, state, .. } = &**insn; + gen_send_iseq_direct( + cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), + *kw_bits, &function.frame_state(*state), *block, + ) + } Insn::PushInlineFrame { cme, iseq, recv, args, blockiseq, state, .. } => { no_output!(gen_push_inline_frame(jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), &function.frame_state(*state), *blockiseq)) }, @@ -712,10 +715,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio let val_type = function.type_of(val); gen_guard_type(jit, asm, opnd!(val), val_type, guard_type, recompile, &function.frame_state(state)) } - &Insn::GuardBitEquals { val, expected, reason, state, recompile } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, recompile, &function.frame_state(state)), - &Insn::GuardAnyBitSet { val, mask, reason, state, recompile, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, recompile, &function.frame_state(state)), - &Insn::GuardNoBitsSet { val, mask, reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), - &Insn::GuardLess { left, right, reason, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), reason, &function.frame_state(state)), + &Insn::GuardBitEquals { val, expected, ref reason, state, recompile } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, **reason, recompile, &function.frame_state(state)), + &Insn::GuardAnyBitSet { val, mask, ref reason, state, recompile, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, **reason, recompile, &function.frame_state(state)), + &Insn::GuardNoBitsSet { val, mask, ref reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, **reason, &function.frame_state(state)), + &Insn::GuardLess { left, right, ref reason, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), **reason, &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state, .. } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, owner: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index fec3b3db5ec284..3608bba453486c 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -71,6 +71,10 @@ impl Annotations { let func_ptr = unsafe { (*bf).func_ptr as *mut c_void }; self.builtin_funcs.get(&func_ptr).copied() } + + pub fn get_builtin_return_type(&self, bf: *const rb_builtin_function) -> Type { + self.get_builtin_properties(bf).map(|p| p.return_type).unwrap_or(types::BasicObject) + } } fn annotate_c_method(props_map: &mut HashMap<*mut c_void, FnProperties>, class: VALUE, method_name: &'static str, props: FnProperties) { @@ -373,11 +377,11 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In let index = fun.coerce_to(block, index, types::Fixnum, state); let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); - let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: SideExitReason::GuardLess, state }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: Box::new(SideExitReason::GuardLess), state }); let index = fun.push_insn(block, hir::Insn::AdjustBounds { index, length }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let result = fun.push_insn(block, hir::Insn::ArrayAref { array: recv, index }); return Some(result); } @@ -398,11 +402,11 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In // Bounds check: unbox Fixnum index and guard 0 <= idx < length. let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); - let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: SideExitReason::GuardLess, state }); + let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, reason: Box::new(SideExitReason::GuardLess), state }); let index = fun.push_insn(block, hir::Insn::AdjustBounds { index, length }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val }); fun.push_insn(block, hir::Insn::WriteBarrier { recv, val }); @@ -498,11 +502,11 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir // the data dependency is gone (say, the StringGetbyte is elided), they can also be elided. // // This is unlike most other guards. - let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: SideExitReason::GuardLess, state }); + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: Box::new(SideExitReason::GuardLess), state }); let unboxed_index = fun.push_insn(block, hir::Insn::AdjustBounds { index: unboxed_index, length: len }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); let result = fun.push_insn(block, hir::Insn::StringGetbyte { string: recv, index: unboxed_index }); return Some(result); } @@ -522,11 +526,11 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir offset: RUBY_OFFSET_RSTRING_LEN, return_type: types::CInt64, }); - let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: SideExitReason::GuardLess, state }); + let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, reason: Box::new(SideExitReason::GuardLess), state }); let unboxed_index = fun.push_insn(block, hir::Insn::AdjustBounds { index: unboxed_index, length: len }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); use crate::hir::SideExitReason; - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: Box::new(SideExitReason::GuardGreaterEq), state }); // We know that all String are HeapObject, so no need to insert a GuardType(HeapObject). fun.guard_not_frozen(block, recv, state); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); @@ -562,7 +566,7 @@ fn guard_string_coderange(fun: &mut hir::Function, block: hir::BlockId, args: &[ let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CUInt64(RUBY_ENC_CODERANGE_MASK.into()) }); let cr = fun.push_insn(block, hir::Insn::IntAnd { left: flags, right: mask }); let min_known = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) }); - Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: SideExitReason::GuardGreaterEq, state })) + Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: Box::new(SideExitReason::GuardGreaterEq), state })) } // Inlines String#ascii_only? (rb_str_is_ascii_only_p): coderange == 7BIT. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index de6f5e2ccccc4b..07bad0df572326 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -856,6 +856,19 @@ pub struct CCallWithFrameData { pub block: Option, } +/// Payload of [`Insn::SendDirect`]. Boxed in the enum to keep `Insn` small. +#[derive(Debug, Clone)] +pub struct SendDirectData { + pub recv: InsnId, + pub cd: *const rb_call_data, + pub cme: *const rb_callable_method_entry_t, + pub iseq: IseqPtr, + pub args: Vec, + pub kw_bits: u32, + pub block: Option, + pub state: InsnId, +} + /// Payload of [`Insn::CCallVariadic`]. Boxed in the enum to keep `Insn` small. #[derive(Debug, Clone)] pub struct CCallVariadicData { @@ -1106,16 +1119,7 @@ pub enum Insn { }, /// Optimized ISEQ call - SendDirect { - recv: InsnId, - cd: *const rb_call_data, - cme: *const rb_callable_method_entry_t, - iseq: IseqPtr, - args: Vec, - kw_bits: u32, - block: Option, - state: InsnId, - }, + SendDirect(Box), /// Push a lighter weight frame used for inlined methods. PushInlineFrame { @@ -1141,7 +1145,7 @@ pub enum Insn { args: Vec, state: InsnId, leaf: bool, - return_type: Option, // None for unannotated builtins + return_type: Type, // BasicObject for unannotated builtins }, /// Set up frame. Remember the address as the JIT entry for the insn_idx in `jit_entry_insns()[jit_entry_idx]`. @@ -1190,15 +1194,15 @@ pub enum Insn { /// Side-exit if val doesn't have the expected type. GuardType { val: InsnId, guard_type: Type, state: InsnId, recompile: Option }, /// Side-exit if val is not the expected Const. - GuardBitEquals { val: InsnId, expected: Const, reason: SideExitReason, state: InsnId, recompile: Option }, + GuardBitEquals { val: InsnId, expected: Const, reason: Box, state: InsnId, recompile: Option }, /// Side-exit if (val & mask) == 0 - GuardAnyBitSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId, recompile: Option }, + GuardAnyBitSet { val: InsnId, mask: Const, mask_name: Option, reason: Box, state: InsnId, recompile: Option }, /// Side-exit if (val & mask) != 0 - GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId }, + GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: Box, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). - GuardGreaterEq { left: InsnId, right: InsnId, reason: SideExitReason, state: InsnId }, + GuardGreaterEq { left: InsnId, right: InsnId, reason: Box, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). - GuardLess { left: InsnId, right: InsnId, reason: SideExitReason, state: InsnId }, + GuardLess { left: InsnId, right: InsnId, reason: Box, state: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -1207,7 +1211,7 @@ pub enum Insn { /// Side-exit into the interpreter. /// If recompile is not None, the side exit will profile and invalidate the ISEQ /// so that it gets recompiled with the new profile data. - SideExit { state: InsnId, reason: SideExitReason, recompile: Option }, + SideExit { state: InsnId, reason: Box, recompile: Option }, /// Increment a counter in ZJIT stats IncrCounter(Counter), @@ -1448,7 +1452,6 @@ macro_rules! for_each_operand_impl { } Insn::Send { recv, args, state, .. } | Insn::SendForward { recv, args, state, .. } - | Insn::SendDirect { recv, args, state, .. } | Insn::PushInlineFrame { recv, args, state, .. } | Insn::InvokeBuiltin { recv, args, state, .. } | Insn::InvokeSuper { recv, args, state, .. } @@ -1458,9 +1461,14 @@ macro_rules! for_each_operand_impl { $visit_many!(args); $visit_one!(*state); } - // CCallWithFrame/CCallVariadic carry their operands behind a Box, which - // stable Rust can't destructure in a pattern. visit_one takes a place, so + // SendDirect/CCallWithFrame/CCallVariadic carry their operands behind a Box, + // which stable Rust can't destructure in a pattern. visit_one takes a place, so // a box field works the same as the deref'd bindings used by other arms. + Insn::SendDirect(insn) => { + $visit_one!(insn.recv); + $visit_many!(insn.args); + $visit_one!(insn.state); + } Insn::CCallWithFrame(insn) => { $visit_one!(insn.recv); $visit_many!(insn.args); @@ -1727,7 +1735,7 @@ impl Insn { Insn::InvokeSuperForward { .. } => effects::Any, Insn::InvokeBlock { .. } => effects::Any, Insn::InvokeBlockIfunc { .. } => effects::Any, - Insn::SendDirect { .. } => effects::Any, + Insn::SendDirect(_) => effects::Any, // TODO (nirvdrum 2026-05-28): Revisit when PushInlineFrame is // actually lightweight. The frame writes here pay for the spill // ceremony in the current full frame-push codegen. A lightweight @@ -2042,7 +2050,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"), Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::CondBranch { val, if_true, if_false } => { write!(f, "CondBranch {val}, {if_true}, {if_false}") }, - Insn::SendDirect { recv, cme, iseq, args, block, .. } => { + Insn::SendDirect(insn) => { + let SendDirectData { recv, cme, iseq, args, block, .. } = &**insn; let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() }); let method_name = unsafe { (**cme).called_id }; write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), method_name, self.ptr_map.map_ptr(iseq))?; @@ -2628,8 +2637,8 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, - // Builtin descriptor and return type (if known) - InvokeLeafBuiltin(*const rb_builtin_function, Option), + // Builtin descriptor and return type + InvokeLeafBuiltin(*const rb_builtin_function, Type), } unsafe extern "C" { @@ -2699,8 +2708,7 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: if !leaf { return None; } // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); Some(IseqReturn::InvokeLeafBuiltin(bf, return_type)) } _ => None, @@ -2930,7 +2938,7 @@ impl Function { if self.assume_bop_not_redefined(block, klass, bop, state) { return true; } - self.push_insn(block, Insn::SideExit { state, reason: SideExitReason::PatchPoint(Invariant::BOPRedefined { klass, bop }), recompile: None }); + self.push_insn(block, Insn::SideExit { state, reason: Box::new(SideExitReason::PatchPoint(Invariant::BOPRedefined { klass, bop })), recompile: None }); false } @@ -3113,7 +3121,7 @@ impl Function { Insn::FixnumLShift { .. } => types::Fixnum, Insn::FixnumRShift { .. } => types::Fixnum, Insn::PutSpecialObject { .. } => types::BasicObject, - Insn::SendDirect { .. } => types::BasicObject, + Insn::SendDirect(_) => types::BasicObject, Insn::Send { .. } => types::BasicObject, Insn::SendForward { .. } => types::BasicObject, Insn::InvokeSuper { .. } => types::BasicObject, @@ -3121,7 +3129,7 @@ impl Function { Insn::InvokeBlock { .. } => types::BasicObject, Insn::InvokeBlockIfunc { .. } => types::BasicObject, Insn::InvokeProc { .. } => types::BasicObject, - Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject), + Insn::InvokeBuiltin { return_type, .. } => *return_type, Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstant { .. } => types::BasicObject, @@ -3670,12 +3678,12 @@ impl Function { pub fn guard_not_frozen(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), mask_name: Some(ID!(RUBY_FL_FREEZE)), reason: SideExitReason::GuardNotFrozen, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), mask_name: Some(ID!(RUBY_FL_FREEZE)), reason: Box::new(SideExitReason::GuardNotFrozen), state }); } pub fn guard_not_shared(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: SideExitReason::GuardNotShared, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: Box::new(SideExitReason::GuardNotShared), state }); } /// `iseq` is the ISEQ that `ep_offset` is relative to, which is the ISEQ that @@ -3746,9 +3754,14 @@ impl Function { /// Try trivially inlining the method. If we can't, emit a SendDirect instruction instead and /// leave it to the general-purpose inliner to handle. fn try_inline_send_direct(&mut self, block: BlockId, insn: Insn) -> InsnId { - let Insn::SendDirect { recv, iseq, cd, ref args, state, .. } = insn else { + let Insn::SendDirect(data) = &insn else { panic!("try_inline_send_direct called with non-SendDirect instruction"); }; + let recv = data.recv; + let iseq = data.iseq; + let cd = data.cd; + let state = data.state; + let args = &data.args; // The trivial inliner runs first to handle simple cases (constant returns, // parameter returns, etc.) without frame push/pop overhead. The general // inliner then handles more complex methods that require full inlining. @@ -3902,7 +3915,7 @@ impl Function { self.insn_types[recv.0] = self.infer_type(recv); } - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: send_block }); + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: send_block }))); self.make_equal_to(insn_id, replacement); } else if !has_block && def_type == VM_METHOD_TYPE_BMETHOD { let procv = unsafe { rb_get_def_bmethod_proc((*cme).def) }; @@ -3945,7 +3958,7 @@ impl Function { recv = self.guard_type_recompile(block, recv, Type::from_profiled_type(profiled_type), state, Recompile); } - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: None }); + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: processed_args, kw_bits, state: send_state, block: None }))); self.make_equal_to(insn_id, replacement); } else if !has_block && def_type == VM_METHOD_TYPE_IVAR && args.is_empty() { // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. @@ -4383,13 +4396,13 @@ impl Function { // Load ep[VM_ENV_DATA_INDEX_ME_CREF] let method_entry = fun.load_field(block, lep, FieldName::VM_ENV_DATA_INDEX_ME_CREF, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF, types::RubyValue); // Guard that it matches the expected CME - fun.push_insn(block, Insn::GuardBitEquals { val: method_entry, expected: Const::Value(current_cme.into()), reason: SideExitReason::GuardSuperMethodEntry, state, recompile: None }); + fun.push_insn(block, Insn::GuardBitEquals { val: method_entry, expected: Const::Value(current_cme.into()), reason: Box::new(SideExitReason::GuardSuperMethodEntry), state, recompile: None }); let block_handler = fun.load_field(block, lep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, types::RubyValue); fun.push_insn(block, Insn::GuardBitEquals { val: block_handler, expected: Const::Value(VALUE(VM_BLOCK_HANDLER_NONE as usize)), - reason: SideExitReason::UnhandledBlockArg, + reason: Box::new(SideExitReason::UnhandledBlockArg), state, recompile: None, }); @@ -4490,7 +4503,7 @@ impl Function { emit_super_call_guards(self, block, super_cme, current_cme, mid, state, frame_state.iseq); // Use SendDirect with the super method's CME and ISEQ. - let replacement = self.try_inline_send_direct(block, Insn::SendDirect { + let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme: super_cme, @@ -4499,7 +4512,7 @@ impl Function { kw_bits, state: send_state, block: None, - }); + }))); self.make_equal_to(insn_id, replacement); } else if def_type == VM_METHOD_TYPE_CFUNC { @@ -4769,18 +4782,18 @@ impl Function { let mut search_start = 0; loop { let Some(offset) = self.blocks[block.0].insns[search_start..].iter() - .position(|&id| matches!(self.find(id), Insn::SendDirect { .. })) + .position(|&id| matches!(self.find(id), Insn::SendDirect(..))) else { break; }; let send_pos = search_start + offset; let send_insn_id = self.blocks[block.0].insns[send_pos]; - let Insn::SendDirect { recv, cd: _, cme, iseq, args, kw_bits, block: call_block, state } - = self.find(send_insn_id) + let Insn::SendDirect(data) = self.find(send_insn_id) else { unreachable!("position {send_insn_id} is not a SendDirect"); }; + let SendDirectData { recv, cme, iseq, args, kw_bits, block: call_block, state, .. } = *data; // SendDirect invariant: block is either None or BlockIseq. // BlockArg is rejected upstream during type specialization. let blockiseq: Option = call_block.map(|bh| match bh { @@ -4810,6 +4823,27 @@ impl Function { let pre_insn_types_len = self.insn_types.len(); let pre_blocks_len = self.blocks.len(); + // Pick the callee body entry matching how many optional parameters + // the caller actually filled. Entry index `k` is where execution + // begins when `lead_num + k + post_num + kw_num` arguments are + // passed: it runs the default-init code for the remaining + // `opt_num - k` optionals (if any) before falling through into the + // post-default body. SendDirect emission already guarantees + // args.len() lies in `lead_num + post_num + kw_num..=lead_num + + // opt_num + post_num + kw_num`, with `kw_num` being the callee's + // full keyword count (zero when the callee has no keywords). After + // `prepare_direct_send_args` runs, the caller's arg list has a slot + // for every callee keyword (filled in callee table order, padded + // with defaults for omitted optional keywords), so the keyword tail + // is a fixed-size addition we subtract off before recovering the + // optional-positional count. + let callee_params = unsafe { iseq.params() }; + let lead_num = callee_params.lead_num as usize; + let opt_num = callee_params.opt_num as usize; + let post_num = callee_params.post_num as usize; + let kw_num = callee_kw_num(iseq); + let passed_opt_num = args.len() - lead_num - post_num - kw_num; + // Create the continuation block before translating the callee so it // can serve as the return_block argument; the callee's leaves become // Jumps to this block directly during translation. The continuation @@ -4825,7 +4859,12 @@ impl Function { // callee sits one level deeper (caller_depth + 1). let caller_depth = self.frame_depth(state); - let mode = AddIseqMode::Inlined { return_block: continuation, caller: state, depth: caller_depth + 1 }; + let mode = AddIseqMode::Inlined { + return_block: continuation, + caller: state, + depth: caller_depth + 1, + jit_entry_idx: passed_opt_num, + }; let add_result = match add_iseq_to_hir(self, iseq, mode) { Ok(r) => r, Err(_) => { @@ -4852,33 +4891,13 @@ impl Function { // pre-Send body, before the PushLightweightFrame and Jump we add // last). let tail = self.blocks[block.0].insns.split_off(send_pos); - debug_assert!(matches!(self.find(tail[0]), Insn::SendDirect { .. })); + debug_assert!(matches!(self.find(tail[0]), Insn::SendDirect(..))); - // Pick the callee body entry block matching how many optional - // parameters the caller actually filled. add_iseq_to_hir returns one - // body entry block per `jit_entry_idx`; entry index `k` is where - // execution begins when `lead_num + k + post_num + kw_num` arguments - // are passed: it runs the default-init code for the remaining - // `opt_num - k` optionals (if any) before falling through into the - // post-default body. SendDirect emission already guarantees - // args.len() lies in `lead_num + post_num + kw_num..=lead_num + - // opt_num + post_num + kw_num`, with `kw_num` being the callee's - // full keyword count (zero when the callee has no keywords). After - // `prepare_direct_send_args` runs, the caller's arg list has a slot - // for every callee keyword (filled in callee table order, padded - // with defaults for omitted optional keywords), so the keyword tail - // is a fixed-size addition we subtract off before recovering the - // optional-positional count. - let callee_params = unsafe { iseq.params() }; - let lead_num = callee_params.lead_num as usize; - let opt_num = callee_params.opt_num as usize; - let post_num = callee_params.post_num as usize; - let kw_num = callee_kw_num(iseq); - let passed_opt_num = args.len() - lead_num - post_num - kw_num; let omitted_opt_num = opt_num - passed_opt_num; let positional_kw_end = lead_num + opt_num + post_num + kw_num; let kw_bits_local_idx = callee_kw_bits_local_idx(iseq); - let callee_entry_body_block = add_result.body_entry_blocks[passed_opt_num]; + let callee_entry_body_block = add_result.body_entry_block + .expect("inlined compilation always produces a body entry block"); // Map callee body entry params to caller values: // @@ -4891,9 +4910,7 @@ impl Function { // * lead locals (indices 0..lead_num) and filled optional locals // (lead_num..lead_num + passed_opt_num) take args in order. // * unfilled optional locals (lead_num + passed_opt_num..lead_num + opt_num) - // are nil-initialized; the body's default-init code (entered via - // the chosen body_entry_blocks[passed_opt_num] target) overwrites - // them. + // are nil-initialized; the body's default-init code overwrites them. // * post-required and keyword locals // (lead_num + opt_num..lead_num + opt_num + post_num + kw_num) take the // trailing args, but their position in the local table leaves a gap @@ -5016,7 +5033,7 @@ impl Function { self.push_insn(block, Insn::GuardBitEquals { val, expected: Const::CShape(expected), - reason: SideExitReason::GuardShape(expected), + reason: Box::new(SideExitReason::GuardShape(expected)), state, recompile, }) @@ -5331,7 +5348,7 @@ impl Function { for insn_id in old_insns { match self.find(insn_id) { Insn::Send { state, reason: SendFallbackReason::SendWithoutBlockNoProfiles | SendFallbackReason::SendNoProfiles, .. } => { - self.push_insn(block, Insn::SideExit { state, reason: SideExitReason::NoProfileSend, recompile: Some(Recompile) }); + self.push_insn(block, Insn::SideExit { state, reason: Box::new(SideExitReason::NoProfileSend), recompile: Some(Recompile) }); // SideExit is a terminator; don't add remaining instructions break; } @@ -5509,7 +5526,7 @@ impl Function { // pass. Every execution would side-exit here, so we replace the guard with an // unconditional exit. The terminator handling below then drops the rest of // the block, which is now unreachable. - self.new_insn(Insn::SideExit { state, reason: SideExitReason::GuardType(guard_type), recompile }) + self.new_insn(Insn::SideExit { state, reason: Box::new(SideExitReason::GuardType(guard_type)), recompile }) } Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => { self.make_equal_to(insn_id, val); @@ -6473,8 +6490,7 @@ impl Function { self.assert_subtype(insn_id, allow_nil, types::BoolExact) } // Instructions with recv and a Vec of Ruby objects - Insn::SendDirect { recv, ref args, .. } - | Insn::PushInlineFrame { recv, ref args, .. } + Insn::PushInlineFrame { recv, ref args, .. } | Insn::Send { recv, ref args, .. } | Insn::SendForward { recv, ref args, .. } | Insn::InvokeSuper { recv, ref args, .. } @@ -6488,6 +6504,13 @@ impl Function { } Ok(()) } + Insn::SendDirect(insn) => { + self.assert_subtype(insn_id, insn.recv, types::BasicObject)?; + for &arg in &insn.args { + self.assert_subtype(insn_id, arg, types::BasicObject)?; + } + Ok(()) + } Insn::CCallWithFrame(insn) => { self.assert_subtype(insn_id, insn.recv, types::BasicObject)?; for &arg in &insn.args { @@ -7222,25 +7245,25 @@ enum AddIseqMode { caller: InsnId, /// Inlining depth of every frame emitted for the callee. depth: InlineDepth, + /// The JIT entry index selected by the call site's argument count. + jit_entry_idx: usize, }, } /// Result of populating a Function with HIR for an ISEQ. struct AddIseqResult { - /// Body entry block per `jit_entry_idx`. Indexed by the same `passed_opt_num` - /// the JIT entry scaffolding uses, this is the body block the scaffolding - /// (when generated) targets after running default-init code for missing - /// optionals. Callers that pass `make_entry_blocks=false` (e.g. the inliner) - /// use this to pick the right entry point without needing the scaffolding. - body_entry_blocks: Vec, + /// The callee body entry block where the inlined callee body begins + /// executing, populated only in inlined mode. + /// + /// `add_iseq_to_hir` slices the opt table to begin at the entry the call + /// site's argument count selects. This is that entry's block. + /// The value is `None` in standalone mode, as standalone has no single body entry + /// block and wires its JIT entry blocks through `fun.jit_entry_blocks` instead. + body_entry_block: Option, /// Profile oracle populated during compilation. The caller decides whether /// to assign it to `fun.profiles` (top-level) or append it to an existing /// oracle (inliner). profiles: ProfileOracle, - /// Number of return paths emitted. Counts `YARVINSN_leave` translations, - /// whether they became `Insn::Return` or `Insn::Jump(return_block, ...)`. - /// Lets callers tell whether the ISEQ has any reachable return paths. - num_returns: usize, } /// Compile ISEQ into High-level IR @@ -7287,7 +7310,6 @@ fn add_iseq_to_hir( ) -> Result { let payload = get_or_create_iseq_payload(iseq); let mut profiles = ProfileOracle::new(); - let mut num_returns: usize = 0; // Build the initial FrameState for a block being translated. In inlined // mode it carries the caller's call-site Snapshot and this frame's depth; @@ -7301,8 +7323,23 @@ fn add_iseq_to_hir( } } - // Compute a map of PC->Block by finding jump targets - let jit_entry_insns = unsafe { iseq.params() }.opt_table_slice().iter().copied().map(VALUE::as_u32).collect::>(); + // Compute a map of PC->Block by finding jump targets. + // + // Standalone compilation translates every opt-table entry because each is a + // reachable JIT-to-JIT entrypoint. The inliner, by contrast, enters at a + // single entry fixed by the call site's argument count. So, the entries before + // it would run default-init code for optionals the caller already supplied. + // Those entries are known to be unreachable so slicing them off here avoids + // translating prologue blocks that would only be discarded later, rather + // than emitting them and relying on a downstream pass to prune the dead CFG. + let jit_entry_start = match mode { + AddIseqMode::Standalone => 0, + AddIseqMode::Inlined { jit_entry_idx, .. } => jit_entry_idx, + }; + let jit_entry_insns = unsafe { iseq.params() }.opt_table_slice() + .get(jit_entry_start..) + .expect("JIT entry index must be within the callee opt table") + .iter().copied().map(VALUE::as_u32).collect::>(); let BytecodeInfo { jump_targets } = compute_bytecode_info(iseq, &jit_entry_insns); let compile_jit_entries = matches!(mode, AddIseqMode::Standalone) && iseq_supports_jit_entry(iseq); @@ -7310,15 +7347,18 @@ fn add_iseq_to_hir( // Make all empty basic blocks. The ordering of the BBs matters for getting fallthrough jumps // in good places, but it's not necessary for correctness. TODO: Higher quality scheduling during lowering. let mut insn_idx_to_block = HashMap::new(); - let mut body_entry_blocks = Vec::with_capacity(jit_entry_insns.len()); - // Make blocks for optionals first, and put them right next to their JIT entrypoint + // Materialize a block at each opt-table entry PC, placing each right after + // its JIT-to-JIT entry block so fallthrough jumps land in good places. + // Standalone mode emits a real JIT entry block per entry. Inlined mode emits + // none here; only `body_entry_block` (the first sliced entry) is the inlined + // body's entry, and the rest are default-init body blocks reached by + // fallthrough. for insn_idx in jit_entry_insns.iter().copied() { if compile_jit_entries { let jit_entry_block = fun.new_block(insn_idx); fun.jit_entry_blocks.push(jit_entry_block); } - let body_entry = *insn_idx_to_block.entry(insn_idx).or_insert_with(|| fun.new_block(insn_idx)); - body_entry_blocks.push(body_entry); + insn_idx_to_block.entry(insn_idx).or_insert_with(|| fun.new_block(insn_idx)); } // Make blocks for the rest of the jump targets for insn_idx in jump_targets { @@ -7327,6 +7367,17 @@ fn add_iseq_to_hir( // Done, drop `mut`. let insn_idx_to_block = insn_idx_to_block; + // The callee body entry block where the inlined callee body begins + // executing. `jit_entry_insns` starts at the entry the call site selected + // (since `opt_table_slice` was sliced above), so its first element is that entry. + // Any later entries are the default-init blocks for the remaining unfilled + // optionals, reached from this one by fallthrough rather than targeted directly. + // Standalone compilation has no single body entry block, so it produces none. + let body_entry_block = match mode { + AddIseqMode::Standalone => None, + AddIseqMode::Inlined { .. } => Some(insn_idx_to_block[&jit_entry_insns[0]]), + }; + if matches!(mode, AddIseqMode::Standalone) { // Compile an entry_block for the interpreter compile_entry_block(fun, jit_entry_insns.as_slice(), &insn_idx_to_block); @@ -7576,7 +7627,7 @@ fn add_iseq_to_hir( } _ => { // Unknown opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledNewarraySend(method), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledNewarraySend(method)), recompile: None }); break; // End the block } }; @@ -7602,7 +7653,7 @@ fn add_iseq_to_hir( let bop = match method_id { x if x == ID!(include_p).0 => BOP_INCLUDE_P, _ => { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledDuparraySend(method_id), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledDuparraySend(method_id)), recompile: None }); break; }, }; @@ -7649,11 +7700,11 @@ fn add_iseq_to_hir( .and_then(|types| types.first()) .map(|dist| TypeDistributionSummary::new(dist)); let Some(summary) = summary else { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwNotProfiled, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwNotProfiled), recompile: None }); break; // End the block }; if !summary.is_monomorphic() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwPolymorphic, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwPolymorphic), recompile: None }); break; // End the block } let ty = Type::from_profiled_type(summary.bucket(0)); @@ -7662,7 +7713,7 @@ fn add_iseq_to_hir( } else if ty.is_subtype(types::HashExact) { fun.push_insn(block, Insn::GuardType { val: hash, guard_type: types::HashExact, state: exit_id, recompile: None }) } else { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SplatKwNotNilOrHash, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SplatKwNotNilOrHash), recompile: None }); break; // End the block }; state.stack_push(obj); @@ -7808,7 +7859,7 @@ fn add_iseq_to_hir( // This can only happen in iseqs taking more than 32 keywords. // In this case, we side exit to the interpreter. if unsafe {(*rb_get_iseq_body_param_keyword(iseq)).num >= VM_KW_SPECIFIED_BITS_MAX.try_into().unwrap()} { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyKeywordParameters, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyKeywordParameters), recompile: None }); break; } let ep_offset = get_arg(pc, 0).as_u32(); @@ -8200,7 +8251,7 @@ fn add_iseq_to_hir( // So to check for either of those cases we can use: val & 0x1 == 0x1 // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyFallbackMiss, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: Box::new(SideExitReason::BlockParamProxyFallbackMiss), state: exit_id, recompile: Some(Recompile) }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing let proxy_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }); let mut args = vec![proxy_val]; @@ -8213,7 +8264,7 @@ fn add_iseq_to_hir( [profiled_handler] => match profiled_handler { ProfiledBlockHandlerFamily::Nil => { let block_handler = fun.load_ep_env_field(unmodified_block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64); - fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: SideExitReason::BlockParamProxyNotNil, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: block_handler, expected: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()), reason: Box::new(SideExitReason::BlockParamProxyNotNil), state: exit_id, recompile: Some(Recompile) }); let nil_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(Qnil) }); let mut args = vec![nil_val]; if let Some(local) = original_local { @@ -8230,7 +8281,7 @@ fn add_iseq_to_hir( // So to check for either of those cases we can use: val & 0x1 == 0x1 // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: Box::new(SideExitReason::BlockParamProxyNotIseqOrIfunc), state: exit_id, recompile: Some(Recompile) }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing let proxy_val = fun.push_insn(unmodified_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) }); let mut args = vec![proxy_val]; @@ -8250,7 +8301,7 @@ fn add_iseq_to_hir( return_type: types::BasicObject, elidable: true, }); - fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: is_proc, expected: Const::Value(Qtrue), reason: SideExitReason::BlockParamProxyNotProc, state: exit_id, recompile: Some(Recompile) }); + fun.push_insn(unmodified_block, Insn::GuardBitEquals { val: is_proc, expected: Const::Value(Qtrue), reason: Box::new(SideExitReason::BlockParamProxyNotProc), state: exit_id, recompile: Some(Recompile) }); let mut args = vec![proc_val]; if let Some(local) = original_local { args.push(local); @@ -8354,7 +8405,7 @@ fn add_iseq_to_hir( } } - fun.push_insn(current_block, Insn::SideExit { state: exit_id, reason: SideExitReason::BlockParamProxyProfileNotCovered, recompile: None }); + fun.push_insn(current_block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::BlockParamProxyProfileNotCovered), recompile: None }); } } @@ -8447,7 +8498,7 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle the call type; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } let argc = crate::profile::num_arguments_on_stack(cd); @@ -8455,7 +8506,7 @@ fn add_iseq_to_hir( // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(argc as usize)?; @@ -8502,7 +8553,6 @@ fn add_iseq_to_hir( YARVINSN_leave => { fun.push_insn(block, Insn::CheckInterrupts { state: exit_id }); let val = state.stack_pop()?; - num_returns += 1; match mode { AddIseqMode::Standalone => fun.push_insn(block, Insn::Return { val }), AddIseqMode::Inlined { return_block, .. } => { fun.push_insn(block, Insn::Jump(BranchEdge { target: return_block, args: vec![val] })) } @@ -8546,7 +8596,7 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } let argc = crate::profile::num_arguments_on_stack(cd); @@ -8563,7 +8613,7 @@ fn add_iseq_to_hir( if mid == ID!(induce_side_exit_bang) && state::zjit_module_method_match_serial(ID!(induce_side_exit_bang), &state::INDUCE_SIDE_EXIT_SERIAL) { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::DirectiveInduced, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::DirectiveInduced), recompile: None }); break; // End the block } if mid == ID!(induce_compile_failure_bang) @@ -8582,7 +8632,7 @@ fn add_iseq_to_hir( // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } @@ -8640,12 +8690,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; @@ -8678,12 +8728,12 @@ fn add_iseq_to_hir( let forwarding = (flags & VM_CALL_FORWARDING) != 0; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle the call type; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -8707,12 +8757,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?; @@ -8737,12 +8787,12 @@ fn add_iseq_to_hir( let forwarding = (flags & VM_CALL_FORWARDING) != 0; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -8765,12 +8815,12 @@ fn add_iseq_to_hir( let flags = unsafe { rb_vm_ci_flag(call_info) }; if let Err(call_type) = unhandled_call_type(flags) { // Can't handle tailcall; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledCallType(call_type)), recompile: None }); break; // End the block } // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::SendWhileTracing), recompile: None }); break; } let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?; @@ -8851,7 +8901,7 @@ fn add_iseq_to_hir( // TODO: We only really need this if self_val is a class/module if !fun.assume_single_ractor_mode(block, exit_id) { // gen_getivar assumes single Ractor; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } if let Some(summary) = fun.polymorphic_summary(&profiles, self_param, exit_id) { @@ -8924,7 +8974,7 @@ fn add_iseq_to_hir( // TODO: We only really need this if self_val is a class/module if !fun.assume_single_ractor_mode(block, exit_id) { // gen_setivar assumes single Ractor; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } let val = state.stack_pop()?; @@ -8976,7 +9026,7 @@ fn add_iseq_to_hir( // TODO: Support passing arguments on the stack in C calls // +2 for ec, self if (unsafe { (*bf).argc } + 2) as usize > C_ARG_OPNDS.len() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyArgsForLir, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyArgsForLir), recompile: None }); break; // End the block } @@ -8989,8 +9039,7 @@ fn add_iseq_to_hir( // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; @@ -9012,7 +9061,7 @@ fn add_iseq_to_hir( // +2 for ec, self let argc = unsafe { (*bf).argc } as usize; if argc + 2 > C_ARG_OPNDS.len() { - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::TooManyArgsForLir, recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::TooManyArgsForLir), recompile: None }); break; // End the block } @@ -9025,8 +9074,7 @@ fn add_iseq_to_hir( // Check if this builtin is annotated let return_type = ZJITState::get_method_annotations() - .get_builtin_properties(bf) - .map(|props| props.return_type); + .get_builtin_return_type(bf); let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; @@ -9092,7 +9140,7 @@ fn add_iseq_to_hir( if svar == 0 { // TODO: Handle non-backref - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownSpecialVariable(key), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnknownSpecialVariable(key)), recompile: None }); // End the block break; } else if svar & 0x01 != 0 { @@ -9115,14 +9163,14 @@ fn add_iseq_to_hir( // (reverse?) // // Unhandled opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } let val = state.stack_pop()?; let array = fun.push_insn(block, Insn::GuardType { val, guard_type: types::ArrayExact, state: exit_id, recompile: None }); let length = fun.push_insn(block, Insn::ArrayLength { array }); let expected = fun.push_insn(block, Insn::Const { val: Const::CInt64(num as i64) }); - fun.push_insn(block, Insn::GuardGreaterEq { left: length, right: expected, reason: SideExitReason::ExpandArray, state: exit_id }); + fun.push_insn(block, Insn::GuardGreaterEq { left: length, right: expected, reason: Box::new(SideExitReason::ExpandArray), state: exit_id }); for i in (0..num).rev() { // We do not emit a length guard here because in-bounds is already // ensured by the expandarray length check above. @@ -9133,7 +9181,7 @@ fn add_iseq_to_hir( } _ => { // Unhandled opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledYARVInsn(opcode), recompile: None }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: Box::new(SideExitReason::UnhandledYARVInsn(opcode)), recompile: None }); break; // End the block } } @@ -9162,7 +9210,7 @@ fn add_iseq_to_hir( } } - Ok(AddIseqResult { body_entry_blocks, profiles, num_returns }) + Ok(AddIseqResult { body_entry_block, profiles }) } /// Compile an entry_block for the interpreter diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index bebf7d693dceb0..b82417d44aa7e7 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -1800,12 +1800,12 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile PushInlineFrame v20 (0x1038), v11 - v35:Fixnum[2] = Const Value(2) + v27:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) - v61:Fixnum[5] = Const Value(5) + v53:Fixnum[5] = Const Value(5) CheckInterrupts PopInlineFrame - Return v61 + Return v53 "); } @@ -1833,10 +1833,10 @@ mod hir_opt_tests { v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile PushInlineFrame v22 (0x1038), v11, v13 PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) - v62:Fixnum[7] = Const Value(7) + v46:Fixnum[7] = Const Value(7) CheckInterrupts PopInlineFrame - Return v62 + Return v46 "); } @@ -1873,8 +1873,8 @@ mod hir_opt_tests { v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) PushInlineFrame v44 (0x1038), v14, v16, v18 - v151:Fixnum[4] = Const Value(4) - v166:ArrayExact = NewArray v14, v16, v18, v151 + v121:Fixnum[4] = Const Value(4) + v136:ArrayExact = NewArray v14, v16, v18, v121 PopInlineFrame v24:Fixnum[10] = Const Value(10) v26:Fixnum[20] = Const Value(20) @@ -1882,7 +1882,7 @@ mod hir_opt_tests { v30:Fixnum[40] = Const Value(40) v32:Fixnum[50] = Const Value(50) v34:BasicObject = Send v44, :target, v24, v26, v28, v30, v32 # SendFallbackReason: Argument count does not match parameter count - v37:ArrayExact = NewArray v101, v166, v34 + v37:ArrayExact = NewArray v101, v136, v34 CheckInterrupts Return v37 "); @@ -4191,11 +4191,11 @@ mod hir_opt_tests { v22:Fixnum[2] = Const Value(2) v24:Fixnum[4] = Const Value(4) v26:Fixnum[3] = Const Value(3) - v103:Fixnum[0] = Const Value(0) + v92:Fixnum[0] = Const Value(0) PushInlineFrame v37 (0x1038), v20, v22, v26, v24 - v98:ArrayExact = NewArray v22, v26 + v87:ArrayExact = NewArray v22, v26 PopInlineFrame - v30:ArrayExact = NewArray v65, v98 + v30:ArrayExact = NewArray v65, v87 Return v30 "); } @@ -4234,11 +4234,11 @@ mod hir_opt_tests { v20:Fixnum[2] = Const Value(2) v22:Fixnum[40] = Const Value(40) v24:Fixnum[30] = Const Value(30) - v107:Fixnum[0] = Const Value(0) + v96:Fixnum[0] = Const Value(0) PushInlineFrame v37 (0x1038), v18, v20, v24, v22 - v102:ArrayExact = NewArray v18, v20, v24, v22 + v91:ArrayExact = NewArray v18, v20, v24, v22 PopInlineFrame - v28:ArrayExact = NewArray v67, v102 + v28:ArrayExact = NewArray v67, v91 Return v28 "); } @@ -18363,14 +18363,14 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1008, add_opts@0x1010, cme:0x1018) v25:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v25 (0x1040), v10, v16 - v42:Fixnum[100] = Const Value(100) + v33:Fixnum[100] = Const Value(100) PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v71:Fixnum = GuardType v10, Fixnum recompile - v72:Fixnum = FixnumAdd v71, v16 - v76:Fixnum = FixnumAdd v72, v42 + v62:Fixnum = GuardType v10, Fixnum recompile + v63:Fixnum = FixnumAdd v62, v16 + v67:Fixnum = FixnumAdd v63, v33 CheckInterrupts PopInlineFrame - Return v76 + Return v67 "); } @@ -18585,12 +18585,12 @@ mod hir_opt_tests { v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v27 (0x1040), v10, v16, v18 PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v72:Fixnum = GuardType v10, Fixnum recompile - v73:Fixnum = FixnumAdd v72, v16 - v77:Fixnum = FixnumAdd v73, v18 + v54:Fixnum = GuardType v10, Fixnum recompile + v55:Fixnum = FixnumAdd v54, v16 + v59:Fixnum = FixnumAdd v55, v18 CheckInterrupts PopInlineFrame - Return v77 + Return v59 "); } @@ -19033,12 +19033,12 @@ mod hir_opt_tests { v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v27 (0x1040), v10, v16, v18 PatchPoint MethodRedefined(Integer@0x1048, +@0x1050, cme:0x1058) - v63:Fixnum = GuardType v10, Fixnum recompile - v64:Fixnum = FixnumAdd v63, v16 - v68:Fixnum = FixnumAdd v64, v18 + v54:Fixnum = GuardType v10, Fixnum recompile + v55:Fixnum = FixnumAdd v54, v16 + v59:Fixnum = FixnumAdd v55, v18 CheckInterrupts PopInlineFrame - Return v68 + Return v59 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index cdb19ddb3b51e1..41506143853119 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -7,7 +7,7 @@ mod size_tests { #[test] fn test_size_of_insn() { - assert_eq!(std::mem::size_of::(), 88); + assert_eq!(std::mem::size_of::(), 80); } #[test]