diff options
Diffstat (limited to 'spec/ruby/shared/kernel')
| -rw-r--r-- | spec/ruby/shared/kernel/at_exit.rb | 11 | ||||
| -rw-r--r-- | spec/ruby/shared/kernel/object_id.rb | 28 | ||||
| -rw-r--r-- | spec/ruby/shared/kernel/raise.rb | 351 |
3 files changed, 334 insertions, 56 deletions
diff --git a/spec/ruby/shared/kernel/at_exit.rb b/spec/ruby/shared/kernel/at_exit.rb index 26ad361a5b..29db79bb39 100644 --- a/spec/ruby/shared/kernel/at_exit.rb +++ b/spec/ruby/shared/kernel/at_exit.rb @@ -30,6 +30,12 @@ describe :kernel_at_exit, shared: true do result.lines.should.include?("The exception matches: true (message=foo)\n") end + it "gives access to an exception raised in a previous handler" do + code = "#{@method} { print '$!.message = ' + $!.message }; #{@method} { raise 'foo' }" + result = ruby_exe(code, args: "2>&1", exit_status: 1) + result.lines.should.include?("$!.message = foo") + end + it "both exceptions in a handler and in the main script are printed" do code = "#{@method} { raise 'at_exit_error' }; raise 'main_script_error'" result = ruby_exe(code, args: "2>&1", exit_status: 1) @@ -54,7 +60,10 @@ describe :kernel_at_exit, shared: true do result = ruby_exe('{', options: "-r#{script}", args: "2>&1", exit_status: 1) $?.should_not.success? result.should.include?("handler ran\n") - result.should.include?("syntax error") + + # it's tempting not to rely on error message and rely only on exception class name, + # but CRuby before 3.2 doesn't print class name for syntax error + result.should include_any_of("syntax error", "SyntaxError") end it "calls the nested handler right after the outer one if a handler is nested into another handler" do diff --git a/spec/ruby/shared/kernel/object_id.rb b/spec/ruby/shared/kernel/object_id.rb index 7acdb27554..099df8ff94 100644 --- a/spec/ruby/shared/kernel/object_id.rb +++ b/spec/ruby/shared/kernel/object_id.rb @@ -52,10 +52,30 @@ describe :object_id, shared: true do o1.send(@method).should_not == o2.send(@method) end - it "returns a different value for two String literals" do - o1 = "hello" - o2 = "hello" - o1.send(@method).should_not == o2.send(@method) + guard -> { "test".frozen? && "test".equal?("test") } do # --enable-frozen-string-literal in $RUBYOPT + it "returns the same value for two identical String literals" do + o1 = "hello" + o2 = "hello" + o1.send(@method).should == o2.send(@method) + end + end + + guard -> { "test".frozen? && !"test".equal?("test") } do # chilled string literals + it "returns a different frozen value for two String literals" do + o1 = "hello" + o2 = "hello" + o1.send(@method).should_not == o2.send(@method) + o1.frozen?.should == true + o2.frozen?.should == true + end + end + + guard -> { !"test".frozen? } do + it "returns a different value for two String literals" do + o1 = "hello" + o2 = "hello" + o1.send(@method).should_not == o2.send(@method) + end end it "returns a different value for an object and its dup" do diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 82fb0333c8..2be06ea797 100644 --- a/spec/ruby/shared/kernel/raise.rb +++ b/spec/ruby/shared/kernel/raise.rb @@ -49,21 +49,6 @@ describe :kernel_raise, shared: true do end end - it "does not allow message and extra keyword arguments" do - data_error = Class.new(StandardError) do - attr_reader :data - def initialize(data) - @data = data - end - end - - -> { @object.raise(data_error, {a: 1}, b: 2) }.should raise_error(StandardError) do |e| - [TypeError, ArgumentError].should.include?(e.class) - end - - -> { @object.raise(data_error, {a: 1}, [], b: 2) }.should raise_error(ArgumentError) - end - it "raises RuntimeError if no exception class is given" do -> { @object.raise }.should raise_error(RuntimeError, "") end @@ -74,7 +59,7 @@ describe :kernel_raise, shared: true do end it "raises a RuntimeError if string given" do - -> { @object.raise("a bad thing") }.should raise_error(RuntimeError) + -> { @object.raise("a bad thing") }.should raise_error(RuntimeError, "a bad thing") end it "passes no arguments to the constructor when given only an exception class" do @@ -86,64 +71,328 @@ describe :kernel_raise, shared: true do end it "raises a TypeError when passed a non-Exception object" do - -> { @object.raise(Object.new) }.should raise_error(TypeError) + -> { @object.raise(Object.new) }.should raise_error(TypeError, "exception class/object expected") + -> { @object.raise(Object.new, "message") }.should raise_error(TypeError, "exception class/object expected") + -> { @object.raise(Object.new, "message", []) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed true" do - -> { @object.raise(true) }.should raise_error(TypeError) + -> { @object.raise(true) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed false" do - -> { @object.raise(false) }.should raise_error(TypeError) + -> { @object.raise(false) }.should raise_error(TypeError, "exception class/object expected") end it "raises a TypeError when passed nil" do - -> { @object.raise(nil) }.should raise_error(TypeError) + -> { @object.raise(nil) }.should raise_error(TypeError, "exception class/object expected") + end + + it "raises a TypeError when passed a message and an extra argument" do + -> { @object.raise("message", {cause: RuntimeError.new()}) }.should raise_error(TypeError, "exception class/object expected") + end + + it "raises TypeError when passed a non-Exception object but it responds to #exception method that doesn't return an instance of Exception class" do + e = Object.new + def e.exception + Array + end + + -> { + @object.raise e + }.should raise_error(TypeError, "exception object expected") end it "re-raises a previously rescued exception without overwriting the backtrace" do - # This spec is written using #backtrace and matching the line number - # from the string, as backtrace_locations is a more advanced - # method that is not always supported by implementations. - # - initial_raise_line = nil - raise_again_line = nil - raised_again = nil - - if defined?(FiberSpecs::NewFiberToRaise) and @object == FiberSpecs::NewFiberToRaise - fiber = Fiber.new do + exception = nil + + begin + raise "raised" + rescue => exception + # Ignore. + end + + backtrace = exception.backtrace + + begin + raised_exception = @object.raise(exception) + rescue => raised_exception + # Ignore. + end + + raised_exception.backtrace.should == backtrace + raised_exception.should == exception + end + + it "allows Exception, message, and backtrace parameters" do + -> do + @object.raise(ArgumentError, "message", caller) + end.should raise_error(ArgumentError, "message") + end + + ruby_version_is "3.4" do + locations = caller_locations(1, 2) + it "allows Exception, message, and backtrace_locations parameters" do + -> do + @object.raise(ArgumentError, "message", locations) + end.should raise_error(ArgumentError, "message") { |error| + error.backtrace_locations.map(&:to_s).should == locations.map(&:to_s) + } + end + end + + ruby_version_is "4.0" do + it "allows cause keyword argument" do + cause = StandardError.new("original error") + result = nil + + -> do + @object.raise("new error", cause: cause) + end.should raise_error(RuntimeError, "new error") do |error| + error.cause.should == cause + end + end + + it "raises an ArgumentError when only cause is given" do + cause = StandardError.new("cause") + -> do + @object.raise(cause: cause) + end.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises an ArgumentError when only cause is given and is nil" do + -> do + @object.raise(cause: nil) + end.should raise_error(ArgumentError, "only cause is given with no arguments") + end + + it "raises a TypeError when given cause is not an instance of Exception" do + cause = Object.new + -> do + @object.raise("message", cause: cause) + end.should raise_error(TypeError, "exception object expected") + end + + it "doesn't set given cause when it equals the raised exception" do + cause = StandardError.new("cause") + result = nil + + -> do + @object.raise(cause, cause: cause) + end.should raise_error(StandardError, "cause") do |error| + error.should == cause + error.cause.should == nil + end + end + + it "accepts cause equal an exception" do + error = RuntimeError.new("message") + result = nil + + -> do + @object.raise(error, cause: error) + end.should raise_error(RuntimeError, "message") do |e| + e.cause.should == nil + end + end + + it "rejects circular causes" do + -> { begin - initial_raise_line = __LINE__; Fiber.yield - rescue => raised + raise "Error 1" + rescue => error1 begin - raise_again_line = __LINE__; Fiber.yield raised - rescue => raised_again - raised_again + raise "Error 2" + rescue => error2 + begin + raise "Error 3" + rescue => error3 + @object.raise(error1, cause: error3) + end end end + }.should raise_error(ArgumentError, "circular causes") + end + + it "supports exception class with message and cause" do + cause = StandardError.new("cause message") + result = nil + + -> do + @object.raise(ArgumentError, "argument error message", cause: cause) + end.should raise_error(ArgumentError, "argument error message") do |error| + error.should be_kind_of(ArgumentError) + error.message.should == "argument error message" + error.cause.should == cause + end + end + + it "supports exception class with message, backtrace and cause" do + cause = StandardError.new("cause message") + backtrace = ["line1", "line2"] + result = nil + + -> do + @object.raise(ArgumentError, "argument error message", backtrace, cause: cause) + end.should raise_error(ArgumentError, "argument error message") do |error| + error.should be_kind_of(ArgumentError) + error.message.should == "argument error message" + error.cause.should == cause + error.backtrace.should == backtrace end - fiber.resume - raised = fiber.raise 'raised' - raised_again = fiber.raise raised - else - begin - initial_raise_line = __LINE__; @object.raise 'raised' - rescue => raised + end + + it "supports automatic cause chaining" do + -> do begin - raise_again_line = __LINE__; @object.raise raised - rescue => raised_again - raised_again + raise "first error" + rescue + # No explicit cause - should chain automatically: + @object.raise("second error") end + end.should raise_error(RuntimeError, "second error") do |error| + error.cause.should be_kind_of(RuntimeError) + error.cause.message.should == "first error" end end - raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:") - raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:") + it "supports cause: nil to prevent automatic cause chaining" do + -> do + begin + raise "first error" + rescue + # Explicit nil prevents chaining: + @object.raise("second error", cause: nil) + end + end.should raise_error(RuntimeError, "second error") do |error| + error.cause.should == nil + end + end end +end - it "allows Exception, message, and backtrace parameters" do - -> do - @object.raise(ArgumentError, "message", caller) - end.should raise_error(ArgumentError, "message") +describe :kernel_raise_across_contexts, shared: true do + ruby_version_is "4.0" do + describe "with cause keyword argument" do + it "uses the cause from the calling context" do + original_cause = nil + result = nil + + # We have no cause ($!) and we don't specify one explicitly either: + @object.raise("second error") do |&block| + begin + begin + raise "first error" + rescue => original_cause + # We have a cause here ($!) but we should ignore it: + block.call + end + rescue => result + # Ignore. + end + end + + result.should be_kind_of(RuntimeError) + result.message.should == "second error" + result.cause.should == nil + end + + it "accepts a cause keyword argument that overrides the last exception" do + original_cause = nil + override_cause = StandardError.new("override cause") + result = nil + + begin + raise "outer error" + rescue + # We have an existing cause, but we want to override it: + @object.raise("second error", cause: override_cause) do |&block| + begin + begin + raise "first error" + rescue => original_cause + # We also have an existing cause here: + block.call + end + rescue => result + # Ignore. + end + end + end + + result.should be_kind_of(RuntimeError) + result.message.should == "second error" + result.cause.should == override_cause + end + + it "supports automatic cause chaining from calling context" do + result = nil + + @object.raise("new error") do |&block| + begin + begin + raise "original error" + rescue + block.call # Let the context yield/sleep + end + rescue => result + # Ignore. + end + end + + result.should be_kind_of(RuntimeError) + result.message.should == "new error" + # Calling context has no current exception: + result.cause.should == nil + end + + it "supports explicit cause: nil to prevent cause chaining" do + result = nil + + begin + raise "calling context error" + rescue + @object.raise("new error", cause: nil) do |&block| + begin + begin + raise "target context error" + rescue + block.call # Let the context yield/sleep + end + rescue => result + # Ignore. + end + end + + result.should be_kind_of(RuntimeError) + result.message.should == "new error" + result.cause.should == nil + end + end + + it "raises TypeError when cause is not an Exception" do + -> { + @object.raise("error", cause: "not an exception") do |&block| + begin + block.call # Let the context yield/sleep + rescue + # Ignore - we expect the TypeError to be raised in the calling context + end + end + }.should raise_error(TypeError, "exception object expected") + end + + it "raises ArgumentError when only cause is given with no arguments" do + -> { + @object.raise(cause: StandardError.new("cause")) do |&block| + begin + block.call # Let the context yield/sleep + rescue + # Ignore - we expect the ArgumentError to be raised in the calling context + end + end + }.should raise_error(ArgumentError, "only cause is given with no arguments") + end + end end end |
