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 2021-02-27 13:00:26 +01:00
parent dbea0be13d
commit 36dde35e02
48 changed files with 811 additions and 64 deletions

View file

@ -1,4 +1,5 @@
require_relative '../../spec_helper' require_relative '../../spec_helper'
require_relative 'fixtures/classes'
describe "Array#drop" do describe "Array#drop" do
it "removes the specified number of elements from the start of the array" do it "removes the specified number of elements from the start of the array" do
@ -48,4 +49,16 @@ describe "Array#drop" do
-> { [1, 2].drop(obj) }.should raise_error(TypeError) -> { [1, 2].drop(obj) }.should raise_error(TypeError)
end end
ruby_version_is ''...'3.0' do
it 'returns a subclass instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should be_an_instance_of(ArraySpecs::MyArray)
end
end
ruby_version_is '3.0' do
it 'returns a Array instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].drop(1).should be_an_instance_of(Array)
end
end
end end

View file

@ -1,4 +1,5 @@
require_relative '../../spec_helper' require_relative '../../spec_helper'
require_relative 'fixtures/classes'
describe "Array#drop_while" do describe "Array#drop_while" do
it "removes elements from the start of the array while the block evaluates to true" do it "removes elements from the start of the array while the block evaluates to true" do
@ -12,4 +13,16 @@ describe "Array#drop_while" do
it "removes elements from the start of the array until the block returns false" do it "removes elements from the start of the array until the block returns false" do
[1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5] [1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5]
end end
ruby_version_is ''...'3.0' do
it 'returns a subclass instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should be_an_instance_of(ArraySpecs::MyArray)
end
end
ruby_version_is '3.0' do
it 'returns a Array instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].drop_while { |n| n < 4 }.should be_an_instance_of(Array)
end
end
end end

View file

@ -185,6 +185,64 @@ describe "Array#slice!" do
a.should == [3, 4] a.should == [3, 4]
end end
end end
describe "with a subclass of Array" do
before :each do
@array = ArraySpecs::MyArray[1, 2, 3, 4, 5]
end
ruby_version_is ''...'3.0' do
it "returns a subclass instance with [n, m]" do
@array.slice!(0, 2).should be_an_instance_of(ArraySpecs::MyArray)
end
it "returns a subclass instance with [-n, m]" do
@array.slice!(-3, 2).should be_an_instance_of(ArraySpecs::MyArray)
end
it "returns a subclass instance with [n..m]" do
@array.slice!(1..3).should be_an_instance_of(ArraySpecs::MyArray)
end
it "returns a subclass instance with [n...m]" do
@array.slice!(1...3).should be_an_instance_of(ArraySpecs::MyArray)
end
it "returns a subclass instance with [-n..-m]" do
@array.slice!(-3..-1).should be_an_instance_of(ArraySpecs::MyArray)
end
it "returns a subclass instance with [-n...-m]" do
@array.slice!(-3...-1).should be_an_instance_of(ArraySpecs::MyArray)
end
end
ruby_version_is '3.0' do
it "returns a Array instance with [n, m]" do
@array.slice!(0, 2).should be_an_instance_of(Array)
end
it "returns a Array instance with [-n, m]" do
@array.slice!(-3, 2).should be_an_instance_of(Array)
end
it "returns a Array instance with [n..m]" do
@array.slice!(1..3).should be_an_instance_of(Array)
end
it "returns a Array instance with [n...m]" do
@array.slice!(1...3).should be_an_instance_of(Array)
end
it "returns a Array instance with [-n..-m]" do
@array.slice!(-3..-1).should be_an_instance_of(Array)
end
it "returns a Array instance with [-n...-m]" do
@array.slice!(-3...-1).should be_an_instance_of(Array)
end
end
end
end end
describe "Array#slice" do describe "Array#slice" do

View file

@ -1,4 +1,5 @@
require_relative '../../spec_helper' require_relative '../../spec_helper'
require_relative 'fixtures/classes'
describe "Array#take" do describe "Array#take" do
it "returns the first specified number of elements" do it "returns the first specified number of elements" do
@ -24,4 +25,16 @@ describe "Array#take" do
it "raises an ArgumentError when the argument is negative" do it "raises an ArgumentError when the argument is negative" do
->{ [1].take(-3) }.should raise_error(ArgumentError) ->{ [1].take(-3) }.should raise_error(ArgumentError)
end end
ruby_version_is ''...'3.0' do
it 'returns a subclass instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should be_an_instance_of(ArraySpecs::MyArray)
end
end
ruby_version_is '3.0' do
it 'returns a Array instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].take(1).should be_an_instance_of(Array)
end
end
end end

View file

@ -1,4 +1,5 @@
require_relative '../../spec_helper' require_relative '../../spec_helper'
require_relative 'fixtures/classes'
describe "Array#take_while" do describe "Array#take_while" do
it "returns all elements until the block returns false" do it "returns all elements until the block returns false" do
@ -12,4 +13,16 @@ describe "Array#take_while" do
it "returns all elements until the block returns false" do it "returns all elements until the block returns false" do
[1, 2, false, 4].take_while{ |element| element }.should == [1, 2] [1, 2, false, 4].take_while{ |element| element }.should == [1, 2]
end end
ruby_version_is ''...'3.0' do
it 'returns a subclass instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should be_an_instance_of(ArraySpecs::MyArray)
end
end
ruby_version_is '3.0' do
it 'returns a Array instance for Array subclasses' do
ArraySpecs::MyArray[1, 2, 3, 4, 5].take_while { |n| n < 4 }.should be_an_instance_of(Array)
end
end
end end

View file

