diff options
author | Benoit Daloze <eregontp@gmail.com> | 2021-02-27 13:00:26 +0100 |
---|---|---|
committer | Benoit Daloze <eregontp@gmail.com> | 2021-02-27 13:00:26 +0100 |
commit | 36dde35e029c7a6607e6c674062ce6fc7a51c0bd (patch) | |
tree | 47f9c820a93d5b9a68f7e903cc01ee607913e2dd | |
parent | dbea0be13dc1f44833eca43a73f3ab898fa27c15 (diff) |
Update to ruby/spec@37e52e5
48 files changed, 812 insertions, 65 deletions
diff --git a/spec/ruby/core/array/drop_spec.rb b/spec/ruby/core/array/drop_spec.rb index 84ea86b04c..f911fd9018 100644 --- a/spec/ruby/core/array/drop_spec.rb +++ b/spec/ruby/core/array/drop_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Array#drop" 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) 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 diff --git a/spec/ruby/core/array/drop_while_spec.rb b/spec/ruby/core/array/drop_while_spec.rb index cfb6b1e267..bb783d22a5 100644 --- a/spec/ruby/core/array/drop_while_spec.rb +++ b/spec/ruby/core/array/drop_while_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Array#drop_while" 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 [1, 2, 3, false, 5].drop_while { |n| n }.should == [false, 5] 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 diff --git a/spec/ruby/core/array/slice_spec.rb b/spec/ruby/core/array/slice_spec.rb index f416d8d0ce..2f98df9488 100644 --- a/spec/ruby/core/array/slice_spec.rb +++ b/spec/ruby/core/array/slice_spec.rb @@ -185,6 +185,64 @@ describe "Array#slice!" do a.should == [3, 4] 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 describe "Array#slice" do diff --git a/spec/ruby/core/array/take_spec.rb b/spec/ruby/core/array/take_spec.rb index 0de99b0a7e..4fb6f0ce75 100644 --- a/spec/ruby/core/array/take_spec.rb +++ b/spec/ruby/core/array/take_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Array#take" 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 ->{ [1].take(-3) }.should raise_error(ArgumentError) 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 diff --git a/spec/ruby/core/array/take_while_spec.rb b/spec/ruby/core/array/take_while_spec.rb index f159e6f251..363419b265 100644 --- a/spec/ruby/core/array/take_while_spec.rb +++ b/spec/ruby/core/array/take_while_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Array#take_while" 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 [1, 2, false, 4].take_while{ |element| element }.should == [1, 2] 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 diff --git a/spec/ruby/core/basicobject/singleton_method_added_spec.rb b/spec/ruby/core/basicobject/singleton_method_added_spec.rb index 8d256e22db..ab6b2a2d10 100644 --- a/spec/ruby/core/basicobject/singleton_method_added_spec.rb +++ b/spec/ruby/core/basicobject/singleton_method_added_spec.rb @@ -83,4 +83,63 @@ describe "BasicObject#singleton_method_added" do end ScratchPad.recorded.should == [:singleton_method_added, :new_method_with_define_method] 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 diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb index e0325a24b8..f890f54eb4 100644 --- a/spec/ruby/core/dir/children_spec.rb +++ b/spec/ruby/core/dir/children_spec.rb @@ -100,10 +100,18 @@ ruby_version_is "2.6" do a.should == %w|.dotfile.ext directory| 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") - a = @dir.children.sort - a.should == %w|.dotfile.ext directory| + dirs = @dir.to_a.sort + 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 it "returns children encoded with the filesystem encoding by default" do diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb index 93b4a1aec1..684f42acc0 100644 --- a/spec/ruby/core/dir/each_child_spec.rb +++ b/spec/ruby/core/dir/each_child_spec.rb @@ -10,6 +10,18 @@ describe "Dir.each_child" do DirSpecs.delete_mock_dirs 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 a, b = [], [] diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb index 33568b6fc4..9aa58657db 100644 --- a/spec/ruby/core/dir/entries_spec.rb +++ b/spec/ruby/core/dir/entries_spec.rb @@ -35,9 +35,16 @@ describe "Dir.entries" do Dir.entries(p) end - it "accepts an options Hash" do - a = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort - a.should == %w|. .. .dotfile.ext directory| + it "accepts an encoding keyword for the encoding of the entries" do + dirs = Dir.entries("#{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.entries("#{DirSpecs.mock_dir}/deeply/nested", nil).to_a.sort + dirs.each {|dir| dir.encoding.should == Encoding.find("filesystem")} + end end it "returns entries encoded with the filesystem encoding by default" do diff --git a/spec/ruby/core/dir/foreach_spec.rb b/spec/ruby/core/dir/foreach_spec.rb index 1560b85f8a..c3ddb27a84 100644 --- a/spec/ruby/core/dir/foreach_spec.rb +++ b/spec/ruby/core/dir/foreach_spec.rb @@ -39,6 +39,18 @@ describe "Dir.foreach" do Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths 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 it "returns an Enumerator" do Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator) diff --git a/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb index d555089872..772bd42de9 100644 --- a/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb +++ b/spec/ruby/core/enumerator/lazy/chunk_while_spec.rb @@ -6,4 +6,9 @@ describe "Enumerator::Lazy#chunk_while" do s.lazy.chunk_while { |a, b| false }.first(100).should == s.first(100).chunk_while { |a, b| false }.to_a 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 diff --git a/spec/ruby/core/enumerator/lazy/slice_after_spec.rb b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb index 438df8d550..8b08a1ecfd 100644 --- a/spec/ruby/core/enumerator/lazy/slice_after_spec.rb +++ b/spec/ruby/core/enumerator/lazy/slice_after_spec.rb @@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_after" do s.lazy.slice_after { |n| true }.first(100).should == s.first(100).slice_after { |n| true }.to_a 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 diff --git a/spec/ruby/core/enumerator/lazy/slice_before_spec.rb b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb index 6c8660c1a1..9c1ec9ba4a 100644 --- a/spec/ruby/core/enumerator/lazy/slice_before_spec.rb +++ b/spec/ruby/core/enumerator/lazy/slice_before_spec.rb @@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_before" do s.lazy.slice_before { |n| true }.first(100).should == s.first(100).slice_before { |n| true }.to_a 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 diff --git a/spec/ruby/core/enumerator/lazy/slice_when_spec.rb b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb index e7673def47..f83403425d 100644 --- a/spec/ruby/core/enumerator/lazy/slice_when_spec.rb +++ b/spec/ruby/core/enumerator/lazy/slice_when_spec.rb @@ -6,4 +6,9 @@ describe "Enumerator::Lazy#slice_when" do s.lazy.slice_when { |a, b| true }.first(100).should == s.first(100).slice_when { |a, b| true }.to_a 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 diff --git a/spec/ruby/core/env/except_spec.rb b/spec/ruby/core/env/except_spec.rb new file mode 100644 index 0000000000..cfe5865abe --- /dev/null +++ b/spec/ruby/core/env/except_spec.rb @@ -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 diff --git a/spec/ruby/core/io/close_spec.rb b/spec/ruby/core/io/close_spec.rb index dda82b34de..eb560eaf67 100644 --- a/spec/ruby/core/io/close_spec.rb +++ b/spec/ruby/core/io/close_spec.rb @@ -45,19 +45,36 @@ describe "IO#close" do end it 'raises an IOError with a clear message' do - read_io, write_io = IO.pipe - 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? - read_io.close - thread.join - write_io.close + matching_exception = nil + + -> do + IOSpecs::THREAD_CLOSE_RETRIES.times do + read_io, write_io = IO.pipe + 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 diff --git a/spec/ruby/core/io/fixtures/classes.rb b/spec/ruby/core/io/fixtures/classes.rb index 5cc42c9b44..5d81d5fcd9 100644 --- a/spec/ruby/core/io/fixtures/classes.rb +++ b/spec/ruby/core/io/fixtures/classes.rb @@ -1,6 +1,9 @@ # -*- encoding: utf-8 -*- module IOSpecs + THREAD_CLOSE_RETRIES = 10 + THREAD_CLOSE_ERROR_MESSAGE = 'stream closed in another thread' + class SubIO < IO end diff --git a/spec/ruby/core/kernel/fixtures/classes.rb b/spec/ruby/core/kernel/fixtures/classes.rb index 8702c925c8..1bf5715c50 100644 --- a/spec/ruby/core/kernel/fixtures/classes.rb +++ b/spec/ruby/core/kernel/fixtures/classes.rb @@ -362,18 +362,19 @@ module KernelSpecs class RespondViaMissing def respond_to_missing?(method, priv=false) case method - when :handled_publicly - true - when :handled_privately - priv - when :not_handled - false - else - raise "Typo in method name" + when :handled_publicly + true + when :handled_privately + priv + when :not_handled + false + else + raise "Typo in method name: #{method.inspect}" end end def method_missing(method, *args) + raise "the method name should be a Symbol" unless Symbol === method "Done #{method}(#{args})" end end diff --git a/spec/ruby/core/kernel/public_send_spec.rb b/spec/ruby/core/kernel/public_send_spec.rb index 6b942a2e4b..4dae419ff9 100644 --- a/spec/ruby/core/kernel/public_send_spec.rb +++ b/spec/ruby/core/kernel/public_send_spec.rb @@ -104,5 +104,13 @@ describe "Kernel#public_send" do }.should raise_error(NoMethodError) 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 end diff --git a/spec/ruby/core/kernel/shared/method.rb b/spec/ruby/core/kernel/shared/method.rb index 3418966b1b..8b6ab23fd3 100644 --- a/spec/ruby/core/kernel/shared/method.rb +++ b/spec/ruby/core/kernel/shared/method.rb @@ -15,12 +15,18 @@ describe :kernel_method, shared: true do m.call.should == 'class done' 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.should be_an_instance_of Method m.call(42).should == "Done handled_publicly([42])" 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 class KernelSpecs::Foo; def bar; 'done'; end; end -> { diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index d656b3b2f2..9a2c38be1a 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -546,8 +546,12 @@ describe :kernel_require, shared: true do ScratchPad.recorded.should == [] 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') provided.each { |feature| features.should =~ /\b#{feature}\.(rb|so|jar)$/ diff --git a/spec/ruby/core/main/ruby2_keywords_spec.rb b/spec/ruby/core/main/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..0999cddeed --- /dev/null +++ b/spec/ruby/core/main/ruby2_keywords_spec.rb @@ -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 diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb index e6a6ac7b66..ba5289cbea 100644 --- a/spec/ruby/core/module/attr_accessor_spec.rb +++ b/spec/ruby/core/module/attr_accessor_spec.rb @@ -33,7 +33,20 @@ describe "Module#attr_accessor" do attr_accessor :spec_attr_accessor 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 it "converts non string/symbol names to strings using to_str" do diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb index dbf30c8144..0e9d201317 100644 --- a/spec/ruby/core/module/attr_writer_spec.rb +++ b/spec/ruby/core/module/attr_writer_spec.rb @@ -29,7 +29,17 @@ describe "Module#attr_writer" do attr_writer :spec_attr_writer 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 it "converts non string/symbol names to strings using to_str" do diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb index 0673adca3d..75730395e8 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -40,6 +40,11 @@ describe "Module#const_defined?" do ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, true).should be_true 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 ConstantSpecs.const_defined?("CS_CONST2").should == true ConstantSpecs.const_defined?("ModuleA").should == true diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb index 0a6702903f..9f9fafe5bb 100644 --- a/spec/ruby/core/module/const_get_spec.rb +++ b/spec/ruby/core/module/const_get_spec.rb @@ -88,6 +88,14 @@ describe "Module#const_get" do end.should raise_error(NameError) 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 ConstantSpecs.const_get("::CS_CONST1").should == :const1 end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index 54217a9326..e5f65f5f96 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -574,6 +574,43 @@ describe "Module#refine" do 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 it "is not honored by Kernel#instance_method" do klass = Class.new refinement = Module.new do @@ -592,7 +629,7 @@ describe "Module#refine" do end ruby_version_is "2.7" do - it "is honored by Kernel#method" do + it "is honored by Kernel#instance_method" do klass = Class.new refinement = Module.new do refine klass do diff --git a/spec/ruby/core/proc/shared/call.rb b/spec/ruby/core/proc/shared/call.rb index c30ea84b23..dbec34df4b 100644 --- a/spec/ruby/core/proc/shared/call.rb +++ b/spec/ruby/core/proc/shared/call.rb @@ -49,6 +49,9 @@ describe :proc_call_on_proc_or_lambda, shared: true do a = proc {|x| x}.send(@method, 1, 2, 3) a.should == 1 + + a = proc {|x:| x}.send(@method, 2, x: 1) + a.should == 1 end it "will call #to_ary on argument and return self if return is nil" do diff --git a/spec/ruby/core/regexp/case_compare_spec.rb b/spec/ruby/core/regexp/case_compare_spec.rb index a621713f6f..5ae8b56c6a 100644 --- a/spec/ruby/core/regexp/case_compare_spec.rb +++ b/spec/ruby/core/regexp/case_compare_spec.rb @@ -22,4 +22,14 @@ describe "Regexp#===" do (/abc/ === nil).should be_false (/abc/ === /abc/).should be_false 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 diff --git a/spec/ruby/core/signal/trap_spec.rb b/spec/ruby/core/signal/trap_spec.rb index 831a5ee23e..45879b7147 100644 --- a/spec/ruby/core/signal/trap_spec.rb +++ b/spec/ruby/core/signal/trap_spec.rb @@ -49,7 +49,21 @@ platform_is_not :windows do it "registers an handler doing nothing with :IGNORE" do 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 + Thread.pass until done + ScratchPad.recorded.should == :block_trap end it "ignores the signal when passed nil" do diff --git a/spec/ruby/core/string/scrub_spec.rb b/spec/ruby/core/string/scrub_spec.rb index 86fd4e85ba..b513acfb70 100644 --- a/spec/ruby/core/string/scrub_spec.rb +++ b/spec/ruby/core/string/scrub_spec.rb @@ -105,4 +105,18 @@ describe "String#scrub!" do input.scrub! { |b| "<?>" } input.should == "a<?>" 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 diff --git a/spec/ruby/core/time/shared/now.rb b/spec/ruby/core/time/shared/now.rb index 80f66a1134..f4018d72f4 100644 --- a/spec/ruby/core/time/shared/now.rb +++ b/spec/ruby/core/time/shared/now.rb @@ -19,15 +19,15 @@ describe :time_now, shared: true do end 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 # not all be a multiple of the next precision up, assuming precisions # are multiples of ten.) 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 diff --git a/spec/ruby/fixtures/code/concurrent.rb b/spec/ruby/fixtures/code/concurrent.rb index 054b8fc055..b3602a3db4 100644 --- a/spec/ruby/fixtures/code/concurrent.rb +++ b/spec/ruby/fixtures/code/concurrent.rb @@ -2,7 +2,7 @@ ScratchPad.recorded << :con_pre Thread.current[:in_concurrent_rb] = true 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 if Thread.current[:con_raise] diff --git a/spec/ruby/language/regexp/back-references_spec.rb b/spec/ruby/language/regexp/back-references_spec.rb index e8df8725c5..26750c20c5 100644 --- a/spec/ruby/language/regexp/back-references_spec.rb +++ b/spec/ruby/language/regexp/back-references_spec.rb @@ -63,6 +63,11 @@ describe "Regexps with back-references" do (/\10()()()()()()()()()()/ =~ "\x08").should == 0 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 /\99999/.match("99999")[0].should == "99999" end diff --git a/spec/ruby/language/regexp/empty_checks_spec.rb b/spec/ruby/language/regexp/empty_checks_spec.rb new file mode 100644 index 0000000000..391e65b003 --- /dev/null +++ b/spec/ruby/language/regexp/empty_checks_spec.rb @@ -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 diff --git a/spec/ruby/language/regexp/repetition_spec.rb b/spec/ruby/language/regexp/repetition_spec.rb index 295b3bf553..9a191d74e2 100644 --- a/spec/ruby/language/regexp/repetition_spec.rb +++ b/spec/ruby/language/regexp/repetition_spec.rb @@ -128,4 +128,15 @@ describe "Regexps with repetition" do RUBY 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 diff --git a/spec/ruby/library/matrix/exponent_spec.rb b/spec/ruby/library/matrix/exponent_spec.rb index 71c5f97769..a310cc2c70 100644 --- a/spec/ruby/library/matrix/exponent_spec.rb +++ b/spec/ruby/library/matrix/exponent_spec.rb @@ -34,7 +34,7 @@ describe "Matrix#**" do 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 it "returns the identity for square matrices" do m = Matrix[ [1, 1], [1, 1] ] diff --git a/spec/ruby/library/set/initialize_spec.rb b/spec/ruby/library/set/initialize_spec.rb index 887cae45fc..76ebc0a20a 100644 --- a/spec/ruby/library/set/initialize_spec.rb +++ b/spec/ruby/library/set/initialize_spec.rb @@ -14,6 +14,31 @@ describe "Set#initialize" do s.should include(3) 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 s = Set.new([]) s.size.should eql(0) diff --git a/spec/ruby/optional/capi/exception_spec.rb b/spec/ruby/optional/capi/exception_spec.rb index 9e82e5c7dd..b0a8a2860e 100644 --- a/spec/ruby/optional/capi/exception_spec.rb +++ b/spec/ruby/optional/capi/exception_spec.rb @@ -36,6 +36,50 @@ describe "C-API Exception function" do runtime_error.set_backtrace [] -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42') 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 describe "rb_set_errinfo" do diff --git a/spec/ruby/optional/capi/ext/exception_spec.c b/spec/ruby/optional/capi/ext/exception_spec.c index 7250792b70..e1114aabb8 100644 --- a/spec/ruby/optional/capi/ext/exception_spec.c +++ b/spec/ruby/optional/capi/ext/exception_spec.c @@ -8,6 +8,10 @@ extern "C" { #endif +VALUE exception_spec_rb_errinfo(VALUE self) { + return rb_errinfo(); +} + VALUE exception_spec_rb_exc_new(VALUE self, VALUE str) { char *cstr = StringValuePtr(str); 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) { 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_new2", exception_spec_rb_exc_new2, 1); rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1); diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index 73e6804013..b4ffe9207a 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -227,6 +227,16 @@ static VALUE io_spec_errno_set(VALUE self, VALUE 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) { VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); 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_cloexec_open", io_spec_rb_cloexec_open, 3); 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 diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index 4048684b2c..a1960a2fe7 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -310,10 +310,6 @@ static VALUE kernel_spec_rb_make_backtrace(VALUE self) { 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) { 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_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_obj_method", kernel_spec_rb_obj_method, 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_with_block", kernel_spec_rb_funcall_with_block, 3); diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index a229301f40..24f9de260e 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -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))); } +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) { 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_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", object_specs_rb_obj_method, 2); 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_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, "speced_allocator?", speced_allocator_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 diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index c3c1189a43..489a01c515 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -375,3 +375,29 @@ describe "rb_cloexec_open" do @io.close_on_exec?.should be_true 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 diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 7539e0c01c..ab66f7fa9e 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -559,20 +559,6 @@ describe "C-API Kernel function" do 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 before :each do @obj = Object.new diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb index 9a0c707263..750d64367d 100644 --- a/spec/ruby/optional/capi/module_spec.rb +++ b/spec/ruby/optional/capi/module_spec.rb @@ -246,11 +246,23 @@ describe "CApiModule" do cls.new.test_method.should == :test_method 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 mod = Module.new @m.rb_define_method(mod, "test_method") mod.should have_instance_method(:test_method) 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 describe "rb_define_module_function" do @@ -263,12 +275,23 @@ describe "CApiModule" do @mod.test_module_function.should == :test_method 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 cls = Class.new cls.include(@mod) cls.should have_private_instance_method(:test_module_function) 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 describe "rb_define_private_method" do diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 83d8f023b3..9b3a0593b0 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -161,6 +161,20 @@ describe "CApiObject" do 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 it "returns true when the given method is bound" do @o.rb_method_boundp(Object, :class, true).should == true diff --git a/spec/ruby/optional/capi/thread_spec.rb b/spec/ruby/optional/capi/thread_spec.rb index df454d1ea8..3b788122ac 100644 --- a/spec/ruby/optional/capi/thread_spec.rb +++ b/spec/ruby/optional/capi/thread_spec.rb @@ -102,13 +102,13 @@ describe "C-API Thread function" do end 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 @t.rb_thread_call_without_gvl end # 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(), # 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 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 thr = Thread.new do @t.rb_thread_call_without_gvl_with_ubf_io end # 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(), # but the thread might not be in the blocking read(2) yet, so wait a bit. |