diff options
Diffstat (limited to 'spec/ruby/shared')
| -rw-r--r-- | spec/ruby/shared/file/socket.rb | 32 | ||||
| -rw-r--r-- | spec/ruby/shared/io/putc.rb | 2 | ||||
| -rw-r--r-- | spec/ruby/shared/kernel/raise.rb | 348 | ||||
| -rw-r--r-- | spec/ruby/shared/queue/deque.rb | 118 | ||||
| -rw-r--r-- | spec/ruby/shared/sizedqueue/enque.rb | 152 |
5 files changed, 458 insertions, 194 deletions
diff --git a/spec/ruby/shared/file/socket.rb b/spec/ruby/shared/file/socket.rb index 55a1cfd284..ef6c482d1c 100644 --- a/spec/ruby/shared/file/socket.rb +++ b/spec/ruby/shared/file/socket.rb @@ -1,3 +1,33 @@ describe :file_socket, shared: true do - it "accepts an object that has a #to_path method" + it "returns false if the file is not a socket" do + filename = tmp("i_exist") + touch(filename) + + @object.send(@method, filename).should == false + + rm_r filename + end + + it "returns true if the file is a socket" do + require 'socket' + + # We need a really short name here. + # On Linux the path length is limited to 107, see unix(7). + name = tmp("s") + server = UNIXServer.new(name) + + @object.send(@method, name).should == true + + server.close + rm_r name + end + + it "accepts an object that has a #to_path method" do + obj = Object.new + def obj.to_path + __FILE__ + end + + @object.send(@method, obj).should == false + end end diff --git a/spec/ruby/shared/io/putc.rb b/spec/ruby/shared/io/putc.rb index e6012c0098..cdf18ac9fd 100644 --- a/spec/ruby/shared/io/putc.rb +++ b/spec/ruby/shared/io/putc.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :io_putc, shared: true do after :each do @io.close if @io && !@io.closed? diff --git a/spec/ruby/shared/kernel/raise.rb b/spec/ruby/shared/kernel/raise.rb index 1917a4c923..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,59 +71,57 @@ 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 - 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 @@ -157,4 +140,259 @@ describe :kernel_raise, shared: true do } 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 + 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_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 + end + + it "supports automatic cause chaining" do + -> do + begin + 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 + + 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 + +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 diff --git a/spec/ruby/shared/queue/deque.rb b/spec/ruby/shared/queue/deque.rb index 0abba5301e..a154da6274 100644 --- a/spec/ruby/shared/queue/deque.rb +++ b/spec/ruby/shared/queue/deque.rb @@ -65,66 +65,64 @@ describe :queue_deq, shared: true do end describe "with a timeout" do - ruby_version_is "3.2" do - it "returns an item if one is available in time" do - q = @object.call - - t = Thread.new { - q.send(@method, timeout: TIME_TOLERANCE).should == 1 - } - Thread.pass until t.status == "sleep" && q.num_waiting == 1 - q << 1 - t.join - end - - it "returns nil if no item is available in time" do - q = @object.call - - Thread.new { - q.send(@method, timeout: 0.001).should == nil - }.join - end - - it "does nothing if the timeout is nil" do - q = @object.call - t = Thread.new { - q.send(@method, timeout: nil).should == 1 - } - Thread.pass until t.status == "sleep" && q.num_waiting == 1 - q << 1 - t.join - end - - it "immediately returns nil if no item is available and the timeout is 0" do - q = @object.call - q << 1 - q.send(@method, timeout: 0).should == 1 - q.send(@method, timeout: 0).should == nil - end - - it "raise TypeError if timeout is not a valid numeric" do - q = @object.call - -> { - q.send(@method, timeout: "1") - }.should raise_error(TypeError, "no implicit conversion to float from string") - - -> { - q.send(@method, timeout: false) - }.should raise_error(TypeError, "no implicit conversion to float from false") - end - - it "raise ArgumentError if non_block = true is passed too" do - q = @object.call - -> { - q.send(@method, true, timeout: 1) - }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") - end - - it "returns nil for a closed empty queue" do - q = @object.call - q.close - q.send(@method, timeout: 0).should == nil - end + it "returns an item if one is available in time" do + q = @object.call + + t = Thread.new { + q.send(@method, timeout: TIME_TOLERANCE).should == 1 + } + Thread.pass until t.status == "sleep" && q.num_waiting == 1 + q << 1 + t.join + end + + it "returns nil if no item is available in time" do + q = @object.call + + Thread.new { + q.send(@method, timeout: 0.001).should == nil + }.join + end + + it "does nothing if the timeout is nil" do + q = @object.call + t = Thread.new { + q.send(@method, timeout: nil).should == 1 + } + Thread.pass until t.status == "sleep" && q.num_waiting == 1 + q << 1 + t.join + end + + it "immediately returns nil if no item is available and the timeout is 0" do + q = @object.call + q << 1 + q.send(@method, timeout: 0).should == 1 + q.send(@method, timeout: 0).should == nil + end + + it "raise TypeError if timeout is not a valid numeric" do + q = @object.call + -> { + q.send(@method, timeout: "1") + }.should raise_error(TypeError, "no implicit conversion to float from string") + + -> { + q.send(@method, timeout: false) + }.should raise_error(TypeError, "no implicit conversion to float from false") + end + + it "raise ArgumentError if non_block = true is passed too" do + q = @object.call + -> { + q.send(@method, true, timeout: 1) + }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") + end + + it "returns nil for a closed empty queue" do + q = @object.call + q.close + q.send(@method, timeout: 0).should == nil end end diff --git a/spec/ruby/shared/sizedqueue/enque.rb b/spec/ruby/shared/sizedqueue/enque.rb index 2f25517675..6804af3fb3 100644 --- a/spec/ruby/shared/sizedqueue/enque.rb +++ b/spec/ruby/shared/sizedqueue/enque.rb @@ -49,83 +49,81 @@ describe :sizedqueue_enq, shared: true do end describe "with a timeout" do - ruby_version_is "3.2" do - it "returns self if the item was pushed in time" do - q = @object.call(1) - q << 1 - - t = Thread.new { - q.send(@method, 2, timeout: TIME_TOLERANCE).should == q - } - Thread.pass until t.status == "sleep" && q.num_waiting == 1 - q.pop - t.join - end - - it "does nothing if the timeout is nil" do - q = @object.call(1) - q << 1 - t = Thread.new { - q.send(@method, 2, timeout: nil).should == q - } - t.join(0.2).should == nil - q.pop - t.join - end - - it "returns nil if no space is available and timeout is 0" do - q = @object.call(1) - q.send(@method, 1, timeout: 0).should == q - q.send(@method, 2, timeout: 0).should == nil - end - - it "returns nil if no space is available in time" do - q = @object.call(1) - q << 1 - Thread.new { - q.send(@method, 2, timeout: 0.001).should == nil - }.join - end - - it "raise TypeError if timeout is not a valid numeric" do - q = @object.call(1) - -> { - q.send(@method, 2, timeout: "1") - }.should raise_error(TypeError, "no implicit conversion to float from string") - - -> { - q.send(@method, 2, timeout: false) - }.should raise_error(TypeError, "no implicit conversion to float from false") - end - - it "raise ArgumentError if non_block = true is passed too" do - q = @object.call(1) - -> { - q.send(@method, 2, true, timeout: 1) - }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") - end - - it "raise ClosedQueueError when closed before enqueued" do - q = @object.call(1) - q.close - -> { q.send(@method, 2, timeout: 1) }.should raise_error(ClosedQueueError, "queue closed") - end - - it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do - q = @object.call(1) - q << 1 - - t = Thread.new { - -> { q.send(@method, 1, timeout: TIME_TOLERANCE) }.should raise_error(ClosedQueueError, "queue closed") - } - - Thread.pass until q.num_waiting == 1 - - q.close - - t.join - q.pop.should == 1 - end + it "returns self if the item was pushed in time" do + q = @object.call(1) + q << 1 + + t = Thread.new { + q.send(@method, 2, timeout: TIME_TOLERANCE).should == q + } + Thread.pass until t.status == "sleep" && q.num_waiting == 1 + q.pop + t.join + end + + it "does nothing if the timeout is nil" do + q = @object.call(1) + q << 1 + t = Thread.new { + q.send(@method, 2, timeout: nil).should == q + } + t.join(0.2).should == nil + q.pop + t.join + end + + it "returns nil if no space is available and timeout is 0" do + q = @object.call(1) + q.send(@method, 1, timeout: 0).should == q + q.send(@method, 2, timeout: 0).should == nil + end + + it "returns nil if no space is available in time" do + q = @object.call(1) + q << 1 + Thread.new { + q.send(@method, 2, timeout: 0.001).should == nil + }.join + end + + it "raise TypeError if timeout is not a valid numeric" do + q = @object.call(1) + -> { + q.send(@method, 2, timeout: "1") + }.should raise_error(TypeError, "no implicit conversion to float from string") + + -> { + q.send(@method, 2, timeout: false) + }.should raise_error(TypeError, "no implicit conversion to float from false") + end + + it "raise ArgumentError if non_block = true is passed too" do + q = @object.call(1) + -> { + q.send(@method, 2, true, timeout: 1) + }.should raise_error(ArgumentError, "can't set a timeout if non_block is enabled") + end + + it "raise ClosedQueueError when closed before enqueued" do + q = @object.call(1) + q.close + -> { q.send(@method, 2, timeout: 1) }.should raise_error(ClosedQueueError, "queue closed") + end + + it "interrupts enqueuing threads with ClosedQueueError when the queue is closed" do + q = @object.call(1) + q << 1 + + t = Thread.new { + -> { q.send(@method, 1, timeout: TIME_TOLERANCE) }.should raise_error(ClosedQueueError, "queue closed") + } + + Thread.pass until q.num_waiting == 1 + + q.close + + t.join + q.pop.should == 1 end end end |