@ -83,4 +83,63 @@ describe "BasicObject#singleton_method_added" do
end end
ScratchPad.recorded.should == [:singleton_method_added, :new_method_with_define_method] ScratchPad.recorded.should == [:singleton_method_added, :new_method_with_define_method]
end end
describe "when singleton_method_added is undefined" do
it "raises NoMethodError for a metaclass" do
class BasicObjectSpecs::NoSingletonMethodAdded
class << self
undef_method :singleton_method_added
end
-> {
def self.foo
end
}.should raise_error(NoMethodError, /undefined method `singleton_method_added' for/)
end
end
it "raises NoMethodError for a singleton instance" do
object = Object.new
class << object
undef_method :singleton_method_added
-> {
def foo
end
}.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
-> {
define_method(:bar) {}
}.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
end
-> {
object.define_singleton_method(:baz) {}
}.should raise_error(NoMethodError, /undefined method `singleton_method_added' for #<Object:/)
end
it "calls #method_missing" do
ScratchPad.record []
object = Object.new
class << object
def method_missing(*args)
ScratchPad << args
end
undef_method :singleton_method_added
def foo
end
define_method(:bar) {}
end
object.define_singleton_method(:baz) {}
ScratchPad.recorded.should == [
[:singleton_method_added, :foo],
[:singleton_method_added, :bar],
[:singleton_method_added, :baz],
]
end
end
end end

View file

@ -100,10 +100,18 @@ ruby_version_is "2.6" do
a.should == %w|.dotfile.ext directory| a.should == %w|.dotfile.ext directory|
end end
it "accepts an options Hash" do it "accepts an encoding keyword for the encoding of the entries" do
@dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8") @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8")
a = @dir.children.sort dirs = @dir.to_a.sort
a.should == %w|.dotfile.ext directory| dirs.each { |d| d.encoding.should == Encoding::UTF_8 }
end
ruby_version_is ""..."2.7" do
it "accepts nil options" do
@dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", nil)
dirs = @dir.to_a.sort
dirs.each { |d| d.encoding.should == Encoding.find("filesystem") }
end
end end
it "returns children encoded with the filesystem encoding by default" do it "returns children encoded with the filesystem encoding by default" do

View file

@ -10,6 +10,18 @@ describe "Dir.each_child" do
DirSpecs.delete_mock_dirs DirSpecs.delete_mock_dirs
end end
it "accepts an encoding keyword for the encoding of the entries" do
dirs = Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
end
ruby_version_is ""..."2.7" do
it "accepts nil options" do
dirs = Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested", nil).to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding.find("filesystem")}
end
end
it "yields all names in an existing directory to the provided block" do it "yields all names in an existing directory to the provided block" do
a, b = [], [] a, b = [], []

View file

@ -35,9 +35,16 @@ describe "Dir.entries" do
Dir.entries(p) Dir.entries(p)
end end
it "accepts an options Hash" do it "accepts an encoding keyword for the encoding of the entries" do
a = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort dirs = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort
a.should == %w|. .. .dotfile.ext directory| dirs.each {|dir| dir.encoding.should == Encoding::UTF_8}
end
ruby_version_is ""..."2.7" do
it "accepts nil options" do
dirs = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", nil).to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding.find("filesystem")}
end
end end
it "returns entries encoded with the filesystem encoding by default" do it "returns entries encoded with the filesystem encoding by default" do

View file

@ -39,6 +39,18 @@ describe "Dir.foreach" do
Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths
end end
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.each {|dir| dir.encoding.should == Encoding::UTF_8}
end
ruby_version_is ""..."2.7" do
it "accepts nil options" do
dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", nil).to_a.sort
dirs.each {|dir| dir.encoding.should == Encoding.find("filesystem")}
end
end
describe "when no block is given" do describe "when no block is given" do
it "returns an Enumerator" do it "returns an Enumerator" do
Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator) Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator)

View file

@ -6,4 +6,9 @@ describe "Enumerator::Lazy#chunk_while" do
s.lazy.chunk_while { |a, b| false }.first(100).should == s.lazy.chunk_while { |a, b| false }.first(100).should ==
s.first(100).chunk_while { |a, b| false }.to_a s.first(100).chunk_while { |a, b| false }.to_a
end end
it "should return a lazy enumerator" do
s = 0..Float::INFINITY
s.lazy.chunk_while { |a, b| false }.should be_kind_of(Enumerator::Lazy)
end
end end

View file

@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_after" do
s.lazy.slice_after { |n| true }.first(100).should == s.lazy.slice_after { |n| true }.first(100).should ==
s.first(100).slice_after { |n| true }.to_a s.first(100).slice_after { |n| true }.to_a
end end
it "should return a lazy enumerator" do
s = 0..Float::INFINITY
s.lazy.slice_after { |n| true }.should be_kind_of(Enumerator::Lazy)
end
end end

View file

@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_before" do
s.lazy.slice_before { |n| true }.first(100).should == s.lazy.slice_before { |n| true }.first(100).should ==
s.first(100).slice_before { |n| true }.to_a s.first(100).slice_before { |n| true }.to_a
end end
it "should return a lazy enumerator" do
s = 0..Float::INFINITY
s.lazy.slice_before { |n| true }.should be_kind_of(Enumerator::Lazy)
end
end end

View file

@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_when" do
s.lazy.slice_when { |a, b| true }.first(100).should == s.lazy.slice_when { |a, b| true }.first(100).should ==
s.first(100).slice_when { |a, b| true }.to_a s.first(100).slice_when { |a, b| true }.to_a
end end
it "should return a lazy enumerator" do
s = 0..Float::INFINITY
s.lazy.slice_when { |a, b| true }.should be_kind_of(Enumerator::Lazy)
end
end end

36
spec/ruby/core/env/except_spec.rb vendored Normal file
View file

@ -0,0 +1,36 @@
require_relative 'spec_helper'
require_relative 'shared/to_hash'
ruby_version_is "3.0" do
describe "ENV.except" do
before do
@orig_hash = ENV.to_hash
end
after do
ENV.replace @orig_hash
end
# Testing the method without arguments is covered via
it_behaves_like :env_to_hash, :except
it "returns a hash without the requested subset" do
ENV.clear
ENV['one'] = '1'
ENV['two'] = '2'
ENV['three'] = '3'
ENV.except('one', 'three').should == { 'two' => '2' }
end
it "ignores keys not present in the original hash" do
ENV.clear
ENV['one'] = '1'
ENV['two'] = '2'
ENV.except('one', 'three').should == { 'two' => '2' }
end
end
end

