diff options
Diffstat (limited to 'spec/ruby/core/fiber')
-rw-r--r-- | spec/ruby/core/fiber/blocking_spec.rb | 79 | ||||
-rw-r--r-- | spec/ruby/core/fiber/inspect_spec.rb | 36 | ||||
-rw-r--r-- | spec/ruby/core/fiber/kill_spec.rb | 90 | ||||
-rw-r--r-- | spec/ruby/core/fiber/raise_spec.rb | 194 | ||||
-rw-r--r-- | spec/ruby/core/fiber/resume_spec.rb | 15 | ||||
-rw-r--r-- | spec/ruby/core/fiber/storage_spec.rb | 158 |
6 files changed, 440 insertions, 132 deletions
diff --git a/spec/ruby/core/fiber/blocking_spec.rb b/spec/ruby/core/fiber/blocking_spec.rb index 5ae5fbd577..ebefa116af 100644 --- a/spec/ruby/core/fiber/blocking_spec.rb +++ b/spec/ruby/core/fiber/blocking_spec.rb @@ -1,61 +1,76 @@ require_relative '../../spec_helper' require_relative 'shared/blocking' -ruby_version_is "3.0" do - require "fiber" +require "fiber" - describe "Fiber.blocking?" do - it_behaves_like :non_blocking_fiber, -> { Fiber.blocking? } +describe "Fiber.blocking?" do + it_behaves_like :non_blocking_fiber, -> { Fiber.blocking? } - context "when fiber is blocking" do - context "root Fiber of the main thread" do - it "returns 1 for blocking: true" do + context "when fiber is blocking" do + context "root Fiber of the main thread" do + it "returns 1 for blocking: true" do + fiber = Fiber.new(blocking: true) { Fiber.blocking? } + blocking = fiber.resume + + blocking.should == 1 + end + end + + context "root Fiber of a new thread" do + it "returns 1 for blocking: true" do + thread = Thread.new do fiber = Fiber.new(blocking: true) { Fiber.blocking? } blocking = fiber.resume blocking.should == 1 end + + thread.join end + end + end +end - context "root Fiber of a new thread" do - it "returns 1 for blocking: true" do - thread = Thread.new do - fiber = Fiber.new(blocking: true) { Fiber.blocking? } - blocking = fiber.resume +describe "Fiber#blocking?" do + it_behaves_like :non_blocking_fiber, -> { Fiber.current.blocking? } - blocking.should == 1 - end + context "when fiber is blocking" do + context "root Fiber of the main thread" do + it "returns true for blocking: true" do + fiber = Fiber.new(blocking: true) { Fiber.current.blocking? } + blocking = fiber.resume - thread.join - end + blocking.should == true end end - end - - describe "Fiber#blocking?" do - it_behaves_like :non_blocking_fiber, -> { Fiber.current.blocking? } - context "when fiber is blocking" do - context "root Fiber of the main thread" do - it "returns true for blocking: true" do + context "root Fiber of a new thread" do + it "returns true for blocking: true" do + thread = Thread.new do fiber = Fiber.new(blocking: true) { Fiber.current.blocking? } blocking = fiber.resume blocking.should == true end - end - context "root Fiber of a new thread" do - it "returns true for blocking: true" do - thread = Thread.new do - fiber = Fiber.new(blocking: true) { Fiber.current.blocking? } - blocking = fiber.resume + thread.join + end + end + end +end - blocking.should == true +ruby_version_is "3.2" do + describe "Fiber.blocking" do + context "when fiber is non-blocking" do + it "can become blocking" do + fiber = Fiber.new(blocking: false) do + Fiber.blocking do |f| + f.blocking? ? :blocking : :non_blocking end - - thread.join end + + blocking = fiber.resume + blocking.should == :blocking end end end diff --git a/spec/ruby/core/fiber/inspect_spec.rb b/spec/ruby/core/fiber/inspect_spec.rb new file mode 100644 index 0000000000..f20a153fc2 --- /dev/null +++ b/spec/ruby/core/fiber/inspect_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require 'fiber' + +describe "Fiber#inspect" do + describe "status" do + it "is resumed for the root Fiber of a Thread" do + inspected = Thread.new { Fiber.current.inspect }.value + inspected.should =~ /\A#<Fiber:0x\h+ .*\(resumed\)>\z/ + end + + it "is created for a Fiber which did not run yet" do + inspected = Fiber.new {}.inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(created\)>\z/ + end + + it "is resumed for a Fiber which was resumed" do + inspected = Fiber.new { Fiber.current.inspect }.resume + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/ + end + + it "is resumed for a Fiber which was transferred" do + inspected = Fiber.new { Fiber.current.inspect }.transfer + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/ + end + + it "is suspended for a Fiber which was resumed and yielded" do + inspected = Fiber.new { Fiber.yield }.tap(&:resume).inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(suspended\)>\z/ + end + + it "is terminated for a Fiber which has terminated" do + inspected = Fiber.new {}.tap(&:resume).inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(terminated\)>\z/ + end + end +end diff --git a/spec/ruby/core/fiber/kill_spec.rb b/spec/ruby/core/fiber/kill_spec.rb new file mode 100644 index 0000000000..2f4c499280 --- /dev/null +++ b/spec/ruby/core/fiber/kill_spec.rb @@ -0,0 +1,90 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../shared/kernel/raise' + +ruby_version_is "3.3" do + describe "Fiber#kill" do + it "kills a non-resumed fiber" do + fiber = Fiber.new{} + + fiber.alive?.should == true + + fiber.kill + fiber.alive?.should == false + end + + it "kills a resumed fiber" do + fiber = Fiber.new{while true; Fiber.yield; end} + fiber.resume + + fiber.alive?.should == true + + fiber.kill + fiber.alive?.should == false + end + + it "can kill itself" do + fiber = Fiber.new do + Fiber.current.kill + end + + fiber.alive?.should == true + + fiber.resume + fiber.alive?.should == false + end + + it "kills a resumed fiber from a child" do + parent = Fiber.new do + child = Fiber.new do + parent.kill + parent.alive?.should == true + end + + child.resume + end + + parent.resume + parent.alive?.should == false + end + + it "executes the ensure block" do + ensure_executed = false + + fiber = Fiber.new do + while true; Fiber.yield; end + ensure + ensure_executed = true + end + + fiber.resume + fiber.kill + ensure_executed.should == true + end + + it "does not execute rescue block" do + rescue_executed = false + + fiber = Fiber.new do + while true; Fiber.yield; end + rescue Exception + rescue_executed = true + end + + fiber.resume + fiber.kill + rescue_executed.should == false + end + + it "repeatedly kills a fiber" do + fiber = Fiber.new do + while true; Fiber.yield; end + ensure + while true; Fiber.yield; end + end + + fiber.kill + fiber.alive?.should == false + end + end +end diff --git a/spec/ruby/core/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb index 2a465c8bfa..b3e021e636 100644 --- a/spec/ruby/core/fiber/raise_spec.rb +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -2,120 +2,138 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' require_relative '../../shared/kernel/raise' -ruby_version_is "2.7" do - describe "Fiber#raise" do - it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise +describe "Fiber#raise" do + it_behaves_like :kernel_raise, :raise, FiberSpecs::NewFiberToRaise +end + +describe "Fiber#raise" do + it 'raises RuntimeError by default' do + -> { FiberSpecs::NewFiberToRaise.raise }.should raise_error(RuntimeError) end - describe "Fiber#raise" do - it 'raises RuntimeError by default' do - -> { FiberSpecs::NewFiberToRaise.raise }.should raise_error(RuntimeError) - end + it "raises FiberError if Fiber is not born" do + fiber = Fiber.new { true } + -> { fiber.raise }.should raise_error(FiberError, "cannot raise exception on unborn fiber") + end - it "raises FiberError if Fiber is not born" do - fiber = Fiber.new { true } - -> { fiber.raise }.should raise_error(FiberError, "cannot raise exception on unborn fiber") - end + it "raises FiberError if Fiber is dead" do + fiber = Fiber.new { true } + fiber.resume + -> { fiber.raise }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) + end - it "raises FiberError if Fiber is dead" do - fiber = Fiber.new { true } - fiber.resume - -> { fiber.raise }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) - end + it 'accepts error class' do + -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError }.should raise_error(FiberSpecs::CustomError) + end - it 'accepts error class' do - -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError }.should raise_error(FiberSpecs::CustomError) - end + it 'accepts error message' do + -> { FiberSpecs::NewFiberToRaise.raise "error message" }.should raise_error(RuntimeError, "error message") + end - it 'accepts error message' do - -> { FiberSpecs::NewFiberToRaise.raise "error message" }.should raise_error(RuntimeError, "error message") - end + it 'does not accept array of backtrace information only' do + -> { FiberSpecs::NewFiberToRaise.raise ['foo'] }.should raise_error(TypeError) + end - it 'does not accept array of backtrace information only' do - -> { FiberSpecs::NewFiberToRaise.raise ['foo'] }.should raise_error(TypeError) - end + it 'does not accept integer' do + -> { FiberSpecs::NewFiberToRaise.raise 100 }.should raise_error(TypeError) + end - it 'does not accept integer' do - -> { FiberSpecs::NewFiberToRaise.raise 100 }.should raise_error(TypeError) - end + it 'accepts error class with error message' do + -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should raise_error(FiberSpecs::CustomError, 'test error') + end - it 'accepts error class with error message' do - -> { FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error' }.should raise_error(FiberSpecs::CustomError, 'test error') - end + it 'accepts error class with with error message and backtrace information' do + -> { + FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo'] + }.should raise_error(FiberSpecs::CustomError) { |e| + e.message.should == 'test error' + e.backtrace.should == ['foo', 'boo'] + } + end + + it 'does not accept only error message and backtrace information' do + -> { FiberSpecs::NewFiberToRaise.raise 'test error', ['foo', 'boo'] }.should raise_error(TypeError) + end - it 'accepts error class with with error message and backtrace information' do + it "raises a FiberError if invoked from a different Thread" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + Thread.new do -> { - FiberSpecs::NewFiberToRaise.raise FiberSpecs::CustomError, 'test error', ['foo', 'boo'] - }.should raise_error(FiberSpecs::CustomError) { |e| - e.message.should == 'test error' - e.backtrace.should == ['foo', 'boo'] - } + fiber.raise + }.should raise_error(FiberError, "fiber called across threads") + end.join + end + + it "kills Fiber" do + fiber = Fiber.new { Fiber.yield :first; :second } + fiber.resume + -> { fiber.raise }.should raise_error + -> { fiber.resume }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) + end + + it "returns to calling fiber after raise" do + fiber_one = Fiber.new do + Fiber.yield :yield_one + :unreachable end - it 'does not accept only error message and backtrace information' do - -> { FiberSpecs::NewFiberToRaise.raise 'test error', ['foo', 'boo'] }.should raise_error(TypeError) + fiber_two = Fiber.new do + results = [] + results << fiber_one.resume + begin + fiber_one.raise + rescue + results << :rescued + end + results end - it "raises a FiberError if invoked from a different Thread" do - fiber = Fiber.new { Fiber.yield } - fiber.resume - Thread.new do - -> { - fiber.raise - }.should raise_error(FiberError, "fiber called across threads") - end.join + fiber_two.resume.should == [:yield_one, :rescued] + end + + ruby_version_is "3.4" do + it "raises on the resumed fiber" do + root_fiber = Fiber.current + f1 = Fiber.new { root_fiber.transfer } + f2 = Fiber.new { f1.resume } + f2.transfer + + -> do + f2.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") end - it "kills Fiber" do - fiber = Fiber.new { Fiber.yield :first; :second } - fiber.resume - -> { fiber.raise }.should raise_error - -> { fiber.resume }.should raise_error(FiberError, /dead fiber called|attempt to resume a terminated fiber/) + it "raises on itself" do + -> do + Fiber.current.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") end - it "returns to calling fiber after raise" do - fiber_one = Fiber.new do - Fiber.yield :yield_one - :unreachable + it "should raise on parent fiber" do + f2 = nil + f1 = Fiber.new do + # This is equivalent to Kernel#raise: + f2.raise(RuntimeError, "Expected error") end - - fiber_two = Fiber.new do - results = [] - results << fiber_one.resume - begin - fiber_one.raise - rescue - results << :rescued - end - results + f2 = Fiber.new do + f1.resume end - fiber_two.resume.should == [:yield_one, :rescued] + -> do + f2.resume + end.should raise_error(RuntimeError, "Expected error") end end - end -ruby_version_is "2.7"..."3.0" do - describe "Fiber#raise" do - it "raises a FiberError if invoked on a transferring Fiber" do - require "fiber" - root = Fiber.current - fiber = Fiber.new { root.transfer } - fiber.transfer - -> { fiber.raise }.should raise_error(FiberError, "cannot resume transferred Fiber") - end - end -end -ruby_version_is "3.0" do - describe "Fiber#raise" do - it "transfers and raises on a transferring fiber" do - require "fiber" - root = Fiber.current - fiber = Fiber.new { root.transfer } - fiber.transfer - -> { fiber.raise "msg" }.should raise_error(RuntimeError, "msg") - end +describe "Fiber#raise" do + it "transfers and raises on a transferring fiber" do + require "fiber" + root = Fiber.current + fiber = Fiber.new { root.transfer } + fiber.transfer + -> { fiber.raise "msg" }.should raise_error(RuntimeError, "msg") end end diff --git a/spec/ruby/core/fiber/resume_spec.rb b/spec/ruby/core/fiber/resume_spec.rb index 273bc866af..ab9a6799ab 100644 --- a/spec/ruby/core/fiber/resume_spec.rb +++ b/spec/ruby/core/fiber/resume_spec.rb @@ -28,18 +28,9 @@ describe "Fiber#resume" do fiber.resume :second end - ruby_version_is '3.0' do - it "raises a FiberError if the Fiber tries to resume itself" do - fiber = Fiber.new { fiber.resume } - -> { fiber.resume }.should raise_error(FiberError, /current fiber/) - end - end - - ruby_version_is '' ... '3.0' do - it "raises a FiberError if the Fiber tries to resume itself" do - fiber = Fiber.new { fiber.resume } - -> { fiber.resume }.should raise_error(FiberError, /double resume/) - end + it "raises a FiberError if the Fiber tries to resume itself" do + fiber = Fiber.new { fiber.resume } + -> { fiber.resume }.should raise_error(FiberError, /current fiber/) end it "returns control to the calling Fiber if called from one" do diff --git a/spec/ruby/core/fiber/storage_spec.rb b/spec/ruby/core/fiber/storage_spec.rb new file mode 100644 index 0000000000..5c87ed5d41 --- /dev/null +++ b/spec/ruby/core/fiber/storage_spec.rb @@ -0,0 +1,158 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.2" do + describe "Fiber.new(storage:)" do + it "creates a Fiber with the given storage" do + storage = {life: 42} + fiber = Fiber.new(storage: storage) { Fiber.current.storage } + fiber.resume.should == storage + end + + it "creates a fiber with lazily initialized storage" do + Fiber.new(storage: nil) { Fiber[:x] = 10; Fiber.current.storage }.resume.should == {x: 10} + end + + it "creates a fiber by inheriting the storage of the parent fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.new { Fiber.current.storage }.resume + end + fiber.resume.should == {life: 42} + end + + it "cannot create a fiber with non-hash storage" do + -> { Fiber.new(storage: 42) {} }.should raise_error(TypeError) + end + + it "cannot create a fiber with a frozen hash as storage" do + -> { Fiber.new(storage: {life: 43}.freeze) {} }.should raise_error(FrozenError) + end + + it "cannot create a fiber with a storage hash with non-symbol keys" do + -> { Fiber.new(storage: {life: 43, Object.new => 44}) {} }.should raise_error(TypeError) + end + end + + describe "Fiber#storage" do + it "cannot be accessed from a different fiber" do + f = Fiber.new(storage: {life: 42}) { nil } + -> { + f.storage + }.should raise_error(ArgumentError, /Fiber storage can only be accessed from the Fiber it belongs to/) + end + end + + describe "Fiber#storage=" do + it "can clear the storage of the fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.current.storage = nil + Fiber[:x] = 10 + Fiber.current.storage + end + fiber.resume.should == {x: 10} + end + + it "can set the storage of the fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Fiber.current.storage = {life: 43} + Fiber.current.storage + end + fiber.resume.should == {life: 43} + end + + it "can't set the storage of the fiber to non-hash" do + -> { Fiber.current.storage = 42 }.should raise_error(TypeError) + end + + it "can't set the storage of the fiber to a frozen hash" do + -> { Fiber.current.storage = {life: 43}.freeze }.should raise_error(FrozenError) + end + + it "can't set the storage of the fiber to a hash with non-symbol keys" do + -> { Fiber.current.storage = {life: 43, Object.new => 44} }.should raise_error(TypeError) + end + end + + describe "Fiber.[]" do + it "returns the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:life] }.resume.should == 42 + end + + it "returns nil if the key is not present in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:death] }.resume.should be_nil + end + + it "returns nil if the current fiber has no storage" do + Fiber.new { Fiber[:life] }.resume.should be_nil + end + + ruby_version_is "3.2.3" do + it "can use dynamically defined keys" do + key = :"#{self.class.name}#.#{self.object_id}" + Fiber.new { Fiber[key] = 42; Fiber[key] }.resume.should == 42 + end + + it "can't use invalid keys" do + invalid_keys = [Object.new, "Foo", 12] + invalid_keys.each do |key| + -> { Fiber[key] }.should raise_error(TypeError) + end + end + end + + it "can access the storage of the parent fiber" do + f = Fiber.new(storage: {life: 42}) do + Fiber.new { Fiber[:life] }.resume + end + f.resume.should == 42 + end + + it "can't access the storage of the fiber with non-symbol keys" do + -> { Fiber[Object.new] }.should raise_error(TypeError) + end + end + + describe "Fiber.[]=" do + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 + end + + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new(storage: {life: 42}) { Fiber[:death] = 43; Fiber[:death] }.resume.should == 43 + end + + it "sets the value of the given key in the storage of the current fiber" do + Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume.should == 43 + end + + it "does not overwrite the storage of the parent fiber" do + f = Fiber.new(storage: {life: 42}) do + Fiber.yield Fiber.new { Fiber[:life] = 43; Fiber[:life] }.resume + Fiber[:life] + end + f.resume.should == 43 # Value of the inner fiber + f.resume.should == 42 # Value of the outer fiber + end + + it "can't access the storage of the fiber with non-symbol keys" do + -> { Fiber[Object.new] = 44 }.should raise_error(TypeError) + end + + ruby_version_is "3.3" do + it "deletes the fiber storage key when assigning nil" do + Fiber.new(storage: {life: 42}) { + Fiber[:life] = nil + Fiber.current.storage + }.resume.should == {} + end + end + end + + describe "Thread.new" do + it "creates a thread with the storage of the current fiber" do + fiber = Fiber.new(storage: {life: 42}) do + Thread.new { Fiber.current.storage }.value + end + fiber.resume.should == {life: 42} + end + end +end |