diff options
author | Benoit Daloze <[email protected]> | 2023-04-25 17:04:25 +0200 |
---|---|---|
committer | Benoit Daloze <[email protected]> | 2023-04-25 17:09:53 +0200 |
commit | d562663e4098801c1d7fa7c64a335ea71231a598 (patch) | |
tree | 5b2ab2c9fbb86b4223263485fc5a3224562ae78d /spec/ruby/core | |
parent | d3da01cd110ca99dd0249ee9af92e12cf845998c (diff) |
Update to ruby/spec@7f69c86
Diffstat (limited to 'spec/ruby/core')
77 files changed, 1557 insertions, 179 deletions
diff --git a/spec/ruby/core/array/fixtures/classes.rb b/spec/ruby/core/array/fixtures/classes.rb index aa5fecd96b..8596245fb8 100644 --- a/spec/ruby/core/array/fixtures/classes.rb +++ b/spec/ruby/core/array/fixtures/classes.rb @@ -160,6 +160,16 @@ module ArraySpecs end end + class ArrayMethodMissing + def initialize(*values, &block) + @values = values; + end + + def method_missing(name, *args) + @values + end + end + class SortSame def <=>(other); 0; end def ==(other); true; end diff --git a/spec/ruby/core/array/pack/c_spec.rb b/spec/ruby/core/array/pack/c_spec.rb index be03551629..ac133ff9b6 100644 --- a/spec/ruby/core/array/pack/c_spec.rb +++ b/spec/ruby/core/array/pack/c_spec.rb @@ -47,7 +47,9 @@ describe :array_pack_8bit, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [1, 2, 3].pack(pack_format("\000", 2)).should == "\x01\x02" + suppress_warning do + [1, 2, 3].pack(pack_format("\000", 2)).should == "\x01\x02" + end end end diff --git a/spec/ruby/core/array/pack/shared/float.rb b/spec/ruby/core/array/pack/shared/float.rb index 9510cffed7..1780d7635e 100644 --- a/spec/ruby/core/array/pack/shared/float.rb +++ b/spec/ruby/core/array/pack/shared/float.rb @@ -27,7 +27,9 @@ describe :array_pack_float_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "\x9a\x99\xa9@33\x13A" + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "\x9a\x99\xa9@33\x13A" + end end end @@ -105,7 +107,9 @@ describe :array_pack_float_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\xa9\x99\x9aA\x1333" + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\xa9\x99\x9aA\x1333" + end end end @@ -175,7 +179,9 @@ describe :array_pack_double_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "333333\x15@ffffff\x22@" + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "333333\x15@ffffff\x22@" + end end end @@ -244,7 +250,9 @@ describe :array_pack_double_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\x15333333@\x22ffffff" + suppress_warning do + [5.3, 9.2].pack(pack_format("\000", 2)).should == "@\x15333333@\x22ffffff" + end end end diff --git a/spec/ruby/core/array/pack/shared/integer.rb b/spec/ruby/core/array/pack/shared/integer.rb index d3ce9b5792..fd21b25b19 100644 --- a/spec/ruby/core/array/pack/shared/integer.rb +++ b/spec/ruby/core/array/pack/shared/integer.rb @@ -43,8 +43,10 @@ describe :array_pack_16bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x78\x65\xcd\xab" + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x78\x65\xcd\xab" + end end end @@ -105,8 +107,10 @@ describe :array_pack_16bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x65\x78\xab\xcd" + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x65\x78\xab\xcd" + end end end @@ -167,8 +171,10 @@ describe :array_pack_32bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde" + end end end @@ -229,8 +235,10 @@ describe :array_pack_32bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) - str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + suppress_warning do + str = [0x1243_6578, 0xdef0_abcd].pack(pack_format("\000", 2)) + str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd" + end end end @@ -351,8 +359,10 @@ describe :array_pack_64bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" + suppress_warning do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + str.should == "\x56\x78\x12\x34\xcd\xab\xf0\xde\xf0\xde\xba\xdc\x21\x43\x65\x78" + end end end @@ -421,8 +431,10 @@ describe :array_pack_64bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) - str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" + suppress_warning do + str = [0xdef0_abcd_3412_7856, 0x7865_4321_dcba_def0].pack(pack_format("\000", 2)) + str.should == "\xde\xf0\xab\xcd\x34\x12\x78\x56\x78\x65\x43\x21\xdc\xba\xde\xf0" + end end end diff --git a/spec/ruby/core/array/pack/shared/unicode.rb b/spec/ruby/core/array/pack/shared/unicode.rb index 130c447bb7..4d8eaef323 100644 --- a/spec/ruby/core/array/pack/shared/unicode.rb +++ b/spec/ruby/core/array/pack/shared/unicode.rb @@ -69,7 +69,9 @@ describe :array_pack_unicode, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [1, 2, 3].pack("U\x00U").should == "\x01\x02" + suppress_warning do + [1, 2, 3].pack("U\x00U").should == "\x01\x02" + end end end diff --git a/spec/ruby/core/array/pack/w_spec.rb b/spec/ruby/core/array/pack/w_spec.rb index e241d1519c..48ed4496a5 100644 --- a/spec/ruby/core/array/pack/w_spec.rb +++ b/spec/ruby/core/array/pack/w_spec.rb @@ -26,7 +26,9 @@ describe "Array#pack with format 'w'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - [1, 2, 3].pack("w\x00w").should == "\x01\x02" + suppress_warning do + [1, 2, 3].pack("w\x00w").should == "\x01\x02" + end end end diff --git a/spec/ruby/core/array/plus_spec.rb b/spec/ruby/core/array/plus_spec.rb index 3962b05c39..635bd131c9 100644 --- a/spec/ruby/core/array/plus_spec.rb +++ b/spec/ruby/core/array/plus_spec.rb @@ -14,10 +14,23 @@ describe "Array#+" do (ary + ary).should == [1, 2, 3, 1, 2, 3] end - it "tries to convert the passed argument to an Array using #to_ary" do - obj = mock('["x", "y"]') - obj.should_receive(:to_ary).and_return(["x", "y"]) - ([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"] + describe "converts the passed argument to an Array using #to_ary" do + it "successfully concatenates the resulting array from the #to_ary call" do + obj = mock('["x", "y"]') + obj.should_receive(:to_ary).and_return(["x", "y"]) + ([1, 2, 3] + obj).should == [1, 2, 3, "x", "y"] + end + + it "raises a Typeerror if the given argument can't be converted to an array" do + -> { [1, 2, 3] + nil }.should raise_error(TypeError) + -> { [1, 2, 3] + "abc" }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do + obj = mock("hello") + obj.should_receive(:to_ary).and_raise(NoMethodError) + -> { [1, 2, 3] + obj }.should raise_error(NoMethodError) + end end it "properly handles recursive arrays" do diff --git a/spec/ruby/core/array/product_spec.rb b/spec/ruby/core/array/product_spec.rb index 07d2880a96..6fb3818508 100644 --- a/spec/ruby/core/array/product_spec.rb +++ b/spec/ruby/core/array/product_spec.rb @@ -9,6 +9,11 @@ describe "Array#product" do ar.called.should == :to_ary end + it "returns converted arguments using :method_missing" do + ar = ArraySpecs::ArrayMethodMissing.new(2,3) + [1].product(ar).should == [[1,2],[1,3]] + end + it "returns the expected result" do [1,2].product([3,4,5],[6,8]).should == [[1, 3, 6], [1, 3, 8], [1, 4, 6], [1, 4, 8], [1, 5, 6], [1, 5, 8], [2, 3, 6], [2, 3, 8], [2, 4, 6], [2, 4, 8], [2, 5, 6], [2, 5, 8]] diff --git a/spec/ruby/core/array/sample_spec.rb b/spec/ruby/core/array/sample_spec.rb index 5b3aac9aed..6ef78594f0 100644 --- a/spec/ruby/core/array/sample_spec.rb +++ b/spec/ruby/core/array/sample_spec.rb @@ -29,6 +29,10 @@ describe "Array#sample" do [4].sample(random: Random.new(42)).should equal(4) end + it "returns a single value when not passed a count and a Random class is given" do + [4].sample(random: Random).should equal(4) + end + it "returns an empty Array when passed zero" do [4].sample(0).should == [] end diff --git a/spec/ruby/core/array/shuffle_spec.rb b/spec/ruby/core/array/shuffle_spec.rb index b255147c75..1d528c124f 100644 --- a/spec/ruby/core/array/shuffle_spec.rb +++ b/spec/ruby/core/array/shuffle_spec.rb @@ -47,6 +47,10 @@ describe "Array#shuffle" do [1, 2].shuffle(random: random).should be_an_instance_of(Array) end + it "accepts a Random class for the value for random: argument" do + [1, 2].shuffle(random: Random).should be_an_instance_of(Array) + end + it "calls #to_int on the Object returned by #rand" do value = mock("array_shuffle_random_value") value.should_receive(:to_int).at_least(1).times.and_return(0) @@ -93,4 +97,14 @@ describe "Array#shuffle!" do -> { ArraySpecs.frozen_array.shuffle! }.should raise_error(FrozenError) -> { ArraySpecs.empty_frozen_array.shuffle! }.should raise_error(FrozenError) end + + it "matches CRuby with random:" do + %w[a b c].shuffle(random: Random.new(1)).should == %w[a c b] + (0..10).to_a.shuffle(random: Random.new(10)).should == [2, 6, 8, 5, 7, 10, 3, 1, 0, 4, 9] + end + + it "matches CRuby with srand" do + srand(123) + %w[a b c d e f g h i j k].shuffle.should == %w[a e f h i j d b g k c] + end end diff --git a/spec/ruby/core/array/try_convert_spec.rb b/spec/ruby/core/array/try_convert_spec.rb index 47b4722d80..bea8815006 100644 --- a/spec/ruby/core/array/try_convert_spec.rb +++ b/spec/ruby/core/array/try_convert_spec.rb @@ -39,7 +39,7 @@ describe "Array.try_convert" do it "sends #to_ary to the argument and raises TypeError if it's not a kind of Array" do obj = mock("to_ary") obj.should_receive(:to_ary).and_return(Object.new) - -> { Array.try_convert obj }.should raise_error(TypeError) + -> { Array.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Array (MockObject#to_ary gives Object)") end it "does not rescue exceptions raised by #to_ary" do diff --git a/spec/ruby/core/complex/inspect_spec.rb b/spec/ruby/core/complex/inspect_spec.rb index 71aabde5be..7a89ec6854 100644 --- a/spec/ruby/core/complex/inspect_spec.rb +++ b/spec/ruby/core/complex/inspect_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative '../numeric/fixtures/classes' describe "Complex#inspect" do it "returns (${real}+${image}i) for positive imaginary parts" do @@ -13,4 +14,22 @@ describe "Complex#inspect" do Complex(-1, -4).inspect.should == "(-1-4i)" Complex(-7, -6.7).inspect.should == "(-7-6.7i)" end + + it "calls #inspect on real and imaginary" do + real = NumericSpecs::Subclass.new + real.should_receive(:inspect).and_return("1") + imaginary = NumericSpecs::Subclass.new + imaginary.should_receive(:inspect).and_return("2") + imaginary.should_receive(:<).any_number_of_times.and_return(false) + Complex(real, imaginary).inspect.should == "(1+2i)" + end + + it "adds an `*' before the `i' if the last character of the imaginary part is not numeric" do + real = NumericSpecs::Subclass.new + real.should_receive(:inspect).and_return("(1)") + imaginary = NumericSpecs::Subclass.new + imaginary.should_receive(:inspect).and_return("(2)") + imaginary.should_receive(:<).any_number_of_times.and_return(false) + Complex(real, imaginary).inspect.should == "((1)+(2)*i)" + end end diff --git a/spec/ruby/core/complex/to_s_spec.rb b/spec/ruby/core/complex/to_s_spec.rb index 989a7ae0b7..7677dcd0b5 100644 --- a/spec/ruby/core/complex/to_s_spec.rb +++ b/spec/ruby/core/complex/to_s_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative '../numeric/fixtures/classes' describe "Complex#to_s" do describe "when self's real component is 0" do @@ -41,4 +42,13 @@ describe "Complex#to_s" do it "returns 1+NaN*i for Complex(1, NaN)" do Complex(1, nan_value).to_s.should == "1+NaN*i" end + + it "treats real and imaginary parts as strings" do + real = NumericSpecs::Subclass.new + real.should_receive(:to_s).and_return("1") + imaginary = NumericSpecs::Subclass.new + imaginary.should_receive(:to_s).and_return("2") + imaginary.should_receive(:<).any_number_of_times.and_return(false) + Complex(real, imaginary).to_s.should == "1+2i" + end end diff --git a/spec/ruby/core/data/define_spec.rb b/spec/ruby/core/data/define_spec.rb index abfdd3e6a7..2aa2c50d4c 100644 --- a/spec/ruby/core/data/define_spec.rb +++ b/spec/ruby/core/data/define_spec.rb @@ -9,18 +9,28 @@ ruby_version_is "3.2" do end it "accepts symbols" do - movie_with_symbol = Data.define(:title, :year) - movie_with_symbol.members.should == [:title, :year] + movie = Data.define(:title, :year) + movie.members.should == [:title, :year] end it "accepts strings" do - movie_with_string = Data.define("title", "year") - movie_with_string.members.should == [:title, :year] + movie = Data.define("title", "year") + movie.members.should == [:title, :year] end it "accepts a mix of strings and symbols" do - blockbuster_movie = Data.define("title", :year, "genre") - blockbuster_movie.members.should == [:title, :year, :genre] + movie = Data.define("title", :year, "genre") + movie.members.should == [:title, :year, :genre] + end + + it "accepts a block" do + movie = Data.define(:title, :year) do + def title_with_year + "#{title} (#{year})" + end + end + movie.members.should == [:title, :year] + movie.new("Matrix", 1999).title_with_year.should == "Matrix (1999)" end end end diff --git a/spec/ruby/core/dir/fchdir_spec.rb b/spec/ruby/core/dir/fchdir_spec.rb index dde459e98e..08b1cdfc7e 100644 --- a/spec/ruby/core/dir/fchdir_spec.rb +++ b/spec/ruby/core/dir/fchdir_spec.rb @@ -6,15 +6,13 @@ ruby_version_is '3.3' do dir = Dir.new('.') Dir.fchdir(dir.fileno) true - rescue NotImplementedError + rescue NotImplementedError, NoMethodError false - rescue Exception - true ensure dir.close end - if has_fchdir + guard -> { has_fchdir } do describe "Dir.fchdir" do before :all do DirSpecs.create_mock_dirs @@ -58,7 +56,7 @@ ruby_version_is '3.3' do end it "raises a SystemCallError if the file descriptor given is not valid" do - -> { Dir.fchdir -1 }.should raise_error(SystemCallError) + -> { Dir.fchdir(-1) }.should raise_error(SystemCallError) -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError) end @@ -67,7 +65,9 @@ ruby_version_is '3.3' do -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError) end end - else + end + + guard_not -> { has_fchdir } do describe "Dir.fchdir" do it "raises NotImplementedError" do -> { Dir.fchdir 1 }.should raise_error(NotImplementedError) diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb index bbe347ba9e..90a008faf1 100644 --- a/spec/ruby/core/dir/home_spec.rb +++ b/spec/ruby/core/dir/home_spec.rb @@ -85,4 +85,10 @@ describe "Dir.home" do it "raises an ArgumentError if the named user doesn't exist" do -> { Dir.home('geuw2n288dh2k') }.should raise_error(ArgumentError) end + + describe "when called with a nil user name" do + it "returns the current user's home directory, reading $HOME first" do + Dir.home(nil).should == "/rubyspec_home" + end + end end diff --git a/spec/ruby/core/enumerable/shared/inject.rb b/spec/ruby/core/enumerable/shared/inject.rb index a934d039c5..693d34d675 100644 --- a/spec/ruby/core/enumerable/shared/inject.rb +++ b/spec/ruby/core/enumerable/shared/inject.rb @@ -28,6 +28,15 @@ describe :enumerable_inject, shared: true do }.should complain(/#{__FILE__}:#{__LINE__-1}: warning: given block not used/, verbose: true) end + it "does not warn when given a Symbol with $VERBOSE true" do + -> { + [1, 2].send(@method, 0, :+) + [1, 2].send(@method, :+) + EnumerableSpecs::Numerous.new(1, 2).send(@method, 0, :+) + EnumerableSpecs::Numerous.new(1, 2).send(@method, :+) + }.should_not complain(verbose: true) + end + it "can take a symbol argument" do EnumerableSpecs::Numerous.new(10, 1, 2, 3).send(@method, :-).should == 4 end diff --git a/spec/ruby/core/enumerable/tally_spec.rb b/spec/ruby/core/enumerable/tally_spec.rb index f09a8f533a..e0edc8dc75 100644 --- a/spec/ruby/core/enumerable/tally_spec.rb +++ b/spec/ruby/core/enumerable/tally_spec.rb @@ -49,6 +49,13 @@ ruby_version_is "3.1" do enum.tally(hash).should equal(hash) end + it "calls #to_hash to convert argument to Hash implicitly if passed not a Hash" do + enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') + object = Object.new + def object.to_hash; { 'foo' => 1 }; end + enum.tally(object).should == { 'foo' => 3, 'bar' => 1, 'baz' => 1} + end + it "raises a FrozenError and does not update the given hash when the hash is frozen" do enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') hash = { 'foo' => 1 }.freeze @@ -56,6 +63,12 @@ ruby_version_is "3.1" do hash.should == { 'foo' => 1 } end + it "raises a FrozenError even if enumerable is empty" do + enum = EnumerableSpecs::Numerous.new() + hash = { 'foo' => 1 }.freeze + -> { enum.tally(hash) }.should raise_error(FrozenError) + end + it "does not call given block" do enum = EnumerableSpecs::Numerous.new('foo', 'bar', 'foo', 'baz') enum.tally({ 'foo' => 1 }) { |v| ScratchPad << v } diff --git a/spec/ruby/core/fiber/inspect_spec.rb b/spec/ruby/core/fiber/inspect_spec.rb new file mode 100644 index 0000000000..ee53af3a39 --- /dev/null +++ b/spec/ruby/core/fiber/inspect_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' +require 'fiber' + +describe "Fiber#inspect" do + describe "status" do + it "is resumed for the root Fiber of a Thread" do + inspected = Thread.new { Fiber.current.inspect }.value + inspected.should =~ /\A#<Fiber:0x\h+ .*\(resumed\)>\z/ + end + + it "is created for a Fiber which did not run yet" do + inspected = Fiber.new {}.inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(created\)>\z/ + end + + it "is resumed for a Fiber which was resumed" do + inspected = Fiber.new { Fiber.current.inspect }.resume + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/ + end + + ruby_version_is "3.0" do + it "is resumed for a Fiber which was transferred" do + inspected = Fiber.new { Fiber.current.inspect }.transfer + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(resumed\)>\z/ + end + end + + it "is suspended for a Fiber which was resumed and yielded" do + inspected = Fiber.new { Fiber.yield }.tap(&:resume).inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(suspended\)>\z/ + end + + it "is terminated for a Fiber which has terminated" do + inspected = Fiber.new {}.tap(&:resume).inspect + inspected.should =~ /\A#<Fiber:0x\h+ .+ \(terminated\)>\z/ + end + end +end diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb index cf0f909f59..8dd6c4ca88 100644 --- a/spec/ruby/core/file/dirname_spec.rb +++ b/spec/ruby/core/file/dirname_spec.rb @@ -12,18 +12,33 @@ describe "File.dirname" do end ruby_version_is '3.1' do - it "returns all the components of filename except the last parts by the level" do - File.dirname('/home/jason', 2).should == '/' - File.dirname('/home/jason/poot.txt', 2).should == '/home' - end - - it "returns the same string if the level is 0" do - File.dirname('poot.txt', 0).should == 'poot.txt' - File.dirname('/', 0).should == '/' - end - - it "raises ArgumentError if the level is negative" do - -> {File.dirname('/home/jason', -1)}.should raise_error(ArgumentError) + context "when level is passed" do + it "returns all the components of filename except the last parts by the level" do + File.dirname('/home/jason', 2).should == '/' + File.dirname('/home/jason/poot.txt', 2).should == '/home' + end + + it "returns the same String if the level is 0" do + File.dirname('poot.txt', 0).should == 'poot.txt' + File.dirname('/', 0).should == '/' + end + + it "raises ArgumentError if the level is negative" do + -> { + File.dirname('/home/jason', -1) + }.should raise_error(ArgumentError, "negative level: -1") + end + + it "returns '/' when level exceeds the number of segments in the path" do + File.dirname("/home/jason", 100).should == '/' + end + + it "calls #to_int if passed not numeric value" do + object = Object.new + def object.to_int; 2; end + + File.dirname("/a/b/c/d", object).should == '/a/b' + end end end diff --git a/spec/ruby/core/hash/try_convert_spec.rb b/spec/ruby/core/hash/try_convert_spec.rb index 44195c5010..d359ae49d8 100644 --- a/spec/ruby/core/hash/try_convert_spec.rb +++ b/spec/ruby/core/hash/try_convert_spec.rb @@ -39,7 +39,7 @@ describe "Hash.try_convert" do it "sends #to_hash to the argument and raises TypeError if it's not a kind of Hash" do obj = mock("to_hash") obj.should_receive(:to_hash).and_return(Object.new) - -> { Hash.try_convert obj }.should raise_error(TypeError) + -> { Hash.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to Hash (MockObject#to_hash gives Object)") end it "does not rescue exceptions raised by #to_hash" do diff --git a/spec/ruby/core/integer/bit_and_spec.rb b/spec/ruby/core/integer/bit_and_spec.rb index 8de5a14aaa..e7face39ac 100644 --- a/spec/ruby/core/integer/bit_and_spec.rb +++ b/spec/ruby/core/integer/bit_and_spec.rb @@ -30,7 +30,7 @@ describe "Integer#&" do it "coerces the rhs and calls #coerce" do obj = mock("fixnum bit and") - obj.should_receive(:coerce).with(6).and_return([3, 6]) + obj.should_receive(:coerce).with(6).and_return([6, 3]) (6 & obj).should == 2 end diff --git a/spec/ruby/core/integer/bit_or_spec.rb b/spec/ruby/core/integer/bit_or_spec.rb index 6f4279c170..fdf8a191e5 100644 --- a/spec/ruby/core/integer/bit_or_spec.rb +++ b/spec/ruby/core/integer/bit_or_spec.rb @@ -30,9 +30,9 @@ describe "Integer#|" do end it "coerces the rhs and calls #coerce" do - obj = mock("fixnum bit and") - obj.should_receive(:coerce).with(6).and_return([3, 6]) - (6 & obj).should == 2 + obj = mock("fixnum bit or") + obj.should_receive(:coerce).with(6).and_return([6, 3]) + (6 | obj).should == 7 end it "raises a TypeError when passed a Float" do diff --git a/spec/ruby/core/integer/bit_xor_spec.rb b/spec/ruby/core/integer/bit_xor_spec.rb index f1150a20d5..1f46bc52f3 100644 --- a/spec/ruby/core/integer/bit_xor_spec.rb +++ b/spec/ruby/core/integer/bit_xor_spec.rb @@ -28,8 +28,8 @@ describe "Integer#^" do end it "coerces the rhs and calls #coerce" do - obj = mock("fixnum bit and") - obj.should_receive(:coerce).with(6).and_return([3, 6]) + obj = mock("fixnum bit xor") + obj.should_receive(:coerce).with(6).and_return([6, 3]) (6 ^ obj).should == 5 end diff --git a/spec/ruby/core/integer/ceildiv_spec.rb b/spec/ruby/core/integer/ceildiv_spec.rb new file mode 100644 index 0000000000..18d07c66d0 --- /dev/null +++ b/spec/ruby/core/integer/ceildiv_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' + +describe "Integer#ceildiv" do + ruby_version_is '3.2' do + it "returns a quotient of division which is rounded up to the nearest integer" do + 0.ceildiv(3).should eql(0) + 1.ceildiv(3).should eql(1) + 3.ceildiv(3).should eql(1) + 4.ceildiv(3).should eql(2) + + 4.ceildiv(-3).should eql(-1) + -4.ceildiv(3).should eql(-1) + -4.ceildiv(-3).should eql(2) + + 3.ceildiv(1.2).should eql(3) + 3.ceildiv(6/5r).should eql(3) + + (10**100-11).ceildiv(10**99-1).should eql(10) + (10**100-9).ceildiv(10**99-1).should eql(11) + end + end +end diff --git a/spec/ruby/core/integer/try_convert_spec.rb b/spec/ruby/core/integer/try_convert_spec.rb index 45c66eec79..4bc7d3851a 100644 --- a/spec/ruby/core/integer/try_convert_spec.rb +++ b/spec/ruby/core/integer/try_convert_spec.rb @@ -28,7 +28,17 @@ ruby_version_is "3.1" do it "sends #to_int to the argument and raises TypeError if it's not a kind of Integer" do obj = mock("to_int") obj.should_receive(:to_int).and_return(Object.new) - -> { Integer.try_convert obj }.should raise_error(TypeError) + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives Object)") + end + + it "responds with a different error message when it raises a TypeError, depending on the type of the non-Integer object :to_int returns" do + obj = mock("to_int") + obj.should_receive(:to_int).and_return("A String") + -> { + Integer.try_convert obj + }.should raise_error(TypeError, "can't convert MockObject to Integer (MockObject#to_int gives String)") end it "does not rescue exceptions raised by #to_int" do diff --git a/spec/ruby/core/io/gets_spec.rb b/spec/ruby/core/io/gets_spec.rb index 1f1c3bb254..f38e3d3974 100644 --- a/spec/ruby/core/io/gets_spec.rb +++ b/spec/ruby/core/io/gets_spec.rb @@ -113,6 +113,35 @@ describe "IO#gets" do $..should == @count += 1 end end + + describe "that consists of multiple bytes" do + platform_is_not :windows do + it "should match the separator even if the buffer is filled over successive reads" do + IO.pipe do |read, write| + + # Write part of the string with the separator split between two write calls. We want + # the read to intertwine such that when the read starts the full data isn't yet + # available in the buffer. + write.write("Aquí está la línea tres\r\n") + + t = Thread.new do + # Continue reading until the separator is encountered or the pipe is closed. + read.gets("\r\n\r\n") + end + + # Write the other half of the separator, which should cause the `gets` call to now + # match. Explicitly close the pipe for good measure so a bug in `gets` doesn't block forever. + Thread.pass until t.stop? + + write.write("\r\nelse\r\n\r\n") + write.close + + t.value.bytes.should == "Aquí está la línea tres\r\n\r\n".bytes + read.read(8).bytes.should == "else\r\n\r\n".bytes + end + end + end + end end describe "when passed chomp" do diff --git a/spec/ruby/core/io/shared/write.rb b/spec/ruby/core/io/shared/write.rb index a4d1259971..964064746a 100644 --- a/spec/ruby/core/io/shared/write.rb +++ b/spec/ruby/core/io/shared/write.rb @@ -97,3 +97,58 @@ describe :io_write, shared: true do end end end + +describe :io_write_transcode, shared: true do + before :each do + @transcode_filename = tmp("io_write_transcode") + end + + after :each do + rm_r @transcode_filename + end + + it "transcodes the given string when the external encoding is set and neither is BINARY" do + utf8_str = "hello" + + File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file| + file.external_encoding.should == Encoding::UTF_16BE + file.send(@method, utf8_str) + end + + result = File.binread(@transcode_filename) + expected = [0, 104, 0, 101, 0, 108, 0, 108, 0, 111] # UTF-16BE bytes for "hello" + + result.bytes.should == expected + end + + it "transcodes the given string when the external encoding is set and the string encoding is BINARY" do + str = "été".b + + File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file| + file.external_encoding.should == Encoding::UTF_16BE + -> { file.send(@method, str) }.should raise_error(Encoding::UndefinedConversionError) + end + end +end + +describe :io_write_no_transcode, shared: true do + before :each do + @transcode_filename = tmp("io_write_no_transcode") + end + + after :each do + rm_r @transcode_filename + end + + it "does not transcode the given string even when the external encoding is set" do + utf8_str = "hello" + + File.open(@transcode_filename, "w", external_encoding: Encoding::UTF_16BE) do |file| + file.external_encoding.should == Encoding::UTF_16BE + file.send(@method, utf8_str) + end + + result = File.binread(@transcode_filename) + result.bytes.should == utf8_str.bytes + end +end diff --git a/spec/ruby/core/io/syswrite_spec.rb b/spec/ruby/core/io/syswrite_spec.rb index eeb8d302a7..8bf61a27c3 100644 --- a/spec/ruby/core/io/syswrite_spec.rb +++ b/spec/ruby/core/io/syswrite_spec.rb @@ -78,4 +78,5 @@ end describe "IO#syswrite" do it_behaves_like :io_write, :syswrite + it_behaves_like :io_write_no_transcode, :syswrite end diff --git a/spec/ruby/core/io/try_convert_spec.rb b/spec/ruby/core/io/try_convert_spec.rb index 5fbd10b6fa..a9e99de7aa 100644 --- a/spec/ruby/core/io/try_convert_spec.rb +++ b/spec/ruby/core/io/try_convert_spec.rb @@ -38,7 +38,7 @@ describe "IO.try_convert" do it "raises a TypeError if the object does not return an IO from #to_io" do obj = mock("io") obj.should_receive(:to_io).and_return("io") - -> { IO.try_convert(obj) }.should raise_error(TypeError) + -> { IO.try_convert(obj) }.should raise_error(TypeError, "can't convert MockObject to IO (MockObject#to_io gives String)") end it "propagates an exception raised by #to_io" do diff --git a/spec/ruby/core/io/write_nonblock_spec.rb b/spec/ruby/core/io/write_nonblock_spec.rb index 5532556d8a..c403c864fd 100644 --- a/spec/ruby/core/io/write_nonblock_spec.rb +++ b/spec/ruby/core/io/write_nonblock_spec.rb @@ -50,6 +50,7 @@ platform_is_not :windows do describe "IO#write_nonblock" do it_behaves_like :io_write, :write_nonblock + it_behaves_like :io_write_no_transcode, :write_nonblock end end diff --git a/spec/ruby/core/io/write_spec.rb b/spec/ruby/core/io/write_spec.rb index 6e89c7cd9e..bf23634372 100644 --- a/spec/ruby/core/io/write_spec.rb +++ b/spec/ruby/core/io/write_spec.rb @@ -220,6 +220,7 @@ end describe "IO#write" do it_behaves_like :io_write, :write + it_behaves_like :io_write_transcode, :write it "accepts multiple arguments" do IO.pipe do |r, w| diff --git a/spec/ruby/core/kernel/Complex_spec.rb b/spec/ruby/core/kernel/Complex_spec.rb index cc8177fa02..346d50ab5e 100644 --- a/spec/ruby/core/kernel/Complex_spec.rb +++ b/spec/ruby/core/kernel/Complex_spec.rb @@ -269,4 +269,8 @@ describe "Kernel.Complex()" do end end end + + it "freezes its result" do + Complex(1).frozen?.should == true + end end diff --git a/spec/ruby/core/kernel/define_singleton_method_spec.rb b/spec/ruby/core/kernel/define_singleton_method_spec.rb index 2d8b1bf413..24acec84f5 100644 --- a/spec/ruby/core/kernel/define_singleton_method_spec.rb +++ b/spec/ruby/core/kernel/define_singleton_method_spec.rb @@ -111,4 +111,10 @@ describe "Kernel#define_singleton_method" do cls.foo.should == :ok }.should_not raise_error(NoMethodError) end + + it "cannot define a singleton method with a frozen singleton class" do + o = Object.new + o.freeze + -> { o.define_singleton_method(:foo) { 1 } }.should raise_error(FrozenError) + end end diff --git a/spec/ruby/core/kernel/method_spec.rb b/spec/ruby/core/kernel/method_spec.rb index caf2ec948b..3fc566d6a6 100644 --- a/spec/ruby/core/kernel/method_spec.rb +++ b/spec/ruby/core/kernel/method_spec.rb @@ -29,7 +29,7 @@ describe "Kernel#method" do m.call.should == :defined end - it "can be called even if we only repond_to_missing? method, true" do + it "can be called even if we only respond_to_missing? method, true" do m = KernelSpecs::RespondViaMissing.new.method(:handled_privately) m.should be_an_instance_of(Method) m.call(1, 2, 3).should == "Done handled_privately([1, 2, 3])" @@ -58,4 +58,23 @@ describe "Kernel#method" do m = cls.new.method(:bar) m.call.should == :bar end + + describe "converts the given name to a String using #to_str" do + it "calls #to_str to convert the given name to a String" do + name = mock("method-name") + name.should_receive(:to_str).and_return("hash") + Object.method(name).should == Object.method(:hash) + end + + it "raises a TypeError if the given name can't be converted to a String" do + -> { Object.method(nil) }.should raise_error(TypeError) + -> { Object.method([]) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + name = mock("method-name") + name.should_receive(:to_str).and_raise(NoMethodError) + -> { Object.method(name) }.should raise_error(NoMethodError) + end + end end diff --git a/spec/ruby/core/kernel/require_spec.rb b/spec/ruby/core/kernel/require_spec.rb index dc3da4b7e6..b0af8b297d 100644 --- a/spec/ruby/core/kernel/require_spec.rb +++ b/spec/ruby/core/kernel/require_spec.rb @@ -16,6 +16,22 @@ describe "Kernel#require" do Kernel.should have_private_instance_method(:require) end + provided = %w[complex enumerator rational thread ruby2_keywords] + ruby_version_is "3.1" do + provided << "fiber" + end + + it "#{provided.join(', ')} are already required" do + out = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems --disable-did-you-mean') + features = out.lines.map { |line| File.basename(line.chomp, '.*') } + features -= %w[encdb transdb] # Ignore CRuby internals + features.sort.should == provided.sort + + code = provided.map { |f| "puts require #{f.inspect}\n" }.join + required = ruby_exe(code, options: '--disable-gems') + required.should == "false\n" * provided.size + end + it_behaves_like :kernel_require_basic, :require, CodeLoadingSpecs::Method.new it_behaves_like :kernel_require, :require, CodeLoadingSpecs::Method.new end diff --git a/spec/ruby/core/kernel/shared/require.rb b/spec/ruby/core/kernel/shared/require.rb index ae814aa317..5cbc11c9ec 100644 --- a/spec/ruby/core/kernel/shared/require.rb +++ b/spec/ruby/core/kernel/shared/require.rb @@ -557,20 +557,6 @@ describe :kernel_require, shared: true do ScratchPad.recorded.should == [] end - provided = %w[complex enumerator rational thread] - provided << 'ruby2_keywords' - - it "#{provided.join(', ')} are already required" do - features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems') - provided.each { |feature| - features.should =~ /\b#{feature}\.(rb|so|jar)$/ - } - - code = provided.map { |f| "puts require #{f.inspect}\n" }.join - required = ruby_exe(code, options: '--disable-gems') - required.should == "false\n" * provided.size - end - it "unicode_normalize is part of core and not $LOADED_FEATURES" do features = ruby_exe("puts $LOADED_FEATURES", options: '--disable-gems') features.lines.each { |feature| diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb index 879ea287ce..f38250b513 100644 --- a/spec/ruby/core/marshal/dump_spec.rb +++ b/spec/ruby/core/marshal/dump_spec.rb @@ -104,19 +104,42 @@ describe "Marshal.dump" do UserMarshal.should_not_receive(:name) Marshal.dump(UserMarshal.new) end + + it "raises TypeError if an Object is an instance of an anonymous class" do + -> { Marshal.dump(Class.new(UserMarshal).new) }.should raise_error(TypeError, /can't dump anonymous class/) + end end describe "with an object responding to #_dump" do - it "dumps the object returned by #_dump" do + it "dumps the String returned by #_dump" do Marshal.dump(UserDefined.new).should == "\004\bu:\020UserDefined\022\004\b[\a:\nstuff;\000" end + it "dumps the String in non US-ASCII and non UTF-8 encoding" do + object = UserDefinedString.new("a".encode("windows-1251")) + Marshal.dump(object).should == "\x04\bIu:\x16UserDefinedString\x06a\x06:\rencoding\"\x11Windows-1251" + end + + it "dumps the String in multibyte encoding" do + object = UserDefinedString.new("a".encode("utf-32le")) + Marshal.dump(object).should == "\x04\bIu:\x16UserDefinedString\ta\x00\x00\x00\x06:\rencoding\"\rUTF-32LE" + end + + it "ignores overridden name method" do + obj = MarshalSpec::UserDefinedWithOverriddenName.new + Marshal.dump(obj).should == "\x04\bu:/MarshalSpec::UserDefinedWithOverriddenName\x12\x04\b[\a:\nstuff;\x00" + end + it "raises a TypeError if _dump returns a non-string" do m = mock("marshaled") m.should_receive(:_dump).and_return(0) -> { Marshal.dump(m) }.should raise_error(TypeError) end + it "raises TypeError if an Object is an instance of an anonymous class" do + -> { Marshal.dump(Class.new(UserDefined).new) }.should raise_error(TypeError, /can't dump anonymous class/) + end + it "favors marshal_dump over _dump" do m = mock("marshaled") m.should_receive(:marshal_dump).and_return(0) @@ -166,8 +189,17 @@ describe "Marshal.dump" do Marshal.dump(UserDefined::Nested).should == "\004\bc\030UserDefined::Nested" end + it "ignores overridden name method" do + Marshal.dump(MarshalSpec::ClassWithOverriddenName).should == "\x04\bc)MarshalSpec::ClassWithOverriddenName" + end + + it "dumps a class with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteぁあぃいClass".force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class" + end + it "raises TypeError with an anonymous Class" do - -> { Marshal.dump(Class.new) }.should raise_error(TypeError) + -> { Marshal.dump(Class.new) }.should raise_error(TypeError, /can't dump anonymous class/) end it "raises TypeError with a singleton Class" do @@ -180,8 +212,17 @@ describe "Marshal.dump" do Marshal.dump(Marshal).should == "\004\bm\fMarshal" end + it "ignores overridden name method" do + Marshal.dump(MarshalSpec::ModuleWithOverriddenName).should == "\x04\bc*MarshalSpec::ModuleWithOverriddenName" + end + + it "dumps a module with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteけげこごModule".force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module" + end + it "raises TypeError with an anonymous Module" do - -> { Marshal.dump(Module.new) }.should raise_error(TypeError) + -> { Marshal.dump(Module.new) }.should raise_error(TypeError, /can't dump anonymous module/) end end @@ -255,6 +296,11 @@ describe "Marshal.dump" do Marshal.dump(UserString.new.extend(Meths).force_encoding("binary")).should == "\004\be:\nMethsC:\017UserString\"\000" end + it "ignores overridden name method when dumps a String subclass" do + obj = MarshalSpec::StringWithOverriddenName.new + Marshal.dump(obj).should == "\x04\bC:*MarshalSpec::StringWithOverriddenName\"\x00" + end + it "dumps a String with instance variables" do str = "" str.instance_variable_set("@foo", "bar") @@ -310,14 +356,42 @@ describe "Marshal.dump" do Marshal.dump(o).should == "\x04\b/\x00\x10" end + it "dumps an ascii-compatible Regexp" do + o = Regexp.new("a".encode("us-ascii"), Regexp::FIXEDENCODING) + Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06EF" + + o = Regexp.new("a".encode("us-ascii")) + Marshal.dump(o).should == "\x04\bI/\x06a\x00\x06:\x06EF" + + o = Regexp.new("a".encode("windows-1251"), Regexp::FIXEDENCODING) + Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\rencoding\"\x11Windows-1251" + + o = Regexp.new("a".encode("windows-1251")) + Marshal.dump(o).should == "\x04\bI/\x06a\x00\x06:\x06EF" + end + it "dumps a UTF-8 Regexp" do o = Regexp.new("".force_encoding("utf-8"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\x06ET" + + o = Regexp.new("a".force_encoding("utf-8"), Regexp::FIXEDENCODING) + Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06ET" + + o = Regexp.new("\u3042".force_encoding("utf-8"), Regexp::FIXEDENCODING) + Marshal.dump(o).should == "\x04\bI/\b\xE3\x81\x82\x10\x06:\x06ET" end it "dumps a Regexp in another encoding" do o = Regexp.new("".force_encoding("utf-16le"), Regexp::FIXEDENCODING) Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\rencoding\"\rUTF-16LE" + + o = Regexp.new("a".encode("utf-16le"), Regexp::FIXEDENCODING) + Marshal.dump(o).should == "\x04\bI/\aa\x00\x10\x06:\rencoding\"\rUTF-16LE" + end + + it "ignores overridden name method when dumps a Regexp subclass" do + obj = MarshalSpec::RegexpWithOverriddenName.new("") + Marshal.dump(obj).should == "\x04\bIC:*MarshalSpec::RegexpWithOverriddenName/\x00\x00\x06:\x06EF" end end @@ -349,6 +423,11 @@ describe "Marshal.dump" do it "dumps an extended Array" do Marshal.dump([].extend(Meths)).should == "\004\be:\nMeths[\000" end + + it "ignores overridden name method when dumps an Array subclass" do + obj = MarshalSpec::ArrayWithOverriddenName.new + Marshal.dump(obj).should == "\x04\bC:)MarshalSpec::ArrayWithOverriddenName[\x00" + end end describe "with a Hash" do @@ -356,6 +435,10 @@ describe "Marshal.dump" do Marshal.dump({}).should == "\004\b{\000" end + it "dumps a non-empty Hash" do + Marshal.dump({a: 1}).should == "\x04\b{\x06:\x06ai\x06" + end + it "dumps a Hash subclass" do Marshal.dump(UserHash.new).should == "\004\bC:\rUserHash{\000" end @@ -364,8 +447,24 @@ describe "Marshal.dump" do Marshal.dump(Hash.new(1)).should == "\004\b}\000i\006" end + ruby_version_is "3.1" do + it "dumps a Hash with compare_by_identity" do + h = {} + h.compare_by_identity + + Marshal.dump(h).should == "\004\bC:\tHash{\x00" + end + + it "dumps a Hash subclass with compare_by_identity" do + h = UserHash.new + h.compare_by_identity + + Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00" + end + end + it "raises a TypeError with hash having default proc" do - -> { Marshal.dump(Hash.new {}) }.should raise_error(TypeError) + -> { Marshal.dump(Hash.new {}) }.should raise_error(TypeError, "can't dump hash with default proc") end it "dumps a Hash with instance variables" do @@ -381,6 +480,11 @@ describe "Marshal.dump" do it "dumps an Hash subclass with a parameter to initialize" do Marshal.dump(UserHashInitParams.new(1)).should == "\004\bIC:\027UserHashInitParams{\000\006:\a@ai\006" end + + it "ignores overridden name method when dumps a Hash subclass" do + obj = MarshalSpec::HashWithOverriddenName.new + Marshal.dump(obj).should == "\x04\bC:(MarshalSpec::HashWithOverriddenName{\x00" + end end describe "with a Struct" do @@ -409,6 +513,15 @@ describe "Marshal.dump" do Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a" Struct.send(:remove_const, :Extended) end + + it "ignores overridden name method" do + obj = MarshalSpec::StructWithOverriddenName.new("member") + Marshal.dump(obj).should == "\x04\bS:*MarshalSpec::StructWithOverriddenName\x06:\x06a\"\vmember" + end + + it "raises TypeError with an anonymous Struct" do + -> { Marshal.dump(Struct.new(:a).new(1)) }.should raise_error(TypeError, /can't dump anonymous class/) + end end describe "with an Object" do @@ -440,13 +553,18 @@ describe "Marshal.dump" do Marshal.dump(obj).should == "\004\bo:\x0BObject\x00" end - it "dumps an Object if it has a singleton class but no singleton methods" do + it "dumps an Object if it has a singleton class but no singleton methods and no singleton instance variables" do obj = Object.new obj.singleton_class Marshal.dump(obj).should == "\004\bo:\x0BObject\x00" end - it "raises if an Object has a singleton class and singleton methods" do + it "ignores overridden name method" do + obj = MarshalSpec::ClassWithOverriddenName.new + Marshal.dump(obj).should == "\x04\bo:)MarshalSpec::ClassWithOverriddenName\x00" + end + + it "raises TypeError if an Object has a singleton class and singleton methods" do obj = Object.new def obj.foo; end -> { @@ -454,10 +572,45 @@ describe "Marshal.dump" do }.should raise_error(TypeError, "singleton can't be dumped") end + it "raises TypeError if an Object has a singleton class and singleton instance variables" do + obj = Object.new + class << obj + @v = 1 + end + + -> { + Marshal.dump(obj) + }.should raise_error(TypeError, "singleton can't be dumped") + end + + it "raises TypeError if an Object is an instance of an anonymous class" do + anonymous_class = Class.new + obj = anonymous_class.new + + -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/) + end + + it "raises TypeError if an Object extends an anonymous module" do + anonymous_module = Module.new + obj = Object.new + obj.extend(anonymous_module) + + -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/) + end + it "dumps a BasicObject subclass if it defines respond_to?" do obj = MarshalSpec::BasicObjectSubWithRespondToFalse.new Marshal.dump(obj).should == "\x04\bo:2MarshalSpec::BasicObjectSubWithRespondToFalse\x00" end + + it "dumps without marshaling any attached finalizer" do + obj = Object.new + finalizer = Object.new + def finalizer.noop(_) + end + ObjectSpace.define_finalizer(obj, finalizer.method(:noop)) + Marshal.load(Marshal.dump(obj)).class.should == Object + end end describe "with a Range" do @@ -483,6 +636,10 @@ describe "Marshal.dump" do load.instance_variable_get(:@foo).should == 42 end end + + it "raises TypeError with an anonymous Range subclass" do + -> { Marshal.dump(Class.new(Range).new(1, 2)) }.should raise_error(TypeError, /can't dump anonymous class/) + end end describe "with a Time" do @@ -520,6 +677,20 @@ describe "Marshal.dump" do zone = ":\tzoneI\"\bUTC\x06:\x06EF" # Last is 'F' (US-ASCII) dump.should == "\x04\bIu:\tTime\r#{@utc_dump}\x06#{zone}" end + + it "ignores overridden name method" do + obj = MarshalSpec::TimeWithOverriddenName.new + Marshal.dump(obj).should include("MarshalSpec::TimeWithOverriddenName") + end + + it "dumps a Time subclass with multibyte characters in name" do + source_object = eval("MarshalSpec::MultibyteぁあぃいTime".force_encoding(Encoding::UTF_8)) + Marshal.dump(source_object).should == "\x04\bc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time" + end + + it "raises TypeError with an anonymous Time subclass" do + -> { Marshal.dump(Class.new(Time).now) }.should raise_error(TypeError) + end end describe "with an Exception" do @@ -560,6 +731,23 @@ describe "Marshal.dump" do reloaded.cause.should be_an_instance_of(StandardError) reloaded.cause.message.should == "the cause" end + + # NoMethodError uses an exception formatter on TruffleRuby and computes a message lazily + it "dumps the message for the raised NoMethodError exception" do + begin + "".foo + rescue => e + end + + Marshal.dump(e).should =~ /undefined method `foo' for ("":String|an instance of String)/ + end + + it "raises TypeError if an Object is an instance of an anonymous class" do + anonymous_class = Class.new(Exception) + obj = anonymous_class.new + + -> { Marshal.dump(obj) }.should raise_error(TypeError, /can't dump anonymous class/) + end end it "dumps subsequent appearances of a symbol as a link" do diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb index 9373ef7ba8..680cb08ac7 100644 --- a/spec/ruby/core/marshal/fixtures/marshal_data.rb +++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb @@ -78,6 +78,22 @@ class UserDefinedImmediate end end +class UserDefinedString + attr_reader :string + + def initialize(string) + @string = string + end + + def _dump(depth) + @string + end + + def self._load(data) + new(data) + end +end + class UserPreviouslyDefinedWithInitializedIvar attr_accessor :field1, :field2 end @@ -167,12 +183,17 @@ module MarshalSpec end end + StructToDump = Struct.new(:a, :b) + class BasicObjectSubWithRespondToFalse < BasicObject def respond_to?(method_name, include_all=false) false end end + module ModuleToExtendBy + end + def self.random_data randomizer = Random.new(42) 1000.times{randomizer.rand} # Make sure we exhaust his first state of 624 random words @@ -192,6 +213,81 @@ module MarshalSpec set_swapped_class(nil) end + class ClassWithOverriddenName + def self.name + "Foo" + end + end + + class ModuleWithOverriddenName + def self.name + "Foo" + end + end + + class TimeWithOverriddenName < Time + def self.name + "Foo" + end + end + + class StructWithOverriddenName < Struct.new(:a) + def self.name + "Foo" + end + end + + class UserDefinedWithOverriddenName < UserDefined + def self.name + "Foo" + end + end + + class StringWithOverriddenName < String + def self.name + "Foo" + end + end + + class ArrayWithOverriddenName < Array + def self.name + "Foo" + end + end + + class HashWithOverriddenName < Hash + def self.name + "Foo" + end + end + + class RegexpWithOverriddenName < Regexp + def self.name + "Foo" + end + end + + module_eval(<<~ruby.force_encoding(Encoding::UTF_8)) + class MultibyteぁあぃいClass + end + + module MultibyteけげこごModule + end + + class MultibyteぁあぃいTime < Time + end + ruby + + class ObjectWithFreezeRaisingException < Object + def freeze + raise + end + end + + class ObjectWithoutFreeze < Object + undef freeze + end + DATA = { "nil" => [nil, "\004\b0"], "1..2" => [(1..2), diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb index 08261e65d7..74e21995ec 100644 --- a/spec/ruby/core/marshal/shared/load.rb +++ b/spec/ruby/core/marshal/shared/load.rb @@ -54,22 +54,77 @@ describe :marshal_load, shared: true do regexp.should.frozen? end + it "returns frozen structs" do + struct = Marshal.send(@method, Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true) + struct.should == MarshalSpec::StructToDump.new(1, 2) + struct.should.frozen? + end + it "returns frozen objects" do source_object = Object.new - source_object.instance_variable_set(:@foo, "bar") object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) object.should.frozen? - object.instance_variable_get(:@foo).should.frozen? + end + + describe "deep freezing" do + it "returns hashes with frozen keys and values" do + key = Object.new + value = Object.new + source_object = {key => value} + + hash = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + hash.size.should == 1 + hash.keys[0].should.frozen? + hash.values[0].should.frozen? + end + + it "returns arrays with frozen elements" do + object = Object.new + source_object = [object] + + array = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + array.size.should == 1 + array[0].should.frozen? + end + + it "returns structs with frozen members" do + object1 = Object.new + object2 = Object.new + source_object = MarshalSpec::StructToDump.new(object1, object2) + + struct = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + struct.a.should.frozen? + struct.b.should.frozen? + end + + it "returns objects with frozen instance variables" do + source_object = Object.new + instance_variable = Object.new + source_object.instance_variable_set(:@a, instance_variable) + + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + object.instance_variable_get(:@a).should != nil + object.instance_variable_get(:@a).should.frozen? + end + + it "deduplicates frozen strings" do + source_object = ["foo" + "bar", "foobar"] + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + + object[0].should equal(object[1]) + end end it "does not freeze modules" do - Marshal.send(@method, Marshal.dump(Kernel), freeze: true) + object = Marshal.send(@method, Marshal.dump(Kernel), freeze: true) + object.should_not.frozen? Kernel.should_not.frozen? end it "does not freeze classes" do - Marshal.send(@method, Marshal.dump(Object), freeze: true) + object = Marshal.send(@method, Marshal.dump(Object), freeze: true) + object.should_not.frozen? Object.should_not.frozen? end @@ -85,6 +140,48 @@ describe :marshal_load, shared: true do end end + ruby_bug "#19427", "3.1"..."3.3" do + ruby_bug "#19427", "3.1"..."3.4" do # https://bugs.ruby-lang.org/issues/19427#note-15 + it "returns frozen object having #_dump method" do + object = Marshal.send(@method, Marshal.dump(UserDefined.new), freeze: true) + object.should.frozen? + end + + it "returns frozen object responding to #marshal_dump and #marshal_load" do + object = Marshal.send(@method, Marshal.dump(UserMarshal.new), freeze: true) + object.should.frozen? + end + end + + it "returns frozen object extended by a module" do + object = Object.new + object.extend(MarshalSpec::ModuleToExtendBy) + + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? + end + end + + it "does not call freeze method" do + object = MarshalSpec::ObjectWithFreezeRaisingException.new + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? + end + + it "returns frozen object even if object does not respond to freeze method" do + object = MarshalSpec::ObjectWithoutFreeze.new + object = Marshal.send(@method, Marshal.dump(object), freeze: true) + object.should.frozen? + end + + it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do + source_object = UserString.new + source_object.instance_variable_set(:@foo, "bar") + + object = Marshal.send(@method, Marshal.dump(source_object), freeze: true) + object.should.frozen? + end + describe "when called with a proc" do it "call the proc with frozen objects" do arr = [] @@ -219,7 +316,19 @@ describe :marshal_load, shared: true do marshaled_obj.field2.should be_nil end - describe "that return an immediate value" do + it "loads the String in non US-ASCII and non UTF-8 encoding" do + source_object = UserDefinedString.new("a".encode("windows-1251")) + object = Marshal.send(@method, Marshal.dump(source_object)) + object.string.should == "a".encode("windows-1251") + end + + it "loads the String in multibyte encoding" do + source_object = UserDefinedString.new("a".encode("utf-32le")) + object = Marshal.send(@method, Marshal.dump(source_object)) + object.string.should == "a".encode("utf-32le") + end + + describe "that returns an immediate value" do it "loads an array containing an instance of the object, followed by multiple instances of another object" do str = "string" @@ -418,6 +527,38 @@ describe :marshal_load, shared: true do unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar' unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar' end + + ruby_version_is "3.1" do + it "preserves compare_by_identity behaviour" do + h = { a: 1 } + h.compare_by_identity + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should.compare_by_identity? + + h = { a: 1 } + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? + end + + it "preserves compare_by_identity behaviour for a Hash subclass" do + h = UserHash.new(a: 1) + h.compare_by_identity + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should.compare_by_identity? + + h = UserHash.new(a: 1) + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should_not.compare_by_identity? + end + end + + it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do + h = UserHash.new(a: 1) + h.compare_by_identity + + unmarshalled = Marshal.send(@method, Marshal.dump(h)) + unmarshalled.should.kind_of?(UserHash) + end end describe "for a Symbol" do @@ -499,6 +640,12 @@ describe :marshal_load, shared: true do Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj end + it "sets binmode if it is loading through StringIO stream" do + io = StringIO.new("\004\b:\vsymbol") + def io.binmode; raise "binmode"; end + -> { Marshal.load(io) }.should raise_error(RuntimeError, "binmode") + end + it "loads a string with an ivar" do str = Marshal.send(@method, "\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF") str.instance_variable_get("@foo").should == "bar" @@ -732,7 +879,7 @@ describe :marshal_load, shared: true do [Meths, MethsMore, Regexp] end - it "loads a extended_user_regexp having ivar" do + it "loads a Regexp subclass instance variables when it is extended with a module" do obj = UserRegexp.new('').extend(Meths) obj.instance_variable_set(:@noise, 'much') @@ -755,6 +902,14 @@ describe :marshal_load, shared: true do new_obj.instance_variable_get(:@regexp_ivar).should == [42] end end + + it "preserves Regexp encoding" do + source_object = Regexp.new("a".encode("utf-32le")) + regexp = Marshal.send(@method, Marshal.dump(source_object)) + + regexp.encoding.should == Encoding::UTF_32LE + regexp.source.should == "a".encode("utf-32le") + end end describe "for a Float" do @@ -888,17 +1043,47 @@ describe :marshal_load, shared: true do t1.should equal t2 end - it "loads the zone" do + it "keeps the local zone" do with_timezone 'AST', 3 do t = Time.local(2012, 1, 1) Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone end end - it "loads nanoseconds" do + it "keeps UTC zone" do + t = Time.now.utc + t2 = Marshal.send(@method, Marshal.dump(t)) + t2.should.utc? + end + + it "keeps the zone" do + t = nil + + with_timezone 'AST', 4 do + t = Time.local(2012, 1, 1) + end + + with_timezone 'EET', -2 do + Marshal.send(@method, Marshal.dump(t)).zone.should == 'AST' + end + end + + it "keeps utc offset" do + t = Time.new(2007,11,1,15,25,0, "+09:00") + t2 = Marshal.send(@method, Marshal.dump(t)) + t2.utc_offset.should == 32400 + end + + it "keeps nanoseconds" do t = Time.now Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec end + + it "does not add any additional instance variable" do + t = Time.now + t2 = Marshal.send(@method, Marshal.dump(t)) + t2.instance_variables.should.empty? + end end describe "for nil" do diff --git a/spec/ruby/core/matchdata/element_reference_spec.rb b/spec/ruby/core/matchdata/element_reference_spec.rb index 7c0f089bb4..0e0d3991cb 100644 --- a/spec/ruby/core/matchdata/element_reference_spec.rb +++ b/spec/ruby/core/matchdata/element_reference_spec.rb @@ -22,6 +22,11 @@ describe "MatchData#[]" do # length argument larger than number of match values is capped to match value length /(.)(.)(\d+)(\d)/.match("THX1138.")[3, 10].should == %w|113 8| + + /(.)(.)(\d+)(\d)/.match("THX1138.")[3, 0].should == [] + + /(.)(.)(\d+)(\d)/.match("THX1138.")[3, -1].should == nil + /(.)(.)(\d+)(\d)/.match("THX1138.")[3, -30].should == nil end it "supports ranges [start..end]" do diff --git a/spec/ruby/core/math/cos_spec.rb b/spec/ruby/core/math/cos_spec.rb index 3ba7a54c38..006afeb2cc 100644 --- a/spec/ruby/core/math/cos_spec.rb +++ b/spec/ruby/core/math/cos_spec.rb @@ -15,7 +15,6 @@ describe "Math.cos" do Math.cos(2*Math::PI).should be_close(1.0, TOLERANCE) end - it "raises a TypeError unless the argument is Numeric and has #to_f" do -> { Math.cos("test") }.should raise_error(TypeError) end @@ -24,14 +23,23 @@ describe "Math.cos" do Math.cos(nan_value).nan?.should be_true end - it "raises a TypeError if the argument is nil" do - -> { Math.cos(nil) }.should raise_error(TypeError) - end - - it "coerces its argument with #to_f" do - f = mock_numeric('8.2') - f.should_receive(:to_f).and_return(8.2) - Math.cos(f).should == Math.cos(8.2) + describe "coerces its argument with #to_f" do + it "coerces its argument with #to_f" do + f = mock_numeric('8.2') + f.should_receive(:to_f).and_return(8.2) + Math.cos(f).should == Math.cos(8.2) + end + + it "raises a TypeError if the given argument can't be converted to a Float" do + -> { Math.cos(nil) }.should raise_error(TypeError) + -> { Math.cos(:abc) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a Float" do + object = mock_numeric('mock-float') + object.should_receive(:to_f).and_raise(NoMethodError) + -> { Math.cos(object) }.should raise_error(NoMethodError) + end end end diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb index e6d51d1b4d..0f730fe013 100644 --- a/spec/ruby/core/method/parameters_spec.rb +++ b/spec/ruby/core/method/parameters_spec.rb @@ -20,6 +20,8 @@ describe "Method#parameters" do local_is_not_parameter = {} end + def underscore_parameters(_, _, _ = 1, *_, _:, _: 2, **_, &_); end + define_method(:one_optional_defined_method) {|x = 1|} end @@ -251,6 +253,20 @@ describe "Method#parameters" do m.method(:writer=).parameters.should == [[:req]] end + it "returns all parameters defined with the name _ as _" do + m = MethodSpecs::Methods.instance_method(:underscore_parameters) + m.parameters.should == [ + [:req, :_], + [:req, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + end + it "returns [[:rest]] for core methods with variable-length argument lists" do # delete! takes rest args "foo".method(:delete!).parameters.should == [[:rest]] diff --git a/spec/ruby/core/module/alias_method_spec.rb b/spec/ruby/core/module/alias_method_spec.rb index 5d3d0c23d9..391efa227a 100644 --- a/spec/ruby/core/module/alias_method_spec.rb +++ b/spec/ruby/core/module/alias_method_spec.rb @@ -81,6 +81,12 @@ describe "Module#alias_method" do -> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError) end + it "raises a NoMethodError if the given name raises a NoMethodError during type coercion using to_str" do + obj = mock("mock-name") + obj.should_receive(:to_str).and_raise(NoMethodError) + -> { @class.make_alias obj, :public_one }.should raise_error(NoMethodError) + end + it "is a public method" do Module.should have_public_instance_method(:alias_method, false) end diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb index 0c15629c08..027cf5a245 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -80,10 +80,23 @@ describe "Module#const_defined?" do ConstantSpecs::ClassA.const_defined?(:CS_CONSTX).should == false end - it "calls #to_str to convert the given name to a String" do - name = mock("ClassA") - name.should_receive(:to_str).and_return("ClassA") - ConstantSpecs.const_defined?(name).should == true + describe "converts the given name to a String using #to_str" do + it "calls #to_str to convert the given name to a String" do + name = mock("ClassA") + name.should_receive(:to_str).and_return("ClassA") + ConstantSpecs.const_defined?(name).should == true + end + + it "raises a TypeError if the given name can't be converted to a String" do + -> { ConstantSpecs.const_defined?(nil) }.should raise_error(TypeError) + -> { ConstantSpecs.const_defined?([]) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + name = mock("classA") + name.should_receive(:to_str).and_raise(NoMethodError) + -> { ConstantSpecs.const_defined?(name) }.should raise_error(NoMethodError) + end end it "special cases Object and checks it's included Modules" do diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index 841900cf87..11085c325b 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -71,7 +71,7 @@ describe "Module#refine" do Module.new do refine("foo") {} end - end.should raise_error(TypeError) + end.should raise_error(TypeError, "wrong argument type String (expected Class or Module)") end it "accepts a module as argument" do diff --git a/spec/ruby/core/proc/parameters_spec.rb b/spec/ruby/core/proc/parameters_spec.rb index 3a56b613cd..1ffaf17315 100644 --- a/spec/ruby/core/proc/parameters_spec.rb +++ b/spec/ruby/core/proc/parameters_spec.rb @@ -115,4 +115,30 @@ describe "Proc#parameters" do local_is_not_parameter = {} end.parameters.should == [[:rest, :args], [:block, :blk]] end + + it "returns all parameters defined with the name _ as _" do + proc = proc {|_, _, _ = 1, *_, _:, _: 2, **_, &_| } + proc.parameters.should == [ + [:opt, :_], + [:opt, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + + lambda = -> _, _, _ = 1, *_, _:, _: 2, **_, &_ {} + lambda.parameters.should == [ + [:req, :_], + [:req, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + end end diff --git a/spec/ruby/core/process/argv0_spec.rb b/spec/ruby/core/process/argv0_spec.rb new file mode 100644 index 0000000000..0d02248b6d --- /dev/null +++ b/spec/ruby/core/process/argv0_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' + +describe "Process.argv0" do + it "returns a String" do + Process.argv0.should be_kind_of(String) + end + + it "is the path given as the main script and the same as __FILE__" do + script = "fixtures/argv0.rb" + + Dir.chdir(File.dirname(__FILE__)) do + ruby_exe(script).should == "#{script}\n#{script}\nOK" + end + end + + it "returns a non frozen object" do + Process.argv0.should_not.frozen? + end + + it "returns every time the same object" do + Process.argv0.should.equal?(Process.argv0) + end +end diff --git a/spec/ruby/core/process/fixtures/argv0.rb b/spec/ruby/core/process/fixtures/argv0.rb new file mode 100644 index 0000000000..847a3e903e --- /dev/null +++ b/spec/ruby/core/process/fixtures/argv0.rb @@ -0,0 +1,6 @@ +puts Process.argv0 +puts __FILE__ + +if Process.argv0 == __FILE__ + print "OK" +end diff --git a/spec/ruby/core/queue/initialize_spec.rb b/spec/ruby/core/queue/initialize_spec.rb index c45abcd29d..c6c1ae63c5 100644 --- a/spec/ruby/core/queue/initialize_spec.rb +++ b/spec/ruby/core/queue/initialize_spec.rb @@ -22,16 +22,29 @@ describe "Queue#initialize" do q.should.empty? end - it "uses #to_a on the provided Enumerable" do - enumerable = MockObject.new('mock-enumerable') - enumerable.should_receive(:to_a).and_return([1, 2, 3]) - q = Queue.new(enumerable) - q.size.should == 3 - q.should_not.empty? - q.pop.should == 1 - q.pop.should == 2 - q.pop.should == 3 - q.should.empty? + describe "converts the given argument to an Array using #to_a" do + it "uses #to_a on the provided Enumerable" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:to_a).and_return([1, 2, 3]) + q = Queue.new(enumerable) + q.size.should == 3 + q.should_not.empty? + q.pop.should == 1 + q.pop.should == 2 + q.pop.should == 3 + q.should.empty? + end + + it "raises a TypeError if the given argument can't be converted to an Array" do + -> { Queue.new(42) }.should raise_error(TypeError) + -> { Queue.new(:abc) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to an Array" do + enumerable = MockObject.new('mock-enumerable') + enumerable.should_receive(:to_a).and_raise(NoMethodError) + -> { Queue.new(enumerable) }.should raise_error(NoMethodError) + end end it "raises TypeError if the provided Enumerable does not respond to #to_a" do diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 9b625c9963..81ea5a3846 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -44,12 +44,12 @@ describe "Range#size" do end ruby_version_is "3.2" do - it 'returns Float::INFINITY for all beginless ranges if the start is numeric' do + it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY end - it 'returns nil for all beginless ranges if the start is numeric' do + it 'returns nil for all beginless ranges if the end is not numeric' do (...'o').size.should == nil end diff --git a/spec/ruby/core/refinement/fixtures/classes.rb b/spec/ruby/core/refinement/fixtures/classes.rb new file mode 100644 index 0000000000..94324db47c --- /dev/null +++ b/spec/ruby/core/refinement/fixtures/classes.rb @@ -0,0 +1,10 @@ +module RefinementSpec + + module ModuleWithAncestors + include Module.new do + def indent(level) + " " * level + self + end + end + end +end diff --git a/spec/ruby/core/refinement/import_methods_spec.rb b/spec/ruby/core/refinement/import_methods_spec.rb index 1c526f5822..05973b2380 100644 --- a/spec/ruby/core/refinement/import_methods_spec.rb +++ b/spec/ruby/core/refinement/import_methods_spec.rb @@ -1,4 +1,5 @@ require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Refinement#import_methods" do ruby_version_is "3.1" do @@ -17,6 +18,229 @@ describe "Refinement#import_methods" do end end end + + it "throws an exception when argument is not a module" do + Module.new do + refine String do + -> { + import_methods Integer + }.should raise_error(TypeError, "wrong argument type Class (expected Module)") + end + end + end + + it "imports methods from multiple modules" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + str_utils_fancy = Module.new do + def indent_star(level) + "*" * level + self + end + end + + Module.new do + refine String do + import_methods str_utils, str_utils_fancy + "foo".indent(3).should == " foo" + "foo".indent_star(3).should == "***foo" + end + end + end + + it "imports a method defined in the last module if method with same name is defined in multiple modules" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + str_utils_fancy = Module.new do + def indent(level) + "*" * level + self + end + end + + Module.new do + refine String do + import_methods str_utils, str_utils_fancy + "foo".indent(3).should == "***foo" + end + end + end + + it "still imports methods of modules listed before a module that contains method not defined in Ruby" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + string_refined = Module.new do + refine String do + -> { + import_methods str_utils, Kernel + }.should raise_error(ArgumentError) + end + end + + Module.new do + using string_refined + "foo".indent(3).should == " foo" + end + end + end + + it "warns if a module includes/prepends some other module" do + module1 = Module.new do + end + + module2 = Module.new do + include module1 + end + + Module.new do + refine String do + -> { + import_methods module2 + }.should complain(/warning: #<Module:\w*> has ancestors, but Refinement#import_methods doesn't import their methods/) + end + end + + Module.new do + refine String do + -> { + import_methods RefinementSpec::ModuleWithAncestors + }.should complain(/warning: RefinementSpec::ModuleWithAncestors has ancestors, but Refinement#import_methods doesn't import their methods/) + end + end + end + + it "doesn't import methods from included/prepended modules" do + Module.new do + refine String do + suppress_warning { import_methods RefinementSpec::ModuleWithAncestors } + end + + using self + -> { + "foo".indent(3) + }.should raise_error(NoMethodError, /undefined method `indent' for ("foo":String|an instance of String)/) + end + end + + it "doesn't import any methods if one of the arguments is not a module" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + string_refined = Module.new do + refine String do + -> { + import_methods str_utils, Integer + }.should raise_error(TypeError) + end + end + + Module.new do + using string_refined + -> { + "foo".indent(3) + }.should raise_error(NoMethodError) + end + end + + it "imports methods from multiple modules so that methods see other's module's methods" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + end + + str_utils_normal = Module.new do + def indent_normal(level) + self.indent(level) + end + end + + Module.new do + refine String do + import_methods str_utils, str_utils_normal + end + + using self + "foo".indent_normal(3).should == " foo" + end + end + + it "imports methods from module so that methods can see each other" do + str_utils = Module.new do + def indent(level) + " " * level + self + end + + def indent_with_dot(level) + self.indent(level) + "." + end + end + + Module.new do + refine String do + import_methods str_utils + end + + using self + "foo".indent_with_dot(3).should == " foo." + end + end + + it "doesn't import module's class methods" do + str_utils = Module.new do + def self.indent(level) + " " * level + self + end + end + + Module.new do + refine String do + import_methods str_utils + end + + using self + -> { + String.indent(3) + }.should raise_error(NoMethodError, /undefined method `indent' for (String:Class|class String)/) + end + end + + it "imports module methods with super" do + class_to_refine = Class.new do + def foo(number) + 2 * number + end + end + + extension = Module.new do + def foo(number) + super * 2 + end + end + + refinement = Module.new do + refine class_to_refine do + import_methods extension + end + end + + Module.new do + using refinement + class_to_refine.new.foo(2).should == 8 + end end context "when methods are not defined in Ruby code" do @@ -29,6 +253,17 @@ describe "Refinement#import_methods" do end end end + + it "raises ArgumentError when importing methods from C extension" do + require 'zlib' + Module.new do + refine String do + -> { + import_methods Zlib + }.should raise_error(ArgumentError, /Can't import method which is not defined with Ruby code: Zlib#*/) + end + end + end end end end diff --git a/spec/ruby/core/regexp/shared/new.rb b/spec/ruby/core/regexp/shared/new.rb index 058a51b1aa..773882e495 100644 --- a/spec/ruby/core/regexp/shared/new.rb +++ b/spec/ruby/core/regexp/shared/new.rb @@ -432,6 +432,10 @@ describe :regexp_new_string, shared: true do Regexp.send(@method, "\056\x42\u3042\x52\076").should == /#{"\x2e\x42\u3042\x52\x3e"}/ end + it "accepts a multiple byte character which need not be escaped" do + Regexp.send(@method, "\�").should == /#{"�"}/ + end + it "raises a RegexpError if less than four digits are given for \\uHHHH" do -> { Regexp.send(@method, "\\" + "u304") }.should raise_error(RegexpError) end diff --git a/spec/ruby/core/regexp/try_convert_spec.rb b/spec/ruby/core/regexp/try_convert_spec.rb index be567e2130..e775dbe971 100644 --- a/spec/ruby/core/regexp/try_convert_spec.rb +++ b/spec/ruby/core/regexp/try_convert_spec.rb @@ -18,4 +18,10 @@ describe "Regexp.try_convert" do rex.should_receive(:to_regexp).and_return(/(p(a)t[e]rn)/) Regexp.try_convert(rex).should == /(p(a)t[e]rn)/ end + + it "raises a TypeError if the object does not return an Regexp from #to_regexp" do + obj = mock("regexp") + obj.should_receive(:to_regexp).and_return("string") + -> { Regexp.try_convert(obj) }.should raise_error(TypeError, "can't convert MockObject to Regexp (MockObject#to_regexp gives String)") + end end diff --git a/spec/ruby/core/string/append_spec.rb b/spec/ruby/core/string/append_spec.rb index e001257621..8497ce8262 100644 --- a/spec/ruby/core/string/append_spec.rb +++ b/spec/ruby/core/string/append_spec.rb @@ -5,6 +5,7 @@ require_relative 'shared/concat' describe "String#<<" do it_behaves_like :string_concat, :<< it_behaves_like :string_concat_encoding, :<< + it_behaves_like :string_concat_type_coercion, :<< it "raises an ArgumentError when given the incorrect number of arguments" do -> { "hello".send(:<<) }.should raise_error(ArgumentError) diff --git a/spec/ruby/core/string/concat_spec.rb b/spec/ruby/core/string/concat_spec.rb index 5f6daadad7..6f487eaa3a 100644 --- a/spec/ruby/core/string/concat_spec.rb +++ b/spec/ruby/core/string/concat_spec.rb @@ -5,6 +5,7 @@ require_relative 'shared/concat' describe "String#concat" do it_behaves_like :string_concat, :concat it_behaves_like :string_concat_encoding, :concat + it_behaves_like :string_concat_type_coercion, :concat it "takes multiple arguments" do str = "hello " diff --git a/spec/ruby/core/string/plus_spec.rb b/spec/ruby/core/string/plus_spec.rb index 5ff198f07e..9da17451c6 100644 --- a/spec/ruby/core/string/plus_spec.rb +++ b/spec/ruby/core/string/plus_spec.rb @@ -3,6 +3,9 @@ require_relative 'fixtures/classes' require_relative 'shared/concat' describe "String#+" do + it_behaves_like :string_concat_encoding, :+ + it_behaves_like :string_concat_type_coercion, :+ + it "returns a new string containing the given string concatenated to self" do ("" + "").should == "" ("" + "Hello").should == "Hello" @@ -31,6 +34,4 @@ describe "String#+" do ("hello" + StringSpecs::MyString.new("foo")).should be_an_instance_of(String) ("hello" + StringSpecs::MyString.new("")).should be_an_instance_of(String) end - - it_behaves_like :string_concat_encoding, :+ end diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb index 54ac1035d3..ee5ef2a98f 100644 --- a/spec/ruby/core/string/shared/concat.rb +++ b/spec/ruby/core/string/shared/concat.rb @@ -5,18 +5,6 @@ describe :string_concat, shared: true do str.should == "hello world" end - it "converts the given argument to a String using to_str" do - obj = mock('world!') - obj.should_receive(:to_str).and_return("world!") - a = 'hello '.send(@method, obj) - a.should == 'hello world!' - end - - it "raises a TypeError if the given argument can't be converted to a String" do - -> { 'hello '.send(@method, []) }.should raise_error(TypeError) - -> { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError) - end - it "raises a FrozenError when self is frozen" do a = "hello" a.freeze @@ -148,3 +136,23 @@ describe :string_concat_encoding, shared: true do end end end + +describe :string_concat_type_coercion, shared: true do + it "converts the given argument to a String using to_str" do + obj = mock('world!') + obj.should_receive(:to_str).and_return("world!") + a = 'hello '.send(@method, obj) + a.should == 'hello world!' + end + + it "raises a TypeError if the given argument can't be converted to a String" do + -> { 'hello '.send(@method, []) }.should raise_error(TypeError) + -> { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + obj = mock('world!') + obj.should_receive(:to_str).and_raise(NoMethodError) + -> { 'hello '.send(@method, obj) }.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/core/string/try_convert_spec.rb b/spec/ruby/core/string/try_convert_spec.rb index 84415c4a75..72ce5dd8b2 100644 --- a/spec/ruby/core/string/try_convert_spec.rb +++ b/spec/ruby/core/string/try_convert_spec.rb @@ -39,7 +39,7 @@ describe "String.try_convert" do it "sends #to_str to the argument and raises TypeError if it's not a kind of String" do obj = mock("to_str") obj.should_receive(:to_str).and_return(Object.new) - -> { String.try_convert obj }.should raise_error(TypeError) + -> { String.try_convert obj }.should raise_error(TypeError, "can't convert MockObject to String (MockObject#to_str gives Object)") end it "does not rescue exceptions raised by #to_str" do diff --git a/spec/ruby/core/string/unpack/b_spec.rb b/spec/ruby/core/string/unpack/b_spec.rb index 2cf5ebad34..5c53eff721 100644 --- a/spec/ruby/core/string/unpack/b_spec.rb +++ b/spec/ruby/core/string/unpack/b_spec.rb @@ -88,7 +88,9 @@ describe "String#unpack with format 'B'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x80\x00".unpack("B\x00B").should == ["1", "0"] + suppress_warning do + "\x80\x00".unpack("B\x00B").should == ["1", "0"] + end end end @@ -194,7 +196,9 @@ describe "String#unpack with format 'b'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x01\x00".unpack("b\x00b").should == ["1", "0"] + suppress_warning do + "\x01\x00".unpack("b\x00b").should == ["1", "0"] + end end end diff --git a/spec/ruby/core/string/unpack/c_spec.rb b/spec/ruby/core/string/unpack/c_spec.rb index dbcbacc74d..c2bf813954 100644 --- a/spec/ruby/core/string/unpack/c_spec.rb +++ b/spec/ruby/core/string/unpack/c_spec.rb @@ -37,7 +37,9 @@ describe :string_unpack_8bit, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "abc".unpack(unpack_format("\000", 2)).should == [97, 98] + suppress_warning do + "abc".unpack(unpack_format("\000", 2)).should == [97, 98] + end end end diff --git a/spec/ruby/core/string/unpack/h_spec.rb b/spec/ruby/core/string/unpack/h_spec.rb index ee08d20926..19c4d63664 100644 --- a/spec/ruby/core/string/unpack/h_spec.rb +++ b/spec/ruby/core/string/unpack/h_spec.rb @@ -58,7 +58,9 @@ describe "String#unpack with format 'H'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x01\x10".unpack("H\x00H").should == ["0", "1"] + suppress_warning do + "\x01\x10".unpack("H\x00H").should == ["0", "1"] + end end end @@ -133,7 +135,9 @@ describe "String#unpack with format 'h'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x01\x10".unpack("h\x00h").should == ["1", "0"] + suppress_warning do + "\x01\x10".unpack("h\x00h").should == ["1", "0"] + end end end diff --git a/spec/ruby/core/string/unpack/shared/float.rb b/spec/ruby/core/string/unpack/shared/float.rb index ccddf94f99..93282bf4c9 100644 --- a/spec/ruby/core/string/unpack/shared/float.rb +++ b/spec/ruby/core/string/unpack/shared/float.rb @@ -58,8 +58,10 @@ describe :string_unpack_float_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - array = "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2)) - array.should == [2.9000000953674316, 1.399999976158142] + suppress_warning do + array = "\x9a\x999@33\xb3?".unpack(unpack_format("\000", 2)) + array.should == [2.9000000953674316, 1.399999976158142] + end end end @@ -135,8 +137,10 @@ describe :string_unpack_float_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - array = "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2)) - array.should == [2.9000000953674316, 1.399999976158142] + suppress_warning do + array = "@9\x99\x9a?\xb333".unpack(unpack_format("\000", 2)) + array.should == [2.9000000953674316, 1.399999976158142] + end end end @@ -215,7 +219,9 @@ describe :string_unpack_double_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] + suppress_warning do + "333333\x07@ffffff\xf6?".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] + end end end @@ -293,7 +299,9 @@ describe :string_unpack_double_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] + suppress_warning do + "@\x07333333?\xf6ffffff".unpack(unpack_format("\000", 2)).should == [2.9, 1.4] + end end end diff --git a/spec/ruby/core/string/unpack/shared/integer.rb b/spec/ruby/core/string/unpack/shared/integer.rb index ba4f149dad..d71a2cf00d 100644 --- a/spec/ruby/core/string/unpack/shared/integer.rb +++ b/spec/ruby/core/string/unpack/shared/integer.rb @@ -34,7 +34,9 @@ describe :string_unpack_16bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "abcd".unpack(unpack_format("\000", 2)).should == [25185, 25699] + suppress_warning do + "abcd".unpack(unpack_format("\000", 2)).should == [25185, 25699] + end end end @@ -97,7 +99,9 @@ describe :string_unpack_16bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "badc".unpack(unpack_format("\000", 2)).should == [25185, 25699] + suppress_warning do + "badc".unpack(unpack_format("\000", 2)).should == [25185, 25699] + end end end @@ -161,7 +165,9 @@ describe :string_unpack_32bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "abcdefgh".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] + suppress_warning do + "abcdefgh".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] + end end end @@ -225,7 +231,9 @@ describe :string_unpack_32bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "dcbahgfe".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] + suppress_warning do + "dcbahgfe".unpack(unpack_format("\000", 2)).should == [1684234849, 1751606885] + end end end @@ -285,8 +293,10 @@ describe :string_unpack_64bit_le, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - array = "abcdefghabghefcd".unpack(unpack_format("\000", 2)) - array.should == [7523094288207667809, 7233738012216484449] + suppress_warning do + array = "abcdefghabghefcd".unpack(unpack_format("\000", 2)) + array.should == [7523094288207667809, 7233738012216484449] + end end end @@ -357,8 +367,10 @@ describe :string_unpack_64bit_be, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - array = "hgfedcbadcfehgba".unpack(unpack_format("\000", 2)) - array.should == [7523094288207667809, 7233738012216484449] + suppress_warning do + array = "hgfedcbadcfehgba".unpack(unpack_format("\000", 2)) + array.should == [7523094288207667809, 7233738012216484449] + end end end diff --git a/spec/ruby/core/string/unpack/shared/unicode.rb b/spec/ruby/core/string/unpack/shared/unicode.rb index ce1f29fe87..9fe07f53ae 100644 --- a/spec/ruby/core/string/unpack/shared/unicode.rb +++ b/spec/ruby/core/string/unpack/shared/unicode.rb @@ -52,7 +52,9 @@ describe :string_unpack_unicode, shared: true do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x01\x02".unpack("U\x00U").should == [1, 2] + suppress_warning do + "\x01\x02".unpack("U\x00U").should == [1, 2] + end end end diff --git a/spec/ruby/core/string/unpack/w_spec.rb b/spec/ruby/core/string/unpack/w_spec.rb index b213b32921..6a1cff1965 100644 --- a/spec/ruby/core/string/unpack/w_spec.rb +++ b/spec/ruby/core/string/unpack/w_spec.rb @@ -17,7 +17,9 @@ describe "String#unpack with directive 'w'" do ruby_version_is ""..."3.3" do it "ignores NULL bytes between directives" do - "\x01\x02\x03".unpack("w\x00w").should == [1, 2] + suppress_warning do + "\x01\x02\x03".unpack("w\x00w").should == [1, 2] + end end end diff --git a/spec/ruby/core/struct/fixtures/classes.rb b/spec/ruby/core/struct/fixtures/classes.rb index 6d620f9060..bf838d05df 100644 --- a/spec/ruby/core/struct/fixtures/classes.rb +++ b/spec/ruby/core/struct/fixtures/classes.rb @@ -13,6 +13,12 @@ module StructClasses end end + class StructWithOverriddenName < Struct.new(:a) + def self.name + "A" + end + end + class SubclassX < Struct end diff --git a/spec/ruby/core/struct/keyword_init_spec.rb b/spec/ruby/core/struct/keyword_init_spec.rb index 061f4c56e0..8de4c14351 100644 --- a/spec/ruby/core/struct/keyword_init_spec.rb +++ b/spec/ruby/core/struct/keyword_init_spec.rb @@ -17,5 +17,24 @@ ruby_version_is "3.1" do struct = Struct.new(:arg) struct.keyword_init?.should be_nil end + + it "returns nil for a struct that does specify keyword_init to be nil" do + struct = Struct.new(:arg, keyword_init: nil) + struct.keyword_init?.should be_nil + end + + it "returns true for any truthy value, not just for true" do + struct = Struct.new(:arg, keyword_init: 1) + struct.keyword_init?.should be_true + + struct = Struct.new(:arg, keyword_init: "") + struct.keyword_init?.should be_true + + struct = Struct.new(:arg, keyword_init: []) + struct.keyword_init?.should be_true + + struct = Struct.new(:arg, keyword_init: {}) + struct.keyword_init?.should be_true + end end end diff --git a/spec/ruby/core/struct/new_spec.rb b/spec/ruby/core/struct/new_spec.rb index 4aeaa066e1..8758051a81 100644 --- a/spec/ruby/core/struct/new_spec.rb +++ b/spec/ruby/core/struct/new_spec.rb @@ -47,6 +47,11 @@ describe "Struct.new" do Struct.const_defined?("Animal2").should be_false end + it "allows non-ASCII member name" do + name = "r\xe9sum\xe9".force_encoding(Encoding::ISO_8859_1).to_sym + struct = Struct.new(name) + struct.new("foo").send(name).should == "foo" + end it "fails with invalid constant name as first argument" do -> { Struct.new('animal', :name, :legs, :eyeballs) }.should raise_error(NameError) diff --git a/spec/ruby/core/struct/shared/inspect.rb b/spec/ruby/core/struct/shared/inspect.rb index e65a4fb45d..1a0fb6a6b2 100644 --- a/spec/ruby/core/struct/shared/inspect.rb +++ b/spec/ruby/core/struct/shared/inspect.rb @@ -25,4 +25,16 @@ describe :struct_inspect, shared: true do m::Foo.new("").send(@method).should == '#<struct a="">' end + + it "does not call #name method" do + struct = StructClasses::StructWithOverriddenName.new("") + struct.send(@method).should == '#<struct StructClasses::StructWithOverriddenName a="">' + end + + it "does not call #name method when struct is anonymous" do + struct = Struct.new(:a) + def struct.name; "A"; end + + struct.new("").send(@method).should == '#<struct a="">' + end end diff --git a/spec/ruby/core/symbol/inspect_spec.rb b/spec/ruby/core/symbol/inspect_spec.rb index 58402ab261..6dbb36c2ad 100644 --- a/spec/ruby/core/symbol/inspect_spec.rb +++ b/spec/ruby/core/symbol/inspect_spec.rb @@ -5,6 +5,8 @@ describe "Symbol#inspect" do fred: ":fred", :fred? => ":fred?", :fred! => ":fred!", + :BAD! => ":BAD!", + :_BAD! => ":_BAD!", :$ruby => ":$ruby", :@ruby => ":@ruby", :@@ruby => ":@@ruby", diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index 727fdf92c2..69ec7bee5d 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -431,6 +431,10 @@ describe "Time.new with a timezone argument" do time.zone.should == nil end + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.new(2000, 1, 1, 0, 0, 0, in: "W").utc_offset.should == 3600 * -10 + end + it "could be a timezone object" do zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) @@ -445,13 +449,29 @@ describe "Time.new with a timezone argument" do time.zone.should == zone end + it "allows omitting minor arguments" do + Time.new(2000, 1, 1, 12, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 1, "+05:00") + Time.new(2000, 1, 1, 12, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 0, "+05:00") + Time.new(2000, 1, 1, 12, in: "+05:00").should == Time.new(2000, 1, 1, 12, 0, 0, "+05:00") + Time.new(2000, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(in: "+05:00").should be_close(Time.now.getlocal("+05:00"), TIME_TOLERANCE) + end + + it "converts to a provided timezone if all the positional arguments are omitted" do + Time.new(in: "+05:00").utc_offset.should == 5*3600 + end + it "raises ArgumentError if format is invalid" do -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError) -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError) end it "raises ArgumentError if two offset arguments are given" do - -> { Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") }.should raise_error(ArgumentError) + -> { + Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") + }.should raise_error(ArgumentError, "timezone argument given as positional and keyword arguments") end end end diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb index 2b2e53a17c..d47f00723e 100644 --- a/spec/ruby/core/time/now_spec.rb +++ b/spec/ruby/core/time/now_spec.rb @@ -4,48 +4,54 @@ require_relative 'shared/now' describe "Time.now" do it_behaves_like :time_now, :now - describe ":in keyword argument" do - it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do - time = Time.now(in: "+05:00") + ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485 + describe ":in keyword argument" do + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.now(in: "+05:00") - time.utc_offset.should == 5*60*60 - time.zone.should == nil + time.utc_offset.should == 5*60*60 + time.zone.should == nil - time = Time.now(in: "-09:00") + time = Time.now(in: "-09:00") - time.utc_offset.should == -9*60*60 - time.zone.should == nil - end + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end - it "could be UTC offset as a number of seconds" do - time = Time.now(in: 5*60*60) + it "could be UTC offset as a number of seconds" do + time = Time.now(in: 5*60*60) - time.utc_offset.should == 5*60*60 - time.zone.should == nil + time.utc_offset.should == 5*60*60 + time.zone.should == nil - time = Time.now(in: -9*60*60) + time = Time.now(in: -9*60*60) - time.utc_offset.should == -9*60*60 - time.zone.should == nil - end + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end - it "could be a timezone object" do - zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") - time = Time.now(in: zone) + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.now(in: "W").utc_offset.should == 3600 * -10 + end - time.utc_offset.should == 5*3600+30*60 - time.zone.should == zone + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.now(in: zone) - zone = TimeSpecs::TimezoneWithName.new(name: "PST") - time = Time.now(in: zone) + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone - time.utc_offset.should == -9*60*60 - time.zone.should == zone - end + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.now(in: zone) + + time.utc_offset.should == -9*60*60 + time.zone.should == zone + end - it "raises ArgumentError if format is invalid" do - -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError) - -> { Time.now(in: "ABC") }.should raise_error(ArgumentError) + it "raises ArgumentError if format is invalid" do + -> { Time.now(in: "+09:99") }.should raise_error(ArgumentError) + -> { Time.now(in: "ABC") }.should raise_error(ArgumentError) + end end end end diff --git a/spec/ruby/core/time/utc_spec.rb b/spec/ruby/core/time/utc_spec.rb index 809accc809..566509fd33 100644 --- a/spec/ruby/core/time/utc_spec.rb +++ b/spec/ruby/core/time/utc_spec.rb @@ -21,12 +21,22 @@ describe "Time#utc?" do Time.new(2022, 1, 1, 0, 0, 0, "UTC").utc?.should == true Time.now.localtime("UTC").utc?.should == true Time.at(Time.now, in: 'UTC').utc?.should == true + + ruby_version_is "3.1" do + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").utc?.should == true + Time.now(in: "UTC").utc?.should == true + end end it "does treat time with Z offset as UTC" do Time.new(2022, 1, 1, 0, 0, 0, "Z").utc?.should == true Time.now.localtime("Z").utc?.should == true Time.at(Time.now, in: 'Z').utc?.should == true + + ruby_version_is "3.1" do + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").utc?.should == true + Time.now(in: "Z").utc?.should == true + end end ruby_version_is "3.1" do diff --git a/spec/ruby/core/time/zone_spec.rb b/spec/ruby/core/time/zone_spec.rb index cbb0977f24..63c92602d1 100644 --- a/spec/ruby/core/time/zone_spec.rb +++ b/spec/ruby/core/time/zone_spec.rb @@ -74,6 +74,17 @@ describe "Time#zone" do Time.now.localtime("-00:00").zone.should == "UTC" Time.at(Time.now, in: '-00:00').zone.should == "UTC" end + + ruby_version_is "3.1" do + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").zone.should == "UTC" + + Time.now(in: 'UTC').zone.should == "UTC" + Time.now(in: 'Z').zone.should == "UTC" + + Time.at(Time.now, in: 'UTC').zone.should == "UTC" + Time.at(Time.now, in: 'Z').zone.should == "UTC" + end end platform_is_not :aix, :windows do diff --git a/spec/ruby/core/warning/warn_spec.rb b/spec/ruby/core/warning/warn_spec.rb index 5ccff3aa2b..e2fcfbf93f 100644 --- a/spec/ruby/core/warning/warn_spec.rb +++ b/spec/ruby/core/warning/warn_spec.rb @@ -73,6 +73,66 @@ describe "Warning.warn" do $VERBOSE = verbose end end + + it "warns when category is :deprecated and Warning[:deprecated] is true" do + warn_deprecated = Warning[:deprecated] + Warning[:deprecated] = true + begin + -> { + Warning.warn("foo", category: :deprecated) + }.should complain("foo") + ensure + Warning[:deprecated] = warn_deprecated + end + end + + it "warns when category is :experimental and Warning[:experimental] is true" do + warn_experimental = Warning[:experimental] + Warning[:experimental] = true + begin + -> { + Warning.warn("foo", category: :experimental) + }.should complain("foo") + ensure + Warning[:experimental] = warn_experimental + end + end + + it "doesn't print message when category is :deprecated but Warning[:deprecated] is false" do + warn_deprecated = Warning[:deprecated] + Warning[:deprecated] = false + begin + -> { + Warning.warn("foo", category: :deprecated) + }.should_not complain + ensure + Warning[:deprecated] = warn_deprecated + end + end + + it "doesn't print message when category is :experimental but Warning[:experimental] is false" do + warn_experimental = Warning[:experimental] + Warning[:experimental] = false + begin + -> { + Warning.warn("foo", category: :experimental) + }.should_not complain + ensure + Warning[:experimental] = warn_experimental + end + end + + it "prints the message when VERBOSE is false" do + -> { Warning.warn("foo") }.should complain("foo") + end + + it "prints the message when VERBOSE is nil" do + -> { Warning.warn("foo") }.should complain("foo", verbose: nil) + end + + it "prints the message when VERBOSE is true" do + -> { Warning.warn("foo") }.should complain("foo", verbose: true) + end end ruby_version_is ''...'3.0' do |