View file

@ -45,19 +45,36 @@ describe "IO#close" do
end end
it 'raises an IOError with a clear message' do it 'raises an IOError with a clear message' do
read_io, write_io = IO.pipe matching_exception = nil
going_to_read = false
thread = Thread.new do
-> do
going_to_read = true
read_io.read
end.should raise_error(IOError, 'stream closed in another thread')
end
Thread.pass until going_to_read && thread.stop? -> do
read_io.close IOSpecs::THREAD_CLOSE_RETRIES.times do
thread.join read_io, write_io = IO.pipe
write_io.close going_to_read = false
thread = Thread.new do
begin
going_to_read = true
read_io.read
rescue IOError => ioe
if ioe.message == IOSpecs::THREAD_CLOSE_ERROR_MESSAGE
matching_exception = ioe
end
# try again
end
end
# best attempt to ensure the thread is actually blocked on read
Thread.pass until going_to_read && thread.stop?
sleep(0.001)
read_io.close
thread.join
write_io.close
matching_exception&.tap {|ex| raise ex}
end
end.should raise_error(IOError, IOSpecs::THREAD_CLOSE_ERROR_MESSAGE)
end end
end end

View file

@ -1,6 +1,9 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
module IOSpecs module IOSpecs
THREAD_CLOSE_RETRIES = 10
THREAD_CLOSE_ERROR_MESSAGE = 'stream closed in another thread'
class SubIO < IO class SubIO < IO
end end

View file

@ -362,18 +362,19 @@ module KernelSpecs
class RespondViaMissing class RespondViaMissing
def respond_to_missing?(method, priv=false) def respond_to_missing?(method, priv=false)
case method case method
when :handled_publicly when :handled_publicly
true true
when :handled_privately when :handled_privately
priv priv
when :not_handled when :not_handled
false false
else else
raise "Typo in method name" raise "Typo in method name: #{method.inspect}"
end end
end end
def method_missing(method, *args) def method_missing(method, *args)
raise "the method name should be a Symbol" unless Symbol === method
"Done #{method}(#{args})" "Done #{method}(#{args})"
end end
end end

View file

@ -104,5 +104,13 @@ describe "Kernel#public_send" do
}.should raise_error(NoMethodError) }.should raise_error(NoMethodError)
end end
it "includes `public_send` in the backtrace when passed not enough arguments" do
-> { public_send() }.should raise_error(ArgumentError) { |e| e.backtrace[0].should.include?("`public_send'") }
end
it "includes `public_send` in the backtrace when passed a single incorrect argument" do
-> { public_send(Object.new) }.should raise_error(TypeError) { |e| e.backtrace[0].should.include?("`public_send'") }
end
it_behaves_like :basicobject_send, :public_send it_behaves_like :basicobject_send, :public_send
end end

View file

@ -15,12 +15,18 @@ describe :kernel_method, shared: true do
m.call.should == 'class done' m.call.should == 'class done'
end end
it "returns a method object if we repond_to_missing? method" do it "returns a method object if respond_to_missing?(method) is true" do
m = KernelSpecs::RespondViaMissing.new.send(@method, :handled_publicly) m = KernelSpecs::RespondViaMissing.new.send(@method, :handled_publicly)
m.should be_an_instance_of Method m.should be_an_instance_of Method
m.call(42).should == "Done handled_publicly([42])" m.call(42).should == "Done handled_publicly([42])"
end end
it "the returned method object if respond_to_missing?(method) calls #method_missing with a Symbol name" do
m = KernelSpecs::RespondViaMissing.new.send(@method, "handled_publicly")
m.should be_an_instance_of Method
m.call(42).should == "Done handled_publicly([42])"
end
it "raises a NameError for an invalid method name" do it "raises a NameError for an invalid method name" do
class KernelSpecs::Foo; def bar; 'done'; end; end class KernelSpecs::Foo; def bar; 'done'; end; end
-> { -> {

View file

@ -546,8 +546,12 @@ describe :kernel_require, shared: true do
ScratchPad.recorded.should == [] ScratchPad.recorded.should == []
end end
it "complex, enumerator, rational and thread are already required" do provided = %w[complex enumerator rational thread]
provided = %w[complex enumerator rational thread] ruby_version_is "2.7" do
provided << 'ruby2_keywords'
end
it "#{provided.join(', ')} are already required" do
features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems') features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems')
provided.each { |feature| provided.each { |feature|
features.should =~ /\b#{feature}\.(rb|so|jar)$/ features.should =~ /\b#{feature}\.(rb|so|jar)$/

View file

@ -0,0 +1,11 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
ruby_version_is "2.7" do
describe "main.ruby2_keywords" do
it "is the same as Object.ruby2_keywords" do
main = TOPLEVEL_BINDING.receiver
main.should have_private_method(:ruby2_keywords)
end
end
end

View file

@ -33,7 +33,20 @@ describe "Module#attr_accessor" do
attr_accessor :spec_attr_accessor attr_accessor :spec_attr_accessor
end end
-> { true.spec_attr_accessor = "a" }.should raise_error(RuntimeError) -> { true.spec_attr_accessor = "a" }.should raise_error(FrozenError)
end
it "raises FrozenError if the receiver if frozen" do
c = Class.new do
attr_accessor :foo
end
obj = c.new
obj.foo = 1
obj.foo.should == 1
obj.freeze
-> { obj.foo = 42 }.should raise_error(FrozenError)
obj.foo.should == 1
end end
it "converts non string/symbol names to strings using to_str" do it "converts non string/symbol names to strings using to_str" do

View file

@ -29,7 +29,17 @@ describe "Module#attr_writer" do
attr_writer :spec_attr_writer attr_writer :spec_attr_writer
end end
-> { true.spec_attr_writer = "a" }.should raise_error(RuntimeError) -> { true.spec_attr_writer = "a" }.should raise_error(FrozenError)
end
it "raises FrozenError if the receiver if frozen" do
c = Class.new do
attr_writer :foo
end
obj = c.new
obj.freeze
-> { obj.foo = 42 }.should raise_error(FrozenError)
end end
it "converts non string/symbol names to strings using to_str" do it "converts non string/symbol names to strings using to_str" do

View file

@ -40,6 +40,11 @@ describe "Module#const_defined?" do
ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, true).should be_true ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, true).should be_true
end end
it "coerces the inherit flag to a boolean" do
ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, nil).should be_false
ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, :true).should be_true
end
it "returns true if the given String names a constant defined in the receiver" do it "returns true if the given String names a constant defined in the receiver" do
ConstantSpecs.const_defined?("CS_CONST2").should == true ConstantSpecs.const_defined?("CS_CONST2").should == true
ConstantSpecs.const_defined?("ModuleA").should == true ConstantSpecs.const_defined?("ModuleA").should == true

