Skip to content

Commit

Permalink
More specs for Proc.new called from C code.
Browse files Browse the repository at this point in the history
  • Loading branch information
brixen committed Dec 20, 2010
1 parent 922620f commit a9acae0
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 39 deletions.
1 change: 0 additions & 1 deletion spec/ruby/optional/capi/ext/mri.h
Expand Up @@ -24,7 +24,6 @@
#undef HAVE_STR2CSTR
#undef HAVE_RB_STR2CSTR
#undef HAVE_RB_SET_KCODE
#undef HAVE_RB_PROC_NEW
#endif

/* RubySpec assumes following are public API */
Expand Down
47 changes: 29 additions & 18 deletions spec/ruby/optional/capi/ext/proc_spec.c
Expand Up @@ -8,42 +8,53 @@ extern "C" {
#endif

#ifdef HAVE_RB_PROC_NEW
VALUE proc_spec_concat_func(VALUE args) {
int i;
char buffer[500] = {0};
for(i = 0; i < RARRAY_LEN(args); ++i) {
VALUE v = RARRAY_PTR(args)[i];
strcat(buffer, StringValuePtr(v));
strcat(buffer, "_");
}
buffer[strlen(buffer) - 1] = 0;
return rb_str_new2(buffer);

VALUE proc_spec_rb_proc_new_function(VALUE args) {
return rb_funcall(args, rb_intern("inspect"), 0);
}

VALUE proc_spec_underline_concat_proc(VALUE self) {
return rb_proc_new(proc_spec_concat_func, Qnil);
VALUE proc_spec_rb_proc_new(VALUE self) {
return rb_proc_new(proc_spec_rb_proc_new_function, Qnil);
}
#endif

/* 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 do_call) {
if(RTEST(do_call)) {
rb_funcall(self, rb_intern("nil?"), 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 proc_spec_rb_Proc_new_helper();
return Qnil;
}

void Init_proc_spec() {
VALUE cls;
cls = rb_define_class("CApiProcSpecs", rb_cObject);

#ifdef HAVE_RB_PROC_NEW
rb_define_method(cls, "underline_concat_proc", proc_spec_underline_concat_proc, 0);
rb_define_method(cls, "rb_proc_new", proc_spec_rb_proc_new, 0);
#endif

rb_define_method(cls, "rb_Proc_new", proc_spec_rb_Proc_new, 1);
Expand Down
20 changes: 20 additions & 0 deletions 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
4 changes: 4 additions & 0 deletions spec/ruby/optional/capi/hash_spec.rb
Expand Up @@ -45,6 +45,10 @@
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_hash_aref" do
Expand Down
113 changes: 93 additions & 20 deletions spec/ruby/optional/capi/proc_spec.rb
@@ -1,39 +1,112 @@
require File.expand_path('../spec_helper', __FILE__)
require File.expand_path('../fixtures/proc', __FILE__)

load_extension("proc")

describe "CApiProc" do
describe "C-API Proc function" do
before :each do
@p = CApiProcSpecs.new
@prc = @p.rb_proc_new
end

it "rb_proc_new should return a new valid Proc" do
my_proc = @p.underline_concat_proc()
my_proc.kind_of?(Proc).should == true
my_proc.call('foo', 'bar').should == 'foo_bar'
my_proc['foo', 'bar'].should == 'foo_bar'
end
describe "rb_proc_new" do
it "returns a new valid Proc" do
@prc.kind_of?(Proc).should == true
end

it "rb_proc_new returned proc should have arity -1" do
my_proc = @p.underline_concat_proc()
my_proc.arity.should == -1
ruby_version_is "" ... "1.8.7" do
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
end

ruby_version_is "1.8.7" ... "1.9" do
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
end

ruby_version_is "1.9" do
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
end

it "returns a Proc instance with #aricy == -1" do
@prc.arity.should == -1
end
end
end

describe "C-API" do
describe "C-API when calling Proc.new from a C function" do
before :each do
@p = CApiProcSpecs.new
end

describe "when calling Proc.new from the C-API" do
it "returns the proc passed to the Ruby method calling into C" do
prc = @p.rb_Proc_new(false) { :called }
prc.call.should == :called
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 exection 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.

it "returns the proc passed to the Ruby method when multiple rb_funcall's are issued" do
prc = @p.rb_Proc_new(true) { :called }
prc.call.should == :called
end
# 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

# Ruby -> C -> Ruby -> Proc.new
it "raises an ArgumentError when the C function calls a Ruby method that calls Proc.new" do
def @p.Proc_new() Proc.new end
lambda { @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
lambda { @p.rb_Proc_new(3) { :called } }.should raise_error(ArgumentError)
end

# 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

# 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

0 comments on commit a9acae0

Please sign in to comment.