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/args_spec.rb5
-rw-r--r--spec/ruby/core/exception/arguments_spec.rb11
-rw-r--r--spec/ruby/core/exception/backtrace_locations_spec.rb39
-rw-r--r--spec/ruby/core/exception/backtrace_spec.rb58
-rw-r--r--spec/ruby/core/exception/case_compare_spec.rb36
-rw-r--r--spec/ruby/core/exception/cause_spec.rb39
-rw-r--r--spec/ruby/core/exception/destination_encoding_name_spec.rb9
-rw-r--r--spec/ruby/core/exception/destination_encoding_spec.rb9
-rw-r--r--spec/ruby/core/exception/detailed_message_spec.rb50
-rw-r--r--spec/ruby/core/exception/dup_spec.rb74
-rw-r--r--spec/ruby/core/exception/equal_value_spec.rb18
-rw-r--r--spec/ruby/core/exception/errno_spec.rb33
-rw-r--r--spec/ruby/core/exception/error_bytes_spec.rb5
-rw-r--r--spec/ruby/core/exception/error_char_spec.rb5
-rw-r--r--spec/ruby/core/exception/exception_spec.rb78
-rw-r--r--spec/ruby/core/exception/exit_value_spec.rb12
-rw-r--r--spec/ruby/core/exception/fixtures/common.rb40
-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.rb54
-rw-r--r--spec/ruby/core/exception/full_message_spec.rb226
-rw-r--r--spec/ruby/core/exception/hierarchy_spec.rb62
-rw-r--r--spec/ruby/core/exception/incomplete_input_spec.rb5
-rw-r--r--spec/ruby/core/exception/initialize_spec.rb1
-rw-r--r--spec/ruby/core/exception/inspect_spec.rb8
-rw-r--r--spec/ruby/core/exception/interrupt_spec.rb37
-rw-r--r--spec/ruby/core/exception/io_error_spec.rb8
-rw-r--r--spec/ruby/core/exception/key_error_spec.rb19
-rw-r--r--spec/ruby/core/exception/load_error_spec.rb2
-rw-r--r--spec/ruby/core/exception/message_spec.rb4
-rw-r--r--spec/ruby/core/exception/name_error_spec.rb29
-rw-r--r--spec/ruby/core/exception/name_spec.rb44
-rw-r--r--spec/ruby/core/exception/new_spec.rb8
-rw-r--r--spec/ruby/core/exception/no_method_error_spec.rb234
-rw-r--r--spec/ruby/core/exception/range_error_spec.rb7
-rw-r--r--spec/ruby/core/exception/readagain_bytes_spec.rb5
-rw-r--r--spec/ruby/core/exception/reason_spec.rb12
-rw-r--r--spec/ruby/core/exception/receiver_spec.rb116
-rw-r--r--spec/ruby/core/exception/result_spec.rb16
-rw-r--r--spec/ruby/core/exception/script_error_spec.rb15
-rw-r--r--spec/ruby/core/exception/set_backtrace_spec.rb55
-rw-r--r--spec/ruby/core/exception/shared/set_backtrace.rb64
-rw-r--r--spec/ruby/core/exception/signal_exception_spec.rb61
-rw-r--r--spec/ruby/core/exception/signm_spec.rb8
-rw-r--r--spec/ruby/core/exception/signo_spec.rb8
-rw-r--r--spec/ruby/core/exception/source_encoding_name_spec.rb9
-rw-r--r--spec/ruby/core/exception/source_encoding_spec.rb9
-rw-r--r--spec/ruby/core/exception/standard_error_spec.rb57
-rw-r--r--spec/ruby/core/exception/status_spec.rb8
-rw-r--r--spec/ruby/core/exception/success_spec.rb14
-rw-r--r--spec/ruby/core/exception/syntax_error_spec.rb25
-rw-r--r--spec/ruby/core/exception/system_call_error_spec.rb122
-rw-r--r--spec/ruby/core/exception/system_exit_spec.rb59
-rw-r--r--spec/ruby/core/exception/system_stack_error_spec.rb7
-rw-r--r--spec/ruby/core/exception/to_s_spec.rb20
-rw-r--r--spec/ruby/core/exception/top_level_spec.rb65
-rw-r--r--spec/ruby/core/exception/uncaught_throw_error_spec.rb9
58 files changed, 1627 insertions, 456 deletions
diff --git a/spec/ruby/core/exception/args_spec.rb b/spec/ruby/core/exception/args_spec.rb
deleted file mode 100644
index 410e21edfb..0000000000
--- a/spec/ruby/core/exception/args_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "NoMethodError#args" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/arguments_spec.rb b/spec/ruby/core/exception/arguments_spec.rb
deleted file mode 100644
index 47c4339e2d..0000000000
--- a/spec/ruby/core/exception/arguments_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "ArgumentError" do
- it "is a subclass of StandardError" do
- StandardError.should be_ancestor_of(ArgumentError)
- end
-
- it "gives its own class name as message if it has no message" do
- ArgumentError.new.message.should == "ArgumentError"
- end
-end
diff --git a/spec/ruby/core/exception/backtrace_locations_spec.rb b/spec/ruby/core/exception/backtrace_locations_spec.rb
new file mode 100644
index 0000000000..86eb9d3413
--- /dev/null
+++ b/spec/ruby/core/exception/backtrace_locations_spec.rb
@@ -0,0 +1,39 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#backtrace_locations" do
+ before :each do
+ @backtrace = ExceptionSpecs::Backtrace.backtrace_locations
+ end
+
+ it "returns nil if no backtrace was set" do
+ Exception.new.backtrace_locations.should be_nil
+ end
+
+ it "returns an Array" do
+ @backtrace.should be_an_instance_of(Array)
+ end
+
+ it "sets each element to a Thread::Backtrace::Location" do
+ @backtrace.each {|l| l.should be_an_instance_of(Thread::Backtrace::Location)}
+ end
+
+ it "produces a backtrace for an exception captured using $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.backtrace_locations.first.path.should =~ /backtrace_locations_spec/
+ end
+
+ it "returns an Array that can be updated" do
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations.unshift "backtrace first"
+ e.backtrace_locations[0].should == "backtrace first"
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb
index 2d115e9b2f..9a65ea3820 100644
--- a/spec/ruby/core/exception/backtrace_spec.rb
+++ b/spec/ruby/core/exception/backtrace_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Exception#backtrace" do
before :each do
@@ -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,16 +38,29 @@ 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
- it "produces a backtrace for an exception captured using $!" do
+ 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
+
+ it "captures the backtrace for an exception into $!" do
exception = begin
raise
rescue RuntimeError
@@ -57,6 +70,16 @@ describe "Exception#backtrace" do
exception.backtrace.first.should =~ /backtrace_spec/
end
+ it "captures the backtrace for an exception into $@" do
+ backtrace = begin
+ raise
+ rescue RuntimeError
+ $@
+ end
+
+ backtrace.first.should =~ /backtrace_spec/
+ end
+
it "returns an Array that can be updated" do
begin
raise
@@ -65,4 +88,19 @@ describe "Exception#backtrace" do
e.backtrace[0].should == "backtrace first"
end
end
+
+ it "returns the same array after duping" do
+ begin
+ raise
+ rescue RuntimeError => err
+ bt = err.backtrace
+ err.dup.backtrace.should equal(bt)
+
+ new_bt = ['hi']
+ err.set_backtrace new_bt
+
+ err.backtrace.should == new_bt
+ err.dup.backtrace.should equal(new_bt)
+ end
+ end
end
diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb
index 7592564855..5fd11ae741 100644
--- a/spec/ruby/core/exception/case_compare_spec.rb
+++ b/spec/ruby/core/exception/case_compare_spec.rb
@@ -1,5 +1,37 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SystemCallError.===" do
- it "needs to be reviewed for spec completeness"
+ before :all do
+ @example_errno_class = Errno::EINVAL
+ @example_errno = @example_errno_class::Errno
+ end
+
+ it "returns true for an instance of the same class" do
+ Errno::EINVAL.should === Errno::EINVAL.new
+ end
+
+ it "returns true if errnos same" do
+ e = SystemCallError.new('foo', @example_errno)
+ @example_errno_class.===(e).should == true
+ end
+
+ it "returns false if errnos different" do
+ e = SystemCallError.new('foo', @example_errno + 1)
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns false if arg is not kind of SystemCallError" do
+ e = Object.new
+ @example_errno_class.===(e).should == false
+ end
+
+ it "returns true if receiver is generic and arg is kind of SystemCallError" do
+ 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
+ e = Object.new
+ SystemCallError.===(e).should == false
+ end
end
diff --git a/spec/ruby/core/exception/cause_spec.rb b/spec/ruby/core/exception/cause_spec.rb
index a1aa39ae34..cf4aaeb188 100644
--- a/spec/ruby/core/exception/cause_spec.rb
+++ b/spec/ruby/core/exception/cause_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Exception#cause" do
it "returns the active exception when an exception is raised" do
@@ -16,4 +16,41 @@ describe "Exception#cause" do
end
end
end
+
+ it "is set for user errors caused by internal errors" do
+ -> {
+ begin
+ 1 / 0
+ rescue
+ raise "foo"
+ end
+ }.should raise_error(RuntimeError) { |e|
+ e.cause.should be_kind_of(ZeroDivisionError)
+ }
+ end
+
+ it "is set for internal errors caused by user errors" do
+ cause = RuntimeError.new "cause"
+ -> {
+ begin
+ raise cause
+ rescue
+ 1 / 0
+ end
+ }.should raise_error(ZeroDivisionError) { |e|
+ e.cause.should equal(cause)
+ }
+ end
+
+ it "is not set to the exception itself when it is re-raised" do
+ -> {
+ begin
+ raise RuntimeError
+ rescue RuntimeError => e
+ raise e
+ end
+ }.should raise_error(RuntimeError) { |e|
+ e.cause.should == nil
+ }
+ end
end
diff --git a/spec/ruby/core/exception/destination_encoding_name_spec.rb b/spec/ruby/core/exception/destination_encoding_name_spec.rb
deleted file mode 100644
index d6a01c3220..0000000000
--- a/spec/ruby/core/exception/destination_encoding_name_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::UndefinedConversionError#destination_encoding_name" do
- it "needs to be reviewed for spec completeness"
-end
-
-describe "Encoding::InvalidByteSequenceError#destination_encoding_name" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/destination_encoding_spec.rb b/spec/ruby/core/exception/destination_encoding_spec.rb
deleted file mode 100644
index 09064a01f3..0000000000
--- a/spec/ruby/core/exception/destination_encoding_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::UndefinedConversionError#destination_encoding" do
- it "needs to be reviewed for spec completeness"
-end
-
-describe "Encoding::InvalidByteSequenceError#destination_encoding" do
- it "needs to be reviewed for spec completeness"
-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..9df164a1cf
--- /dev/null
+++ b/spec/ruby/core/exception/detailed_message_spec.rb
@@ -0,0 +1,50 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#detailed_message" 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 "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 other than RuntimeError with empty message" do
+ DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C"
+ StandardError.new("").detailed_message.should == "StandardError"
+ 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
+
+ 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 "accepts highlight keyword argument and adds escape control sequences for an instance of RuntimeError with empty message" do
+ RuntimeError.new("").detailed_message(highlight: true).should == "\e[1;4munhandled exception\e[m"
+ end
+
+ it "accepts highlight keyword argument and adds escape control sequences for an instance other than RuntimeError with empty message" do
+ StandardError.new("").detailed_message(highlight: true).should == "\e[1;4mStandardError\e[m"
+ end
+
+ it "allows and ignores other keyword arguments" do
+ RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)"
+ end
+end
diff --git a/spec/ruby/core/exception/dup_spec.rb b/spec/ruby/core/exception/dup_spec.rb
new file mode 100644
index 0000000000..edd54bfb37
--- /dev/null
+++ b/spec/ruby/core/exception/dup_spec.rb
@@ -0,0 +1,74 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "Exception#dup" do
+ before :each do
+ @obj = ExceptionSpecs::InitializeException.new("my exception")
+ end
+
+ it "calls #initialize_copy on the new instance" do
+ dup = @obj.dup
+ ScratchPad.recorded.should_not == @obj.object_id
+ ScratchPad.recorded.should == dup.object_id
+ end
+
+ it "copies instance variables" do
+ dup = @obj.dup
+ dup.ivar.should == 1
+ end
+
+ it "does not copy singleton methods" do
+ def @obj.special() :the_one end
+ dup = @obj.dup
+ -> { dup.special }.should raise_error(NameError)
+ end
+
+ it "does not copy modules included in the singleton class" do
+ class << @obj
+ include ExceptionSpecs::ExceptionModule
+ end
+
+ dup = @obj.dup
+ -> { dup.repr }.should raise_error(NameError)
+ end
+
+ it "does not copy constants defined in the singleton class" do
+ class << @obj
+ CLONE = :clone
+ end
+
+ dup = @obj.dup
+ -> { class << dup; CLONE; end }.should raise_error(NameError)
+ end
+
+ it "does copy the message" do
+ @obj.dup.message.should == @obj.message
+ end
+
+ it "does copy the backtrace" do
+ begin
+ # Explicitly raise so a backtrace is associated with the exception.
+ # It's tempting to call `set_backtrace` instead, but that complicates
+ # the test because it might affect other state (e.g., instance variables)
+ # on some implementations.
+ raise ExceptionSpecs::InitializeException.new("my exception")
+ rescue => e
+ @obj = e
+ end
+
+ @obj.dup.backtrace.should == @obj.backtrace
+ end
+
+ it "does copy the cause" do
+ begin
+ raise StandardError, "the cause"
+ rescue StandardError => cause
+ begin
+ raise RuntimeError, "the consequence"
+ rescue RuntimeError => e
+ e.cause.should equal(cause)
+ e.dup.cause.should equal(cause)
+ end
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb
index 3aad809377..e8f3ce0f8d 100644
--- a/spec/ruby/core/exception/equal_value_spec.rb
+++ b/spec/ruby/core/exception/equal_value_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Exception#==" do
it "returns true if both exceptions are the same object" do
@@ -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 f7f5b45d8a..1ab4277700 100644
--- a/spec/ruby/core/exception/errno_spec.rb
+++ b/spec/ruby/core/exception/errno_spec.rb
@@ -1,9 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
-
-describe "SystemCallError#errno" do
- it "needs to be reviewed for spec completeness"
-end
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Errno::EINVAL.new" do
it "can be called with no arguments" do
@@ -33,6 +29,8 @@ describe "Errno::EMFILE" do
ExceptionSpecs::EMFILESub = Class.new(Errno::EMFILE)
exc = ExceptionSpecs::EMFILESub.new
exc.should be_an_instance_of(ExceptionSpecs::EMFILESub)
+ ensure
+ ExceptionSpecs.send(:remove_const, :EMFILESub)
end
end
@@ -46,3 +44,26 @@ describe "Errno::EAGAIN" do
end
end
end
+
+describe "Errno::ENOTSUP" do
+ it "is defined" do
+ Errno.should have_constant(:ENOTSUP)
+ end
+
+ it "is the same class as Errno::EOPNOTSUPP if they represent the same errno value" do
+ if Errno::ENOTSUP::Errno == Errno::EOPNOTSUPP::Errno
+ Errno::ENOTSUP.should == Errno::EOPNOTSUPP
+ else
+ Errno::ENOTSUP.should_not == Errno::EOPNOTSUPP
+ 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/error_bytes_spec.rb b/spec/ruby/core/exception/error_bytes_spec.rb
deleted file mode 100644
index e5027d0cf3..0000000000
--- a/spec/ruby/core/exception/error_bytes_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::InvalidByteSequenceError#error_bytes" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/error_char_spec.rb b/spec/ruby/core/exception/error_char_spec.rb
deleted file mode 100644
index 8842424e90..0000000000
--- a/spec/ruby/core/exception/error_char_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::UndefinedConversionError#error_char" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/exception_spec.rb b/spec/ruby/core/exception/exception_spec.rb
index afa482b9d7..d6f5283bd9 100644
--- a/spec/ruby/core/exception/exception_spec.rb
+++ b/spec/ruby/core/exception/exception_spec.rb
@@ -1,51 +1,9 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
-require File.expand_path('../shared/new', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
describe "Exception.exception" do
- it_behaves_like(:exception_new, :exception)
-end
-
-describe "Exception" do
- it "is a Class" do
- Exception.should be_kind_of(Class)
- end
-
- it "is a superclass of NoMemoryError" do
- Exception.should be_ancestor_of(NoMemoryError)
- end
-
- it "is a superclass of ScriptError" do
- Exception.should be_ancestor_of(ScriptError)
- end
-
- it "is a superclass of SignalException" do
- Exception.should be_ancestor_of(SignalException)
- end
-
- it "is a superclass of Interrupt" do
- SignalException.should be_ancestor_of(Interrupt)
- end
-
- it "is a superclass of StandardError" do
- Exception.should be_ancestor_of(StandardError)
- end
-
- it "is a superclass of SystemExit" do
- Exception.should be_ancestor_of(SystemExit)
- end
-
- it "is a superclass of SystemStackError" do
- Exception.should be_ancestor_of(SystemStackError)
- end
-
- it "is a superclass of SecurityError" do
- Exception.should be_ancestor_of(SecurityError)
- end
-
- it "is a superclass of EncodingError" do
- Exception.should be_ancestor_of(EncodingError)
- end
+ it_behaves_like :exception_new, :exception
end
describe "Exception#exception" do
@@ -66,6 +24,34 @@ describe "Exception#exception" do
e2.message.should == "message"
end
+ it "when raised will be rescued as the new exception" do
+ begin
+ begin
+ raised_first = StandardError.new('first')
+ raise raised_first
+ rescue => caught_first
+ raised_second = raised_first.exception('second')
+ raise raised_second
+ end
+ rescue => caught_second
+ end
+
+ raised_first.should == caught_first
+ raised_second.should == caught_second
+ end
+
+ it "captures an exception into $!" do
+ exception = begin
+ raise
+ rescue RuntimeError
+ $!
+ end
+
+ exception.class.should == RuntimeError
+ exception.message.should == ""
+ exception.backtrace.first.should =~ /exception_spec/
+ end
+
class CustomArgumentError < StandardError
attr_reader :val
def initialize(val)
diff --git a/spec/ruby/core/exception/exit_value_spec.rb b/spec/ruby/core/exception/exit_value_spec.rb
index daa5eb0b94..99987dd1bc 100644
--- a/spec/ruby/core/exception/exit_value_spec.rb
+++ b/spec/ruby/core/exception/exit_value_spec.rb
@@ -1,5 +1,13 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "LocalJumpError#exit_value" do
- it "needs to be reviewed for spec completeness"
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns the value given to return" do
+ -> { get_me_a_return.call }.should raise_error(LocalJumpError) { |e|
+ e.exit_value.should == 42
+ }
+ end
end
diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb
index 51dd0bf9ed..3d8a3c3430 100644
--- a/spec/ruby/core/exception/fixtures/common.rb
+++ b/spec/ruby/core/exception/fixtures/common.rb
@@ -4,11 +4,19 @@ module ExceptionSpecs
class Backtrace
def self.backtrace
begin
- raise # Do not move this line or update backtrace_spec.rb
+ raise # If you move this line, update backtrace_spec.rb
rescue RuntimeError => e
e.backtrace
end
end
+
+ def self.backtrace_locations
+ begin
+ raise
+ rescue RuntimeError => e
+ e.backtrace_locations
+ end
+ end
end
class UnExceptional < Exception
@@ -38,6 +46,26 @@ module ExceptionSpecs
""
end
end
+
+ class InitializeException < StandardError
+ attr_reader :ivar
+
+ def initialize(message = nil)
+ super
+ @ivar = 1
+ end
+
+ def initialize_copy(other)
+ super
+ ScratchPad.record object_id
+ end
+ end
+
+ module ExceptionModule
+ def repr
+ 1
+ end
+ end
end
module NoMethodErrorSpecs
@@ -53,6 +81,12 @@ module NoMethodErrorSpecs
end
class NoMethodErrorD; end
+
+ class InstanceException < Exception
+ end
+
+ class AClass; end
+ module AModule; end
end
class NameErrorSpecs
@@ -62,3 +96,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
new file mode 100644
index 0000000000..af2e925661
--- /dev/null
+++ b/spec/ruby/core/exception/frozen_error_spec.rb
@@ -0,0 +1,54 @@
+require_relative '../../spec_helper'
+
+describe "FrozenError.new" do
+ 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
+ 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 "FrozenError#message" do
+ it "includes a receiver" do
+ object = Object.new
+ object.freeze
+
+ msg_class = ruby_version_is("4.0") ? "Object" : "object"
+
+ -> {
+ def object.x; end
+ }.should raise_error(FrozenError, "can't modify frozen #{msg_class}: #{object}")
+
+ object = [].freeze
+ -> { object << nil }.should raise_error(FrozenError, "can't modify frozen Array: []")
+ 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
new file mode 100644
index 0000000000..0761d2b40c
--- /dev/null
+++ b/spec/ruby/core/exception/full_message_spec.rb
@@ -0,0 +1,226 @@
+require_relative '../../spec_helper'
+
+describe "Exception#full_message" do
+ it "returns formatted string of exception using the same format that is used to print an uncaught exceptions to stderr" do
+ e = RuntimeError.new("Some runtime error")
+ e.set_backtrace(["a.rb:1", "b.rb:2"])
+
+ full_message = e.full_message
+ full_message.should include "RuntimeError"
+ full_message.should include "Some runtime error"
+ full_message.should include "a.rb:1"
+ full_message.should include "b.rb:2"
+ end
+
+ 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: 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
+
+ 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
+
+ it "adds escape sequences to highlight some strings if the message is not specified and :highlight option is specified" do
+ e = RuntimeError.new("")
+
+ full_message = e.full_message(highlight: true, order: :top).lines
+ full_message[0].should.end_with? "\e[1;4munhandled exception\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[1;4munhandled exception\e[m\n"
+
+ full_message = e.full_message(highlight: false, order: :top).lines
+ full_message[0].should.end_with? "unhandled exception\n"
+
+ 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? "unhandled exception\n"
+ end
+ end
+
+ 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
+
+ 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 "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 "contains cause of exception" do
+ begin
+ begin
+ 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
+
+ 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
+ end
+
+ exception.full_message.should include "last exception"
+ exception.full_message.should include "intermediate exception"
+ exception.full_message.should include "origin exception"
+ end
+
+ 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 "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
+
+ 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 "allows cause with empty backtrace" do
+ begin
+ raise RuntimeError.new("Some runtime error"), cause: RuntimeError.new("Some other runtime error")
+ rescue => e
+ end
+
+ full_message = e.full_message
+ full_message.should include "RuntimeError"
+ full_message.should include "Some runtime error"
+ full_message.should include "Some other runtime error"
+ end
+end
diff --git a/spec/ruby/core/exception/hierarchy_spec.rb b/spec/ruby/core/exception/hierarchy_spec.rb
new file mode 100644
index 0000000000..6514eb1994
--- /dev/null
+++ b/spec/ruby/core/exception/hierarchy_spec.rb
@@ -0,0 +1,62 @@
+require_relative '../../spec_helper'
+
+describe "Exception" do
+ it "has the right class hierarchy" do
+ hierarchy = {
+ Exception => {
+ NoMemoryError => nil,
+ ScriptError => {
+ LoadError => nil,
+ NotImplementedError => nil,
+ SyntaxError => nil,
+ },
+ SecurityError => nil,
+ SignalException => {
+ Interrupt => nil,
+ },
+ StandardError => {
+ ArgumentError => {
+ UncaughtThrowError => nil,
+ },
+ EncodingError => nil,
+ FiberError => nil,
+ IOError => {
+ EOFError => nil,
+ },
+ IndexError => {
+ KeyError => nil,
+ StopIteration => {
+ ClosedQueueError => nil,
+ },
+ },
+ LocalJumpError => nil,
+ NameError => {
+ NoMethodError => nil,
+ },
+ RangeError => {
+ FloatDomainError => nil,
+ },
+ RegexpError => nil,
+ RuntimeError => {
+ FrozenError => nil,
+ },
+ SystemCallError => nil,
+ ThreadError => nil,
+ TypeError => nil,
+ ZeroDivisionError => nil,
+ },
+ SystemExit => nil,
+ SystemStackError => nil,
+ },
+ }
+
+ traverse = -> parent_class, parent_subclass_hash {
+ parent_subclass_hash.each do |child_class, child_subclass_hash|
+ child_class.class.should == Class
+ child_class.superclass.should == parent_class
+ traverse.call(child_class, child_subclass_hash) if child_subclass_hash
+ end
+ }
+ traverse.call(Object, hierarchy)
+ end
+end
diff --git a/spec/ruby/core/exception/incomplete_input_spec.rb b/spec/ruby/core/exception/incomplete_input_spec.rb
deleted file mode 100644
index a64d4be3f3..0000000000
--- a/spec/ruby/core/exception/incomplete_input_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::InvalidByteSequenceError#incomplete_input?" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/initialize_spec.rb b/spec/ruby/core/exception/initialize_spec.rb
deleted file mode 100644
index 14fb93ef07..0000000000
--- a/spec/ruby/core/exception/initialize_spec.rb
+++ /dev/null
@@ -1 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
diff --git a/spec/ruby/core/exception/inspect_spec.rb b/spec/ruby/core/exception/inspect_spec.rb
index 5b06ffee71..6f380a36c7 100644
--- a/spec/ruby/core/exception/inspect_spec.rb
+++ b/spec/ruby/core/exception/inspect_spec.rb
@@ -1,11 +1,15 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Exception#inspect" do
it "returns '#<Exception: Exception>' when no message given" do
Exception.new.inspect.should == "#<Exception: Exception>"
end
+ it "keeps message encoding" do
+ Exception.new('å').inspect.should == "#<Exception: å>"
+ end
+
it "includes #to_s when the result is non-empty" do
ExceptionSpecs::OverrideToS.new.inspect.should == "#<ExceptionSpecs::OverrideToS: this is from #to_s>"
end
diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb
index ef24743936..90d261e470 100644
--- a/spec/ruby/core/exception/interrupt_spec.rb
+++ b/spec/ruby/core/exception/interrupt_spec.rb
@@ -1,10 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Interrupt" do
- it "is a subclass of SignalException" do
- Interrupt.superclass.should == SignalException
- end
-end
+require_relative '../../spec_helper'
describe "Interrupt.new" do
it "returns an instance of interrupt with no message given" do
@@ -20,7 +14,7 @@ describe "Interrupt.new" do
end
end
-describe "rescueing Interrupt" do
+describe "rescuing Interrupt" do
before do
@original_sigint_proc = Signal.trap(:INT, :SIG_DFL)
end
@@ -35,7 +29,32 @@ describe "rescueing Interrupt" do
sleep
rescue Interrupt => e
e.signo.should == Signal.list["INT"]
- e.signm.should == ""
+ ["", "Interrupt"].should.include?(e.message)
+ end
+ end
+end
+
+describe "Interrupt" do
+ # This spec is basically the same as above,
+ # but it does not rely on Signal.trap(:INT, :SIG_DFL) which can be tricky
+ it "is raised on the main Thread by the default SIGINT handler" do
+ out = ruby_exe(<<-'RUBY', args: "2>&1")
+ begin
+ Process.kill :INT, Process.pid
+ sleep
+ rescue Interrupt => e
+ puts "Interrupt: #{e.signo}"
+ end
+ 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/io_error_spec.rb b/spec/ruby/core/exception/io_error_spec.rb
index 0971be332f..ab8a72518f 100644
--- a/spec/ruby/core/exception/io_error_spec.rb
+++ b/spec/ruby/core/exception/io_error_spec.rb
@@ -1,10 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "IOError" do
- it "is a superclass of EOFError" do
- IOError.should be_ancestor_of(EOFError)
- end
-end
+require_relative '../../spec_helper'
describe "IO::EAGAINWaitReadable" do
it "combines Errno::EAGAIN and IO::WaitReadable" do
diff --git a/spec/ruby/core/exception/key_error_spec.rb b/spec/ruby/core/exception/key_error_spec.rb
new file mode 100644
index 0000000000..c5e2b1efbc
--- /dev/null
+++ b/spec/ruby/core/exception/key_error_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "KeyError" do
+ it "accepts :receiver and :key options" do
+ receiver = mock("receiver")
+ key = mock("key")
+
+ error = KeyError.new(receiver: receiver, key: key)
+
+ error.receiver.should == receiver
+ error.key.should == key
+
+ error = KeyError.new("message", receiver: receiver, key: key)
+
+ error.message.should == "message"
+ error.receiver.should == receiver
+ error.key.should == key
+ end
+end
diff --git a/spec/ruby/core/exception/load_error_spec.rb b/spec/ruby/core/exception/load_error_spec.rb
index 2999c66117..0056403e58 100644
--- a/spec/ruby/core/exception/load_error_spec.rb
+++ b/spec/ruby/core/exception/load_error_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "LoadError#path" do
before :each do
diff --git a/spec/ruby/core/exception/message_spec.rb b/spec/ruby/core/exception/message_spec.rb
index 7eee6d99de..8d7476126e 100644
--- a/spec/ruby/core/exception/message_spec.rb
+++ b/spec/ruby/core/exception/message_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Exception#message" do
it "returns the class name if there is no message" do
diff --git a/spec/ruby/core/exception/name_error_spec.rb b/spec/ruby/core/exception/name_error_spec.rb
index e5b19d6219..ddd51a92e5 100644
--- a/spec/ruby/core/exception/name_error_spec.rb
+++ b/spec/ruby/core/exception/name_error_spec.rb
@@ -1,13 +1,28 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "NameError" do
- it "is a superclass of NoMethodError" do
- NameError.should be_ancestor_of(NoMethodError)
- end
-end
+require_relative '../../spec_helper'
describe "NameError.new" do
it "should take optional name argument" do
NameError.new("msg","name").name.should == "name"
end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NameError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
+end
+
+describe "NameError#dup" do
+ it "copies the name and receiver" do
+ begin
+ foo
+ rescue NameError => ne
+ name_error_dup = ne.dup
+ name_error_dup.name.should == :foo
+ name_error_dup.receiver.should == self
+ end
+ end
end
diff --git a/spec/ruby/core/exception/name_spec.rb b/spec/ruby/core/exception/name_spec.rb
index e8a3c011d2..c8a49b40e2 100644
--- a/spec/ruby/core/exception/name_spec.rb
+++ b/spec/ruby/core/exception/name_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "NameError#name" do
it "returns a method name as a symbol" do
@@ -21,41 +21,23 @@ describe "NameError#name" do
it "returns a class variable name as a symbol" do
-> {
- -> {
- @@doesnt_exist
- }.should complain(/class variable access from toplevel/)
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
}.should raise_error(NameError) { |e| e.name.should == :@@doesnt_exist }
end
- ruby_version_is ""..."2.3" do
- it "always returns a symbol when a NameError is raised from #instance_variable_get" do
- -> {
- Object.new.instance_variable_get("invalid_ivar_name")
- }.should raise_error(NameError) { |e| e.name.should == :invalid_ivar_name }
- end
-
- it "always returns a symbol when a NameError is raised from #class_variable_get" do
- -> {
- Object.class_variable_get("invalid_cvar_name")
- }.should raise_error(NameError) { |e| e.name.should == :invalid_cvar_name }
- end
- end
-
- ruby_version_is "2.3" do
- it "returns the first argument passed to the method when a NameError is raised from #instance_variable_get" do
- invalid_ivar_name = "invalid_ivar_name"
+ it "returns the first argument passed to the method when a NameError is raised from #instance_variable_get" do
+ invalid_ivar_name = "invalid_ivar_name"
- -> {
- Object.new.instance_variable_get(invalid_ivar_name)
- }.should raise_error(NameError) {|e| e.name.should equal(invalid_ivar_name) }
- end
+ -> {
+ Object.new.instance_variable_get(invalid_ivar_name)
+ }.should raise_error(NameError) {|e| e.name.should equal(invalid_ivar_name) }
+ end
- it "returns the first argument passed to the method when a NameError is raised from #class_variable_get" do
- invalid_cvar_name = "invalid_cvar_name"
+ it "returns the first argument passed to the method when a NameError is raised from #class_variable_get" do
+ invalid_cvar_name = "invalid_cvar_name"
- -> {
- Object.class_variable_get(invalid_cvar_name)
- }.should raise_error(NameError) {|e| e.name.should equal(invalid_cvar_name) }
- end
+ -> {
+ Object.class_variable_get(invalid_cvar_name)
+ }.should raise_error(NameError) {|e| e.name.should equal(invalid_cvar_name) }
end
end
diff --git a/spec/ruby/core/exception/new_spec.rb b/spec/ruby/core/exception/new_spec.rb
index 61d35a1dfa..100dbb0a24 100644
--- a/spec/ruby/core/exception/new_spec.rb
+++ b/spec/ruby/core/exception/new_spec.rb
@@ -1,7 +1,7 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
-require File.expand_path('../shared/new', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/new'
describe "Exception.new" do
- it_behaves_like(:exception_new, :new)
+ it_behaves_like :exception_new, :new
end
diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb
index cf3fe58b1d..772c569f67 100644
--- a/spec/ruby/core/exception/no_method_error_spec.rb
+++ b/spec/ruby/core/exception/no_method_error_spec.rb
@@ -1,14 +1,23 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "NoMethodError.new" do
it "allows passing method args" do
- NoMethodError.new("msg","name","args").args.should == "args"
+ NoMethodError.new("msg", "name", ["args"]).args.should == ["args"]
end
it "does not require a name" do
NoMethodError.new("msg").message.should == "msg"
end
+
+ it "accepts a :receiver keyword argument" do
+ receiver = mock("receiver")
+
+ error = NoMethodError.new("msg", :name, receiver: receiver)
+
+ error.receiver.should == receiver
+ error.name.should == :name
+ end
end
describe "NoMethodError#args" do
@@ -26,7 +35,7 @@ describe "NoMethodError#args" do
NoMethodErrorSpecs::NoMethodErrorB.new.foo(1,a)
rescue Exception => e
e.args.should == [1,a]
- e.args[1].object_id.should == a.object_id
+ e.args[1].should equal a
end
end
end
@@ -53,7 +62,222 @@ describe "NoMethodError#message" do
NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method
rescue Exception => e
e.should be_kind_of(NoMethodError)
- e.message.match(/private method/).should_not == nil
+ e.message.lines[0].should =~ /private method [`']a_private_method' called for /
+ end
+ end
+
+ ruby_version_is ""..."3.3" do
+ it "calls #inspect 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 NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for <inspect>:#<Class:0x\h+>$/
+ ScratchPad.recorded.should == [:inspect_called]
+ end
+ end
+
+ it "fallbacks to a simpler representation of the receiver when receiver.inspect raises an exception" do
+ test_class = Class.new do
+ def inspect
+ raise NoMethodErrorSpecs::InstanceException
+ end
+ end
+ instance = test_class.new
+
+ begin
+ instance.bar
+ rescue NoMethodError => error
+ message = error.message
+ message.should =~ /undefined method.+\bbar\b/
+ message.should include test_class.inspect
+ end
+ end
+
+ it "uses #name to display the receiver if it is a class" do
+ klass = Class.new { def self.name; "MyClass"; end }
+
+ begin
+ klass.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for MyClass:Class$/
+ end
+ end
+
+ it "uses #name to display the receiver if it is a module" do
+ mod = Module.new { def self.name; "MyModule"; end }
+
+ begin
+ mod.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for MyModule:Module$/
+ end
+ end
+ end
+
+ ruby_version_is "3.3" do
+ it "uses a literal name when receiver is nil" do
+ begin
+ nil.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for nil\Z/
+ end
+ end
+
+ it "uses a literal name when receiver is true" do
+ begin
+ true.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for true\Z/
+ end
+ end
+
+ it "uses a literal name when receiver is false" do
+ begin
+ false.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for false\Z/
+ end
+ end
+
+ it "uses #name when receiver is a class" do
+ klass = Class.new { def self.name; "MyClass"; end }
+
+ begin
+ klass.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for class MyClass\Z/
+ end
+ end
+
+ it "uses class' string representation when receiver is an anonymous class" do
+ klass = Class.new
+
+ begin
+ klass.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for class #<Class:0x\h+>\Z/
+ end
+ end
+
+ it "uses class' string representation when receiver is a singleton class" do
+ obj = Object.new
+ singleton_class = obj.singleton_class
+
+ begin
+ singleton_class.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for class #<Class:#<Object:0x\h+>>\Z/
+ end
+ end
+
+ it "uses #name when receiver is a module" do
+ mod = Module.new { def self.name; "MyModule"; end }
+
+ begin
+ mod.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for module MyModule\Z/
+ end
+ end
+
+ it "uses module's string representation when receiver is an anonymous module" do
+ m = Module.new
+
+ begin
+ m.foo
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for module #<Module:0x\h+>\Z/
+ end
+ end
+
+ it "uses class #name when receiver is an ordinary object" do
+ klass = Class.new { def self.name; "MyClass"; end }
+ instance = klass.new
+
+ begin
+ instance.bar
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for an instance of MyClass\Z/
+ end
+ end
+
+ it "uses class string representation when receiver is an instance of anonymous class" do
+ klass = Class.new
+ instance = klass.new
+
+ begin
+ instance.bar
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/
+ end
+ end
+
+ it "uses class name when receiver has a singleton class" do
+ instance = NoMethodErrorSpecs::NoMethodErrorA.new
+ def instance.foo; end
+
+ begin
+ instance.bar
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for #<NoMethodErrorSpecs::NoMethodErrorA:0x\h+>\Z/
+ end
+ end
+
+ it "does not call #inspect 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 NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']bar' for an instance of #<Class:0x\h+>\Z/
+ ScratchPad.recorded.should == []
+ end
+ end
+
+ it "does not truncate long class names" do
+ class_name = 'ExceptionSpecs::A' + 'a'*100
+
+ begin
+ eval <<~RUBY
+ class #{class_name}
+ end
+
+ obj = #{class_name}.new
+ obj.foo
+ RUBY
+ rescue NoMethodError => error
+ error.message.should =~ /\Aundefined method [`']foo' for an instance of #{class_name}\Z/
+ end
+ end
+ end
+end
+
+describe "NoMethodError#dup" do
+ it "copies the name, arguments and receiver" do
+ begin
+ receiver = Object.new
+ receiver.foo(:one, :two)
+ rescue NoMethodError => nme
+ no_method_error_dup = nme.dup
+ no_method_error_dup.name.should == :foo
+ no_method_error_dup.receiver.should == receiver
+ no_method_error_dup.args.should == [:one, :two]
end
end
end
diff --git a/spec/ruby/core/exception/range_error_spec.rb b/spec/ruby/core/exception/range_error_spec.rb
deleted file mode 100644
index 9c0462bbf7..0000000000
--- a/spec/ruby/core/exception/range_error_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "RangeError" do
- it "is a superclass of FloatDomainError" do
- RangeError.should be_ancestor_of(FloatDomainError)
- end
-end
diff --git a/spec/ruby/core/exception/readagain_bytes_spec.rb b/spec/ruby/core/exception/readagain_bytes_spec.rb
deleted file mode 100644
index 30efb67686..0000000000
--- a/spec/ruby/core/exception/readagain_bytes_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::InvalidByteSequenceError#readagain_bytes" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/reason_spec.rb b/spec/ruby/core/exception/reason_spec.rb
index fad4d47c64..210bbc9725 100644
--- a/spec/ruby/core/exception/reason_spec.rb
+++ b/spec/ruby/core/exception/reason_spec.rb
@@ -1,5 +1,13 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "LocalJumpError#reason" do
- it "needs to be reviewed for spec completeness"
+ def get_me_a_return
+ Proc.new { return 42 }
+ end
+
+ it "returns 'return' for a return" do
+ -> { get_me_a_return.call }.should raise_error(LocalJumpError) { |e|
+ e.reason.should == :return
+ }
+ end
end
diff --git a/spec/ruby/core/exception/receiver_spec.rb b/spec/ruby/core/exception/receiver_spec.rb
index 83f8d5927c..d1c23b67be 100644
--- a/spec/ruby/core/exception/receiver_spec.rb
+++ b/spec/ruby/core/exception/receiver_spec.rb
@@ -1,62 +1,58 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
-
-ruby_version_is "2.3" do
- describe "NameError#receiver" do
- class ::ReceiverClass
- def call_undefined_class_variable; @@doesnt_exist end
- end
-
- it "returns the object that raised the exception" do
- receiver = Object.new
-
- -> {
- receiver.doesnt_exist
- }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
- end
-
- it "returns the Object class when an undefined constant is called without namespace" do
- -> {
- DoesntExist
- }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
- end
-
- it "returns a class when an undefined constant is called" do
- -> {
- NameErrorSpecs::ReceiverClass::DoesntExist
- }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
- end
-
- it "returns the Object class when an undefined class variable is called" do
- -> {
- -> {
- @@doesnt_exist
- }.should complain(/class variable access from toplevel/)
- }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
- end
-
- it "returns a class when an undefined class variable is called in a subclass' namespace" do
- -> {
- NameErrorSpecs::ReceiverClass.new.call_undefined_class_variable
- }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
- end
-
- it "returns the receiver when raised from #instance_variable_get" do
- receiver = Object.new
-
- -> {
- receiver.instance_variable_get("invalid_ivar_name")
- }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
- end
-
- it "returns the receiver when raised from #class_variable_get" do
- -> {
- Object.class_variable_get("invalid_cvar_name")
- }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
- end
-
- it "raises an ArgumentError when the receiver is none" do
- -> { NameError.new.receiver }.should raise_error(ArgumentError)
- end
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+
+describe "NameError#receiver" do
+ class ::ReceiverClass
+ def call_undefined_class_variable; @@doesnt_exist end
+ end
+
+ it "returns the object that raised the exception" do
+ receiver = Object.new
+
+ -> {
+ receiver.doesnt_exist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
+ end
+
+ it "returns the Object class when an undefined constant is called without namespace" do
+ -> {
+ DoesntExist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
+ end
+
+ it "returns a class when an undefined constant is called" do
+ -> {
+ NameErrorSpecs::ReceiverClass::DoesntExist
+ }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the Object class when an undefined class variable is called" do
+ -> {
+ eval("class singleton_class::A; @@doesnt_exist end", binding, __FILE__, __LINE__)
+ }.should raise_error(NameError) {|e| e.receiver.should equal(singleton_class::A) }
+ end
+
+ it "returns a class when an undefined class variable is called in a subclass' namespace" do
+ -> {
+ NameErrorSpecs::ReceiverClass.new.call_undefined_class_variable
+ }.should raise_error(NameError) {|e| e.receiver.should equal(NameErrorSpecs::ReceiverClass) }
+ end
+
+ it "returns the receiver when raised from #instance_variable_get" do
+ receiver = Object.new
+
+ -> {
+ receiver.instance_variable_get("invalid_ivar_name")
+ }.should raise_error(NameError) {|e| e.receiver.should equal(receiver) }
+ end
+
+ it "returns the receiver when raised from #class_variable_get" do
+ -> {
+ Object.class_variable_get("invalid_cvar_name")
+ }.should raise_error(NameError) {|e| e.receiver.should equal(Object) }
+ end
+
+ it "raises an ArgumentError when the receiver is none" do
+ -> { NameError.new.receiver }.should raise_error(ArgumentError)
end
end
diff --git a/spec/ruby/core/exception/result_spec.rb b/spec/ruby/core/exception/result_spec.rb
index 350c071f60..d42fcdffcb 100644
--- a/spec/ruby/core/exception/result_spec.rb
+++ b/spec/ruby/core/exception/result_spec.rb
@@ -1,10 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "StopIteration" do
- it "is a subclass of IndexError" do
- StopIteration.superclass.should equal(IndexError)
- end
-end
+require_relative '../../spec_helper'
describe "StopIteration#result" do
before :each do
@@ -20,10 +14,8 @@ describe "StopIteration#result" do
it "returns the method-returned-object from an Enumerator" do
@enum.next
@enum.next
- lambda { @enum.next }.should(
- raise_error(StopIteration) do |error|
- error.result.should equal(:method_returned)
- end
- )
+ -> { @enum.next }.should raise_error(StopIteration) { |error|
+ error.result.should equal(:method_returned)
+ }
end
end
diff --git a/spec/ruby/core/exception/script_error_spec.rb b/spec/ruby/core/exception/script_error_spec.rb
deleted file mode 100644
index 5ca0333261..0000000000
--- a/spec/ruby/core/exception/script_error_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "ScriptError" do
- it "is a superclass of LoadError" do
- ScriptError.should be_ancestor_of(LoadError)
- end
-
- it "is a superclass of NotImplementedError" do
- ScriptError.should be_ancestor_of(NotImplementedError)
- end
-
- it "is a superclass of SyntaxError" do
- ScriptError.should be_ancestor_of(SyntaxError)
- end
-end
diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb
index db58a193ef..2cd93326ec 100644
--- a/spec/ruby/core/exception/set_backtrace_spec.rb
+++ b/spec/ruby/core/exception/set_backtrace_spec.rb
@@ -1,56 +1,23 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
+require_relative 'shared/set_backtrace'
describe "Exception#set_backtrace" do
- it "accepts an Array of Strings" do
- err = RuntimeError.new
- err.set_backtrace ["unhappy"]
- err.backtrace.should == ["unhappy"]
- end
-
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
- end
-
- it "accepts an empty Array" do
- err = RuntimeError.new
- err.set_backtrace []
- err.backtrace.should == []
- end
-
- it "accepts a String" do
- err = RuntimeError.new
- err.set_backtrace "unhappy"
- err.backtrace.should == ["unhappy"]
- end
- it "accepts nil" do
- err = RuntimeError.new
- err.set_backtrace nil
- err.backtrace.should be_nil
- end
-
- it "raises a TypeError when passed a Symbol" do
- err = RuntimeError.new
- lambda { err.set_backtrace :unhappy }.should raise_error(TypeError)
+ err.backtrace.should == bt
+ err.backtrace_locations.should == nil
end
- it "raises a TypeError when the Array contains a Symbol" do
+ it_behaves_like :exception_set_backtrace, -> backtrace {
err = RuntimeError.new
- lambda { err.set_backtrace ["String", :unhappy] }.should raise_error(TypeError)
- end
-
- it "raises a TypeError when the array contains nil" do
- err = Exception.new
- lambda { err.set_backtrace ["String", nil] }.should raise_error(TypeError)
- end
-
- it "raises a TypeError when the argument is a nested array" do
- err = Exception.new
- lambda { err.set_backtrace ["String", ["String"]] }.should raise_error(TypeError)
- end
+ err.set_backtrace(backtrace)
+ err
+ }
end
diff --git a/spec/ruby/core/exception/shared/set_backtrace.rb b/spec/ruby/core/exception/shared/set_backtrace.rb
new file mode 100644
index 0000000000..c6213b42b4
--- /dev/null
+++ b/spec/ruby/core/exception/shared/set_backtrace.rb
@@ -0,0 +1,64 @@
+require_relative '../fixtures/common'
+
+describe :exception_set_backtrace, shared: true do
+ it "accepts an Array of Strings" do
+ err = @method.call(["unhappy"])
+ err.backtrace.should == ["unhappy"]
+ end
+
+ it "allows the user to set the backtrace from a rescued exception" do
+ bt = ExceptionSpecs::Backtrace.backtrace
+ err = @method.call(bt)
+ err.backtrace.should == bt
+ 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 = @method.call(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
+ err = @method.call([])
+ err.backtrace.should == []
+ end
+
+ it "accepts a String" do
+ err = @method.call("unhappy")
+ err.backtrace.should == ["unhappy"]
+ end
+
+ it "accepts nil" do
+ err = @method.call(nil)
+ err.backtrace.should be_nil
+ end
+
+ it "raises a TypeError when passed a Symbol" do
+ -> { @method.call(:unhappy) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the Array contains a Symbol" do
+ -> { @method.call(["String", :unhappy]) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the array contains nil" do
+ -> { @method.call(["String", nil]) }.should raise_error(TypeError)
+ end
+
+ it "raises a TypeError when the argument is a nested array" do
+ -> { @method.call(["String", ["String"]]) }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/ruby/core/exception/signal_exception_spec.rb b/spec/ruby/core/exception/signal_exception_spec.rb
index 3b2d1aad61..1a0940743f 100644
--- a/spec/ruby/core/exception/signal_exception_spec.rb
+++ b/spec/ruby/core/exception/signal_exception_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SignalException.new" do
it "takes a signal number as the first argument" do
@@ -9,7 +9,7 @@ describe "SignalException.new" do
end
it "raises an exception with an invalid signal number" do
- lambda { SignalException.new(100000) }.should raise_error(ArgumentError)
+ -> { SignalException.new(100000) }.should raise_error(ArgumentError)
end
it "takes a signal name without SIG prefix as the first argument" do
@@ -27,7 +27,11 @@ describe "SignalException.new" do
end
it "raises an exception with an invalid signal name" do
- lambda { SignalException.new("NONEXISTANT") }.should raise_error(ArgumentError)
+ -> { SignalException.new("NONEXISTENT") }.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
@@ -45,7 +49,7 @@ describe "SignalException.new" do
end
it "raises an exception with an invalid signal name" do
- lambda { SignalException.new(:NONEXISTANT) }.should raise_error(ArgumentError)
+ -> { SignalException.new(:NONEXISTENT) }.should raise_error(ArgumentError)
end
it "takes an optional message argument with a signal number" do
@@ -56,11 +60,11 @@ describe "SignalException.new" do
end
it "raises an exception for an optional argument with a signal name" do
- lambda { SignalException.new("INT","name") }.should raise_error(ArgumentError)
+ -> { SignalException.new("INT","name") }.should raise_error(ArgumentError)
end
end
-describe "rescueing SignalException" do
+describe "rescuing SignalException" do
it "raises a SignalException when sent a signal" do
begin
Process.kill :TERM, Process.pid
@@ -72,3 +76,48 @@ describe "rescueing SignalException" do
end
end
end
+
+describe "SignalException" do
+ it "can be rescued" do
+ ruby_exe(<<-RUBY)
+ begin
+ raise SignalException, 'SIGKILL'
+ rescue SignalException
+ exit(0)
+ end
+ exit(1)
+ RUBY
+
+ $?.exitstatus.should == 0
+ end
+
+ platform_is_not :windows do
+ it "runs after at_exit" do
+ output = ruby_exe(<<-RUBY, exit_status: :SIGKILL)
+ at_exit do
+ puts "hello"
+ $stdout.flush
+ end
+
+ raise SignalException, 'SIGKILL'
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("KILL")
+ output.should == "hello\n"
+ end
+
+ it "cannot be trapped with Signal.trap" do
+ ruby_exe(<<-RUBY, exit_status: :SIGPROF)
+ Signal.trap("PROF") {}
+ raise(SignalException, "PROF")
+ RUBY
+
+ $?.termsig.should == Signal.list.fetch("PROF")
+ end
+
+ it "self-signals for USR1" do
+ ruby_exe("raise(SignalException, 'USR1')", exit_status: :SIGUSR1)
+ $?.termsig.should == Signal.list.fetch('USR1')
+ end
+ end
+end
diff --git a/spec/ruby/core/exception/signm_spec.rb b/spec/ruby/core/exception/signm_spec.rb
index e205b79e19..4adff3b5ee 100644
--- a/spec/ruby/core/exception/signm_spec.rb
+++ b/spec/ruby/core/exception/signm_spec.rb
@@ -1,5 +1,9 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SignalException#signm" do
- it "needs to be reviewed for spec completeness"
+ it "returns the signal name" do
+ -> { Process.kill(:TERM, Process.pid) }.should raise_error(SignalException) { |e|
+ e.signm.should == 'SIGTERM'
+ }
+ end
end
diff --git a/spec/ruby/core/exception/signo_spec.rb b/spec/ruby/core/exception/signo_spec.rb
index 08c85274ca..62fc321516 100644
--- a/spec/ruby/core/exception/signo_spec.rb
+++ b/spec/ruby/core/exception/signo_spec.rb
@@ -1,5 +1,9 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SignalException#signo" do
- it "needs to be reviewed for spec completeness"
+ it "returns the signal number" do
+ -> { Process.kill(:TERM, Process.pid) }.should raise_error(SignalException) { |e|
+ e.signo.should == Signal.list['TERM']
+ }
+ end
end
diff --git a/spec/ruby/core/exception/source_encoding_name_spec.rb b/spec/ruby/core/exception/source_encoding_name_spec.rb
deleted file mode 100644
index 5796072121..0000000000
--- a/spec/ruby/core/exception/source_encoding_name_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::UndefinedConversionError#source_encoding_name" do
- it "needs to be reviewed for spec completeness"
-end
-
-describe "Encoding::InvalidByteSequenceError#source_encoding_name" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/source_encoding_spec.rb b/spec/ruby/core/exception/source_encoding_spec.rb
deleted file mode 100644
index 796bec88f6..0000000000
--- a/spec/ruby/core/exception/source_encoding_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "Encoding::UndefinedConversionError#source_encoding" do
- it "needs to be reviewed for spec completeness"
-end
-
-describe "Encoding::InvalidByteSequenceError#source_encoding" do
- it "needs to be reviewed for spec completeness"
-end
diff --git a/spec/ruby/core/exception/standard_error_spec.rb b/spec/ruby/core/exception/standard_error_spec.rb
index 9b3af4b322..17e98ce7f0 100644
--- a/spec/ruby/core/exception/standard_error_spec.rb
+++ b/spec/ruby/core/exception/standard_error_spec.rb
@@ -1,50 +1,23 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "StandardError" do
- it "is a superclass of ArgumentError" do
- StandardError.should be_ancestor_of(ArgumentError)
+ it "rescues StandardError" do
+ begin
+ raise StandardError
+ rescue => exception
+ exception.class.should == StandardError
+ end
end
- it "is a superclass of IOError" do
- StandardError.should be_ancestor_of(IOError)
+ it "rescues subclass of StandardError" do
+ begin
+ raise RuntimeError
+ rescue => exception
+ exception.class.should == RuntimeError
+ end
end
- it "is a superclass of IndexError" do
- StandardError.should be_ancestor_of(IndexError)
- end
-
- it "is a superclass of LocalJumpError" do
- StandardError.should be_ancestor_of(LocalJumpError)
- end
-
- it "is a superclass of NameError" do
- StandardError.should be_ancestor_of(NameError)
- end
-
- it "is a superclass of RangeError" do
- StandardError.should be_ancestor_of(RangeError)
- end
-
- it "is a superclass of RegexpError" do
- StandardError.should be_ancestor_of(RegexpError)
- end
-
- it "is a superclass of RuntimeError" do
- StandardError.should be_ancestor_of(RuntimeError)
- end
-
- it "is a superclass of SystemCallError" do
- StandardError.should be_ancestor_of(SystemCallError.new("").class)
- end
- it "is a superclass of ThreadError" do
- StandardError.should be_ancestor_of(ThreadError)
- end
-
- it "is a superclass of TypeError" do
- StandardError.should be_ancestor_of(TypeError)
- end
-
- it "is a superclass of ZeroDivisionError" do
- StandardError.should be_ancestor_of(ZeroDivisionError)
+ it "does not rescue superclass of StandardError" do
+ -> { begin; raise Exception; rescue; end }.should raise_error(Exception)
end
end
diff --git a/spec/ruby/core/exception/status_spec.rb b/spec/ruby/core/exception/status_spec.rb
index e648dc0adc..8ace00fe10 100644
--- a/spec/ruby/core/exception/status_spec.rb
+++ b/spec/ruby/core/exception/status_spec.rb
@@ -1,5 +1,9 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SystemExit#status" do
- it "needs to be reviewed for spec completeness"
+ it "returns the exit status" do
+ -> { exit 42 }.should raise_error(SystemExit) { |e|
+ e.status.should == 42
+ }
+ end
end
diff --git a/spec/ruby/core/exception/success_spec.rb b/spec/ruby/core/exception/success_spec.rb
index d9b69b4f45..6f21743340 100644
--- a/spec/ruby/core/exception/success_spec.rb
+++ b/spec/ruby/core/exception/success_spec.rb
@@ -1,5 +1,15 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "SystemExit#success?" do
- it "needs to be reviewed for spec completeness"
+ it "returns true if the process exited successfully" do
+ -> { exit 0 }.should raise_error(SystemExit) { |e|
+ e.should.success?
+ }
+ end
+
+ it "returns false if the process exited unsuccessfully" do
+ -> { exit(-1) }.should raise_error(SystemExit) { |e|
+ e.should_not.success?
+ }
+ 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..4c713a3507
--- /dev/null
+++ b/spec/ruby/core/exception/syntax_error_spec.rb
@@ -0,0 +1,25 @@
+require_relative '../../spec_helper'
+
+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
diff --git a/spec/ruby/core/exception/system_call_error_spec.rb b/spec/ruby/core/exception/system_call_error_spec.rb
index edcc8d3734..4fe51901c8 100644
--- a/spec/ruby/core/exception/system_call_error_spec.rb
+++ b/spec/ruby/core/exception/system_call_error_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "SystemCallError" do
before :each do
@@ -16,45 +16,111 @@ describe "SystemCallError" do
exc = ExceptionSpecs::SCESub.new
ScratchPad.recorded.should equal(:initialize)
exc.should be_an_instance_of(ExceptionSpecs::SCESub)
+ ensure
+ ExceptionSpecs.send(:remove_const, :SCESub)
end
end
describe "SystemCallError.new" do
+ before :all do
+ @example_errno = Errno::EINVAL::Errno
+ @example_errno_class = Errno::EINVAL
+ @last_known_errno = Errno.constants.size - 1
+ @unknown_errno = Errno.constants.size
+ @some_human_readable = /[[:graph:]]+/
+ end
+
it "requires at least one argument" do
- lambda { SystemCallError.new }.should raise_error(ArgumentError)
+ -> { SystemCallError.new }.should raise_error(ArgumentError)
end
- it "accepts single Fixnum argument as errno" do
+ it "accepts single Integer argument as errno" do
SystemCallError.new(-2**24).errno.should == -2**24
- SystemCallError.new(42).errno.should == 42
+ SystemCallError.new(-1).errno.should == -1
+ SystemCallError.new(0).errno.should == 0
+ SystemCallError.new(@last_known_errno).errno.should == @last_known_errno
+ SystemCallError.new(@unknown_errno).errno.should == @unknown_errno
SystemCallError.new(2**24).errno.should == 2**24
end
+ it "constructs a SystemCallError for an unknown error number" do
+ SystemCallError.new(-2**24).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(-1).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(@unknown_errno).should be_an_instance_of(SystemCallError)
+ SystemCallError.new(2**24).should be_an_instance_of(SystemCallError)
+ end
+
it "constructs the appropriate Errno class" do
- # EINVAL should be more or less mortable across the platforms,
- # so let's use it then.
- SystemCallError.new(22).should be_kind_of(SystemCallError)
- SystemCallError.new(22).should be_an_instance_of(Errno::EINVAL)
- SystemCallError.new(2**28).should be_an_instance_of(SystemCallError)
+ e = SystemCallError.new(@example_errno)
+ e.should be_kind_of(SystemCallError)
+ e.should be_an_instance_of(@example_errno_class)
+ end
+
+ it "sets an error message corresponding to an appropriate Errno class" do
+ e = SystemCallError.new(@example_errno)
+ e.message.should == 'Invalid argument'
end
it "accepts an optional custom message preceding the errno" do
- exc = SystemCallError.new("custom message", 22)
- exc.should be_an_instance_of(Errno::EINVAL)
- exc.errno.should == 22
- exc.message.should == "Invalid argument - custom message"
+ exc = SystemCallError.new("custom message", @example_errno)
+ exc.should be_an_instance_of(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument - custom message'
end
it "accepts an optional third argument specifying the location" do
- exc = SystemCallError.new("custom message", 22, "location")
- exc.should be_an_instance_of(Errno::EINVAL)
- exc.errno.should == 22
- exc.message.should == "Invalid argument @ location - custom message"
+ exc = SystemCallError.new("custom message", @example_errno, "location")
+ exc.should be_an_instance_of(@example_errno_class)
+ exc.errno.should == @example_errno
+ exc.message.should == 'Invalid argument @ location - custom message'
+ end
+
+ it "coerces location if it is not a String" do
+ e = SystemCallError.new('foo', 1, :not_a_string)
+ e.message.should =~ /@ not_a_string - foo/
end
it "returns an arity of -1 for the initialize method" do
SystemCallError.instance_method(:initialize).arity.should == -1
end
+
+ it "converts to Integer if errno is a Float" do
+ SystemCallError.new('foo', 2.0).should == SystemCallError.new('foo', 2)
+ SystemCallError.new('foo', 2.9).should == SystemCallError.new('foo', 2)
+ end
+
+ it "treats nil errno as unknown error value" do
+ SystemCallError.new(nil).should be_an_instance_of(SystemCallError)
+ end
+
+ it "treats nil custom message as if it is not passed at all" do
+ exc = SystemCallError.new(nil, @example_errno)
+ exc.message.should == 'Invalid argument'
+ end
+
+ it "sets an 'unknown error' message when an unknown error number" do
+ SystemCallError.new(-1).message.should =~ @some_human_readable
+ end
+
+ it "adds a custom error message to an 'unknown error' message when an unknown error number and a custom message specified" do
+ SystemCallError.new("custom message", -1).message.should =~ /#{@some_human_readable}.* - custom message/
+ end
+
+ it "converts to Integer if errno is a Complex convertible to Integer" do
+ SystemCallError.new('foo', Complex(2.9, 0)).should == SystemCallError.new('foo', 2)
+ end
+
+ it "raises TypeError if message is not a String" do
+ -> { SystemCallError.new(:foo, 1) }.should raise_error(TypeError, /no implicit conversion of Symbol into String/)
+ end
+
+ it "raises TypeError if errno is not an Integer" do
+ -> { SystemCallError.new('foo', 'bar') }.should raise_error(TypeError, /no implicit conversion of String into Integer/)
+ end
+
+ it "raises RangeError if errno is a Complex not convertible to Integer" do
+ -> { SystemCallError.new('foo', Complex(2.9, 1)) }.should raise_error(RangeError, /can't convert/)
+ end
end
describe "SystemCallError#errno" do
@@ -74,12 +140,7 @@ end
describe "SystemCallError#message" do
it "returns the default message when no message is given" do
- platform_is :aix do
- SystemCallError.new(2**28).message.should =~ /Error .*occurred/i
- end
- platform_is_not :aix do
- SystemCallError.new(2**28).message.should =~ /Unknown error/i
- end
+ SystemCallError.new(2**28).message.should =~ @some_human_readable
end
it "returns the message given as an argument to new" do
@@ -87,3 +148,16 @@ describe "SystemCallError#message" do
SystemCallError.new("XXX").message.should =~ /XXX/
end
end
+
+describe "SystemCallError#dup" do
+ it "copies the errno" do
+ dup_sce = SystemCallError.new("message", 42).dup
+ dup_sce.errno.should == 42
+ end
+end
+
+describe "SystemCallError#backtrace" do
+ it "is nil if not raised" do
+ SystemCallError.new("message", 42).backtrace.should == nil
+ end
+end
diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb
new file mode 100644
index 0000000000..d899844c4e
--- /dev/null
+++ b/spec/ruby/core/exception/system_exit_spec.rb
@@ -0,0 +1,59 @@
+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", 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", exit_status: 8)
+ result.should == ""
+ $?.exitstatus.should == 8
+ end
+end
diff --git a/spec/ruby/core/exception/system_stack_error_spec.rb b/spec/ruby/core/exception/system_stack_error_spec.rb
deleted file mode 100644
index acd56c0a0f..0000000000
--- a/spec/ruby/core/exception/system_stack_error_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "SystemStackError" do
- it "is a subclass of Exception" do
- SystemStackError.superclass.should == Exception
- end
-end
diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb
index 83234bfe23..65c0d73a98 100644
--- a/spec/ruby/core/exception/to_s_spec.rb
+++ b/spec/ruby/core/exception/to_s_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/common', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/common'
describe "Exception#to_s" do
it "returns the self's name if no message is set" do
@@ -19,5 +19,19 @@ describe "Exception#to_s" do
end
describe "NameError#to_s" do
- it "needs to be reviewed for spec completeness"
+ it "raises its own message for an undefined variable" do
+ begin
+ puts not_defined
+ rescue => exception
+ exception.message.should =~ /undefined local variable or method [`']not_defined'/
+ end
+ end
+
+ it "raises its own message for an undefined constant" do
+ begin
+ puts NotDefined
+ rescue => exception
+ exception.message.should =~ /uninitialized constant NotDefined/
+ end
+ end
end
diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb
new file mode 100644
index 0000000000..cc961d06d5
--- /dev/null
+++ b/spec/ruby/core/exception/top_level_spec.rb
@@ -0,0 +1,65 @@
+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", exit_status: 1).should =~ /in [`']<main>': foo \(RuntimeError\)/
+ end
+
+ 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
+ it "is printed on STDERR" do
+ code = <<-RUBY
+ raise RuntimeError, "foo", [
+ "/dir/foo.rb:10:in `raising'",
+ "/dir/bar.rb:20:in `caller'",
+ ]
+ RUBY
+ 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
diff --git a/spec/ruby/core/exception/uncaught_throw_error_spec.rb b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
index 3ed166af5b..9267df6670 100644
--- a/spec/ruby/core/exception/uncaught_throw_error_spec.rb
+++ b/spec/ruby/core/exception/uncaught_throw_error_spec.rb
@@ -1,10 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-
-describe "UncaughtThrowError" do
- it "is a subclass of ArgumentError" do
- ArgumentError.should be_ancestor_of(UncaughtThrowError)
- end
-end
+require_relative '../../spec_helper'
describe "UncaughtThrowError#tag" do
it "returns the object thrown" do
@@ -16,4 +10,3 @@ describe "UncaughtThrowError#tag" do
end
end
end
-