1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
This commit is contained in:
Benoit Daloze 2022-03-28 17:47:04 +02:00
parent ae650f0372
commit 8db4f25bf4
41 changed files with 821 additions and 56 deletions

View file

@ -39,6 +39,8 @@ CodingUS_ASCII
CodingUTF_8 CodingUTF_8
ComparisonTest ComparisonTest
ConstantSpecsIncludedModule ConstantSpecsIncludedModule
ConstantSpecsTwo
ConstantSpecsThree
ConstantVisibility ConstantVisibility
Coverage Coverage
CoverageSpecs CoverageSpecs

View file

@ -18,13 +18,13 @@ Every example code has a textual description, which presents several advantages:
The specs are written with syntax similar to RSpec 2. The specs are written with syntax similar to RSpec 2.
They are run with MSpec, the purpose-built framework for running the Ruby Spec Suite. They are run with MSpec, the purpose-built framework for running the Ruby Spec Suite.
For more information, see the [MSpec](http://github.com/ruby/mspec) project. For more information, see the [MSpec](https://github.com/ruby/mspec) project.
The specs describe the [language syntax](language/), the [core library](core/), the [standard library](library/), the [C API for extensions](optional/capi) and the [command line flags](command_line/). The specs describe the [language syntax](language/), the [core library](core/), the [standard library](library/), the [C API for extensions](optional/capi) and the [command line flags](command_line/).
The language specs are grouped by keyword while the core and standard library specs are grouped by class and method. The language specs are grouped by keyword while the core and standard library specs are grouped by class and method.
ruby/spec is known to be tested in these implementations for every commit: ruby/spec is known to be tested in these implementations for every commit:
* [MRI](http://rubyci.org/) on 30 platforms and 4 versions * [MRI](https://rubyci.org/) on 30 platforms and 4 versions
* [JRuby](https://github.com/jruby/jruby/tree/master/spec/ruby) for both 1.7 and 9.x * [JRuby](https://github.com/jruby/jruby/tree/master/spec/ruby) for both 1.7 and 9.x
* [TruffleRuby](https://github.com/oracle/truffleruby/tree/master/spec/ruby) * [TruffleRuby](https://github.com/oracle/truffleruby/tree/master/spec/ruby)
* [Opal](https://github.com/opal/opal/tree/master/spec) * [Opal](https://github.com/opal/opal/tree/master/spec)
@ -70,7 +70,7 @@ Then move to it:
$ cd spec $ cd spec
Clone [MSpec](http://github.com/ruby/mspec): Clone [MSpec](https://github.com/ruby/mspec):
$ git clone https://github.com/ruby/mspec.git ../mspec $ git clone https://github.com/ruby/mspec.git ../mspec
@ -152,5 +152,5 @@ This project was originally born from [Rubinius](https://github.com/rubinius/rub
The revision history of these specs is available [here](https://github.com/ruby/spec/blob/2b886623/CHANGES.before-2008-05-10). The revision history of these specs is available [here](https://github.com/ruby/spec/blob/2b886623/CHANGES.before-2008-05-10).
These specs were later extracted to their own project, RubySpec, with a specific vision and principles. These specs were later extracted to their own project, RubySpec, with a specific vision and principles.
At the end of 2014, Brian Shirai, the creator of RubySpec, decided to [end RubySpec](http://rubinius.com/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/). At the end of 2014, Brian Shirai, the creator of RubySpec, decided to [end RubySpec](http://rubinius.com/2014/12/31/matz-s-ruby-developers-don-t-use-rubyspec/).
A couple months later, the different repositories were merged and [the project was revived](http://eregon.github.io/rubyspec/2015/07/29/rubyspec-is-reborn.html). A couple months later, the different repositories were merged and [the project was revived](https://eregon.github.io/rubyspec/2015/07/29/rubyspec-is-reborn.html).
On 12 January 2016, the name was changed to "The Ruby Spec Suite" for clarity and to let the RubySpec ideology rest in peace. On 12 January 2016, the name was changed to "The Ruby Spec Suite" for clarity and to let the RubySpec ideology rest in peace.

View file

@ -30,6 +30,7 @@ describe "Array#pack with format 'x'" do
it "does not add a NULL byte when passed the '*' modifier" do it "does not add a NULL byte when passed the '*' modifier" do
[].pack("x*").should == "" [].pack("x*").should == ""
[1, 2].pack("Cx*C").should == "\x01\x02"
end end
end end

View file

@ -19,10 +19,18 @@ describe "Array#sample" do
[].sample.should be_nil [].sample.should be_nil
end end
it "returns nil for an empty array when called without n and a Random is given" do
[].sample(random: Random.new(42)).should be_nil
end
it "returns a single value when not passed a count" do it "returns a single value when not passed a count" do
[4].sample.should equal(4) [4].sample.should equal(4)
end end
it "returns a single value when not passed a count and a Random is given" do
[4].sample(random: Random.new(42)).should equal(4)
end
it "returns an empty Array when passed zero" do it "returns an empty Array when passed zero" do
[4].sample(0).should == [] [4].sample(0).should == []
end end

View file

@ -42,6 +42,13 @@ describe "Dir.foreach" do
it "accepts an encoding keyword for the encoding of the entries" do it "accepts an encoding keyword for the encoding of the entries" do
dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding::UTF_8} dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::UTF_16LE).to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding::UTF_16LE}
Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::UTF_16LE) do |f|
f.encoding.should == Encoding::UTF_16LE
end
end end
ruby_version_is ""..."2.7" do ruby_version_is ""..."2.7" do

View file

@ -66,4 +66,11 @@ describe :enumerable_inject, shared: true do
it "returns nil when fails(legacy rubycon)" do it "returns nil when fails(legacy rubycon)" do
EnumerableSpecs::EachDefiner.new().send(@method) {|acc,x| 999 }.should == nil EnumerableSpecs::EachDefiner.new().send(@method) {|acc,x| 999 }.should == nil
end end
ruby_bug '#18635', ''...'3.2' do
it "raises an ArgumentError when no parameters or block is given" do
-> { [1,2].send(@method) }.should raise_error(ArgumentError)
-> { {one: 1, two: 2}.send(@method) }.should raise_error(ArgumentError)
end
end
end end

View file

@ -26,5 +26,13 @@ ruby_version_is "2.7" do
(0..Float::INFINITY).lazy.with_index { |i, idx| result << [i * 2, idx] }.first(3) (0..Float::INFINITY).lazy.with_index { |i, idx| result << [i * 2, idx] }.first(3)
result.should == [[0,0],[2,1],[4,2]] result.should == [[0,0],[2,1],[4,2]]
end end
it "resets after a new call to each" do
enum = (0..2).lazy.with_index.map { |i, idx| [i, idx] }
result = []
enum.each { |i, idx| result << [i, idx] }
enum.each { |i, idx| result << [i, idx] }
result.should == [[0,0], [1,1], [2,2], [0,0], [1,1], [2,2]]
end
end end
end end

View file

@ -15,13 +15,19 @@ describe "Exception#full_message" do
it "supports :highlight option and adds escape sequences to highlight some strings" do it "supports :highlight option and adds escape sequences to highlight some strings" do
e = RuntimeError.new("Some runtime error") e = RuntimeError.new("Some runtime error")
full_message = e.full_message(highlight: true, order: :bottom) full_message = e.full_message(highlight: true, order: :top).lines
full_message.should include "\e[1mTraceback\e[m (most recent call last)" full_message[0].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
full_message.should include "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)"
full_message = e.full_message(highlight: false, order: :bottom) full_message = e.full_message(highlight: true, order: :bottom).lines
full_message.should include "Traceback (most recent call last)" full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n"
full_message.should include "Some runtime error (RuntimeError)" full_message[-1].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
full_message = e.full_message(highlight: false, order: :top).lines
full_message[0].should.end_with? "Some runtime error (RuntimeError)\n"
full_message = e.full_message(highlight: false, order: :bottom).lines
full_message[0].should == "Traceback (most recent call last):\n"
full_message[-1].should.end_with? "Some runtime error (RuntimeError)\n"
end end
it "supports :order option and places the error message and the backtrace at the top or the bottom" do it "supports :order option and places the error message and the backtrace at the top or the bottom" do
@ -35,9 +41,9 @@ describe "Exception#full_message" do
it "shows the caller if the exception has no backtrace" do it "shows the caller if the exception has no backtrace" do
e = RuntimeError.new("Some runtime error") e = RuntimeError.new("Some runtime error")
e.backtrace.should == nil e.backtrace.should == nil
full_message = e.full_message(highlight: false, order: :top) full_message = e.full_message(highlight: false, order: :top).lines
full_message.should include("#{__FILE__}:#{__LINE__-1}:in `") full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in `")
full_message.should include("': Some runtime error (RuntimeError)\n") full_message[0].should.end_with?("': Some runtime error (RuntimeError)\n")
end end
it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do
@ -45,12 +51,24 @@ describe "Exception#full_message" do
line = __LINE__; raise "first line\nsecond line" line = __LINE__; raise "first line\nsecond line"
rescue => e rescue => e
full_message = e.full_message(highlight: false, order: :top).lines full_message = e.full_message(highlight: false, order: :top).lines
full_message[0].should include("#{__FILE__}:#{line}:in `") full_message[0].should.start_with?("#{__FILE__}:#{line}:in `")
full_message[0].should include(": first line (RuntimeError)\n") full_message[0].should.end_with?(": first line (RuntimeError)\n")
full_message[1].should == "second line\n" full_message[1].should == "second line\n"
end end
end end
it "highlights the entire message when the message contains multiple lines" do
begin
line = __LINE__; raise "first line\nsecond line\nthird line"
rescue => e
full_message = e.full_message(highlight: true, order: :top).lines
full_message[0].should.start_with?("#{__FILE__}:#{line}:in `")
full_message[0].should.end_with?(": \e[1mfirst line (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n")
full_message[1].should == "\e[1msecond line\e[m\n"
full_message[2].should == "\e[1mthird line\e[m\n"
end
end
it "contains cause of exception" do it "contains cause of exception" do
begin begin
begin begin

View file

@ -1,6 +1,48 @@
require_relative '../../spec_helper' require_relative '../../spec_helper'
describe "SystemExit" do describe "SystemExit" do
describe "#initialize" do
it "accepts a status and message" do
exc = SystemExit.new(42, "message")
exc.status.should == 42
exc.message.should == "message"
exc = SystemExit.new(true, "message")
exc.status.should == 0
exc.message.should == "message"
exc = SystemExit.new(false, "message")
exc.status.should == 1
exc.message.should == "message"
end
it "accepts a status only" do
exc = SystemExit.new(42)
exc.status.should == 42
exc.message.should == "SystemExit"
exc = SystemExit.new(true)
exc.status.should == 0
exc.message.should == "SystemExit"
exc = SystemExit.new(false)
exc.status.should == 1
exc.message.should == "SystemExit"
end
it "accepts a message only" do
exc = SystemExit.new("message")
exc.status.should == 0
exc.message.should == "message"
end
it "accepts no arguments" do
exc = SystemExit.new
exc.status.should == 0
exc.message.should == "SystemExit"
end
end
it "sets the exit status and exits silently when raised" do it "sets the exit status and exits silently when raised" do
code = 'raise SystemExit.new(7)' code = 'raise SystemExit.new(7)'
result = ruby_exe(code, args: "2>&1", exit_status: 7) result = ruby_exe(code, args: "2>&1", exit_status: 7)

View file

@ -77,6 +77,10 @@ describe "File.utime" do
File.mtime(@file1).nsec.should.between?(0, 123500000) File.mtime(@file1).nsec.should.between?(0, 123500000)
end end
it "returns the number of filenames in the arguments" do
File.utime(@atime.to_f, @mtime.to_f, @file1, @file2).should == 2
end
platform_is :linux do platform_is :linux do
platform_is wordsize: 64 do platform_is wordsize: 64 do
it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19)" do it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19)" do

View file

@ -40,6 +40,20 @@ ruby_version_is "2.7" do
kw.should == h kw.should == h
end end
it "copies instance variables" do
h = {a: 1}
h.instance_variable_set(:@foo, 42)
kw = Hash.ruby2_keywords_hash(h)
kw.instance_variable_get(:@foo).should == 42
end
it "copies the hash internals" do
h = {a: 1}
kw = Hash.ruby2_keywords_hash(h)
h[:a] = 2
kw[:a].should == 1
end
it "raises TypeError for non-Hash" do it "raises TypeError for non-Hash" do
-> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError) -> { Hash.ruby2_keywords_hash(nil) }.should raise_error(TypeError)
end end

View file

@ -2,7 +2,6 @@ require_relative '../../../spec_helper'
require_relative '../fixtures/classes' require_relative '../fixtures/classes'
describe :hash_to_s, shared: true do describe :hash_to_s, shared: true do
it "returns a string representation with same order as each()" do it "returns a string representation with same order as each()" do
h = { a: [1, 2], b: -2, d: -6, nil => nil } h = { a: [1, 2], b: -2, d: -6, nil => nil }
@ -95,4 +94,8 @@ describe :hash_to_s, shared: true do
{a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}' {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}'
end end
it "works for keys and values whose #inspect return a frozen String" do
{ true => false }.to_s.should == "{true=>false}"
end
end end

View file

@ -73,6 +73,11 @@ describe :io_readlines_options_19, shared: true do
result = IO.send(@method, @name, 10, &@object) result = IO.send(@method, @name, 10, &@object)
(result ? result : ScratchPad.recorded).should == IOSpecs.lines_limit (result ? result : ScratchPad.recorded).should == IOSpecs.lines_limit
end end
it "ignores the object as a limit if it is negative" do
result = IO.send(@method, @name, -2, &@object)
(result ? result : ScratchPad.recorded).should == IOSpecs.lines
end
end end
describe "when the object is a String" do describe "when the object is a String" do

View file

@ -96,4 +96,19 @@ describe "Kernel#define_singleton_method" do
o.define(:foo) { raise "not used" } o.define(:foo) { raise "not used" }
}.should raise_error(ArgumentError) }.should raise_error(ArgumentError)
end end
it "always defines the method with public visibility" do
cls = Class.new
def cls.define(name, &block)
private
define_singleton_method(name, &block)
end
-> {
suppress_warning do
cls.define(:foo) { :ok }
end
cls.foo.should == :ok
}.should_not raise_error(NoMethodError)
end
end end

View file

@ -27,6 +27,10 @@ describe "Math.sqrt" do
it "accepts any argument that can be coerced with Float()" do it "accepts any argument that can be coerced with Float()" do
Math.sqrt(MathSpecs::Float.new).should be_close(1.0, TOLERANCE) Math.sqrt(MathSpecs::Float.new).should be_close(1.0, TOLERANCE)
end end
it "raises a Math::DomainError when given a negative number" do
-> { Math.sqrt(-1) }.should raise_error(Math::DomainError)
end
end end
describe "Math#sqrt" do describe "Math#sqrt" do

View file

@ -100,6 +100,16 @@ describe "Module#const_get" do
ConstantSpecs.const_get("::CS_CONST1").should == :const1 ConstantSpecs.const_get("::CS_CONST1").should == :const1
end end
it "accepts a toplevel scope qualifier when inherit is false" do
ConstantSpecs.const_get("::CS_CONST1", false).should == :const1
-> { ConstantSpecs.const_get("CS_CONST1", false) }.should raise_error(NameError)
end
it "returns a constant whose module is defined the the toplevel" do
ConstantSpecs.const_get("ConstantSpecsTwo::Foo").should == :cs_two_foo
ConstantSpecsThree.const_get("ConstantSpecsTwo::Foo").should == :cs_three_foo
end
it "accepts a scoped constant name" do it "accepts a scoped constant name" do
ConstantSpecs.const_get("ClassA::CS_CONST10").should == :const10_10 ConstantSpecs.const_get("ClassA::CS_CONST10").should == :const10_10
end end
@ -140,6 +150,10 @@ describe "Module#const_get" do
Object.const_get('CSAutoloadD::InnerModule').name.should == 'CSAutoloadD::InnerModule' Object.const_get('CSAutoloadD::InnerModule').name.should == 'CSAutoloadD::InnerModule'
end end
it "raises a NameError when the nested constant does not exist on the module but exists in Object" do
-> { Object.const_get('ConstantSpecs::CS_CONST1') }.should raise_error(NameError)
end
describe "with statically assigned constants" do describe "with statically assigned constants" do
it "searches the immediate class or module first" do it "searches the immediate class or module first" do
ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10 ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10

View file

@ -15,6 +15,83 @@ ruby_version_is "2.7" do
Hash.ruby2_keywords_hash?(last).should == true Hash.ruby2_keywords_hash?(last).should == true
end end
it "makes a copy of the hash and only marks the copy as keyword hash" do
obj = Object.new
obj.singleton_class.class_exec do
def regular(*args)
args.last
end
ruby2_keywords def foo(*args)
args.last
end
end
h = {a: 1}
ruby_version_is "3.0" do
obj.regular(**h).should.equal?(h)
end
last = obj.foo(**h)
Hash.ruby2_keywords_hash?(last).should == true
Hash.ruby2_keywords_hash?(h).should == false
last2 = obj.foo(**last) # last is already marked
Hash.ruby2_keywords_hash?(last2).should == true
Hash.ruby2_keywords_hash?(last).should == true
last2.should_not.equal?(last)
Hash.ruby2_keywords_hash?(h).should == false
end
it "makes a copy and unmark at the call site when calling with marked *args" do
obj = Object.new
obj.singleton_class.class_exec do
ruby2_keywords def foo(*args)
args
end
def single(arg)
arg
end
def splat(*args)
args.last
end
def kwargs(**kw)
kw
end
end
h = { a: 1 }
args = obj.foo(**h)
marked = args.last
Hash.ruby2_keywords_hash?(marked).should == true
after_usage = obj.single(*args)
after_usage.should == h
after_usage.should_not.equal?(h)
after_usage.should_not.equal?(marked)
Hash.ruby2_keywords_hash?(after_usage).should == false
Hash.ruby2_keywords_hash?(marked).should == true
after_usage = obj.splat(*args)
after_usage.should == h
after_usage.should_not.equal?(h)
after_usage.should_not.equal?(marked)
ruby_bug "#18625", ""..."3.3" do # might be fixed in 3.2
Hash.ruby2_keywords_hash?(after_usage).should == false
end
Hash.ruby2_keywords_hash?(marked).should == true
after_usage = obj.kwargs(*args)
after_usage.should == h
after_usage.should_not.equal?(h)
after_usage.should_not.equal?(marked)
Hash.ruby2_keywords_hash?(after_usage).should == false
Hash.ruby2_keywords_hash?(marked).should == true
end
it "applies to the underlying method and applies across aliasing" do it "applies to the underlying method and applies across aliasing" do
obj = Object.new obj = Object.new
@ -80,7 +157,7 @@ ruby_version_is "2.7" do
}.should raise_error(NameError, /undefined method `not_existing'/) }.should raise_error(NameError, /undefined method `not_existing'/)
end end
it "acceps String as well" do it "accepts String as well" do
obj = Object.new obj = Object.new
obj.singleton_class.class_exec do obj.singleton_class.class_exec do

View file

@ -31,7 +31,7 @@ describe "Proc#parameters" do
end end
it "regards named parameters in lambda as optional if lambda: false keyword used" do it "regards named parameters in lambda as optional if lambda: false keyword used" do
lambda {|x| }.parameters(lambda: false).first.first.should == :opt -> x { }.parameters(lambda: false).first.first.should == :opt
end end
end end

View file

@ -205,6 +205,11 @@ describe "Random#rand with Range" do
Random.new(42).rand(0..1.0).should be_kind_of(Float) Random.new(42).rand(0..1.0).should be_kind_of(Float)
end end
it "returns a float within a given float range" do
Random.new(42).rand(0.0...100.0).should == 37.454011884736246
Random.new(42).rand(-100.0...0.0).should == -62.545988115263754
end
it "raises an ArgumentError when the startpoint lacks #+ and #- methods" do it "raises an ArgumentError when the startpoint lacks #+ and #- methods" do
-> do -> do
Random.new.rand(Object.new..67) Random.new.rand(Object.new..67)

View file

@ -5,4 +5,9 @@ require_relative 'shared/concat'
describe "String#<<" do describe "String#<<" do
it_behaves_like :string_concat, :<< it_behaves_like :string_concat, :<<
it_behaves_like :string_concat_encoding, :<< it_behaves_like :string_concat_encoding, :<<
it "raises an ArgumentError when given the incorrect number of arguments" do
-> { "hello".send(:<<) }.should raise_error(ArgumentError)
-> { "hello".send(:<<, "one", "two") }.should raise_error(ArgumentError)
end
end end

View file

@ -75,6 +75,10 @@ describe "String#<=> with String" do
(xff_1 <=> xff_2).should == -1 (xff_1 <=> xff_2).should == -1
(xff_2 <=> xff_1).should == 1 (xff_2 <=> xff_1).should == 1
end end
it "returns 0 when comparing 2 empty strings but one is not ASCII-compatible" do
("" <=> "".force_encoding('iso-2022-jp')).should == 0
end
end end
# Note: This is inconsistent with Array#<=> which calls #to_ary instead of # Note: This is inconsistent with Array#<=> which calls #to_ary instead of

View file

@ -12,7 +12,7 @@ describe "String#lstrip" do
"hello".lstrip.should == "hello" "hello".lstrip.should == "hello"
end end
ruby_version_is '3.1' do ruby_version_is '3.0' do
it "strips leading \\0" do it "strips leading \\0" do
"\x00hello".lstrip.should == "hello" "\x00hello".lstrip.should == "hello"
"\000 \000hello\000 \000".lstrip.should == "hello\000 \000" "\000 \000hello\000 \000".lstrip.should == "hello\000 \000"
@ -35,7 +35,7 @@ describe "String#lstrip!" do
a.should == "hello " a.should == "hello "
end end
ruby_version_is '3.1' do ruby_version_is '3.0' do
it "strips leading \\0" do it "strips leading \\0" do
a = "\000 \000hello\000 \000" a = "\000 \000hello\000 \000"
a.lstrip! a.lstrip!

View file

@ -4,13 +4,17 @@ require_relative 'fixtures/classes'
require_relative 'fixtures/utf-8-encoding' require_relative 'fixtures/utf-8-encoding'
describe "String#rindex with object" do describe "String#rindex with object" do
it "raises a TypeError if obj isn't a String, Integer or Regexp" do it "raises a TypeError if obj isn't a String or Regexp" do
not_supported_on :opal do not_supported_on :opal do
-> { "hello".rindex(:sym) }.should raise_error(TypeError) -> { "hello".rindex(:sym) }.should raise_error(TypeError)
end end
-> { "hello".rindex(mock('x')) }.should raise_error(TypeError) -> { "hello".rindex(mock('x')) }.should raise_error(TypeError)
end end
it "raises a TypeError if obj is an Integer" do
-> { "hello".rindex(42) }.should raise_error(TypeError)
end
it "doesn't try to convert obj to an integer via to_int" do it "doesn't try to convert obj to an integer via to_int" do
obj = mock('x') obj = mock('x')
obj.should_not_receive(:to_int) obj.should_not_receive(:to_int)

View file

@ -31,4 +31,8 @@ describe :string_eql_value, shared: true do
a.send(@method, b).should be_true a.send(@method, b).should be_true
b.send(@method, a).should be_true b.send(@method, a).should be_true
end end
it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do
"".send(@method, "".force_encoding('iso-2022-jp')).should == true
end
end end

View file

@ -589,4 +589,11 @@ describe "String#split with Regexp" do
end end
end end
end end
it "raises a TypeError when not called with nil, String, or Regexp" do
-> { "hello".split(42) }.should raise_error(TypeError)
-> { "hello".split(:ll) }.should raise_error(TypeError)
-> { "hello".split(false) }.should raise_error(TypeError)
-> { "hello".split(Object.new) }.should raise_error(TypeError)
end
end end

View file

@ -11,7 +11,7 @@ describe "String#strip" do
"\tgoodbye\r\v\n".strip.should == "goodbye" "\tgoodbye\r\v\n".strip.should == "goodbye"
end end
ruby_version_is '3.1' do ruby_version_is '3.0' do
it "returns a copy of self without leading and trailing NULL bytes and whitespace" do it "returns a copy of self without leading and trailing NULL bytes and whitespace" do
" \x00 goodbye \x00 ".strip.should == "goodbye" " \x00 goodbye \x00 ".strip.should == "goodbye"
end end
@ -43,7 +43,7 @@ describe "String#strip!" do
a.should == "hello" a.should == "hello"
end end
ruby_version_is '3.1' do ruby_version_is '3.0' do
it "removes leading and trailing NULL bytes and whitespace" do it "removes leading and trailing NULL bytes and whitespace" do
a = "\000 goodbye \000" a = "\000 goodbye \000"
a.strip! a.strip!

View file

@ -60,14 +60,21 @@ describe "Struct.new" do
-> { Struct.new(:animal, nil) }.should raise_error(TypeError) -> { Struct.new(:animal, nil) }.should raise_error(TypeError)
-> { Struct.new(:animal, true) }.should raise_error(TypeError) -> { Struct.new(:animal, true) }.should raise_error(TypeError)
-> { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError) -> { Struct.new(:animal, ['chris', 'evan']) }.should raise_error(TypeError)
ruby_version_is "3.2" do
-> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
end
end end
ruby_version_is ""..."3.2" do ruby_version_is ""..."3.2" do
it "raises a ArgumentError if passed a Hash with an unknown key" do it "raises a TypeError or ArgumentError if passed a Hash with an unknown key" do
-> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(ArgumentError) # CRuby < 3.2 raises ArgumentError: unknown keyword: :name, but that seems a bug:
# https://bugs.ruby-lang.org/issues/18632
-> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(StandardError) { |e|
[ArgumentError, TypeError].should.include?(e.class)
}
end
end
ruby_version_is "3.2" do
it "raises a TypeError if passed a Hash with an unknown key" do
-> { Struct.new(:animal, { name: 'chris' }) }.should raise_error(TypeError)
end end
end end

View file

@ -66,6 +66,26 @@ describe :thread_exit, shared: true do
ScratchPad.recorded.should == nil ScratchPad.recorded.should == nil
end end
it "does not reset $!" do
ScratchPad.record []
exc = RuntimeError.new("foo")
thread = Thread.new do
begin
raise exc
ensure
ScratchPad << $!
begin
Thread.current.send(@method)
ensure
ScratchPad << $!
end
end
end
thread.join
ScratchPad.recorded.should == [exc, exc]
end
it "cannot be rescued" do it "cannot be rescued" do
thread = Thread.new do thread = Thread.new do
begin begin
@ -73,7 +93,7 @@ describe :thread_exit, shared: true do
rescue Exception rescue Exception
ScratchPad.record :in_rescue ScratchPad.record :in_rescue
end end
ScratchPad.record :end_of_thread_block ScratchPad.record :end_of_thread_block
end end
thread.join thread.join

View file

@ -299,4 +299,14 @@ module ConstantSpecs
private_constant :CS_PRIVATE private_constant :CS_PRIVATE
end end
module ConstantSpecsThree
module ConstantSpecsTwo
Foo = :cs_three_foo
end
end
module ConstantSpecsTwo
Foo = :cs_two_foo
end
include ConstantSpecs::ModuleA include ConstantSpecs::ModuleA

View file

@ -0,0 +1,307 @@
require_relative '../spec_helper'
ruby_version_is "3.0" do
describe "Keyword arguments" do
def target(*args, **kwargs)
[args, kwargs]
end
it "are separated from positional arguments" do
def m(*args, **kwargs)
[args, kwargs]
end
empty = {}
m(**empty).should == [[], {}]
m(empty).should == [[{}], {}]
m(a: 1).should == [[], {a: 1}]
m({a: 1}).should == [[{a: 1}], {}]
end
it "when the receiving method has not keyword parameters it treats kwargs as positional" do
def m(*a)
a
end
m(a: 1).should == [{a: 1}]
m({a: 1}).should == [{a: 1}]
end
context "empty kwargs are treated as if they were not passed" do
it "when calling a method" do
def m(*a)
a
end
empty = {}
m(**empty).should == []
m(empty).should == [{}]
end
it "when yielding to a block" do
def y(*args, **kwargs)
yield(*args, **kwargs)
end
empty = {}
y(**empty) { |*a| a }.should == []
y(empty) { |*a| a }.should == [{}]
end
end
it "extra keywords are not allowed without **kwrest" do
def m(*a, kw:)
a
end
m(kw: 1).should == []
-> { m(kw: 1, kw2: 2) }.should raise_error(ArgumentError, 'unknown keyword: :kw2')
-> { m(kw: 1, true => false) }.should raise_error(ArgumentError, 'unknown keyword: true')
end
it "handle * and ** at the same call site" do
def m(*a)
a
end
m(*[], **{}).should == []
m(*[], 42, **{}).should == [42]
end
context "**" do
it "does not copy a non-empty Hash for a method taking (*args)" do
def m(*args)
args[0]
end
h = {a: 1}
m(**h).should.equal?(h)
end
it "copies the given Hash for a method taking (**kwargs)" do
def m(**kw)
kw
end
empty = {}
m(**empty).should == empty
m(**empty).should_not.equal?(empty)
h = {a: 1}
m(**h).should == h
m(**h).should_not.equal?(h)
end
end
context "delegation" do
it "works with (*args, **kwargs)" do
def m(*args, **kwargs)
target(*args, **kwargs)
end
empty = {}
m(**empty).should == [[], {}]
m(empty).should == [[{}], {}]
m(a: 1).should == [[], {a: 1}]
m({a: 1}).should == [[{a: 1}], {}]
end
it "works with proc { |*args, **kwargs| }" do
m = proc do |*args, **kwargs|
target(*args, **kwargs)
end
empty = {}
m.(**empty).should == [[], {}]
m.(empty).should == [[{}], {}]
m.(a: 1).should == [[], {a: 1}]
m.({a: 1}).should == [[{a: 1}], {}]
# no autosplatting for |*args, **kwargs|
m.([1, 2]).should == [[[1, 2]], {}]
end
it "works with -> (*args, **kwargs) {}" do
m = -> *args, **kwargs do
target(*args, **kwargs)
end
empty = {}
m.(**empty).should == [[], {}]
m.(empty).should == [[{}], {}]
m.(a: 1).should == [[], {a: 1}]
m.({a: 1}).should == [[{a: 1}], {}]
end
it "works with (...)" do
instance_eval <<~DEF
def m(...)
target(...)
end
DEF
empty = {}
m(**empty).should == [[], {}]
m(empty).should == [[{}], {}]
m(a: 1).should == [[], {a: 1}]
m({a: 1}).should == [[{a: 1}], {}]
end
it "works with call(*ruby2_keyword_args)" do
class << self
ruby2_keywords def m(*args)
target(*args)
end
end
empty = {}
m(**empty).should == [[], {}]
Hash.ruby2_keywords_hash?(empty).should == false
m(empty).should == [[{}], {}]
Hash.ruby2_keywords_hash?(empty).should == false
m(a: 1).should == [[], {a: 1}]
m({a: 1}).should == [[{a: 1}], {}]
kw = {a: 1}
m(**kw).should == [[], {a: 1}]
m(**kw)[1].should == kw
m(**kw)[1].should_not.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
m(kw).should == [[{a: 1}], {}]
m(kw)[0][0].should.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
end
it "works with super(*ruby2_keyword_args)" do
parent = Class.new do
def m(*args, **kwargs)
[args, kwargs]
end
end
child = Class.new(parent) do
ruby2_keywords def m(*args)
super(*args)
end
end
obj = child.new
empty = {}
obj.m(**empty).should == [[], {}]
Hash.ruby2_keywords_hash?(empty).should == false
obj.m(empty).should == [[{}], {}]
Hash.ruby2_keywords_hash?(empty).should == false
obj.m(a: 1).should == [[], {a: 1}]
obj.m({a: 1}).should == [[{a: 1}], {}]
kw = {a: 1}
obj.m(**kw).should == [[], {a: 1}]
obj.m(**kw)[1].should == kw
obj.m(**kw)[1].should_not.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
obj.m(kw).should == [[{a: 1}], {}]
obj.m(kw)[0][0].should.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
end
it "works with zsuper" do
parent = Class.new do
def m(*args, **kwargs)
[args, kwargs]
end
end
child = Class.new(parent) do
ruby2_keywords def m(*args)
super
end
end
obj = child.new
empty = {}
obj.m(**empty).should == [[], {}]
Hash.ruby2_keywords_hash?(empty).should == false
obj.m(empty).should == [[{}], {}]
Hash.ruby2_keywords_hash?(empty).should == false
obj.m(a: 1).should == [[], {a: 1}]
obj.m({a: 1}).should == [[{a: 1}], {}]
kw = {a: 1}
obj.m(**kw).should == [[], {a: 1}]
obj.m(**kw)[1].should == kw
obj.m(**kw)[1].should_not.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
Hash.ruby2_keywords_hash?(obj.m(**kw)[1]).should == false
obj.m(kw).should == [[{a: 1}], {}]
obj.m(kw)[0][0].should.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
end
it "works with yield(*ruby2_keyword_args)" do
class << self
def y(args)
yield(*args)
end
ruby2_keywords def m(*outer_args)
y(outer_args, &-> *args, **kwargs { target(*args, **kwargs) })
end
end
empty = {}
m(**empty).should == [[], {}]
Hash.ruby2_keywords_hash?(empty).should == false
m(empty).should == [[{}], {}]
Hash.ruby2_keywords_hash?(empty).should == false
m(a: 1).should == [[], {a: 1}]
m({a: 1}).should == [[{a: 1}], {}]
kw = {a: 1}
m(**kw).should == [[], {a: 1}]
m(**kw)[1].should == kw
m(**kw)[1].should_not.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
Hash.ruby2_keywords_hash?(m(**kw)[1]).should == false
m(kw).should == [[{a: 1}], {}]
m(kw)[0][0].should.equal?(kw)
Hash.ruby2_keywords_hash?(kw).should == false
end
it "does not work with (*args)" do
class << self
def m(*args)
target(*args)
end
end
empty = {}
m(**empty).should == [[], {}]
m(empty).should == [[{}], {}]
m(a: 1).should == [[{a: 1}], {}]
m({a: 1}).should == [[{a: 1}], {}]
end
end
end
end

View file

@ -15,14 +15,14 @@ describe "Logger::LogDevice#close" do
rm_r @file_path rm_r @file_path
end end
ruby_version_is ""..."2.7" do version_is Logger::VERSION, ""..."1.4.0" do
it "closes the LogDevice's stream" do it "closes the LogDevice's stream" do
@device.close @device.close
-> { @device.write("Test") }.should complain(/\Alog writing failed\./) -> { @device.write("Test") }.should complain(/\Alog writing failed\./)
end end
end end
ruby_version_is "2.7" do version_is Logger::VERSION, "1.4.0" do
it "closes the LogDevice's stream" do it "closes the LogDevice's stream" do
@device.close @device.close
-> { @device.write("Test") }.should complain(/\Alog shifting failed\./) -> { @device.write("Test") }.should complain(/\Alog shifting failed\./)

View file

@ -35,14 +35,14 @@ describe "Logger::LogDevice#write" do
rm_r path rm_r path
end end
ruby_version_is ""..."2.7" do version_is Logger::VERSION, ""..."1.4.0" do
it "fails if the device is already closed" do it "fails if the device is already closed" do
@device.close @device.close
-> { @device.write "foo" }.should complain(/\Alog writing failed\./) -> { @device.write "foo" }.should complain(/\Alog writing failed\./)
end end
end end
ruby_version_is "2.7" do version_is Logger::VERSION, "1.4.0" do
it "fails if the device is already closed" do it "fails if the device is already closed" do
@device.close @device.close
-> { @device.write "foo" }.should complain(/\Alog shifting failed\./) -> { @device.write "foo" }.should complain(/\Alog shifting failed\./)

View file

@ -108,17 +108,37 @@ describe "C-API Class function" do
describe "rb_class_new_instance" do describe "rb_class_new_instance" do
it "allocates and initializes a new object" do it "allocates and initializes a new object" do
o = @s.rb_class_new_instance(0, nil, CApiClassSpecs::Alloc) o = @s.rb_class_new_instance([], CApiClassSpecs::Alloc)
o.class.should == CApiClassSpecs::Alloc o.class.should == CApiClassSpecs::Alloc
o.initialized.should be_true o.initialized.should be_true
end end
it "passes arguments to the #initialize method" do it "passes arguments to the #initialize method" do
o = @s.rb_class_new_instance(2, [:one, :two], CApiClassSpecs::Alloc) o = @s.rb_class_new_instance([:one, :two], CApiClassSpecs::Alloc)
o.arguments.should == [:one, :two] o.arguments.should == [:one, :two]
end end
end end
ruby_version_is "3.0" do
describe "rb_class_new_instance_kw" do
it "passes arguments and keywords to the #initialize method" do
obj = @s.rb_class_new_instance_kw([{pos: 1}, {kw: 2}], CApiClassSpecs::KeywordAlloc)
obj.args.should == [{pos: 1}]
obj.kwargs.should == {kw: 2}
obj = @s.rb_class_new_instance_kw([{}], CApiClassSpecs::KeywordAlloc)
obj.args.should == []
obj.kwargs.should == {}
end
it "raises TypeError if the last argument is not a Hash" do
-> {
@s.rb_class_new_instance_kw([42], CApiClassSpecs::KeywordAlloc)
}.should raise_error(TypeError, 'no implicit conversion of Integer into Hash')
end
end
end
describe "rb_include_module" do describe "rb_include_module" do
it "includes a module into a class" do it "includes a module into a class" do
c = Class.new c = Class.new

View file

@ -61,19 +61,16 @@ static VALUE class_spec_rb_class_new(VALUE self, VALUE super) {
return rb_class_new(super); return rb_class_new(super);
} }
static VALUE class_spec_rb_class_new_instance(VALUE self, static VALUE class_spec_rb_class_new_instance(VALUE self, VALUE args, VALUE klass) {
VALUE nargs, VALUE args, return rb_class_new_instance(RARRAY_LENINT(args), RARRAY_PTR(args), klass);
VALUE klass) {
int c_nargs = FIX2INT(nargs);
VALUE *c_args = (VALUE*)alloca(sizeof(VALUE) * c_nargs);
int i;
for (i = 0; i < c_nargs; i++)
c_args[i] = rb_ary_entry(args, i);
return rb_class_new_instance(c_nargs, c_args, klass);
} }
#ifdef RUBY_VERSION_IS_3_0
static VALUE class_spec_rb_class_new_instance_kw(VALUE self, VALUE args, VALUE klass) {
return rb_class_new_instance_kw(RARRAY_LENINT(args), RARRAY_PTR(args), klass, RB_PASS_KEYWORDS);
}
#endif
static VALUE class_spec_rb_class_real(VALUE self, VALUE object) { static VALUE class_spec_rb_class_real(VALUE self, VALUE object) {
if(rb_type_p(object, T_FIXNUM)) { if(rb_type_p(object, T_FIXNUM)) {
return INT2FIX(rb_class_real(FIX2INT(object))); return INT2FIX(rb_class_real(FIX2INT(object)));
@ -171,7 +168,10 @@ void Init_class_spec(void) {
rb_define_method(cls, "rb_class_protected_instance_methods", class_spec_rb_class_protected_instance_methods, -1); rb_define_method(cls, "rb_class_protected_instance_methods", class_spec_rb_class_protected_instance_methods, -1);
rb_define_method(cls, "rb_class_private_instance_methods", class_spec_rb_class_private_instance_methods, -1); rb_define_method(cls, "rb_class_private_instance_methods", class_spec_rb_class_private_instance_methods, -1);
rb_define_method(cls, "rb_class_new", class_spec_rb_class_new, 1); rb_define_method(cls, "rb_class_new", class_spec_rb_class_new, 1);
rb_define_method(cls, "rb_class_new_instance", class_spec_rb_class_new_instance, 3); rb_define_method(cls, "rb_class_new_instance", class_spec_rb_class_new_instance, 2);
#ifdef RUBY_VERSION_IS_3_0
rb_define_method(cls, "rb_class_new_instance_kw", class_spec_rb_class_new_instance_kw, 2);
#endif
rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1); rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1);
rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1); rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1);
rb_define_method(cls, "rb_cvar_defined", class_spec_cvar_defined, 2); rb_define_method(cls, "rb_cvar_defined", class_spec_cvar_defined, 2);

View file

@ -142,7 +142,7 @@ VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) {
argc = 2; argc = 2;
return rb_funcall2(proc, rb_intern("call"), argc, argv); return rb_funcallv(proc, rb_intern("call"), argc, argv);
} }
VALUE kernel_spec_rb_rescue(VALUE self, VALUE main_proc, VALUE arg, VALUE kernel_spec_rb_rescue(VALUE self, VALUE main_proc, VALUE arg,
@ -318,8 +318,22 @@ static VALUE kernel_spec_rb_make_backtrace(VALUE self) {
return rb_make_backtrace(); return rb_make_backtrace();
} }
static VALUE kernel_spec_rb_funcall3(VALUE self, VALUE obj, VALUE method) { static VALUE kernel_spec_rb_funcallv(VALUE self, VALUE obj, VALUE method, VALUE args) {
return rb_funcall3(obj, SYM2ID(method), 0, NULL); return rb_funcallv(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args));
}
#ifdef RUBY_VERSION_IS_3_0
static VALUE kernel_spec_rb_funcallv_kw(VALUE self, VALUE obj, VALUE method, VALUE args) {
return rb_funcallv_kw(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), RB_PASS_KEYWORDS);
}
static VALUE kernel_spec_rb_keyword_given_p(int argc, VALUE *args, VALUE self) {
return rb_keyword_given_p() ? Qtrue : Qfalse;
}
#endif
static VALUE kernel_spec_rb_funcallv_public(VALUE self, VALUE obj, VALUE method) {
return rb_funcallv_public(obj, SYM2ID(method), 0, NULL);
} }
static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE block) { static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE block) {
@ -371,7 +385,12 @@ void Init_kernel_spec(void) {
rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1); rb_define_method(cls, "rb_set_end_proc", kernel_spec_rb_set_end_proc, 1);
rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1); rb_define_method(cls, "rb_f_sprintf", kernel_spec_rb_f_sprintf, 1);
rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0); rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0);
rb_define_method(cls, "rb_funcall3", kernel_spec_rb_funcall3, 2); rb_define_method(cls, "rb_funcallv", kernel_spec_rb_funcallv, 3);
#ifdef RUBY_VERSION_IS_3_0
rb_define_method(cls, "rb_funcallv_kw", kernel_spec_rb_funcallv_kw, 3);
rb_define_method(cls, "rb_keyword_given_p", kernel_spec_rb_keyword_given_p, -1);
#endif
rb_define_method(cls, "rb_funcallv_public", kernel_spec_rb_funcallv_public, 2);
rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2); rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2);
rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3); rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3);
} }

