1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
|
require_relative '../spec_helper'
require_relative 'fixtures/break'
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
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
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
-> { @program.break_in_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :d, :b]
end
it "raises a LocalJumpError when invoking the block from a method" do
-> { @program.break_in_nested_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
end
it "raises a LocalJumpError when yielding to the block" do
-> { @program.break_in_yielding_method }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :xa, :cc, :aa, :b]
end
end
describe "from a scope that has returned" do
it "raises a LocalJumpError when calling the block from a method" do
-> { @program.break_in_method_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :zb]
end
it "raises a LocalJumpError when yielding to the block" do
-> { @program.break_in_yield_captured }.should raise_error(LocalJumpError)
ScratchPad.recorded.should == [:a, :za, :xa, :zd, :aa, :zb]
end
end
describe "from another thread" do
it "raises a LocalJumpError when getting the value from another thread" do
thread_with_break = Thread.new do
begin
break :break
rescue LocalJumpError => e
e
end
end
thread_with_break.value.should be_an_instance_of(LocalJumpError)
end
end
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
l = -> {
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)
-> {
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
-> { break obj.method :value do |x| x end }.call.should == :value
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
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
end
|