diff --git a/spec/ruby/optional/capi/ext/mri.h b/spec/ruby/optional/capi/ext/mri.h index 7697403fba..b529d0d951 100644 --- a/spec/ruby/optional/capi/ext/mri.h +++ b/spec/ruby/optional/capi/ext/mri.h @@ -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 */ diff --git a/spec/ruby/optional/capi/ext/proc_spec.c b/spec/ruby/optional/capi/ext/proc_spec.c index 41f65f5766..6c59a90d8c 100644 --- a/spec/ruby/optional/capi/ext/proc_spec.c +++ b/spec/ruby/optional/capi/ext/proc_spec.c @@ -8,34 +8,45 @@ 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() { @@ -43,7 +54,7 @@ void Init_proc_spec() { 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); 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/hash_spec.rb b/spec/ruby/optional/capi/hash_spec.rb index 610da0a407..6ba2279f40 100644 --- a/spec/ruby/optional/capi/hash_spec.rb +++ b/spec/ruby/optional/capi/hash_spec.rb @@ -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 diff --git a/spec/ruby/optional/capi/proc_spec.rb b/spec/ruby/optional/capi/proc_spec.rb index c107050170..57ef76fb21 100644 --- a/spec/ruby/optional/capi/proc_spec.rb +++ b/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