diff options
Diffstat (limited to 'spec/ruby/core/thread')
52 files changed, 1242 insertions, 343 deletions
diff --git a/spec/ruby/core/thread/abort_on_exception_spec.rb b/spec/ruby/core/thread/abort_on_exception_spec.rb index 88de77b1a8..49be84ea9f 100644 --- a/spec/ruby/core/thread/abort_on_exception_spec.rb +++ b/spec/ruby/core/thread/abort_on_exception_spec.rb @@ -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/alive_spec.rb b/spec/ruby/core/thread/alive_spec.rb index d4ba149d87..c2f5f5371d 100644 --- a/spec/ruby/core/thread/alive_spec.rb +++ b/spec/ruby/core/thread/alive_spec.rb @@ -3,39 +3,39 @@ 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 cd9aa1ee43..cfd556812f 100644 --- a/spec/ruby/core/thread/allocate_spec.rb +++ b/spec/ruby/core/thread/allocate_spec.rb @@ -2,7 +2,7 @@ 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 d38659c257..68a69049d9 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -10,11 +10,28 @@ describe 'Thread::Backtrace::Location#absolute_path' 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 filename" do + it "returns nil with absolute_path" do code = "caller_locations(0)[0].absolute_path" - eval(code, nil, "foo.rb").should == "foo.rb" - eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" + + eval(code, nil, "foo.rb").should == nil + eval(code, nil, "foo/bar.rb").should == nil end end @@ -29,33 +46,48 @@ describe 'Thread::Backtrace::Location#absolute_path' do end end - platform_is_not :windows do - before :each do - @file = fixture(__FILE__, "absolute_path.rb") - @symlink = tmp("symlink.rb") - File.symlink(@file, @symlink) - ScratchPad.record [] + 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 - after :each do - rm_r @symlink - 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 - it "returns a canonical path without symlinks, even when __FILE__ does not" do - realpath = File.realpath(@symlink) - realpath.should_not == @symlink + after :each do + rm_r @symlink + end - load @symlink - ScratchPad.recorded.should == [@symlink, realpath] - 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 + 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] + 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 139a68e2c8..739f62f42f 100644 --- a/spec/ruby/core/thread/backtrace/location/base_label_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/base_label_spec.rb @@ -19,4 +19,31 @@ describe 'Thread::Backtrace::Location#base_label' 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_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/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/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb index be8da5646f..85ddccc8e3 100644 --- a/spec/ruby/core/thread/backtrace/location/label_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb @@ -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 dc93d32d75..10457f80f0 100644 --- a/spec/ruby/core/thread/backtrace/location/lineno_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/lineno_spec.rb @@ -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 b1a3439747..75f76833a9 100644 --- a/spec/ruby/core/thread/backtrace/location/path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/path_spec.rb @@ -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 @@ -86,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_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 84ed574d5c..15bb29a349 100644 --- a/spec/ruby/core/thread/backtrace_spec.rb +++ b/spec/ruby/core/thread/backtrace_spec.rb @@ -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 @@ -32,4 +32,38 @@ describe "Thread#backtrace" do 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/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 4de33e1456..fde9d1f440 100644 --- a/spec/ruby/core/thread/element_reference_spec.rb +++ b/spec/ruby/core/thread/element_reference_spec.rb @@ -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 c109468a5e..f205177304 100644 --- a/spec/ruby/core/thread/element_set_spec.rb +++ b/spec/ruby/core/thread/element_set_spec.rb @@ -6,19 +6,42 @@ describe "Thread#[]=" do Thread.current[:value] = nil end - it "raises a #{frozen_error_class} 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(frozen_error_class, /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 9de427fb52..0000000000 --- a/spec/ruby/core/thread/exclusive_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require_relative '../../spec_helper' - -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 "blocks the caller if another thread is also in an exclusive block" do - m = Mutex.new - q1 = Queue.new - q2 = Queue.new - - t = Thread.new { - Thread.exclusive { - q1.push :ready - q2.pop - } - } - - q1.pop.should == :ready - - lambda { Thread.exclusive { } }.should block_caller - - q2.push :done - t.join - end - - it "is not recursive" do - Thread.exclusive do - lambda { Thread.exclusive { } }.should raise_error(ThreadError) - end - end -end diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb index d71c938880..85ffb71874 100644 --- a/spec/ruby/core/thread/fetch_spec.rb +++ b/spec/ruby/core/thread/fetch_spec.rb @@ -1,38 +1,66 @@ require_relative '../../spec_helper' -ruby_version_is '2.5' do - 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 - - 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) +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/group_spec.rb b/spec/ruby/core/thread/group_spec.rb index 59f5ac37c8..d0d4704b66 100644 --- a/spec/ruby/core/thread/group_spec.rb +++ b/spec/ruby/core/thread/group_spec.rb @@ -1,5 +1,16 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' + 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 80e058a120..4fca900cd8 100644 --- a/spec/ruby/core/thread/initialize_spec.rb +++ b/spec/ruby/core/thread/initialize_spec.rb @@ -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 4c635b7aaa..bd6e0c31fc 100644 --- a/spec/ruby/core/thread/inspect_spec.rb +++ b/spec/ruby/core/thread/inspect_spec.rb @@ -1,44 +1,6 @@ require_relative '../../spec_helper' -require_relative 'fixtures/classes' +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 f60f91e436..213fe2e505 100644 --- a/spec/ruby/core/thread/join_spec.rb +++ b/spec/ruby/core/thread/join_spec.rb @@ -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 c4ed0f9c0d..339fa98f53 100644 --- a/spec/ruby/core/thread/key_spec.rb +++ b/spec/ruby/core/thread/key_spec.rb @@ -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/kill_spec.rb b/spec/ruby/core/thread/kill_spec.rb index f932bf5232..4b62c686c7 100644 --- a/spec/ruby/core/thread/kill_spec.rb +++ b/spec/ruby/core/thread/kill_spec.rb @@ -9,10 +9,6 @@ platform_is_not :mingw do it_behaves_like :thread_exit, :kill end - describe "Thread#kill!" do - it "needs to be reviewed for spec completeness" - end - describe "Thread.kill" do it "causes the given thread to exit" do thread = Thread.new { sleep } diff --git a/spec/ruby/core/thread/list_spec.rb b/spec/ruby/core/thread/list_spec.rb index bd56cb4c52..3c6f70c13e 100644 --- a/spec/ruby/core/thread/list_spec.rb +++ b/spec/ruby/core/thread/list_spec.rb @@ -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/name_spec.rb b/spec/ruby/core/thread/name_spec.rb index adb2d08fae..9b3d2f4b09 100644 --- a/spec/ruby/core/thread/name_spec.rb +++ b/spec/ruby/core/thread/name_spec.rb @@ -34,7 +34,7 @@ describe "Thread#name=" do end it "raises an ArgumentError if the name includes a null byte" do - lambda { + -> { @thread.name = "new thread\0name" }.should raise_error(ArgumentError) 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 c1e0e2a5ad..47a836201c 100644 --- a/spec/ruby/core/thread/new_spec.rb +++ b/spec/ruby/core/thread/new_spec.rb @@ -3,10 +3,10 @@ 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/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 5da6216b53..e13ad478b5 100644 --- a/spec/ruby/core/thread/priority_spec.rb +++ b/spec/ruby/core/thread/priority_spec.rb @@ -59,7 +59,7 @@ 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 diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb index 38571854ef..b473eabd42 100644 --- a/spec/ruby/core/thread/raise_spec.rb +++ b/spec/ruby/core/thread/raise_spec.rb @@ -3,6 +3,9 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/raise' describe "Thread#raise" 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? @@ -102,6 +105,30 @@ describe "Thread#raise on a sleeping thread" do raised_again.backtrace.first.should_not include("#{__FILE__}:#{raise_again_line}:") end end + + it "calls #exception in both the caller and in the target thread" do + cls = Class.new(Exception) do + attr_accessor :log + def initialize(*args) + @log = [] # This is shared because the super #exception uses a shallow clone + super + end + + def exception(*args) + @log << [self, Thread.current, args] + super + end + end + exc = cls.new + + @thr.raise exc, "Thread#raise #exception spec" + @thr.join + ScratchPad.recorded.should.is_a?(cls) + exc.log.should == [ + [exc, Thread.current, ["Thread#raise #exception spec"]], + [ScratchPad.recorded, @thr, []] + ] + end end describe "Thread#raise on a running thread" do @@ -138,11 +165,14 @@ 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 -> { t.value }.should raise_error(RuntimeError) end @@ -200,6 +230,6 @@ describe "Thread#raise on same thread" do Thread.current.raise end end - -> { 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 16597f3a4b..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_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/shared/exit.rb b/spec/ruby/core/thread/shared/exit.rb index 3c63517d92..13e8832684 100644 --- a/spec/ruby/core/thread/shared/exit.rb +++ b/spec/ruby/core/thread/shared/exit.rb @@ -66,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 @@ -73,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 @@ -118,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 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/stop_spec.rb b/spec/ruby/core/thread/stop_spec.rb index 33cf8f7b5c..084ab46ef6 100644 --- a/spec/ruby/core/thread/stop_spec.rb +++ b/spec/ruby/core/thread/stop_spec.rb @@ -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/thread_variable_get_spec.rb b/spec/ruby/core/thread/thread_variable_get_spec.rb index 38f90d5830..1ea34cf2b3 100644 --- a/spec/ruby/core/thread/thread_variable_get_spec.rb +++ b/spec/ruby/core/thread/thread_variable_get_spec.rb @@ -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 1338c306c7..eadee76afb 100644 --- a/spec/ruby/core/thread/thread_variable_set_spec.rb +++ b/spec/ruby/core/thread/thread_variable_set_spec.rb @@ -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 6bd1950c04..1b021e9404 100644 --- a/spec/ruby/core/thread/thread_variable_spec.rb +++ b/spec/ruby/core/thread/thread_variable_spec.rb @@ -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 1bd68b17f1..51ceef3376 100644 --- a/spec/ruby/core/thread/thread_variables_spec.rb +++ b/spec/ruby/core/thread/thread_variables_spec.rb @@ -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 30b12db125..30e43abd1a 100644 --- a/spec/ruby/core/thread/value_spec.rb +++ b/spec/ruby/core/thread/value_spec.rb @@ -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 |
