diff --git a/core/fiber/kill_spec.rb b/core/fiber/kill_spec.rb index abf23ff17..10c76ac6d 100644 --- a/core/fiber/kill_spec.rb +++ b/core/fiber/kill_spec.rb @@ -75,6 +75,108 @@ rescue_executed.should == false end + it "returns the fiber" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + + fiber.kill.should.equal?(fiber) + end + + it "returns the fiber when it is already dead" do + fiber = Fiber.new { :done } + fiber.resume + + fiber.kill.should.equal?(fiber) + end + + it "does nothing when killing a fiber twice" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + + fiber.kill + fiber.kill + fiber.alive?.should == false + end + + it "runs the ensure block in the fiber being killed, while it is still alive" do + states = [] + + fiber = Fiber.new do + Fiber.yield + ensure + states << Fiber.current.equal?(fiber) << fiber.alive? + end + + fiber.resume + fiber.kill + states.should == [true, true] + end + + it "propagates an exception raised by the ensure block to the killing fiber" do + fiber = Fiber.new do + Fiber.yield + ensure + raise "from ensure" + end + + fiber.resume + -> { fiber.kill }.should.raise(RuntimeError, "from ensure") + end + + it "raises a FiberError when killing a fiber from a different Thread" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + + Thread.new do + -> { fiber.kill }.should.raise(FiberError) + end.join + end + + it "kills a fiber which transferred, from the fiber it transferred to" do + states = [] + outer = nil + + outer = Fiber.new do + Fiber.new do + outer.kill + states << :inner_resumed + end.transfer + states << :outer_resumed + end + + outer.resume + # Killing outer transfers into it to unwind, and the inner fiber is left + # suspended, so neither fiber runs again. + states.should == [] + outer.alive?.should == false + end + + it "kills a fiber whose ensure block transfers to another fiber" do + other = Fiber.new { :other } + + fiber = Fiber.new do + Fiber.yield + ensure + other.transfer + end + + fiber.resume + fiber.kill + fiber.alive?.should == false + other.alive?.should == false + end + + it "returns control to the transferring fiber when killing a fiber entered by Fiber#transfer" do + states = [] + runner = Fiber.new do + Fiber.new { Fiber.current.kill; states << :unreachable }.transfer + states << :runner_resumed + end + + runner.resume + states.should == [:runner_resumed] + end + it "repeatedly kills a fiber" do fiber = Fiber.new do while true; Fiber.yield; end diff --git a/core/fiber/transfer_spec.rb b/core/fiber/transfer_spec.rb index d8737aeeb..4f695db1b 100644 --- a/core/fiber/transfer_spec.rb +++ b/core/fiber/transfer_spec.rb @@ -20,6 +20,54 @@ f2.transfer.should == :fiber_2 end + it "returns control to the transferring fiber when the target finishes" do + states = [] + runner = Fiber.new do + Fiber.new { states << :target_end }.transfer + states << :runner_resumed + end + + runner.resume + states.should == [:target_end, :runner_resumed] + end + + it "returns control to a transferring fiber which was itself entered by Fiber#raise" do + states = [] + target = Fiber.new do + begin + Fiber.yield + rescue RuntimeError + Fiber.new { states << :inner_end }.transfer + states << :target_after_transfer + end + end + + target.resume + target.raise "raised" + states.should == [:inner_end, :target_after_transfer] + end + + it "returns control according to the resume chain, not to the Fiber which transferred" do + states = [] + outer = Fiber.new do + middle = Fiber.new do + inner = Fiber.new do + Fiber.new { states << :leaf_end }.transfer + states << :inner_after_transfer + end + inner.resume + states << :middle_after_resume + end + middle.transfer + states << :outer_after_transfer + end + + outer.resume + # The leaf has no resumer, so control follows resumes down from the root Fiber and + # stops at outer. inner, which transferred to the leaf, stays suspended. + states.should == [:leaf_end, :outer_after_transfer] + end + it "can be invoked from the same Fiber it transfers control to" do states = [] fiber = Fiber.new { states << :start; fiber.transfer; states << :end }