diff options
Diffstat (limited to 'spec/ruby/optional')
54 files changed, 2126 insertions, 475 deletions
diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb index 9c35017e21..7e87859856 100644 --- a/spec/ruby/optional/capi/array_spec.rb +++ b/spec/ruby/optional/capi/array_spec.rb @@ -343,37 +343,39 @@ describe "C-API Array function" do end end - describe "rb_iterate" do - it "calls an callback function as a block passed to an method" do - s = [1,2,3,4] - s2 = @s.rb_iterate(s) + ruby_version_is ""..."4.0" do + describe "rb_iterate" do + it "calls an callback function as a block passed to an method" do + s = [1,2,3,4] + s2 = @s.rb_iterate(s) - s2.should == s + s2.should == s - # Make sure they're different objects - s2.equal?(s).should be_false - end + # Make sure they're different objects + s2.equal?(s).should be_false + end - it "calls a function with the other function available as a block" do - h = {a: 1, b: 2} + it "calls a function with the other function available as a block" do + h = {a: 1, b: 2} - @s.rb_iterate_each_pair(h).sort.should == [1,2] - end + @s.rb_iterate_each_pair(h).sort.should == [1,2] + end - it "calls a function which can yield into the original block" do - s2 = [] + it "calls a function which can yield into the original block" do + s2 = [] - o = Object.new - def o.each - yield 1 - yield 2 - yield 3 - yield 4 - end + o = Object.new + def o.each + yield 1 + yield 2 + yield 3 + yield 4 + end - @s.rb_iterate_then_yield(o) { |x| s2 << x } + @s.rb_iterate_then_yield(o) { |x| s2 << x } - s2.should == [1,2,3,4] + s2.should == [1,2,3,4] + end end end diff --git a/spec/ruby/optional/capi/bignum_spec.rb b/spec/ruby/optional/capi/bignum_spec.rb index cde929af28..179f053eec 100644 --- a/spec/ruby/optional/capi/bignum_spec.rb +++ b/spec/ruby/optional/capi/bignum_spec.rb @@ -7,21 +7,23 @@ def ensure_bignum(n) n end -full_range_longs = (fixnum_max == 2**(0.size * 8 - 1) - 1) +full_range_longs = (fixnum_max == max_long) +max_ulong = begin + require 'rbconfig/sizeof' + RbConfig::LIMITS['ULONG_MAX'] +rescue LoadError + nil +end +# If the system doesn't offer ULONG_MAX, assume 2's complement and derive it +# from LONG_MAX. +max_ulong ||= 2 * (max_long + 1) - 1 describe "CApiBignumSpecs" do before :each do @s = CApiBignumSpecs.new - - if full_range_longs - @max_long = 2**(0.size * 8 - 1) - 1 - @min_long = -@max_long - 1 - @max_ulong = ensure_bignum(2**(0.size * 8) - 1) - else - @max_long = ensure_bignum(2**(0.size * 8 - 1) - 1) - @min_long = ensure_bignum(-@max_long - 1) - @max_ulong = ensure_bignum(2**(0.size * 8) - 1) - end + @max_long = max_long + @min_long = min_long + @max_ulong = ensure_bignum(max_ulong) end describe "rb_big2long" do @@ -123,7 +125,7 @@ describe "CApiBignumSpecs" do val.should == @max_ulong end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "packs max_ulong into 2 ulongs to allow sign bit" do val = @s.rb_big_pack_length(@max_ulong) val.should == 2 diff --git a/spec/ruby/optional/capi/binding_spec.rb b/spec/ruby/optional/capi/binding_spec.rb index 2165705457..5d0ef2efb8 100644 --- a/spec/ruby/optional/capi/binding_spec.rb +++ b/spec/ruby/optional/capi/binding_spec.rb @@ -8,21 +8,9 @@ describe "CApiBindingSpecs" do end describe "Kernel#binding" do - ruby_version_is '3.2' do - it "raises when called from C" do - foo = 14 - -> { @b.get_binding }.should raise_error(RuntimeError) - end - end - - ruby_version_is ''...'3.2' do - it "gives the top-most Ruby binding when called from C" do - foo = 14 - b = @b.get_binding - b.local_variable_get(:foo).should == 14 - b.local_variable_set :foo, 12 - foo.should == 12 - end + it "raises when called from C" do + foo = 14 + -> { @b.get_binding }.should raise_error(RuntimeError) end end end diff --git a/spec/ruby/optional/capi/class_spec.rb b/spec/ruby/optional/capi/class_spec.rb index a231245ebe..7abb5d4ed9 100644 --- a/spec/ruby/optional/capi/class_spec.rb +++ b/spec/ruby/optional/capi/class_spec.rb @@ -365,6 +365,8 @@ describe "C-API Class function" do "ClassUnder6", Class.new) }.should raise_error(TypeError) + ensure + CApiClassSpecs.send(:remove_const, :ClassUnder6) end it "defines a class for an existing Autoload" do diff --git a/spec/ruby/optional/capi/data_spec.rb b/spec/ruby/optional/capi/data_spec.rb index 18c769332e..d000eba6e3 100644 --- a/spec/ruby/optional/capi/data_spec.rb +++ b/spec/ruby/optional/capi/data_spec.rb @@ -1,52 +1,53 @@ require_relative 'spec_helper' +ruby_version_is ""..."3.4" do + load_extension("data") -load_extension("data") - -describe "CApiAllocSpecs (a class with an alloc func defined)" do - it "calls the alloc func" do - @s = CApiAllocSpecs.new - @s.wrapped_data.should == 42 # not defined in initialize - end -end - -describe "CApiWrappedStruct" do - before :each do - @s = CApiWrappedStructSpecs.new - end - - it "wraps with Data_Wrap_Struct and Data_Get_Struct returns data" do - a = @s.wrap_struct(1024) - @s.get_struct(a).should == 1024 + describe "CApiAllocSpecs (a class with an alloc func defined)" do + it "calls the alloc func" do + @s = CApiAllocSpecs.new + @s.wrapped_data.should == 42 # not defined in initialize + end end - describe "RDATA()" do - it "returns the struct data" do - a = @s.wrap_struct(1024) - @s.get_struct_rdata(a).should == 1024 + describe "CApiWrappedStruct" do + before :each do + @s = CApiWrappedStructSpecs.new end - it "allows changing the wrapped struct" do + it "wraps with Data_Wrap_Struct and Data_Get_Struct returns data" do a = @s.wrap_struct(1024) - @s.change_struct(a, 100) - @s.get_struct(a).should == 100 + @s.get_struct(a).should == 1024 end - it "raises a TypeError if the object does not wrap a struct" do - -> { @s.get_struct(Object.new) }.should raise_error(TypeError) + describe "RDATA()" do + it "returns the struct data" do + a = @s.wrap_struct(1024) + @s.get_struct_rdata(a).should == 1024 + end + + it "allows changing the wrapped struct" do + a = @s.wrap_struct(1024) + @s.change_struct(a, 100) + @s.get_struct(a).should == 100 + end + + it "raises a TypeError if the object does not wrap a struct" do + -> { @s.get_struct(Object.new) }.should raise_error(TypeError) + end end - end - describe "rb_check_type" do - it "does not raise an exception when checking data objects" do - a = @s.wrap_struct(1024) - @s.rb_check_type(a, a).should == true + describe "rb_check_type" do + it "does not raise an exception when checking data objects" do + a = @s.wrap_struct(1024) + @s.rb_check_type(a, a).should == true + end end - end - describe "DATA_PTR" do - it "returns the struct data" do - a = @s.wrap_struct(1024) - @s.get_struct_data_ptr(a).should == 1024 + describe "DATA_PTR" do + it "returns the struct data" do + a = @s.wrap_struct(1024) + @s.get_struct_data_ptr(a).should == 1024 + end end end end diff --git a/spec/ruby/optional/capi/debug_spec.rb b/spec/ruby/optional/capi/debug_spec.rb index 148b8c38fb..14ba25609c 100644 --- a/spec/ruby/optional/capi/debug_spec.rb +++ b/spec/ruby/optional/capi/debug_spec.rb @@ -37,9 +37,12 @@ describe "C-API Debug function" do it "matches the locations in rb_debug_inspector_backtrace_locations" do frames = @o.rb_debug_inspector_open(42) - frames.each do |_s, _klass, binding, _iseq, backtrace_location| + frames.each do |_s, klass, binding, iseq, backtrace_location| if binding - binding.source_location.should == [backtrace_location.path, backtrace_location.lineno] + # YJIT modifies Array#each backtraces but leaves its source_location as is + unless defined?(RubyVM::YJIT) && klass == Array && iseq.label == "each" + binding.source_location.should == [backtrace_location.path, backtrace_location.lineno] + end method_name = binding.eval('__method__') if method_name method_name.should == backtrace_location.base_label.to_sym diff --git a/spec/ruby/optional/capi/digest_spec.rb b/spec/ruby/optional/capi/digest_spec.rb new file mode 100644 index 0000000000..65c5ecebb1 --- /dev/null +++ b/spec/ruby/optional/capi/digest_spec.rb @@ -0,0 +1,103 @@ +require_relative 'spec_helper' + +begin + require 'fiddle' +rescue LoadError + return +end + +load_extension('digest') + +describe "C-API Digest functions" do + before :each do + @s = CApiDigestSpecs.new + end + + describe "rb_digest_make_metadata" do + before :each do + @metadata = @s.rb_digest_make_metadata + end + + it "should store the block length" do + @s.block_length(@metadata).should == 40 + end + + it "should store the digest length" do + @s.digest_length(@metadata).should == 20 + end + + it "should store the context size" do + @s.context_size(@metadata).should == 129 + end + end + + describe "digest plugin" do + before :each do + @s = CApiDigestSpecs.new + @digest = Digest::TestDigest.new + + # A pointer to the CTX type defined in the extension for this spec. Digest does not make the context directly + # accessible as part of its API. However, to ensure we are properly loading the plugin, it's useful to have + # direct access to the context pointer to verify its contents. + @context = Fiddle::Pointer.new(@s.context(@digest)) + end + + it "should report the block length" do + @digest.block_length.should == 40 + end + + it "should report the digest length" do + @digest.digest_length.should == 20 + end + + it "should initialize the context" do + # Our test plugin always writes the string "Initialized\n" when its init function is called. + verify_context("Initialized\n") + end + + it "should update the digest" do + @digest.update("hello world") + + # Our test plugin always writes the string "Updated: <data>\n" when its update function is called. + current = "Initialized\nUpdated: hello world" + verify_context(current) + + @digest << "blah" + + current = "Initialized\nUpdated: hello worldUpdated: blah" + verify_context(current) + end + + it "should finalize the digest" do + @digest.update("") + + finish_string = @digest.instance_eval { finish } + + # We expect the plugin to write out the last `@digest.digest_length` bytes, followed by the string "Finished\n". + # + finish_string.should == "d\nUpdated: Finished\n" + finish_string.encoding.should == Encoding::ASCII_8BIT + end + + it "should reset the context" do + @digest.update("foo") + verify_context("Initialized\nUpdated: foo") + + @digest.reset + + # The context will be recreated as a result of the `reset` so we must fetch the latest context pointer. + @context = Fiddle::Pointer.new(@s.context(@digest)) + + verify_context("Initialized\n") + end + + def verify_context(current_body) + # In the CTX type, the length of the current context contents is stored in the first byte. + byte_count = @context[0] + byte_count.should == current_body.bytesize + + # After the size byte follows a string. + @context[1, byte_count].should == current_body + end + end +end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb index 1529e012b0..c14983c7ea 100644 --- a/spec/ruby/optional/capi/encoding_spec.rb +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -171,7 +171,7 @@ describe "C-API Encoding function" do describe "rb_enc_mbc_to_codepoint" do it "returns the correct codepoint for the given character and size" do - @s.rb_enc_mbc_to_codepoint("é").should == 0xE9 + @s.rb_enc_mbc_to_codepoint("é").should == 0xE9 end it "returns 0 if p == e" do @@ -724,25 +724,25 @@ describe "C-API Encoding function" do end describe "rb_define_dummy_encoding" do + run = 0 + it "defines the dummy encoding" do - @s.rb_define_dummy_encoding("FOO") - enc = Encoding.find("FOO") + @s.rb_define_dummy_encoding("FOO#{run += 1}") + enc = Encoding.find("FOO#{run}") enc.should.dummy? end it "returns the index of the dummy encoding" do - index = @s.rb_define_dummy_encoding("BAR") + index = @s.rb_define_dummy_encoding("BAR#{run += 1}") index.should == Encoding.list.size - 1 end - ruby_version_is "3.2" do - it "raises EncodingError if too many encodings" do - code = <<-RUBY - require #{extension_path.dump} - 1_000.times {|i| CApiEncodingSpecs.new.rb_define_dummy_encoding("R_\#{i}") } - RUBY - ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') - end + it "raises EncodingError if too many encodings" do + code = <<-RUBY + require #{extension_path.dump} + 1_000.times {|i| CApiEncodingSpecs.new.rb_define_dummy_encoding("R_\#{i}") } + RUBY + ruby_exe(code, args: "2>&1", exit_status: 1).should.include?('too many encoding (> 256) (EncodingError)') end end end diff --git a/spec/ruby/optional/capi/exception_spec.rb b/spec/ruby/optional/capi/exception_spec.rb index 5bb60608b2..5bc8e26c62 100644 --- a/spec/ruby/optional/capi/exception_spec.rb +++ b/spec/ruby/optional/capi/exception_spec.rb @@ -100,6 +100,26 @@ describe "C-API Exception function" do end end + describe "rb_error_frozen_object" do + it "raises a FrozenError regardless of the object's frozen state" do + # The type of the argument we supply doesn't matter. The choice here is arbitrary and we only change the type + # of the argument to ensure the exception messages are set correctly. + -> { @s.rb_error_frozen_object(Array.new) }.should raise_error(FrozenError, "can't modify frozen Array: []") + -> { @s.rb_error_frozen_object(Array.new.freeze) }.should raise_error(FrozenError, "can't modify frozen Array: []") + end + + it "properly handles recursive rb_error_frozen_object calls" do + klass = Class.new(Object) + object = klass.new + s = @s + klass.define_method :inspect do + s.rb_error_frozen_object(object) + end + + -> { @s.rb_error_frozen_object(object) }.should raise_error(FrozenError, "can't modify frozen #{klass}: ...") + end + end + describe "rb_syserr_new" do it "returns system error with default message when passed message is NULL" do exception = @s.rb_syserr_new(Errno::ENOENT::Errno, nil) diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c index 2347798bb4..628c4df9d7 100644 --- a/spec/ruby/optional/capi/ext/array_spec.c +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -196,6 +196,7 @@ static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { return rb_ary_push(new_ary, el); } +#ifndef RUBY_VERSION_IS_4_0 static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -203,6 +204,7 @@ static VALUE array_spec_rb_iterate(VALUE self, VALUE ary) { return new_ary; } +#endif static VALUE array_spec_rb_block_call(VALUE self, VALUE ary) { VALUE new_ary = rb_ary_new(); @@ -216,6 +218,7 @@ static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { return rb_ary_push(holder, rb_ary_entry(el, 1)); } +#ifndef RUBY_VERSION_IS_4_0 static VALUE each_pair(VALUE obj) { return rb_funcall(obj, rb_intern("each_pair"), 0); } @@ -227,6 +230,7 @@ static VALUE array_spec_rb_iterate_each_pair(VALUE self, VALUE obj) { return new_ary; } +#endif static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { VALUE new_ary = rb_ary_new(); @@ -241,10 +245,12 @@ static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { return Qnil; } +#ifndef RUBY_VERSION_IS_4_0 static VALUE array_spec_rb_iterate_then_yield(VALUE self, VALUE obj) { rb_iterate(rb_each, obj, iter_yield, obj); return Qnil; } +#endif static VALUE array_spec_rb_block_call_then_yield(VALUE self, VALUE obj) { rb_block_call(obj, rb_intern("each"), 0, 0, iter_yield, obj); @@ -308,9 +314,11 @@ void Init_array_spec(void) { rb_define_method(cls, "rb_ary_plus", array_spec_rb_ary_plus, 2); rb_define_method(cls, "rb_ary_unshift", array_spec_rb_ary_unshift, 2); rb_define_method(cls, "rb_assoc_new", array_spec_rb_assoc_new, 2); +#ifndef RUBY_VERSION_IS_4_0 rb_define_method(cls, "rb_iterate", array_spec_rb_iterate, 1); rb_define_method(cls, "rb_iterate_each_pair", array_spec_rb_iterate_each_pair, 1); rb_define_method(cls, "rb_iterate_then_yield", array_spec_rb_iterate_then_yield, 1); +#endif rb_define_method(cls, "rb_block_call", array_spec_rb_block_call, 1); rb_define_method(cls, "rb_block_call_each_pair", array_spec_rb_block_call_each_pair, 1); rb_define_method(cls, "rb_block_call_then_yield", array_spec_rb_block_call_then_yield, 1); diff --git a/spec/ruby/optional/capi/ext/class_spec.c b/spec/ruby/optional/capi/ext/class_spec.c index c13f02ecf2..8ac0e7a93f 100644 --- a/spec/ruby/optional/capi/ext/class_spec.c +++ b/spec/ruby/optional/capi/ext/class_spec.c @@ -65,11 +65,9 @@ static VALUE class_spec_rb_class_new_instance(VALUE self, VALUE args, VALUE klas return rb_class_new_instance(RARRAY_LENINT(args), RARRAY_PTR(args), klass); } -#ifdef RUBY_VERSION_IS_3_0 static VALUE class_spec_rb_class_new_instance_kw(VALUE self, VALUE args, VALUE klass) { return rb_class_new_instance_kw(RARRAY_LENINT(args), RARRAY_PTR(args), klass, RB_PASS_KEYWORDS); } -#endif static VALUE class_spec_rb_class_real(VALUE self, VALUE object) { if (rb_type_p(object, T_FIXNUM)) { @@ -160,9 +158,7 @@ void Init_class_spec(void) { rb_define_method(cls, "rb_class_private_instance_methods", class_spec_rb_class_private_instance_methods, -1); rb_define_method(cls, "rb_class_new", class_spec_rb_class_new, 1); rb_define_method(cls, "rb_class_new_instance", class_spec_rb_class_new_instance, 2); -#ifdef RUBY_VERSION_IS_3_0 rb_define_method(cls, "rb_class_new_instance_kw", class_spec_rb_class_new_instance_kw, 2); -#endif rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1); rb_define_method(cls, "rb_class_get_superclass", class_spec_rb_class_get_superclass, 1); rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1); diff --git a/spec/ruby/optional/capi/ext/constants_spec.c b/spec/ruby/optional/capi/ext/constants_spec.c index 9aee8db37f..05819ea476 100644 --- a/spec/ruby/optional/capi/ext/constants_spec.c +++ b/spec/ruby/optional/capi/ext/constants_spec.c @@ -14,9 +14,6 @@ defconstfunc(rb_cBinding) defconstfunc(rb_cClass) defconstfunc(rb_cComplex) defconstfunc(rb_mComparable) -#ifndef RUBY_VERSION_IS_3_0 -defconstfunc(rb_cData) -#endif defconstfunc(rb_cDir) defconstfunc(rb_cEncoding) defconstfunc(rb_mEnumerable) @@ -97,9 +94,6 @@ void Init_constants_spec(void) { rb_define_method(cls, "rb_cClass", constants_spec_rb_cClass, 0); rb_define_method(cls, "rb_cComplex", constants_spec_rb_cComplex, 0); rb_define_method(cls, "rb_mComparable", constants_spec_rb_mComparable, 0); - #ifndef RUBY_VERSION_IS_3_0 - rb_define_method(cls, "rb_cData", constants_spec_rb_cData, 0); - #endif rb_define_method(cls, "rb_cDir", constants_spec_rb_cDir, 0); rb_define_method(cls, "rb_cEncoding", constants_spec_rb_cEncoding, 0); rb_define_method(cls, "rb_mEnumerable", constants_spec_rb_mEnumerable, 0); diff --git a/spec/ruby/optional/capi/ext/data_spec.c b/spec/ruby/optional/capi/ext/data_spec.c index ef069ef0ba..efefe37c3a 100644 --- a/spec/ruby/optional/capi/ext/data_spec.c +++ b/spec/ruby/optional/capi/ext/data_spec.c @@ -3,6 +3,7 @@ #include <string.h> +#ifndef RUBY_VERSION_IS_3_4 #ifdef __cplusplus extern "C" { #endif @@ -70,8 +71,10 @@ VALUE sws_rb_check_type(VALUE self, VALUE obj, VALUE other) { rb_check_type(obj, TYPE(other)); return Qtrue; } +#endif void Init_data_spec(void) { +#ifndef RUBY_VERSION_IS_3_4 VALUE cls = rb_define_class("CApiAllocSpecs", rb_cObject); rb_define_alloc_func(cls, sdaf_alloc_func); rb_define_method(cls, "wrapped_data", sdaf_get_struct, 0); @@ -82,6 +85,7 @@ void Init_data_spec(void) { rb_define_method(cls, "get_struct_data_ptr", sws_get_struct_data_ptr, 1); rb_define_method(cls, "change_struct", sws_change_struct, 2); rb_define_method(cls, "rb_check_type", sws_rb_check_type, 2); +#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/digest_spec.c b/spec/ruby/optional/capi/ext/digest_spec.c new file mode 100644 index 0000000000..65c8defa20 --- /dev/null +++ b/spec/ruby/optional/capi/ext/digest_spec.c @@ -0,0 +1,168 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include "ruby/digest.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DIGEST_LENGTH 20 +#define BLOCK_LENGTH 40 + +const char *init_string = "Initialized\n"; +const char *update_string = "Updated: "; +const char *finish_string = "Finished\n"; + +#define PAYLOAD_SIZE 128 + +typedef struct CTX { + uint8_t pos; + char payload[PAYLOAD_SIZE]; +} CTX; + +void* context = NULL; + +int digest_spec_plugin_init(void *raw_ctx) { + // Make the context accessible to tests. This isn't safe, but there's no way to access the context otherwise. + context = raw_ctx; + + struct CTX *ctx = (struct CTX *)raw_ctx; + size_t len = strlen(init_string); + + // Clear the payload since this init function will be invoked as part of the `reset` operation. + memset(ctx->payload, 0, PAYLOAD_SIZE); + + // Write a simple value we can verify in tests. + // This is not what a real digest would do, but we're using a dummy digest plugin to test interactions. + memcpy(ctx->payload, init_string, len); + ctx->pos = (uint8_t) len; + + return 1; +} + +void digest_spec_plugin_update(void *raw_ctx, unsigned char *ptr, size_t size) { + struct CTX *ctx = (struct CTX *)raw_ctx; + size_t update_str_len = strlen(update_string); + + if (ctx->pos + update_str_len + size >= PAYLOAD_SIZE) { + rb_raise(rb_eRuntimeError, "update size too large; reset the digest and write fewer updates"); + } + + // Write the supplied value to the payload so it can be easily verified in test. + // This is not what a real digest would do, but we're using a dummy digest plugin to test interactions. + memcpy(ctx->payload + ctx->pos, update_string, update_str_len); + ctx->pos += update_str_len; + + memcpy(ctx->payload + ctx->pos, ptr, size); + ctx->pos += size; + + return; +} + +int digest_spec_plugin_finish(void *raw_ctx, unsigned char *ptr) { + struct CTX *ctx = (struct CTX *)raw_ctx; + size_t finish_string_len = strlen(finish_string); + + // We're always going to write DIGEST_LENGTH bytes. In a real plugin, this would be the digest value. Here we + // write out a text string in order to make validation in tests easier. + // + // In order to delineate the output more clearly from an `Digest#update` call, we always write out the + // `finish_string` message. That leaves `DIGEST_LENGTH - finish_string_len` bytes to read out of the context. + size_t context_bytes = DIGEST_LENGTH - finish_string_len; + + memcpy(ptr, ctx->payload + (ctx->pos - context_bytes), context_bytes); + memcpy(ptr + context_bytes, finish_string, finish_string_len); + + return 1; +} + +static const rb_digest_metadata_t metadata = { + // The RUBY_DIGEST_API_VERSION value comes from ruby/digest.h and may vary based on the Ruby being tested. Since + // it isn't publicly exposed in the digest gem, we ignore for these tests. Either the test hard-codes an expected + // value and is subject to breaking depending on the Ruby being run or we publicly expose `RUBY_DIGEST_API_VERSION`, + // in which case the test would pass trivially. + RUBY_DIGEST_API_VERSION, + DIGEST_LENGTH, + BLOCK_LENGTH, + sizeof(CTX), + (rb_digest_hash_init_func_t) digest_spec_plugin_init, + (rb_digest_hash_update_func_t) digest_spec_plugin_update, + (rb_digest_hash_finish_func_t) digest_spec_plugin_finish, +}; + +// The `get_metadata_ptr` function is not publicly available in the digest gem. However, we need to use +// to extract the `rb_digest_metadata_t*` value set up by the plugin so we reproduce and adjust the +// definition here. +// +// Taken and adapted from https://github.com/ruby/digest/blob/v3.2.0/ext/digest/digest.c#L558-L568 +static rb_digest_metadata_t * get_metadata_ptr(VALUE obj) { + rb_digest_metadata_t *algo; + +#ifdef DIGEST_USE_RB_EXT_RESOLVE_SYMBOL + // In the digest gem there is an additional data type check performed before reading the value out. + // Since the type definition isn't public, we can't use it as part of a type check here so we omit it. + // This is safe to do because this code is intended to only load digest plugins written as part of this test suite. + algo = (rb_digest_metadata_t *) RTYPEDDATA_DATA(obj); +#else +# undef RUBY_UNTYPED_DATA_WARNING +# define RUBY_UNTYPED_DATA_WARNING 0 + Data_Get_Struct(obj, rb_digest_metadata_t, algo); +#endif + + return algo; +} + +VALUE digest_spec_rb_digest_make_metadata(VALUE self) { + return rb_digest_make_metadata(&metadata); +} + +VALUE digest_spec_block_length(VALUE self, VALUE meta) { + rb_digest_metadata_t* algo = get_metadata_ptr(meta); + + return SIZET2NUM(algo->block_len); +} + +VALUE digest_spec_digest_length(VALUE self, VALUE meta) { + rb_digest_metadata_t* algo = get_metadata_ptr(meta); + + return SIZET2NUM(algo->digest_len); +} + +VALUE digest_spec_context_size(VALUE self, VALUE meta) { + rb_digest_metadata_t* algo = get_metadata_ptr(meta); + + return SIZET2NUM(algo->ctx_size); +} + +#ifndef PTR2NUM +#define PTR2NUM(x) (rb_int2inum((intptr_t)(void *)(x))) +#endif + +VALUE digest_spec_context(VALUE self, VALUE digest) { + return PTR2NUM(context); +} + +void Init_digest_spec(void) { + VALUE cls; + + cls = rb_define_class("CApiDigestSpecs", rb_cObject); + rb_define_method(cls, "rb_digest_make_metadata", digest_spec_rb_digest_make_metadata, 0); + rb_define_method(cls, "block_length", digest_spec_block_length, 1); + rb_define_method(cls, "digest_length", digest_spec_digest_length, 1); + rb_define_method(cls, "context_size", digest_spec_context_size, 1); + rb_define_method(cls, "context", digest_spec_context, 1); + + VALUE mDigest, cDigest_Base, cDigest; + + mDigest = rb_define_module("Digest"); + mDigest = rb_digest_namespace(); + cDigest_Base = rb_const_get(mDigest, rb_intern_const("Base")); + + cDigest = rb_define_class_under(mDigest, "TestDigest", cDigest_Base); + rb_iv_set(cDigest, "metadata", rb_digest_make_metadata(&metadata)); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/exception_spec.c b/spec/ruby/optional/capi/ext/exception_spec.c index 0e8347ab0d..c3b94d7bcd 100644 --- a/spec/ruby/optional/capi/ext/exception_spec.c +++ b/spec/ruby/optional/capi/ext/exception_spec.c @@ -36,6 +36,13 @@ VALUE exception_spec_rb_set_errinfo(VALUE self, VALUE exc) { return Qnil; } +NORETURN(VALUE exception_spec_rb_error_frozen_object(VALUE self, VALUE object)); + +VALUE exception_spec_rb_error_frozen_object(VALUE self, VALUE object) { + rb_error_frozen_object(object); + UNREACHABLE_RETURN(Qnil); +} + VALUE exception_spec_rb_syserr_new(VALUE self, VALUE num, VALUE msg) { int n = NUM2INT(num); char *cstr = NULL; @@ -66,6 +73,7 @@ void Init_exception_spec(void) { rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1); rb_define_method(cls, "rb_exc_raise", exception_spec_rb_exc_raise, 1); rb_define_method(cls, "rb_set_errinfo", exception_spec_rb_set_errinfo, 1); + rb_define_method(cls, "rb_error_frozen_object", exception_spec_rb_error_frozen_object, 1); rb_define_method(cls, "rb_syserr_new", exception_spec_rb_syserr_new, 2); rb_define_method(cls, "rb_syserr_new_str", exception_spec_rb_syserr_new_str, 2); rb_define_method(cls, "rb_make_exception", exception_spec_rb_make_exception, 1); diff --git a/spec/ruby/optional/capi/ext/fiber_spec.c b/spec/ruby/optional/capi/ext/fiber_spec.c index f06a54494e..db54f7ad8c 100644 --- a/spec/ruby/optional/capi/ext/fiber_spec.c +++ b/spec/ruby/optional/capi/ext/fiber_spec.c @@ -44,12 +44,10 @@ VALUE fiber_spec_rb_fiber_new(VALUE self) { return rb_fiber_new(fiber_spec_rb_fiber_new_function, Qnil); } -#ifdef RUBY_VERSION_IS_3_1 VALUE fiber_spec_rb_fiber_raise(int argc, VALUE *argv, VALUE self) { VALUE fiber = argv[0]; return rb_fiber_raise(fiber, argc-1, argv+1); } -#endif void Init_fiber_spec(void) { VALUE cls = rb_define_class("CApiFiberSpecs", rb_cObject); @@ -58,10 +56,7 @@ void Init_fiber_spec(void) { rb_define_method(cls, "rb_fiber_resume", fiber_spec_rb_fiber_resume, 2); rb_define_method(cls, "rb_fiber_yield", fiber_spec_rb_fiber_yield, 1); rb_define_method(cls, "rb_fiber_new", fiber_spec_rb_fiber_new, 0); - -#ifdef RUBY_VERSION_IS_3_1 rb_define_method(cls, "rb_fiber_raise", fiber_spec_rb_fiber_raise, -1); -#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/finalizer_spec.c b/spec/ruby/optional/capi/ext/finalizer_spec.c new file mode 100644 index 0000000000..83347da912 --- /dev/null +++ b/spec/ruby/optional/capi/ext/finalizer_spec.c @@ -0,0 +1,25 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE define_finalizer(VALUE self, VALUE obj, VALUE finalizer) { + return rb_define_finalizer(obj, finalizer); +} + +static VALUE undefine_finalizer(VALUE self, VALUE obj) { + return rb_undefine_finalizer(obj); +} + +void Init_finalizer_spec(void) { + VALUE cls = rb_define_class("CApiFinalizerSpecs", rb_cObject); + + rb_define_method(cls, "rb_define_finalizer", define_finalizer, 2); + rb_define_method(cls, "rb_undefine_finalizer", undefine_finalizer, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/hash_spec.c b/spec/ruby/optional/capi/ext/hash_spec.c index 69ef02d5da..653917f2c4 100644 --- a/spec/ruby/optional/capi/ext/hash_spec.c +++ b/spec/ruby/optional/capi/ext/hash_spec.c @@ -105,11 +105,9 @@ VALUE hash_spec_rb_hash_new(VALUE self) { return rb_hash_new(); } -#ifdef RUBY_VERSION_IS_3_2 VALUE hash_spec_rb_hash_new_capa(VALUE self, VALUE capacity) { return rb_hash_new_capa(NUM2LONG(capacity)); } -#endif VALUE rb_ident_hash_new(void); /* internal.h, used in ripper */ @@ -134,6 +132,20 @@ VALUE hash_spec_compute_a_hash_code(VALUE self, VALUE seed) { return ULONG2NUM(h); } +VALUE hash_spec_rb_hash_bulk_insert(VALUE self, VALUE array_len, VALUE array, VALUE hash) { + VALUE* ptr; + + if (array == Qnil) { + ptr = NULL; + } else { + ptr = RARRAY_PTR(array); + } + + long len = FIX2LONG(array_len); + rb_hash_bulk_insert(len, ptr, hash); + return Qnil; +} + void Init_hash_spec(void) { VALUE cls = rb_define_class("CApiHashSpecs", rb_cObject); rb_define_method(cls, "rb_hash", hash_spec_rb_hash, 1); @@ -155,13 +167,12 @@ void Init_hash_spec(void) { rb_define_method(cls, "rb_hash_lookup2", hash_spec_rb_hash_lookup2, 3); rb_define_method(cls, "rb_hash_lookup2_default_undef", hash_spec_rb_hash_lookup2_default_undef, 2); rb_define_method(cls, "rb_hash_new", hash_spec_rb_hash_new, 0); -#ifdef RUBY_VERSION_IS_3_2 rb_define_method(cls, "rb_hash_new_capa", hash_spec_rb_hash_new_capa, 1); -#endif rb_define_method(cls, "rb_ident_hash_new", hash_spec_rb_ident_hash_new, 0); rb_define_method(cls, "rb_hash_size", hash_spec_rb_hash_size, 1); rb_define_method(cls, "rb_hash_set_ifnone", hash_spec_rb_hash_set_ifnone, 2); rb_define_method(cls, "compute_a_hash_code", hash_spec_compute_a_hash_code, 1); + rb_define_method(cls, "rb_hash_bulk_insert", hash_spec_rb_hash_bulk_insert, 3); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index 1a73331386..f3ede15729 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -28,13 +28,7 @@ static int set_non_blocking(int fd) { } static int io_spec_get_fd(VALUE io) { -#ifdef RUBY_VERSION_IS_3_1 return rb_io_descriptor(io); -#else - rb_io_t* fp; - GetOpenFile(io, fp); - return fp->fd; -#endif } VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) { @@ -143,7 +137,7 @@ VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) { errno = saved_errno; } - ret = rb_io_wait_readable(fd); + ret = rb_io_maybe_wait_readable(errno, io, Qnil); if (RTEST(read_p)) { ssize_t r = read(fd, buf, RB_IO_WAIT_READABLE_BUF); @@ -162,18 +156,15 @@ VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) { } VALUE io_spec_rb_io_wait_writable(VALUE self, VALUE io) { - int ret = rb_io_wait_writable(io_spec_get_fd(io)); + int ret = rb_io_maybe_wait_writable(errno, io, Qnil); return ret ? Qtrue : Qfalse; } -#ifdef RUBY_VERSION_IS_3_1 VALUE io_spec_rb_io_maybe_wait_writable(VALUE self, VALUE error, VALUE io, VALUE timeout) { int ret = rb_io_maybe_wait_writable(NUM2INT(error), io, timeout); return INT2NUM(ret); } -#endif -#ifdef RUBY_VERSION_IS_3_1 #ifdef SET_NON_BLOCKING_FAILS_ALWAYS NORETURN(VALUE io_spec_rb_io_maybe_wait_readable(VALUE self, VALUE error, VALUE io, VALUE timeout, VALUE read_p)); #endif @@ -216,13 +207,10 @@ VALUE io_spec_rb_io_maybe_wait_readable(VALUE self, VALUE error, VALUE io, VALUE UNREACHABLE_RETURN(Qnil); #endif } -#endif -#ifdef RUBY_VERSION_IS_3_1 VALUE io_spec_rb_io_maybe_wait(VALUE self, VALUE error, VALUE io, VALUE events, VALUE timeout) { return rb_io_maybe_wait(NUM2INT(error), io, events, timeout); } -#endif VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) { rb_thread_wait_fd(io_spec_get_fd(io)); @@ -230,13 +218,13 @@ VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) { } VALUE io_spec_rb_wait_for_single_fd(VALUE self, VALUE io, VALUE events, VALUE secs, VALUE usecs) { - int fd = io_spec_get_fd(io); - struct timeval tv; + VALUE timeout = Qnil; if (!NIL_P(secs)) { - tv.tv_sec = FIX2INT(secs); - tv.tv_usec = FIX2INT(usecs); + timeout = rb_float_new((double)FIX2INT(secs) + (0.000001 * FIX2INT(usecs))); } - return INT2FIX(rb_wait_for_single_fd(fd, FIX2INT(events), NIL_P(secs) ? NULL : &tv)); + VALUE result = rb_io_wait(io, events, timeout); + if (result == Qfalse) return INT2FIX(0); + else return result; } VALUE io_spec_rb_thread_fd_writable(VALUE self, VALUE io) { @@ -330,13 +318,15 @@ static VALUE io_spec_errno_set(VALUE self, VALUE val) { } VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) { + int mode; #ifdef RUBY_VERSION_IS_3_3 - if (rb_io_mode(io) & FMODE_SYNC) { + mode = rb_io_mode(io); #else rb_io_t *fp; GetOpenFile(io, fp); - if (fp->mode & FMODE_SYNC) { + mode = fp->mode; #endif + if (mode & FMODE_SYNC) { return Qtrue; } else { return Qfalse; @@ -351,6 +341,25 @@ static VALUE io_spec_rb_io_mode(VALUE self, VALUE io) { static VALUE io_spec_rb_io_path(VALUE self, VALUE io) { return rb_io_path(io); } + +static VALUE io_spec_rb_io_closed_p(VALUE self, VALUE io) { + return rb_io_closed_p(io); +} + +static VALUE io_spec_rb_io_open_descriptor(VALUE self, VALUE klass, VALUE descriptor, VALUE mode, VALUE path, VALUE timeout, VALUE internal_encoding, VALUE external_encoding, VALUE ecflags, VALUE ecopts) { + struct rb_io_encoding io_encoding; + + io_encoding.enc = rb_to_encoding(internal_encoding); + io_encoding.enc2 = rb_to_encoding(external_encoding); + io_encoding.ecflags = FIX2INT(ecflags); + io_encoding.ecopts = ecopts; + + return rb_io_open_descriptor(klass, FIX2INT(descriptor), FIX2INT(mode), path, timeout, &io_encoding); +} + +static VALUE io_spec_rb_io_open_descriptor_without_encoding(VALUE self, VALUE klass, VALUE descriptor, VALUE mode, VALUE path, VALUE timeout) { + return rb_io_open_descriptor(klass, FIX2INT(descriptor), FIX2INT(mode), path, timeout, NULL); +} #endif void Init_io_spec(void) { @@ -370,11 +379,9 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_taint_check", io_spec_rb_io_taint_check, 1); rb_define_method(cls, "rb_io_wait_readable", io_spec_rb_io_wait_readable, 2); rb_define_method(cls, "rb_io_wait_writable", io_spec_rb_io_wait_writable, 1); -#ifdef RUBY_VERSION_IS_3_1 rb_define_method(cls, "rb_io_maybe_wait_writable", io_spec_rb_io_maybe_wait_writable, 3); rb_define_method(cls, "rb_io_maybe_wait_readable", io_spec_rb_io_maybe_wait_readable, 4); rb_define_method(cls, "rb_io_maybe_wait", io_spec_rb_io_maybe_wait, 4); -#endif rb_define_method(cls, "rb_thread_wait_fd", io_spec_rb_thread_wait_fd, 1); rb_define_method(cls, "rb_thread_fd_writable", io_spec_rb_thread_fd_writable, 1); rb_define_method(cls, "rb_thread_fd_select_read", io_spec_rb_thread_fd_select_read, 1); @@ -389,6 +396,14 @@ void Init_io_spec(void) { #if defined(RUBY_VERSION_IS_3_3) || defined(TRUFFLERUBY) rb_define_method(cls, "rb_io_mode", io_spec_rb_io_mode, 1); rb_define_method(cls, "rb_io_path", io_spec_rb_io_path, 1); + rb_define_method(cls, "rb_io_closed_p", io_spec_rb_io_closed_p, 1); + rb_define_method(cls, "rb_io_open_descriptor", io_spec_rb_io_open_descriptor, 9); + rb_define_method(cls, "rb_io_open_descriptor_without_encoding", io_spec_rb_io_open_descriptor_without_encoding, 5); + rb_define_const(cls, "FMODE_READABLE", INT2FIX(FMODE_READABLE)); + rb_define_const(cls, "FMODE_WRITABLE", INT2FIX(FMODE_WRITABLE)); + rb_define_const(cls, "FMODE_BINMODE", INT2FIX(FMODE_BINMODE)); + rb_define_const(cls, "FMODE_TEXTMODE", INT2FIX(FMODE_TEXTMODE)); + rb_define_const(cls, "ECONV_UNIVERSAL_NEWLINE_DECORATOR", INT2FIX(ECONV_UNIVERSAL_NEWLINE_DECORATOR)); #endif } diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c index 1761599081..a8fed21b59 100644 --- a/spec/ruby/optional/capi/ext/kernel_spec.c +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -7,16 +7,12 @@ extern "C" { #endif -VALUE kernel_spec_call_proc(VALUE arg_array) { +static VALUE kernel_spec_call_proc(VALUE arg_array) { VALUE arg = rb_ary_pop(arg_array); VALUE proc = rb_ary_pop(arg_array); return rb_funcall(proc, rb_intern("call"), 1, arg); } -VALUE kernel_spec_call_proc_raise(VALUE arg_array, VALUE raised_exc) { - return kernel_spec_call_proc(arg_array); -} - static VALUE kernel_spec_rb_block_given_p(VALUE self) { return rb_block_given_p() ? Qtrue : Qfalse; } @@ -71,11 +67,20 @@ VALUE kernel_spec_rb_block_call_no_func(VALUE self, VALUE ary) { return rb_block_call(ary, rb_intern("map"), 0, NULL, (rb_block_call_func_t)NULL, Qnil); } - VALUE kernel_spec_rb_frame_this_func(VALUE self) { return ID2SYM(rb_frame_this_func()); } +VALUE kernel_spec_rb_category_warn_deprecated(VALUE self) { + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "foo"); + return Qnil; +} + +VALUE kernel_spec_rb_category_warn_deprecated_with_integer_extra_value(VALUE self, VALUE value) { + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "foo %d", FIX2INT(value)); + return Qnil; +} + VALUE kernel_spec_rb_ensure(VALUE self, VALUE main_proc, VALUE arg, VALUE ensure_proc, VALUE arg2) { VALUE main_array, ensure_array; @@ -112,9 +117,11 @@ VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) { return rb_eval_string(RSTRING_PTR(str)); } +#ifndef RUBY_VERSION_IS_4_0 VALUE kernel_spec_rb_eval_cmd_kw(VALUE self, VALUE cmd, VALUE args, VALUE kw_splat) { return rb_eval_cmd_kw(cmd, args, NUM2INT(kw_splat)); } +#endif VALUE kernel_spec_rb_raise(VALUE self, VALUE hash) { rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("before"))); @@ -134,7 +141,16 @@ VALUE kernel_spec_rb_throw_obj(VALUE self, VALUE obj, VALUE result) { return ID2SYM(rb_intern("rb_throw_failed")); } -VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) { +VALUE kernel_spec_rb_errinfo(VALUE self) { + return rb_errinfo(); +} + +VALUE kernel_spec_rb_set_errinfo(VALUE self, VALUE exc) { + rb_set_errinfo(exc); + return Qnil; +} + +static VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) { VALUE argv[2]; int argc; @@ -181,7 +197,7 @@ VALUE kernel_spec_rb_rescue2(int argc, VALUE *args, VALUE self) { rb_ary_push(raise_array, args[3]); return rb_rescue2(kernel_spec_call_proc, main_array, - kernel_spec_call_proc_raise, raise_array, args[4], args[5], (VALUE)0); + kernel_spec_call_proc_with_raised_exc, raise_array, args[4], args[5], (VALUE)0); } static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) { @@ -195,7 +211,7 @@ static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) { return res; } -static VALUE kernel_spec_rb_protect_errinfo(VALUE self, VALUE obj, VALUE ary) { +static VALUE kernel_spec_rb_protect_ignore_status(VALUE self, VALUE obj, VALUE ary) { int status = 0; VALUE res = rb_protect(rb_yield, obj, &status); rb_ary_store(ary, 0, INT2NUM(23)); @@ -221,7 +237,7 @@ static VALUE kernel_spec_rb_eval_string_protect(VALUE self, VALUE str, VALUE ary VALUE kernel_spec_rb_sys_fail(VALUE self, VALUE msg) { errno = 1; if (msg == Qnil) { - rb_sys_fail(0); + rb_sys_fail(NULL); } else if (self != Qundef) { rb_sys_fail(StringValuePtr(msg)); } @@ -237,6 +253,13 @@ VALUE kernel_spec_rb_syserr_fail(VALUE self, VALUE err, VALUE msg) { return Qnil; } +VALUE kernel_spec_rb_syserr_fail_str(VALUE self, VALUE err, VALUE msg) { + if (self != Qundef) { + rb_syserr_fail_str(NUM2INT(err), msg); + } + return Qnil; +} + VALUE kernel_spec_rb_warn(VALUE self, VALUE msg) { rb_warn("%s", StringValuePtr(msg)); return Qnil; @@ -318,6 +341,10 @@ static VALUE kernel_spec_rb_f_sprintf(VALUE self, VALUE ary) { return rb_f_sprintf((int)RARRAY_LEN(ary), RARRAY_PTR(ary)); } +static VALUE kernel_spec_rb_str_format(VALUE self, VALUE count, VALUE ary, VALUE format) { + return rb_str_format(FIX2INT(count), RARRAY_PTR(ary), format); +} + static VALUE kernel_spec_rb_make_backtrace(VALUE self) { return rb_make_backtrace(); } @@ -326,7 +353,6 @@ static VALUE kernel_spec_rb_funcallv(VALUE self, VALUE obj, VALUE method, VALUE return rb_funcallv(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args)); } -#ifdef RUBY_VERSION_IS_3_0 static VALUE kernel_spec_rb_funcallv_kw(VALUE self, VALUE obj, VALUE method, VALUE args) { return rb_funcallv_kw(obj, SYM2ID(method), RARRAY_LENINT(args), RARRAY_PTR(args), RB_PASS_KEYWORDS); } @@ -334,7 +360,6 @@ static VALUE kernel_spec_rb_funcallv_kw(VALUE self, VALUE obj, VALUE method, VAL static VALUE kernel_spec_rb_keyword_given_p(int argc, VALUE *args, VALUE self) { return rb_keyword_given_p() ? Qtrue : Qfalse; } -#endif static VALUE kernel_spec_rb_funcallv_public(VALUE self, VALUE obj, VALUE method) { return rb_funcallv_public(obj, SYM2ID(method), 0, NULL); @@ -376,22 +401,30 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_block_lambda", kernel_spec_rb_block_lambda, 0); rb_define_method(cls, "rb_frame_this_func_test", kernel_spec_rb_frame_this_func, 0); rb_define_method(cls, "rb_frame_this_func_test_again", kernel_spec_rb_frame_this_func, 0); + rb_define_method(cls, "rb_category_warn_deprecated", kernel_spec_rb_category_warn_deprecated, 0); + rb_define_method(cls, "rb_category_warn_deprecated_with_integer_extra_value", kernel_spec_rb_category_warn_deprecated_with_integer_extra_value, 1); rb_define_method(cls, "rb_ensure", kernel_spec_rb_ensure, 4); rb_define_method(cls, "rb_eval_string", kernel_spec_rb_eval_string, 1); +#ifndef RUBY_VERSION_IS_4_0 rb_define_method(cls, "rb_eval_cmd_kw", kernel_spec_rb_eval_cmd_kw, 3); +#endif rb_define_method(cls, "rb_raise", kernel_spec_rb_raise, 1); rb_define_method(cls, "rb_throw", kernel_spec_rb_throw, 1); rb_define_method(cls, "rb_throw_obj", kernel_spec_rb_throw_obj, 2); + rb_define_method(cls, "rb_errinfo", kernel_spec_rb_errinfo, 0); + rb_define_method(cls, "rb_set_errinfo", kernel_spec_rb_set_errinfo, 1); + rb_define_method(cls, "rb_rescue", kernel_spec_rb_rescue, 4); rb_define_method(cls, "rb_rescue", kernel_spec_rb_rescue, 4); rb_define_method(cls, "rb_rescue2", kernel_spec_rb_rescue2, -1); rb_define_method(cls, "rb_protect_yield", kernel_spec_rb_protect_yield, 2); - rb_define_method(cls, "rb_protect_errinfo", kernel_spec_rb_protect_errinfo, 2); + rb_define_method(cls, "rb_protect_ignore_status", kernel_spec_rb_protect_ignore_status, 2); rb_define_method(cls, "rb_protect_null_status", kernel_spec_rb_protect_null_status, 1); rb_define_method(cls, "rb_eval_string_protect", kernel_spec_rb_eval_string_protect, 2); rb_define_method(cls, "rb_catch", kernel_spec_rb_catch, 2); rb_define_method(cls, "rb_catch_obj", kernel_spec_rb_catch_obj, 2); rb_define_method(cls, "rb_sys_fail", kernel_spec_rb_sys_fail, 1); rb_define_method(cls, "rb_syserr_fail", kernel_spec_rb_syserr_fail, 2); + rb_define_method(cls, "rb_syserr_fail_str", kernel_spec_rb_syserr_fail_str, 2); rb_define_method(cls, "rb_warn", kernel_spec_rb_warn, 1); rb_define_method(cls, "rb_yield", kernel_spec_rb_yield, 1); rb_define_method(cls, "rb_yield_indirected", kernel_spec_rb_yield_indirected, 1); @@ -402,12 +435,11 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_exec_recursive", kernel_spec_rb_exec_recursive, 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_str_format", kernel_spec_rb_str_format, 3); rb_define_method(cls, "rb_make_backtrace", kernel_spec_rb_make_backtrace, 0); rb_define_method(cls, "rb_funcallv", kernel_spec_rb_funcallv, 3); -#ifdef RUBY_VERSION_IS_3_0 rb_define_method(cls, "rb_funcallv_kw", kernel_spec_rb_funcallv_kw, 3); rb_define_method(cls, "rb_keyword_given_p", kernel_spec_rb_keyword_given_p, -1); -#endif rb_define_method(cls, "rb_funcallv_public", kernel_spec_rb_funcallv_public, 2); rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2); rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 4); diff --git a/spec/ruby/optional/capi/ext/mutex_spec.c b/spec/ruby/optional/capi/ext/mutex_spec.c index c2fdf917ac..d2c8f98e89 100644 --- a/spec/ruby/optional/capi/ext/mutex_spec.c +++ b/spec/ruby/optional/capi/ext/mutex_spec.c @@ -29,15 +29,34 @@ VALUE mutex_spec_rb_mutex_sleep(VALUE self, VALUE mutex, VALUE timeout) { return rb_mutex_sleep(mutex, timeout); } - VALUE mutex_spec_rb_mutex_callback(VALUE arg) { return rb_funcall(arg, rb_intern("call"), 0); } +VALUE mutex_spec_rb_mutex_naughty_callback(VALUE arg) { + int *result = (int *) arg; + return (VALUE) result; +} + +VALUE mutex_spec_rb_mutex_callback_basic(VALUE arg) { + return arg; +} + VALUE mutex_spec_rb_mutex_synchronize(VALUE self, VALUE mutex, VALUE value) { return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback, value); } +VALUE mutex_spec_rb_mutex_synchronize_with_naughty_callback(VALUE self, VALUE mutex) { + // a naughty callback accepts or returns not a Ruby object but arbitrary value + int arg = 42; + VALUE result = rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_naughty_callback, (VALUE) &arg); + return INT2NUM(*((int *) result)); +} + +VALUE mutex_spec_rb_mutex_synchronize_with_native_callback(VALUE self, VALUE mutex, VALUE value) { + return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback_basic, value); +} + void Init_mutex_spec(void) { VALUE cls = rb_define_class("CApiMutexSpecs", rb_cObject); rb_define_method(cls, "rb_mutex_new", mutex_spec_rb_mutex_new, 0); @@ -47,6 +66,8 @@ void Init_mutex_spec(void) { rb_define_method(cls, "rb_mutex_unlock", mutex_spec_rb_mutex_unlock, 1); rb_define_method(cls, "rb_mutex_sleep", mutex_spec_rb_mutex_sleep, 2); rb_define_method(cls, "rb_mutex_synchronize", mutex_spec_rb_mutex_synchronize, 2); + rb_define_method(cls, "rb_mutex_synchronize_with_naughty_callback", mutex_spec_rb_mutex_synchronize_with_naughty_callback, 1); + rb_define_method(cls, "rb_mutex_synchronize_with_native_callback", mutex_spec_rb_mutex_synchronize_with_native_callback, 2); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index aa60662e1e..995bc38fcf 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -15,11 +15,6 @@ static VALUE object_spec_FL_ABLE(VALUE self, VALUE obj) { static int object_spec_FL_TEST_flag(VALUE flag_string) { char *flag_cstr = StringValueCStr(flag_string); -#ifndef RUBY_VERSION_IS_3_1 - if (strcmp(flag_cstr, "FL_TAINT") == 0) { - return FL_TAINT; - } -#endif if (strcmp(flag_cstr, "FL_FREEZE") == 0) { return FL_FREEZE; } @@ -30,22 +25,6 @@ static VALUE object_spec_FL_TEST(VALUE self, VALUE obj, VALUE flag) { return INT2FIX(FL_TEST(obj, object_spec_FL_TEST_flag(flag))); } -#ifndef RUBY_VERSION_IS_3_1 -static VALUE object_spec_OBJ_TAINT(VALUE self, VALUE obj) { - OBJ_TAINT(obj); - return Qnil; -} - -static VALUE object_spec_OBJ_TAINTED(VALUE self, VALUE obj) { - return OBJ_TAINTED(obj) ? Qtrue : Qfalse; -} - -static VALUE object_spec_OBJ_INFECT(VALUE self, VALUE host, VALUE source) { - OBJ_INFECT(host, source); - return Qnil; -} -#endif - static VALUE object_spec_rb_any_to_s(VALUE self, VALUE obj) { return rb_any_to_s(obj); } @@ -154,12 +133,6 @@ static VALUE object_specs_rb_obj_method(VALUE self, VALUE obj, VALUE method) { return rb_obj_method(obj, method); } -#ifndef RUBY_VERSION_IS_3_2 -static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) { - return rb_obj_taint(obj); -} -#endif - static VALUE so_require(VALUE self) { rb_require("fixtures/foo"); return Qnil; @@ -410,15 +383,20 @@ static VALUE object_spec_custom_alloc_func_p(VALUE self, VALUE klass) { return allocator ? Qtrue : Qfalse; } +static VALUE object_spec_redefine_frozen(VALUE self) { + // The purpose of this spec is to verify that `frozen?` + // and `RB_OBJ_FROZEN` do not mutually recurse infinitely. + if (RB_OBJ_FROZEN(self)) { + return Qtrue; + } + + return Qfalse; +} + void Init_object_spec(void) { VALUE cls = rb_define_class("CApiObjectSpecs", rb_cObject); rb_define_method(cls, "FL_ABLE", object_spec_FL_ABLE, 1); rb_define_method(cls, "FL_TEST", object_spec_FL_TEST, 2); -#ifndef RUBY_VERSION_IS_3_1 - rb_define_method(cls, "OBJ_TAINT", object_spec_OBJ_TAINT, 1); - rb_define_method(cls, "OBJ_TAINTED", object_spec_OBJ_TAINTED, 1); - rb_define_method(cls, "OBJ_INFECT", object_spec_OBJ_INFECT, 2); -#endif rb_define_method(cls, "rb_any_to_s", object_spec_rb_any_to_s, 1); rb_define_method(cls, "rb_attr_get", so_attr_get, 2); rb_define_method(cls, "rb_obj_instance_variables", object_spec_rb_obj_instance_variables, 1); @@ -443,15 +421,11 @@ void Init_object_spec(void) { 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); -#ifndef RUBY_VERSION_IS_3_2 - rb_define_method(cls, "rb_obj_taint", object_spec_rb_obj_taint, 1); -#endif 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_method_boundp", object_spec_rb_method_boundp, 3); rb_define_method(cls, "rb_obj_respond_to", so_obj_respond_to, 3); rb_define_method(cls, "rb_special_const_p", object_spec_rb_special_const_p, 1); - rb_define_method(cls, "rb_to_id", so_to_id, 1); rb_define_method(cls, "RTEST", object_spec_RTEST, 1); rb_define_method(cls, "rb_check_type", so_check_type, 2); @@ -491,6 +465,9 @@ void Init_object_spec(void) { 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); + + cls = rb_define_class("CApiObjectRedefinitionSpecs", rb_cObject); + rb_define_method(cls, "frozen?", object_spec_redefine_frozen, 0); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/range_spec.c b/spec/ruby/optional/capi/ext/range_spec.c index b0cf1a8662..9faed3e5ee 100644 --- a/spec/ruby/optional/capi/ext/range_spec.c +++ b/spec/ruby/optional/capi/ext/range_spec.c @@ -25,9 +25,9 @@ VALUE range_spec_rb_range_values(VALUE self, VALUE range) { return ary; } -VALUE range_spec_rb_range_beg_len(VALUE self, VALUE range, VALUE begpv, VALUE lenpv, VALUE lenv, VALUE errv) { - long begp = FIX2LONG(begpv); - long lenp = FIX2LONG(lenpv); +VALUE range_spec_rb_range_beg_len(VALUE self, VALUE range, VALUE lenv, VALUE errv) { + long begp = 0; + long lenp = 0; long len = FIX2LONG(lenv); int err = FIX2INT(errv); VALUE ary = rb_ary_new(); @@ -38,11 +38,51 @@ VALUE range_spec_rb_range_beg_len(VALUE self, VALUE range, VALUE begpv, VALUE le return ary; } +VALUE range_spec_rb_arithmetic_sequence_extract(VALUE self, VALUE object) { + VALUE ary = rb_ary_new(); + rb_arithmetic_sequence_components_t components; + + int status = rb_arithmetic_sequence_extract(object, &components); + + if (!status) { + rb_ary_store(ary, 0, LONG2FIX(status)); + return ary; + } + + rb_ary_store(ary, 0, LONG2FIX(status)); + rb_ary_store(ary, 1, components.begin); + rb_ary_store(ary, 2, components.end); + rb_ary_store(ary, 3, components.step); + rb_ary_store(ary, 4, components.exclude_end ? Qtrue : Qfalse); + return ary; +} + +VALUE range_spec_rb_arithmetic_sequence_beg_len_step(VALUE self, VALUE aseq, VALUE lenv, VALUE errv) { + long begp = 0; + long lenp = 0; + long stepp = 0; + + long len = FIX2LONG(lenv); + int err = FIX2INT(errv); + + VALUE success = rb_arithmetic_sequence_beg_len_step(aseq, &begp, &lenp, &stepp, len, err); + + VALUE ary = rb_ary_new(); + rb_ary_store(ary, 0, success); + rb_ary_store(ary, 1, LONG2FIX(begp)); + rb_ary_store(ary, 2, LONG2FIX(lenp)); + rb_ary_store(ary, 3, LONG2FIX(stepp)); + + return ary; +} + void Init_range_spec(void) { VALUE cls = rb_define_class("CApiRangeSpecs", rb_cObject); rb_define_method(cls, "rb_range_new", range_spec_rb_range_new, -1); rb_define_method(cls, "rb_range_values", range_spec_rb_range_values, 1); - rb_define_method(cls, "rb_range_beg_len", range_spec_rb_range_beg_len, 5); + rb_define_method(cls, "rb_range_beg_len", range_spec_rb_range_beg_len, 3); + rb_define_method(cls, "rb_arithmetic_sequence_extract", range_spec_rb_arithmetic_sequence_extract, 1); + rb_define_method(cls, "rb_arithmetic_sequence_beg_len_step", range_spec_rb_arithmetic_sequence_beg_len_step, 3); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c index 26be2fed6d..5a95b92804 100644 --- a/spec/ruby/optional/capi/ext/rbasic_spec.c +++ b/spec/ruby/optional/capi/ext/rbasic_spec.c @@ -31,13 +31,6 @@ static const VALUE DATA_VISIBLE_BITS = FL_FREEZE | ~(FL_USER0 - 1); #error "unsupported" #endif - -#ifndef RUBY_VERSION_IS_3_1 -VALUE rbasic_spec_taint_flag(VALUE self) { - return VALUE2NUM(RUBY_FL_TAINT); -} -#endif - VALUE rbasic_spec_freeze_flag(VALUE self) { return VALUE2NUM(RUBY_FL_FREEZE); } @@ -93,9 +86,6 @@ static VALUE rbasic_rdata_spec_get_klass(VALUE self, VALUE structure) { void Init_rbasic_spec(void) { VALUE cls = rb_define_class("CApiRBasicSpecs", rb_cObject); -#ifndef RUBY_VERSION_IS_3_1 - rb_define_method(cls, "taint_flag", rbasic_spec_taint_flag, 0); -#endif rb_define_method(cls, "freeze_flag", rbasic_spec_freeze_flag, 0); rb_define_method(cls, "get_flags", rbasic_spec_get_flags, 1); rb_define_method(cls, "set_flags", rbasic_spec_set_flags, 2); diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h index 09135774af..6c4bea5da0 100644 --- a/spec/ruby/optional/capi/ext/rubyspec.h +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -5,11 +5,7 @@ * guards to assist with version incompatibilities. */ #include <ruby.h> -#ifdef HAVE_RUBY_VERSION_H -# include <ruby/version.h> -#else -# include <version.h> -#endif +#include <ruby/version.h> /* copied from ext/-test-/cxxanyargs/cxxanyargs.cpp */ #if 0 /* Ignore deprecation warnings */ @@ -34,32 +30,21 @@ #endif -#ifndef RUBY_VERSION_MAJOR -#define RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR -#define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR -#define RUBY_VERSION_TEENY RUBY_API_VERSION_TEENY -#endif - -#define RUBY_VERSION_BEFORE(major,minor,teeny) \ - ((RUBY_VERSION_MAJOR < (major)) || \ - (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR < (minor)) || \ - (RUBY_VERSION_MAJOR == (major) && RUBY_VERSION_MINOR == (minor) && RUBY_VERSION_TEENY < (teeny))) -#define RUBY_VERSION_SINCE(major,minor,teeny) (!RUBY_VERSION_BEFORE(major, minor, teeny)) +#define RUBY_VERSION_BEFORE(major,minor) \ + ((RUBY_API_VERSION_MAJOR < (major)) || \ + (RUBY_API_VERSION_MAJOR == (major) && RUBY_API_VERSION_MINOR < (minor))) +#define RUBY_VERSION_SINCE(major,minor) (!RUBY_VERSION_BEFORE(major, minor)) -#if RUBY_VERSION_SINCE(3, 3, 0) -#define RUBY_VERSION_IS_3_3 -#endif - -#if RUBY_VERSION_SINCE(3, 2, 0) -#define RUBY_VERSION_IS_3_2 +#if RUBY_VERSION_SINCE(4, 0) +#define RUBY_VERSION_IS_4_0 #endif -#if RUBY_VERSION_SINCE(3, 1, 0) -#define RUBY_VERSION_IS_3_1 +#if RUBY_VERSION_SINCE(3, 4) +#define RUBY_VERSION_IS_3_4 #endif -#if RUBY_VERSION_SINCE(3, 0, 0) -#define RUBY_VERSION_IS_3_0 +#if RUBY_VERSION_SINCE(3, 3) +#define RUBY_VERSION_IS_3_3 #endif #endif diff --git a/spec/ruby/optional/capi/ext/set_spec.c b/spec/ruby/optional/capi/ext/set_spec.c new file mode 100644 index 0000000000..11a271b361 --- /dev/null +++ b/spec/ruby/optional/capi/ext/set_spec.c @@ -0,0 +1,65 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef RUBY_VERSION_IS_4_0 +#ifdef __cplusplus +extern "C" { +#endif + +#define RBOOL(x) ((x) ? Qtrue : Qfalse) + +int yield_element_and_arg(VALUE element, VALUE arg) { + return RTEST(rb_yield_values(2, element, arg)) ? ST_CONTINUE : ST_STOP; +} + +VALUE set_spec_rb_set_foreach(VALUE self, VALUE set, VALUE arg) { + rb_set_foreach(set, yield_element_and_arg, arg); + return Qnil; +} + +VALUE set_spec_rb_set_new(VALUE self) { + return rb_set_new(); +} + +VALUE set_spec_rb_set_new_capa(VALUE self, VALUE capa) { + return rb_set_new_capa(NUM2INT(capa)); +} + +VALUE set_spec_rb_set_lookup(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_lookup(set, element)); +} + +VALUE set_spec_rb_set_add(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_add(set, element)); +} + +VALUE set_spec_rb_set_clear(VALUE self, VALUE set) { + return rb_set_clear(set); +} + +VALUE set_spec_rb_set_delete(VALUE self, VALUE set, VALUE element) { + return RBOOL(rb_set_delete(set, element)); +} + +VALUE set_spec_rb_set_size(VALUE self, VALUE set) { + return SIZET2NUM(rb_set_size(set)); +} + +void Init_set_spec(void) { + VALUE cls = rb_define_class("CApiSetSpecs", rb_cObject); + + rb_define_method(cls, "rb_set_foreach", set_spec_rb_set_foreach, 2); + rb_define_method(cls, "rb_set_new", set_spec_rb_set_new, 0); + rb_define_method(cls, "rb_set_new_capa", set_spec_rb_set_new_capa, 1); + rb_define_method(cls, "rb_set_lookup", set_spec_rb_set_lookup, 2); + rb_define_method(cls, "rb_set_add", set_spec_rb_set_add, 2); + rb_define_method(cls, "rb_set_clear", set_spec_rb_set_clear, 1); + rb_define_method(cls, "rb_set_delete", set_spec_rb_set_delete, 2); + rb_define_method(cls, "rb_set_size", set_spec_rb_set_size, 1); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c index cec3f65f45..094013e049 100644 --- a/spec/ruby/optional/capi/ext/string_spec.c +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -117,6 +117,10 @@ VALUE string_spec_rb_str_cmp(VALUE self, VALUE str1, VALUE str2) { return INT2NUM(rb_str_cmp(str1, str2)); } +VALUE string_spec_rb_str_strlen(VALUE self, VALUE str) { + return LONG2NUM(rb_str_strlen(str)); +} + VALUE string_spec_rb_str_conv_enc(VALUE self, VALUE str, VALUE from, VALUE to) { rb_encoding* from_enc; rb_encoding* to_enc; @@ -248,16 +252,6 @@ VALUE string_spec_rb_str_new5(VALUE self, VALUE str, VALUE ptr, VALUE len) { return rb_str_new5(str, RSTRING_PTR(ptr), FIX2INT(len)); } -#ifndef RUBY_VERSION_IS_3_2 -VALUE string_spec_rb_tainted_str_new(VALUE self, VALUE str, VALUE len) { - return rb_tainted_str_new(RSTRING_PTR(str), FIX2INT(len)); -} - -VALUE string_spec_rb_tainted_str_new2(VALUE self, VALUE str) { - return rb_tainted_str_new2(RSTRING_PTR(str)); -} -#endif - VALUE string_spec_rb_str_plus(VALUE self, VALUE str1, VALUE str2) { return rb_str_plus(str1, str2); } @@ -446,6 +440,7 @@ static VALUE string_spec_rb_str_free(VALUE self, VALUE str) { static VALUE string_spec_rb_sprintf1(VALUE self, VALUE str, VALUE repl) { return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl)); } + static VALUE string_spec_rb_sprintf2(VALUE self, VALUE str, VALUE repl1, VALUE repl2) { return rb_sprintf(RSTRING_PTR(str), RSTRING_PTR(repl1), RSTRING_PTR(repl2)); } @@ -577,6 +572,11 @@ static VALUE string_spec_rb_enc_interned_str_cstr(VALUE self, VALUE str, VALUE e return rb_enc_interned_str_cstr(RSTRING_PTR(str), e); } +static VALUE string_spec_rb_enc_interned_str(VALUE self, VALUE str, VALUE len, VALUE enc) { + rb_encoding *e = NIL_P(enc) ? 0 : rb_to_encoding(enc); + return rb_enc_interned_str(RSTRING_PTR(str), FIX2LONG(len), e); +} + static VALUE string_spec_rb_str_to_interned_str(VALUE self, VALUE str) { return rb_str_to_interned_str(str); } @@ -600,6 +600,7 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_str_cat_cstr", string_spec_rb_str_cat_cstr, 2); rb_define_method(cls, "rb_str_cat_cstr_constant", string_spec_rb_str_cat_cstr_constant, 1); rb_define_method(cls, "rb_str_cmp", string_spec_rb_str_cmp, 2); + rb_define_method(cls, "rb_str_strlen", string_spec_rb_str_strlen, 1); rb_define_method(cls, "rb_str_conv_enc", string_spec_rb_str_conv_enc, 3); rb_define_method(cls, "rb_str_conv_enc_opts", string_spec_rb_str_conv_enc_opts, 5); rb_define_method(cls, "rb_str_drop_bytes", string_spec_rb_str_drop_bytes, 2); @@ -625,10 +626,6 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_str_new3", string_spec_rb_str_new3, 1); rb_define_method(cls, "rb_str_new4", string_spec_rb_str_new4, 1); rb_define_method(cls, "rb_str_new5", string_spec_rb_str_new5, 3); -#ifndef RUBY_VERSION_IS_3_2 - rb_define_method(cls, "rb_tainted_str_new", string_spec_rb_tainted_str_new, 2); - rb_define_method(cls, "rb_tainted_str_new2", string_spec_rb_tainted_str_new2, 1); -#endif rb_define_method(cls, "rb_str_plus", string_spec_rb_str_plus, 2); rb_define_method(cls, "rb_str_times", string_spec_rb_str_times, 2); rb_define_method(cls, "rb_str_modify_expand", string_spec_rb_str_modify_expand, 2); @@ -682,6 +679,7 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_str_locktmp", string_spec_rb_str_locktmp, 1); rb_define_method(cls, "rb_str_unlocktmp", string_spec_rb_str_unlocktmp, 1); rb_define_method(cls, "rb_enc_interned_str_cstr", string_spec_rb_enc_interned_str_cstr, 2); + rb_define_method(cls, "rb_enc_interned_str", string_spec_rb_enc_interned_str, 3); rb_define_method(cls, "rb_str_to_interned_str", string_spec_rb_str_to_interned_str, 1); } diff --git a/spec/ruby/optional/capi/ext/struct_spec.c b/spec/ruby/optional/capi/ext/struct_spec.c index 9c45bd5672..756cfca8dd 100644 --- a/spec/ruby/optional/capi/ext/struct_spec.c +++ b/spec/ruby/optional/capi/ext/struct_spec.c @@ -28,7 +28,7 @@ static VALUE struct_spec_rb_struct_aset(VALUE self, VALUE st, VALUE key, VALUE v } /* Only allow setting three attributes, should be sufficient for testing. */ -static VALUE struct_spec_struct_define(VALUE self, VALUE name, +static VALUE struct_spec_rb_struct_define(VALUE self, VALUE name, VALUE attr1, VALUE attr2, VALUE attr3) { const char *a1 = StringValuePtr(attr1); @@ -42,7 +42,7 @@ static VALUE struct_spec_struct_define(VALUE self, VALUE name, } /* Only allow setting three attributes, should be sufficient for testing. */ -static VALUE struct_spec_struct_define_under(VALUE self, VALUE outer, +static VALUE struct_spec_rb_struct_define_under(VALUE self, VALUE outer, VALUE name, VALUE attr1, VALUE attr2, VALUE attr3) { const char *nm = StringValuePtr(name); @@ -62,6 +62,27 @@ static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st) { return rb_struct_size(st); } +static VALUE struct_spec_rb_struct_initialize(VALUE self, VALUE st, VALUE values) { + return rb_struct_initialize(st, values); +} + +#if defined(RUBY_VERSION_IS_3_3) +/* Only allow setting three attributes, should be sufficient for testing. */ +static VALUE struct_spec_rb_data_define(VALUE self, VALUE superclass, + VALUE attr1, VALUE attr2, VALUE attr3) { + + const char *a1 = StringValuePtr(attr1); + const char *a2 = StringValuePtr(attr2); + const char *a3 = StringValuePtr(attr3); + + if (superclass == Qnil) { + superclass = 0; + } + + return rb_data_define(superclass, a1, a2, a3, NULL); +} +#endif + void Init_struct_spec(void) { VALUE cls = rb_define_class("CApiStructSpecs", rb_cObject); rb_define_method(cls, "rb_struct_aref", struct_spec_rb_struct_aref, 2); @@ -69,10 +90,14 @@ void Init_struct_spec(void) { rb_define_method(cls, "rb_struct_s_members", struct_spec_rb_struct_s_members, 1); rb_define_method(cls, "rb_struct_members", struct_spec_rb_struct_members, 1); rb_define_method(cls, "rb_struct_aset", struct_spec_rb_struct_aset, 3); - rb_define_method(cls, "rb_struct_define", struct_spec_struct_define, 4); - rb_define_method(cls, "rb_struct_define_under", struct_spec_struct_define_under, 5); + rb_define_method(cls, "rb_struct_define", struct_spec_rb_struct_define, 4); + rb_define_method(cls, "rb_struct_define_under", struct_spec_rb_struct_define_under, 5); rb_define_method(cls, "rb_struct_new", struct_spec_rb_struct_new, 4); rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1); + rb_define_method(cls, "rb_struct_initialize", struct_spec_rb_struct_initialize, 2); +#if defined(RUBY_VERSION_IS_3_3) + rb_define_method(cls, "rb_data_define", struct_spec_rb_data_define, 4); +#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/thread_spec.c b/spec/ruby/optional/capi/ext/thread_spec.c index 3511c2fbcf..ac77e4e813 100644 --- a/spec/ruby/optional/capi/ext/thread_spec.c +++ b/spec/ruby/optional/capi/ext/thread_spec.c @@ -118,7 +118,6 @@ static VALUE thread_spec_rb_thread_wait_for(VALUE self, VALUE s, VALUE ms) { return Qnil; } - VALUE thread_spec_call_proc(void *arg_ptr) { VALUE arg_array = (VALUE)arg_ptr; VALUE arg = rb_ary_pop(arg_array); @@ -167,6 +166,12 @@ static VALUE thread_spec_ruby_native_thread_p_new_thread(VALUE self) { #endif } +#ifdef RUBY_VERSION_IS_4_0 +static VALUE thread_spec_ruby_thread_has_gvl_p(VALUE self) { + return ruby_thread_has_gvl_p() ? Qtrue : Qfalse; +} +#endif + void Init_thread_spec(void) { VALUE cls = rb_define_class("CApiThreadSpecs", rb_cObject); rb_define_method(cls, "rb_thread_alone", thread_spec_rb_thread_alone, 0); @@ -180,6 +185,9 @@ void Init_thread_spec(void) { rb_define_method(cls, "rb_thread_create", thread_spec_rb_thread_create, 2); rb_define_method(cls, "ruby_native_thread_p", thread_spec_ruby_native_thread_p, 0); rb_define_method(cls, "ruby_native_thread_p_new_thread", thread_spec_ruby_native_thread_p_new_thread, 0); +#ifdef RUBY_VERSION_IS_4_0 + rb_define_method(cls, "ruby_thread_has_gvl_p", thread_spec_ruby_thread_has_gvl_p, 0); +#endif } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/ext/typed_data_spec.c b/spec/ruby/optional/capi/ext/typed_data_spec.c index 38889ecf5c..221f1c8ac4 100644 --- a/spec/ruby/optional/capi/ext/typed_data_spec.c +++ b/spec/ruby/optional/capi/ext/typed_data_spec.c @@ -106,6 +106,8 @@ VALUE sws_typed_wrap_struct(VALUE self, VALUE val) { return TypedData_Wrap_Struct(rb_cObject, &sample_typed_wrapped_struct_data_type, bar); } +#undef RUBY_UNTYPED_DATA_WARNING +#define RUBY_UNTYPED_DATA_WARNING 0 VALUE sws_untyped_wrap_struct(VALUE self, VALUE val) { int* data = (int*) malloc(sizeof(int)); *data = FIX2INT(val); diff --git a/spec/ruby/optional/capi/fiber_spec.rb b/spec/ruby/optional/capi/fiber_spec.rb index 357033f860..05d867498c 100644 --- a/spec/ruby/optional/capi/fiber_spec.rb +++ b/spec/ruby/optional/capi/fiber_spec.rb @@ -1,5 +1,4 @@ require_relative 'spec_helper' -require 'fiber' load_extension('fiber') @@ -49,41 +48,39 @@ describe "C-API Fiber function" do end end - ruby_version_is '3.1' do - describe "rb_fiber_raise" do - it "raises an exception on the resumed fiber" do - fiber = Fiber.new do - begin - Fiber.yield - rescue => error - error - end + describe "rb_fiber_raise" do + it "raises an exception on the resumed fiber" do + fiber = Fiber.new do + begin + Fiber.yield + rescue => error + error end + end - fiber.resume + fiber.resume - result = @s.rb_fiber_raise(fiber, "Boom!") - result.should be_an_instance_of(RuntimeError) - result.message.should == "Boom!" - end + result = @s.rb_fiber_raise(fiber, "Boom!") + result.should be_an_instance_of(RuntimeError) + result.message.should == "Boom!" + end - it "raises an exception on the transferred fiber" do - main = Fiber.current + it "raises an exception on the transferred fiber" do + main = Fiber.current - fiber = Fiber.new do - begin - main.transfer - rescue => error - error - end + fiber = Fiber.new do + begin + main.transfer + rescue => error + error end + end - fiber.transfer + fiber.transfer - result = @s.rb_fiber_raise(fiber, "Boom!") - result.should be_an_instance_of(RuntimeError) - result.message.should == "Boom!" - end + result = @s.rb_fiber_raise(fiber, "Boom!") + result.should be_an_instance_of(RuntimeError) + result.message.should == "Boom!" end end end diff --git a/spec/ruby/optional/capi/finalizer_spec.rb b/spec/ruby/optional/capi/finalizer_spec.rb new file mode 100644 index 0000000000..162e8ea693 --- /dev/null +++ b/spec/ruby/optional/capi/finalizer_spec.rb @@ -0,0 +1,40 @@ +require_relative "spec_helper" + +extension_path = load_extension("finalizer") + +describe "CApiFinalizerSpecs" do + before :each do + @s = CApiFinalizerSpecs.new + end + + describe "rb_define_finalizer" do + it "defines a finalizer on the object" do + code = <<~RUBY + require #{extension_path.dump} + + obj = Object.new + finalizer = Proc.new { puts "finalizer run" } + CApiFinalizerSpecs.new.rb_define_finalizer(obj, finalizer) + RUBY + + ruby_exe(code).should == "finalizer run\n" + end + end + + describe "rb_undefine_finalizer" do + ruby_bug "#20981", "3.4.0"..."3.4.2" do + it "removes finalizers from the object" do + code = <<~RUBY + require #{extension_path.dump} + + obj = Object.new + finalizer = Proc.new { puts "finalizer run" } + ObjectSpace.define_finalizer(obj, finalizer) + CApiFinalizerSpecs.new.rb_undefine_finalizer(obj) + RUBY + + ruby_exe(code).should.empty? + end + end + end +end diff --git a/spec/ruby/optional/capi/fixnum_spec.rb b/spec/ruby/optional/capi/fixnum_spec.rb index aa02a0543b..e691aa3893 100644 --- a/spec/ruby/optional/capi/fixnum_spec.rb +++ b/spec/ruby/optional/capi/fixnum_spec.rb @@ -25,7 +25,7 @@ describe "CApiFixnumSpecs" do end end - platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + platform_is c_long_size: 64 do # sizeof(long) > sizeof(int) it "raises a TypeError if passed nil" do -> { @s.FIX2INT(nil) }.should raise_error(TypeError) end @@ -74,7 +74,7 @@ describe "CApiFixnumSpecs" do end end - platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + platform_is c_long_size: 64 do # sizeof(long) > sizeof(int) it "raises a TypeError if passed nil" do -> { @s.FIX2UINT(nil) }.should raise_error(TypeError) end diff --git a/spec/ruby/optional/capi/globals_spec.rb b/spec/ruby/optional/capi/globals_spec.rb index 48677620bc..4657293e15 100644 --- a/spec/ruby/optional/capi/globals_spec.rb +++ b/spec/ruby/optional/capi/globals_spec.rb @@ -41,14 +41,19 @@ describe "CApiGlobalSpecs" do @f.sb_get_global_value.should == "XYZ" end + run = 0 + it "rb_define_readonly_variable should define a new readonly global variable" do + name = "ro_gvar#{run += 1}" + eval <<~RUBY # Check the gvar doesn't exist and ensure rb_gv_get doesn't implicitly declare the gvar, # otherwise the rb_define_readonly_variable call will conflict. - suppress_warning { @f.sb_gv_get("ro_gvar") } .should == nil + suppress_warning { @f.sb_gv_get("#{name}") }.should == nil - @f.rb_define_readonly_variable("ro_gvar", 15) - $ro_gvar.should == 15 - -> { $ro_gvar = 10 }.should raise_error(NameError) + @f.rb_define_readonly_variable("#{name}", 15) + $#{name}.should == 15 + -> { $#{name} = 10 }.should raise_error(NameError) + RUBY end it "rb_define_hooked_variable should define a C hooked global variable" do diff --git a/spec/ruby/optional/capi/hash_spec.rb b/spec/ruby/optional/capi/hash_spec.rb index a0e49ffc4c..3a27de3bfa 100644 --- a/spec/ruby/optional/capi/hash_spec.rb +++ b/spec/ruby/optional/capi/hash_spec.rb @@ -50,19 +50,17 @@ describe "C-API Hash function" do end end - ruby_version_is '3.2' do - describe "rb_hash_new_capa" do - it "returns a new hash" do - @s.rb_hash_new_capa(3).should == {} - end + describe "rb_hash_new_capa" do + it "returns a new hash" do + @s.rb_hash_new_capa(3).should == {} + end - it "creates a hash with no default proc" do - @s.rb_hash_new_capa(3) {}.default_proc.should be_nil - end + it "creates a hash with no default proc" do + @s.rb_hash_new_capa(3) {}.default_proc.should be_nil + end - it "raises RuntimeError when negative index is provided" do - -> { @s.rb_hash_new_capa(-1) }.should raise_error(RuntimeError, "st_table too big") - end + it "raises RuntimeError when negative index is provided" do + -> { @s.rb_hash_new_capa(-1) }.should raise_error(RuntimeError, "st_table too big") end end @@ -194,6 +192,61 @@ describe "C-API Hash function" do end end + describe "rb_hash_bulk_insert" do + it 'inserts key-value pairs into the hash' do + arr = [:a, 1, :b, 2, :c, 3] + hash = {} + + @s.rb_hash_bulk_insert(arr.length, arr, hash) + + hash.should == {a: 1, b: 2, c: 3} + end + + it 'overwrites existing keys' do + arr = [:a, 4, :b, 5, :c, 6] + hash = {a: 1, b: 2} + + @s.rb_hash_bulk_insert(arr.length, arr, hash) + + hash.should == {a: 4, b: 5, c: 6} + end + + it 'uses the last key in the array if it appears multiple times' do + arr = [:a, 1, :b, 2, :a, 3] + hash = {} + + @s.rb_hash_bulk_insert(arr.length, arr, hash) + + hash.should == {a: 3, b: 2} + end + + it 'allows the array to be NULL if the length is zero' do + hash = {} + + @s.rb_hash_bulk_insert(0, nil, hash) + + hash.should == {} + end + + it 'does not include any keys after the given length' do + arr = [:a, 1, :b, 2, :c, 3, :d, 4] + hash = {} + + @s.rb_hash_bulk_insert(arr.length - 2, arr, hash) + + hash.should == {a: 1, b: 2, c: 3} + end + + it 'does not modify the hash if the length is zero' do + arr = [] + hash = {a: 1, b: 2} + + @s.rb_hash_bulk_insert(arr.length, arr, hash) + + hash.should == {a: 1, b: 2} + end + end + describe "rb_hash_size" do it "returns the size of the hash" do hsh = {fast: 'car', good: 'music'} diff --git a/spec/ruby/optional/capi/integer_spec.rb b/spec/ruby/optional/capi/integer_spec.rb index 089872381c..f177374569 100644 --- a/spec/ruby/optional/capi/integer_spec.rb +++ b/spec/ruby/optional/capi/integer_spec.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary require_relative 'spec_helper' load_extension("integer") diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index 870abef3ea..ab7a7fc8f6 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -1,4 +1,5 @@ require_relative 'spec_helper' +require_relative '../../fixtures/io' load_extension('io') @@ -261,24 +262,38 @@ describe "C-API IO function" do end end - ruby_version_is "3.1" do - describe "rb_io_maybe_wait_writable" do - it "returns mask for events if operation was interrupted" do - @o.rb_io_maybe_wait_writable(Errno::EINTR::Errno, @w_io, nil).should == IO::WRITABLE - end + describe "rb_io_maybe_wait_writable" do + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait_writable(Errno::EINTR::Errno, @w_io, nil).should == IO::WRITABLE + end - it "returns 0 if there is no error condition" do - @o.rb_io_maybe_wait_writable(0, @w_io, nil).should == 0 - end + it "returns 0 if there is no error condition" do + @o.rb_io_maybe_wait_writable(0, @w_io, nil).should == 0 + end - it "raises an IOError if the IO is closed" do - @w_io.close - -> { @o.rb_io_maybe_wait_writable(0, @w_io, nil) }.should raise_error(IOError, "closed stream") - end + it "raises an IOError if the IO is closed" do + @w_io.close + -> { @o.rb_io_maybe_wait_writable(0, @w_io, nil) }.should raise_error(IOError, "closed stream") + end - it "raises an IOError if the IO is not initialized" do - -> { @o.rb_io_maybe_wait_writable(0, IO.allocate, nil) }.should raise_error(IOError, "uninitialized stream") + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait_writable(0, IO.allocate, nil) }.should raise_error(IOError, "uninitialized stream") + end + + it "can be interrupted" do + IOSpec.exhaust_write_buffer(@w_io) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + @o.rb_io_maybe_wait_writable(0, @w_io, 10) end + + Thread.pass until t.stop? + t.kill + t.join + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 end end @@ -332,37 +347,50 @@ describe "C-API IO function" do end end - ruby_version_is "3.1" do - describe "rb_io_maybe_wait_readable" do - it "returns mask for events if operation was interrupted" do - @o.rb_io_maybe_wait_readable(Errno::EINTR::Errno, @r_io, nil, false).should == IO::READABLE - end + describe "rb_io_maybe_wait_readable" do + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait_readable(Errno::EINTR::Errno, @r_io, nil, false).should == IO::READABLE + end - it "returns 0 if there is no error condition" do - @o.rb_io_maybe_wait_readable(0, @r_io, nil, false).should == 0 + it "returns 0 if there is no error condition" do + @o.rb_io_maybe_wait_readable(0, @r_io, nil, false).should == 0 + end + + it "blocks until the io is readable and returns events that actually occurred" do + @o.instance_variable_set :@write_data, false + thr = Thread.new do + Thread.pass until @o.instance_variable_get(:@write_data) + @w_io.write "rb_io_wait_readable" end - it "blocks until the io is readable and returns events that actually occurred" do - @o.instance_variable_set :@write_data, false - thr = Thread.new do - Thread.pass until @o.instance_variable_get(:@write_data) - @w_io.write "rb_io_wait_readable" - end + @o.rb_io_maybe_wait_readable(Errno::EAGAIN::Errno, @r_io, IO::READABLE, true).should == IO::READABLE + @o.instance_variable_get(:@read_data).should == "rb_io_wait_re" + + thr.join + end - @o.rb_io_maybe_wait_readable(Errno::EAGAIN::Errno, @r_io, IO::READABLE, true).should == IO::READABLE - @o.instance_variable_get(:@read_data).should == "rb_io_wait_re" + it "can be interrupted" do + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) - thr.join + t = Thread.new do + @o.rb_io_maybe_wait_readable(0, @r_io, 10, false) end - it "raises an IOError if the IO is closed" do - @r_io.close - -> { @o.rb_io_maybe_wait_readable(0, @r_io, nil, false) }.should raise_error(IOError, "closed stream") - end + Thread.pass until t.stop? + t.kill + t.join - it "raises an IOError if the IO is not initialized" do - -> { @o.rb_io_maybe_wait_readable(0, IO.allocate, nil, false) }.should raise_error(IOError, "uninitialized stream") - end + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + end + + it "raises an IOError if the IO is closed" do + @r_io.close + -> { @o.rb_io_maybe_wait_readable(0, @r_io, nil, false) }.should raise_error(IOError, "closed stream") + end + + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait_readable(0, IO.allocate, nil, false) }.should raise_error(IOError, "uninitialized stream") end end end @@ -405,39 +433,64 @@ describe "C-API IO function" do end end - ruby_version_is "3.1" do - describe "rb_io_maybe_wait" do - it "waits til an fd is ready for reading" do - start = false - thr = Thread.new do - start = true - sleep 0.05 - @w_io.write "rb_io_maybe_wait" - end + describe "rb_io_maybe_wait" do + it "waits til an fd is ready for reading" do + start = false + thr = Thread.new do + start = true + sleep 0.05 + @w_io.write "rb_io_maybe_wait" + end + + Thread.pass until start - Thread.pass until start + @o.rb_io_maybe_wait(Errno::EAGAIN::Errno, @r_io, IO::READABLE, nil).should == IO::READABLE - @o.rb_io_maybe_wait(Errno::EAGAIN::Errno, @r_io, IO::READABLE, nil).should == IO::READABLE + thr.join + end - thr.join - end + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait(Errno::EINTR::Errno, @w_io, IO::WRITABLE, nil).should == IO::WRITABLE + end - it "returns mask for events if operation was interrupted" do - @o.rb_io_maybe_wait(Errno::EINTR::Errno, @w_io, IO::WRITABLE, nil).should == IO::WRITABLE - end + it "raises an IOError if the IO is closed" do + @w_io.close + -> { @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil) }.should raise_error(IOError, "closed stream") + end - it "returns false if there is no error condition" do - @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil).should == false - end + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait(0, IO.allocate, IO::WRITABLE, nil) }.should raise_error(IOError, "uninitialized stream") + end - it "raises an IOError if the IO is closed" do - @w_io.close - -> { @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil) }.should raise_error(IOError, "closed stream") + it "can be interrupted when waiting for READABLE event" do + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + @o.rb_io_maybe_wait(0, @r_io, IO::READABLE, 10) end - it "raises an IOError if the IO is not initialized" do - -> { @o.rb_io_maybe_wait(0, IO.allocate, IO::WRITABLE, nil) }.should raise_error(IOError, "uninitialized stream") + Thread.pass until t.stop? + t.kill + t.join + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 + end + + it "can be interrupted when waiting for WRITABLE event" do + IOSpec.exhaust_write_buffer(@w_io) + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + t = Thread.new do + @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, 10) end + + Thread.pass until t.stop? + t.kill + t.join + + finish = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (finish - start).should < 9 end end @@ -457,6 +510,160 @@ describe "C-API IO function" do @o.rb_io_path(@rw_io).should == @name end end + + describe "rb_io_closed_p" do + it "returns false when io is not closed" do + @o.rb_io_closed_p(@r_io).should == false + @r_io.closed?.should == false + end + + it "returns true when io is closed" do + @r_io.close + + @o.rb_io_closed_p(@r_io).should == true + @r_io.closed?.should == true + end + end + + quarantine! do # "Errno::EBADF: Bad file descriptor" at closing @r_io, @rw_io etc in the after :each hook + describe "rb_io_open_descriptor" do + it "creates a new IO instance" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.is_a?(IO) + end + + it "return an instance of the specified class" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.class.should == File + + io = @o.rb_io_open_descriptor(IO, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.class.should == IO + end + + it "sets the specified file descriptor" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.fileno.should == @r_io.fileno + end + + it "sets the specified path" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should == "a.txt" + end + + it "sets the specified mode" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_BINMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.binmode? + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, CApiIOSpecs::FMODE_TEXTMODE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should_not.binmode? + end + + it "sets the specified timeout" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.timeout.should == 60 + end + + it "sets the specified internal encoding" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.internal_encoding.should == Encoding::US_ASCII + end + + it "sets the specified external encoding" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.external_encoding.should == Encoding::UTF_8 + end + + it "does not apply the specified encoding flags" do + name = tmp("rb_io_open_descriptor_specs") + File.write(name, "123\r\n456\n89") + file = File.open(name, "r") + + io = @o.rb_io_open_descriptor(File, file.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", CApiIOSpecs::ECONV_UNIVERSAL_NEWLINE_DECORATOR, {}) + io.read_nonblock(20).should == "123\r\n456\n89" + ensure + file.close + rm_r name + end + + it "ignores the IO open options" do + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {external_encoding: "windows-1251"}) + io.external_encoding.should == Encoding::UTF_8 + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {internal_encoding: "windows-1251"}) + io.internal_encoding.should == Encoding::US_ASCII + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {encoding: "windows-1251:binary"}) + io.external_encoding.should == Encoding::UTF_8 + io.internal_encoding.should == Encoding::US_ASCII + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {textmode: false}) + io.should_not.binmode? + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {binmode: true}) + io.should_not.binmode? + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {autoclose: false}) + io.should.autoclose? + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, "a.txt", 60, "US-ASCII", "UTF-8", 0, {path: "a.txt"}) + io.path.should == "a.txt" + end + + it "ignores the IO encoding options" do + io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_WRITABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {crlf_newline: true}) + + io.write("123\r\n456\n89") + io.flush + + @r_io.read_nonblock(20).should == "123\r\n456\n89" + end + + it "allows wrong mode" do + io = @o.rb_io_open_descriptor(File, @w_io.fileno, CApiIOSpecs::FMODE_READABLE, "a.txt", 60, "US-ASCII", "UTF-8", 0, {}) + io.should.is_a?(File) + + platform_is_not :windows do + -> { io.read_nonblock(1) }.should raise_error(Errno::EBADF) + end + + platform_is :windows do + -> { io.read_nonblock(1) }.should raise_error(IO::EWOULDBLOCKWaitReadable) + end + end + + it "tolerates NULL as rb_io_encoding *encoding parameter" do + io = @o.rb_io_open_descriptor_without_encoding(File, @r_io.fileno, 0, "a.txt", 60) + io.should.is_a?(File) + end + + it "deduplicates path String" do + path = "a.txt".dup + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should_not equal(path) + + path = "a.txt".freeze + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + io.path.should_not equal(path) + end + + it "calls #to_str to convert a path to a String" do + path = Object.new + def path.to_str; "a.txt"; end + + io = @o.rb_io_open_descriptor(File, @r_io.fileno, 0, path, 60, "US-ASCII", "UTF-8", 0, {}) + + io.path.should == "a.txt" + end + end + end + end + + ruby_version_is "3.4" do + describe "rb_io_maybe_wait" do + it "returns nil if there is no error condition" do + @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil).should == nil + end + end end end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb index 3b61d4f0f1..6633ee50c1 100644 --- a/spec/ruby/optional/capi/kernel_spec.rb +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -3,11 +3,19 @@ require_relative 'fixtures/kernel' kernel_path = load_extension("kernel") +class CApiKernelSpecs::Exc < StandardError +end +exception_class = CApiKernelSpecs::Exc + describe "C-API Kernel function" do before :each do @s = CApiKernelSpecs.new end + after :each do + @s.rb_errinfo.should == nil + end + describe "rb_block_given_p" do it "returns false if no block is passed" do @s.should_not.rb_block_given_p @@ -78,6 +86,22 @@ describe "C-API Kernel function" do -> { @s.rb_raise(h) }.should raise_error(TypeError) h[:stage].should == :before end + + it "re-raises a rescued exception" do + -> do + begin + raise StandardError, "aaa" + rescue Exception + begin + @s.rb_raise({}) + rescue TypeError + end + + # should raise StandardError "aaa" + raise + end + end.should raise_error(StandardError, "aaa") + end end describe "rb_throw" do @@ -132,26 +156,16 @@ describe "C-API Kernel function" do end describe "rb_warn" do - before :each do - @stderr, $stderr = $stderr, IOStub.new - @verbose = $VERBOSE - end - - after :each do - $stderr = @stderr - $VERBOSE = @verbose - end - it "prints a message to $stderr if $VERBOSE evaluates to true" do - $VERBOSE = true - @s.rb_warn("This is a warning") - $stderr.should =~ /This is a warning/ + -> { + @s.rb_warn("This is a warning") + }.should complain(/warning: This is a warning/, verbose: true) end it "prints a message to $stderr if $VERBOSE evaluates to false" do - $VERBOSE = false - @s.rb_warn("This is a warning") - $stderr.should =~ /This is a warning/ + -> { + @s.rb_warn("This is a warning") + }.should complain(/warning: This is a warning/, verbose: false) end end @@ -173,13 +187,35 @@ describe "C-API Kernel function" do it "raises an exception from the given error" do -> do @s.rb_syserr_fail(Errno::EINVAL::Errno, "additional info") - end.should raise_error(Errno::EINVAL, /additional info/) + end.should raise_error(Errno::EINVAL, "Invalid argument - additional info") end it "can take a NULL message" do -> do @s.rb_syserr_fail(Errno::EINVAL::Errno, nil) - end.should raise_error(Errno::EINVAL) + end.should raise_error(Errno::EINVAL, "Invalid argument") + end + + it "uses some kind of string as message when errno is unknown" do + -> { @s.rb_syserr_fail(-10, nil) }.should raise_error(SystemCallError, /[[:graph:]]+/) + end + end + + describe "rb_syserr_fail_str" do + it "raises an exception from the given error" do + -> do + @s.rb_syserr_fail_str(Errno::EINVAL::Errno, "additional info") + end.should raise_error(Errno::EINVAL, "Invalid argument - additional info") + end + + it "can take nil as a message" do + -> do + @s.rb_syserr_fail_str(Errno::EINVAL::Errno, nil) + end.should raise_error(Errno::EINVAL, "Invalid argument") + end + + it "uses some kind of string as message when errno is unknown" do + -> { @s.rb_syserr_fail_str(-10, nil) }.should raise_error(SystemCallError, /[[:graph:]]+/) end end @@ -206,10 +242,8 @@ describe "C-API Kernel function" do @s.rb_yield(1) { break 73 }.should == 73 end - platform_is_not :"solaris2.10" do # NOTE: i386-pc-solaris2.10 - it "rb_yield through a callback to a block that breaks with a value returns the value" do - @s.rb_yield_indirected(1) { break 73 }.should == 73 - end + it "rb_yield through a callback to a block that breaks with a value returns the value" do + @s.rb_yield_indirected(1) { break 73 }.should == 73 end it "rb_yield to block passed to enumerator" do @@ -295,7 +329,7 @@ describe "C-API Kernel function" do it "will allow cleanup code to run after a raise" do proof = [] # Hold proof of work performed after the yield. -> do - @s.rb_protect_yield(77, proof) { |x| raise NameError} + @s.rb_protect_yield(77, proof) { |x| raise NameError } end.should raise_error(NameError) proof[0].should == 23 end @@ -303,7 +337,7 @@ describe "C-API Kernel function" do it "will return nil if an error was raised" do proof = [] # Hold proof of work performed after the yield. -> do - @s.rb_protect_yield(77, proof) { |x| raise NameError} + @s.rb_protect_yield(77, proof) { |x| raise NameError } end.should raise_error(NameError) proof[0].should == 23 proof[1].should == nil @@ -311,14 +345,21 @@ describe "C-API Kernel function" do it "accepts NULL as status and returns nil if it failed" do @s.rb_protect_null_status(42) { |x| x + 1 }.should == 43 - @s.rb_protect_null_status(42) { |x| raise }.should == nil + @s.rb_protect_null_status(42) { |x| raise NameError }.should == nil + @s.rb_errinfo().should.is_a? NameError + ensure + @s.rb_set_errinfo(nil) end - it "populates errinfo with the captured exception" do + it "populates rb_errinfo() with the captured exception" do proof = [] - @s.rb_protect_errinfo(77, proof) { |x| raise NameError }.class.should == NameError + @s.rb_protect_ignore_status(77, proof) { |x| raise NameError } + @s.rb_errinfo().should.is_a? NameError + # Note: on CRuby $! is the NameError here, but not clear if that is desirable or bug proof[0].should == 23 proof[1].should == nil + ensure + @s.rb_set_errinfo(nil) end end @@ -382,9 +423,21 @@ describe "C-API Kernel function" do -> { @s.rb_rescue(@std_error_proc, nil, @std_error_proc, nil) }.should raise_error(StandardError) end - it "makes $! available only during the 'rescue function' execution" do - @s.rb_rescue(@std_error_proc, nil, -> *_ { $! }, nil).class.should == StandardError + it "sets $! and rb_errinfo() during the 'rescue function' execution" do + @s.rb_rescue(-> *_ { raise exception_class, '' }, nil, -> _, exc { + exc.should.is_a?(exception_class) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, nil) + + @s.rb_rescue(-> _ { @s.rb_raise({}) }, nil, -> _, exc { + exc.should.is_a?(TypeError) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, nil) + $!.should == nil + @s.rb_errinfo.should == nil end it "returns the break value if the passed function yields to a block with a break" do @@ -402,7 +455,7 @@ describe "C-API Kernel function" do describe "rb_rescue2" do it "only rescues if one of the passed exceptions is raised" do - proc = -> x { x } + proc = -> x, _exc { x } arg_error_proc = -> *_ { raise ArgumentError, '' } run_error_proc = -> *_ { raise RuntimeError, '' } type_error_proc = -> *_ { raise Exception, 'custom error' } @@ -418,6 +471,23 @@ describe "C-API Kernel function" do @s.rb_rescue2(-> *_ { raise RuntimeError, "foo" }, :no_exc, -> x { x }, :exc, Object.new, 42) }.should raise_error(TypeError, /class or module required/) end + + it "sets $! and rb_errinfo() during the 'rescue function' execution" do + @s.rb_rescue2(-> *_ { raise exception_class, '' }, :no_exc, -> _, exc { + exc.should.is_a?(exception_class) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, :exc, exception_class, ScriptError) + + @s.rb_rescue2(-> *_ { @s.rb_raise({}) }, :no_exc, -> _, exc { + exc.should.is_a?(TypeError) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, :exc, TypeError, ArgumentError) + + $!.should == nil + @s.rb_errinfo.should == nil + end end describe "rb_catch" do @@ -470,6 +540,40 @@ describe "C-API Kernel function" do end end + describe "rb_category_warn" do + it "emits a warning into stderr" do + Warning[:deprecated] = true + + -> { + @s.rb_category_warn_deprecated + }.should complain(/warning: foo/, verbose: true) + end + + it "supports printf format modifiers" do + Warning[:deprecated] = true + + -> { + @s.rb_category_warn_deprecated_with_integer_extra_value(42) + }.should complain(/warning: foo 42/, verbose: true) + end + + it "does not emits a warning when a category is disabled" do + Warning[:deprecated] = false + + -> { + @s.rb_category_warn_deprecated + }.should_not complain(verbose: true) + end + + it "does not emits a warning when $VERBOSE is nil" do + Warning[:deprecated] = true + + -> { + @s.rb_category_warn_deprecated + }.should_not complain(verbose: nil) + end + end + describe "rb_ensure" do it "executes passed function and returns its value" do proc = -> x { x } @@ -486,12 +590,33 @@ describe "C-API Kernel function" do it "executes passed 'ensure function' when an exception is raised" do foo = nil - raise_proc = -> { raise '' } + raise_proc = -> _ { raise exception_class } ensure_proc = -> x { foo = x } - @s.rb_ensure(raise_proc, nil, ensure_proc, :foo) rescue nil + -> { + @s.rb_ensure(raise_proc, nil, ensure_proc, :foo) + }.should raise_error(exception_class) foo.should == :foo end + it "sets $! and rb_errinfo() during the 'ensure function' execution" do + -> { + @s.rb_ensure(-> _ { raise exception_class }, nil, -> _ { + $!.should.is_a?(exception_class) + @s.rb_errinfo.should.is_a?(exception_class) + }, nil) + }.should raise_error(exception_class) + + -> { + @s.rb_ensure(-> _ { @s.rb_raise({}) }, nil, -> _ { + $!.should.is_a?(TypeError) + @s.rb_errinfo.should.is_a?(TypeError) + }, nil) + }.should raise_error(TypeError) + + $!.should == nil + @s.rb_errinfo.should == nil + end + it "raises the same exception raised inside passed function" do raise_proc = -> *_ { raise RuntimeError, 'foo' } proc = -> *_ { } @@ -510,22 +635,24 @@ describe "C-API Kernel function" do end end - describe "rb_eval_cmd_kw" do - it "evaluates a string of ruby code" do - @s.rb_eval_cmd_kw("1+1", [], 0).should == 2 - end + ruby_version_is ""..."4.0" do + describe "rb_eval_cmd_kw" do + it "evaluates a string of ruby code" do + @s.rb_eval_cmd_kw("1+1", [], 0).should == 2 + end - it "calls a proc with the supplied arguments" do - @s.rb_eval_cmd_kw(-> *x { x.map { |i| i + 1 } }, [1, 3, 7], 0).should == [2, 4, 8] - end + it "calls a proc with the supplied arguments" do + @s.rb_eval_cmd_kw(-> *x { x.map { |i| i + 1 } }, [1, 3, 7], 0).should == [2, 4, 8] + end - it "calls a proc with keyword arguments if kw_splat is non zero" do - a_proc = -> *x, **y { - res = x.map { |i| i + 1 } - y.each { |k, v| res << k; res << v } - res - } - @s.rb_eval_cmd_kw(a_proc, [1, 3, 7, {a: 1, b: 2, c: 3}], 1).should == [2, 4, 8, :a, 1, :b, 2, :c, 3] + it "calls a proc with keyword arguments if kw_splat is non zero" do + a_proc = -> *x, **y { + res = x.map { |i| i + 1 } + y.each { |k, v| res << k; res << v } + res + } + @s.rb_eval_cmd_kw(a_proc, [1, 3, 7, {a: 1, b: 2, c: 3}], 1).should == [2, 4, 8, :a, 1, :b, 2, :c, 3] + end end end @@ -787,4 +914,10 @@ describe "C-API Kernel function" do @s.rb_check_funcall(object, :protected_method, []).should == :protected end end + + describe "rb_str_format" do + it "returns a string according to format and arguments" do + @s.rb_str_format(3, [10, 2.5, "test"], "%d %f %s").should == "10 2.500000 test" + end + end end diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb index d7c0ab9c52..af39ec0192 100644 --- a/spec/ruby/optional/capi/module_spec.rb +++ b/spec/ruby/optional/capi/module_spec.rb @@ -22,6 +22,8 @@ describe "CApiModule" do it "sets a new constant on a module" do @m.rb_const_set(CApiModuleSpecs::C, :W, 7) CApiModuleSpecs::C::W.should == 7 + ensure + CApiModuleSpecs::C.send(:remove_const, :W) end it "sets an existing constant's value" do @@ -36,7 +38,7 @@ describe "CApiModule" do CApiModuleSpecs::C.const_set(:_INVALID, 1) }.should raise_error(NameError, /wrong constant name/) - @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) + suppress_warning { @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) } @m.rb_const_get(CApiModuleSpecs::C, :_INVALID).should == 2 # Ruby-level should still not allow access @@ -93,6 +95,8 @@ describe "CApiModule" do it "defines a new constant on a module" do @m.rb_define_const(CApiModuleSpecs::C, "V", 7) CApiModuleSpecs::C::V.should == 7 + ensure + CApiModuleSpecs::C.send(:remove_const, :V) end it "sets an existing constant's value" do diff --git a/spec/ruby/optional/capi/mutex_spec.rb b/spec/ruby/optional/capi/mutex_spec.rb index 34659974f5..71a2212e36 100644 --- a/spec/ruby/optional/capi/mutex_spec.rb +++ b/spec/ruby/optional/capi/mutex_spec.rb @@ -85,5 +85,18 @@ describe "C-API Mutex functions" do callback = -> { @m.locked?.should be_true } @s.rb_mutex_synchronize(@m, callback) end + + it "returns a value returned from a callback" do + callback = -> { :foo } + @s.rb_mutex_synchronize(@m, callback).should == :foo + end + + it "calls a C-function that accepts and returns non-VALUE values" do + @s.rb_mutex_synchronize_with_naughty_callback(@m).should == 42 + end + + it "calls a native function" do + @s.rb_mutex_synchronize_with_native_callback(@m, 42).should == 42 + end end end diff --git a/spec/ruby/optional/capi/numeric_spec.rb b/spec/ruby/optional/capi/numeric_spec.rb index 95213d3f2b..e9667da5ba 100644 --- a/spec/ruby/optional/capi/numeric_spec.rb +++ b/spec/ruby/optional/capi/numeric_spec.rb @@ -106,7 +106,7 @@ describe "CApiNumericSpecs" do @s.NUM2LONG(5).should == 5 end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "converts -1 to an signed number" do @s.NUM2LONG(-1).should == -1 end @@ -120,7 +120,7 @@ describe "CApiNumericSpecs" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "converts -1 to an signed number" do @s.NUM2LONG(-1).should == -1 end @@ -210,7 +210,7 @@ describe "CApiNumericSpecs" do @s.NUM2ULONG(5).should == 5 end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "converts -1 to an unsigned number" do @s.NUM2ULONG(-1).should == 4294967295 end @@ -231,7 +231,7 @@ describe "CApiNumericSpecs" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "converts -1 to an unsigned number" do @s.NUM2ULONG(-1).should == 18446744073709551615 end diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index 7bc7bd992a..8b4d8a9bba 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -691,6 +691,16 @@ describe "CApiObject" do end end + describe "redefining frozen? works" do + it "allows an object to override frozen?" do + obj = CApiObjectRedefinitionSpecs.new + + obj.frozen?.should == false + obj.freeze + obj.frozen?.should == true + end + end + describe "rb_obj_taint" do end @@ -888,7 +898,7 @@ describe "CApiObject" do end end - # The `generic_iv_tbl` table and `*_generic_ivar` functions are for mutable + # The `generic_fields_tbl` table and `*_generic_ivar` functions are for mutable # objects which do not store ivars directly in MRI such as RString, because # there is no member iv_index_tbl (ivar table) such as in RObject and RClass. diff --git a/spec/ruby/optional/capi/range_spec.rb b/spec/ruby/optional/capi/range_spec.rb index 7a52dc7ff8..80c052e79a 100644 --- a/spec/ruby/optional/capi/range_spec.rb +++ b/spec/ruby/optional/capi/range_spec.rb @@ -69,7 +69,7 @@ describe "C-API Range function" do describe "rb_range_beg_len" do it "returns correct begin, length and result" do r = 2..5 - begp, lenp, result = @s.rb_range_beg_len(r, 0, 0, 10, 0) + begp, lenp, result = @s.rb_range_beg_len(r, 10, 0) result.should be_true begp.should == 2 lenp.should == 4 @@ -77,19 +77,155 @@ describe "C-API Range function" do it "returns nil when not in range" do r = 2..5 - begp, lenp, result = @s.rb_range_beg_len(r, 0, 0, 1, 0) + begp, lenp, result = @s.rb_range_beg_len(r, 1, 0) result.should be_nil end it "raises a RangeError when not in range and err is 1" do r = -5..-1 - -> { @s.rb_range_beg_len(r, 0, 0, 1, 1) }.should raise_error(RangeError) + -> { @s.rb_range_beg_len(r, 1, 1) }.should raise_error(RangeError) end it "returns nil when not in range and err is 0" do r = -5..-1 - begp, lenp, result = @s.rb_range_beg_len(r, 0, 0, 1, 0) + begp, lenp, result = @s.rb_range_beg_len(r, 1, 0) result.should be_nil end end + + describe "rb_arithmetic_sequence_extract" do + it "returns begin, end, step, exclude end of an instance of an Enumerator::ArithmeticSequence" do + enum = (10..20).step(5) + enum.should.kind_of?(Enumerator::ArithmeticSequence) + + @s.rb_arithmetic_sequence_extract(enum).should == [1, 10, 20, 5, false] + end + + it "returns begin, end, step, exclude end of an instance of a Range" do + range = (10..20) + @s.rb_arithmetic_sequence_extract(range).should == [1, 10, 20, 1, false] + end + + it "returns begin, end, step, exclude end of a non-Range object with Range properties" do + object = Object.new + def object.begin + 10 + end + def object.end + 20 + end + def object.exclude_end? + false + end + + @s.rb_arithmetic_sequence_extract(object).should == [1, 10, 20, 1, false] + end + + it "returns failed status if given object is not Enumerator::ArithmeticSequence or Range or Range-like object" do + object = Object.new + @s.rb_arithmetic_sequence_extract(object).should == [0] + end + end + + describe "rb_arithmetic_sequence_beg_len_step" do + it "returns correct begin, length, step and result" do + as = (2..5).step(5) + error_code = 0 + + success, beg, len, step = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + + beg.should == 2 + len.should == 4 + step.should == 5 + end + + it "takes into account excluded end boundary" do + as = (2...5).step(1) + error_code = 0 + + success, _, len, _ = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + len.should == 3 + end + + it "adds length to negative begin boundary" do + as = (-2..5).step(1) + error_code = 0 + + success, beg, len, _ = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + + beg.should == 4 + len.should == 2 + end + + it "adds length to negative end boundary" do + as = (2..-1).step(1) + error_code = 0 + + success, beg, len, _ = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + + beg.should == 2 + len.should == 4 + end + + it "truncates arithmetic sequence length if end boundary greater than specified length value" do + as = (2..10).step(1) + error_code = 0 + + success, _, len, _ = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + len.should == 4 + end + + it "returns inverted begin and end boundaries when step is negative" do + as = (2..5).step(-2) + error_code = 0 + + success, beg, len, step = @s.rb_arithmetic_sequence_beg_len_step(as, 6, error_code) + success.should be_true + + beg.should == 5 + len.should == 0 + step.should == -2 + end + + it "returns nil when not in range and error code = 0" do + as = (2..5).step(1) + error_code = 0 + + success, = @s.rb_arithmetic_sequence_beg_len_step(as, 1, error_code) + success.should be_nil + end + + it "returns nil when not in range, negative boundaries and error code = 0" do + as = (-5..-1).step(1) + error_code = 0 + + success, = @s.rb_arithmetic_sequence_beg_len_step(as, 1, 0) + success.should be_nil + end + + it "returns begin, length and step and doesn't raise a RangeError when not in range and error code = 1" do + as = (2..5).step(1) + error_code = 1 + + success, beg, len, step = @s.rb_arithmetic_sequence_beg_len_step(as, 1, error_code) + success.should be_true + + beg.should == 2 + len.should == 4 + step.should == 1 + end + + it "returns nil and doesn't raise a RangeError when not in range, negative boundaries and error code = 1" do + as = (-5..-1).step(1) + error_code = 1 + + success, = @s.rb_arithmetic_sequence_beg_len_step(as, 1, error_code) + success.should be_nil + end + end end diff --git a/spec/ruby/optional/capi/rbasic_spec.rb b/spec/ruby/optional/capi/rbasic_spec.rb index f3367e05ff..7b5b5b2fed 100644 --- a/spec/ruby/optional/capi/rbasic_spec.rb +++ b/spec/ruby/optional/capi/rbasic_spec.rb @@ -1,7 +1,9 @@ require_relative 'spec_helper' require_relative 'shared/rbasic' load_extension("rbasic") -load_extension("data") +ruby_version_is ""..."3.4" do + load_extension("data") +end load_extension("array") describe "RBasic support for regular objects" do @@ -12,33 +14,35 @@ describe "RBasic support for regular objects" do it_should_behave_like :rbasic end -describe "RBasic support for RData" do - before :all do - @specs = CApiRBasicRDataSpecs.new - @wrapping = CApiWrappedStructSpecs.new - @data = -> { [@wrapping.wrap_struct(1024), @wrapping.wrap_struct(1025)] } - end - it_should_behave_like :rbasic +ruby_version_is ""..."3.4" do + describe "RBasic support for RData" do + before :all do + @specs = CApiRBasicRDataSpecs.new + @wrapping = CApiWrappedStructSpecs.new + @data = -> { [@wrapping.wrap_struct(1024), @wrapping.wrap_struct(1025)] } + end + it_should_behave_like :rbasic - it "supports user flags" do - obj, _ = @data.call - initial = @specs.get_flags(obj) - @specs.set_flags(obj, 1 << 14 | 1 << 16 | initial).should == 1 << 14 | 1 << 16 | initial - @specs.get_flags(obj).should == 1 << 14 | 1 << 16 | initial - @specs.set_flags(obj, initial).should == initial - end + it "supports user flags" do + obj, _ = @data.call + initial = @specs.get_flags(obj) + @specs.set_flags(obj, 1 << 14 | 1 << 16 | initial).should == 1 << 14 | 1 << 16 | initial + @specs.get_flags(obj).should == 1 << 14 | 1 << 16 | initial + @specs.set_flags(obj, initial).should == initial + end - it "supports copying the flags from one object over to the other" do - obj1, obj2 = @data.call - initial = @specs.get_flags(obj1) - @specs.get_flags(obj2).should == initial - @specs.set_flags(obj1, 1 << 14 | 1 << 16 | initial) - @specs.get_flags(obj1).should == 1 << 14 | 1 << 16 | initial + it "supports copying the flags from one object over to the other" do + obj1, obj2 = @data.call + initial = @specs.get_flags(obj1) + @specs.get_flags(obj2).should == initial + @specs.set_flags(obj1, 1 << 14 | 1 << 16 | initial) + @specs.get_flags(obj1).should == 1 << 14 | 1 << 16 | initial - @specs.copy_flags(obj2, obj1) - @specs.get_flags(obj2).should == 1 << 14 | 1 << 16 | initial - @specs.set_flags(obj1, initial) - @specs.copy_flags(obj2, obj1) - @specs.get_flags(obj2).should == initial + @specs.copy_flags(obj2, obj1) + @specs.get_flags(obj2).should == 1 << 14 | 1 << 16 | initial + @specs.set_flags(obj1, initial) + @specs.copy_flags(obj2, obj1) + @specs.get_flags(obj2).should == initial + end end end diff --git a/spec/ruby/optional/capi/regexp_spec.rb b/spec/ruby/optional/capi/regexp_spec.rb index af366e17a2..49ac79f5c4 100644 --- a/spec/ruby/optional/capi/regexp_spec.rb +++ b/spec/ruby/optional/capi/regexp_spec.rb @@ -77,7 +77,7 @@ describe "C-API Regexp function" do end it "returns MatchData when used with rb_reg_match" do - @p.rb_reg_match_backref_get(/a/, 'ab')[0].should == 'a' + @p.rb_reg_match_backref_get(/a/, 'ab')[0].should == 'a' end end @@ -110,7 +110,7 @@ describe "C-API Regexp function" do end end - describe "rb_memicmp" do + describe "rb_memcicmp" do it "returns 0 for identical strings" do @p.rb_memcicmp('Hello', 'Hello').should == 0 end diff --git a/spec/ruby/optional/capi/set_spec.rb b/spec/ruby/optional/capi/set_spec.rb new file mode 100644 index 0000000000..3e35be0505 --- /dev/null +++ b/spec/ruby/optional/capi/set_spec.rb @@ -0,0 +1,96 @@ +require_relative 'spec_helper' + +ruby_version_is "4.0" do + load_extension("set") + + describe "C-API Set function" do + before :each do + @s = CApiSetSpecs.new + end + + describe "rb_set_foreach" do + it "calls function with each element and arg" do + a = [] + @s.rb_set_foreach(Set[1, 2], 3) {|*args| a.concat(args) } + a.should == [1, 3, 2, 3] + end + + it "respects function return value" do + a = [] + @s.rb_set_foreach(Set[1, 2], 3) do |*args| + a.concat(args) + false + end + a.should == [1, 3] + end + end + + describe "rb_set_new" do + it "returns a new set" do + @s.rb_set_new.should == Set[] + end + end + + describe "rb_set_new_capa" do + it "returns a new set" do + @s.rb_set_new_capa(3).should == Set[] + end + end + + describe "rb_set_lookup" do + it "returns whether the element is in the set" do + set = Set[1] + @s.rb_set_lookup(set, 1).should == true + @s.rb_set_lookup(set, 2).should == false + end + end + + describe "rb_set_add" do + it "adds element to set" do + set = Set[] + @s.rb_set_add(set, 1).should == true + set.should == Set[1] + @s.rb_set_add(set, 2).should == true + set.should == Set[1, 2] + end + + it "returns false if element is already in set" do + set = Set[1] + @s.rb_set_add(set, 1).should == false + set.should == Set[1] + end + end + + describe "rb_set_clear" do + it "empties and returns self" do + set = Set[1] + @s.rb_set_clear(set).should equal(set) + set.should == Set[] + end + end + + describe "rb_set_delete" do + it "removes element from set" do + set = Set[1, 2] + @s.rb_set_delete(set, 1).should == true + set.should == Set[2] + @s.rb_set_delete(set, 2).should == true + set.should == Set[] + end + + it "returns false if element is not already in set" do + set = Set[2] + @s.rb_set_delete(set, 1).should == false + set.should == Set[2] + end + end + + describe "rb_set_size" do + it "returns number of elements in set" do + @s.rb_set_size(Set[]).should == 0 + @s.rb_set_size(Set[1]).should == 1 + @s.rb_set_size(Set[1,2]).should == 2 + end + end + end +end diff --git a/spec/ruby/optional/capi/shared/rbasic.rb b/spec/ruby/optional/capi/shared/rbasic.rb index 9d80a93e1d..e3485d4b7b 100644 --- a/spec/ruby/optional/capi/shared/rbasic.rb +++ b/spec/ruby/optional/capi/shared/rbasic.rb @@ -1,7 +1,6 @@ describe :rbasic, shared: true do before :all do specs = CApiRBasicSpecs.new - @taint = ruby_version_is(''...'3.1') ? specs.taint_flag : 0 @freeze = specs.freeze_flag end diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb index 2691aa1332..e7abf46e6c 100644 --- a/spec/ruby/optional/capi/spec_helper.rb +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -74,12 +74,19 @@ def compile_extension(name) init_mkmf unless required create_makefile(ext, tmpdir) else + # Workaround for digest C-API specs to find the ruby/digest.h header + # when run in the CRuby repository via make test-spec + if MSpecScript.instance_variable_defined?(:@testing_ruby) + ruby_repository_extra_include_dir = "-I#{RbConfig::CONFIG.fetch("prefix")}/#{RbConfig::CONFIG.fetch("EXTOUT")}/include" + end + File.write("extconf.rb", <<-RUBY) require 'mkmf' $ruby = ENV.values_at('RUBY_EXE', 'RUBY_FLAGS').join(' ') # MRI magic to consider building non-bundled extensions $extout = nil append_cflags '-Wno-declaration-after-statement' + #{"append_cflags #{ruby_repository_extra_include_dir.inspect}" if ruby_repository_extra_include_dir} create_makefile(#{ext.inspect}) RUBY output = ruby_exe("extconf.rb") @@ -115,13 +122,9 @@ def setup_make opts = {} if /(?:\A|\s)--jobserver-(?:auth|fds)=(\d+),(\d+)/ =~ make_flags - begin - r = IO.for_fd($1.to_i(10), "rb", autoclose: false) - w = IO.for_fd($2.to_i(10), "wb", autoclose: false) - rescue Errno::EBADF - else - opts[r] = r - opts[w] = w + [$1, $2].each do |fd| + fd = fd.to_i(10) + opts[fd] = fd end end diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index a8edf998b5..72f20ee6a5 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -191,11 +191,19 @@ describe "C-API String function" do end it "returns a new String object filled with \\0 bytes" do - s = @s.rb_str_tmp_new(4) - s.encoding.should == Encoding::BINARY - s.bytesize.should == 4 - s.size.should == 4 - s.should == "\x00\x00\x00\x00" + lens = [4] + + ruby_version_is "4.0" do + lens << 100 + end + + lens.each do |len| + s = @s.rb_str_tmp_new(len) + s.encoding.should == Encoding::BINARY + s.bytesize.should == len + s.size.should == len + s.should == "\x00" * len + end end end @@ -449,6 +457,20 @@ describe "C-API String function" do end end + describe "rb_str_strlen" do + it 'returns 0 as the length of an empty string' do + @s.rb_str_strlen('').should == 0 + end + + it 'returns the number of characters in a string' do + @s.rb_str_strlen('hello').should == 5 + end + + it 'returns the number of characters in a string with multi-byte characters' do + @s.rb_str_strlen('こんにちは').should == 5 + end + end + describe "rb_str_split" do it "splits strings over a splitter" do @s.rb_str_split("Hello,Goodbye").should == ["Hello", "Goodbye"] @@ -888,16 +910,20 @@ describe "C-API String function" do end it "returns the original String if a transcoding error occurs" do - a = [0xEE].pack('C').force_encoding("utf-8") - @s.rb_str_conv_enc(a, Encoding::UTF_8, Encoding::EUC_JP).should equal(a) + a = [0xEE].pack('C').force_encoding(Encoding::UTF_8) + @s.rb_str_conv_enc(a, Encoding::UTF_8, Encoding::EUC_JP).should.equal?(a) + a.encoding.should == Encoding::UTF_8 + + a = "\x80".b + @s.rb_str_conv_enc(a, Encoding::BINARY, Encoding::UTF_8).should.equal?(a) + a.encoding.should == Encoding::BINARY end it "returns a transcoded String" do - a = "\xE3\x81\x82\xE3\x82\x8C".dup.force_encoding("utf-8") + a = "\xE3\x81\x82\xE3\x82\x8C".dup.force_encoding(Encoding::UTF_8) result = @s.rb_str_conv_enc(a, Encoding::UTF_8, Encoding::EUC_JP) - x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4').force_encoding('utf-8') - result.should == x.force_encoding("euc-jp") - result.encoding.should equal(Encoding::EUC_JP) + result.should == [0xA4, 0xA2, 0xA4, 0xEC].pack('C4').force_encoding(Encoding::EUC_JP) + result.encoding.should == Encoding::EUC_JP end describe "when the String encoding is equal to the destination encoding" do @@ -994,7 +1020,7 @@ describe "C-API String function" do result = @s.rb_str_export_to_enc(source, Encoding::UTF_8) source.bytes.should == [0, 255] end -end + end describe "rb_sprintf" do it "replaces the parts like sprintf" do @@ -1022,11 +1048,19 @@ end @s.rb_sprintf3(true.class).should == s end - ruby_bug "#19167", ""..."3.2" do - it "formats a TrueClass VALUE as 'true' if sign specified in format" do - s = 'Result: TrueClass.' - @s.rb_sprintf4(true.class).should == s - end + it "formats a TrueClass VALUE as 'true' if sign specified in format" do + s = 'Result: TrueClass.' + @s.rb_sprintf4(true.class).should == s + end + + it "formats nil using to_s if sign not specified in format" do + s = 'Result: .' + @s.rb_sprintf3(nil).should == s + end + + it "formats nil using inspect if sign specified in format" do + s = 'Result: nil.' + @s.rb_sprintf4(nil).should == s end it "truncates a string to a supplied precision if that is shorter than the string" do @@ -1096,7 +1130,7 @@ end end it "tries to convert the passed argument to a string by calling #to_s" do - @s.rb_String({"bar" => "foo"}).should == '{"bar"=>"foo"}' + @s.rb_String({"bar" => "foo"}).should == {"bar" => "foo"}.to_s end end @@ -1185,28 +1219,50 @@ end describe "rb_str_locktmp" do it "raises an error when trying to lock an already locked string" do - str = "test" + str = +"test" @s.rb_str_locktmp(str).should == str -> { @s.rb_str_locktmp(str) }.should raise_error(RuntimeError, 'temporal locking already locked string') end it "locks a string so that modifications would raise an error" do - str = "test" + str = +"test" @s.rb_str_locktmp(str).should == str -> { str.upcase! }.should raise_error(RuntimeError, 'can\'t modify string; temporarily locked') end + + ruby_version_is "4.0" do + it "raises FrozenError if string is frozen" do + str = -"rb_str_locktmp" + -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) + + str = +"rb_str_locktmp" + str.freeze + -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) + end + end end describe "rb_str_unlocktmp" do it "unlocks a locked string" do - str = "test" + str = +"test" @s.rb_str_locktmp(str) @s.rb_str_unlocktmp(str).should == str str.upcase!.should == "TEST" end it "raises an error when trying to unlock an already unlocked string" do - -> { @s.rb_str_unlocktmp("test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') + -> { @s.rb_str_unlocktmp(+"test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') + end + + ruby_version_is "4.0" do + it "raises FrozenError if string is frozen" do + str = -"rb_str_locktmp" + -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) + + str = +"rb_str_locktmp" + str.freeze + -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) + end end end @@ -1247,6 +1303,51 @@ end end end + describe "rb_enc_interned_str" do + it "returns a frozen string" do + str = "hello" + val = @s.rb_enc_interned_str(str, str.bytesize, Encoding::US_ASCII) + + val.should.is_a?(String) + val.encoding.should == Encoding::US_ASCII + val.should.frozen? + end + + it "returns the same frozen string" do + str = "hello" + result1 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::US_ASCII) + result2 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::US_ASCII) + result1.should.equal?(result2) + end + + it "returns different frozen strings for different encodings" do + str = "hello" + result1 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::US_ASCII) + result2 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::UTF_8) + result1.should_not.equal?(result2) + end + + it 'returns the same string when using non-ascii characters' do + str = 'こんにちは' + result1 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::UTF_8) + result2 = @s.rb_enc_interned_str(str, str.bytesize, Encoding::UTF_8) + result1.should.equal?(result2) + end + + it "returns the same string as String#-@" do + str = "hello" + @s.rb_enc_interned_str(str, str.bytesize, Encoding::UTF_8).should.equal?(-str) + end + + ruby_bug "#20322", ""..."3.4" do + it "uses the default encoding if encoding is null" do + str = "hello" + val = @s.rb_enc_interned_str(str, str.bytesize, nil) + val.encoding.should == Encoding::ASCII_8BIT + end + end + end + describe "rb_str_to_interned_str" do it "returns a frozen string" do str = "hello" diff --git a/spec/ruby/optional/capi/struct_spec.rb b/spec/ruby/optional/capi/struct_spec.rb index 0e9e366908..cc8d7f932e 100644 --- a/spec/ruby/optional/capi/struct_spec.rb +++ b/spec/ruby/optional/capi/struct_spec.rb @@ -208,4 +208,109 @@ describe "C-API Struct function" do @s.rb_struct_size(@struct).should == 3 end end + + describe "rb_struct_initialize" do + it "sets all members" do + @s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil + @struct.a.should == 1 + @struct.b.should == 2 + @struct.c.should == 3 + end + + it "does not freeze the Struct instance" do + @s.rb_struct_initialize(@struct, [1, 2, 3]).should == nil + @struct.should_not.frozen? + @s.rb_struct_initialize(@struct, [4, 5, 6]).should == nil + @struct.a.should == 4 + @struct.b.should == 5 + @struct.c.should == 6 + end + + it "raises ArgumentError if too many values" do + -> { @s.rb_struct_initialize(@struct, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") + end + + it "treats missing values as nil" do + @s.rb_struct_initialize(@struct, [1, 2]).should == nil + @struct.a.should == 1 + @struct.b.should == 2 + @struct.c.should == nil + end + end +end + +ruby_version_is "3.3" do + describe "C-API Data function" do + before :all do + @s = CApiStructSpecs.new + @klass = @s.rb_data_define(nil, "a", "b", "c") + end + + describe "rb_data_define" do + it "returns a subclass of Data class when passed nil as the first argument" do + @klass.should.is_a? Class + @klass.superclass.should == Data + end + + it "returns a subclass of a class when passed as the first argument" do + superclass = Class.new(Data) + klass = @s.rb_data_define(superclass, "a", "b", "c") + + klass.should.is_a? Class + klass.superclass.should == superclass + end + + it "creates readers for the members" do + obj = @klass.new(1, 2, 3) + + obj.a.should == 1 + obj.b.should == 2 + obj.c.should == 3 + end + + it "returns the member names as Symbols" do + obj = @klass.new(0, 0, 0) + + obj.members.should == [:a, :b, :c] + end + + it "raises an ArgumentError if arguments contain duplicate member name" do + -> { @s.rb_data_define(nil, "a", "b", "a") }.should raise_error(ArgumentError) + end + + it "raises when first argument is not a class" do + -> { @s.rb_data_define([], "a", "b", "c") }.should raise_error(TypeError, "wrong argument type Array (expected Class)") + end + end + + describe "rb_struct_initialize" do + it "sets all members for a Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == 3 + end + + it "freezes the Data instance" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2, 3]).should == nil + data.should.frozen? + -> { @s.rb_struct_initialize(data, [1, 2, 3]) }.should raise_error(FrozenError) + end + + it "raises ArgumentError if too many values" do + data = @klass.allocate + -> { @s.rb_struct_initialize(data, [1, 2, 3, 4]) }.should raise_error(ArgumentError, "struct size differs") + end + + it "treats missing values as nil" do + data = @klass.allocate + @s.rb_struct_initialize(data, [1, 2]).should == nil + data.a.should == 1 + data.b.should == 2 + data.c.should == nil + end + end + end end diff --git a/spec/ruby/optional/capi/thread_spec.rb b/spec/ruby/optional/capi/thread_spec.rb index af641f0564..117726f0e2 100644 --- a/spec/ruby/optional/capi/thread_spec.rb +++ b/spec/ruby/optional/capi/thread_spec.rb @@ -184,4 +184,12 @@ describe "C-API Thread function" do thr.value.should be_true end end + + ruby_version_is "4.0" do + describe "ruby_thread_has_gvl_p" do + it "returns true if the current thread has the GVL" do + @t.ruby_thread_has_gvl_p.should be_true + end + end + end end diff --git a/spec/ruby/optional/capi/util_spec.rb b/spec/ruby/optional/capi/util_spec.rb index 9ff8b4760a..6cf064bf97 100644 --- a/spec/ruby/optional/capi/util_spec.rb +++ b/spec/ruby/optional/capi/util_spec.rb @@ -209,7 +209,7 @@ describe "C-API Util function" do end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "rb_long2int" do it "raises a RangeError if the value is outside the range of a C int" do -> { @o.rb_long2int(0xffff_ffff_ffff) }.should raise_error(RangeError) diff --git a/spec/ruby/optional/thread_safety/fixtures/classes.rb b/spec/ruby/optional/thread_safety/fixtures/classes.rb new file mode 100644 index 0000000000..4f0ad030e5 --- /dev/null +++ b/spec/ruby/optional/thread_safety/fixtures/classes.rb @@ -0,0 +1,39 @@ +module ThreadSafetySpecs + # Returns the number of processors, rounded up so it's always a multiple of 2 + def self.processors + require 'etc' + n = Etc.nprocessors + raise "expected at least 1 processor" if n < 1 + n += 1 if n.odd? # ensure it's a multiple of 2 + n + end + + class Counter + def initialize + @value = 0 + @mutex = Mutex.new + end + + def get + @mutex.synchronize { @value } + end + + def increment + @mutex.synchronize do + @value += 1 + end + end + end + + class Barrier + def initialize(parties) + @parties = parties + @counter = Counter.new + end + + def wait + @counter.increment + Thread.pass until @counter.get == @parties + end + end +end diff --git a/spec/ruby/optional/thread_safety/hash_spec.rb b/spec/ruby/optional/thread_safety/hash_spec.rb new file mode 100644 index 0000000000..53127fc973 --- /dev/null +++ b/spec/ruby/optional/thread_safety/hash_spec.rb @@ -0,0 +1,210 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Hash thread safety" do + it "supports concurrent #[]=" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = t * n + n.times do |j| + h[base+j] = t + end + } + } + + barrier.wait + threads.each(&:join) + + h.size.should == operations + h.each_pair { |key, value| + (key / n).should == value + } + end + + # can't add a new key into hash during iteration (RuntimeError) on CRuby. + # Yet it's good to test this for implementations that support it. + guard_not -> { PlatformGuard.standard? } do + it "supports concurrent #[]= and #delete and iteration" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier1 = ThreadSafetySpecs::Barrier.new(n_threads + 2) + barrier2 = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier1.wait + base = t * n + n.times do |j| + h[base+j] = t + end + + barrier2.wait + n.times do |j| + # delete only even keys + h.delete(base+j).should == t if (base+j).even? + end + } + } + + read = true + reader = Thread.new { + barrier1.wait + while read + h.each_pair { |k,v| + k.should.is_a?(Integer) + v.should.is_a?(Integer) + (k / n).should == v + } + end + } + + barrier1.wait + barrier2.wait + threads.each(&:join) + read = false + reader.join + + # odd keys are left + h.size.should == operations + h.each_pair { |key, value| + key.should.odd? + (key / n).should == value + } + end + end + + it "supports concurrent #[]= and #[]" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = (t / 2) * n + + if t.even? + n.times do |j| + k = base + j + h[k] = k + end + else + n.times do |j| + k = base + j + Thread.pass until v = h[k] + v.should == k + end + end + } + } + + barrier.wait + threads.each(&:join) + + h.size.should == operations + h.each_pair { |key, value| + key.should == value + } + end + + it "supports concurrent #[]= and #[] with change to #compare_by_identity in the middle" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + base = (t / 2) * n + + if t.even? + n.times do |j| + k = base + j + h[k] = k + end + else + n.times do |j| + k = base + j + Thread.pass until v = h[k] + v.should == k + end + end + } + } + + changer = Thread.new { + Thread.pass until h.size >= operations / 2 + h.should_not.compare_by_identity? + h.compare_by_identity + h.should.compare_by_identity? + } + + barrier.wait + threads.each(&:join) + changer.join + + h.size.should == operations + h.each_pair { |key, value| + key.should == value + } + end + + it "supports repeated concurrent #[]= and #delete and always returns a #size >= 0" do + n_threads = ThreadSafetySpecs.processors + + n = 1_000 + operations = n * n_threads / 2 + + h = {} + barrier = ThreadSafetySpecs::Barrier.new(n_threads + 1) + deleted = ThreadSafetySpecs::Counter.new + + threads = n_threads.times.map { |t| + Thread.new { + barrier.wait + key = t / 2 + + if t.even? + n.times { + Thread.pass until h.delete(key) + h.size.should >= 0 + deleted.increment + } + else + n.times { + h[key] = key + Thread.pass while h.key?(key) + } + end + } + } + + barrier.wait + threads.each(&:join) + + deleted.get.should == operations + h.size.should == 0 + h.should.empty? + end +end |
