diff options
Diffstat (limited to 'spec/ruby/core/exception')
53 files changed, 1242 insertions, 471 deletions
diff --git a/spec/ruby/core/exception/args_spec.rb b/spec/ruby/core/exception/args_spec.rb deleted file mode 100644 index 005c2dd198..0000000000 --- a/spec/ruby/core/exception/args_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -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 0b283e9a54..0000000000 --- a/spec/ruby/core/exception/arguments_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require_relative '../../spec_helper' - -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_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb index 5e140f8d9b..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,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 diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb index a4c9eaa6bf..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_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/destination_encoding_name_spec.rb b/spec/ruby/core/exception/destination_encoding_name_spec.rb deleted file mode 100644 index b6ffff8c9c..0000000000 --- a/spec/ruby/core/exception/destination_encoding_name_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require_relative '../../spec_helper' - -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 c3ad0342a1..0000000000 --- a/spec/ruby/core/exception/destination_encoding_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require_relative '../../spec_helper' - -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 index c7fcd280dd..edd54bfb37 100644 --- a/spec/ruby/core/exception/dup_spec.rb +++ b/spec/ruby/core/exception/dup_spec.rb @@ -20,7 +20,7 @@ describe "Exception#dup" do it "does not copy singleton methods" do def @obj.special() :the_one end dup = @obj.dup - lambda { dup.special }.should raise_error(NameError) + -> { dup.special }.should raise_error(NameError) end it "does not copy modules included in the singleton class" do @@ -29,7 +29,7 @@ describe "Exception#dup" do end dup = @obj.dup - lambda { dup.repr }.should raise_error(NameError) + -> { dup.repr }.should raise_error(NameError) end it "does not copy constants defined in the singleton class" do @@ -38,7 +38,7 @@ describe "Exception#dup" do end dup = @obj.dup - lambda { class << dup; CLONE; end }.should raise_error(NameError) + -> { class << dup; CLONE; end }.should raise_error(NameError) end it "does copy the message" do 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 9e0bf0086a..1ab4277700 100644 --- a/spec/ruby/core/exception/errno_spec.rb +++ b/spec/ruby/core/exception/errno_spec.rb @@ -1,10 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/common' -describe "SystemCallError#errno" do - it "needs to be reviewed for spec completeness" -end - describe "Errno::EINVAL.new" do it "can be called with no arguments" do exc = Errno::EINVAL.new @@ -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 2a95bcfdf4..0000000000 --- a/spec/ruby/core/exception/error_bytes_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -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 c0256af03a..0000000000 --- a/spec/ruby/core/exception/error_char_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -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 3a01366920..d6f5283bd9 100644 --- a/spec/ruby/core/exception/exception_spec.rb +++ b/spec/ruby/core/exception/exception_spec.rb @@ -6,48 +6,6 @@ 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 -end - describe "Exception#exception" do it "returns self when passed no argument" do e = RuntimeError.new @@ -82,6 +40,18 @@ describe "Exception#exception" do 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 43de56af8b..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_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 0ffb3ed855..3d8a3c3430 100644 --- a/spec/ruby/core/exception/fixtures/common.rb +++ b/spec/ruby/core/exception/fixtures/common.rb @@ -84,6 +84,9 @@ module NoMethodErrorSpecs class InstanceException < Exception end + + class AClass; end + module AModule; end end class NameErrorSpecs @@ -93,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 index 7b356253c4..af2e925661 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -1,34 +1,54 @@ require_relative '../../spec_helper' -describe "FrozenError" do - ruby_version_is "2.5" do - it "is a subclass of RuntimeError" do - RuntimeError.should be_ancestor_of(FrozenError) - end +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.new" do - ruby_version_is "2.7" do - it "should take optional receiver argument" do - o = Object.new - FrozenError.new("msg", o).receiver.should equal(o) +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#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 +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 index 3df2d47f61..0761d2b40c 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -1,94 +1,226 @@ require_relative '../../spec_helper' -ruby_version_is "2.5" do - 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 +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 - 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 "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: 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)" + 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("") - 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)" + err.full_message.should =~ /unhandled exception/ + err.full_message(highlight: true).should =~ /unhandled exception/ + err.full_message(highlight: false).should =~ /unhandled exception/ 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"]) + it "should not report as unhandled if the message is not empty" do + err = RuntimeError.new("non-empty") - 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 + err.full_message.should !~ /unhandled exception/ + err.full_message(highlight: true).should !~ /unhandled exception/ + err.full_message(highlight: false).should !~ /unhandled exception/ 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 "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 "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 include("#{__FILE__}:#{line}:in `") - full_message[0].should include(": first line (RuntimeError)\n") - full_message[1].should == "second line\n" - 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 - ruby_version_is "2.6" do - it "contains cause of exception" do - begin - begin - raise 'the cause' - rescue - raise 'main exception' - end - rescue => e - exception = e - 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 - exception.full_message.should include "main exception" - exception.full_message.should include "the cause" + 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 - it 'contains all the chain of exceptions' do + 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 - begin - begin - raise 'origin exception' - rescue - raise 'intermediate exception' - end - rescue - raise 'last exception' - end - rescue => e - exception = e + raise 'origin exception' + rescue + raise 'intermediate exception' end - - exception.full_message.should include "last exception" - exception.full_message.should include "intermediate exception" - exception.full_message.should include "origin exception" + 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 b033b33f56..0000000000 --- a/spec/ruby/core/exception/incomplete_input_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -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 e724feaa39..0000000000 --- a/spec/ruby/core/exception/initialize_spec.rb +++ /dev/null @@ -1 +0,0 @@ -require_relative '../../spec_helper' diff --git a/spec/ruby/core/exception/inspect_spec.rb b/spec/ruby/core/exception/inspect_spec.rb index 519ce574ae..6f380a36c7 100644 --- a/spec/ruby/core/exception/inspect_spec.rb +++ b/spec/ruby/core/exception/inspect_spec.rb @@ -6,6 +6,10 @@ describe "Exception#inspect" 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 bc01b7b703..90d261e470 100644 --- a/spec/ruby/core/exception/interrupt_spec.rb +++ b/spec/ruby/core/exception/interrupt_spec.rb @@ -1,11 +1,5 @@ require_relative '../../spec_helper' -describe "Interrupt" do - it "is a subclass of SignalException" do - Interrupt.superclass.should == SignalException - end -end - describe "Interrupt.new" do it "returns an instance of interrupt with no message given" do e = Interrupt.new @@ -35,7 +29,32 @@ describe "rescuing 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 8dc10cc6ce..ab8a72518f 100644 --- a/spec/ruby/core/exception/io_error_spec.rb +++ b/spec/ruby/core/exception/io_error_spec.rb @@ -1,11 +1,5 @@ require_relative '../../spec_helper' -describe "IOError" do - it "is a superclass of EOFError" do - IOError.should be_ancestor_of(EOFError) - end -end - describe "IO::EAGAINWaitReadable" do it "combines Errno::EAGAIN and IO::WaitReadable" do IO::EAGAINWaitReadable.superclass.should == Errno::EAGAIN diff --git a/spec/ruby/core/exception/key_error_spec.rb b/spec/ruby/core/exception/key_error_spec.rb index ad280279d8..c5e2b1efbc 100644 --- a/spec/ruby/core/exception/key_error_spec.rb +++ b/spec/ruby/core/exception/key_error_spec.rb @@ -1,15 +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 - end + 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/name_error_spec.rb b/spec/ruby/core/exception/name_error_spec.rb index d0a810029b..ddd51a92e5 100644 --- a/spec/ruby/core/exception/name_error_spec.rb +++ b/spec/ruby/core/exception/name_error_spec.rb @@ -1,24 +1,28 @@ require_relative '../../spec_helper' -describe "NameError" do - it "is a superclass of NoMethodError" do - NameError.should be_ancestor_of(NoMethodError) - end -end - describe "NameError.new" do it "should take optional name argument" 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 +end - error.receiver.should == receiver - error.name.should == :name +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 d1def51f24..c8a49b40e2 100644 --- a/spec/ruby/core/exception/name_spec.rb +++ b/spec/ruby/core/exception/name_spec.rb @@ -21,9 +21,7 @@ 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 diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 28c3549562..772c569f67 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,43 +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 - 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 #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 - 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] + + 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 - 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 + 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 - instance = test_class.new + + 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 - instance.bar - rescue Exception => e - e.name.should == :bar - message = e.message - message.should =~ /undefined method.+\bbar\b/ - message.should include test_class.inspect + 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 7cfbd0f1ec..0000000000 --- a/spec/ruby/core/exception/range_error_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require_relative '../../spec_helper' - -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 f7e8d9d1d3..0000000000 --- a/spec/ruby/core/exception/readagain_bytes_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require_relative '../../spec_helper' - -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 6f18aaae13..210bbc9725 100644 --- a/spec/ruby/core/exception/reason_spec.rb +++ b/spec/ruby/core/exception/reason_spec.rb @@ -1,5 +1,13 @@ 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 7c57d63c3e..d1c23b67be 100644 --- a/spec/ruby/core/exception/receiver_spec.rb +++ b/spec/ruby/core/exception/receiver_spec.rb @@ -28,10 +28,8 @@ describe "NameError#receiver" do 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) } + 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 diff --git a/spec/ruby/core/exception/result_spec.rb b/spec/ruby/core/exception/result_spec.rb index 62247e9d85..d42fcdffcb 100644 --- a/spec/ruby/core/exception/result_spec.rb +++ b/spec/ruby/core/exception/result_spec.rb @@ -1,11 +1,5 @@ require_relative '../../spec_helper' -describe "StopIteration" do - it "is a subclass of IndexError" do - StopIteration.superclass.should equal(IndexError) - end -end - describe "StopIteration#result" do before :each do obj = Object.new @@ -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 e33a5d3a58..0000000000 --- a/spec/ruby/core/exception/script_error_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require_relative '../../spec_helper' - -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 4ebe574dcb..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_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 8e2f273a67..1a0940743f 100644 --- a/spec/ruby/core/exception/signal_exception_spec.rb +++ b/spec/ruby/core/exception/signal_exception_spec.rb @@ -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("NONEXISTENT") }.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(:NONEXISTENT) }.should raise_error(ArgumentError) + -> { SignalException.new(:NONEXISTENT) }.should raise_error(ArgumentError) end it "takes an optional message argument with a signal number" do @@ -56,7 +60,7 @@ 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 @@ -72,3 +76,48 @@ describe "rescuing 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 8e3adcddae..4adff3b5ee 100644 --- a/spec/ruby/core/exception/signm_spec.rb +++ b/spec/ruby/core/exception/signm_spec.rb @@ -1,5 +1,9 @@ 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 2d04cd7805..62fc321516 100644 --- a/spec/ruby/core/exception/signo_spec.rb +++ b/spec/ruby/core/exception/signo_spec.rb @@ -1,5 +1,9 @@ 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 bd8bc359b6..0000000000 --- a/spec/ruby/core/exception/source_encoding_name_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require_relative '../../spec_helper' - -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 65ac98d791..0000000000 --- a/spec/ruby/core/exception/source_encoding_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require_relative '../../spec_helper' - -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 1b469b5090..17e98ce7f0 100644 --- a/spec/ruby/core/exception/standard_error_spec.rb +++ b/spec/ruby/core/exception/standard_error_spec.rb @@ -1,56 +1,23 @@ require_relative '../../spec_helper' describe "StandardError" do - it "is a superclass of ArgumentError" do - StandardError.should be_ancestor_of(ArgumentError) - end - - it "is a superclass of IOError" do - StandardError.should be_ancestor_of(IOError) - 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) + it "rescues StandardError" do + begin + raise StandardError + rescue => exception + exception.class.should == StandardError + end end - it "is a superclass of ZeroDivisionError" do - StandardError.should be_ancestor_of(ZeroDivisionError) + it "rescues subclass of StandardError" do + begin + raise RuntimeError + rescue => exception + exception.class.should == RuntimeError + end end - ruby_version_is '2.5' do - it "is a superclass of FrozenError" do - StandardError.should be_ancestor_of(FrozenError) - end + 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 1609bff3a5..8ace00fe10 100644 --- a/spec/ruby/core/exception/status_spec.rb +++ b/spec/ruby/core/exception/status_spec.rb @@ -1,5 +1,9 @@ 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 82e3df92c6..6f21743340 100644 --- a/spec/ruby/core/exception/success_spec.rb +++ b/spec/ruby/core/exception/success_spec.rb @@ -1,5 +1,15 @@ 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 d9e303bad8..4fe51901c8 100644 --- a/spec/ruby/core/exception/system_call_error_spec.rb +++ b/spec/ruby/core/exception/system_call_error_spec.rb @@ -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 0343d2da59..0000000000 --- a/spec/ruby/core/exception/system_stack_error_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require_relative '../../spec_helper' - -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 8570b18cfd..65c0d73a98 100644 --- a/spec/ruby/core/exception/to_s_spec.rb +++ b/spec/ruby/core/exception/to_s_spec.rb @@ -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 57f391d755..9267df6670 100644 --- a/spec/ruby/core/exception/uncaught_throw_error_spec.rb +++ b/spec/ruby/core/exception/uncaught_throw_error_spec.rb @@ -1,11 +1,5 @@ require_relative '../../spec_helper' -describe "UncaughtThrowError" do - it "is a subclass of ArgumentError" do - ArgumentError.should be_ancestor_of(UncaughtThrowError) - end -end - describe "UncaughtThrowError#tag" do it "returns the object thrown" do begin |
