summaryrefslogtreecommitdiff
path: root/spec/ruby/core/thread/raise_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/thread/raise_spec.rb')
-rw-r--r--spec/ruby/core/thread/raise_spec.rb242
1 files changed, 242 insertions, 0 deletions
diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb
new file mode 100644
index 0000000000..3b02a2e005
--- /dev/null
+++ b/spec/ruby/core/thread/raise_spec.rb
@@ -0,0 +1,242 @@
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
+
+describe "Thread#raise" do
+ it "is a public method" do
+ Thread.public_instance_methods.should.include?(:raise)
+ end
+
+ it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise
+ it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise
+ ruby_version_is "4.0" do
+ it_behaves_like :kernel_raise_with_cause, :raise, ThreadSpecs::NewThreadToRaise
+ end
+
+ it "ignores dead threads and returns nil" do
+ t = Thread.new { :dead }
+ Thread.pass while t.alive?
+ t.raise("Kill the thread").should == nil
+ t.join
+ end
+end
+
+describe "Thread#raise on a sleeping thread" do
+ before :each do
+ ScratchPad.clear
+ @thr = ThreadSpecs.sleeping_thread
+ Thread.pass while @thr.status and @thr.status != "sleep"
+ end
+
+ after :each do
+ @thr.kill
+ @thr.join
+ end
+
+ it "raises a RuntimeError if no exception class is given" do
+ @thr.raise
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(Exception)
+ end
+
+ it "raises the given exception with the given message" do
+ @thr.raise Exception, "get to work"
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(Exception)
+ ScratchPad.recorded.message.should == "get to work"
+ end
+
+ it "raises the given exception and the backtrace is the one of the interrupted thread" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(Exception)
+ ScratchPad.recorded.backtrace[0].should.include?("sleep")
+ end
+
+ it "is captured and raised by Thread#value" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ sleep
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ t.raise
+ -> { t.value }.should.raise(RuntimeError)
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ sleep
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ ThreadSpecs.spin_until_sleeping(t)
+ t.raise
+ end
+ -> { t.value }.should.raise(RuntimeError)
+ end
+
+ it "re-raises a previously rescued exception without overwriting the backtrace" do
+ t = Thread.new do
+ -> { # To make sure there is at least one entry in the call stack
+ begin
+ sleep
+ rescue => e
+ e
+ end
+ }.call
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ begin
+ initial_raise_line = __LINE__; raise 'raised'
+ rescue => raised
+ raise_again_line = __LINE__; t.raise raised
+ raised_again = t.value
+
+ raised_again.backtrace.first.should.include?("#{__FILE__}:#{initial_raise_line}:")
+ raised_again.backtrace.first.should_not.include?("#{__FILE__}:#{raise_again_line}:")
+ end
+ end
+
+ it "calls #exception in both the caller and in the target thread" do
+ cls = Class.new(Exception) do
+ attr_accessor :log
+ def initialize(*args)
+ @log = [] # This is shared because the super #exception uses a shallow clone
+ super
+ end
+
+ def exception(*args)
+ @log << [self, Thread.current, args]
+ super
+ end
+ end
+ exc = cls.new
+
+ @thr.raise exc, "Thread#raise #exception spec"
+ @thr.join
+ ScratchPad.recorded.should.is_a?(cls)
+ exc.log.should == [
+ [exc, Thread.current, ["Thread#raise #exception spec"]],
+ [ScratchPad.recorded, @thr, []]
+ ]
+ end
+end
+
+describe "Thread#raise on a running thread" do
+ before :each do
+ ScratchPad.clear
+ ThreadSpecs.clear_state
+
+ @thr = ThreadSpecs.running_thread
+ Thread.pass until ThreadSpecs.state == :running
+ end
+
+ after :each do
+ @thr.kill
+ @thr.join
+ end
+
+ it "raises a RuntimeError if no exception class is given" do
+ @thr.raise
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(Exception)
+ end
+
+ it "raises the given exception with the given message" do
+ @thr.raise Exception, "get to work"
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should.is_a?(Exception)
+ ScratchPad.recorded.message.should == "get to work"
+ end
+
+ it "can go unhandled" do
+ q = Queue.new
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ q << true
+ loop { Thread.pass }
+ end
+
+ q.pop # wait for `report_on_exception = false`.
+ t.raise
+ -> { t.value }.should.raise(RuntimeError)
+ end
+
+ it "raises the given argument even when there is an active exception" do
+ raised = false
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ raised = true
+ loop { Thread.pass }
+ end
+ end
+ begin
+ raise "Create an active exception for the current thread too"
+ rescue
+ Thread.pass until raised
+ t.raise RangeError
+ -> { t.value }.should.raise(RangeError)
+ end
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ raised = false
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ raised = true
+ loop { Thread.pass }
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ Thread.pass until raised
+ t.raise
+ end
+ -> { t.value }.should.raise(RuntimeError)
+ end
+end
+
+describe "Thread#raise on same thread" do
+ it_behaves_like :kernel_raise, :raise, Thread.current
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ t = Thread.new do
+ Thread.current.report_on_exception = false
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ Thread.current.raise
+ end
+ end
+ -> { t.value }.should.raise(RuntimeError, '')
+ end
+end