View file

@ -47,10 +47,19 @@ VALUE symbol_spec_rb_id2name(VALUE self, VALUE symbol) {
return rb_str_new(c_str, strlen(c_str)); return rb_str_new(c_str, strlen(c_str));
} }
VALUE symbol_spec_rb_id2name_id_zero(VALUE self) {
const char* c_str = rb_id2name((ID) 0);
return c_str ? rb_str_new(c_str, strlen(c_str)) : Qnil;
}
VALUE symbol_spec_rb_id2str(VALUE self, VALUE symbol) { VALUE symbol_spec_rb_id2str(VALUE self, VALUE symbol) {
return rb_id2str(SYM2ID(symbol)); return rb_id2str(SYM2ID(symbol));
} }
VALUE symbol_spec_rb_id2str_id_zero(VALUE self) {
return rb_id2str((ID) 0);
}
VALUE symbol_spec_rb_intern_str(VALUE self, VALUE str) { VALUE symbol_spec_rb_intern_str(VALUE self, VALUE str) {
return ID2SYM(rb_intern_str(str)); return ID2SYM(rb_intern_str(str));
} }
@ -90,7 +99,9 @@ void Init_symbol_spec(void) {
rb_define_method(cls, "rb_intern3", symbol_spec_rb_intern3, 3); rb_define_method(cls, "rb_intern3", symbol_spec_rb_intern3, 3);
rb_define_method(cls, "rb_intern3_c_compare", symbol_spec_rb_intern3_c_compare, 4); rb_define_method(cls, "rb_intern3_c_compare", symbol_spec_rb_intern3_c_compare, 4);
rb_define_method(cls, "rb_id2name", symbol_spec_rb_id2name, 1); rb_define_method(cls, "rb_id2name", symbol_spec_rb_id2name, 1);
rb_define_method(cls, "rb_id2name_id_zero", symbol_spec_rb_id2name_id_zero, 0);
rb_define_method(cls, "rb_id2str", symbol_spec_rb_id2str, 1); rb_define_method(cls, "rb_id2str", symbol_spec_rb_id2str, 1);
rb_define_method(cls, "rb_id2str_id_zero", symbol_spec_rb_id2str_id_zero, 0);
rb_define_method(cls, "rb_intern_str", symbol_spec_rb_intern_str, 1); rb_define_method(cls, "rb_intern_str", symbol_spec_rb_intern_str, 1);
rb_define_method(cls, "rb_check_symbol_cstr", symbol_spec_rb_check_symbol_cstr, 1); rb_define_method(cls, "rb_check_symbol_cstr", symbol_spec_rb_check_symbol_cstr, 1);
rb_define_method(cls, "rb_is_class_id", symbol_spec_rb_is_class_id, 1); rb_define_method(cls, "rb_is_class_id", symbol_spec_rb_is_class_id, 1);

View file

@ -15,6 +15,16 @@ class CApiClassSpecs
end end
end end
class KeywordAlloc
attr_reader :initialized, :args, :kwargs
def initialize(*args, **kwargs)
@initialized = true
@args = args
@kwargs = kwargs
end
end
class Attr class Attr
def initialize def initialize
@foo, @bar, @baz = 1, 2, 3 @foo, @bar, @baz = 1, 2, 3

View file

@ -567,7 +567,64 @@ describe "C-API Kernel function" do
end end
end end
describe "rb_funcall3" do describe "rb_funcallv" do
def empty
42
end
def sum(a, b)
a + b
end
it "calls a method" do
@s.rb_funcallv(self, :empty, []).should == 42
@s.rb_funcallv(self, :sum, [1, 2]).should == 3
end
end
ruby_version_is "3.0" do
describe "rb_funcallv_kw" do
it "passes keyword arguments to the callee" do
def m(*args, **kwargs)
[args, kwargs]
end
@s.rb_funcallv_kw(self, :m, [{}]).should == [[], {}]
@s.rb_funcallv_kw(self, :m, [{a: 1}]).should == [[], {a: 1}]
@s.rb_funcallv_kw(self, :m, [{b: 2}, {a: 1}]).should == [[{b: 2}], {a: 1}]
@s.rb_funcallv_kw(self, :m, [{b: 2}, {}]).should == [[{b: 2}], {}]
end
it "raises TypeError if the last argument is not a Hash" do
def m(*args, **kwargs)
[args, kwargs]
end
-> {
@s.rb_funcallv_kw(self, :m, [42])
}.should raise_error(TypeError, 'no implicit conversion of Integer into Hash')
end
end
describe "rb_keyword_given_p" do
it "returns whether keywords were given to the C extension method" do
h = {a: 1}
empty = {}
@s.rb_keyword_given_p(a: 1).should == true
@s.rb_keyword_given_p("foo" => "bar").should == true
@s.rb_keyword_given_p(**h).should == true
@s.rb_keyword_given_p(h).should == false
@s.rb_keyword_given_p().should == false
@s.rb_keyword_given_p(**empty).should == false
@s.rb_funcallv_kw(@s, :rb_keyword_given_p, [{a: 1}]).should == true
@s.rb_funcallv_kw(@s, :rb_keyword_given_p, [{}]).should == false
end
end
end
describe "rb_funcallv_public" do
before :each do before :each do
@obj = Object.new @obj = Object.new
class << @obj class << @obj
@ -578,10 +635,11 @@ describe "C-API Kernel function" do
end end
it "calls a public method" do it "calls a public method" do
@s.rb_funcall3(@obj, :method_public).should == :method_public @s.rb_funcallv_public(@obj, :method_public).should == :method_public
end end
it "does not call a private method" do it "does not call a private method" do
-> { @s.rb_funcall3(@obj, :method_private) }.should raise_error(NoMethodError, /private/) -> { @s.rb_funcallv_public(@obj, :method_private) }.should raise_error(NoMethodError, /private/)
end end
end end
@ -599,6 +657,7 @@ describe "C-API Kernel function" do
@s.rb_funcall_many_args(@obj, :many_args).should == 15.downto(1).to_a @s.rb_funcall_many_args(@obj, :many_args).should == 15.downto(1).to_a
end end
end end
describe 'rb_funcall_with_block' do describe 'rb_funcall_with_block' do
before :each do before :each do
@obj = Object.new @obj = Object.new

View file

@ -61,6 +61,10 @@ describe "C-API Symbol function" do
it "converts a symbol to a C char array" do it "converts a symbol to a C char array" do
@s.rb_id2name(:test_symbol).should == "test_symbol" @s.rb_id2name(:test_symbol).should == "test_symbol"
end end
it "returns (char*) NULL for (ID) 0" do
@s.rb_id2name_id_zero.should == nil
end
end end
describe "rb_id2str" do describe "rb_id2str" do
@ -72,6 +76,10 @@ describe "C-API Symbol function" do
str = "test_symbol".encode(Encoding::UTF_16LE) str = "test_symbol".encode(Encoding::UTF_16LE)
@s.rb_id2str(str.to_sym).encoding.should == Encoding::UTF_16LE @s.rb_id2str(str.to_sym).encoding.should == Encoding::UTF_16LE
end end
it "returns (VALUE) 0 = Qfalse for (ID) 0" do
@s.rb_id2str_id_zero.should == false
end
end end
describe "rb_intern_str" do describe "rb_intern_str" do

View file

@ -29,11 +29,41 @@ describe :kernel_raise, shared: true do
@data = data @data = data
end end
end end
-> { @object.raise(data_error, {:data => 42}) }.should raise_error(data_error) do |ex|
ex.data.should == {:data => 42} -> { @object.raise(data_error, {data: 42}) }.should raise_error(data_error) do |ex|
ex.data.should == {data: 42}
end end
end end
# https://bugs.ruby-lang.org/issues/8257#note-36
it "allows extra keyword arguments for compatibility" do
data_error = Class.new(StandardError) do
attr_reader :data
def initialize(data)
@data = data
end
end
-> { @object.raise(data_error, data: 42) }.should raise_error(data_error) do |ex|
ex.data.should == {data: 42}
end
end
it "does not allow message and extra keyword arguments" do
data_error = Class.new(StandardError) do
attr_reader :data
def initialize(data)
@data = data
end
end
-> { @object.raise(data_error, {a: 1}, b: 2) }.should raise_error(StandardError) do |e|
[TypeError, ArgumentError].should.include?(e.class)
end
-> { @object.raise(data_error, {a: 1}, [], b: 2) }.should raise_error(ArgumentError)
end
it "raises RuntimeError if no exception class is given" do it "raises RuntimeError if no exception class is given" do
-> { @object.raise }.should raise_error(RuntimeError, "") -> { @object.raise }.should raise_error(RuntimeError, "")
end end

View file

@ -21,6 +21,12 @@ describe :process_exit, shared: true do
end end
end end
it "raises a SystemExit with message 'exit'" do
-> { @object.exit }.should raise_error(SystemExit) { |e|
e.message.should == "exit"
}
end
it "tries to convert the passed argument to an Integer using #to_int" do it "tries to convert the passed argument to an Integer using #to_int" do
obj = mock('5') obj = mock('5')
obj.should_receive(:to_int).and_return(5) obj.should_receive(:to_int).and_return(5)