diff options
Diffstat (limited to 'spec/ruby/core/thread')
47 files changed, 852 insertions, 255 deletions
diff --git a/spec/ruby/core/thread/abort_on_exception_spec.rb b/spec/ruby/core/thread/abort_on_exception_spec.rb index 34b648ca0f..aeca50e5c1 100644 --- a/spec/ruby/core/thread/abort_on_exception_spec.rb +++ b/spec/ruby/core/thread/abort_on_exception_spec.rb @@ -13,12 +13,12 @@ describe "Thread#abort_on_exception" do end it "is false by default" do - @thread.abort_on_exception.should be_false + @thread.abort_on_exception.should == false end it "returns true when #abort_on_exception= is passed true" do @thread.abort_on_exception = true - @thread.abort_on_exception.should be_true + @thread.abort_on_exception.should == true end end @@ -39,7 +39,7 @@ describe :thread_abort_on_exception, shared: true do ThreadSpecs.state = :run # Wait for the main thread to be interrupted sleep - end.should raise_error(RuntimeError, "Thread#abort_on_exception= specs") + end.should.raise(RuntimeError, "Thread#abort_on_exception= specs") ScratchPad << :after rescue Exception => e @@ -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 @@ -81,7 +81,7 @@ describe "Thread.abort_on_exception" do it "returns true when .abort_on_exception= is passed true" do Thread.abort_on_exception = true - Thread.abort_on_exception.should be_true + Thread.abort_on_exception.should == true end end diff --git a/spec/ruby/core/thread/allocate_spec.rb b/spec/ruby/core/thread/allocate_spec.rb index cfd556812f..0b4e4f1b1f 100644 --- a/spec/ruby/core/thread/allocate_spec.rb +++ b/spec/ruby/core/thread/allocate_spec.rb @@ -4,6 +4,6 @@ describe "Thread.allocate" do it "raises a TypeError" do -> { Thread.allocate - }.should raise_error(TypeError) + }.should.raise(TypeError) end 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 4136f09348..6d9482f2ae 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -17,21 +17,21 @@ describe 'Thread::Backtrace::Location#absolute_path' do end end - context "when used in eval with a given filename" do - code = "caller_locations(0)[0].absolute_path" - - ruby_version_is ""..."3.1" do - it "returns filename with absolute_path" do - eval(code, nil, "foo.rb").should == "foo.rb" - eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" - 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 - ruby_version_is "3.1" do - it "returns nil with absolute_path" do - eval(code, nil, "foo.rb").should == nil - eval(code, nil, "foo/bar.rb").should == nil - 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 @@ -42,7 +42,7 @@ describe 'Thread::Backtrace::Location#absolute_path' do 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' + locations[0].label.should.include? 'MethodAddedAbsolutePath' end end @@ -50,7 +50,7 @@ describe 'Thread::Backtrace::Location#absolute_path' do it "returns nil" do location = nil tap { location = caller_locations(1, 1)[0] } - location.label.should == "tap" + location.label.should =~ /\A(?:Kernel#)?tap\z/ if location.path.start_with?("<internal:") location.absolute_path.should == nil else diff --git a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb index e903c3e450..103c36b3a0 100644 --- a/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb +++ b/spec/ruby/core/thread/backtrace/location/fixtures/classes.rb @@ -1,10 +1,26 @@ +# These are top-level def on purpose to test those cases + +def label_top_method = ThreadBacktraceLocationSpecs::LABEL.call + +def self.label_sdef_method_of_main = ThreadBacktraceLocationSpecs::LABEL.call + +class << self + def label_sclass_method_of_main = ThreadBacktraceLocationSpecs::LABEL.call +end + module ThreadBacktraceLocationSpecs MODULE_LOCATION = caller_locations(0) rescue nil + INSTANCE = Object.new.extend(self) + LABEL = -> { caller_locations(1, 1)[0].label } def self.locations caller_locations end + def instance_method_location + caller_locations(0) + end + def self.method_location caller_locations(0) end @@ -15,6 +31,12 @@ module ThreadBacktraceLocationSpecs end end + def instance_block_location + 1.times do + return caller_locations(0) + end + end + def self.locations_inside_nested_blocks first_level_location = nil second_level_location = nil @@ -32,4 +54,86 @@ module ThreadBacktraceLocationSpecs [first_level_location, second_level_location, third_level_location] end + + def instance_locations_inside_nested_block + loc = nil + 1.times do + 1.times do + loc = caller_locations(0) + end + end + loc + end + + def original_method = LABEL.call + alias_method :aliased_method, :original_method + + module M + class C + def regular_instance_method = LABEL.call + + def self.sdef_class_method = LABEL.call + + class << self + def sclass_method = LABEL.call + + def block_in_sclass_method + -> { + -> { LABEL.call }.call + }.call + end + end + block_in_sclass_method + end + end + + class M::D + def scoped_method = LABEL.call + + def self.sdef_scoped_method = LABEL.call + + class << self + def sclass_scoped_method = LABEL.call + end + + module ::ThreadBacktraceLocationSpecs + def top = LABEL.call + end + + class ::ThreadBacktraceLocationSpecs::Nested + def top_nested = LABEL.call + + class C + def top_nested_c = LABEL.call + end + end + end + + SOME_OBJECT = Object.new + SOME_OBJECT.instance_exec do + def unknown_def_singleton_method = LABEL.call + + def self.unknown_sdef_singleton_method = LABEL.call + end + + M.module_eval do + def module_eval_method = LABEL.call + + def self.sdef_module_eval_method = LABEL.call + end + + def ThreadBacktraceLocationSpecs.string_class_method = LABEL.call + + module M + def ThreadBacktraceLocationSpecs.nested_class_method = LABEL.call + end + + module M + module_function def mod_function = LABEL.call + end + + expr = self + def expr.sdef_expression = LABEL.call + + def expr.block_in_sdef_expression = -> { LABEL.call }.call end 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 20e477a5a6..4df88a2f33 100644 --- a/spec/ruby/core/thread/backtrace/location/inspect_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/inspect_spec.rb @@ -8,6 +8,6 @@ describe 'Thread::Backtrace::Location#inspect' do end it 'converts the call frame to a String' do - @frame.inspect.should include("#{__FILE__}:#{@line}:in ") + @frame.inspect.should.include?("#{__FILE__}:#{@line}:in ") end end diff --git a/spec/ruby/core/thread/backtrace/location/label_spec.rb b/spec/ruby/core/thread/backtrace/location/label_spec.rb index 7312d017e5..5f6a7b73df 100644 --- a/spec/ruby/core/thread/backtrace/location/label_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/label_spec.rb @@ -3,28 +3,28 @@ require_relative 'fixtures/classes' describe 'Thread::Backtrace::Location#label' do it 'returns the base label of the call frame' do - ThreadBacktraceLocationSpecs.locations[0].label.should include('<top (required)>') + ThreadBacktraceLocationSpecs.locations[0].label.should.include?('<top (required)>') end it 'returns the method name for a method location' do - ThreadBacktraceLocationSpecs.method_location[0].label.should == "method_location" + 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" + ThreadBacktraceLocationSpecs::MODULE_LOCATION[0].label.should == "<module: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 == 'block in locations_inside_nested_blocks' - second_level_location.label.should == 'block (2 levels) in locations_inside_nested_blocks' - third_level_location.label.should == 'block (3 levels) in 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 @@ -34,4 +34,194 @@ describe 'Thread::Backtrace::Location#label' do main_label.should == "block in <main>\n" required_label.should == "block in <top (required)>\n" end + + it "return the same name as the caller for eval" do + this = caller_locations(0)[0].label + eval("caller_locations(0)[0]").label.should == this + + b = binding + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var1, 1) + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var2, 2) + b.eval("caller_locations(0)[0]").label.should == this + + b.local_variable_set(:binding_var2, 2) + eval("caller_locations(0)[0]", b).label.should == this + end + + ruby_version_is "3.4" do + describe "is Module#method for" do + it "a core method defined natively" do + BasicObject.instance_method(:instance_exec).should_not.source_location + loc = nil + loc = instance_exec { caller_locations(1, 1)[0] } + loc.label.should == "BasicObject#instance_exec" + end + + it "a core method defined in Ruby" do + Kernel.instance_method(:tap).should.source_location + loc = nil + tap { loc = caller_locations(1, 1)[0] } + loc.label.should == "Kernel#tap" + end + + it "an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_method_location[0].label.should == "ThreadBacktraceLocationSpecs#instance_method_location" + end + + it "a block in an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_block_location[0].label.should == "block in ThreadBacktraceLocationSpecs#instance_block_location" + end + + it "a nested block in an instance method defined in Ruby" do + ThreadBacktraceLocationSpecs::INSTANCE.instance_locations_inside_nested_block[0].label.should == "block (2 levels) in ThreadBacktraceLocationSpecs#instance_locations_inside_nested_block" + end + + it "a method defined via module_exec" do + ThreadBacktraceLocationSpecs.module_exec do + def in_module_exec + caller_locations(0) + end + end + ThreadBacktraceLocationSpecs::INSTANCE.in_module_exec[0].label.should == "ThreadBacktraceLocationSpecs#in_module_exec" + end + + it "a method defined via module_eval" do + ThreadBacktraceLocationSpecs.module_eval <<~RUBY + def in_module_eval + caller_locations(0) + end + RUBY + ThreadBacktraceLocationSpecs::INSTANCE.in_module_eval[0].label.should == "ThreadBacktraceLocationSpecs#in_module_eval" + end + end + + describe "is Module.method for" do + it "a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.method_location[0].label.should == "ThreadBacktraceLocationSpecs.method_location" + end + + it "a block in a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.block_location[0].label.should == "block in ThreadBacktraceLocationSpecs.block_location" + end + + it "a nested block in a singleton method defined in Ruby" do + ThreadBacktraceLocationSpecs.locations_inside_nested_blocks[2].label.should == "block (3 levels) in ThreadBacktraceLocationSpecs.locations_inside_nested_blocks" + end + + it "a singleton method defined via def Const.method" do + def ThreadBacktraceLocationSpecs.def_singleton + caller_locations(0) + end + ThreadBacktraceLocationSpecs.def_singleton[0].label.should == "ThreadBacktraceLocationSpecs.def_singleton" + end + end + + it "shows the original method name for an aliased method" do + ThreadBacktraceLocationSpecs::INSTANCE.aliased_method.should == "ThreadBacktraceLocationSpecs#original_method" + end + + # A wide variety of cases. + # These show interesting cases when trying to determine the name statically/at parse time + describe "is correct for" do + base = ThreadBacktraceLocationSpecs + + it "M::C#regular_instance_method" do + base::M::C.new.regular_instance_method.should == "#{base}::M::C#regular_instance_method" + end + + it "M::C.sdef_class_method" do + base::M::C.sdef_class_method.should == "#{base}::M::C.sdef_class_method" + end + + it "M::C.sclass_method" do + base::M::C.sclass_method.should == "#{base}::M::C.sclass_method" + end + + it "M::C.block_in_sclass_method" do + base::M::C.block_in_sclass_method.should == "block (2 levels) in #{base}::M::C.block_in_sclass_method" + end + + it "M::D#scoped_method" do + base::M::D.new.scoped_method.should == "#{base}::M::D#scoped_method" + end + + it "M::D.sdef_scoped_method" do + base::M::D.sdef_scoped_method.should == "#{base}::M::D.sdef_scoped_method" + end + + it "M::D.sclass_scoped_method" do + base::M::D.sclass_scoped_method.should == "#{base}::M::D.sclass_scoped_method" + end + + it "ThreadBacktraceLocationSpecs#top" do + ThreadBacktraceLocationSpecs::INSTANCE.top.should == "ThreadBacktraceLocationSpecs#top" + end + + it "ThreadBacktraceLocationSpecs::Nested#top_nested" do + ThreadBacktraceLocationSpecs::Nested.new.top_nested.should == "ThreadBacktraceLocationSpecs::Nested#top_nested" + end + + it "ThreadBacktraceLocationSpecs::Nested::C#top_nested_c" do + ThreadBacktraceLocationSpecs::Nested::C.new.top_nested_c.should == "ThreadBacktraceLocationSpecs::Nested::C#top_nested_c" + end + + it "Object#label_top_method" do + label_top_method.should == "Object#label_top_method" + end + + it "main.label_sdef_method_of_main" do + main = TOPLEVEL_BINDING.receiver + main.label_sdef_method_of_main.should == "label_sdef_method_of_main" + end + + it "main.label_sclass_method_of_main" do + main = TOPLEVEL_BINDING.receiver + main.label_sclass_method_of_main.should == "label_sclass_method_of_main" + end + + it "unknown_def_singleton_method" do + base::SOME_OBJECT.unknown_def_singleton_method.should == "unknown_def_singleton_method" + end + + it "unknown_sdef_singleton_method" do + base::SOME_OBJECT.unknown_sdef_singleton_method.should == "unknown_sdef_singleton_method" + end + + it "M#module_eval_method" do + Object.new.extend(base::M).module_eval_method.should == "#{base}::M#module_eval_method" + end + + it "M.sdef_module_eval_method" do + base::M.sdef_module_eval_method.should == "#{base}::M.sdef_module_eval_method" + end + + it "ThreadBacktraceLocationSpecs.string_class_method" do + ThreadBacktraceLocationSpecs.string_class_method.should == "ThreadBacktraceLocationSpecs.string_class_method" + end + + it "ThreadBacktraceLocationSpecs.nested_class_method" do + ThreadBacktraceLocationSpecs.nested_class_method.should == "ThreadBacktraceLocationSpecs.nested_class_method" + end + + it "M#mod_function" do + Object.new.extend(base::M).send(:mod_function).should == "#{base}::M#mod_function" + end + + it "M.mod_function" do + base::M.mod_function.should == "#{base}::M.mod_function" + end + + it "sdef_expression" do + base.sdef_expression.should == "#{base}.sdef_expression" + end + + it "block_in_sdef_expression" do + base.block_in_sdef_expression.should == "block in #{base}.block_in_sdef_expression" + end + end + 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 d14cf17514..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,7 @@ 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 diff --git a/spec/ruby/core/thread/backtrace/location/path_spec.rb b/spec/ruby/core/thread/backtrace/location/path_spec.rb index 7863c055d3..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 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 5911cdced0..983ce4c3f8 100644 --- a/spec/ruby/core/thread/backtrace/location/to_s_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/to_s_spec.rb @@ -8,6 +8,6 @@ describe 'Thread::Backtrace::Location#to_s' do end it 'converts the call frame to a String' do - @frame.to_s.should include("#{__FILE__}:#{@line}:in ") + @frame.to_s.should.include?("#{__FILE__}:#{@line}:in ") end end diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index c970ae023b..28a488f311 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -3,20 +3,20 @@ 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 + locations.should.instance_of?(Array) + locations.should_not.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) } + locations.each { |loc| loc.should.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) } + locations.should.instance_of?(Array) + locations.should_not.empty? + locations.each { |loc| loc.should.instance_of?(Thread::Backtrace::Location) } end it "can be called with a number of locations to omit" do @@ -70,7 +70,7 @@ describe "Thread#backtrace_locations" do end it "the first location reports the call to #backtrace_locations" do - Thread.current.backtrace_locations(0..0)[0].to_s.should == "#{__FILE__ }:#{__LINE__ }:in `backtrace_locations'" + 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 diff --git a/spec/ruby/core/thread/backtrace_spec.rb b/spec/ruby/core/thread/backtrace_spec.rb index 9001b1b7eb..770c300f06 100644 --- a/spec/ruby/core/thread/backtrace_spec.rb +++ b/spec/ruby/core/thread/backtrace_spec.rb @@ -12,8 +12,8 @@ describe "Thread#backtrace" do Thread.pass while t.status && t.status != 'sleep' backtrace = t.backtrace - backtrace.should be_kind_of(Array) - backtrace.first.should =~ /`sleep'/ + backtrace.should.is_a?(Array) + backtrace.first.should =~ /[`'](?:Kernel#)?sleep'/ t.raise 'finish the thread' t.join @@ -30,7 +30,7 @@ describe "Thread#backtrace" do backtrace = t.backtrace t.kill t.join - backtrace.should be_kind_of(Array) + backtrace.should.is_a?(Array) end it "can be called with a number of locations to omit" do diff --git a/spec/ruby/core/thread/current_spec.rb b/spec/ruby/core/thread/current_spec.rb index f5ed1d95cd..f893f078ba 100644 --- a/spec/ruby/core/thread/current_spec.rb +++ b/spec/ruby/core/thread/current_spec.rb @@ -4,13 +4,13 @@ require_relative 'fixtures/classes' describe "Thread.current" do it "returns a thread" do current = Thread.current - current.should be_kind_of(Thread) + current.should.is_a?(Thread) end it "returns the current thread" do t = Thread.new { Thread.current } - t.value.should equal(t) - Thread.current.should_not equal(t.value) + t.value.should.equal?(t) + Thread.current.should_not.equal?(t.value) end it "returns the correct thread in a Fiber" do @@ -22,10 +22,10 @@ describe "Thread.current" do cur = Thread.current Fiber.new { Thread.current - }.resume.should equal cur + }.resume.should.equal? cur cur } - t.value.should equal t + 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..15fda1a37b --- /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.is_a?(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.is_a?(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(LocalJumpError, "no block given") + end + + it "doesn't accept keyword arguments" do + -> { + Thread.each_caller_location(12, foo: 10) {} + }.should.raise(ArgumentError); + end +end diff --git a/spec/ruby/core/thread/element_reference_spec.rb b/spec/ruby/core/thread/element_reference_spec.rb index 85280cb287..72892f6c50 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 - -> { Thread.current[nil] }.should raise_error(TypeError) - -> { Thread.current[5] }.should raise_error(TypeError) + -> { Thread.current[nil] }.should.raise(TypeError) + -> { Thread.current[5] }.should.raise(TypeError) end end diff --git a/spec/ruby/core/thread/element_set_spec.rb b/spec/ruby/core/thread/element_set_spec.rb index c7498f7ac9..97d6c23980 100644 --- a/spec/ruby/core/thread/element_set_spec.rb +++ b/spec/ruby/core/thread/element_set_spec.rb @@ -12,13 +12,36 @@ describe "Thread#[]=" do th.freeze -> { th[:foo] = "bar" - }.should raise_error(FrozenError, /frozen/) + }.should.raise(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 - -> { Thread.current[nil] = true }.should raise_error(TypeError) - -> { Thread.current[5] = true }.should raise_error(TypeError) + -> { Thread.current[nil] = true }.should.raise(TypeError) + -> { Thread.current[5] = true }.should.raise(TypeError) end it "is not shared across fibers" do @@ -28,7 +51,7 @@ describe "Thread#[]=" do Thread.current[:value].should == 1 end fib.resume - Thread.current[:value].should be_nil + Thread.current[:value].should == nil Thread.current[:value] = 2 fib.resume Thread.current[:value] = 2 diff --git a/spec/ruby/core/thread/exclusive_spec.rb b/spec/ruby/core/thread/exclusive_spec.rb deleted file mode 100644 index 37c4b19d1a..0000000000 --- a/spec/ruby/core/thread/exclusive_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require_relative '../../spec_helper' - -ruby_version_is ''...'3.0' do - describe "Thread.exclusive" do - before :each do - ScratchPad.clear - $VERBOSE, @verbose = nil, $VERBOSE - end - - after :each do - $VERBOSE = @verbose - 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 - - -> { Thread.exclusive { } }.should block_caller - - q2.push :done - t.join - end - - it "is not recursive" do - Thread.exclusive do - -> { Thread.exclusive { } }.should raise_error(ThreadError) - end - end - end -end diff --git a/spec/ruby/core/thread/exit_spec.rb b/spec/ruby/core/thread/exit_spec.rb index c3f710920e..b2e923c680 100644 --- a/spec/ruby/core/thread/exit_spec.rb +++ b/spec/ruby/core/thread/exit_spec.rb @@ -10,6 +10,6 @@ describe "Thread.exit" do it "causes the current thread to exit" do thread = Thread.new { Thread.exit; sleep } thread.join - thread.status.should be_false + thread.status.should == false end end diff --git a/spec/ruby/core/thread/fetch_spec.rb b/spec/ruby/core/thread/fetch_spec.rb index 6b37d4cfc5..fe27dec4a2 100644 --- a/spec/ruby/core/thread/fetch_spec.rb +++ b/spec/ruby/core/thread/fetch_spec.rb @@ -19,7 +19,7 @@ describe 'Thread#fetch' 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) + -> { th.fetch(:cat) }.should.raise(KeyError) end it 'returns the value of the fiber-local variable if value has been assigned' do @@ -29,8 +29,38 @@ describe 'Thread#fetch' do 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) + -> { Thread.current.fetch() }.should.raise(ArgumentError) + -> { Thread.current.fetch(1, 2, 3) }.should.raise(ArgumentError) end end diff --git a/spec/ruby/core/thread/fixtures/classes.rb b/spec/ruby/core/thread/fixtures/classes.rb index 23a090feb0..14d5d2f7bf 100644 --- a/spec/ruby/core/thread/fixtures/classes.rb +++ b/spec/ruby/core/thread/fixtures/classes.rb @@ -6,6 +6,31 @@ 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, :to_s def initialize(thread) @@ -182,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 } - -> { t.join }.should raise_error(RuntimeError, "In dying thread") + -> { t.join }.should.raise(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 index ea7e81cb98..aa03d4c66d 100644 --- a/spec/ruby/core/thread/handle_interrupt_spec.rb +++ b/spec/ruby/core/thread/handle_interrupt_spec.rb @@ -75,7 +75,7 @@ describe "Thread.handle_interrupt" do Thread.handle_interrupt(RuntimeError => :immediate) { flunk "not reached" } - }.should raise_error(RuntimeError, "interrupt immediate") + }.should.raise(RuntimeError, "interrupt immediate") Thread.pending_interrupt?.should == false end end @@ -95,7 +95,7 @@ describe "Thread.handle_interrupt" do Thread.handle_interrupt(RuntimeError => :immediate) { flunk "not reached" } - }.should raise_error(RuntimeError, "interrupt with fibers") + }.should.raise(RuntimeError, "interrupt with fibers") Thread.pending_interrupt?.should == false end @@ -115,7 +115,7 @@ describe "Thread.handle_interrupt" do executed = true raise "regular exception" end - }.should raise_error(RuntimeError, "interrupt exception") + }.should.raise(RuntimeError, "interrupt exception") executed.should == true end diff --git a/spec/ruby/core/thread/ignore_deadlock_spec.rb b/spec/ruby/core/thread/ignore_deadlock_spec.rb index 53cc2a7f5b..b48bc9f9b0 100644 --- a/spec/ruby/core/thread/ignore_deadlock_spec.rb +++ b/spec/ruby/core/thread/ignore_deadlock_spec.rb @@ -1,21 +1,19 @@ require_relative '../../spec_helper' -ruby_version_is "3.0" do - describe "Thread.ignore_deadlock" do - it "returns false by default" do - Thread.ignore_deadlock.should == false - end +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 +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 4fca900cd8..b9a94560ee 100644 --- a/spec/ruby/core/thread/initialize_spec.rb +++ b/spec/ruby/core/thread/initialize_spec.rb @@ -19,7 +19,7 @@ describe "Thread#initialize" do @t.instance_eval do initialize {} end - }.should raise_error(ThreadError) + }.should.raise(ThreadError) end end diff --git a/spec/ruby/core/thread/join_spec.rb b/spec/ruby/core/thread/join_spec.rb index 213fe2e505..f4332167f1 100644 --- a/spec/ruby/core/thread/join_spec.rb +++ b/spec/ruby/core/thread/join_spec.rb @@ -4,28 +4,28 @@ require_relative 'fixtures/classes' describe "Thread#join" do it "returns the thread when it is finished" do t = Thread.new {} - t.join.should equal(t) + t.join.should.equal?(t) end it "returns the thread when it is finished when given a timeout" do t = Thread.new {} t.join - t.join(0).should equal(t) + t.join(0).should.equal?(t) end it "coerces timeout to a Float if it is not nil" do t = Thread.new {} t.join - t.join(0).should equal(t) - t.join(0.0).should equal(t) - t.join(nil).should equal(t) + t.join(0).should.equal?(t) + t.join(0.0).should.equal?(t) + t.join(nil).should.equal?(t) 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 + -> { t.join(:foo) }.should.raise TypeError + -> { t.join("bar") }.should.raise TypeError end it "returns nil if it is not finished when given a timeout" do @@ -55,16 +55,16 @@ describe "Thread#join" do Thread.current.report_on_exception = false raise NotImplementedError.new("Just kidding") } - -> { t.join }.should raise_error(NotImplementedError) + -> { t.join }.should.raise(NotImplementedError) end it "returns the dead thread" do t = Thread.new { Thread.current.kill } - t.join.should equal(t) + t.join.should.equal?(t) end it "raises any uncaught exception encountered in ensure block" do t = ThreadSpecs.dying_thread_ensures { raise NotImplementedError.new("Just kidding") } - -> { t.join }.should raise_error(NotImplementedError) + -> { t.join }.should.raise(NotImplementedError) end end diff --git a/spec/ruby/core/thread/key_spec.rb b/spec/ruby/core/thread/key_spec.rb index 6940cf5f28..a14aeb8d31 100644 --- a/spec/ruby/core/thread/key_spec.rb +++ b/spec/ruby/core/thread/key_spec.rb @@ -16,31 +16,38 @@ 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 - -> { Thread.current.key? nil }.should raise_error(TypeError) - -> { Thread.current.key? 5 }.should raise_error(TypeError) + -> { Thread.current.key? nil }.should.raise(TypeError) + -> { Thread.current.key? 5 }.should.raise(TypeError) end it "is not shared across fibers" do fib = Fiber.new do Thread.current[:val1] = 1 Fiber.yield - Thread.current.key?(:val1).should be_true - Thread.current.key?(:val2).should be_false + Thread.current.key?(:val1).should == true + Thread.current.key?(:val2).should == false end - Thread.current.key?(:val1).should_not be_true + Thread.current.key?(:val1).should_not == true fib.resume Thread.current[:val2] = 2 fib.resume - Thread.current.key?(:val1).should be_false - Thread.current.key?(:val2).should be_true + Thread.current.key?(:val1).should == false + Thread.current.key?(:val2).should == true end it "stores a local in another thread when in a fiber" do fib = Fiber.new do t = Thread.new do sleep - Thread.current.key?(:value).should be_true + Thread.current.key?(:value).should == true end Thread.pass while t.status and t.status != "sleep" diff --git a/spec/ruby/core/thread/keys_spec.rb b/spec/ruby/core/thread/keys_spec.rb index 15efda51d6..3a2edd2456 100644 --- a/spec/ruby/core/thread/keys_spec.rb +++ b/spec/ruby/core/thread/keys_spec.rb @@ -16,22 +16,22 @@ describe "Thread#keys" do fib = Fiber.new do Thread.current[:val1] = 1 Fiber.yield - Thread.current.keys.should include(:val1) - Thread.current.keys.should_not include(:val2) + Thread.current.keys.should.include?(:val1) + Thread.current.keys.should_not.include?(:val2) end - Thread.current.keys.should_not include(:val1) + Thread.current.keys.should_not.include?(:val1) fib.resume Thread.current[:val2] = 2 fib.resume - Thread.current.keys.should include(:val2) - Thread.current.keys.should_not include(:val1) + Thread.current.keys.should.include?(:val2) + Thread.current.keys.should_not.include?(:val1) end it "stores a local in another thread when in a fiber" do fib = Fiber.new do t = Thread.new do sleep - Thread.current.keys.should include(:value) + Thread.current.keys.should.include?(:value) end Thread.pass while t.status and t.status != "sleep" diff --git a/spec/ruby/core/thread/kill_spec.rb b/spec/ruby/core/thread/kill_spec.rb index f932bf5232..f9f1f46744 100644 --- a/spec/ruby/core/thread/kill_spec.rb +++ b/spec/ruby/core/thread/kill_spec.rb @@ -9,17 +9,13 @@ 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 } Thread.pass while thread.status and thread.status != "sleep" Thread.kill(thread).should == thread thread.join - thread.status.should be_false + thread.status.should == false end end end diff --git a/spec/ruby/core/thread/list_spec.rb b/spec/ruby/core/thread/list_spec.rb index 3c6f70c13e..5036841d58 100644 --- a/spec/ruby/core/thread/list_spec.rb +++ b/spec/ruby/core/thread/list_spec.rb @@ -3,15 +3,15 @@ require_relative 'fixtures/classes' describe "Thread.list" do it "includes the current and main thread" do - Thread.list.should include(Thread.current) - Thread.list.should include(Thread.main) + Thread.list.should.include?(Thread.current) + Thread.list.should.include?(Thread.main) end it "includes threads of non-default thread groups" do t = Thread.new { sleep } begin ThreadGroup.new.add(t) - Thread.list.should include(t) + Thread.list.should.include?(t) ensure t.kill t.join @@ -21,7 +21,7 @@ describe "Thread.list" do it "does not include deceased threads" do t = Thread.new { 1; } t.join - Thread.list.should_not include(t) + Thread.list.should_not.include?(t) end it "includes waiting threads" do @@ -29,7 +29,7 @@ describe "Thread.list" do t = Thread.new { q.pop } begin Thread.pass while t.status and t.status != 'sleep' - Thread.list.should include(t) + Thread.list.should.include?(t) ensure q << nil t.join @@ -45,7 +45,7 @@ describe "Thread.list" do begin Thread.list.each { |th| - th.should be_kind_of(Thread) + th.should.is_a?(Thread) } end while spawner.alive? diff --git a/spec/ruby/core/thread/name_spec.rb b/spec/ruby/core/thread/name_spec.rb index 9b3d2f4b09..47d807be4d 100644 --- a/spec/ruby/core/thread/name_spec.rb +++ b/spec/ruby/core/thread/name_spec.rb @@ -36,7 +36,7 @@ describe "Thread#name=" do it "raises an ArgumentError if the name includes a null byte" do -> { @thread.name = "new thread\0name" - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "can be reset to nil" do 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..cc72e0b853 --- /dev/null +++ b/spec/ruby/core/thread/native_thread_id_spec.rb @@ -0,0 +1,31 @@ +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.is_a?(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 + + # native_thread_id can be nil on a M:N scheduler + t_thread_id.should.is_a?(Integer) if t_thread_id != nil + + 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 47a836201c..acb6cd4e30 100644 --- a/spec/ruby/core/thread/new_spec.rb +++ b/spec/ruby/core/thread/new_spec.rb @@ -18,7 +18,7 @@ describe "Thread.new" do end it "raises an exception when not given a block" do - -> { Thread.new }.should raise_error(ThreadError) + -> { Thread.new }.should.raise(ThreadError) end it "creates a subclass of thread calls super with a block in initialize" do @@ -36,7 +36,7 @@ describe "Thread.new" do -> { c.new - }.should raise_error(ThreadError) + }.should.raise(ThreadError) end it "calls and respects #initialize for the block to use" do diff --git a/spec/ruby/core/thread/pending_interrupt_spec.rb b/spec/ruby/core/thread/pending_interrupt_spec.rb index cd565d92a4..5fbe7422a9 100644 --- a/spec/ruby/core/thread/pending_interrupt_spec.rb +++ b/spec/ruby/core/thread/pending_interrupt_spec.rb @@ -19,7 +19,7 @@ describe "Thread.pending_interrupt?" do Thread.pending_interrupt?.should == true executed = true end - }.should raise_error(RuntimeError, "interrupt") + }.should.raise(RuntimeError, "interrupt") executed.should == true Thread.pending_interrupt?.should == false end diff --git a/spec/ruby/core/thread/priority_spec.rb b/spec/ruby/core/thread/priority_spec.rb index e13ad478b5..970f7f9971 100644 --- a/spec/ruby/core/thread/priority_spec.rb +++ b/spec/ruby/core/thread/priority_spec.rb @@ -15,19 +15,19 @@ describe "Thread#priority" do end it "inherits the priority of the current thread while running" do - @thread.alive?.should be_true + @thread.alive?.should == true @thread.priority.should == @current_priority end it "maintain the priority of the current thread after death" do ThreadSpecs.state = :exit @thread.join - @thread.alive?.should be_false + @thread.alive?.should == false @thread.priority.should == @current_priority end it "returns an integer" do - @thread.priority.should be_kind_of(Integer) + @thread.priority.should.is_a?(Integer) end end @@ -59,7 +59,7 @@ describe "Thread#priority=" do describe "when set with a non-integer" do it "raises a type error" do - ->{ @thread.priority = Object.new }.should raise_error(TypeError) + ->{ @thread.priority = Object.new }.should.raise(TypeError) end end diff --git a/spec/ruby/core/thread/raise_spec.rb b/spec/ruby/core/thread/raise_spec.rb index 49323cf270..3b02a2e005 100644 --- a/spec/ruby/core/thread/raise_spec.rb +++ b/spec/ruby/core/thread/raise_spec.rb @@ -3,6 +3,16 @@ require_relative 'fixtures/classes' require_relative '../../shared/kernel/raise' describe "Thread#raise" do + it "is a public method" do + Thread.public_instance_methods.should.include?(:raise) + end + + it_behaves_like :kernel_raise, :raise, ThreadSpecs::NewThreadToRaise + it_behaves_like :kernel_raise_across_contexts, :raise, ThreadSpecs::NewThreadToRaise + ruby_version_is "4.0" do + it_behaves_like :kernel_raise_with_cause, :raise, ThreadSpecs::NewThreadToRaise + end + it "ignores dead threads and returns nil" do t = Thread.new { :dead } Thread.pass while t.alive? @@ -26,27 +36,27 @@ describe "Thread#raise on a sleeping thread" do it "raises a RuntimeError if no exception class is given" do @thr.raise Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(RuntimeError) + ScratchPad.recorded.should.is_a?(RuntimeError) end it "raises the given exception" do @thr.raise Exception Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(Exception) + ScratchPad.recorded.should.is_a?(Exception) end it "raises the given exception with the given message" do @thr.raise Exception, "get to work" Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(Exception) + ScratchPad.recorded.should.is_a?(Exception) ScratchPad.recorded.message.should == "get to work" end it "raises the given exception and the backtrace is the one of the interrupted thread" do @thr.raise Exception Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(Exception) - ScratchPad.recorded.backtrace[0].should include("sleep") + ScratchPad.recorded.should.is_a?(Exception) + ScratchPad.recorded.backtrace[0].should.include?("sleep") end it "is captured and raised by Thread#value" do @@ -58,7 +68,7 @@ describe "Thread#raise on a sleeping thread" do ThreadSpecs.spin_until_sleeping(t) t.raise - -> { t.value }.should raise_error(RuntimeError) + -> { t.value }.should.raise(RuntimeError) end it "raises a RuntimeError when called with no arguments inside rescue" do @@ -76,7 +86,7 @@ describe "Thread#raise on a sleeping thread" do ThreadSpecs.spin_until_sleeping(t) t.raise end - -> { t.value }.should raise_error(RuntimeError) + -> { t.value }.should.raise(RuntimeError) end it "re-raises a previously rescued exception without overwriting the backtrace" do @@ -98,8 +108,8 @@ describe "Thread#raise on a sleeping thread" do 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}:") + raised_again.backtrace.first.should.include?("#{__FILE__}:#{initial_raise_line}:") + raised_again.backtrace.first.should_not.include?("#{__FILE__}:#{raise_again_line}:") end end @@ -145,19 +155,19 @@ describe "Thread#raise on a running thread" do it "raises a RuntimeError if no exception class is given" do @thr.raise Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(RuntimeError) + ScratchPad.recorded.should.is_a?(RuntimeError) end it "raises the given exception" do @thr.raise Exception Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(Exception) + ScratchPad.recorded.should.is_a?(Exception) end it "raises the given exception with the given message" do @thr.raise Exception, "get to work" Thread.pass while @thr.status - ScratchPad.recorded.should be_kind_of(Exception) + ScratchPad.recorded.should.is_a?(Exception) ScratchPad.recorded.message.should == "get to work" end @@ -171,7 +181,7 @@ describe "Thread#raise on a running thread" do q.pop # wait for `report_on_exception = false`. t.raise - -> { t.value }.should raise_error(RuntimeError) + -> { t.value }.should.raise(RuntimeError) end it "raises the given argument even when there is an active exception" do @@ -190,7 +200,7 @@ describe "Thread#raise on a running thread" do rescue Thread.pass until raised t.raise RangeError - -> { t.value }.should raise_error(RangeError) + -> { t.value }.should.raise(RangeError) end end @@ -211,7 +221,7 @@ describe "Thread#raise on a running thread" do Thread.pass until raised t.raise end - -> { t.value }.should raise_error(RuntimeError) + -> { t.value }.should.raise(RuntimeError) end end @@ -227,6 +237,6 @@ describe "Thread#raise on same thread" do Thread.current.raise end end - -> { t.value }.should raise_error(RuntimeError, '') + -> { t.value }.should.raise(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 9279fa1da5..9cf5260808 100644 --- a/spec/ruby/core/thread/report_on_exception_spec.rb +++ b/spec/ruby/core/thread/report_on_exception_spec.rb @@ -58,37 +58,35 @@ describe "Thread#report_on_exception=" do -> { t.join - }.should raise_error(RuntimeError, "Thread#report_on_exception specs") + }.should.raise(RuntimeError, "Thread#report_on_exception specs") end - ruby_version_is "3.0" do - 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 + 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 - line_call_foo = __LINE__ + 5 - go = false - t = Thread.new { - Thread.current.report_on_exception = true - Thread.pass until go - foo - } + 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("", <<ERR) -#{t.inspect} terminated with exception (report_on_exception is true): -#{__FILE__}:#{line_raise}:in `foo': Thread#report_on_exception specs backtrace order (RuntimeError) -\tfrom #{__FILE__}:#{line_call_foo}:in `block (5 levels) in <top (required)>' -ERR + -> { + 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 + -> { + t.join + }.should.raise(RuntimeError, "Thread#report_on_exception specs backtrace order") end it "prints the backtrace even if the thread was killed just after Thread#raise" do @@ -109,7 +107,7 @@ ERR -> { t.join - }.should raise_error(RuntimeError, "Thread#report_on_exception before kill spec") + }.should.raise(RuntimeError, "Thread#report_on_exception before kill spec") end end @@ -126,7 +124,7 @@ ERR -> { t.join - }.should raise_error(RuntimeError, "Thread#report_on_exception specs") + }.should.raise(RuntimeError, "Thread#report_on_exception specs") end end @@ -146,12 +144,12 @@ ERR -> { mutex.sleep(5) - }.should raise_error(RuntimeError, "Thread#report_on_exception specs") + }.should.raise(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") + }.should.raise(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 3663827579..a294c3a4d6 100644 --- a/spec/ruby/core/thread/shared/exit.rb +++ b/spec/ruby/core/thread/shared/exit.rb @@ -56,8 +56,8 @@ describe :thread_exit, shared: true do Thread.pass while @inner.status and @inner.status != "sleep" @outer.send(@method) @outer.join - ScratchPad.recorded.should include(:inner_ensure_clause) - ScratchPad.recorded.should include(:outer_ensure_clause) + ScratchPad.recorded.should.include?(:inner_ensure_clause) + ScratchPad.recorded.should.include?(:outer_ensure_clause) end it "does not set $!" do @@ -113,6 +113,25 @@ describe :thread_exit, shared: true do 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 + 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 quarantine! do it "killing dying running does nothing" do @@ -136,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) { } - -> { thread.join }.should raise_error(RuntimeError, "In dying thread") + -> { thread.join }.should.raise(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 2ba926bf00..c5d62ab098 100644 --- a/spec/ruby/core/thread/shared/start.rb +++ b/spec/ruby/core/thread/shared/start.rb @@ -6,22 +6,22 @@ describe :thread_start, shared: true do it "raises an ArgumentError if not passed a block" do -> { Thread.send(@method) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "spawns a new Thread running the block" do run = false t = Thread.send(@method) { run = true } - t.should be_kind_of(Thread) + t.should.is_a?(Thread) t.join - run.should be_true + run.should == true end it "respects Thread subclasses" do c = Class.new(Thread) t = c.send(@method) { } - t.should be_kind_of(c) + t.should.is_a?(c) t.join end diff --git a/spec/ruby/core/thread/shared/to_s.rb b/spec/ruby/core/thread/shared/to_s.rb index 43640deb33..27e53ba4b8 100644 --- a/spec/ruby/core/thread/shared/to_s.rb +++ b/spec/ruby/core/thread/shared/to_s.rb @@ -12,42 +12,42 @@ describe :thread_to_s, shared: true do end it "can check it's own status" do - ThreadSpecs.status_of_current_thread.send(@method).should include('run') + 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') + 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') + 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') + 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') + 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') + 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') + 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') + 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') + 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') + 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 6f010fea25..c89235ba60 100644 --- a/spec/ruby/core/thread/shared/wakeup.rb +++ b/spec/ruby/core/thread/shared/wakeup.rb @@ -57,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 - -> { t.send @method }.should raise_error(ThreadError) + -> { t.send @method }.should.raise(ThreadError) 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..3d92cd5479 100644 --- a/spec/ruby/core/thread/thread_variable_get_spec.rb +++ b/spec/ruby/core/thread/thread_variable_get_spec.rb @@ -10,16 +10,51 @@ describe "Thread#thread_variable_get" do end it "returns nil if the variable is not set" do - @t.thread_variable_get(:a).should be_nil + @t.thread_variable_get(:a).should == 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 - Thread.current.thread_variable_get(:thread_variable_get_spec).should be_nil + @t.thread_variable_set(:thread_variable_get_spec, 82) + Thread.current.thread_variable_get(:thread_variable_get_spec).should == 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 == 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(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(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(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..f8d25364ae 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 + Thread.current.thread_variable_get(:thread_variable_get_spec).should == 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 == false + end + + it "raises a FrozenError if the thread is frozen" do + @t.freeze + -> { @t.thread_variable_set(:a, 1) }.should.raise(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(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(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..ebafd4f3eb 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?(:b).should be_false + @t.thread_variable_set(:a, 2) + @t.thread_variable?(:b).should == false end it "returns true if the thread variables contain 'key'" do - @t.thread_variable_set :a, 2 - @t.thread_variable?(:a).should be_true + @t.thread_variable_set(:a, 2) + @t.thread_variable?(:a).should == true + end + + it "accepts String and Symbol keys interchangeably" do + @t.thread_variable?('a').should == false + @t.thread_variable?(:a).should == false + + @t.thread_variable_set(:a, 49) + + @t.thread_variable?('a').should == true + @t.thread_variable?(:a).should == 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 == true + end + + it "does not raise FrozenError if the thread is frozen" do + @t.freeze + @t.thread_variable?(:a).should == 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(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(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(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..f15c681a8f 100644 --- a/spec/ruby/core/thread/thread_variables_spec.rb +++ b/spec/ruby/core/thread/thread_variables_spec.rb @@ -10,20 +10,31 @@ 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 - Thread.current.thread_variables.should_not include(:a, :b) + 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) + Thread.current.thread_variables.should_not.include?(:b) end it "only contains user thread variables and is empty initially" 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/value_spec.rb b/spec/ruby/core/thread/value_spec.rb index 30e43abd1a..50c823171d 100644 --- a/spec/ruby/core/thread/value_spec.rb +++ b/spec/ruby/core/thread/value_spec.rb @@ -11,7 +11,7 @@ describe "Thread#value" do Thread.current.report_on_exception = false raise "Hello" } - -> { t.value }.should raise_error(RuntimeError, "Hello") + -> { t.value }.should.raise(RuntimeError, "Hello") end it "is nil for a killed thread" do |
