summaryrefslogtreecommitdiff
path: root/spec/ruby/shared/kernel/raise.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/shared/kernel/raise.rb')
-rw-r--r--spec/ruby/shared/kernel/raise.rb362
1 files changed, 297 insertions, 65 deletions
diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb
index 1917a4c923..07b6a30de2 100644
--- a/spec/ruby/shared/kernel/raise.rb
+++ b/spec/ruby/shared/kernel/raise.rb
@@ -7,9 +7,9 @@ describe :kernel_raise, shared: true do
-> do
@object.raise Exception, "abort"
ScratchPad.record :no_abort
- end.should raise_error(Exception, "abort")
+ end.should.raise(Exception, "abort")
- ScratchPad.recorded.should be_nil
+ ScratchPad.recorded.should == nil
end
it "accepts an exception that implements to_hash" do
@@ -19,7 +19,7 @@ describe :kernel_raise, shared: true do
end
end
error = custom_error.new
- -> { @object.raise(error) }.should raise_error(custom_error)
+ -> { @object.raise(error) }.should.raise(custom_error)
end
it "allows the message parameter to be a hash" do
@@ -30,7 +30,7 @@ describe :kernel_raise, shared: true do
end
end
- -> { @object.raise(data_error, {data: 42}) }.should raise_error(data_error) do |ex|
+ -> { @object.raise(data_error, {data: 42}) }.should.raise(data_error) do |ex|
ex.data.should == {data: 42}
end
end
@@ -44,37 +44,22 @@ describe :kernel_raise, shared: true do
end
end
- -> { @object.raise(data_error, data: 42) }.should raise_error(data_error) do |ex|
+ -> { @object.raise(data_error, data: 42) }.should.raise(data_error) do |ex|
ex.data.should == {data: 42}
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, "")
+ -> { @object.raise }.should.raise(RuntimeError, "")
end
it "raises a given Exception instance" do
error = RuntimeError.new
- -> { @object.raise(error) }.should raise_error(error)
+ -> { @object.raise(error) }.should.raise(error)
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(RuntimeError, "a bad thing")
end
it "passes no arguments to the constructor when given only an exception class" do
@@ -82,69 +67,67 @@ describe :kernel_raise, shared: true do
def initialize
end
end
- -> { @object.raise(klass) }.should raise_error(klass) { |e| e.message.should == klass.to_s }
+ -> { @object.raise(klass) }.should.raise(klass) { |e| e.message.should == klass.to_s }
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(TypeError, "exception class/object expected")
+ -> { @object.raise(Object.new, "message") }.should.raise(TypeError, "exception class/object expected")
+ -> { @object.raise(Object.new, "message", []) }.should.raise(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(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(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(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(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(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
- begin
- initial_raise_line = __LINE__; Fiber.yield
- rescue => raised
- begin
- raise_again_line = __LINE__; Fiber.yield raised
- rescue => raised_again
- raised_again
- end
- end
- end
- fiber.resume
- raised = fiber.raise 'raised'
- raised_again = fiber.raise raised
- else
- begin
- initial_raise_line = __LINE__; @object.raise 'raised'
- rescue => raised
- begin
- raise_again_line = __LINE__; @object.raise raised
- rescue => raised_again
- raised_again
- end
- end
+ exception = nil
+
+ begin
+ raise "raised"
+ rescue => exception
+ # Ignore.
end
- raised_again.backtrace.first.should include("#{__FILE__}:#{initial_raise_line}:")
- raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:")
+ 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.should.raise(ArgumentError, "message")
end
ruby_version_is "3.4" do
@@ -152,9 +135,258 @@ describe :kernel_raise, shared: true do
it "allows Exception, message, and backtrace_locations parameters" do
-> do
@object.raise(ArgumentError, "message", locations)
- end.should raise_error(ArgumentError, "message") { |error|
+ end.should.raise(ArgumentError, "message") { |error|
error.backtrace_locations.map(&:to_s).should == locations.map(&:to_s)
}
end
end
end
+
+describe :kernel_raise_with_cause, shared: true do
+ context "without cause keyword argument" do
+ it "sets cause to nil when there is no previous exception" do
+ -> do
+ @object.raise("error without a cause")
+ end.should.raise(RuntimeError, "error without a cause", cause: nil)
+ end
+
+ it "supports automatic cause chaining from a previous exception" do
+ begin
+ raise StandardError,"first error"
+ rescue => cause
+ -> { @object.raise("second error") }.should.raise(RuntimeError, "second error", cause:)
+ end
+ end
+ end
+
+ context "with cause keyword argument" do
+ it "allows setting exception's cause" do
+ cause = StandardError.new("original error")
+
+ -> do
+ @object.raise("new error", cause:)
+ end.should.raise(RuntimeError, "new error", cause:)
+ end
+
+ it "allows setting cause to nil" do
+ -> do
+ @object.raise("error without a cause", cause: nil)
+ end.should.raise(RuntimeError, "error without a cause") do |error|
+ error.cause.should == nil
+ end
+ end
+
+ it "raises an ArgumentError when only cause is given" do
+ cause = StandardError.new("cause")
+ -> do
+ @object.raise(cause:)
+ end.should.raise(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(ArgumentError, "only cause is given with no arguments")
+ end
+
+ it "raises a TypeError when given cause is not an instance of Exception or nil" do
+ cause = Object.new
+ -> do
+ @object.raise("message", cause:)
+ end.should.raise(TypeError, "exception object expected")
+ end
+
+ it "doesn't set given cause when it equals the raised exception" do
+ cause = StandardError.new("cause")
+
+ -> do
+ @object.raise(cause, cause:)
+ end.should.raise(StandardError, "cause", cause: nil)
+ end
+
+ it "raises ArgumentError when cause creates a circular reference" do
+ -> {
+ begin
+ raise "Error 1"
+ rescue => error1
+ begin
+ raise "Error 2"
+ rescue => error2
+ begin
+ raise "Error 3"
+ rescue => error3
+ @object.raise(error1, cause: error3)
+ end
+ end
+ end
+ }.should.raise(ArgumentError, "circular causes")
+ end
+
+ it "supports exception class with message and cause" do
+ cause = StandardError.new("cause message")
+
+ -> do
+ @object.raise(ArgumentError, "argument error message", cause:)
+ end.should.raise(ArgumentError, "argument error message", cause:)
+ end
+
+ it "supports exception class with message, backtrace and cause" do
+ cause = StandardError.new("cause message")
+ backtrace = ["line1", "line2"]
+
+ -> do
+ @object.raise(ArgumentError, "argument error message", backtrace, cause:)
+ end.should.raise(ArgumentError, "argument error message", cause:) do |error|
+ error.backtrace.should == backtrace
+ end
+ end
+
+ it "supports cause: exception, overriding previous exception" do
+ custom_error = StandardError.new("custom error")
+ -> do
+ begin
+ raise "first error"
+ rescue
+ @object.raise("second error", cause: custom_error)
+ end
+ end.should.raise(RuntimeError, "second error", cause: custom_error)
+ end
+
+ it "supports cause: nil, discarding previous exception" do
+ -> do
+ begin
+ raise "first error"
+ rescue
+ # Explicit nil prevents chaining:
+ @object.raise("second error", cause: nil)
+ end
+ end.should.raise(RuntimeError, "second error", cause: nil)
+ end
+ end
+end
+
+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.is_a?(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.is_a?(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.is_a?(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.is_a?(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(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(ArgumentError, "only cause is given with no arguments")
+ end
+ end
+ end
+end