diff options
Diffstat (limited to 'spec/ruby/language/ensure_spec.rb')
| -rw-r--r-- | spec/ruby/language/ensure_spec.rb | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/spec/ruby/language/ensure_spec.rb b/spec/ruby/language/ensure_spec.rb new file mode 100644 index 0000000000..04ff0305ab --- /dev/null +++ b/spec/ruby/language/ensure_spec.rb @@ -0,0 +1,346 @@ +require_relative '../spec_helper' +require_relative 'fixtures/ensure' + +describe "An ensure block inside a begin block" do + before :each do + ScratchPad.record [] + end + + it "is executed when an exception is raised in its corresponding begin block" do + -> { + begin + ScratchPad << :begin + raise EnsureSpec::Error + ensure + ScratchPad << :ensure + end + }.should.raise(EnsureSpec::Error) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "is executed when an exception is raised and rescued in its corresponding begin block" do + begin + ScratchPad << :begin + raise "An exception occurred!" + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + + ScratchPad.recorded.should == [:begin, :rescue, :ensure] + end + + it "is executed even when a symbol is thrown in its corresponding begin block" do + catch(:symbol) do + begin + ScratchPad << :begin + throw(:symbol) + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "is executed when nothing is raised or thrown in its corresponding begin block" do + begin + ScratchPad << :begin + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "has no return value" do + begin + :begin + ensure + :ensure + end.should == :begin + end + + it "sets exception cause if raises exception in block and in ensure" do + -> { + begin + raise "from block" + ensure + raise "from ensure" + end + }.should.raise(RuntimeError, "from ensure") { |e| + e.cause.message.should == "from block" + } + end +end + +describe "The value of an ensure expression," do + it "in no-exception scenarios, is the value of the last statement of the protected body" do + begin + v = 1 + eval('x=1') # to prevent opts from triggering + v + ensure + v = 2 + end.should == 1 + end + + it "when an exception is rescued, is the value of the rescuing block" do + begin + raise 'foo' + rescue + v = 3 + ensure + v = 2 + end.should == 3 + end +end + +describe "An ensure block inside a method" do + before :each do + @obj = EnsureSpec::Container.new + end + + it "is executed when an exception is raised in the method" do + -> { @obj.raise_in_method_with_ensure }.should.raise(EnsureSpec::Error) + @obj.executed.should == [:method, :ensure] + end + + it "is executed when an exception is raised and rescued in the method" do + @obj.raise_and_rescue_in_method_with_ensure + @obj.executed.should == [:method, :rescue, :ensure] + end + + it "is executed even when a symbol is thrown in the method" do + catch(:symbol) { @obj.throw_in_method_with_ensure } + @obj.executed.should == [:method, :ensure] + end + + it "has no impact on the method's implicit return value" do + @obj.implicit_return_in_method_with_ensure.should == :method + end + + it "has an impact on the method's explicit return value" do + @obj.explicit_return_in_method_with_ensure.should == :ensure + end + + it "has an impact on the method's explicit return value from rescue if returns explicitly" do + @obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure" + end + + it "has no impact on the method's explicit return value from rescue if returns implicitly" do + @obj.explicit_return_in_rescue_and_implicit_return_in_ensure.should == "returned in rescue" + end + + it "suppresses exception raised in method if returns value explicitly" do + @obj.raise_and_explicit_return_in_ensure.should == "returned in ensure" + end + + it "suppresses exception raised in rescue if returns value explicitly" do + @obj.raise_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure" + end + + it "overrides exception raised in rescue if raises exception itself" do + -> { + @obj.raise_in_rescue_and_raise_in_ensure + }.should.raise(RuntimeError, "raised in ensure") + end + + it "suppresses exception raised in method if raises exception itself" do + -> { + @obj.raise_in_method_and_raise_in_ensure + }.should.raise(RuntimeError, "raised in ensure") + end +end + +describe "An ensure block inside a class" do + before :each do + ScratchPad.record [] + end + + it "is executed when an exception is raised" do + -> { + eval <<-ruby + class EnsureInClassExample + ScratchPad << :class + raise EnsureSpec::Error + ensure + ScratchPad << :ensure + end + ruby + }.should.raise(EnsureSpec::Error) + + ScratchPad.recorded.should == [:class, :ensure] + end + + it "is executed when an exception is raised and rescued" do + eval <<-ruby + class EnsureInClassExample + ScratchPad << :class + raise + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + + ScratchPad.recorded.should == [:class, :rescue, :ensure] + end + + it "is executed even when a symbol is thrown" do + catch(:symbol) do + eval <<-ruby + class EnsureInClassExample + ScratchPad << :class + throw(:symbol) + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + end + + ScratchPad.recorded.should == [:class, :ensure] + end + + it "is executed when nothing is raised or thrown" do + eval <<-ruby + class EnsureInClassExample + ScratchPad << :class + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + + ScratchPad.recorded.should == [:class, :ensure] + end + + it "has no return value" do + result = eval <<-ruby + class EnsureInClassExample + :class + ensure + :ensure + end + ruby + + result.should == :class + end +end + +describe "An ensure block inside {} block" do + it "is not allowed" do + -> { + eval <<-ruby + lambda { + raise + ensure + } + ruby + }.should.raise(SyntaxError) + end +end + +describe "An ensure block inside 'do end' block" do + before :each do + ScratchPad.record [] + end + + it "is executed when an exception is raised in its corresponding begin block" do + -> { + eval(<<-ruby).call + lambda do + ScratchPad << :begin + raise EnsureSpec::Error + ensure + ScratchPad << :ensure + end + ruby + }.should.raise(EnsureSpec::Error) + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "is executed when an exception is raised and rescued in its corresponding begin block" do + eval(<<-ruby).call + lambda do + ScratchPad << :begin + raise "An exception occurred!" + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + + ScratchPad.recorded.should == [:begin, :rescue, :ensure] + end + + it "is executed even when a symbol is thrown in its corresponding begin block" do + catch(:symbol) do + eval(<<-ruby).call + lambda do + ScratchPad << :begin + throw(:symbol) + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + end + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "is executed when nothing is raised or thrown in its corresponding begin block" do + eval(<<-ruby).call + lambda do + ScratchPad << :begin + rescue + ScratchPad << :rescue + ensure + ScratchPad << :ensure + end + ruby + + ScratchPad.recorded.should == [:begin, :ensure] + end + + it "has no return value" do + result = eval(<<-ruby).call + lambda do + :begin + ensure + :ensure + end + ruby + + result.should == :begin + end + + ruby_version_is "3.4" do + it "does not introduce extra backtrace entries" do + def foo + begin + raise "oops" + ensure + return caller(0, 2) # rubocop:disable Lint/EnsureReturn + end + end + line = __LINE__ + foo[0].should =~ /#{__FILE__}:#{line-3}:in 'foo'/ + foo[1].should =~ /#{__FILE__}:#{line+2}:in 'block/ + end + end +end |
