This commit is contained in:
Andrew Konchin 2024-05-09 20:35:01 +03:00 committed by KJ Tsanaktsidis
parent dbbaf871de
commit ad636033e2
31 changed files with 425 additions and 25 deletions

View File

@ -10,4 +10,21 @@ describe "Binding#dup" do
bind.frozen?.should == true
bind.dup.frozen?.should == false
end
it "retains original binding variables but the list is distinct" do
bind1 = binding
eval "a = 1", bind1
bind2 = bind1.dup
eval("a = 2", bind2)
eval("a", bind1).should == 2
eval("a", bind2).should == 2
eval("b = 2", bind2)
-> { eval("b", bind1) }.should raise_error(NameError)
eval("b", bind2).should == 2
bind1.local_variables.sort.should == [:a, :bind1, :bind2]
bind2.local_variables.sort.should == [:a, :b, :bind1, :bind2]
end
end

View File

@ -11,6 +11,7 @@ describe "Enumerator#next_values" do
yield :e1, :e2, :e3
yield nil
yield
yield [:f1, :f2]
end
@e = o.to_enum
@ -48,8 +49,13 @@ describe "Enumerator#next_values" do
@e.next_values.should == []
end
it "raises StopIteration if called on a finished enumerator" do
it "returns an array of array if yield is called with an array" do
7.times { @e.next }
@e.next_values.should == [[:f1, :f2]]
end
it "raises StopIteration if called on a finished enumerator" do
8.times { @e.next }
-> { @e.next_values }.should raise_error(StopIteration)
end
end

View File

@ -11,6 +11,7 @@ describe "Enumerator#peek_values" do
yield :e1, :e2, :e3
yield nil
yield
yield [:f1, :f2]
end
@e = o.to_enum
@ -50,8 +51,13 @@ describe "Enumerator#peek_values" do
@e.peek_values.should == []
end
it "raises StopIteration if called on a finished enumerator" do
it "returns an array of array if yield is called with an array" do
7.times { @e.next }
@e.peek_values.should == [[:f1, :f2]]
end
it "raises StopIteration if called on a finished enumerator" do
8.times { @e.next }
-> { @e.peek_values }.should raise_error(StopIteration)
end
end

View File

