summaryrefslogtreecommitdiff
path: root/spec/rubyspec/core/thread
diff options
context:
space:
mode:
Diffstat (limited to 'spec/rubyspec/core/thread')
-rw-r--r--spec/rubyspec/core/thread/abort_on_exception_spec.rb106
-rw-r--r--spec/rubyspec/core/thread/add_trace_func_spec.rb5
-rw-r--r--spec/rubyspec/core/thread/alive_spec.rb58
-rw-r--r--spec/rubyspec/core/thread/allocate_spec.rb9
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/absolute_path_spec.rb12
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/base_label_spec.rb12
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/fixtures/classes.rb17
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/fixtures/main.rb5
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/inspect_spec.rb13
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/label_spec.rb20
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/lineno_spec.rb13
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/path_spec.rb91
-rw-r--r--spec/rubyspec/core/thread/backtrace/location/to_s_spec.rb13
-rw-r--r--spec/rubyspec/core/thread/backtrace_spec.rb27
-rw-r--r--spec/rubyspec/core/thread/current_spec.rb15
-rw-r--r--spec/rubyspec/core/thread/element_reference_spec.rb44
-rw-r--r--spec/rubyspec/core/thread/element_set_spec.rb52
-rw-r--r--spec/rubyspec/core/thread/exclusive_spec.rb18
-rw-r--r--spec/rubyspec/core/thread/exit_spec.rb15
-rw-r--r--spec/rubyspec/core/thread/fixtures/classes.rb288
-rw-r--r--spec/rubyspec/core/thread/fork_spec.rb9
-rw-r--r--spec/rubyspec/core/thread/group_spec.rb5
-rw-r--r--spec/rubyspec/core/thread/initialize_spec.rb27
-rw-r--r--spec/rubyspec/core/thread/inspect_spec.rb42
-rw-r--r--spec/rubyspec/core/thread/join_spec.rb62
-rw-r--r--spec/rubyspec/core/thread/key_spec.rb53
-rw-r--r--spec/rubyspec/core/thread/keys_spec.rb44
-rw-r--r--spec/rubyspec/core/thread/kill_spec.rb21
-rw-r--r--spec/rubyspec/core/thread/list_spec.rb42
-rw-r--r--spec/rubyspec/core/thread/main_spec.rb10
-rw-r--r--spec/rubyspec/core/thread/name_spec.rb56
-rw-r--r--spec/rubyspec/core/thread/new_spec.rb56
-rw-r--r--spec/rubyspec/core/thread/pass_spec.rb8
-rw-r--r--spec/rubyspec/core/thread/priority_spec.rb68
-rw-r--r--spec/rubyspec/core/thread/raise_spec.rb175
-rw-r--r--spec/rubyspec/core/thread/run_spec.rb9
-rw-r--r--spec/rubyspec/core/thread/set_trace_func_spec.rb5
-rw-r--r--spec/rubyspec/core/thread/shared/exit.rb176
-rw-r--r--spec/rubyspec/core/thread/shared/start.rb41
-rw-r--r--spec/rubyspec/core/thread/shared/wakeup.rb61
-rw-r--r--spec/rubyspec/core/thread/start_spec.rb9
-rw-r--r--spec/rubyspec/core/thread/status_spec.rb42
-rw-r--r--spec/rubyspec/core/thread/stop_spec.rb56
-rw-r--r--spec/rubyspec/core/thread/terminate_spec.rb11
-rw-r--r--spec/rubyspec/core/thread/thread_variable_get_spec.rb25
-rw-r--r--spec/rubyspec/core/thread/thread_variable_set_spec.rb26
-rw-r--r--spec/rubyspec/core/thread/thread_variable_spec.rb21
-rw-r--r--spec/rubyspec/core/thread/thread_variables_spec.rb24
-rw-r--r--spec/rubyspec/core/thread/value_spec.rb18
-rw-r--r--spec/rubyspec/core/thread/wakeup_spec.rb7
50 files changed, 2042 insertions, 0 deletions
diff --git a/spec/rubyspec/core/thread/abort_on_exception_spec.rb b/spec/rubyspec/core/thread/abort_on_exception_spec.rb
new file mode 100644
index 0000000000..e424b2fd26
--- /dev/null
+++ b/spec/rubyspec/core/thread/abort_on_exception_spec.rb
@@ -0,0 +1,106 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#abort_on_exception" do
+ before do
+ ThreadSpecs.clear_state
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ end
+
+ after do
+ ThreadSpecs.state = :exit
+ @thread.join
+ end
+
+ it "is false by default" do
+ @thread.abort_on_exception.should be_false
+ end
+
+ it "returns true when #abort_on_exception= is passed true" do
+ @thread.abort_on_exception = true
+ @thread.abort_on_exception.should be_true
+ end
+end
+
+describe :thread_abort_on_exception, shared: true do
+ before do
+ @thread = Thread.new do
+ Thread.pass until ThreadSpecs.state == :run
+ raise RuntimeError, "Thread#abort_on_exception= specs"
+ end
+ end
+
+ it "causes the main thread to raise the exception raised in the thread" do
+ begin
+ ScratchPad << :before
+
+ @thread.abort_on_exception = true if @object
+ lambda do
+ ThreadSpecs.state = :run
+ # Wait for the main thread to be interrupted
+ sleep
+ end.should raise_error(RuntimeError, "Thread#abort_on_exception= specs")
+
+ ScratchPad << :after
+ rescue Exception => e
+ ScratchPad << [:rescue, e]
+ end
+
+ ScratchPad.recorded.should == [:before, :after]
+ end
+end
+
+describe "Thread#abort_on_exception=" do
+ describe "when enabled and the thread dies due to an exception" do
+ before do
+ ScratchPad.record []
+ ThreadSpecs.clear_state
+ @stderr, $stderr = $stderr, IOStub.new
+ end
+
+ after do
+ $stderr = @stderr
+ end
+
+ it_behaves_like :thread_abort_on_exception, nil, true
+ end
+end
+
+describe "Thread.abort_on_exception" do
+ before do
+ @abort_on_exception = Thread.abort_on_exception
+ end
+
+ after do
+ Thread.abort_on_exception = @abort_on_exception
+ end
+
+ it "is false by default" do
+ Thread.abort_on_exception.should == false
+ end
+
+ it "returns true when .abort_on_exception= is passed true" do
+ Thread.abort_on_exception = true
+ Thread.abort_on_exception.should be_true
+ end
+end
+
+describe "Thread.abort_on_exception=" do
+ describe "when enabled and a non-main thread dies due to an exception" do
+ before :each do
+ ScratchPad.record []
+ ThreadSpecs.clear_state
+ @stderr, $stderr = $stderr, IOStub.new
+
+ @abort_on_exception = Thread.abort_on_exception
+ Thread.abort_on_exception = true
+ end
+
+ after :each do
+ Thread.abort_on_exception = @abort_on_exception
+ $stderr = @stderr
+ end
+
+ it_behaves_like :thread_abort_on_exception, nil, false
+ end
+end
diff --git a/spec/rubyspec/core/thread/add_trace_func_spec.rb b/spec/rubyspec/core/thread/add_trace_func_spec.rb
new file mode 100644
index 0000000000..c2010ef317
--- /dev/null
+++ b/spec/rubyspec/core/thread/add_trace_func_spec.rb
@@ -0,0 +1,5 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#add_trace_func" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/alive_spec.rb b/spec/rubyspec/core/thread/alive_spec.rb
new file mode 100644
index 0000000000..c1459ac693
--- /dev/null
+++ b/spec/rubyspec/core/thread/alive_spec.rb
@@ -0,0 +1,58 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#alive?" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.alive?.should == true
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.alive?.should == true
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.alive?.should == true
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.alive?.should == true
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.alive?.should == false
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.alive?.should == false
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.alive?.should == false
+ end
+
+ it "describes a dying running thread" do
+ ThreadSpecs.status_of_dying_running_thread.alive?.should == true
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.alive?.should == true
+ end
+
+ it "returns true for a killed but still running thread" do
+ exit = false
+ t = Thread.new do
+ begin
+ sleep
+ ensure
+ Thread.pass until exit
+ end
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ t.kill
+ t.alive?.should == true
+ exit = true
+ t.join
+ end
+end
diff --git a/spec/rubyspec/core/thread/allocate_spec.rb b/spec/rubyspec/core/thread/allocate_spec.rb
new file mode 100644
index 0000000000..1db05878ba
--- /dev/null
+++ b/spec/rubyspec/core/thread/allocate_spec.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread.allocate" do
+ it "raises a TypeError" do
+ lambda {
+ Thread.allocate
+ }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/absolute_path_spec.rb b/spec/rubyspec/core/thread/backtrace/location/absolute_path_spec.rb
new file mode 100644
index 0000000000..6810bdcd78
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/absolute_path_spec.rb
@@ -0,0 +1,12 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#absolute_path' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ end
+
+ it 'returns the absolute path of the call frame' do
+ @frame.absolute_path.should == File.realpath(__FILE__)
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/base_label_spec.rb b/spec/rubyspec/core/thread/backtrace/location/base_label_spec.rb
new file mode 100644
index 0000000000..cba7e3f34c
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/base_label_spec.rb
@@ -0,0 +1,12 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#base_label' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ end
+
+ it 'returns the base label of the call frame' do
+ @frame.base_label.should == '<top (required)>'
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/fixtures/classes.rb b/spec/rubyspec/core/thread/backtrace/location/fixtures/classes.rb
new file mode 100644
index 0000000000..3e42d8cf81
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/fixtures/classes.rb
@@ -0,0 +1,17 @@
+module ThreadBacktraceLocationSpecs
+ MODULE_LOCATION = caller_locations(0) rescue nil
+
+ def self.locations
+ caller_locations
+ end
+
+ def self.method_location
+ caller_locations(0)
+ end
+
+ def self.block_location
+ 1.times do
+ return caller_locations(0)
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/fixtures/main.rb b/spec/rubyspec/core/thread/backtrace/location/fixtures/main.rb
new file mode 100644
index 0000000000..d2d14ac957
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/fixtures/main.rb
@@ -0,0 +1,5 @@
+def example
+ caller_locations[0].path
+end
+
+print example
diff --git a/spec/rubyspec/core/thread/backtrace/location/inspect_spec.rb b/spec/rubyspec/core/thread/backtrace/location/inspect_spec.rb
new file mode 100644
index 0000000000..56d440c04a
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/inspect_spec.rb
@@ -0,0 +1,13 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#inspect' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'converts the call frame to a String' do
+ @frame.inspect.should include("#{__FILE__}:#{@line}:in ")
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/label_spec.rb b/spec/rubyspec/core/thread/backtrace/location/label_spec.rb
new file mode 100644
index 0000000000..4e67509d0f
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/label_spec.rb
@@ -0,0 +1,20 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#label' do
+ it 'returns the base label of the call frame' do
+ ThreadBacktraceLocationSpecs.locations[0].label.should include('<top (required)>')
+ end
+
+ it 'returns the method name for a method location' do
+ ThreadBacktraceLocationSpecs.method_location[0].label.should == "method_location"
+ end
+
+ it 'returns the block name for a block location' do
+ ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in block_location"
+ end
+
+ it 'returns the module name for a module location' do
+ ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should include "ThreadBacktraceLocationSpecs"
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/lineno_spec.rb b/spec/rubyspec/core/thread/backtrace/location/lineno_spec.rb
new file mode 100644
index 0000000000..7d203008e5
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/lineno_spec.rb
@@ -0,0 +1,13 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#lineno' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'returns the absolute path of the call frame' do
+ @frame.lineno.should == @line
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/path_spec.rb b/spec/rubyspec/core/thread/backtrace/location/path_spec.rb
new file mode 100644
index 0000000000..c2f2058990
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/path_spec.rb
@@ -0,0 +1,91 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#path' do
+ context 'outside a main script' do
+ it 'returns an absolute path' do
+ frame = ThreadBacktraceLocationSpecs.locations[0]
+
+ frame.path.should == __FILE__
+ end
+ end
+
+ context 'in a main script' do
+ before do
+ @script = fixture(__FILE__, 'main.rb')
+ end
+
+ context 'when the script is in the working directory' do
+ before do
+ @directory = File.dirname(@script)
+ end
+
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ Dir.chdir(@directory) {
+ ruby_exe('main.rb')
+ }.should == 'main.rb'
+ end
+ end
+
+ context 'when using an absolute script path' do
+ it 'returns an absolute path' do
+ Dir.chdir(@directory) {
+ ruby_exe(@script)
+ }.should == @script
+ end
+ end
+ end
+
+ context 'when the script is in a sub directory of the working directory' do
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ path = 'fixtures/main.rb'
+ directory = File.dirname(__FILE__)
+ Dir.chdir(directory) {
+ ruby_exe(path)
+ }.should == path
+ end
+ end
+
+ context 'when using an absolute script path' do
+ it 'returns an absolute path' do
+ ruby_exe(@script).should == @script
+ end
+ end
+ end
+
+ context 'when the script is outside of the working directory' do
+ before do
+ @parent_dir = tmp('path_outside_pwd')
+ @sub_dir = File.join(@parent_dir, 'sub')
+ @script = File.join(@parent_dir, 'main.rb')
+ source = fixture(__FILE__, 'main.rb')
+
+ mkdir_p(@sub_dir)
+
+ cp(source, @script)
+ end
+
+ after do
+ rm_r(@script)
+ rm_r(@sub_dir)
+ rm_r(@parent_dir)
+ end
+
+ context 'when using a relative script path' do
+ it 'returns a path relative to the working directory' do
+ Dir.chdir(@sub_dir) {
+ ruby_exe('../main.rb')
+ }.should == '../main.rb'
+ end
+ end
+
+ context 'when using an absolute path' do
+ it 'returns an absolute path' do
+ ruby_exe(@script).should == @script
+ end
+ end
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace/location/to_s_spec.rb b/spec/rubyspec/core/thread/backtrace/location/to_s_spec.rb
new file mode 100644
index 0000000000..486d7da4c9
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace/location/to_s_spec.rb
@@ -0,0 +1,13 @@
+require File.expand_path('../../../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe 'Thread::Backtrace::Location#to_s' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.locations[0]
+ @line = __LINE__ - 1
+ end
+
+ it 'converts the call frame to a String' do
+ @frame.to_s.should include("#{__FILE__}:#{@line}:in ")
+ end
+end
diff --git a/spec/rubyspec/core/thread/backtrace_spec.rb b/spec/rubyspec/core/thread/backtrace_spec.rb
new file mode 100644
index 0000000000..a20fdee956
--- /dev/null
+++ b/spec/rubyspec/core/thread/backtrace_spec.rb
@@ -0,0 +1,27 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#backtrace" do
+ it "returns the current backtrace of a thread" do
+ t = Thread.new do
+ begin
+ sleep
+ rescue
+ end
+ end
+
+ Thread.pass while t.status && t.status != 'sleep'
+
+ backtrace = t.backtrace
+ backtrace.should be_kind_of(Array)
+ backtrace.first.should =~ /`sleep'/
+
+ t.raise 'finish the thread'
+ t.join
+ end
+
+ it "returns nil for dead thread" do
+ t = Thread.new {}
+ t.join
+ t.backtrace.should == nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/current_spec.rb b/spec/rubyspec/core/thread/current_spec.rb
new file mode 100644
index 0000000000..cc969b71c4
--- /dev/null
+++ b/spec/rubyspec/core/thread/current_spec.rb
@@ -0,0 +1,15 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.current" do
+ it "returns a thread" do
+ current = Thread.current
+ current.should be_kind_of(Thread)
+ end
+
+ it "returns the current thread" do
+ t = Thread.new { Thread.current }
+ t.value.should equal(t)
+ Thread.current.should_not equal(t.value)
+ end
+end
diff --git a/spec/rubyspec/core/thread/element_reference_spec.rb b/spec/rubyspec/core/thread/element_reference_spec.rb
new file mode 100644
index 0000000000..81b11d2c09
--- /dev/null
+++ b/spec/rubyspec/core/thread/element_reference_spec.rb
@@ -0,0 +1,44 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#[]" do
+ it "gives access to thread local values" do
+ th = Thread.new do
+ Thread.current[:value] = 5
+ end
+ th.join
+ th[:value].should == 5
+ Thread.current[:value].should == nil
+ end
+
+ it "is not shared across threads" do
+ t1 = Thread.new do
+ Thread.current[:value] = 1
+ end
+ t2 = Thread.new do
+ Thread.current[:value] = 2
+ end
+ [t1,t2].each {|x| x.join}
+ t1[:value].should == 1
+ t2[:value].should == 2
+ end
+
+ it "is accessible using strings or symbols" do
+ t1 = Thread.new do
+ Thread.current[:value] = 1
+ end
+ t2 = Thread.new do
+ Thread.current["value"] = 2
+ end
+ [t1,t2].each {|x| x.join}
+ t1[:value].should == 1
+ t1["value"].should == 1
+ t2[:value].should == 2
+ t2["value"].should == 2
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ lambda { Thread.current[nil] }.should raise_error(TypeError)
+ lambda { Thread.current[5] }.should raise_error(TypeError)
+ end
+end
diff --git a/spec/rubyspec/core/thread/element_set_spec.rb b/spec/rubyspec/core/thread/element_set_spec.rb
new file mode 100644
index 0000000000..47b4d06328
--- /dev/null
+++ b/spec/rubyspec/core/thread/element_set_spec.rb
@@ -0,0 +1,52 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#[]=" do
+ after :each do
+ Thread.current[:value] = nil
+ end
+
+ it "raises a RuntimeError if the thread is frozen" do
+ running = false
+ t = Thread.new do
+ Thread.pass until running
+ t.freeze
+ t[:foo] = "bar"
+ end
+ running = true
+ lambda { t.join }.should raise_error(RuntimeError)
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ lambda { Thread.current[nil] = true }.should raise_error(TypeError)
+ lambda { Thread.current[5] = true }.should raise_error(TypeError)
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:value] = 1
+ Fiber.yield
+ Thread.current[:value].should == 1
+ end
+ fib.resume
+ Thread.current[:value].should be_nil
+ Thread.current[:value] = 2
+ fib.resume
+ Thread.current[:value] = 2
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current[:value].should == 1
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/rubyspec/core/thread/exclusive_spec.rb b/spec/rubyspec/core/thread/exclusive_spec.rb
new file mode 100644
index 0000000000..66c87f4713
--- /dev/null
+++ b/spec/rubyspec/core/thread/exclusive_spec.rb
@@ -0,0 +1,18 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread.exclusive" do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "yields to the block" do
+ Thread.exclusive { ScratchPad.record true }
+ ScratchPad.recorded.should == true
+ end
+
+ it "returns the result of yielding" do
+ Thread.exclusive { :result }.should == :result
+ end
+
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/exit_spec.rb b/spec/rubyspec/core/thread/exit_spec.rb
new file mode 100644
index 0000000000..0fb329e66f
--- /dev/null
+++ b/spec/rubyspec/core/thread/exit_spec.rb
@@ -0,0 +1,15 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/exit', __FILE__)
+
+describe "Thread#exit!" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Thread.exit" do
+ it "causes the current thread to exit" do
+ thread = Thread.new { Thread.exit; sleep }
+ thread.join
+ thread.status.should be_false
+ end
+end
diff --git a/spec/rubyspec/core/thread/fixtures/classes.rb b/spec/rubyspec/core/thread/fixtures/classes.rb
new file mode 100644
index 0000000000..7f7bb69a61
--- /dev/null
+++ b/spec/rubyspec/core/thread/fixtures/classes.rb
@@ -0,0 +1,288 @@
+unless defined? Channel
+ require 'thread'
+ class Channel < Queue
+ alias receive shift
+ end
+end
+
+module ThreadSpecs
+
+ class SubThread < Thread
+ def initialize(*args)
+ super { args.first << 1 }
+ end
+ end
+
+ class Status
+ attr_reader :thread, :inspect, :status
+ def initialize(thread)
+ @thread = thread
+ @alive = thread.alive?
+ @inspect = thread.inspect
+ @status = thread.status
+ @stop = thread.stop?
+ end
+
+ def alive?
+ @alive
+ end
+
+ def stop?
+ @stop
+ end
+ end
+
+ # TODO: In the great Thread spec rewrite, abstract this
+ class << self
+ attr_accessor :state
+ end
+
+ def self.clear_state
+ @state = nil
+ end
+
+ def self.spin_until_sleeping(t)
+ Thread.pass while t.status and t.status != "sleep"
+ end
+
+ def self.sleeping_thread
+ Thread.new do
+ begin
+ sleep
+ ScratchPad.record :woken
+ rescue Object => e
+ ScratchPad.record e
+ end
+ end
+ end
+
+ def self.running_thread
+ Thread.new do
+ begin
+ ThreadSpecs.state = :running
+ loop { Thread.pass }
+ ScratchPad.record :woken
+ rescue Object => e
+ ScratchPad.record e
+ end
+ end
+ end
+
+ def self.completed_thread
+ Thread.new {}
+ end
+
+ def self.status_of_current_thread
+ Thread.new { Status.new(Thread.current) }.value
+ end
+
+ def self.status_of_running_thread
+ t = running_thread
+ Thread.pass while t.status and t.status != "run"
+ status = Status.new t
+ t.kill
+ t.join
+ status
+ end
+
+ def self.status_of_completed_thread
+ t = completed_thread
+ t.join
+ Status.new t
+ end
+
+ def self.status_of_sleeping_thread
+ t = sleeping_thread
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ t.run
+ t.join
+ status
+ end
+
+ def self.status_of_blocked_thread
+ m = Mutex.new
+ m.lock
+ t = Thread.new { m.lock }
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ m.unlock
+ t.join
+ status
+ end
+
+ def self.status_of_aborting_thread
+ end
+
+ def self.status_of_killed_thread
+ t = Thread.new { sleep }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.kill
+ t.join
+ Status.new t
+ end
+
+ def self.status_of_thread_with_uncaught_exception
+ t = Thread.new { raise "error" }
+ begin
+ t.join
+ rescue RuntimeError
+ end
+ Status.new t
+ end
+
+ def self.status_of_dying_running_thread
+ status = nil
+ t = dying_thread_ensures { status = Status.new Thread.current }
+ t.join
+ status
+ end
+
+ def self.status_of_dying_sleeping_thread
+ t = dying_thread_ensures { Thread.stop; }
+ Thread.pass while t.status and t.status != 'sleep'
+ status = Status.new t
+ t.wakeup
+ t.join
+ status
+ end
+
+ def self.dying_thread_ensures(kill_method_name=:kill)
+ Thread.new do
+ begin
+ Thread.current.send(kill_method_name)
+ ensure
+ yield
+ end
+ end
+ end
+
+ def self.dying_thread_with_outer_ensure(kill_method_name=:kill)
+ Thread.new do
+ begin
+ begin
+ Thread.current.send(kill_method_name)
+ ensure
+ raise "In dying thread"
+ end
+ ensure
+ yield
+ end
+ end
+ end
+
+ def self.join_dying_thread_with_outer_ensure(kill_method_name=:kill)
+ t = dying_thread_with_outer_ensure(kill_method_name) { yield }
+ lambda { t.join }.should raise_error(RuntimeError, "In dying thread")
+ return t
+ end
+
+ def self.wakeup_dying_sleeping_thread(kill_method_name=:kill)
+ t = ThreadSpecs.dying_thread_ensures(kill_method_name) { yield }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.wakeup
+ t.join
+ end
+
+ def self.critical_is_reset
+ # Create another thread to verify that it can call Thread.critical=
+ t = Thread.new do
+ initial_critical = Thread.critical
+ Thread.critical = true
+ Thread.critical = false
+ initial_critical == false && Thread.critical == false
+ end
+ v = t.value
+ t.join
+ v
+ end
+
+ def self.counter
+ @@counter
+ end
+
+ def self.counter= c
+ @@counter = c
+ end
+
+ def self.increment_counter(incr)
+ incr.times do
+ begin
+ Thread.critical = true
+ @@counter += 1
+ ensure
+ Thread.critical = false
+ end
+ end
+ end
+
+ def self.critical_thread1
+ Thread.critical = true
+ Thread.current.key?(:thread_specs).should == false
+ end
+
+ def self.critical_thread2(is_thread_stop)
+ Thread.current[:thread_specs].should == 101
+ Thread.critical.should == !is_thread_stop
+ unless is_thread_stop
+ Thread.critical = false
+ end
+ end
+
+ def self.main_thread1(critical_thread, is_thread_sleep, is_thread_stop)
+ # Thread.stop resets Thread.critical. Also, with native threads, the Thread.Stop may not have executed yet
+ # since the main thread will race with the critical thread
+ unless is_thread_stop
+ Thread.critical.should == true
+ end
+ critical_thread[:thread_specs] = 101
+ if is_thread_sleep or is_thread_stop
+ # Thread#wakeup calls are not queued up. So we need to ensure that the thread is sleeping before calling wakeup
+ Thread.pass while critical_thread.status and critical_thread.status != "sleep"
+ critical_thread.wakeup
+ end
+ end
+
+ def self.main_thread2(critical_thread)
+ Thread.pass # The join below seems to cause a deadlock with CRuby unless Thread.pass is called first
+ critical_thread.join
+ Thread.critical.should == false
+ end
+
+ def self.critical_thread_yields_to_main_thread(is_thread_sleep=false, is_thread_stop=false)
+ @@after_first_sleep = false
+
+ critical_thread = Thread.new do
+ Thread.pass while Thread.main.status and Thread.main.status != "sleep"
+ critical_thread1()
+ Thread.main.wakeup
+ yield
+ Thread.pass while @@after_first_sleep != true # Need to ensure that the next statement does not see the first sleep itself
+ Thread.pass while Thread.main.status and Thread.main.status != "sleep"
+ critical_thread2(is_thread_stop)
+ Thread.main.wakeup
+ end
+
+ sleep 5
+ @@after_first_sleep = true
+ main_thread1(critical_thread, is_thread_sleep, is_thread_stop)
+ sleep 5
+ main_thread2(critical_thread)
+ end
+
+ def self.create_critical_thread
+ Thread.new do
+ Thread.critical = true
+ yield
+ Thread.critical = false
+ end
+ end
+
+ def self.create_and_kill_critical_thread(pass_after_kill=false)
+ ThreadSpecs.create_critical_thread do
+ Thread.current.kill
+ Thread.pass if pass_after_kill
+ ScratchPad.record("status=" + Thread.current.status)
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/fork_spec.rb b/spec/rubyspec/core/thread/fork_spec.rb
new file mode 100644
index 0000000000..d321230812
--- /dev/null
+++ b/spec/rubyspec/core/thread/fork_spec.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/start', __FILE__)
+
+describe "Thread.fork" do
+ describe "Thread.start" do
+ it_behaves_like :thread_start, :fork
+ end
+end
diff --git a/spec/rubyspec/core/thread/group_spec.rb b/spec/rubyspec/core/thread/group_spec.rb
new file mode 100644
index 0000000000..aecc1422ba
--- /dev/null
+++ b/spec/rubyspec/core/thread/group_spec.rb
@@ -0,0 +1,5 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+describe "Thread#group" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/initialize_spec.rb b/spec/rubyspec/core/thread/initialize_spec.rb
new file mode 100644
index 0000000000..b6345f03de
--- /dev/null
+++ b/spec/rubyspec/core/thread/initialize_spec.rb
@@ -0,0 +1,27 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#initialize" do
+
+ describe "already initialized" do
+
+ before do
+ @t = Thread.new { sleep }
+ end
+
+ after do
+ @t.kill
+ @t.join
+ end
+
+ it "raises a ThreadError" do
+ lambda {
+ @t.instance_eval do
+ initialize {}
+ end
+ }.should raise_error(ThreadError)
+ end
+
+ end
+
+end
diff --git a/spec/rubyspec/core/thread/inspect_spec.rb b/spec/rubyspec/core/thread/inspect_spec.rb
new file mode 100644
index 0000000000..759f6e756c
--- /dev/null
+++ b/spec/rubyspec/core/thread/inspect_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#inspect" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.inspect.should include('run')
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.inspect.should include('run')
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.inspect.should include('sleep')
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.inspect.should include('sleep')
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.inspect.should include('dead')
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.inspect.should include('dead')
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.inspect.should include('dead')
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.status.should include('sleep')
+ end
+
+ quarantine! do
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_aborting_thread.inspect.should include('aborting')
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/join_spec.rb b/spec/rubyspec/core/thread/join_spec.rb
new file mode 100644
index 0000000000..a6dd2da9a3
--- /dev/null
+++ b/spec/rubyspec/core/thread/join_spec.rb
@@ -0,0 +1,62 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#join" do
+ it "returns the thread when it is finished" do
+ t = Thread.new {}
+ t.join.should equal(t)
+ end
+
+ it "returns the thread when it is finished when given a timeout" do
+ t = Thread.new {}
+ t.join
+ t.join(0).should equal(t)
+ end
+
+ it "coerces timeout to a Float if it is not nil" do
+ t = Thread.new {}
+ t.join
+ t.join(0).should equal(t)
+ t.join(0.0).should equal(t)
+ t.join(nil).should equal(t)
+ lambda { t.join(:foo) }.should raise_error TypeError
+ lambda { t.join("bar") }.should raise_error TypeError
+ end
+
+ it "returns nil if it is not finished when given a timeout" do
+ c = Channel.new
+ t = Thread.new { c.receive }
+ begin
+ t.join(0).should == nil
+ ensure
+ c << true
+ end
+ t.join.should == t
+ end
+
+ it "accepts a floating point timeout length" do
+ c = Channel.new
+ t = Thread.new { c.receive }
+ begin
+ t.join(0.01).should == nil
+ ensure
+ c << true
+ end
+ t.join.should == t
+ end
+
+ it "raises any exceptions encountered in the thread body" do
+ t = Thread.new { raise NotImplementedError.new("Just kidding") }
+ lambda { t.join }.should raise_error(NotImplementedError)
+ end
+
+ it "returns the dead thread" do
+ t = Thread.new { Thread.current.kill }
+ t.join.should equal(t)
+ end
+
+ it "raises any uncaught exception encountered in ensure block" do
+ t = ThreadSpecs.dying_thread_ensures { raise NotImplementedError.new("Just kidding") }
+ lambda { t.join }.should raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/rubyspec/core/thread/key_spec.rb b/spec/rubyspec/core/thread/key_spec.rb
new file mode 100644
index 0000000000..6cdfc3ca7f
--- /dev/null
+++ b/spec/rubyspec/core/thread/key_spec.rb
@@ -0,0 +1,53 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#key?" do
+ before :each do
+ @th = Thread.new do
+ Thread.current[:oliver] = "a"
+ end
+ @th.join
+ end
+
+ it "tests for existance of thread local variables using symbols or strings" do
+ @th.key?(:oliver).should == true
+ @th.key?("oliver").should == true
+ @th.key?(:stanley).should == false
+ @th.key?(:stanley.to_s).should == false
+ end
+
+ it "raises exceptions on the wrong type of keys" do
+ lambda { Thread.current.key? nil }.should raise_error(TypeError)
+ lambda { Thread.current.key? 5 }.should raise_error(TypeError)
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:val1] = 1
+ Fiber.yield
+ Thread.current.key?(:val1).should be_true
+ Thread.current.key?(:val2).should be_false
+ end
+ Thread.current.key?(:val1).should_not be_true
+ fib.resume
+ Thread.current[:val2] = 2
+ fib.resume
+ Thread.current.key?(:val1).should be_false
+ Thread.current.key?(:val2).should be_true
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current.key?(:value).should be_true
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/rubyspec/core/thread/keys_spec.rb b/spec/rubyspec/core/thread/keys_spec.rb
new file mode 100644
index 0000000000..0fc8184e06
--- /dev/null
+++ b/spec/rubyspec/core/thread/keys_spec.rb
@@ -0,0 +1,44 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#keys" do
+ it "returns an array of the names of the thread-local variables as symbols" do
+ th = Thread.new do
+ Thread.current["cat"] = 'woof'
+ Thread.current[:cat] = 'meow'
+ Thread.current[:dog] = 'woof'
+ end
+ th.join
+ th.keys.sort_by {|x| x.to_s}.should == [:cat,:dog]
+ end
+
+ it "is not shared across fibers" do
+ fib = Fiber.new do
+ Thread.current[:val1] = 1
+ Fiber.yield
+ Thread.current.keys.should include(:val1)
+ Thread.current.keys.should_not include(:val2)
+ end
+ Thread.current.keys.should_not include(:val1)
+ fib.resume
+ Thread.current[:val2] = 2
+ fib.resume
+ Thread.current.keys.should include(:val2)
+ Thread.current.keys.should_not include(:val1)
+ end
+
+ it "stores a local in another thread when in a fiber" do
+ fib = Fiber.new do
+ t = Thread.new do
+ sleep
+ Thread.current.keys.should include(:value)
+ end
+
+ Thread.pass while t.status and t.status != "sleep"
+ t[:value] = 1
+ t.wakeup
+ t.join
+ end
+ fib.resume
+ end
+end
diff --git a/spec/rubyspec/core/thread/kill_spec.rb b/spec/rubyspec/core/thread/kill_spec.rb
new file mode 100644
index 0000000000..cf71307af5
--- /dev/null
+++ b/spec/rubyspec/core/thread/kill_spec.rb
@@ -0,0 +1,21 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/exit', __FILE__)
+
+describe "Thread#kill" do
+ it_behaves_like :thread_exit, :kill
+end
+
+describe "Thread#kill!" do
+ it "needs to be reviewed for spec completeness"
+end
+
+describe "Thread.kill" do
+ it "causes the given thread to exit" do
+ thread = Thread.new { sleep }
+ Thread.pass while thread.status and thread.status != "sleep"
+ Thread.kill(thread).should == thread
+ thread.join
+ thread.status.should be_false
+ end
+end
diff --git a/spec/rubyspec/core/thread/list_spec.rb b/spec/rubyspec/core/thread/list_spec.rb
new file mode 100644
index 0000000000..b8deb98260
--- /dev/null
+++ b/spec/rubyspec/core/thread/list_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.list" do
+ it "includes the current and main thread" do
+ Thread.list.should include(Thread.current)
+ Thread.list.should include(Thread.main)
+ end
+
+ it "includes threads of non-default thread groups" do
+ t = Thread.new { sleep }
+ begin
+ ThreadGroup.new.add(t)
+ Thread.list.should include(t)
+ ensure
+ t.kill
+ t.join
+ end
+ end
+
+ it "does not include deceased threads" do
+ t = Thread.new { 1; }
+ t.join
+ Thread.list.should_not include(t)
+ end
+
+ it "includes waiting threads" do
+ c = Channel.new
+ t = Thread.new { c.receive }
+ begin
+ Thread.pass while t.status and t.status != 'sleep'
+ Thread.list.should include(t)
+ ensure
+ c << nil
+ t.join
+ end
+ end
+end
+
+describe "Thread.list" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/main_spec.rb b/spec/rubyspec/core/thread/main_spec.rb
new file mode 100644
index 0000000000..0cada8f59d
--- /dev/null
+++ b/spec/rubyspec/core/thread/main_spec.rb
@@ -0,0 +1,10 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.main" do
+ it "returns the main thread" do
+ Thread.new { @main = Thread.main ; @current = Thread.current}.join
+ @main.should_not == @current
+ @main.should == Thread.current
+ end
+end
diff --git a/spec/rubyspec/core/thread/name_spec.rb b/spec/rubyspec/core/thread/name_spec.rb
new file mode 100644
index 0000000000..0417d7a500
--- /dev/null
+++ b/spec/rubyspec/core/thread/name_spec.rb
@@ -0,0 +1,56 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+ruby_version_is '2.3' do
+ describe "Thread#name" do
+ before :each do
+ @thread = Thread.new {}
+ end
+
+ after :each do
+ @thread.join
+ end
+
+ it "is nil initially" do
+ @thread.name.should == nil
+ end
+
+ it "returns the thread name" do
+ @thread.name = "thread_name"
+ @thread.name.should == "thread_name"
+ end
+ end
+
+ describe "Thread#name=" do
+ before :each do
+ @thread = Thread.new {}
+ end
+
+ after :each do
+ @thread.join
+ end
+
+ it "can be set to a String" do
+ @thread.name = "new thread name"
+ @thread.name.should == "new thread name"
+ end
+
+ it "raises an ArgumentError if the name includes a null byte" do
+ lambda {
+ @thread.name = "new thread\0name"
+ }.should raise_error(ArgumentError)
+ end
+
+ it "can be reset to nil" do
+ @thread.name = nil
+ @thread.name.should == nil
+ end
+
+ it "calls #to_str to convert name to String" do
+ name = mock("Thread#name")
+ name.should_receive(:to_str).and_return("a thread name")
+
+ @thread.name = name
+ @thread.name.should == "a thread name"
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/new_spec.rb b/spec/rubyspec/core/thread/new_spec.rb
new file mode 100644
index 0000000000..b1ed5560a1
--- /dev/null
+++ b/spec/rubyspec/core/thread/new_spec.rb
@@ -0,0 +1,56 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.new" do
+ it "creates a thread executing the given block" do
+ c = Channel.new
+ Thread.new { c << true }.join
+ c << false
+ c.receive.should == true
+ end
+
+ it "can pass arguments to the thread block" do
+ arr = []
+ a, b, c = 1, 2, 3
+ t = Thread.new(a,b,c) {|d,e,f| arr << d << e << f }
+ t.join
+ arr.should == [a,b,c]
+ end
+
+ it "raises an exception when not given a block" do
+ lambda { Thread.new }.should raise_error(ThreadError)
+ end
+
+ it "creates a subclass of thread calls super with a block in initialize" do
+ arr = []
+ t = ThreadSpecs::SubThread.new(arr)
+ t.join
+ arr.should == [1]
+ end
+
+ it "calls #initialize and raises an error if super not used" do
+ c = Class.new(Thread) do
+ def initialize
+ end
+ end
+
+ lambda {
+ c.new
+ }.should raise_error(ThreadError)
+ end
+
+ it "calls and respects #initialize for the block to use" do
+ c = Class.new(Thread) do
+ def initialize
+ ScratchPad.record [:good]
+ super { ScratchPad << :in_thread }
+ end
+ end
+
+ t = c.new
+ t.join
+
+ ScratchPad.recorded.should == [:good, :in_thread]
+ end
+
+end
diff --git a/spec/rubyspec/core/thread/pass_spec.rb b/spec/rubyspec/core/thread/pass_spec.rb
new file mode 100644
index 0000000000..128de934ac
--- /dev/null
+++ b/spec/rubyspec/core/thread/pass_spec.rb
@@ -0,0 +1,8 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.pass" do
+ it "returns nil" do
+ Thread.pass.should == nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/priority_spec.rb b/spec/rubyspec/core/thread/priority_spec.rb
new file mode 100644
index 0000000000..b986fb7a0d
--- /dev/null
+++ b/spec/rubyspec/core/thread/priority_spec.rb
@@ -0,0 +1,68 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#priority" do
+ before do
+ @current_priority = Thread.current.priority
+ ThreadSpecs.clear_state
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ end
+
+ after do
+ ThreadSpecs.state = :exit
+ @thread.join
+ end
+
+ it "inherits the priority of the current thread while running" do
+ @thread.alive?.should be_true
+ @thread.priority.should == @current_priority
+ end
+
+ it "maintain the priority of the current thread after death" do
+ ThreadSpecs.state = :exit
+ @thread.join
+ @thread.alive?.should be_false
+ @thread.priority.should == @current_priority
+ end
+
+ it "returns an integer" do
+ @thread.priority.should be_kind_of(Integer)
+ end
+end
+
+describe "Thread#priority=" do
+ before do
+ ThreadSpecs.clear_state
+ @thread = Thread.new {}
+ end
+
+ after do
+ @thread.join
+ end
+
+ describe "when set with an integer" do
+ it "returns an integer" do
+ value = (@thread.priority = 3)
+ value.should == 3
+ end
+
+ it "clamps the priority to -3..3" do
+ @thread.priority = 42
+ @thread.priority.should == 3
+ @thread.priority = -42
+ @thread.priority.should == -3
+ end
+ end
+
+ describe "when set with a non-integer" do
+ it "raises a type error" do
+ lambda{ @thread.priority = Object.new }.should raise_error(TypeError)
+ end
+ end
+
+ it "sets priority even when the thread has died" do
+ @thread.join
+ @thread.priority = 3
+ @thread.priority.should == 3
+ end
+end
diff --git a/spec/rubyspec/core/thread/raise_spec.rb b/spec/rubyspec/core/thread/raise_spec.rb
new file mode 100644
index 0000000000..93e0f048b1
--- /dev/null
+++ b/spec/rubyspec/core/thread/raise_spec.rb
@@ -0,0 +1,175 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../../../shared/kernel/raise', __FILE__)
+
+describe "Thread#raise" do
+ it "ignores dead threads" do
+ t = Thread.new { :dead }
+ Thread.pass while t.alive?
+ lambda {t.raise("Kill the thread")}.should_not raise_error
+ lambda {t.value}.should_not raise_error
+ 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 be_kind_of(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(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 be_kind_of(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 be_kind_of(Exception)
+ ScratchPad.recorded.backtrace[0].should include("sleep")
+ end
+
+ it "is captured and raised by Thread#value" do
+ t = Thread.new do
+ sleep
+ end
+
+ ThreadSpecs.spin_until_sleeping(t)
+
+ t.raise
+ lambda { t.value }.should raise_error(RuntimeError)
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ t = Thread.new do
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ sleep
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ ThreadSpecs.spin_until_sleeping(t)
+ t.raise
+ end
+ lambda {t.value}.should raise_error(RuntimeError)
+ 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 be_kind_of(RuntimeError)
+ end
+
+ it "raises the given exception" do
+ @thr.raise Exception
+ Thread.pass while @thr.status
+ ScratchPad.recorded.should be_kind_of(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 be_kind_of(Exception)
+ ScratchPad.recorded.message.should == "get to work"
+ end
+
+ it "can go unhandled" do
+ t = Thread.new do
+ loop { Thread.pass }
+ end
+
+ t.raise
+ lambda {t.value}.should raise_error(RuntimeError)
+ end
+
+ it "raises the given argument even when there is an active exception" do
+ raised = false
+ t = Thread.new do
+ 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
+ lambda {t.value}.should raise_error(RangeError)
+ end
+ end
+
+ it "raises a RuntimeError when called with no arguments inside rescue" do
+ raised = false
+ t = Thread.new do
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ raised = true
+ loop { }
+ end
+ end
+ begin
+ raise RangeError
+ rescue
+ Thread.pass until raised
+ t.raise
+ end
+ lambda {t.value}.should raise_error(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
+ begin
+ 1/0
+ rescue ZeroDivisionError
+ Thread.current.raise
+ end
+ end
+ lambda {t.value}.should raise_error(RuntimeError)
+ end
+end
diff --git a/spec/rubyspec/core/thread/run_spec.rb b/spec/rubyspec/core/thread/run_spec.rb
new file mode 100644
index 0000000000..26ed9ed961
--- /dev/null
+++ b/spec/rubyspec/core/thread/run_spec.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+require File.expand_path('../shared/wakeup', __FILE__)
+
+describe "Thread#run" do
+ it_behaves_like :thread_wakeup, :run
+end
+
diff --git a/spec/rubyspec/core/thread/set_trace_func_spec.rb b/spec/rubyspec/core/thread/set_trace_func_spec.rb
new file mode 100644
index 0000000000..6dd5448d79
--- /dev/null
+++ b/spec/rubyspec/core/thread/set_trace_func_spec.rb
@@ -0,0 +1,5 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#set_trace_func" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/shared/exit.rb b/spec/rubyspec/core/thread/shared/exit.rb
new file mode 100644
index 0000000000..f15da360fd
--- /dev/null
+++ b/spec/rubyspec/core/thread/shared/exit.rb
@@ -0,0 +1,176 @@
+describe :thread_exit, shared: true do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "kills sleeping thread" do
+ sleeping_thread = Thread.new do
+ sleep
+ ScratchPad.record :after_sleep
+ end
+ Thread.pass while sleeping_thread.status and sleeping_thread.status != "sleep"
+ sleeping_thread.send(@method)
+ sleeping_thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "kills current thread" do
+ thread = Thread.new do
+ Thread.current.send(@method)
+ ScratchPad.record :after_sleep
+ end
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "runs ensure clause" do
+ thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record :in_ensure_clause }
+ thread.join
+ ScratchPad.recorded.should == :in_ensure_clause
+ end
+
+ it "runs nested ensure clauses" do
+ ScratchPad.record []
+ @outer = Thread.new do
+ begin
+ @inner = Thread.new do
+ begin
+ sleep
+ ensure
+ ScratchPad << :inner_ensure_clause
+ end
+ end
+ sleep
+ ensure
+ ScratchPad << :outer_ensure_clause
+ @inner.send(@method)
+ @inner.join
+ end
+ end
+ Thread.pass while @outer.status and @outer.status != "sleep"
+ Thread.pass until @inner
+ Thread.pass while @inner.status and @inner.status != "sleep"
+ @outer.send(@method)
+ @outer.join
+ ScratchPad.recorded.should include(:inner_ensure_clause)
+ ScratchPad.recorded.should include(:outer_ensure_clause)
+ end
+
+ it "does not set $!" do
+ thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record $! }
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ it "cannot be rescued" do
+ thread = Thread.new do
+ begin
+ Thread.current.send(@method)
+ rescue Exception
+ ScratchPad.record :in_rescue
+ end
+ ScratchPad.record :end_of_thread_block
+ end
+
+ thread.join
+ ScratchPad.recorded.should == nil
+ end
+
+ with_feature :fiber do
+ it "kills the entire thread when a fiber is active" do
+ t = Thread.new do
+ Fiber.new do
+ sleep
+ end.resume
+ ScratchPad.record :fiber_resumed
+ end
+ Thread.pass while t.status and t.status != "sleep"
+ t.send(@method)
+ t.join
+ ScratchPad.recorded.should == nil
+ end
+ end
+
+ # This spec is a mess. It fails randomly, it hangs on MRI, it needs to be removed
+ quarantine! do
+ it "killing dying running does nothing" do
+ in_ensure_clause = false
+ exit_loop = true
+ t = ThreadSpecs.dying_thread_ensures do
+ in_ensure_clause = true
+ loop { if exit_loop then break end }
+ ScratchPad.record :after_stop
+ end
+
+ Thread.pass until in_ensure_clause == true
+ 10.times { t.send(@method); Thread.pass }
+ exit_loop = true
+ t.join
+ ScratchPad.recorded.should == :after_stop
+ end
+ end
+
+ quarantine! do
+
+ it "propogates inner exception to Thread.join if there is an outer ensure clause" do
+ thread = ThreadSpecs.dying_thread_with_outer_ensure(@method) { }
+ lambda { thread.join }.should raise_error(RuntimeError, "In dying thread")
+ end
+
+ it "runs all outer ensure clauses even if inner ensure clause raises exception" do
+ ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record :in_outer_ensure_clause }
+ ScratchPad.recorded.should == :in_outer_ensure_clause
+ end
+
+ it "sets $! in outer ensure clause if inner ensure clause raises exception" do
+ ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record $! }
+ ScratchPad.recorded.to_s.should == "In dying thread"
+ end
+ end
+
+ it "can be rescued by outer rescue clause when inner ensure clause raises exception" do
+ thread = Thread.new do
+ begin
+ begin
+ Thread.current.send(@method)
+ ensure
+ raise "In dying thread"
+ end
+ rescue Exception
+ ScratchPad.record $!
+ end
+ :end_of_thread_block
+ end
+
+ thread.value.should == :end_of_thread_block
+ ScratchPad.recorded.to_s.should == "In dying thread"
+ end
+
+ it "is deferred if ensure clause does Thread.stop" do
+ ThreadSpecs.wakeup_dying_sleeping_thread(@method) { Thread.stop; ScratchPad.record :after_sleep }
+ ScratchPad.recorded.should == :after_sleep
+ end
+
+ # Hangs on 1.8.6.114 OS X, possibly also on Linux
+ quarantine! do
+ it "is deferred if ensure clause sleeps" do
+ ThreadSpecs.wakeup_dying_sleeping_thread(@method) { sleep; ScratchPad.record :after_sleep }
+ ScratchPad.recorded.should == :after_sleep
+ end
+ end
+
+ # This case occurred in JRuby where native threads are used to provide
+ # the same behavior as MRI green threads. Key to this issue was the fact
+ # that the thread which called #exit in its block was also being explicitly
+ # sent #join from outside the thread. The 100.times provides a certain
+ # probability that the deadlock will occur. It was sufficient to reliably
+ # reproduce the deadlock in JRuby.
+ it "does not deadlock when called from within the thread while being joined from without" do
+ 100.times do
+ t = Thread.new { Thread.stop; Thread.current.send(@method) }
+ Thread.pass while t.status and t.status != "sleep"
+ t.wakeup.should == t
+ t.join.should == t
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/shared/start.rb b/spec/rubyspec/core/thread/shared/start.rb
new file mode 100644
index 0000000000..80ce063a0e
--- /dev/null
+++ b/spec/rubyspec/core/thread/shared/start.rb
@@ -0,0 +1,41 @@
+describe :thread_start, shared: true do
+ before :each do
+ ScratchPad.clear
+ end
+
+ it "raises an ArgumentError if not passed a block" do
+ lambda {
+ Thread.send(@method)
+ }.should raise_error(ArgumentError)
+ end
+
+ it "spawns a new Thread running the block" do
+ run = false
+ t = Thread.send(@method) { run = true }
+ t.should be_kind_of(Thread)
+ t.join
+
+ run.should be_true
+ end
+
+ it "respects Thread subclasses" do
+ c = Class.new(Thread)
+ t = c.send(@method) { }
+ t.should be_kind_of(c)
+
+ t.join
+ end
+
+ it "does not call #initialize" do
+ c = Class.new(Thread) do
+ def initialize
+ ScratchPad.record :bad
+ end
+ end
+
+ t = c.send(@method) { }
+ t.join
+
+ ScratchPad.recorded.should == nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/shared/wakeup.rb b/spec/rubyspec/core/thread/shared/wakeup.rb
new file mode 100644
index 0000000000..71838d88e5
--- /dev/null
+++ b/spec/rubyspec/core/thread/shared/wakeup.rb
@@ -0,0 +1,61 @@
+describe :thread_wakeup, shared: true do
+ it "can interrupt Kernel#sleep" do
+ exit_loop = false
+ after_sleep1 = false
+ after_sleep2 = false
+
+ t = Thread.new do
+ while true
+ break if exit_loop == true
+ Thread.pass
+ end
+
+ sleep
+ after_sleep1 = true
+
+ sleep
+ after_sleep2 = true
+ end
+
+ 10.times { t.send(@method); Thread.pass }
+ t.status.should_not == "sleep"
+
+ exit_loop = true
+
+ 10.times { sleep 0.1 if t.status and t.status != "sleep" }
+ after_sleep1.should == false # t should be blocked on the first sleep
+ t.send(@method)
+
+ 10.times { sleep 0.1 if after_sleep1 != true }
+ 10.times { sleep 0.1 if t.status and t.status != "sleep" }
+ after_sleep2.should == false # t should be blocked on the second sleep
+ t.send(@method)
+
+ t.join
+ end
+
+ it "does not result in a deadlock" do
+ t = Thread.new do
+ 100.times { Thread.stop }
+ end
+
+ while t.status
+ begin
+ t.send(@method)
+ rescue ThreadError
+ # The thread might die right after.
+ t.status.should == false
+ end
+ Thread.pass
+ end
+
+ t.status.should == false
+ t.join
+ end
+
+ it "raises a ThreadError when trying to wake up a dead thread" do
+ t = Thread.new { 1 }
+ t.join
+ lambda { t.send @method }.should raise_error(ThreadError)
+ end
+end
diff --git a/spec/rubyspec/core/thread/start_spec.rb b/spec/rubyspec/core/thread/start_spec.rb
new file mode 100644
index 0000000000..932e782382
--- /dev/null
+++ b/spec/rubyspec/core/thread/start_spec.rb
@@ -0,0 +1,9 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/start', __FILE__)
+
+describe "Thread.start" do
+ describe "Thread.start" do
+ it_behaves_like :thread_start, :start
+ end
+end
diff --git a/spec/rubyspec/core/thread/status_spec.rb b/spec/rubyspec/core/thread/status_spec.rb
new file mode 100644
index 0000000000..00d7a03ab8
--- /dev/null
+++ b/spec/rubyspec/core/thread/status_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#status" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.status.should == 'run'
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.status.should == 'run'
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.status.should == 'sleep'
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.status.should == 'sleep'
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.status.should == false
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.status.should == false
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.status.should == nil
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.status.should == 'sleep'
+ end
+
+ quarantine! do
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_aborting_thread.status.should == 'aborting'
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/stop_spec.rb b/spec/rubyspec/core/thread/stop_spec.rb
new file mode 100644
index 0000000000..79be623906
--- /dev/null
+++ b/spec/rubyspec/core/thread/stop_spec.rb
@@ -0,0 +1,56 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread.stop" do
+ it "causes the current thread to sleep indefinitely" do
+ t = Thread.new { Thread.stop; 5 }
+ Thread.pass while t.status and t.status != 'sleep'
+ t.status.should == 'sleep'
+ t.run
+ t.value.should == 5
+ end
+end
+
+describe "Thread#stop?" do
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.stop?.should == false
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.stop?.should == false
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.stop?.should == true
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.stop?.should == true
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.stop?.should == true
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.stop?.should == true
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.stop?.should == true
+ end
+
+ it "describes a dying running thread" do
+ ThreadSpecs.status_of_dying_running_thread.stop?.should == false
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.stop?.should == true
+ end
+
+ quarantine! do
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_aborting_thread.stop?.should == false
+ end
+ end
+end
diff --git a/spec/rubyspec/core/thread/terminate_spec.rb b/spec/rubyspec/core/thread/terminate_spec.rb
new file mode 100644
index 0000000000..9ac4a5b6f8
--- /dev/null
+++ b/spec/rubyspec/core/thread/terminate_spec.rb
@@ -0,0 +1,11 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/exit', __FILE__)
+
+describe "Thread#terminate" do
+ it_behaves_like :thread_exit, :terminate
+end
+
+describe "Thread#terminate!" do
+ it "needs to be reviewed for spec completeness"
+end
diff --git a/spec/rubyspec/core/thread/thread_variable_get_spec.rb b/spec/rubyspec/core/thread/thread_variable_get_spec.rb
new file mode 100644
index 0000000000..0e02c30fad
--- /dev/null
+++ b/spec/rubyspec/core/thread/thread_variable_get_spec.rb
@@ -0,0 +1,25 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#thread_variable_get" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns nil if the variable is not set" do
+ @t.thread_variable_get(:a).should be_nil
+ end
+
+ it "returns the value previously set by #[]=" do
+ @t.thread_variable_set :a, 49
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "returns a value private to self" do
+ @t.thread_variable_set :thread_variable_get_spec, 82
+ Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/thread_variable_set_spec.rb b/spec/rubyspec/core/thread/thread_variable_set_spec.rb
new file mode 100644
index 0000000000..0f55341132
--- /dev/null
+++ b/spec/rubyspec/core/thread/thread_variable_set_spec.rb
@@ -0,0 +1,26 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#thread_variable_set" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns the value set" do
+ (@t.thread_variable_set :a, 2).should == 2
+ end
+
+ it "sets a value that will be returned by #thread_variable_get" do
+ @t.thread_variable_set :a, 49
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "sets a value private to self" do
+ @t.thread_variable_set :thread_variable_get_spec, 82
+ @t.thread_variable_get(:thread_variable_get_spec).should == 82
+ Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/thread_variable_spec.rb b/spec/rubyspec/core/thread/thread_variable_spec.rb
new file mode 100644
index 0000000000..b409b3abfc
--- /dev/null
+++ b/spec/rubyspec/core/thread/thread_variable_spec.rb
@@ -0,0 +1,21 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#thread_variable?" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns false if the thread variables do not contain 'key'" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable?(:b).should be_false
+ end
+
+ it "returns true if the thread variables contain 'key'" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable?(:a).should be_true
+ end
+end
diff --git a/spec/rubyspec/core/thread/thread_variables_spec.rb b/spec/rubyspec/core/thread/thread_variables_spec.rb
new file mode 100644
index 0000000000..538c85c5e4
--- /dev/null
+++ b/spec/rubyspec/core/thread/thread_variables_spec.rb
@@ -0,0 +1,24 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+
+describe "Thread#thread_variables" do
+ before :each do
+ @t = Thread.new { }
+ end
+
+ after :each do
+ @t.join
+ end
+
+ it "returns the keys of all the values set" do
+ @t.thread_variable_set :a, 2
+ @t.thread_variable_set :b, 4
+ @t.thread_variable_set :c, 6
+ @t.thread_variables.sort.should == [:a, :b, :c]
+ end
+
+ it "sets a value private to self" do
+ @t.thread_variable_set :thread_variables_spec_a, 82
+ @t.thread_variable_set :thread_variables_spec_b, 82
+ Thread.current.thread_variables.should == []
+ end
+end
diff --git a/spec/rubyspec/core/thread/value_spec.rb b/spec/rubyspec/core/thread/value_spec.rb
new file mode 100644
index 0000000000..82c0cbf762
--- /dev/null
+++ b/spec/rubyspec/core/thread/value_spec.rb
@@ -0,0 +1,18 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+
+describe "Thread#value" do
+ it "returns the result of the block" do
+ Thread.new { 3 }.value.should == 3
+ end
+
+ it "re-raises an error for an uncaught exception" do
+ t = Thread.new { raise "Hello" }
+ lambda { t.value }.should raise_error(RuntimeError, "Hello")
+ end
+
+ it "is nil for a killed thread" do
+ t = Thread.new { Thread.current.exit }
+ t.value.should == nil
+ end
+end
diff --git a/spec/rubyspec/core/thread/wakeup_spec.rb b/spec/rubyspec/core/thread/wakeup_spec.rb
new file mode 100644
index 0000000000..5197a03a35
--- /dev/null
+++ b/spec/rubyspec/core/thread/wakeup_spec.rb
@@ -0,0 +1,7 @@
+require File.expand_path('../../../spec_helper', __FILE__)
+require File.expand_path('../fixtures/classes', __FILE__)
+require File.expand_path('../shared/wakeup', __FILE__)
+
+describe "Thread#wakeup" do
+ it_behaves_like :thread_wakeup, :wakeup
+end