2018-03-04 10:09:32 -05:00
|
|
|
require_relative '../spec_helper'
|
|
|
|
require_relative 'fixtures/break'
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
describe "The break statement in a block" do
|
|
|
|
before :each do
|
|
|
|
ScratchPad.record []
|
|
|
|
@program = BreakSpecs::Block.new
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns nil to method invoking the method yielding to the block when not passed an argument" do
|
|
|
|
@program.break_nil
|
|
|
|
ScratchPad.recorded.should == [:a, :aa, :b, nil, :d]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value to the method invoking the method yielding to the block" do
|
|
|
|
@program.break_value
|
|
|
|
ScratchPad.recorded.should == [:a, :aa, :b, :break, :d]
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "yielded inside a while" do
|
|
|
|
it "breaks out of the block" do
|
|
|
|
value = @program.break_in_block_in_while
|
|
|
|
ScratchPad.recorded.should == [:aa, :break]
|
|
|
|
value.should == :value
|
|
|
|
end
|
|
|
|
end
|
2017-12-01 10:41:50 -05:00
|
|
|
|
|
|
|
describe "captured and delegated to another method repeatedly" do
|
|
|
|
it "breaks out of the block" do
|
|
|
|
@program.looped_break_in_captured_block
|
|
|
|
ScratchPad.recorded.should == [:begin,
|
|
|
|
:preloop,
|
|
|
|
:predele,
|
|
|
|
:preyield,
|
|
|
|
:prebreak,
|
|
|
|
:postbreak,
|
|
|
|
:postyield,
|
|
|
|
:postdele,
|
|
|
|
:predele,
|
|
|
|
:preyield,
|
|
|
|
:prebreak,
|
|
|
|
:end]
|
|
|
|
end
|
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe "The break statement in a captured block" do
|
|
|
|
before :each do
|
|
|
|
ScratchPad.record []
|
|
|
|
@program = BreakSpecs::Block.new
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "when the invocation of the scope creating the block is still active" do
|
|
|
|
it "raises a LocalJumpError when invoking the block from the scope creating the block" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { @program.break_in_method }.should raise_error(LocalJumpError)
|
2017-05-07 08:04:49 -04:00
|
|
|
ScratchPad.recorded.should == [:a, :xa, :d, :b]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a LocalJumpError when invoking the block from a method" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
|
2017-07-27 08:10:41 -04:00
|
|
|
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a LocalJumpError when yielding to the block" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
|
2017-07-27 08:10:41 -04:00
|
|
|
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "from a scope that has returned" do
|
|
|
|
it "raises a LocalJumpError when calling the block from a method" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
|
2017-05-07 08:04:49 -04:00
|
|
|
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a LocalJumpError when yielding to the block" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
|
2017-05-07 08:04:49 -04:00
|
|
|
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
|
|
|
|
end
|
|
|
|
end
|
2017-07-27 08:10:41 -04:00
|
|
|
|
|
|
|
describe "from another thread" do
|
|
|
|
it "raises a LocalJumpError when getting the value from another thread" do
|
|
|
|
thread_with_break = Thread.new do
|
2017-10-28 11:15:48 -04:00
|
|
|
begin
|
|
|
|
break :break
|
|
|
|
rescue LocalJumpError => e
|
|
|
|
e
|
|
|
|
end
|
2017-07-27 08:10:41 -04:00
|
|
|
end
|
2017-10-28 11:15:48 -04:00
|
|
|
thread_with_break.value.should be_an_instance_of(LocalJumpError)
|
2017-07-27 08:10:41 -04:00
|
|
|
end
|
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe "The break statement in a lambda" do
|
|
|
|
before :each do
|
|
|
|
ScratchPad.record []
|
|
|
|
@program = BreakSpecs::Lambda.new
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns from the lambda" do
|
2019-07-27 06:40:09 -04:00
|
|
|
l = -> {
|
2017-05-07 08:04:49 -04:00
|
|
|
ScratchPad << :before
|
|
|
|
break :foo
|
|
|
|
ScratchPad << :after
|
|
|
|
}
|
|
|
|
l.call.should == :foo
|
|
|
|
ScratchPad.recorded.should == [:before]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns from the call site if the lambda is passed as a block" do
|
|
|
|
def mid(&b)
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
ScratchPad << :before
|
|
|
|
b.call
|
|
|
|
ScratchPad << :unreachable1
|
|
|
|
}.call
|
|
|
|
ScratchPad << :unreachable2
|
|
|
|
end
|
|
|
|
|
|
|
|
result = [1].each do |e|
|
|
|
|
mid {
|
|
|
|
break # This breaks from mid
|
|
|
|
ScratchPad << :unreachable3
|
|
|
|
}
|
|
|
|
ScratchPad << :after
|
|
|
|
end
|
|
|
|
result.should == [1]
|
|
|
|
ScratchPad.recorded.should == [:before, :after]
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "when the invocation of the scope creating the lambda is still active" do
|
|
|
|
it "returns nil when not passed an argument" do
|
|
|
|
@program.break_in_defining_scope false
|
|
|
|
ScratchPad.recorded.should == [:a, :b, nil, :d]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value to the scope creating and calling the lambda" do
|
|
|
|
@program.break_in_defining_scope
|
|
|
|
ScratchPad.recorded.should == [:a, :b, :break, :d]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value to the method scope below invoking the lambda" do
|
|
|
|
@program.break_in_nested_scope
|
|
|
|
ScratchPad.recorded.should == [:a, :d, :aa, :b, :break, :bb, :e]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value to a block scope invoking the lambda in a method below" do
|
|
|
|
@program.break_in_nested_scope_block
|
|
|
|
ScratchPad.recorded.should == [:a, :d, :aa, :aaa, :bb, :b, :break, :cc, :bbb, :dd, :e]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns from the lambda" do
|
|
|
|
@program.break_in_nested_scope_yield
|
|
|
|
ScratchPad.recorded.should == [:a, :d, :aaa, :b, :bbb, :e]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "created at the toplevel" do
|
|
|
|
it "returns a value when invoking from the toplevel" do
|
|
|
|
code = fixture __FILE__, "break_lambda_toplevel.rb"
|
|
|
|
ruby_exe(code).chomp.should == "a,b,break,d"
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value when invoking from a method" do
|
|
|
|
code = fixture __FILE__, "break_lambda_toplevel_method.rb"
|
|
|
|
ruby_exe(code).chomp.should == "a,d,b,break,e,f"
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value when invoking from a block" do
|
|
|
|
code = fixture __FILE__, "break_lambda_toplevel_block.rb"
|
|
|
|
ruby_exe(code).chomp.should == "a,d,f,b,break,g,e,h"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "from a scope that has returned" do
|
|
|
|
it "returns a value to the method scope invoking the lambda" do
|
|
|
|
@program.break_in_method
|
|
|
|
ScratchPad.recorded.should == [:a, :la, :ld, :lb, :break, :b]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a value to the block scope invoking the lambda in a method" do
|
|
|
|
@program.break_in_block_in_method
|
|
|
|
ScratchPad.recorded.should == [:a, :aaa, :b, :la, :ld, :lb, :break, :c, :bbb, :d]
|
|
|
|
end
|
|
|
|
|
|
|
|
# By passing a lambda as a block argument, the user is requesting to treat
|
|
|
|
# the lambda as a block, which in this case means breaking to a scope that
|
|
|
|
# has returned. This is a subtle and confusing semantic where a block pass
|
|
|
|
# is removing the lambda-ness of a lambda.
|
|
|
|
it "raises a LocalJumpError when yielding to a lambda passed as a block argument" do
|
|
|
|
@program.break_in_method_yield
|
|
|
|
ScratchPad.recorded.should == [:a, :la, :ld, :aaa, :lb, :bbb, :b]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Break inside a while loop" do
|
|
|
|
describe "with a value" do
|
|
|
|
it "exits the loop and returns the value" do
|
|
|
|
a = while true; break; end; a.should == nil
|
|
|
|
a = while true; break nil; end; a.should == nil
|
|
|
|
a = while true; break 1; end; a.should == 1
|
|
|
|
a = while true; break []; end; a.should == []
|
|
|
|
a = while true; break [1]; end; a.should == [1]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "passes the value returned by a method with omitted parenthesis and passed block" do
|
|
|
|
obj = BreakSpecs::Block.new
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { break obj.method :value do |x| x end }.call.should == :value
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with a splat" do
|
|
|
|
it "exits the loop and makes the splat an Array" do
|
|
|
|
a = while true; break *[1,2]; end; a.should == [1,2]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "treats nil as an empty array" do
|
|
|
|
a = while true; break *nil; end; a.should == []
|
|
|
|
end
|
|
|
|
|
|
|
|
it "preserves an array as is" do
|
|
|
|
a = while true; break *[]; end; a.should == []
|
|
|
|
a = while true; break *[1,2]; end; a.should == [1,2]
|
|
|
|
a = while true; break *[nil]; end; a.should == [nil]
|
|
|
|
a = while true; break *[[]]; end; a.should == [[]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "wraps a non-Array in an Array" do
|
|
|
|
a = while true; break *1; end; a.should == [1]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "stops a while loop when run" do
|
|
|
|
i = 0
|
|
|
|
while true
|
|
|
|
break if i == 2
|
|
|
|
i+=1
|
|
|
|
end
|
|
|
|
i.should == 2
|
|
|
|
end
|
|
|
|
|
|
|
|
it "causes a call with a block to return when run" do
|
|
|
|
at = 0
|
|
|
|
0.upto(5) do |i|
|
|
|
|
at = i
|
|
|
|
break i if i == 2
|
|
|
|
end.should == 2
|
|
|
|
at.should == 2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: Rewrite all the specs from here to the end of the file in the style
|
|
|
|
# above.
|
|
|
|
describe "Executing break from within a block" do
|
|
|
|
|
|
|
|
before :each do
|
|
|
|
ScratchPad.clear
|
|
|
|
end
|
|
|
|
|
|
|
|
# Discovered in JRuby (see JRUBY-2756)
|
|
|
|
it "returns from the original invoking method even in case of chained calls" do
|
|
|
|
class BreakTest
|
|
|
|
# case #1: yield
|
|
|
|
def self.meth_with_yield(&b)
|
|
|
|
yield
|
|
|
|
fail("break returned from yield to wrong place")
|
|
|
|
end
|
|
|
|
def self.invoking_method(&b)
|
|
|
|
meth_with_yield(&b)
|
|
|
|
fail("break returned from 'meth_with_yield' method to wrong place")
|
|
|
|
end
|
|
|
|
|
|
|
|
# case #2: block.call
|
|
|
|
def self.meth_with_block_call(&b)
|
|
|
|
b.call
|
|
|
|
fail("break returned from b.call to wrong place")
|
|
|
|
end
|
|
|
|
def self.invoking_method2(&b)
|
|
|
|
meth_with_block_call(&b)
|
|
|
|
fail("break returned from 'meth_with_block_call' method to wrong place")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# this calls a method that calls another method that yields to the block
|
|
|
|
BreakTest.invoking_method do
|
|
|
|
break
|
|
|
|
fail("break didn't, well, break")
|
|
|
|
end
|
|
|
|
|
|
|
|
# this calls a method that calls another method that calls the block
|
|
|
|
BreakTest.invoking_method2 do
|
|
|
|
break
|
|
|
|
fail("break didn't, well, break")
|
|
|
|
end
|
|
|
|
|
|
|
|
res = BreakTest.invoking_method do
|
|
|
|
break :return_value
|
|
|
|
fail("break didn't, well, break")
|
|
|
|
end
|
|
|
|
res.should == :return_value
|
|
|
|
|
|
|
|
res = BreakTest.invoking_method2 do
|
|
|
|
break :return_value
|
|
|
|
fail("break didn't, well, break")
|
|
|
|
end
|
|
|
|
res.should == :return_value
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
class BreakTest2
|
|
|
|
def one
|
|
|
|
two { yield }
|
|
|
|
end
|
|
|
|
|
|
|
|
def two
|
|
|
|
yield
|
|
|
|
ensure
|
|
|
|
ScratchPad << :two_ensure
|
|
|
|
end
|
|
|
|
|
|
|
|
def three
|
|
|
|
begin
|
|
|
|
one { break }
|
|
|
|
ScratchPad << :three_post
|
|
|
|
ensure
|
|
|
|
ScratchPad << :three_ensure
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "runs ensures when continuing upward" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
bt2 = BreakTest2.new
|
|
|
|
bt2.one { break }
|
|
|
|
ScratchPad.recorded.should == [:two_ensure]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "runs ensures when breaking from a loop" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
while true
|
|
|
|
begin
|
|
|
|
ScratchPad << :begin
|
|
|
|
break if true
|
|
|
|
ensure
|
|
|
|
ScratchPad << :ensure
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ScratchPad.recorded.should == [:begin, :ensure]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't run ensures in the destination method" do
|
|
|
|
ScratchPad.record []
|
|
|
|
|
|
|
|
bt2 = BreakTest2.new
|
|
|
|
bt2.three
|
|
|
|
ScratchPad.recorded.should == [:two_ensure, :three_post, :three_ensure]
|
|
|
|
end
|
2020-07-27 15:41:08 -04:00
|
|
|
|
|
|
|
it "works when passing through a super call" do
|
|
|
|
cls1 = Class.new { def foo; yield; end }
|
|
|
|
cls2 = Class.new(cls1) { def foo; super { break 1 }; end }
|
|
|
|
|
|
|
|
-> do
|
|
|
|
cls2.new.foo.should == 1
|
|
|
|
end.should_not raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises LocalJumpError when converted into a proc during a a super call" do
|
|
|
|
cls1 = Class.new { def foo(&b); b; end }
|
|
|
|
cls2 = Class.new(cls1) { def foo; super { break 1 }.call; end }
|
|
|
|
|
|
|
|
-> do
|
|
|
|
cls2.new.foo
|
|
|
|
end.should raise_error(LocalJumpError)
|
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|