Skip to content

Commit

Permalink
Merge pull request #2033 from herwinw/language_ensure
Browse files Browse the repository at this point in the history
Fix compilation error in ensure_spec
  • Loading branch information
herwinw committed May 20, 2024
2 parents b1c3065 + e88e551 commit a0bfdfa
Show file tree
Hide file tree
Showing 3 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/natalie/compiler/pass1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def expand_macros(node)
end

def transform_body(body, location:, used:)
return transform_begin_node(body, used:) if body.is_a?(Prism::BeginNode)
body = body.body if body.is_a?(Prism::StatementsNode)
*body, last = body
nil_node = Prism.nil_node(location: location)
Expand Down
360 changes: 360 additions & 0 deletions spec/language/ensure_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
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 it's corresponding begin block" do
-> {
begin
ScratchPad << :begin
raise EnsureSpec::Error
ensure
ScratchPad << :ensure
end
}.should raise_error(EnsureSpec::Error)

ScratchPad.recorded.should == [:begin, :ensure]
end

it "is executed when an exception is raised and rescued in it's 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 it's corresponding begin block" do
catch(:symbol) do
begin
ScratchPad << :begin
throw(:symbol)
rescue
ScratchPad << :rescue
ensure
ScratchPad << :ensure
end
end

NATFIXME "it is executed even when a symbol is thrown in it's corresponding begin block", exception: SpecFailedException do
ScratchPad.recorded.should == [:begin, :ensure]
end
end

it "is executed when nothing is raised or thrown in it's 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_error(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_error(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 }
NATFIXME 'it is executed even when a symbol is thrown in the method', exception: SpecFailedException do
@obj.executed.should == [:method, :ensure]
end
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
NATFIXME "it has an impact on the method's explicit return value", exception: SpecFailedException do
@obj.explicit_return_in_method_with_ensure.should == :ensure
end
end

it "has an impact on the method's explicit return value from rescue if returns explicitly" do
NATFIXME "it has an impact on the method's explicit return value from rescue if returns explicitly", exception: SpecFailedException do
@obj.explicit_return_in_rescue_and_explicit_return_in_ensure.should == "returned in ensure"
end
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_error(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_error(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_error(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

NATFIXME 'it is executed even when a symbol is thrown', exception: SpecFailedException do
ScratchPad.recorded.should == [:class, :ensure]
end
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_error(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 it's corresponding begin block" do
-> {
eval(<<-ruby).call
lambda do
ScratchPad << :begin
raise EnsureSpec::Error
ensure
ScratchPad << :ensure
end
ruby
}.should raise_error(EnsureSpec::Error)

ScratchPad.recorded.should == [:begin, :ensure]
end

it "is executed when an exception is raised and rescued in it's 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 it's 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

NATFIXME "it is executed even when a symbol is thrown in it's corresponding begin block", exception: SpecFailedException do
ScratchPad.recorded.should == [:begin, :ensure]
end
end

it "is executed when nothing is raised or thrown in it's 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.should == [
"#{__FILE__}:#{line-3}:in 'foo'",
"#{__FILE__}:#{line+1}:in 'block (3 levels) in <top (required)>'"
]
end
end
end
Loading

0 comments on commit a0bfdfa

Please sign in to comment.