@ -22,7 +22,7 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do
it "accepts a length, an offset, and an output buffer" do
buffer = +"foo"
@file.pread(3, 4, buffer)
@file.pread(3, 4, buffer).should.equal?(buffer)
buffer.should == "567"
end
@ -38,6 +38,13 @@ guard -> { platform_is_not :windows or ruby_version_is "3.3" } do
buffer.should == "12345"
end
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@file.pread(10, 0, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
it "does not advance the file pointer" do
@file.pread(4, 0).should == "1234"
@file.read.should == "1234567890"

View File

@ -376,6 +376,21 @@ describe "IO#read" do
buf.should == @contents[0..4]
end
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@io.read(10, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
# https://bugs.ruby-lang.org/issues/20416
it "does not preserve the encoding of the given buffer when max length is not provided" do
buffer = ''.encode(Encoding::ISO_8859_1)
@io.read(nil, buffer)
buffer.encoding.should_not == Encoding::ISO_8859_1
end
it "returns the given buffer" do
buf = +""

View File

@ -62,7 +62,7 @@ describe "IO#readpartial" do
buffer = +"existing content"
@wr.write("hello world")
@wr.close
@rd.readpartial(11, buffer)
@rd.readpartial(11, buffer).should.equal?(buffer)
buffer.should == "hello world"
end
@ -106,6 +106,7 @@ describe "IO#readpartial" do
@wr.write("abc")
@wr.close
@rd.readpartial(10, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
end

View File

@ -97,7 +97,7 @@ describe "IO#sysread on a file" do
it "discards the existing buffer content upon successful read" do
buffer = +"existing content"
@file.sysread(11, buffer)
@file.sysread(11, buffer).should.equal?(buffer)
buffer.should == "01234567890"
end
@ -107,6 +107,13 @@ describe "IO#sysread on a file" do
-> { @file.sysread(1, buffer) }.should raise_error(EOFError)
buffer.should be_empty
end
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
string = @file.sysread(10, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
end
describe "IO#sysread" do

View File

@ -47,6 +47,34 @@ describe "Module#include" do
-> { ModuleSpecs::SubclassSpec.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
end
ruby_version_is ""..."3.2" do
it "raises ArgumentError when the argument is a refinement" do
refinement = nil
Module.new do
refine String do
refinement = self
end
end
-> { ModuleSpecs::Basic.include(refinement) }.should raise_error(ArgumentError, "refinement module is not allowed")
end
end
ruby_version_is "3.2" do
it "raises a TypeError when the argument is a refinement" do
refinement = nil
Module.new do
refine String do
refinement = self
end
end
-> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement")
end
end
it "imports constants to modules and classes" do
ModuleSpecs::A.constants.should include(:CONSTANT_A)
ModuleSpecs::B.constants.should include(:CONSTANT_A, :CONSTANT_B)

View File

@ -75,6 +75,26 @@ describe "Module#prepend" do
foo.call.should == 'm'
end
it "updates the optimized method when a prepended module is updated" do
out = ruby_exe(<<~RUBY)
module M; end
class Integer
prepend M
end
l = -> { 1 + 2 }
p l.call
M.module_eval do
def +(o)
$called = true
super(o)
end
end
p l.call
p $called
RUBY
out.should == "3\n3\ntrue\n"
end
it "updates the method when there is a base included method and the prepended module overrides it" do
base_module = Module.new do
def foo
@ -415,6 +435,34 @@ describe "Module#prepend" do
-> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError)
end
ruby_version_is ""..."3.2" do
it "raises ArgumentError when the argument is a refinement" do
refinement = nil
Module.new do
refine String do
refinement = self
end
end
-> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(ArgumentError, "refinement module is not allowed")
end
end
ruby_version_is "3.2" do
it "raises a TypeError when the argument is a refinement" do
refinement = nil
Module.new do
refine String do
refinement = self
end
end
-> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(TypeError, "Cannot prepend refinement")
end
end
it "imports constants" do
m1 = Module.new
m1::MY_CONSTANT = 1

View File

@ -0,0 +1,28 @@
require_relative '../../spec_helper'
describe "Performance warnings" do
guard -> { ruby_version_is("3.4") || RUBY_ENGINE == "truffleruby" } do
# Optimising Integer, Float or Symbol methods is kind of implementation detail
# but multiple implementations do so. So it seems reasonable to have a test case
# for at least one such common method.
# See https://bugs.ruby-lang.org/issues/20429
context "when redefined optimised methods" do
it "emits performance warning for redefining Integer#+" do
code = <<~CODE
Warning[:performance] = true
class Integer
ORIG_METHOD = instance_method(:+)
def +(...)
ORIG_METHOD.bind(self).call(...)
end
end
CODE
ruby_exe(code, args: "2>&1").should.include?("warning: Redefining 'Integer#+' disables interpreter and JIT optimizations")
end
end
end
end

View File

@ -252,6 +252,25 @@ describe "Break inside a while loop" do
end
end
describe "The break statement in a method" do
it "is invalid and raises a SyntaxError" do
-> {
eval("def m; break; end")
}.should raise_error(SyntaxError)
end
end
describe "The break statement in a module literal" do
it "is invalid and raises a SyntaxError" do
code = <<~RUBY
module BreakSpecs:ModuleWithBreak
break
end
RUBY
-> { eval(code) }.should raise_error(SyntaxError)
end
end
# TODO: Rewrite all the specs from here to the end of the file in the style
# above.

View File

@ -38,7 +38,7 @@ describe "``" do
2.times do
runner.instance_exec do
`test #{:command}`
`test #{:command}` # rubocop:disable Lint/LiteralInInterpolation
end
end
@ -84,7 +84,7 @@ describe "%x" do
2.times do
runner.instance_exec do
%x{test #{:command}}
%x{test #{:command}} # rubocop:disable Lint/LiteralInInterpolation
end
end

View File

@ -86,6 +86,30 @@ describe "Hash literal" do
-> { eval("{:a ==> 1}") }.should raise_error(SyntaxError)
end
it "recognizes '!' at the end of the key" do
eval("{:a! =>1}").should == {:"a!" => 1}
eval("{:a! => 1}").should == {:"a!" => 1}
eval("{a!:1}").should == {:"a!" => 1}
eval("{a!: 1}").should == {:"a!" => 1}
end
it "raises a SyntaxError if there is no space between `!` and `=>`" do
-> { eval("{:a!=> 1}") }.should raise_error(SyntaxError)
end
it "recognizes '?' at the end of the key" do
eval("{:a? =>1}").should == {:"a?" => 1}
eval("{:a? => 1}").should == {:"a?" => 1}
eval("{a?:1}").should == {:"a?" => 1}
eval("{a?: 1}").should == {:"a?" => 1}
end
it "raises a SyntaxError if there is no space between `?` and `=>`" do
-> { eval("{:a?=> 1}") }.should raise_error(SyntaxError)
end
it "constructs a new hash with the given elements" do
{foo: 123}.should == {foo: 123}
h = {rbx: :cool, specs: 'fail_sometimes'}
@ -271,6 +295,14 @@ describe "The ** operator" do
a.new.foo(1).should == {bar: "baz", val: 1}
end
it "raises a SyntaxError when the hash key ends with `!`" do
-> { eval("{a!:}") }.should raise_error(SyntaxError, /identifier a! is not valid to get/)
end
it "raises a SyntaxError when the hash key ends with `?`" do
-> { eval("{a?:}") }.should raise_error(SyntaxError, /identifier a\? is not valid to get/)
end
end
end
end

View File

@ -395,4 +395,32 @@ describe "Keyword arguments" do
end
end
end
context "in define_method(name, &proc)" do
# This tests that a free-standing proc used in define_method and converted to ruby2_keywords adopts that logic.
# See jruby/jruby#8119 for a case where aggressive JIT optimization broke later ruby2_keywords changes.
it "works with ruby2_keywords" do
m = Class.new do
def bar(a, foo: nil)
[a, foo]
end
# define_method and ruby2_keywords using send to avoid peephole optimizations
def self.setup
pr = make_proc
send :define_method, :foo, &pr
send :ruby2_keywords, :foo
end
# create proc in isolated method to force jit compilation on some implementations
def self.make_proc
proc { |a, *args| bar(a, *args) }
end
end
m.setup
m.new.foo(1, foo:2).should == [1, 2]
end
end
end

View File

@ -22,6 +22,15 @@ describe "Regexps with back-references" do
$10.should == "0"
end
it "returns nil for numbered variable with too large index" do
-> {
eval(<<~CODE).should == nil
"a" =~ /(.)/
eval('$4294967296')
CODE
}.should complain(/warning: ('|`)\$4294967296' is too big for a number variable, always nil/)
end
it "will not clobber capture variables across threads" do
cap1, cap2, cap3 = nil
"foo" =~ /(o+)/

View File

@ -31,8 +31,11 @@ describe "The retry statement" do
results.should == [1, 2, 3, 1, 2, 4, 5, 6, 4, 5]
end
it "raises a SyntaxError when used outside of a begin statement" do
it "raises a SyntaxError when used outside of a rescue statement" do
-> { eval 'retry' }.should raise_error(SyntaxError)
-> { eval 'begin; retry; end' }.should raise_error(SyntaxError)
-> { eval 'def m; retry; end' }.should raise_error(SyntaxError)
-> { eval 'module RetrySpecs; retry; end' }.should raise_error(SyntaxError)
end
end

View File

@ -206,3 +206,15 @@ describe "Using yield in non-lambda block" do
-> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
end
end
describe "Using yield in a module literal" do
it 'raises a SyntaxError' do
code = <<~RUBY
module YieldSpecs::ModuleWithYield
yield
end
RUBY
-> { eval(code) }.should raise_error(SyntaxError, /Invalid yield/)
end
end

View File

@ -1,5 +1,5 @@
require_relative '../../spec_helper'
require_relative '../../../spec_helper'
require 'time'
describe "Time#to_date" do

View File

@ -1,4 +1,4 @@
require_relative '../../spec_helper'
require_relative '../../../spec_helper'
require 'time'
require 'date'
date_version = defined?(Date::VERSION) ? Date::VERSION : '3.1.0'

View File

@ -52,9 +52,19 @@ describe "Socket::BasicSocket#recv_nonblock" do
@s2.send("data", 0, @s1.getsockname)
IO.select([@s1], nil, nil, 2)
buf = +"foo"
@s1.recv_nonblock(5, 0, buf)
buf.should == "data"
buffer = +"foo"
@s1.recv_nonblock(5, 0, buffer).should.equal?(buffer)
buffer.should == "data"
end
it "preserves the encoding of the given buffer" do
@s1.bind(Socket.pack_sockaddr_in(0, ip_address))
@s2.send("data", 0, @s1.getsockname)
IO.select([@s1], nil, nil, 2)
buffer = ''.encode(Encoding::ISO_8859_1)
@s1.recv_nonblock(5, 0, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
it "does not block if there's no data available" do

View File

@ -100,13 +100,29 @@ describe "BasicSocket#recv" do
socket.write("data")
client = @server.accept
buf = +"foo"
buffer = +"foo"
begin
client.recv(4, 0, buf)
client.recv(4, 0, buffer).should.equal?(buffer)
ensure
client.close
end
buf.should == "data"
buffer.should == "data"
socket.close
end
it "preserves the encoding of the given buffer" do
socket = TCPSocket.new('127.0.0.1', @port)
socket.write("data")
client = @server.accept
buffer = ''.encode(Encoding::ISO_8859_1)
begin
client.recv(4, 0, buffer)
ensure
client.close
end
buffer.encoding.should == Encoding::ISO_8859_1
socket.close
end

View File

@ -52,6 +52,27 @@ describe 'Socket#recvfrom_nonblock' do
end
end
it "allows an output buffer as third argument" do
@client.write('hello')
IO.select([@server])
buffer = +''
message, = @server.recvfrom_nonblock(5, 0, buffer)
message.should.equal?(buffer)
buffer.should == 'hello'
end
it "preserves the encoding of the given buffer" do
@client.write('hello')
IO.select([@server])
buffer = ''.encode(Encoding::ISO_8859_1)
@server.recvfrom_nonblock(5, 0, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
describe 'the returned data' do
it 'is the same as the sent data' do
5.times do

View File

@ -58,6 +58,15 @@ describe 'UDPSocket#recvfrom_nonblock' do
buffer.should == 'h'
end
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
IO.select([@server])
message, = @server.recvfrom_nonblock(1, 0, buffer)
message.should.equal?(buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
describe 'the returned Array' do
before do
IO.select([@server])

View File

@ -31,6 +31,29 @@ with_feature :unix_socket do
sock.close
end
it "allows an output buffer as third argument" do
buffer = +''
@client.send("foobar", 0)
sock = @server.accept
message, = sock.recvfrom(6, 0, buffer)
sock.close
message.should.equal?(buffer)
buffer.should == "foobar"
end
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@client.send("foobar", 0)
sock = @server.accept
sock.recvfrom(6, 0, buffer)
sock.close
buffer.encoding.should == Encoding::ISO_8859_1
end
it "uses different message options" do
@client.send("foobar", Socket::MSG_PEEK)
sock = @server.accept

View File

@ -11,10 +11,28 @@ describe :stringio_read, shared: true do
end
it "reads length bytes and writes them to the buffer String" do
@io.send(@method, 7, buffer = +"")
@io.send(@method, 7, buffer = +"").should.equal?(buffer)
buffer.should == "example"
end
ruby_version_is ""..."3.4" do
it "does not preserve the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@io.send(@method, 7, buffer)
buffer.encoding.should_not == Encoding::ISO_8859_1
end
end
ruby_version_is "3.4" do
it "preserves the encoding of the given buffer" do
buffer = ''.encode(Encoding::ISO_8859_1)
@io.send(@method, 7, buffer)
buffer.encoding.should == Encoding::ISO_8859_1
end
end
it "tries to convert the passed buffer Object to a String using #to_str" do
obj = mock("to_str")
obj.should_receive(:to_str).and_return(buffer = +"")

View File

@ -487,4 +487,16 @@ describe "C-API Class function" do
@s.rb_class_real(0).should == 0
end
end
describe "rb_class_get_superclass" do
it "returns parent class for a provided class" do
a = Class.new
@s.rb_class_get_superclass(Class.new(a)).should == a
end
it "returns false when there is no parent class" do
@s.rb_class_get_superclass(BasicObject).should == false
@s.rb_class_get_superclass(Module.new).should == false
end
end
end

View File

@ -79,6 +79,10 @@ static VALUE class_spec_rb_class_real(VALUE self, VALUE object) {
}
}
static VALUE class_spec_rb_class_get_superclass(VALUE self, VALUE klass) {
return rb_class_get_superclass(klass);
}
static VALUE class_spec_rb_class_superclass(VALUE self, VALUE klass) {
return rb_class_superclass(klass);
}
@ -160,6 +164,7 @@ void Init_class_spec(void) {
rb_define_method(cls, "rb_class_new_instance_kw", class_spec_rb_class_new_instance_kw, 2);
#endif
rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1);
rb_define_method(cls, "rb_class_get_superclass", class_spec_rb_class_get_superclass, 1);
rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1);
rb_define_method(cls, "rb_cvar_defined", class_spec_cvar_defined, 2);
rb_define_method(cls, "rb_cvar_get", class_spec_cvar_get, 2);

View File

@ -16,6 +16,8 @@ VALUE registered_after_rb_global_variable_bignum;
VALUE registered_after_rb_global_variable_float;
VALUE rb_gc_register_address_outside_init;
VALUE rb_gc_register_mark_object_not_referenced_float;
static VALUE registered_tagged_address(VALUE self) {
return registered_tagged_value;
}
@ -90,6 +92,10 @@ static VALUE gc_spec_rb_gc_register_mark_object(VALUE self, VALUE obj) {
return Qnil;
}
static VALUE gc_spec_rb_gc_register_mark_object_not_referenced_float(VALUE self) {
return rb_gc_register_mark_object_not_referenced_float;
}
void Init_gc_spec(void) {
VALUE cls = rb_define_class("CApiGCSpecs", rb_cObject);
@ -115,6 +121,9 @@ void Init_gc_spec(void) {
registered_after_rb_global_variable_float = DBL2NUM(6.28);
rb_global_variable(&registered_after_rb_global_variable_float);
rb_gc_register_mark_object_not_referenced_float = DBL2NUM(1.61);
rb_gc_register_mark_object(rb_gc_register_mark_object_not_referenced_float);
rb_define_method(cls, "registered_tagged_address", registered_tagged_address, 0);
rb_define_method(cls, "registered_reference_address", registered_reference_address, 0);
rb_define_method(cls, "registered_before_rb_gc_register_address", get_registered_before_rb_gc_register_address, 0);
@ -131,6 +140,7 @@ void Init_gc_spec(void) {
rb_define_method(cls, "rb_gc", gc_spec_rb_gc, 0);
rb_define_method(cls, "rb_gc_adjust_memory_usage", gc_spec_rb_gc_adjust_memory_usage, 1);
rb_define_method(cls, "rb_gc_register_mark_object", gc_spec_rb_gc_register_mark_object, 1);
rb_define_method(cls, "rb_gc_register_mark_object_not_referenced_float", gc_spec_rb_gc_register_mark_object_not_referenced_float, 0);
rb_define_method(cls, "rb_gc_latest_gc_info", gc_spec_rb_gc_latest_gc_info, 1);
}

View File

@ -390,22 +390,22 @@ static VALUE speced_allocator(VALUE klass) {
return instance;
}
static VALUE define_alloc_func(VALUE self, VALUE klass) {
static VALUE object_spec_rb_define_alloc_func(VALUE self, VALUE klass) {
rb_define_alloc_func(klass, speced_allocator);
return Qnil;
}
static VALUE undef_alloc_func(VALUE self, VALUE klass) {
static VALUE object_spec_rb_undef_alloc_func(VALUE self, VALUE klass) {
rb_undef_alloc_func(klass);
return Qnil;
}
static VALUE speced_allocator_p(VALUE self, VALUE klass) {
static VALUE object_spec_speced_allocator_p(VALUE self, VALUE klass) {
rb_alloc_func_t allocator = rb_get_alloc_func(klass);
return (allocator == speced_allocator) ? Qtrue : Qfalse;
}
static VALUE custom_alloc_func_p(VALUE self, VALUE klass) {
static VALUE object_spec_custom_alloc_func_p(VALUE self, VALUE klass) {
rb_alloc_func_t allocator = rb_get_alloc_func(klass);
return allocator ? Qtrue : Qfalse;
}
@ -485,10 +485,10 @@ void Init_object_spec(void) {
rb_define_method(cls, "rb_ivar_defined", object_spec_rb_ivar_defined, 2);
rb_define_method(cls, "rb_copy_generic_ivar", object_spec_rb_copy_generic_ivar, 2);
rb_define_method(cls, "rb_free_generic_ivar", object_spec_rb_free_generic_ivar, 1);
rb_define_method(cls, "rb_define_alloc_func", define_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, "custom_alloc_func?", custom_alloc_func_p, 1);
rb_define_method(cls, "rb_define_alloc_func", object_spec_rb_define_alloc_func, 1);
rb_define_method(cls, "rb_undef_alloc_func", object_spec_rb_undef_alloc_func, 1);
rb_define_method(cls, "speced_allocator?", object_spec_speced_allocator_p, 1);
rb_define_method(cls, "custom_alloc_func?", object_spec_custom_alloc_func_p, 1);
rb_define_method(cls, "not_implemented_method", rb_f_notimplement, -1);
rb_define_method(cls, "rb_ivar_foreach", object_spec_rb_ivar_foreach, 1);
}

View File

@ -108,6 +108,10 @@ describe "CApiGCSpecs" do
it "can be called with an object" do
@f.rb_gc_register_mark_object(Object.new).should be_nil
end
it "keeps the value alive even if the value is not referenced by any Ruby object" do
@f.rb_gc_register_mark_object_not_referenced_float.should == 1.61
end
end
describe "rb_gc_latest_gc_info" do

View File

@ -30,6 +30,12 @@ describe :kernel_at_exit, shared: true do
result.lines.should.include?("The exception matches: true (message=foo)\n")
end
it "gives access to an exception raised in a previous handler" do
code = "#{@method} { print '$!.message = ' + $!.message }; #{@method} { raise 'foo' }"
result = ruby_exe(code, args: "2>&1", exit_status: 1)
result.lines.should.include?("$!.message = foo")
end
it "both exceptions in a handler and in the main script are printed" do
code = "#{@method} { raise 'at_exit_error' }; raise 'main_script_error'"
result = ruby_exe(code, args: "2>&1", exit_status: 1)