View file

@ -88,6 +88,14 @@ describe "Module#const_get" do
end.should raise_error(NameError) end.should raise_error(NameError)
end end
it "coerces the inherit flag to a boolean" do
ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, :true).should == :const4
-> do
ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1, nil)
end.should raise_error(NameError)
end
it "accepts a toplevel scope qualifier" do it "accepts a toplevel scope qualifier" do
ConstantSpecs.const_get("::CS_CONST1").should == :const1 ConstantSpecs.const_get("::CS_CONST1").should == :const1
end end

View file

@ -573,6 +573,43 @@ describe "Module#refine" do
end end
end end
ruby_version_is "" ... "2.7" do
it "is not honored by Kernel#public_method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
-> {
Module.new do
using refinement
klass.new.public_method(:foo)
end
}.should raise_error(NameError, /undefined method `foo'/)
end
end
ruby_version_is "2.7" do
it "is honored by Kernel#public_method" do
klass = Class.new
refinement = Module.new do
refine klass do
def foo; end
end
end
result = nil
Module.new do
using refinement
result = klass.new.public_method(:foo).class
end
result.should == Method
end
end
ruby_version_is "" ... "2.7" do ruby_version_is "" ... "2.7" do
it "is not honored by Kernel#instance_method" do it "is not honored by Kernel#instance_method" do
klass = Class.new klass = Class.new
@ -592,7 +629,7 @@ describe "Module#refine" do
end end
ruby_version_is "2.7" do ruby_version_is "2.7" do
it "is honored by Kernel#method" do it "is honored by Kernel#instance_method" do
klass = Class.new klass = Class.new
refinement = Module.new do refinement = Module.new do
refine klass do refine klass do

View file

@ -49,6 +49,9 @@ describe :proc_call_on_proc_or_lambda, shared: true do
a = proc {|x| x}.send(@method, 1, 2, 3) a = proc {|x| x}.send(@method, 1, 2, 3)
a.should == 1 a.should == 1
a = proc {|x:| x}.send(@method, 2, x: 1)
a.should == 1
end end
it "will call #to_ary on argument and return self if return is nil" do it "will call #to_ary on argument and return self if return is nil" do

View file

@ -22,4 +22,14 @@ describe "Regexp#===" do
(/abc/ === nil).should be_false (/abc/ === nil).should be_false
(/abc/ === /abc/).should be_false (/abc/ === /abc/).should be_false
end end
it "uses #to_str on string-like objects" do
stringlike = Class.new do
def to_str
"abc"
end
end.new
(/abc/ === stringlike).should be_true
end
end end

View file

@ -49,7 +49,21 @@ platform_is_not :windows do
it "registers an handler doing nothing with :IGNORE" do it "registers an handler doing nothing with :IGNORE" do
Signal.trap :HUP, :IGNORE Signal.trap :HUP, :IGNORE
Signal.trap(:HUP, @saved_trap).should == "IGNORE"
end
it "can register a new handler after :IGNORE" do
Signal.trap :HUP, :IGNORE
done = false
Signal.trap(:HUP) do
ScratchPad.record :block_trap
done = true
end
Process.kill(:HUP, Process.pid).should == 1 Process.kill(:HUP, Process.pid).should == 1
Thread.pass until done
ScratchPad.recorded.should == :block_trap
end end
it "ignores the signal when passed nil" do it "ignores the signal when passed nil" do

View file

@ -105,4 +105,18 @@ describe "String#scrub!" do
input.scrub! { |b| "<?>" } input.scrub! { |b| "<?>" }
input.should == "a<?>" input.should == "a<?>"
end end
it "maintains the state of frozen strings that are already valid" do
input = "a"
input.freeze
input.scrub!
input.frozen?.should be_true
end
it "preserves the instance variables of already valid strings" do
input = "a"
input.instance_variable_set(:@a, 'b')
input.scrub!
input.instance_variable_get(:@a).should == 'b'
end
end end

View file

@ -19,15 +19,15 @@ describe :time_now, shared: true do
end end
it "has at least microsecond precision" do it "has at least microsecond precision" do
times = []
10_000.times do
times << Time.now.nsec
end
# The clock should not be less accurate than expected (times should # The clock should not be less accurate than expected (times should
# not all be a multiple of the next precision up, assuming precisions # not all be a multiple of the next precision up, assuming precisions
# are multiples of ten.) # are multiples of ten.)
expected = 1_000 expected = 1_000
times.select { |t| t % (expected * 10) == 0 }.size.should_not == times.size t = 0
10_000.times.find do
t = Time.now.nsec
t % (expected * 10) != 0
end
(t % (expected * 10)).should != 0
end end
end end

View file

@ -2,7 +2,7 @@ ScratchPad.recorded << :con_pre
Thread.current[:in_concurrent_rb] = true Thread.current[:in_concurrent_rb] = true
if t = Thread.current[:wait_for] if t = Thread.current[:wait_for]
Thread.pass until t.backtrace && t.backtrace.any? { |call| call.include? 'require' } Thread.pass until t.backtrace && t.backtrace.any? { |call| call.include? 'require' } && t.stop?
end end
if Thread.current[:con_raise] if Thread.current[:con_raise]

View file

@ -63,6 +63,11 @@ describe "Regexps with back-references" do
(/\10()()()()()()()()()()/ =~ "\x08").should == 0 (/\10()()()()()()()()()()/ =~ "\x08").should == 0
end end
it "fails when trying to match a backreference to an unmatched capture group" do
/\1()/.match("").should == nil
/(?:(a)|b)\1/.match("b").should == nil
end
it "ignores backreferences > 1000" do it "ignores backreferences > 1000" do
/\99999/.match("99999")[0].should == "99999" /\99999/.match("99999")[0].should == "99999"
end end

View file

@ -0,0 +1,135 @@
require_relative '../../spec_helper'
require_relative '../fixtures/classes'
describe "empty checks in Regexps" do
it "allow extra empty iterations" do
/()?/.match("").to_a.should == ["", ""]
/(a*)?/.match("").to_a.should == ["", ""]
/(a*)*/.match("").to_a.should == ["", ""]
# The bounds are high to avoid DFA-based matchers in implementations
# and to check backtracking behavior.
/(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
# Variations with non-greedy loops.
/()??/.match("").to_a.should == ["", nil]
/(a*?)?/.match("").to_a.should == ["", ""]
/(a*)??/.match("").to_a.should == ["", nil]
/(a*?)??/.match("").to_a.should == ["", nil]
/(a*?)*/.match("").to_a.should == ["", ""]
/(a*)*?/.match("").to_a.should == ["", nil]
/(a*?)*?/.match("").to_a.should == ["", nil]
end
it "allow empty iterations in the middle of a loop" do
# One empty iteration between a's and b's.
/(a|\2b|())*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
/(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
# Two empty iterations between a's and b's.
/(a|\2b|\3()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", ""]
/(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
# Check that the empty iteration correctly updates the loop counter.
/(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
# Variations with non-greedy loops.
/(a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
/(a|\2b|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", ""]
/(a|\2b|\3()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
/(a|\2b|\3()|()){2,4}/.match("aaabbb").to_a.should == ["aaa", "", nil, ""]
/(a|\2b|()){20,24}/.match("a" * 20 + "b" * 5).to_a.should == ["a" * 20 + "b" * 3, "b", ""]
end
it "make the Regexp proceed past the quantified expression on failure" do
# If the contents of the ()* quantified group are empty (i.e., they fail
# the empty check), the loop will abort. It will not try to backtrack
# and try other alternatives (e.g. matching the "a") like in other Regexp
# dialects such as ECMAScript.
/(?:|a)*/.match("aaa").to_a.should == [""]
/(?:()|a)*/.match("aaa").to_a.should == ["", ""]
/(|a)*/.match("aaa").to_a.should == ["", ""]
/(()|a)*/.match("aaa").to_a.should == ["", "", ""]
# Same expressions, but with backreferences, to force the use of non-DFA-based
# engines.
/()\1(?:|a)*/.match("aaa").to_a.should == ["", ""]
/()\1(?:()|a)*/.match("aaa").to_a.should == ["", "", ""]
/()\1(|a)*/.match("aaa").to_a.should == ["", "", ""]
/()\1(()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
# Variations with other zero-width contents of the quantified
# group: backreferences, capture groups, lookarounds
/()(?:\1|a)*/.match("aaa").to_a.should == ["", ""]
/()(?:()\1|a)*/.match("aaa").to_a.should == ["", "", ""]
/()(?:(\1)|a)*/.match("aaa").to_a.should == ["", "", ""]
/()(?:\1()|a)*/.match("aaa").to_a.should == ["", "", ""]
/()(\1|a)*/.match("aaa").to_a.should == ["", "", ""]
/()(()\1|a)*/.match("aaa").to_a.should == ["", "", "", ""]
/()((\1)|a)*/.match("aaa").to_a.should == ["", "", "", ""]
/()(\1()|a)*/.match("aaa").to_a.should == ["", "", "", ""]
/(?:(?=a)|a)*/.match("aaa").to_a.should == [""]
/(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", ""]
/(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
/(?:((?=a))|a)*/.match("aaa").to_a.should == ["", ""]
/()\1(?:(?=a)|a)*/.match("aaa").to_a.should == ["", ""]
/()\1(?:(?=a)()|a)*/.match("aaa").to_a.should == ["", "", ""]
/()\1(?:()(?=a)|a)*/.match("aaa").to_a.should == ["", "", ""]
/()\1(?:((?=a))|a)*/.match("aaa").to_a.should == ["", "", ""]
# Variations with non-greedy loops.
/(?:|a)*?/.match("aaa").to_a.should == [""]
/(?:()|a)*?/.match("aaa").to_a.should == ["", nil]
/(|a)*?/.match("aaa").to_a.should == ["", nil]
/(()|a)*?/.match("aaa").to_a.should == ["", nil, nil]
/()\1(?:|a)*?/.match("aaa").to_a.should == ["", ""]
/()\1(?:()|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()\1(|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()\1(()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
/()(?:\1|a)*?/.match("aaa").to_a.should == ["", ""]
/()(?:()\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()(?:(\1)|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()(?:\1()|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()(\1|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()(()\1|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
/()((\1)|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
/()(\1()|a)*?/.match("aaa").to_a.should == ["", "", nil, nil]
/(?:(?=a)|a)*?/.match("aaa").to_a.should == [""]
/(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", nil]
/(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", nil]
/(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", nil]
/()\1(?:(?=a)|a)*?/.match("aaa").to_a.should == ["", ""]
/()\1(?:(?=a)()|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()\1(?:()(?=a)|a)*?/.match("aaa").to_a.should == ["", "", nil]
/()\1(?:((?=a))|a)*?/.match("aaa").to_a.should == ["", "", nil]
end
it "shouldn't cause the Regexp parser to get stuck in a loop" do
/(|a|\2b|())*/.match("aaabbb").to_a.should == ["", "", nil]
/(a||\2b|())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
/(a|\2b||())*/.match("aaabbb").to_a.should == ["aaa", "", nil]
/(a|\2b|()|)*/.match("aaabbb").to_a.should == ["aaabbb", "", ""]
/(()|a|\3b|())*/.match("aaabbb").to_a.should == ["", "", "", nil]
/(a|()|\3b|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
/(a|\2b|()|())*/.match("aaabbb").to_a.should == ["aaabbb", "", "", nil]
/(a|\3b|()|())*/.match("aaabbb").to_a.should == ["aaa", "", "", nil]
/(a|()|())*/.match("aaa").to_a.should == ["aaa", "", "", nil]
/^(()|a|())*$/.match("aaa").to_a.should == ["aaa", "", "", nil]
# Variations with non-greedy loops.
/(|a|\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
/(a||\2b|())*?/.match("aaabbb").to_a.should == ["", nil, nil]
/(a|\2b||())*?/.match("aaabbb").to_a.should == ["", nil, nil]
/(a|\2b|()|)*?/.match("aaabbb").to_a.should == ["", nil, nil]
/(()|a|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
/(a|()|\3b|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
/(a|\2b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
/(a|\3b|()|())*?/.match("aaabbb").to_a.should == ["", nil, nil, nil]
/(a|()|())*?/.match("aaa").to_a.should == ["", nil, nil, nil]
/^(()|a|())*?$/.match("aaa").to_a.should == ["aaa", "a", "", nil]
end
end

View file

@ -128,4 +128,15 @@ describe "Regexps with repetition" do
RUBY RUBY
end end
end end
it "treats ? after {n} quantifier as another quantifier, not as non-greedy marker" do
/a{2}?/.match("").to_a.should == [""]
end
it "matches zero-width capture groups in optional iterations of loops" do
/()?/.match("").to_a.should == ["", ""]
/(a*)?/.match("").to_a.should == ["", ""]
/(a*)*/.match("").to_a.should == ["", ""]
/(?:a|()){500,1000}/.match("a" * 500).to_a.should == ["a" * 500, ""]
end
end end

View file

@ -34,7 +34,7 @@ describe "Matrix#**" do
end end
end end
ruby_bug '#17521', ''...'3.0.1' do ruby_version_is '3.0.1' do # https://bugs.ruby-lang.org/issues/17521
describe "that is 0" do describe "that is 0" do
it "returns the identity for square matrices" do it "returns the identity for square matrices" do
m = Matrix[ [1, 1], [1, 1] ] m = Matrix[ [1, 1], [1, 1] ]

View file

@ -14,6 +14,31 @@ describe "Set#initialize" do
s.should include(3) s.should include(3)
end end
it "uses #each_entry on the provided Enumerable" do
enumerable = MockObject.new('mock-enumerable')
enumerable.should_receive(:each_entry).and_yield(1).and_yield(2).and_yield(3)
s = Set.new(enumerable)
s.size.should eql(3)
s.should include(1)
s.should include(2)
s.should include(3)
end
it "uses #each on the provided Enumerable if it does not respond to #each_entry" do
enumerable = MockObject.new('mock-enumerable')
enumerable.should_receive(:each).and_yield(1).and_yield(2).and_yield(3)
s = Set.new(enumerable)
s.size.should eql(3)
s.should include(1)
s.should include(2)
s.should include(3)
end
it "raises if the provided Enumerable does not respond to #each_entry or #each" do
enumerable = MockObject.new('mock-enumerable')
-> { Set.new(enumerable) }.should raise_error(ArgumentError, "value must be enumerable")
end
it "should initialize with empty array and set" do it "should initialize with empty array and set" do
s = Set.new([]) s = Set.new([])
s.size.should eql(0) s.size.should eql(0)

View file

@ -36,6 +36,50 @@ describe "C-API Exception function" do
runtime_error.set_backtrace [] runtime_error.set_backtrace []
-> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42') -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42')
end end
it "sets $! to the raised exception when not rescuing from an another exception" do
runtime_error = RuntimeError.new '42'
runtime_error.set_backtrace []
begin
@s.rb_exc_raise(runtime_error)
rescue
$!.should == runtime_error
end
end
it "sets $! to the raised exception when $! when rescuing from an another exception" do
runtime_error = RuntimeError.new '42'
runtime_error.set_backtrace []
begin
begin
raise StandardError
rescue
@s.rb_exc_raise(runtime_error)
end
rescue
$!.should == runtime_error
end
end
end
describe "rb_errinfo" do
it "is cleared when entering a C method" do
begin
raise StandardError
rescue
$!.class.should == StandardError
@s.rb_errinfo().should == nil
end
end
it "does not clear $! in the calling method" do
begin
raise StandardError
rescue
@s.rb_errinfo()
$!.class.should == StandardError
end
end
end end
describe "rb_set_errinfo" do describe "rb_set_errinfo" do

View file

@ -8,6 +8,10 @@
extern "C" { extern "C" {
#endif #endif
VALUE exception_spec_rb_errinfo(VALUE self) {
return rb_errinfo();
}
VALUE exception_spec_rb_exc_new(VALUE self, VALUE str) { VALUE exception_spec_rb_exc_new(VALUE self, VALUE str) {
char *cstr = StringValuePtr(str); char *cstr = StringValuePtr(str);
return rb_exc_new(rb_eException, cstr, strlen(cstr)); return rb_exc_new(rb_eException, cstr, strlen(cstr));
@ -41,6 +45,7 @@ VALUE exception_spec_rb_make_exception(VALUE self, VALUE ary) {
void Init_exception_spec(void) { void Init_exception_spec(void) {
VALUE cls = rb_define_class("CApiExceptionSpecs", rb_cObject); VALUE cls = rb_define_class("CApiExceptionSpecs", rb_cObject);
rb_define_method(cls, "rb_errinfo", exception_spec_rb_errinfo, 0);
rb_define_method(cls, "rb_exc_new", exception_spec_rb_exc_new, 1); rb_define_method(cls, "rb_exc_new", exception_spec_rb_exc_new, 1);
rb_define_method(cls, "rb_exc_new2", exception_spec_rb_exc_new2, 1); rb_define_method(cls, "rb_exc_new2", exception_spec_rb_exc_new2, 1);
rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1); rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1);

View file

@ -227,6 +227,16 @@ static VALUE io_spec_errno_set(VALUE self, VALUE val) {
return val; return val;
} }
VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) {
rb_io_t *fp;
GetOpenFile(io, fp);
if (fp->mode & FMODE_SYNC) {
return Qtrue;
} else {
return Qfalse;
}
}
void Init_io_spec(void) { void Init_io_spec(void) {
VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject);
rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1); rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1);
@ -251,6 +261,7 @@ void Init_io_spec(void) {
rb_define_method(cls, "rb_fd_fix_cloexec", io_spec_rb_fd_fix_cloexec, 1); rb_define_method(cls, "rb_fd_fix_cloexec", io_spec_rb_fd_fix_cloexec, 1);
rb_define_method(cls, "rb_cloexec_open", io_spec_rb_cloexec_open, 3); rb_define_method(cls, "rb_cloexec_open", io_spec_rb_cloexec_open, 3);
rb_define_method(cls, "errno=", io_spec_errno_set, 1); rb_define_method(cls, "errno=", io_spec_errno_set, 1);
rb_define_method(cls, "rb_io_mode_sync_flag", io_spec_mode_sync_flag, 1);
} }
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -310,10 +310,6 @@ static VALUE kernel_spec_rb_make_backtrace(VALUE self) {
return rb_make_backtrace(); return rb_make_backtrace();
} }
static VALUE kernel_spec_rb_obj_method(VALUE self, VALUE obj, VALUE method) {
return rb_obj_method(obj, method);
}
static VALUE kernel_spec_rb_funcall3(VALUE self, VALUE obj, VALUE method) { static VALUE kernel_spec_rb_funcall3(VALUE self, VALUE obj, VALUE method) {
return rb_funcall3(obj, SYM2ID(method), 0, NULL); return rb_funcall3(obj, SYM2ID(method), 0, NULL);
} }
@ -366,7 +362,6 @@ 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_obj_method", kernel_spec_rb_obj_method, 2);
rb_define_method(cls, "rb_funcall3", kernel_spec_rb_funcall3, 2); rb_define_method(cls, "rb_funcall3", kernel_spec_rb_funcall3, 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

@ -147,6 +147,10 @@ static VALUE object_specs_rb_obj_method_arity(VALUE self, VALUE obj, VALUE mid)
return INT2FIX(rb_obj_method_arity(obj, SYM2ID(mid))); return INT2FIX(rb_obj_method_arity(obj, SYM2ID(mid)));
} }
static VALUE object_specs_rb_obj_method(VALUE self, VALUE obj, VALUE method) {
return rb_obj_method(obj, method);
}
static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) { static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) {
return rb_obj_taint(obj); return rb_obj_taint(obj);
} }
@ -420,6 +424,7 @@ void Init_object_spec(void) {
rb_define_method(cls, "rb_obj_is_instance_of", so_instance_of, 2); rb_define_method(cls, "rb_obj_is_instance_of", so_instance_of, 2);
rb_define_method(cls, "rb_obj_is_kind_of", so_kind_of, 2); rb_define_method(cls, "rb_obj_is_kind_of", so_kind_of, 2);
rb_define_method(cls, "rb_obj_method_arity", object_specs_rb_obj_method_arity, 2); rb_define_method(cls, "rb_obj_method_arity", object_specs_rb_obj_method_arity, 2);
rb_define_method(cls, "rb_obj_method", object_specs_rb_obj_method, 2);
rb_define_method(cls, "rb_obj_taint", object_spec_rb_obj_taint, 1); rb_define_method(cls, "rb_obj_taint", object_spec_rb_obj_taint, 1);
rb_define_method(cls, "rb_require", so_require, 0); rb_define_method(cls, "rb_require", so_require, 0);
rb_define_method(cls, "rb_respond_to", so_respond_to, 2); rb_define_method(cls, "rb_respond_to", so_respond_to, 2);
@ -462,7 +467,7 @@ void Init_object_spec(void) {
rb_define_method(cls, "rb_undef_alloc_func", undef_alloc_func, 1); rb_define_method(cls, "rb_undef_alloc_func", undef_alloc_func, 1);
rb_define_method(cls, "speced_allocator?", speced_allocator_p, 1); rb_define_method(cls, "speced_allocator?", speced_allocator_p, 1);
rb_define_method(cls, "custom_alloc_func?", custom_alloc_func_p, 1); rb_define_method(cls, "custom_alloc_func?", custom_alloc_func_p, 1);
rb_define_method(cls, "not_implemented_method", rb_f_notimplement, 1); rb_define_method(cls, "not_implemented_method", rb_f_notimplement, -1);
} }
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -375,3 +375,29 @@ describe "rb_cloexec_open" do
@io.close_on_exec?.should be_true @io.close_on_exec?.should be_true
end end
end end
describe "rb_io_t modes flags" do
before :each do
@o = CApiIOSpecs.new
@name = tmp("c_api_rb_io_specs")
touch @name
end
after :each do
rm_r @name
end
it "has the sync flag set if the IO object is synced in Ruby" do
File.open(@name) { |io|
io.sync = true
@o.rb_io_mode_sync_flag(io).should == true
}
end
it "has the sync flag unset if the IO object is not synced in Ruby" do
File.open(@name) { |io|
io.sync = false
@o.rb_io_mode_sync_flag(io).should == false
}
end
end

View file

@ -559,20 +559,6 @@ describe "C-API Kernel function" do
end end
end end
describe "rb_obj_method" do
it "returns the method object for a symbol" do
method = @s.rb_obj_method("test", :size)
method.owner.should == String
method.name.to_sym.should == :size
end
it "returns the method object for a string" do
method = @s.rb_obj_method("test", "size")
method.owner.should == String
method.name.to_sym.should == :size
end
end
describe "rb_funcall3" do describe "rb_funcall3" do
before :each do before :each do
@obj = Object.new @obj = Object.new

View file

@ -246,11 +246,23 @@ describe "CApiModule" do
cls.new.test_method.should == :test_method cls.new.test_method.should == :test_method
end end
it "returns the correct arity of the method in class" do
cls = Class.new
@m.rb_define_method(cls, "test_method")
cls.new.method(:test_method).arity.should == 0
end
it "defines a method on a module" do it "defines a method on a module" do
mod = Module.new mod = Module.new
@m.rb_define_method(mod, "test_method") @m.rb_define_method(mod, "test_method")
mod.should have_instance_method(:test_method) mod.should have_instance_method(:test_method)
end end
it "returns the correct arity of the method in module" do
mod = Module.new
@m.rb_define_method(mod, "test_method")
mod.instance_method(:test_method).arity.should == 0
end
end end
describe "rb_define_module_function" do describe "rb_define_module_function" do
@ -263,12 +275,23 @@ describe "CApiModule" do
@mod.test_module_function.should == :test_method @mod.test_module_function.should == :test_method
end end
it "returns the correct arity of the module function" do
@mod.method(:test_module_function).arity.should == 0
end
it "defines a private instance method" do it "defines a private instance method" do
cls = Class.new cls = Class.new
cls.include(@mod) cls.include(@mod)
cls.should have_private_instance_method(:test_module_function) cls.should have_private_instance_method(:test_module_function)
end end
it "returns the correct arity for private instance method" do
cls = Class.new
cls.include(@mod)
@mod.instance_method(:test_module_function).arity.should == 0
end
end end
describe "rb_define_private_method" do describe "rb_define_private_method" do

View file

@ -161,6 +161,20 @@ describe "CApiObject" do
end end
end end
describe "rb_obj_method" do
it "returns the method object for a symbol" do
method = @o.rb_obj_method("test", :size)
method.owner.should == String
method.name.to_sym.should == :size
end
it "returns the method object for a string" do
method = @o.rb_obj_method("test", "size")
method.owner.should == String
method.name.to_sym.should == :size
end
end
describe "rb_method_boundp" do describe "rb_method_boundp" do
it "returns true when the given method is bound" do it "returns true when the given method is bound" do
@o.rb_method_boundp(Object, :class, true).should == true @o.rb_method_boundp(Object, :class, true).should == true

View file

@ -102,13 +102,13 @@ describe "C-API Thread function" do
end end
describe "rb_thread_call_without_gvl" do describe "rb_thread_call_without_gvl" do
it "runs a C function with the global lock unlocked" do it "runs a C function with the global lock unlocked and can be woken by Thread#wakeup" do
thr = Thread.new do thr = Thread.new do
@t.rb_thread_call_without_gvl @t.rb_thread_call_without_gvl
end end
# Wait until it's blocking... # Wait until it's blocking...
Thread.pass while thr.status and thr.status != "sleep" Thread.pass until thr.stop?
# The thread status is set to sleep by rb_thread_call_without_gvl(), # The thread status is set to sleep by rb_thread_call_without_gvl(),
# but the thread might not be in the blocking read(2) yet, so wait a bit. # but the thread might not be in the blocking read(2) yet, so wait a bit.
@ -121,14 +121,48 @@ describe "C-API Thread function" do
thr.value.should be_true thr.value.should be_true
end end
guard -> { platform_is :mingw and ruby_version_is ""..."2.7" } do platform_is_not :windows do
it "runs a C function with the global lock unlocked and can be woken by a signal" do
# Ruby signal handlers run on the main thread, so we need to reverse roles here and have a thread interrupt us
thr = Thread.current
thr.should == Thread.main
going_to_block = false
interrupter = Thread.new do
# Wait until it's blocking...
Thread.pass until going_to_block and thr.stop?
# The thread status is set to sleep by rb_thread_call_without_gvl(),
# but the thread might not be in the blocking read(2) yet, so wait a bit.
sleep 0.1
# Wake it up by sending a signal
done = false
prev_handler = Signal.trap(:HUP) { done = true }
begin
Process.kill :HUP, Process.pid
sleep 0.001 until done
ensure
Signal.trap(:HUP, prev_handler)
end
end
going_to_block = true
# Make sure it stopped and we got a proper value
@t.rb_thread_call_without_gvl.should be_true
interrupter.join
end
end
guard_not -> { platform_is :mingw and ruby_version_is ""..."2.7" } do
it "runs a C function with the global lock unlocked and unlocks IO with the generic RUBY_UBF_IO" do it "runs a C function with the global lock unlocked and unlocks IO with the generic RUBY_UBF_IO" do
thr = Thread.new do thr = Thread.new do
@t.rb_thread_call_without_gvl_with_ubf_io @t.rb_thread_call_without_gvl_with_ubf_io
end end
# Wait until it's blocking... # Wait until it's blocking...
Thread.pass while thr.status and thr.status != "sleep" Thread.pass until thr.stop?
# The thread status is set to sleep by rb_thread_call_without_gvl(), # The thread status is set to sleep by rb_thread_call_without_gvl(),
# but the thread might not be in the blocking read(2) yet, so wait a bit. # but the thread might not be in the blocking read(2) yet, so wait a bit.