diff options
Diffstat (limited to 'spec/ruby/language/rescue_spec.rb')
| -rw-r--r-- | spec/ruby/language/rescue_spec.rb | 384 |
1 files changed, 350 insertions, 34 deletions
diff --git a/spec/ruby/language/rescue_spec.rb b/spec/ruby/language/rescue_spec.rb index 281bb8fde8..cf16d8f6f8 100644 --- a/spec/ruby/language/rescue_spec.rb +++ b/spec/ruby/language/rescue_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../spec_helper', __FILE__) -require File.expand_path('../fixtures/rescue', __FILE__) +require_relative '../spec_helper' +require_relative 'fixtures/rescue' class SpecificExampleException < StandardError end @@ -23,16 +23,154 @@ describe "The rescue keyword" do end.should == :caught end - it "can capture the raised exception in a local variable" do + describe 'can capture the raised exception' do + before :all do + require_relative 'fixtures/rescue_captures' + end + + it 'in a local variable' do + RescueSpecs::LocalVariableCaptor.should_capture_exception + end + + it 'in a class variable' do + RescueSpecs::ClassVariableCaptor.should_capture_exception + end + + it 'in a constant' do + RescueSpecs::ConstantCaptor.should_capture_exception + end + + it 'in a global variable' do + RescueSpecs::GlobalVariableCaptor.should_capture_exception + end + + it 'in an instance variable' do + RescueSpecs::InstanceVariableCaptor.should_capture_exception + end + + it 'using a safely navigated setter method' do + RescueSpecs::SafeNavigationSetterCaptor.should_capture_exception + end + + it 'using a safely navigated setter method on a nil target' do + target = nil + begin + raise SpecificExampleException, "Raising this to be handled below" + rescue SpecificExampleException => target&.captured_error + :caught + end.should == :caught + target.should == nil + end + + it 'using a setter method' do + RescueSpecs::SetterCaptor.should_capture_exception + end + + it 'using a square brackets setter' do + RescueSpecs::SquareBracketsCaptor.should_capture_exception + end + end + + describe 'capturing in a local variable (that defines it)' do + it 'captures successfully in a method' do + ScratchPad.record [] + + def a + raise "message" + rescue => e + ScratchPad << e.message + end + + a + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a block' do + ScratchPad.record [] + + p = proc do + raise "message" + rescue => e + ScratchPad << e.message + end + + p.call + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a class' do + ScratchPad.record [] + + class RescueSpecs::C + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully in a module' do + ScratchPad.record [] + + module RescueSpecs::M + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures sucpcessfully in a singleton class' do + ScratchPad.record [] + + class << Object.new + raise "message" + rescue => e + ScratchPad << e.message + end + + ScratchPad.recorded.should == ["message"] + end + + it 'captures successfully at the top-level' do + ScratchPad.record [] + loaded_features = $".dup + begin + require_relative 'fixtures/rescue/top_level' + + ScratchPad.recorded.should == ["message"] + ensure + $".replace loaded_features + end + end + end + + it "returns value from `rescue` if an exception was raised" do begin - raise SpecificExampleException, "some text" - rescue SpecificExampleException => e - e.message.should == "some text" + raise + rescue + :caught + end.should == :caught + end + + it "returns value from `else` section if no exceptions were raised" do + result = begin + :begin + rescue + :rescue + else + :else + ensure + :ensure end + + result.should == :else end it "can rescue multiple raised exceptions with a single rescue block" do - [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].map do |block| + [->{raise ArbitraryException}, ->{raise SpecificExampleException}].map do |block| begin block.call rescue SpecificExampleException, ArbitraryException @@ -48,9 +186,9 @@ describe "The rescue keyword" do rescue *exception_list caught_it = true end - caught_it.should be_true + caught_it.should == true caught = [] - [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block| + [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block| begin block.call rescue *exception_list @@ -59,8 +197,20 @@ describe "The rescue keyword" do end caught.size.should == 2 exception_list.each do |exception_class| - caught.map{|e| e.class}.should include(exception_class) + caught.map{|e| e.class}.should.include?(exception_class) + end + end + + it "converts the splatted list of exceptions using #to_a" do + exceptions = mock("to_a") + exceptions.should_receive(:to_a).and_return(exception_list) + caught_it = false + begin + raise SpecificExampleException, "not important" + rescue *exceptions + caught_it = true end + caught_it.should == true end it "can combine a splatted list of exceptions with a literal list of exceptions" do @@ -70,9 +220,9 @@ describe "The rescue keyword" do rescue ArbitraryException, *exception_list caught_it = true end - caught_it.should be_true + caught_it.should == true caught = [] - [lambda{raise ArbitraryException}, lambda{raise SpecificExampleException}].each do |block| + [->{raise ArbitraryException}, ->{raise SpecificExampleException}].each do |block| begin block.call rescue ArbitraryException, *exception_list @@ -81,17 +231,56 @@ describe "The rescue keyword" do end caught.size.should == 2 exception_list.each do |exception_class| - caught.map{|e| e.class}.should include(exception_class) + caught.map{|e| e.class}.should.include?(exception_class) end end it "will only rescue the specified exceptions when doing a splat rescue" do - lambda do + -> do begin raise OtherCustomException, "not rescued!" rescue *exception_list end - end.should raise_error(OtherCustomException) + end.should.raise(OtherCustomException) + end + + it "can rescue different types of exceptions in different ways" do + begin + raise Exception + rescue RuntimeError + rescue StandardError + rescue Exception + ScratchPad << :exception + end + + ScratchPad.recorded.should == [:exception] + end + + it "rescues exception within the first suitable section in order of declaration" do + begin + raise StandardError + rescue RuntimeError + ScratchPad << :runtime_error + rescue StandardError + ScratchPad << :standard_error + rescue Exception + ScratchPad << :exception + end + + ScratchPad.recorded.should == [:standard_error] + end + + it "rescues the exception in the deepest rescue block declared to handle the appropriate exception type" do + begin + begin + RescueSpecs.raise_standard_error + rescue ArgumentError + end + rescue StandardError => e + e.backtrace.first.should =~ /:in [`'](?:RescueSpecs\.)?raise_standard_error'/ + else + fail("exception wasn't handled by the correct rescue block") + end end it "will execute an else block only if no exceptions were raised" do @@ -147,6 +336,18 @@ describe "The rescue keyword" do ScratchPad.recorded.should == [:one, :else_ran, :ensure_ran, :outside_begin] end + it "raises SyntaxError when else is used without rescue and ensure" do + -> { + eval <<-ruby + begin + ScratchPad << :begin + else + ScratchPad << :else + end + ruby + }.should.raise(SyntaxError, /else without rescue is useless/) + end + it "will not execute an else block if an exception was raised" do result = begin ScratchPad << :one @@ -203,7 +404,7 @@ describe "The rescue keyword" do end it "will not rescue errors raised in an else block in the rescue block above it" do - lambda do + -> do begin ScratchPad << :one rescue Exception @@ -212,7 +413,7 @@ describe "The rescue keyword" do ScratchPad << :two raise SpecificExampleException, "an error from else" end - end.should raise_error(SpecificExampleException) + end.should.raise(SpecificExampleException) ScratchPad.recorded.should == [:one, :two] end @@ -223,14 +424,31 @@ describe "The rescue keyword" do a.should == 'ac' end - it "without classes will not rescue Exception" do - lambda do + context "without rescue expression" do + it "will rescue only StandardError and its subclasses" do begin - raise Exception + raise StandardError rescue - 'Exception wrongly rescued' + ScratchPad << :caught + end + + ScratchPad.recorded.should == [:caught] + end + + it "will not rescue exceptions except StandardError" do + [ Exception.new, NoMemoryError.new, ScriptError.new, SecurityError.new, + SignalException.new('INT'), SystemExit.new, SystemStackError.new + ].each do |exception| + -> { + begin + raise exception + rescue + ScratchPad << :caught + end + }.should.raise(exception.class) end - end.should raise_error(Exception) + ScratchPad.recorded.should == [] + end end it "uses === to compare against rescued classes" do @@ -253,34 +471,46 @@ describe "The rescue keyword" do it "only accepts Module or Class in rescue clauses" do rescuer = 42 - lambda { + -> { begin raise "error" rescue rescuer end - }.should raise_error(TypeError) { |e| + }.should.raise(TypeError) { |e| e.message.should =~ /class or module required for rescue clause/ } end it "only accepts Module or Class in splatted rescue clauses" do rescuer = [42] - lambda { + -> { begin raise "error" rescue *rescuer end - }.should raise_error(TypeError) { |e| + }.should.raise(TypeError) { |e| e.message.should =~ /class or module required for rescue clause/ } end it "evaluates rescue expressions only when needed" do - invalid_rescuer = Object.new begin - :foo - rescue rescuer - end.should == :foo + ScratchPad << :foo + rescue -> { ScratchPad << :bar; StandardError }.call + end + + ScratchPad.recorded.should == [:foo] + end + + it "suppresses exception from block when raises one from rescue expression" do + -> { + begin + raise "from block" + rescue (raise "from rescue expression") + end + }.should.raise(RuntimeError, "from rescue expression") { |e| + e.cause.message.should == "from block" + } end it "should splat the handling Error classes" do @@ -291,10 +521,96 @@ describe "The rescue keyword" do end.should == :expected end - ruby_version_is "2.4" do - it "allows 'rescue' in method arguments" do - two = eval '1.+ (raise("Error") rescue 1)' - two.should == 2 + it "allows rescue in class" do + eval <<-ruby + class RescueInClassExample + raise SpecificExampleException + rescue SpecificExampleException + ScratchPad << :caught + end + ruby + + ScratchPad.recorded.should == [:caught] + end + + it "does not allow rescue in {} block" do + -> { + eval <<-ruby + lambda { + raise SpecificExampleException + rescue SpecificExampleException + :caught + } + ruby + }.should.raise(SyntaxError) + end + + it "allows rescue in 'do end' block" do + lambda = eval <<-ruby + lambda do + raise SpecificExampleException + rescue SpecificExampleException + ScratchPad << :caught + end.call + ruby + + ScratchPad.recorded.should == [:caught] + end + + it "allows 'rescue' in method arguments" do + two = eval '1.+ (raise("Error") rescue 1)' + two.should == 2 + end + + it "requires the 'rescue' in method arguments to be wrapped in parens" do + -> { eval '1.+(1 rescue 1)' }.should.raise(SyntaxError) + eval('1.+((1 rescue 1))').should == 2 + end + + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + rescue + return caller(0, 2) + end + end + line = __LINE__ + foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/ + foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/ + end + end + + describe "inline form" do + it "can be inlined" do + a = 1/0 rescue 1 + a.should == 1 + end + + it "doesn't except rescue expression" do + -> { + eval <<-ruby + a = 1 rescue RuntimeError 2 + ruby + }.should.raise(SyntaxError) + end + + it "rescues only StandardError and its subclasses" do + a = raise(StandardError) rescue 1 + a.should == 1 + + -> { + a = raise(Exception) rescue 1 + }.should.raise(Exception) + end + + it "rescues with multiple assignment" do + + a, b = raise rescue [1, 2] + + a.should == 1 + b.should == 2 end end end |
