summaryrefslogtreecommitdiff
path: root/spec/ruby/core/thread
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/thread')
-rw-r--r--spec/ruby/core/thread/abort_on_exception_spec.rb8
-rw-r--r--spec/ruby/core/thread/add_trace_func_spec.rb2
-rw-r--r--spec/ruby/core/thread/alive_spec.rb24
-rw-r--r--spec/ruby/core/thread/allocate_spec.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/limit_spec.rb13
-rw-r--r--spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb85
-rw-r--r--spec/ruby/core/thread/backtrace/location/base_label_spec.rb41
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb10
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/classes.rb18
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb5
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb3
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/main.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/path.rb2
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb11
-rw-r--r--spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb1
-rw-r--r--spec/ruby/core/thread/backtrace/location/inspect_spec.rb4
-rw-r--r--spec/ruby/core/thread/backtrace/location/label_spec.rb25
-rw-r--r--spec/ruby/core/thread/backtrace/location/lineno_spec.rb16
-rw-r--r--spec/ruby/core/thread/backtrace/location/path_spec.rb47
-rw-r--r--spec/ruby/core/thread/backtrace/location/to_s_spec.rb4
-rw-r--r--spec/ruby/core/thread/backtrace_locations_spec.rb79
-rw-r--r--spec/ruby/core/thread/backtrace_spec.rb46
-rw-r--r--spec/ruby/core/thread/current_spec.rb20
-rw-r--r--spec/ruby/core/thread/each_caller_location_spec.rb47
-rw-r--r--spec/ruby/core/thread/element_reference_spec.rb19
-rw-r--r--spec/ruby/core/thread/element_set_spec.rb35
-rw-r--r--spec/ruby/core/thread/exclusive_spec.rb18
-rw-r--r--spec/ruby/core/thread/exit_spec.rb6
-rw-r--r--spec/ruby/core/thread/fetch_spec.rb66
-rw-r--r--spec/ruby/core/thread/fixtures/classes.rb37
-rw-r--r--spec/ruby/core/thread/fork_spec.rb6
-rw-r--r--spec/ruby/core/thread/group_spec.rb17
-rw-r--r--spec/ruby/core/thread/handle_interrupt_spec.rb125
-rw-r--r--spec/ruby/core/thread/ignore_deadlock_spec.rb19
-rw-r--r--spec/ruby/core/thread/initialize_spec.rb6
-rw-r--r--spec/ruby/core/thread/inspect_spec.rb44
-rw-r--r--spec/ruby/core/thread/join_spec.rb29
-rw-r--r--spec/ruby/core/thread/key_spec.rb15
-rw-r--r--spec/ruby/core/thread/keys_spec.rb4
-rw-r--r--spec/ruby/core/thread/kill_spec.rb34
-rw-r--r--spec/ruby/core/thread/list_spec.rb29
-rw-r--r--spec/ruby/core/thread/main_spec.rb4
-rw-r--r--spec/ruby/core/thread/name_spec.rb106
-rw-r--r--spec/ruby/core/thread/native_thread_id_spec.rb35
-rw-r--r--spec/ruby/core/thread/new_spec.rb43
-rw-r--r--spec/ruby/core/thread/pass_spec.rb4
-rw-r--r--spec/ruby/core/thread/pending_interrupt_spec.rb32
-rw-r--r--spec/ruby/core/thread/priority_spec.rb26
-rw-r--r--spec/ruby/core/thread/raise_spec.rb80
-rw-r--r--spec/ruby/core/thread/report_on_exception_spec.rb227
-rw-r--r--spec/ruby/core/thread/run_spec.rb7
-rw-r--r--spec/ruby/core/thread/set_trace_func_spec.rb2
-rw-r--r--spec/ruby/core/thread/shared/exit.rb67
-rw-r--r--spec/ruby/core/thread/shared/start.rb2
-rw-r--r--spec/ruby/core/thread/shared/to_s.rb53
-rw-r--r--spec/ruby/core/thread/shared/wakeup.rb5
-rw-r--r--spec/ruby/core/thread/start_spec.rb6
-rw-r--r--spec/ruby/core/thread/status_spec.rb4
-rw-r--r--spec/ruby/core/thread/stop_spec.rb24
-rw-r--r--spec/ruby/core/thread/terminate_spec.rb6
-rw-r--r--spec/ruby/core/thread/thread_variable_get_spec.rb43
-rw-r--r--spec/ruby/core/thread/thread_variable_set_spec.rb44
-rw-r--r--spec/ruby/core/thread/thread_variable_spec.rb45
-rw-r--r--spec/ruby/core/thread/thread_variables_spec.rb24
-rw-r--r--spec/ruby/core/thread/to_s_spec.rb6
-rw-r--r--spec/ruby/core/thread/value_spec.rb16
-rw-r--r--spec/ruby/core/thread/wakeup_spec.rb6
69 files changed, 1521 insertions, 430 deletions
diff --git a/spec/ruby/core/thread/abort_on_exception_spec.rb b/spec/ruby/core/thread/abort_on_exception_spec.rb
index e424b2fd26..49be84ea9f 100644
--- a/spec/ruby/core/thread/abort_on_exception_spec.rb
+++ b/spec/ruby/core/thread/abort_on_exception_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#abort_on_exception" do
before do
@@ -35,7 +35,7 @@ describe :thread_abort_on_exception, shared: true do
ScratchPad << :before
@thread.abort_on_exception = true if @object
- lambda do
+ -> do
ThreadSpecs.state = :run
# Wait for the main thread to be interrupted
sleep
@@ -72,7 +72,7 @@ describe "Thread.abort_on_exception" do
end
after do
- Thread.abort_on_exception = @abort_on_exception
+ Thread.abort_on_exception = @abort_on_exception
end
it "is false by default" do
diff --git a/spec/ruby/core/thread/add_trace_func_spec.rb b/spec/ruby/core/thread/add_trace_func_spec.rb
index c2010ef317..0abae81a78 100644
--- a/spec/ruby/core/thread/add_trace_func_spec.rb
+++ b/spec/ruby/core/thread/add_trace_func_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#add_trace_func" do
it "needs to be reviewed for spec completeness"
diff --git a/spec/ruby/core/thread/alive_spec.rb b/spec/ruby/core/thread/alive_spec.rb
index c1459ac693..c2f5f5371d 100644
--- a/spec/ruby/core/thread/alive_spec.rb
+++ b/spec/ruby/core/thread/alive_spec.rb
@@ -1,41 +1,41 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#alive?" do
it "can check it's own status" do
- ThreadSpecs.status_of_current_thread.alive?.should == true
+ ThreadSpecs.status_of_current_thread.should.alive?
end
it "describes a running thread" do
- ThreadSpecs.status_of_running_thread.alive?.should == true
+ ThreadSpecs.status_of_running_thread.should.alive?
end
it "describes a sleeping thread" do
- ThreadSpecs.status_of_sleeping_thread.alive?.should == true
+ ThreadSpecs.status_of_sleeping_thread.should.alive?
end
it "describes a blocked thread" do
- ThreadSpecs.status_of_blocked_thread.alive?.should == true
+ ThreadSpecs.status_of_blocked_thread.should.alive?
end
it "describes a completed thread" do
- ThreadSpecs.status_of_completed_thread.alive?.should == false
+ ThreadSpecs.status_of_completed_thread.should_not.alive?
end
it "describes a killed thread" do
- ThreadSpecs.status_of_killed_thread.alive?.should == false
+ ThreadSpecs.status_of_killed_thread.should_not.alive?
end
it "describes a thread with an uncaught exception" do
- ThreadSpecs.status_of_thread_with_uncaught_exception.alive?.should == false
+ ThreadSpecs.status_of_thread_with_uncaught_exception.should_not.alive?
end
it "describes a dying running thread" do
- ThreadSpecs.status_of_dying_running_thread.alive?.should == true
+ ThreadSpecs.status_of_dying_running_thread.should.alive?
end
it "describes a dying sleeping thread" do
- ThreadSpecs.status_of_dying_sleeping_thread.alive?.should == true
+ ThreadSpecs.status_of_dying_sleeping_thread.should.alive?
end
it "returns true for a killed but still running thread" do
@@ -51,7 +51,7 @@ describe "Thread#alive?" do
ThreadSpecs.spin_until_sleeping(t)
t.kill
- t.alive?.should == true
+ t.should.alive?
exit = true
t.join
end
diff --git a/spec/ruby/core/thread/allocate_spec.rb b/spec/ruby/core/thread/allocate_spec.rb
index 1db05878ba..cfd556812f 100644
--- a/spec/ruby/core/thread/allocate_spec.rb
+++ b/spec/ruby/core/thread/allocate_spec.rb
@@ -1,8 +1,8 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread.allocate" do
it "raises a TypeError" do
- lambda {
+ -> {
Thread.allocate
}.should raise_error(TypeError)
end
diff --git a/spec/ruby/core/thread/backtrace/limit_spec.rb b/spec/ruby/core/thread/backtrace/limit_spec.rb
new file mode 100644
index 0000000000..b55ca67ea0
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/limit_spec.rb
@@ -0,0 +1,13 @@
+require_relative '../../../spec_helper'
+
+describe "Thread::Backtrace.limit" do
+ it "returns maximum backtrace length set by --backtrace-limit command-line option" do
+ out = ruby_exe("print Thread::Backtrace.limit", options: "--backtrace-limit=2")
+ out.should == "2"
+ end
+
+ it "returns -1 when --backtrace-limit command-line option is not set" do
+ out = ruby_exe("print Thread::Backtrace.limit")
+ out.should == "-1"
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb
index 6810bdcd78..68a69049d9 100644
--- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#absolute_path' do
before :each do
@@ -9,4 +9,85 @@ describe 'Thread::Backtrace::Location#absolute_path' do
it 'returns the absolute path of the call frame' do
@frame.absolute_path.should == File.realpath(__FILE__)
end
+
+ it 'returns an absolute path when using a relative main script path' do
+ script = fixture(__FILE__, 'absolute_path_main.rb')
+ Dir.chdir(File.dirname(script)) do
+ ruby_exe('absolute_path_main.rb').should == "absolute_path_main.rb\n#{script}\n"
+ end
+ end
+
+ it 'returns the correct absolute path when using a relative main script path and changing CWD' do
+ script = fixture(__FILE__, 'subdir/absolute_path_main_chdir.rb')
+ sibling = fixture(__FILE__, 'subdir/sibling.rb')
+ subdir = File.dirname script
+ Dir.chdir(fixture(__FILE__)) do
+ ruby_exe('subdir/absolute_path_main_chdir.rb').should == "subdir/absolute_path_main_chdir.rb\n#{subdir}\n#{subdir}\n#{script}\n#{sibling}\n"
+ end
+ end
+
+ context "when used in eval with a given filename" do
+ it "returns nil with absolute_path" do
+ code = "caller_locations(0)[0].absolute_path"
+
+ eval(code, nil, "foo.rb").should == nil
+ eval(code, nil, "foo/bar.rb").should == nil
+ end
+ end
+
+ context "when used in #method_added" do
+ it "returns the user filename that defined the method" do
+ path = fixture(__FILE__, "absolute_path_method_added.rb")
+ load path
+ locations = ScratchPad.recorded
+ locations[0].absolute_path.should == path
+ # Make sure it's from the class body, not from the file top-level
+ locations[0].label.should include 'MethodAddedAbsolutePath'
+ end
+ end
+
+ context "when used in a core method" do
+ it "returns nil" do
+ location = nil
+ tap { location = caller_locations(1, 1)[0] }
+ location.label.should =~ /\A(?:Kernel#)?tap\z/
+ if location.path.start_with?("<internal:")
+ location.absolute_path.should == nil
+ else
+ location.absolute_path.should == File.realpath(__FILE__)
+ end
+ end
+ end
+
+ context "canonicalization" do
+ platform_is_not :windows do
+ before :each do
+ @file = fixture(__FILE__, "absolute_path.rb")
+ @symlink = tmp("symlink.rb")
+ File.symlink(@file, @symlink)
+ ScratchPad.record []
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "returns a canonical path without symlinks, even when __FILE__ does not" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, realpath]
+ end
+
+ it "returns a canonical path without symlinks, even when __FILE__ is removed" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ ScratchPad << -> { rm_r(@symlink) }
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, realpath]
+ end
+ end
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/base_label_spec.rb b/spec/ruby/core/thread/backtrace/location/base_label_spec.rb
index cba7e3f34c..739f62f42f 100644
--- a/spec/ruby/core/thread/backtrace/location/base_label_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/base_label_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#base_label' do
before :each do
@@ -9,4 +9,41 @@ describe 'Thread::Backtrace::Location#base_label' do
it 'returns the base label of the call frame' do
@frame.base_label.should == '<top (required)>'
end
+
+ describe 'when call frame is inside a block' do
+ before :each do
+ @frame = ThreadBacktraceLocationSpecs.block_location[0]
+ end
+
+ it 'returns the name of the method that contains the block' do
+ @frame.base_label.should == 'block_location'
+ end
+ end
+
+ it "is <module:A> for a module body" do
+ module ThreadBacktraceLocationSpecs
+ module ModuleLabel
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should == '<module:ModuleLabel>'
+ end
+
+ it "is <class:A> for a class body" do
+ module ThreadBacktraceLocationSpecs
+ class ClassLabel
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should == '<class:ClassLabel>'
+ end
+
+ it "is 'singleton class' for a singleton class body" do
+ module ThreadBacktraceLocationSpecs
+ class << Object.new
+ ScratchPad.record caller_locations(0, 1)[0].base_label
+ end
+ end
+ ScratchPad.recorded.should =~ /\A(singleton class|<singleton class>)\z/
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb
new file mode 100644
index 0000000000..875e97ffac
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path.rb
@@ -0,0 +1,4 @@
+action = ScratchPad.recorded.pop
+ScratchPad << __FILE__
+action.call if action
+ScratchPad << caller_locations(0)[0].absolute_path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb
new file mode 100644
index 0000000000..d2b23393d4
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_main.rb
@@ -0,0 +1,2 @@
+puts __FILE__
+puts caller_locations(0)[0].absolute_path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb
new file mode 100644
index 0000000000..26d6298a19
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/absolute_path_method_added.rb
@@ -0,0 +1,10 @@
+module ThreadBacktraceLocationSpecs
+ class MethodAddedAbsolutePath
+ def self.method_added(name)
+ ScratchPad.record caller_locations
+ end
+
+ def foo
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb
index 3e42d8cf81..e903c3e450 100644
--- a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb
@@ -14,4 +14,22 @@ module ThreadBacktraceLocationSpecs
return caller_locations(0)
end
end
+
+ def self.locations_inside_nested_blocks
+ first_level_location = nil
+ second_level_location = nil
+ third_level_location = nil
+
+ 1.times do
+ first_level_location = locations[0]
+ 1.times do
+ second_level_location = locations[0]
+ 1.times do
+ third_level_location = locations[0]
+ end
+ end
+ end
+
+ [first_level_location, second_level_location, third_level_location]
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb
new file mode 100644
index 0000000000..b124c8161c
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_main.rb
@@ -0,0 +1,5 @@
+1.times do
+ puts Thread.current.backtrace_locations(1..1)[0].label
+end
+
+require_relative 'locations_in_required'
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb
new file mode 100644
index 0000000000..5f5ed89e98
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/locations_in_required.rb
@@ -0,0 +1,3 @@
+1.times do
+ puts Thread.current.backtrace_locations(1..1)[0].label
+end
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/main.rb b/spec/ruby/core/thread/backtrace/location/fixtures/main.rb
index d2d14ac957..bde208a059 100644
--- a/spec/ruby/core/thread/backtrace/location/fixtures/main.rb
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/main.rb
@@ -1,5 +1,5 @@
-def example
+def backtrace_location_example
caller_locations[0].path
end
-print example
+print backtrace_location_example
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/path.rb b/spec/ruby/core/thread/backtrace/location/fixtures/path.rb
new file mode 100644
index 0000000000..fba34cb0bc
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/path.rb
@@ -0,0 +1,2 @@
+ScratchPad << __FILE__
+ScratchPad << caller_locations(0)[0].path
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb
new file mode 100644
index 0000000000..33c8fb36ef
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/absolute_path_main_chdir.rb
@@ -0,0 +1,11 @@
+puts __FILE__
+puts __dir__
+Dir.chdir __dir__
+
+# Check __dir__ is still correct after chdir
+puts __dir__
+
+puts caller_locations(0)[0].absolute_path
+
+# require_relative also needs to know the absolute path of the current file so we test it here too
+require_relative 'sibling'
diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb
new file mode 100644
index 0000000000..2a854ddccd
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace/location/fixtures/subdir/sibling.rb
@@ -0,0 +1 @@
+puts __FILE__
diff --git a/spec/ruby/core/thread/backtrace/location/inspect_spec.rb b/spec/ruby/core/thread/backtrace/location/inspect_spec.rb
index 56d440c04a..20e477a5a6 100644
--- a/spec/ruby/core/thread/backtrace/location/inspect_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/inspect_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#inspect' do
before :each do
diff --git a/spec/ruby/core/thread/backtrace/location/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb
index 4e67509d0f..85ddccc8e3 100644
--- a/spec/ruby/core/thread/backtrace/location/label_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#label' do
it 'returns the base label of the call frame' do
@@ -7,14 +7,31 @@ describe 'Thread::Backtrace::Location#label' do
end
it 'returns the method name for a method location' do
- ThreadBacktraceLocationSpecs.method_location[0].label.should == "method_location"
+ ThreadBacktraceLocationSpecs.method_location[0].label.should =~ /\A(?:ThreadBacktraceLocationSpecs\.)?method_location\z/
end
it 'returns the block name for a block location' do
- ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in block_location"
+ ThreadBacktraceLocationSpecs.block_location[0].label.should =~ /\Ablock in (?:ThreadBacktraceLocationSpecs\.)?block_location\z/
end
it 'returns the module name for a module location' do
ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should include "ThreadBacktraceLocationSpecs"
end
+
+ it 'includes the nesting level of a block as part of the location label' do
+ first_level_location, second_level_location, third_level_location =
+ ThreadBacktraceLocationSpecs.locations_inside_nested_blocks
+
+ first_level_location.label.should =~ /\Ablock in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/
+ second_level_location.label.should =~ /\Ablock \(2 levels\) in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/
+ third_level_location.label.should =~ /\Ablock \(3 levels\) in (?:ThreadBacktraceLocationSpecs\.)?locations_inside_nested_blocks\z/
+ end
+
+ it 'sets the location label for a top-level block differently depending on it being in the main file or a required file' do
+ path = fixture(__FILE__, "locations_in_main.rb")
+ main_label, required_label = ruby_exe(path).lines
+
+ main_label.should == "block in <main>\n"
+ required_label.should == "block in <top (required)>\n"
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb
index 7d203008e5..10457f80f0 100644
--- a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#lineno' do
before :each do
@@ -7,7 +7,17 @@ describe 'Thread::Backtrace::Location#lineno' do
@line = __LINE__ - 1
end
- it 'returns the absolute path of the call frame' do
+ it 'returns the line number of the call frame' do
@frame.lineno.should == @line
end
+
+ it 'should be the same line number as in #to_s, including for core methods' do
+ # Get the caller_locations from a call made into a core library method
+ locations = [:non_empty].map { caller_locations }[0]
+
+ locations.each do |location|
+ line_number = location.to_s[/:(\d+):/, 1]
+ location.lineno.should == Integer(line_number)
+ end
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb
index c2f2058990..75f76833a9 100644
--- a/spec/ruby/core/thread/backtrace/location/path_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#path' do
context 'outside a main script' do
@@ -41,7 +41,7 @@ describe 'Thread::Backtrace::Location#path' 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__)
+ directory = __dir__
Dir.chdir(directory) {
ruby_exe(path)
}.should == path
@@ -56,7 +56,7 @@ describe 'Thread::Backtrace::Location#path' do
end
context 'when the script is outside of the working directory' do
- before do
+ before :each do
@parent_dir = tmp('path_outside_pwd')
@sub_dir = File.join(@parent_dir, 'sub')
@script = File.join(@parent_dir, 'main.rb')
@@ -67,9 +67,7 @@ describe 'Thread::Backtrace::Location#path' do
cp(source, @script)
end
- after do
- rm_r(@script)
- rm_r(@sub_dir)
+ after :each do
rm_r(@parent_dir)
end
@@ -88,4 +86,39 @@ describe 'Thread::Backtrace::Location#path' do
end
end
end
+
+ it 'should be the same path as in #to_s, including for core methods' do
+ # Get the caller_locations from a call made into a core library method
+ locations = [:non_empty].map { caller_locations }[0]
+
+ locations.each do |location|
+ filename = location.to_s[/^(.+):\d+:/, 1]
+ path = location.path
+
+ path.should == filename
+ end
+ end
+
+ context "canonicalization" do
+ platform_is_not :windows do
+ before :each do
+ @file = fixture(__FILE__, "path.rb")
+ @symlink = tmp("symlink.rb")
+ File.symlink(@file, @symlink)
+ ScratchPad.record []
+ end
+
+ after :each do
+ rm_r @symlink
+ end
+
+ it "returns a non-canonical path with symlinks, the same as __FILE__" do
+ realpath = File.realpath(@symlink)
+ realpath.should_not == @symlink
+
+ load @symlink
+ ScratchPad.recorded.should == [@symlink, @symlink]
+ end
+ end
+ end
end
diff --git a/spec/ruby/core/thread/backtrace/location/to_s_spec.rb b/spec/ruby/core/thread/backtrace/location/to_s_spec.rb
index 486d7da4c9..5911cdced0 100644
--- a/spec/ruby/core/thread/backtrace/location/to_s_spec.rb
+++ b/spec/ruby/core/thread/backtrace/location/to_s_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../../../spec_helper'
+require_relative 'fixtures/classes'
describe 'Thread::Backtrace::Location#to_s' do
before :each do
diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb
new file mode 100644
index 0000000000..09fe622e0d
--- /dev/null
+++ b/spec/ruby/core/thread/backtrace_locations_spec.rb
@@ -0,0 +1,79 @@
+require_relative '../../spec_helper'
+
+describe "Thread#backtrace_locations" do
+ it "returns an Array" do
+ locations = Thread.current.backtrace_locations
+ locations.should be_an_instance_of(Array)
+ locations.should_not be_empty
+ end
+
+ it "sets each element to a Thread::Backtrace::Location" do
+ locations = Thread.current.backtrace_locations
+ locations.each { |loc| loc.should be_an_instance_of(Thread::Backtrace::Location) }
+ end
+
+ it "can be called on any Thread" do
+ locations = Thread.new { Thread.current.backtrace_locations }.value
+ locations.should be_an_instance_of(Array)
+ locations.should_not be_empty
+ locations.each { |loc| loc.should be_an_instance_of(Thread::Backtrace::Location) }
+ end
+
+ it "can be called with a number of locations to omit" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2)
+ locations2.length.should == locations1[2..-1].length
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "can be called with a maximum number of locations to return as second parameter" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2, 3)
+ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s)
+ end
+
+ it "can be called with a range" do
+ locations1 = Thread.current.backtrace_locations
+ locations2 = Thread.current.backtrace_locations(2..4)
+ locations2.map(&:to_s).should == locations1[2..4].map(&:to_s)
+ end
+
+ it "can be called with a range whose end is negative" do
+ Thread.current.backtrace_locations(2..-1).map(&:to_s).should == Thread.current.backtrace_locations[2..-1].map(&:to_s)
+ Thread.current.backtrace_locations(2..-2).map(&:to_s).should == Thread.current.backtrace_locations[2..-2].map(&:to_s)
+ end
+
+ it "can be called with an endless range" do
+ locations1 = Thread.current.backtrace_locations(0)
+ locations2 = Thread.current.backtrace_locations(eval("(2..)"))
+ locations2.map(&:to_s).should == locations1[2..-1].map(&:to_s)
+ end
+
+ it "can be called with an beginless range" do
+ locations1 = Thread.current.backtrace_locations(0)
+ locations2 = Thread.current.backtrace_locations((..5))
+ locations2.map(&:to_s)[eval("(2..)")].should == locations1[(..5)].map(&:to_s)[eval("(2..)")]
+ end
+
+ it "returns nil if omitting more locations than available" do
+ Thread.current.backtrace_locations(100).should == nil
+ Thread.current.backtrace_locations(100..-1).should == nil
+ end
+
+ it "returns [] if omitting exactly the number of locations available" do
+ omit = Thread.current.backtrace_locations.length
+ Thread.current.backtrace_locations(omit).should == []
+ end
+
+ it "without argument is the same as showing all locations with 0..-1" do
+ Thread.current.backtrace_locations.map(&:to_s).should == Thread.current.backtrace_locations(0..-1).map(&:to_s)
+ end
+
+ it "the first location reports the call to #backtrace_locations" do
+ Thread.current.backtrace_locations(0..0)[0].to_s.should =~ /\A#{__FILE__ }:#{__LINE__ }:in [`'](?:Thread#)?backtrace_locations'\z/
+ end
+
+ it "[1..-1] is the same as #caller_locations(0..-1) for Thread.current" do
+ Thread.current.backtrace_locations(1..-1).map(&:to_s).should == caller_locations(0..-1).map(&:to_s)
+ end
+end
diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb
index a20fdee956..15bb29a349 100644
--- a/spec/ruby/core/thread/backtrace_spec.rb
+++ b/spec/ruby/core/thread/backtrace_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#backtrace" do
it "returns the current backtrace of a thread" do
@@ -13,7 +13,7 @@ describe "Thread#backtrace" do
backtrace = t.backtrace
backtrace.should be_kind_of(Array)
- backtrace.first.should =~ /`sleep'/
+ backtrace.first.should =~ /[`'](?:Kernel#)?sleep'/
t.raise 'finish the thread'
t.join
@@ -24,4 +24,46 @@ describe "Thread#backtrace" do
t.join
t.backtrace.should == nil
end
+
+ it "returns an array (which may be empty) immediately after the thread is created" do
+ t = Thread.new { sleep }
+ backtrace = t.backtrace
+ t.kill
+ t.join
+ backtrace.should be_kind_of(Array)
+ end
+
+ it "can be called with a number of locations to omit" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2)
+ locations1[2..-1].length.should == locations2.length
+ locations1[2..-1].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a maximum number of locations to return as second parameter" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2, 3)
+ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a range" do
+ locations1 = Thread.current.backtrace
+ locations2 = Thread.current.backtrace(2..4)
+ locations1[2..4].map(&:to_s).should == locations2.map(&:to_s)
+ end
+
+ it "can be called with a range whose end is negative" do
+ Thread.current.backtrace(2..-1).should == Thread.current.backtrace[2..-1]
+ Thread.current.backtrace(2..-2).should == Thread.current.backtrace[2..-2]
+ end
+
+ it "returns nil if omitting more locations than available" do
+ Thread.current.backtrace(100).should == nil
+ Thread.current.backtrace(100..-1).should == nil
+ end
+
+ it "returns [] if omitting exactly the number of locations available" do
+ omit = Thread.current.backtrace.length
+ Thread.current.backtrace(omit).should == []
+ end
end
diff --git a/spec/ruby/core/thread/current_spec.rb b/spec/ruby/core/thread/current_spec.rb
index cc969b71c4..f5ed1d95cd 100644
--- a/spec/ruby/core/thread/current_spec.rb
+++ b/spec/ruby/core/thread/current_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread.current" do
it "returns a thread" do
@@ -12,4 +12,20 @@ describe "Thread.current" do
t.value.should equal(t)
Thread.current.should_not equal(t.value)
end
+
+ it "returns the correct thread in a Fiber" do
+ # This catches a bug where Fibers are running on a thread-pool
+ # and Fibers from a different Ruby Thread reuse the same native thread.
+ # Caching the Ruby Thread based on the native thread is not correct in that case.
+ 2.times do
+ t = Thread.new {
+ cur = Thread.current
+ Fiber.new {
+ Thread.current
+ }.resume.should equal cur
+ cur
+ }
+ t.value.should equal t
+ end
+ end
end
diff --git a/spec/ruby/core/thread/each_caller_location_spec.rb b/spec/ruby/core/thread/each_caller_location_spec.rb
new file mode 100644
index 0000000000..aa7423675b
--- /dev/null
+++ b/spec/ruby/core/thread/each_caller_location_spec.rb
@@ -0,0 +1,47 @@
+require_relative '../../spec_helper'
+
+describe "Thread.each_caller_location" do
+ it "iterates through the current execution stack and matches caller_locations content and type" do
+ ScratchPad.record []
+ Thread.each_caller_location { |l| ScratchPad << l; }
+
+ ScratchPad.recorded.map(&:to_s).should == caller_locations.map(&:to_s)
+ ScratchPad.recorded[0].should be_kind_of(Thread::Backtrace::Location)
+ end
+
+ it "returns subset of 'Thread.to_enum(:each_caller_location)' locations" do
+ ar = []
+ ecl = Thread.each_caller_location { |x| ar << x }
+
+ (ar.map(&:to_s) - Thread.to_enum(:each_caller_location).to_a.map(&:to_s)).should.empty?
+ end
+
+ it "stops the backtrace iteration if 'break' occurs" do
+ i = 0
+ ar = []
+ ecl = Thread.each_caller_location do |x|
+ ar << x
+ i += 1
+ break x if i == 2
+ end
+
+ ar.map(&:to_s).should == caller_locations(1, 2).map(&:to_s)
+ ecl.should be_kind_of(Thread::Backtrace::Location)
+ end
+
+ it "returns nil" do
+ Thread.each_caller_location {}.should == nil
+ end
+
+ it "raises LocalJumpError when called without a block" do
+ -> {
+ Thread.each_caller_location
+ }.should raise_error(LocalJumpError, "no block given")
+ end
+
+ it "doesn't accept keyword arguments" do
+ -> {
+ Thread.each_caller_location(12, foo: 10) {}
+ }.should raise_error(ArgumentError);
+ end
+end
diff --git a/spec/ruby/core/thread/element_reference_spec.rb b/spec/ruby/core/thread/element_reference_spec.rb
index 81b11d2c09..fde9d1f440 100644
--- a/spec/ruby/core/thread/element_reference_spec.rb
+++ b/spec/ruby/core/thread/element_reference_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#[]" do
it "gives access to thread local values" do
@@ -37,8 +37,19 @@ describe "Thread#[]" do
t2["value"].should == 2
end
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('value')
+ key.should_receive(:to_str).and_return('value')
+
+ th = Thread.new do
+ Thread.current[:value] = 1
+ end.join
+
+ th[key].should == 1
+ 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)
+ -> { Thread.current[nil] }.should raise_error(TypeError)
+ -> { Thread.current[5] }.should raise_error(TypeError)
end
end
diff --git a/spec/ruby/core/thread/element_set_spec.rb b/spec/ruby/core/thread/element_set_spec.rb
index ed92a84fa3..f205177304 100644
--- a/spec/ruby/core/thread/element_set_spec.rb
+++ b/spec/ruby/core/thread/element_set_spec.rb
@@ -1,24 +1,47 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#[]=" do
after :each do
Thread.current[:value] = nil
end
- it "raises a RuntimeError if the thread is frozen" do
+ it "raises a FrozenError if the thread is frozen" do
Thread.new do
th = Thread.current
th.freeze
-> {
th[:foo] = "bar"
- }.should raise_error(RuntimeError, /frozen/)
+ }.should raise_error(FrozenError, "can't modify frozen thread locals")
end.join
end
+ it "accepts Strings and Symbols" do
+ t1 = Thread.new do
+ Thread.current[:value] = 1
+ end.join
+ t2 = Thread.new do
+ Thread.current["value"] = 2
+ end.join
+
+ t1[:value].should == 1
+ t2[:value].should == 2
+ end
+
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('value')
+ key.should_receive(:to_str).and_return('value')
+
+ th = Thread.new do
+ Thread.current[key] = 1
+ end.join
+
+ th[:value].should == 1
+ 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)
+ -> { Thread.current[nil] = true }.should raise_error(TypeError)
+ -> { Thread.current[5] = true }.should raise_error(TypeError)
end
it "is not shared across fibers" do
diff --git a/spec/ruby/core/thread/exclusive_spec.rb b/spec/ruby/core/thread/exclusive_spec.rb
deleted file mode 100644
index 66c87f4713..0000000000
--- a/spec/ruby/core/thread/exclusive_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-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/ruby/core/thread/exit_spec.rb b/spec/ruby/core/thread/exit_spec.rb
index 0fb329e66f..c3f710920e 100644
--- a/spec/ruby/core/thread/exit_spec.rb
+++ b/spec/ruby/core/thread/exit_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/exit', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
describe "Thread#exit!" do
it "needs to be reviewed for spec completeness"
diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb
new file mode 100644
index 0000000000..85ffb71874
--- /dev/null
+++ b/spec/ruby/core/thread/fetch_spec.rb
@@ -0,0 +1,66 @@
+require_relative '../../spec_helper'
+
+describe 'Thread#fetch' do
+ describe 'with 2 arguments' do
+ it 'returns the value of the fiber-local variable if value has been assigned' do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ th.fetch(:cat, true).should == 'meow'
+ end
+
+ it "returns the default value if fiber-local variable hasn't been assigned" do
+ th = Thread.new {}
+ th.join
+ th.fetch(:cat, true).should == true
+ end
+ end
+
+ describe 'with 1 argument' do
+ it 'raises a KeyError when the Thread does not have a fiber-local variable of the same name' do
+ th = Thread.new {}
+ th.join
+ -> { th.fetch(:cat) }.should raise_error(KeyError)
+ end
+
+ it 'returns the value of the fiber-local variable if value has been assigned' do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ th.fetch(:cat).should == 'meow'
+ end
+ end
+
+ describe 'with a block' do
+ it 'returns the value of the fiber-local variable if value has been assigned' do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ th.fetch(:cat) { true }.should == 'meow'
+ end
+
+ it "returns the block value if fiber-local variable hasn't been assigned" do
+ th = Thread.new {}
+ th.join
+ th.fetch(:cat) { true }.should == true
+ end
+
+ it "does not call the block if value has been assigned" do
+ th = Thread.new { Thread.current[:cat] = 'meow' }
+ th.join
+ var = :not_updated
+ th.fetch(:cat) { var = :updated }.should == 'meow'
+ var.should == :not_updated
+ end
+
+ it "uses the block if a default is given and warns about it" do
+ th = Thread.new {}
+ th.join
+ -> {
+ th.fetch(:cat, false) { true }.should == true
+ }.should complain(/warning: block supersedes default value argument/)
+ end
+ end
+
+ it 'raises an ArgumentError when not passed one or two arguments' do
+ -> { Thread.current.fetch() }.should raise_error(ArgumentError)
+ -> { Thread.current.fetch(1, 2, 3) }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/ruby/core/thread/fixtures/classes.rb b/spec/ruby/core/thread/fixtures/classes.rb
index 601e515e3e..7c485660a8 100644
--- a/spec/ruby/core/thread/fixtures/classes.rb
+++ b/spec/ruby/core/thread/fixtures/classes.rb
@@ -1,10 +1,3 @@
-unless defined? Channel
- require 'thread'
- class Channel < Queue
- alias receive shift
- end
-end
-
module ThreadSpecs
class SubThread < Thread
@@ -13,12 +6,38 @@ module ThreadSpecs
end
end
+ class NewThreadToRaise
+ def self.raise(*args, **kwargs, &block)
+ thread = Thread.new do
+ Thread.current.report_on_exception = false
+
+ if block_given?
+ block.call do
+ sleep
+ end
+ else
+ sleep
+ end
+ end
+
+ Thread.pass until thread.stop?
+
+ thread.raise(*args, **kwargs)
+
+ thread.join
+ ensure
+ thread.kill if thread.alive?
+ Thread.pass while thread.alive? # Thread#kill may not terminate a thread immediately so it may be detected as a leaked one
+ end
+ end
+
class Status
- attr_reader :thread, :inspect, :status
+ attr_reader :thread, :inspect, :status, :to_s
def initialize(thread)
@thread = thread
@alive = thread.alive?
@inspect = thread.inspect
+ @to_s = thread.to_s
@status = thread.status
@stop = thread.stop?
end
@@ -188,7 +207,7 @@ module ThreadSpecs
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")
+ -> { t.join }.should raise_error(RuntimeError, "In dying thread")
return t
end
diff --git a/spec/ruby/core/thread/fork_spec.rb b/spec/ruby/core/thread/fork_spec.rb
index d321230812..a2f4181298 100644
--- a/spec/ruby/core/thread/fork_spec.rb
+++ b/spec/ruby/core/thread/fork_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/start', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/start'
describe "Thread.fork" do
describe "Thread.start" do
diff --git a/spec/ruby/core/thread/group_spec.rb b/spec/ruby/core/thread/group_spec.rb
index aecc1422ba..d0d4704b66 100644
--- a/spec/ruby/core/thread/group_spec.rb
+++ b/spec/ruby/core/thread/group_spec.rb
@@ -1,5 +1,16 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+
describe "Thread#group" do
- it "needs to be reviewed for spec completeness"
+ it "returns the default thread group for the main thread" do
+ Thread.main.group.should == ThreadGroup::Default
+ end
+
+ it "returns the thread group explicitly set for this thread" do
+ thread = Thread.new { nil }
+ thread_group = ThreadGroup.new
+ thread_group.add(thread)
+ thread.group.should == thread_group
+ ensure
+ thread.join if thread
+ end
end
diff --git a/spec/ruby/core/thread/handle_interrupt_spec.rb b/spec/ruby/core/thread/handle_interrupt_spec.rb
new file mode 100644
index 0000000000..ea7e81cb98
--- /dev/null
+++ b/spec/ruby/core/thread/handle_interrupt_spec.rb
@@ -0,0 +1,125 @@
+require_relative '../../spec_helper'
+
+describe "Thread.handle_interrupt" do
+ def make_handle_interrupt_thread(interrupt_config, blocking = true)
+ interrupt_class = Class.new(RuntimeError)
+
+ ScratchPad.record []
+
+ in_handle_interrupt = Queue.new
+ can_continue = Queue.new
+
+ thread = Thread.new do
+ begin
+ Thread.handle_interrupt(interrupt_config) do
+ begin
+ in_handle_interrupt << true
+ if blocking
+ Thread.pass # Make it clearer the other thread needs to wait for this one to be in #pop
+ can_continue.pop
+ else
+ begin
+ can_continue.pop(true)
+ rescue ThreadError
+ Thread.pass
+ retry
+ end
+ end
+ rescue interrupt_class
+ ScratchPad << :interrupted
+ end
+ end
+ rescue interrupt_class
+ ScratchPad << :deferred
+ end
+ end
+
+ in_handle_interrupt.pop
+ if blocking
+ # Ensure the thread is inside Thread#pop, as if thread.raise is done before it would be deferred
+ Thread.pass until thread.stop?
+ end
+ thread.raise interrupt_class, "interrupt"
+ can_continue << true
+ thread.join
+
+ ScratchPad.recorded
+ end
+
+ before :each do
+ Thread.pending_interrupt?.should == false # sanity check
+ end
+
+ it "with :never defers interrupts until exiting the handle_interrupt block" do
+ make_handle_interrupt_thread(RuntimeError => :never).should == [:deferred]
+ end
+
+ it "with :on_blocking defers interrupts until the next blocking call" do
+ make_handle_interrupt_thread(RuntimeError => :on_blocking).should == [:interrupted]
+ make_handle_interrupt_thread({ RuntimeError => :on_blocking }, false).should == [:deferred]
+ end
+
+ it "with :immediate handles interrupts immediately" do
+ make_handle_interrupt_thread(RuntimeError => :immediate).should == [:interrupted]
+ end
+
+ it "with :immediate immediately runs pending interrupts, before the block" do
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt immediate"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ -> {
+ Thread.handle_interrupt(RuntimeError => :immediate) {
+ flunk "not reached"
+ }
+ }.should raise_error(RuntimeError, "interrupt immediate")
+ Thread.pending_interrupt?.should == false
+ end
+ end
+
+ it "also works with suspended Fibers and does not duplicate interrupts" do
+ fiber = Fiber.new { Fiber.yield }
+ fiber.resume
+
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt with fibers"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ -> {
+ Thread.handle_interrupt(RuntimeError => :immediate) {
+ flunk "not reached"
+ }
+ }.should raise_error(RuntimeError, "interrupt with fibers")
+ Thread.pending_interrupt?.should == false
+ end
+
+ fiber.resume
+ end
+
+ it "runs pending interrupts at the end of the block, even if there was an exception raised in the block" do
+ executed = false
+ -> {
+ Thread.handle_interrupt(RuntimeError => :never) do
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt exception"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ executed = true
+ raise "regular exception"
+ end
+ }.should raise_error(RuntimeError, "interrupt exception")
+ executed.should == true
+ end
+
+ it "supports multiple pairs in the Hash" do
+ make_handle_interrupt_thread(ArgumentError => :never, RuntimeError => :never).should == [:deferred]
+ end
+end
diff --git a/spec/ruby/core/thread/ignore_deadlock_spec.rb b/spec/ruby/core/thread/ignore_deadlock_spec.rb
new file mode 100644
index 0000000000..b48bc9f9b0
--- /dev/null
+++ b/spec/ruby/core/thread/ignore_deadlock_spec.rb
@@ -0,0 +1,19 @@
+require_relative '../../spec_helper'
+
+describe "Thread.ignore_deadlock" do
+ it "returns false by default" do
+ Thread.ignore_deadlock.should == false
+ end
+end
+
+describe "Thread.ignore_deadlock=" do
+ it "changes the value of Thread.ignore_deadlock" do
+ ignore_deadlock = Thread.ignore_deadlock
+ Thread.ignore_deadlock = true
+ begin
+ Thread.ignore_deadlock.should == true
+ ensure
+ Thread.ignore_deadlock = ignore_deadlock
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/initialize_spec.rb b/spec/ruby/core/thread/initialize_spec.rb
index b6345f03de..4fca900cd8 100644
--- a/spec/ruby/core/thread/initialize_spec.rb
+++ b/spec/ruby/core/thread/initialize_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#initialize" do
@@ -15,7 +15,7 @@ describe "Thread#initialize" do
end
it "raises a ThreadError" do
- lambda {
+ -> {
@t.instance_eval do
initialize {}
end
diff --git a/spec/ruby/core/thread/inspect_spec.rb b/spec/ruby/core/thread/inspect_spec.rb
index 95e598eb6a..bd6e0c31fc 100644
--- a/spec/ruby/core/thread/inspect_spec.rb
+++ b/spec/ruby/core/thread/inspect_spec.rb
@@ -1,44 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
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.inspect.should include('sleep')
- end
-
- it "reports aborting on a killed thread" do
- ThreadSpecs.status_of_dying_running_thread.inspect.should include('aborting')
- end
-
- it "reports aborting on a killed thread after sleep" do
- ThreadSpecs.status_of_dying_thread_after_sleep.inspect.should include('aborting')
- end
+ it_behaves_like :thread_to_s, :inspect
end
diff --git a/spec/ruby/core/thread/join_spec.rb b/spec/ruby/core/thread/join_spec.rb
index 249b3d333e..213fe2e505 100644
--- a/spec/ruby/core/thread/join_spec.rb
+++ b/spec/ruby/core/thread/join_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#join" do
it "returns the thread when it is finished" do
@@ -19,28 +19,33 @@ describe "Thread#join" do
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 "raises TypeError if the argument is not a valid timeout" do
+ t = Thread.new { }
+ t.join
+ -> { t.join(:foo) }.should raise_error TypeError
+ -> { 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 }
+ q = Queue.new
+ t = Thread.new { q.pop }
begin
t.join(0).should == nil
ensure
- c << true
+ q << true
end
t.join.should == t
end
it "accepts a floating point timeout length" do
- c = Channel.new
- t = Thread.new { c.receive }
+ q = Queue.new
+ t = Thread.new { q.pop }
begin
t.join(0.01).should == nil
ensure
- c << true
+ q << true
end
t.join.should == t
end
@@ -50,7 +55,7 @@ describe "Thread#join" do
Thread.current.report_on_exception = false
raise NotImplementedError.new("Just kidding")
}
- lambda { t.join }.should raise_error(NotImplementedError)
+ -> { t.join }.should raise_error(NotImplementedError)
end
it "returns the dead thread" do
@@ -60,6 +65,6 @@ describe "Thread#join" do
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)
+ -> { t.join }.should raise_error(NotImplementedError)
end
end
diff --git a/spec/ruby/core/thread/key_spec.rb b/spec/ruby/core/thread/key_spec.rb
index d82a21ab39..339fa98f53 100644
--- a/spec/ruby/core/thread/key_spec.rb
+++ b/spec/ruby/core/thread/key_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#key?" do
before :each do
@@ -16,9 +16,16 @@ describe "Thread#key?" do
@th.key?(:stanley.to_s).should == false
end
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('key')
+ key.should_receive(:to_str).and_return('oliver')
+
+ @th.key?(key).should == true
+ 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)
+ -> { Thread.current.key? nil }.should raise_error(TypeError)
+ -> { Thread.current.key? 5 }.should raise_error(TypeError)
end
it "is not shared across fibers" do
diff --git a/spec/ruby/core/thread/keys_spec.rb b/spec/ruby/core/thread/keys_spec.rb
index 0fc8184e06..15efda51d6 100644
--- a/spec/ruby/core/thread/keys_spec.rb
+++ b/spec/ruby/core/thread/keys_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#keys" do
it "returns an array of the names of the thread-local variables as symbols" do
diff --git a/spec/ruby/core/thread/kill_spec.rb b/spec/ruby/core/thread/kill_spec.rb
index cf71307af5..4b62c686c7 100644
--- a/spec/ruby/core/thread/kill_spec.rb
+++ b/spec/ruby/core/thread/kill_spec.rb
@@ -1,21 +1,21 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/exit', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
-describe "Thread#kill" do
- it_behaves_like :thread_exit, :kill
-end
-
-describe "Thread#kill!" do
- it "needs to be reviewed for spec completeness"
-end
+# This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/builds/19473223/job/f69derxnlo09xhuj
+# TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard.
+platform_is_not :mingw do
+ describe "Thread#kill" do
+ it_behaves_like :thread_exit, :kill
+ 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
+ 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
end
diff --git a/spec/ruby/core/thread/list_spec.rb b/spec/ruby/core/thread/list_spec.rb
index b8deb98260..3c6f70c13e 100644
--- a/spec/ruby/core/thread/list_spec.rb
+++ b/spec/ruby/core/thread/list_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread.list" do
it "includes the current and main thread" do
@@ -25,18 +25,31 @@ describe "Thread.list" do
end
it "includes waiting threads" do
- c = Channel.new
- t = Thread.new { c.receive }
+ q = Queue.new
+ t = Thread.new { q.pop }
begin
Thread.pass while t.status and t.status != 'sleep'
Thread.list.should include(t)
ensure
- c << nil
+ q << nil
t.join
end
end
-end
-describe "Thread.list" do
- it "needs to be reviewed for spec completeness"
+ it "returns instances of Thread and not null or nil values" do
+ spawner = Thread.new do
+ Array.new(100) do
+ Thread.new {}
+ end
+ end
+
+ begin
+ Thread.list.each { |th|
+ th.should be_kind_of(Thread)
+ }
+ end while spawner.alive?
+
+ threads = spawner.value
+ threads.each(&:join)
+ end
end
diff --git a/spec/ruby/core/thread/main_spec.rb b/spec/ruby/core/thread/main_spec.rb
index 0cada8f59d..ec91709576 100644
--- a/spec/ruby/core/thread/main_spec.rb
+++ b/spec/ruby/core/thread/main_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread.main" do
it "returns the main thread" do
diff --git a/spec/ruby/core/thread/name_spec.rb b/spec/ruby/core/thread/name_spec.rb
index 0417d7a500..9b3d2f4b09 100644
--- a/spec/ruby/core/thread/name_spec.rb
+++ b/spec/ruby/core/thread/name_spec.rb
@@ -1,56 +1,54 @@
-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
+require_relative '../../spec_helper'
+
+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
+ -> {
+ @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
diff --git a/spec/ruby/core/thread/native_thread_id_spec.rb b/spec/ruby/core/thread/native_thread_id_spec.rb
new file mode 100644
index 0000000000..374cc59279
--- /dev/null
+++ b/spec/ruby/core/thread/native_thread_id_spec.rb
@@ -0,0 +1,35 @@
+require_relative '../../spec_helper'
+
+platform_is :linux, :darwin, :windows, :freebsd do
+ describe "Thread#native_thread_id" do
+ it "returns an integer when the thread is alive" do
+ Thread.current.native_thread_id.should be_kind_of(Integer)
+ end
+
+ it "returns nil when the thread is not running" do
+ t = Thread.new {}
+ t.join
+ t.native_thread_id.should == nil
+ end
+
+ it "each thread has different native thread id" do
+ t = Thread.new { sleep }
+ Thread.pass until t.stop?
+ main_thread_id = Thread.current.native_thread_id
+ t_thread_id = t.native_thread_id
+
+ if ruby_version_is "3.3"
+ # native_thread_id can be nil on a M:N scheduler
+ t_thread_id.should be_kind_of(Integer) if t_thread_id != nil
+ else
+ t_thread_id.should be_kind_of(Integer)
+ end
+
+ main_thread_id.should_not == t_thread_id
+
+ t.run
+ t.join
+ t.native_thread_id.should == nil
+ end
+ end
+end
diff --git a/spec/ruby/core/thread/new_spec.rb b/spec/ruby/core/thread/new_spec.rb
index b1ed5560a1..47a836201c 100644
--- a/spec/ruby/core/thread/new_spec.rb
+++ b/spec/ruby/core/thread/new_spec.rb
@@ -1,12 +1,12 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
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
+ q = Queue.new
+ Thread.new { q << true }.join
+ q << false
+ q.pop.should == true
end
it "can pass arguments to the thread block" do
@@ -18,7 +18,7 @@ describe "Thread.new" do
end
it "raises an exception when not given a block" do
- lambda { Thread.new }.should raise_error(ThreadError)
+ -> { Thread.new }.should raise_error(ThreadError)
end
it "creates a subclass of thread calls super with a block in initialize" do
@@ -34,7 +34,7 @@ describe "Thread.new" do
end
end
- lambda {
+ -> {
c.new
}.should raise_error(ThreadError)
end
@@ -53,4 +53,31 @@ describe "Thread.new" do
ScratchPad.recorded.should == [:good, :in_thread]
end
+ it "releases Mutexes held by the Thread when the Thread finishes" do
+ m1 = Mutex.new
+ m2 = Mutex.new
+ t = Thread.new {
+ m1.lock
+ m1.should.locked?
+ m2.lock
+ m2.should.locked?
+ }
+ t.join
+ m1.should_not.locked?
+ m2.should_not.locked?
+ end
+
+ it "releases Mutexes held by the Thread when the Thread finishes, also with Mutex#synchronize" do
+ m = Mutex.new
+ t = Thread.new {
+ m.synchronize {
+ m.unlock
+ m.lock
+ }
+ m.lock
+ m.should.locked?
+ }
+ t.join
+ m.should_not.locked?
+ end
end
diff --git a/spec/ruby/core/thread/pass_spec.rb b/spec/ruby/core/thread/pass_spec.rb
index 128de934ac..a5ac11a58c 100644
--- a/spec/ruby/core/thread/pass_spec.rb
+++ b/spec/ruby/core/thread/pass_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread.pass" do
it "returns nil" do
diff --git a/spec/ruby/core/thread/pending_interrupt_spec.rb b/spec/ruby/core/thread/pending_interrupt_spec.rb
new file mode 100644
index 0000000000..cd565d92a4
--- /dev/null
+++ b/spec/ruby/core/thread/pending_interrupt_spec.rb
@@ -0,0 +1,32 @@
+require_relative '../../spec_helper'
+
+describe "Thread.pending_interrupt?" do
+ it "returns false if there are no pending interrupts, e.g., outside any Thread.handle_interrupt block" do
+ Thread.pending_interrupt?.should == false
+ end
+
+ it "returns true if there are pending interrupts, e.g., Thread#raise inside Thread.handle_interrupt" do
+ executed = false
+ -> {
+ Thread.handle_interrupt(RuntimeError => :never) do
+ Thread.pending_interrupt?.should == false
+
+ current = Thread.current
+ Thread.new {
+ current.raise "interrupt"
+ }.join
+
+ Thread.pending_interrupt?.should == true
+ executed = true
+ end
+ }.should raise_error(RuntimeError, "interrupt")
+ executed.should == true
+ Thread.pending_interrupt?.should == false
+ end
+end
+
+describe "Thread#pending_interrupt?" do
+ it "returns whether the given threads has pending interrupts" do
+ Thread.current.pending_interrupt?.should == false
+ end
+end
diff --git a/spec/ruby/core/thread/priority_spec.rb b/spec/ruby/core/thread/priority_spec.rb
index b986fb7a0d..e13ad478b5 100644
--- a/spec/ruby/core/thread/priority_spec.rb
+++ b/spec/ruby/core/thread/priority_spec.rb
@@ -1,14 +1,15 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#priority" do
- before do
+ before :each do
@current_priority = Thread.current.priority
ThreadSpecs.clear_state
@thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ Thread.pass until @thread.alive?
end
- after do
+ after :each do
ThreadSpecs.state = :exit
@thread.join
end
@@ -31,12 +32,14 @@ describe "Thread#priority" do
end
describe "Thread#priority=" do
- before do
+ before :each do
ThreadSpecs.clear_state
- @thread = Thread.new {}
+ @thread = Thread.new { Thread.pass until ThreadSpecs.state == :exit }
+ Thread.pass until @thread.alive?
end
- after do
+ after :each do
+ ThreadSpecs.state = :exit
@thread.join
end
@@ -56,13 +59,14 @@ describe "Thread#priority=" do
describe "when set with a non-integer" do
it "raises a type error" do
- lambda{ @thread.priority = Object.new }.should raise_error(TypeError)
+ ->{ @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
+ thread = Thread.new {}
+ thread.join
+ thread.priority = 3
+ thread.priority.should == 3
end
end
diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb
index 8724d26202..b473eabd42 100644
--- a/spec/ruby/core/thread/raise_spec.rb
+++ b/spec/ruby/core/thread/raise_spec.rb
@@ -1,13 +1,16 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../../../shared/kernel/raise', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative '../../shared/kernel/raise'
describe "Thread#raise" do
- it "ignores dead threads" do
+ it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise
+ it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise
+
+ it "ignores dead threads and returns nil" 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
+ t.raise("Kill the thread").should == nil
+ t.join
end
end
@@ -58,7 +61,7 @@ describe "Thread#raise on a sleeping thread" do
ThreadSpecs.spin_until_sleeping(t)
t.raise
- lambda { t.value }.should raise_error(RuntimeError)
+ -> { t.value }.should raise_error(RuntimeError)
end
it "raises a RuntimeError when called with no arguments inside rescue" do
@@ -76,7 +79,55 @@ describe "Thread#raise on a sleeping thread" do
ThreadSpecs.spin_until_sleeping(t)
t.raise
end
- lambda {t.value}.should raise_error(RuntimeError)
+ -> { t.value }.should raise_error(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
@@ -114,13 +165,16 @@ describe "Thread#raise on a running thread" do
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
- lambda {t.value}.should raise_error(RuntimeError)
+ -> { t.value }.should raise_error(RuntimeError)
end
it "raises the given argument even when there is an active exception" do
@@ -139,7 +193,7 @@ describe "Thread#raise on a running thread" do
rescue
Thread.pass until raised
t.raise RangeError
- lambda {t.value}.should raise_error(RangeError)
+ -> { t.value }.should raise_error(RangeError)
end
end
@@ -151,7 +205,7 @@ describe "Thread#raise on a running thread" do
1/0
rescue ZeroDivisionError
raised = true
- loop { }
+ loop { Thread.pass }
end
end
begin
@@ -160,7 +214,7 @@ describe "Thread#raise on a running thread" do
Thread.pass until raised
t.raise
end
- lambda {t.value}.should raise_error(RuntimeError)
+ -> { t.value }.should raise_error(RuntimeError)
end
end
@@ -176,6 +230,6 @@ describe "Thread#raise on same thread" do
Thread.current.raise
end
end
- lambda {t.value}.should raise_error(RuntimeError)
+ -> { t.value }.should raise_error(RuntimeError, '')
end
end
diff --git a/spec/ruby/core/thread/report_on_exception_spec.rb b/spec/ruby/core/thread/report_on_exception_spec.rb
index d8400c080a..d9daa041cd 100644
--- a/spec/ruby/core/thread/report_on_exception_spec.rb
+++ b/spec/ruby/core/thread/report_on_exception_spec.rb
@@ -1,120 +1,155 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
-ruby_version_is "2.4" do
- describe "Thread.report_on_exception" do
- ruby_version_is "2.4"..."2.5" do
- it "defaults to false" do
- ruby_exe("p Thread.report_on_exception").should == "false\n"
- end
- end
+describe "Thread.report_on_exception" do
+ it "defaults to true" do
+ ruby_exe("p Thread.report_on_exception").should == "true\n"
+ end
+end
- ruby_version_is "2.5" do
- it "defaults to true" do
- ruby_exe("p Thread.report_on_exception").should == "true\n"
- end
- end
+describe "Thread.report_on_exception=" do
+ before :each do
+ @report_on_exception = Thread.report_on_exception
end
- describe "Thread.report_on_exception=" do
- before :each do
- @report_on_exception = Thread.report_on_exception
- end
+ after :each do
+ Thread.report_on_exception = @report_on_exception
+ end
- after :each do
- Thread.report_on_exception = @report_on_exception
- end
+ it "changes the default value for new threads" do
+ Thread.report_on_exception = true
+ Thread.report_on_exception.should == true
+ t = Thread.new {}
+ t.join
+ t.report_on_exception.should == true
+ end
+end
- it "changes the default value for new threads" do
- Thread.report_on_exception = true
- Thread.report_on_exception.should == true
- t = Thread.new {}
- t.join
- t.report_on_exception.should == true
- end
+describe "Thread#report_on_exception" do
+ it "returns true for the main Thread" do
+ Thread.current.report_on_exception.should == true
end
- describe "Thread#report_on_exception" do
- ruby_version_is "2.5" do
- it "returns true for the main Thread" do
- Thread.current.report_on_exception.should == true
- end
+ it "returns true for new Threads" do
+ Thread.new { Thread.current.report_on_exception }.value.should == true
+ end
- it "returns true for new Threads" do
- Thread.new { Thread.current.report_on_exception }.value.should == true
- end
+ it "returns whether the Thread will print a backtrace if it exits with an exception" do
+ t = Thread.new { Thread.current.report_on_exception = true }
+ t.join
+ t.report_on_exception.should == true
+
+ t = Thread.new { Thread.current.report_on_exception = false }
+ t.join
+ t.report_on_exception.should == false
+ end
+end
+
+describe "Thread#report_on_exception=" do
+ describe "when set to true" do
+ it "prints a backtrace on $stderr if it terminates with an exception" do
+ t = nil
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
+ Thread.pass while t.alive?
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
end
- it "returns whether the Thread will print a backtrace if it exits with an exception" do
- t = Thread.new { Thread.current.report_on_exception = true }
- t.join
- t.report_on_exception.should == true
+ it "prints a backtrace on $stderr in the regular backtrace order" do
+ line_raise = __LINE__ + 2
+ def foo
+ raise RuntimeError, "Thread#report_on_exception specs backtrace order"
+ end
- t = Thread.new { Thread.current.report_on_exception = false }
- t.join
- t.report_on_exception.should == false
+ line_call_foo = __LINE__ + 5
+ go = false
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ Thread.pass until go
+ foo
+ }
+
+ -> {
+ go = true
+ Thread.pass while t.alive?
+ }.should output("", /\A
+#{Regexp.quote(t.inspect)}\sterminated\swith\sexception\s\(report_on_exception\sis\strue\):\n
+#{Regexp.quote(__FILE__)}:#{line_raise}:in\s[`']foo':\sThread\#report_on_exception\sspecs\sbacktrace\sorder\s\(RuntimeError\)\n
+\tfrom\s#{Regexp.quote(__FILE__)}:#{line_call_foo}:in\s[`']block\s\(4\slevels\)\sin\s<top\s\(required\)>'\n
+\z/x)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs backtrace order")
end
- end
- describe "Thread#report_on_exception=" do
- describe "when set to true" do
- it "prints a backtrace on $stderr if it terminates with an exception" do
- t = nil
- -> {
- t = Thread.new {
- Thread.current.report_on_exception = true
- raise RuntimeError, "Thread#report_on_exception specs"
- }
- Thread.pass while t.alive?
- }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
+ it "prints the backtrace even if the thread was killed just after Thread#raise" do
+ t = nil
+ ready = false
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = true
+ ready = true
+ sleep
+ }
+
+ Thread.pass until ready and t.stop?
+ t.raise RuntimeError, "Thread#report_on_exception before kill spec"
+ t.kill
+ Thread.pass while t.alive?
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception before kill spec/m)
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception before kill spec")
+ end
+ end
- -> {
- t.join
- }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
- end
+ describe "when set to false" do
+ it "lets the thread terminates silently with an exception" do
+ t = nil
+ -> {
+ t = Thread.new {
+ Thread.current.report_on_exception = false
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
+ Thread.pass while t.alive?
+ }.should output("", "")
+
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
end
+ end
- describe "when set to false" do
- it "lets the thread terminates silently with an exception" do
- t = nil
- -> {
- t = Thread.new {
- Thread.current.report_on_exception = false
- raise RuntimeError, "Thread#report_on_exception specs"
- }
- Thread.pass while t.alive?
- }.should output("", "")
+ describe "when used in conjunction with Thread#abort_on_exception" do
+ it "first reports then send the exception back to the main Thread" do
+ t = nil
+ mutex = Mutex.new
+ mutex.lock
+ -> {
+ t = Thread.new {
+ Thread.current.abort_on_exception = true
+ Thread.current.report_on_exception = true
+ mutex.lock
+ mutex.unlock
+ raise RuntimeError, "Thread#report_on_exception specs"
+ }
-> {
- t.join
+ mutex.sleep(5)
}.should raise_error(RuntimeError, "Thread#report_on_exception specs")
- end
- end
+ }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
- ruby_bug "#13163", "2.4"..."2.5" do
- describe "when used in conjunction with Thread#abort_on_exception" do
- it "first reports then send the exception back to the main Thread" do
- t = nil
- mutex = Mutex.new
- mutex.lock
- -> {
- t = Thread.new {
- Thread.current.abort_on_exception = true
- Thread.current.report_on_exception = true
- mutex.lock
- mutex.unlock
- raise RuntimeError, "Thread#report_on_exception specs"
- }
-
- -> {
- mutex.sleep(5)
- }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
- }.should output("", /Thread.+terminated with exception.+Thread#report_on_exception specs/m)
-
- -> {
- t.join
- }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
- end
- end
+ -> {
+ t.join
+ }.should raise_error(RuntimeError, "Thread#report_on_exception specs")
end
end
end
diff --git a/spec/ruby/core/thread/run_spec.rb b/spec/ruby/core/thread/run_spec.rb
index 26ed9ed961..f86f793489 100644
--- a/spec/ruby/core/thread/run_spec.rb
+++ b/spec/ruby/core/thread/run_spec.rb
@@ -1,9 +1,8 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
-require File.expand_path('../shared/wakeup', __FILE__)
+require_relative 'shared/wakeup'
describe "Thread#run" do
it_behaves_like :thread_wakeup, :run
end
-
diff --git a/spec/ruby/core/thread/set_trace_func_spec.rb b/spec/ruby/core/thread/set_trace_func_spec.rb
index 6dd5448d79..e5d8298ae0 100644
--- a/spec/ruby/core/thread/set_trace_func_spec.rb
+++ b/spec/ruby/core/thread/set_trace_func_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#set_trace_func" do
it "needs to be reviewed for spec completeness"
diff --git a/spec/ruby/core/thread/shared/exit.rb b/spec/ruby/core/thread/shared/exit.rb
index 0c9198c538..13e8832684 100644
--- a/spec/ruby/core/thread/shared/exit.rb
+++ b/spec/ruby/core/thread/shared/exit.rb
@@ -3,6 +3,10 @@ describe :thread_exit, shared: true do
ScratchPad.clear
end
+ # This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/builds/19390874/job/wv1bsm8skd4e1pxl
+ # TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard.
+ platform_is_not :mingw do
+
it "kills sleeping thread" do
sleeping_thread = Thread.new do
sleep
@@ -62,6 +66,26 @@ describe :thread_exit, shared: true do
ScratchPad.recorded.should == nil
end
+ it "does not reset $!" do
+ ScratchPad.record []
+
+ exc = RuntimeError.new("foo")
+ thread = Thread.new do
+ begin
+ raise exc
+ ensure
+ ScratchPad << $!
+ begin
+ Thread.current.send(@method)
+ ensure
+ ScratchPad << $!
+ end
+ end
+ end
+ thread.join
+ ScratchPad.recorded.should == [exc, exc]
+ end
+
it "cannot be rescued" do
thread = Thread.new do
begin
@@ -69,26 +93,43 @@ describe :thread_exit, shared: true do
rescue Exception
ScratchPad.record :in_rescue
end
- ScratchPad.record :end_of_thread_block
+ 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
+ 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
+
+ it "kills other fibers of that thread without running their ensure clauses" do
+ t = Thread.new do
+ f = Fiber.new do
ScratchPad.record :fiber_resumed
+ begin
+ Fiber.yield
+ ensure
+ ScratchPad.record :fiber_ensure
+ end
end
- Thread.pass while t.status and t.status != "sleep"
- t.send(@method)
- t.join
- ScratchPad.recorded.should == nil
+ f.resume
+ sleep
end
+ Thread.pass until t.stop?
+ t.send(@method)
+ t.join
+ ScratchPad.recorded.should == :fiber_resumed
end
# This spec is a mess. It fails randomly, it hangs on MRI, it needs to be removed
@@ -114,7 +155,7 @@ describe :thread_exit, shared: true do
it "propagates 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")
+ -> { thread.join }.should raise_error(RuntimeError, "In dying thread")
end
it "runs all outer ensure clauses even if inner ensure clause raises exception" do
@@ -173,4 +214,6 @@ describe :thread_exit, shared: true do
t.join.should == t
end
end
+
+ end # platform_is_not :mingw
end
diff --git a/spec/ruby/core/thread/shared/start.rb b/spec/ruby/core/thread/shared/start.rb
index 80ce063a0e..2ba926bf00 100644
--- a/spec/ruby/core/thread/shared/start.rb
+++ b/spec/ruby/core/thread/shared/start.rb
@@ -4,7 +4,7 @@ describe :thread_start, shared: true do
end
it "raises an ArgumentError if not passed a block" do
- lambda {
+ -> {
Thread.send(@method)
}.should raise_error(ArgumentError)
end
diff --git a/spec/ruby/core/thread/shared/to_s.rb b/spec/ruby/core/thread/shared/to_s.rb
new file mode 100644
index 0000000000..43640deb33
--- /dev/null
+++ b/spec/ruby/core/thread/shared/to_s.rb
@@ -0,0 +1,53 @@
+require_relative '../fixtures/classes'
+
+describe :thread_to_s, shared: true do
+ it "returns a description including file and line number" do
+ thread, line = Thread.new { "hello" }, __LINE__
+ thread.join
+ thread.send(@method).should =~ /^#<Thread:([^ ]*?) #{Regexp.escape __FILE__}:#{line} \w+>$/
+ end
+
+ it "has a binary encoding" do
+ ThreadSpecs.status_of_current_thread.send(@method).encoding.should == Encoding::BINARY
+ end
+
+ it "can check it's own status" do
+ ThreadSpecs.status_of_current_thread.send(@method).should include('run')
+ end
+
+ it "describes a running thread" do
+ ThreadSpecs.status_of_running_thread.send(@method).should include('run')
+ end
+
+ it "describes a sleeping thread" do
+ ThreadSpecs.status_of_sleeping_thread.send(@method).should include('sleep')
+ end
+
+ it "describes a blocked thread" do
+ ThreadSpecs.status_of_blocked_thread.send(@method).should include('sleep')
+ end
+
+ it "describes a completed thread" do
+ ThreadSpecs.status_of_completed_thread.send(@method).should include('dead')
+ end
+
+ it "describes a killed thread" do
+ ThreadSpecs.status_of_killed_thread.send(@method).should include('dead')
+ end
+
+ it "describes a thread with an uncaught exception" do
+ ThreadSpecs.status_of_thread_with_uncaught_exception.send(@method).should include('dead')
+ end
+
+ it "describes a dying sleeping thread" do
+ ThreadSpecs.status_of_dying_sleeping_thread.send(@method).should include('sleep')
+ end
+
+ it "reports aborting on a killed thread" do
+ ThreadSpecs.status_of_dying_running_thread.send(@method).should include('aborting')
+ end
+
+ it "reports aborting on a killed thread after sleep" do
+ ThreadSpecs.status_of_dying_thread_after_sleep.send(@method).should include('aborting')
+ end
+end
diff --git a/spec/ruby/core/thread/shared/wakeup.rb b/spec/ruby/core/thread/shared/wakeup.rb
index 71838d88e5..6f010fea25 100644
--- a/spec/ruby/core/thread/shared/wakeup.rb
+++ b/spec/ruby/core/thread/shared/wakeup.rb
@@ -36,7 +36,7 @@ describe :thread_wakeup, shared: true do
it "does not result in a deadlock" do
t = Thread.new do
- 100.times { Thread.stop }
+ 10.times { Thread.stop }
end
while t.status
@@ -47,6 +47,7 @@ describe :thread_wakeup, shared: true do
t.status.should == false
end
Thread.pass
+ sleep 0.001
end
t.status.should == false
@@ -56,6 +57,6 @@ describe :thread_wakeup, shared: true do
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)
+ -> { t.send @method }.should raise_error(ThreadError)
end
end
diff --git a/spec/ruby/core/thread/start_spec.rb b/spec/ruby/core/thread/start_spec.rb
index 932e782382..3dd040f98b 100644
--- a/spec/ruby/core/thread/start_spec.rb
+++ b/spec/ruby/core/thread/start_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/start', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/start'
describe "Thread.start" do
describe "Thread.start" do
diff --git a/spec/ruby/core/thread/status_spec.rb b/spec/ruby/core/thread/status_spec.rb
index 6cfdf0be40..4fde663c91 100644
--- a/spec/ruby/core/thread/status_spec.rb
+++ b/spec/ruby/core/thread/status_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#status" do
it "can check it's own status" do
diff --git a/spec/ruby/core/thread/stop_spec.rb b/spec/ruby/core/thread/stop_spec.rb
index 0bc99487fd..084ab46ef6 100644
--- a/spec/ruby/core/thread/stop_spec.rb
+++ b/spec/ruby/core/thread/stop_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread.stop" do
it "causes the current thread to sleep indefinitely" do
@@ -13,42 +13,42 @@ end
describe "Thread#stop?" do
it "can check it's own status" do
- ThreadSpecs.status_of_current_thread.stop?.should == false
+ ThreadSpecs.status_of_current_thread.should_not.stop?
end
it "describes a running thread" do
- ThreadSpecs.status_of_running_thread.stop?.should == false
+ ThreadSpecs.status_of_running_thread.should_not.stop?
end
it "describes a sleeping thread" do
- ThreadSpecs.status_of_sleeping_thread.stop?.should == true
+ ThreadSpecs.status_of_sleeping_thread.should.stop?
end
it "describes a blocked thread" do
- ThreadSpecs.status_of_blocked_thread.stop?.should == true
+ ThreadSpecs.status_of_blocked_thread.should.stop?
end
it "describes a completed thread" do
- ThreadSpecs.status_of_completed_thread.stop?.should == true
+ ThreadSpecs.status_of_completed_thread.should.stop?
end
it "describes a killed thread" do
- ThreadSpecs.status_of_killed_thread.stop?.should == true
+ ThreadSpecs.status_of_killed_thread.should.stop?
end
it "describes a thread with an uncaught exception" do
- ThreadSpecs.status_of_thread_with_uncaught_exception.stop?.should == true
+ ThreadSpecs.status_of_thread_with_uncaught_exception.should.stop?
end
it "describes a dying running thread" do
- ThreadSpecs.status_of_dying_running_thread.stop?.should == false
+ ThreadSpecs.status_of_dying_running_thread.should_not.stop?
end
it "describes a dying sleeping thread" do
- ThreadSpecs.status_of_dying_sleeping_thread.stop?.should == true
+ ThreadSpecs.status_of_dying_sleeping_thread.should.stop?
end
it "describes a dying thread after sleep" do
- ThreadSpecs.status_of_dying_thread_after_sleep.stop?.should == false
+ ThreadSpecs.status_of_dying_thread_after_sleep.should_not.stop?
end
end
diff --git a/spec/ruby/core/thread/terminate_spec.rb b/spec/ruby/core/thread/terminate_spec.rb
index bb89d87762..cf6cab472b 100644
--- a/spec/ruby/core/thread/terminate_spec.rb
+++ b/spec/ruby/core/thread/terminate_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/exit', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/exit'
describe "Thread#terminate" do
it_behaves_like :thread_exit, :terminate
diff --git a/spec/ruby/core/thread/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb
index 0e02c30fad..1ea34cf2b3 100644
--- a/spec/ruby/core/thread/thread_variable_get_spec.rb
+++ b/spec/ruby/core/thread/thread_variable_get_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#thread_variable_get" do
before :each do
@@ -13,13 +13,48 @@ describe "Thread#thread_variable_get" do
@t.thread_variable_get(:a).should be_nil
end
- it "returns the value previously set by #[]=" do
- @t.thread_variable_set :a, 49
+ it "returns the value previously set by #thread_variable_set" 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
+ @t.thread_variable_set(:thread_variable_get_spec, 82)
Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil
end
+
+ it "accepts String and Symbol keys interchangeably" do
+ @t.thread_variable_set("a", 49)
+ @t.thread_variable_get("a").should == 49
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('key')
+ key.should_receive(:to_str).and_return('a')
+ @t.thread_variable_set(:a, 49)
+ @t.thread_variable_get(key).should == 49
+ end
+
+ it "does not raise FrozenError if the thread is frozen" do
+ @t.freeze
+ @t.thread_variable_get(:a).should be_nil
+ end
+
+ it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do
+ @t.thread_variable_set(:a, 49)
+ -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/)
+ end
+
+ ruby_version_is '3.4' do
+ it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do
+ -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/)
+ end
+
+ it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do
+ key = mock('key')
+ key.should_not_receive(:to_sym)
+ -> { @t.thread_variable_get(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/)
+ end
+ end
end
diff --git a/spec/ruby/core/thread/thread_variable_set_spec.rb b/spec/ruby/core/thread/thread_variable_set_spec.rb
index 0f55341132..eadee76afb 100644
--- a/spec/ruby/core/thread/thread_variable_set_spec.rb
+++ b/spec/ruby/core/thread/thread_variable_set_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#thread_variable_set" do
before :each do
@@ -10,17 +10,53 @@ describe "Thread#thread_variable_set" do
end
it "returns the value set" do
- (@t.thread_variable_set :a, 2).should == 2
+ @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_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_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
+
+ it "accepts String and Symbol keys interchangeably" do
+ @t.thread_variable_set('a', 49)
+ @t.thread_variable_get('a').should == 49
+
+ @t.thread_variable_set(:a, 50)
+ @t.thread_variable_get('a').should == 50
+ end
+
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('key')
+ key.should_receive(:to_str).and_return('a')
+ @t.thread_variable_set(key, 49)
+ @t.thread_variable_get(:a).should == 49
+ end
+
+ it "removes a key if the value is nil" do
+ @t.thread_variable_set(:a, 52)
+ @t.thread_variable_set(:a, nil)
+ @t.thread_variable?(:a).should be_false
+ end
+
+ it "raises a FrozenError if the thread is frozen" do
+ @t.freeze
+ -> { @t.thread_variable_set(:a, 1) }.should raise_error(FrozenError, "can't modify frozen thread locals")
+ end
+
+ it "raises a TypeError if the key is neither Symbol nor String, nor responds to #to_str" do
+ -> { @t.thread_variable_set(123, 1) }.should raise_error(TypeError, /123 is not a symbol/)
+ end
+
+ it "does not try to convert the key with #to_sym" do
+ key = mock('key')
+ key.should_not_receive(:to_sym)
+ -> { @t.thread_variable_set(key, 42) }.should raise_error(TypeError, /#{Regexp.quote(key.inspect)} is not a symbol/)
+ end
end
diff --git a/spec/ruby/core/thread/thread_variable_spec.rb b/spec/ruby/core/thread/thread_variable_spec.rb
index b409b3abfc..1b021e9404 100644
--- a/spec/ruby/core/thread/thread_variable_spec.rb
+++ b/spec/ruby/core/thread/thread_variable_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#thread_variable?" do
before :each do
@@ -10,12 +10,51 @@ describe "Thread#thread_variable?" do
end
it "returns false if the thread variables do not contain 'key'" do
- @t.thread_variable_set :a, 2
+ @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_set(:a, 2)
@t.thread_variable?(:a).should be_true
end
+
+ it "accepts String and Symbol keys interchangeably" do
+ @t.thread_variable?('a').should be_false
+ @t.thread_variable?(:a).should be_false
+
+ @t.thread_variable_set(:a, 49)
+
+ @t.thread_variable?('a').should be_true
+ @t.thread_variable?(:a).should be_true
+ end
+
+ it "converts a key that is neither String nor Symbol with #to_str" do
+ key = mock('key')
+ key.should_receive(:to_str).and_return('a')
+ @t.thread_variable_set(:a, 49)
+ @t.thread_variable?(key).should be_true
+ end
+
+ it "does not raise FrozenError if the thread is frozen" do
+ @t.freeze
+ @t.thread_variable?(:a).should be_false
+ end
+
+ it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do
+ @t.thread_variable_set(:a, 49)
+ -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/)
+ end
+
+ ruby_version_is '3.4' do
+ it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do
+ -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/)
+ end
+
+ it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do
+ key = mock('key')
+ key.should_not_receive(:to_sym)
+ -> { @t.thread_variable?(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/)
+ end
+ end
end
diff --git a/spec/ruby/core/thread/thread_variables_spec.rb b/spec/ruby/core/thread/thread_variables_spec.rb
index 39299cf20e..51ceef3376 100644
--- a/spec/ruby/core/thread/thread_variables_spec.rb
+++ b/spec/ruby/core/thread/thread_variables_spec.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../../spec_helper', __FILE__)
+require_relative '../../spec_helper'
describe "Thread#thread_variables" do
before :each do
@@ -10,15 +10,15 @@ describe "Thread#thread_variables" do
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_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 :a, 82
- @t.thread_variable_set :b, 82
+ it "returns the keys private to self" do
+ @t.thread_variable_set(:a, 82)
+ @t.thread_variable_set(:b, 82)
Thread.current.thread_variables.should_not include(:a, :b)
end
@@ -26,4 +26,14 @@ describe "Thread#thread_variables" do
Thread.current.thread_variables.should == []
@t.thread_variables.should == []
end
+
+ it "returns keys as Symbols" do
+ key = mock('key')
+ key.should_receive(:to_str).and_return('a')
+
+ @t.thread_variable_set(key, 49)
+ @t.thread_variable_set('b', 50)
+ @t.thread_variable_set(:c, 51)
+ @t.thread_variables.sort.should == [:a, :b, :c]
+ end
end
diff --git a/spec/ruby/core/thread/to_s_spec.rb b/spec/ruby/core/thread/to_s_spec.rb
new file mode 100644
index 0000000000..cb182a017f
--- /dev/null
+++ b/spec/ruby/core/thread/to_s_spec.rb
@@ -0,0 +1,6 @@
+require_relative '../../spec_helper'
+require_relative 'shared/to_s'
+
+describe "Thread#to_s" do
+ it_behaves_like :thread_to_s, :to_s
+end
diff --git a/spec/ruby/core/thread/value_spec.rb b/spec/ruby/core/thread/value_spec.rb
index 3d900959df..30e43abd1a 100644
--- a/spec/ruby/core/thread/value_spec.rb
+++ b/spec/ruby/core/thread/value_spec.rb
@@ -1,5 +1,5 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
describe "Thread#value" do
it "returns the result of the block" do
@@ -11,11 +11,21 @@ describe "Thread#value" do
Thread.current.report_on_exception = false
raise "Hello"
}
- lambda { t.value }.should raise_error(RuntimeError, "Hello")
+ -> { 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
+
+ it "returns when the thread finished" do
+ q = Queue.new
+ t = Thread.new {
+ q.pop
+ }
+ -> { t.value }.should block_caller
+ q.push :result
+ t.value.should == :result
+ end
end
diff --git a/spec/ruby/core/thread/wakeup_spec.rb b/spec/ruby/core/thread/wakeup_spec.rb
index 5197a03a35..da5dfea377 100644
--- a/spec/ruby/core/thread/wakeup_spec.rb
+++ b/spec/ruby/core/thread/wakeup_spec.rb
@@ -1,6 +1,6 @@
-require File.expand_path('../../../spec_helper', __FILE__)
-require File.expand_path('../fixtures/classes', __FILE__)
-require File.expand_path('../shared/wakeup', __FILE__)
+require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
+require_relative 'shared/wakeup'
describe "Thread#wakeup" do
it_behaves_like :thread_wakeup, :wakeup