diff options
Diffstat (limited to 'spec/ruby/language/return_spec.rb')
| -rw-r--r-- | spec/ruby/language/return_spec.rb | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/spec/ruby/language/return_spec.rb b/spec/ruby/language/return_spec.rb new file mode 100644 index 0000000000..a62ed1242d --- /dev/null +++ b/spec/ruby/language/return_spec.rb @@ -0,0 +1,490 @@ +require_relative '../spec_helper' +require_relative 'fixtures/return' + +describe "The return keyword" do + it "returns any object directly" do + def r; return 1; end + r().should == 1 + end + + it "returns an single element array directly" do + def r; return [1]; end + r().should == [1] + end + + it "returns an multi element array directly" do + def r; return [1,2]; end + r().should == [1,2] + end + + it "returns nil by default" do + def r; return; end + r().should be_nil + end + + describe "in a Thread" do + it "raises a LocalJumpError if used to exit a thread" do + t = Thread.new { + begin + return + rescue LocalJumpError => e + e + end + } + t.value.should be_an_instance_of(LocalJumpError) + end + end + + describe "when passed a splat" do + it "returns [] when the ary is empty" do + def r; ary = []; return *ary; end + r.should == [] + end + + it "returns the array when the array is size of 1" do + def r; ary = [1]; return *ary; end + r.should == [1] + end + + it "returns the whole array when size is greater than 1" do + def r; ary = [1,2]; return *ary; end + r.should == [1,2] + + def r; ary = [1,2,3]; return *ary; end + r.should == [1,2,3] + end + + it "returns an array when used as a splat" do + def r; value = 1; return *value; end + r.should == [1] + end + + it "calls 'to_a' on the splatted value first" do + def r + obj = Object.new + def obj.to_a + [1,2] + end + + return *obj + end + + r().should == [1,2] + end + end + + describe "within a begin" do + before :each do + ScratchPad.record [] + end + + it "executes ensure before returning" do + def f() + begin + ScratchPad << :begin + return :begin + ScratchPad << :after_begin + ensure + ScratchPad << :ensure + end + ScratchPad << :function + end + f().should == :begin + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "returns last value returned in ensure" do + def f() + begin + ScratchPad << :begin + return :begin + ScratchPad << :after_begin + ensure + ScratchPad << :ensure + return :ensure + ScratchPad << :after_ensure + end + ScratchPad << :function + end + f().should == :ensure + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "executes nested ensures before returning" do + def f() + begin + begin + ScratchPad << :inner_begin + return :inner_begin + ScratchPad << :after_inner_begin + ensure + ScratchPad << :inner_ensure + end + ScratchPad << :outer_begin + return :outer_begin + ScratchPad << :after_outer_begin + ensure + ScratchPad << :outer_ensure + end + ScratchPad << :function + end + f().should == :inner_begin + ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure] + end + + it "returns last value returned in nested ensures" do + def f() + begin + begin + ScratchPad << :inner_begin + return :inner_begin + ScratchPad << :after_inner_begin + ensure + ScratchPad << :inner_ensure + return :inner_ensure + ScratchPad << :after_inner_ensure + end + ScratchPad << :outer_begin + return :outer_begin + ScratchPad << :after_outer_begin + ensure + ScratchPad << :outer_ensure + return :outer_ensure + ScratchPad << :after_outer_ensure + end + ScratchPad << :function + end + f().should == :outer_ensure + ScratchPad.recorded.should == [:inner_begin, :inner_ensure, :outer_ensure] + end + + it "executes the ensure clause when begin/ensure are inside a lambda" do + -> do + begin + return + ensure + ScratchPad.recorded << :ensure + end + end.call + ScratchPad.recorded.should == [:ensure] + end + end + + describe "within a block" do + before :each do + ScratchPad.clear + end + + it "causes lambda to return nil if invoked without any arguments" do + -> { return; 456 }.call.should be_nil + end + + it "causes lambda to return nil if invoked with an empty expression" do + -> { return (); 456 }.call.should be_nil + end + + it "causes lambda to return the value passed to return" do + -> { return 123; 456 }.call.should == 123 + end + + it "causes the method that lexically encloses the block to return" do + ReturnSpecs::Blocks.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "returns from the lexically enclosing method even in case of chained calls" do + ReturnSpecs::NestedCalls.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "returns from the lexically enclosing method even in case of chained calls(in yield)" do + ReturnSpecs::NestedBlocks.new.enclosing_method.should == :return_value + ScratchPad.recorded.should == :before_return + end + + it "causes the method to return even when the immediate parent has already returned" do + ReturnSpecs::SavedInnerBlock.new.start.should == :return_value + ScratchPad.recorded.should == :before_return + end + + # jruby/jruby#3143 + describe "downstream from a lambda" do + it "returns to its own return-capturing lexical enclosure" do + def a + ->{ yield }.call + return 2 + end + def b + a { return 1 } + end + + b.should == 1 + end + end + + end + + describe "within two blocks" do + it "causes the method that lexically encloses the block to return" do + def f + 1.times { 1.times {return true}; false}; false + end + f.should be_true + end + end + + describe "within define_method" do + it "goes through the method via a closure" do + ReturnSpecs::ThroughDefineMethod.new.outer.should == :good + end + + it "stops at the method when the return is used directly" do + ReturnSpecs::DefineMethod.new.outer.should == :good + end + end + + describe "invoked with a method call without parentheses with a block" do + it "returns the value returned from the method call" do + ReturnSpecs::MethodWithBlock.new.method1.should == 5 + ReturnSpecs::MethodWithBlock.new.method2.should == [0, 1, 2] + end + end + + describe "at top level" do + before :each do + @filename = tmp("top_return.rb") + ScratchPad.record [] + end + + after do + rm_r @filename + end + + it "stops file execution" do + ruby_exe(<<-END_OF_CODE).should == "before return\n" + puts "before return" + return + + puts "after return" + END_OF_CODE + + $?.exitstatus.should == 0 + end + + describe "within if" do + it "is allowed" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before if" + if true + return + end + + ScratchPad << "after if" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before if"] + end + end + + describe "within while loop" do + it "is allowed" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before while" + while true + return + end + + ScratchPad << "after while" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before while"] + end + end + + describe "within a begin" do + it "is allowed in begin block" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before begin" + begin + return + end + + ScratchPad << "after begin" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before begin"] + end + + it "is allowed in ensure block" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before begin" + begin + ensure + return + end + + ScratchPad << "after begin" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before begin"] + end + + it "is allowed in rescue block" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before begin" + begin + raise + rescue RuntimeError + return + end + + ScratchPad << "after begin" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before begin"] + end + + it "fires ensure block before returning" do + ruby_exe(<<-END_OF_CODE).should == "within ensure\n" + begin + return + ensure + puts "within ensure" + end + + puts "after begin" + END_OF_CODE + end + + it "fires ensure block before returning while loads file" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before begin" + begin + return + ensure + ScratchPad << "within ensure" + end + + ScratchPad << "after begin" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before begin", "within ensure"] + end + + it "swallows exception if returns in ensure block" do + File.write(@filename, <<-END_OF_CODE) + begin + raise + ensure + ScratchPad << "before return" + return + end + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before return"] + end + end + + describe "within a block" do + it "is allowed" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before call" + proc { return }.call + + ScratchPad << "after call" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before call"] + end + end + + describe "within a class" do + it "raises a SyntaxError" do + File.write(@filename, <<-END_OF_CODE) + class ReturnSpecs::A + ScratchPad << "before return" + return + + ScratchPad << "after return" + end + END_OF_CODE + + -> { load @filename }.should raise_error(SyntaxError) + end + end + + describe "within a block within a class" do + it "is not allowed" do + File.write(@filename, <<-END_OF_CODE) + class ReturnSpecs::A + ScratchPad << "before return" + 1.times { return } + ScratchPad << "after return" + end + END_OF_CODE + + -> { load @filename }.should raise_error(LocalJumpError) + end + end + + describe "within BEGIN" do + it "is allowed" do + File.write(@filename, <<-END_OF_CODE) + BEGIN { + ScratchPad << "before call" + return + ScratchPad << "after call" + } + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before call"] + end + end + + describe "file loading" do + it "stops file loading and execution" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before return" + return + ScratchPad << "after return" + END_OF_CODE + + load @filename + ScratchPad.recorded.should == ["before return"] + end + end + + describe "file requiring" do + it "stops file loading and execution" do + File.write(@filename, <<-END_OF_CODE) + ScratchPad << "before return" + return + ScratchPad << "after return" + END_OF_CODE + + require @filename + ScratchPad.recorded.should == ["before return"] + end + end + + describe "return with argument" do + it "warns but does not affect exit status" do + err = ruby_exe(<<-END_OF_CODE, args: "2>&1") + return 10 + END_OF_CODE + $?.exitstatus.should == 0 + + err.should =~ /warning: argument of top-level return is ignored/ + end + end + end +end |
