diff options
Diffstat (limited to 'spec/ruby/core/exception')
19 files changed, 473 insertions, 87 deletions
diff --git a/spec/ruby/core/exception/backtrace_spec.rb b/spec/ruby/core/exception/backtrace_spec.rb index 3f74c4cefe..9a65ea3820 100644 --- a/spec/ruby/core/exception/backtrace_spec.rb +++ b/spec/ruby/core/exception/backtrace_spec.rb @@ -27,7 +27,7 @@ describe "Exception#backtrace" do end it "includes the name of the method from where self raised in the first element" do - @backtrace.first.should =~ /in `backtrace'/ + @backtrace.first.should =~ /in [`'](?:ExceptionSpecs::Backtrace\.)?backtrace'/ end it "includes the filename of the location immediately prior to where self raised in the second element" do @@ -38,12 +38,25 @@ describe "Exception#backtrace" do @backtrace[1].should =~ /:6(:in )?/ end - it "contains lines of the same format for each prior position in the stack" do - @backtrace[2..-1].each do |line| - # This regexp is deliberately imprecise to account for the need to abstract out - # the paths of the included mspec files and the desire to avoid specifying in any - # detail what the in `...' portion looks like. - line.should =~ /^.+:\d+:in `[^`]+'$/ + ruby_version_is ""..."3.4" do + it "contains lines of the same format for each prior position in the stack" do + @backtrace[2..-1].each do |line| + # This regexp is deliberately imprecise to account for the need to abstract out + # the paths of the included mspec files and the desire to avoid specifying in any + # detail what the in `...' portion looks like. + line.should =~ /^.+:\d+:in `[^`]+'$/ + end + end + end + + ruby_version_is "3.4" do + it "contains lines of the same format for each prior position in the stack" do + @backtrace[2..-1].each do |line| + # This regexp is deliberately imprecise to account for the need to abstract out + # the paths of the included mspec files and the desire to avoid specifying in any + # detail what the in '...' portion looks like. + line.should =~ /^.+:\d+:in '[^`]+'$/ + end end end diff --git a/spec/ruby/core/exception/case_compare_spec.rb b/spec/ruby/core/exception/case_compare_spec.rb index 87b9dee3ca..5fd11ae741 100644 --- a/spec/ruby/core/exception/case_compare_spec.rb +++ b/spec/ruby/core/exception/case_compare_spec.rb @@ -26,13 +26,11 @@ describe "SystemCallError.===" do end it "returns true if receiver is generic and arg is kind of SystemCallError" do - unknown_error_number = Errno.constants.size e = SystemCallError.new('foo', @example_errno) SystemCallError.===(e).should == true end it "returns false if receiver is generic and arg is not kind of SystemCallError" do - unknown_error_number = Errno.constants.size e = Object.new SystemCallError.===(e).should == false end diff --git a/spec/ruby/core/exception/detailed_message_spec.rb b/spec/ruby/core/exception/detailed_message_spec.rb new file mode 100644 index 0000000000..fbe4443daa --- /dev/null +++ b/spec/ruby/core/exception/detailed_message_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Exception#detailed_message" do + ruby_version_is "3.2" do + it "returns decorated message" do + RuntimeError.new("new error").detailed_message.should == "new error (RuntimeError)" + end + + it "is called by #full_message to allow message customization" do + exception = Exception.new("new error") + def exception.detailed_message(**) + "<prefix>#{message}<suffix>" + end + exception.full_message(highlight: false).should.include? "<prefix>new error<suffix>" + end + + it "accepts highlight keyword argument and adds escape control sequences" do + RuntimeError.new("new error").detailed_message(highlight: true).should == "\e[1mnew error (\e[1;4mRuntimeError\e[m\e[1m)\e[m" + end + + it "allows and ignores other keyword arguments" do + RuntimeError.new("new error").detailed_message(foo: true).should == "new error (RuntimeError)" + end + + it "returns just a message if exception class is anonymous" do + Class.new(RuntimeError).new("message").detailed_message.should == "message" + end + + it "returns 'unhandled exception' for an instance of RuntimeError with empty message" do + RuntimeError.new("").detailed_message.should == "unhandled exception" + end + + it "returns just class name for an instance of RuntimeError subclass with empty message" do + DetailedMessageSpec::C.new("").detailed_message.should == "DetailedMessageSpec::C" + end + + it "returns a generated class name for an instance of RuntimeError anonymous subclass with empty message" do + klass = Class.new(RuntimeError) + klass.new("").detailed_message.should =~ /\A#<Class:0x\h+>\z/ + end + end +end diff --git a/spec/ruby/core/exception/equal_value_spec.rb b/spec/ruby/core/exception/equal_value_spec.rb index 7f2065511a..e8f3ce0f8d 100644 --- a/spec/ruby/core/exception/equal_value_spec.rb +++ b/spec/ruby/core/exception/equal_value_spec.rb @@ -22,18 +22,18 @@ describe "Exception#==" do it "returns true if both exceptions have the same class, the same message, and the same backtrace" do one = TypeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = TypeError.new("message") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] one.should == two end it "returns false if the two exceptions inherit from Exception but have different classes" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] one.should be_kind_of(Exception) two = TypeError.new("message") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] two.should be_kind_of(Exception) one.should_not == two end @@ -52,7 +52,7 @@ describe "Exception#==" do it "returns false if the two exceptions differ only in their backtrace" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = RuntimeError.new("message") two.set_backtrace nil one.should_not == two @@ -60,9 +60,9 @@ describe "Exception#==" do it "returns false if the two exceptions differ only in their message" do one = RuntimeError.new("message") - one.set_backtrace [File.dirname(__FILE__)] + one.set_backtrace [__dir__] two = RuntimeError.new("message2") - two.set_backtrace [File.dirname(__FILE__)] + two.set_backtrace [__dir__] one.should_not == two end end diff --git a/spec/ruby/core/exception/errno_spec.rb b/spec/ruby/core/exception/errno_spec.rb index 095a926e09..a063e522ea 100644 --- a/spec/ruby/core/exception/errno_spec.rb +++ b/spec/ruby/core/exception/errno_spec.rb @@ -56,3 +56,12 @@ describe "Errno::ENOTSUP" do end end end + +describe "Errno::ENOENT" do + it "lets subclasses inherit the default error message" do + c = Class.new(Errno::ENOENT) + raise c, "custom message" + rescue => e + e.message.should == "No such file or directory - custom message" + end +end diff --git a/spec/ruby/core/exception/fixtures/common.rb b/spec/ruby/core/exception/fixtures/common.rb index 0ffb3ed855..1e243098bd 100644 --- a/spec/ruby/core/exception/fixtures/common.rb +++ b/spec/ruby/core/exception/fixtures/common.rb @@ -93,3 +93,7 @@ class NameErrorSpecs end end end + +module DetailedMessageSpec + C = Class.new(RuntimeError) +end diff --git a/spec/ruby/core/exception/fixtures/syntax_error.rb b/spec/ruby/core/exception/fixtures/syntax_error.rb new file mode 100644 index 0000000000..ccec62f7a1 --- /dev/null +++ b/spec/ruby/core/exception/fixtures/syntax_error.rb @@ -0,0 +1,3 @@ +# rubocop:disable Lint/Syntax +1+1=2 +# rubocop:enable Lint/Syntax diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb new file mode 100644 index 0000000000..c109ec6247 --- /dev/null +++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure.rb @@ -0,0 +1,22 @@ +ready = false +t = Thread.new do + f = Fiber.new do + begin + Fiber.yield + ensure + STDERR.puts "suspended fiber ensure" + end + end + f.resume + + begin + ready = true + sleep + ensure + STDERR.puts "current fiber ensure" + end +end + +Thread.pass until ready && t.stop? + +# let the program end, it's the same as #exit or an exception for this behavior diff --git a/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb new file mode 100644 index 0000000000..3364ed06d0 --- /dev/null +++ b/spec/ruby/core/exception/fixtures/thread_fiber_ensure_non_root_fiber.rb @@ -0,0 +1,25 @@ +ready = false +t = Thread.new do + f = Fiber.new do + begin + Fiber.yield + ensure + STDERR.puts "suspended fiber ensure" + end + end + f.resume + + f2 = Fiber.new do + begin + ready = true + sleep + ensure + STDERR.puts "current fiber ensure" + end + end + f2.resume +end + +Thread.pass until ready && t.stop? + +# let the program end, it's the same as #exit or an exception for this behavior diff --git a/spec/ruby/core/exception/frozen_error_spec.rb b/spec/ruby/core/exception/frozen_error_spec.rb index f27b33295c..979ec2ff98 100644 --- a/spec/ruby/core/exception/frozen_error_spec.rb +++ b/spec/ruby/core/exception/frozen_error_spec.rb @@ -1,26 +1,38 @@ require_relative '../../spec_helper' describe "FrozenError.new" do - ruby_version_is "2.7" do - it "should take optional receiver argument" do - o = Object.new - FrozenError.new("msg", receiver: o).receiver.should equal(o) - end + it "should take optional receiver argument" do + o = Object.new + FrozenError.new("msg", receiver: o).receiver.should equal(o) end end describe "FrozenError#receiver" do - ruby_version_is "2.7" do - it "should return frozen object that modification was attempted on" do - o = Object.new.freeze - begin - def o.x; end - rescue => e - e.should be_kind_of(FrozenError) - e.receiver.should equal(o) - else - raise - end + it "should return frozen object that modification was attempted on" do + o = Object.new.freeze + begin + def o.x; end + rescue => e + e.should be_kind_of(FrozenError) + e.receiver.should equal(o) + else + raise + end + end +end + +describe "Modifying a frozen object" do + context "#inspect is redefined and modifies the object" do + it "returns ... instead of String representation of object" do + object = Object.new + def object.inspect; @a = 1 end + def object.modify; @a = 2 end + + object.freeze + + # CRuby's message contains multiple whitespaces before '...'. + # So handle both multiple and single whitespace. + -> { object.modify }.should raise_error(FrozenError, /can't modify frozen .*?: \s*.../) end end end diff --git a/spec/ruby/core/exception/full_message_spec.rb b/spec/ruby/core/exception/full_message_spec.rb index 4cece9ebf9..5154354555 100644 --- a/spec/ruby/core/exception/full_message_spec.rb +++ b/spec/ruby/core/exception/full_message_spec.rb @@ -15,13 +15,19 @@ describe "Exception#full_message" do 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: :bottom) - full_message.should include "\e[1mTraceback\e[m (most recent call last)" - full_message.should include "\e[1mSome runtime error (\e[1;4mRuntimeError\e[m\e[1m)" + full_message = e.full_message(highlight: 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: false, order: :bottom) - full_message.should include "Traceback (most recent call last)" - full_message.should include "Some runtime error (RuntimeError)" + 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 @@ -35,9 +41,52 @@ describe "Exception#full_message" do 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") + 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 + 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 @@ -45,12 +94,24 @@ describe "Exception#full_message" do 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[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 @@ -85,4 +146,53 @@ describe "Exception#full_message" do exception.full_message.should include "intermediate exception" exception.full_message.should include "origin exception" end + + ruby_version_is "3.2" do + it "relies on #detailed_message" do + e = RuntimeError.new("new error") + e.define_singleton_method(:detailed_message) { |**| "DETAILED MESSAGE" } + + e.full_message.lines.first.should =~ /DETAILED MESSAGE/ + end + + it "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 + end end diff --git a/spec/ruby/core/exception/interrupt_spec.rb b/spec/ruby/core/exception/interrupt_spec.rb index a7501efadc..90d261e470 100644 --- a/spec/ruby/core/exception/interrupt_spec.rb +++ b/spec/ruby/core/exception/interrupt_spec.rb @@ -48,4 +48,13 @@ describe "Interrupt" do RUBY out.should == "Interrupt: #{Signal.list["INT"]}\n" end + + platform_is_not :windows do + it "shows the backtrace and has a signaled exit status" do + err = IO.popen([*ruby_exe, '-e', 'Process.kill :INT, Process.pid; sleep'], err: [:child, :out], &:read) + $?.termsig.should == Signal.list.fetch('INT') + err.should.include? ': Interrupt' + err.should =~ /from -e:1:in [`']<main>'/ + end + end end diff --git a/spec/ruby/core/exception/no_method_error_spec.rb b/spec/ruby/core/exception/no_method_error_spec.rb index 8428ba0382..26df3338e9 100644 --- a/spec/ruby/core/exception/no_method_error_spec.rb +++ b/spec/ruby/core/exception/no_method_error_spec.rb @@ -62,26 +62,49 @@ describe "NoMethodError#message" do NoMethodErrorSpecs::NoMethodErrorC.new.a_private_method rescue Exception => e e.should be_kind_of(NoMethodError) - e.message.lines[0].should =~ /private method `a_private_method' called for #<NoMethodErrorSpecs::NoMethodErrorC:0x[\h]+>/ + e.message.lines[0].should =~ /private method [`']a_private_method' called for / end end - it "calls receiver.inspect only when calling Exception#message" do - ScratchPad.record [] - test_class = Class.new do - def inspect - ScratchPad << :inspect_called - "<inspect>" + ruby_version_is ""..."3.3" do + it "calls receiver.inspect only when calling Exception#message" do + ScratchPad.record [] + test_class = Class.new do + def inspect + ScratchPad << :inspect_called + "<inspect>" + end + end + instance = test_class.new + begin + instance.bar + rescue Exception => e + e.name.should == :bar + ScratchPad.recorded.should == [] + e.message.should =~ /undefined method.+\bbar\b/ + ScratchPad.recorded.should == [:inspect_called] end end - instance = test_class.new - begin - instance.bar - rescue Exception => e - e.name.should == :bar - ScratchPad.recorded.should == [] - e.message.should =~ /undefined method.+\bbar\b/ - ScratchPad.recorded.should == [:inspect_called] + end + + ruby_version_is "3.3" do + it "does not call receiver.inspect even when calling Exception#message" do + ScratchPad.record [] + test_class = Class.new do + def inspect + ScratchPad << :inspect_called + "<inspect>" + end + end + instance = test_class.new + begin + instance.bar + rescue Exception => e + e.name.should == :bar + ScratchPad.recorded.should == [] + e.message.should =~ /undefined method.+\bbar\b/ + ScratchPad.recorded.should == [] + end end end @@ -102,21 +125,19 @@ describe "NoMethodError#message" do end end - ruby_version_is "3.0" do - it "uses #name to display the receiver if it is a class or a module" do - klass = Class.new { def self.name; "MyClass"; end } - begin - klass.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should == "undefined method `foo' for MyClass:Class" - end + it "uses #name to display the receiver if it is a class or a module" do + klass = Class.new { def self.name; "MyClass"; end } + begin + klass.foo + rescue NoMethodError => error + error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for / + end - mod = Module.new { def self.name; "MyModule"; end } - begin - mod.foo - rescue NoMethodError => error - error.message.lines.first.chomp.should == "undefined method `foo' for MyModule:Module" - end + mod = Module.new { def self.name; "MyModule"; end } + begin + mod.foo + rescue NoMethodError => error + error.message.lines.first.chomp.should =~ /^undefined method [`']foo' for / end end end diff --git a/spec/ruby/core/exception/set_backtrace_spec.rb b/spec/ruby/core/exception/set_backtrace_spec.rb index ba2e1bf7aa..12c1da919c 100644 --- a/spec/ruby/core/exception/set_backtrace_spec.rb +++ b/spec/ruby/core/exception/set_backtrace_spec.rb @@ -11,9 +11,37 @@ describe "Exception#set_backtrace" do it "allows the user to set the backtrace from a rescued exception" do bt = ExceptionSpecs::Backtrace.backtrace err = RuntimeError.new + err.backtrace.should == nil + err.backtrace_locations.should == nil err.set_backtrace bt + err.backtrace.should == bt + err.backtrace_locations.should == nil + end + + ruby_version_is "3.4" do + it "allows the user to set backtrace locations from a rescued exception" do + bt_locations = ExceptionSpecs::Backtrace.backtrace_locations + err = RuntimeError.new + err.backtrace.should == nil + err.backtrace_locations.should == nil + + err.set_backtrace bt_locations + + err.backtrace_locations.size.should == bt_locations.size + err.backtrace_locations.each_with_index do |loc, index| + other_loc = bt_locations[index] + + loc.path.should == other_loc.path + loc.label.should == other_loc.label + loc.base_label.should == other_loc.base_label + loc.lineno.should == other_loc.lineno + loc.absolute_path.should == other_loc.absolute_path + loc.to_s.should == other_loc.to_s + end + err.backtrace.size.should == err.backtrace_locations.size + end end it "accepts an empty Array" do diff --git a/spec/ruby/core/exception/signal_exception_spec.rb b/spec/ruby/core/exception/signal_exception_spec.rb index 566bcb4672..1a0940743f 100644 --- a/spec/ruby/core/exception/signal_exception_spec.rb +++ b/spec/ruby/core/exception/signal_exception_spec.rb @@ -93,7 +93,7 @@ describe "SignalException" do platform_is_not :windows do it "runs after at_exit" do - output = ruby_exe(<<-RUBY, exit_status: nil) + output = ruby_exe(<<-RUBY, exit_status: :SIGKILL) at_exit do puts "hello" $stdout.flush @@ -107,7 +107,7 @@ describe "SignalException" do end it "cannot be trapped with Signal.trap" do - ruby_exe(<<-RUBY, exit_status: nil) + ruby_exe(<<-RUBY, exit_status: :SIGPROF) Signal.trap("PROF") {} raise(SignalException, "PROF") RUBY @@ -116,7 +116,7 @@ describe "SignalException" do end it "self-signals for USR1" do - ruby_exe("raise(SignalException, 'USR1')", exit_status: nil) + ruby_exe("raise(SignalException, 'USR1')", exit_status: :SIGUSR1) $?.termsig.should == Signal.list.fetch('USR1') end end diff --git a/spec/ruby/core/exception/syntax_error_spec.rb b/spec/ruby/core/exception/syntax_error_spec.rb new file mode 100644 index 0000000000..6cc8522de3 --- /dev/null +++ b/spec/ruby/core/exception/syntax_error_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.2" do + describe "SyntaxError#path" do + it "returns the file path provided to eval" do + filename = "speccing.rb" + + -> { + eval("if true", TOPLEVEL_BINDING, filename) + }.should raise_error(SyntaxError) { |e| + e.path.should == filename + } + end + + it "returns the file path that raised an exception" do + expected_path = fixture(__FILE__, "syntax_error.rb") + + -> { + require_relative "fixtures/syntax_error" + }.should raise_error(SyntaxError) { |e| e.path.should == expected_path } + end + + it "returns nil when constructed directly" do + SyntaxError.new.path.should == nil + end + end +end diff --git a/spec/ruby/core/exception/system_exit_spec.rb b/spec/ruby/core/exception/system_exit_spec.rb index eef9faf271..d899844c4e 100644 --- a/spec/ruby/core/exception/system_exit_spec.rb +++ b/spec/ruby/core/exception/system_exit_spec.rb @@ -1,6 +1,48 @@ 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) diff --git a/spec/ruby/core/exception/to_s_spec.rb b/spec/ruby/core/exception/to_s_spec.rb index 4c4c7ab432..65c0d73a98 100644 --- a/spec/ruby/core/exception/to_s_spec.rb +++ b/spec/ruby/core/exception/to_s_spec.rb @@ -23,7 +23,7 @@ describe "NameError#to_s" do begin puts not_defined rescue => exception - exception.message.should =~ /undefined local variable or method `not_defined'/ + exception.message.should =~ /undefined local variable or method [`']not_defined'/ end end diff --git a/spec/ruby/core/exception/top_level_spec.rb b/spec/ruby/core/exception/top_level_spec.rb index b47648425e..cc961d06d5 100644 --- a/spec/ruby/core/exception/top_level_spec.rb +++ b/spec/ruby/core/exception/top_level_spec.rb @@ -2,30 +2,38 @@ require_relative '../../spec_helper' describe "An Exception reaching the top level" do it "is printed on STDERR" do - ruby_exe('raise "foo"', args: "2>&1", exit_status: 1).should.include?("in `<main>': foo (RuntimeError)") + 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" + raise "the cause" # 2 end def raise_wrapped - raise "wrapped" + raise "wrapped" # 5 end begin - raise_cause + raise_cause # 8 rescue - raise_wrapped + raise_wrapped # 10 end RUBY lines = ruby_exe(code, args: "2>&1", exit_status: 1).lines - lines.reject! { |l| l.include?('rescue in') } - lines.map! { |l| l.chomp[/:(in.+)/, 1] } - lines.should == ["in `raise_wrapped': wrapped (RuntimeError)", - "in `<main>'", - "in `raise_cause': the cause (RuntimeError)", - "in `<main>'"] + + 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 @@ -42,4 +50,16 @@ describe "An Exception reaching the top level" do 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 |