From 2e32b919b4f2f5b7f2e1509d6fa985526ef1f61c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 28 Jan 2021 17:08:57 +0100 Subject: [PATCH] Update to ruby/spec@8cafaa5 --- spec/ruby/core/integer/digits_spec.rb | 9 ++ .../ruby/core/kernel/caller_locations_spec.rb | 8 ++ spec/ruby/core/kernel/caller_spec.rb | 8 ++ spec/ruby/core/kernel/lambda_spec.rb | 12 ++ spec/ruby/core/kernel/rand_spec.rb | 42 ++++++ spec/ruby/core/module/ruby2_keywords_spec.rb | 112 ++++++++++++++++ spec/ruby/core/module/to_s_spec.rb | 23 ++++ spec/ruby/core/proc/ruby2_keywords_spec.rb | 64 +++++++++ spec/ruby/core/range/shared/cover.rb | 42 ++++++ .../core/range/shared/cover_and_include.rb | 7 + spec/ruby/core/range/to_a_spec.rb | 5 + spec/ruby/core/range/to_s_spec.rb | 7 + spec/ruby/core/string/scrub_spec.rb | 7 + spec/ruby/core/string/shared/slice.rb | 10 ++ spec/ruby/core/string/split_spec.rb | 8 ++ spec/ruby/core/struct/hash_spec.rb | 4 + spec/ruby/core/symbol/name_spec.rb | 19 +++ .../core/thread/backtrace_locations_spec.rb | 8 ++ .../ruby/core/thread/handle_interrupt_spec.rb | 125 ++++++++++++++++++ .../core/thread/pending_interrupt_spec.rb | 32 +++++ spec/ruby/core/time/inspect_spec.rb | 26 +++- spec/ruby/language/constants_spec.rb | 19 ++- .../language/fixtures/constant_visibility.rb | 18 ++- .../language/fixtures/squiggly_heredoc.rb | 8 ++ spec/ruby/language/heredoc_spec.rb | 5 + spec/ruby/library/pathname/inspect_spec.rb | 10 ++ .../library/socket/basicsocket/send_spec.rb | 8 ++ .../socket/tcpsocket/recv_nonblock_spec.rb | 14 ++ .../udpsocket/recvfrom_nonblock_spec.rb | 7 + spec/ruby/optional/capi/debug_spec.rb | 68 ++++++++++ spec/ruby/optional/capi/ext/debug_spec.c | 93 +++++++++++++ spec/ruby/optional/capi/ext/object_spec.c | 1 + spec/ruby/optional/capi/fixtures/module.rb | 4 + spec/ruby/optional/capi/module_spec.rb | 4 + spec/ruby/optional/capi/object_spec.rb | 5 + 35 files changed, 832 insertions(+), 10 deletions(-) create mode 100644 spec/ruby/core/module/ruby2_keywords_spec.rb create mode 100644 spec/ruby/core/proc/ruby2_keywords_spec.rb create mode 100644 spec/ruby/core/symbol/name_spec.rb create mode 100644 spec/ruby/core/thread/handle_interrupt_spec.rb create mode 100644 spec/ruby/core/thread/pending_interrupt_spec.rb create mode 100644 spec/ruby/library/pathname/inspect_spec.rb create mode 100644 spec/ruby/optional/capi/debug_spec.rb create mode 100644 spec/ruby/optional/capi/ext/debug_spec.c diff --git a/spec/ruby/core/integer/digits_spec.rb b/spec/ruby/core/integer/digits_spec.rb index 4a8e33980c..3d0a64c25f 100644 --- a/spec/ruby/core/integer/digits_spec.rb +++ b/spec/ruby/core/integer/digits_spec.rb @@ -29,4 +29,13 @@ describe "Integer#digits" do it "raises Math::DomainError when calling digits on a negative number" do -> { -12345.digits(7) }.should raise_error(Math::DomainError) end + + it "returns integer values > 9 when base is above 10" do + 1234.digits(16).should == [2, 13, 4] + end + + it "can be used with base > 37" do + 1234.digits(100).should == [34, 12] + 980099.digits(100).should == [99, 0, 98] + end end diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb index 8050b5b3ab..bbb7e7b737 100644 --- a/spec/ruby/core/kernel/caller_locations_spec.rb +++ b/spec/ruby/core/kernel/caller_locations_spec.rb @@ -36,6 +36,14 @@ describe 'Kernel#caller_locations' do end end + ruby_version_is "2.7" do + it "works with beginless ranges" do + locations1 = caller_locations(0) + locations2 = caller_locations(eval("(...5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(...5)")].map(&:to_s)[eval("(2..)")] + end + end + it "can be called with a range whose end is negative" do locations1 = caller_locations(0) locations2 = caller_locations(2..-1) diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb index 06e9ea6fc8..6c175868cb 100644 --- a/spec/ruby/core/kernel/caller_spec.rb +++ b/spec/ruby/core/kernel/caller_spec.rb @@ -52,6 +52,14 @@ describe 'Kernel#caller' do end end + ruby_version_is "2.7" do + it "works with beginless ranges" do + locations1 = KernelSpecs::CallerTest.locations(0) + locations2 = KernelSpecs::CallerTest.locations(eval("(..5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(..5)")].map(&:to_s)[eval("(2..)")] + end + end + guard -> { Kernel.instance_method(:tap).source_location } do it "includes core library methods defined in Ruby" do file, line = Kernel.instance_method(:tap).source_location diff --git a/spec/ruby/core/kernel/lambda_spec.rb b/spec/ruby/core/kernel/lambda_spec.rb index 4dd34c6ca9..9a960f3589 100644 --- a/spec/ruby/core/kernel/lambda_spec.rb +++ b/spec/ruby/core/kernel/lambda_spec.rb @@ -123,4 +123,16 @@ describe "Kernel.lambda" do it "allows long returns to flow through it" do KernelSpecs::Lambda.new.outer.should == :good end + + it "treats the block as a Proc when lambda is re-defined" do + klass = Class.new do + def lambda (&block); block; end + def ret + lambda { return 1 }.call + 2 + end + end + klass.new.lambda { 42 }.should be_an_instance_of Proc + klass.new.ret.should == 1 + end end diff --git a/spec/ruby/core/kernel/rand_spec.rb b/spec/ruby/core/kernel/rand_spec.rb index a82b4fba74..481189d969 100644 --- a/spec/ruby/core/kernel/rand_spec.rb +++ b/spec/ruby/core/kernel/rand_spec.rb @@ -117,6 +117,48 @@ describe "Kernel.rand" do end end + context "given an inclusive range between 0 and 1" do + it "returns an Integer between the two Integers" do + x = rand(0..1) + x.should be_kind_of(Integer) + (0..1).should include(x) + end + + it "returns a Float if at least one side is Float" do + seed = 42 + x1 = Random.new(seed).rand(0..1.0) + x2 = Random.new(seed).rand(0.0..1.0) + x3 = Random.new(seed).rand(0.0..1) + + x3.should be_kind_of(Float) + x1.should equal(x3) + x2.should equal(x3) + + (0.0..1.0).should include(x3) + end + end + + context "given an exclusive range between 0 and 1" do + it "returns zero as an Integer" do + x = rand(0...1) + x.should be_kind_of(Integer) + x.should eql(0) + end + + it "returns a Float if at least one side is Float" do + seed = 42 + x1 = Random.new(seed).rand(0...1.0) + x2 = Random.new(seed).rand(0.0...1.0) + x3 = Random.new(seed).rand(0.0...1) + + x3.should be_kind_of(Float) + x1.should equal(x3) + x2.should equal(x3) + + (0.0...1.0).should include(x3) + end + end + it "returns a numeric for an range argument where max is < 1" do rand(0.25..0.75).should be_kind_of(Numeric) end diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..34c45cb1bc --- /dev/null +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -0,0 +1,112 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "2.7" do + describe "Module#ruby2_keywords" do + it "marks the final hash argument as keyword hash" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + ruby2_keywords :foo + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + ruby_version_is "2.7" ... "3.0" do + it "fixes delegation warnings when calling a method accepting keywords" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) bar(*a) end + def bar(*a, **b) end + end + + -> { obj.foo(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/) + + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + + -> { obj.foo(1, 2, {a: "a"}) }.should_not complain + end + end + + it "returns nil" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) end + + ruby2_keywords(:foo).should == nil + end + end + + it "raises NameError when passed not existing method name" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :not_existing + end + }.should raise_error(NameError, /undefined method `not_existing'/) + end + + it "acceps String as well" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + ruby2_keywords "foo" + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "raises TypeError when passed not Symbol or String" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords Object.new + end + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "prints warning when a method does not accept argument splat" do + obj = Object.new + def obj.foo(a, b, c) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keywords" do + obj = Object.new + def obj.foo(a:, b:) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keyword splat" do + obj = Object.new + def obj.foo(**a) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end +end diff --git a/spec/ruby/core/module/to_s_spec.rb b/spec/ruby/core/module/to_s_spec.rb index 29f6ecf726..6b1a615ef9 100644 --- a/spec/ruby/core/module/to_s_spec.rb +++ b/spec/ruby/core/module/to_s_spec.rb @@ -42,4 +42,27 @@ describe "Module#to_s" do obj = ModuleSpecs::NamedClass.new obj.singleton_class.to_s.should =~ /\A#>\z/ end + + it "always show the refinement name, even if the module is named" do + module ModuleSpecs::RefinementInspect + R = refine String do + end + end + + ModuleSpecs::RefinementInspect::R.name.should == 'ModuleSpecs::RefinementInspect::R' + ModuleSpecs::RefinementInspect::R.to_s.should == '#' + end + + it 'does not call #inspect or #to_s for singleton classes' do + klass = Class.new + obj = klass.new + def obj.to_s + "to_s" + end + def obj.inspect + "inspect" + end + sclass = obj.singleton_class + sclass.to_s.should =~ /\A#>\z/ + end end diff --git a/spec/ruby/core/proc/ruby2_keywords_spec.rb b/spec/ruby/core/proc/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..4f6bc151b6 --- /dev/null +++ b/spec/ruby/core/proc/ruby2_keywords_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' + +ruby_version_is "2.7" do + describe "Proc#ruby2_keywords" do + it "marks the final hash argument as keyword hash" do + f = -> *a { a.last } + f.ruby2_keywords + + last = f.call(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + ruby_version_is "2.7" ... "3.0" do + it "fixes delegation warnings when calling a method accepting keywords" do + obj = Object.new + def obj.foo(*a, **b) end + + f = -> *a { obj.foo(*a) } + + -> { f.call(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/) + f.ruby2_keywords + -> { f.call(1, 2, {a: "a"}) }.should_not complain + end + + it "fixes delegation warnings when calling a proc accepting keywords" do + g = -> *a, **b { } + f = -> *a { g.call(*a) } + + -> { f.call(1, 2, {a: "a"}) }.should complain(/Using the last argument as keyword parameters is deprecated/) + f.ruby2_keywords + -> { f.call(1, 2, {a: "a"}) }.should_not complain + end + end + + it "returns self" do + f = -> *a { } + f.ruby2_keywords.should equal f + end + + it "prints warning when a proc does not accept argument splat" do + f = -> a, b, c { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a proc accepts keywords" do + f = -> a:, b: { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a proc accepts keyword splat" do + f = -> **a { } + + -> { + f.ruby2_keywords + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end +end diff --git a/spec/ruby/core/range/shared/cover.rb b/spec/ruby/core/range/shared/cover.rb index 5b09cea4e0..e04682ba71 100644 --- a/spec/ruby/core/range/shared/cover.rb +++ b/spec/ruby/core/range/shared/cover.rb @@ -152,4 +152,46 @@ describe :range_cover_subrange, shared: true do end end end + + ruby_version_is "2.7" do + it "allows self to be a beginless range" do + eval("(...10)").send(@method, (3..7)).should be_true + eval("(...10)").send(@method, (3..15)).should be_false + + eval("(..7.9)").send(@method, (2.5..6.5)).should be_true + eval("(..7.9)").send(@method, (2.5..8.5)).should be_false + + eval("(..'i')").send(@method, ('d'..'f')).should be_true + eval("(..'i')").send(@method, ('d'..'z')).should be_false + end + + it "allows self to be a endless range" do + eval("(0...)").send(@method, (3..7)).should be_true + eval("(5...)").send(@method, (3..15)).should be_false + + eval("(1.1..)").send(@method, (2.5..6.5)).should be_true + eval("(3.3..)").send(@method, (2.5..8.5)).should be_false + + eval("('a'..)").send(@method, ('d'..'f')).should be_true + eval("('p'..)").send(@method, ('d'..'z')).should be_false + end + + it "accepts beginless range argument" do + eval("(..10)").send(@method, eval("(...10)")).should be_true + (0..10).send(@method, eval("(...10)")).should be_false + + (1.1..7.9).send(@method, eval("(...10.5)")).should be_false + + ('c'..'i').send(@method, eval("(..'i')")).should be_false + end + + it "accepts endless range argument" do + eval("(0..)").send(@method, eval("(0...)")).should be_true + (0..10).send(@method, eval("(0...)")).should be_false + + (1.1..7.9).send(@method, eval("(0.8...)")).should be_false + + ('c'..'i').send(@method, eval("('a'..)")).should be_false + end + end end diff --git a/spec/ruby/core/range/shared/cover_and_include.rb b/spec/ruby/core/range/shared/cover_and_include.rb index b308524310..0267e3ccb0 100644 --- a/spec/ruby/core/range/shared/cover_and_include.rb +++ b/spec/ruby/core/range/shared/cover_and_include.rb @@ -26,6 +26,13 @@ describe :range_cover_and_include, shared: true do end end + ruby_version_is "2.7" do + it "returns true if other is an element of self for beginless ranges" do + eval("(..10)").send(@method, 2.4).should == true + eval("(...10.5)").send(@method, 2.4).should == true + end + end + it "compares values using <=>" do rng = (1..5) m = mock("int") diff --git a/spec/ruby/core/range/to_a_spec.rb b/spec/ruby/core/range/to_a_spec.rb index 08f50e4d9e..9a1352b7b0 100644 --- a/spec/ruby/core/range/to_a_spec.rb +++ b/spec/ruby/core/range/to_a_spec.rb @@ -16,6 +16,11 @@ describe "Range#to_a" do (0xffff...0xfffd).to_a.should == [] end + it "works with Ranges of 64-bit integers" do + large = 1 << 40 + (large..large+1).to_a.should == [1099511627776, 1099511627777] + end + it "works with Ranges of Symbols" do (:A..:z).to_a.size.should == 58 end diff --git a/spec/ruby/core/range/to_s_spec.rb b/spec/ruby/core/range/to_s_spec.rb index ccbc5d8e7e..59672da822 100644 --- a/spec/ruby/core/range/to_s_spec.rb +++ b/spec/ruby/core/range/to_s_spec.rb @@ -18,6 +18,13 @@ describe "Range#to_s" do end end + ruby_version_is "2.7" do + it "can show beginless ranges" do + eval("(..1)").to_s.should == "..1" + eval("(...1.0)").to_s.should == "...1.0" + end + end + ruby_version_is ''...'2.7' do it "returns a tainted string if either end is tainted" do (("a".taint)..."c").to_s.tainted?.should be_true diff --git a/spec/ruby/core/string/scrub_spec.rb b/spec/ruby/core/string/scrub_spec.rb index 390035ef30..86fd4e85ba 100644 --- a/spec/ruby/core/string/scrub_spec.rb +++ b/spec/ruby/core/string/scrub_spec.rb @@ -39,6 +39,13 @@ describe "String#scrub with a custom replacement" do "abc\u3042#{x81}".scrub("*").should == "abc\u3042*" end + it "replaces invalid byte sequences in frozen strings" do + x81 = [0x81].pack('C').force_encoding('utf-8') + (-"abc\u3042#{x81}").scrub("*").should == "abc\u3042*" + utf16_str = ("abc".encode('UTF-16LE').bytes + [0x81]).pack('c*').force_encoding('UTF-16LE') + (-(utf16_str)).scrub("*".encode('UTF-16LE')).should == "abc*".encode('UTF-16LE') + end + it "replaces an incomplete character at the end with a single replacement" do xE3x80 = [0xE3, 0x80].pack('CC').force_encoding 'utf-8' xE3x80.scrub("*").should == "*" diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 69997b7c1d..a674e0b6ef 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -335,6 +335,16 @@ describe :string_slice_range, shared: true do "hello there".send(@method, eval("(-4...)")).should == "here" end end + + ruby_version_is "2.7" do + it "works with beginless ranges" do + "hello there".send(@method, eval("(..5)")).should == "hello " + "hello there".send(@method, eval("(...5)")).should == "hello" + "hello there".send(@method, eval("(..-4)")).should == "hello th" + "hello there".send(@method, eval("(...-4)")).should == "hello t" + "hello there".send(@method, eval("(...nil)")).should == "hello there" + end + end end describe :string_slice_regexp, shared: true do diff --git a/spec/ruby/core/string/split_spec.rb b/spec/ruby/core/string/split_spec.rb index c5441e3a49..2ebfe1e353 100644 --- a/spec/ruby/core/string/split_spec.rb +++ b/spec/ruby/core/string/split_spec.rb @@ -471,6 +471,14 @@ describe "String#split with Regexp" do a.should == ["Chunky", "Bacon"] end + it "yields each split substring with default pattern for a non-ASCII string" do + a = [] + returned_object = "l'été arrive bientôt".split { |str| a << str } + + returned_object.should == "l'été arrive bientôt" + a.should == ["l'été", "arrive", "bientôt"] + end + it "yields the string when limit is 1" do a = [] returned_object = "chunky bacon".split("", 1) { |str| a << str.capitalize } diff --git a/spec/ruby/core/struct/hash_spec.rb b/spec/ruby/core/struct/hash_spec.rb index d3c95fbe56..53361eb7a9 100644 --- a/spec/ruby/core/struct/hash_spec.rb +++ b/spec/ruby/core/struct/hash_spec.rb @@ -56,5 +56,9 @@ describe "Struct#hash" do # See the Struct#eql? specs end + it "returns different hashes for different struct classes" do + Struct.new(:x).new(1).hash.should != Struct.new(:y).new(1).hash + end + it_behaves_like :struct_accessor, :hash end diff --git a/spec/ruby/core/symbol/name_spec.rb b/spec/ruby/core/symbol/name_spec.rb new file mode 100644 index 0000000000..15b9aa75e9 --- /dev/null +++ b/spec/ruby/core/symbol/name_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +ruby_version_is "3.0" do + describe "Symbol#name" do + it "returns string" do + :ruby.name.should == "ruby" + :ルビー.name.should == "ルビー" + end + + it "returns same string instance" do + :"ruby_3".name.should.equal?(:ruby_3.name) + :"ruby_#{1+2}".name.should.equal?(:ruby_3.name) + end + + it "returns frozen string" do + :symbol.name.should.frozen? + end + end +end diff --git a/spec/ruby/core/thread/backtrace_locations_spec.rb b/spec/ruby/core/thread/backtrace_locations_spec.rb index ead4be2d8c..1f77e13378 100644 --- a/spec/ruby/core/thread/backtrace_locations_spec.rb +++ b/spec/ruby/core/thread/backtrace_locations_spec.rb @@ -51,6 +51,14 @@ describe "Thread#backtrace_locations" do end end + ruby_version_is "2.7" do + it "can be called with an beginless range" do + locations1 = Thread.current.backtrace_locations(0) + locations2 = Thread.current.backtrace_locations(eval("(..5)")) + locations2.map(&:to_s)[eval("(2..)")].should == locations1[eval("(..5)")].map(&:to_s)[eval("(2..)")] + end + 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 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/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/time/inspect_spec.rb b/spec/ruby/core/time/inspect_spec.rb index 85133838e2..6f1b2e3ef1 100644 --- a/spec/ruby/core/time/inspect_spec.rb +++ b/spec/ruby/core/time/inspect_spec.rb @@ -5,17 +5,31 @@ describe "Time#inspect" do it_behaves_like :inspect, :inspect ruby_version_is "2.7" do - it "preserves milliseconds" do + it "preserves microseconds" do t = Time.utc(2007, 11, 1, 15, 25, 0, 123456) t.inspect.should == "2007-11-01 15:25:00.123456 UTC" end - it "formats nanoseconds as a Rational" do - t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789) - t.nsec.should == 123456789 - t.strftime("%N").should == "123456789" + it "omits trailing zeros from microseconds" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 100000) + t.inspect.should == "2007-11-01 15:25:00.1 UTC" + end - t.inspect.should == "2007-11-01 15:25:00 8483885939586761/68719476736000000 UTC" + it "uses the correct time zone without microseconds" do + t = Time.utc(2000, 1, 1) + t = t.localtime(9*3600) + t.inspect.should == "2000-01-01 09:00:00 +0900" + end + + it "uses the correct time zone with microseconds" do + t = Time.utc(2000, 1, 1, 0, 0, 0, 123456) + t = t.localtime(9*3600) + t.inspect.should == "2000-01-01 09:00:00.123456 +0900" + end + + it "preserves nanoseconds" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r) + t.inspect.should == "2007-11-01 15:25:00.123456789 UTC" end end end diff --git a/spec/ruby/language/constants_spec.rb b/spec/ruby/language/constants_spec.rb index 4d46cf2f84..6d3b1a5dd7 100644 --- a/spec/ruby/language/constants_spec.rb +++ b/spec/ruby/language/constants_spec.rb @@ -551,11 +551,24 @@ describe "Module#private_constant marked constants" do end it "can be accessed from classes that include the module" do - ConstantVisibility::PrivConstModuleChild.new.private_constant_from_include.should be_true + ConstantVisibility::ClassIncludingPrivConstModule.new.private_constant_from_include.should be_true + end + + it "can be accessed from modules that include the module" do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_from_include.should be_true + end + + it "raises a NameError when accessed directly from modules that include the module" do + -> do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_self_from_include + end.should raise_error(NameError) + -> do + ConstantVisibility::ModuleIncludingPrivConstModule.private_constant_named_from_include + end.should raise_error(NameError) end it "is defined? from classes that include the module" do - ConstantVisibility::PrivConstModuleChild.new.defined_from_include.should == "constant" + ConstantVisibility::ClassIncludingPrivConstModule.new.defined_from_include.should == "constant" end end @@ -673,7 +686,7 @@ describe "Module#private_constant marked constants" do } -> do - ConstantVisibility::PrivConstModuleChild::PRIVATE_CONSTANT_MODULE + ConstantVisibility::ClassIncludingPrivConstModule::PRIVATE_CONSTANT_MODULE end.should raise_error(NameError) {|e| e.receiver.should == ConstantVisibility::PrivConstModule e.name.should == :PRIVATE_CONSTANT_MODULE diff --git a/spec/ruby/language/fixtures/constant_visibility.rb b/spec/ruby/language/fixtures/constant_visibility.rb index 022554430e..af38b2d8f2 100644 --- a/spec/ruby/language/fixtures/constant_visibility.rb +++ b/spec/ruby/language/fixtures/constant_visibility.rb @@ -65,7 +65,7 @@ module ConstantVisibility end end - class PrivConstModuleChild + class ClassIncludingPrivConstModule include PrivConstModule def private_constant_from_include @@ -77,6 +77,22 @@ module ConstantVisibility end end + module ModuleIncludingPrivConstModule + include PrivConstModule + + def self.private_constant_from_include + PRIVATE_CONSTANT_MODULE + end + + def self.private_constant_self_from_include + self::PRIVATE_CONSTANT_MODULE + end + + def self.private_constant_named_from_include + PrivConstModule::PRIVATE_CONSTANT_MODULE + end + end + class PrivConstClassChild < PrivConstClass def private_constant_from_subclass PRIVATE_CONSTANT_CLASS diff --git a/spec/ruby/language/fixtures/squiggly_heredoc.rb b/spec/ruby/language/fixtures/squiggly_heredoc.rb index d223966b93..984a629e5b 100644 --- a/spec/ruby/language/fixtures/squiggly_heredoc.rb +++ b/spec/ruby/language/fixtures/squiggly_heredoc.rb @@ -29,6 +29,14 @@ module SquigglyHeredocSpecs HERE end + def self.backslash + <<~HERE + a + b\ + c + HERE + end + def self.least_indented_on_the_first_line <<~HERE a diff --git a/spec/ruby/language/heredoc_spec.rb b/spec/ruby/language/heredoc_spec.rb index 95df8457e4..61a27ad8e0 100644 --- a/spec/ruby/language/heredoc_spec.rb +++ b/spec/ruby/language/heredoc_spec.rb @@ -100,6 +100,11 @@ HERE SquigglyHeredocSpecs.singlequoted.should == "singlequoted \#{\"interpolated\"}\n" end + it "allows HEREDOC with <<~'identifier', no interpolation, with backslash" do + require_relative 'fixtures/squiggly_heredoc' + SquigglyHeredocSpecs.backslash.should == "a\nbc\n" + end + it "selects the least-indented line and removes its indentation from all the lines" do require_relative 'fixtures/squiggly_heredoc' SquigglyHeredocSpecs.least_indented_on_the_first_line.should == "a\n b\n c\n" diff --git a/spec/ruby/library/pathname/inspect_spec.rb b/spec/ruby/library/pathname/inspect_spec.rb new file mode 100644 index 0000000000..304746fbe5 --- /dev/null +++ b/spec/ruby/library/pathname/inspect_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'pathname' + +describe "Pathname#inspect" do + it "returns a consistent String" do + result = Pathname.new('/tmp').inspect + result.should be_an_instance_of(String) + result.should == "#" + end +end diff --git a/spec/ruby/library/socket/basicsocket/send_spec.rb b/spec/ruby/library/socket/basicsocket/send_spec.rb index 041ee46998..868801df30 100644 --- a/spec/ruby/library/socket/basicsocket/send_spec.rb +++ b/spec/ruby/library/socket/basicsocket/send_spec.rb @@ -99,6 +99,14 @@ describe 'BasicSocket#send' do @server.close end + describe 'with an object implementing #to_str' do + it 'returns the amount of sent bytes' do + data = mock('message') + data.should_receive(:to_str).and_return('hello') + @client.send(data, 0, @server.getsockname).should == 5 + end + end + describe 'without a destination address' do it "raises #{SocketSpecs.dest_addr_req_error}" do -> { @client.send('hello', 0) }.should raise_error(SocketSpecs.dest_addr_req_error) diff --git a/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb index bfd815c658..6ce5a41b58 100644 --- a/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb +++ b/spec/ruby/library/socket/tcpsocket/recv_nonblock_spec.rb @@ -27,6 +27,20 @@ describe "TCPSocket#recv_nonblock" do @socket.recv_nonblock(50).should == "TCPSocket#recv_nonblock" end + it 'writes the read to a buffer from the socket' do + @socket = TCPSocket.new @hostname, @server.port + @socket.write "TCPSocket#recv_nonblock" + + # Wait for the server to echo. This spec is testing the return + # value, not the non-blocking behavior. + # + # TODO: Figure out a good way to test non-blocking. + IO.select([@socket]) + buffer = "".b + @socket.recv_nonblock(50, 0, buffer) + buffer.should == 'TCPSocket#recv_nonblock' + end + it 'returns :wait_readable in exceptionless mode' do @socket = TCPSocket.new @hostname, @server.port @socket.recv_nonblock(50, exception: false).should == :wait_readable diff --git a/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb index c66d1df84d..650a061221 100644 --- a/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb +++ b/spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb @@ -51,6 +51,13 @@ describe 'UDPSocket#recvfrom_nonblock' do @server.recvfrom_nonblock(1).should be_an_instance_of(Array) end + it 'writes the data to the buffer when one is present' do + buffer = "".b + IO.select([@server]) + @server.recvfrom_nonblock(1, 0, buffer) + buffer.should == 'h' + end + describe 'the returned Array' do before do IO.select([@server]) diff --git a/spec/ruby/optional/capi/debug_spec.rb b/spec/ruby/optional/capi/debug_spec.rb new file mode 100644 index 0000000000..89e72f0049 --- /dev/null +++ b/spec/ruby/optional/capi/debug_spec.rb @@ -0,0 +1,68 @@ +require_relative 'spec_helper' + +load_extension('debug') + +describe "C-API Debug function" do + before :each do + @o = CApiDebugSpecs.new + end + + describe "rb_debug_inspector_open" do + it "creates a debug context and calls the given callback" do + @o.rb_debug_inspector_open(42).should be_kind_of(Array) + @o.debug_spec_callback_data.should == 42 + end + end + + describe "rb_debug_inspector_frame_self_get" do + it "returns self" do + @o.rb_debug_inspector_frame_self_get(0).should == @o + end + end + + describe "rb_debug_inspector_frame_class_get" do + it "returns the frame class" do + @o.rb_debug_inspector_frame_class_get(0).should == CApiDebugSpecs + end + end + + describe "rb_debug_inspector_frame_binding_get" do + it "returns the current binding" do + a = "test" + b = @o.rb_debug_inspector_frame_binding_get(1) + b.should be_an_instance_of(Binding) + b.local_variable_get(:a).should == "test" + end + + ruby_version_is "2.6" do + it "matches the locations in rb_debug_inspector_backtrace_locations" do + frames = @o.rb_debug_inspector_open(42); + frames.each do |_s, _klass, binding, _iseq, backtrace_location| + if binding + "#{backtrace_location.path}:#{backtrace_location.lineno}".should == "#{binding.source_location[0]}:#{binding.source_location[1]}" + end + end + end + end + end + + describe "rb_debug_inspector_frame_iseq_get" do + it "returns an InstructionSequence" do + if defined?(RubyVM::InstructionSequence) + @o.rb_debug_inspector_frame_iseq_get(1).should be_an_instance_of(RubyVM::InstructionSequence) + else + @o.rb_debug_inspector_frame_iseq_get(1).should == nil + end + end + end + + describe "rb_debug_inspector_backtrace_locations" do + it "returns an array of Thread::Backtrace::Location" do + bts = @o.rb_debug_inspector_backtrace_locations + bts.should_not.empty? + bts.each { |bt| bt.should be_kind_of(Thread::Backtrace::Location) } + location = "#{__FILE__}:#{__LINE__ - 3}" + bts[1].to_s.should include(location) + end + end +end diff --git a/spec/ruby/optional/capi/ext/debug_spec.c b/spec/ruby/optional/capi/ext/debug_spec.c new file mode 100644 index 0000000000..344dfc33fa --- /dev/null +++ b/spec/ruby/optional/capi/ext/debug_spec.c @@ -0,0 +1,93 @@ +#include "ruby.h" +#include "rubyspec.h" +#include "ruby/debug.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static VALUE callback_data = Qfalse; + +static VALUE rb_debug_inspector_open_callback(const rb_debug_inspector_t *dc, void *ptr) { + if (!dc) { + rb_raise(rb_eRuntimeError, "rb_debug_inspector_t should not be NULL"); + } + + VALUE locations = rb_debug_inspector_backtrace_locations(dc); + int len = RARRAY_LENINT(locations); + VALUE results = rb_ary_new2(len); + for (int i = 0; i < len; i++) { + VALUE ary = rb_ary_new2(5); // [self, klass, binding, iseq, backtrace_location] + rb_ary_store(ary, 0, rb_debug_inspector_frame_self_get(dc, i)); + rb_ary_store(ary, 1, rb_debug_inspector_frame_class_get(dc, i)); + rb_ary_store(ary, 2, rb_debug_inspector_frame_binding_get(dc, i)); + rb_ary_store(ary, 3, rb_debug_inspector_frame_iseq_get(dc, i)); + rb_ary_store(ary, 4, rb_ary_entry(locations, i)); + rb_ary_push(results, ary); + } + callback_data = (VALUE)ptr; + return results; +} + +static VALUE rb_debug_inspector_frame_self_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_self_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_class_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_class_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_binding_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_binding_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE rb_debug_inspector_frame_iseq_get_callback(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_frame_iseq_get(dc, NUM2LONG((VALUE) ptr)); +} + +static VALUE debug_spec_callback_data(VALUE self){ + return callback_data; +} + +VALUE debug_spec_rb_debug_inspector_open(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_open_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_self_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_self_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_class_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_class_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_binding_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_binding_get_callback, (void *)index); +} + +VALUE debug_spec_rb_debug_inspector_frame_iseq_get(VALUE self, VALUE index) { + return rb_debug_inspector_open(rb_debug_inspector_frame_iseq_get_callback, (void *)index); +} + +static VALUE rb_debug_inspector_backtrace_locations_func(const rb_debug_inspector_t *dc, void *ptr) { + return rb_debug_inspector_backtrace_locations(dc); +} + +VALUE debug_spec_rb_debug_inspector_backtrace_locations(VALUE self) { + return rb_debug_inspector_open(rb_debug_inspector_backtrace_locations_func, (void *)self); +} + +void Init_debug_spec(void) { + VALUE cls = rb_define_class("CApiDebugSpecs", rb_cObject); + rb_define_method(cls, "rb_debug_inspector_open", debug_spec_rb_debug_inspector_open, 1); + rb_define_method(cls, "rb_debug_inspector_frame_self_get", debug_spec_rb_debug_inspector_frame_self_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_class_get", debug_spec_rb_debug_inspector_frame_class_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_binding_get", debug_spec_rb_debug_inspector_frame_binding_get, 1); + rb_define_method(cls, "rb_debug_inspector_frame_iseq_get", debug_spec_rb_debug_inspector_frame_iseq_get, 1); + rb_define_method(cls, "rb_debug_inspector_backtrace_locations", debug_spec_rb_debug_inspector_backtrace_locations, 0); + rb_define_method(cls, "debug_spec_callback_data", debug_spec_callback_data, 0); +} + +#ifdef __cplusplus +} +#endif diff --git a/spec/ruby/optional/capi/ext/object_spec.c b/spec/ruby/optional/capi/ext/object_spec.c index 59237e8549..a229301f40 100644 --- a/spec/ruby/optional/capi/ext/object_spec.c +++ b/spec/ruby/optional/capi/ext/object_spec.c @@ -462,6 +462,7 @@ void Init_object_spec(void) { rb_define_method(cls, "rb_undef_alloc_func", undef_alloc_func, 1); rb_define_method(cls, "speced_allocator?", speced_allocator_p, 1); rb_define_method(cls, "custom_alloc_func?", custom_alloc_func_p, 1); + rb_define_method(cls, "not_implemented_method", rb_f_notimplement, 1); } #ifdef __cplusplus diff --git a/spec/ruby/optional/capi/fixtures/module.rb b/spec/ruby/optional/capi/fixtures/module.rb index ba90eb7181..aac8bfbfb3 100644 --- a/spec/ruby/optional/capi/fixtures/module.rb +++ b/spec/ruby/optional/capi/fixtures/module.rb @@ -13,6 +13,10 @@ class CApiModuleSpecs autoload :D, File.expand_path('../const_get.rb', __FILE__) X = 1 + Q = 1 + R = 2 + S = 3 + T = 5 end class B < A diff --git a/spec/ruby/optional/capi/module_spec.rb b/spec/ruby/optional/capi/module_spec.rb index fde86d2223..9a0c707263 100644 --- a/spec/ruby/optional/capi/module_spec.rb +++ b/spec/ruby/optional/capi/module_spec.rb @@ -134,6 +134,10 @@ describe "CApiModule" do @m.rb_const_get(CApiModuleSpecs::A, :X).should == 1 end + it "returns a constant defined in the module for multiple constants" do + [:Q, :R, :S, :T].each { |x| @m.rb_const_get(CApiModuleSpecs::A, x).should == CApiModuleSpecs::A.const_get(x) } + end + it "returns a constant defined at toplevel" do @m.rb_const_get(CApiModuleSpecs::A, :Integer).should == Integer end diff --git a/spec/ruby/optional/capi/object_spec.rb b/spec/ruby/optional/capi/object_spec.rb index be149898bd..83d8f023b3 100644 --- a/spec/ruby/optional/capi/object_spec.rb +++ b/spec/ruby/optional/capi/object_spec.rb @@ -115,6 +115,11 @@ describe "CApiObject" do @o.rb_respond_to(true, :object_id).should == true @o.rb_respond_to(14, :succ).should == true end + + it "returns 0 if the method has been defined as rb_f_notimplement" do + @o.respond_to?(:not_implemented_method).should == false + @o.rb_respond_to(@o, :not_implemented_method).should == false + end end describe "rb_obj_respond_to" do