summaryrefslogtreecommitdiff
path: root/spec/ruby/core/exception
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/exception')
-rw-r--r--spec/ruby/core/exception/backtrace_spec.rb27
-rw-r--r--spec/ruby/core/exception/case_compare_spec.rb2
-rw-r--r--spec/ruby/core/exception/detailed_message_spec.rb43
-rw-r--r--spec/ruby/core/exception/equal_value_spec.rb14
-rw-r--r--spec/ruby/core/exception/errno_spec.rb9
-rw-r--r--spec/ruby/core/exception/fixtures/common.rb4
-rw-r--r--spec/ruby/core/exception/fixtures/syntax_error.rb3
-rw-r--r--spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb22
-rw-r--r--spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb25
-rw-r--r--spec/ruby/core/exception/frozen_error_spec.rb44
-rw-r--r--spec/ruby/core/exception/full_message_spec.rb208
-rw-r--r--spec/ruby/core/exception/interrupt_spec.rb9
-rw-r--r--spec/ruby/core/exception/key_error_spec.rb22
-rw-r--r--spec/ruby/core/exception/name_error_spec.rb12
-rw-r--r--spec/ruby/core/exception/no_method_error_spec.rb91
-rw-r--r--spec/ruby/core/exception/set_backtrace_spec.rb28
-rw-r--r--spec/ruby/core/exception/signal_exception_spec.rb12
-rw-r--r--spec/ruby/core/exception/syntax_error_spec.rb27
-rw-r--r--spec/ruby/core/exception/system_exit_spec.rb46
-rw-r--r--spec/ruby/core/exception/to_s_spec.rb2
-rw-r--r--spec/ruby/core/exception/top_level_spec.rb66
21 files changed, 544 insertions, 172 deletions
diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb
index 3f74c4cefe..9a65ea3820 100644
--- a/spec/ruby/core/exception/backtrace_spec.rb
+++ b/spec/ruby/core/exception/backtrace_spec.rb
@@ -27,7 +27,7 @@ describe "Exception#backtrace" do
end
it "includes the name of the method from where self raised in the first element" do
- @backtrace.first.should =~ /in `backtrace'/
+ @backtrace.first.should =~ /in [`'](?:ExceptionSpecs::Backtrace\.)?backtrace'/
end
it "includes the filename of the location immediately prior to where self raised in the second element" do
@@ -38,12 +38,25 @@ describe "Exception#backtrace" do
@backtrace[1].should =~ /:6(:in )?/
end
- it "contains lines of the same format for each prior position in the stack" do
- @backtrace[2..-1].each do |line|
- # This regexp is deliberately imprecise to account for the need to abstract out
- # the paths of the included mspec files and the desire to avoid specifying in any
- # detail what the in `...' portion looks like.
- line.should =~ /^.+:\d+:in `[^`]+'$/
+ ruby_version_is ""..."3.4" do
+ it "contains lines of the same format for each prior position in the stack" do
+ @backtrace[2..-1].each do |line|
+ # This regexp is deliberately imprecise to account for the need to abstract out
+ # the paths of the included mspec files and the desire to avoid specifying in any
+ # detail what the in `...' portion looks like.
+ line.should =~ /^.+:\d+:in `[^`]+'$/
+ end
+ end
+ end
+
+ ruby_version_is "3.4" do
+ it "contains lines of the same format for each prior position in the stack" do
+ @backtrace[2..-1].each do |line|
+ # This regexp is deliberately imprecise to account for the need to abstract out
+ # the paths of the included mspec files and the desire to avoid specifying in any
+ # detail what the in '...' portion looks like.
+ line.should =~ /^.+:\d+:in '[^`]+'$/
+ end
end
end
diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb
index 87b9dee3ca..5fd11ae741 100644
--- a/spec/ruby/core/exception/case_compare_spec.rb
+++ b/spec/ruby/core/exception/case_compare_spec.rb
@@ -26,13 +26,11 @@ describe "SystemCallError.===" do
end
it "returns true if receiver is generic and arg is kind of SystemCallError" do
- unknown_error_number = Errno.constants.size
e = SystemCallError.new('foo', @example_errno)
SystemCallError.===(e).should == true
end
it "returns false if receiver is generic and arg is not kind of SystemCallError" do
- unknown_error_number = Errno.constants.size
e = Object.new
SystemCallError.===(e).should == false
end
diff --git a/spec/ruby/core/exception/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb
new file mode 100644
index 0000000000..fbe4443daa
--- /dev/null
+++ b/spec/ruby/core/exception/detailed_message_spec.rb
@@ -0,0 +1,43 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#detailed_message" do
+ ruby_version_is "3.2" do
+ it "returns decorated message" do
+ RuntimeError.new("new error").detailed_message.should == "new error (RuntimeError)"
+ end
+
+ it "is called by #full_message to allow message customization" do
+ exception = Exception.new("new error")
+ def exception.detailed_message(**)
+ "<prefix>#{message}<suffix>"
+ end
+ exception.full_message(highlight: false).should.include? "<prefix>new error<suffix>"
+ end
+
+ it "accepts highlight keyword argument and adds escape control sequences" do
+ RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m"
+ end
+
+ it "allows and ignores other keyword arguments" do
+ RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)"
+ end
+
+ it "returns just a message if exception class is anonymous" do
+ Class.new(RuntimeError).new("message").detailed_message.should == "message"
+ end
+
+ it "returns 'unhandled exception' for an instance of RuntimeError with empty message" do
+ RuntimeError.new("").detailed_message.should == "unhandled exception"
+ end
+
+ it "returns just class name for an instance of RuntimeError subclass with empty message" do
+ DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C"
+ end
+
+ it "returns a generated class name for an instance of RuntimeError anonymous subclass with empty message" do
+ klass = Class.new(RuntimeError)
+ klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb
index 7f2065511a..e8f3ce0f8d 100644
--- a/spec/ruby/core/exception/equal_value_spec.rb
+++ b/spec/ruby/core/exception/equal_value_spec.rb
@@ -22,18 +22,18 @@ describe "Exception#==" do
it "returns true if both exceptions have the same class, the same message, and the same backtrace" do
one = TypeError.new("message")
- one.set_backtrace [File.dirname(__FILE__)]
+ one.set_backtrace [__dir__]
two = TypeError.new("message")
- two.set_backtrace [File.dirname(__FILE__)]
+ two.set_backtrace [__dir__]
one.should == two
end
it "returns false if the two exceptions inherit from Exception but have different classes" do
one = RuntimeError.new("message")
- one.set_backtrace [File.dirname(__FILE__)]
+ one.set_backtrace [__dir__]
one.should be_kind_of(Exception)
two = TypeError.new("message")
- two.set_backtrace [File.dirname(__FILE__)]
+ two.set_backtrace [__dir__]
two.should be_kind_of(Exception)
one.should_not == two
end
@@ -52,7 +52,7 @@ describe "Exception#==" do
it "returns false if the two exceptions differ only in their backtrace" do
one = RuntimeError.new("message")
- one.set_backtrace [File.dirname(__FILE__)]
+ one.set_backtrace [__dir__]
two = RuntimeError.new("message")
two.set_backtrace nil
one.should_not == two
@@ -60,9 +60,9 @@ describe "Exception#==" do
it "returns false if the two exceptions differ only in their message" do
one = RuntimeError.new("message")
- one.set_backtrace [File.dirname(__FILE__)]
+ one.set_backtrace [__dir__]
two = RuntimeError.new("message2")
- two.set_backtrace [File.dirname(__FILE__)]
+ two.set_backtrace [__dir__]
one.should_not == two
end
end
diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb
index 095a926e09..a063e522ea 100644
--- a/spec/ruby/core/exception/errno_spec.rb
+++ b/spec/ruby/core/exception/errno_spec.rb
@@ -56,3 +56,12 @@ describe "Errno::ENOTSUP" do
end
end
end
+
+describe "Errno::ENOENT" do
+ it "lets subclasses inherit the default error message" do
+ c = Class.new(Errno::ENOENT)
+ raise c, "custom message"
+ rescue => e
+ e.message.should == "No such file or directory - custom message"
+ end
+end
diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb
index 0ffb3ed855..1e243098bd 100644
--- a/spec/ruby/core/exception/fixtures/common.rb
+++ b/spec/ruby/core/exception/fixtures/common.rb
@@ -93,3 +93,7 @@ class NameErrorSpecs
end
end
end
+
+module DetailedMessageSpec
+ C = Class.new(RuntimeError)
+end
diff --git a/spec/ruby/core/exception/fixtures/syntax_error.rb b/spec/ruby/core/exception/fixtures/syntax_error.rb
new file mode 100644
index 0000000000..ccec62f7a1
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/syntax_error.rb
@@ -0,0 +1,3 @@
+# rubocop:disable Lint/Syntax
+1+1=2
+# rubocop:enable Lint/Syntax
diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb
new file mode 100644
index 0000000000..c109ec6247
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb
@@ -0,0 +1,22 @@
+ready = false
+t = Thread.new do
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ STDERR.puts "suspended fiber ensure"
+ end
+ end
+ f.resume
+
+ begin
+ ready = true
+ sleep
+ ensure
+ STDERR.puts "current fiber ensure"
+ end
+end
+
+Thread.pass until ready && t.stop?
+
+# let the program end, it's the same as #exit or an exception for this behavior
diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb
new file mode 100644
index 0000000000..3364ed06d0
--- /dev/null
+++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb
@@ -0,0 +1,25 @@
+ready = false
+t = Thread.new do
+ f = Fiber.new do
+ begin
+ Fiber.yield
+ ensure
+ STDERR.puts "suspended fiber ensure"
+ end
+ end
+ f.resume
+
+ f2 = Fiber.new do
+ begin
+ ready = true
+ sleep
+ ensure
+ STDERR.puts "current fiber ensure"
+ end
+ end
+ f2.resume
+end
+
+Thread.pass until ready && t.stop?
+
+# let the program end, it's the same as #exit or an exception for this behavior
diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb
index f27b33295c..979ec2ff98 100644
--- a/spec/ruby/core/exception/frozen_error_spec.rb
+++ b/spec/ruby/core/exception/frozen_error_spec.rb
@@ -1,26 +1,38 @@
require_relative '../../spec_helper'
describe "FrozenError.new" do
- ruby_version_is "2.7" do
- it "should take optional receiver argument" do
- o = Object.new
- FrozenError.new("msg", receiver: o).receiver.should equal(o)
- end
+ it "should take optional receiver argument" do
+ o = Object.new
+ FrozenError.new("msg", receiver: o).receiver.should equal(o)
end
end
describe "FrozenError#receiver" do
- ruby_version_is "2.7" do
- it "should return frozen object that modification was attempted on" do
- o = Object.new.freeze
- begin
- def o.x; end
- rescue => e
- e.should be_kind_of(FrozenError)
- e.receiver.should equal(o)
- else
- raise
- end
+ it "should return frozen object that modification was attempted on" do
+ o = Object.new.freeze
+ begin
+ def o.x; end
+ rescue => e
+ e.should be_kind_of(FrozenError)
+ e.receiver.should equal(o)
+ else
+ raise
+ end
+ end
+end
+
+describe "Modifying a frozen object" do
+ context "#inspect is redefined and modifies the object" do
+ it "returns ... instead of String representation of object" do
+ object = Object.new
+ def object.inspect; @a = 1 end
+ def object.modify; @a = 2 end
+
+ object.freeze
+
+ # CRuby's message contains multiple whitespaces before '...'.
+ # So handle both multiple and single whitespace.
+ -> { object.modify }.should raise_error(FrozenError, /can't modify frozen .*?: \s*.../)
end
end
end
diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb
index 9cac9fb037..5154354555 100644
--- a/spec/ruby/core/exception/full_message_spec.rb
+++ b/spec/ruby/core/exception/full_message_spec.rb
@@ -12,81 +12,187 @@ describe "Exception#full_message" do
full_message.should include "b.rb:2"
end
- ruby_version_is "2.5.1" do
- it "supports :highlight option and adds escape sequences to highlight some strings" do
- e = RuntimeError.new("Some runtime error")
+ it "supports :highlight option and adds escape sequences to highlight some strings" do
+ e = RuntimeError.new("Some runtime error")
+
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
+
+ full_message = e.full_message(highlight: true, order: :bottom).lines
+ full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n"
+ full_message[-1].should.end_with? "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n"
+
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.end_with? "Some runtime error (RuntimeError)\n"
- full_message = e.full_message(highlight: true, order: :bottom)
- full_message.should include "\e[1mTraceback\e[m (most recent call last)"
- full_message.should include "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)"
+ full_message = e.full_message(highlight: false, order: :bottom).lines
+ full_message[0].should == "Traceback (most recent call last):\n"
+ full_message[-1].should.end_with? "Some runtime error (RuntimeError)\n"
+ end
+
+ it "supports :order option and places the error message and the backtrace at the top or the bottom" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ e.full_message(order: :top, highlight: false).should =~ /a.rb:1.*b.rb:2/m
+ e.full_message(order: :bottom, highlight: false).should =~ /b.rb:2.*a.rb:1/m
+ end
+
+ it "shows the caller if the exception has no backtrace" do
+ e = RuntimeError.new("Some runtime error")
+ e.backtrace.should == nil
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{__LINE__-1}:in ")
+ full_message[0].should.end_with?("': Some runtime error (RuntimeError)\n")
+ end
- full_message = e.full_message(highlight: false, order: :bottom)
- full_message.should include "Traceback (most recent call last)"
- full_message.should include "Some runtime error (RuntimeError)"
+ describe "includes details about whether an exception was handled" do
+ describe "RuntimeError" do
+ it "should report as unhandled if message is empty" do
+ err = RuntimeError.new("")
+
+ err.full_message.should =~ /unhandled exception/
+ err.full_message(highlight: true).should =~ /unhandled exception/
+ err.full_message(highlight: false).should =~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is not empty" do
+ err = RuntimeError.new("non-empty")
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is nil" do
+ err = RuntimeError.new(nil)
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
+
+ it "should not report as unhandled if the message is not specified" do
+ err = RuntimeError.new()
+
+ err.full_message.should !~ /unhandled exception/
+ err.full_message(highlight: true).should !~ /unhandled exception/
+ err.full_message(highlight: false).should !~ /unhandled exception/
+ end
end
- it "supports :order option and places the error message and the backtrace at the top or the bottom" do
- e = RuntimeError.new("Some runtime error")
- e.set_backtrace(["a.rb:1", "b.rb:2"])
+ describe "generic Error" do
+ it "should not report as unhandled in any event" do
+ StandardError.new("").full_message.should !~ /unhandled exception/
+ StandardError.new("non-empty").full_message.should !~ /unhandled exception/
+ end
+ end
+ end
- e.full_message(order: :top, highlight: false).should =~ /a.rb:1.*b.rb:2/m
- e.full_message(order: :bottom, highlight: false).should =~ /b.rb:2.*a.rb:1/m
+ it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line"
+ rescue => e
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in ")
+ full_message[0].should.end_with?(": first line (RuntimeError)\n")
+ full_message[1].should == "second line\n"
end
+ end
- it "shows the caller if the exception has no backtrace" do
- e = RuntimeError.new("Some runtime error")
- e.backtrace.should == nil
- full_message = e.full_message(highlight: false, order: :top)
- full_message.should include("#{__FILE__}:#{__LINE__-1}:in `")
- full_message.should include("': Some runtime error (RuntimeError)\n")
+ it "highlights the entire message when the message contains multiple lines" do
+ begin
+ line = __LINE__; raise "first line\nsecond line\nthird line"
+ rescue => e
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.start_with?("#{__FILE__}:#{line}:in ")
+ full_message[0].should.end_with?(": \e[1mfirst line (\e[1;4mRuntimeError\e[m\e[1m)\e[m\n")
+ full_message[1].should == "\e[1msecond line\e[m\n"
+ full_message[2].should == "\e[1mthird line\e[m\n"
end
+ end
- it "shows the exception class at the end of the first line of the message when the message contains multiple lines" do
+ it "contains cause of exception" do
+ begin
begin
- line = __LINE__; raise "first line\nsecond line"
- rescue => e
- full_message = e.full_message(highlight: false, order: :top).lines
- full_message[0].should include("#{__FILE__}:#{line}:in `")
- full_message[0].should include(": first line (RuntimeError)\n")
- full_message[1].should == "second line\n"
+ raise 'the cause'
+ rescue
+ raise 'main exception'
end
+ rescue => e
+ exception = e
end
+
+ exception.full_message.should include "main exception"
+ exception.full_message.should include "the cause"
end
- ruby_version_is "2.6" do
- it "contains cause of exception" do
+ it 'contains all the chain of exceptions' do
+ begin
begin
begin
- raise 'the cause'
+ raise 'origin exception'
rescue
- raise 'main exception'
+ raise 'intermediate exception'
end
- rescue => e
- exception = e
+ rescue
+ raise 'last exception'
end
+ rescue => e
+ exception = e
+ end
- exception.full_message.should include "main exception"
- exception.full_message.should include "the cause"
+ exception.full_message.should include "last exception"
+ exception.full_message.should include "intermediate exception"
+ exception.full_message.should include "origin exception"
+ end
+
+ ruby_version_is "3.2" do
+ it "relies on #detailed_message" do
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| "DETAILED MESSAGE" }
+
+ e.full_message.lines.first.should =~ /DETAILED MESSAGE/
end
- it 'contains all the chain of exceptions' do
- begin
- begin
- begin
- raise 'origin exception'
- rescue
- raise 'intermediate exception'
- end
- rescue
- raise 'last exception'
- end
- rescue => e
- exception = e
+ it "passes all its own keyword arguments (with :highlight default value and without :order default value) to #detailed_message" do
+ e = RuntimeError.new("new error")
+ options_passed = nil
+ e.define_singleton_method(:detailed_message) do |**options|
+ options_passed = options
+ "DETAILED MESSAGE"
+ end
+
+ e.full_message(foo: "bar")
+ options_passed.should == { foo: "bar", highlight: Exception.to_tty? }
+ end
+
+ it "converts #detailed_message returned value to String if it isn't a String" do
+ message = Object.new
+ def message.to_str; "DETAILED MESSAGE"; end
+
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| message }
+
+ e.full_message.lines.first.should =~ /DETAILED MESSAGE/
+ end
+
+ it "uses class name if #detailed_message returns nil" do
+ e = RuntimeError.new("new error")
+ e.define_singleton_method(:detailed_message) { |**| nil }
+
+ e.full_message(highlight: false).lines.first.should =~ /RuntimeError/
+ e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/
+ end
+
+ it "uses class name if exception object doesn't respond to #detailed_message" do
+ e = RuntimeError.new("new error")
+ class << e
+ undef :detailed_message
end
- exception.full_message.should include "last exception"
- exception.full_message.should include "intermediate exception"
- exception.full_message.should include "origin exception"
+ e.full_message(highlight: false).lines.first.should =~ /RuntimeError/
+ e.full_message(highlight: true).lines.first.should =~ /#{Regexp.escape("\e[1;4mRuntimeError\e[m")}/
end
end
end
diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb
index a7501efadc..90d261e470 100644
--- a/spec/ruby/core/exception/interrupt_spec.rb
+++ b/spec/ruby/core/exception/interrupt_spec.rb
@@ -48,4 +48,13 @@ describe "Interrupt" do
RUBY
out.should == "Interrupt: #{Signal.list["INT"]}\n"
end
+
+ platform_is_not :windows do
+ it "shows the backtrace and has a signaled exit status" do
+ err = IO.popen([*ruby_exe, '-e', 'Process.kill :INT, Process.pid; sleep'], err: [:child, :out], &:read)
+ $?.termsig.should == Signal.list.fetch('INT')
+ err.should.include? ': Interrupt'
+ err.should =~ /from -e:1:in [`']<main>'/
+ end
+ end
end
diff --git a/spec/ruby/core/exception/key_error_spec.rb b/spec/ruby/core/exception/key_error_spec.rb
index 71bf2b46ff..c5e2b1efbc 100644
--- a/spec/ruby/core/exception/key_error_spec.rb
+++ b/spec/ruby/core/exception/key_error_spec.rb
@@ -1,21 +1,19 @@
require_relative '../../spec_helper'
describe "KeyError" do
- ruby_version_is "2.6" do
- it "accepts :receiver and :key options" do
- receiver = mock("receiver")
- key = mock("key")
+ it "accepts :receiver and :key options" do
+ receiver = mock("receiver")
+ key = mock("key")
- error = KeyError.new(receiver: receiver, key: key)
+ error = KeyError.new(receiver: receiver, key: key)
- error.receiver.should == receiver
- error.key.should == key
+ error.receiver.should == receiver
+ error.key.should == key
- error = KeyError.new("message", receiver: receiver, key: key)
+ error = KeyError.new("message", receiver: receiver, key: key)
- error.message.should == "message"
- error.receiver.should == receiver
- error.key.should == key
- end
+ error.message.should == "message"
+ error.receiver.should == receiver
+ error.key.should == key
end
end
diff --git a/spec/ruby/core/exception/name_error_spec.rb b/spec/ruby/core/exception/name_error_spec.rb
index e901a80c42..ddd51a92e5 100644
--- a/spec/ruby/core/exception/name_error_spec.rb
+++ b/spec/ruby/core/exception/name_error_spec.rb
@@ -5,15 +5,13 @@ describe "NameError.new" do
NameError.new("msg","name").name.should == "name"
end
- ruby_version_is "2.6" do
- it "accepts a :receiver keyword argument" do
- receiver = mock("receiver")
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
- error = NameError.new("msg", :name, receiver: receiver)
+ error = NameError.new("msg", :name, receiver: receiver)
- error.receiver.should == receiver
- error.name.should == :name
- end
+ error.receiver.should == receiver
+ error.name.should == :name
end
end
diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb
index 570ffc47b1..26df3338e9 100644
--- a/spec/ruby/core/exception/no_method_error_spec.rb
+++ b/spec/ruby/core/exception/no_method_error_spec.rb
@@ -10,15 +10,13 @@ describe "NoMethodError.new" do
NoMethodError.new("msg").message.should == "msg"
end
- ruby_version_is "2.6" do
- it "accepts a :receiver keyword argument" do
- receiver = mock("receiver")
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
- error = NoMethodError.new("msg", :name, receiver: receiver)
+ error = NoMethodError.new("msg", :name, receiver: receiver)
- error.receiver.should == receiver
- error.name.should == :name
- end
+ error.receiver.should == receiver
+ error.name.should == :name
end
end
@@ -64,26 +62,49 @@ describe "NoMethodError#message" do
NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method
rescue Exception => e
e.should be_kind_of(NoMethodError)
- e.message.lines[0].should =~ /private method `a_private_method' called for #<NoMethodErrorSpecs::NoMethodErrorC:0x[\h]+>/
+ e.message.lines[0].should =~ /private method [`']a_private_method' called for /
end
end
- it "calls receiver.inspect only when calling Exception#message" do
- ScratchPad.record []
- test_class = Class.new do
- def inspect
- ScratchPad << :inspect_called
- "<inspect>"
+ ruby_version_is ""..."3.3" do
+ it "calls receiver.inspect only when calling Exception#message" do
+ ScratchPad.record []
+ test_class = Class.new do
+ def inspect
+ ScratchPad << :inspect_called
+ "<inspect>"
+ end
+ end
+ instance = test_class.new
+ begin
+ instance.bar
+ rescue Exception => e
+ e.name.should == :bar
+ ScratchPad.recorded.should == []
+ e.message.should =~ /undefined method.+\bbar\b/
+ ScratchPad.recorded.should == [:inspect_called]
end
end
- instance = test_class.new
- begin
- instance.bar
- rescue Exception => e
- e.name.should == :bar
- ScratchPad.recorded.should == []
- e.message.should =~ /undefined method.+\bbar\b/
- ScratchPad.recorded.should == [:inspect_called]
+ end
+
+ ruby_version_is "3.3" do
+ it "does not call receiver.inspect even when calling Exception#message" do
+ ScratchPad.record []
+ test_class = Class.new do
+ def inspect
+ ScratchPad << :inspect_called
+ "<inspect>"
+ end
+ end
+ instance = test_class.new
+ begin
+ instance.bar
+ rescue Exception => e
+ e.name.should == :bar
+ ScratchPad.recorded.should == []
+ e.message.should =~ /undefined method.+\bbar\b/
+ ScratchPad.recorded.should == []
+ end
end
end
@@ -104,21 +125,19 @@ describe "NoMethodError#message" do
end
end
- ruby_version_is "3.0" do
- it "uses #name to display the receiver if it is a class or a module" do
- klass = Class.new { def self.name; "MyClass"; end }
- begin
- klass.foo
- rescue NoMethodError => error
- error.message.lines.first.should == "undefined method `foo' for MyClass:Class"
- end
+ it "uses #name to display the receiver if it is a class or a module" do
+ klass = Class.new { def self.name; "MyClass"; end }
+ begin
+ klass.foo
+ rescue NoMethodError => error
+ error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for /
+ end
- mod = Module.new { def self.name; "MyModule"; end }
- begin
- mod.foo
- rescue NoMethodError => error
- error.message.lines.first.should == "undefined method `foo' for MyModule:Module"
- end
+ mod = Module.new { def self.name; "MyModule"; end }
+ begin
+ mod.foo
+ rescue NoMethodError => error
+ error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for /
end
end
end
diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb
index ba2e1bf7aa..12c1da919c 100644
--- a/spec/ruby/core/exception/set_backtrace_spec.rb
+++ b/spec/ruby/core/exception/set_backtrace_spec.rb
@@ -11,9 +11,37 @@ describe "Exception#set_backtrace" do
it "allows the user to set the backtrace from a rescued exception" do
bt = ExceptionSpecs::Backtrace.backtrace
err = RuntimeError.new
+ err.backtrace.should == nil
+ err.backtrace_locations.should == nil
err.set_backtrace bt
+
err.backtrace.should == bt
+ err.backtrace_locations.should == nil
+ end
+
+ ruby_version_is "3.4" do
+ it "allows the user to set backtrace locations from a rescued exception" do
+ bt_locations = ExceptionSpecs::Backtrace.backtrace_locations
+ err = RuntimeError.new
+ err.backtrace.should == nil
+ err.backtrace_locations.should == nil
+
+ err.set_backtrace bt_locations
+
+ err.backtrace_locations.size.should == bt_locations.size
+ err.backtrace_locations.each_with_index do |loc, index|
+ other_loc = bt_locations[index]
+
+ loc.path.should == other_loc.path
+ loc.label.should == other_loc.label
+ loc.base_label.should == other_loc.base_label
+ loc.lineno.should == other_loc.lineno
+ loc.absolute_path.should == other_loc.absolute_path
+ loc.to_s.should == other_loc.to_s
+ end
+ err.backtrace.size.should == err.backtrace_locations.size
+ end
end
it "accepts an empty Array" do
diff --git a/spec/ruby/core/exception/signal_exception_spec.rb b/spec/ruby/core/exception/signal_exception_spec.rb
index e494e18cde..1a0940743f 100644
--- a/spec/ruby/core/exception/signal_exception_spec.rb
+++ b/spec/ruby/core/exception/signal_exception_spec.rb
@@ -30,10 +30,8 @@ describe "SignalException.new" do
-> { SignalException.new("NONEXISTENT") }.should raise_error(ArgumentError)
end
- ruby_version_is "2.6" do
- it "raises an exception with an invalid first argument type" do
- -> { SignalException.new(Object.new) }.should raise_error(ArgumentError)
- end
+ it "raises an exception with an invalid first argument type" do
+ -> { SignalException.new(Object.new) }.should raise_error(ArgumentError)
end
it "takes a signal symbol without SIG prefix as the first argument" do
@@ -95,7 +93,7 @@ describe "SignalException" do
platform_is_not :windows do
it "runs after at_exit" do
- output = ruby_exe(<<-RUBY)
+ output = ruby_exe(<<-RUBY, exit_status: :SIGKILL)
at_exit do
puts "hello"
$stdout.flush
@@ -109,7 +107,7 @@ describe "SignalException" do
end
it "cannot be trapped with Signal.trap" do
- ruby_exe(<<-RUBY)
+ ruby_exe(<<-RUBY, exit_status: :SIGPROF)
Signal.trap("PROF") {}
raise(SignalException, "PROF")
RUBY
@@ -118,7 +116,7 @@ describe "SignalException" do
end
it "self-signals for USR1" do
- ruby_exe("raise(SignalException, 'USR1')")
+ ruby_exe("raise(SignalException, 'USR1')", exit_status: :SIGUSR1)
$?.termsig.should == Signal.list.fetch('USR1')
end
end
diff --git a/spec/ruby/core/exception/syntax_error_spec.rb b/spec/ruby/core/exception/syntax_error_spec.rb
new file mode 100644
index 0000000000..6cc8522de3
--- /dev/null
+++ b/spec/ruby/core/exception/syntax_error_spec.rb
@@ -0,0 +1,27 @@
+require_relative '../../spec_helper'
+
+ruby_version_is "3.2" do
+ describe "SyntaxError#path" do
+ it "returns the file path provided to eval" do
+ filename = "speccing.rb"
+
+ -> {
+ eval("if true", TOPLEVEL_BINDING, filename)
+ }.should raise_error(SyntaxError) { |e|
+ e.path.should == filename
+ }
+ end
+
+ it "returns the file path that raised an exception" do
+ expected_path = fixture(__FILE__, "syntax_error.rb")
+
+ -> {
+ require_relative "fixtures/syntax_error"
+ }.should raise_error(SyntaxError) { |e| e.path.should == expected_path }
+ end
+
+ it "returns nil when constructed directly" do
+ SyntaxError.new.path.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb
index 5c6116576b..d899844c4e 100644
--- a/spec/ruby/core/exception/system_exit_spec.rb
+++ b/spec/ruby/core/exception/system_exit_spec.rb
@@ -1,16 +1,58 @@
require_relative '../../spec_helper'
describe "SystemExit" do
+ describe "#initialize" do
+ it "accepts a status and message" do
+ exc = SystemExit.new(42, "message")
+ exc.status.should == 42
+ exc.message.should == "message"
+
+ exc = SystemExit.new(true, "message")
+ exc.status.should == 0
+ exc.message.should == "message"
+
+ exc = SystemExit.new(false, "message")
+ exc.status.should == 1
+ exc.message.should == "message"
+ end
+
+ it "accepts a status only" do
+ exc = SystemExit.new(42)
+ exc.status.should == 42
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(true)
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+
+ exc = SystemExit.new(false)
+ exc.status.should == 1
+ exc.message.should == "SystemExit"
+ end
+
+ it "accepts a message only" do
+ exc = SystemExit.new("message")
+ exc.status.should == 0
+ exc.message.should == "message"
+ end
+
+ it "accepts no arguments" do
+ exc = SystemExit.new
+ exc.status.should == 0
+ exc.message.should == "SystemExit"
+ end
+ end
+
it "sets the exit status and exits silently when raised" do
code = 'raise SystemExit.new(7)'
- result = ruby_exe(code, args: "2>&1")
+ result = ruby_exe(code, args: "2>&1", exit_status: 7)
result.should == ""
$?.exitstatus.should == 7
end
it "sets the exit status and exits silently when raised when subclassed" do
code = 'class CustomExit < SystemExit; end; raise CustomExit.new(8)'
- result = ruby_exe(code, args: "2>&1")
+ result = ruby_exe(code, args: "2>&1", exit_status: 8)
result.should == ""
$?.exitstatus.should == 8
end
diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb
index 4c4c7ab432..65c0d73a98 100644
--- a/spec/ruby/core/exception/to_s_spec.rb
+++ b/spec/ruby/core/exception/to_s_spec.rb
@@ -23,7 +23,7 @@ describe "NameError#to_s" do
begin
puts not_defined
rescue => exception
- exception.message.should =~ /undefined local variable or method `not_defined'/
+ exception.message.should =~ /undefined local variable or method [`']not_defined'/
end
end
diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb
index 501c7253c3..cc961d06d5 100644
--- a/spec/ruby/core/exception/top_level_spec.rb
+++ b/spec/ruby/core/exception/top_level_spec.rb
@@ -2,32 +2,38 @@ require_relative '../../spec_helper'
describe "An Exception reaching the top level" do
it "is printed on STDERR" do
- ruby_exe('raise "foo"', args: "2>&1").should.include?("in `<main>': foo (RuntimeError)")
+ ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should =~ /in [`']<main>': foo \(RuntimeError\)/
end
- ruby_version_is "2.6" do
- it "the Exception#cause is printed to STDERR with backtraces" do
- code = <<-RUBY
- def raise_cause
- raise "the cause"
- end
- def raise_wrapped
- raise "wrapped"
- end
- begin
- raise_cause
- rescue
- raise_wrapped
- end
- RUBY
- lines = ruby_exe(code, args: "2>&1").lines
- lines.reject! { |l| l.include?('rescue in') }
- lines.map! { |l| l.chomp[/:(in.+)/, 1] }
- lines.should == ["in `raise_wrapped': wrapped (RuntimeError)",
- "in `<main>'",
- "in `raise_cause': the cause (RuntimeError)",
- "in `<main>'"]
+ it "the Exception#cause is printed to STDERR with backtraces" do
+ code = <<-RUBY
+ def raise_cause
+ raise "the cause" # 2
+ end
+ def raise_wrapped
+ raise "wrapped" # 5
+ end
+ begin
+ raise_cause # 8
+ rescue
+ raise_wrapped # 10
+ end
+ RUBY
+ lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines
+
+ lines.map! { |l| l.chomp[/:(\d+:in.+)/, 1] }
+ lines[0].should =~ /\A5:in [`'](?:Object#)?raise_wrapped': wrapped \(RuntimeError\)\z/
+ if lines[1].include? 'rescue in'
+ # CRuby < 3.4 has an extra 'rescue in' backtrace entry
+ lines[1].should =~ /\A10:in [`']rescue in <main>'\z/
+ lines.delete_at 1
+ lines[1].should =~ /\A7:in [`']<main>'\z/
+ else
+ lines[1].should =~ /\A10:in [`']<main>'\z/
end
+ lines[2].should =~ /\A2:in [`'](?:Object#)?raise_cause': the cause \(RuntimeError\)\z/
+ lines[3].should =~ /\A8:in [`']<main>'\z/
+ lines.size.should == 4
end
describe "with a custom backtrace" do
@@ -38,10 +44,22 @@ describe "An Exception reaching the top level" do
"/dir/bar.rb:20:in `caller'",
]
RUBY
- ruby_exe(code, args: "2>&1").should == <<-EOS
+ ruby_exe(code, args: "2>&1", exit_status: 1).should == <<-EOS
/dir/foo.rb:10:in `raising': foo (RuntimeError)
\tfrom /dir/bar.rb:20:in `caller'
EOS
end
end
+
+ describe "kills all threads and fibers, ensure clauses are only run for threads current fibers, not for suspended fibers" do
+ it "with ensure on the root fiber" do
+ file = fixture(__FILE__, "thread_fiber_ensure.rb")
+ ruby_exe(file, args: "2>&1", exit_status: 0).should == "current fiber ensure\n"
+ end
+
+ it "with ensure on non-root fiber" do
+ file = fixture(__FILE__, "thread_fiber_ensure_non_root_fiber.rb")
+ ruby_exe(file, args: "2>&1", exit_status: 0).should == "current fiber ensure\n"
+ end
+ end
end