diff options
Diffstat (limited to 'spec/ruby/optional')
107 files changed, 16117 insertions, 0 deletions
diff --git a/spec/ruby/optional/capi/README b/spec/ruby/optional/capi/README new file mode 100644 index 0000000000..069ca3c106 --- /dev/null +++ b/spec/ruby/optional/capi/README @@ -0,0 +1,13 @@ +C-API Specs + +These specs test the C-API from Ruby. The following are conventions for the +specs: + +1. Put specs for functions related to a Ruby class in a file named according + to the class. For example, for rb_ary_new function, put the specs in + optional/capi/array_spec.rb +2. Put the C file containing the C functions for array_spec.rb in + optional/capi/ext/array_spec.c +3. Name the C extension class 'CApiArraySpecs'. +4. Name the C functions 'array_spec_rb_ary_new'. +5. Attach the C function to the class using the name 'rb_ary_new' diff --git a/spec/ruby/optional/capi/array_spec.rb b/spec/ruby/optional/capi/array_spec.rb new file mode 100644 index 0000000000..8e90980c6a --- /dev/null +++ b/spec/ruby/optional/capi/array_spec.rb @@ -0,0 +1,497 @@ +require_relative 'spec_helper' + +load_extension("array") + +describe :rb_ary_new2, shared: true do + it "returns an empty array" do + @s.send(@method, 5).should == [] + end + + it "raises an ArgumentError when the given argument is negative" do + -> { @s.send(@method, -1) }.should raise_error(ArgumentError) + end +end + +describe "C-API Array function" do + before :each do + @s = CApiArraySpecs.new + end + + describe "rb_Array" do + it "returns obj if it is an array" do + arr = @s.rb_Array([1,2]) + arr.should == [1, 2] + end + + it "tries to convert obj to an array" do + arr = @s.rb_Array({"bar" => "foo"}) + arr.should == [["bar", "foo"]] + end + + it "returns obj wrapped in an array if it cannot be converted to an array" do + arr = @s.rb_Array("a") + arr.should == ["a"] + end + end + + describe "rb_ary_new" do + it "returns an empty array" do + @s.rb_ary_new.should == [] + end + end + + describe "rb_ary_new2" do + it_behaves_like :rb_ary_new2, :rb_ary_new2 + end + + describe "rb_ary_new_capa" do + it_behaves_like :rb_ary_new2, :rb_ary_new_capa + end + + describe "rb_ary_new3" do + it "returns an array with the passed cardinality and varargs" do + @s.rb_ary_new3(1,2,3).should == [1,2,3] + end + end + + describe "rb_ary_new_from_args" do + it "returns an array with the passed cardinality and varargs" do + @s.rb_ary_new_from_args(1,2,3).should == [1,2,3] + end + end + + describe "rb_ary_new4" do + it "returns an array with the passed values" do + @s.rb_ary_new4(1,2,3).should == [1,2,3] + end + end + + describe "rb_ary_new_from_values" do + it "returns an array with the passed values" do + @s.rb_ary_new_from_values(1,2,3).should == [1,2,3] + end + end + + describe "rb_ary_push" do + it "adds an element to the array" do + @s.rb_ary_push([], 4).should == [4] + end + end + + describe "rb_ary_cat" do + it "pushes the given objects onto the end of the array" do + @s.rb_ary_cat([1, 2], 3, 4).should == [1, 2, 3, 4] + end + + it "raises a FrozenError if the array is frozen" do + -> { @s.rb_ary_cat([].freeze, 1) }.should raise_error(FrozenError) + end + end + + describe "rb_ary_pop" do + it "removes and returns the last element in the array" do + a = [1,2,3] + @s.rb_ary_pop(a).should == 3 + a.should == [1,2] + end + end + + describe "rb_ary_join" do + it "joins elements of an array with a string" do + a = [1,2,3] + b = "," + @s.rb_ary_join(a,b).should == "1,2,3" + end + end + + describe "rb_ary_to_s" do + it "creates an Array literal representation as a String" do + @s.rb_ary_to_s([1,2,3]).should == "[1, 2, 3]" + @s.rb_ary_to_s([]).should == "[]" + end + end + + describe "rb_ary_reverse" do + it "reverses the order of elements in the array" do + a = [1,2,3] + @s.rb_ary_reverse(a) + a.should == [3,2,1] + end + + it "returns the original array" do + a = [1,2,3] + @s.rb_ary_reverse(a).should equal(a) + end + end + + describe "rb_ary_rotate" do + it "rotates the array so that the element at the specified position comes first" do + @s.rb_ary_rotate([1, 2, 3, 4], 2).should == [3, 4, 1, 2] + @s.rb_ary_rotate([1, 2, 3, 4], -3).should == [2, 3, 4, 1] + end + + it "raises a FrozenError if the array is frozen" do + -> { @s.rb_ary_rotate([].freeze, 1) }.should raise_error(FrozenError) + end + end + + describe "rb_ary_entry" do + it "returns nil when passed an empty array" do + @s.rb_ary_entry([], 0).should == nil + end + + it "returns elements from the end when passed a negative index" do + @s.rb_ary_entry([1, 2, 3], -1).should == 3 + @s.rb_ary_entry([1, 2, 3], -2).should == 2 + end + + it "returns nil if the index is out of range" do + @s.rb_ary_entry([1, 2, 3], 3).should == nil + @s.rb_ary_entry([1, 2, 3], -10).should == nil + end + end + + describe "rb_ary_clear" do + it "removes all elements from the array" do + @s.rb_ary_clear([]).should == [] + @s.rb_ary_clear([1, 2, 3]).should == [] + end + end + + describe "rb_ary_dup" do + it "duplicates the array" do + @s.rb_ary_dup([]).should == [] + + a = [1, 2, 3] + b = @s.rb_ary_dup(a) + + b.should == a + b.should_not equal(a) + end + end + + describe "rb_ary_unshift" do + it "prepends the element to the array" do + a = [1, 2, 3] + @s.rb_ary_unshift(a, "a").should == ["a", 1, 2, 3] + a.should == ['a', 1, 2, 3] + end + end + + describe "rb_ary_shift" do + it "removes and returns the first element" do + a = [5, 1, 1, 5, 4] + @s.rb_ary_shift(a).should == 5 + a.should == [1, 1, 5, 4] + end + + it "returns nil when the array is empty" do + @s.rb_ary_shift([]).should == nil + end + end + + describe "rb_ary_sort" do + it "returns a new sorted array" do + a = [2, 1, 3] + @s.rb_ary_sort(a).should == [1, 2, 3] + a.should == [2, 1, 3] + end + end + + describe "rb_ary_sort_bang" do + it "sorts the given array" do + a = [2, 1, 3] + @s.rb_ary_sort_bang(a).should == [1, 2, 3] + a.should == [1, 2, 3] + end + end + + describe "rb_ary_store" do + it "overwrites the element at the given position" do + a = [1, 2, 3] + @s.rb_ary_store(a, 1, 5) + a.should == [1, 5, 3] + end + + it "writes to elements offset from the end if passed a negative index" do + a = [1, 2, 3] + @s.rb_ary_store(a, -1, 5) + a.should == [1, 2, 5] + end + + it "raises an IndexError if the negative index is greater than the length" do + a = [1, 2, 3] + -> { @s.rb_ary_store(a, -10, 5) }.should raise_error(IndexError) + end + + it "enlarges the array as needed" do + a = [] + @s.rb_ary_store(a, 2, 7) + a.should == [nil, nil, 7] + end + + it "raises a FrozenError if the array is frozen" do + a = [1, 2, 3].freeze + -> { @s.rb_ary_store(a, 1, 5) }.should raise_error(FrozenError) + end + end + + describe "rb_ary_concat" do + it "concats two arrays" do + a = [5, 1, 1, 5, 4] + b = [2, 3] + @s.rb_ary_concat(a, b).should == [5, 1, 1, 5, 4, 2, 3] + end + end + + describe "rb_ary_plus" do + it "adds two arrays together" do + @s.rb_ary_plus([10], [20]).should == [10, 20] + end + end + + describe "RARRAY_PTR" do + it "returns a pointer to a C array of the array's elements" do + a = [1, 2, 3] + b = [] + @s.RARRAY_PTR_iterate(a) do |e| + b << e + end + a.should == b + end + + it "allows assigning to the elements of the C array" do + a = [1, 2, 3] + @s.RARRAY_PTR_assign(a, :set) + a.should == [:set, :set, :set] + end + + it "allows memcpying between arrays" do + a = [1, 2, 3] + b = [0, 0, 0] + @s.RARRAY_PTR_memcpy(a, b) + b.should == [1, 2, 3] + a.should == [1, 2, 3] # check a was not modified + end + end + + describe "RARRAY_LEN" do + it "returns the size of the array" do + @s.RARRAY_LEN([1, 2, 3]).should == 3 + end + end + + describe "RARRAY_AREF" do + # This macro does NOT do any bounds checking! + it "returns an element from the array" do + @s.RARRAY_AREF([1, 2, 3], 1).should == 2 + end + end + + describe "RARRAY_ASET" do + # This macro does NOT do any bounds checking! + it "writes an element in the array" do + ary = [1, 2, 3] + @s.RARRAY_ASET(ary, 0, 0) + @s.RARRAY_ASET(ary, 2, 42) + ary.should == [0, 2, 42] + end + end + + describe "rb_assoc_new" do + it "returns an array containing the two elements" do + @s.rb_assoc_new(1, 2).should == [1, 2] + @s.rb_assoc_new(:h, [:a, :b]).should == [:h, [:a, :b]] + end + end + + describe "rb_ary_includes" do + it "returns true if the array includes the element" do + @s.rb_ary_includes([1, 2, 3], 2).should be_true + end + + it "returns false if the array does not include the element" do + @s.rb_ary_includes([1, 2, 3], 4).should be_false + end + end + + describe "rb_ary_aref" do + it "returns the element at the given index" do + @s.rb_ary_aref([:me, :you], 0).should == :me + @s.rb_ary_aref([:me, :you], 1).should == :you + end + + it "returns nil for an out of range index" do + @s.rb_ary_aref([1, 2, 3], 6).should be_nil + end + + it "returns a new array where the first argument is the index and the second is the length" do + @s.rb_ary_aref([1, 2, 3, 4], 0, 2).should == [1, 2] + @s.rb_ary_aref([1, 2, 3, 4], -4, 3).should == [1, 2, 3] + end + + it "accepts a range" do + @s.rb_ary_aref([1, 2, 3, 4], 0..-1).should == [1, 2, 3, 4] + end + + it "returns nil when the start of a range is out of bounds" do + @s.rb_ary_aref([1, 2, 3, 4], 6..10).should be_nil + end + + it "returns an empty array when the start of a range equals the last element" do + @s.rb_ary_aref([1, 2, 3, 4], 4..10).should == [] + end + end + + describe "rb_block_call" do + it "calls an callback function as a block passed to an method" do + s = [1,2,3,4] + s2 = @s.rb_block_call(s) + + s2.should == s + + # 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} + + @s.rb_block_call_each_pair(h).sort.should == [1,2] + end + + 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 + + @s.rb_block_call_then_yield(o) { |x| s2 << x } + + s2.should == [1,2,3,4] + end + end + + describe "rb_ary_delete" do + it "removes an element from an array and returns it" do + ary = [1, 2, 3, 4] + @s.rb_ary_delete(ary, 3).should == 3 + ary.should == [1, 2, 4] + end + + it "returns nil if the element is not in the array" do + ary = [1, 2, 3, 4] + @s.rb_ary_delete(ary, 5).should be_nil + ary.should == [1, 2, 3, 4] + end + end + + describe "rb_mem_clear" do + it "sets elements of a C array to nil" do + @s.rb_mem_clear(1).should == nil + end + end + + describe "rb_ary_freeze" do + it "freezes the object exactly like Kernel#freeze" do + ary = [1,2] + @s.rb_ary_freeze(ary) + ary.frozen?.should be_true + end + end + + describe "rb_ary_delete_at" do + before :each do + @array = [1, 2, 3, 4] + end + + it "removes an element from an array at a positive index" do + @s.rb_ary_delete_at(@array, 2).should == 3 + @array.should == [1, 2, 4] + end + + it "removes an element from an array at a negative index" do + @s.rb_ary_delete_at(@array, -3).should == 2 + @array.should == [1, 3, 4] + end + + it "returns nil if the index is out of bounds" do + @s.rb_ary_delete_at(@array, 4).should be_nil + @array.should == [1, 2, 3, 4] + end + + it "returns nil if the negative index is out of bounds" do + @s.rb_ary_delete_at(@array, -5).should be_nil + @array.should == [1, 2, 3, 4] + end + end + + describe "rb_ary_to_ary" do + + describe "with an array" do + + it "returns the given array" do + array = [1, 2, 3] + @s.rb_ary_to_ary(array).should equal(array) + end + + end + + describe "with an object that responds to to_ary" do + + it "calls to_ary on the object" do + obj = mock('to_ary') + obj.stub!(:to_ary).and_return([1, 2, 3]) + @s.rb_ary_to_ary(obj).should == [1, 2, 3] + end + + end + + describe "with an object that responds to to_a" do + + it "returns the original object in an array" do + obj = mock('to_a') + obj.stub!(:to_a).and_return([1, 2, 3]) + @s.rb_ary_to_ary(obj).should == [obj] + end + + end + + describe "with an object that doesn't respond to to_ary" do + + it "returns the original object in an array" do + obj = mock('no_to_ary') + @s.rb_ary_to_ary(obj).should == [obj] + end + + end + + end + + describe "rb_ary_subseq" do + it "returns a subsequence of the given array" do + @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, 3).should == [2, 3, 4] + end + + it "returns an empty array for a subsequence of 0 elements" do + @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, 0).should == [] + end + + it "returns nil if the begin index is out of bound" do + @s.rb_ary_subseq([1, 2, 3, 4, 5], 6, 3).should be_nil + end + + it "returns the existing subsequence of the length is out of bounds" do + @s.rb_ary_subseq([1, 2, 3, 4, 5], 4, 3).should == [5] + end + + it "returns nil if the size is negative" do + @s.rb_ary_subseq([1, 2, 3, 4, 5], 1, -1).should be_nil + end + end +end diff --git a/spec/ruby/optional/capi/basic_object_spec.rb b/spec/ruby/optional/capi/basic_object_spec.rb new file mode 100644 index 0000000000..2922a421da --- /dev/null +++ b/spec/ruby/optional/capi/basic_object_spec.rb @@ -0,0 +1,24 @@ +require_relative 'spec_helper' + +load_extension("basic_object") + +describe "C-API basic object" do + before :each do + @s = CApiBasicObjectSpecs.new + end + + describe "RBASIC_CLASS" do + it "returns the class of an object" do + c = Class.new + o = c.new + @s.RBASIC_CLASS(o).should == c + end + + it "returns the singleton class" do + o = Object.new + @s.RBASIC_CLASS(o).should == Object + singleton_class = o.singleton_class + @s.RBASIC_CLASS(o).should == singleton_class + end + end +end diff --git a/spec/ruby/optional/capi/bignum_spec.rb b/spec/ruby/optional/capi/bignum_spec.rb new file mode 100644 index 0000000000..cde929af28 --- /dev/null +++ b/spec/ruby/optional/capi/bignum_spec.rb @@ -0,0 +1,224 @@ +require_relative 'spec_helper' + +load_extension("bignum") + +def ensure_bignum(n) + raise "Bignum#coerce returned Fixnum" if fixnum_min <= n && n <= fixnum_max + n +end + +full_range_longs = (fixnum_max == 2**(0.size * 8 - 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 + end + + describe "rb_big2long" do + unless full_range_longs + it "converts a Bignum" do + @s.rb_big2long(@max_long).should == @max_long + @s.rb_big2long(@min_long).should == @min_long + end + end + + it "raises RangeError if passed Bignum overflow long" do + -> { @s.rb_big2long(ensure_bignum(@max_long + 1)) }.should raise_error(RangeError) + -> { @s.rb_big2long(ensure_bignum(@min_long - 1)) }.should raise_error(RangeError) + end + end + + describe "rb_big2ll" do + unless full_range_longs + it "converts a Bignum" do + @s.rb_big2ll(@max_long).should == @max_long + @s.rb_big2ll(@min_long).should == @min_long + end + end + + it "raises RangeError if passed Bignum overflow long" do + -> { @s.rb_big2ll(ensure_bignum(@max_long << 40)) }.should raise_error(RangeError) + -> { @s.rb_big2ll(ensure_bignum(@min_long << 40)) }.should raise_error(RangeError) + end + end + + describe "rb_big2ulong" do + it "converts a Bignum" do + @s.rb_big2ulong(@max_ulong).should == @max_ulong + end + + unless full_range_longs + it "wraps around if passed a negative bignum" do + @s.rb_big2ulong(ensure_bignum(@min_long + 1)).should == -(@min_long - 1) + @s.rb_big2ulong(ensure_bignum(@min_long)).should == -(@min_long) + end + end + + it "raises RangeError if passed Bignum overflow long" do + -> { @s.rb_big2ulong(ensure_bignum(@max_ulong + 1)) }.should raise_error(RangeError) + -> { @s.rb_big2ulong(ensure_bignum(@min_long - 1)) }.should raise_error(RangeError) + end + end + + describe "rb_big2dbl" do + it "converts a Bignum to a double value" do + @s.rb_big2dbl(ensure_bignum(Float::MAX.to_i)).eql?(Float::MAX).should == true + end + + it "returns Infinity if the number is too big for a double" do + huge_bignum = ensure_bignum(Float::MAX.to_i * 2) + @s.rb_big2dbl(huge_bignum).should == infinity_value + end + + it "returns -Infinity if the number is negative and too big for a double" do + huge_bignum = -ensure_bignum(Float::MAX.to_i * 2) + @s.rb_big2dbl(huge_bignum).should == -infinity_value + end + end + + describe "rb_big2str" do + + it "converts a Bignum to a string with base 10" do + @s.rb_big2str(ensure_bignum(2**70), 10).eql?("1180591620717411303424").should == true + end + + it "converts a Bignum to a string with a different base" do + @s.rb_big2str(ensure_bignum(2**70), 16).eql?("400000000000000000").should == true + end + end + + describe "RBIGNUM_SIGN" do + it "returns 1 for a positive Bignum" do + @s.RBIGNUM_SIGN(bignum_value(1)).should == 1 + end + + it "returns 0 for a negative Bignum" do + @s.RBIGNUM_SIGN(-bignum_value(1)).should == 0 + end + end + + describe "rb_big_cmp" do + it "compares a Bignum with a Bignum" do + @s.rb_big_cmp(bignum_value, bignum_value(1)).should == -1 + end + + it "compares a Bignum with a Fixnum" do + @s.rb_big_cmp(bignum_value, 5).should == 1 + end + end + + describe "rb_big_pack" do + it "packs a Bignum into an unsigned long" do + val = @s.rb_big_pack(@max_ulong) + val.should == @max_ulong + end + + platform_is wordsize: 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 + val = @s.rb_big_pack_array(@max_ulong, 2) + val[0].should == @max_ulong + val[1].should == 0 + end + + it "packs a 72-bit positive Bignum into 2 unsigned longs" do + num = 2 ** 71 + val = @s.rb_big_pack_length(num) + val.should == 2 + end + + it "packs a 72-bit positive Bignum into correct 2 longs" do + num = 2 ** 71 + 1 + val = @s.rb_big_pack_array(num, 2) + val[0].should == 1; + val[1].should == 0x80; + end + + it "packs a 72-bit negative Bignum into correct 2 longs" do + num = -(2 ** 71 + 1) + val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num)) + val[0].should == @max_ulong; + val[1].should == @max_ulong - 0x80; + end + + it "packs lower order bytes into least significant bytes of longs for positive bignum" do + num = 0 + 32.times { |i| num += i << (i * 8) } + val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num)) + val.size.should == 4 + 32.times do |i| + a_long = val[i/8] + a_byte = (a_long >> ((i % 8) * 8)) & 0xff + a_byte.should == i + end + end + + it "packs lower order bytes into least significant bytes of longs for negative bignum" do + num = 0 + 32.times { |i| num += i << (i * 8) } + num = -num + val = @s.rb_big_pack_array(num, @s.rb_big_pack_length(num)) + val.size.should == 4 + expected_bytes = [0x00, 0xff, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, + 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0 ] + 32.times do |i| + a_long = val[i/8] + a_byte = (a_long >> ((i % 8) * 8)) & 0xff + a_byte.should == expected_bytes[i] + end + end + end + end + + describe "rb_dbl2big" do + it "returns a Fixnum for a Fixnum input value" do + val = @s.rb_dbl2big(2) + + val.kind_of?(Integer).should == true + val.should == 2 + end + + it "returns a Fixnum for a Float input value" do + val = @s.rb_dbl2big(2.5) + + val.kind_of?(Integer).should == true + val.should == 2 + end + + it "returns a Bignum for a large enough Float input value" do + input = 219238102380912830988.5 # chosen by fair dice roll + val = @s.rb_dbl2big(input) + + val.kind_of?(Integer).should == true + + # This value is based on the output of a simple C extension that uses + # rb_dbl2big() to convert the above input value to a Bignum. + val.should == 219238102380912836608 + end + + it "raises FloatDomainError for Infinity values" do + inf = 1.0 / 0 + + -> { @s.rb_dbl2big(inf) }.should raise_error(FloatDomainError) + end + + it "raises FloatDomainError for NaN values" do + nan = 0.0 / 0 + + -> { @s.rb_dbl2big(nan) }.should raise_error(FloatDomainError) + end + end +end diff --git a/spec/ruby/optional/capi/binding_spec.rb b/spec/ruby/optional/capi/binding_spec.rb new file mode 100644 index 0000000000..966d650c46 --- /dev/null +++ b/spec/ruby/optional/capi/binding_spec.rb @@ -0,0 +1,19 @@ +require_relative 'spec_helper' + +load_extension("binding") + +describe "CApiBindingSpecs" do + before :each do + @b = CApiBindingSpecs.new + end + + describe "Kernel#binding" 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 + end +end diff --git a/spec/ruby/optional/capi/boolean_spec.rb b/spec/ruby/optional/capi/boolean_spec.rb new file mode 100644 index 0000000000..351419cbec --- /dev/null +++ b/spec/ruby/optional/capi/boolean_spec.rb @@ -0,0 +1,33 @@ +require_relative 'spec_helper' + +load_extension("boolean") + +describe "CApiBooleanSpecs" do + before :each do + @b = CApiBooleanSpecs.new + end + + describe "a true value from Ruby" do + it "is truthy in C" do + @b.is_true(true).should == 1 + end + end + + describe "a true value from Qtrue" do + it "is truthy in C" do + @b.is_true(@b.q_true).should == 1 + end + end + + describe "a false value from Ruby" do + it "is falsey in C" do + @b.is_true(false).should == 2 + end + end + + describe "a false value from Qfalse" do + it "is falsey in C" do + @b.is_true(@b.q_false).should == 2 + end + end +end diff --git a/spec/ruby/optional/capi/class_spec.rb b/spec/ruby/optional/capi/class_spec.rb new file mode 100644 index 0000000000..a2d8b3e38a --- /dev/null +++ b/spec/ruby/optional/capi/class_spec.rb @@ -0,0 +1,451 @@ +require_relative 'spec_helper' +require_relative 'fixtures/class' +require_relative '../../core/module/fixtures/classes' + +load_extension("class") +compile_extension("class_under_autoload") +compile_extension("class_id_under_autoload") + +autoload :ClassUnderAutoload, "#{object_path}/class_under_autoload_spec" +autoload :ClassIdUnderAutoload, "#{object_path}/class_id_under_autoload_spec" + +describe :rb_path_to_class, shared: true do + it "returns a class or module from a scoped String" do + @s.send(@method, "CApiClassSpecs::A::B").should equal(CApiClassSpecs::A::B) + @s.send(@method, "CApiClassSpecs::A::M").should equal(CApiClassSpecs::A::M) + end + + it "resolves autoload constants" do + @s.send(@method, "CApiClassSpecs::A::D").name.should == "CApiClassSpecs::A::D" + end + + it "raises an ArgumentError if a constant in the path does not exist" do + -> { @s.send(@method, "CApiClassSpecs::NotDefined::B") }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if the final constant does not exist" do + -> { @s.send(@method, "CApiClassSpecs::NotDefined") }.should raise_error(ArgumentError) + end + + it "raises a TypeError if the constant is not a class or module" do + -> { + @s.send(@method, "CApiClassSpecs::A::C") + }.should raise_error(TypeError, 'CApiClassSpecs::A::C does not refer to class/module') + end + + it "raises an ArgumentError even if a constant in the path exists on toplevel" do + -> { @s.send(@method, "CApiClassSpecs::Object") }.should raise_error(ArgumentError) + end +end + +describe "C-API Class function" do + before :each do + @s = CApiClassSpecs.new + end + + describe "rb_class_instance_methods" do + it "returns the public and protected methods of self and its ancestors" do + methods = @s.rb_class_instance_methods(ModuleSpecs::Basic) + methods.should include(:protected_module, :public_module) + + methods = @s.rb_class_instance_methods(ModuleSpecs::Basic, true) + methods.should include(:protected_module, :public_module) + end + + it "when passed false as a parameter, returns the instance methods of the class" do + methods = @s.rb_class_instance_methods(ModuleSpecs::Child, false) + methods.should include(:protected_child, :public_child) + end + end + + describe "rb_class_public_instance_methods" do + it "returns a list of public methods in module and its ancestors" do + methods = @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild) + methods.should include(:public_3) + methods.should include(:public_2) + methods.should include(:public_1) + + methods = @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, true) + methods.should include(:public_3) + methods.should include(:public_2) + methods.should include(:public_1) + end + + it "when passed false as a parameter, should return only methods defined in that module" do + @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, false).should == [:public_1] + end + end + + describe "rb_class_protected_instance_methods" do + it "returns a list of protected methods in module and its ancestors" do + methods = @s.rb_class_protected_instance_methods(ModuleSpecs::CountsChild) + methods.should include(:protected_3) + methods.should include(:protected_2) + methods.should include(:protected_1) + + methods = @s.rb_class_protected_instance_methods(ModuleSpecs::CountsChild, true) + methods.should include(:protected_3) + methods.should include(:protected_2) + methods.should include(:protected_1) + end + + it "when passed false as a parameter, should return only methods defined in that module" do + @s.rb_class_public_instance_methods(ModuleSpecs::CountsChild, false).should == [:public_1] + end + end + + describe "rb_class_private_instance_methods" do + it "returns a list of private methods in module and its ancestors" do + @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild).should == ModuleSpecs::CountsChild.private_instance_methods + @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild, true).should == ModuleSpecs::CountsChild.private_instance_methods + end + + it "when passed false as a parameter, should return only methods defined in that module" do + methods = @s.rb_class_private_instance_methods(ModuleSpecs::CountsChild, false) + methods.should == [:private_1] + end + end + + describe "rb_class_new_instance" do + it "allocates and initializes a new object" do + o = @s.rb_class_new_instance(0, nil, CApiClassSpecs::Alloc) + o.class.should == CApiClassSpecs::Alloc + o.initialized.should be_true + end + + it "passes arguments to the #initialize method" do + o = @s.rb_class_new_instance(2, [:one, :two], CApiClassSpecs::Alloc) + o.arguments.should == [:one, :two] + end + end + + describe "rb_include_module" do + it "includes a module into a class" do + c = Class.new + o = c.new + -> { o.included? }.should raise_error(NameError) + @s.rb_include_module(c, CApiClassSpecs::M) + o.included?.should be_true + end + end + + describe "rb_define_attr" do + before :each do + @a = CApiClassSpecs::Attr.new + end + + it "defines an attr_reader when passed true, false" do + @s.rb_define_attr(CApiClassSpecs::Attr, :foo, true, false) + @a.foo.should == 1 + -> { @a.foo = 5 }.should raise_error(NameError) + end + + it "defines an attr_writer when passed false, true" do + @s.rb_define_attr(CApiClassSpecs::Attr, :bar, false, true) + -> { @a.bar }.should raise_error(NameError) + @a.bar = 5 + @a.instance_variable_get(:@bar).should == 5 + end + + it "defines an attr_accessor when passed true, true" do + @s.rb_define_attr(CApiClassSpecs::Attr, :baz, true, true) + @a.baz.should == 3 + @a.baz = 6 + @a.baz.should == 6 + end + end + + describe "rb_call_super" do + it "calls the method in the superclass" do + @s.define_call_super_method CApiClassSpecs::Sub, "call_super_method" + obj = CApiClassSpecs::Sub.new + obj.call_super_method.should == :super_method + end + + it "calls the method in the superclass with the correct self" do + @s.define_call_super_method CApiClassSpecs::SubSelf, "call_super_method" + obj = CApiClassSpecs::SubSelf.new + obj.call_super_method.should equal obj + end + + it "calls the method in the superclass through two native levels" do + @s.define_call_super_method CApiClassSpecs::Sub, "call_super_method" + @s.define_call_super_method CApiClassSpecs::SubSub, "call_super_method" + obj = CApiClassSpecs::SubSub.new + obj.call_super_method.should == :super_method + end + end + + describe "rb_define_method" do + it "defines a method taking variable arguments as a C array if the argument count is -1" do + @s.rb_method_varargs_1(1, 3, 7, 4).should == [1, 3, 7, 4] + end + + it "defines a method taking variable arguments as a Ruby array if the argument count is -2" do + @s.rb_method_varargs_2(1, 3, 7, 4).should == [1, 3, 7, 4] + end + end + + describe "rb_class2name" do + it "returns the class name" do + @s.rb_class2name(CApiClassSpecs).should == "CApiClassSpecs" + end + + it "returns a string for an anonymous class" do + @s.rb_class2name(Class.new).should be_kind_of(String) + end + end + + describe "rb_class_path" do + it "returns a String of a class path with no scope modifiers" do + @s.rb_class_path(Array).should == "Array" + end + + it "returns a String of a class path with scope modifiers" do + @s.rb_class_path(File::Stat).should == "File::Stat" + end + end + + describe "rb_class_name" do + it "returns the class name" do + @s.rb_class_name(CApiClassSpecs).should == "CApiClassSpecs" + end + + it "returns a string for an anonymous class" do + @s.rb_class_name(Class.new).should be_kind_of(String) + end + end + + describe "rb_path2class" do + it_behaves_like :rb_path_to_class, :rb_path2class + end + + describe "rb_path_to_class" do + it_behaves_like :rb_path_to_class, :rb_path_to_class + end + + describe "rb_cvar_defined" do + it "returns false when the class variable is not defined" do + @s.rb_cvar_defined(CApiClassSpecs::CVars, "@@nocvar").should be_false + end + + it "returns true when the class variable is defined" do + @s.rb_cvar_defined(CApiClassSpecs::CVars, "@@cvar").should be_true + end + + it "returns true if the class instance variable is defined" do + @s.rb_cvar_defined(CApiClassSpecs::CVars, "@c_ivar").should be_true + end + end + + describe "rb_cv_set" do + it "sets a class variable" do + o = CApiClassSpecs::CVars.new + o.new_cv.should be_nil + @s.rb_cv_set(CApiClassSpecs::CVars, "@@new_cv", 1) + o.new_cv.should == 1 + CApiClassSpecs::CVars.remove_class_variable :@@new_cv + end + end + + describe "rb_cv_get" do + it "returns the value of the class variable" do + @s.rb_cvar_get(CApiClassSpecs::CVars, "@@cvar").should == :cvar + end + + it "raises a NameError if the class variable is not defined" do + -> { + @s.rb_cv_get(CApiClassSpecs::CVars, "@@no_cvar") + }.should raise_error(NameError, /class variable @@no_cvar/) + end + end + + describe "rb_cvar_set" do + it "sets a class variable" do + o = CApiClassSpecs::CVars.new + o.new_cvar.should be_nil + @s.rb_cvar_set(CApiClassSpecs::CVars, "@@new_cvar", 1) + o.new_cvar.should == 1 + CApiClassSpecs::CVars.remove_class_variable :@@new_cvar + end + + end + + describe "rb_define_class" do + before :each do + @cls = @s.rb_define_class("ClassSpecDefineClass", CApiClassSpecs::Super) + end + + it "creates a subclass of the superclass" do + @cls.should be_kind_of(Class) + ClassSpecDefineClass.should equal(@cls) + @cls.superclass.should == CApiClassSpecs::Super + end + + it "sets the class name" do + @cls.name.should == "ClassSpecDefineClass" + end + + it "calls #inherited on the superclass" do + CApiClassSpecs::Super.should_receive(:inherited) + @s.rb_define_class("ClassSpecDefineClass2", CApiClassSpecs::Super) + Object.send(:remove_const, :ClassSpecDefineClass2) + end + + it "raises a TypeError when given a non class object to superclass" do + -> { + @s.rb_define_class("ClassSpecDefineClass3", Module.new) + }.should raise_error(TypeError) + end + + it "raises a TypeError when given a mismatched class to superclass" do + -> { + @s.rb_define_class("ClassSpecDefineClass", Object) + }.should raise_error(TypeError) + end + + it "raises a ArgumentError when given NULL as superclass" do + -> { + @s.rb_define_class("ClassSpecDefineClass4", nil) + }.should raise_error(ArgumentError) + end + end + + describe "rb_define_class_under" do + it "creates a subclass of the superclass contained in a module" do + cls = @s.rb_define_class_under(CApiClassSpecs, + "ClassUnder1", + CApiClassSpecs::Super) + cls.should be_kind_of(Class) + CApiClassSpecs::Super.should be_ancestor_of(CApiClassSpecs::ClassUnder1) + end + + it "sets the class name" do + cls = @s.rb_define_class_under(CApiClassSpecs, "ClassUnder3", Object) + cls.name.should == "CApiClassSpecs::ClassUnder3" + end + + it "calls #inherited on the superclass" do + CApiClassSpecs::Super.should_receive(:inherited) + @s.rb_define_class_under(CApiClassSpecs, "ClassUnder4", CApiClassSpecs::Super) + CApiClassSpecs.send(:remove_const, :ClassUnder4) + end + + it "raises a TypeError when given a non class object to superclass" do + -> { @s.rb_define_class_under(CApiClassSpecs, + "ClassUnder5", + Module.new) + }.should raise_error(TypeError) + end + + it "raises a TypeError when given a mismatched class to superclass" do + CApiClassSpecs::ClassUnder6 = Class.new(CApiClassSpecs::Super) + -> { @s.rb_define_class_under(CApiClassSpecs, + "ClassUnder6", + Class.new) + }.should raise_error(TypeError) + end + + it "defines a class for an existing Autoload" do + ClassUnderAutoload.name.should == "ClassUnderAutoload" + end + + it "raises a TypeError if class is defined and its superclass mismatches the given one" do + -> { @s.rb_define_class_under(CApiClassSpecs, "Sub", Object) }.should raise_error(TypeError) + end + end + + describe "rb_define_class_id_under" do + it "creates a subclass of the superclass contained in a module" do + cls = @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder1, CApiClassSpecs::Super) + cls.should be_kind_of(Class) + CApiClassSpecs::Super.should be_ancestor_of(CApiClassSpecs::ClassIdUnder1) + end + + it "sets the class name" do + cls = @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder3, Object) + cls.name.should == "CApiClassSpecs::ClassIdUnder3" + end + + it "calls #inherited on the superclass" do + CApiClassSpecs::Super.should_receive(:inherited) + @s.rb_define_class_id_under(CApiClassSpecs, :ClassIdUnder4, CApiClassSpecs::Super) + CApiClassSpecs.send(:remove_const, :ClassIdUnder4) + end + + it "defines a class for an existing Autoload" do + ClassIdUnderAutoload.name.should == "ClassIdUnderAutoload" + end + + it "raises a TypeError if class is defined and its superclass mismatches the given one" do + -> { @s.rb_define_class_id_under(CApiClassSpecs, :Sub, Object) }.should raise_error(TypeError) + end + end + + describe "rb_define_class_variable" do + it "sets a class variable" do + o = CApiClassSpecs::CVars.new + o.rbdcv_cvar.should be_nil + @s.rb_define_class_variable(CApiClassSpecs::CVars, "@@rbdcv_cvar", 1) + o.rbdcv_cvar.should == 1 + CApiClassSpecs::CVars.remove_class_variable :@@rbdcv_cvar + end + end + + describe "rb_cvar_get" do + it "returns the value of the class variable" do + @s.rb_cvar_get(CApiClassSpecs::CVars, "@@cvar").should == :cvar + end + + it "raises a NameError if the class variable is not defined" do + -> { + @s.rb_cvar_get(CApiClassSpecs::CVars, "@@no_cvar") + }.should raise_error(NameError, /class variable @@no_cvar/) + end + end + + describe "rb_class_new" do + it "returns a new subclass of the superclass" do + subclass = @s.rb_class_new(CApiClassSpecs::NewClass) + CApiClassSpecs::NewClass.should be_ancestor_of(subclass) + end + + it "raises a TypeError if passed Class as the superclass" do + -> { @s.rb_class_new(Class) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed a singleton class as the superclass" do + metaclass = Object.new.singleton_class + -> { @s.rb_class_new(metaclass) }.should raise_error(TypeError) + end + end + + describe "rb_class_superclass" do + it "returns the superclass of a class" do + cls = @s.rb_class_superclass(CApiClassSpecs::Sub) + cls.should == CApiClassSpecs::Super + end + + it "returns nil if the class has no superclass" do + @s.rb_class_superclass(BasicObject).should be_nil + end + end + + describe "rb_class_real" do + it "returns the class of an object ignoring the singleton class" do + obj = CApiClassSpecs::Sub.new + def obj.some_method() end + + @s.rb_class_real(obj).should == CApiClassSpecs::Sub + end + + it "returns the class of an object ignoring included modules" do + obj = CApiClassSpecs::SubM.new + @s.rb_class_real(obj).should == CApiClassSpecs::SubM + end + + it "returns 0 if passed 0" do + @s.rb_class_real(0).should == 0 + end + end +end diff --git a/spec/ruby/optional/capi/complex_spec.rb b/spec/ruby/optional/capi/complex_spec.rb new file mode 100644 index 0000000000..3d8142d172 --- /dev/null +++ b/spec/ruby/optional/capi/complex_spec.rb @@ -0,0 +1,45 @@ +require_relative 'spec_helper' + +load_extension("complex") + +describe :rb_Complex, shared: true do + it "creates a new Complex with numerator and denominator" do + @r.send(@method, 1, 2).should == Complex(1, 2) + end +end + +describe :rb_complex_new, shared: true do + it "creates a normalized Complex" do + r = @r.send(@method, 10, 4) + r.real.should == 10 + r.imag.should == 4 + end +end + +describe "CApiComplexSpecs" do + before :each do + @r = CApiComplexSpecs.new + end + + describe "rb_Complex" do + it_behaves_like :rb_Complex, :rb_Complex + end + + describe "rb_Complex2" do + it_behaves_like :rb_Complex, :rb_Complex2 + end + + describe "rb_Complex1" do + it "creates a new Complex with real and imaginary of 0" do + @r.rb_Complex1(5).should == Complex(5, 0) + end + end + + describe "rb_complex_new" do + it_behaves_like :rb_complex_new, :rb_complex_new + end + + describe "rb_complex_new2" do + it_behaves_like :rb_complex_new, :rb_complex_new2 + end +end diff --git a/spec/ruby/optional/capi/constants_spec.rb b/spec/ruby/optional/capi/constants_spec.rb new file mode 100644 index 0000000000..172d10a788 --- /dev/null +++ b/spec/ruby/optional/capi/constants_spec.rb @@ -0,0 +1,325 @@ +require_relative 'spec_helper' + +load_extension("constants") + +describe "C-API constant" do + before :each do + @s = CApiConstantsSpecs.new + end + + specify "rb_cArray references the Array class" do + @s.rb_cArray.should == Array + end + + specify "rb_cBasicObject references the BasicObject class" do + @s.rb_cBasicObject.should == BasicObject + end + + specify "rb_cBinding references the Binding class" do + @s.rb_cBinding.should == Binding + end + + specify "rb_cClass references the Class class" do + @s.rb_cClass.should == Class + end + + specify "rb_cComplex references the Complex class" do + @s.rb_cComplex.should == Complex + end + + specify "rb_mComparable references the Comparable module" do + @s.rb_mComparable.should == Comparable + end + + specify "rb_cDir references the Dir class" do + @s.rb_cDir.should == Dir + end + + specify "rb_cEncoding references the Encoding class" do + @s.rb_cEncoding.should == Encoding + end + + specify "rb_mEnumerable references the Enumerable module" do + @s.rb_mEnumerable.should == Enumerable + end + + specify "rb_cEnumerator references the Enumerator class" do + @s.rb_cEnumerator.should == Enumerator + end + + specify "rb_cFalseClass references the FalseClass class" do + @s.rb_cFalseClass.should == FalseClass + end + + specify "rb_cFile references the File class" do + @s.rb_cFile.should == File + end + + specify "rb_mFileTest references the FileTest module" do + @s.rb_mFileTest.should == FileTest + end + + specify "rb_cFloat references the Float class" do + @s.rb_cFloat.should == Float + end + + specify "rb_mGC references the GC module" do + @s.rb_mGC.should == GC + end + + specify "rb_cHash references the Hash class" do + @s.rb_cHash.should == Hash + end + + specify "rb_cInteger references the Integer class" do + @s.rb_cInteger.should == Integer + end + + specify "rb_cIO references the IO class" do + @s.rb_cIO.should == IO + end + + specify "rb_mKernel references the Kernel module" do + @s.rb_mKernel.should == Kernel + end + + specify "rb_mMath references the Math module" do + @s.rb_mMath.should == Math + end + + specify "rb_cMatch references the MatchData class" do + @s.rb_cMatch.should == MatchData + end + + specify "rb_cMethod references the Method class" do + @s.rb_cMethod.should == Method + end + + specify "rb_cModule references the Module class" do + @s.rb_cModule.should == Module + end + + specify "rb_cNilClass references the NilClass class" do + @s.rb_cNilClass.should == NilClass + end + + specify "rb_cNumeric references the Numeric class" do + @s.rb_cNumeric.should == Numeric + end + + specify "rb_cObject references the Object class" do + @s.rb_cObject.should == Object + end + + specify "rb_cProc references the Proc class" do + @s.rb_cProc.should == Proc + end + + specify "rb_mProcess references the Process module" do + @s.rb_mProcess.should == Process + end + + specify "rb_cRandom references the Random class" do + @s.rb_cRandom.should == Random + end + + specify "rb_cRange references the Range class" do + @s.rb_cRange.should == Range + end + + specify "rb_cRational references the Rational class" do + @s.rb_cRational.should == Rational + end + + specify "rb_cRegexp references the Regexp class" do + @s.rb_cRegexp.should == Regexp + end + + specify "rb_cStat references the File::Stat class" do + @s.rb_cStat.should == File::Stat + end + + specify "rb_cString references the String class" do + @s.rb_cString.should == String + end + + specify "rb_cStruct references the Struct class" do + @s.rb_cStruct.should == Struct + end + + specify "rb_cSymbol references the Symbol class" do + @s.rb_cSymbol.should == Symbol + end + + specify "rb_cTime references the Time class" do + @s.rb_cTime.should == Time + end + + specify "rb_cThread references the Thread class" do + @s.rb_cThread.should == Thread + end + + specify "rb_cTrueClass references the TrueClass class" do + @s.rb_cTrueClass.should == TrueClass + end + + specify "rb_cUnboundMethod references the UnboundMethod class" do + @s.rb_cUnboundMethod.should == UnboundMethod + end +end + +describe "C-API exception constant" do + before :each do + @s = CApiConstantsSpecs.new + end + + specify "rb_eArgError references the ArgumentError class" do + @s.rb_eArgError.should == ArgumentError + end + + specify "rb_eEncodingError references the EncodingError class" do + @s.rb_eEncodingError.should == EncodingError + end + + specify "rb_eEncCompatError references the Encoding::CompatibilityError" do + @s.rb_eEncCompatError.should == Encoding::CompatibilityError + end + + specify "rb_eEOFError references the EOFError class" do + @s.rb_eEOFError.should == EOFError + end + + specify "rb_eErrno references the Errno module" do + @s.rb_mErrno.should == Errno + end + + specify "rb_eException references the Exception class" do + @s.rb_eException.should == Exception + end + + specify "rb_eFatal references the fatal class" do + fatal = @s.rb_eFatal + fatal.should be_kind_of(Class) + fatal.should < Exception + end + + specify "rb_eFloatDomainError references the FloatDomainError class" do + @s.rb_eFloatDomainError.should == FloatDomainError + end + + specify "rb_eFrozenError references the FrozenError class" do + @s.rb_eFrozenError.should == FrozenError + end + + specify "rb_eIndexError references the IndexError class" do + @s.rb_eIndexError.should == IndexError + end + + specify "rb_eInterrupt references the Interrupt class" do + @s.rb_eInterrupt.should == Interrupt + end + + specify "rb_eIOError references the IOError class" do + @s.rb_eIOError.should == IOError + end + + specify "rb_eKeyError references the KeyError class" do + @s.rb_eKeyError.should == KeyError + end + + specify "rb_eLoadError references the LoadError class" do + @s.rb_eLoadError.should == LoadError + end + + specify "rb_eLocalJumpError references the LocalJumpError class" do + @s.rb_eLocalJumpError.should == LocalJumpError + end + + specify "rb_eMathDomainError references the Math::DomainError class" do + @s.rb_eMathDomainError.should == Math::DomainError + end + + specify "rb_eNameError references the NameError class" do + @s.rb_eNameError.should == NameError + end + + specify "rb_eNoMemError references the NoMemoryError class" do + @s.rb_eNoMemError.should == NoMemoryError + end + + specify "rb_eNoMethodError references the NoMethodError class" do + @s.rb_eNoMethodError.should == NoMethodError + end + + specify "rb_eNotImpError references the NotImplementedError class" do + @s.rb_eNotImpError.should == NotImplementedError + end + + specify "rb_eRangeError references the RangeError class" do + @s.rb_eRangeError.should == RangeError + end + + specify "rb_eRegexpError references the RegexpError class" do + @s.rb_eRegexpError.should == RegexpError + end + + specify "rb_eRuntimeError references the RuntimeError class" do + @s.rb_eRuntimeError.should == RuntimeError + end + + specify "rb_eScriptError references the ScriptError class" do + @s.rb_eScriptError.should == ScriptError + end + + specify "rb_eSecurityError references the SecurityError class" do + @s.rb_eSecurityError.should == SecurityError + end + + specify "rb_eSignal references the SignalException class" do + @s.rb_eSignal.should == SignalException + end + + specify "rb_eStandardError references the StandardError class" do + @s.rb_eStandardError.should == StandardError + end + + specify "rb_eStopIteration references the StopIteration class" do + @s.rb_eStopIteration.should == StopIteration + end + + specify "rb_eSyntaxError references the SyntaxError class" do + @s.rb_eSyntaxError.should == SyntaxError + end + + specify "rb_eSystemCallError references the SystemCallError class" do + @s.rb_eSystemCallError.should == SystemCallError + end + + specify "rb_eSystemExit references the SystemExit class" do + @s.rb_eSystemExit.should == SystemExit + end + + specify "rb_eSysStackError references the SystemStackError class" do + @s.rb_eSysStackError.should == SystemStackError + end + + specify "rb_eTypeError references the TypeError class" do + @s.rb_eTypeError.should == TypeError + end + + specify "rb_eThreadError references the ThreadError class" do + @s.rb_eThreadError.should == ThreadError + end + + specify "rb_mWaitReadable references the IO::WaitReadable module" do + @s.rb_mWaitReadable.should == IO::WaitReadable + end + + specify "rb_mWaitWritable references the IO::WaitWritable module" do + @s.rb_mWaitWritable.should == IO::WaitWritable + end + + specify "rb_eZeroDivError references the ZeroDivisionError class" do + @s.rb_eZeroDivError.should == ZeroDivisionError + end +end diff --git a/spec/ruby/optional/capi/data_spec.rb b/spec/ruby/optional/capi/data_spec.rb new file mode 100644 index 0000000000..18c769332e --- /dev/null +++ b/spec/ruby/optional/capi/data_spec.rb @@ -0,0 +1,52 @@ +require_relative 'spec_helper' + +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 + end + + 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 + + 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 + + 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 diff --git a/spec/ruby/optional/capi/debug_spec.rb b/spec/ruby/optional/capi/debug_spec.rb new file mode 100644 index 0000000000..c8c91417d1 --- /dev/null +++ b/spec/ruby/optional/capi/debug_spec.rb @@ -0,0 +1,66 @@ +require_relative 'spec_helper' + +load_extension('debug') + +describe "C-API Debug function" do + before :each do + @o = CApiDebugSpecs.new + end + + describe "rb_debug_inspector_open" do + it "creates a debug context and calls the given callback" do + @o.rb_debug_inspector_open(42).should be_kind_of(Array) + @o.debug_spec_callback_data.should == 42 + end + end + + describe "rb_debug_inspector_frame_self_get" do + it "returns self" do + @o.rb_debug_inspector_frame_self_get(0).should == @o + end + end + + describe "rb_debug_inspector_frame_class_get" do + it "returns the frame class" do + @o.rb_debug_inspector_frame_class_get(0).should == CApiDebugSpecs + end + end + + describe "rb_debug_inspector_frame_binding_get" do + it "returns the current binding" do + a = "test" + b = @o.rb_debug_inspector_frame_binding_get(1) + b.should be_an_instance_of(Binding) + b.local_variable_get(:a).should == "test" + end + + 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| + if binding + "#{backtrace_location.path}:#{backtrace_location.lineno}".should == "#{binding.source_location[0]}:#{binding.source_location[1]}" + end + end + end + end + + describe "rb_debug_inspector_frame_iseq_get" do + it "returns an InstructionSequence" do + if defined?(RubyVM::InstructionSequence) + @o.rb_debug_inspector_frame_iseq_get(1).should be_an_instance_of(RubyVM::InstructionSequence) + else + @o.rb_debug_inspector_frame_iseq_get(1).should == nil + end + end + end + + describe "rb_debug_inspector_backtrace_locations" do + it "returns an array of Thread::Backtrace::Location" do + bts = @o.rb_debug_inspector_backtrace_locations + bts.should_not.empty? + bts.each { |bt| bt.should be_kind_of(Thread::Backtrace::Location) } + location = "#{__FILE__}:#{__LINE__ - 3}" + bts[1].to_s.should include(location) + end + end +end diff --git a/spec/ruby/optional/capi/encoding_spec.rb b/spec/ruby/optional/capi/encoding_spec.rb new file mode 100644 index 0000000000..66c2dd40de --- /dev/null +++ b/spec/ruby/optional/capi/encoding_spec.rb @@ -0,0 +1,645 @@ +# -*- encoding: utf-8 -*- +require_relative 'spec_helper' +require_relative 'fixtures/encoding' + +load_extension('encoding') + +describe :rb_enc_get_index, shared: true do + it "returns the index of the encoding of a String" do + @s.send(@method, "string").should >= 0 + end + + it "returns the index of the encoding of a Regexp" do + @s.send(@method, /regexp/).should >= 0 + end +end + +describe :rb_enc_set_index, shared: true do + it "sets the object's encoding to the Encoding specified by the index" do + obj = "abc" + result = @s.send(@method, obj, 2) + + # This is used because indexes should be considered implementation + # dependent. So a pair is returned: + # [rb_enc_find_index() -> name, rb_enc_get(obj) -> name] + result.first.should == result.last + end + + it "associates an encoding with a subclass of String" do + str = CApiEncodingSpecs::S.new "abc" + result = @s.send(@method, str, 1) + result.first.should == result.last + end + + it "raises an ArgumentError for a non-encoding capable object" do + obj = Object.new + -> { + result = @s.send(@method, obj, 1) + }.should raise_error(ArgumentError, "cannot set encoding on non-encoding capable object") + end +end + +describe "C-API Encoding function" do + @n = 0 + + before :each do + @s = CApiEncodingSpecs.new + end + + describe "rb_enc_alias" do + it "creates an alias for an existing Encoding" do + name = "ZOMGWTFBBQ#{@n += 1}" + @s.rb_enc_alias(name, "UTF-8").should >= 0 + Encoding.find(name).name.should == "UTF-8" + end + end + + describe "rb_enc_codelen" do + it "returns the correct length for the given codepoint" do + @s.rb_enc_codelen(0x24, Encoding::UTF_8).should == 1 + @s.rb_enc_codelen(0xA2, Encoding::UTF_8).should == 2 + @s.rb_enc_codelen(0x20AC, Encoding::UTF_8).should == 3 + @s.rb_enc_codelen(0x24B62, Encoding::UTF_8).should == 4 + end + end + + describe "rb_enc_find" do + it "returns the encoding of an Encoding" do + @s.rb_enc_find("UTF-8").should == "UTF-8" + end + + it "returns the encoding of an Encoding specified with lower case" do + @s.rb_enc_find("utf-8").should == "UTF-8" + end + end + + describe "rb_enc_find_index" do + it "returns the index of an Encoding" do + @s.rb_enc_find_index("UTF-8").should >= 0 + end + + it "returns the index of an Encoding specified with lower case" do + @s.rb_enc_find_index("utf-8").should >= 0 + end + + it "returns -1 for an non existing encoding" do + @s.rb_enc_find_index("non-existent-encoding").should == -1 + end + end + + describe "rb_enc_isalnum" do + it "returns non-zero for alpha-numeric characters" do + @s.rb_enc_isalnum("a".ord, Encoding::US_ASCII).should == true + @s.rb_enc_isalnum("2".ord, Encoding::US_ASCII).should == true + @s.rb_enc_isalnum("a".ord, Encoding::UTF_8).should == true + @s.rb_enc_isalnum("2".ord, Encoding::UTF_8).should == true + @s.rb_enc_isalnum("é".encode(Encoding::ISO_8859_1).ord, Encoding::ISO_8859_1).should == true + end + + it "returns zero for non alpha-numeric characters" do + @s.rb_enc_isalnum("-".ord, Encoding::US_ASCII).should == false + @s.rb_enc_isalnum(" ".ord, Encoding::US_ASCII).should == false + @s.rb_enc_isalnum("-".ord, Encoding::UTF_8).should == false + @s.rb_enc_isalnum(" ".ord, Encoding::UTF_8).should == false + end + end + + describe "rb_enc_isspace" do + it "returns non-zero for space characters" do + @s.rb_enc_isspace(" ".ord, Encoding::US_ASCII).should == true + @s.rb_enc_isspace(" ".ord, Encoding::UTF_8).should == true + end + + it "returns zero for non space characters" do + @s.rb_enc_isspace("-".ord, Encoding::US_ASCII).should == false + @s.rb_enc_isspace("A".ord, Encoding::US_ASCII).should == false + @s.rb_enc_isspace("3".ord, Encoding::US_ASCII).should == false + @s.rb_enc_isspace("-".ord, Encoding::UTF_8).should == false + @s.rb_enc_isspace("A".ord, Encoding::UTF_8).should == false + @s.rb_enc_isspace("3".ord, Encoding::UTF_8).should == false + end + end + + describe "rb_enc_from_index" do + it "returns an Encoding" do + @s.rb_enc_from_index(0).should be_an_instance_of(String) + end + end + + 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("é", 2).should == 0x00E9 + @s.rb_enc_mbc_to_codepoint("éa", 2).should == 0x00E9 + @s.rb_enc_mbc_to_codepoint("éa", 1).should == 0xC3 + @s.rb_enc_mbc_to_codepoint("éa", 3).should == 0x00E9 + end + end + + describe "rb_enc_mbcput" do + it "writes the correct bytes to the buffer" do + @s.rb_enc_mbcput(0x24, Encoding::UTF_8).should == "$" + @s.rb_enc_mbcput(0xA2, Encoding::UTF_8).should == "¢" + @s.rb_enc_mbcput(0x20AC, Encoding::UTF_8).should == "€" + @s.rb_enc_mbcput(0x24B62, Encoding::UTF_8).should == "𤭢" + + @s.rb_enc_mbcput(0x24, Encoding::UTF_16BE).bytes.should == [0, 0x24] + @s.rb_enc_mbcput(0x24B62, Encoding::UTF_16LE).bytes.should == [82, 216, 98, 223] + end + end + + describe "rb_usascii_encoding" do + it "returns the encoding for Encoding::US_ASCII" do + @s.rb_usascii_encoding.should == "US-ASCII" + end + end + + describe "rb_ascii8bit_encoding" do + it "returns the encoding for Encoding::BINARY" do + @s.rb_ascii8bit_encoding.should == "ASCII-8BIT" + end + end + + describe "rb_utf8_encoding" do + it "returns the encoding for Encoding::UTF_8" do + @s.rb_utf8_encoding.should == "UTF-8" + end + end + + describe "rb_enc_from_encoding" do + it "returns an Encoding instance from an encoding data structure" do + @s.rb_enc_from_encoding("UTF-8").should == Encoding::UTF_8 + end + end + + describe "rb_locale_encoding" do + it "returns the encoding for the current locale" do + @s.rb_locale_encoding.should == Encoding.find('locale').name + end + end + + describe "rb_filesystem_encoding" do + it "returns the encoding for the current filesystem" do + @s.rb_filesystem_encoding.should == Encoding.find('filesystem').name + end + end + + describe "rb_enc_get" do + it "returns the encoding associated with an object" do + str = "abc".encode Encoding::BINARY + @s.rb_enc_get(str).should == "ASCII-8BIT" + end + end + + describe "rb_enc_precise_mbclen" do + it "returns the correct length for single byte characters" do + @s.rb_enc_precise_mbclen("hello", 7).should == 1 + @s.rb_enc_precise_mbclen("hello", 5).should == 1 + @s.rb_enc_precise_mbclen("hello", 1).should == 1 + @s.rb_enc_precise_mbclen("hello", 0).should == -2 + @s.rb_enc_precise_mbclen("hello", -1).should == -2 + @s.rb_enc_precise_mbclen("hello", -5).should == -2 + end + + it "returns the correct length for multi-byte characters" do + @s.rb_enc_precise_mbclen("ésumé", 2).should == 2 + @s.rb_enc_precise_mbclen("ésumé", 3).should == 2 + @s.rb_enc_precise_mbclen("ésumé", 0).should == -2 + @s.rb_enc_precise_mbclen("ésumé", 1).should == -2 + @s.rb_enc_precise_mbclen("あ", 20).should == 3 + @s.rb_enc_precise_mbclen("あ", 3).should == 3 + @s.rb_enc_precise_mbclen("あ", 2).should == -2 + @s.rb_enc_precise_mbclen("あ", 0).should == -2 + @s.rb_enc_precise_mbclen("あ", -2).should == -2 + end + end + + describe "rb_obj_encoding" do + it "returns the encoding associated with an object" do + str = "abc".encode Encoding::BINARY + @s.rb_obj_encoding(str).should == Encoding::BINARY + end + end + + describe "rb_enc_get_index" do + it_behaves_like :rb_enc_get_index, :rb_enc_get_index + + it "returns the index of the encoding of a Symbol" do + @s.rb_enc_get_index(:symbol).should >= 0 + end + + it "returns -1 as the index of nil" do + @s.rb_enc_get_index(nil).should == -1 + end + + it "returns -1 as the index for immediates" do + @s.rb_enc_get_index(1).should == -1 + end + + it "returns -1 for an object without an encoding" do + obj = Object.new + @s.rb_enc_get_index(obj).should == -1 + end + end + + describe "rb_enc_set_index" do + it_behaves_like :rb_enc_set_index, :rb_enc_set_index + end + + describe "rb_enc_str_new" do + it "returns a String in US-ASCII encoding when high bits are set" do + xEE = [0xEE].pack('C').force_encoding('utf-8') + result = @s.rb_enc_str_new(xEE, 1, Encoding::US_ASCII) + result.encoding.should equal(Encoding::US_ASCII) + end + end + + describe "rb_enc_str_new_cstr" do + it "creates a new ruby string from a c string literal" do + result = @s.rb_enc_str_new_cstr_constant(Encoding::US_ASCII) + result.should == "test string literal" + result.encoding.should == Encoding::US_ASCII + end + + it "creates a new ruby string from a c string variable" do + result = @s.rb_enc_str_new_cstr("test string", Encoding::US_ASCII) + result.should == "test string" + result.encoding.should == Encoding::US_ASCII + end + + it "when null encoding is given with a c string literal, it creates a new ruby string with ASCII_8BIT encoding" do + result = @s.rb_enc_str_new_cstr_constant(nil) + result.should == "test string literal" + result.encoding.should == Encoding::ASCII_8BIT + end + end + + describe "rb_enc_str_coderange" do + describe "when the encoding is BINARY" do + it "returns ENC_CODERANGE_7BIT if there are no high bits set" do + result = @s.rb_enc_str_coderange("abc".force_encoding("binary")) + result.should == :coderange_7bit + end + + it "returns ENC_CODERANGE_VALID if there are high bits set" do + xEE = [0xEE].pack('C').force_encoding('utf-8') + result = @s.rb_enc_str_coderange(xEE.force_encoding("binary")) + result.should == :coderange_valid + end + end + + describe "when the encoding is UTF-8" do + it "returns ENC_CODERANGE_7BIT if there are no high bits set" do + result = @s.rb_enc_str_coderange("abc".force_encoding("utf-8")) + result.should == :coderange_7bit + end + + it "returns ENC_CODERANGE_VALID if there are high bits set in a valid string" do + result = @s.rb_enc_str_coderange("\xE3\x81\x82".force_encoding("utf-8")) + result.should == :coderange_valid + end + + it "returns ENC_CODERANGE_BROKEN if there are high bits set in an invalid string" do + result = @s.rb_enc_str_coderange([0xEE].pack('C').force_encoding("utf-8")) + result.should == :coderange_broken + end + end + + describe "when the encoding is US-ASCII" do + it "returns ENC_CODERANGE_7BIT if there are no high bits set" do + result = @s.rb_enc_str_coderange("abc".force_encoding("us-ascii")) + result.should == :coderange_7bit + end + + it "returns ENC_CODERANGE_BROKEN if there are high bits set" do + result = @s.rb_enc_str_coderange([0xEE].pack('C').force_encoding("us-ascii")) + result.should == :coderange_broken + end + end + end + + describe "MBCLEN_CHARFOUND_P" do + it "returns non-zero for valid character" do + @s.MBCLEN_CHARFOUND_P("a".ord).should == 1 + end + + it "returns zero for invalid characters" do + @s.MBCLEN_CHARFOUND_P(0).should == 0 + @s.MBCLEN_CHARFOUND_P(-1).should == 0 + end + end + + describe "ENCODING_GET" do + it_behaves_like :rb_enc_get_index, :ENCODING_GET + end + + describe "ENCODING_SET" do + it_behaves_like :rb_enc_set_index, :ENCODING_SET + end + + describe "ENC_CODERANGE_ASCIIONLY" do + it "returns true if the object encoding is only ASCII" do + str = "abc".force_encoding("us-ascii") + str.valid_encoding? # make sure to set the coderange + @s.ENC_CODERANGE_ASCIIONLY(str).should be_true + end + + it "returns false if the object encoding is not ASCII only" do + str = "ありがとう".force_encoding("utf-8") + @s.ENC_CODERANGE_ASCIIONLY(str).should be_false + end + end + + describe "rb_to_encoding" do + it "returns the encoding for the Encoding instance passed" do + @s.rb_to_encoding(Encoding::BINARY).should == "ASCII-8BIT" + end + + it "returns the correct encoding for a replicated encoding" do + @s.rb_to_encoding(Encoding::IBM857).should == "IBM857" + end + + it "returns the encoding when passed a String" do + @s.rb_to_encoding("ASCII").should == "US-ASCII" + end + + it "calls #to_str to convert the argument to a String" do + obj = mock("rb_to_encoding Encoding name") + obj.should_receive(:to_str).and_return("utf-8") + + @s.rb_to_encoding(obj).should == "UTF-8" + end + + describe "when the rb_encoding struct is stored in native memory" do + it "can still read the name of the encoding" do + address = @s.rb_to_encoding_native_store(Encoding::UTF_8) + address.should be_kind_of(Integer) + @s.rb_to_encoding_native_name(address).should == "UTF-8" + end + end + end + + describe "rb_to_encoding_index" do + it "returns the index of the encoding for the Encoding instance passed" do + @s.rb_to_encoding_index(Encoding::BINARY).should >= 0 + end + + it "returns the index of the encoding when passed a String" do + @s.rb_to_encoding_index("ASCII").should >= 0 + end + + it "returns the index of the dummy encoding of an Object" do + index = Encoding.list.index(Encoding::UTF_16) + @s.rb_to_encoding_index(Encoding::UTF_16.name).should == index + end + + it "calls #to_str to convert the argument to a String" do + obj = mock("rb_to_encoding Encoding name") + obj.should_receive(:to_str).and_return("utf-8") + + @s.rb_to_encoding_index(obj).should >= 0 + end + end + + describe "rb_enc_compatible" do + it "returns 0 if the encodings of the Strings are not compatible" do + a = [0xff].pack('C').force_encoding "binary" + b = "\u3042".encode("utf-8") + @s.rb_enc_compatible(a, b).should == 0 + end + + # The coverage of this sucks, but there is not a simple way (yet?) to + # easily share the specs between rb_enc_compatible and + # Encoding.compatible? + it "returns the same value as Encoding.compatible? if the Strings have a compatible encoding" do + a = "abc".force_encoding("us-ascii") + b = "\u3042".encode("utf-8") + @s.rb_enc_compatible(a, b).should == Encoding.compatible?(a, b) + end + end + + describe "rb_enc_copy" do + before :each do + @obj = "rb_enc_copy".encode(Encoding::US_ASCII) + end + + it "sets the encoding of a String to that of the second argument" do + @s.rb_enc_copy("string", @obj).encoding.should == Encoding::US_ASCII + end + + it "raises a RuntimeError if the second argument is a Symbol" do + -> { @s.rb_enc_copy(:symbol, @obj) }.should raise_error(RuntimeError) + end + + it "sets the encoding of a Regexp to that of the second argument" do + @s.rb_enc_copy(/regexp/.dup, @obj).encoding.should == Encoding::US_ASCII + end + end + + describe "rb_default_internal_encoding" do + before :each do + @default = Encoding.default_internal + end + + after :each do + Encoding.default_internal = @default + end + + it "returns 0 if Encoding.default_internal is nil" do + Encoding.default_internal = nil + @s.rb_default_internal_encoding.should be_nil + end + + it "returns the encoding for Encoding.default_internal" do + Encoding.default_internal = "US-ASCII" + @s.rb_default_internal_encoding.should == "US-ASCII" + Encoding.default_internal = "UTF-8" + @s.rb_default_internal_encoding.should == "UTF-8" + end + end + + describe "rb_default_external_encoding" do + before :each do + @default = Encoding.default_external + end + + after :each do + Encoding.default_external = @default + end + + it "returns the encoding for Encoding.default_external" do + Encoding.default_external = "ASCII-8BIT" + @s.rb_default_external_encoding.should == "ASCII-8BIT" + end + end + + describe "rb_enc_associate" do + it "sets the encoding of a String to the encoding" do + @s.rb_enc_associate("string", "BINARY").encoding.should == Encoding::BINARY + end + + it "raises a RuntimeError if the argument is Symbol" do + -> { @s.rb_enc_associate(:symbol, "US-ASCII") }.should raise_error(RuntimeError) + end + + it "sets the encoding of a Regexp to the encoding" do + @s.rb_enc_associate(/regexp/.dup, "BINARY").encoding.should == Encoding::BINARY + end + + it "sets the encoding of a String to a default when the encoding is NULL" do + @s.rb_enc_associate("string", nil).encoding.should == Encoding::BINARY + end + end + + describe "rb_enc_associate_index" do + it "sets the encoding of a String to the encoding" do + index = @s.rb_enc_find_index("BINARY") + enc = @s.rb_enc_associate_index("string", index).encoding + enc.should == Encoding::BINARY + end + + it "sets the encoding of a Regexp to the encoding" do + index = @s.rb_enc_find_index("UTF-8") + enc = @s.rb_enc_associate_index(/regexp/.dup, index).encoding + enc.should == Encoding::UTF_8 + end + + it "sets the encoding of a Symbol to the encoding" do + index = @s.rb_enc_find_index("UTF-8") + -> { @s.rb_enc_associate_index(:symbol, index) }.should raise_error(RuntimeError) + end + end + + describe "rb_ascii8bit_encindex" do + it "returns an index for the ASCII-8BIT encoding" do + @s.rb_ascii8bit_encindex().should >= 0 + end + end + + describe "rb_utf8_encindex" do + it "returns an index for the UTF-8 encoding" do + @s.rb_utf8_encindex().should >= 0 + end + end + + describe "rb_usascii_encindex" do + it "returns an index for the US-ASCII encoding" do + @s.rb_usascii_encindex().should >= 0 + end + end + + describe "rb_locale_encindex" do + it "returns an index for the locale encoding" do + @s.rb_locale_encindex().should >= 0 + end + end + + describe "rb_filesystem_encindex" do + it "returns an index for the filesystem encoding" do + @s.rb_filesystem_encindex().should >= 0 + end + end + + describe "rb_enc_to_index" do + it "returns an index for the encoding" do + @s.rb_enc_to_index("UTF-8").should >= 0 + end + + it "returns a non-negative int if the encoding is not defined" do + # Encoding indexes are an implementation detail and not guaranteed + # across implementations. + @s.rb_enc_to_index("FTU-81").should >= 0 + end + end + + describe "rb_enc_nth" do + it "returns the byte index of the given character index" do + @s.rb_enc_nth("hüllo", 3).should == 4 + end + end + + describe "rb_enc_codepoint_len" do + it "raises ArgumentError if an empty string is given" do + -> do + @s.rb_enc_codepoint_len("") + end.should raise_error(ArgumentError) + end + + it "raises ArgumentError if an invalid byte sequence is given" do + -> do + @s.rb_enc_codepoint_len([0xa0, 0xa1].pack('CC').force_encoding('utf-8')) # Invalid sequence identifier + end.should raise_error(ArgumentError) + end + + it "returns codepoint 0x24 and length 1 for character '$'" do + codepoint, length = @s.rb_enc_codepoint_len("$") + + codepoint.should == 0x24 + length.should == 1 + end + + it "returns codepoint 0xA2 and length 2 for character '¢'" do + codepoint, length = @s.rb_enc_codepoint_len("¢") + + codepoint.should == 0xA2 + length.should == 2 + end + + it "returns codepoint 0x20AC and length 3 for character '€'" do + codepoint, length = @s.rb_enc_codepoint_len("€") + + codepoint.should == 0x20AC + length.should == 3 + end + + it "returns codepoint 0x24B62 and length 4 for character '𤭢'" do + codepoint, length = @s.rb_enc_codepoint_len("𤭢") + + codepoint.should == 0x24B62 + length.should == 4 + end + end + + describe "rb_enc_str_asciionly_p" do + it "returns true for an ASCII string" do + @s.rb_enc_str_asciionly_p("hello").should be_true + end + + it "returns false for a non-ASCII string" do + @s.rb_enc_str_asciionly_p("hüllo").should be_false + end + end + + describe "rb_uv_to_utf8" do + it 'converts a Unicode codepoint to a UTF-8 C string' do + str = ' ' * 6 + { + 0 => "\x01", + 0x7f => "\xC2\x80", + 0x7ff => "\xE0\xA0\x80", + 0xffff => "\xF0\x90\x80\x80", + 0x1fffff => "\xF8\x88\x80\x80\x80", + 0x3ffffff => "\xFC\x84\x80\x80\x80\x80", + }.each do |num, result| + len = @s.rb_uv_to_utf8(str, num + 1) + str[0..len-1].should == result + end + end + end + + describe "ONIGENC_MBC_CASE_FOLD" do + it "returns the correct case fold for the given string" do + @s.ONIGENC_MBC_CASE_FOLD("lower").should == ["l", 1] + @s.ONIGENC_MBC_CASE_FOLD("Upper").should == ["u", 1] + end + + it "works with other encodings" do + @s.ONIGENC_MBC_CASE_FOLD("lower".force_encoding("binary")).should == ["l", 1] + @s.ONIGENC_MBC_CASE_FOLD("Upper".force_encoding("binary")).should == ["u", 1] + @s.ONIGENC_MBC_CASE_FOLD("É").should == ["é", 2] + + str, length = @s.ONIGENC_MBC_CASE_FOLD('$'.encode(Encoding::UTF_16BE)) + length.should == 2 + str.bytes.should == [0, 0x24] + end + end +end diff --git a/spec/ruby/optional/capi/enumerator_spec.rb b/spec/ruby/optional/capi/enumerator_spec.rb new file mode 100644 index 0000000000..9ed68c9063 --- /dev/null +++ b/spec/ruby/optional/capi/enumerator_spec.rb @@ -0,0 +1,66 @@ +require_relative 'spec_helper' + +load_extension("enumerator") + +describe "C-API Enumerator function" do + before :each do + @s = CApiEnumeratorSpecs.new + end + + describe "rb_enumeratorize" do + before do + @enumerable = [1, 2, 3] + end + + it "constructs a new Enumerator for the given object, method and arguments" do + enumerator = @s.rb_enumeratorize(@enumerable, :each, :arg1, :arg2) + enumerator.class.should == Enumerator + end + + it "enumerates the given object" do + enumerator = @s.rb_enumeratorize(@enumerable, :each) + enumerated = [] + enumerator.each { |i| enumerated << i } + enumerated.should == @enumerable + end + + it "uses the given method for enumeration" do + enumerator = @s.rb_enumeratorize(@enumerable, :awesome_each) + @enumerable.should_receive(:awesome_each) + enumerator.each {} + end + + it "passes the given arguments to the enumeration method" do + enumerator = @s.rb_enumeratorize(@enumerable, :each, :arg1, :arg2) + @enumerable.should_receive(:each).with(:arg1, :arg2) + enumerator.each {} + end + end + + describe "rb_enumeratorize_with_size" do + + it "enumerates the given object" do + enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each) + enumerated = [] + enumerator.each { |i| enumerated << i } + enumerated.should == @enumerable + end + + it "uses the given method for enumeration" do + enumerator = @s.rb_enumeratorize_with_size(@enumerable, :awesome_each) + @enumerable.should_receive(:awesome_each) + enumerator.each {} + end + + it "passes the given arguments to the enumeration method" do + enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each, :arg1, :arg2) + @enumerable.should_receive(:each).with(:arg1, :arg2) + enumerator.each {} + end + + it "uses the size function to report the size" do + enumerator = @s.rb_enumeratorize_with_size(@enumerable, :each, :arg1, :arg2) + enumerator.size.should == 7 + end + end +end diff --git a/spec/ruby/optional/capi/exception_spec.rb b/spec/ruby/optional/capi/exception_spec.rb new file mode 100644 index 0000000000..b0a8a2860e --- /dev/null +++ b/spec/ruby/optional/capi/exception_spec.rb @@ -0,0 +1,147 @@ +require_relative 'spec_helper' + +load_extension("exception") + +describe "C-API Exception function" do + before :each do + @s = CApiExceptionSpecs.new + end + + describe "rb_exc_new" do + it "creates an exception from a C string and length" do + @s.rb_exc_new('foo').to_s.should == 'foo' + end + end + + describe "rb_exc_new2" do + it "creates an exception from a C string" do + @s.rb_exc_new2('foo').to_s.should == 'foo' + end + end + + describe "rb_exc_new3" do + it "creates an exception from a Ruby string" do + @s.rb_exc_new3('foo').to_s.should == 'foo' + end + end + + describe "rb_exc_raise" do + it "raises passed exception" do + runtime_error = RuntimeError.new '42' + -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42') + end + + it "raises an exception with an empty backtrace" do + runtime_error = RuntimeError.new '42' + runtime_error.set_backtrace [] + -> { @s.rb_exc_raise(runtime_error) }.should raise_error(RuntimeError, '42') + end + + it "sets $! to the raised exception when not rescuing from an another exception" do + runtime_error = RuntimeError.new '42' + runtime_error.set_backtrace [] + begin + @s.rb_exc_raise(runtime_error) + rescue + $!.should == runtime_error + end + end + + it "sets $! to the raised exception when $! when rescuing from an another exception" do + runtime_error = RuntimeError.new '42' + runtime_error.set_backtrace [] + begin + begin + raise StandardError + rescue + @s.rb_exc_raise(runtime_error) + end + rescue + $!.should == runtime_error + end + end + end + + describe "rb_errinfo" do + it "is cleared when entering a C method" do + begin + raise StandardError + rescue + $!.class.should == StandardError + @s.rb_errinfo().should == nil + end + end + + it "does not clear $! in the calling method" do + begin + raise StandardError + rescue + @s.rb_errinfo() + $!.class.should == StandardError + end + end + end + + describe "rb_set_errinfo" do + after :each do + @s.rb_set_errinfo(nil) + end + + it "accepts nil" do + @s.rb_set_errinfo(nil).should be_nil + end + + it "accepts an Exception instance" do + @s.rb_set_errinfo(Exception.new).should be_nil + end + + it "raises a TypeError if the object is not nil or an Exception instance" do + -> { @s.rb_set_errinfo("error") }.should raise_error(TypeError) + end + end + + describe "rb_make_exception" do + it "returns a RuntimeError when given a String argument" do + e = @s.rb_make_exception(["Message"]) + e.class.should == RuntimeError + e.message.should == "Message" + end + + it "returns the exception when given an Exception argument" do + exc = Exception.new + e = @s.rb_make_exception([exc]) + e.should == exc + end + + it "returns the exception with the given class and message" do + e = @s.rb_make_exception([Exception, "Message"]) + e.class.should == Exception + e.message.should == "Message" + end + + it "returns the exception with the given class, message, and backtrace" do + e = @s.rb_make_exception([Exception, "Message", ["backtrace 1"]]) + e.class.should == Exception + e.message.should == "Message" + e.backtrace.should == ["backtrace 1"] + end + + it "raises a TypeError for incorrect types" do + -> { @s.rb_make_exception([nil]) }.should raise_error(TypeError) + -> { @s.rb_make_exception([Object.new]) }.should raise_error(TypeError) + obj = Object.new + def obj.exception + "not exception type" + end + -> { @s.rb_make_exception([obj]) }.should raise_error(TypeError) + end + + it "raises an ArgumentError for too many arguments" do + -> { @s.rb_make_exception([Exception, "Message", ["backtrace 1"], "extra"]) }.should raise_error(ArgumentError) + end + + it "returns nil for empty arguments" do + @s.rb_make_exception([]).should == nil + end + end +end diff --git a/spec/ruby/optional/capi/ext/.gitignore b/spec/ruby/optional/capi/ext/.gitignore new file mode 100644 index 0000000000..577d117bb1 --- /dev/null +++ b/spec/ruby/optional/capi/ext/.gitignore @@ -0,0 +1,9 @@ +# signature of implementation that +# last compiled an extension +*.sig + +# build artifacts +*.o +*.so +*.bundle +*.dll diff --git a/spec/ruby/optional/capi/ext/array_spec.c b/spec/ruby/optional/capi/ext/array_spec.c new file mode 100644 index 0000000000..9386239813 --- /dev/null +++ b/spec/ruby/optional/capi/ext/array_spec.c @@ -0,0 +1,297 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE array_spec_rb_Array(VALUE self, VALUE object) { + return rb_Array(object); +} + +static VALUE array_spec_RARRAY_PTR_iterate(VALUE self, VALUE array) { + int i; + VALUE* ptr; + + ptr = RARRAY_PTR(array); + for(i = 0; i < RARRAY_LEN(array); i++) { + rb_yield(ptr[i]); + } + return Qnil; +} + +static VALUE array_spec_RARRAY_PTR_assign(VALUE self, VALUE array, VALUE value) { + int i; + VALUE* ptr; + + ptr = RARRAY_PTR(array); + for(i = 0; i < RARRAY_LEN(array); i++) { + ptr[i] = value; + } + return Qnil; +} + + +static VALUE array_spec_RARRAY_PTR_memcpy(VALUE self, VALUE array1, VALUE array2) { + VALUE *ptr1, *ptr2; + long size; + size = RARRAY_LEN(array1); + ptr1 = RARRAY_PTR(array1); + ptr2 = RARRAY_PTR(array2); + if (ptr1 != NULL && ptr2 != NULL) { + memcpy(ptr2, ptr1, size * sizeof(VALUE)); + } + return Qnil; +} + +static VALUE array_spec_RARRAY_LEN(VALUE self, VALUE array) { + return INT2FIX(RARRAY_LEN(array)); +} + +static VALUE array_spec_RARRAY_AREF(VALUE self, VALUE array, VALUE index) { + return RARRAY_AREF(array, FIX2INT(index)); +} + +static VALUE array_spec_RARRAY_ASET(VALUE self, VALUE array, VALUE index, VALUE value) { + RARRAY_ASET(array, FIX2INT(index), value); + return value; +} + +static VALUE array_spec_rb_ary_aref(int argc, VALUE *argv, VALUE self) { + VALUE ary, args; + rb_scan_args(argc, argv, "1*", &ary, &args); + return rb_ary_aref((int)RARRAY_LEN(args), RARRAY_PTR(args), ary); +} + +static VALUE array_spec_rb_ary_clear(VALUE self, VALUE array) { + return rb_ary_clear(array); +} + +static VALUE array_spec_rb_ary_delete(VALUE self, VALUE array, VALUE item) { + return rb_ary_delete(array, item); +} + +static VALUE array_spec_rb_ary_delete_at(VALUE self, VALUE array, VALUE index) { + return rb_ary_delete_at(array, NUM2LONG(index)); +} + +static VALUE array_spec_rb_ary_dup(VALUE self, VALUE array) { + return rb_ary_dup(array); +} + +static VALUE array_spec_rb_ary_entry(VALUE self, VALUE array, VALUE offset) { + return rb_ary_entry(array, FIX2INT(offset)); +} + +static VALUE array_spec_rb_ary_includes(VALUE self, VALUE ary, VALUE item) { + return rb_ary_includes(ary, item); +} + +static VALUE array_spec_rb_ary_join(VALUE self, VALUE array1, VALUE array2) { + return rb_ary_join(array1, array2); +} + +static VALUE array_spec_rb_ary_to_s(VALUE self, VALUE array) { + return rb_ary_to_s(array); +} + +static VALUE array_spec_rb_ary_new(VALUE self) { + VALUE ret; + ret = rb_ary_new(); + return ret; +} + +static VALUE array_spec_rb_ary_new2(VALUE self, VALUE length) { + return rb_ary_new2(NUM2LONG(length)); +} + +static VALUE array_spec_rb_ary_new_capa(VALUE self, VALUE length) { + return rb_ary_new_capa(NUM2LONG(length)); +} + +static VALUE array_spec_rb_ary_new3(VALUE self, VALUE first, VALUE second, VALUE third) { + return rb_ary_new3(3, first, second, third); +} + +static VALUE array_spec_rb_ary_new_from_args(VALUE self, VALUE first, VALUE second, VALUE third) { + return rb_ary_new_from_args(3, first, second, third); +} + +static VALUE array_spec_rb_ary_new4(VALUE self, VALUE first, VALUE second, VALUE third) { + VALUE values[3]; + values[0] = first; + values[1] = second; + values[2] = third; + return rb_ary_new4(3, values); +} + +static VALUE array_spec_rb_ary_new_from_values(VALUE self, VALUE first, VALUE second, VALUE third) { + VALUE values[3]; + values[0] = first; + values[1] = second; + values[2] = third; + return rb_ary_new_from_values(3, values); +} + +static VALUE array_spec_rb_ary_pop(VALUE self, VALUE array) { + return rb_ary_pop(array); +} + +static VALUE array_spec_rb_ary_push(VALUE self, VALUE array, VALUE item) { + rb_ary_push(array, item); + return array; +} + +static VALUE array_spec_rb_ary_cat(int argc, VALUE *argv, VALUE self) { + VALUE ary, args; + rb_scan_args(argc, argv, "1*", &ary, &args); + return rb_ary_cat(ary, RARRAY_PTR(args), RARRAY_LEN(args)); +} + +static VALUE array_spec_rb_ary_reverse(VALUE self, VALUE array) { + return rb_ary_reverse(array); +} + +static VALUE array_spec_rb_ary_rotate(VALUE self, VALUE array, VALUE count) { + return rb_ary_rotate(array, NUM2LONG(count)); +} + +static VALUE array_spec_rb_ary_shift(VALUE self, VALUE array) { + return rb_ary_shift(array); +} + +static VALUE array_spec_rb_ary_sort(VALUE self, VALUE array) { + return rb_ary_sort(array); +} + +static VALUE array_spec_rb_ary_sort_bang(VALUE self, VALUE array) { + return rb_ary_sort_bang(array); +} + +static VALUE array_spec_rb_ary_store(VALUE self, VALUE array, VALUE offset, VALUE value) { + rb_ary_store(array, FIX2INT(offset), value); + + return Qnil; +} + +static VALUE array_spec_rb_ary_concat(VALUE self, VALUE array1, VALUE array2) { + return rb_ary_concat(array1, array2); +} + +static VALUE array_spec_rb_ary_plus(VALUE self, VALUE array1, VALUE array2) { + return rb_ary_plus(array1, array2); +} + +static VALUE array_spec_rb_ary_unshift(VALUE self, VALUE array, VALUE val) { + return rb_ary_unshift(array, val); +} + +static VALUE array_spec_rb_assoc_new(VALUE self, VALUE first, VALUE second) { + return rb_assoc_new(first, second); +} + +static VALUE copy_ary(RB_BLOCK_CALL_FUNC_ARGLIST(el, new_ary)) { + return rb_ary_push(new_ary, el); +} + +static VALUE array_spec_rb_block_call(VALUE self, VALUE ary) { + VALUE new_ary = rb_ary_new(); + + rb_block_call(ary, rb_intern("each"), 0, 0, copy_ary, new_ary); + + return new_ary; +} + +static VALUE sub_pair(RB_BLOCK_CALL_FUNC_ARGLIST(el, holder)) { + return rb_ary_push(holder, rb_ary_entry(el, 1)); +} + +static VALUE array_spec_rb_block_call_each_pair(VALUE self, VALUE obj) { + VALUE new_ary = rb_ary_new(); + + rb_block_call(obj, rb_intern("each_pair"), 0, 0, sub_pair, new_ary); + + return new_ary; +} + +static VALUE iter_yield(RB_BLOCK_CALL_FUNC_ARGLIST(el, ary)) { + rb_yield(el); + return Qnil; +} + +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); + return Qnil; +} + +static VALUE array_spec_rb_mem_clear(VALUE self, VALUE obj) { + VALUE ary[1]; + ary[0] = obj; + rb_mem_clear(ary, 1); + return ary[0]; +} + +static VALUE array_spec_rb_ary_freeze(VALUE self, VALUE ary) { + return rb_ary_freeze(ary); +} + +static VALUE array_spec_rb_ary_to_ary(VALUE self, VALUE ary) { + return rb_ary_to_ary(ary); +} + +static VALUE array_spec_rb_ary_subseq(VALUE self, VALUE ary, VALUE begin, VALUE len) { + return rb_ary_subseq(ary, FIX2LONG(begin), FIX2LONG(len)); +} + +void Init_array_spec(void) { + VALUE cls = rb_define_class("CApiArraySpecs", rb_cObject); + rb_define_method(cls, "rb_Array", array_spec_rb_Array, 1); + rb_define_method(cls, "RARRAY_LEN", array_spec_RARRAY_LEN, 1); + rb_define_method(cls, "RARRAY_PTR_iterate", array_spec_RARRAY_PTR_iterate, 1); + rb_define_method(cls, "RARRAY_PTR_assign", array_spec_RARRAY_PTR_assign, 2); + rb_define_method(cls, "RARRAY_PTR_memcpy", array_spec_RARRAY_PTR_memcpy, 2); + rb_define_method(cls, "RARRAY_AREF", array_spec_RARRAY_AREF, 2); + rb_define_method(cls, "RARRAY_ASET", array_spec_RARRAY_ASET, 3); + rb_define_method(cls, "rb_ary_aref", array_spec_rb_ary_aref, -1); + rb_define_method(cls, "rb_ary_clear", array_spec_rb_ary_clear, 1); + rb_define_method(cls, "rb_ary_delete", array_spec_rb_ary_delete, 2); + rb_define_method(cls, "rb_ary_delete_at", array_spec_rb_ary_delete_at, 2); + rb_define_method(cls, "rb_ary_dup", array_spec_rb_ary_dup, 1); + rb_define_method(cls, "rb_ary_entry", array_spec_rb_ary_entry, 2); + rb_define_method(cls, "rb_ary_includes", array_spec_rb_ary_includes, 2); + rb_define_method(cls, "rb_ary_join", array_spec_rb_ary_join, 2); + rb_define_method(cls, "rb_ary_to_s", array_spec_rb_ary_to_s, 1); + rb_define_method(cls, "rb_ary_new", array_spec_rb_ary_new, 0); + rb_define_method(cls, "rb_ary_new2", array_spec_rb_ary_new2, 1); + rb_define_method(cls, "rb_ary_new_capa", array_spec_rb_ary_new_capa, 1); + rb_define_method(cls, "rb_ary_new3", array_spec_rb_ary_new3, 3); + rb_define_method(cls, "rb_ary_new_from_args", array_spec_rb_ary_new_from_args, 3); + rb_define_method(cls, "rb_ary_new4", array_spec_rb_ary_new4, 3); + rb_define_method(cls, "rb_ary_new_from_values", array_spec_rb_ary_new_from_values, 3); + rb_define_method(cls, "rb_ary_pop", array_spec_rb_ary_pop, 1); + rb_define_method(cls, "rb_ary_push", array_spec_rb_ary_push, 2); + rb_define_method(cls, "rb_ary_cat", array_spec_rb_ary_cat, -1); + rb_define_method(cls, "rb_ary_reverse", array_spec_rb_ary_reverse, 1); + rb_define_method(cls, "rb_ary_rotate", array_spec_rb_ary_rotate, 2); + rb_define_method(cls, "rb_ary_shift", array_spec_rb_ary_shift, 1); + rb_define_method(cls, "rb_ary_sort", array_spec_rb_ary_sort, 1); + rb_define_method(cls, "rb_ary_sort_bang", array_spec_rb_ary_sort_bang, 1); + rb_define_method(cls, "rb_ary_store", array_spec_rb_ary_store, 3); + rb_define_method(cls, "rb_ary_concat", array_spec_rb_ary_concat, 2); + 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); + 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); + rb_define_method(cls, "rb_mem_clear", array_spec_rb_mem_clear, 1); + rb_define_method(cls, "rb_ary_freeze", array_spec_rb_ary_freeze, 1); + rb_define_method(cls, "rb_ary_to_ary", array_spec_rb_ary_to_ary, 1); + rb_define_method(cls, "rb_ary_subseq", array_spec_rb_ary_subseq, 3); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/basic_object_spec.c b/spec/ruby/optional/capi/ext/basic_object_spec.c new file mode 100644 index 0000000000..1618670ceb --- /dev/null +++ b/spec/ruby/optional/capi/ext/basic_object_spec.c @@ -0,0 +1,19 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE basic_object_spec_RBASIC_CLASS(VALUE self, VALUE obj) { + return RBASIC_CLASS(obj); +} + +void Init_basic_object_spec(void) { + VALUE cls = rb_define_class("CApiBasicObjectSpecs", rb_cObject); + rb_define_method(cls, "RBASIC_CLASS", basic_object_spec_RBASIC_CLASS, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/bignum_spec.c b/spec/ruby/optional/capi/ext/bignum_spec.c new file mode 100644 index 0000000000..a950d8b16f --- /dev/null +++ b/spec/ruby/optional/capi/ext/bignum_spec.c @@ -0,0 +1,106 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE bignum_spec_rb_big2dbl(VALUE self, VALUE num) { + return rb_float_new(rb_big2dbl(num)); +} + +static VALUE bignum_spec_rb_dbl2big(VALUE self, VALUE num) { + double dnum = NUM2DBL(num); + + return rb_dbl2big(dnum); +} + +static VALUE bignum_spec_rb_big2ll(VALUE self, VALUE num) { + return rb_ll2inum(rb_big2ll(num)); +} + +static VALUE bignum_spec_rb_big2long(VALUE self, VALUE num) { + return LONG2NUM(rb_big2long(num)); +} + +static VALUE bignum_spec_rb_big2str(VALUE self, VALUE num, VALUE base) { + return rb_big2str(num, FIX2INT(base)); +} + +static VALUE bignum_spec_rb_big2ulong(VALUE self, VALUE num) { + return ULONG2NUM(rb_big2ulong(num)); +} + +static VALUE bignum_spec_RBIGNUM_SIGN(VALUE self, VALUE val) { + return INT2FIX(RBIGNUM_SIGN(val)); +} + +static VALUE bignum_spec_rb_big_cmp(VALUE self, VALUE x, VALUE y) { + return rb_big_cmp(x, y); +} + +static VALUE bignum_spec_rb_big_pack(VALUE self, VALUE val) { + unsigned long buff; + + rb_big_pack(val, &buff, 1); + + return ULONG2NUM(buff); +} + +static VALUE bignum_spec_rb_big_pack_length(VALUE self, VALUE val) { + long long_len; + int leading_bits = 0; + int divisor = SIZEOF_LONG; + size_t len = rb_absint_size(val, &leading_bits); + if (leading_bits == 0) { + len += 1; + } + + long_len = len / divisor + ((len % divisor == 0) ? 0 : 1); + return LONG2NUM(long_len); +} + +static VALUE bignum_spec_rb_big_pack_array(VALUE self, VALUE val, VALUE len) { + int i; + long long_len = NUM2LONG(len); + + VALUE ary = rb_ary_new_capa(long_len); + unsigned long *buf = (unsigned long*) malloc(long_len * SIZEOF_LONG); + + /* The array should be filled with recognisable junk so we can check + it is all cleared properly. */ + + for (i = 0; i < long_len; i++) { +#if SIZEOF_LONG == 8 + buf[i] = 0xfedcba9876543210L; +#else + buf[i] = 0xfedcba98L; +#endif + } + + rb_big_pack(val, buf, long_len); + for (i = 0; i < long_len; i++) { + rb_ary_store(ary, i, ULONG2NUM(buf[i])); + } + free(buf); + return ary; +} + +void Init_bignum_spec(void) { + VALUE cls = rb_define_class("CApiBignumSpecs", rb_cObject); + rb_define_method(cls, "rb_big2dbl", bignum_spec_rb_big2dbl, 1); + rb_define_method(cls, "rb_dbl2big", bignum_spec_rb_dbl2big, 1); + rb_define_method(cls, "rb_big2ll", bignum_spec_rb_big2ll, 1); + rb_define_method(cls, "rb_big2long", bignum_spec_rb_big2long, 1); + rb_define_method(cls, "rb_big2str", bignum_spec_rb_big2str, 2); + rb_define_method(cls, "rb_big2ulong", bignum_spec_rb_big2ulong, 1); + rb_define_method(cls, "RBIGNUM_SIGN", bignum_spec_RBIGNUM_SIGN, 1); + rb_define_method(cls, "rb_big_cmp", bignum_spec_rb_big_cmp, 2); + rb_define_method(cls, "rb_big_pack", bignum_spec_rb_big_pack, 1); + rb_define_method(cls, "rb_big_pack_array", bignum_spec_rb_big_pack_array, 2); + rb_define_method(cls, "rb_big_pack_length", bignum_spec_rb_big_pack_length, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/binding_spec.c b/spec/ruby/optional/capi/ext/binding_spec.c new file mode 100644 index 0000000000..b2e3c88b6d --- /dev/null +++ b/spec/ruby/optional/capi/ext/binding_spec.c @@ -0,0 +1,19 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE binding_spec_get_binding(VALUE self) { + return rb_funcall(self, rb_intern("binding"), 0); +} + +void Init_binding_spec(void) { + VALUE cls = rb_define_class("CApiBindingSpecs", rb_cObject); + rb_define_method(cls, "get_binding", binding_spec_get_binding, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/boolean_spec.c b/spec/ruby/optional/capi/ext/boolean_spec.c new file mode 100644 index 0000000000..081cffa103 --- /dev/null +++ b/spec/ruby/optional/capi/ext/boolean_spec.c @@ -0,0 +1,33 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE boolean_spec_is_true(VALUE self, VALUE boolean) { + if (boolean) { + return INT2NUM(1); + } else { + return INT2NUM(2); + } +} + +static VALUE boolean_spec_q_true(VALUE self) { + return Qtrue; +} + +static VALUE boolean_spec_q_false(VALUE self) { + return Qfalse; +} + +void Init_boolean_spec(void) { + VALUE cls = rb_define_class("CApiBooleanSpecs", rb_cObject); + rb_define_method(cls, "is_true", boolean_spec_is_true, 1); + rb_define_method(cls, "q_true", boolean_spec_q_true, 0); + rb_define_method(cls, "q_false", boolean_spec_q_false, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c b/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c new file mode 100644 index 0000000000..cc5550f041 --- /dev/null +++ b/spec/ruby/optional/capi/ext/class_id_under_autoload_spec.c @@ -0,0 +1,13 @@ +#include "ruby.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void Init_class_id_under_autoload_spec(void) { + rb_define_class_id_under(rb_cObject, rb_intern("ClassIdUnderAutoload"), rb_cObject); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/class_spec.c b/spec/ruby/optional/capi/ext/class_spec.c new file mode 100644 index 0000000000..36b8c8f2f3 --- /dev/null +++ b/spec/ruby/optional/capi/ext/class_spec.c @@ -0,0 +1,194 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <stdio.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE class_spec_call_super_method(VALUE self) { + return rb_call_super(0, 0); +} + +static VALUE class_spec_define_call_super_method(VALUE self, VALUE obj, VALUE str_name) { + rb_define_method(obj, RSTRING_PTR(str_name), class_spec_call_super_method, 0); + return Qnil; +} + +static VALUE class_spec_rb_class_path(VALUE self, VALUE klass) { + return rb_class_path(klass); +} + +static VALUE class_spec_rb_class_name(VALUE self, VALUE klass) { + return rb_class_name(klass); +} + +static VALUE class_spec_rb_class2name(VALUE self, VALUE klass) { + return rb_str_new2( rb_class2name(klass) ); +} + +static VALUE class_spec_rb_path2class(VALUE self, VALUE path) { + return rb_path2class(RSTRING_PTR(path)); +} + +static VALUE class_spec_rb_path_to_class(VALUE self, VALUE path) { + return rb_path_to_class(path); +} + +static VALUE class_spec_rb_class_instance_methods(int argc, VALUE* argv, VALUE self) { + VALUE mod = argv[0]; + return rb_class_instance_methods(--argc, ++argv, mod); +} + +static VALUE class_spec_rb_class_public_instance_methods(int argc, VALUE* argv, VALUE self) { + VALUE mod = argv[0]; + return rb_class_public_instance_methods(--argc, ++argv, mod); +} + +static VALUE class_spec_rb_class_protected_instance_methods(int argc, VALUE* argv, VALUE self) { + VALUE mod = argv[0]; + return rb_class_protected_instance_methods(--argc, ++argv, mod); +} + +static VALUE class_spec_rb_class_private_instance_methods(int argc, VALUE* argv, VALUE self) { + VALUE mod = argv[0]; + return rb_class_private_instance_methods(--argc, ++argv, mod); +} + +static VALUE class_spec_rb_class_new(VALUE self, VALUE super) { + return rb_class_new(super); +} + +static VALUE class_spec_rb_class_new_instance(VALUE self, + VALUE nargs, VALUE args, + VALUE klass) { + int c_nargs = FIX2INT(nargs); + VALUE *c_args = (VALUE*)alloca(sizeof(VALUE) * c_nargs); + int i; + + for (i = 0; i < c_nargs; i++) + c_args[i] = rb_ary_entry(args, i); + + return rb_class_new_instance(c_nargs, c_args, klass); +} + +static VALUE class_spec_rb_class_real(VALUE self, VALUE object) { + if(rb_type_p(object, T_FIXNUM)) { + return INT2FIX(rb_class_real(FIX2INT(object))); + } else { + return rb_class_real(CLASS_OF(object)); + } +} + +static VALUE class_spec_rb_class_superclass(VALUE self, VALUE klass) { + return rb_class_superclass(klass); +} + +static VALUE class_spec_cvar_defined(VALUE self, VALUE klass, VALUE id) { + ID as_id = rb_intern(StringValuePtr(id)); + return rb_cvar_defined(klass, as_id); +} + +static VALUE class_spec_cvar_get(VALUE self, VALUE klass, VALUE name) { + return rb_cvar_get(klass, rb_intern(StringValuePtr(name))); +} + +static VALUE class_spec_cvar_set(VALUE self, VALUE klass, VALUE name, VALUE val) { + rb_cvar_set(klass, rb_intern(StringValuePtr(name)), val); + return Qnil; +} + +static VALUE class_spec_cv_get(VALUE self, VALUE klass, VALUE name) { + return rb_cv_get(klass, StringValuePtr(name)); +} + +static VALUE class_spec_cv_set(VALUE self, VALUE klass, VALUE name, VALUE val) { + rb_cv_set(klass, StringValuePtr(name), val); + + return Qnil; +} + +VALUE class_spec_define_attr(VALUE self, VALUE klass, VALUE sym, VALUE read, VALUE write) { + int int_read, int_write; + int_read = read == Qtrue ? 1 : 0; + int_write = write == Qtrue ? 1 : 0; + rb_define_attr(klass, rb_id2name(SYM2ID(sym)), int_read, int_write); + return Qnil; +} + +static VALUE class_spec_rb_define_class(VALUE self, VALUE name, VALUE super) { + if(NIL_P(super)) super = 0; + return rb_define_class(RSTRING_PTR(name), super); +} + +static VALUE class_spec_rb_define_class_under(VALUE self, VALUE outer, + VALUE name, VALUE super) { + if(NIL_P(super)) super = 0; + return rb_define_class_under(outer, RSTRING_PTR(name), super); +} + +static VALUE class_spec_rb_define_class_id_under(VALUE self, VALUE outer, + VALUE name, VALUE super) { + if(NIL_P(super)) super = 0; + return rb_define_class_id_under(outer, SYM2ID(name), super); +} + +static VALUE class_spec_define_class_variable(VALUE self, VALUE klass, VALUE name, VALUE val) { + rb_define_class_variable(klass, StringValuePtr(name), val); + return Qnil; +} + +static VALUE class_spec_include_module(VALUE self, VALUE klass, VALUE module) { + rb_include_module(klass, module); + return klass; +} + +static VALUE class_spec_method_var_args_1(int argc, VALUE *argv, VALUE self) { + VALUE ary = rb_ary_new(); + int i; + for (i = 0; i < argc; i++) { + rb_ary_push(ary, argv[i]); + } + return ary; +} + +static VALUE class_spec_method_var_args_2(VALUE self, VALUE argv) { + return argv; +} + +void Init_class_spec(void) { + VALUE cls = rb_define_class("CApiClassSpecs", rb_cObject); + rb_define_method(cls, "define_call_super_method", class_spec_define_call_super_method, 2); + rb_define_method(cls, "rb_class_path", class_spec_rb_class_path, 1); + rb_define_method(cls, "rb_class_name", class_spec_rb_class_name, 1); + rb_define_method(cls, "rb_class2name", class_spec_rb_class2name, 1); + rb_define_method(cls, "rb_path2class", class_spec_rb_path2class, 1); + rb_define_method(cls, "rb_path_to_class", class_spec_rb_path_to_class, 1); + rb_define_method(cls, "rb_class_instance_methods", class_spec_rb_class_instance_methods, -1); + rb_define_method(cls, "rb_class_public_instance_methods", class_spec_rb_class_public_instance_methods, -1); + rb_define_method(cls, "rb_class_protected_instance_methods", class_spec_rb_class_protected_instance_methods, -1); + rb_define_method(cls, "rb_class_private_instance_methods", class_spec_rb_class_private_instance_methods, -1); + rb_define_method(cls, "rb_class_new", class_spec_rb_class_new, 1); + rb_define_method(cls, "rb_class_new_instance", class_spec_rb_class_new_instance, 3); + rb_define_method(cls, "rb_class_real", class_spec_rb_class_real, 1); + rb_define_method(cls, "rb_class_superclass", class_spec_rb_class_superclass, 1); + rb_define_method(cls, "rb_cvar_defined", class_spec_cvar_defined, 2); + rb_define_method(cls, "rb_cvar_get", class_spec_cvar_get, 2); + rb_define_method(cls, "rb_cvar_set", class_spec_cvar_set, 3); + rb_define_method(cls, "rb_cv_get", class_spec_cv_get, 2); + rb_define_method(cls, "rb_cv_set", class_spec_cv_set, 3); + rb_define_method(cls, "rb_define_attr", class_spec_define_attr, 4); + rb_define_method(cls, "rb_define_class", class_spec_rb_define_class, 2); + rb_define_method(cls, "rb_define_class_under", class_spec_rb_define_class_under, 3); + rb_define_method(cls, "rb_define_class_id_under", class_spec_rb_define_class_id_under, 3); + rb_define_method(cls, "rb_define_class_variable", class_spec_define_class_variable, 3); + rb_define_method(cls, "rb_include_module", class_spec_include_module, 2); + rb_define_method(cls, "rb_method_varargs_1", class_spec_method_var_args_1, -1); + rb_define_method(cls, "rb_method_varargs_2", class_spec_method_var_args_2, -2); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/class_under_autoload_spec.c b/spec/ruby/optional/capi/ext/class_under_autoload_spec.c new file mode 100644 index 0000000000..e0b1f249c0 --- /dev/null +++ b/spec/ruby/optional/capi/ext/class_under_autoload_spec.c @@ -0,0 +1,13 @@ +#include "ruby.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void Init_class_under_autoload_spec(void) { + rb_define_class_under(rb_cObject, "ClassUnderAutoload", rb_cObject); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/complex_spec.c b/spec/ruby/optional/capi/ext/complex_spec.c new file mode 100644 index 0000000000..dfccd7a037 --- /dev/null +++ b/spec/ruby/optional/capi/ext/complex_spec.c @@ -0,0 +1,45 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE complex_spec_rb_Complex(VALUE self, VALUE num, VALUE den) { + return rb_Complex(num, den); +} + +static VALUE complex_spec_rb_Complex1(VALUE self, VALUE num) { + return rb_Complex1(num); +} + +static VALUE complex_spec_rb_Complex2(VALUE self, VALUE num, VALUE den) { + return rb_Complex2(num, den); +} + +static VALUE complex_spec_rb_complex_new(VALUE self, VALUE num, VALUE den) { + return rb_complex_new(num, den); +} + +static VALUE complex_spec_rb_complex_new1(VALUE self, VALUE num) { + return rb_complex_new1(num); +} + +static VALUE complex_spec_rb_complex_new2(VALUE self, VALUE num, VALUE den) { + return rb_complex_new2(num, den); +} + +void Init_complex_spec(void) { + VALUE cls = rb_define_class("CApiComplexSpecs", rb_cObject); + rb_define_method(cls, "rb_Complex", complex_spec_rb_Complex, 2); + rb_define_method(cls, "rb_Complex1", complex_spec_rb_Complex1, 1); + rb_define_method(cls, "rb_Complex2", complex_spec_rb_Complex2, 2); + rb_define_method(cls, "rb_complex_new", complex_spec_rb_complex_new, 2); + rb_define_method(cls, "rb_complex_new1", complex_spec_rb_complex_new1, 1); + rb_define_method(cls, "rb_complex_new2", complex_spec_rb_complex_new2, 2); +} + +#ifdef __cplusplus +} +#endif + diff --git a/spec/ruby/optional/capi/ext/constants_spec.c b/spec/ruby/optional/capi/ext/constants_spec.c new file mode 100644 index 0000000000..9aee8db37f --- /dev/null +++ b/spec/ruby/optional/capi/ext/constants_spec.c @@ -0,0 +1,178 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define defconstfunc(name) \ +static VALUE constants_spec_##name(VALUE self) { return name; } + +defconstfunc(rb_cArray) +defconstfunc(rb_cBasicObject) +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) +defconstfunc(rb_cEnumerator) +defconstfunc(rb_cFalseClass) +defconstfunc(rb_cFile) +defconstfunc(rb_mFileTest) +defconstfunc(rb_cFloat) +defconstfunc(rb_mGC) +defconstfunc(rb_cHash) +defconstfunc(rb_cInteger) +defconstfunc(rb_cIO) +defconstfunc(rb_mKernel) +defconstfunc(rb_mMath) +defconstfunc(rb_cMatch) +defconstfunc(rb_cMethod) +defconstfunc(rb_cModule) +defconstfunc(rb_cNilClass) +defconstfunc(rb_cNumeric) +defconstfunc(rb_cObject) +defconstfunc(rb_cProc) +defconstfunc(rb_mProcess) +defconstfunc(rb_cRandom) +defconstfunc(rb_cRange) +defconstfunc(rb_cRational) +defconstfunc(rb_cRegexp) +defconstfunc(rb_cStat) +defconstfunc(rb_cString) +defconstfunc(rb_cStruct) +defconstfunc(rb_cSymbol) +defconstfunc(rb_cTime) +defconstfunc(rb_cThread) +defconstfunc(rb_cTrueClass) +defconstfunc(rb_cUnboundMethod) +defconstfunc(rb_eArgError) +defconstfunc(rb_eEncodingError) +defconstfunc(rb_eEncCompatError) +defconstfunc(rb_eEOFError) +defconstfunc(rb_mErrno) +defconstfunc(rb_eException) +defconstfunc(rb_eFatal) +defconstfunc(rb_eFloatDomainError) +defconstfunc(rb_eFrozenError) +defconstfunc(rb_eIndexError) +defconstfunc(rb_eInterrupt) +defconstfunc(rb_eIOError) +defconstfunc(rb_eKeyError) +defconstfunc(rb_eLoadError) +defconstfunc(rb_eLocalJumpError) +defconstfunc(rb_eMathDomainError) +defconstfunc(rb_eNameError) +defconstfunc(rb_eNoMemError) +defconstfunc(rb_eNoMethodError) +defconstfunc(rb_eNotImpError) +defconstfunc(rb_eRangeError) +defconstfunc(rb_eRegexpError) +defconstfunc(rb_eRuntimeError) +defconstfunc(rb_eScriptError) +defconstfunc(rb_eSecurityError) +defconstfunc(rb_eSignal) +defconstfunc(rb_eStandardError) +defconstfunc(rb_eStopIteration) +defconstfunc(rb_eSyntaxError) +defconstfunc(rb_eSystemCallError) +defconstfunc(rb_eSystemExit) +defconstfunc(rb_eSysStackError) +defconstfunc(rb_eTypeError) +defconstfunc(rb_eThreadError) +defconstfunc(rb_mWaitReadable) +defconstfunc(rb_mWaitWritable) +defconstfunc(rb_eZeroDivError) + +void Init_constants_spec(void) { + VALUE cls = rb_define_class("CApiConstantsSpecs", rb_cObject); + rb_define_method(cls, "rb_cArray", constants_spec_rb_cArray, 0); + rb_define_method(cls, "rb_cBasicObject", constants_spec_rb_cBasicObject, 0); + rb_define_method(cls, "rb_cBinding", constants_spec_rb_cBinding, 0); + 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); + rb_define_method(cls, "rb_cEnumerator", constants_spec_rb_cEnumerator, 0); + rb_define_method(cls, "rb_cFalseClass", constants_spec_rb_cFalseClass, 0); + rb_define_method(cls, "rb_cFile", constants_spec_rb_cFile, 0); + rb_define_method(cls, "rb_mFileTest", constants_spec_rb_mFileTest, 0); + rb_define_method(cls, "rb_cFloat", constants_spec_rb_cFloat, 0); + rb_define_method(cls, "rb_mGC", constants_spec_rb_mGC, 0); + rb_define_method(cls, "rb_cHash", constants_spec_rb_cHash, 0); + rb_define_method(cls, "rb_cInteger", constants_spec_rb_cInteger, 0); + rb_define_method(cls, "rb_cIO", constants_spec_rb_cIO, 0); + rb_define_method(cls, "rb_mKernel", constants_spec_rb_mKernel, 0); + rb_define_method(cls, "rb_mMath", constants_spec_rb_mMath, 0); + rb_define_method(cls, "rb_cMatch", constants_spec_rb_cMatch, 0); + rb_define_method(cls, "rb_cMethod", constants_spec_rb_cMethod, 0); + rb_define_method(cls, "rb_cModule", constants_spec_rb_cModule, 0); + rb_define_method(cls, "rb_cNilClass", constants_spec_rb_cNilClass, 0); + rb_define_method(cls, "rb_cNumeric", constants_spec_rb_cNumeric, 0); + rb_define_method(cls, "rb_cObject", constants_spec_rb_cObject, 0); + rb_define_method(cls, "rb_cProc", constants_spec_rb_cProc, 0); + rb_define_method(cls, "rb_mProcess", constants_spec_rb_mProcess, 0); + rb_define_method(cls, "rb_cRandom", constants_spec_rb_cRandom, 0); + rb_define_method(cls, "rb_cRange", constants_spec_rb_cRange, 0); + rb_define_method(cls, "rb_cRational", constants_spec_rb_cRational, 0); + rb_define_method(cls, "rb_cRegexp", constants_spec_rb_cRegexp, 0); + rb_define_method(cls, "rb_cStat", constants_spec_rb_cStat, 0); + rb_define_method(cls, "rb_cString", constants_spec_rb_cString, 0); + rb_define_method(cls, "rb_cStruct", constants_spec_rb_cStruct, 0); + rb_define_method(cls, "rb_cSymbol", constants_spec_rb_cSymbol, 0); + rb_define_method(cls, "rb_cTime", constants_spec_rb_cTime, 0); + rb_define_method(cls, "rb_cThread", constants_spec_rb_cThread, 0); + rb_define_method(cls, "rb_cTrueClass", constants_spec_rb_cTrueClass, 0); + rb_define_method(cls, "rb_cUnboundMethod", constants_spec_rb_cUnboundMethod, 0); + rb_define_method(cls, "rb_eArgError", constants_spec_rb_eArgError, 0); + rb_define_method(cls, "rb_eEncodingError", constants_spec_rb_eEncodingError, 0); + rb_define_method(cls, "rb_eEncCompatError", constants_spec_rb_eEncCompatError, 0); + rb_define_method(cls, "rb_eEOFError", constants_spec_rb_eEOFError, 0); + rb_define_method(cls, "rb_mErrno", constants_spec_rb_mErrno, 0); + rb_define_method(cls, "rb_eException", constants_spec_rb_eException, 0); + rb_define_method(cls, "rb_eFatal", constants_spec_rb_eFatal, 0); + rb_define_method(cls, "rb_eFloatDomainError", constants_spec_rb_eFloatDomainError, 0); + rb_define_method(cls, "rb_eFrozenError", constants_spec_rb_eFrozenError, 0); + rb_define_method(cls, "rb_eIndexError", constants_spec_rb_eIndexError, 0); + rb_define_method(cls, "rb_eInterrupt", constants_spec_rb_eInterrupt, 0); + rb_define_method(cls, "rb_eIOError", constants_spec_rb_eIOError, 0); + rb_define_method(cls, "rb_eKeyError", constants_spec_rb_eKeyError, 0); + rb_define_method(cls, "rb_eLoadError", constants_spec_rb_eLoadError, 0); + rb_define_method(cls, "rb_eLocalJumpError", constants_spec_rb_eLocalJumpError, 0); + rb_define_method(cls, "rb_eMathDomainError", constants_spec_rb_eMathDomainError, 0); + rb_define_method(cls, "rb_eNameError", constants_spec_rb_eNameError, 0); + rb_define_method(cls, "rb_eNoMemError", constants_spec_rb_eNoMemError, 0); + rb_define_method(cls, "rb_eNoMethodError", constants_spec_rb_eNoMethodError, 0); + rb_define_method(cls, "rb_eNotImpError", constants_spec_rb_eNotImpError, 0); + rb_define_method(cls, "rb_eRangeError", constants_spec_rb_eRangeError, 0); + rb_define_method(cls, "rb_eRegexpError", constants_spec_rb_eRegexpError, 0); + rb_define_method(cls, "rb_eRuntimeError", constants_spec_rb_eRuntimeError, 0); + rb_define_method(cls, "rb_eScriptError", constants_spec_rb_eScriptError, 0); + rb_define_method(cls, "rb_eSecurityError", constants_spec_rb_eSecurityError, 0); + rb_define_method(cls, "rb_eSignal", constants_spec_rb_eSignal, 0); + rb_define_method(cls, "rb_eStandardError", constants_spec_rb_eStandardError, 0); + rb_define_method(cls, "rb_eStopIteration", constants_spec_rb_eStopIteration, 0); + rb_define_method(cls, "rb_eSyntaxError", constants_spec_rb_eSyntaxError, 0); + rb_define_method(cls, "rb_eSystemCallError", constants_spec_rb_eSystemCallError, 0); + rb_define_method(cls, "rb_eSystemExit", constants_spec_rb_eSystemExit, 0); + rb_define_method(cls, "rb_eSysStackError", constants_spec_rb_eSysStackError, 0); + rb_define_method(cls, "rb_eTypeError", constants_spec_rb_eTypeError, 0); + rb_define_method(cls, "rb_eThreadError", constants_spec_rb_eThreadError, 0); + rb_define_method(cls, "rb_mWaitReadable", constants_spec_rb_mWaitReadable, 0); + rb_define_method(cls, "rb_mWaitWritable", constants_spec_rb_mWaitWritable, 0); + rb_define_method(cls, "rb_eZeroDivError", constants_spec_rb_eZeroDivError, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/data_spec.c b/spec/ruby/optional/capi/ext/data_spec.c new file mode 100644 index 0000000000..ef069ef0ba --- /dev/null +++ b/spec/ruby/optional/capi/ext/data_spec.c @@ -0,0 +1,89 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct sample_wrapped_struct { + int foo; +}; + +void sample_wrapped_struct_free(void* st) { + free(st); +} + +void sample_wrapped_struct_mark(void* st) { +} + +VALUE sdaf_alloc_func(VALUE klass) { + struct sample_wrapped_struct* bar = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct)); + bar->foo = 42; + return Data_Wrap_Struct(klass, &sample_wrapped_struct_mark, &sample_wrapped_struct_free, bar); +} + +VALUE sdaf_get_struct(VALUE self) { + struct sample_wrapped_struct* bar; + Data_Get_Struct(self, struct sample_wrapped_struct, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_wrap_struct(VALUE self, VALUE val) { + struct sample_wrapped_struct* bar = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct)); + bar->foo = FIX2INT(val); + return Data_Wrap_Struct(rb_cObject, &sample_wrapped_struct_mark, &sample_wrapped_struct_free, bar); +} + +VALUE sws_get_struct(VALUE self, VALUE obj) { + struct sample_wrapped_struct* bar; + Data_Get_Struct(obj, struct sample_wrapped_struct, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_get_struct_rdata(VALUE self, VALUE obj) { + struct sample_wrapped_struct* bar; + bar = (struct sample_wrapped_struct*) RDATA(obj)->data; + return INT2FIX(bar->foo); +} + +VALUE sws_get_struct_data_ptr(VALUE self, VALUE obj) { + struct sample_wrapped_struct* bar; + bar = (struct sample_wrapped_struct*) DATA_PTR(obj); + return INT2FIX(bar->foo); +} + +VALUE sws_change_struct(VALUE self, VALUE obj, VALUE new_val) { + struct sample_wrapped_struct *old_struct, *new_struct; + new_struct = (struct sample_wrapped_struct*) malloc(sizeof(struct sample_wrapped_struct)); + new_struct->foo = FIX2INT(new_val); + old_struct = (struct sample_wrapped_struct*) RDATA(obj)->data; + free(old_struct); + RDATA(obj)->data = new_struct; + return Qnil; +} + +VALUE sws_rb_check_type(VALUE self, VALUE obj, VALUE other) { + rb_check_type(obj, TYPE(other)); + return Qtrue; +} + +void Init_data_spec(void) { + 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); + cls = rb_define_class("CApiWrappedStructSpecs", rb_cObject); + rb_define_method(cls, "wrap_struct", sws_wrap_struct, 1); + rb_define_method(cls, "get_struct", sws_get_struct, 1); + rb_define_method(cls, "get_struct_rdata", sws_get_struct_rdata, 1); + 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); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/debug_spec.c b/spec/ruby/optional/capi/ext/debug_spec.c new file mode 100644 index 0000000000..344dfc33fa --- /dev/null +++ b/spec/ruby/optional/capi/ext/debug_spec.c @@ -0,0 +1,93 @@ +#include "ruby.h" +#include "rubyspec.h" +#include "ruby/debug.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE callback_data = Qfalse; + +static VALUE rb_debug_inspector_open_callback(const rb_debug_inspector_t *dc, void *ptr) { + if (!dc) { + rb_raise(rb_eRuntimeError, "rb_debug_inspector_t should not be NULL"); + } + + VALUE locations = rb_debug_inspector_backtrace_locations(dc); + int len = RARRAY_LENINT(locations); + VALUE results = rb_ary_new2(len); + for (int i = 0; i < len; i++) { + VALUE ary = rb_ary_new2(5); // [self, klass, binding, iseq, backtrace_location] + rb_ary_store(ary, 0, rb_debug_inspector_frame_self_get(dc, i)); + rb_ary_store(ary, 1, rb_debug_inspector_frame_class_get(dc, i)); + rb_ary_store(ary, 2, rb_debug_inspector_frame_binding_get(dc, i)); + rb_ary_store(ary, 3, rb_debug_inspector_frame_iseq_get(dc, i)); + rb_ary_store(ary, 4, rb_ary_entry(locations, i)); + rb_ary_push(results, ary); + } + callback_data = (VALUE)ptr; + return results; +} + +static VALUE rb_debug_inspector_frame_self_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_self_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_class_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_class_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_binding_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_binding_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_iseq_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_iseq_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE debug_spec_callback_data(VALUE self){ + return callback_data; +} + +VALUE debug_spec_rb_debug_inspector_open(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_open_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_self_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_self_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_class_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_class_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_binding_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_binding_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_iseq_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_iseq_get_callback, (void *)index); +} + +static VALUE rb_debug_inspector_backtrace_locations_func(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_backtrace_locations(dc); +} + +VALUE debug_spec_rb_debug_inspector_backtrace_locations(VALUE self) { + return rb_debug_inspector_open(rb_debug_inspector_backtrace_locations_func, (void *)self); +} + +void Init_debug_spec(void) { + VALUE cls = rb_define_class("CApiDebugSpecs", rb_cObject); + rb_define_method(cls, "rb_debug_inspector_open", debug_spec_rb_debug_inspector_open, 1); + rb_define_method(cls, "rb_debug_inspector_frame_self_get", debug_spec_rb_debug_inspector_frame_self_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_class_get", debug_spec_rb_debug_inspector_frame_class_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_binding_get", debug_spec_rb_debug_inspector_frame_binding_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_iseq_get", debug_spec_rb_debug_inspector_frame_iseq_get, 1); + rb_define_method(cls, "rb_debug_inspector_backtrace_locations", debug_spec_rb_debug_inspector_backtrace_locations, 0); + rb_define_method(cls, "debug_spec_callback_data", debug_spec_callback_data, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/encoding_spec.c b/spec/ruby/optional/capi/ext/encoding_spec.c new file mode 100644 index 0000000000..cde4d0c351 --- /dev/null +++ b/spec/ruby/optional/capi/ext/encoding_spec.c @@ -0,0 +1,370 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include "ruby/encoding.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE encoding_spec_MBCLEN_CHARFOUND_P(VALUE self, VALUE obj) { + return INT2FIX(MBCLEN_CHARFOUND_P(FIX2INT(obj))); +} + +static VALUE encoding_spec_ENC_CODERANGE_ASCIIONLY(VALUE self, VALUE obj) { + if(ENC_CODERANGE_ASCIIONLY(obj)) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE encoding_spec_rb_usascii_encoding(VALUE self) { + return rb_str_new2(rb_usascii_encoding()->name); +} + +static VALUE encoding_spec_rb_usascii_encindex(VALUE self) { + return INT2NUM(rb_usascii_encindex()); +} + +static VALUE encoding_spec_rb_ascii8bit_encoding(VALUE self) { + return rb_str_new2(rb_ascii8bit_encoding()->name); +} + +static VALUE encoding_spec_rb_ascii8bit_encindex(VALUE self) { + return INT2NUM(rb_ascii8bit_encindex()); +} + +static VALUE encoding_spec_rb_utf8_encoding(VALUE self) { + return rb_str_new2(rb_utf8_encoding()->name); +} + +static VALUE encoding_spec_rb_utf8_encindex(VALUE self) { + return INT2NUM(rb_utf8_encindex()); +} + +static VALUE encoding_spec_rb_locale_encoding(VALUE self) { + return rb_str_new2(rb_locale_encoding()->name); +} + +static VALUE encoding_spec_rb_locale_encindex(VALUE self) { + return INT2NUM(rb_locale_encindex()); +} + +static VALUE encoding_spec_rb_filesystem_encoding(VALUE self) { + return rb_str_new2(rb_filesystem_encoding()->name); +} + +static VALUE encoding_spec_rb_filesystem_encindex(VALUE self) { + return INT2NUM(rb_filesystem_encindex()); +} + +static VALUE encoding_spec_rb_default_internal_encoding(VALUE self) { + rb_encoding* enc = rb_default_internal_encoding(); + if(enc == 0) return Qnil; + return rb_str_new2(enc->name); +} + +static VALUE encoding_spec_rb_default_external_encoding(VALUE self) { + rb_encoding* enc = rb_default_external_encoding(); + if(enc == 0) return Qnil; + return rb_str_new2(enc->name); +} + +#ifdef RUBY_VERSION_IS_2_6 +static VALUE encoding_spec_rb_enc_alias(VALUE self, VALUE alias, VALUE orig) { + return INT2NUM(rb_enc_alias(RSTRING_PTR(alias), RSTRING_PTR(orig))); +} +#endif + +static VALUE encoding_spec_rb_enc_associate(VALUE self, VALUE obj, VALUE enc) { + return rb_enc_associate(obj, NIL_P(enc) ? NULL : rb_enc_find(RSTRING_PTR(enc))); +} + +static VALUE encoding_spec_rb_enc_associate_index(VALUE self, VALUE obj, VALUE index) { + return rb_enc_associate_index(obj, FIX2INT(index)); +} + +static VALUE encoding_spec_rb_enc_compatible(VALUE self, VALUE a, VALUE b) { + rb_encoding* enc = rb_enc_compatible(a, b); + + if(!enc) return INT2FIX(0); + + return rb_enc_from_encoding(enc); +} + +static VALUE encoding_spec_rb_enc_copy(VALUE self, VALUE dest, VALUE src) { + rb_enc_copy(dest, src); + return dest; +} + +static VALUE encoding_spec_rb_enc_find(VALUE self, VALUE name) { + return rb_str_new2(rb_enc_find(RSTRING_PTR(name))->name); +} + +static VALUE encoding_spec_rb_enc_find_index(VALUE self, VALUE name) { + return INT2NUM(rb_enc_find_index(RSTRING_PTR(name))); +} + +static VALUE encoding_spec_rb_enc_isalnum(VALUE self, VALUE chr, VALUE encoding) { + rb_encoding *e = rb_to_encoding(encoding); + return rb_enc_isalnum(FIX2INT(chr), e) ? Qtrue : Qfalse; +} + +static VALUE encoding_spec_rb_enc_isspace(VALUE self, VALUE chr, VALUE encoding) { + rb_encoding *e = rb_to_encoding(encoding); + return rb_enc_isspace(FIX2INT(chr), e) ? Qtrue : Qfalse; +} + +static VALUE encoding_spec_rb_enc_from_index(VALUE self, VALUE index) { + return rb_str_new2(rb_enc_from_index(NUM2INT(index))->name); +} + +static VALUE encoding_spec_rb_enc_mbc_to_codepoint(VALUE self, VALUE str, VALUE offset) { + int o = FIX2INT(offset); + char *p = RSTRING_PTR(str); + char *e = p + o; + return INT2FIX(rb_enc_mbc_to_codepoint(p, e, rb_enc_get(str))); +} + +static VALUE encoding_spec_rb_enc_mbcput(VALUE self, VALUE code, VALUE encoding) { + unsigned int c = FIX2UINT(code); + rb_encoding *enc = rb_to_encoding(encoding); + char buf[ONIGENC_CODE_TO_MBC_MAXLEN]; + memset(buf, '\1', sizeof(buf)); + int len = rb_enc_mbcput(c, buf, enc); + if (buf[len] != '\1') { + rb_raise(rb_eRuntimeError, "should not change bytes after len"); + } + return rb_enc_str_new(buf, len, enc); +} + +static VALUE encoding_spec_rb_enc_from_encoding(VALUE self, VALUE name) { + return rb_enc_from_encoding(rb_enc_find(RSTRING_PTR(name))); +} + +static VALUE encoding_spec_rb_enc_get(VALUE self, VALUE obj) { + return rb_str_new2(rb_enc_get(obj)->name); +} + +static VALUE encoding_spec_rb_enc_precise_mbclen(VALUE self, VALUE str, VALUE offset) { + int o = FIX2INT(offset); + char *p = RSTRING_PTR(str); + char *e = p + o; + return INT2FIX(rb_enc_precise_mbclen(p, e, rb_enc_get(str))); +} + +static VALUE encoding_spec_rb_obj_encoding(VALUE self, VALUE obj) { + return rb_obj_encoding(obj); +} + +static VALUE encoding_spec_rb_enc_get_index(VALUE self, VALUE obj) { + return INT2NUM(rb_enc_get_index(obj)); +} + +static VALUE encoding_spec_rb_enc_set_index(VALUE self, VALUE obj, VALUE index) { + int i = NUM2INT(index); + + rb_encoding* enc = rb_enc_from_index(i); + rb_enc_set_index(obj, i); + + return rb_ary_new3(2, rb_str_new2(rb_enc_name(enc)), + rb_str_new2(rb_enc_name(rb_enc_get(obj)))); +} + +static VALUE encoding_spec_rb_enc_str_coderange(VALUE self, VALUE str) { + int coderange = rb_enc_str_coderange(str); + + switch(coderange) { + case ENC_CODERANGE_UNKNOWN: + return ID2SYM(rb_intern("coderange_unknown")); + case ENC_CODERANGE_7BIT: + return ID2SYM(rb_intern("coderange_7bit")); + case ENC_CODERANGE_VALID: + return ID2SYM(rb_intern("coderange_valid")); + case ENC_CODERANGE_BROKEN: + return ID2SYM(rb_intern("coderange_broken")); + default: + return ID2SYM(rb_intern("coderange_unrecognized")); + } +} + +static VALUE encoding_spec_rb_enc_str_new_cstr(VALUE self, VALUE str, VALUE enc) { + rb_encoding *e = rb_to_encoding(enc); + return rb_enc_str_new_cstr(StringValueCStr(str), e); +} + +static VALUE encoding_spec_rb_enc_str_new_cstr_constant(VALUE self, VALUE enc) { + if (NIL_P(enc)) { + rb_encoding *e = NULL; + return rb_enc_str_new_static("test string literal", strlen("test string literal"), e); + } else { + rb_encoding *e = rb_to_encoding(enc); + return rb_enc_str_new_cstr("test string literal", e); + } +} + +static VALUE encoding_spec_rb_enc_str_new(VALUE self, VALUE str, VALUE len, VALUE enc) { + return rb_enc_str_new(RSTRING_PTR(str), FIX2INT(len), rb_to_encoding(enc)); +} + +static VALUE encoding_spec_ENCODING_GET(VALUE self, VALUE obj) { + return INT2NUM(ENCODING_GET(obj)); +} + +static VALUE encoding_spec_ENCODING_SET(VALUE self, VALUE obj, VALUE index) { + int i = NUM2INT(index); + + rb_encoding* enc = rb_enc_from_index(i); + ENCODING_SET(obj, i); + + return rb_ary_new3(2, rb_str_new2(rb_enc_name(enc)), + rb_str_new2(rb_enc_name(rb_enc_get(obj)))); +} + +static VALUE encoding_spec_rb_enc_to_index(VALUE self, VALUE name) { + return INT2NUM(rb_enc_to_index(NIL_P(name) ? NULL : rb_enc_find(RSTRING_PTR(name)))); +} + +static VALUE encoding_spec_rb_to_encoding(VALUE self, VALUE obj) { + return rb_str_new2(rb_to_encoding(obj)->name); +} + +static rb_encoding** native_rb_encoding_pointer; + +static VALUE encoding_spec_rb_to_encoding_native_store(VALUE self, VALUE obj) { + rb_encoding* enc = rb_to_encoding(obj); + VALUE address = SIZET2NUM((size_t) native_rb_encoding_pointer); + *native_rb_encoding_pointer = enc; + return address; +} + +static VALUE encoding_spec_rb_to_encoding_native_name(VALUE self, VALUE address) { + rb_encoding** ptr = (rb_encoding**) NUM2SIZET(address); + rb_encoding* enc = *ptr; + return rb_str_new2(enc->name); +} + +static VALUE encoding_spec_rb_to_encoding_index(VALUE self, VALUE obj) { + return INT2NUM(rb_to_encoding_index(obj)); +} + +static VALUE encoding_spec_rb_enc_nth(VALUE self, VALUE str, VALUE index) { + char* start = RSTRING_PTR(str); + char* end = start + RSTRING_LEN(str); + char* ptr = rb_enc_nth(start, end, FIX2LONG(index), rb_enc_get(str)); + return LONG2NUM(ptr - start); +} + +static VALUE encoding_spec_rb_enc_codepoint_len(VALUE self, VALUE str) { + char* start = RSTRING_PTR(str); + char* end = start + RSTRING_LEN(str); + + int len; + unsigned int codepoint = rb_enc_codepoint_len(start, end, &len, rb_enc_get(str)); + + return rb_ary_new3(2, LONG2NUM(codepoint), LONG2NUM(len)); +} + +static VALUE encoding_spec_rb_enc_str_asciionly_p(VALUE self, VALUE str) { + if (rb_enc_str_asciionly_p(str)) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE encoding_spec_rb_uv_to_utf8(VALUE self, VALUE buf, VALUE num) { + return INT2NUM(rb_uv_to_utf8(RSTRING_PTR(buf), NUM2INT(num))); +} + +static VALUE encoding_spec_ONIGENC_MBC_CASE_FOLD(VALUE self, VALUE str) { + char *beg = RSTRING_PTR(str); + char *beg_initial = beg; + char *end = beg + 2; + OnigUChar fold[ONIGENC_GET_CASE_FOLD_CODES_MAX_NUM]; + memset(fold, '\1', sizeof(fold)); + rb_encoding *enc = rb_enc_get(str); + int r = ONIGENC_MBC_CASE_FOLD(enc, ONIGENC_CASE_FOLD, &beg, (const OnigUChar *)end, fold); + if (r > 0 && fold[r] != '\1') { + rb_raise(rb_eRuntimeError, "should not change bytes after len"); + } + VALUE str_result = r <= 0 ? Qnil : rb_enc_str_new((char *)fold, r, enc); + long bytes_used = beg - beg_initial; + return rb_ary_new3(2, str_result, INT2FIX(bytes_used)); +} + +static VALUE encoding_spec_rb_enc_codelen(VALUE self, VALUE code, VALUE encoding) { + unsigned int c = FIX2UINT(code); + rb_encoding *enc = rb_to_encoding(encoding); + return INT2FIX(rb_enc_codelen(c, enc)); +} + +void Init_encoding_spec(void) { + VALUE cls; + native_rb_encoding_pointer = (rb_encoding**) malloc(sizeof(rb_encoding*)); + + cls = rb_define_class("CApiEncodingSpecs", rb_cObject); + rb_define_method(cls, "ENC_CODERANGE_ASCIIONLY", + encoding_spec_ENC_CODERANGE_ASCIIONLY, 1); + + rb_define_method(cls, "rb_usascii_encoding", encoding_spec_rb_usascii_encoding, 0); + rb_define_method(cls, "rb_usascii_encindex", encoding_spec_rb_usascii_encindex, 0); + rb_define_method(cls, "rb_ascii8bit_encoding", encoding_spec_rb_ascii8bit_encoding, 0); + rb_define_method(cls, "rb_ascii8bit_encindex", encoding_spec_rb_ascii8bit_encindex, 0); + rb_define_method(cls, "rb_utf8_encoding", encoding_spec_rb_utf8_encoding, 0); + rb_define_method(cls, "rb_utf8_encindex", encoding_spec_rb_utf8_encindex, 0); + rb_define_method(cls, "rb_locale_encoding", encoding_spec_rb_locale_encoding, 0); + rb_define_method(cls, "rb_locale_encindex", encoding_spec_rb_locale_encindex, 0); + rb_define_method(cls, "rb_filesystem_encoding", encoding_spec_rb_filesystem_encoding, 0); + rb_define_method(cls, "rb_filesystem_encindex", encoding_spec_rb_filesystem_encindex, 0); + rb_define_method(cls, "rb_default_internal_encoding", + encoding_spec_rb_default_internal_encoding, 0); + + rb_define_method(cls, "rb_default_external_encoding", + encoding_spec_rb_default_external_encoding, 0); + +#ifdef RUBY_VERSION_IS_2_6 + rb_define_method(cls, "rb_enc_alias", encoding_spec_rb_enc_alias, 2); +#endif + + rb_define_method(cls, "MBCLEN_CHARFOUND_P", encoding_spec_MBCLEN_CHARFOUND_P, 1); + rb_define_method(cls, "rb_enc_associate", encoding_spec_rb_enc_associate, 2); + rb_define_method(cls, "rb_enc_associate_index", encoding_spec_rb_enc_associate_index, 2); + rb_define_method(cls, "rb_enc_compatible", encoding_spec_rb_enc_compatible, 2); + rb_define_method(cls, "rb_enc_copy", encoding_spec_rb_enc_copy, 2); + rb_define_method(cls, "rb_enc_codelen", encoding_spec_rb_enc_codelen, 2); + rb_define_method(cls, "rb_enc_find", encoding_spec_rb_enc_find, 1); + rb_define_method(cls, "rb_enc_find_index", encoding_spec_rb_enc_find_index, 1); + rb_define_method(cls, "rb_enc_isalnum", encoding_spec_rb_enc_isalnum, 2); + rb_define_method(cls, "rb_enc_isspace", encoding_spec_rb_enc_isspace, 2); + rb_define_method(cls, "rb_enc_from_index", encoding_spec_rb_enc_from_index, 1); + rb_define_method(cls, "rb_enc_mbc_to_codepoint", encoding_spec_rb_enc_mbc_to_codepoint, 2); + rb_define_method(cls, "rb_enc_mbcput", encoding_spec_rb_enc_mbcput, 2); + rb_define_method(cls, "rb_enc_from_encoding", encoding_spec_rb_enc_from_encoding, 1); + rb_define_method(cls, "rb_enc_get", encoding_spec_rb_enc_get, 1); + rb_define_method(cls, "rb_enc_precise_mbclen", encoding_spec_rb_enc_precise_mbclen, 2); + rb_define_method(cls, "rb_obj_encoding", encoding_spec_rb_obj_encoding, 1); + rb_define_method(cls, "rb_enc_get_index", encoding_spec_rb_enc_get_index, 1); + rb_define_method(cls, "rb_enc_set_index", encoding_spec_rb_enc_set_index, 2); + rb_define_method(cls, "rb_enc_str_coderange", encoding_spec_rb_enc_str_coderange, 1); + rb_define_method(cls, "rb_enc_str_new_cstr", encoding_spec_rb_enc_str_new_cstr, 2); + rb_define_method(cls, "rb_enc_str_new_cstr_constant", encoding_spec_rb_enc_str_new_cstr_constant, 1); + rb_define_method(cls, "rb_enc_str_new", encoding_spec_rb_enc_str_new, 3); + rb_define_method(cls, "ENCODING_GET", encoding_spec_ENCODING_GET, 1); + rb_define_method(cls, "ENCODING_SET", encoding_spec_ENCODING_SET, 2); + rb_define_method(cls, "rb_enc_to_index", encoding_spec_rb_enc_to_index, 1); + rb_define_method(cls, "rb_to_encoding", encoding_spec_rb_to_encoding, 1); + rb_define_method(cls, "rb_to_encoding_native_store", encoding_spec_rb_to_encoding_native_store, 1); + rb_define_method(cls, "rb_to_encoding_native_name", encoding_spec_rb_to_encoding_native_name, 1); + rb_define_method(cls, "rb_to_encoding_index", encoding_spec_rb_to_encoding_index, 1); + rb_define_method(cls, "rb_enc_nth", encoding_spec_rb_enc_nth, 2); + rb_define_method(cls, "rb_enc_codepoint_len", encoding_spec_rb_enc_codepoint_len, 1); + rb_define_method(cls, "rb_enc_str_asciionly_p", encoding_spec_rb_enc_str_asciionly_p, 1); + rb_define_method(cls, "rb_uv_to_utf8", encoding_spec_rb_uv_to_utf8, 2); + rb_define_method(cls, "ONIGENC_MBC_CASE_FOLD", encoding_spec_ONIGENC_MBC_CASE_FOLD, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/enumerator_spec.c b/spec/ruby/optional/capi/ext/enumerator_spec.c new file mode 100644 index 0000000000..917621c003 --- /dev/null +++ b/spec/ruby/optional/capi/ext/enumerator_spec.c @@ -0,0 +1,32 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE enumerator_spec_rb_enumeratorize(int argc, VALUE *argv, VALUE self) { + VALUE obj, meth, args; + rb_scan_args(argc, argv, "2*", &obj, &meth, &args); + return rb_enumeratorize(obj, meth, (int)RARRAY_LEN(args), RARRAY_PTR(args)); +} + +VALUE enumerator_spec_size_fn(VALUE obj, VALUE args, VALUE anEnum) { + return INT2NUM(7); +} + +VALUE enumerator_spec_rb_enumeratorize_with_size(int argc, VALUE *argv, VALUE self) { + VALUE obj, meth, args; + rb_scan_args(argc, argv, "2*", &obj, &meth, &args); + return rb_enumeratorize_with_size(obj, meth, (int)RARRAY_LEN(args), RARRAY_PTR(args), enumerator_spec_size_fn); +} + +void Init_enumerator_spec(void) { + VALUE cls = rb_define_class("CApiEnumeratorSpecs", rb_cObject); + rb_define_method(cls, "rb_enumeratorize", enumerator_spec_rb_enumeratorize, -1); + rb_define_method(cls, "rb_enumeratorize_with_size", enumerator_spec_rb_enumeratorize_with_size, -1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/exception_spec.c b/spec/ruby/optional/capi/ext/exception_spec.c new file mode 100644 index 0000000000..e1114aabb8 --- /dev/null +++ b/spec/ruby/optional/capi/ext/exception_spec.c @@ -0,0 +1,59 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <stdio.h> +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE exception_spec_rb_errinfo(VALUE self) { + return rb_errinfo(); +} + +VALUE exception_spec_rb_exc_new(VALUE self, VALUE str) { + char *cstr = StringValuePtr(str); + return rb_exc_new(rb_eException, cstr, strlen(cstr)); +} + +VALUE exception_spec_rb_exc_new2(VALUE self, VALUE str) { + char *cstr = StringValuePtr(str); + return rb_exc_new2(rb_eException, cstr); +} + +VALUE exception_spec_rb_exc_new3(VALUE self, VALUE str) { + return rb_exc_new3(rb_eException, str); +} + +VALUE exception_spec_rb_exc_raise(VALUE self, VALUE exc) { + if (self != Qundef) rb_exc_raise(exc); + return Qnil; +} + +VALUE exception_spec_rb_set_errinfo(VALUE self, VALUE exc) { + rb_set_errinfo(exc); + return Qnil; +} + + +VALUE exception_spec_rb_make_exception(VALUE self, VALUE ary) { + int argc = RARRAY_LENINT(ary); + VALUE *argv = RARRAY_PTR(ary); + return rb_make_exception(argc, argv); +} + +void Init_exception_spec(void) { + VALUE cls = rb_define_class("CApiExceptionSpecs", rb_cObject); + rb_define_method(cls, "rb_errinfo", exception_spec_rb_errinfo, 0); + rb_define_method(cls, "rb_exc_new", exception_spec_rb_exc_new, 1); + rb_define_method(cls, "rb_exc_new2", exception_spec_rb_exc_new2, 1); + rb_define_method(cls, "rb_exc_new3", exception_spec_rb_exc_new3, 1); + 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_make_exception", exception_spec_rb_make_exception, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/fiber_spec.c b/spec/ruby/optional/capi/ext/fiber_spec.c new file mode 100644 index 0000000000..f06a54494e --- /dev/null +++ b/spec/ruby/optional/capi/ext/fiber_spec.c @@ -0,0 +1,69 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE fiber_spec_rb_fiber_current(VALUE self) { + return rb_fiber_current(); +} + +VALUE fiber_spec_rb_fiber_alive_p(VALUE self, VALUE fiber) { + return rb_fiber_alive_p(fiber); +} + +VALUE fiber_spec_rb_fiber_resume(VALUE self, VALUE fiber, VALUE ary) { + long argc = RARRAY_LEN(ary); + VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc); + int i; + + for (i = 0; i < argc; i++) { + argv[i] = rb_ary_entry(ary, i); + } + + return rb_fiber_resume(fiber, (int)argc, argv); +} + +VALUE fiber_spec_rb_fiber_yield(VALUE self, VALUE ary) { + long argc = RARRAY_LEN(ary); + VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc); + int i; + + for (i = 0; i < argc; i++) { + argv[i] = rb_ary_entry(ary, i); + } + return rb_fiber_yield((int)argc, argv); +} + +VALUE fiber_spec_rb_fiber_new_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, dummy)) { + return rb_funcall(args, rb_intern("inspect"), 0); +} + +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); + rb_define_method(cls, "rb_fiber_current", fiber_spec_rb_fiber_current, 0); + rb_define_method(cls, "rb_fiber_alive_p", fiber_spec_rb_fiber_alive_p, 1); + 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 +} +#endif diff --git a/spec/ruby/optional/capi/ext/file_spec.c b/spec/ruby/optional/capi/ext/file_spec.c new file mode 100644 index 0000000000..cd4a653765 --- /dev/null +++ b/spec/ruby/optional/capi/ext/file_spec.c @@ -0,0 +1,29 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE file_spec_rb_file_open(VALUE self, VALUE name, VALUE mode) { + return rb_file_open(RSTRING_PTR(name), RSTRING_PTR(mode)); +} + +VALUE file_spec_rb_file_open_str(VALUE self, VALUE name, VALUE mode) { + return rb_file_open_str(name, RSTRING_PTR(mode)); +} + +VALUE file_spec_FilePathValue(VALUE self, VALUE obj) { + return FilePathValue(obj); +} + +void Init_file_spec(void) { + VALUE cls = rb_define_class("CApiFileSpecs", rb_cObject); + rb_define_method(cls, "rb_file_open", file_spec_rb_file_open, 2); + rb_define_method(cls, "rb_file_open_str", file_spec_rb_file_open_str, 2); + rb_define_method(cls, "FilePathValue", file_spec_FilePathValue, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/fixnum_spec.c b/spec/ruby/optional/capi/ext/fixnum_spec.c new file mode 100644 index 0000000000..7048ce3f13 --- /dev/null +++ b/spec/ruby/optional/capi/ext/fixnum_spec.c @@ -0,0 +1,26 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE fixnum_spec_FIX2INT(VALUE self, VALUE value) { + int i = FIX2INT(value); + return INT2NUM(i); +} + +static VALUE fixnum_spec_FIX2UINT(VALUE self, VALUE value) { + unsigned int i = FIX2UINT(value); + return UINT2NUM(i); +} + +void Init_fixnum_spec(void) { + VALUE cls = rb_define_class("CApiFixnumSpecs", rb_cObject); + rb_define_method(cls, "FIX2INT", fixnum_spec_FIX2INT, 1); + rb_define_method(cls, "FIX2UINT", fixnum_spec_FIX2UINT, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/float_spec.c b/spec/ruby/optional/capi/ext/float_spec.c new file mode 100644 index 0000000000..3db05cef8c --- /dev/null +++ b/spec/ruby/optional/capi/ext/float_spec.c @@ -0,0 +1,47 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <math.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE float_spec_new_zero(VALUE self) { + double flt = 0; + return rb_float_new(flt); +} + +static VALUE float_spec_new_point_five(VALUE self) { + double flt = 0.555; + return rb_float_new(flt); +} + +static VALUE float_spec_rb_Float(VALUE self, VALUE float_str) { + return rb_Float(float_str); +} + +static VALUE float_spec_RFLOAT_VALUE(VALUE self, VALUE float_h) { + return rb_float_new(RFLOAT_VALUE(float_h)); +} + +static VALUE float_spec_RB_FLOAT_TYPE_P(VALUE self, VALUE val) { + if (RB_FLOAT_TYPE_P(val)) { + return Qtrue; + } else { + return Qfalse; + } +} + +void Init_float_spec(void) { + VALUE cls = rb_define_class("CApiFloatSpecs", rb_cObject); + rb_define_method(cls, "new_zero", float_spec_new_zero, 0); + rb_define_method(cls, "new_point_five", float_spec_new_point_five, 0); + rb_define_method(cls, "rb_Float", float_spec_rb_Float, 1); + rb_define_method(cls, "RFLOAT_VALUE", float_spec_RFLOAT_VALUE, 1); + rb_define_method(cls, "RB_FLOAT_TYPE_P", float_spec_RB_FLOAT_TYPE_P, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/gc_spec.c b/spec/ruby/optional/capi/ext/gc_spec.c new file mode 100644 index 0000000000..7dc9c347c7 --- /dev/null +++ b/spec/ruby/optional/capi/ext/gc_spec.c @@ -0,0 +1,66 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE registered_tagged_value; +VALUE registered_reference_value; + +static VALUE registered_tagged_address(VALUE self) { + return registered_tagged_value; +} + +static VALUE registered_reference_address(VALUE self) { + return registered_reference_value; +} + +static VALUE gc_spec_rb_gc_enable(VALUE self) { + return rb_gc_enable(); +} + +static VALUE gc_spec_rb_gc_disable(VALUE self) { + return rb_gc_disable(); +} + +static VALUE gc_spec_rb_gc(VALUE self) { + rb_gc(); + return Qnil; +} + +static VALUE gc_spec_rb_gc_latest_gc_info(VALUE self, VALUE hash_or_key){ + return rb_gc_latest_gc_info(hash_or_key); +} + +static VALUE gc_spec_rb_gc_adjust_memory_usage(VALUE self, VALUE diff) { + rb_gc_adjust_memory_usage(NUM2SSIZET(diff)); + return Qnil; +} + +static VALUE gc_spec_rb_gc_register_mark_object(VALUE self, VALUE obj) { + rb_gc_register_mark_object(obj); + return Qnil; +} + +void Init_gc_spec(void) { + VALUE cls = rb_define_class("CApiGCSpecs", rb_cObject); + registered_tagged_value = INT2NUM(10); + registered_reference_value = rb_str_new2("Globally registered data"); + + rb_gc_register_address(®istered_tagged_value); + rb_gc_register_address(®istered_reference_value); + + rb_define_method(cls, "registered_tagged_address", registered_tagged_address, 0); + rb_define_method(cls, "registered_reference_address", registered_reference_address, 0); + rb_define_method(cls, "rb_gc_enable", gc_spec_rb_gc_enable, 0); + rb_define_method(cls, "rb_gc_disable", gc_spec_rb_gc_disable, 0); + rb_define_method(cls, "rb_gc", gc_spec_rb_gc, 0); + rb_define_method(cls, "rb_gc_adjust_memory_usage", gc_spec_rb_gc_adjust_memory_usage, 1); + rb_define_method(cls, "rb_gc_register_mark_object", gc_spec_rb_gc_register_mark_object, 1); + rb_define_method(cls, "rb_gc_latest_gc_info", gc_spec_rb_gc_latest_gc_info, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/globals_spec.c b/spec/ruby/optional/capi/ext/globals_spec.c new file mode 100644 index 0000000000..28a9633f98 --- /dev/null +++ b/spec/ruby/optional/capi/ext/globals_spec.c @@ -0,0 +1,127 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE g_hooked_var; + +VALUE var_2x_getter(ID id, VALUE *data) { + return *data; +} + +void var_2x_setter(VALUE val, ID id, VALUE *var) { + *var = INT2NUM(NUM2INT(val) * 2); +} + +static VALUE sb_define_hooked_variable(VALUE self, VALUE var_name) { + rb_define_hooked_variable(StringValuePtr(var_name), &g_hooked_var, var_2x_getter, var_2x_setter); + return Qnil; +} + +VALUE g_ro_var; + +static VALUE sb_define_readonly_variable(VALUE self, VALUE var_name, VALUE val) { + g_ro_var = val; + rb_define_readonly_variable(StringValuePtr(var_name), &g_ro_var); + return Qnil; +} + +VALUE g_var; + +static VALUE sb_get_global_value(VALUE self) { + return g_var; +} + +static VALUE sb_define_variable(VALUE self, VALUE var_name, VALUE val) { + g_var = val; + rb_define_variable(StringValuePtr(var_name), &g_var); + return Qnil; +} + +static VALUE sb_f_global_variables(VALUE self) { + return rb_f_global_variables(); +} + +static VALUE sb_gv_get(VALUE self, VALUE var) { + return rb_gv_get(StringValuePtr(var)); +} + +static VALUE sb_gv_set(VALUE self, VALUE var, VALUE val) { + return rb_gv_set(StringValuePtr(var), val); +} + +static VALUE global_spec_rb_stdin(VALUE self) { + return rb_stdin; +} + +static VALUE global_spec_rb_stdout(VALUE self) { + return rb_stdout; +} + +static VALUE global_spec_rb_stderr(VALUE self) { + return rb_stderr; +} + +static VALUE global_spec_rb_defout(VALUE self) { + return rb_defout; +} + +static VALUE global_spec_rb_fs(VALUE self) { + return rb_fs; +} + +static VALUE global_spec_rb_rs(VALUE self) { + return rb_rs; +} + +static VALUE global_spec_rb_default_rs(VALUE self) { + return rb_default_rs; +} + +static VALUE global_spec_rb_output_rs(VALUE self) { + return rb_output_rs; +} + +static VALUE global_spec_rb_output_fs(VALUE self) { + return rb_output_fs; +} + +static VALUE global_spec_rb_lastline_set(VALUE self, VALUE line) { + rb_lastline_set(line); + return Qnil; +} + +static VALUE global_spec_rb_lastline_get(VALUE self) { + return rb_lastline_get(); +} + +void Init_globals_spec(void) { + VALUE cls = rb_define_class("CApiGlobalSpecs", rb_cObject); + g_hooked_var = Qnil; + rb_define_method(cls, "rb_define_hooked_variable_2x", sb_define_hooked_variable, 1); + g_ro_var = Qnil; + rb_define_method(cls, "rb_define_readonly_variable", sb_define_readonly_variable, 2); + g_var = Qnil; + rb_define_method(cls, "rb_define_variable", sb_define_variable, 2); + rb_define_method(cls, "sb_get_global_value", sb_get_global_value, 0); + rb_define_method(cls, "rb_f_global_variables", sb_f_global_variables, 0); + rb_define_method(cls, "sb_gv_get", sb_gv_get, 1); + rb_define_method(cls, "sb_gv_set", sb_gv_set, 2); + rb_define_method(cls, "rb_stdin", global_spec_rb_stdin, 0); + rb_define_method(cls, "rb_stdout", global_spec_rb_stdout, 0); + rb_define_method(cls, "rb_stderr", global_spec_rb_stderr, 0); + rb_define_method(cls, "rb_defout", global_spec_rb_defout, 0); + rb_define_method(cls, "rb_fs", global_spec_rb_fs, 0); + rb_define_method(cls, "rb_rs", global_spec_rb_rs, 0); + rb_define_method(cls, "rb_default_rs", global_spec_rb_default_rs, 0); + rb_define_method(cls, "rb_output_rs", global_spec_rb_output_rs, 0); + rb_define_method(cls, "rb_output_fs", global_spec_rb_output_fs, 0); + rb_define_method(cls, "rb_lastline_set", global_spec_rb_lastline_set, 1); + rb_define_method(cls, "rb_lastline_get", global_spec_rb_lastline_get, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/hash_spec.c b/spec/ruby/optional/capi/ext/hash_spec.c new file mode 100644 index 0000000000..7f38708915 --- /dev/null +++ b/spec/ruby/optional/capi/ext/hash_spec.c @@ -0,0 +1,160 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE hash_spec_rb_hash(VALUE self, VALUE hash) { + return rb_hash(hash); +} + +VALUE hash_spec_rb_Hash(VALUE self, VALUE val) { + return rb_Hash(val); +} + +VALUE hash_spec_rb_hash_dup(VALUE self, VALUE hash) { + return rb_hash_dup(hash); +} + +VALUE hash_spec_rb_hash_fetch(VALUE self, VALUE hash, VALUE key) { + return rb_hash_fetch(hash, key); +} + +VALUE hash_spec_rb_hash_freeze(VALUE self, VALUE hash) { + return rb_hash_freeze(hash); +} + +VALUE hash_spec_rb_hash_aref(VALUE self, VALUE hash, VALUE key) { + return rb_hash_aref(hash, key); +} + +VALUE hash_spec_rb_hash_aref_nil(VALUE self, VALUE hash, VALUE key) { + VALUE ret = rb_hash_aref(hash, key); + return NIL_P(ret) ? Qtrue : Qfalse; +} + +VALUE hash_spec_rb_hash_aset(VALUE self, VALUE hash, VALUE key, VALUE val) { + return rb_hash_aset(hash, key, val); +} + +VALUE hash_spec_rb_hash_clear(VALUE self, VALUE hash) { + return rb_hash_clear(hash); +} + +VALUE hash_spec_rb_hash_delete(VALUE self, VALUE hash, VALUE key) { + return rb_hash_delete(hash, key); +} + +VALUE hash_spec_rb_hash_delete_if(VALUE self, VALUE hash) { + return rb_hash_delete_if(hash); +} + +static int foreach_i(VALUE key, VALUE val, VALUE other) { + rb_hash_aset(other, key, val); + return 0; /* ST_CONTINUE; */ +} + +static int foreach_stop_i(VALUE key, VALUE val, VALUE other) { + rb_hash_aset(other, key, val); + return 1; /* ST_STOP; */ +} + +static int foreach_delete_i(VALUE key, VALUE val, VALUE other) { + rb_hash_aset(other, key, val); + return 2; /* ST_DELETE; */ +} + +VALUE hash_spec_rb_hash_foreach(VALUE self, VALUE hsh) { + VALUE other = rb_hash_new(); + rb_hash_foreach(hsh, foreach_i, other); + return other; +} + +VALUE hash_spec_rb_hash_foreach_stop(VALUE self, VALUE hsh) { + VALUE other = rb_hash_new(); + rb_hash_foreach(hsh, foreach_stop_i, other); + return other; +} + +VALUE hash_spec_rb_hash_foreach_delete(VALUE self, VALUE hsh) { + VALUE other = rb_hash_new(); + rb_hash_foreach(hsh, foreach_delete_i, other); + return other; +} + +VALUE hash_spec_rb_hash_lookup(VALUE self, VALUE hash, VALUE key) { + return rb_hash_lookup(hash, key); +} + +VALUE hash_spec_rb_hash_lookup_nil(VALUE self, VALUE hash, VALUE key) { + VALUE ret = rb_hash_lookup(hash, key); + return ret == Qnil ? Qtrue : Qfalse; +} + +VALUE hash_spec_rb_hash_lookup2(VALUE self, VALUE hash, VALUE key, VALUE def) { + return rb_hash_lookup2(hash, key, def); +} + +VALUE hash_spec_rb_hash_lookup2_default_undef(VALUE self, VALUE hash, VALUE key) { + VALUE ret = rb_hash_lookup2(hash, key, Qundef); + return ret == Qundef ? Qtrue : Qfalse; +} + +VALUE hash_spec_rb_hash_new(VALUE self) { + return rb_hash_new(); +} + +VALUE rb_ident_hash_new(void); /* internal.h, used in ripper */ + +VALUE hash_spec_rb_ident_hash_new(VALUE self) { + return rb_ident_hash_new(); +} + +VALUE hash_spec_rb_hash_size(VALUE self, VALUE hash) { + return rb_hash_size(hash); +} + +VALUE hash_spec_rb_hash_set_ifnone(VALUE self, VALUE hash, VALUE def) { + return rb_hash_set_ifnone(hash, def); +} + +VALUE hash_spec_compute_a_hash_code(VALUE self, VALUE seed) { + int int_seed = FIX2INT(seed); + st_index_t h = rb_hash_start(int_seed); + h = rb_hash_uint32(h, 540u); + h = rb_hash_uint32(h, 340u); + h = rb_hash_end(h); + return ULONG2NUM(h); +} + +void Init_hash_spec(void) { + VALUE cls = rb_define_class("CApiHashSpecs", rb_cObject); + rb_define_method(cls, "rb_hash", hash_spec_rb_hash, 1); + rb_define_method(cls, "rb_Hash", hash_spec_rb_Hash, 1); + rb_define_method(cls, "rb_hash_dup", hash_spec_rb_hash_dup, 1); + rb_define_method(cls, "rb_hash_freeze", hash_spec_rb_hash_freeze, 1); + rb_define_method(cls, "rb_hash_aref", hash_spec_rb_hash_aref, 2); + rb_define_method(cls, "rb_hash_aref_nil", hash_spec_rb_hash_aref_nil, 2); + rb_define_method(cls, "rb_hash_aset", hash_spec_rb_hash_aset, 3); + rb_define_method(cls, "rb_hash_clear", hash_spec_rb_hash_clear, 1); + rb_define_method(cls, "rb_hash_delete", hash_spec_rb_hash_delete, 2); + rb_define_method(cls, "rb_hash_delete_if", hash_spec_rb_hash_delete_if, 1); + rb_define_method(cls, "rb_hash_fetch", hash_spec_rb_hash_fetch, 2); + rb_define_method(cls, "rb_hash_foreach", hash_spec_rb_hash_foreach, 1); + rb_define_method(cls, "rb_hash_foreach_stop", hash_spec_rb_hash_foreach_stop, 1); + rb_define_method(cls, "rb_hash_foreach_delete", hash_spec_rb_hash_foreach_delete, 1); + rb_define_method(cls, "rb_hash_lookup_nil", hash_spec_rb_hash_lookup_nil, 2); + rb_define_method(cls, "rb_hash_lookup", hash_spec_rb_hash_lookup, 2); + 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); + 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); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/integer_spec.c b/spec/ruby/optional/capi/ext/integer_spec.c new file mode 100644 index 0000000000..16cd95f111 --- /dev/null +++ b/spec/ruby/optional/capi/ext/integer_spec.c @@ -0,0 +1,41 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE integer_spec_rb_integer_pack(VALUE self, VALUE value, + VALUE words, VALUE numwords, VALUE wordsize, VALUE nails, VALUE flags) +{ + int result = rb_integer_pack(value, (void*)RSTRING_PTR(words), FIX2INT(numwords), + FIX2INT(wordsize), FIX2INT(nails), FIX2INT(flags)); + return INT2FIX(result); +} + +RUBY_EXTERN VALUE rb_int_positive_pow(long x, unsigned long y); /* internal.h, used in ripper */ + +static VALUE integer_spec_rb_int_positive_pow(VALUE self, VALUE a, VALUE b){ + return rb_int_positive_pow(FIX2INT(a), FIX2INT(b)); +} + +void Init_integer_spec(void) { + VALUE cls = rb_define_class("CApiIntegerSpecs", rb_cObject); + rb_define_const(cls, "MSWORD", INT2NUM(INTEGER_PACK_MSWORD_FIRST)); + rb_define_const(cls, "LSWORD", INT2NUM(INTEGER_PACK_LSWORD_FIRST)); + rb_define_const(cls, "MSBYTE", INT2NUM(INTEGER_PACK_MSBYTE_FIRST)); + rb_define_const(cls, "LSBYTE", INT2NUM(INTEGER_PACK_LSBYTE_FIRST)); + rb_define_const(cls, "NATIVE", INT2NUM(INTEGER_PACK_NATIVE_BYTE_ORDER)); + rb_define_const(cls, "PACK_2COMP", INT2NUM(INTEGER_PACK_2COMP)); + rb_define_const(cls, "LITTLE_ENDIAN", INT2NUM(INTEGER_PACK_LITTLE_ENDIAN)); + rb_define_const(cls, "BIG_ENDIAN", INT2NUM(INTEGER_PACK_BIG_ENDIAN)); + rb_define_const(cls, "FORCE_BIGNUM", INT2NUM(INTEGER_PACK_FORCE_BIGNUM)); + rb_define_const(cls, "NEGATIVE", INT2NUM(INTEGER_PACK_NEGATIVE)); + + rb_define_method(cls, "rb_integer_pack", integer_spec_rb_integer_pack, 6); + rb_define_method(cls, "rb_int_positive_pow", integer_spec_rb_int_positive_pow, 2); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c new file mode 100644 index 0000000000..b4ffe9207a --- /dev/null +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -0,0 +1,269 @@ +#include "ruby.h" +#include "rubyspec.h" +#include "ruby/io.h" +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static int set_non_blocking(int fd) { +#if defined(O_NONBLOCK) && defined(F_GETFL) + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + flags = 0; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#elif defined(FIOBIO) + int flags = 1; + return ioctl(fd, FIOBIO, &flags); +#else +#define SET_NON_BLOCKING_FAILS_ALWAYS 1 + errno = ENOSYS; + return -1; +#endif +} + +static int io_spec_get_fd(VALUE io) { + rb_io_t* fp; + GetOpenFile(io, fp); + return fp->fd; +} + +VALUE io_spec_GetOpenFile_fd(VALUE self, VALUE io) { + return INT2NUM(io_spec_get_fd(io)); +} + +VALUE io_spec_rb_io_addstr(VALUE self, VALUE io, VALUE str) { + return rb_io_addstr(io, str); +} + +VALUE io_spec_rb_io_printf(VALUE self, VALUE io, VALUE ary) { + long argc = RARRAY_LEN(ary); + VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc); + int i; + + for (i = 0; i < argc; i++) { + argv[i] = rb_ary_entry(ary, i); + } + + return rb_io_printf((int)argc, argv, io); +} + +VALUE io_spec_rb_io_print(VALUE self, VALUE io, VALUE ary) { + long argc = RARRAY_LEN(ary); + VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc); + int i; + + for (i = 0; i < argc; i++) { + argv[i] = rb_ary_entry(ary, i); + } + + return rb_io_print((int)argc, argv, io); +} + +VALUE io_spec_rb_io_puts(VALUE self, VALUE io, VALUE ary) { + long argc = RARRAY_LEN(ary); + VALUE *argv = (VALUE*) alloca(sizeof(VALUE) * argc); + int i; + + for (i = 0; i < argc; i++) { + argv[i] = rb_ary_entry(ary, i); + } + + return rb_io_puts((int)argc, argv, io); +} + +VALUE io_spec_rb_io_write(VALUE self, VALUE io, VALUE str) { + return rb_io_write(io, str); +} + +VALUE io_spec_rb_io_check_io(VALUE self, VALUE io) { + return rb_io_check_io(io); +} + +VALUE io_spec_rb_io_check_readable(VALUE self, VALUE io) { + rb_io_t* fp; + GetOpenFile(io, fp); + rb_io_check_readable(fp); + return Qnil; +} + +VALUE io_spec_rb_io_check_writable(VALUE self, VALUE io) { + rb_io_t* fp; + GetOpenFile(io, fp); + rb_io_check_writable(fp); + return Qnil; +} + +VALUE io_spec_rb_io_check_closed(VALUE self, VALUE io) { + rb_io_t* fp; + GetOpenFile(io, fp); + rb_io_check_closed(fp); + return Qnil; +} + +VALUE io_spec_rb_io_taint_check(VALUE self, VALUE io) { + /*rb_io_t* fp; + GetOpenFile(io, fp);*/ + rb_io_taint_check(io); + return io; +} + +#define RB_IO_WAIT_READABLE_BUF 13 + +#ifdef SET_NON_BLOCKING_FAILS_ALWAYS +NORETURN(VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p)); +#endif + +VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) { + int fd = io_spec_get_fd(io); +#ifndef SET_NON_BLOCKING_FAILS_ALWAYS + char buf[RB_IO_WAIT_READABLE_BUF]; + int ret, saved_errno; +#endif + + if (set_non_blocking(fd) == -1) + rb_sys_fail("set_non_blocking failed"); + +#ifndef SET_NON_BLOCKING_FAILS_ALWAYS + if(RTEST(read_p)) { + if (read(fd, buf, RB_IO_WAIT_READABLE_BUF) != -1) { + return Qnil; + } + saved_errno = errno; + rb_ivar_set(self, rb_intern("@write_data"), Qtrue); + errno = saved_errno; + } + + ret = rb_io_wait_readable(fd); + + if(RTEST(read_p)) { + ssize_t r = read(fd, buf, RB_IO_WAIT_READABLE_BUF); + if (r != RB_IO_WAIT_READABLE_BUF) { + perror("read"); + return SSIZET2NUM(r); + } + rb_ivar_set(self, rb_intern("@read_data"), + rb_str_new(buf, RB_IO_WAIT_READABLE_BUF)); + } + + return ret ? Qtrue : Qfalse; +#else + UNREACHABLE; +#endif +} + +VALUE io_spec_rb_io_wait_writable(VALUE self, VALUE io) { + int ret = rb_io_wait_writable(io_spec_get_fd(io)); + return ret ? Qtrue : Qfalse; +} + +VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) { + rb_thread_wait_fd(io_spec_get_fd(io)); + return Qnil; +} + +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; + if (!NIL_P(secs)) { + tv.tv_sec = FIX2INT(secs); + tv.tv_usec = FIX2INT(usecs); + } + return INT2FIX(rb_wait_for_single_fd(fd, FIX2INT(events), NIL_P(secs) ? NULL : &tv)); +} + +VALUE io_spec_rb_thread_fd_writable(VALUE self, VALUE io) { + rb_thread_fd_writable(io_spec_get_fd(io)); + return Qnil; +} + +VALUE io_spec_rb_io_binmode(VALUE self, VALUE io) { + return rb_io_binmode(io); +} + +VALUE io_spec_rb_fd_fix_cloexec(VALUE self, VALUE io) { + rb_fd_fix_cloexec(io_spec_get_fd(io)); + return Qnil; +} + +VALUE io_spec_rb_cloexec_open(VALUE self, VALUE path, VALUE flags, VALUE mode) { + const char *pathname = StringValuePtr(path); + int fd = rb_cloexec_open(pathname, FIX2INT(flags), FIX2INT(mode)); + return rb_funcall(rb_cIO, rb_intern("for_fd"), 1, INT2FIX(fd)); +} + +VALUE io_spec_rb_io_close(VALUE self, VALUE io) { + return rb_io_close(io); +} + +VALUE io_spec_rb_io_set_nonblock(VALUE self, VALUE io) { + rb_io_t* fp; +#ifdef F_GETFL + int flags; +#endif + GetOpenFile(io, fp); + rb_io_set_nonblock(fp); +#ifdef F_GETFL + flags = fcntl(fp->fd, F_GETFL, 0); + return flags & O_NONBLOCK ? Qtrue : Qfalse; +#else + return Qfalse; +#endif +} + +/* + * this is needed to ensure rb_io_wait_*able functions behave + * predictably because errno may be set to unexpected values + * otherwise. + */ +static VALUE io_spec_errno_set(VALUE self, VALUE val) { + int e = NUM2INT(val); + errno = e; + return val; +} + +VALUE io_spec_mode_sync_flag(VALUE self, VALUE io) { + rb_io_t *fp; + GetOpenFile(io, fp); + if (fp->mode & FMODE_SYNC) { + return Qtrue; + } else { + return Qfalse; + } +} + +void Init_io_spec(void) { + VALUE cls = rb_define_class("CApiIOSpecs", rb_cObject); + rb_define_method(cls, "GetOpenFile_fd", io_spec_GetOpenFile_fd, 1); + rb_define_method(cls, "rb_io_addstr", io_spec_rb_io_addstr, 2); + rb_define_method(cls, "rb_io_printf", io_spec_rb_io_printf, 2); + rb_define_method(cls, "rb_io_print", io_spec_rb_io_print, 2); + rb_define_method(cls, "rb_io_puts", io_spec_rb_io_puts, 2); + rb_define_method(cls, "rb_io_write", io_spec_rb_io_write, 2); + rb_define_method(cls, "rb_io_close", io_spec_rb_io_close, 1); + rb_define_method(cls, "rb_io_check_io", io_spec_rb_io_check_io, 1); + rb_define_method(cls, "rb_io_check_readable", io_spec_rb_io_check_readable, 1); + rb_define_method(cls, "rb_io_check_writable", io_spec_rb_io_check_writable, 1); + rb_define_method(cls, "rb_io_check_closed", io_spec_rb_io_check_closed, 1); + rb_define_method(cls, "rb_io_set_nonblock", io_spec_rb_io_set_nonblock, 1); + 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); + 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_wait_for_single_fd", io_spec_rb_wait_for_single_fd, 4); + rb_define_method(cls, "rb_io_binmode", io_spec_rb_io_binmode, 1); + rb_define_method(cls, "rb_fd_fix_cloexec", io_spec_rb_fd_fix_cloexec, 1); + rb_define_method(cls, "rb_cloexec_open", io_spec_rb_cloexec_open, 3); + rb_define_method(cls, "errno=", io_spec_errno_set, 1); + rb_define_method(cls, "rb_io_mode_sync_flag", io_spec_mode_sync_flag, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/kernel_spec.c b/spec/ruby/optional/capi/ext/kernel_spec.c new file mode 100644 index 0000000000..bbfeb198b7 --- /dev/null +++ b/spec/ruby/optional/capi/ext/kernel_spec.c @@ -0,0 +1,381 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <errno.h> + +#ifdef __cplusplus +extern "C" { +#endif + +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; +} + +VALUE kernel_spec_rb_need_block(VALUE self) { + rb_need_block(); + return Qnil; +} + +VALUE kernel_spec_rb_block_proc(VALUE self) { + return rb_block_proc(); +} + +VALUE kernel_spec_rb_block_lambda(VALUE self) { + return rb_block_lambda(); +} + +VALUE block_call_inject(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, data2)) { + /* yield_value yields the first block argument */ + VALUE elem = yield_value; + VALUE elem_incr = INT2FIX(FIX2INT(elem) + 1); + return elem_incr; +} + +VALUE kernel_spec_rb_block_call(VALUE self, VALUE ary) { + return rb_block_call(ary, rb_intern("map"), 0, NULL, block_call_inject, Qnil); +} + +VALUE block_call_inject_multi_arg(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, data2)) { + /* yield_value yields the first block argument */ + VALUE sum = yield_value; + VALUE elem = argv[1]; + + return INT2FIX(FIX2INT(sum) + FIX2INT(elem)); +} + +VALUE kernel_spec_rb_block_call_multi_arg(VALUE self, VALUE ary) { + VALUE method_args[1]; + method_args[0] = INT2FIX(0); + return rb_block_call(ary, rb_intern("inject"), 1, method_args, block_call_inject_multi_arg, Qnil); +} + +static VALUE return_extra_data(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, extra_data)) { + return extra_data; +} + +VALUE rb_block_call_extra_data(VALUE self, VALUE object) { + return rb_block_call(object, rb_intern("instance_exec"), 0, NULL, return_extra_data, object); +} + +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_ensure(VALUE self, VALUE main_proc, VALUE arg, + VALUE ensure_proc, VALUE arg2) { + VALUE main_array, ensure_array; + + main_array = rb_ary_new(); + rb_ary_push(main_array, main_proc); + rb_ary_push(main_array, arg); + + ensure_array = rb_ary_new(); + rb_ary_push(ensure_array, ensure_proc); + rb_ary_push(ensure_array, arg2); + + return rb_ensure(kernel_spec_call_proc, main_array, + kernel_spec_call_proc, ensure_array); +} + +VALUE kernel_spec_call_proc_with_catch(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) { + return rb_funcall(data, rb_intern("call"), 0); +} + +VALUE kernel_spec_rb_catch(VALUE self, VALUE sym, VALUE main_proc) { + return rb_catch(StringValuePtr(sym), kernel_spec_call_proc_with_catch, main_proc); +} + +VALUE kernel_spec_call_proc_with_catch_obj(RB_BLOCK_CALL_FUNC_ARGLIST(arg, data)) { + return rb_funcall(data, rb_intern("call"), 0); +} + +VALUE kernel_spec_rb_catch_obj(VALUE self, VALUE obj, VALUE main_proc) { + return rb_catch_obj(obj, kernel_spec_call_proc_with_catch_obj, main_proc); +} + +VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) { + return rb_eval_string(RSTRING_PTR(str)); +} + +VALUE kernel_spec_rb_raise(VALUE self, VALUE hash) { + rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("before"))); + if (self != Qundef) + rb_raise(rb_eTypeError, "Wrong argument type %s (expected %s)", "Integer", "String"); + rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("after"))); + return Qnil; +} + +VALUE kernel_spec_rb_throw(VALUE self, VALUE result) { + if (self != Qundef) rb_throw("foo", result); + return ID2SYM(rb_intern("rb_throw_failed")); +} + +VALUE kernel_spec_rb_throw_obj(VALUE self, VALUE obj, VALUE result) { + if (self != Qundef) rb_throw_obj(obj, result); + return ID2SYM(rb_intern("rb_throw_failed")); +} + +VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) { + VALUE argv[2]; + int argc; + + VALUE arg = rb_ary_pop(arg_array); + VALUE proc = rb_ary_pop(arg_array); + + argv[0] = arg; + argv[1] = raised_exc; + + argc = 2; + + return rb_funcall2(proc, rb_intern("call"), argc, argv); +} + +VALUE kernel_spec_rb_rescue(VALUE self, VALUE main_proc, VALUE arg, + VALUE raise_proc, VALUE arg2) { + VALUE main_array, raise_array; + + main_array = rb_ary_new(); + rb_ary_push(main_array, main_proc); + rb_ary_push(main_array, arg); + + if (raise_proc == Qnil) { + return rb_rescue(kernel_spec_call_proc, main_array, NULL, arg2); + } + + raise_array = rb_ary_new(); + rb_ary_push(raise_array, raise_proc); + rb_ary_push(raise_array, arg2); + + return rb_rescue(kernel_spec_call_proc, main_array, + kernel_spec_call_proc_with_raised_exc, raise_array); +} + +VALUE kernel_spec_rb_rescue2(int argc, VALUE *args, VALUE self) { + VALUE main_array, raise_array; + + main_array = rb_ary_new(); + rb_ary_push(main_array, args[0]); + rb_ary_push(main_array, args[1]); + + raise_array = rb_ary_new(); + rb_ary_push(raise_array, args[2]); + 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); +} + +static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) { + int status = 0; + VALUE res = rb_protect(rb_yield, obj, &status); + rb_ary_store(ary, 0, INT2NUM(23)); + rb_ary_store(ary, 1, res); + if (status) { + rb_jump_tag(status); + } + return res; +} + +static VALUE kernel_spec_rb_protect_errinfo(VALUE self, VALUE obj, VALUE ary) { + int status = 0; + VALUE res = rb_protect(rb_yield, obj, &status); + rb_ary_store(ary, 0, INT2NUM(23)); + rb_ary_store(ary, 1, res); + return rb_errinfo(); +} + +static VALUE kernel_spec_rb_protect_null_status(VALUE self, VALUE obj) { + return rb_protect(rb_yield, obj, NULL); +} + +static VALUE kernel_spec_rb_eval_string_protect(VALUE self, VALUE str, VALUE ary) { + int status = 0; + VALUE res = rb_eval_string_protect(RSTRING_PTR(str), &status); + rb_ary_store(ary, 0, INT2NUM(23)); + rb_ary_store(ary, 1, res); + if (status) { + rb_jump_tag(status); + } + return res; +} + +VALUE kernel_spec_rb_sys_fail(VALUE self, VALUE msg) { + errno = 1; + if(msg == Qnil) { + rb_sys_fail(0); + } else if (self != Qundef) { + rb_sys_fail(StringValuePtr(msg)); + } + return Qnil; +} + +VALUE kernel_spec_rb_syserr_fail(VALUE self, VALUE err, VALUE msg) { + if(msg == Qnil) { + rb_syserr_fail(NUM2INT(err), NULL); + } else if (self != Qundef) { + rb_syserr_fail(NUM2INT(err), StringValuePtr(msg)); + } + return Qnil; +} + +VALUE kernel_spec_rb_warn(VALUE self, VALUE msg) { + rb_warn("%s", StringValuePtr(msg)); + return Qnil; +} + +static VALUE kernel_spec_rb_yield(VALUE self, VALUE obj) { + return rb_yield(obj); +} + +static VALUE kernel_spec_rb_yield_each(int argc, VALUE *args, VALUE self) { + int i; + for(i = 0; i < 4; i++) { + rb_yield(INT2FIX(i)); + } + return INT2FIX(4); +} + +static VALUE kernel_spec_rb_yield_define_each(VALUE self, VALUE cls) { + rb_define_method(cls, "each", kernel_spec_rb_yield_each, -1); + return Qnil; +} + +static int kernel_cb(const void *a, const void *b) { + rb_yield(Qtrue); + return 0; +} + +static VALUE kernel_indirected(int (*compar)(const void *, const void *)) { + int bob[] = { 1, 1, 2, 3, 5, 8, 13 }; + qsort(bob, 7, sizeof(int), compar); + return Qfalse; +} + +static VALUE kernel_spec_rb_yield_indirected(VALUE self, VALUE obj) { + return kernel_indirected(kernel_cb); +} + +static VALUE kernel_spec_rb_yield_splat(VALUE self, VALUE ary) { + return rb_yield_splat(ary); +} + +static VALUE kernel_spec_rb_yield_values(VALUE self, VALUE obj1, VALUE obj2) { + return rb_yield_values(2, obj1, obj2); +} + +static VALUE kernel_spec_rb_yield_values2(VALUE self, VALUE ary) { + long len = RARRAY_LEN(ary); + VALUE *args = (VALUE*)alloca(sizeof(VALUE) * len); + for (int i = 0; i < len; i++) { + args[i] = rb_ary_entry(ary, i); + } + return rb_yield_values2((int)len, args); +} + +static VALUE do_rec(VALUE obj, VALUE arg, int is_rec) { + if(is_rec) { + return obj; + } else if(arg == Qtrue) { + return rb_exec_recursive(do_rec, obj, Qnil); + } else { + return Qnil; + } +} + +static VALUE kernel_spec_rb_exec_recursive(VALUE self, VALUE obj) { + return rb_exec_recursive(do_rec, obj, Qtrue); +} + +static void write_io(VALUE io) { + rb_funcall(io, rb_intern("write"), 1, rb_str_new2("in write_io")); +} + +static VALUE kernel_spec_rb_set_end_proc(VALUE self, VALUE io) { + rb_set_end_proc(write_io, io); + return Qnil; +} + +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_make_backtrace(VALUE self) { + return rb_make_backtrace(); +} + +static VALUE kernel_spec_rb_funcall3(VALUE self, VALUE obj, VALUE method) { + return rb_funcall3(obj, SYM2ID(method), 0, NULL); +} + +static VALUE kernel_spec_rb_funcall_with_block(VALUE self, VALUE obj, VALUE method, VALUE block) { + return rb_funcall_with_block(obj, SYM2ID(method), 0, NULL, block); +} + +static VALUE kernel_spec_rb_funcall_many_args(VALUE self, VALUE obj, VALUE method) { + return rb_funcall(obj, SYM2ID(method), 15, + INT2FIX(15), INT2FIX(14), INT2FIX(13), INT2FIX(12), INT2FIX(11), + INT2FIX(10), INT2FIX(9), INT2FIX(8), INT2FIX(7), INT2FIX(6), + INT2FIX(5), INT2FIX(4), INT2FIX(3), INT2FIX(2), INT2FIX(1)); +} + +void Init_kernel_spec(void) { + VALUE cls = rb_define_class("CApiKernelSpecs", rb_cObject); + rb_define_method(cls, "rb_block_given_p", kernel_spec_rb_block_given_p, 0); + rb_define_method(cls, "rb_need_block", kernel_spec_rb_need_block, 0); + rb_define_method(cls, "rb_block_call", kernel_spec_rb_block_call, 1); + rb_define_method(cls, "rb_block_call_multi_arg", kernel_spec_rb_block_call_multi_arg, 1); + rb_define_method(cls, "rb_block_call_no_func", kernel_spec_rb_block_call_no_func, 1); + rb_define_method(cls, "rb_block_call_extra_data", rb_block_call_extra_data, 1); + rb_define_method(cls, "rb_block_proc", kernel_spec_rb_block_proc, 0); + 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_ensure", kernel_spec_rb_ensure, 4); + rb_define_method(cls, "rb_eval_string", kernel_spec_rb_eval_string, 1); + 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_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_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_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); + rb_define_method(cls, "rb_yield_define_each", kernel_spec_rb_yield_define_each, 1); + rb_define_method(cls, "rb_yield_values", kernel_spec_rb_yield_values, 2); + rb_define_method(cls, "rb_yield_values2", kernel_spec_rb_yield_values2, 1); + rb_define_method(cls, "rb_yield_splat", kernel_spec_rb_yield_splat, 1); + 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_make_backtrace", kernel_spec_rb_make_backtrace, 0); + rb_define_method(cls, "rb_funcall3", kernel_spec_rb_funcall3, 2); + rb_define_method(cls, "rb_funcall_many_args", kernel_spec_rb_funcall_many_args, 2); + rb_define_method(cls, "rb_funcall_with_block", kernel_spec_rb_funcall_with_block, 3); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/language_spec.c b/spec/ruby/optional/capi/ext/language_spec.c new file mode 100644 index 0000000000..749c188956 --- /dev/null +++ b/spec/ruby/optional/capi/ext/language_spec.c @@ -0,0 +1,42 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE language_spec_switch(VALUE self, VALUE value) { + if (value == ID2SYM(rb_intern("undef"))) { + value = Qundef; + } + + switch (value) { + case Qtrue: + return ID2SYM(rb_intern("true")); + case Qfalse: + return ID2SYM(rb_intern("false")); + case Qnil: + return ID2SYM(rb_intern("nil")); + case Qundef: + return ID2SYM(rb_intern("undef")); + default: + return ID2SYM(rb_intern("default")); + } +} + +/* Defining a local variable rb_mProcess which already exists as a global variable + * For instance eventmachine does this in Init_rubyeventmachine() */ +static VALUE language_spec_global_local_var(VALUE self) { + VALUE rb_mProcess = rb_const_get(rb_cObject, rb_intern("Process")); + return rb_mProcess; +} + +void Init_language_spec(void) { + VALUE cls = rb_define_class("CApiLanguageSpecs", rb_cObject); + rb_define_method(cls, "switch", language_spec_switch, 1); + rb_define_method(cls, "global_local_var", language_spec_global_local_var, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/marshal_spec.c b/spec/ruby/optional/capi/ext/marshal_spec.c new file mode 100644 index 0000000000..ea8e3d5a07 --- /dev/null +++ b/spec/ruby/optional/capi/ext/marshal_spec.c @@ -0,0 +1,24 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE marshal_spec_rb_marshal_dump(VALUE self, VALUE obj, VALUE port) { + return rb_marshal_dump(obj, port); +} + +VALUE marshal_spec_rb_marshal_load(VALUE self, VALUE data) { + return rb_marshal_load(data); +} + +void Init_marshal_spec(void) { + VALUE cls = rb_define_class("CApiMarshalSpecs", rb_cObject); + rb_define_method(cls, "rb_marshal_dump", marshal_spec_rb_marshal_dump, 2); + rb_define_method(cls, "rb_marshal_load", marshal_spec_rb_marshal_load, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/module_spec.c b/spec/ruby/optional/capi/ext/module_spec.c new file mode 100644 index 0000000000..7475994fa1 --- /dev/null +++ b/spec/ruby/optional/capi/ext/module_spec.c @@ -0,0 +1,186 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE module_specs_test_method(VALUE self) { + return ID2SYM(rb_intern("test_method")); +} + +static VALUE module_specs_test_method_2required(VALUE self, VALUE arg1, VALUE arg2) { + return ID2SYM(rb_intern("test_method_2required")); +} + +static VALUE module_specs_test_method_c_array(int argc, VALUE *argv, VALUE self) { + return ID2SYM(rb_intern("test_method_c_array")); +} + +static VALUE module_specs_test_method_ruby_array(VALUE self, VALUE args) { + return ID2SYM(rb_intern("test_method_ruby_array")); +} + +static VALUE module_specs_const_defined(VALUE self, VALUE klass, VALUE id) { + return rb_const_defined(klass, SYM2ID(id)) ? Qtrue : Qfalse; +} + +static VALUE module_specs_const_defined_at(VALUE self, VALUE klass, VALUE id) { + return rb_const_defined_at(klass, SYM2ID(id)) ? Qtrue : Qfalse; +} + +static VALUE module_specs_const_get(VALUE self, VALUE klass, VALUE val) { + return rb_const_get(klass, SYM2ID(val)); +} + +static VALUE module_specs_const_get_at(VALUE self, VALUE klass, VALUE val) { + return rb_const_get_at(klass, SYM2ID(val)); +} + +static VALUE module_specs_const_get_from(VALUE self, VALUE klass, VALUE val) { + return rb_const_get_from(klass, SYM2ID(val)); +} + +static VALUE module_specs_const_set(VALUE self, VALUE klass, VALUE name, VALUE val) { + rb_const_set(klass, SYM2ID(name), val); + return Qnil; +} + +static VALUE module_specs_rb_define_alias(VALUE self, VALUE obj, + VALUE new_name, VALUE old_name) { + + rb_define_alias(obj, RSTRING_PTR(new_name), RSTRING_PTR(old_name)); + return Qnil; +} + +static VALUE module_specs_rb_alias(VALUE self, VALUE obj, + VALUE new_name, VALUE old_name) { + + rb_alias(obj, SYM2ID(new_name), SYM2ID(old_name)); + return Qnil; +} + +static VALUE module_specs_rb_define_module(VALUE self, VALUE name) { + return rb_define_module(RSTRING_PTR(name)); +} + +static VALUE module_specs_rb_define_module_under(VALUE self, VALUE outer, VALUE name) { + return rb_define_module_under(outer, RSTRING_PTR(name)); +} + +static VALUE module_specs_define_const(VALUE self, VALUE klass, VALUE str_name, VALUE val) { + rb_define_const(klass, RSTRING_PTR(str_name), val); + return Qnil; +} + +static VALUE module_specs_define_global_const(VALUE self, VALUE str_name, VALUE obj) { + rb_define_global_const(RSTRING_PTR(str_name), obj); + return Qnil; +} + +static VALUE module_specs_rb_define_global_function(VALUE self, VALUE str_name) { + rb_define_global_function(RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_define_method(VALUE self, VALUE cls, VALUE str_name) { + rb_define_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_define_method_2required(VALUE self, VALUE cls, VALUE str_name) { + rb_define_method(cls, RSTRING_PTR(str_name), module_specs_test_method_2required, 2); + return Qnil; +} + +static VALUE module_specs_rb_define_method_c_array(VALUE self, VALUE cls, VALUE str_name) { + rb_define_method(cls, RSTRING_PTR(str_name), module_specs_test_method_c_array, -1); + return Qnil; +} + +static VALUE module_specs_rb_define_method_ruby_array(VALUE self, VALUE cls, VALUE str_name) { + rb_define_method(cls, RSTRING_PTR(str_name), module_specs_test_method_ruby_array, -2); + return Qnil; +} + +static VALUE module_specs_rb_define_module_function(VALUE self, VALUE cls, VALUE str_name) { + rb_define_module_function(cls, RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_define_private_method(VALUE self, VALUE cls, VALUE str_name) { + rb_define_private_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_define_protected_method(VALUE self, VALUE cls, VALUE str_name) { + rb_define_protected_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_define_singleton_method(VALUE self, VALUE cls, VALUE str_name) { + rb_define_singleton_method(cls, RSTRING_PTR(str_name), module_specs_test_method, 0); + return Qnil; +} + +static VALUE module_specs_rb_undef_method(VALUE self, VALUE cls, VALUE str_name) { + rb_undef_method(cls, RSTRING_PTR(str_name)); + return Qnil; +} + +static VALUE module_specs_rb_undef(VALUE self, VALUE cls, VALUE symbol_name) { + rb_undef(cls, SYM2ID(symbol_name)); + return Qnil; +} + +static VALUE module_specs_rbclass2name(VALUE self, VALUE klass) { + return rb_str_new2(rb_class2name(klass)); +} + +static VALUE module_specs_rb_mod_ancestors(VALUE self, VALUE klass) { + return rb_mod_ancestors(klass); +} + +void Init_module_spec(void) { + VALUE cls = rb_define_class("CApiModuleSpecs", rb_cObject); + rb_define_method(cls, "rb_const_defined", module_specs_const_defined, 2); + rb_define_method(cls, "rb_const_defined_at", module_specs_const_defined_at, 2); + rb_define_method(cls, "rb_const_get", module_specs_const_get, 2); + rb_define_method(cls, "rb_const_get_at", module_specs_const_get_at, 2); + rb_define_method(cls, "rb_const_get_from", module_specs_const_get_from, 2); + rb_define_method(cls, "rb_const_set", module_specs_const_set, 3); + rb_define_method(cls, "rb_define_alias", module_specs_rb_define_alias, 3); + rb_define_method(cls, "rb_alias", module_specs_rb_alias, 3); + rb_define_method(cls, "rb_define_module", module_specs_rb_define_module, 1); + rb_define_method(cls, "rb_define_module_under", module_specs_rb_define_module_under, 2); + rb_define_method(cls, "rb_define_const", module_specs_define_const, 3); + rb_define_method(cls, "rb_define_global_const", module_specs_define_global_const, 2); + rb_define_method(cls, "rb_define_global_function", + module_specs_rb_define_global_function, 1); + + rb_define_method(cls, "rb_define_method", module_specs_rb_define_method, 2); + rb_define_method(cls, "rb_define_method_2required", module_specs_rb_define_method_2required, 2); + rb_define_method(cls, "rb_define_method_c_array", module_specs_rb_define_method_c_array, 2); + rb_define_method(cls, "rb_define_method_ruby_array", module_specs_rb_define_method_ruby_array, 2); + + rb_define_method(cls, "rb_define_module_function", + module_specs_rb_define_module_function, 2); + + rb_define_method(cls, "rb_define_private_method", + module_specs_rb_define_private_method, 2); + + rb_define_method(cls, "rb_define_protected_method", + module_specs_rb_define_protected_method, 2); + + rb_define_method(cls, "rb_define_singleton_method", + module_specs_rb_define_singleton_method, 2); + + rb_define_method(cls, "rb_undef_method", module_specs_rb_undef_method, 2); + rb_define_method(cls, "rb_undef", module_specs_rb_undef, 2); + rb_define_method(cls, "rb_class2name", module_specs_rbclass2name, 1); + rb_define_method(cls, "rb_mod_ancestors", module_specs_rb_mod_ancestors, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/module_under_autoload_spec.c b/spec/ruby/optional/capi/ext/module_under_autoload_spec.c new file mode 100644 index 0000000000..b19466e555 --- /dev/null +++ b/spec/ruby/optional/capi/ext/module_under_autoload_spec.c @@ -0,0 +1,15 @@ +#include "ruby.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void Init_module_under_autoload_spec(void) { + VALUE specs = rb_const_get(rb_cObject, rb_intern("CApiModuleSpecs")); + rb_define_module_under(specs, "ModuleUnderAutoload"); + rb_define_module_under(specs, "RubyUnderAutoload"); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/mutex_spec.c b/spec/ruby/optional/capi/ext/mutex_spec.c new file mode 100644 index 0000000000..c2fdf917ac --- /dev/null +++ b/spec/ruby/optional/capi/ext/mutex_spec.c @@ -0,0 +1,55 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE mutex_spec_rb_mutex_new(VALUE self) { + return rb_mutex_new(); +} + +VALUE mutex_spec_rb_mutex_locked_p(VALUE self, VALUE mutex) { + return rb_mutex_locked_p(mutex); +} + +VALUE mutex_spec_rb_mutex_trylock(VALUE self, VALUE mutex) { + return rb_mutex_trylock(mutex); +} + +VALUE mutex_spec_rb_mutex_lock(VALUE self, VALUE mutex) { + return rb_mutex_lock(mutex); +} + +VALUE mutex_spec_rb_mutex_unlock(VALUE self, VALUE mutex) { + return rb_mutex_unlock(mutex); +} + +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_synchronize(VALUE self, VALUE mutex, VALUE value) { + return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback, 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); + rb_define_method(cls, "rb_mutex_locked_p", mutex_spec_rb_mutex_locked_p, 1); + rb_define_method(cls, "rb_mutex_trylock", mutex_spec_rb_mutex_trylock, 1); + rb_define_method(cls, "rb_mutex_lock", mutex_spec_rb_mutex_lock, 1); + 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); +} + +#ifdef __cplusplus +} +#endif + diff --git a/spec/ruby/optional/capi/ext/numeric_spec.c b/spec/ruby/optional/capi/ext/numeric_spec.c new file mode 100644 index 0000000000..d3639580a8 --- /dev/null +++ b/spec/ruby/optional/capi/ext/numeric_spec.c @@ -0,0 +1,130 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE numeric_spec_size_of_VALUE(VALUE self) { + return INT2FIX(sizeof(VALUE)); +} + +static VALUE numeric_spec_size_of_long_long(VALUE self) { + return INT2FIX(sizeof(LONG_LONG)); +} + +static VALUE numeric_spec_NUM2CHR(VALUE self, VALUE value) { + return INT2FIX(NUM2CHR(value)); +} + +static VALUE numeric_spec_rb_int2inum_14(VALUE self) { + return rb_int2inum(14); +} + +static VALUE numeric_spec_rb_uint2inum_14(VALUE self) { + return rb_uint2inum(14); +} + +static VALUE numeric_spec_rb_uint2inum_n14(VALUE self) { + return rb_uint2inum(-14); +} + +static VALUE numeric_spec_rb_Integer(VALUE self, VALUE str) { + return rb_Integer(str); +} + +static VALUE numeric_spec_rb_ll2inum_14(VALUE self) { + return rb_ll2inum(14); +} + +static VALUE numeric_spec_rb_ull2inum_14(VALUE self) { + return rb_ull2inum(14); +} + +static VALUE numeric_spec_rb_ull2inum_n14(VALUE self) { + return rb_ull2inum(-14); +} + +static VALUE numeric_spec_NUM2DBL(VALUE self, VALUE num) { + return rb_float_new(NUM2DBL(num)); +} + +static VALUE numeric_spec_NUM2INT(VALUE self, VALUE num) { + return LONG2NUM(NUM2INT(num)); +} + +static VALUE numeric_spec_INT2NUM(VALUE self, VALUE num) { + return INT2NUM(NUM2INT(num)); +} + +static VALUE numeric_spec_NUM2LONG(VALUE self, VALUE num) { + return LONG2NUM(NUM2LONG(num)); +} + +static VALUE numeric_spec_NUM2SHORT(VALUE self, VALUE num) { + return LONG2NUM(NUM2SHORT(num)); +} + +static VALUE numeric_spec_NUM2UINT(VALUE self, VALUE num) { + return ULONG2NUM(NUM2UINT(num)); +} + +static VALUE numeric_spec_NUM2ULONG(VALUE self, VALUE num) { + return ULONG2NUM(NUM2ULONG(num)); +} + +static VALUE numeric_spec_rb_num_zerodiv(VALUE self) { + rb_num_zerodiv(); + return Qnil; +} + +static VALUE numeric_spec_rb_cmpint(VALUE self, VALUE val, VALUE b) { + return INT2FIX(rb_cmpint(val, val, b)); +} + +static VALUE numeric_spec_rb_num_coerce_bin(VALUE self, VALUE x, VALUE y, VALUE op) { + return rb_num_coerce_bin(x, y, SYM2ID(op)); +} + +static VALUE numeric_spec_rb_num_coerce_cmp(VALUE self, VALUE x, VALUE y, VALUE op) { + return rb_num_coerce_cmp(x, y, SYM2ID(op)); +} + +static VALUE numeric_spec_rb_num_coerce_relop(VALUE self, VALUE x, VALUE y, VALUE op) { + return rb_num_coerce_relop(x, y, SYM2ID(op)); +} + +static VALUE numeric_spec_rb_absint_singlebit_p(VALUE self, VALUE num) { + return INT2FIX(rb_absint_singlebit_p(num)); +} + +void Init_numeric_spec(void) { + VALUE cls = rb_define_class("CApiNumericSpecs", rb_cObject); + rb_define_method(cls, "size_of_VALUE", numeric_spec_size_of_VALUE, 0); + rb_define_method(cls, "size_of_long_long", numeric_spec_size_of_long_long, 0); + rb_define_method(cls, "NUM2CHR", numeric_spec_NUM2CHR, 1); + rb_define_method(cls, "rb_int2inum_14", numeric_spec_rb_int2inum_14, 0); + rb_define_method(cls, "rb_uint2inum_14", numeric_spec_rb_uint2inum_14, 0); + rb_define_method(cls, "rb_uint2inum_n14", numeric_spec_rb_uint2inum_n14, 0); + rb_define_method(cls, "rb_Integer", numeric_spec_rb_Integer, 1); + rb_define_method(cls, "rb_ll2inum_14", numeric_spec_rb_ll2inum_14, 0); + rb_define_method(cls, "rb_ull2inum_14", numeric_spec_rb_ull2inum_14, 0); + rb_define_method(cls, "rb_ull2inum_n14", numeric_spec_rb_ull2inum_n14, 0); + rb_define_method(cls, "NUM2DBL", numeric_spec_NUM2DBL, 1); + rb_define_method(cls, "NUM2INT", numeric_spec_NUM2INT, 1); + rb_define_method(cls, "NUM2LONG", numeric_spec_NUM2LONG, 1); + rb_define_method(cls, "NUM2SHORT", numeric_spec_NUM2SHORT, 1); + rb_define_method(cls, "INT2NUM", numeric_spec_INT2NUM, 1); + rb_define_method(cls, "NUM2UINT", numeric_spec_NUM2UINT, 1); + rb_define_method(cls, "NUM2ULONG", numeric_spec_NUM2ULONG, 1); + rb_define_method(cls, "rb_num_zerodiv", numeric_spec_rb_num_zerodiv, 0); + rb_define_method(cls, "rb_cmpint", numeric_spec_rb_cmpint, 2); + rb_define_method(cls, "rb_num_coerce_bin", numeric_spec_rb_num_coerce_bin, 3); + rb_define_method(cls, "rb_num_coerce_cmp", numeric_spec_rb_num_coerce_cmp, 3); + rb_define_method(cls, "rb_num_coerce_relop", numeric_spec_rb_num_coerce_relop, 3); +rb_define_method(cls, "rb_absint_singlebit_p", numeric_spec_rb_absint_singlebit_p, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c new file mode 100644 index 0000000000..c1f3b09ff8 --- /dev/null +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -0,0 +1,499 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE object_spec_FL_ABLE(VALUE self, VALUE obj) { + if (FL_ABLE(obj)) { + return Qtrue; + } else { + return Qfalse; + } +} + +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; + } + return 0; +} + +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); +} + +static VALUE so_attr_get(VALUE self, VALUE obj, VALUE attr) { + return rb_attr_get(obj, SYM2ID(attr)); +} + +static VALUE object_spec_rb_obj_instance_variables(VALUE self, VALUE obj) { + return rb_obj_instance_variables(obj); +} + +static VALUE so_check_array_type(VALUE self, VALUE ary) { + return rb_check_array_type(ary); +} + +static VALUE so_check_convert_type(VALUE self, VALUE obj, VALUE klass, VALUE method) { + return rb_check_convert_type(obj, T_ARRAY, RSTRING_PTR(klass), RSTRING_PTR(method)); +} + +static VALUE so_check_to_integer(VALUE self, VALUE obj, VALUE method) { + return rb_check_to_integer(obj, RSTRING_PTR(method)); +} + +static VALUE object_spec_rb_check_frozen(VALUE self, VALUE obj) { + rb_check_frozen(obj); + return Qnil; +} + +static VALUE so_check_string_type(VALUE self, VALUE str) { + return rb_check_string_type(str); +} + +static VALUE so_rbclassof(VALUE self, VALUE obj) { + return rb_class_of(obj); +} + +static VALUE so_convert_type(VALUE self, VALUE obj, VALUE klass, VALUE method) { + return rb_convert_type(obj, T_ARRAY, RSTRING_PTR(klass), RSTRING_PTR(method)); +} + +static VALUE object_spec_rb_extend_object(VALUE self, VALUE obj, VALUE mod) { + rb_extend_object(obj, mod); + return obj; +} + +static VALUE so_inspect(VALUE self, VALUE obj) { + return rb_inspect(obj); +} + +static VALUE so_rb_obj_alloc(VALUE self, VALUE klass) { + return rb_obj_alloc(klass); +} + +static VALUE so_rb_obj_dup(VALUE self, VALUE klass) { + return rb_obj_dup(klass); +} + +static VALUE so_rb_obj_call_init(VALUE self, VALUE object, + VALUE nargs, VALUE args) { + int c_nargs = FIX2INT(nargs); + VALUE *c_args = (VALUE*) alloca(sizeof(VALUE) * c_nargs); + int i; + + for (i = 0; i < c_nargs; i++) + c_args[i] = rb_ary_entry(args, i); + + rb_obj_call_init(object, c_nargs, c_args); + + return Qnil; +} + +static VALUE so_rbobjclassname(VALUE self, VALUE obj) { + return rb_str_new2(rb_obj_classname(obj)); +} + + +static VALUE object_spec_rb_obj_freeze(VALUE self, VALUE obj) { + return rb_obj_freeze(obj); +} + +static VALUE object_spec_rb_obj_frozen_p(VALUE self, VALUE obj) { + return rb_obj_frozen_p(obj); +} + +static VALUE object_spec_rb_obj_id(VALUE self, VALUE obj) { + return rb_obj_id(obj); +} + +static VALUE so_instance_of(VALUE self, VALUE obj, VALUE klass) { + return rb_obj_is_instance_of(obj, klass); +} + +static VALUE so_kind_of(VALUE self, VALUE obj, VALUE klass) { + return rb_obj_is_kind_of(obj, klass); +} + +static VALUE object_specs_rb_obj_method_arity(VALUE self, VALUE obj, VALUE mid) { + return INT2FIX(rb_obj_method_arity(obj, SYM2ID(mid))); +} + +static VALUE object_specs_rb_obj_method(VALUE self, VALUE obj, VALUE method) { + return rb_obj_method(obj, method); +} + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +# endif +#endif + +static VALUE object_spec_rb_obj_taint(VALUE self, VALUE obj) { + return rb_obj_taint(obj); +} + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic pop +# endif +#endif + +static VALUE so_require(VALUE self) { + rb_require("fixtures/foo"); + return Qnil; +} + +static VALUE so_respond_to(VALUE self, VALUE obj, VALUE sym) { + return rb_respond_to(obj, SYM2ID(sym)) ? Qtrue : Qfalse; +} + +static VALUE so_obj_respond_to(VALUE self, VALUE obj, VALUE sym, VALUE priv) { + return rb_obj_respond_to(obj, SYM2ID(sym), priv == Qtrue ? 1 : 0) ? Qtrue : Qfalse; +} + +static VALUE object_spec_rb_method_boundp(VALUE self, VALUE obj, VALUE method, VALUE exclude_private) { + ID id = SYM2ID(method); + return rb_method_boundp(obj, id, exclude_private == Qtrue ? 1 : 0) ? Qtrue : Qfalse; +} + +static VALUE object_spec_rb_special_const_p(VALUE self, VALUE value) { + if (rb_special_const_p(value)) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE so_to_id(VALUE self, VALUE obj) { + return ID2SYM(rb_to_id(obj)); +} + +static VALUE object_spec_RTEST(VALUE self, VALUE value) { + return RTEST(value) ? Qtrue : Qfalse; +} + +static VALUE so_check_type(VALUE self, VALUE obj, VALUE other) { + rb_check_type(obj, TYPE(other)); + return Qtrue; +} + +static VALUE so_is_type_nil(VALUE self, VALUE obj) { + if(TYPE(obj) == T_NIL) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_type_object(VALUE self, VALUE obj) { + if(TYPE(obj) == T_OBJECT) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_type_array(VALUE self, VALUE obj) { + if(TYPE(obj) == T_ARRAY) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_type_module(VALUE self, VALUE obj) { + if(TYPE(obj) == T_MODULE) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_type_class(VALUE self, VALUE obj) { + if(TYPE(obj) == T_CLASS) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_type_data(VALUE self, VALUE obj) { + if(TYPE(obj) == T_DATA) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_nil(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_NIL)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_object(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_OBJECT)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_array(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_ARRAY)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_module(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_MODULE)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_class(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_CLASS)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_rb_type_p_data(VALUE self, VALUE obj) { + if(rb_type_p(obj, T_DATA)) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_builtin_type_object(VALUE self, VALUE obj) { + if(BUILTIN_TYPE(obj) == T_OBJECT) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_builtin_type_array(VALUE self, VALUE obj) { + if(BUILTIN_TYPE(obj) == T_ARRAY) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_builtin_type_module(VALUE self, VALUE obj) { + if(BUILTIN_TYPE(obj) == T_MODULE) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_builtin_type_class(VALUE self, VALUE obj) { + if(BUILTIN_TYPE(obj) == T_CLASS) { + return Qtrue; + } + return Qfalse; +} + +static VALUE so_is_builtin_type_data(VALUE self, VALUE obj) { + if(BUILTIN_TYPE(obj) == T_DATA) { + return Qtrue; + } + return Qfalse; +} + +static VALUE object_spec_rb_to_int(VALUE self, VALUE obj) { + return rb_to_int(obj); +} + +static VALUE object_spec_rb_obj_instance_eval(VALUE self, VALUE obj) { + return rb_obj_instance_eval(0, NULL, obj); +} + +static VALUE object_spec_rb_iv_get(VALUE self, VALUE obj, VALUE name) { + return rb_iv_get(obj, RSTRING_PTR(name)); +} + +static VALUE object_spec_rb_iv_set(VALUE self, VALUE obj, VALUE name, VALUE value) { + return rb_iv_set(obj, RSTRING_PTR(name), value); +} + +static VALUE object_spec_rb_ivar_count(VALUE self, VALUE obj) { + return ULONG2NUM(rb_ivar_count(obj)); +} + +static VALUE object_spec_rb_ivar_get(VALUE self, VALUE obj, VALUE sym_name) { + return rb_ivar_get(obj, SYM2ID(sym_name)); +} + +static VALUE object_spec_rb_ivar_set(VALUE self, VALUE obj, VALUE sym_name, VALUE value) { + return rb_ivar_set(obj, SYM2ID(sym_name), value); +} + +static VALUE object_spec_rb_ivar_defined(VALUE self, VALUE obj, VALUE sym_name) { + return rb_ivar_defined(obj, SYM2ID(sym_name)); +} + +static VALUE object_spec_rb_copy_generic_ivar(VALUE self, VALUE clone, VALUE obj) { + rb_copy_generic_ivar(clone, obj); + return self; +} + +static VALUE object_spec_rb_free_generic_ivar(VALUE self, VALUE obj) { + rb_free_generic_ivar(obj); + return self; +} + +static VALUE object_spec_rb_equal(VALUE self, VALUE a, VALUE b) { + return rb_equal(a, b); +} + +static VALUE object_spec_rb_class_inherited_p(VALUE self, VALUE mod, VALUE arg) { + return rb_class_inherited_p(mod, arg); +} + +static VALUE speced_allocator(VALUE klass) { + VALUE flags = 0; + VALUE instance; + if (RTEST(rb_class_inherited_p(klass, rb_cString))) { + flags = T_STRING; + } else if (RTEST(rb_class_inherited_p(klass, rb_cArray))) { + flags = T_ARRAY; + } else { + flags = T_OBJECT; + } + instance = rb_newobj_of(klass, flags); + rb_iv_set(instance, "@from_custom_allocator", Qtrue); + return instance; +} + +static VALUE define_alloc_func(VALUE self, VALUE klass) { + rb_define_alloc_func(klass, speced_allocator); + return Qnil; +} + +static VALUE undef_alloc_func(VALUE self, VALUE klass) { + rb_undef_alloc_func(klass); + return Qnil; +} + +static VALUE speced_allocator_p(VALUE self, VALUE klass) { + rb_alloc_func_t allocator = rb_get_alloc_func(klass); + return (allocator == speced_allocator) ? Qtrue : Qfalse; +} + +static VALUE custom_alloc_func_p(VALUE self, VALUE klass) { + rb_alloc_func_t allocator = rb_get_alloc_func(klass); + return allocator ? Qtrue : 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); + rb_define_method(cls, "rb_check_array_type", so_check_array_type, 1); + rb_define_method(cls, "rb_check_convert_type", so_check_convert_type, 3); + rb_define_method(cls, "rb_check_to_integer", so_check_to_integer, 2); + rb_define_method(cls, "rb_check_frozen", object_spec_rb_check_frozen, 1); + rb_define_method(cls, "rb_check_string_type", so_check_string_type, 1); + rb_define_method(cls, "rb_class_of", so_rbclassof, 1); + rb_define_method(cls, "rb_convert_type", so_convert_type, 3); + rb_define_method(cls, "rb_extend_object", object_spec_rb_extend_object, 2); + rb_define_method(cls, "rb_inspect", so_inspect, 1); + rb_define_method(cls, "rb_obj_alloc", so_rb_obj_alloc, 1); + rb_define_method(cls, "rb_obj_dup", so_rb_obj_dup, 1); + rb_define_method(cls, "rb_obj_call_init", so_rb_obj_call_init, 3); + rb_define_method(cls, "rb_obj_classname", so_rbobjclassname, 1); + rb_define_method(cls, "rb_obj_freeze", object_spec_rb_obj_freeze, 1); + rb_define_method(cls, "rb_obj_frozen_p", object_spec_rb_obj_frozen_p, 1); + rb_define_method(cls, "rb_obj_id", object_spec_rb_obj_id, 1); + rb_define_method(cls, "rb_obj_is_instance_of", so_instance_of, 2); + rb_define_method(cls, "rb_obj_is_kind_of", so_kind_of, 2); + rb_define_method(cls, "rb_obj_method_arity", object_specs_rb_obj_method_arity, 2); + rb_define_method(cls, "rb_obj_method", object_specs_rb_obj_method, 2); + rb_define_method(cls, "rb_obj_taint", object_spec_rb_obj_taint, 1); + rb_define_method(cls, "rb_require", so_require, 0); + rb_define_method(cls, "rb_respond_to", so_respond_to, 2); + 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); + rb_define_method(cls, "rb_is_type_nil", so_is_type_nil, 1); + rb_define_method(cls, "rb_is_type_object", so_is_type_object, 1); + rb_define_method(cls, "rb_is_type_array", so_is_type_array, 1); + rb_define_method(cls, "rb_is_type_module", so_is_type_module, 1); + rb_define_method(cls, "rb_is_type_class", so_is_type_class, 1); + rb_define_method(cls, "rb_is_type_data", so_is_type_data, 1); + rb_define_method(cls, "rb_is_rb_type_p_nil", so_is_rb_type_p_nil, 1); + rb_define_method(cls, "rb_is_rb_type_p_object", so_is_rb_type_p_object, 1); + rb_define_method(cls, "rb_is_rb_type_p_array", so_is_rb_type_p_array, 1); + rb_define_method(cls, "rb_is_rb_type_p_module", so_is_rb_type_p_module, 1); + rb_define_method(cls, "rb_is_rb_type_p_class", so_is_rb_type_p_class, 1); + rb_define_method(cls, "rb_is_rb_type_p_data", so_is_rb_type_p_data, 1); + rb_define_method(cls, "rb_is_builtin_type_object", so_is_builtin_type_object, 1); + rb_define_method(cls, "rb_is_builtin_type_array", so_is_builtin_type_array, 1); + rb_define_method(cls, "rb_is_builtin_type_module", so_is_builtin_type_module, 1); + rb_define_method(cls, "rb_is_builtin_type_class", so_is_builtin_type_class, 1); + rb_define_method(cls, "rb_is_builtin_type_data", so_is_builtin_type_data, 1); + rb_define_method(cls, "rb_to_int", object_spec_rb_to_int, 1); + rb_define_method(cls, "rb_equal", object_spec_rb_equal, 2); + rb_define_method(cls, "rb_class_inherited_p", object_spec_rb_class_inherited_p, 2); + rb_define_method(cls, "rb_obj_instance_eval", object_spec_rb_obj_instance_eval, 1); + rb_define_method(cls, "rb_iv_get", object_spec_rb_iv_get, 2); + rb_define_method(cls, "rb_iv_set", object_spec_rb_iv_set, 3); + rb_define_method(cls, "rb_ivar_count", object_spec_rb_ivar_count, 1); + rb_define_method(cls, "rb_ivar_get", object_spec_rb_ivar_get, 2); + rb_define_method(cls, "rb_ivar_set", object_spec_rb_ivar_set, 3); + rb_define_method(cls, "rb_ivar_defined", object_spec_rb_ivar_defined, 2); + rb_define_method(cls, "rb_copy_generic_ivar", object_spec_rb_copy_generic_ivar, 2); + rb_define_method(cls, "rb_free_generic_ivar", object_spec_rb_free_generic_ivar, 1); + rb_define_method(cls, "rb_define_alloc_func", define_alloc_func, 1); + rb_define_method(cls, "rb_undef_alloc_func", undef_alloc_func, 1); + rb_define_method(cls, "speced_allocator?", speced_allocator_p, 1); + rb_define_method(cls, "custom_alloc_func?", custom_alloc_func_p, 1); + rb_define_method(cls, "not_implemented_method", rb_f_notimplement, -1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/proc_spec.c b/spec/ruby/optional/capi/ext/proc_spec.c new file mode 100644 index 0000000000..e0bd8b1bbc --- /dev/null +++ b/spec/ruby/optional/capi/ext/proc_spec.c @@ -0,0 +1,73 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE proc_spec_rb_proc_new_function(RB_BLOCK_CALL_FUNC_ARGLIST(args, dummy)) { + return rb_funcall(args, rb_intern("inspect"), 0); +} + +VALUE proc_spec_rb_proc_new(VALUE self) { + return rb_proc_new(proc_spec_rb_proc_new_function, Qnil); +} + +VALUE proc_spec_rb_proc_arity(VALUE self, VALUE prc) { + return INT2FIX(rb_proc_arity(prc)); +} + +VALUE proc_spec_rb_proc_call(VALUE self, VALUE prc, VALUE args) { + return rb_proc_call(prc, args); +} + +VALUE proc_spec_rb_obj_is_proc(VALUE self, VALUE prc) { + return rb_obj_is_proc(prc); +} + +/* This helper is not strictly necessary but reflects the code in wxRuby that + * originally exposed issues with this Proc.new behavior. + */ +VALUE proc_spec_rb_Proc_new_helper(void) { + return rb_funcall(rb_cProc, rb_intern("new"), 0); +} + +VALUE proc_spec_rb_Proc_new(VALUE self, VALUE scenario) { + switch(FIX2INT(scenario)) { + case 0: + return proc_spec_rb_Proc_new_helper(); + case 1: + rb_funcall(self, rb_intern("call_nothing"), 0); + return proc_spec_rb_Proc_new_helper(); + case 2: + return rb_funcall(self, rb_intern("call_Proc_new"), 0); + case 3: + return rb_funcall(self, rb_intern("call_rb_Proc_new"), 0); + case 4: + return rb_funcall(self, rb_intern("call_rb_Proc_new_with_block"), 0); + case 5: + rb_funcall(self, rb_intern("call_rb_Proc_new_with_block"), 0); + return proc_spec_rb_Proc_new_helper(); + case 6: + return rb_funcall(self, rb_intern("call_block_given?"), 0); + default: + rb_raise(rb_eException, "invalid scenario"); + } + + return Qnil; +} + +void Init_proc_spec(void) { + VALUE cls = rb_define_class("CApiProcSpecs", rb_cObject); + rb_define_method(cls, "rb_proc_new", proc_spec_rb_proc_new, 0); + rb_define_method(cls, "rb_proc_arity", proc_spec_rb_proc_arity, 1); + rb_define_method(cls, "rb_proc_call", proc_spec_rb_proc_call, 2); + rb_define_method(cls, "rb_Proc_new", proc_spec_rb_Proc_new, 1); + rb_define_method(cls, "rb_obj_is_proc", proc_spec_rb_obj_is_proc, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/range_spec.c b/spec/ruby/optional/capi/ext/range_spec.c new file mode 100644 index 0000000000..7a475ec695 --- /dev/null +++ b/spec/ruby/optional/capi/ext/range_spec.c @@ -0,0 +1,50 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE range_spec_rb_range_new(int argc, VALUE* argv, VALUE self) { + int exclude_end = 0; + if(argc == 3) { + exclude_end = RTEST(argv[2]); + } + return rb_range_new(argv[0], argv[1], exclude_end); +} + +VALUE range_spec_rb_range_values(VALUE self, VALUE range) { + VALUE beg; + VALUE end; + int excl; + VALUE ary = rb_ary_new(); + rb_range_values(range, &beg, &end, &excl); + rb_ary_store(ary, 0, beg); + rb_ary_store(ary, 1, end); + rb_ary_store(ary, 2, excl ? Qtrue : Qfalse); + 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); + long len = FIX2LONG(lenv); + int err = FIX2INT(errv); + VALUE ary = rb_ary_new(); + VALUE res = rb_range_beg_len(range, &begp, &lenp, len, err); + rb_ary_store(ary, 0, LONG2FIX(begp)); + rb_ary_store(ary, 1, LONG2FIX(lenp)); + rb_ary_store(ary, 2, res); + 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); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/rational_spec.c b/spec/ruby/optional/capi/ext/rational_spec.c new file mode 100644 index 0000000000..6273af68c5 --- /dev/null +++ b/spec/ruby/optional/capi/ext/rational_spec.c @@ -0,0 +1,54 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE rational_spec_rb_Rational(VALUE self, VALUE num, VALUE den) { + return rb_Rational(num, den); +} + +static VALUE rational_spec_rb_Rational1(VALUE self, VALUE num) { + return rb_Rational1(num); +} + +static VALUE rational_spec_rb_Rational2(VALUE self, VALUE num, VALUE den) { + return rb_Rational2(num, den); +} + +static VALUE rational_spec_rb_rational_new(VALUE self, VALUE num, VALUE den) { + return rb_rational_new(num, den); +} + +static VALUE rational_spec_rb_rational_new1(VALUE self, VALUE num) { + return rb_rational_new1(num); +} + +static VALUE rational_spec_rb_rational_new2(VALUE self, VALUE num, VALUE den) { + return rb_rational_new2(num, den); +} + +static VALUE rational_spec_rb_rational_num(VALUE self, VALUE rational) { + return rb_rational_num(rational); +} + +static VALUE rational_spec_rb_rational_den(VALUE self, VALUE rational) { + return rb_rational_den(rational); +} + +void Init_rational_spec(void) { + VALUE cls = rb_define_class("CApiRationalSpecs", rb_cObject); + rb_define_method(cls, "rb_Rational", rational_spec_rb_Rational, 2); + rb_define_method(cls, "rb_Rational1", rational_spec_rb_Rational1, 1); + rb_define_method(cls, "rb_Rational2", rational_spec_rb_Rational2, 2); + rb_define_method(cls, "rb_rational_new", rational_spec_rb_rational_new, 2); + rb_define_method(cls, "rb_rational_new1", rational_spec_rb_rational_new1, 1); + rb_define_method(cls, "rb_rational_new2", rational_spec_rb_rational_new2, 2); + rb_define_method(cls, "rb_rational_num", rational_spec_rb_rational_num, 1); + rb_define_method(cls, "rb_rational_den", rational_spec_rb_rational_den, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/rbasic_spec.c b/spec/ruby/optional/capi/ext/rbasic_spec.c new file mode 100644 index 0000000000..9178e5f639 --- /dev/null +++ b/spec/ruby/optional/capi/ext/rbasic_spec.c @@ -0,0 +1,100 @@ +#include "ruby.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef FL_SHAREABLE +static const VALUE VISIBLE_BITS = FL_TAINT | FL_FREEZE; +static const VALUE DATA_VISIBLE_BITS = FL_TAINT | FL_FREEZE | ~(FL_USER0 - 1); +#else +static const VALUE VISIBLE_BITS = FL_FREEZE; +static const VALUE DATA_VISIBLE_BITS = FL_FREEZE | ~(FL_USER0 - 1); +#endif + +#if SIZEOF_VALUE == SIZEOF_LONG +#define VALUE2NUM(v) ULONG2NUM(v) +#define NUM2VALUE(n) NUM2ULONG(n) +#elif SIZEOF_VALUE == SIZEOF_LONG_LONG +#define VALUE2NUM(v) ULL2NUM(v) +#define NUM2VALUE(n) NUM2ULL(n) +#else +#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); +} + + static VALUE spec_get_flags(const struct RBasic *b, VALUE visible_bits) { + VALUE flags = b->flags & visible_bits; + return VALUE2NUM(flags); +} + +static VALUE spec_set_flags(struct RBasic *b, VALUE flags, VALUE visible_bits) { + flags &= visible_bits; + b->flags = (b->flags & ~visible_bits) | flags; + return VALUE2NUM(flags); +} + +VALUE rbasic_spec_get_flags(VALUE self, VALUE val) { + return spec_get_flags(RBASIC(val), VISIBLE_BITS); +} + +VALUE rbasic_spec_set_flags(VALUE self, VALUE val, VALUE flags) { + return spec_set_flags(RBASIC(val), NUM2VALUE(flags), VISIBLE_BITS); +} + +VALUE rbasic_spec_copy_flags(VALUE self, VALUE to, VALUE from) { + return spec_set_flags(RBASIC(to), RBASIC(from)->flags, VISIBLE_BITS); +} + +VALUE rbasic_spec_get_klass(VALUE self, VALUE val) { + return RBASIC(val)->klass; +} + +VALUE rbasic_rdata_spec_get_flags(VALUE self, VALUE structure) { + return spec_get_flags(&RDATA(structure)->basic, DATA_VISIBLE_BITS); +} + +VALUE rbasic_rdata_spec_set_flags(VALUE self, VALUE structure, VALUE flags) { + return spec_set_flags(&RDATA(structure)->basic, NUM2VALUE(flags), DATA_VISIBLE_BITS); +} + +VALUE rbasic_rdata_spec_copy_flags(VALUE self, VALUE to, VALUE from) { + return spec_set_flags(&RDATA(to)->basic, RDATA(from)->basic.flags, DATA_VISIBLE_BITS); +} + +VALUE rbasic_rdata_spec_get_klass(VALUE self, VALUE structure) { + return RDATA(structure)->basic.klass; +} + +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); + rb_define_method(cls, "copy_flags", rbasic_spec_copy_flags, 2); + rb_define_method(cls, "get_klass", rbasic_spec_get_klass, 1); + + cls = rb_define_class("CApiRBasicRDataSpecs", rb_cObject); + rb_define_method(cls, "get_flags", rbasic_rdata_spec_get_flags, 1); + rb_define_method(cls, "set_flags", rbasic_rdata_spec_set_flags, 2); + rb_define_method(cls, "copy_flags", rbasic_rdata_spec_copy_flags, 2); + rb_define_method(cls, "get_klass", rbasic_rdata_spec_get_klass, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/regexp_spec.c b/spec/ruby/optional/capi/ext/regexp_spec.c new file mode 100644 index 0000000000..0a62616f33 --- /dev/null +++ b/spec/ruby/optional/capi/ext/regexp_spec.c @@ -0,0 +1,67 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include "ruby/re.h" + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE regexp_spec_re(VALUE self, VALUE str, VALUE options) { + char *cstr = StringValueCStr(str); + int opts = FIX2INT(options); + return rb_reg_new(cstr, strlen(cstr), opts); +} + +VALUE regexp_spec_reg_1st_match(VALUE self, VALUE md) { + return rb_reg_nth_match(1, md); +} + +VALUE regexp_spec_rb_reg_options(VALUE self, VALUE regexp) { + return INT2FIX(rb_reg_options(regexp)); +} + +VALUE regexp_spec_rb_reg_regcomp(VALUE self, VALUE str) { + return rb_reg_regcomp(str); +} + +VALUE regexp_spec_reg_match(VALUE self, VALUE re, VALUE str) { + return rb_reg_match(re, str); +} + +VALUE regexp_spec_backref_get(VALUE self) { + return rb_backref_get(); +} + +static VALUE regexp_spec_backref_set(VALUE self, VALUE backref) { + rb_backref_set(backref); + return Qnil; +} + +VALUE regexp_spec_reg_match_backref_get(VALUE self, VALUE re, VALUE str) { + rb_reg_match(re, str); + return rb_backref_get(); +} + +VALUE regexp_spec_match(VALUE self, VALUE regexp, VALUE str) { + return rb_funcall(regexp, rb_intern("match"), 1, str); +} + +void Init_regexp_spec(void) { + VALUE cls = rb_define_class("CApiRegexpSpecs", rb_cObject); + rb_define_method(cls, "match", regexp_spec_match, 2); + rb_define_method(cls, "a_re", regexp_spec_re, 2); + rb_define_method(cls, "a_re_1st_match", regexp_spec_reg_1st_match, 1); + rb_define_method(cls, "rb_reg_match", regexp_spec_reg_match, 2); + rb_define_method(cls, "rb_backref_get", regexp_spec_backref_get, 0); + rb_define_method(cls, "rb_backref_set", regexp_spec_backref_set, 1); + rb_define_method(cls, "rb_reg_match_backref_get", regexp_spec_reg_match_backref_get, 2); + rb_define_method(cls, "rb_reg_options", regexp_spec_rb_reg_options, 1); + rb_define_method(cls, "rb_reg_regcomp", regexp_spec_rb_reg_regcomp, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/rubyspec.h b/spec/ruby/optional/capi/ext/rubyspec.h new file mode 100644 index 0000000000..7e4a252b38 --- /dev/null +++ b/spec/ruby/optional/capi/ext/rubyspec.h @@ -0,0 +1,63 @@ +#ifndef RUBYSPEC_H +#define RUBYSPEC_H + +/* Define convenience macros similar to the mspec + * guards to assist with version incompatibilities. */ + +#include <ruby.h> +#ifdef HAVE_RUBY_VERSION_H +# include <ruby/version.h> +#else +# include <version.h> +#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))) + +#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 1) +#define RUBY_VERSION_IS_3_1 +#endif + +#if RUBY_VERSION_MAJOR > 3 || (RUBY_VERSION_MAJOR == 3 && RUBY_VERSION_MINOR >= 0) +#define RUBY_VERSION_IS_3_0 +#endif + +#if RUBY_VERSION_MAJOR > 2 || (RUBY_VERSION_MAJOR == 2 && RUBY_VERSION_MINOR >= 7) +#define RUBY_VERSION_IS_2_7 +#endif + +#if RUBY_VERSION_MAJOR > 2 || (RUBY_VERSION_MAJOR == 2 && RUBY_VERSION_MINOR >= 6) +#define RUBY_VERSION_IS_2_6 +#endif + +#if defined(__cplusplus) && !defined(RUBY_VERSION_IS_2_7) +/* Ruby < 2.7 needs this to let these function with callbacks and compile in C++ code */ +#define rb_define_method(mod, name, func, argc) rb_define_method(mod, name, RUBY_METHOD_FUNC(func), argc) +#define rb_define_protected_method(mod, name, func, argc) rb_define_protected_method(mod, name, RUBY_METHOD_FUNC(func), argc) +#define rb_define_private_method(mod, name, func, argc) rb_define_private_method(mod, name, RUBY_METHOD_FUNC(func), argc) +#define rb_define_singleton_method(mod, name, func, argc) rb_define_singleton_method(mod, name, RUBY_METHOD_FUNC(func), argc) +#define rb_define_module_function(mod, name, func, argc) rb_define_module_function(mod, name, RUBY_METHOD_FUNC(func), argc) +#define rb_define_global_function(name, func, argc) rb_define_global_function(name, RUBY_METHOD_FUNC(func), argc) +#define rb_hash_foreach(hash, func, farg) rb_hash_foreach(hash, (int (*)(...))func, farg) +#define st_foreach(tab, func, arg) st_foreach(tab, (int (*)(...))func, arg) +#define rb_block_call(object, name, args_count, args, block_call_func, data) rb_block_call(object, name, args_count, args, RUBY_METHOD_FUNC(block_call_func), data) +#define rb_ensure(b_proc, data1, e_proc, data2) rb_ensure(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2) +#define rb_rescue(b_proc, data1, e_proc, data2) rb_rescue(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2) +#define rb_rescue2(b_proc, data1, e_proc, data2, ...) rb_rescue2(RUBY_METHOD_FUNC(b_proc), data1, RUBY_METHOD_FUNC(e_proc), data2, __VA_ARGS__) +#define rb_catch(tag, func, data) rb_catch(tag, RUBY_METHOD_FUNC(func), data) +#define rb_catch_obj(tag, func, data) rb_catch_obj(tag, RUBY_METHOD_FUNC(func), data) +#define rb_proc_new(fn, arg) rb_proc_new(RUBY_METHOD_FUNC(fn), arg) +#define rb_fiber_new(fn, arg) rb_fiber_new(RUBY_METHOD_FUNC(fn), arg) +#define rb_thread_create(fn, arg) rb_thread_create(RUBY_METHOD_FUNC(fn), arg) +#define rb_define_hooked_variable(name, var, getter, setter) rb_define_hooked_variable(name, var, RUBY_METHOD_FUNC(getter), (void (*)(...))setter) +#endif + +#endif diff --git a/spec/ruby/optional/capi/ext/st_spec.c b/spec/ruby/optional/capi/ext/st_spec.c new file mode 100644 index 0000000000..0fb5b5dc2d --- /dev/null +++ b/spec/ruby/optional/capi/ext/st_spec.c @@ -0,0 +1,83 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <string.h> +#include <stdarg.h> + +#include <ruby/st.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +#if SIZEOF_LONG == SIZEOF_VOIDP +# define ST2NUM(x) ULONG2NUM(x) +#else +# define ST2NUM(x) ULL2NUM(x) +#endif + +VALUE st_spec_st_init_numtable(VALUE self) { + st_table *tbl = st_init_numtable(); + st_index_t entries = tbl->num_entries; + st_free_table(tbl); + return ST2NUM(entries); +} + +VALUE st_spec_st_init_numtable_with_size(VALUE self) { + st_table *tbl = st_init_numtable_with_size(128); + st_index_t entries = tbl->num_entries; + st_free_table(tbl); + return ST2NUM(entries); +} + +VALUE st_spec_st_insert(VALUE self) { + st_index_t entries; + st_table *tbl = st_init_numtable_with_size(128); + st_insert(tbl, 1, 1); + entries = tbl->num_entries; + st_free_table(tbl); + return ST2NUM(entries); +} + +static int sum(st_data_t key, st_data_t value, st_data_t arg) { + *(int*)arg += (int)value; + return ST_CONTINUE; +} + +VALUE st_spec_st_foreach(VALUE self) { + int total = 0; + st_table *tbl = st_init_numtable_with_size(128); + st_insert(tbl, 1, 3); + st_insert(tbl, 2, 4); + st_foreach(tbl, sum, (st_data_t)&total); + st_free_table(tbl); + return INT2FIX(total); +} + +VALUE st_spec_st_lookup(VALUE self) { + st_data_t result = (st_data_t)0; + st_table *tbl = st_init_numtable_with_size(128); + st_insert(tbl, 7, 42); + st_insert(tbl, 2, 4); + st_lookup(tbl, (st_data_t)7, &result); + st_free_table(tbl); +#if SIZEOF_LONG == SIZEOF_VOIDP + return ULONG2NUM(result); +#else + return ULL2NUM(result); +#endif +} + +void Init_st_spec(void) { + VALUE cls = rb_define_class("CApiStSpecs", rb_cObject); + rb_define_method(cls, "st_init_numtable", st_spec_st_init_numtable, 0); + rb_define_method(cls, "st_init_numtable_with_size", st_spec_st_init_numtable_with_size, 0); + rb_define_method(cls, "st_insert", st_spec_st_insert, 0); + rb_define_method(cls, "st_foreach", st_spec_st_foreach, 0); + rb_define_method(cls, "st_lookup", st_spec_st_lookup, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/string_spec.c b/spec/ruby/optional/capi/ext/string_spec.c new file mode 100644 index 0000000000..2a21304a10 --- /dev/null +++ b/spec/ruby/optional/capi/ext/string_spec.c @@ -0,0 +1,689 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <fcntl.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> + +#include "ruby/encoding.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Make sure the RSTRING_PTR and the bytes are in native memory. + * On TruffleRuby RSTRING_PTR and the bytes remain in managed memory + * until they must be written to native memory. + * In some specs we want to test using the native memory. */ +#ifndef NATIVE_RSTRING_PTR +#define NATIVE_RSTRING_PTR(str) RSTRING_PTR(str) +#endif + +VALUE string_spec_rb_cstr2inum(VALUE self, VALUE str, VALUE inum) { + int num = FIX2INT(inum); + return rb_cstr2inum(RSTRING_PTR(str), num); +} + +static VALUE string_spec_rb_cstr_to_inum(VALUE self, VALUE str, VALUE inum, VALUE badcheck) { + int num = FIX2INT(inum); + return rb_cstr_to_inum(RSTRING_PTR(str), num, RTEST(badcheck)); +} + +VALUE string_spec_rb_str2inum(VALUE self, VALUE str, VALUE inum) { + int num = FIX2INT(inum); + return rb_str2inum(str, num); +} + +VALUE string_spec_rb_str_append(VALUE self, VALUE str, VALUE str2) { + return rb_str_append(str, str2); +} + +VALUE string_spec_rb_str_set_len(VALUE self, VALUE str, VALUE len) { + rb_str_set_len(str, NUM2LONG(len)); + + return str; +} + +VALUE string_spec_rb_str_set_len_RSTRING_LEN(VALUE self, VALUE str, VALUE len) { + rb_str_set_len(str, NUM2LONG(len)); + + return INT2FIX(RSTRING_LEN(str)); +} + +VALUE rb_fstring(VALUE str); /* internal.h, used in ripper */ + +VALUE string_spec_rb_str_fstring(VALUE self, VALUE str) { + return rb_fstring(str); +} + +VALUE string_spec_rb_str_buf_new(VALUE self, VALUE len, VALUE str) { + VALUE buf; + + buf = rb_str_buf_new(NUM2LONG(len)); + + if(RTEST(str)) { + snprintf(RSTRING_PTR(buf), NUM2LONG(len), "%s", RSTRING_PTR(str)); + } + + return buf; +} + +VALUE string_spec_rb_str_capacity(VALUE self, VALUE str) { + return SIZET2NUM(rb_str_capacity(str)); +} + +VALUE string_spec_rb_str_buf_new2(VALUE self) { + return rb_str_buf_new2("hello\0invisible"); +} + +VALUE string_spec_rb_str_tmp_new(VALUE self, VALUE len) { + VALUE str = rb_str_tmp_new(NUM2LONG(len)); + rb_obj_reveal(str, rb_cString); + return str; +} + +VALUE string_spec_rb_str_tmp_new_klass(VALUE self, VALUE len) { + return RBASIC_CLASS(rb_str_tmp_new(NUM2LONG(len))); +} + +VALUE string_spec_rb_str_buf_cat(VALUE self, VALUE str) { + const char *question_mark = "?"; + rb_str_buf_cat(str, question_mark, strlen(question_mark)); + return str; +} + +VALUE string_spec_rb_enc_str_buf_cat(VALUE self, VALUE str, VALUE other, VALUE encoding) { + char *cstr = StringValueCStr(other); + rb_encoding* enc = rb_to_encoding(encoding); + return rb_enc_str_buf_cat(str, cstr, strlen(cstr), enc); +} + +VALUE string_spec_rb_str_cat(VALUE self, VALUE str) { + return rb_str_cat(str, "?", 1); +} + +VALUE string_spec_rb_str_cat2(VALUE self, VALUE str) { + return rb_str_cat2(str, "?"); +} + +VALUE string_spec_rb_str_cat_cstr(VALUE self, VALUE str, VALUE other) { + return rb_str_cat_cstr(str, StringValueCStr(other)); +} + +VALUE string_spec_rb_str_cat_cstr_constant(VALUE self, VALUE str) { + return rb_str_cat_cstr(str, "?"); +} + +VALUE string_spec_rb_str_cmp(VALUE self, VALUE str1, VALUE str2) { + return INT2NUM(rb_str_cmp(str1, str2)); +} + +VALUE string_spec_rb_str_conv_enc(VALUE self, VALUE str, VALUE from, VALUE to) { + rb_encoding* from_enc; + rb_encoding* to_enc; + + from_enc = rb_to_encoding(from); + + if(NIL_P(to)) { + to_enc = 0; + } else { + to_enc = rb_to_encoding(to); + } + + return rb_str_conv_enc(str, from_enc, to_enc); +} + +VALUE string_spec_rb_str_conv_enc_opts(VALUE self, VALUE str, VALUE from, VALUE to, + VALUE ecflags, VALUE ecopts) +{ + rb_encoding* from_enc; + rb_encoding* to_enc; + + from_enc = rb_to_encoding(from); + + if(NIL_P(to)) { + to_enc = 0; + } else { + to_enc = rb_to_encoding(to); + } + + return rb_str_conv_enc_opts(str, from_enc, to_enc, FIX2INT(ecflags), ecopts); +} + +VALUE string_spec_rb_str_drop_bytes(VALUE self, VALUE str, VALUE len) { + return rb_str_drop_bytes(str, NUM2LONG(len)); +} + +VALUE string_spec_rb_str_export(VALUE self, VALUE str) { + return rb_str_export(str); +} + +VALUE string_spec_rb_str_export_locale(VALUE self, VALUE str) { + return rb_str_export_locale(str); +} + +VALUE string_spec_rb_str_dup(VALUE self, VALUE str) { + return rb_str_dup(str); +} + +VALUE string_spec_rb_str_freeze(VALUE self, VALUE str) { + return rb_str_freeze(str); +} + +VALUE string_spec_rb_str_inspect(VALUE self, VALUE str) { + return rb_str_inspect(str); +} + +VALUE string_spec_rb_str_intern(VALUE self, VALUE str) { + return rb_str_intern(str); +} + +VALUE string_spec_rb_str_length(VALUE self, VALUE str) { + return rb_str_length(str); +} + +VALUE string_spec_rb_str_new(VALUE self, VALUE str, VALUE len) { + return rb_str_new(RSTRING_PTR(str), FIX2INT(len)); +} + +VALUE string_spec_rb_str_new_native(VALUE self, VALUE str, VALUE len) { + return rb_str_new(NATIVE_RSTRING_PTR(str), FIX2INT(len)); +} + +VALUE string_spec_rb_str_new_offset(VALUE self, VALUE str, VALUE offset, VALUE len) { + return rb_str_new(RSTRING_PTR(str) + FIX2INT(offset), FIX2INT(len)); +} + +VALUE string_spec_rb_str_new2(VALUE self, VALUE str) { + if(NIL_P(str)) { + return rb_str_new2(""); + } else { + return rb_str_new2(RSTRING_PTR(str)); + } +} + +VALUE string_spec_rb_str_encode(VALUE self, VALUE str, VALUE enc, VALUE flags, VALUE opts) { + return rb_str_encode(str, enc, FIX2INT(flags), opts); +} + +VALUE string_spec_rb_str_export_to_enc(VALUE self, VALUE str, VALUE enc) { + return rb_str_export_to_enc(str, rb_to_encoding(enc)); +} + +VALUE string_spec_rb_str_new_cstr(VALUE self, VALUE str) { + if(NIL_P(str)) { + return rb_str_new_cstr(""); + } else { + return rb_str_new_cstr(RSTRING_PTR(str)); + } +} + +VALUE string_spec_rb_external_str_new(VALUE self, VALUE str) { + return rb_external_str_new(RSTRING_PTR(str), RSTRING_LEN(str)); +} + +VALUE string_spec_rb_external_str_new_cstr(VALUE self, VALUE str) { + return rb_external_str_new_cstr(RSTRING_PTR(str)); +} + +VALUE string_spec_rb_external_str_new_with_enc(VALUE self, VALUE str, VALUE len, VALUE encoding) { + return rb_external_str_new_with_enc(RSTRING_PTR(str), FIX2LONG(len), rb_to_encoding(encoding)); +} + +VALUE string_spec_rb_locale_str_new(VALUE self, VALUE str, VALUE len) { + return rb_locale_str_new(RSTRING_PTR(str), FIX2INT(len)); +} + +VALUE string_spec_rb_locale_str_new_cstr(VALUE self, VALUE str) { + return rb_locale_str_new_cstr(RSTRING_PTR(str)); +} + +VALUE string_spec_rb_str_new3(VALUE self, VALUE str) { + return rb_str_new3(str); +} + +VALUE string_spec_rb_str_new4(VALUE self, VALUE str) { + return rb_str_new4(str); +} + +VALUE string_spec_rb_str_new5(VALUE self, VALUE str, VALUE ptr, VALUE len) { + return rb_str_new5(str, RSTRING_PTR(ptr), FIX2INT(len)); +} + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +# endif +#endif + +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)); +} + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#elif defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# pragma clang diagnostic pop +# endif +#endif + +VALUE string_spec_rb_str_plus(VALUE self, VALUE str1, VALUE str2) { + return rb_str_plus(str1, str2); +} + +VALUE string_spec_rb_str_times(VALUE self, VALUE str, VALUE times) { + return rb_str_times(str, times); +} + +VALUE string_spec_rb_str_modify_expand(VALUE self, VALUE str, VALUE size) { + rb_str_modify_expand(str, FIX2LONG(size)); + return str; +} + +VALUE string_spec_rb_str_resize(VALUE self, VALUE str, VALUE size) { + return rb_str_resize(str, FIX2INT(size)); +} + +VALUE string_spec_rb_str_resize_RSTRING_LEN(VALUE self, VALUE str, VALUE size) { + VALUE modified = rb_str_resize(str, FIX2INT(size)); + return INT2FIX(RSTRING_LEN(modified)); +} + +VALUE string_spec_rb_str_resize_copy(VALUE self, VALUE str) { + rb_str_modify_expand(str, 5); + char *buffer = RSTRING_PTR(str); + buffer[1] = 'e'; + buffer[2] = 's'; + buffer[3] = 't'; + rb_str_resize(str, 4); + return str; +} + +VALUE string_spec_rb_str_split(VALUE self, VALUE str) { + return rb_str_split(str, ","); +} + +VALUE string_spec_rb_str_subseq(VALUE self, VALUE str, VALUE beg, VALUE len) { + return rb_str_subseq(str, FIX2INT(beg), FIX2INT(len)); +} + +VALUE string_spec_rb_str_substr(VALUE self, VALUE str, VALUE beg, VALUE len) { + return rb_str_substr(str, FIX2INT(beg), FIX2INT(len)); +} + +VALUE string_spec_rb_str_to_str(VALUE self, VALUE arg) { + return rb_str_to_str(arg); +} + +VALUE string_spec_RSTRING_LEN(VALUE self, VALUE str) { + return INT2FIX(RSTRING_LEN(str)); +} + +VALUE string_spec_RSTRING_LENINT(VALUE self, VALUE str) { + return INT2FIX(RSTRING_LENINT(str)); +} + +VALUE string_spec_RSTRING_PTR_iterate(VALUE self, VALUE str) { + int i; + char* ptr; + + ptr = RSTRING_PTR(str); + for(i = 0; i < RSTRING_LEN(str); i++) { + rb_yield(CHR2FIX(ptr[i])); + } + return Qnil; +} + +VALUE string_spec_RSTRING_PTR_iterate_uint32(VALUE self, VALUE str) { + uint32_t* ptr; + long i, l = RSTRING_LEN(str) / sizeof(uint32_t); + + ptr = (uint32_t *)RSTRING_PTR(str); + for(i = 0; i < l; i++) { + rb_yield(UINT2NUM(ptr[i])); + } + return Qnil; +} + +VALUE string_spec_RSTRING_PTR_short_memcpy(VALUE self, VALUE str) { + /* Short memcpy operations may be optimised by the compiler to a single write. */ + if (RSTRING_LEN(str) >= 8) { + memcpy(RSTRING_PTR(str), "Infinity", 8); + } + return str; +} + +VALUE string_spec_RSTRING_PTR_assign(VALUE self, VALUE str, VALUE chr) { + int i; + char c; + char* ptr; + + ptr = RSTRING_PTR(str); + c = FIX2INT(chr); + + for(i = 0; i < RSTRING_LEN(str); i++) { + ptr[i] = c; + } + return Qnil; +} + +VALUE string_spec_RSTRING_PTR_set(VALUE self, VALUE str, VALUE i, VALUE chr) { + RSTRING_PTR(str)[FIX2INT(i)] = (char) FIX2INT(chr); + return str; +} + +VALUE string_spec_RSTRING_PTR_after_funcall(VALUE self, VALUE str, VALUE cb) { + /* Silence gcc 4.3.2 warning about computed value not used */ + if(RSTRING_PTR(str)) { /* force it out */ + rb_funcall(cb, rb_intern("call"), 1, str); + } + + return rb_str_new2(RSTRING_PTR(str)); +} + +VALUE string_spec_RSTRING_PTR_after_yield(VALUE self, VALUE str) { + char* ptr = NATIVE_RSTRING_PTR(str); + long len = RSTRING_LEN(str); + VALUE from_rstring_ptr; + + ptr[0] = '1'; + rb_yield(str); + ptr[2] = '2'; + + from_rstring_ptr = rb_str_new(ptr, len); + return from_rstring_ptr; +} + +VALUE string_spec_RSTRING_PTR_read(VALUE self, VALUE str, VALUE path) { + char *cpath = StringValueCStr(path); + int fd = open(cpath, O_RDONLY); + VALUE capacities = rb_ary_new(); + if (fd < 0) { + rb_syserr_fail(errno, "open"); + } + + rb_str_modify_expand(str, 30); + rb_ary_push(capacities, SIZET2NUM(rb_str_capacity(str))); + char *buffer = RSTRING_PTR(str); + if (read(fd, buffer, 30) < 0) { + rb_syserr_fail(errno, "read"); + } + + rb_str_modify_expand(str, 53); + rb_ary_push(capacities, SIZET2NUM(rb_str_capacity(str))); + char *buffer2 = RSTRING_PTR(str); + if (read(fd, buffer2 + 30, 53 - 30) < 0) { + rb_syserr_fail(errno, "read"); + } + + rb_str_set_len(str, 53); + close(fd); + return capacities; +} + +VALUE string_spec_StringValue(VALUE self, VALUE str) { + return StringValue(str); +} + +static VALUE string_spec_SafeStringValue(VALUE self, VALUE str) { + SafeStringValue(str); + return str; +} + +static VALUE string_spec_rb_str_hash(VALUE self, VALUE str) { + st_index_t val = rb_str_hash(str); + + return ST2FIX(val); +} + +static VALUE string_spec_rb_str_update(VALUE self, VALUE str, VALUE beg, VALUE end, VALUE replacement) { + rb_str_update(str, FIX2LONG(beg), FIX2LONG(end), replacement); + return str; +} + +static VALUE string_spec_rb_str_free(VALUE self, VALUE str) { + rb_str_free(str); + return Qnil; +} + +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)); +} + +static VALUE string_spec_rb_sprintf3(VALUE self, VALUE str) { + return rb_sprintf("Result: %" PRIsVALUE ".", str); +} + +static VALUE string_spec_rb_sprintf4(VALUE self, VALUE str) { + return rb_sprintf("Result: %+" PRIsVALUE ".", str); +} + +static VALUE string_spec_rb_sprintf5(VALUE self, VALUE width, VALUE precision, VALUE str) { + return rb_sprintf("Result: %*.*s.", FIX2INT(width), FIX2INT(precision), RSTRING_PTR(str)); +} + +static VALUE string_spec_rb_sprintf6(VALUE self, VALUE width, VALUE precision, VALUE str) { + return rb_sprintf("Result: %*.*" PRIsVALUE ".", FIX2INT(width), FIX2INT(precision), str); +} + +static VALUE string_spec_rb_sprintf7(VALUE self, VALUE str, VALUE obj) { + VALUE results = rb_ary_new(); + rb_ary_push(results, rb_sprintf(RSTRING_PTR(str), obj)); + char cstr[256]; + int len = snprintf(cstr, 256, RSTRING_PTR(str), obj); + rb_ary_push(results, rb_str_new(cstr, len)); + return results; +} + +static VALUE string_spec_rb_sprintf8(VALUE self, VALUE str, VALUE num) { + VALUE results = rb_ary_new(); + rb_ary_push(results, rb_sprintf(RSTRING_PTR(str), FIX2LONG(num))); + char cstr[256]; + int len = snprintf(cstr, 256, RSTRING_PTR(str), FIX2LONG(num)); + rb_ary_push(results, rb_str_new(cstr, len)); + return results; +} + +PRINTF_ARGS(static VALUE string_spec_rb_vsprintf_worker(char* fmt, ...), 1, 2); +static VALUE string_spec_rb_vsprintf_worker(char* fmt, ...) { + va_list varargs; + VALUE str; + + va_start(varargs, fmt); + str = rb_vsprintf(fmt, varargs); + va_end(varargs); + + return str; +} + +static VALUE string_spec_rb_vsprintf(VALUE self, VALUE fmt, VALUE str, VALUE i, VALUE f) { + return string_spec_rb_vsprintf_worker(RSTRING_PTR(fmt), RSTRING_PTR(str), + FIX2INT(i), RFLOAT_VALUE(f)); +} + +VALUE string_spec_rb_str_equal(VALUE self, VALUE str1, VALUE str2) { + return rb_str_equal(str1, str2); +} + +static VALUE string_spec_rb_usascii_str_new(VALUE self, VALUE str, VALUE len) { + return rb_usascii_str_new(RSTRING_PTR(str), NUM2INT(len)); +} + +static VALUE string_spec_rb_usascii_str_new_lit(VALUE self) { + return rb_usascii_str_new_lit("nokogiri"); +} + +static VALUE string_spec_rb_usascii_str_new_lit_non_ascii(VALUE self) { + return rb_usascii_str_new_lit("r\xc3\xa9sum\xc3\xa9"); +} + +static VALUE string_spec_rb_usascii_str_new_cstr(VALUE self, VALUE str) { + return rb_usascii_str_new_cstr(RSTRING_PTR(str)); +} + +static VALUE string_spec_rb_String(VALUE self, VALUE val) { + return rb_String(val); +} + +static VALUE string_spec_rb_string_value_cstr(VALUE self, VALUE str) { + char *c_str = rb_string_value_cstr(&str); + return c_str ? Qtrue : Qfalse; +} + +static VALUE string_spec_rb_str_modify(VALUE self, VALUE str) { + rb_str_modify(str); + return str; +} + +static VALUE string_spec_rb_utf8_str_new_static(VALUE self) { + return rb_utf8_str_new_static("nokogiri", 8); +} + +static VALUE string_spec_rb_utf8_str_new(VALUE self) { + return rb_utf8_str_new("nokogiri", 8); +} + +static VALUE string_spec_rb_utf8_str_new_cstr(VALUE self) { + return rb_utf8_str_new_cstr("nokogiri"); +} + +PRINTF_ARGS(static VALUE call_rb_str_vcatf(VALUE mesg, const char *fmt, ...), 2, 3); +static VALUE call_rb_str_vcatf(VALUE mesg, const char *fmt, ...){ + va_list ap; + va_start(ap, fmt); + VALUE result = rb_str_vcatf(mesg, fmt, ap); + va_end(ap); + return result; +} + +static VALUE string_spec_rb_str_vcatf(VALUE self, VALUE mesg) { + return call_rb_str_vcatf(mesg, "fmt %d %d number", 42, 7); +} + +static VALUE string_spec_rb_str_catf(VALUE self, VALUE mesg) { + return rb_str_catf(mesg, "fmt %d %d number", 41, 6); +} + +static VALUE string_spec_rb_str_locktmp(VALUE self, VALUE str) { + return rb_str_locktmp(str); +} + +static VALUE string_spec_rb_str_unlocktmp(VALUE self, VALUE str) { + return rb_str_unlocktmp(str); +} + +void Init_string_spec(void) { + VALUE cls = rb_define_class("CApiStringSpecs", rb_cObject); + rb_define_method(cls, "rb_cstr2inum", string_spec_rb_cstr2inum, 2); + rb_define_method(cls, "rb_cstr_to_inum", string_spec_rb_cstr_to_inum, 3); + rb_define_method(cls, "rb_fstring", string_spec_rb_str_fstring, 1); + rb_define_method(cls, "rb_str2inum", string_spec_rb_str2inum, 2); + rb_define_method(cls, "rb_str_append", string_spec_rb_str_append, 2); + rb_define_method(cls, "rb_str_buf_new", string_spec_rb_str_buf_new, 2); + rb_define_method(cls, "rb_str_capacity", string_spec_rb_str_capacity, 1); + rb_define_method(cls, "rb_str_buf_new2", string_spec_rb_str_buf_new2, 0); + rb_define_method(cls, "rb_str_tmp_new", string_spec_rb_str_tmp_new, 1); + rb_define_method(cls, "rb_str_tmp_new_klass", string_spec_rb_str_tmp_new_klass, 1); + rb_define_method(cls, "rb_str_buf_cat", string_spec_rb_str_buf_cat, 1); + rb_define_method(cls, "rb_enc_str_buf_cat", string_spec_rb_enc_str_buf_cat, 3); + rb_define_method(cls, "rb_str_cat", string_spec_rb_str_cat, 1); + rb_define_method(cls, "rb_str_cat2", string_spec_rb_str_cat2, 1); + 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_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); + rb_define_method(cls, "rb_str_export", string_spec_rb_str_export, 1); + rb_define_method(cls, "rb_str_export_locale", string_spec_rb_str_export_locale, 1); + rb_define_method(cls, "rb_str_dup", string_spec_rb_str_dup, 1); + rb_define_method(cls, "rb_str_freeze", string_spec_rb_str_freeze, 1); + rb_define_method(cls, "rb_str_inspect", string_spec_rb_str_inspect, 1); + rb_define_method(cls, "rb_str_intern", string_spec_rb_str_intern, 1); + rb_define_method(cls, "rb_str_length", string_spec_rb_str_length, 1); + rb_define_method(cls, "rb_str_new", string_spec_rb_str_new, 2); + rb_define_method(cls, "rb_str_new_native", string_spec_rb_str_new_native, 2); + rb_define_method(cls, "rb_str_new_offset", string_spec_rb_str_new_offset, 3); + rb_define_method(cls, "rb_str_new2", string_spec_rb_str_new2, 1); + rb_define_method(cls, "rb_str_encode", string_spec_rb_str_encode, 4); + rb_define_method(cls, "rb_str_export_to_enc", string_spec_rb_str_export_to_enc, 2); + rb_define_method(cls, "rb_str_new_cstr", string_spec_rb_str_new_cstr, 1); + rb_define_method(cls, "rb_external_str_new", string_spec_rb_external_str_new, 1); + rb_define_method(cls, "rb_external_str_new_cstr", string_spec_rb_external_str_new_cstr, 1); + rb_define_method(cls, "rb_external_str_new_with_enc", string_spec_rb_external_str_new_with_enc, 3); + rb_define_method(cls, "rb_locale_str_new", string_spec_rb_locale_str_new, 2); + rb_define_method(cls, "rb_locale_str_new_cstr", string_spec_rb_locale_str_new_cstr, 1); + 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); + 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); + 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); + rb_define_method(cls, "rb_str_resize", string_spec_rb_str_resize, 2); + rb_define_method(cls, "rb_str_resize_RSTRING_LEN", string_spec_rb_str_resize_RSTRING_LEN, 2); + rb_define_method(cls, "rb_str_resize_copy", string_spec_rb_str_resize_copy, 1); + rb_define_method(cls, "rb_str_set_len", string_spec_rb_str_set_len, 2); + rb_define_method(cls, "rb_str_set_len_RSTRING_LEN", string_spec_rb_str_set_len_RSTRING_LEN, 2); + rb_define_method(cls, "rb_str_split", string_spec_rb_str_split, 1); + rb_define_method(cls, "rb_str_subseq", string_spec_rb_str_subseq, 3); + rb_define_method(cls, "rb_str_substr", string_spec_rb_str_substr, 3); + rb_define_method(cls, "rb_str_to_str", string_spec_rb_str_to_str, 1); + rb_define_method(cls, "RSTRING_LEN", string_spec_RSTRING_LEN, 1); + rb_define_method(cls, "RSTRING_LENINT", string_spec_RSTRING_LENINT, 1); + rb_define_method(cls, "RSTRING_PTR_iterate", string_spec_RSTRING_PTR_iterate, 1); + rb_define_method(cls, "RSTRING_PTR_iterate_uint32", string_spec_RSTRING_PTR_iterate_uint32, 1); + rb_define_method(cls, "RSTRING_PTR_short_memcpy", string_spec_RSTRING_PTR_short_memcpy, 1); + rb_define_method(cls, "RSTRING_PTR_assign", string_spec_RSTRING_PTR_assign, 2); + rb_define_method(cls, "RSTRING_PTR_set", string_spec_RSTRING_PTR_set, 3); + rb_define_method(cls, "RSTRING_PTR_after_funcall", string_spec_RSTRING_PTR_after_funcall, 2); + rb_define_method(cls, "RSTRING_PTR_after_yield", string_spec_RSTRING_PTR_after_yield, 1); + rb_define_method(cls, "RSTRING_PTR_read", string_spec_RSTRING_PTR_read, 2); + rb_define_method(cls, "StringValue", string_spec_StringValue, 1); + rb_define_method(cls, "SafeStringValue", string_spec_SafeStringValue, 1); + rb_define_method(cls, "rb_str_hash", string_spec_rb_str_hash, 1); + rb_define_method(cls, "rb_str_update", string_spec_rb_str_update, 4); + rb_define_method(cls, "rb_str_free", string_spec_rb_str_free, 1); + rb_define_method(cls, "rb_sprintf1", string_spec_rb_sprintf1, 2); + rb_define_method(cls, "rb_sprintf2", string_spec_rb_sprintf2, 3); + rb_define_method(cls, "rb_sprintf3", string_spec_rb_sprintf3, 1); + rb_define_method(cls, "rb_sprintf4", string_spec_rb_sprintf4, 1); + rb_define_method(cls, "rb_sprintf5", string_spec_rb_sprintf5, 3); + rb_define_method(cls, "rb_sprintf6", string_spec_rb_sprintf6, 3); + rb_define_method(cls, "rb_sprintf7", string_spec_rb_sprintf7, 2); + rb_define_method(cls, "rb_sprintf8", string_spec_rb_sprintf8, 2); + rb_define_method(cls, "rb_vsprintf", string_spec_rb_vsprintf, 4); + rb_define_method(cls, "rb_str_equal", string_spec_rb_str_equal, 2); + rb_define_method(cls, "rb_usascii_str_new", string_spec_rb_usascii_str_new, 2); + rb_define_method(cls, "rb_usascii_str_new_lit", string_spec_rb_usascii_str_new_lit, 0); + rb_define_method(cls, "rb_usascii_str_new_lit_non_ascii", string_spec_rb_usascii_str_new_lit_non_ascii, 0); + rb_define_method(cls, "rb_usascii_str_new_cstr", string_spec_rb_usascii_str_new_cstr, 1); + rb_define_method(cls, "rb_String", string_spec_rb_String, 1); + rb_define_method(cls, "rb_string_value_cstr", string_spec_rb_string_value_cstr, 1); + rb_define_method(cls, "rb_str_modify", string_spec_rb_str_modify, 1); + rb_define_method(cls, "rb_utf8_str_new_static", string_spec_rb_utf8_str_new_static, 0); + rb_define_method(cls, "rb_utf8_str_new", string_spec_rb_utf8_str_new, 0); + rb_define_method(cls, "rb_utf8_str_new_cstr", string_spec_rb_utf8_str_new_cstr, 0); + rb_define_method(cls, "rb_str_vcatf", string_spec_rb_str_vcatf, 1); + rb_define_method(cls, "rb_str_catf", string_spec_rb_str_catf, 1); + 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); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/struct_spec.c b/spec/ruby/optional/capi/ext/struct_spec.c new file mode 100644 index 0000000000..0393d6937d --- /dev/null +++ b/spec/ruby/optional/capi/ext/struct_spec.c @@ -0,0 +1,85 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include "ruby/intern.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE struct_spec_rb_struct_aref(VALUE self, VALUE st, VALUE key) { + return rb_struct_aref(st, key); +} + +static VALUE struct_spec_rb_struct_getmember(VALUE self, VALUE st, VALUE key) { + return rb_struct_getmember(st, SYM2ID(key)); +} + +static VALUE struct_spec_rb_struct_s_members(VALUE self, VALUE klass) +{ + return rb_ary_dup(rb_struct_s_members(klass)); +} + +static VALUE struct_spec_rb_struct_members(VALUE self, VALUE st) +{ + return rb_ary_dup(rb_struct_members(st)); +} + +static VALUE struct_spec_rb_struct_aset(VALUE self, VALUE st, VALUE key, VALUE value) { + return rb_struct_aset(st, key, value); +} + +/* Only allow setting three attributes, should be sufficient for testing. */ +static VALUE struct_spec_struct_define(VALUE self, VALUE name, + VALUE attr1, VALUE attr2, VALUE attr3) { + + const char *a1 = StringValuePtr(attr1); + const char *a2 = StringValuePtr(attr2); + const char *a3 = StringValuePtr(attr3); + char *nm = NULL; + + if (name != Qnil) nm = StringValuePtr(name); + + return rb_struct_define(nm, a1, a2, a3, NULL); +} + +/* Only allow setting three attributes, should be sufficient for testing. */ +static VALUE struct_spec_struct_define_under(VALUE self, VALUE outer, + VALUE name, VALUE attr1, VALUE attr2, VALUE attr3) { + + const char *nm = StringValuePtr(name); + const char *a1 = StringValuePtr(attr1); + const char *a2 = StringValuePtr(attr2); + const char *a3 = StringValuePtr(attr3); + + return rb_struct_define_under(outer, nm, a1, a2, a3, NULL); +} + +static VALUE struct_spec_rb_struct_new(VALUE self, VALUE klass, + VALUE a, VALUE b, VALUE c) +{ + + return rb_struct_new(klass, a, b, c); +} + +static VALUE struct_spec_rb_struct_size(VALUE self, VALUE st) +{ + return rb_struct_size(st); +} + +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); + rb_define_method(cls, "rb_struct_getmember", struct_spec_rb_struct_getmember, 2); + 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_new", struct_spec_rb_struct_new, 4); + rb_define_method(cls, "rb_struct_size", struct_spec_rb_struct_size, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/symbol_spec.c b/spec/ruby/optional/capi/ext/symbol_spec.c new file mode 100644 index 0000000000..7d9a7b4379 --- /dev/null +++ b/spec/ruby/optional/capi/ext/symbol_spec.c @@ -0,0 +1,105 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include "ruby/encoding.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE symbol_spec_SYMBOL_P(VALUE self, VALUE obj) { + return SYMBOL_P(obj) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_intern(VALUE self, VALUE string) { + return ID2SYM(rb_intern(RSTRING_PTR(string))); +} + +VALUE symbol_spec_rb_intern2(VALUE self, VALUE string, VALUE len) { + return ID2SYM(rb_intern2(RSTRING_PTR(string), FIX2LONG(len))); +} + +VALUE symbol_spec_rb_intern_const(VALUE self, VALUE string) { + return ID2SYM(rb_intern_const(RSTRING_PTR(string))); +} + +VALUE symbol_spec_rb_intern_c_compare(VALUE self, VALUE string, VALUE sym) { + ID symbol = rb_intern(RSTRING_PTR(string)); + return (SYM2ID(sym) == symbol) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_intern2_c_compare(VALUE self, VALUE string, VALUE len, VALUE sym) { + ID symbol = rb_intern2(RSTRING_PTR(string), FIX2LONG(len)); + return (SYM2ID(sym) == symbol) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_intern3(VALUE self, VALUE string, VALUE len, VALUE enc) { + return ID2SYM(rb_intern3(RSTRING_PTR(string), FIX2LONG(len), rb_enc_get(enc))); +} + +VALUE symbol_spec_rb_intern3_c_compare(VALUE self, VALUE string, VALUE len, VALUE enc, VALUE sym) { + ID symbol = rb_intern3(RSTRING_PTR(string), FIX2LONG(len), rb_enc_get(enc)); + return (SYM2ID(sym) == symbol) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_id2name(VALUE self, VALUE symbol) { + const char* c_str = rb_id2name(SYM2ID(symbol)); + return rb_str_new(c_str, strlen(c_str)); +} + +VALUE symbol_spec_rb_id2str(VALUE self, VALUE symbol) { + return rb_id2str(SYM2ID(symbol)); +} + +VALUE symbol_spec_rb_intern_str(VALUE self, VALUE str) { + return ID2SYM(rb_intern_str(str)); +} + +VALUE symbol_spec_rb_check_symbol_cstr(VALUE self, VALUE str) { + return rb_check_symbol_cstr(RSTRING_PTR(str), RSTRING_LEN(str), rb_enc_get(str)); +} + +VALUE symbol_spec_rb_is_class_id(VALUE self, VALUE sym) { + return rb_is_class_id(SYM2ID(sym)) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_is_const_id(VALUE self, VALUE sym) { + return rb_is_const_id(SYM2ID(sym)) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_is_instance_id(VALUE self, VALUE sym) { + return rb_is_instance_id(SYM2ID(sym)) ? Qtrue : Qfalse; +} + +VALUE symbol_spec_rb_sym2str(VALUE self, VALUE sym) { + return rb_sym2str(sym); +} + +VALUE symbol_spec_rb_to_symbol(VALUE self, VALUE val) { + return rb_to_symbol(val); +} + +void Init_symbol_spec(void) { + VALUE cls = rb_define_class("CApiSymbolSpecs", rb_cObject); + rb_define_method(cls, "SYMBOL_P", symbol_spec_SYMBOL_P, 1); + rb_define_method(cls, "rb_intern", symbol_spec_rb_intern, 1); + rb_define_method(cls, "rb_intern2", symbol_spec_rb_intern2, 2); + rb_define_method(cls, "rb_intern_const", symbol_spec_rb_intern_const, 1); + rb_define_method(cls, "rb_intern_c_compare", symbol_spec_rb_intern_c_compare, 2); + rb_define_method(cls, "rb_intern2_c_compare", symbol_spec_rb_intern2_c_compare, 3); + rb_define_method(cls, "rb_intern3", symbol_spec_rb_intern3, 3); + rb_define_method(cls, "rb_intern3_c_compare", symbol_spec_rb_intern3_c_compare, 4); + rb_define_method(cls, "rb_id2name", symbol_spec_rb_id2name, 1); + rb_define_method(cls, "rb_id2str", symbol_spec_rb_id2str, 1); + rb_define_method(cls, "rb_intern_str", symbol_spec_rb_intern_str, 1); + rb_define_method(cls, "rb_check_symbol_cstr", symbol_spec_rb_check_symbol_cstr, 1); + rb_define_method(cls, "rb_is_class_id", symbol_spec_rb_is_class_id, 1); + rb_define_method(cls, "rb_is_const_id", symbol_spec_rb_is_const_id, 1); + rb_define_method(cls, "rb_is_instance_id", symbol_spec_rb_is_instance_id, 1); + rb_define_method(cls, "rb_sym2str", symbol_spec_rb_sym2str, 1); + rb_define_method(cls, "rb_to_symbol", symbol_spec_rb_to_symbol, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/thread_spec.c b/spec/ruby/optional/capi/ext/thread_spec.c new file mode 100644 index 0000000000..21f98dec52 --- /dev/null +++ b/spec/ruby/optional/capi/ext/thread_spec.c @@ -0,0 +1,150 @@ +#include "ruby.h" +#include "ruby/thread.h" +#include "rubyspec.h" + +#include <math.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#if defined(_WIN32) +#define pipe(p) rb_w32_pipe(p) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE thread_spec_rb_thread_alone(VALUE self) { + return rb_thread_alone() ? Qtrue : Qfalse; +} + +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +/* This is unblocked by unblock_func(). */ +static void* blocking_gvl_func(void* data) { + int rfd = *(int *)data; + char dummy = ' '; + ssize_t r; + + do { + r = read(rfd, &dummy, 1); + } while (r == -1 && errno == EINTR); + + close(rfd); + + return (void*)((r == 1 && dummy == 'A') ? Qtrue : Qfalse); +} + +static void unblock_gvl_func(void *data) { + int wfd = *(int *)data; + char dummy = 'A'; + ssize_t r; + + do { + r = write(wfd, &dummy, 1); + } while (r == -1 && errno == EINTR); + + close(wfd); +} + +/* Returns true if the thread is interrupted. */ +static VALUE thread_spec_rb_thread_call_without_gvl(VALUE self) { + int fds[2]; + void* ret; + + if (pipe(fds) == -1) { + rb_raise(rb_eRuntimeError, "could not create pipe"); + } + ret = rb_thread_call_without_gvl(blocking_gvl_func, &fds[0], + unblock_gvl_func, &fds[1]); + return (VALUE)ret; +} + +/* This is unblocked by a signal. */ +static void* blocking_gvl_func_for_udf_io(void *data) { + int rfd = (int)(size_t)data; + char dummy; + + if (read(rfd, &dummy, 1) == -1 && errno == EINTR) { + return (void*)Qtrue; + } else { + return (void*)Qfalse; + } +} + +/* Returns true if the thread is interrupted. */ +static VALUE thread_spec_rb_thread_call_without_gvl_with_ubf_io(VALUE self) { + int fds[2]; + void* ret; + + if (pipe(fds) == -1) { + rb_raise(rb_eRuntimeError, "could not create pipe"); + } + + ret = rb_thread_call_without_gvl(blocking_gvl_func_for_udf_io, + (void*)(size_t)fds[0], RUBY_UBF_IO, 0); + close(fds[0]); + close(fds[1]); + return (VALUE)ret; +} + +static VALUE thread_spec_rb_thread_current(VALUE self) { + return rb_thread_current(); +} + +static VALUE thread_spec_rb_thread_local_aref(VALUE self, VALUE thr, VALUE sym) { + return rb_thread_local_aref(thr, SYM2ID(sym)); +} + +static VALUE thread_spec_rb_thread_local_aset(VALUE self, VALUE thr, VALUE sym, VALUE value) { + return rb_thread_local_aset(thr, SYM2ID(sym), value); +} + +static VALUE thread_spec_rb_thread_wakeup(VALUE self, VALUE thr) { + return rb_thread_wakeup(thr); +} + +static VALUE thread_spec_rb_thread_wait_for(VALUE self, VALUE s, VALUE ms) { + struct timeval tv; + tv.tv_sec = NUM2INT(s); + tv.tv_usec = NUM2INT(ms); + rb_thread_wait_for(tv); + return Qnil; +} + + +VALUE thread_spec_call_proc(void *arg_ptr) { + VALUE arg_array = (VALUE)arg_ptr; + VALUE arg = rb_ary_pop(arg_array); + VALUE proc = rb_ary_pop(arg_array); + return rb_funcall(proc, rb_intern("call"), 1, arg); +} + +static VALUE thread_spec_rb_thread_create(VALUE self, VALUE proc, VALUE arg) { + VALUE args = rb_ary_new(); + rb_ary_push(args, proc); + rb_ary_push(args, arg); + + return rb_thread_create(thread_spec_call_proc, (void*)args); +} + + +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); + rb_define_method(cls, "rb_thread_call_without_gvl", thread_spec_rb_thread_call_without_gvl, 0); + rb_define_method(cls, "rb_thread_call_without_gvl_with_ubf_io", thread_spec_rb_thread_call_without_gvl_with_ubf_io, 0); + rb_define_method(cls, "rb_thread_current", thread_spec_rb_thread_current, 0); + rb_define_method(cls, "rb_thread_local_aref", thread_spec_rb_thread_local_aref, 2); + rb_define_method(cls, "rb_thread_local_aset", thread_spec_rb_thread_local_aset, 3); + rb_define_method(cls, "rb_thread_wakeup", thread_spec_rb_thread_wakeup, 1); + rb_define_method(cls, "rb_thread_wait_for", thread_spec_rb_thread_wait_for, 2); + rb_define_method(cls, "rb_thread_create", thread_spec_rb_thread_create, 2); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/time_spec.c b/spec/ruby/optional/capi/ext/time_spec.c new file mode 100644 index 0000000000..fec70dea9d --- /dev/null +++ b/spec/ruby/optional/capi/ext/time_spec.c @@ -0,0 +1,81 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE time_spec_rb_time_new(VALUE self, VALUE sec, VALUE usec) { + return rb_time_new(NUM2TIMET(sec), NUM2LONG(usec)); +} + +static VALUE time_spec_rb_time_nano_new(VALUE self, VALUE sec, VALUE nsec) { + return rb_time_nano_new(NUM2TIMET(sec), NUM2LONG(nsec)); +} + +static VALUE time_spec_rb_time_num_new(VALUE self, VALUE ts, VALUE offset) { + return rb_time_num_new(ts, offset); +} + +static VALUE time_spec_rb_time_interval(VALUE self, VALUE ts) { + struct timeval interval = rb_time_interval(ts); + VALUE ary = rb_ary_new(); + rb_ary_push(ary, TIMET2NUM(interval.tv_sec)); + rb_ary_push(ary, TIMET2NUM(interval.tv_usec)); + return ary; +} + +static VALUE time_spec_rb_time_timeval(VALUE self, VALUE ts) { + struct timeval tv = rb_time_timeval(ts); + VALUE ary = rb_ary_new(); + rb_ary_push(ary, TIMET2NUM(tv.tv_sec)); + rb_ary_push(ary, TIMET2NUM(tv.tv_usec)); + return ary; +} + +static VALUE time_spec_rb_time_timespec(VALUE self, VALUE time) { + struct timespec ts = rb_time_timespec(time); + VALUE ary = rb_ary_new(); + rb_ary_push(ary, TIMET2NUM(ts.tv_sec)); + rb_ary_push(ary, TIMET2NUM(ts.tv_nsec)); + return ary; +} + +static VALUE time_spec_rb_time_timespec_new(VALUE self, VALUE sec, VALUE nsec, VALUE offset) { + struct timespec ts; + ts.tv_sec = NUM2TIMET(sec); + ts.tv_nsec = NUM2LONG(nsec); + + return rb_time_timespec_new(&ts, NUM2INT(offset)); +} + +static VALUE time_spec_rb_time_from_timspec_now(VALUE self, VALUE offset) { + struct timespec ts; + rb_timespec_now(&ts); + + return rb_time_timespec_new(&ts, NUM2INT(offset)); +} + +static VALUE time_spec_TIMET2NUM(VALUE self) { + time_t t = 10; + return TIMET2NUM(t); +} + +void Init_time_spec(void) { + VALUE cls = rb_define_class("CApiTimeSpecs", rb_cObject); + rb_define_method(cls, "rb_time_new", time_spec_rb_time_new, 2); + rb_define_method(cls, "TIMET2NUM", time_spec_TIMET2NUM, 0); + rb_define_method(cls, "rb_time_nano_new", time_spec_rb_time_nano_new, 2); + rb_define_method(cls, "rb_time_num_new", time_spec_rb_time_num_new, 2); + rb_define_method(cls, "rb_time_interval", time_spec_rb_time_interval, 1); + rb_define_method(cls, "rb_time_timeval", time_spec_rb_time_timeval, 1); + rb_define_method(cls, "rb_time_timespec", time_spec_rb_time_timespec, 1); + rb_define_method(cls, "rb_time_timespec_new", time_spec_rb_time_timespec_new, 3); + rb_define_method(cls, "rb_time_from_timespec", time_spec_rb_time_from_timspec_now, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/tracepoint_spec.c b/spec/ruby/optional/capi/ext/tracepoint_spec.c new file mode 100644 index 0000000000..78c459d6cb --- /dev/null +++ b/spec/ruby/optional/capi/ext/tracepoint_spec.c @@ -0,0 +1,49 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <ruby/debug.h> + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE callback_called = Qnil; + +static void callback(VALUE tpval, void *data) { + callback_called = (VALUE) data; +} + +static VALUE tracepoint_spec_rb_tracepoint_new(VALUE self, VALUE data) { + return rb_tracepoint_new(Qnil, RUBY_EVENT_LINE, callback, (void*) data); +} + +static VALUE tracepoint_spec_callback_called(VALUE self){ + return callback_called; +} + +static VALUE tracepoint_spec_rb_tracepoint_disable(VALUE self, VALUE trace) { + rb_tracepoint_disable(trace); + return rb_tracepoint_enabled_p(trace); +} + +static VALUE tracepoint_spec_rb_tracepoint_enable(VALUE self, VALUE trace) { + rb_tracepoint_enable(trace); + return rb_tracepoint_enabled_p(trace); +} + +static VALUE tracepoint_spec_rb_tracepoint_enabled_p(VALUE self, VALUE trace) { + return rb_tracepoint_enabled_p(trace); +} + +void Init_tracepoint_spec(void) { + VALUE cls = rb_define_class("CApiTracePointSpecs", rb_cObject); + rb_define_method(cls, "rb_tracepoint_new", tracepoint_spec_rb_tracepoint_new, 1); + rb_define_method(cls, "rb_tracepoint_disable", tracepoint_spec_rb_tracepoint_disable, 1); + rb_define_method(cls, "rb_tracepoint_enable", tracepoint_spec_rb_tracepoint_enable, 1); + rb_define_method(cls, "rb_tracepoint_enabled_p", tracepoint_spec_rb_tracepoint_enabled_p, 1); + rb_define_method(cls, "callback_called?", tracepoint_spec_callback_called, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/typed_data_spec.c b/spec/ruby/optional/capi/ext/typed_data_spec.c new file mode 100644 index 0000000000..eca2b667cc --- /dev/null +++ b/spec/ruby/optional/capi/ext/typed_data_spec.c @@ -0,0 +1,189 @@ +#include "ruby.h" +#include "rubyspec.h" + +#include <string.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct sample_typed_wrapped_struct_parent { + int foo; +}; + +void sample_typed_wrapped_struct_parent_free(void* st) { + free(st); +} + +void sample_typed_wrapped_struct_parent_mark(void* st) { +} + +size_t sample_typed_wrapped_struct_parent_memsize(const void* st) { + return sizeof(struct sample_typed_wrapped_struct_parent); +} + +static const rb_data_type_t sample_typed_wrapped_struct_parent_data_type = { + "sample_typed_wrapped_struct_parent", + { + sample_typed_wrapped_struct_parent_mark, + sample_typed_wrapped_struct_parent_free, + sample_typed_wrapped_struct_parent_memsize, + }, +}; + +struct sample_typed_wrapped_struct { + int foo; +}; + +void sample_typed_wrapped_struct_free(void* st) { + free(st); +} + +void sample_typed_wrapped_struct_mark(void* st) { +} + +size_t sample_typed_wrapped_struct_memsize(const void* st) { + if (st == NULL) { + return 0; + } else { + return ((struct sample_typed_wrapped_struct *)st)->foo; + } +} + +static const rb_data_type_t sample_typed_wrapped_struct_data_type = { + "sample_typed_wrapped_struct", + { + sample_typed_wrapped_struct_mark, + sample_typed_wrapped_struct_free, + sample_typed_wrapped_struct_memsize, + }, + &sample_typed_wrapped_struct_parent_data_type, +}; + +struct sample_typed_wrapped_struct_other { + int foo; +}; + +void sample_typed_wrapped_struct_other_free(void* st) { + free(st); +} + +void sample_typed_wrapped_struct_other_mark(void* st) { +} + +size_t sample_typed_wrapped_struct_other_memsize(const void* st) { + return sizeof(struct sample_typed_wrapped_struct_other); +} + +static const rb_data_type_t sample_typed_wrapped_struct_other_data_type = { + "sample_typed_wrapped_struct_other", + { + sample_typed_wrapped_struct_other_mark, + sample_typed_wrapped_struct_other_free, + sample_typed_wrapped_struct_other_memsize, + }, +}; + + +VALUE sdaf_alloc_typed_func(VALUE klass) { + struct sample_typed_wrapped_struct* bar; + bar = (struct sample_typed_wrapped_struct *) malloc(sizeof(struct sample_typed_wrapped_struct)); + bar->foo = 42; + return TypedData_Wrap_Struct(klass, &sample_typed_wrapped_struct_data_type, bar); +} + +VALUE sdaf_typed_get_struct(VALUE self) { + struct sample_typed_wrapped_struct* bar; + TypedData_Get_Struct(self, struct sample_typed_wrapped_struct, &sample_typed_wrapped_struct_data_type, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_typed_wrap_struct(VALUE self, VALUE val) { + struct sample_typed_wrapped_struct* bar; + bar = (struct sample_typed_wrapped_struct *) malloc(sizeof(struct sample_typed_wrapped_struct)); + bar->foo = FIX2INT(val); + return TypedData_Wrap_Struct(rb_cObject, &sample_typed_wrapped_struct_data_type, bar); +} + +VALUE sws_typed_get_struct(VALUE self, VALUE obj) { + struct sample_typed_wrapped_struct* bar; + TypedData_Get_Struct(obj, struct sample_typed_wrapped_struct, &sample_typed_wrapped_struct_data_type, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_typed_get_struct_different_type(VALUE self, VALUE obj) { + struct sample_typed_wrapped_struct_other* bar; + TypedData_Get_Struct(obj, struct sample_typed_wrapped_struct_other, &sample_typed_wrapped_struct_other_data_type, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_typed_get_struct_parent_type(VALUE self, VALUE obj) { + struct sample_typed_wrapped_struct_parent* bar; + TypedData_Get_Struct(obj, struct sample_typed_wrapped_struct_parent, &sample_typed_wrapped_struct_parent_data_type, bar); + + return INT2FIX((*bar).foo); +} + +VALUE sws_typed_get_struct_rdata(VALUE self, VALUE obj) { + struct sample_typed_wrapped_struct* bar; + bar = (struct sample_typed_wrapped_struct*) RTYPEDDATA(obj)->data; + return INT2FIX(bar->foo); +} + +VALUE sws_typed_get_struct_data_ptr(VALUE self, VALUE obj) { + struct sample_typed_wrapped_struct* bar; + bar = (struct sample_typed_wrapped_struct*) DATA_PTR(obj); + return INT2FIX(bar->foo); +} + +VALUE sws_typed_change_struct(VALUE self, VALUE obj, VALUE new_val) { + struct sample_typed_wrapped_struct *new_struct; + new_struct = (struct sample_typed_wrapped_struct *) malloc(sizeof(struct sample_typed_wrapped_struct)); + new_struct->foo = FIX2INT(new_val); + free(RTYPEDDATA(obj)->data); + RTYPEDDATA(obj)->data = new_struct; + return Qnil; +} + +VALUE sws_typed_rb_check_type(VALUE self, VALUE obj, VALUE other) { + rb_check_type(obj, TYPE(other)); + return Qtrue; +} + +VALUE sws_typed_rb_check_typeddata_same_type(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + +VALUE sws_typed_rb_check_typeddata_same_type_parent(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_parent_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + +VALUE sws_typed_rb_check_typeddata_different_type(VALUE self, VALUE obj) { + return rb_check_typeddata(obj, &sample_typed_wrapped_struct_other_data_type) == DATA_PTR(obj) ? Qtrue : Qfalse; +} + +void Init_typed_data_spec(void) { + VALUE cls = rb_define_class("CApiAllocTypedSpecs", rb_cObject); + rb_define_alloc_func(cls, sdaf_alloc_typed_func); + rb_define_method(cls, "typed_wrapped_data", sdaf_typed_get_struct, 0); + cls = rb_define_class("CApiWrappedTypedStructSpecs", rb_cObject); + rb_define_method(cls, "typed_wrap_struct", sws_typed_wrap_struct, 1); + rb_define_method(cls, "typed_get_struct", sws_typed_get_struct, 1); + rb_define_method(cls, "typed_get_struct_other", sws_typed_get_struct_different_type, 1); + rb_define_method(cls, "typed_get_struct_parent", sws_typed_get_struct_parent_type, 1); + rb_define_method(cls, "typed_get_struct_rdata", sws_typed_get_struct_rdata, 1); + rb_define_method(cls, "typed_get_struct_data_ptr", sws_typed_get_struct_data_ptr, 1); + rb_define_method(cls, "typed_change_struct", sws_typed_change_struct, 2); + rb_define_method(cls, "rb_check_type", sws_typed_rb_check_type, 2); + rb_define_method(cls, "rb_check_typeddata_same_type", sws_typed_rb_check_typeddata_same_type, 1); + rb_define_method(cls, "rb_check_typeddata_same_type_parent", sws_typed_rb_check_typeddata_same_type_parent, 1); + rb_define_method(cls, "rb_check_typeddata_different_type", sws_typed_rb_check_typeddata_different_type, 1); +} + +#ifdef __cplusplus +} +#endif + diff --git a/spec/ruby/optional/capi/ext/util_spec.c b/spec/ruby/optional/capi/ext/util_spec.c new file mode 100644 index 0000000000..a7269353c2 --- /dev/null +++ b/spec/ruby/optional/capi/ext/util_spec.c @@ -0,0 +1,123 @@ +#include "ruby.h" +#include "ruby/util.h" +#include "rubyspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VALUE util_spec_rb_scan_args(VALUE self, VALUE argv, VALUE fmt, VALUE expected, VALUE acc) { + int i, result, argc = (int)RARRAY_LEN(argv); + VALUE args[6], failed, a1, a2, a3, a4, a5, a6; + + failed = rb_intern("failed"); + a1 = a2 = a3 = a4 = a5 = a6 = failed; + + for(i = 0; i < argc; i++) { + args[i] = rb_ary_entry(argv, i); + } + +#ifdef RB_SCAN_ARGS_KEYWORDS + if (*RSTRING_PTR(fmt) == 'k') { + result = rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, args, RSTRING_PTR(fmt)+1, &a1, &a2, &a3, &a4, &a5, &a6); + } else { +#endif + result = rb_scan_args(argc, args, RSTRING_PTR(fmt), &a1, &a2, &a3, &a4, &a5, &a6); +#ifdef RB_SCAN_ARGS_KEYWORDS + } +#endif + + switch(NUM2INT(expected)) { + case 6: + rb_ary_unshift(acc, a6); + /* FALLTHROUGH */ + case 5: + rb_ary_unshift(acc, a5); + /* FALLTHROUGH */ + case 4: + rb_ary_unshift(acc, a4); + /* FALLTHROUGH */ + case 3: + rb_ary_unshift(acc, a3); + /* FALLTHROUGH */ + case 2: + rb_ary_unshift(acc, a2); + /* FALLTHROUGH */ + case 1: + rb_ary_unshift(acc, a1); + break; + default: + rb_raise(rb_eException, "unexpected number of arguments returned by rb_scan_args"); + } + + return INT2NUM(result); +} + +static VALUE util_spec_rb_get_kwargs(VALUE self, VALUE keyword_hash, VALUE keys, VALUE required, VALUE optional) { + int req = FIX2INT(required); + int opt = FIX2INT(optional); + int len = RARRAY_LENINT(keys); + + int values_len = req + (opt < 0 ? -1 - opt : opt); + int i = 0; + + ID *ids = (ID*) malloc(sizeof(VALUE) * len); + VALUE *results = (VALUE*) malloc(sizeof(VALUE) * values_len); + int extracted = 0; + VALUE ary = Qundef; + + for (i = 0; i < len; i++) { + ids[i] = SYM2ID(rb_ary_entry(keys, i)); + } + + extracted = rb_get_kwargs(keyword_hash, ids, req, opt, results); + ary = rb_ary_new_from_values(extracted, results); + free(results); + free(ids); + return ary; +} + +static VALUE util_spec_rb_long2int(VALUE self, VALUE n) { + return INT2NUM(rb_long2int(NUM2LONG(n))); +} + +static VALUE util_spec_rb_iter_break(VALUE self) { + rb_iter_break(); + return Qnil; +} + +static VALUE util_spec_rb_sourcefile(VALUE self) { + return rb_str_new2(rb_sourcefile()); +} + +static VALUE util_spec_rb_sourceline(VALUE self) { + return INT2NUM(rb_sourceline()); +} + +static VALUE util_spec_strtod(VALUE self, VALUE string) { + char *endptr = NULL; + double value = strtod(RSTRING_PTR(string), &endptr); + return rb_ary_new_from_args(2, rb_float_new(value), endptr ? rb_str_new2(endptr) : Qnil); +} + +static VALUE util_spec_ruby_strtod(VALUE self, VALUE string) { + char *endptr = NULL; + double value = ruby_strtod(RSTRING_PTR(string), &endptr); + return rb_ary_new_from_args(2, rb_float_new(value), endptr ? rb_str_new2(endptr) : Qnil); +} + +void Init_util_spec(void) { + VALUE cls = rb_define_class("CApiUtilSpecs", rb_cObject); + rb_define_method(cls, "rb_scan_args", util_spec_rb_scan_args, 4); + rb_define_method(cls, "rb_get_kwargs", util_spec_rb_get_kwargs, 4); + rb_define_method(cls, "rb_long2int", util_spec_rb_long2int, 1); + rb_define_method(cls, "rb_iter_break", util_spec_rb_iter_break, 0); + rb_define_method(cls, "rb_sourcefile", util_spec_rb_sourcefile, 0); + rb_define_method(cls, "rb_sourceline", util_spec_rb_sourceline, 0); + rb_define_method(cls, "strtod", util_spec_strtod, 1); + rb_define_method(cls, "ruby_strtod", util_spec_ruby_strtod, 1); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/fiber_spec.rb b/spec/ruby/optional/capi/fiber_spec.rb new file mode 100644 index 0000000000..357033f860 --- /dev/null +++ b/spec/ruby/optional/capi/fiber_spec.rb @@ -0,0 +1,89 @@ +require_relative 'spec_helper' +require 'fiber' + +load_extension('fiber') + +describe "C-API Fiber function" do + before :each do + @s = CApiFiberSpecs.new + end + + describe "rb_fiber_current" do + it "returns the current fiber" do + result = @s.rb_fiber_current() + result.should be_an_instance_of(Fiber) + result.should == Fiber.current + end + end + + describe "rb_fiber_alive_p" do + it "returns the fibers alive status" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + @s.rb_fiber_alive_p(fiber).should be_true + fiber.resume + @s.rb_fiber_alive_p(fiber).should be_false + end + end + + describe "rb_fiber_resume" do + it "resumes the fiber" do + fiber = Fiber.new { |arg| Fiber.yield arg } + @s.rb_fiber_resume(fiber, [1]).should == 1 + @s.rb_fiber_resume(fiber, [2]).should == 2 + end + end + + describe "rb_fiber_yield" do + it "yields the fiber" do + fiber = Fiber.new { @s.rb_fiber_yield([1]) } + fiber.resume.should == 1 + end + end + + describe "rb_fiber_new" do + it "returns a new fiber" do + fiber = @s.rb_fiber_new + fiber.should be_an_instance_of(Fiber) + fiber.resume(42).should == "42" + 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 + end + + fiber.resume + + 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 + + fiber = Fiber.new do + begin + main.transfer + rescue => error + error + end + end + + fiber.transfer + + result = @s.rb_fiber_raise(fiber, "Boom!") + result.should be_an_instance_of(RuntimeError) + result.message.should == "Boom!" + end + end + end +end diff --git a/spec/ruby/optional/capi/file_spec.rb b/spec/ruby/optional/capi/file_spec.rb new file mode 100644 index 0000000000..96d731e4fa --- /dev/null +++ b/spec/ruby/optional/capi/file_spec.rb @@ -0,0 +1,89 @@ +require_relative 'spec_helper' + +load_extension('file') + +describe :rb_file_open, shared: true do + it "raises an ArgumentError if passed an empty mode string" do + touch @name + -> { @s.rb_file_open(@name, "") }.should raise_error(ArgumentError) + end + + it "opens a file in read-only mode with 'r'" do + touch(@name) { |f| f.puts "readable" } + @file = @s.send(@method, @name, "r") + @file.should be_an_instance_of(File) + @file.read.chomp.should == "readable" + end + + it "creates and opens a non-existent file with 'w'" do + @file = @s.send(@method, @name, "w") + @file.write "writable" + @file.flush + File.read(@name).should == "writable" + end + + it "truncates an existing file with 'w'" do + touch(@name) { |f| f.puts "existing" } + @file = @s.send(@method, @name, "w") + File.read(@name).should == "" + end +end + +describe "C-API File function" do + before :each do + @s = CApiFileSpecs.new + @name = tmp("rb_file_open") + end + + after :each do + @file.close if @file and !@file.closed? + rm_r @name + end + + describe "rb_file_open" do + it_behaves_like :rb_file_open, :rb_file_open + end + + describe "rb_file_open_str" do + it_behaves_like :rb_file_open, :rb_file_open_str + end + + describe "rb_file_open_str" do + it "calls #to_path to convert on object to a path" do + path = mock("rb_file_open_str to_path") + path.should_receive(:to_path).and_return(@name) + @file = @s.rb_file_open_str(path, "w") + end + + it "calls #to_str to convert an object to a path if #to_path isn't defined" do + path = mock("rb_file_open_str to_str") + path.should_receive(:to_str).and_return(@name) + @file = @s.rb_file_open_str(path, "w") + end + end + + describe "FilePathValue" do + it "returns a String argument unchanged" do + obj = "path" + @s.FilePathValue(obj).should eql(obj) + end + + it "does not call #to_str on a String" do + obj = "path" + obj.should_not_receive(:to_str) + @s.FilePathValue(obj).should eql(obj) + end + + it "calls #to_path to convert an object to a String" do + obj = mock("FilePathValue to_path") + obj.should_receive(:to_path).and_return("path") + @s.FilePathValue(obj).should == "path" + end + + it "calls #to_str to convert an object to a String if #to_path isn't defined" do + obj = mock("FilePathValue to_str") + obj.should_receive(:to_str).and_return("path") + @s.FilePathValue(obj).should == "path" + end + end +end diff --git a/spec/ruby/optional/capi/fixnum_spec.rb b/spec/ruby/optional/capi/fixnum_spec.rb new file mode 100644 index 0000000000..aa02a0543b --- /dev/null +++ b/spec/ruby/optional/capi/fixnum_spec.rb @@ -0,0 +1,101 @@ +require_relative 'spec_helper' + +load_extension("fixnum") + +describe "CApiFixnumSpecs" do + before :each do + @s = CApiFixnumSpecs.new + end + + describe "FIX2INT" do + max_int = (1 << 31) - 1 + min_int = -(1 << 31) + + it "converts a Fixnum to a native int" do + @s.FIX2INT(42).should == 42 + @s.FIX2INT(-14).should == -14 + @s.FIX2INT(-1).should == -1 + @s.FIX2INT(1).should == 1 + end + + guard -> { fixnum_min <= min_int and max_int <= fixnum_max } do + it "converts a Fixnum representing the minimum and maximum native int" do + @s.FIX2INT(max_int).should == max_int + @s.FIX2INT(min_int).should == min_int + end + end + + platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + it "raises a TypeError if passed nil" do + -> { @s.FIX2INT(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.FIX2INT(25.4567).should == 25 + end + + it "converts a negative Bignum into an signed number" do + @s.FIX2INT(-2147442171).should == -2147442171 + end + + it "raises a RangeError if the value does not fit a native int" do + -> { @s.FIX2INT(0x7fff_ffff+1) }.should raise_error(RangeError) + -> { @s.FIX2INT(-(1 << 31) - 1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.FIX2INT(0xffff_ffff+1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is more than 64bits" do + -> { @s.FIX2INT(0xffff_ffff_ffff_ffff+1) }.should raise_error(RangeError) + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.FIX2INT(obj).should == 2 + end + end + end + + describe "FIX2UINT" do + max_uint = (1 << 32) - 1 + + it "converts a Fixnum" do + @s.FIX2UINT(0).should == 0 + @s.FIX2UINT(1).should == 1 + @s.FIX2UINT(42).should == 42 + end + + guard -> { max_uint <= fixnum_max } do + it "converts a Fixnum representing the maximum native uint" do + @s.FIX2UINT(max_uint).should == max_uint + end + end + + platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + it "raises a TypeError if passed nil" do + -> { @s.FIX2UINT(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.FIX2UINT(25.4567).should == 25 + end + + it "raises a RangeError if the value does not fit a native uint" do + # Interestingly, on MRI FIX2UINT(-1) is allowed + -> { @s.FIX2UINT(0xffff_ffff+1) }.should raise_error(RangeError) + -> { @s.FIX2UINT(-(1 << 31) - 1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.FIX2UINT(0xffff_ffff+1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is more than 64bits" do + -> { @s.FIX2UINT(0xffff_ffff_ffff_ffff+1) }.should raise_error(RangeError) + end + end + end +end diff --git a/spec/ruby/optional/capi/fixtures/class.rb b/spec/ruby/optional/capi/fixtures/class.rb new file mode 100644 index 0000000000..193c7174e0 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/class.rb @@ -0,0 +1,94 @@ +class CApiClassSpecs + module M + def included? + true + end + end + + class Alloc + attr_reader :initialized + attr_reader :arguments + + def initialize(*args) + @initialized = true + @arguments = args + end + end + + class Attr + def initialize + @foo, @bar, @baz = 1, 2, 3 + end + end + + class CVars + @@cvar = :cvar + @c_ivar = :c_ivar + + def new_cv + @@new_cv if defined? @@new_cv + end + + def new_cvar + @@new_cvar if defined? @@new_cvar + end + + def rbdcv_cvar + @@rbdcv_cvar if defined? @@rbdcv_cvar + end + end + + class Inherited + def self.inherited(klass) + klass + end + end + + class NewClass + def self.inherited(klass) + raise "#{name}.inherited called" + end + end + + class Super + def call_super_method + :super_method + end + end + + class Sub < Super + def call_super_method + :subclass_method + end + end + + class SubM < Super + include M + end + + class SubSub < Sub + def call_super_method + :subsubclass_method + end + end + + class SuperSelf + def call_super_method + self + end + end + + class SubSelf < SuperSelf + end + + class A + C = 1 + autoload :D, File.expand_path('../path_to_class.rb', __FILE__) + + class B + end + + module M + end + end +end diff --git a/spec/ruby/optional/capi/fixtures/const_get.rb b/spec/ruby/optional/capi/fixtures/const_get.rb new file mode 100644 index 0000000000..b261a07f7e --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/const_get.rb @@ -0,0 +1,5 @@ +class CApiModuleSpecs + class A + D = 123 + end +end diff --git a/spec/ruby/optional/capi/fixtures/const_get_at.rb b/spec/ruby/optional/capi/fixtures/const_get_at.rb new file mode 100644 index 0000000000..700b570607 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/const_get_at.rb @@ -0,0 +1,5 @@ +class CApiModuleSpecs + class A + B = 123 + end +end diff --git a/spec/ruby/optional/capi/fixtures/const_get_from.rb b/spec/ruby/optional/capi/fixtures/const_get_from.rb new file mode 100644 index 0000000000..9e297daba7 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/const_get_from.rb @@ -0,0 +1,5 @@ +class CApiModuleSpecs + class A + C = 123 + end +end diff --git a/spec/ruby/optional/capi/fixtures/const_get_object.rb b/spec/ruby/optional/capi/fixtures/const_get_object.rb new file mode 100644 index 0000000000..03d9365a3e --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/const_get_object.rb @@ -0,0 +1,3 @@ +class Object + CApiModuleSpecsAutoload = 123 +end diff --git a/spec/ruby/optional/capi/fixtures/encoding.rb b/spec/ruby/optional/capi/fixtures/encoding.rb new file mode 100644 index 0000000000..0858807aa0 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/encoding.rb @@ -0,0 +1,3 @@ +class CApiEncodingSpecs + class S < String; end +end diff --git a/spec/ruby/optional/capi/fixtures/foo.rb b/spec/ruby/optional/capi/fixtures/foo.rb new file mode 100644 index 0000000000..bc4e8f3f7d --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/foo.rb @@ -0,0 +1 @@ +$foo = 7 diff --git a/spec/ruby/optional/capi/fixtures/module.rb b/spec/ruby/optional/capi/fixtures/module.rb new file mode 100644 index 0000000000..aac8bfbfb3 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/module.rb @@ -0,0 +1,39 @@ +class Object + autoload :CApiModuleSpecsAutoload, File.expand_path('../const_get_object.rb', __FILE__) + + module CApiModuleSpecsModuleA + X = 1 + end +end + +class CApiModuleSpecs + class A + autoload :B, File.expand_path('../const_get_at.rb', __FILE__) + autoload :C, File.expand_path('../const_get_from.rb', __FILE__) + autoload :D, File.expand_path('../const_get.rb', __FILE__) + + X = 1 + Q = 1 + R = 2 + S = 3 + T = 5 + end + + class B < A + Y = 2 + end + + class C + Z = 3 + end + + module M + end + + class Super + end + + autoload :ModuleUnderAutoload, "#{object_path}/module_under_autoload_spec" + autoload :RubyUnderAutoload, File.expand_path('../module_autoload', __FILE__) + +end diff --git a/spec/ruby/optional/capi/fixtures/module_autoload.rb b/spec/ruby/optional/capi/fixtures/module_autoload.rb new file mode 100644 index 0000000000..8130a24421 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/module_autoload.rb @@ -0,0 +1,4 @@ +class CApiModuleSpecs + module RubyUnderAutoload + end +end diff --git a/spec/ruby/optional/capi/fixtures/path_to_class.rb b/spec/ruby/optional/capi/fixtures/path_to_class.rb new file mode 100644 index 0000000000..acd577ff24 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/path_to_class.rb @@ -0,0 +1,6 @@ +class CApiClassSpecs + class A + module D + end + end +end diff --git a/spec/ruby/optional/capi/fixtures/proc.rb b/spec/ruby/optional/capi/fixtures/proc.rb new file mode 100644 index 0000000000..fbe37312da --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/proc.rb @@ -0,0 +1,20 @@ +class CApiProcSpecs + def call_nothing + end + + def call_Proc_new + Proc.new + end + + def call_block_given? + block_given? + end + + def call_rb_Proc_new + rb_Proc_new(0) + end + + def call_rb_Proc_new_with_block + rb_Proc_new(0) { :calling_with_block } + end +end diff --git a/spec/ruby/optional/capi/fixtures/read.txt b/spec/ruby/optional/capi/fixtures/read.txt new file mode 100644 index 0000000000..f7065a35d0 --- /dev/null +++ b/spec/ruby/optional/capi/fixtures/read.txt @@ -0,0 +1 @@ +fixture file contents to test read() with RSTRING_PTR diff --git a/spec/ruby/optional/capi/float_spec.rb b/spec/ruby/optional/capi/float_spec.rb new file mode 100644 index 0000000000..4b98902b59 --- /dev/null +++ b/spec/ruby/optional/capi/float_spec.rb @@ -0,0 +1,43 @@ +require_relative 'spec_helper' + +load_extension("float") + +describe "CApiFloatSpecs" do + before :each do + @f = CApiFloatSpecs.new + end + + describe "rb_float_new" do + it "creates a new float" do + ((@f.new_zero - 0).abs < 0.000001).should == true + ((@f.new_point_five - 0.555).abs < 0.000001).should == true + end + end + + describe "RFLOAT_VALUE" do + it "returns the C double value of the Float" do + @f.RFLOAT_VALUE(2.3).should == 2.3 + end + end + + describe "rb_Float" do + it "creates a new Float from a String" do + f = @f.rb_Float("101.99") + f.should be_kind_of(Float) + f.should eql(101.99) + end + end + + describe "RB_FLOAT_TYPE_P" do + it "returns true for floats" do + @f.RB_FLOAT_TYPE_P(2.0).should == true + end + + it "returns false for non-floats" do + @f.RB_FLOAT_TYPE_P(nil).should == false + @f.RB_FLOAT_TYPE_P(10).should == false + @f.RB_FLOAT_TYPE_P("string").should == false + @f.RB_FLOAT_TYPE_P(Object.new).should == false + end + end +end diff --git a/spec/ruby/optional/capi/gc_spec.rb b/spec/ruby/optional/capi/gc_spec.rb new file mode 100644 index 0000000000..23e2b7c9ab --- /dev/null +++ b/spec/ruby/optional/capi/gc_spec.rb @@ -0,0 +1,87 @@ +require_relative 'spec_helper' + +load_extension("gc") + +describe "CApiGCSpecs" do + before :each do + @f = CApiGCSpecs.new + end + + it "correctly gets the value from a registered address" do + @f.registered_tagged_address.should == 10 + @f.registered_tagged_address.should equal(@f.registered_tagged_address) + @f.registered_reference_address.should == "Globally registered data" + @f.registered_reference_address.should equal(@f.registered_reference_address) + end + + describe "rb_gc_enable" do + + after do + GC.enable + end + + it "enables GC when disabled" do + GC.disable + @f.rb_gc_enable.should be_true + end + + it "GC stays enabled when enabled" do + GC.enable + @f.rb_gc_enable.should be_false + end + + it "disables GC when enabled" do + GC.enable + @f.rb_gc_disable.should be_false + end + + it "GC stays disabled when disabled" do + GC.disable + @f.rb_gc_disable.should be_true + end + end + + describe "rb_gc" do + it "increases gc count" do + gc_count = GC.count + @f.rb_gc + GC.count.should > gc_count + end + end + + describe "rb_gc_adjust_memory_usage" do + # Just check that it does not throw, as it seems hard to observe any effect + it "adjusts the amount of registered external memory" do + -> { + @f.rb_gc_adjust_memory_usage(8) + @f.rb_gc_adjust_memory_usage(-8) + }.should_not raise_error + end + end + + describe "rb_gc_register_mark_object" do + it "can be called with an object" do + @f.rb_gc_register_mark_object(Object.new).should be_nil + end + end + + describe "rb_gc_latest_gc_info" do + it "raises a TypeError when hash or symbol not given" do + -> { @f.rb_gc_latest_gc_info("foo") }.should raise_error(TypeError) + end + + it "raises an ArgumentError when unknown symbol given" do + -> { @f.rb_gc_latest_gc_info(:unknown) }.should raise_error(ArgumentError) + end + + it "returns the populated hash when a hash is given" do + h = {} + @f.rb_gc_latest_gc_info(h).should == h + h.size.should_not == 0 + end + + it "returns a value when symbol is given" do + @f.rb_gc_latest_gc_info(:state).should be_kind_of(Symbol) + end + end +end diff --git a/spec/ruby/optional/capi/globals_spec.rb b/spec/ruby/optional/capi/globals_spec.rb new file mode 100644 index 0000000000..cc6f6ef3a8 --- /dev/null +++ b/spec/ruby/optional/capi/globals_spec.rb @@ -0,0 +1,243 @@ +require_relative 'spec_helper' +require "stringio" + +load_extension("globals") + +describe "CApiGlobalSpecs" do + before :each do + @f = CApiGlobalSpecs.new + end + + it "correctly gets global values" do + @f.sb_gv_get("$BLAH").should == nil + @f.sb_gv_get("$\\").should == nil + @f.sb_gv_get("\\").should == nil # rb_gv_get should change \ to $\ + end + + it "returns $~" do + 'a' =~ /a/ + @f.sb_gv_get("$~").to_a.should == ['a'] + @f.sb_gv_get("~").to_a.should == ['a'] + end + + it "correctly sets global values" do + @f.sb_gv_get("$BLAH").should == nil + @f.sb_gv_set("$BLAH", 10) + begin + @f.sb_gv_get("$BLAH").should == 10 + ensure + @f.sb_gv_set("$BLAH", nil) + end + end + + it "lists all global variables" do + @f.rb_f_global_variables.should == Kernel.global_variables + end + + it "rb_define_variable should define a new global variable" do + @f.rb_define_variable("my_gvar", "ABC") + $my_gvar.should == "ABC" + $my_gvar = "XYZ" + @f.sb_get_global_value.should == "XYZ" + end + + it "rb_define_readonly_variable should define a new readonly global variable" do + @f.rb_define_readonly_variable("ro_gvar", 15) + $ro_gvar.should == 15 + -> { $ro_gvar = 10 }.should raise_error(NameError) + end + + it "rb_define_hooked_variable should define a C hooked global variable" do + @f.rb_define_hooked_variable_2x("$hooked_gvar") + $hooked_gvar = 2 + $hooked_gvar.should == 4 + end + + describe "rb_fs" do + before :each do + @field_separator = $; + end + + after :each do + suppress_warning { $; = @field_separator } + end + + it "returns nil by default" do + @f.rb_fs.should == nil + end + + it "returns the value of $;" do + suppress_warning { $; = "foo" } + @f.rb_fs.should == "foo" + end + end + + describe "rb_rs" do + before :each do + @dollar_slash = $/ + end + + after :each do + suppress_warning { $/ = @dollar_slash } + end + + it "returns \\n by default" do + @f.rb_rs.should == "\n" + end + + it "returns the value of $/" do + suppress_warning { $/ = "foo" } + @f.rb_rs.should == "foo" + end + end + + context "rb_std streams" do + before :each do + @name = tmp("rb_std_streams") + @stream = new_io @name + end + + after :each do + @stream.close + rm_r @name + end + + describe "rb_stdin" do + after :each do + $stdin = STDIN + end + + it "returns $stdin" do + $stdin = @stream + @f.rb_stdin.should equal($stdin) + end + end + + describe "rb_stdout" do + after :each do + $stdout = STDOUT + end + + it "returns $stdout" do + $stdout = @stream + @f.rb_stdout.should equal($stdout) + end + end + + describe "rb_stderr" do + after :each do + $stderr = STDERR + end + + it "returns $stderr" do + $stderr = @stream + @f.rb_stderr.should equal($stderr) + end + end + + describe "rb_defout" do + after :each do + $stdout = STDOUT + end + + it "is an alias of rb_stdout" do + $stdout = @stream + @f.rb_defout.should equal($stdout) + end + end + end + + describe "rb_default_rs" do + it "returns \\n" do + @f.rb_default_rs.should == "\n" + end + end + + describe "rb_output_rs" do + before :each do + @dollar_backslash = $\ + end + + after :each do + suppress_warning {$\ = @dollar_backslash} + end + + it "returns nil by default" do + @f.rb_output_rs.should be_nil + end + + it "returns the value of $\\" do + suppress_warning {$\ = "foo"} + @f.rb_output_rs.should == "foo" + end + end + + describe "rb_output_fs" do + before :each do + @dollar_comma = $, + end + + after :each do + suppress_warning {$, = @dollar_comma} + end + + it "returns nil by default" do + @f.rb_output_fs.should be_nil + end + + it "returns the value of $\\" do + suppress_warning {$, = "foo"} + @f.rb_output_fs.should == "foo" + end + end + + describe "rb_lastline_set" do + it "sets the value of $_" do + @f.rb_lastline_set("last line") + $_.should == "last line" + end + + it "sets a Thread-local value" do + $_ = nil + running = false + + thr = Thread.new do + @f.rb_lastline_set("last line") + $_.should == "last line" + running = true + end + + Thread.pass while thr.status and !running + $_.should be_nil + + thr.join + end + end + + describe "rb_lastline_get" do + before do + @io = StringIO.new("last line") + end + + it "gets the value of $_" do + @io.gets + @f.rb_lastline_get.should == "last line" + end + + it "gets a Thread-local value" do + $_ = nil + running = false + + thr = Thread.new do + @io.gets + @f.rb_lastline_get.should == "last line" + running = true + end + + Thread.pass while thr.status and !running + $_.should be_nil + + thr.join + end + end +end diff --git a/spec/ruby/optional/capi/hash_spec.rb b/spec/ruby/optional/capi/hash_spec.rb new file mode 100644 index 0000000000..a60467a66b --- /dev/null +++ b/spec/ruby/optional/capi/hash_spec.rb @@ -0,0 +1,274 @@ +require_relative 'spec_helper' +require_relative '../../shared/hash/key_error' + +load_extension("hash") + +describe "C-API Hash function" do + before :each do + @s = CApiHashSpecs.new + end + + describe "rb_hash" do + it "calls #hash on the object" do + obj = mock("rb_hash") + obj.should_receive(:hash).and_return(5) + @s.rb_hash(obj).should == 5 + end + + it "converts a Bignum returned by #hash to a Fixnum" do + obj = mock("rb_hash bignum") + obj.should_receive(:hash).and_return(bignum_value) + + # The actual conversion is an implementation detail. + # We only care that ultimately we get a Fixnum instance. + @s.rb_hash(obj).should.between?(fixnum_min, fixnum_max) + end + + it "calls #to_int to converts a value returned by #hash to a Fixnum" do + obj = mock("rb_hash to_int") + obj.should_receive(:hash).and_return(obj) + obj.should_receive(:to_int).and_return(12) + + @s.rb_hash(obj).should == 12 + end + + it "raises a TypeError if the object does not implement #to_int" do + obj = mock("rb_hash no to_int") + obj.should_receive(:hash).and_return(nil) + + -> { @s.rb_hash(obj) }.should raise_error(TypeError) + end + end + + describe "rb_hash_new" do + it "returns a new hash" do + @s.rb_hash_new.should == {} + end + + it "creates a hash with no default proc" do + @s.rb_hash_new {}.default_proc.should be_nil + end + end + + describe "rb_ident_hash_new" do + it "returns a new compare by identity hash" do + result = @s.rb_ident_hash_new + result.should == {} + result.compare_by_identity?.should == true + end + end + + describe "rb_hash_dup" do + it "returns a copy of the hash" do + hsh = {} + dup = @s.rb_hash_dup(hsh) + dup.should == hsh + dup.should_not equal(hsh) + end + end + + describe "rb_hash_freeze" do + it "freezes the hash" do + @s.rb_hash_freeze({}).frozen?.should be_true + end + end + + describe "rb_hash_aref" do + it "returns the value associated with the key" do + hsh = {chunky: 'bacon'} + @s.rb_hash_aref(hsh, :chunky).should == 'bacon' + end + + it "returns the default value if it exists" do + hsh = Hash.new(0) + @s.rb_hash_aref(hsh, :chunky).should == 0 + @s.rb_hash_aref_nil(hsh, :chunky).should be_false + end + + it "returns nil if the key does not exist" do + hsh = { } + @s.rb_hash_aref(hsh, :chunky).should be_nil + @s.rb_hash_aref_nil(hsh, :chunky).should be_true + end + end + + describe "rb_hash_aset" do + it "adds the key/value pair and returns the value" do + hsh = {} + @s.rb_hash_aset(hsh, :chunky, 'bacon').should == 'bacon' + hsh.should == {chunky: 'bacon'} + end + end + + describe "rb_hash_clear" do + it "returns self that cleared keys and values" do + hsh = { :key => 'value' } + @s.rb_hash_clear(hsh).should equal(hsh) + hsh.should == {} + end + end + + describe "rb_hash_delete" do + it "removes the key and returns the value" do + hsh = {chunky: 'bacon'} + @s.rb_hash_delete(hsh, :chunky).should == 'bacon' + hsh.should == {} + end + end + + describe "rb_hash_delete_if" do + it "removes an entry if the block returns true" do + h = { a: 1, b: 2, c: 3 } + @s.rb_hash_delete_if(h) { |k, v| v == 2 } + h.should == { a: 1, c: 3 } + end + + it "returns an Enumerator when no block is passed" do + @s.rb_hash_delete_if({a: 1}).should be_an_instance_of(Enumerator) + end + end + + describe "rb_hash_fetch" do + before :each do + @hsh = {:a => 1, :b => 2} + end + + it "returns the value associated with the key" do + @s.rb_hash_fetch(@hsh, :b).should == 2 + end + + it "raises a KeyError if the key is not found and default is set" do + @hsh.default = :d + -> { @s.rb_hash_fetch(@hsh, :c) }.should raise_error(KeyError) + end + + it "raises a KeyError if the key is not found and no default is set" do + -> { @s.rb_hash_fetch(@hsh, :c) }.should raise_error(KeyError) + end + + context "when key is not found" do + it_behaves_like :key_error, -> obj, key { + @s.rb_hash_fetch(obj, key) + }, { a: 1 } + end + end + + describe "rb_hash_foreach" do + it "iterates over the hash" do + hsh = {name: "Evan", sign: :libra} + + out = @s.rb_hash_foreach(hsh) + out.equal?(hsh).should == false + out.should == hsh + end + + it "stops via the callback" do + hsh = {name: "Evan", sign: :libra} + + out = @s.rb_hash_foreach_stop(hsh) + out.size.should == 1 + end + + it "deletes via the callback" do + hsh = {name: "Evan", sign: :libra} + + out = @s.rb_hash_foreach_delete(hsh) + out.should == {name: "Evan", sign: :libra} + hsh.should == {} + end + end + + describe "rb_hash_size" do + it "returns the size of the hash" do + hsh = {fast: 'car', good: 'music'} + @s.rb_hash_size(hsh).should == 2 + end + + it "returns zero for an empty hash" do + @s.rb_hash_size({}).should == 0 + end + end + + describe "rb_hash_lookup" do + it "returns the value associated with the key" do + hsh = {chunky: 'bacon'} + @s.rb_hash_lookup(hsh, :chunky).should == 'bacon' + end + + it "does not return the default value if it exists" do + hsh = Hash.new(0) + @s.rb_hash_lookup(hsh, :chunky).should be_nil + @s.rb_hash_lookup_nil(hsh, :chunky).should be_true + end + + it "returns nil if the key does not exist" do + hsh = { } + @s.rb_hash_lookup(hsh, :chunky).should be_nil + @s.rb_hash_lookup_nil(hsh, :chunky).should be_true + end + + describe "rb_hash_lookup2" do + it "returns the value associated with the key" do + hash = {chunky: 'bacon'} + + @s.rb_hash_lookup2(hash, :chunky, nil).should == 'bacon' + end + + it "returns the default value if the key does not exist" do + hash = {} + + @s.rb_hash_lookup2(hash, :chunky, 10).should == 10 + end + + it "returns undefined if that is the default value specified" do + hsh = Hash.new(0) + @s.rb_hash_lookup2_default_undef(hsh, :chunky).should be_true + end + end + end + + describe "rb_hash_set_ifnone" do + it "sets the default value of non existing keys" do + hash = {} + + @s.rb_hash_set_ifnone(hash, 10) + + hash[:chunky].should == 10 + end + end + + describe "rb_Hash" do + it "returns an empty hash when the argument is nil" do + @s.rb_Hash(nil).should == {} + end + + it "returns an empty hash when the argument is []" do + @s.rb_Hash([]).should == {} + end + + it "tries to convert the passed argument to a hash by calling #to_hash" do + h = BasicObject.new + def h.to_hash; {"bar" => "foo"}; end + @s.rb_Hash(h).should == {"bar" => "foo"} + end + + it "raises a TypeError if the argument does not respond to #to_hash" do + -> { @s.rb_Hash(42) }.should raise_error(TypeError) + end + + it "raises a TypeError if #to_hash does not return a hash" do + h = BasicObject.new + def h.to_hash; 42; end + -> { @s.rb_Hash(h) }.should raise_error(TypeError) + end + end + + describe "hash code functions" do + it "computes a deterministic number" do + hash_code = @s.compute_a_hash_code(53) + hash_code.should be_an_instance_of(Integer) + hash_code.should == @s.compute_a_hash_code(53) + @s.compute_a_hash_code(90).should == @s.compute_a_hash_code(90) + end + end +end diff --git a/spec/ruby/optional/capi/integer_spec.rb b/spec/ruby/optional/capi/integer_spec.rb new file mode 100644 index 0000000000..e26735824e --- /dev/null +++ b/spec/ruby/optional/capi/integer_spec.rb @@ -0,0 +1,290 @@ +# -*- encoding: binary -*- +require_relative 'spec_helper' + +load_extension("integer") + +describe "CApiIntegerSpecs" do + before :each do + @s = CApiIntegerSpecs.new + end + + describe "rb_integer_pack" do + it "converts zero" do + words = "\000" * 9 + result = @s.rb_integer_pack(0, words, 1, 9, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 0 + words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + describe "without two's complement flag" do + before :each do + @value = 0x9876_abcd_4532_ef01_0123_4567_89ab_cdef + @words = "\000" * 16 + end + + describe "with big endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 2, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN) + result.should == 1 + @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 2, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN) + result.should == -1 + @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number exactly -2**(numwords*wordsize*8)" do + result = @s.rb_integer_pack(-2**(2*8*8), @words, 2, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN) + result.should == -2 + @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + end + + describe "with little endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 2, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN) + result.should == 1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 2, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN) + result.should == -1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98" + end + + it "converts a negative number exactly -2**(numwords*wordsize*8)" do + result = @s.rb_integer_pack(-2**(2*8*8), @words, 2, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN) + result.should == -2 + @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + end + end + + describe "with two's complement flag" do + describe "with input less than 64 bits" do + before :each do + @value = 0x0123_4567_89ab_cdef + @words = "\000" * 8 + end + + describe "with big endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\xFE\xDC\xBA\x98\x76\x54\x32\x11" + end + end + + describe "with little endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE" + end + end + + describe "with native endian output" do + big_endian do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 8, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 8, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\xFE\xDC\xBA\x98\x76\x54\x32\x11" + end + end + + little_endian do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 8, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 8, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE" + end + end + end + end + + describe "with input greater than 64 bits" do + before :each do + @value = 0x9876_abcd_4532_ef01_0123_4567_89ab_cdef + @words = "\000" * 16 + end + + describe "with big endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 2, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 2, 8, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x67\x89\x54\x32\xBA\xCD\x10\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11" + end + + describe "with overflow" do + before :each do + @words = "\000" * 9 + end + + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 9, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 2 + @words.should == "\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 9, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -2 + @words.should == "\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11" + end + + it "converts a negative number exactly -2**(numwords*wordsize*8)" do + result = @s.rb_integer_pack(-2**(9*8), @words, 1, 9, 0, + CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + end + end + + describe "with little endian output" do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 2, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 2, 8, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE\x10\xCD\xBA\x32\x54\x89\x67" + end + + describe "with overflow" do + before :each do + @words = "\000" * 9 + end + + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 9, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == 2 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 9, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -2 + @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE" + end + + it "converts a negative number exactly -2**(numwords*wordsize*8)" do + result = @s.rb_integer_pack(-2**(9*8), @words, 1, 9, 0, + CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + end + end + + describe "with native endian output" do + big_endian do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 16, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 16, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x67\x89\x54\x32\xBA\xCD\x10\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11" + end + end + + little_endian do + it "converts a positive number" do + result = @s.rb_integer_pack(@value, @words, 1, 16, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == 1 + @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98" + end + + it "converts a negative number" do + result = @s.rb_integer_pack(-@value, @words, 1, 16, 0, + CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP) + result.should == -1 + @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE\x10\xCD\xBA\x32\x54\x89\x67" + end + end + end + end + end + end + + describe "rb_int_positive_pow" do + it "raises an integer to given power" do + @s.rb_int_positive_pow(2, 3).should == 8 + end + + it "raises a negative integer to given power" do + @s.rb_int_positive_pow(-2, 3).should == -8 + @s.rb_int_positive_pow(-2, 4).should == 16 + end + + it "overflows for large inputs" do + @s.rb_int_positive_pow(8, 23).should == 590295810358705651712 + end + end +end diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb new file mode 100644 index 0000000000..489a01c515 --- /dev/null +++ b/spec/ruby/optional/capi/io_spec.rb @@ -0,0 +1,403 @@ +require_relative 'spec_helper' + +load_extension('io') + +describe "C-API IO function" do + before :each do + @o = CApiIOSpecs.new + + @name = tmp("c_api_rb_io_specs") + touch @name + + @io = new_io @name, "w:utf-8" + @io.sync = true + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + describe "rb_io_addstr" do + it "calls #to_s to convert the object to a String" do + obj = mock("rb_io_addstr string") + obj.should_receive(:to_s).and_return("rb_io_addstr data") + + @o.rb_io_addstr(@io, obj) + File.read(@name).should == "rb_io_addstr data" + end + + it "writes the String to the IO" do + @o.rb_io_addstr(@io, "rb_io_addstr data") + File.read(@name).should == "rb_io_addstr data" + end + + it "returns the io" do + @o.rb_io_addstr(@io, "rb_io_addstr data").should eql(@io) + end + end + + describe "rb_io_printf" do + it "calls #to_str to convert the format object to a String" do + obj = mock("rb_io_printf format") + obj.should_receive(:to_str).and_return("%s") + + @o.rb_io_printf(@io, [obj, "rb_io_printf"]) + File.read(@name).should == "rb_io_printf" + end + + it "calls #to_s to convert the object to a String" do + obj = mock("rb_io_printf string") + obj.should_receive(:to_s).and_return("rb_io_printf") + + @o.rb_io_printf(@io, ["%s", obj]) + File.read(@name).should == "rb_io_printf" + end + + it "writes the Strings to the IO" do + @o.rb_io_printf(@io, ["%s_%s_%s", "rb", "io", "printf"]) + File.read(@name).should == "rb_io_printf" + end + end + + describe "rb_io_print" do + it "calls #to_s to convert the object to a String" do + obj = mock("rb_io_print string") + obj.should_receive(:to_s).and_return("rb_io_print") + + @o.rb_io_print(@io, [obj]) + File.read(@name).should == "rb_io_print" + end + + it "writes the Strings to the IO with no separator" do + @o.rb_io_print(@io, ["rb_", "io_", "print"]) + File.read(@name).should == "rb_io_print" + end + end + + describe "rb_io_puts" do + it "calls #to_s to convert the object to a String" do + obj = mock("rb_io_puts string") + obj.should_receive(:to_s).and_return("rb_io_puts") + + @o.rb_io_puts(@io, [obj]) + File.read(@name).should == "rb_io_puts\n" + end + + it "writes the Strings to the IO separated by newlines" do + @o.rb_io_puts(@io, ["rb", "io", "write"]) + File.read(@name).should == "rb\nio\nwrite\n" + end + end + + describe "rb_io_write" do + it "calls #to_s to convert the object to a String" do + obj = mock("rb_io_write string") + obj.should_receive(:to_s).and_return("rb_io_write") + + @o.rb_io_write(@io, obj) + File.read(@name).should == "rb_io_write" + end + + it "writes the String to the IO" do + @o.rb_io_write(@io, "rb_io_write") + File.read(@name).should == "rb_io_write" + end + end +end + +describe "C-API IO function" do + before :each do + @o = CApiIOSpecs.new + + @name = tmp("c_api_io_specs") + touch @name + + @io = new_io @name, "r:utf-8" + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + describe "rb_io_close" do + it "closes an IO object" do + @io.closed?.should be_false + @o.rb_io_close(@io) + @io.closed?.should be_true + end + end + + describe "rb_io_check_io" do + it "returns the IO object if it is valid" do + @o.rb_io_check_io(@io).should == @io + end + + it "returns nil for non IO objects" do + @o.rb_io_check_io({}).should be_nil + end + end + + describe "rb_io_check_closed" do + it "does not raise an exception if the IO is not closed" do + # The MRI function is void, so we use should_not raise_error + -> { @o.rb_io_check_closed(@io) }.should_not raise_error + end + + it "raises an error if the IO is closed" do + @io.close + -> { @o.rb_io_check_closed(@io) }.should raise_error(IOError) + end + end + + describe "rb_io_set_nonblock" do + platform_is_not :windows do + it "returns true when nonblock flag is set" do + require 'io/nonblock' + @o.rb_io_set_nonblock(@io) + @io.nonblock?.should be_true + end + end + end + + # NOTE: unlike the name might suggest in MRI this function checks if an + # object is frozen, *not* if it's tainted. + describe "rb_io_taint_check" do + it "does not raise an exception if the IO is not frozen" do + -> { @o.rb_io_taint_check(@io) }.should_not raise_error + end + + it "raises an exception if the IO is frozen" do + @io.freeze + + -> { @o.rb_io_taint_check(@io) }.should raise_error(RuntimeError) + end + end + + describe "GetOpenFile" do + it "allows access to the system fileno" do + @o.GetOpenFile_fd($stdin).should == 0 + @o.GetOpenFile_fd($stdout).should == 1 + @o.GetOpenFile_fd($stderr).should == 2 + @o.GetOpenFile_fd(@io).should == @io.fileno + end + end + + describe "rb_io_binmode" do + it "returns self" do + @o.rb_io_binmode(@io).should == @io + end + + it "sets binmode" do + @o.rb_io_binmode(@io) + @io.binmode?.should be_true + end + end +end + +describe "C-API IO function" do + before :each do + @o = CApiIOSpecs.new + @r_io, @w_io = IO.pipe + + @name = tmp("c_api_io_specs") + touch @name + @rw_io = new_io @name, "w+" + end + + after :each do + @r_io.close unless @r_io.closed? + @w_io.close unless @w_io.closed? + @rw_io.close unless @rw_io.closed? + rm_r @name + end + + describe "rb_io_check_readable" do + it "does not raise an exception if the IO is opened for reading" do + # The MRI function is void, so we use should_not raise_error + -> { @o.rb_io_check_readable(@r_io) }.should_not raise_error + end + + it "does not raise an exception if the IO is opened for read and write" do + -> { @o.rb_io_check_readable(@rw_io) }.should_not raise_error + end + + it "raises an IOError if the IO is not opened for reading" do + -> { @o.rb_io_check_readable(@w_io) }.should raise_error(IOError) + end + + end + + describe "rb_io_check_writable" do + it "does not raise an exception if the IO is opened for writing" do + # The MRI function is void, so we use should_not raise_error + -> { @o.rb_io_check_writable(@w_io) }.should_not raise_error + end + + it "does not raise an exception if the IO is opened for read and write" do + -> { @o.rb_io_check_writable(@rw_io) }.should_not raise_error + end + + it "raises an IOError if the IO is not opened for reading" do + -> { @o.rb_io_check_writable(@r_io) }.should raise_error(IOError) + end + end + + describe "rb_io_wait_writable" do + it "returns false if there is no error condition" do + @o.errno = 0 + @o.rb_io_wait_writable(@w_io).should be_false + end + + it "raises an IOError if the IO is closed" do + @w_io.close + -> { @o.rb_io_wait_writable(@w_io) }.should raise_error(IOError) + end + end + + describe "rb_thread_fd_writable" do + it "waits til an fd is ready for writing" do + @o.rb_thread_fd_writable(@w_io).should be_nil + end + end + + platform_is_not :windows do + describe "rb_io_wait_readable" do + it "returns false if there is no error condition" do + @o.errno = 0 + @o.rb_io_wait_readable(@r_io, false).should be_false + end + + it "raises and IOError if passed a closed stream" do + @r_io.close + -> { + @o.rb_io_wait_readable(@r_io, false) + }.should raise_error(IOError) + end + + it "blocks until the io is readable and returns true" 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.errno = Errno::EAGAIN.new.errno + @o.rb_io_wait_readable(@r_io, true).should be_true + @o.instance_variable_get(:@read_data).should == "rb_io_wait_re" + + thr.join + end + end + end + + describe "rb_thread_wait_fd" 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_wait_readable" + end + + Thread.pass until start + + @o.rb_thread_wait_fd(@r_io).should be_nil + + thr.join + end + end + + describe "rb_wait_for_single_fd" 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_wait_readable" + end + + Thread.pass until start + + @o.rb_wait_for_single_fd(@r_io, 1, nil, nil).should == 1 + + thr.join + end + + it "polls whether an fd is ready for reading if timeout is 0" do + @o.rb_wait_for_single_fd(@r_io, 1, 0, 0).should == 0 + end + end +end + +describe "rb_fd_fix_cloexec" do + + before :each do + @o = CApiIOSpecs.new + + @name = tmp("c_api_rb_io_specs") + touch @name + + @io = new_io @name, "w:utf-8" + @io.close_on_exec = false + @io.sync = true + end + + after :each do + @io.close unless @io.closed? + rm_r @name + end + + it "sets close_on_exec on the IO" do + @o.rb_fd_fix_cloexec(@io) + @io.close_on_exec?.should be_true + end + +end + +describe "rb_cloexec_open" do + before :each do + @o = CApiIOSpecs.new + @name = tmp("c_api_rb_io_specs") + touch @name + + @io = nil + end + + after :each do + @io.close unless @io.nil? || @io.closed? + rm_r @name + end + + it "sets close_on_exec on the newly-opened IO" do + @io = @o.rb_cloexec_open(@name, 0, 0) + @io.close_on_exec?.should be_true + end +end + +describe "rb_io_t modes flags" do + before :each do + @o = CApiIOSpecs.new + @name = tmp("c_api_rb_io_specs") + touch @name + end + + after :each do + rm_r @name + end + + it "has the sync flag set if the IO object is synced in Ruby" do + File.open(@name) { |io| + io.sync = true + @o.rb_io_mode_sync_flag(io).should == true + } + end + + it "has the sync flag unset if the IO object is not synced in Ruby" do + File.open(@name) { |io| + io.sync = false + @o.rb_io_mode_sync_flag(io).should == false + } + end +end diff --git a/spec/ruby/optional/capi/kernel_spec.rb b/spec/ruby/optional/capi/kernel_spec.rb new file mode 100644 index 0000000000..758d944da9 --- /dev/null +++ b/spec/ruby/optional/capi/kernel_spec.rb @@ -0,0 +1,620 @@ +require_relative 'spec_helper' + +kernel_path = load_extension("kernel") + +describe "C-API Kernel function" do + before :each do + @s = CApiKernelSpecs.new + end + + describe "rb_block_given_p" do + it "returns false if no block is passed" do + @s.should_not.rb_block_given_p + end + + it "returns true if a block is passed" do + (@s.rb_block_given_p { puts "FOO" } ).should == true + end + end + + describe "rb_need_block" do + it "raises a LocalJumpError if no block is given" do + -> { @s.rb_need_block }.should raise_error(LocalJumpError) + end + + it "does not raise a LocalJumpError if a block is given" do + @s.rb_need_block { }.should == nil + end + end + + describe "rb_block_call" do + before :each do + ScratchPad.record [] + end + + it "calls the block with a single argument" do + ary = [1, 3, 5] + @s.rb_block_call(ary).should == [2, 4, 6] + end + + it "calls the block with multiple arguments in argc / argv" do + ary = [1, 3, 5] + @s.rb_block_call_multi_arg(ary).should == 9 + end + + it "calls the method with no function callback and no block" do + ary = [1, 3, 5] + @s.rb_block_call_no_func(ary).should be_kind_of(Enumerator) + end + + it "calls the method with no function callback and a block" do + ary = [1, 3, 5] + @s.rb_block_call_no_func(ary) do |i| + i + 1 + end.should == [2, 4, 6] + end + + it "can pass extra data to the function" do + ary = [3] + @s.rb_block_call_extra_data(ary).should equal(ary) + end + end + + describe "rb_frame_this_func" do + it "returns the name of the method called" do + @s.rb_frame_this_func_test.should == :rb_frame_this_func_test + @s.rb_frame_this_func_test_again.should == :rb_frame_this_func_test_again + end + end + + describe "rb_raise" do + it "raises an exception" do + -> { @s.rb_raise({}) }.should raise_error(TypeError) + end + + it "terminates the function at the point it was called" do + h = {} + -> { @s.rb_raise(h) }.should raise_error(TypeError) + h[:stage].should == :before + end + end + + describe "rb_throw" do + before :each do + ScratchPad.record [] + end + + it "sets the return value of the catch block to the specified value" do + catch(:foo) do + @s.rb_throw(:return_value) + end.should == :return_value + end + + it "terminates the function at the point it was called" do + catch(:foo) do + ScratchPad << :before_throw + @s.rb_throw(:thrown_value) + ScratchPad << :after_throw + end.should == :thrown_value + ScratchPad.recorded.should == [:before_throw] + end + + it "raises an ArgumentError if there is no catch block for the symbol" do + -> { @s.rb_throw(nil) }.should raise_error(ArgumentError) + end + end + + describe "rb_throw_obj" do + before :each do + ScratchPad.record [] + @tag = Object.new + end + + it "sets the return value of the catch block to the specified value" do + catch(@tag) do + @s.rb_throw_obj(@tag, :thrown_value) + end.should == :thrown_value + end + + it "terminates the function at the point it was called" do + catch(@tag) do + ScratchPad << :before_throw + @s.rb_throw_obj(@tag, :thrown_value) + ScratchPad << :after_throw + end.should == :thrown_value + ScratchPad.recorded.should == [:before_throw] + end + + it "raises an ArgumentError if there is no catch block for the symbol" do + -> { @s.rb_throw(nil) }.should raise_error(ArgumentError) + end + 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/ + 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/ + end + end + + describe "rb_sys_fail" do + it "raises an exception from the value of errno" do + -> do + @s.rb_sys_fail("additional info") + end.should raise_error(SystemCallError, /additional info/) + end + + it "can take a NULL message" do + -> do + @s.rb_sys_fail(nil) + end.should raise_error(Errno::EPERM) + end + end + + describe "rb_syserr_fail" 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 + + it "can take a NULL message" do + -> do + @s.rb_syserr_fail(Errno::EINVAL::Errno, nil) + end.should raise_error(Errno::EINVAL) + end + end + + describe "rb_yield" do + it "yields passed argument" do + ret = nil + @s.rb_yield(1) { |z| ret = z } + ret.should == 1 + end + + it "returns the result from block evaluation" do + @s.rb_yield(1) { |z| z * 1000 }.should == 1000 + end + + it "raises LocalJumpError when no block is given" do + -> { @s.rb_yield(1) }.should raise_error(LocalJumpError) + end + + it "rb_yield to a block that breaks does not raise an error" do + @s.rb_yield(1) { break }.should == nil + end + + it "rb_yield to a block that breaks with a value returns the value" 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 + end + + it "rb_yield to block passed to enumerator" do + enum_class = Class.new do + include Enumerable + end + @s.rb_yield_define_each(enum_class) + res = enum_class.new.collect { |i| i * 2} + res.should == [0, 2, 4, 6] + end + + end + + describe "rb_yield_values" do + it "yields passed arguments" do + ret = nil + @s.rb_yield_values(1, 2) { |x, y| ret = x + y } + ret.should == 3 + end + + it "returns the result from block evaluation" do + @s.rb_yield_values(1, 2) { |x, y| x + y }.should == 3 + end + + it "raises LocalJumpError when no block is given" do + -> { @s.rb_yield_splat([1, 2]) }.should raise_error(LocalJumpError) + end + end + + describe "rb_yield_values2" do + it "yields passed arguments" do + ret = nil + @s.rb_yield_values2([1, 2]) { |x, y| ret = x + y } + ret.should == 3 + end + + it "returns the result from block evaluation" do + @s.rb_yield_values2([1, 2]) { |x, y| x + y }.should == 3 + end + end + + describe "rb_yield_splat" do + it "yields with passed array's contents" do + ret = nil + @s.rb_yield_splat([1, 2]) { |x, y| ret = x + y } + ret.should == 3 + end + + it "returns the result from block evaluation" do + @s.rb_yield_splat([1, 2]) { |x, y| x + y }.should == 3 + end + + it "passes arguments to a block accepting splatted args" do + @s.rb_yield_splat([1, 2]) { |*v| v }.should == [1, 2] + end + + it "raises LocalJumpError when no block is given" do + -> { @s.rb_yield_splat([1, 2]) }.should raise_error(LocalJumpError) + end + end + + describe "rb_protect" do + it "will run a function with an argument" do + proof = [] # Hold proof of work performed after the yield. + res = @s.rb_protect_yield(77, proof) { |x| x + 1 } + res.should == 78 + proof[0].should == 23 + end + + it "will allow cleanup code to run after break" do + proof = [] # Hold proof of work performed after the yield. + @s.rb_protect_yield(77, proof) { |x| break } + proof[0].should == 23 + end + + it "will allow cleanup code to run after break with value" do + proof = [] # Hold proof of work performed after the yield. + res = @s.rb_protect_yield(77, proof) { |x| break x + 1 } + res.should == 78 + proof[0].should == 23 + end + + 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} + end.should raise_error(NameError) + proof[0].should == 23 + end + + 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} + end.should raise_error(NameError) + proof[0].should == 23 + proof[1].should == nil + end + + 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 + end + + it "populates errinfo with the captured exception" do + proof = [] + @s.rb_protect_errinfo(77, proof) { |x| raise NameError }.class.should == NameError + proof[0].should == 23 + proof[1].should == nil + end + + end + + describe "rb_eval_string_protect" do + it "will evaluate the given string" do + proof = [] + res = @s.rb_eval_string_protect('1 + 7', proof) + proof.should == [23, 8] + end + + it "will allow cleanup code to be run when an exception is raised" do + proof = [] + -> do + @s.rb_eval_string_protect('raise RuntimeError', proof) + end.should raise_error(RuntimeError) + proof.should == [23, nil] + end + end + + describe "rb_rescue" do + before :each do + @proc = -> x { x } + @rescue_proc_returns_sentinel = -> *_ { :rescue_proc_executed } + @rescue_proc_returns_arg = -> *a { a } + @arg_error_proc = -> *_ { raise ArgumentError, '' } + @std_error_proc = -> *_ { raise StandardError, '' } + @exc_error_proc = -> *_ { raise Exception, '' } + end + + it "executes passed function" do + @s.rb_rescue(@proc, :no_exc, @rescue_proc_returns_arg, :exc).should == :no_exc + end + + it "executes the passed 'rescue function' if a StandardError exception is raised" do + @s.rb_rescue(@arg_error_proc, nil, @rescue_proc_returns_sentinel, :exc).should == :rescue_proc_executed + @s.rb_rescue(@std_error_proc, nil, @rescue_proc_returns_sentinel, :exc).should == :rescue_proc_executed + end + + it "passes the user supplied argument to the 'rescue function' if a StandardError exception is raised" do + arg1, _ = @s.rb_rescue(@arg_error_proc, nil, @rescue_proc_returns_arg, :exc1) + arg1.should == :exc1 + + arg2, _ = @s.rb_rescue(@std_error_proc, nil, @rescue_proc_returns_arg, :exc2) + arg2.should == :exc2 + end + + it "passes the raised exception to the 'rescue function' if a StandardError exception is raised" do + _, exc1 = @s.rb_rescue(@arg_error_proc, nil, @rescue_proc_returns_arg, :exc) + exc1.class.should == ArgumentError + + _, exc2 = @s.rb_rescue(@std_error_proc, nil, @rescue_proc_returns_arg, :exc) + exc2.class.should == StandardError + end + + it "raises an exception if passed function raises an exception other than StandardError" do + -> { @s.rb_rescue(@exc_error_proc, nil, @rescue_proc_returns_arg, nil) }.should raise_error(Exception) + end + + it "raises an exception if any exception is raised inside the 'rescue 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 + $!.should == nil + end + + it "returns the break value if the passed function yields to a block with a break" do + def proc_caller + @s.rb_rescue(-> *_ { yield }, nil, @proc, nil) + end + + proc_caller { break :value }.should == :value + end + + it "returns nil if the 'rescue function' is null" do + @s.rb_rescue(@std_error_proc, nil, nil, nil).should == nil + end + end + + describe "rb_rescue2" do + it "only rescues if one of the passed exceptions is raised" do + proc = -> x { x } + arg_error_proc = -> *_ { raise ArgumentError, '' } + run_error_proc = -> *_ { raise RuntimeError, '' } + type_error_proc = -> *_ { raise Exception, 'custom error' } + @s.rb_rescue2(arg_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError).should == :exc + @s.rb_rescue2(run_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError).should == :exc + -> { + @s.rb_rescue2(type_error_proc, :no_exc, proc, :exc, ArgumentError, RuntimeError) + }.should raise_error(Exception, 'custom error') + end + + ruby_bug "#17305", ""..."2.7" do + it "raises TypeError if one of the passed exceptions is not a Module" do + -> { + @s.rb_rescue2(-> *_ { raise RuntimeError, "foo" }, :no_exc, -> x { x }, :exc, Object.new, 42) + }.should raise_error(TypeError, /class or module required/) + end + end + end + + describe "rb_catch" do + before :each do + ScratchPad.record [] + end + + it "executes passed function" do + @s.rb_catch("foo", -> { 1 }).should == 1 + end + + it "terminates the function at the point it was called" do + proc = -> do + ScratchPad << :before_throw + throw :thrown_value + ScratchPad << :after_throw + end + @s.rb_catch("thrown_value", proc).should be_nil + ScratchPad.recorded.should == [:before_throw] + end + + it "raises an ArgumentError if the throw symbol isn't caught" do + -> { @s.rb_catch("foo", -> { throw :bar }) }.should raise_error(ArgumentError) + end + end + + describe "rb_catch_obj" do + + before :each do + ScratchPad.record [] + @tag = Object.new + end + + it "executes passed function" do + @s.rb_catch_obj(@tag, -> { 1 }).should == 1 + end + + it "terminates the function at the point it was called" do + proc = -> do + ScratchPad << :before_throw + throw @tag + ScratchPad << :after_throw + end + @s.rb_catch_obj(@tag, proc).should be_nil + ScratchPad.recorded.should == [:before_throw] + end + + it "raises an ArgumentError if the throw symbol isn't caught" do + -> { @s.rb_catch("foo", -> { throw :bar }) }.should raise_error(ArgumentError) + end + end + + describe "rb_ensure" do + it "executes passed function and returns its value" do + proc = -> x { x } + @s.rb_ensure(proc, :proc, proc, :ensure_proc).should == :proc + end + + it "executes passed 'ensure function' when no exception is raised" do + foo = nil + proc = -> *_ { } + ensure_proc = -> x { foo = x } + @s.rb_ensure(proc, nil, ensure_proc, :foo) + foo.should == :foo + end + + it "executes passed 'ensure function' when an exception is raised" do + foo = nil + raise_proc = -> { raise '' } + ensure_proc = -> x { foo = x } + @s.rb_ensure(raise_proc, nil, ensure_proc, :foo) rescue nil + foo.should == :foo + end + + it "raises the same exception raised inside passed function" do + raise_proc = -> *_ { raise RuntimeError, 'foo' } + proc = -> *_ { } + -> { @s.rb_ensure(raise_proc, nil, proc, nil) }.should raise_error(RuntimeError, 'foo') + end + end + + describe "rb_eval_string" do + it "evaluates a string of ruby code" do + @s.rb_eval_string("1+1").should == 2 + end + end + + describe "rb_block_proc" do + it "converts the implicit block into a proc" do + proc = @s.rb_block_proc { 1+1 } + proc.should be_kind_of(Proc) + proc.call.should == 2 + proc.should_not.lambda? + end + + it "passes through an existing lambda and does not convert to a proc" do + b = -> { 1+1 } + proc = @s.rb_block_proc(&b) + proc.should equal(b) + proc.call.should == 2 + proc.should.lambda? + end + end + + describe "rb_block_lambda" do + it "converts the implicit block into a lambda" do + proc = @s.rb_block_lambda { 1+1 } + proc.should be_kind_of(Proc) + proc.call.should == 2 + proc.should.lambda? + end + + it "passes through an existing Proc and does not convert to a lambda" do + b = proc { 1+1 } + proc = @s.rb_block_lambda(&b) + proc.should equal(b) + proc.call.should == 2 + proc.should_not.lambda? + end + end + + describe "rb_exec_recursive" do + it "detects recursive invocations of a method and indicates as such" do + s = "hello" + @s.rb_exec_recursive(s).should == s + end + end + + describe "rb_set_end_proc" do + it "runs a C function on shutdown" do + ruby_exe("require #{kernel_path.inspect}; CApiKernelSpecs.new.rb_set_end_proc(STDOUT)").should == "in write_io" + end + end + + describe "rb_f_sprintf" do + it "returns a string according to format and arguments" do + @s.rb_f_sprintf(["%d %f %s", 10, 2.5, "test"]).should == "10 2.500000 test" + end + end + + describe "rb_make_backtrace" do + it "returns a caller backtrace" do + backtrace = @s.rb_make_backtrace + lines = backtrace.select {|l| l =~ /#{__FILE__}/ } + lines.should_not be_empty + end + end + + describe "rb_funcall3" do + before :each do + @obj = Object.new + class << @obj + def method_public; :method_public end + def method_private; :method_private end + private :method_private + end + end + + it "calls a public method" do + @s.rb_funcall3(@obj, :method_public).should == :method_public + end + it "does not call a private method" do + -> { @s.rb_funcall3(@obj, :method_private) }.should raise_error(NoMethodError, /private/) + end + end + + describe 'rb_funcall' do + before :each do + @obj = Object.new + class << @obj + def many_args(*args) + args + end + end + end + + it "can call a public method with 15 arguments" do + @s.rb_funcall_many_args(@obj, :many_args).should == 15.downto(1).to_a + end + end + describe 'rb_funcall_with_block' do + before :each do + @obj = Object.new + class << @obj + def method_public; yield end + def method_private; yield end + private :method_private + end + end + + it "calls a method with block" do + @s.rb_funcall_with_block(@obj, :method_public, proc { :result }).should == :result + end + + it "does not call a private method" do + -> { @s.rb_funcall_with_block(@obj, :method_private, proc { :result }) }.should raise_error(NoMethodError, /private/) + end + end +end diff --git a/spec/ruby/optional/capi/language_spec.rb b/spec/ruby/optional/capi/language_spec.rb new file mode 100644 index 0000000000..f59b87f2a1 --- /dev/null +++ b/spec/ruby/optional/capi/language_spec.rb @@ -0,0 +1,37 @@ +require_relative 'spec_helper' + +load_extension("language") + +describe "C language construct" do + before :each do + @s = CApiLanguageSpecs.new + end + + describe "switch (VALUE)" do + it "works for Qtrue" do + @s.switch(true).should == :true + end + + it "works for Qfalse" do + @s.switch(false).should == :false + end + + it "works for Qnil" do + @s.switch(nil).should == :nil + end + + it "works for Qundef" do + @s.switch(:undef).should == :undef + end + + it "works for the default case" do + @s.switch(Object.new).should == :default + end + end + + describe "local variable assignment with the same name as a global" do + it "works for rb_mProcess" do + @s.global_local_var.should.equal?(Process) + end + end +end diff --git a/spec/ruby/optional/capi/marshal_spec.rb b/spec/ruby/optional/capi/marshal_spec.rb new file mode 100644 index 0000000000..f15b6b705a --- /dev/null +++ b/spec/ruby/optional/capi/marshal_spec.rb @@ -0,0 +1,46 @@ +require_relative 'spec_helper' + +load_extension("marshal") + +describe "CApiMarshalSpecs" do + before :each do + @s = CApiMarshalSpecs.new + end + + describe "rb_marshal_dump" do + before :each do + @obj = "foo" + end + + it "marshals an object" do + expected = Marshal.dump(@obj) + + @s.rb_marshal_dump(@obj, nil).should == expected + end + + it "marshals an object and write to an IO when passed" do + expected_io = IOStub.new + test_io = IOStub.new + + Marshal.dump(@obj, expected_io) + + @s.rb_marshal_dump(@obj, test_io) + + test_io.should == expected_io + end + + end + + describe "rb_marshal_load" do + before :each do + @obj = "foo" + @data = Marshal.dump(@obj) + end + + it "unmarshals an object" do + @s.rb_marshal_load(@data).should == @obj + end + + end + +end diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb new file mode 100644 index 0000000000..acf4d1fe48 --- /dev/null +++ b/spec/ruby/optional/capi/module_spec.rb @@ -0,0 +1,423 @@ +require_relative 'spec_helper' +require_relative 'fixtures/module' + +load_extension('module') +compile_extension("module_under_autoload") + +describe "CApiModule" do + + before :each do + @m = CApiModuleSpecs.new + end + + describe "rb_define_global_const" do + it "defines a constant on Object" do + @m.rb_define_global_const("CApiModuleSpecsGlobalConst", 7) + ::CApiModuleSpecsGlobalConst.should == 7 + Object.send :remove_const, :CApiModuleSpecsGlobalConst + end + end + + describe "rb_const_set given a symbol name and a value" do + it "sets a new constant on a module" do + @m.rb_const_set(CApiModuleSpecs::C, :W, 7) + CApiModuleSpecs::C::W.should == 7 + end + + it "sets an existing constant's value" do + -> { + @m.rb_const_set(CApiModuleSpecs::C, :Z, 8) + }.should complain(/already initialized constant/) + CApiModuleSpecs::C::Z.should == 8 + end + + it "allows arbitrary names, including constant names not valid in Ruby" do + -> { + CApiModuleSpecs::C.const_set(:_INVALID, 1) + }.should raise_error(NameError, /wrong constant name/) + + @m.rb_const_set(CApiModuleSpecs::C, :_INVALID, 2) + @m.rb_const_get(CApiModuleSpecs::C, :_INVALID).should == 2 + + # Ruby-level should still not allow access + -> { + CApiModuleSpecs::C.const_get(:_INVALID) + }.should raise_error(NameError, /wrong constant name/) + end + end + + describe "rb_define_module" do + it "returns the module if it is already defined" do + mod = @m.rb_define_module("CApiModuleSpecsModuleA") + mod.const_get(:X).should == 1 + end + + it "raises a TypeError if the constant is not a module" do + ::CApiModuleSpecsGlobalConst = 7 + -> { @m.rb_define_module("CApiModuleSpecsGlobalConst") }.should raise_error(TypeError) + Object.send :remove_const, :CApiModuleSpecsGlobalConst + end + + it "defines a new module at toplevel" do + mod = @m.rb_define_module("CApiModuleSpecsModuleB") + mod.should be_kind_of(Module) + mod.name.should == "CApiModuleSpecsModuleB" + ::CApiModuleSpecsModuleB.should be_kind_of(Module) + Object.send :remove_const, :CApiModuleSpecsModuleB + end + end + + describe "rb_define_module_under" do + it "creates a new module inside the inner class" do + mod = @m.rb_define_module_under(CApiModuleSpecs, "ModuleSpecsModuleUnder1") + mod.should be_kind_of(Module) + end + + it "sets the module name" do + mod = @m.rb_define_module_under(CApiModuleSpecs, "ModuleSpecsModuleUnder2") + mod.name.should == "CApiModuleSpecs::ModuleSpecsModuleUnder2" + end + end + + describe "rb_define_module_under" do + it "defines a module for an existing Autoload with an extension" do + CApiModuleSpecs::ModuleUnderAutoload.name.should == "CApiModuleSpecs::ModuleUnderAutoload" + end + + it "defines a module for an existing Autoload with a ruby object" do + CApiModuleSpecs::RubyUnderAutoload.name.should == "CApiModuleSpecs::RubyUnderAutoload" + end + end + + describe "rb_define_const given a String name and a value" do + it "defines a new constant on a module" do + @m.rb_define_const(CApiModuleSpecs::C, "V", 7) + CApiModuleSpecs::C::V.should == 7 + end + + it "sets an existing constant's value" do + -> { + @m.rb_define_const(CApiModuleSpecs::C, "Z", 9) + }.should complain(/already initialized constant/) + CApiModuleSpecs::C::Z.should == 9 + end + end + + describe "rb_const_defined" do + # The fixture converts C boolean test to Ruby 'true' / 'false' + it "returns C non-zero if a constant is defined" do + @m.rb_const_defined(CApiModuleSpecs::A, :X).should be_true + end + + it "returns C non-zero if a constant is defined in Object" do + @m.rb_const_defined(CApiModuleSpecs::A, :Module).should be_true + end + end + + describe "rb_const_defined_at" do + # The fixture converts C boolean test to Ruby 'true' / 'false' + it "returns C non-zero if a constant is defined" do + @m.rb_const_defined_at(CApiModuleSpecs::A, :X).should be_true + end + + it "does not search in ancestors for the constant" do + @m.rb_const_defined_at(CApiModuleSpecs::B, :X).should be_false + end + + it "does not search in Object" do + @m.rb_const_defined_at(CApiModuleSpecs::A, :Module).should be_false + end + end + + describe "rb_const_get" do + it "returns a constant defined in the module" do + @m.rb_const_get(CApiModuleSpecs::A, :X).should == 1 + end + + it "returns a constant defined in the module for multiple constants" do + [:Q, :R, :S, :T].each { |x| @m.rb_const_get(CApiModuleSpecs::A, x).should == CApiModuleSpecs::A.const_get(x) } + end + + it "returns a constant defined at toplevel" do + @m.rb_const_get(CApiModuleSpecs::A, :Integer).should == Integer + end + + it "returns a constant defined in a superclass" do + @m.rb_const_get(CApiModuleSpecs::B, :X).should == 1 + end + + it "calls #const_missing if the constant is not defined in the class or ancestors" do + CApiModuleSpecs::A.should_receive(:const_missing).with(:CApiModuleSpecsUndefined) + @m.rb_const_get(CApiModuleSpecs::A, :CApiModuleSpecsUndefined) + end + + it "resolves autoload constants in classes" do + @m.rb_const_get(CApiModuleSpecs::A, :D).should == 123 + end + + it "resolves autoload constants in Object" do + @m.rb_const_get(Object, :CApiModuleSpecsAutoload).should == 123 + end + + it "allows arbitrary names, including constant names not valid in Ruby" do + -> { + CApiModuleSpecs::A.const_get(:_INVALID) + }.should raise_error(NameError, /wrong constant name/) + + -> { + @m.rb_const_get(CApiModuleSpecs::A, :_INVALID) + }.should raise_error(NameError, /uninitialized constant/) + end + end + + describe "rb_const_get_from" do + it "returns a constant defined in the module" do + @m.rb_const_get_from(CApiModuleSpecs::B, :Y).should == 2 + end + + it "returns a constant defined in a superclass" do + @m.rb_const_get_from(CApiModuleSpecs::B, :X).should == 1 + end + + it "calls #const_missing if the constant is not defined in the class or ancestors" do + CApiModuleSpecs::M.should_receive(:const_missing).with(:Integer) + @m.rb_const_get_from(CApiModuleSpecs::M, :Integer) + end + + it "resolves autoload constants" do + @m.rb_const_get_from(CApiModuleSpecs::A, :C).should == 123 + end + end + + describe "rb_const_get_at" do + it "returns a constant defined in the module" do + @m.rb_const_get_at(CApiModuleSpecs::B, :Y).should == 2 + end + + it "resolves autoload constants" do + @m.rb_const_get_at(CApiModuleSpecs::A, :B).should == 123 + end + + it "calls #const_missing if the constant is not defined in the module" do + CApiModuleSpecs::B.should_receive(:const_missing).with(:X) + @m.rb_const_get_at(CApiModuleSpecs::B, :X) + end + end + + describe "rb_define_alias" do + it "defines an alias for an existing method" do + cls = Class.new do + def method_to_be_aliased + :method_to_be_aliased + end + end + + @m.rb_define_alias cls, "method_alias", "method_to_be_aliased" + cls.new.method_alias.should == :method_to_be_aliased + end + end + + describe "rb_alias" do + it "defines an alias for an existing method" do + cls = Class.new do + def method_to_be_aliased + :method_to_be_aliased + end + end + + @m.rb_alias cls, :method_alias, :method_to_be_aliased + cls.new.method_alias.should == :method_to_be_aliased + end + end + + describe "rb_define_global_function" do + it "defines a method on Kernel" do + @m.rb_define_global_function("module_specs_global_function") + Kernel.should have_method(:module_specs_global_function) + module_specs_global_function.should == :test_method + end + end + + describe "rb_define_method" do + it "defines a method on a class" do + cls = Class.new + @m.rb_define_method(cls, "test_method") + cls.should have_instance_method(:test_method) + cls.new.test_method.should == :test_method + end + + it "returns the correct arity when argc of the method in class is 0" do + cls = Class.new + @m.rb_define_method(cls, "test_method") + cls.new.method(:test_method).arity.should == 0 + end + + it "returns the correct arity when argc of the method in class is -1" do + cls = Class.new + @m.rb_define_method_c_array(cls, "test_method_c_array") + cls.new.method(:test_method_c_array).arity.should == -1 + end + + it "returns the correct arity when argc of the method in class is -2" do + cls = Class.new + @m.rb_define_method_ruby_array(cls, "test_method_ruby_array") + cls.new.method(:test_method_ruby_array).arity.should == -1 + end + + it "returns the correct arity when argc of the method in class is 2" do + cls = Class.new + @m.rb_define_method_2required(cls, "test_method_2required") + cls.new.method(:test_method_2required).arity.should == 2 + end + + it "defines a method on a module" do + mod = Module.new + @m.rb_define_method(mod, "test_method") + mod.should have_instance_method(:test_method) + end + + it "returns the correct arity of the method in module" do + mod = Module.new + @m.rb_define_method(mod, "test_method") + mod.instance_method(:test_method).arity.should == 0 + end + end + + describe "rb_define_module_function" do + before :each do + @mod = Module.new + @m.rb_define_module_function @mod, "test_module_function" + end + + it "defines a module function" do + @mod.test_module_function.should == :test_method + end + + it "returns the correct arity of the module function" do + @mod.method(:test_module_function).arity.should == 0 + end + + it "defines a private instance method" do + cls = Class.new + cls.include(@mod) + + cls.should have_private_instance_method(:test_module_function) + end + + it "returns the correct arity for private instance method" do + cls = Class.new + cls.include(@mod) + + @mod.instance_method(:test_module_function).arity.should == 0 + end + end + + describe "rb_define_private_method" do + it "defines a private method on a class" do + cls = Class.new + @m.rb_define_private_method(cls, "test_method") + cls.should have_private_instance_method(:test_method) + cls.new.send(:test_method).should == :test_method + end + + it "defines a private method on a module" do + mod = Module.new + @m.rb_define_private_method(mod, "test_method") + mod.should have_private_instance_method(:test_method) + end + end + + describe "rb_define_protected_method" do + it "defines a protected method on a class" do + cls = Class.new + @m.rb_define_protected_method(cls, "test_method") + cls.should have_protected_instance_method(:test_method) + cls.new.send(:test_method).should == :test_method + end + + it "defines a protected method on a module" do + mod = Module.new + @m.rb_define_protected_method(mod, "test_method") + mod.should have_protected_instance_method(:test_method) + end + end + + describe "rb_define_singleton_method" do + it "defines a method on the singleton class" do + cls = Class.new + a = cls.new + @m.rb_define_singleton_method a, "module_specs_singleton_method" + a.module_specs_singleton_method.should == :test_method + -> { cls.new.module_specs_singleton_method }.should raise_error(NoMethodError) + end + end + + describe "rb_undef_method" do + before :each do + @class = Class.new do + def ruby_test_method + :ruby_test_method + end + end + end + + it "undef'ines a method on a class" do + @class.new.ruby_test_method.should == :ruby_test_method + @m.rb_undef_method @class, "ruby_test_method" + @class.should_not have_instance_method(:ruby_test_method) + end + + it "undefines private methods also" do + @m.rb_undef_method @class, "initialize_copy" + -> { @class.new.dup }.should raise_error(NoMethodError) + end + + it "does not raise exceptions when passed a missing name" do + -> { @m.rb_undef_method @class, "not_exist" }.should_not raise_error + end + + describe "when given a frozen Class" do + before :each do + @frozen = @class.dup.freeze + end + + it "raises a FrozenError when passed a name" do + -> { @m.rb_undef_method @frozen, "ruby_test_method" }.should raise_error(FrozenError) + end + + it "raises a FrozenError when passed a missing name" do + -> { @m.rb_undef_method @frozen, "not_exist" }.should raise_error(FrozenError) + end + end + end + + describe "rb_undef" do + it "undef'ines a method on a class" do + cls = Class.new do + def ruby_test_method + :ruby_test_method + end + end + + cls.new.ruby_test_method.should == :ruby_test_method + @m.rb_undef cls, :ruby_test_method + cls.should_not have_instance_method(:ruby_test_method) + end + end + + describe "rb_class2name" do + it "returns the module name" do + @m.rb_class2name(CApiModuleSpecs::M).should == "CApiModuleSpecs::M" + end + end + + describe "rb_mod_ancestors" do + it "returns an array of ancestors" do + one = Module.new + two = Module.new do + include one + end + @m.rb_mod_ancestors(two).should == [two, one] + end + end +end diff --git a/spec/ruby/optional/capi/mutex_spec.rb b/spec/ruby/optional/capi/mutex_spec.rb new file mode 100644 index 0000000000..34659974f5 --- /dev/null +++ b/spec/ruby/optional/capi/mutex_spec.rb @@ -0,0 +1,89 @@ +require_relative 'spec_helper' + +load_extension("mutex") + +describe "C-API Mutex functions" do + before :each do + @s = CApiMutexSpecs.new + @m = Mutex.new + end + + describe "rb_mutex_new" do + it "creates a new mutex" do + @s.rb_mutex_new.should be_an_instance_of(Mutex) + end + end + + describe "rb_mutex_locked_p" do + it "returns false if the mutex is not locked" do + @s.rb_mutex_locked_p(@m).should be_false + end + + it "returns true if the mutex is locked" do + @m.lock + @s.rb_mutex_locked_p(@m).should be_true + end + end + + describe "rb_mutex_trylock" do + it "locks the mutex if not locked" do + @s.rb_mutex_trylock(@m).should be_true + @m.locked?.should be_true + end + + it "returns false if the mutex is already locked" do + @m.lock + @s.rb_mutex_trylock(@m).should be_false + @m.locked?.should be_true + end + end + + describe "rb_mutex_lock" do + it "returns when the mutex isn't locked" do + @s.rb_mutex_lock(@m).should == @m + @m.locked?.should be_true + end + + it "throws an exception when already locked in the same thread" do + @m.lock + -> { @s.rb_mutex_lock(@m) }.should raise_error(ThreadError) + @m.locked?.should be_true + end + end + + describe "rb_mutex_unlock" do + it "raises an exception when not locked" do + -> { @s.rb_mutex_unlock(@m) }.should raise_error(ThreadError) + @m.locked?.should be_false + end + + it "unlocks the mutex when locked" do + @m.lock + @s.rb_mutex_unlock(@m).should == @m + @m.locked?.should be_false + end + end + + describe "rb_mutex_sleep" do + it "throws an exception when the mutex is not locked" do + -> { @s.rb_mutex_sleep(@m, 0.1) }.should raise_error(ThreadError) + @m.locked?.should be_false + end + + it "sleeps when the mutex is locked" do + @m.lock + t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @s.rb_mutex_sleep(@m, 0.001) + t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (t2 - t1).should >= 0 + @m.locked?.should be_true + end + end + + describe "rb_mutex_synchronize" do + it "calls the function while the mutex is locked" do + callback = -> { @m.locked?.should be_true } + @s.rb_mutex_synchronize(@m, callback) + end + end +end diff --git a/spec/ruby/optional/capi/numeric_spec.rb b/spec/ruby/optional/capi/numeric_spec.rb new file mode 100644 index 0000000000..95213d3f2b --- /dev/null +++ b/spec/ruby/optional/capi/numeric_spec.rb @@ -0,0 +1,495 @@ +require_relative 'spec_helper' + +load_extension("numeric") + +describe "CApiNumericSpecs" do + before :each do + @s = CApiNumericSpecs.new + end + + describe "NUM2INT" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2INT(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2INT(4.2).should == 4 + end + + it "converts a Bignum" do + @s.NUM2INT(0x7fff_ffff).should == 0x7fff_ffff + end + + it "converts a Fixnum" do + @s.NUM2INT(5).should == 5 + end + + it "converts -1 to an signed number" do + @s.NUM2INT(-1).should == -1 + end + + it "converts a negative Bignum into an signed number" do + @s.NUM2INT(-2147442171).should == -2147442171 + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.NUM2INT(0xffff_ffff+1) }.should raise_error(RangeError) + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.NUM2INT(obj).should == 2 + end + end + + describe "NUM2UINT" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2UINT(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2UINT(4.2).should == 4 + end + + it "converts a Bignum" do + @s.NUM2UINT(0xffff_ffff).should == 0xffff_ffff + end + + it "converts a Fixnum" do + @s.NUM2UINT(5).should == 5 + end + + it "converts a negative number to the complement" do + @s.NUM2UINT(-1).should == 4294967295 + end + + it "converts a signed int value to the complement" do + @s.NUM2UINT(-0x8000_0000).should == 2147483648 + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.NUM2UINT(0xffff_ffff+1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is less than 32bits negative" do + -> { @s.NUM2UINT(-0x8000_0000-1) }.should raise_error(RangeError) + end + + it "raises a RangeError if the value is more than 64bits" do + -> do + @s.NUM2UINT(0xffff_ffff_ffff_ffff+1) + end.should raise_error(RangeError) + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.NUM2UINT(obj).should == 2 + end + end + + describe "NUM2LONG" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2LONG(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2LONG(4.2).should == 4 + end + + it "converts a Bignum" do + @s.NUM2LONG(0x7fff_ffff).should == 0x7fff_ffff + end + + it "converts a Fixnum" do + @s.NUM2LONG(5).should == 5 + end + + platform_is wordsize: 32 do + it "converts -1 to an signed number" do + @s.NUM2LONG(-1).should == -1 + end + + it "converts a negative Bignum into an signed number" do + @s.NUM2LONG(-2147442171).should == -2147442171 + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.NUM2LONG(0xffff_ffff+1) }.should raise_error(RangeError) + end + end + + platform_is wordsize: 64 do + it "converts -1 to an signed number" do + @s.NUM2LONG(-1).should == -1 + end + + it "converts a negative Bignum into an signed number" do + @s.NUM2LONG(-9223372036854734331).should == -9223372036854734331 + end + + it "raises a RangeError if the value is more than 64bits" do + -> do + @s.NUM2LONG(0xffff_ffff_ffff_ffff+1) + end.should raise_error(RangeError) + end + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.NUM2LONG(obj).should == 2 + end + end + + describe "NUM2SHORT" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2SHORT(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2SHORT(4.2).should == 4 + end + + it "converts a Fixnum" do + @s.NUM2SHORT(5).should == 5 + end + + it "converts -1 to an signed number" do + @s.NUM2SHORT(-1).should == -1 + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.NUM2SHORT(0xffff_ffff+1) }.should raise_error(RangeError) + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.NUM2SHORT(obj).should == 2 + end + end + + describe "INT2NUM" do + it "raises a TypeError if passed nil" do + -> { @s.INT2NUM(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.INT2NUM(4.2).should == 4 + end + + it "raises a RangeError when passed a Bignum" do + -> { @s.INT2NUM(bignum_value) }.should raise_error(RangeError) + end + + it "converts a Fixnum" do + @s.INT2NUM(5).should == 5 + end + + it "converts a negative Fixnum" do + @s.INT2NUM(-11).should == -11 + end + end + + describe "NUM2ULONG" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2ULONG(nil) }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2ULONG(4.2).should == 4 + end + + it "converts a Bignum" do + @s.NUM2ULONG(0xffff_ffff).should == 0xffff_ffff + end + + it "converts a Fixnum" do + @s.NUM2ULONG(5).should == 5 + end + + platform_is wordsize: 32 do + it "converts -1 to an unsigned number" do + @s.NUM2ULONG(-1).should == 4294967295 + end + + it "converts a negative Bignum into an unsigned number" do + @s.NUM2ULONG(-2147442171).should == 2147525125 + end + + it "converts positive Bignums if the values is less than 64bits" do + @s.NUM2ULONG(0xffff_ffff).should == 0xffff_ffff + @s.NUM2ULONG(2**30).should == 2**30 + @s.NUM2ULONG(fixnum_max+1).should == fixnum_max+1 + @s.NUM2ULONG(fixnum_max).should == fixnum_max + end + + it "raises a RangeError if the value is more than 32bits" do + -> { @s.NUM2ULONG(0xffff_ffff+1) }.should raise_error(RangeError) + end + end + + platform_is wordsize: 64 do + it "converts -1 to an unsigned number" do + @s.NUM2ULONG(-1).should == 18446744073709551615 + end + + it "converts a negative Bignum into an unsigned number" do + @s.NUM2ULONG(-9223372036854734331).should == 9223372036854817285 + end + + it "converts positive Bignums if the values is less than 64bits" do + @s.NUM2ULONG(0xffff_ffff_ffff_ffff).should == 0xffff_ffff_ffff_ffff + @s.NUM2ULONG(2**62).should == 2**62 + @s.NUM2ULONG(fixnum_max+1).should == fixnum_max+1 + @s.NUM2ULONG(fixnum_max).should == fixnum_max + end + + it "raises a RangeError if the value is more than 64bits" do + -> do + @s.NUM2ULONG(0xffff_ffff_ffff_ffff+1) + end.should raise_error(RangeError) + end + end + + it "calls #to_int to coerce the value" do + obj = mock("number") + obj.should_receive(:to_int).and_return(2) + @s.NUM2ULONG(obj).should == 2 + end + end + + describe "rb_Integer" do + it "creates an Integer from a String" do + i = @s.rb_Integer("8675309") + i.should == 8675309 + end + end + + describe "rb_ll2inum" do + it "creates a Fixnum from a small signed long long" do + i = @s.rb_ll2inum_14() + i.should == 14 + end + end + + describe "rb_ull2inum" do + it "creates a Fixnum from a small unsigned long long" do + i = @s.rb_ull2inum_14() + i.should == 14 + end + + it "creates a positive Bignum from a negative long long" do + i = @s.rb_ull2inum_n14() + i.should == (2 ** (@s.size_of_long_long * 8) - 14) + end + end + + describe "rb_int2inum" do + it "creates a Fixnum from a long" do + i = @s.rb_int2inum_14() + i.should == 14 + end + end + + describe "rb_uint2inum" do + it "creates a Fixnum from a long" do + i = @s.rb_uint2inum_14() + i.should == 14 + end + + it "creates a positive Bignum from a negative long" do + i = @s.rb_uint2inum_n14() + i.should == (2 ** (@s.size_of_VALUE * 8) - 14) + end + end + + describe "NUM2DBL" do + it "raises a TypeError if passed nil" do + -> { @s.NUM2DBL(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if passed a String" do + -> { @s.NUM2DBL("1.2") }.should raise_error(TypeError) + end + + it "converts a Float" do + @s.NUM2DBL(4.2).should == 4.2 + end + + it "converts a Bignum" do + @s.NUM2DBL(2**70).should == (2**70).to_f + end + + it "converts a Fixnum" do + @s.NUM2DBL(5).should == 5.0 + end + + it "calls #to_f to coerce the value" do + obj = mock("number") + obj.should_receive(:to_f).and_return(2.0) + @s.NUM2DBL(obj).should == 2.0 + end + end + + describe "NUM2CHR" do + it "returns the first character of a String" do + @s.NUM2CHR("Abc").should == 65 + end + + it "returns the least significant byte of an Integer" do + @s.NUM2CHR(0xa7c).should == 0x07c + end + + it "returns the least significant byte of a Float converted to an Integer" do + @s.NUM2CHR(0xa7c.to_f).should == 0x07c + end + + it "raises a TypeError when passed an empty String" do + -> { @s.NUM2CHR("") }.should raise_error(TypeError) + end + end + + describe "rb_num_zerodiv" do + it "raises a RuntimeError" do + -> { @s.rb_num_zerodiv() }.should raise_error(ZeroDivisionError, 'divided by 0') + end + end + + describe "rb_cmpint" do + it "returns a Fixnum if passed one" do + @s.rb_cmpint(1, 2).should == 1 + end + + it "uses > to check if the value is greater than 1" do + m = mock("number") + m.should_receive(:>).and_return(true) + @s.rb_cmpint(m, 4).should == 1 + end + + it "uses < to check if the value is less than 1" do + m = mock("number") + m.should_receive(:>).and_return(false) + m.should_receive(:<).and_return(true) + @s.rb_cmpint(m, 4).should == -1 + end + + it "returns 0 if < and > are false" do + m = mock("number") + m.should_receive(:>).and_return(false) + m.should_receive(:<).and_return(false) + @s.rb_cmpint(m, 4).should == 0 + end + + it "raises an ArgumentError when passed nil" do + -> { + @s.rb_cmpint(nil, 4) + }.should raise_error(ArgumentError) + end + end + + describe "rb_num_coerce_bin" do + it "calls #coerce on the first argument" do + obj = mock("rb_num_coerce_bin") + obj.should_receive(:coerce).with(2).and_return([1, 2]) + + @s.rb_num_coerce_bin(2, obj, :+).should == 3 + end + + it "calls the specified method on the first argument returned by #coerce" do + obj = mock("rb_num_coerce_bin") + obj.should_receive(:coerce).with(2).and_return([obj, 2]) + obj.should_receive(:+).with(2).and_return(3) + + @s.rb_num_coerce_bin(2, obj, :+).should == 3 + end + + it "raises a TypeError if #coerce does not return an Array" do + obj = mock("rb_num_coerce_bin") + obj.should_receive(:coerce).with(2).and_return(nil) + + -> { @s.rb_num_coerce_bin(2, obj, :+) }.should raise_error(TypeError) + end + end + + describe "rb_num_coerce_cmp" do + it "calls #coerce on the first argument" do + obj = mock("rb_num_coerce_cmp") + obj.should_receive(:coerce).with(2).and_return([1, 2]) + + @s.rb_num_coerce_cmp(2, obj, :<=>).should == -1 + end + + it "calls the specified method on the first argument returned by #coerce" do + obj = mock("rb_num_coerce_cmp") + obj.should_receive(:coerce).with(2).and_return([obj, 2]) + obj.should_receive(:<=>).with(2).and_return(-1) + + @s.rb_num_coerce_cmp(2, obj, :<=>).should == -1 + end + + it "lets the exception go through if #coerce raises an exception" do + obj = mock("rb_num_coerce_cmp") + obj.should_receive(:coerce).with(2).and_raise(RuntimeError.new("my error")) + -> { + @s.rb_num_coerce_cmp(2, obj, :<=>) + }.should raise_error(RuntimeError, "my error") + end + + it "returns nil if #coerce does not return an Array" do + obj = mock("rb_num_coerce_cmp") + obj.should_receive(:coerce).with(2).and_return(nil) + + @s.rb_num_coerce_cmp(2, obj, :<=>).should be_nil + end + end + + describe "rb_num_coerce_relop" do + it "calls #coerce on the first argument" do + obj = mock("rb_num_coerce_relop") + obj.should_receive(:coerce).with(2).and_return([1, 2]) + + @s.rb_num_coerce_relop(2, obj, :<).should be_true + end + + it "calls the specified method on the first argument returned by #coerce" do + obj = mock("rb_num_coerce_relop") + obj.should_receive(:coerce).with(2).and_return([obj, 2]) + obj.should_receive(:<).with(2).and_return(false) + + @s.rb_num_coerce_relop(2, obj, :<).should be_false + end + + it "raises an ArgumentError if #<op> returns nil" do + obj = mock("rb_num_coerce_relop") + obj.should_receive(:coerce).with(2).and_return([obj, 2]) + obj.should_receive(:<).with(2).and_return(nil) + + -> { @s.rb_num_coerce_relop(2, obj, :<) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if #coerce does not return an Array" do + obj = mock("rb_num_coerce_relop") + obj.should_receive(:coerce).with(2).and_return(nil) + + -> { @s.rb_num_coerce_relop(2, obj, :<) }.should raise_error(ArgumentError) + end + end + + describe "rb_absint_singlebit_p" do + it "returns 1 if absolute value fits into a bit" do + @s.rb_absint_singlebit_p(1).should == 1 + @s.rb_absint_singlebit_p(2).should == 1 + @s.rb_absint_singlebit_p(3).should == 0 + @s.rb_absint_singlebit_p(-1).should == 1 + @s.rb_absint_singlebit_p(-2).should == 1 + @s.rb_absint_singlebit_p(-3).should == 0 + @s.rb_absint_singlebit_p(bignum_value).should == 1 + @s.rb_absint_singlebit_p(bignum_value(1)).should == 0 + @s.rb_absint_singlebit_p(-bignum_value).should == 1 + @s.rb_absint_singlebit_p(-bignum_value(1)).should == 0 + end + end +end diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb new file mode 100644 index 0000000000..ab11367060 --- /dev/null +++ b/spec/ruby/optional/capi/object_spec.rb @@ -0,0 +1,1020 @@ +require_relative 'spec_helper' + +load_extension("object") + +# TODO: fix all these specs + +class CApiObjectSpecs + class Alloc + attr_reader :initialized, :arguments + + def initialize(*args) + @initialized = true + @arguments = args + end + end + + class SubArray < ::Array + def to_array + self + end + end +end + +describe "CApiObject" do + + before do + @o = CApiObjectSpecs.new + end + + class ObjectTest + def initialize + @foo = 7 + end + + def foo + end + + def private_foo + end + private :private_foo + end + + class AryChild < Array + end + + class StrChild < String + end + + class DescObjectTest < ObjectTest + end + + class MethodArity + def one; end + def two(a); end + def three(*a); end + def four(a, b); end + def five(a, b, *c); end + def six(a, b, *c, &d); end + end + + describe "rb_obj_alloc" do + it "allocates a new uninitialized object" do + o = @o.rb_obj_alloc(CApiObjectSpecs::Alloc) + o.class.should == CApiObjectSpecs::Alloc + o.initialized.should be_nil + end + end + + describe "rb_obj_dup" do + it "duplicates an object" do + obj1 = ObjectTest.new + obj2 = @o.rb_obj_dup(obj1) + + obj2.class.should == obj1.class + + obj2.foo.should == obj1.foo + + obj2.should_not equal(obj1) + end + end + + describe "rb_obj_call_init" do + it "sends #initialize" do + o = @o.rb_obj_alloc(CApiObjectSpecs::Alloc) + o.initialized.should be_nil + + @o.rb_obj_call_init(o, 2, [:one, :two]) + o.initialized.should be_true + o.arguments.should == [:one, :two] + end + end + + describe "rb_is_instance_of" do + it "returns true if an object is an instance" do + @o.rb_obj_is_instance_of(ObjectTest.new, ObjectTest).should == true + @o.rb_obj_is_instance_of(DescObjectTest.new, ObjectTest).should == false + end + end + + describe "rb_is_kind_of" do + it "returns true if an object is an instance or descendent" do + @o.rb_obj_is_kind_of(ObjectTest.new, ObjectTest).should == true + @o.rb_obj_is_kind_of(DescObjectTest.new, ObjectTest).should == true + @o.rb_obj_is_kind_of(Object.new, ObjectTest).should == false + end + end + + describe "rb_respond_to" do + it "returns 1 if respond_to? is true and 0 if respond_to? is false" do + @o.rb_respond_to(ObjectTest.new, :foo).should == true + @o.rb_respond_to(ObjectTest.new, :bar).should == false + end + + it "can be used with primitives" do + @o.rb_respond_to(true, :object_id).should == true + @o.rb_respond_to(14, :succ).should == true + end + + it "returns 0 if the method has been defined as rb_f_notimplement" do + @o.respond_to?(:not_implemented_method).should == false + @o.rb_respond_to(@o, :not_implemented_method).should == false + end + end + + describe "rb_obj_respond_to" do + it "returns true if respond_to? is true and false if respond_to? is false" do + @o.rb_obj_respond_to(ObjectTest.new, :foo, true).should == true + @o.rb_obj_respond_to(ObjectTest.new, :bar, true).should == false + @o.rb_obj_respond_to(ObjectTest.new, :private_foo, false).should == false + @o.rb_obj_respond_to(ObjectTest.new, :private_foo, true).should == true + end + end + + describe "rb_obj_method_arity" do + before :each do + @obj = MethodArity.new + end + + it "returns 0 when the method takes no arguments" do + @o.rb_obj_method_arity(@obj, :one).should == 0 + end + + it "returns 1 when the method takes a single, required argument" do + @o.rb_obj_method_arity(@obj, :two).should == 1 + end + + it "returns -1 when the method takes a variable number of arguments" do + @o.rb_obj_method_arity(@obj, :three).should == -1 + end + + it "returns 2 when the method takes two required arguments" do + @o.rb_obj_method_arity(@obj, :four).should == 2 + end + + it "returns -N-1 when the method takes N required and variable additional arguments" do + @o.rb_obj_method_arity(@obj, :five).should == -3 + end + + it "returns -N-1 when the method takes N required, variable additional, and a block argument" do + @o.rb_obj_method_arity(@obj, :six).should == -3 + end + end + + describe "rb_obj_method" do + it "returns the method object for a symbol" do + method = @o.rb_obj_method("test", :size) + method.owner.should == String + method.name.to_sym.should == :size + end + + it "returns the method object for a string" do + method = @o.rb_obj_method("test", "size") + method.owner.should == String + method.name.to_sym.should == :size + end + end + + describe "rb_method_boundp" do + it "returns true when the given method is bound" do + @o.rb_method_boundp(Object, :class, true).should == true + @o.rb_method_boundp(Object, :class, false).should == true + @o.rb_method_boundp(Object, :initialize, true).should == false + @o.rb_method_boundp(Object, :initialize, false).should == true + end + + it "returns false when the given method is not bound" do + @o.rb_method_boundp(Object, :foo, true).should == false + @o.rb_method_boundp(Object, :foo, false).should == false + end + end + + describe "rb_to_id" do + it "returns a symbol representation of the object" do + @o.rb_to_id("foo").should == :foo + @o.rb_to_id(:foo).should == :foo + end + end + + describe "rb_require" do + before :each do + @saved_loaded_features = $LOADED_FEATURES.dup + $foo = nil + end + + after :each do + $foo = nil + $LOADED_FEATURES.replace @saved_loaded_features + end + + it "requires a ruby file" do + $:.unshift File.dirname(__FILE__) + @o.rb_require() + $foo.should == 7 + end + end + + describe "rb_attr_get" do + it "gets an instance variable" do + o = ObjectTest.new + @o.rb_attr_get(o, :@foo).should == 7 + end + end + + describe "rb_obj_instance_variables" do + it "returns an array with instance variable names as symbols" do + o = ObjectTest.new + @o.rb_obj_instance_variables(o).should include(:@foo) + end + end + + describe "rb_check_convert_type" do + it "returns the passed object and does not call the converting method if the object is the specified type" do + ary = [1, 2] + ary.should_not_receive(:to_ary) + + @o.rb_check_convert_type(ary, "Array", "to_ary").should equal(ary) + end + + it "returns the passed object and does not call the converting method if the object is a subclass of the specified type" do + obj = CApiObjectSpecs::SubArray.new + obj.should_not_receive(:to_array) + + @o.rb_check_convert_type(obj, "Array", "to_array").should equal(obj) + end + + it "returns nil if the converting method returns nil" do + obj = mock("rb_check_convert_type") + obj.should_receive(:to_array).and_return(nil) + + @o.rb_check_convert_type(obj, "Array", "to_array").should be_nil + end + + it "raises a TypeError if the converting method returns an object that is not the specified type" do + obj = mock("rb_check_convert_type") + obj.should_receive(:to_array).and_return("string") + + -> do + @o.rb_check_convert_type(obj, "Array", "to_array") + end.should raise_error(TypeError) + end + end + + describe "rb_convert_type" do + it "returns the passed object and does not call the converting method if the object is the specified type" do + ary = [1, 2] + ary.should_not_receive(:to_ary) + + @o.rb_convert_type(ary, "Array", "to_ary").should equal(ary) + end + + it "returns the passed object and does not call the converting method if the object is a subclass of the specified type" do + obj = CApiObjectSpecs::SubArray.new + obj.should_not_receive(:to_array) + + @o.rb_convert_type(obj, "Array", "to_array").should equal(obj) + end + + it "raises a TypeError if the converting method returns nil" do + obj = mock("rb_convert_type") + obj.should_receive(:to_array).and_return(nil) + + -> do + @o.rb_convert_type(obj, "Array", "to_array") + end.should raise_error(TypeError) + end + + it "raises a TypeError if the converting method returns an object that is not the specified type" do + obj = mock("rb_convert_type") + obj.should_receive(:to_array).and_return("string") + + -> do + @o.rb_convert_type(obj, "Array", "to_array") + end.should raise_error(TypeError) + end + end + + describe "rb_check_array_type" do + it "returns the argument if it's an Array" do + x = Array.new + @o.rb_check_array_type(x).should equal(x) + end + + it "returns the argument if it's a kind of Array" do + x = AryChild.new + @o.rb_check_array_type(x).should equal(x) + end + + it "returns nil when the argument does not respond to #to_ary" do + @o.rb_check_array_type(Object.new).should be_nil + end + + it "sends #to_ary to the argument and returns the result if it's nil" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(nil) + @o.rb_check_array_type(obj).should be_nil + end + + it "sends #to_ary to the argument and returns the result if it's an Array" do + x = Array.new + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(x) + @o.rb_check_array_type(obj).should equal(x) + end + + it "sends #to_ary to the argument and returns the result if it's a kind of Array" do + x = AryChild.new + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(x) + @o.rb_check_array_type(obj).should equal(x) + end + + it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_return(Object.new) + -> { @o.rb_check_array_type obj }.should raise_error(TypeError) + end + + it "does not rescue exceptions raised by #to_ary" do + obj = mock("to_ary") + obj.should_receive(:to_ary).and_raise(FrozenError) + -> { @o.rb_check_array_type obj }.should raise_error(FrozenError) + end + end + + describe "rb_check_string_type" do + it "returns the argument if it's a String" do + x = String.new + @o.rb_check_string_type(x).should equal(x) + end + + it "returns the argument if it's a kind of String" do + x = StrChild.new + @o.rb_check_string_type(x).should equal(x) + end + + it "returns nil when the argument does not respond to #to_str" do + @o.rb_check_string_type(Object.new).should be_nil + end + + it "sends #to_str to the argument and returns the result if it's nil" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(nil) + @o.rb_check_string_type(obj).should be_nil + end + + it "sends #to_str to the argument and returns the result if it's a String" do + x = String.new + obj = mock("to_str") + obj.should_receive(:to_str).and_return(x) + @o.rb_check_string_type(obj).should equal(x) + end + + it "sends #to_str to the argument and returns the result if it's a kind of String" do + x = StrChild.new + obj = mock("to_str") + obj.should_receive(:to_str).and_return(x) + @o.rb_check_string_type(obj).should equal(x) + end + + it "sends #to_str to the argument and raises TypeError if it's not a kind of String" do + obj = mock("to_str") + obj.should_receive(:to_str).and_return(Object.new) + -> { @o.rb_check_string_type obj }.should raise_error(TypeError) + end + + it "does not rescue exceptions raised by #to_str" do + obj = mock("to_str") + obj.should_receive(:to_str).and_raise(RuntimeError) + -> { @o.rb_check_string_type obj }.should raise_error(RuntimeError) + end + end + + describe "rb_check_to_integer" do + it "returns the object when passed a Fixnum" do + @o.rb_check_to_integer(5, "to_int").should equal(5) + end + + it "returns the object when passed a Bignum" do + @o.rb_check_to_integer(bignum_value, "to_int").should == bignum_value + end + + it "calls the converting method and returns a Fixnum value" do + obj = mock("rb_check_to_integer") + obj.should_receive(:to_integer).and_return(10) + + @o.rb_check_to_integer(obj, "to_integer").should equal(10) + end + + it "calls the converting method and returns a Bignum value" do + obj = mock("rb_check_to_integer") + obj.should_receive(:to_integer).and_return(bignum_value) + + @o.rb_check_to_integer(obj, "to_integer").should == bignum_value + end + + it "returns nil when the converting method returns nil" do + obj = mock("rb_check_to_integer") + obj.should_receive(:to_integer).and_return(nil) + + @o.rb_check_to_integer(obj, "to_integer").should be_nil + end + + it "returns nil when the converting method does not return an Integer" do + obj = mock("rb_check_to_integer") + obj.should_receive(:to_integer).and_return("string") + + @o.rb_check_to_integer(obj, "to_integer").should be_nil + end + end + + describe "FL_ABLE" do + it "returns correct boolean for type" do + @o.FL_ABLE(Object.new).should be_true + @o.FL_ABLE(true).should be_false + @o.FL_ABLE(nil).should be_false + @o.FL_ABLE(1).should be_false + end + end + + describe "FL_TEST" do + ruby_version_is ''...'2.7' do + it "returns correct status for FL_TAINT" do + obj = Object.new + @o.FL_TEST(obj, "FL_TAINT").should == 0 + obj.taint + @o.FL_TEST(obj, "FL_TAINT").should_not == 0 + end + end + + it "returns correct status for FL_FREEZE" do + obj = Object.new + @o.FL_TEST(obj, "FL_FREEZE").should == 0 + obj.freeze + @o.FL_TEST(obj, "FL_FREEZE").should_not == 0 + end + end + + describe "rb_inspect" do + it "returns a string with the inspect representation" do + @o.rb_inspect(nil).should == "nil" + @o.rb_inspect(0).should == '0' + @o.rb_inspect([1,2,3]).should == '[1, 2, 3]' + @o.rb_inspect("0").should == '"0"' + end + end + + describe "rb_class_of" do + it "returns the class of an object" do + @o.rb_class_of(nil).should == NilClass + @o.rb_class_of(0).should == Integer + @o.rb_class_of(0.1).should == Float + @o.rb_class_of(ObjectTest.new).should == ObjectTest + end + + it "returns the singleton class if it exists" do + o = ObjectTest.new + @o.rb_class_of(o).should equal ObjectTest + s = o.singleton_class + @o.rb_class_of(o).should equal s + end + end + + describe "rb_obj_classname" do + it "returns the class name of an object" do + @o.rb_obj_classname(nil).should == 'NilClass' + @o.rb_obj_classname(0).should == 'Integer' + @o.rb_obj_classname(0.1).should == 'Float' + @o.rb_obj_classname(ObjectTest.new).should == 'ObjectTest' + end + end + + describe "rb_type" do + it "returns the type constant for the object" do + class DescArray < Array + end + @o.rb_is_type_nil(nil).should == true + @o.rb_is_type_object([]).should == false + @o.rb_is_type_object(ObjectTest.new).should == true + @o.rb_is_type_array([]).should == true + @o.rb_is_type_array(DescArray.new).should == true + @o.rb_is_type_module(ObjectTest).should == false + @o.rb_is_type_module(Module.new).should == true + @o.rb_is_type_class(ObjectTest).should == true + @o.rb_is_type_data(Time.now).should == true + end + end + + describe "rb_check_type" do + it "checks if the object is of the given type" do + @o.rb_check_type(nil, nil).should == true + @o.rb_check_type(ObjectTest.new, Object.new).should == true + @o.rb_check_type([], []).should == true + @o.rb_check_type(Class.new(Array).new, []).should == true + @o.rb_check_type(ObjectTest, Object).should == true + end + + it "raises an exception if the object is not of the expected type" do + -> { + @o.rb_check_type([], Object.new) + }.should raise_error(TypeError, 'wrong argument type Array (expected Object)') + + -> { + @o.rb_check_type(ObjectTest, Module.new) + }.should raise_error(TypeError, 'wrong argument type Class (expected Module)') + + -> { + @o.rb_check_type(nil, "string") + }.should raise_error(TypeError, 'wrong argument type nil (expected String)') + end + end + + describe "rb_type_p" do + it "returns whether object is of the given type" do + @o.rb_is_rb_type_p_nil(nil).should == true + @o.rb_is_rb_type_p_object([]).should == false + @o.rb_is_rb_type_p_object(ObjectTest.new).should == true + @o.rb_is_rb_type_p_array([]).should == true + @o.rb_is_rb_type_p_array(Class.new(Array).new).should == true + @o.rb_is_rb_type_p_module(ObjectTest).should == false + @o.rb_is_rb_type_p_class(ObjectTest).should == true + @o.rb_is_rb_type_p_data(Time.now).should == true + end + end + + describe "BUILTIN_TYPE" do + it "returns the type constant for the object" do + @o.rb_is_builtin_type_object([]).should == false + @o.rb_is_builtin_type_object(ObjectTest.new).should == true + @o.rb_is_builtin_type_array([]).should == true + @o.rb_is_builtin_type_array(Class.new(Array).new).should == true + @o.rb_is_builtin_type_module(ObjectTest).should == false + @o.rb_is_builtin_type_class(ObjectTest).should == true + @o.rb_is_builtin_type_data(Time.now).should == true + end + end + + describe "RTEST" do + it "returns C false if passed Qfalse" do + @o.RTEST(false).should be_false + end + + it "returns C false if passed Qnil" do + @o.RTEST(nil).should be_false + end + + it "returns C true if passed Qtrue" do + @o.RTEST(true).should be_true + end + + it "returns C true if passed a Symbol" do + @o.RTEST(:test).should be_true + end + + it "returns C true if passed an Object" do + @o.RTEST(Object.new).should be_true + end + end + + describe "rb_special_const_p" do + it "returns true if passed Qfalse" do + @o.rb_special_const_p(false).should be_true + end + + it "returns true if passed Qtrue" do + @o.rb_special_const_p(true).should be_true + end + + it "returns true if passed Qnil" do + @o.rb_special_const_p(nil).should be_true + end + + it "returns true if passed a Symbol" do + @o.rb_special_const_p(:test).should be_true + end + + it "returns true if passed a Fixnum" do + @o.rb_special_const_p(10).should be_true + end + + it "returns false if passed an Object" do + @o.rb_special_const_p(Object.new).should be_false + end + end + + describe "rb_extend_object" do + it "adds the module's instance methods to the object" do + module CApiObjectSpecs::Extend + def reach + :extended + end + end + + obj = mock("extended object") + @o.rb_extend_object(obj, CApiObjectSpecs::Extend) + obj.reach.should == :extended + end + end + + describe "OBJ_TAINT" do + ruby_version_is ''...'2.7' do + it "taints the object" do + obj = mock("tainted") + @o.OBJ_TAINT(obj) + obj.tainted?.should be_true + end + end + end + + describe "OBJ_TAINTED" do + ruby_version_is ''...'2.7' do + it "returns C true if the object is tainted" do + obj = mock("tainted") + obj.taint + @o.OBJ_TAINTED(obj).should be_true + end + + it "returns C false if the object is not tainted" do + obj = mock("untainted") + @o.OBJ_TAINTED(obj).should be_false + end + end + end + + describe "OBJ_INFECT" do + ruby_version_is ''...'2.7' do + it "does not taint the first argument if the second argument is not tainted" do + host = mock("host") + source = mock("source") + @o.OBJ_INFECT(host, source) + host.tainted?.should be_false + end + + it "taints the first argument if the second argument is tainted" do + host = mock("host") + source = mock("source").taint + @o.OBJ_INFECT(host, source) + host.tainted?.should be_true + end + + it "does not untrust the first argument if the second argument is trusted" do + host = mock("host") + source = mock("source") + @o.OBJ_INFECT(host, source) + host.untrusted?.should be_false + end + + it "untrusts the first argument if the second argument is untrusted" do + host = mock("host") + source = mock("source").untrust + @o.OBJ_INFECT(host, source) + host.untrusted?.should be_true + end + + it "propagates both taint and distrust" do + host = mock("host") + source = mock("source").taint.untrust + @o.OBJ_INFECT(host, source) + host.tainted?.should be_true + host.untrusted?.should be_true + end + end + end + + describe "rb_obj_freeze" do + it "freezes the object passed to it" do + obj = "" + @o.rb_obj_freeze(obj).should == obj + obj.frozen?.should be_true + end + end + + describe "rb_obj_instance_eval" do + it "evaluates the block in the object context, that includes private methods" do + obj = ObjectTest + -> do + @o.rb_obj_instance_eval(obj) { include Kernel } + end.should_not raise_error(NoMethodError) + end + end + + describe "rb_obj_frozen_p" do + it "returns true if object passed to it is frozen" do + obj = "" + obj.freeze + @o.rb_obj_frozen_p(obj).should == true + end + + it "returns false if object passed to it is not frozen" do + obj = "" + @o.rb_obj_frozen_p(obj).should == false + end + end + + describe "rb_obj_taint" do + ruby_version_is ''...'2.7' do + it "marks the object passed as tainted" do + obj = "" + obj.should_not.tainted? + @o.rb_obj_taint(obj) + obj.should.tainted? + end + + it "raises a FrozenError if the object passed is frozen" do + -> { @o.rb_obj_taint("".freeze) }.should raise_error(FrozenError) + end + end + end + + describe "rb_check_frozen" do + it "raises a FrozenError if the obj is frozen" do + -> { @o.rb_check_frozen("".freeze) }.should raise_error(FrozenError) + end + + it "does nothing when object isn't frozen" do + obj = "" + -> { @o.rb_check_frozen(obj) }.should_not raise_error(TypeError) + end + end + + describe "rb_any_to_s" do + it "converts an Integer to string" do + obj = 1 + i = @o.rb_any_to_s(obj) + i.should be_kind_of(String) + end + + it "converts an Object to string" do + obj = Object.new + i = @o.rb_any_to_s(obj) + i.should be_kind_of(String) + end + end + + describe "rb_to_int" do + it "returns self when called on an Integer" do + @o.rb_to_int(5).should == 5 + end + + it "returns self when called on a Bignum" do + @o.rb_to_int(bignum_value).should == bignum_value + end + + it "calls #to_int to convert and object to an integer" do + x = mock("to_int") + x.should_receive(:to_int).and_return(5) + @o.rb_to_int(x).should == 5 + end + + it "converts a Float to an Integer by truncation" do + @o.rb_to_int(1.35).should == 1 + end + + it "raises a TypeError if #to_int does not return an Integer" do + x = mock("to_int") + x.should_receive(:to_int).and_return("5") + -> { @o.rb_to_int(x) }.should raise_error(TypeError) + end + + it "raises a TypeError if called with nil" do + -> { @o.rb_to_int(nil) }.should raise_error(TypeError) + end + + it "raises a TypeError if called with true" do + -> { @o.rb_to_int(true) }.should raise_error(TypeError) + end + + it "raises a TypeError if called with false" do + -> { @o.rb_to_int(false) }.should raise_error(TypeError) + end + + it "raises a TypeError if called with a String" do + -> { @o.rb_to_int("1") }.should raise_error(TypeError) + end + end + + describe "rb_equal" do + it "returns true if the arguments are the same exact object" do + s = "hello" + @o.rb_equal(s, s).should be_true + end + + it "calls == to check equality and coerces to true/false" do + m = mock("string") + m.should_receive(:==).and_return(8) + @o.rb_equal(m, "hello").should be_true + + m2 = mock("string") + m2.should_receive(:==).and_return(nil) + @o.rb_equal(m2, "hello").should be_false + end + end + + describe "rb_class_inherited_p" do + + it "returns true if mod equals arg" do + @o.rb_class_inherited_p(Array, Array).should be_true + end + + it "returns true if mod is a subclass of arg" do + @o.rb_class_inherited_p(Array, Object).should be_true + end + + it "returns nil if mod is not a subclass of arg" do + @o.rb_class_inherited_p(Array, Hash).should be_nil + end + + it "raises a TypeError if arg is no class or module" do + ->{ + @o.rb_class_inherited_p(1, 2) + }.should raise_error(TypeError) + end + + end + + describe "instance variable access" do + before do + @test = ObjectTest.new + end + + describe "rb_iv_get" do + it "returns the instance variable on an object" do + @o.rb_iv_get(@test, "@foo").should == @test.instance_eval { @foo } + end + + it "returns nil if the instance variable has not been initialized" do + @o.rb_iv_get(@test, "@bar").should == nil + end + end + + describe "rb_iv_set" do + it "sets and returns the instance variable on an object" do + @o.rb_iv_set(@test, "@foo", 42).should == 42 + @test.instance_eval { @foo }.should == 42 + end + + it "sets and returns the instance variable with a bare name" do + @o.rb_iv_set(@test, "foo", 42).should == 42 + @o.rb_iv_get(@test, "foo").should == 42 + @test.instance_eval { @foo }.should == 7 + end + end + + describe "rb_ivar_count" do + it "returns the number of instance variables" do + obj = Object.new + @o.rb_ivar_count(obj).should == 0 + obj.instance_variable_set(:@foo, 42) + @o.rb_ivar_count(obj).should == 1 + end + end + + describe "rb_ivar_get" do + it "returns the instance variable on an object" do + @o.rb_ivar_get(@test, :@foo).should == @test.instance_eval { @foo } + end + + it "returns nil if the instance variable has not been initialized" do + @o.rb_ivar_get(@test, :@bar).should == nil + end + + it "returns nil if the instance variable has not been initialized and is not a valid Ruby name" do + @o.rb_ivar_get(@test, :bar).should == nil + @o.rb_ivar_get(@test, :mesg).should == nil + end + + it 'returns the instance variable when it is not a valid Ruby name' do + @o.rb_ivar_set(@test, :foo, 27) + @o.rb_ivar_get(@test, :foo).should == 27 + end + end + + describe "rb_ivar_set" do + it "sets and returns the instance variable on an object" do + @o.rb_ivar_set(@test, :@foo, 42).should == 42 + @test.instance_eval { @foo }.should == 42 + end + + it "sets and returns the instance variable on an object" do + @o.rb_ivar_set(@test, :@foo, 42).should == 42 + @test.instance_eval { @foo }.should == 42 + end + + it 'sets and returns the instance variable when it is not a valid Ruby name' do + @o.rb_ivar_set(@test, :foo, 27).should == 27 + end + end + + describe "rb_ivar_defined" do + it "returns true if the instance variable is defined" do + @o.rb_ivar_defined(@test, :@foo).should == true + end + + it "returns false if the instance variable is not defined" do + @o.rb_ivar_defined(@test, :@bar).should == false + end + + it "does not throw an error if the instance variable is not a valid Ruby name" do + @o.rb_ivar_defined(@test, :bar).should == false + @o.rb_ivar_defined(@test, :mesg).should == false + end + end + + # The `generic_iv_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. + + describe "rb_copy_generic_ivar for objects which do not store ivars directly" do + it "copies the instance variables from one object to another" do + original = "abc" + original.instance_variable_set(:@foo, :bar) + clone = "def" + @o.rb_copy_generic_ivar(clone, original) + clone.instance_variable_get(:@foo).should == :bar + end + end + + describe "rb_free_generic_ivar for objects which do not store ivars directly" do + it "removes the instance variables from an object" do + o = "abc" + o.instance_variable_set(:@baz, :flibble) + @o.rb_free_generic_ivar(o) + o.instance_variables.should == [] + end + end + end + + describe "allocator accessors" do + describe "rb_define_alloc_func" do + it "sets up the allocator" do + klass = Class.new + @o.rb_define_alloc_func(klass) + obj = klass.allocate + obj.class.should.equal?(klass) + obj.should have_instance_variable(:@from_custom_allocator) + end + + it "sets up the allocator for a subclass of String" do + klass = Class.new(String) + @o.rb_define_alloc_func(klass) + obj = klass.allocate + obj.class.should.equal?(klass) + obj.should have_instance_variable(:@from_custom_allocator) + obj.should == "" + end + + it "sets up the allocator for a subclass of Array" do + klass = Class.new(Array) + @o.rb_define_alloc_func(klass) + obj = klass.allocate + obj.class.should.equal?(klass) + obj.should have_instance_variable(:@from_custom_allocator) + obj.should == [] + end + end + + describe "rb_get_alloc_func" do + it "gets the allocator that is defined directly on a class" do + klass = Class.new + @o.rb_define_alloc_func(klass) + @o.speced_allocator?(Object).should == false + @o.speced_allocator?(klass).should == true + end + + it "gets the allocator that is inherited" do + parent = Class.new + @o.rb_define_alloc_func(parent) + klass = Class.new(parent) + @o.speced_allocator?(Object).should == false + @o.speced_allocator?(klass).should == true + end + end + + describe "rb_undef_alloc_func" do + it "makes rb_get_alloc_func() return NULL for a class without a custom allocator" do + klass = Class.new + @o.rb_undef_alloc_func(klass) + @o.custom_alloc_func?(klass).should == false + end + + it "undefs the allocator for the class" do + klass = Class.new + @o.rb_define_alloc_func(klass) + @o.speced_allocator?(klass).should == true + @o.rb_undef_alloc_func(klass) + @o.custom_alloc_func?(klass).should == false + end + + it "undefs the allocator for a class that inherits a allocator" do + parent = Class.new + @o.rb_define_alloc_func(parent) + klass = Class.new(parent) + @o.speced_allocator?(klass).should == true + @o.rb_undef_alloc_func(klass) + @o.custom_alloc_func?(klass).should == false + + @o.speced_allocator?(parent).should == true + end + end + end +end diff --git a/spec/ruby/optional/capi/proc_spec.rb b/spec/ruby/optional/capi/proc_spec.rb new file mode 100644 index 0000000000..6e797fdeb4 --- /dev/null +++ b/spec/ruby/optional/capi/proc_spec.rb @@ -0,0 +1,136 @@ +require_relative 'spec_helper' +require_relative 'fixtures/proc' + +load_extension("proc") + +describe "C-API Proc function" do + before :each do + @p = CApiProcSpecs.new + @prc = @p.rb_proc_new + end + + describe "rb_proc_new" do + it "returns a new valid Proc" do + @prc.kind_of?(Proc).should == true + end + + it "calls the C function wrapped by the Proc instance when sent #call" do + @prc.call(:foo_bar).should == ":foo_bar" + @prc.call([:foo, :bar]).should == "[:foo, :bar]" + end + + it "calls the C function wrapped by the Proc instance when sent #[]" do + @prc[:foo_bar].should == ":foo_bar" + @prc[[:foo, :bar]].should == "[:foo, :bar]" + end + + it "returns a Proc instance correctly described in #inspect without source location" do + @prc.inspect.should =~ /^#<Proc:([^ :@]*?)>$/ + end + + it "returns a Proc instance with #arity == -1" do + @prc.arity.should == -1 + end + + it "shouldn't be equal to another one" do + @prc.should_not == @p.rb_proc_new + end + + it "returns a Proc instance with #source_location == nil" do + @prc.source_location.should == nil + end + end + + describe "rb_proc_arity" do + it "returns the correct arity" do + prc = Proc.new {|a,b,c|} + @p.rb_proc_arity(prc).should == 3 + end + end + + describe "rb_proc_call" do + it "calls the Proc" do + prc = Proc.new {|a,b| a * b } + @p.rb_proc_call(prc, [6, 7]).should == 42 + end + end + + describe "rb_obj_is_proc" do + it "returns true for Proc" do + prc = Proc.new {|a,b| a * b } + @p.rb_obj_is_proc(prc).should be_true + end + + it "returns true for subclass of Proc" do + prc = Class.new(Proc).new {} + @p.rb_obj_is_proc(prc).should be_true + end + + it "returns false for non Proc instances" do + @p.rb_obj_is_proc("aoeui").should be_false + @p.rb_obj_is_proc(123).should be_false + @p.rb_obj_is_proc(true).should be_false + @p.rb_obj_is_proc([]).should be_false + end + end +end + +describe "C-API when calling Proc.new from a C function" do + before :each do + @p = CApiProcSpecs.new + end + + # In the scenarios below: X -> Y means execution context X called to Y. + # For example: Ruby -> C means a Ruby code called a C function. + # + # X -> Y <- X -> Z means execution context X called Y which returned to X, + # then X called Z. + # For example: C -> Ruby <- C -> Ruby means a C function called into Ruby + # code which returned to C, then C called into Ruby code again. + + ruby_version_is ""..."2.7" do + # Ruby -> C -> rb_funcall(Proc.new) + it "returns the Proc passed by the Ruby code calling the C function" do + prc = @p.rb_Proc_new(0) { :called } + prc.call.should == :called + end + + # Ruby -> C -> Ruby <- C -> rb_funcall(Proc.new) + it "returns the Proc passed to the Ruby method when the C function calls other Ruby methods before calling Proc.new" do + prc = @p.rb_Proc_new(1) { :called } + prc.call.should == :called + end + end + + # Ruby -> C -> Ruby -> Proc.new + it "raises an ArgumentError when the C function calls a Ruby method that calls Proc.new" do + -> { + @p.rb_Proc_new(2) { :called } + }.should raise_error(ArgumentError) + end + + # Ruby -> C -> Ruby -> C -> rb_funcall(Proc.new) + it "raises an ArgumentError when the C function calls a Ruby method and that method calls a C function that calls Proc.new" do + def @p.redispatch() rb_Proc_new(0) end + -> { @p.rb_Proc_new(3) { :called } }.should raise_error(ArgumentError) + end + + ruby_version_is ""..."2.7" do + # Ruby -> C -> Ruby -> C (with new block) -> rb_funcall(Proc.new) + it "returns the most recent Proc passed when the Ruby method called the C function" do + prc = @p.rb_Proc_new(4) { :called } + prc.call.should == :calling_with_block + end + + # Ruby -> C -> Ruby -> C (with new block) <- Ruby <- C -> # rb_funcall(Proc.new) + it "returns the Proc passed from the original Ruby call to the C function" do + prc = @p.rb_Proc_new(5) { :called } + prc.call.should == :called + end + end + + # Ruby -> C -> Ruby -> block_given? + it "returns false from block_given? in a Ruby method called by the C function" do + @p.rb_Proc_new(6).should be_false + end +end diff --git a/spec/ruby/optional/capi/rake_helper.rb b/spec/ruby/optional/capi/rake_helper.rb new file mode 100644 index 0000000000..c13f1189c5 --- /dev/null +++ b/spec/ruby/optional/capi/rake_helper.rb @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/clean' + +input = "#{$cwd}/#{$ext_name}.c" +common = "-I shotgun/lib/subtend -g #{input}" + +case PLATFORM +when /darwin/ + output = "#{$cwd}/#{$ext_name}.bundle" + build_cmd = "cc -bundle -undefined suppress -flat_namespace #{common} -o #{output}" +else + output = "#{$cwd}/#{$ext_name}.so" + build_cmd = "cc -shared #{common} -o #{output}" +end + +CLOBBER.include(output) + +task default: [output] + +file output => [input] do + sh build_cmd +end diff --git a/spec/ruby/optional/capi/range_spec.rb b/spec/ruby/optional/capi/range_spec.rb new file mode 100644 index 0000000000..7a52dc7ff8 --- /dev/null +++ b/spec/ruby/optional/capi/range_spec.rb @@ -0,0 +1,95 @@ +require_relative 'spec_helper' + +load_extension("range") + +describe "C-API Range function" do + before :each do + @s = CApiRangeSpecs.new + end + + describe "rb_range_new" do + it "constructs a range using the given start and end" do + range = @s.rb_range_new('a', 'c') + range.should == ('a'..'c') + + range.first.should == 'a' + range.last.should == 'c' + end + + it "includes the end object when the third parameter is omitted or false" do + @s.rb_range_new('a', 'c').to_a.should == ['a', 'b', 'c'] + @s.rb_range_new(1, 3).to_a.should == [1, 2, 3] + + @s.rb_range_new('a', 'c', false).to_a.should == ['a', 'b', 'c'] + @s.rb_range_new(1, 3, false).to_a.should == [1, 2, 3] + + @s.rb_range_new('a', 'c', true).to_a.should == ['a', 'b'] + @s.rb_range_new(1, 3, 1).to_a.should == [1, 2] + + @s.rb_range_new(1, 3, mock('[1,2]')).to_a.should == [1, 2] + @s.rb_range_new(1, 3, :test).to_a.should == [1, 2] + end + + it "raises an ArgumentError when the given start and end can't be compared by using #<=>" do + -> { @s.rb_range_new(1, mock('x')) }.should raise_error(ArgumentError) + -> { @s.rb_range_new(mock('x'), mock('y')) }.should raise_error(ArgumentError) + end + end + + describe "rb_range_values" do + it "stores the range properties" do + beg, fin, excl = @s.rb_range_values(10..20) + beg.should == 10 + fin.should == 20 + excl.should be_false + end + + it "stores the range properties of non-Range object" do + range_like = mock('range') + + def range_like.begin + 10 + end + + def range_like.end + 20 + end + + def range_like.exclude_end? + false + end + + beg, fin, excl = @s.rb_range_values(range_like) + beg.should == 10 + fin.should == 20 + excl.should be_false + end + end + + 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) + result.should be_true + begp.should == 2 + lenp.should == 4 + end + + it "returns nil when not in range" do + r = 2..5 + begp, lenp, result = @s.rb_range_beg_len(r, 0, 0, 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) + 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) + result.should be_nil + end + end +end diff --git a/spec/ruby/optional/capi/rational_spec.rb b/spec/ruby/optional/capi/rational_spec.rb new file mode 100644 index 0000000000..1c241ac48e --- /dev/null +++ b/spec/ruby/optional/capi/rational_spec.rb @@ -0,0 +1,57 @@ +require_relative 'spec_helper' + +load_extension("rational") + +describe :rb_Rational, shared: true do + it "creates a new Rational with numerator and denominator" do + @r.send(@method, 1, 2).should == Rational(1, 2) + end +end + +describe :rb_rational_new, shared: true do + it "creates a normalized Rational" do + r = @r.send(@method, 10, 4) + r.numerator.should == 5 + r.denominator.should == 2 + end +end + +describe "CApiRationalSpecs" do + before :each do + @r = CApiRationalSpecs.new + end + + describe "rb_Rational" do + it_behaves_like :rb_Rational, :rb_Rational + end + + describe "rb_Rational2" do + it_behaves_like :rb_Rational, :rb_Rational2 + end + + describe "rb_Rational1" do + it "creates a new Rational with numerator and denominator of 1" do + @r.rb_Rational1(5).should == Rational(5, 1) + end + end + + describe "rb_rational_new" do + it_behaves_like :rb_rational_new, :rb_rational_new + end + + describe "rb_rational_new2" do + it_behaves_like :rb_rational_new, :rb_rational_new2 + end + + describe "rb_rational_num" do + it "returns the numerator of a Rational" do + @r.rb_rational_num(Rational(7, 2)).should == 7 + end + end + + describe "rb_rational_den" do + it "returns the denominator of a Rational" do + @r.rb_rational_den(Rational(7, 2)).should == 2 + end + end +end diff --git a/spec/ruby/optional/capi/rbasic_spec.rb b/spec/ruby/optional/capi/rbasic_spec.rb new file mode 100644 index 0000000000..6300680d99 --- /dev/null +++ b/spec/ruby/optional/capi/rbasic_spec.rb @@ -0,0 +1,43 @@ +require_relative 'spec_helper' +require_relative 'shared/rbasic' +load_extension("rbasic") +return if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS') # not working from the beginning +load_extension("data") +load_extension("array") + +describe "RBasic support for regular objects" do + before :all do + @specs = CApiRBasicSpecs.new + @data = -> { [Object.new, Object.new] } + end + 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 + + 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.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 diff --git a/spec/ruby/optional/capi/regexp_spec.rb b/spec/ruby/optional/capi/regexp_spec.rb new file mode 100644 index 0000000000..81844e2a6e --- /dev/null +++ b/spec/ruby/optional/capi/regexp_spec.rb @@ -0,0 +1,112 @@ +require_relative 'spec_helper' + +load_extension("regexp") + +describe "C-API Regexp function" do + before :each do + @p = CApiRegexpSpecs.new + end + + describe "rb_reg_new" do + it "returns a new valid Regexp" do + my_re = @p.a_re("a", 0) + my_re.kind_of?(Regexp).should == true + ('1a' =~ my_re).should == 1 + ('1b' =~ my_re).should == nil + my_re.source.should == 'a' + end + + it "returns a Regexp with the given options" do + @p.a_re("a", 0).options == 0 + @p.a_re("a", Regexp::IGNORECASE).options.should == Regexp::IGNORECASE + @p.a_re("a", Regexp::EXTENDED).options.should == Regexp::EXTENDED + @p.a_re("a", Regexp::EXTENDED | Regexp::IGNORECASE).options.should == Regexp::EXTENDED | Regexp::IGNORECASE + @p.a_re("a", Regexp::MULTILINE).options.should == Regexp::MULTILINE + end + end + + describe "rb_reg_nth_match" do + it "returns a the appropriate match data entry" do + @p.a_re_1st_match(/([ab])/.match("a")).should == 'a' + @p.a_re_1st_match(/([ab])/.match("b")).should == 'b' + @p.a_re_1st_match(/[ab]/.match("a")).should == nil + @p.a_re_1st_match(/[ab]/.match("c")).should == nil + end + end + + describe "rb_reg_options" do + it "returns the options used to create the regexp" do + @p.rb_reg_options(/42/im).should == //im.options + @p.rb_reg_options(/42/i).should == //i.options + @p.rb_reg_options(/42/m).should == //m.options + end + end + + describe "rb_reg_regcomp" do + it "creates a valid regexp from a string" do + regexp = /\b([A-Z0-9._%+-]+)\.{2,4}/ + @p.rb_reg_regcomp(regexp.source).should == regexp + end + end + + it "allows matching in C, properly setting the back references" do + mail_regexp = /\b([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,4})\b/i + name = "john.doe" + domain = "example.com" + @p.match(mail_regexp, "#{name}@#{domain}") + $1.should == name + $2.should == domain + end + + describe "rb_reg_match" do + it "returns the matched position or nil" do + @p.rb_reg_match(/a/, 'ab').should == 0 + @p.rb_reg_match(/b/, 'ab').should == 1 + @p.rb_reg_match(/c/, 'ab').should == nil + end + end + + describe "rb_backref_get" do + it "returns the last MatchData" do + md = /a/.match('ab') + @p.rb_backref_get.should == md + md = /b/.match('ab') + @p.rb_backref_get.should == md + md = /c/.match('ab') + @p.rb_backref_get.should == md + end + + it "returns MatchData when used with rb_reg_match" do + @p.rb_reg_match_backref_get(/a/, 'ab')[0].should == 'a' + end + end + + describe "rb_backref_set" do + before :each do + @md = "foo".match(/foo/) + $~ = nil + end + + it "sets the value of $~" do + @p.rb_backref_set(@md) + @p.rb_backref_get.should == @md + $~.should == @md + end + + it "sets a Thread-local value" do + running = false + + thr = Thread.new do + @p.rb_backref_set(@md) + @p.rb_backref_get.should == @md + $~.should == @md + running = true + end + + Thread.pass while thr.status and !running + $~.should be_nil + + thr.join + end + end +end diff --git a/spec/ruby/optional/capi/shared/rbasic.rb b/spec/ruby/optional/capi/shared/rbasic.rb new file mode 100644 index 0000000000..5ef63e81e3 --- /dev/null +++ b/spec/ruby/optional/capi/shared/rbasic.rb @@ -0,0 +1,63 @@ +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 + + it "reports the appropriate FREEZE flag for the object when reading" do + obj, _ = @data.call + initial = @specs.get_flags(obj) + obj.freeze + @specs.get_flags(obj).should == @freeze | initial + end + + it "supports setting the FREEZE flag" do + obj, _ = @data.call + initial = @specs.get_flags(obj) + @specs.set_flags(obj, @freeze | initial).should == @freeze | initial + obj.should.frozen? + end + + ruby_version_is ""..."2.7" do + it "reports the appropriate FREEZE and TAINT flags for the object when reading" do + obj, _ = @data.call + initial = @specs.get_flags(obj) + obj.taint + @specs.get_flags(obj).should == @taint | initial + obj.untaint + @specs.get_flags(obj).should == initial + obj.freeze + @specs.get_flags(obj).should == @freeze | initial + + obj, _ = @data.call + obj.taint + obj.freeze + @specs.get_flags(obj).should == @freeze | @taint | initial + end + + it "supports setting the FREEZE and TAINT flags" do + obj, _ = @data.call + initial = @specs.get_flags(obj) + @specs.set_flags(obj, @taint | initial).should == @taint | initial + obj.should.tainted? + @specs.set_flags(obj, initial).should == initial + obj.should_not.tainted? + @specs.set_flags(obj, @freeze | initial).should == @freeze | initial + obj.should.frozen? + + obj, _ = @data.call + @specs.set_flags(obj, @freeze | @taint | initial).should == @freeze | @taint | initial + obj.should.tainted? + obj.should.frozen? + end + end + + it "supports retrieving the (meta)class" do + obj, _ = @data.call + @specs.get_klass(obj).should == obj.class + obj.singleton_class # ensure the singleton class exists + @specs.get_klass(obj).should == obj.singleton_class + end +end diff --git a/spec/ruby/optional/capi/spec_helper.rb b/spec/ruby/optional/capi/spec_helper.rb new file mode 100644 index 0000000000..9bd2d9791c --- /dev/null +++ b/spec/ruby/optional/capi/spec_helper.rb @@ -0,0 +1,143 @@ +# Require the main spec_helper.rb at the end to let `ruby ...spec.rb` work + +# MRI magic to use built but not installed ruby +$extmk = false + +require 'rbconfig' + +OBJDIR ||= File.expand_path("../../../ext/#{RUBY_ENGINE}/#{RUBY_VERSION}", __FILE__) + +def object_path + path = OBJDIR + if ENV['SPEC_CAPI_CXX'] == 'true' + path = "#{path}/cxx" + end + mkdir_p(path) + path +end + +def compile_extension(name) + debug = false + cxx = ENV['SPEC_CAPI_CXX'] == 'true' + run_mkmf_in_process = RUBY_ENGINE == 'truffleruby' + + core_ext_dir = File.expand_path("../ext", __FILE__) + + spec_caller_location = caller_locations.find { |c| c.path.end_with?('_spec.rb') } + spec_file_path = spec_caller_location.path + spec_ext_dir = File.expand_path("../ext", spec_file_path) + + ext = "#{name}_spec" + lib = "#{object_path}/#{ext}.#{RbConfig::CONFIG['DLEXT']}" + ruby_header = "#{RbConfig::CONFIG['rubyhdrdir']}/ruby.h" + + if RbConfig::CONFIG["ENABLE_SHARED"] == "yes" + libdirname = RbConfig::CONFIG['libdirname'] # defined since 2.1 + libruby = "#{RbConfig::CONFIG[libdirname]}/#{RbConfig::CONFIG['LIBRUBY']}" + end + + begin + mtime = File.mtime(lib) + rescue Errno::ENOENT + # not found, then compile + else + case # if lib is older than headers, source or libruby, then recompile + when mtime <= File.mtime("#{core_ext_dir}/rubyspec.h") + when mtime <= File.mtime("#{spec_ext_dir}/#{ext}.c") + when mtime <= File.mtime(ruby_header) + when libruby && mtime <= File.mtime(libruby) + else + return lib # up-to-date + end + end + + # Copy needed source files to tmpdir + tmpdir = tmp("cext_#{name}") + Dir.mkdir(tmpdir) + begin + ["#{core_ext_dir}/rubyspec.h", "#{spec_ext_dir}/#{ext}.c"].each do |file| + if cxx and file.end_with?('.c') + cp file, "#{tmpdir}/#{File.basename(file, '.c')}.cpp" + else + cp file, "#{tmpdir}/#{File.basename(file)}" + end + end + + Dir.chdir(tmpdir) do + if run_mkmf_in_process + required = require 'mkmf' + # Reinitialize mkmf if already required + init_mkmf unless required + create_makefile(ext, tmpdir) + else + 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' + create_makefile(#{ext.inspect}) + RUBY + output = ruby_exe("extconf.rb") + raise "extconf failed:\n#{output}" unless $?.success? + $stderr.puts output if debug + end + + # Do not capture stderr as we want to show compiler warnings + make, opts = setup_make + output = IO.popen([make, "V=1", "DESTDIR=", opts], &:read) + raise "#{make} failed:\n#{output}" unless $?.success? + $stderr.puts output if debug + + cp File.basename(lib), lib + end + ensure + rm_r tmpdir + end + + File.chmod(0755, lib) + lib +end + +def setup_make + make = ENV['MAKE'] + make ||= (RbConfig::CONFIG['host_os'].include?("mswin") ? "nmake" : "make") + make_flags = ENV["MAKEFLAGS"] || '' + + # suppress logo of nmake.exe to stderr + if File.basename(make, ".*").downcase == "nmake" and !make_flags.include?("l") + ENV["MAKEFLAGS"] = "l#{make_flags}" + end + + 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 + end + end + + [make, opts] +end + +def load_extension(name) + ext_path = compile_extension(name) + require ext_path + ext_path +rescue LoadError => e + if %r{/usr/sbin/execerror ruby "\(ld 3 1 main ([/a-zA-Z0-9_\-.]+_spec\.so)"} =~ e.message + system('/usr/sbin/execerror', "#{RbConfig::CONFIG["bindir"]}/ruby", "(ld 3 1 main #{$1}") + end + raise +end + +# Constants +CAPI_SIZEOF_LONG = [0].pack('l!').size + +# Require the main spec_helper.rb only here so load_extension() is defined +# when running specs with `ruby ...spec.rb` +require_relative '../../spec_helper' diff --git a/spec/ruby/optional/capi/st_spec.rb b/spec/ruby/optional/capi/st_spec.rb new file mode 100644 index 0000000000..8ca2950d83 --- /dev/null +++ b/spec/ruby/optional/capi/st_spec.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 +require_relative 'spec_helper' + +load_extension('st') + +describe "st hash table function" do + before :each do + @s = CApiStSpecs.new + end + + describe "st_init_numtable" do + it "initializes without error" do + @s.st_init_numtable.should == 0 + end + end + + describe "st_init_numtable_with_size" do + it "initializes without error" do + @s.st_init_numtable_with_size.should == 0 + end + end + + describe "st_insert" do + it "returns size 1 after insert" do + @s.st_insert.should == 1 + end + end + + describe "st_foreach" do + it "iterates over each pair of key and value" do + @s.st_foreach.should == 7 + end + end + + describe "st_lookup" do + it "returns the expected value" do + @s.st_lookup.should == 42 + end + end + +end diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb new file mode 100644 index 0000000000..ce387ffa49 --- /dev/null +++ b/spec/ruby/optional/capi/string_spec.rb @@ -0,0 +1,1239 @@ +# encoding: utf-8 +require_relative 'spec_helper' +require_relative '../../shared/string/times' + +load_extension('string') + +class CApiStringSpecs + class ValidTostrTest + def to_str + "ruby" + end + end + + class InvalidTostrTest + def to_str + [] + end + end + + class ToSOrInspect + def to_s + 'A string' + end + + def inspect + 'A different string' + end + end +end + +describe :rb_str_new2, shared: true do + it "returns a new string object calling strlen on the passed C string" do + # Hardcoded to pass const char * = "hello\0invisible" + @s.send(@method, "hello\0not used").should == "hello" + end + + it "encodes the string with ASCII_8BIT" do + @s.send(@method, "hello").encoding.should == Encoding::BINARY + end +end + +describe "C-API String function" do + before :each do + @s = CApiStringSpecs.new + end + + [Encoding::BINARY, Encoding::UTF_8].each do |enc| + describe "rb_str_set_len on a #{enc.name} String" do + before :each do + @str = "abcdefghij".force_encoding(enc) + # Make sure to unshare the string + @s.rb_str_modify(@str) + end + + it "reduces the size of the string" do + @s.rb_str_set_len(@str, 5).should == "abcde" + end + + it "inserts a NULL byte at the length" do + @s.rb_str_set_len(@str, 5).should == "abcde" + @s.rb_str_set_len(@str, 8).should == "abcde\x00gh" + end + + it "updates the byte size" do + @s.rb_str_set_len(@str, 4) + @str.bytesize.should == 4 + @str.should == "abcd" + end + + it "invalidates the character size" do + @str.size.should == 10 + @s.rb_str_set_len(@str, 4) + @str.size.should == 4 + @str.should == "abcd" + end + + it "invalidates the code range" do + @s.rb_str_set_len(@str, 4) + @str.should.ascii_only? + end + + it "updates the string's attributes visible in C code" do + @s.rb_str_set_len_RSTRING_LEN(@str, 4).should == 4 + end + + it "can reveal characters written from C with RSTRING_PTR" do + @s.rb_str_set_len(@str, 1) + @str.should == "a" + + @s.RSTRING_PTR_set(@str, 1, 'B'.ord) + @s.RSTRING_PTR_set(@str, 2, 'C'.ord) + @s.rb_str_set_len(@str, 3) + + @str.bytesize.should == 3 + @str.should == "aBC" + end + end + end + + describe "rb_str_buf_new" do + it "returns the equivalent of an empty string" do + buf = @s.rb_str_buf_new(10, nil) + buf.should == "" + buf.bytesize.should == 0 + buf.size.should == 0 + @s.RSTRING_LEN(buf).should == 0 + end + + it "returns a string with the given capacity" do + buf = @s.rb_str_buf_new(256, nil) + @s.rb_str_capacity(buf).should >= 256 + end + + it "returns a string that can be appended to" do + str = @s.rb_str_buf_new(10, "defg") + str << "abcde" + str.should == "abcde" + end + + it "returns a string that can be concatenated to another string" do + str = @s.rb_str_buf_new(10, "defg") + ("abcde" + str).should == "abcde" + end + + it "returns a string whose bytes can be accessed by RSTRING_PTR" do + str = @s.rb_str_buf_new(10, "abcdefghi") + @s.rb_str_new(str, 10).should == "abcdefghi\x00" + end + + it "returns a string that can be modified by rb_str_set_len" do + str = @s.rb_str_buf_new(10, "abcdef") + @s.rb_str_set_len(str, 4) + str.should == "abcd" + + @s.rb_str_set_len(str, 8) + str[0, 6].should == "abcd\x00f" + @s.RSTRING_LEN(str).should == 8 + end + + it "can be used as a general buffer and reveal characters with rb_str_set_len" do + str = @s.rb_str_buf_new(10, "abcdef") + + @s.RSTRING_PTR_set(str, 0, 195) + @s.RSTRING_PTR_set(str, 1, 169) + @s.rb_str_set_len(str, 2) + + str.force_encoding(Encoding::UTF_8) + str.bytesize.should == 2 + str.size.should == 1 + str.should == "é" + end + end + + describe "rb_str_buf_new2" do + it "returns a new string object calling strlen on the passed C string" do + # Hardcoded to pass const char * = "hello\0invisible" + @s.rb_str_buf_new2.should == "hello" + end + end + + describe "rb_str_tmp_new" do + it "returns a hidden string (RBasic->klass is NULL)" do + @s.rb_str_tmp_new_klass(4).should == false + 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" + end + end + + describe "rb_str_new" do + it "creates a new String with BINARY Encoding" do + @s.rb_str_new("", 0).encoding.should == Encoding::BINARY + end + + it "returns a new string object from a char buffer of len characters" do + @s.rb_str_new("hello", 3).should == "hel" + end + + ruby_version_is ''...'2.7' do + it "returns a non-tainted string" do + @s.rb_str_new("hello", 5).should_not.tainted? + end + end + + it "returns an empty string if len is 0" do + @s.rb_str_new("hello", 0).should == "" + end + + it "copy length bytes and does not stop at the first \\0 byte" do + @s.rb_str_new("he\x00llo", 6).should == "he\x00llo" + @s.rb_str_new_native("he\x00llo", 6).should == "he\x00llo" + end + + it "returns a string from an offset char buffer" do + @s.rb_str_new_offset("hello", 1, 3).should == "ell" + end + end + + describe "rb_str_new2" do + it_behaves_like :rb_str_new2, :rb_str_new2 + end + + describe "rb_str_new_cstr" do + it_behaves_like :rb_str_new2, :rb_str_new_cstr + end + + describe "rb_usascii_str_new" do + it "creates a new String with US-ASCII Encoding from a char buffer of len characters" do + str = "abc".force_encoding("us-ascii") + result = @s.rb_usascii_str_new("abcdef", 3) + result.should == str + result.encoding.should == Encoding::US_ASCII + end + end + + describe "rb_usascii_str_new_lit" do + it "returns a US-ASCII string of the correct characters" do + str = @s.rb_usascii_str_new_lit + str.should == "nokogiri" + str.encoding.should == Encoding::US_ASCII + end + + it "returns US-ASCII string for non-US-ASCII string literal" do + str = @s.rb_usascii_str_new_lit_non_ascii + str.should == "r\xC3\xA9sum\xC3\xA9".force_encoding(Encoding::US_ASCII) + str.encoding.should == Encoding::US_ASCII + end + end + + describe "rb_usascii_str_new_cstr" do + it "creates a new String with US-ASCII Encoding" do + str = "abc".force_encoding("us-ascii") + result = @s.rb_usascii_str_new_cstr("abc") + result.should == str + result.encoding.should == Encoding::US_ASCII + end + end + + describe "rb_str_encode" do + it "returns a String in the destination encoding" do + result = @s.rb_str_encode("abc", Encoding::ISO_8859_1, 0, nil) + result.encoding.should == Encoding::ISO_8859_1 + end + + it "transcodes the String" do + result = @s.rb_str_encode("ありがとう", "euc-jp", 0, nil) + euc_jp = [0xa4, 0xa2, 0xa4, 0xea, 0xa4, 0xac, 0xa4, 0xc8, 0xa4, 0xa6].pack('C*').force_encoding("euc-jp") + result.should == euc_jp + result.encoding.should == Encoding::EUC_JP + end + + it "returns a dup of the original String" do + a = "abc" + b = @s.rb_str_encode("abc", "us-ascii", 0, nil) + a.should_not equal(b) + end + + it "returns a duplicate of the original when the encoding doesn't change" do + a = "abc" + b = @s.rb_str_encode("abc", Encoding::UTF_8, 0, nil) + a.should_not equal(b) + end + + it "accepts encoding flags" do + xFF = [0xFF].pack('C').force_encoding('utf-8') + result = @s.rb_str_encode("a#{xFF}c", "us-ascii", + Encoding::Converter::INVALID_REPLACE, nil) + result.should == "a?c" + result.encoding.should == Encoding::US_ASCII + end + + it "accepts an encoding options Hash specifying replacement String" do + # Yeah, MRI aborts with rb_bug() if the options Hash is not frozen + options = { replace: "b" }.freeze + xFF = [0xFF].pack('C').force_encoding('utf-8') + result = @s.rb_str_encode("a#{xFF}c", "us-ascii", + Encoding::Converter::INVALID_REPLACE, + options) + result.should == "abc" + result.encoding.should == Encoding::US_ASCII + end + end + + describe "rb_str_new3" do + it "returns a copy of the string" do + str1 = "hi" + str2 = @s.rb_str_new3 str1 + str1.should == str2 + str1.should_not equal str2 + end + end + + describe "rb_str_new4" do + it "returns the original string if it is already frozen" do + str1 = "hi" + str1.freeze + str2 = @s.rb_str_new4 str1 + str1.should == str2 + str1.should equal(str2) + str1.should.frozen? + str2.should.frozen? + end + + it "returns a frozen copy of the string" do + str1 = "hi" + str2 = @s.rb_str_new4 str1 + str1.should == str2 + str1.should_not equal(str2) + str2.should.frozen? + end + end + + describe "rb_str_dup" do + it "returns a copy of the string" do + str1 = "hi" + str2 = @s.rb_str_dup str1 + str1.should == str2 + str1.should_not equal str2 + end + end + + describe "rb_str_new5" do + it "returns a new string with the same class as the passed string" do + string_class = Class.new(String) + template_string = string_class.new("hello world") + new_string = @s.rb_str_new5(template_string, "hello world", 11) + + new_string.should == "hello world" + new_string.class.should == string_class + end + end + + ruby_version_is ''...'2.7' do + describe "rb_tainted_str_new" do + it "creates a new tainted String" do + newstring = @s.rb_tainted_str_new("test", 4) + newstring.should == "test" + newstring.tainted?.should be_true + end + end + + describe "rb_tainted_str_new2" do + it "creates a new tainted String" do + newstring = @s.rb_tainted_str_new2("test") + newstring.should == "test" + newstring.tainted?.should be_true + end + end + end + + describe "rb_str_append" do + it "appends a string to another string" do + @s.rb_str_append("Hello", " Goodbye").should == "Hello Goodbye" + end + + it "raises a TypeError trying to append non-String-like object" do + -> { @s.rb_str_append("Hello", 32323)}.should raise_error(TypeError) + end + + it "changes Encoding if a string is appended to an empty string" do + string = "パスタ".encode(Encoding::ISO_2022_JP) + @s.rb_str_append("", string).encoding.should == Encoding::ISO_2022_JP + end + end + + describe "rb_str_plus" do + it "returns a new string from concatenating two other strings" do + @s.rb_str_plus("Hello", " Goodbye").should == "Hello Goodbye" + end + end + + describe "rb_str_times" do + it_behaves_like :string_times, :rb_str_times, -> str, times { @s.rb_str_times(str, times) } + end + + describe "rb_str_buf_cat" do + it "concatenates a C string to a ruby string" do + @s.rb_str_buf_cat("Your house is on fire").should == "Your house is on fire?" + end + end + + describe "rb_str_cat" do + it "concatenates a C string to ruby string" do + @s.rb_str_cat("Your house is on fire").should == "Your house is on fire?" + end + end + + describe "rb_str_cat2" do + it "concatenates a C string to a ruby string" do + @s.rb_str_cat2("Your house is on fire").should == "Your house is on fire?" + end + end + + describe "rb_str_cat_cstr" do + it "concatenates a C string literal to a ruby string" do + @s.rb_str_cat_cstr_constant("Your house is on fire").should == "Your house is on fire?" + end + + it "concatenates a variable C string to a ruby string" do + @s.rb_str_cat_cstr("Your house is on fire", "?").should == "Your house is on fire?" + end + end + + describe "rb_enc_str_buf_cat" do + it "concatenates a C string literal to a ruby string with the given encoding" do + input = "hello ".force_encoding(Encoding::US_ASCII) + result = @s.rb_enc_str_buf_cat(input, "résumé", Encoding::UTF_8) + result.should == "hello résumé" + result.encoding.should == Encoding::UTF_8 + result.object_id.should == input.object_id + end + end + + describe "rb_str_cmp" do + it "returns 0 if two strings are identical" do + @s.rb_str_cmp("ppp", "ppp").should == 0 + end + + it "returns -1 if the first string is shorter than the second" do + @s.rb_str_cmp("xxx", "xxxx").should == -1 + end + + it "returns -1 if the first string is lexically less than the second" do + @s.rb_str_cmp("xxx", "yyy").should == -1 + end + + it "returns 1 if the first string is longer than the second" do + @s.rb_str_cmp("xxxx", "xxx").should == 1 + end + + it "returns 1 if the first string is lexically greater than the second" do + @s.rb_str_cmp("yyy", "xxx").should == 1 + end + end + + describe "rb_str_split" do + it "splits strings over a splitter" do + @s.rb_str_split("Hello,Goodbye").should == ["Hello", "Goodbye"] + end + end + + describe "rb_str2inum" do + it "converts a string to a number given a base" do + @s.rb_str2inum("10", 10).should == 10 + @s.rb_str2inum("A", 16).should == 10 + end + end + + describe "rb_cstr2inum" do + it "converts a C string to a Fixnum given a base" do + @s.rb_cstr2inum("10", 10).should == 10 + @s.rb_cstr2inum("10", 16).should == 16 + end + + it "converts a C string to a Bignum given a base" do + @s.rb_cstr2inum(bignum_value.to_s, 10).should == bignum_value + end + + it "converts a C string to a Fixnum non-strictly if base is not 0" do + @s.rb_cstr2inum("1234a", 10).should == 1234 + end + + it "converts a C string to a Fixnum strictly if base is 0" do + -> { @s.rb_cstr2inum("1234a", 0) }.should raise_error(ArgumentError) + end + end + + describe "rb_cstr_to_inum" do + it "converts a C string to a Fixnum given a base" do + @s.rb_cstr_to_inum("1234", 10, true).should == 1234 + end + + it "converts a C string to a Bignum given a base" do + @s.rb_cstr_to_inum(bignum_value.to_s, 10, true).should == bignum_value + end + + it "converts a C string to a Fixnum non-strictly" do + @s.rb_cstr_to_inum("1234a", 10, false).should == 1234 + end + + it "converts a C string to a Fixnum strictly" do + -> { @s.rb_cstr_to_inum("1234a", 10, true) }.should raise_error(ArgumentError) + end + end + + describe "rb_fstring" do + it 'returns self if the String is frozen' do + input = 'foo'.freeze + output = @s.rb_fstring(input) + + output.should equal(input) + output.should.frozen? + end + + it 'returns a frozen copy if the String is not frozen' do + input = 'foo' + output = @s.rb_fstring(input) + + output.should.frozen? + output.should_not equal(input) + output.should == 'foo' + end + end + + describe "rb_str_subseq" do + it "returns a byte-indexed substring" do + str = "\x00\x01\x02\x03\x04".force_encoding("binary") + @s.rb_str_subseq(str, 1, 2).should == "\x01\x02".force_encoding("binary") + end + end + + describe "rb_str_substr" do + it "returns a substring" do + "hello".length.times do |time| + @s.rb_str_substr("hello", 0, time + 1).should == "hello"[0..time] + end + end + end + + describe "rb_str_to_str" do + it "calls #to_str to coerce the value to a String" do + @s.rb_str_to_str("foo").should == "foo" + @s.rb_str_to_str(CApiStringSpecs::ValidTostrTest.new).should == "ruby" + end + + it "raises a TypeError if coercion fails" do + -> { @s.rb_str_to_str(0) }.should raise_error(TypeError) + -> { @s.rb_str_to_str(CApiStringSpecs::InvalidTostrTest.new) }.should raise_error(TypeError) + end + end + + describe "RSTRING_PTR" do + it "returns a pointer to the string's contents" do + str = "abc" + chars = [] + @s.RSTRING_PTR_iterate(str) do |c| + chars << c + end + chars.should == [97, 98, 99] + end + + it "allows changing the characters in the string" do + str = "abc" + @s.RSTRING_PTR_assign(str, 'A'.ord) + str.should == "AAA" + end + + it "reflects changes after a rb_funcall" do + lamb = proc { |s| s.replace "NEW CONTENT" } + + str = "beforebefore" + + ret = @s.RSTRING_PTR_after_funcall(str, lamb) + + str.should == "NEW CONTENT" + ret.should == str + end + + it "reflects changes from native memory and from String#setbyte in bounds" do + str = "abc" + from_rstring_ptr = @s.RSTRING_PTR_after_yield(str) { str.setbyte(1, 'B'.ord) } + from_rstring_ptr.should == "1B2" + str.should == "1B2" + end + + it "returns a pointer to the contents of encoded pointer-sized string" do + s = "70パク". + encode(Encoding::UTF_16LE). + force_encoding(Encoding::UTF_16LE). + encode(Encoding::UTF_8) + + chars = [] + @s.RSTRING_PTR_iterate(s) do |c| + chars << c + end + chars.should == [55, 48, 227, 131, 145, 227, 130, 175] + end + + it "returns a pointer which can be cast and used as another type" do + s = "70パク". + encode(Encoding::UTF_16LE). + force_encoding(Encoding::UTF_16LE). + encode(Encoding::UTF_8) + + ints = [] + @s.RSTRING_PTR_iterate_uint32(s) do |i| + ints << i + end + ints.should == s.unpack('LL') + end + + it "allows a short memcpy to the string which may be converted to a single write operation by the compiler" do + str = " " + @s.RSTRING_PTR_short_memcpy(str).should == "Infinity" + end + + it "allows read() to update the string contents" do + filename = fixture(__FILE__, "read.txt") + str = "" + capacities = @s.RSTRING_PTR_read(str, filename) + capacities.should == [30, 53] + str.should == "fixture file contents to test read() with RSTRING_PTR" + end + end + + describe "RSTRING_LEN" do + it "returns the size of the string" do + @s.RSTRING_LEN("gumdrops").should == 8 + end + end + + describe "RSTRING_LENINT" do + it "returns the size of a string" do + @s.RSTRING_LENINT("silly").should == 5 + end + end + + describe :string_value_macro, shared: true do + before :each do + @s = CApiStringSpecs.new + end + + it "does not call #to_str on a String" do + str = "genuine" + str.should_not_receive(:to_str) + @s.send(@method, str) + end + + it "does not call #to_s on a String" do + str = "genuine" + str.should_not_receive(:to_str) + @s.send(@method, str) + end + + it "calls #to_str on non-String objects" do + str = mock("fake") + str.should_receive(:to_str).and_return("wannabe") + @s.send(@method, str).should == "wannabe" + end + + it "does not call #to_s on non-String objects" do + str = mock("fake") + str.should_not_receive(:to_s) + -> { @s.send(@method, str) }.should raise_error(TypeError) + end + end + + describe "StringValue" do + it_behaves_like :string_value_macro, :StringValue + end + + describe "SafeStringValue" do + ruby_version_is ''...'2.7' do + it "raises for tained string when $SAFE is 1" do + begin + Thread.new { + $SAFE = 1 + -> { + @s.SafeStringValue("str".taint) + }.should raise_error(SecurityError) + }.join + ensure + $SAFE = 0 + end + end + + it_behaves_like :string_value_macro, :SafeStringValue + end + end + + describe "rb_str_modify" do + it "raises an error if the string is frozen" do + -> { @s.rb_str_modify("frozen".freeze) }.should raise_error(FrozenError) + end + end + + describe "rb_str_modify_expand" do + it "grows the capacity to bytesize + expand, not changing the bytesize" do + str = @s.rb_str_buf_new(256, "abcd") + @s.rb_str_capacity(str).should >= 256 + + @s.rb_str_set_len(str, 3) + str.bytesize.should == 3 + @s.RSTRING_LEN(str).should == 3 + @s.rb_str_capacity(str).should >= 256 + + @s.rb_str_modify_expand(str, 4) + str.bytesize.should == 3 + @s.RSTRING_LEN(str).should == 3 + @s.rb_str_capacity(str).should >= 7 + + @s.rb_str_modify_expand(str, 1024) + str.bytesize.should == 3 + @s.RSTRING_LEN(str).should == 3 + @s.rb_str_capacity(str).should >= 1027 + + @s.rb_str_modify_expand(str, 1) + str.bytesize.should == 3 + @s.RSTRING_LEN(str).should == 3 + @s.rb_str_capacity(str).should >= 4 + end + + it "raises an error if the string is frozen" do + -> { @s.rb_str_modify_expand("frozen".freeze, 10) }.should raise_error(FrozenError) + end + end + + describe "rb_str_resize" do + it "reduces the size of the string" do + str = @s.rb_str_resize("test", 2) + str.size.should == 2 + str.bytesize.should == 2 + @s.RSTRING_LEN(str).should == 2 + str.should == "te" + end + + it "updates the string's attributes visible in C code" do + @s.rb_str_resize_RSTRING_LEN("test", 2).should == 2 + end + + it "copies the existing bytes" do + str = "t" + @s.rb_str_resize_copy(str).should == "test" + end + + it "increases the size of the string" do + expected = "test".force_encoding("US-ASCII") + str = @s.rb_str_resize(expected.dup, 12) + str.size.should == 12 + str.bytesize.should == 12 + @s.RSTRING_LEN(str).should == 12 + str[0, 4].should == expected + end + end + + describe "rb_str_inspect" do + it "returns the equivalent of calling #inspect on the String" do + @s.rb_str_inspect("value").should == %["value"] + end + end + + describe "rb_str_intern" do + it "returns a symbol created from the string" do + @s.rb_str_intern("symbol").should == :symbol + end + + it "returns a symbol even if passed an empty string" do + @s.rb_str_intern("").should == "".to_sym + end + + it "returns a symbol even if the passed string contains NULL characters" do + @s.rb_str_intern("no\0no").should == "no\0no".to_sym + end + end + + describe "rb_str_freeze" do + it "freezes the string" do + s = "" + @s.rb_str_freeze(s).should == s + s.frozen?.should be_true + end + end + + describe "rb_str_hash" do + it "hashes the string into a number" do + s = "hello" + @s.rb_str_hash(s).should be_kind_of(Integer) + end + end + + describe "rb_str_update" do + it "splices the replacement string into the original at the given location" do + @s.rb_str_update("hello", 2, 3, "wuh").should == "hewuh" + end + end +end + +describe "rb_str_free" do + # This spec only really exists to make sure the symbol + # is available. There is no guarantee this even does + # anything at all + it "indicates data for a string might be freed" do + @s.rb_str_free("xyz").should be_nil + end +end + +describe :rb_external_str_new, shared: true do + it "returns a String in the default external encoding" do + Encoding.default_external = "UTF-8" + @s.send(@method, "abc").encoding.should == Encoding::UTF_8 + end + + it "returns a binary encoded string if any non-ascii bytes are present and default external is US-ASCII" do + Encoding.default_external = "US-ASCII" + x80 = [0x80].pack('C') + @s.send(@method, "#{x80}abc").encoding.should == Encoding::BINARY + end + + ruby_version_is ''...'2.7' do + it "returns a tainted String" do + @s.send(@method, "abc").tainted?.should be_true + end + end +end + +describe "C-API String function" do + before :each do + @s = CApiStringSpecs.new + @external = Encoding.default_external + @internal = Encoding.default_internal + end + + after :each do + Encoding.default_external = @external + Encoding.default_internal = @internal + end + + describe "rb_str_length" do + it "returns the string's length" do + @s.rb_str_length("dewdrops").should == 8 + end + + it "counts characters in multi byte encodings" do + @s.rb_str_length("düwdrops").should == 8 + end + end + + describe "rb_str_equal" do + it "compares two same strings" do + s = "hello" + @s.rb_str_equal(s, "hello").should be_true + end + + it "compares two different strings" do + s = "hello" + @s.rb_str_equal(s, "hella").should be_false + end + end + + describe "rb_external_str_new" do + it_behaves_like :rb_external_str_new, :rb_external_str_new + end + + describe "rb_external_str_new_cstr" do + it_behaves_like :rb_external_str_new, :rb_external_str_new_cstr + end + + describe "rb_external_str_new_with_enc" do + it "returns a String in the specified encoding" do + s = @s.rb_external_str_new_with_enc("abc", 3, Encoding::UTF_8) + s.encoding.should == Encoding::UTF_8 + end + + it "returns a binary encoded String if any non-ascii bytes are present and the specified encoding is US-ASCII" do + x80 = [0x80].pack('C') + s = @s.rb_external_str_new_with_enc("#{x80}abc", 4, Encoding::US_ASCII) + s.encoding.should == Encoding::BINARY + end + + +# it "transcodes a String to Encoding.default_internal if it is set" do +# Encoding.default_internal = Encoding::EUC_JP +# +# - a = "\xE3\x81\x82\xe3\x82\x8c".force_encoding("utf-8") +# + a = [0xE3, 0x81, 0x82, 0xe3, 0x82, 0x8c].pack('C6').force_encoding("utf-8") +# s = @s.rb_external_str_new_with_enc(a, a.bytesize, Encoding::UTF_8) +# - +# - s.should == "\xA4\xA2\xA4\xEC".force_encoding("euc-jp") +# + x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4')#.force_encoding('binary') +# + s.should == x +# s.encoding.should equal(Encoding::EUC_JP) +# end + + it "transcodes a String to Encoding.default_internal if it is set" do + Encoding.default_internal = Encoding::EUC_JP + + a = [0xE3, 0x81, 0x82, 0xe3, 0x82, 0x8c].pack('C6').force_encoding("utf-8") + s = @s.rb_external_str_new_with_enc(a, a.bytesize, Encoding::UTF_8) + x = [0xA4, 0xA2, 0xA4, 0xEC].pack('C4').force_encoding('euc-jp') + s.should == x + s.encoding.should equal(Encoding::EUC_JP) + end + + ruby_version_is ''...'2.7' do + it "returns a tainted String" do + s = @s.rb_external_str_new_with_enc("abc", 3, Encoding::US_ASCII) + s.tainted?.should be_true + end + end + end + + describe "rb_locale_str_new" do + it "returns a String with 'locale' encoding" do + s = @s.rb_locale_str_new("abc", 3) + s.should == "abc".force_encoding(Encoding.find("locale")) + s.encoding.should equal(Encoding.find("locale")) + end + end + + describe "rb_locale_str_new_cstr" do + it "returns a String with 'locale' encoding" do + s = @s.rb_locale_str_new_cstr("abc") + s.should == "abc".force_encoding(Encoding.find("locale")) + s.encoding.should equal(Encoding.find("locale")) + end + end + + describe "rb_str_conv_enc" do + it "returns the original String when to encoding is not specified" do + a = "abc".force_encoding("us-ascii") + @s.rb_str_conv_enc(a, Encoding::US_ASCII, nil).should equal(a) + 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) + end + + it "returns a transcoded String" do + a = "\xE3\x81\x82\xE3\x82\x8C".force_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) + end + + describe "when the String encoding is equal to the destination encoding" do + it "returns the original String" do + a = "abc".force_encoding("us-ascii") + @s.rb_str_conv_enc(a, Encoding::US_ASCII, Encoding::US_ASCII).should equal(a) + end + + it "returns the original String if the destination encoding is ASCII compatible and the String has no high bits set" do + a = "abc".encode("us-ascii") + @s.rb_str_conv_enc(a, Encoding::UTF_8, Encoding::US_ASCII).should equal(a) + end + + it "returns the origin String if the destination encoding is BINARY" do + a = "abc".force_encoding("binary") + @s.rb_str_conv_enc(a, Encoding::US_ASCII, Encoding::BINARY).should equal(a) + end + end + end + + describe "rb_str_conv_enc_opts" do + it "returns the original String when to encoding is not specified" do + a = "abc".force_encoding("us-ascii") + @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, nil, 0, nil).should equal(a) + 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_opts(a, Encoding::UTF_8, + Encoding::EUC_JP, 0, nil).should equal(a) + end + + it "returns a transcoded String" do + a = "\xE3\x81\x82\xE3\x82\x8C".force_encoding("utf-8") + result = @s.rb_str_conv_enc_opts(a, Encoding::UTF_8, Encoding::EUC_JP, 0, nil) + 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) + end + + describe "when the String encoding is equal to the destination encoding" do + it "returns the original String" do + a = "abc".force_encoding("us-ascii") + @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, + Encoding::US_ASCII, 0, nil).should equal(a) + end + + it "returns the original String if the destination encoding is ASCII compatible and the String has no high bits set" do + a = "abc".encode("us-ascii") + @s.rb_str_conv_enc_opts(a, Encoding::UTF_8, + Encoding::US_ASCII, 0, nil).should equal(a) + end + + it "returns the origin String if the destination encoding is BINARY" do + a = "abc".force_encoding("binary") + @s.rb_str_conv_enc_opts(a, Encoding::US_ASCII, + Encoding::BINARY, 0, nil).should equal(a) + end + end + end + + describe "rb_str_export" do + it "returns the original String with the external encoding" do + Encoding.default_external = Encoding::ISO_8859_1 + s = @s.rb_str_export("Hëllo") + s.encoding.should equal(Encoding::ISO_8859_1) + end + end + + describe "rb_str_export_locale" do + it "returns the original String with the locale encoding" do + s = @s.rb_str_export_locale("abc") + s.should == "abc".force_encoding(Encoding.find("locale")) + s.encoding.should equal(Encoding.find("locale")) + end + end + + describe "rb_str_export_to_enc" do + it "returns a copy of an ascii string converted to the new encoding" do + source = "A simple string".encode(Encoding::US_ASCII) + result = @s.rb_str_export_to_enc(source, Encoding::UTF_8) + result.should == source.encode(Encoding::UTF_8) + result.encoding.should == Encoding::UTF_8 + end + + it "returns the source string if it can not be converted" do + source = ["00ff"].pack("H*"); + result = @s.rb_str_export_to_enc(source, Encoding::UTF_8) + result.should equal(source) + end + + it "does not alter the source string if it can not be converted" do + source = ["00ff"].pack("H*"); + result = @s.rb_str_export_to_enc(source, Encoding::UTF_8) + source.bytes.should == [0, 255] + end +end + + describe "rb_sprintf" do + it "replaces the parts like sprintf" do + @s.rb_sprintf1("Awesome %s is replaced", "string").should == "Awesome string is replaced" + @s.rb_sprintf1("%s", "TestFoobarTest").should == "TestFoobarTest" + end + + it "accepts multiple arguments" do + s = "Awesome %s is here with %s" + @s.rb_sprintf2(s, "string", "content").should == "Awesome string is here with content" + end + + it "formats a string VALUE using to_s if sign not specified in format" do + s = 'Result: A string.' + @s.rb_sprintf3(CApiStringSpecs::ToSOrInspect.new).should == s + end + + it "formats a string VALUE using inspect if sign specified in format" do + s = 'Result: A different string.' + @s.rb_sprintf4(CApiStringSpecs::ToSOrInspect.new).should == s + end + + it "formats a TrueClass VALUE as `TrueClass` if sign not specified in format" do + s = 'Result: TrueClass.' + @s.rb_sprintf3(true.class).should == s + end + + it "formats a TrueClass VALUE as 'true' if sign specified in format" do + s = 'Result: true.' + @s.rb_sprintf4(true.class).should == s + end + + it "truncates a string to a supplied precision if that is shorter than the string" do + s = 'Result: Hel.' + @s.rb_sprintf5(0, 3, "Hello").should == s + end + + it "does not truncates a string to a supplied precision if that is longer than the string" do + s = 'Result: Hello.' + @s.rb_sprintf5(0, 8, "Hello").should == s + end + + it "pads a string to a supplied width if that is longer than the string" do + s = 'Result: Hello.' + @s.rb_sprintf5(8, 5, "Hello").should == s + end + + it "truncates a VALUE string to a supplied precision if that is shorter than the VALUE string" do + s = 'Result: Hel.' + @s.rb_sprintf6(0, 3, "Hello").should == s + end + + it "does not truncates a VALUE string to a supplied precision if that is longer than the VALUE string" do + s = 'Result: Hello.' + @s.rb_sprintf6(0, 8, "Hello").should == s + end + + it "pads a VALUE string to a supplied width if that is longer than the VALUE string" do + s = 'Result: Hello.' + @s.rb_sprintf6(8, 5, "Hello").should == s + end + + it "can format a nil VALUE as a pointer and gives the same output as sprintf in C" do + res = @s.rb_sprintf7("%p", nil); + res[0].should == res[1] + end + + it "can format a string VALUE as a pointer and gives the same output as sprintf in C" do + res = @s.rb_sprintf7("%p", "Hello") + res[0].should == res[1] + end + + it "can format a raw number a pointer and gives the same output as sprintf in C" do + res = @s.rb_sprintf7("%p", 0x223643); + res[0].should == res[1] + end + end + + describe "rb_vsprintf" do + it "returns a formatted String from a variable number of arguments" do + s = @s.rb_vsprintf("%s, %d, %.2f", "abc", 42, 2.7); + s.should == "abc, 42, 2.70" + end + end + + describe "rb_String" do + it "returns the passed argument if it is a string" do + @s.rb_String("a").should == "a" + end + + it "tries to convert the passed argument to a string by calling #to_str first" do + @s.rb_String(CApiStringSpecs::ValidTostrTest.new).should == "ruby" + end + + it "raises a TypeError if #to_str does not return a string" do + -> { @s.rb_String(CApiStringSpecs::InvalidTostrTest.new) }.should raise_error(TypeError) + end + + it "tries to convert the passed argument to a string by calling #to_s" do + @s.rb_String({"bar" => "foo"}).should == '{"bar"=>"foo"}' + end + end + + describe "rb_string_value_cstr" do + it "returns a non-null pointer for a simple string" do + @s.rb_string_value_cstr("Hello").should == true + end + + it "returns a non-null pointer for a UTF-16 string" do + @s.rb_string_value_cstr("Hello".encode('UTF-16BE')).should == true + end + + it "raises an error if a string contains a null" do + -> { @s.rb_string_value_cstr("Hello\0 with a null.") }.should raise_error(ArgumentError) + end + + it "raises an error if a UTF-16 string contains a null" do + -> { @s.rb_string_value_cstr("Hello\0 with a null.".encode('UTF-16BE')) }.should raise_error(ArgumentError) + end + + end + + describe "rb_str_drop_bytes" do + it "drops N characters for an ASCII string" do + str = "12345678".encode("US-ASCII") + @s.rb_str_drop_bytes(str, 4) + str.should == "5678".encode("US-ASCII") + end + + it "drop N/2 characters for a UTF-16 string" do + str = "12345678".encode("UTF-16LE") + @s.rb_str_drop_bytes(str, 4) + str.should == "345678".encode("UTF-16LE") + end + + it "drop N/4 characters for a UTF-32 string" do + str = "12345678".encode("UTF-32LE") + @s.rb_str_drop_bytes(str, 4) + str.should == "2345678".encode("UTF-32LE") + end + end + + describe "rb_utf8_str_new_static" do + it "returns a UTF-8 string of the correct characters and length" do + str = @s.rb_utf8_str_new_static + str.should == "nokogiri" + str.encoding.should == Encoding::UTF_8 + end + end + + describe "rb_utf8_str_new" do + it "returns a UTF-8 string of the correct characters and length" do + str = @s.rb_utf8_str_new + str.should == "nokogiri" + str.encoding.should == Encoding::UTF_8 + end + end + + describe "rb_utf8_str_new_cstr" do + it "returns a UTF-8 string of the correct characters and length" do + str = @s.rb_utf8_str_new_cstr + str.should == "nokogiri" + str.encoding.should == Encoding::UTF_8 + end + end + + describe "rb_str_vcatf" do + it "appends the message to the string" do + @s.rb_str_vcatf("").should == "fmt 42 7 number" + + str = "test " + @s.rb_str_vcatf(str) + str.should == "test fmt 42 7 number" + end + end + + describe "rb_str_catf" do + it "appends the message to the string" do + @s.rb_str_catf("").should == "fmt 41 6 number" + + str = "test " + @s.rb_str_catf(str) + str.should == "test fmt 41 6 number" + end + end + + describe "rb_str_locktmp" do + it "raises an error when trying to lock an already locked string" do + 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" + @s.rb_str_locktmp(str).should == str + -> { str.upcase! }.should raise_error(RuntimeError, 'can\'t modify string; temporarily locked') + end + end + + describe "rb_str_unlocktmp" do + it "unlocks a locked string" do + 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') + end + end +end diff --git a/spec/ruby/optional/capi/struct_spec.rb b/spec/ruby/optional/capi/struct_spec.rb new file mode 100644 index 0000000000..0e9e366908 --- /dev/null +++ b/spec/ruby/optional/capi/struct_spec.rb @@ -0,0 +1,211 @@ +require_relative 'spec_helper' + +load_extension("struct") + +describe "C-API Struct function" do + before :each do + @s = CApiStructSpecs.new + @struct = @s.rb_struct_define("CAPIStruct", "a", "b", "c") + end + + after :each do + Struct.send(:remove_const, :CAPIStruct) + end + + describe "rb_struct_define" do + it "creates accessors for the struct members" do + instance = @struct.new + instance.a = 1 + instance.b = 2 + instance.c = 3 + instance.a.should == 1 + instance.b.should == 2 + instance.c.should == 3 + end + + it "has a value of nil for the member of a newly created instance" do + # Verify that attributes are on an instance basis + Struct::CAPIStruct.new.b.should be_nil + end + + it "creates a constant scoped under Struct for the named Struct" do + Struct.should have_constant(:CAPIStruct) + end + + it "returns the member names as Symbols" do + @struct.members.should == [:a, :b, :c] + end + end +end + +describe "C-API Struct function" do + before :each do + @s = CApiStructSpecs.new + @struct = @s.rb_struct_define(nil, "a", "b", "c") + end + + describe "rb_struct_define for an anonymous struct" do + it "creates accessors for the struct members" do + instance = @struct.new + instance.a = 1 + instance.b = 2 + instance.c = 3 + instance.a.should == 1 + instance.b.should == 2 + instance.c.should == 3 + end + + it "returns the member names as Symbols" do + @struct.members.should == [:a, :b, :c] + end + end +end + +describe "C-API Struct function" do + before :all do + @s = CApiStructSpecs.new + @struct = @s.rb_struct_define_under(CApiStructSpecs, "CAPIStructUnder", "a", "b", "c") + end + + describe "rb_struct_define_under" do + it "creates accessors for the struct members" do + instance = @struct.new + instance.a = 1 + instance.b = 2 + instance.c = 3 + instance.a.should == 1 + instance.b.should == 2 + instance.c.should == 3 + end + + it "has a value of nil for the member of a newly created instance" do + # Verify that attributes are on an instance basis + CApiStructSpecs::CAPIStructUnder.new.b.should be_nil + end + + it "does not create a constant scoped under Struct for the named Struct" do + Struct.should_not have_constant(:CAPIStructUnder) + end + + it "creates a constant scoped under the namespace of the given class" do + CApiStructSpecs.should have_constant(:CAPIStructUnder) + end + + it "returns the member names as Symbols" do + @struct.members.should == [:a, :b, :c] + end + end +end + +describe "C-API Struct function" do + before :each do + @s = CApiStructSpecs.new + @klass = Struct.new(:a, :b, :c) + @struct = @klass.new + end + + describe "rb_struct_define" do + it "raises an ArgumentError if arguments contain duplicate member name" do + -> { @s.rb_struct_define(nil, "a", "b", "a") }.should raise_error(ArgumentError) + end + + it "raises a NameError if an invalid constant name is given" do + -> { @s.rb_struct_define("foo", "a", "b", "c") }.should raise_error(NameError) + end + end + + describe "rb_struct_aref" do + it "returns the value of a struct member with a symbol key" do + @struct[:a] = 2 + @s.rb_struct_aref(@struct, :a).should == 2 + end + + it "returns the value of a struct member with a string key" do + @struct[:b] = 2 + @s.rb_struct_aref(@struct, "b").should == 2 + end + + it "returns the value of a struct member by index" do + @struct[:c] = 3 + @s.rb_struct_aref(@struct, 2).should == 3 + end + + it "raises a NameError if the struct member does not exist" do + -> { @s.rb_struct_aref(@struct, :d) }.should raise_error(NameError) + end + + it "raises an IndexError if the given index is out of range" do + -> { @s.rb_struct_aref(@struct, -4) }.should raise_error(IndexError) + -> { @s.rb_struct_aref(@struct, 3) }.should raise_error(IndexError) + end + end + + describe "rb_struct_getmember" do + it "returns the value of a struct member" do + @struct[:a] = 2 + @s.rb_struct_getmember(@struct, :a).should == 2 + end + + it "raises a NameError if the struct member does not exist" do + -> { @s.rb_struct_getmember(@struct, :d) }.should raise_error(NameError) + end + end + + describe "rb_struct_s_members" do + it "returns the struct members as an array of symbols" do + @s.rb_struct_s_members(@klass).should == [:a, :b, :c] + end + end + + describe "rb_struct_members" do + it "returns the struct members as an array of symbols" do + @s.rb_struct_members(@struct).should == [:a, :b, :c] + end + end + + describe "rb_struct_aset" do + it "sets the value of a struct member with a symbol key" do + @s.rb_struct_aset(@struct, :a, 1) + @struct[:a].should == 1 + end + + it "sets the value of a struct member with a string key" do + @s.rb_struct_aset(@struct, "b", 1) + @struct[:b].should == 1 + end + + it "sets the value of a struct member by index" do + @s.rb_struct_aset(@struct, 2, 1) + @struct[:c].should == 1 + end + + it "raises a NameError if the struct member does not exist" do + -> { @s.rb_struct_aset(@struct, :d, 1) }.should raise_error(NameError) + end + + it "raises an IndexError if the given index is out of range" do + -> { @s.rb_struct_aset(@struct, -4, 1) }.should raise_error(IndexError) + -> { @s.rb_struct_aset(@struct, 3, 1) }.should raise_error(IndexError) + end + + it "raises a FrozenError if the struct is frozen" do + @struct.freeze + -> { @s.rb_struct_aset(@struct, :a, 1) }.should raise_error(FrozenError) + end + end + + describe "rb_struct_new" do + it "creates a new instance of a struct" do + i = @s.rb_struct_new(@klass, 1, 2, 3) + i.a.should == 1 + i.b.should == 2 + i.c.should == 3 + end + end + + describe "rb_struct_size" do + it "returns the number of struct members" do + @s.rb_struct_size(@struct).should == 3 + end + end +end diff --git a/spec/ruby/optional/capi/symbol_spec.rb b/spec/ruby/optional/capi/symbol_spec.rb new file mode 100644 index 0000000000..b8fda34c0e --- /dev/null +++ b/spec/ruby/optional/capi/symbol_spec.rb @@ -0,0 +1,172 @@ +# -*- encoding: utf-8 -*- +require_relative 'spec_helper' + +load_extension('symbol') + +describe "C-API Symbol function" do + before :each do + @s = CApiSymbolSpecs.new + end + + describe "SYMBOL_P" do + it "returns true for a Symbol" do + @s.SYMBOL_P(:foo).should == true + end + + it "returns false for non-Symbols" do + @s.SYMBOL_P('bar').should == false + end + end + + describe "rb_intern" do + it "converts a string to a symbol, uniquely" do + @s.rb_intern("test_symbol").should == :test_symbol + @s.rb_intern_c_compare("test_symbol", :test_symbol).should == true + end + end + + describe "rb_intern2" do + it "converts a string to a symbol, uniquely, for a string of given length" do + @s.rb_intern2("test_symbol", 4).should == :test + @s.rb_intern2_c_compare("test_symbol", 4, :test).should == true + end + end + + describe "rb_intern3" do + it "converts a multibyte symbol with the encoding" do + sym = @s.rb_intern3("Ω", 2, Encoding::UTF_8) + sym.encoding.should == Encoding::UTF_8 + sym.should == :Ω + @s.rb_intern3_c_compare("Ω", 2, Encoding::UTF_8, :Ω).should == true + end + + it "converts an ascii compatible symbol with the ascii encoding" do + sym = @s.rb_intern3("foo", 3, Encoding::UTF_8) + sym.encoding.should == Encoding::US_ASCII + sym.should == :foo + end + + it "should respect the symbol encoding via rb_intern3" do + :Ω.to_s.encoding.should == Encoding::UTF_8 + end + end + + describe "rb_intern_const" do + it "converts a string to a Symbol" do + @s.rb_intern_const("test").should == :test + end + end + + describe "rb_id2name" do + it "converts a symbol to a C char array" do + @s.rb_id2name(:test_symbol).should == "test_symbol" + end + end + + describe "rb_id2str" do + it "converts a symbol to a Ruby string" do + @s.rb_id2str(:test_symbol).should == "test_symbol" + end + + it "creates a string with the same encoding as the symbol" do + str = "test_symbol".encode(Encoding::UTF_16LE) + @s.rb_id2str(str.to_sym).encoding.should == Encoding::UTF_16LE + end + end + + describe "rb_intern_str" do + it "converts a Ruby String to a Symbol" do + str = "test_symbol" + @s.rb_intern_str(str).should == :test_symbol + end + end + + describe "rb_check_symbol_cstr" do + it "returns a Symbol if a Symbol already exists for the given C string" do + sym = :test_symbol + @s.rb_check_symbol_cstr('test_symbol').should == sym + end + + it "returns nil if the Symbol does not exist yet and does not create it" do + str = "symbol_does_not_exist_#{Object.new.object_id}_#{rand}" + @s.rb_check_symbol_cstr(str).should == nil # does not create the Symbol + @s.rb_check_symbol_cstr(str).should == nil + end + end + + describe "rb_is_const_id" do + it "returns true given a const-like symbol" do + @s.rb_is_const_id(:Foo).should == true + end + + it "returns false given an ivar-like symbol" do + @s.rb_is_const_id(:@foo).should == false + end + + it "returns false given a cvar-like symbol" do + @s.rb_is_const_id(:@@foo).should == false + end + + it "returns false given an undecorated symbol" do + @s.rb_is_const_id(:foo).should == false + end + end + + describe "rb_is_instance_id" do + it "returns false given a const-like symbol" do + @s.rb_is_instance_id(:Foo).should == false + end + + it "returns true given an ivar-like symbol" do + @s.rb_is_instance_id(:@foo).should == true + end + + it "returns false given a cvar-like symbol" do + @s.rb_is_instance_id(:@@foo).should == false + end + + it "returns false given an undecorated symbol" do + @s.rb_is_instance_id(:foo).should == false + end + end + + describe "rb_is_class_id" do + it "returns false given a const-like symbol" do + @s.rb_is_class_id(:Foo).should == false + end + + it "returns false given an ivar-like symbol" do + @s.rb_is_class_id(:@foo).should == false + end + + it "returns true given a cvar-like symbol" do + @s.rb_is_class_id(:@@foo).should == true + end + + it "returns false given an undecorated symbol" do + @s.rb_is_class_id(:foo).should == false + end + end + + describe "rb_sym2str" do + it "converts a Symbol to a String" do + @s.rb_sym2str(:bacon).should == "bacon" + end + end + + describe "rb_to_symbol" do + it "returns a Symbol for a Symbol" do + @s.rb_to_symbol(:foo).should == :foo + end + + it "returns a Symbol for a String" do + @s.rb_to_symbol("foo").should == :foo + end + + it "coerces to Symbol using to_str" do + o = mock('o') + o.should_receive(:to_str).and_return("foo") + @s.rb_to_symbol(o).should == :foo + end + end +end diff --git a/spec/ruby/optional/capi/thread_spec.rb b/spec/ruby/optional/capi/thread_spec.rb new file mode 100644 index 0000000000..30e29681eb --- /dev/null +++ b/spec/ruby/optional/capi/thread_spec.rb @@ -0,0 +1,179 @@ +require_relative 'spec_helper' +require_relative '../../core/thread/shared/wakeup' + +load_extension("thread") + +class Thread + def self.capi_thread_specs=(t) + @@capi_thread_specs = t + end + + def call_capi_rb_thread_wakeup + @@capi_thread_specs.rb_thread_wakeup(self) + end +end + +describe "C-API Thread function" do + before :each do + @t = CApiThreadSpecs.new + ScratchPad.clear + Thread.capi_thread_specs = @t + end + + describe "rb_thread_wait_for" do + it "sleeps the current thread for the give amount of time" do + start = Time.now + @t.rb_thread_wait_for(0, 100_000) + (Time.now - start).should be_close(0.1, TIME_TOLERANCE) + end + end + + describe "rb_thread_alone" do + it "returns true if there is only one thread" do + pred = Thread.list.size == 1 + @t.rb_thread_alone.should == pred + end + end + + describe "rb_thread_current" do + it "equals Thread.current" do + @t.rb_thread_current.should == Thread.current + end + end + + describe "rb_thread_local_aref" do + it "returns the value of a thread-local variable" do + thr = Thread.current + sym = :thread_capi_specs_aref + thr[sym] = 1 + @t.rb_thread_local_aref(thr, sym).should == 1 + end + + it "returns nil if the value has not been set" do + @t.rb_thread_local_aref(Thread.current, :thread_capi_specs_undefined).should be_nil + end + end + + describe "rb_thread_local_aset" do + it "sets the value of a thread-local variable" do + thr = Thread.current + sym = :thread_capi_specs_aset + @t.rb_thread_local_aset(thr, sym, 2).should == 2 + thr[sym].should == 2 + end + end + + describe "rb_thread_wakeup" do + it_behaves_like :thread_wakeup, :call_capi_rb_thread_wakeup + end + + describe "rb_thread_create" do + it "creates a new thread" do + obj = Object.new + proc = -> x { ScratchPad.record x } + thr = @t.rb_thread_create(proc, obj) + thr.should be_kind_of(Thread) + thr.join + ScratchPad.recorded.should == obj + end + + it "handles throwing an exception in the thread" do + prc = -> x { + Thread.current.report_on_exception = false + raise "my error" + } + thr = @t.rb_thread_create(prc, nil) + thr.should be_kind_of(Thread) + + -> { + thr.join + }.should raise_error(RuntimeError, "my error") + end + + it "sets the thread's group" do + thr = @t.rb_thread_create(-> x { }, nil) + begin + thread_group = thr.group + thread_group.should be_an_instance_of(ThreadGroup) + ensure + thr.join + end + end + end + + describe "rb_thread_call_without_gvl" do + it "runs a C function with the global lock unlocked and can be woken by Thread#wakeup" do + thr = Thread.new do + @t.rb_thread_call_without_gvl + end + + # Wait until it's blocking... + Thread.pass until thr.stop? + + # The thread status is set to sleep by rb_thread_call_without_gvl(), + # but the thread might not be in the blocking read(2) yet, so wait a bit. + sleep 0.1 + + # Wake it up, causing the unblock function to be run. + thr.wakeup + + # Make sure it stopped and we got a proper value + thr.value.should be_true + end + + platform_is_not :windows do + it "runs a C function with the global lock unlocked and can be woken by a signal" do + # Ruby signal handlers run on the main thread, so we need to reverse roles here and have a thread interrupt us + thr = Thread.current + thr.should == Thread.main + + going_to_block = false + interrupter = Thread.new do + # Wait until it's blocking... + Thread.pass until going_to_block and thr.stop? + + # The thread status is set to sleep by rb_thread_call_without_gvl(), + # but the thread might not be in the blocking read(2) yet, so wait a bit. + sleep 0.1 + + # Wake it up by sending a signal + done = false + prev_handler = Signal.trap(:HUP) { done = true } + begin + Process.kill :HUP, Process.pid + sleep 0.001 until done + ensure + Signal.trap(:HUP, prev_handler) + end + end + + going_to_block = true + # Make sure it stopped and we got a proper value + @t.rb_thread_call_without_gvl.should be_true + + interrupter.join + end + end + + platform_is_not :mingw do + it "runs a C function with the global lock unlocked and unlocks IO with the generic RUBY_UBF_IO" do + thr = Thread.new do + @t.rb_thread_call_without_gvl_with_ubf_io + end + + # Wait until it's blocking... + Thread.pass until thr.stop? + + # The thread status is set to sleep by rb_thread_call_without_gvl(), + # but the thread might not be in the blocking read(2) yet, so wait a bit. + sleep 0.1 + + # Wake it up, causing the unblock function to be run. + thr.wakeup + + # Make sure it stopped and we got a proper value + thr.value.should be_true + end + end + end +end diff --git a/spec/ruby/optional/capi/time_spec.rb b/spec/ruby/optional/capi/time_spec.rb new file mode 100644 index 0000000000..579e81fc19 --- /dev/null +++ b/spec/ruby/optional/capi/time_spec.rb @@ -0,0 +1,296 @@ +require_relative 'spec_helper' + +load_extension("time") + +describe "CApiTimeSpecs" do + before :each do + @s = CApiTimeSpecs.new + end + + describe "rb_time_new" do + it "creates a Time from the sec and usec" do + usec = CAPI_SIZEOF_LONG == 8 ? 4611686018427387903 : 1413123123 + @s.rb_time_new(1232141421, usec).should == Time.at(1232141421, usec) + end + end + + describe "TIMET2NUM" do + it "returns an Integer" do + @s.TIMET2NUM.should be_kind_of(Integer) + end + end + + describe "rb_time_nano_new" do + it "creates a Time from the sec and usec" do + time = @s.rb_time_nano_new(1232141421, 1413123123) + time.to_i.should == 1232141422 + time.nsec.should == 413123123 + end + end + + describe "rb_time_num_new" do + it "creates a Time in the local zone with only a timestamp" do + with_timezone("Europe/Amsterdam") do + time = @s.rb_time_num_new(1232141421, nil) + time.should be_an_instance_of(Time) + time.to_i.should == 1232141421 + platform_is_not :windows do + time.gmt_offset.should == 3600 + end + end + end + + it "creates a Time with the given offset" do + with_timezone("Europe/Amsterdam") do + time = @s.rb_time_num_new(1232141421, 7200) + time.should be_an_instance_of(Time) + time.to_i.should == 1232141421 + time.gmt_offset.should == 7200 + end + end + + it "creates a Time with a Float timestamp" do + with_timezone("Europe/Amsterdam") do + time = @s.rb_time_num_new(1.5, 7200) + time.should be_an_instance_of(Time) + time.to_i.should == 1 + time.nsec.should == 500000000 + time.gmt_offset.should == 7200 + end + end + + it "creates a Time with a Rational timestamp" do + with_timezone("Europe/Amsterdam") do + time = @s.rb_time_num_new(Rational(3, 2), 7200) + time.should be_an_instance_of(Time) + time.to_i.should == 1 + time.nsec.should == 500000000 + time.gmt_offset.should == 7200 + end + end + end + + describe "rb_time_interval" do + it "creates a timeval interval for a Fixnum" do + sec, usec = @s.rb_time_interval(1232141421) + sec.should be_kind_of(Integer) + sec.should == 1232141421 + usec.should be_kind_of(Integer) + usec.should == 0 + end + + it "creates a timeval interval for a Float" do + sec, usec = @s.rb_time_interval(1.5) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval interval for a Rational" do + sec, usec = @s.rb_time_interval(Rational(3, 2)) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "throws an argument error for a negative value" do + -> { @s.rb_time_interval(-1232141421) }.should raise_error(ArgumentError) + -> { @s.rb_time_interval(Rational(-3, 2)) }.should raise_error(ArgumentError) + -> { @s.rb_time_interval(-1.5) }.should raise_error(ArgumentError) + end + + end + + describe "rb_time_interval" do + it "creates a timeval interval for a Fixnum" do + sec, usec = @s.rb_time_interval(1232141421) + sec.should be_kind_of(Integer) + sec.should == 1232141421 + usec.should be_kind_of(Integer) + usec.should == 0 + end + + it "creates a timeval interval for a Float" do + sec, usec = @s.rb_time_interval(1.5) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval interval for a Rational" do + sec, usec = @s.rb_time_interval(Rational(3, 2)) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "throws an argument error for a negative value" do + -> { @s.rb_time_interval(-1232141421) }.should raise_error(ArgumentError) + -> { @s.rb_time_interval(Rational(-3, 2)) }.should raise_error(ArgumentError) + -> { @s.rb_time_interval(-1.5) }.should raise_error(ArgumentError) + end + + it "throws an argument error when given a Time instance" do + -> { @s.rb_time_interval(Time.now) }.should raise_error(TypeError) + end + + end + + describe "rb_time_timeval" do + it "creates a timeval for a Fixnum" do + sec, usec = @s.rb_time_timeval(1232141421) + sec.should be_kind_of(Integer) + sec.should == 1232141421 + usec.should be_kind_of(Integer) + usec.should == 0 + end + + it "creates a timeval for a Float" do + sec, usec = @s.rb_time_timeval(1.5) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval for a Rational" do + sec, usec = @s.rb_time_timeval(Rational(3, 2)) + sec.should be_kind_of(Integer) + sec.should == 1 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval for a negative Fixnum" do + sec, usec = @s.rb_time_timeval(-1232141421) + sec.should be_kind_of(Integer) + sec.should == -1232141421 + usec.should be_kind_of(Integer) + usec.should == 0 + end + + it "creates a timeval for a negative Float" do + sec, usec = @s.rb_time_timeval(-1.5) + sec.should be_kind_of(Integer) + sec.should == -2 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval for a negative Rational" do + sec, usec = @s.rb_time_timeval(Rational(-3, 2)) + sec.should be_kind_of(Integer) + sec.should == -2 + usec.should be_kind_of(Integer) + usec.should == 500000 + end + + it "creates a timeval from a Time object" do + t = Time.now + sec, usec = @s.rb_time_timeval(t) + sec.should == t.to_i + usec.should == t.nsec.div(1000) + end + end + + describe "rb_time_timespec" do + it "creates a timespec for a Fixnum" do + sec, nsec = @s.rb_time_timespec(1232141421) + sec.should be_kind_of(Integer) + sec.should == 1232141421 + nsec.should be_kind_of(Integer) + nsec.should == 0 + end + + it "creates a timespec for a Float" do + sec, nsec = @s.rb_time_timespec(1.5) + sec.should be_kind_of(Integer) + sec.should == 1 + nsec.should be_kind_of(Integer) + nsec.should == 500000000 + end + + it "creates a timespec for a Rational" do + sec, nsec = @s.rb_time_timespec(Rational(3, 2)) + sec.should be_kind_of(Integer) + sec.should == 1 + nsec.should be_kind_of(Integer) + nsec.should == 500000000 + end + + it "creates a timespec for a negative Fixnum" do + sec, nsec = @s.rb_time_timespec(-1232141421) + sec.should be_kind_of(Integer) + sec.should == -1232141421 + nsec.should be_kind_of(Integer) + nsec.should == 0 + end + + it "creates a timespec for a negative Float" do + sec, nsec = @s.rb_time_timespec(-1.5) + sec.should be_kind_of(Integer) + sec.should == -2 + nsec.should be_kind_of(Integer) + nsec.should == 500000000 + end + + it "creates a timespec for a negative Rational" do + sec, nsec = @s.rb_time_timespec(Rational(-3, 2)) + sec.should be_kind_of(Integer) + sec.should == -2 + nsec.should be_kind_of(Integer) + nsec.should == 500000000 + end + + it "creates a timespec from a Time object" do + t = Time.now + sec, nsec = @s.rb_time_timespec(t) + sec.should == t.to_i + nsec.should == t.nsec + end + end + + describe "rb_time_timespec_new" do + it "returns a time object with the given timespec and UTC offset" do + @s.rb_time_timespec_new(1447087832, 476451125, 32400).should == Time.at(1447087832, 476451.125).localtime(32400) + end + + describe "when offset given is within range of -86400 and 86400 (exclusive)" do + it "sets time's is_gmt to false" do + @s.rb_time_timespec_new(1447087832, 476451125, 0).gmt?.should be_false + end + + it "sets time's offset to the offset given" do + @s.rb_time_timespec_new(1447087832, 476451125, 86399).gmtoff.should == 86399 + end + end + + it "returns time object in UTC if offset given equals INT_MAX - 1" do + @s.rb_time_timespec_new(1447087832, 476451125, 0x7ffffffe).utc?.should be_true + end + + it "returns time object in localtime if offset given equals INT_MAX" do + @s.rb_time_timespec_new(1447087832, 476451125, 0x7fffffff).should == Time.at(1447087832, 476451.125).localtime + t = Time.now + @s.rb_time_timespec_new(t.tv_sec, t.tv_nsec, 0x7fffffff).gmtoff.should == t.gmtoff + end + + it "raises an ArgumentError if offset passed is not within range of -86400 and 86400 (exclusive)" do + -> { @s.rb_time_timespec_new(1447087832, 476451125, 86400) }.should raise_error(ArgumentError) + -> { @s.rb_time_timespec_new(1447087832, 476451125, -86400) }.should raise_error(ArgumentError) + end + end + + describe "rb_timespec_now" do + it "fills a struct timespec with the current time" do + now = Time.now + time = @s.rb_time_from_timespec(now.utc_offset) + time.should be_an_instance_of(Time) + (time - now).should be_close(0, TIME_TOLERANCE) + end + end +end diff --git a/spec/ruby/optional/capi/tracepoint_spec.rb b/spec/ruby/optional/capi/tracepoint_spec.rb new file mode 100644 index 0000000000..2043b7c941 --- /dev/null +++ b/spec/ruby/optional/capi/tracepoint_spec.rb @@ -0,0 +1,56 @@ +require_relative 'spec_helper' + +load_extension("tracepoint") + +describe "CApiTracePointSpecs" do + before :each do + @s = CApiTracePointSpecs.new + end + + after :each do + @trace.disable if @trace and @trace.enabled? + end + + describe "rb_tracepoint_new" do + it "returns a tracepoint object" do + @trace = @s.rb_tracepoint_new(7) + @trace.should be_an_instance_of(TracePoint) + @trace.should_not.enabled? + end + + it "traces lines when given RUBY_EVENT_LINE" do + @trace = @s.rb_tracepoint_new(8) + @trace.enable + @s.callback_called?.should == 8 + end + end + + describe "rb_tracepoint_disable" do + it "disables an enabled TracePoint" do + @trace = @s.rb_tracepoint_new(9) + @trace.should_not.enabled? + @trace.enable + @trace.should.enabled? + @s.rb_tracepoint_disable(@trace).should == false + @trace.should_not.enabled? + end + end + + describe "rb_tracepoint_enable" do + it "enables a disabled TracePoint" do + @trace = @s.rb_tracepoint_new(10) + @trace.should_not.enabled? + @s.rb_tracepoint_enable(@trace).should == true + @trace.should.enabled? + end + end + + describe "rb_tracepoint_enabled_p" do + it "returns correct enabled status" do + @trace = @s.rb_tracepoint_new(11) + @s.rb_tracepoint_enabled_p(@trace).should == false + @trace.enable + @s.rb_tracepoint_enabled_p(@trace).should == true + end + end +end diff --git a/spec/ruby/optional/capi/typed_data_spec.rb b/spec/ruby/optional/capi/typed_data_spec.rb new file mode 100644 index 0000000000..23b7c157ef --- /dev/null +++ b/spec/ruby/optional/capi/typed_data_spec.rb @@ -0,0 +1,88 @@ +require_relative 'spec_helper' +require 'objspace' + +load_extension("typed_data") + +describe "CApiAllocTypedSpecs (a class with an alloc func defined)" do + it "calls the alloc func" do + @s = CApiAllocTypedSpecs.new + @s.typed_wrapped_data.should == 42 # not defined in initialize + end + + it "uses the specified memsize function for ObjectSpace.memsize" do + @s = CApiAllocTypedSpecs.new + # The defined memsize function for the type should return 42 as + # the size, and this should be added to the size of the object as + # known by Ruby. + ObjectSpace.memsize_of(@s).should > 42 + end +end + +describe "CApiWrappedTypedStruct" do + before :each do + @s = CApiWrappedTypedStructSpecs.new + end + + it "wraps and unwraps data" do + a = @s.typed_wrap_struct(1024) + @s.typed_get_struct(a).should == 1024 + end + + it "throws an exception for a wrong type" do + a = @s.typed_wrap_struct(1024) + -> { @s.typed_get_struct_other(a) }.should raise_error(TypeError) + end + + it "unwraps data for a parent type" do + a = @s.typed_wrap_struct(1024) + @s.typed_get_struct_parent(a).should == 1024 + end + + describe "RTYPEDATA" do + it "returns the struct data" do + a = @s.typed_wrap_struct(1024) + @s.typed_get_struct_rdata(a).should == 1024 + end + + it "can be used to change the wrapped struct" do + a = @s.typed_wrap_struct(1024) + @s.typed_change_struct(a, 100) + @s.typed_get_struct(a).should == 100 + end + end + + describe "DATA_PTR" do + it "returns the struct data" do + a = @s.typed_wrap_struct(1024) + @s.typed_get_struct_data_ptr(a).should == 1024 + end + end + + describe "rb_check_type" do + it "raises an exception when checking typed data objects" do + -> { + a = @s.typed_wrap_struct(1024) + @s.rb_check_type(a, a) + }.should raise_error(TypeError) { |e| + e.message.should == 'wrong argument type Object (expected Data)' + } + end + end + + describe "rb_check_typeddata" do + it "returns data pointer when the struct has the given type" do + a = @s.typed_wrap_struct(1024) + @s.rb_check_typeddata_same_type(a).should == true + end + + it "returns data pointer when the parent struct has the given type" do + a = @s.typed_wrap_struct(1024) + @s.rb_check_typeddata_same_type_parent(a).should == true + end + + it "raises an error for different types" do + a = @s.typed_wrap_struct(1024) + -> { @s.rb_check_typeddata_different_type(a) }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/optional/capi/util_spec.rb b/spec/ruby/optional/capi/util_spec.rb new file mode 100644 index 0000000000..a90c28a78e --- /dev/null +++ b/spec/ruby/optional/capi/util_spec.rb @@ -0,0 +1,326 @@ +require_relative 'spec_helper' + +load_extension('util') + +describe "C-API Util function" do + before :each do + @o = CApiUtilSpecs.new + end + + describe "rb_scan_args" do + before :each do + @prc = -> { 1 } + @acc = [] + @keyword_prefix = 'k' if RUBY_VERSION >= '2.7' + ScratchPad.record @acc + end + + it "assigns the required arguments scanned" do + @o.rb_scan_args([1, 2], "2", 2, @acc).should == 2 + ScratchPad.recorded.should == [1, 2] + end + + it "raises an ArgumentError if there are insufficient arguments" do + -> { @o.rb_scan_args([1, 2], "3", 0, @acc) }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if there are too many arguments" do + -> { @o.rb_scan_args([1, 2, 3, 4], "3", 0, @acc) }.should raise_error(ArgumentError) + end + + it "assigns the required and optional arguments scanned" do + @o.rb_scan_args([1, 2], "11", 2, @acc).should == 2 + ScratchPad.recorded.should == [1, 2] + end + + it "assigns the optional arguments scanned" do + @o.rb_scan_args([1, 2], "02", 2, @acc).should == 2 + ScratchPad.recorded.should == [1, 2] + end + + it "assigns nil for optional arguments that are not present" do + @o.rb_scan_args([1], "03", 3, @acc).should == 1 + ScratchPad.recorded.should == [1, nil, nil] + end + + it "assigns the required and optional arguments and splats the rest" do + @o.rb_scan_args([1, 2, 3, 4], "11*", 3, @acc).should == 4 + ScratchPad.recorded.should == [1, 2, [3, 4]] + end + + it "assigns the required and optional arguments and and empty Array when there are no arguments to splat" do + @o.rb_scan_args([1, 2], "11*", 3, @acc).should == 2 + ScratchPad.recorded.should == [1, 2, []] + end + + it "assigns required, optional arguments scanned and the passed block" do + @o.rb_scan_args([1, 2], "11&", 3, @acc, &@prc).should == 2 + ScratchPad.recorded.should == [1, 2, @prc] + end + + it "assigns required, optional, splatted arguments scanned and the passed block" do + @o.rb_scan_args([1, 2, 3, 4], "11*&", 4, @acc, &@prc).should == 4 + ScratchPad.recorded.should == [1, 2, [3, 4], @prc] + end + + it "assigns required arguments, nil for missing optional arguments and the passed block" do + @o.rb_scan_args([1], "12&", 4, @acc, &@prc).should == 1 + ScratchPad.recorded.should == [1, nil, nil, @prc] + end + + it "assigns required, splatted arguments and the passed block" do + @o.rb_scan_args([1, 2, 3], "1*&", 3, @acc, &@prc).should == 3 + ScratchPad.recorded.should == [1, [2, 3], @prc] + end + + it "assigns post-splat arguments" do + @o.rb_scan_args([1, 2, 3], "00*1", 2, @acc).should == 3 + ScratchPad.recorded.should == [[1, 2], 3] + end + + it "assigns required, optional, splat and post-splat arguments" do + @o.rb_scan_args([1, 2, 3, 4, 5], "11*1", 4, @acc).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], 5] + end + + it "assigns required, splat, post-splat arguments" do + @o.rb_scan_args([1, 2, 3, 4], "10*1", 3, @acc).should == 4 + ScratchPad.recorded.should == [1, [2, 3], 4] + end + + it "assigns optional, splat, post-splat arguments" do + @o.rb_scan_args([1, 2, 3, 4], "01*1", 3, @acc).should == 4 + ScratchPad.recorded.should == [1, [2, 3], 4] + end + + it "assigns required, optional, splat, post-splat and block arguments" do + @o.rb_scan_args([1, 2, 3, 4, 5], "11*1&", 5, @acc, &@prc).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, @prc] + end + + it "assigns Hash arguments" do + h = {a: 1, b: 2} + @o.rb_scan_args([h], "#{@keyword_prefix}0:", 1, @acc).should == 0 + ScratchPad.recorded.should == [h] + end + + it "assigns required and Hash arguments" do + h = {a: 1, b: 2} + @o.rb_scan_args([1, h], "#{@keyword_prefix}1:", 2, @acc).should == 1 + ScratchPad.recorded.should == [1, h] + end + + it "assigns required and Hash arguments with optional Hash" do + @o.rb_scan_args([1], "1:", 2, @acc).should == 1 + ScratchPad.recorded.should == [1, nil] + end + + ruby_version_is ''...'3.0' do + it "assigns required and Hash arguments with nil Hash" do + suppress_warning do + @o.rb_scan_args([1, nil], "1:", 2, @acc).should == 1 + end + ScratchPad.recorded.should == [1, nil] + end + end + + ruby_version_is '3.0' do + it "rejects the use of nil as a hash" do + -> { + @o.rb_scan_args([1, nil], "1:", 2, @acc).should == 1 + }.should raise_error(ArgumentError) + ScratchPad.recorded.should == [] + end + end + + it "assigns required and optional arguments with no hash argument given" do + @o.rb_scan_args([1, 7, 4], "21:", 3, @acc).should == 3 + ScratchPad.recorded.should == [1, 7, 4] + end + + it "assigns required, optional, splat, post-splat, Hash and block arguments" do + h = {a: 1, b: 2} + @o.rb_scan_args([1, 2, 3, 4, 5, h], "#{@keyword_prefix}11*1:&", 6, @acc, &@prc).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, h, @prc] + end + + ruby_version_is ''...'3.0' do + # r43934 + it "rejects non-keyword arguments" do + h = {1 => 2, 3 => 4} + -> { + suppress_warning do + @o.rb_scan_args([h], "#{@keyword_prefix}0:", 1, @acc) + end + }.should raise_error(ArgumentError) + ScratchPad.recorded.should == [] + end + + it "rejects required and non-keyword arguments" do + h = {1 => 2, 3 => 4} + -> { + suppress_warning do + @o.rb_scan_args([1, h], "#{@keyword_prefix}1:", 2, @acc) + end + }.should raise_error(ArgumentError) + ScratchPad.recorded.should == [] + end + + it "considers the hash as a post argument when there is a splat" do + h = {1 => 2, 3 => 4} + suppress_warning do + @o.rb_scan_args([1, 2, 3, 4, 5, h], "#{@keyword_prefix}11*1:&", 6, @acc, &@prc).should == 6 + end + ScratchPad.recorded.should == [1, 2, [3, 4, 5], h, nil, @prc] + end + end + + ruby_version_is '3.0' do + it "does not reject non-symbol keys in keyword arguments" do + h = {1 => 2, 3 => 4} + @o.rb_scan_args([h], "#{@keyword_prefix}0:", 1, @acc).should == 0 + ScratchPad.recorded.should == [h] + end + + it "does not reject non-symbol keys in keyword arguments with required argument" do + h = {1 => 2, 3 => 4} + @o.rb_scan_args([1, h], "#{@keyword_prefix}1:", 2, @acc).should == 1 + ScratchPad.recorded.should == [1, h] + end + + it "considers keyword arguments with non-symbol keys as keywords when using splat and post arguments" do + h = {1 => 2, 3 => 4} + @o.rb_scan_args([1, 2, 3, 4, 5, h], "#{@keyword_prefix}11*1:&", 6, @acc, &@prc).should == 5 + ScratchPad.recorded.should == [1, 2, [3, 4], 5, h, @prc] + end + end + end + + describe "rb_get_kwargs" do + it "extracts required arguments in the order requested" do + h = { :a => 7, :b => 5 } + @o.rb_get_kwargs(h, [:b, :a], 2, 0).should == [5, 7] + h.should == {} + end + + it "extracts required and optional arguments in the order requested" do + h = { :a => 7, :c => 12, :b => 5 } + @o.rb_get_kwargs(h, [:b, :a, :c], 2, 1).should == [5, 7, 12] + h.should == {} + end + + it "accepts nil instead of a hash when only optional arguments are requested" do + h = nil + @o.rb_get_kwargs(h, [:b, :a, :c], 0, 3).should == [] + h.should == nil + end + + it "raises an error if a required argument is not in the hash" do + h = { :a => 7, :c => 12, :b => 5 } + -> { @o.rb_get_kwargs(h, [:b, :d], 2, 0) }.should raise_error(ArgumentError, /missing keyword: :?d/) + h.should == {:a => 7, :c => 12} + end + + it "does not raise an error for an optional argument not in the hash" do + h = { :a => 7, :b => 5 } + @o.rb_get_kwargs(h, [:b, :a, :c], 2, 1).should == [5, 7] + h.should == {} + end + + it "raises an error if there are additional arguments and optional is positive" do + h = { :a => 7, :c => 12, :b => 5 } + -> { @o.rb_get_kwargs(h, [:b, :a], 2, 0) }.should raise_error(ArgumentError, /unknown keyword: :?c/) + h.should == {:c => 12} + end + + it "leaves additional arguments in the hash if optional is negative" do + h = { :a => 7, :c => 12, :b => 5 } + @o.rb_get_kwargs(h, [:b, :a], 2, -1).should == [5, 7] + h.should == {:c => 12} + end + end + + platform_is wordsize: 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) + end + end + + it "returns the C int value" do + @o.rb_long2int(1234).should == 1234 + end + end + + # #7896 + describe "rb_iter_break" do + before :each do + ScratchPad.record [] + end + + it "breaks a loop" do + 3.times do |i| + if i == 2 + @o.rb_iter_break + end + ScratchPad << i + end + ScratchPad.recorded.should == [0, 1] + end + + it "breaks the inner loop" do + 3.times do |i| + 3.times do |j| + if i == 1 + @o.rb_iter_break + end + ScratchPad << [i, j] + end + end + ScratchPad.recorded.should == [[0, 0], [0, 1], [0, 2], [2, 0], [2, 1], [2, 2]] + end + end + + describe "rb_sourcefile" do + it "returns the current ruby file" do + @o.rb_sourcefile.should == __FILE__ + end + end + + describe "rb_sourceline" do + it "returns the current ruby file" do + @o.rb_sourceline.should be_kind_of(Integer) + end + end + + # ruby/util.h redefines strtod as a macro calling ruby_strtod + + describe "strtod" do + it "converts a string to a double and returns the remaining string" do + d, s = @o.strtod("14.25test") + d.should == 14.25 + s.should == "test" + end + + it "returns 0 and the full string if there's no numerical value" do + d, s = @o.strtod("test") + d.should == 0 + s.should == "test" + end + end + + describe "ruby_strtod" do + it "converts a string to a double and returns the remaining string" do + d, s = @o.ruby_strtod("14.25test") + d.should == 14.25 + s.should == "test" + end + + it "returns 0 and the full string if there's no numerical value" do + d, s = @o.ruby_strtod("test") + d.should == 0 + s.should == "test" + end + end + +end |
