From d562663e4098801c1d7fa7c64a335ea71231a598 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 25 Apr 2023 17:04:25 +0200 Subject: Update to ruby/spec@7f69c86 --- spec/ruby/.mspec.constants | 1 + .../fixtures/freeze_flag_required_diff_enc.rb | 1 - spec/ruby/core/array/fixtures/classes.rb | 10 + spec/ruby/core/array/pack/c_spec.rb | 4 +- spec/ruby/core/array/pack/shared/float.rb | 16 +- spec/ruby/core/array/pack/shared/integer.rb | 36 ++-- spec/ruby/core/array/pack/shared/unicode.rb | 4 +- spec/ruby/core/array/pack/w_spec.rb | 4 +- spec/ruby/core/array/plus_spec.rb | 21 +- spec/ruby/core/array/product_spec.rb | 5 + spec/ruby/core/array/sample_spec.rb | 4 + spec/ruby/core/array/shuffle_spec.rb | 14 ++ spec/ruby/core/array/try_convert_spec.rb | 2 +- spec/ruby/core/complex/inspect_spec.rb | 19 ++ spec/ruby/core/complex/to_s_spec.rb | 10 + spec/ruby/core/data/define_spec.rb | 22 +- spec/ruby/core/dir/fchdir_spec.rb | 12 +- spec/ruby/core/dir/home_spec.rb | 6 + spec/ruby/core/enumerable/shared/inject.rb | 9 + spec/ruby/core/enumerable/tally_spec.rb | 13 ++ spec/ruby/core/fiber/inspect_spec.rb | 38 ++++ spec/ruby/core/file/dirname_spec.rb | 39 ++-- spec/ruby/core/hash/try_convert_spec.rb | 2 +- spec/ruby/core/integer/bit_and_spec.rb | 2 +- spec/ruby/core/integer/bit_or_spec.rb | 6 +- spec/ruby/core/integer/bit_xor_spec.rb | 4 +- spec/ruby/core/integer/ceildiv_spec.rb | 22 ++ spec/ruby/core/integer/try_convert_spec.rb | 12 +- spec/ruby/core/io/gets_spec.rb | 29 +++ spec/ruby/core/io/shared/write.rb | 55 +++++ spec/ruby/core/io/syswrite_spec.rb | 1 + spec/ruby/core/io/try_convert_spec.rb | 2 +- spec/ruby/core/io/write_nonblock_spec.rb | 1 + spec/ruby/core/io/write_spec.rb | 1 + spec/ruby/core/kernel/Complex_spec.rb | 4 + .../core/kernel/define_singleton_method_spec.rb | 6 + spec/ruby/core/kernel/method_spec.rb | 21 +- spec/ruby/core/kernel/require_spec.rb | 16 ++ spec/ruby/core/kernel/shared/require.rb | 14 -- spec/ruby/core/marshal/dump_spec.rb | 200 +++++++++++++++++- spec/ruby/core/marshal/fixtures/marshal_data.rb | 96 +++++++++ spec/ruby/core/marshal/shared/load.rb | 201 +++++++++++++++++- spec/ruby/core/matchdata/element_reference_spec.rb | 5 + spec/ruby/core/math/cos_spec.rb | 26 ++- spec/ruby/core/method/parameters_spec.rb | 16 ++ spec/ruby/core/module/alias_method_spec.rb | 6 + spec/ruby/core/module/const_defined_spec.rb | 21 +- spec/ruby/core/module/refine_spec.rb | 2 +- spec/ruby/core/proc/parameters_spec.rb | 26 +++ spec/ruby/core/process/argv0_spec.rb | 23 ++ spec/ruby/core/process/fixtures/argv0.rb | 6 + spec/ruby/core/queue/initialize_spec.rb | 33 ++- spec/ruby/core/range/size_spec.rb | 4 +- spec/ruby/core/refinement/fixtures/classes.rb | 10 + spec/ruby/core/refinement/import_methods_spec.rb | 235 +++++++++++++++++++++ spec/ruby/core/regexp/shared/new.rb | 4 + spec/ruby/core/regexp/try_convert_spec.rb | 6 + spec/ruby/core/string/append_spec.rb | 1 + spec/ruby/core/string/concat_spec.rb | 1 + spec/ruby/core/string/plus_spec.rb | 5 +- spec/ruby/core/string/shared/concat.rb | 32 +-- spec/ruby/core/string/try_convert_spec.rb | 2 +- spec/ruby/core/string/unpack/b_spec.rb | 8 +- spec/ruby/core/string/unpack/c_spec.rb | 4 +- spec/ruby/core/string/unpack/h_spec.rb | 8 +- spec/ruby/core/string/unpack/shared/float.rb | 20 +- spec/ruby/core/string/unpack/shared/integer.rb | 28 ++- spec/ruby/core/string/unpack/shared/unicode.rb | 4 +- spec/ruby/core/string/unpack/w_spec.rb | 4 +- spec/ruby/core/struct/fixtures/classes.rb | 6 + spec/ruby/core/struct/keyword_init_spec.rb | 19 ++ spec/ruby/core/struct/new_spec.rb | 5 + spec/ruby/core/struct/shared/inspect.rb | 12 ++ spec/ruby/core/symbol/inspect_spec.rb | 2 + spec/ruby/core/time/new_spec.rb | 22 +- spec/ruby/core/time/now_spec.rb | 66 +++--- spec/ruby/core/time/utc_spec.rb | 10 + spec/ruby/core/time/zone_spec.rb | 11 + spec/ruby/core/warning/warn_spec.rb | 60 ++++++ .../freeze_magic_comment_required_diff_enc.rb | 1 - spec/ruby/language/fixtures/variables.rb | 72 +++++++ spec/ruby/language/pattern_matching_spec.rb | 16 +- spec/ruby/language/predefined_spec.rb | 4 + spec/ruby/language/proc_spec.rb | 12 ++ spec/ruby/language/singleton_class_spec.rb | 17 ++ spec/ruby/language/variables_spec.rb | 80 +++++++ spec/ruby/library/fiber/current_spec.rb | 6 + spec/ruby/library/io-wait/fixtures/classes.rb | 12 ++ spec/ruby/library/io-wait/wait_spec.rb | 147 +++++++++++++ spec/ruby/library/monitor/exit_spec.rb | 10 + spec/ruby/library/objectspace/dump_all_spec.rb | 114 ++++++++++ spec/ruby/library/objectspace/dump_spec.rb | 74 +++++++ spec/ruby/library/set/shared/inspect.rb | 8 + spec/ruby/library/stringio/set_encoding_spec.rb | 8 + spec/ruby/library/stringio/shared/write.rb | 21 ++ spec/ruby/optional/capi/ext/io_spec.c | 59 ++++++ spec/ruby/optional/capi/io_spec.rb | 91 ++++++++ spec/ruby/optional/capi/time_spec.rb | 5 + spec/ruby/shared/rational/Rational.rb | 4 + 99 files changed, 2316 insertions(+), 183 deletions(-) create mode 100644 spec/ruby/core/fiber/inspect_spec.rb create mode 100644 spec/ruby/core/integer/ceildiv_spec.rb create mode 100644 spec/ruby/core/process/argv0_spec.rb create mode 100644 spec/ruby/core/process/fixtures/argv0.rb create mode 100644 spec/ruby/core/refinement/fixtures/classes.rb create mode 100644 spec/ruby/library/io-wait/fixtures/classes.rb create mode 100644 spec/ruby/library/io-wait/wait_spec.rb create mode 100644 spec/ruby/library/monitor/exit_spec.rb create mode 100644 spec/ruby/library/objectspace/dump_all_spec.rb create mode 100644 spec/ruby/library/objectspace/dump_spec.rb (limited to 'spec/ruby') diff --git a/spec/ruby/.mspec.constants b/spec/ruby/.mspec.constants index 5dd477eb66..6e09a44362 100644 --- a/spec/ruby/.mspec.constants +++ b/spec/ruby/.mspec.constants @@ -204,6 +204,7 @@ UserArray UserCustomConstructorString UserDefined UserDefinedImmediate +UserDefinedString UserDefinedWithIvar UserHash UserHashInitParams diff --git a/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb index fd92a126bc..df4b952c46 100644 --- a/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb +++ b/spec/ruby/command_line/fixtures/freeze_flag_required_diff_enc.rb @@ -1,4 +1,3 @@ # encoding: euc-jp # built-in for old regexp option $second_literal_id = "abc".object_id - 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#\z/ + end + + it "is created for a Fiber which did not run yet" do + inspected = Fiber.new {}.inspect + inspected.should =~ /\A#\z/ + end + + it "is resumed for a Fiber which was resumed" do + inspected = Fiber.new { Fiber.current.inspect }.resume + inspected.should =~ /\A#\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#\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#\z/ + end + + it "is terminated for a Fiber which has terminated" do + inspected = Fiber.new {}.tap(&:resume).inspect + inspected.should =~ /\A#\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: # 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 == '#' end + + it "does not call #name method" do + struct = StructClasses::StructWithOverriddenName.new("") + struct.send(@method).should == '#' + 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 == '#' + 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 diff --git a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb index 7a1f872e05..f72a32e879 100644 --- a/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb +++ b/spec/ruby/language/fixtures/freeze_magic_comment_required_diff_enc.rb @@ -2,4 +2,3 @@ # frozen_string_literal: true $second_literal_id = "abc".object_id - diff --git a/spec/ruby/language/fixtures/variables.rb b/spec/ruby/language/fixtures/variables.rb index 07265dbb2b..527caa7a78 100644 --- a/spec/ruby/language/fixtures/variables.rb +++ b/spec/ruby/language/fixtures/variables.rb @@ -82,4 +82,76 @@ module VariablesSpecs def self.false false end + + class EvalOrder + attr_reader :order + + def initialize + @order = [] + end + + def reset + @order = [] + end + + def foo + self << "foo" + FooClass.new(self) + end + + def bar + self << "bar" + BarClass.new(self) + end + + def a + self << "a" + end + + def b + self << "b" + end + + def node + self << "node" + + node = Node.new + node.left = Node.new + node.left.right = Node.new + + node + end + + def <<(value) + order << value + end + + class FooClass + attr_reader :evaluator + + def initialize(evaluator) + @evaluator = evaluator + end + + def []=(_index, _value) + evaluator << "foo[]=" + end + end + + class BarClass + attr_reader :evaluator + + def initialize(evaluator) + @evaluator = evaluator + end + + def baz=(_value) + evaluator << "bar.baz=" + end + end + + class Node + attr_accessor :left, :right + end + end end diff --git a/spec/ruby/language/pattern_matching_spec.rb b/spec/ruby/language/pattern_matching_spec.rb index f3cc86fa0b..e4b7a5105e 100644 --- a/spec/ruby/language/pattern_matching_spec.rb +++ b/spec/ruby/language/pattern_matching_spec.rb @@ -205,7 +205,7 @@ describe "Pattern matching" do in [] end RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `in'/) + }.should raise_error(SyntaxError, /syntax error, unexpected `in'|\(eval\):3: syntax error, unexpected keyword_in/) -> { eval <<~RUBY @@ -214,7 +214,7 @@ describe "Pattern matching" do when 1 == 1 end RUBY - }.should raise_error(SyntaxError, /syntax error, unexpected `when'/) + }.should raise_error(SyntaxError, /syntax error, unexpected `when'|\(eval\):3: syntax error, unexpected keyword_when/) end it "checks patterns until the first matching" do @@ -251,6 +251,18 @@ describe "Pattern matching" do }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) end + it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do + evals = 0 + -> { + eval <<~RUBY + case (evals += 1; [0, 1]) + in [0] + end + RUBY + }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) + evals.should == 1 + end + it "does not allow calculation or method calls in a pattern" do -> { eval <<~RUBY diff --git a/spec/ruby/language/predefined_spec.rb b/spec/ruby/language/predefined_spec.rb index d1cda25918..f6cec5fa75 100644 --- a/spec/ruby/language/predefined_spec.rb +++ b/spec/ruby/language/predefined_spec.rb @@ -979,6 +979,10 @@ describe "Global variable $VERBOSE" do $VERBOSE = @verbose end + it "is false by default" do + $VERBOSE.should be_false + end + it "converts truthy values to true" do [true, 1, 0, [], ""].each do |true_value| $VERBOSE = true_value diff --git a/spec/ruby/language/proc_spec.rb b/spec/ruby/language/proc_spec.rb index f8a29962b0..c5876fb2ed 100644 --- a/spec/ruby/language/proc_spec.rb +++ b/spec/ruby/language/proc_spec.rb @@ -161,6 +161,18 @@ describe "A Proc" do end end + describe "taking |*a, b| arguments" do + it "assigns [] to the argument when passed no values" do + proc { |*a, b| [a, b] }.call.should == [[], nil] + end + end + + describe "taking |a, *b, c| arguments" do + it "assigns [] to the argument when passed no values" do + proc { |a, *b, c| [a, b, c] }.call.should == [nil, [], nil] + end + end + describe "taking |a, | arguments" do before :each do @l = lambda { |a, | a } diff --git a/spec/ruby/language/singleton_class_spec.rb b/spec/ruby/language/singleton_class_spec.rb index c1fb682ea0..7512f0eb39 100644 --- a/spec/ruby/language/singleton_class_spec.rb +++ b/spec/ruby/language/singleton_class_spec.rb @@ -291,3 +291,20 @@ describe "Instantiating a singleton class" do }.should raise_error(TypeError) end end + +describe "Frozen properties" do + it "is frozen if the object it is created from is frozen" do + o = Object.new + o.freeze + klass = o.singleton_class + klass.frozen?.should == true + end + + it "will be frozen if the object it is created from becomes frozen" do + o = Object.new + klass = o.singleton_class + klass.frozen?.should == false + o.freeze + klass.frozen?.should == true + end +end diff --git a/spec/ruby/language/variables_spec.rb b/spec/ruby/language/variables_spec.rb index c900c03d37..cd862727ac 100644 --- a/spec/ruby/language/variables_spec.rb +++ b/spec/ruby/language/variables_spec.rb @@ -1,6 +1,86 @@ require_relative '../spec_helper' require_relative 'fixtures/variables' +describe "Evaluation order during assignment" do + context "with single assignment" do + it "evaluates from left to right" do + obj = VariablesSpecs::EvalOrder.new + obj.instance_eval do + foo[0] = a + end + + obj.order.should == ["foo", "a", "foo[]="] + end + end + + context "with multiple assignment" do + ruby_version_is ""..."3.1" do + it "does not evaluate from left to right" do + obj = VariablesSpecs::EvalOrder.new + + obj.instance_eval do + foo[0], bar.baz = a, b + end + + obj.order.should == ["a", "b", "foo", "foo[]=", "bar", "bar.baz="] + end + + it "cannot be used to swap variables with nested method calls" do + node = VariablesSpecs::EvalOrder.new.node + + original_node = node + original_node_left = node.left + original_node_left_right = node.left.right + + node.left, node.left.right, node = node.left.right, node, node.left + # Should evaluate in the order of: + # RHS: node.left.right, node, node.left + # LHS: + # * node(original_node), original_node.left = original_node_left_right + # * node(original_node), node.left(changed in the previous assignment to original_node_left_right), + # original_node_left_right.right = original_node + # * node = original_node_left + + node.should == original_node_left + node.right.should_not == original_node + node.right.left.should_not == original_node_left_right + end + end + + ruby_version_is "3.1" do + it "evaluates from left to right, receivers first then methods" do + obj = VariablesSpecs::EvalOrder.new + obj.instance_eval do + foo[0], bar.baz = a, b + end + + obj.order.should == ["foo", "bar", "a", "b", "foo[]=", "bar.baz="] + end + + it "can be used to swap variables with nested method calls" do + node = VariablesSpecs::EvalOrder.new.node + + original_node = node + original_node_left = node.left + original_node_left_right = node.left.right + + node.left, node.left.right, node = node.left.right, node, node.left + # Should evaluate in the order of: + # LHS: node, node.left(original_node_left) + # RHS: original_node_left_right, original_node, original_node_left + # Ops: + # * node(original_node), original_node.left = original_node_left_right + # * original_node_left.right = original_node + # * node = original_node_left + + node.should == original_node_left + node.right.should == original_node + node.right.left.should == original_node_left_right + end + end + end +end + describe "Multiple assignment" do context "with a single RHS value" do it "assigns a simple MLHS" do diff --git a/spec/ruby/library/fiber/current_spec.rb b/spec/ruby/library/fiber/current_spec.rb index e67d7d050a..e18603f069 100644 --- a/spec/ruby/library/fiber/current_spec.rb +++ b/spec/ruby/library/fiber/current_spec.rb @@ -3,6 +3,12 @@ require_relative '../../spec_helper' require 'fiber' describe "Fiber.current" do + ruby_version_is "3.1" do + it "is available without an extra require" do + ruby_exe("print Fiber.current.class", options: '--disable-gems --disable-did-you-mean').should == "Fiber" + end + end + it "returns the root Fiber when called outside of a Fiber" do root = Fiber.current root.should be_an_instance_of(Fiber) diff --git a/spec/ruby/library/io-wait/fixtures/classes.rb b/spec/ruby/library/io-wait/fixtures/classes.rb new file mode 100644 index 0000000000..837c7edd06 --- /dev/null +++ b/spec/ruby/library/io-wait/fixtures/classes.rb @@ -0,0 +1,12 @@ +module IOWaitSpec + def self.exhaust_write_buffer(io) + written = 0 + buf = " " * 4096 + + begin + written += io.write_nonblock(buf) + rescue Errno::EAGAIN, Errno::EWOULDBLOCK + return written + end while true + end +end diff --git a/spec/ruby/library/io-wait/wait_spec.rb b/spec/ruby/library/io-wait/wait_spec.rb new file mode 100644 index 0000000000..669ee70561 --- /dev/null +++ b/spec/ruby/library/io-wait/wait_spec.rb @@ -0,0 +1,147 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is ''...'3.2' do + require 'io/wait' +end + +describe "IO#wait" do + before :each do + @io = File.new(__FILE__ ) + + if /mswin|mingw/ =~ RUBY_PLATFORM + @r, @w = Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0) + else + @r, @w = IO.pipe + end + end + + after :each do + @io.close unless @io.closed? + + @r.close unless @r.closed? + @w.close unless @w.closed? + end + + ruby_version_is "3.0" do + context "[events, timeout] passed" do + ruby_version_is "3.0"..."3.2" do + it "returns self when the READABLE event is ready during the timeout" do + @w.write('data to read') + @r.wait(IO::READABLE, 2).should.equal?(@r) + end + + it "returns self when the WRITABLE event is ready during the timeout" do + @w.wait(IO::WRITABLE, 0).should.equal?(@w) + end + end + + ruby_version_is "3.2" do + it "returns events mask when the READABLE event is ready during the timeout" do + @w.write('data to read') + @r.wait(IO::READABLE, 2).should == IO::READABLE + end + + it "returns events mask when the WRITABLE event is ready during the timeout" do + @w.wait(IO::WRITABLE, 0).should == IO::WRITABLE + end + end + + ruby_version_is "3.0" do + it "waits for the READABLE event to be ready" do + queue = Queue.new + thread = Thread.new { queue.pop; sleep 1; @w.write('data to read') }; + + queue.push('signal'); + @r.wait(IO::READABLE, 2).should_not == nil + + thread.join + end + + it "waits for the WRITABLE event to be ready" do + written_bytes = IOWaitSpec.exhaust_write_buffer(@w) + + queue = Queue.new + thread = Thread.new { queue.pop; sleep 1; @r.read(written_bytes) }; + + queue.push('signal'); + @w.wait(IO::WRITABLE, 2).should_not == nil + + thread.join + end + + it "returns nil when the READABLE event is not ready during the timeout" do + @w.wait(IO::READABLE, 0).should == nil + end + + it "returns nil when the WRITABLE event is not ready during the timeout" do + IOWaitSpec.exhaust_write_buffer(@w) + @w.wait(IO::WRITABLE, 0).should == nil + end + + it "raises IOError when io is closed (closed stream (IOError))" do + @io.close + -> { @io.wait(IO::READABLE, 0) }.should raise_error(IOError, "closed stream") + end + + ruby_version_is "3.2" do + it "raises ArgumentError when events is not positive" do + -> { @w.wait(0, 0) }.should raise_error(ArgumentError, "Events must be positive integer!") + -> { @w.wait(-1, 0) }.should raise_error(ArgumentError, "Events must be positive integer!") + end + end + end + end + end + + context "[timeout, mode] passed" do + it "accepts :r, :read, :readable mode to check READABLE event" do + @io.wait(0, :r).should == @io + @io.wait(0, :read).should == @io + @io.wait(0, :readable).should == @io + end + + it "accepts :w, :write, :writable mode to check WRITABLE event" do + @io.wait(0, :w).should == @io + @io.wait(0, :write).should == @io + @io.wait(0, :writable).should == @io + end + + it "accepts :rw, :read_write, :readable_writable mode to check READABLE and WRITABLE events" do + @io.wait(0, :rw).should == @io + @io.wait(0, :read_write).should == @io + @io.wait(0, :readable_writable).should == @io + end + + it "accepts a list of modes" do + @io.wait(0, :r, :w, :rw).should == @io + end + + # It works at least since 2.7 but by some reason may fail on 3.1 + ruby_version_is "3.2" do + it "accepts timeout and mode in any order" do + @io.wait(0, :r).should == @io + @io.wait(:r, 0).should == @io + @io.wait(:r, 0, :w).should == @io + end + end + + it "raises ArgumentError when passed wrong Symbol value as mode argument" do + -> { @io.wait(0, :wrong) }.should raise_error(ArgumentError, "unsupported mode: wrong") + end + + # It works since 3.0 but by some reason may fail on 3.1 + ruby_version_is "3.2" do + it "raises ArgumentError when several Integer arguments passed" do + -> { @w.wait(0, 10, :r) }.should raise_error(ArgumentError, "timeout given more than once") + end + end + + ruby_version_is "3.2" do + it "raises IOError when io is closed (closed stream (IOError))" do + @io.close + -> { @io.wait(0, :r) }.should raise_error(IOError, "closed stream") + end + end + end +end diff --git a/spec/ruby/library/monitor/exit_spec.rb b/spec/ruby/library/monitor/exit_spec.rb new file mode 100644 index 0000000000..952ad9525d --- /dev/null +++ b/spec/ruby/library/monitor/exit_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require 'monitor' + +describe "Monitor#exit" do + it "raises ThreadError when monitor is not entered" do + m = Monitor.new + + -> { m.exit }.should raise_error(ThreadError) + end +end diff --git a/spec/ruby/library/objectspace/dump_all_spec.rb b/spec/ruby/library/objectspace/dump_all_spec.rb new file mode 100644 index 0000000000..dbf5bf1f76 --- /dev/null +++ b/spec/ruby/library/objectspace/dump_all_spec.rb @@ -0,0 +1,114 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.dump_all" do + it "dumps Ruby heap to string when passed output: :string" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + dump = ObjectSpace.dump_all(output: :string) + puts dump.class + puts dump.include?('"value":"abc"') + RUBY + + stdout.should == "String\ntrue\n" + end + + it "dumps Ruby heap to a temporary file when passed output: :file" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all(output: :file) + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + + it "dumps Ruby heap to a temporary file when :output not specified" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + + ruby_version_is "3.0" do + it "dumps Ruby heap to a temporary file when passed output: :nil" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + file = ObjectSpace.dump_all(output: nil) + + begin + file.flush + file.rewind + content = file.read + + puts file.class + puts content.include?('"value":"abc"') + ensure + file.close + File.unlink file.path + end + RUBY + + stdout.should == "File\ntrue\n" + end + end + + it "dumps Ruby heap to stdout when passed output: :stdout" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace") + string = "abc" + ObjectSpace.dump_all(output: :stdout) + RUBY + + stdout.should include('"value":"abc"') + end + + it "dumps Ruby heap to provided IO when passed output: IO" do + stdout = ruby_exe(<<~RUBY, options: "-robjspace -rtempfile") + string = "abc" + io = Tempfile.create("object_space_dump_all") + + begin + result = ObjectSpace.dump_all(output: io) + io.rewind + content = io.read + + puts result.equal?(io) + puts content.include?('"value":"abc"') + ensure + io.close + File.unlink io.path + end + RUBY + + stdout.should == "true\ntrue\n" + end + + it "raises ArgumentError when passed not supported :output value" do + -> { ObjectSpace.dump_all(output: Object.new) }.should raise_error(ArgumentError, /wrong output option/) + end +end diff --git a/spec/ruby/library/objectspace/dump_spec.rb b/spec/ruby/library/objectspace/dump_spec.rb new file mode 100644 index 0000000000..eacce51ba5 --- /dev/null +++ b/spec/ruby/library/objectspace/dump_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' +require 'objspace' + +describe "ObjectSpace.dump" do + it "dumps the content of object as JSON" do + require 'json' + string = ObjectSpace.dump("abc") + dump = JSON.parse(string) + + dump['type'].should == "STRING" + dump['value'].should == "abc" + end + + it "dumps to string when passed output: :string" do + string = ObjectSpace.dump("abc", output: :string) + string.should be_kind_of(String) + string.should include('"value":"abc"') + end + + it "dumps to string when :output not specified" do + string = ObjectSpace.dump("abc") + string.should be_kind_of(String) + string.should include('"value":"abc"') + end + + ruby_version_is "3.0" do + it "dumps to a temporary file when passed output: :file" do + file = ObjectSpace.dump("abc", output: :file) + file.should be_kind_of(File) + + file.rewind + content = file.read + content.should include('"value":"abc"') + ensure + file.close + File.unlink file.path + end + + it "dumps to a temporary file when passed output: :nil" do + file = ObjectSpace.dump("abc", output: nil) + file.should be_kind_of(File) + + file.rewind + file.read.should include('"value":"abc"') + ensure + file.close + File.unlink file.path + end + end + + it "dumps to stdout when passed output: :stdout" do + stdout = ruby_exe('ObjectSpace.dump("abc", output: :stdout)', options: "-robjspace").chomp + stdout.should include('"value":"abc"') + end + + ruby_version_is "3.0" do + it "dumps to provided IO when passed output: IO" do + filename = tmp("io_read.txt") + io = File.open(filename, "w+") + result = ObjectSpace.dump("abc", output: io) + result.should.equal? io + + io.rewind + io.read.should include('"value":"abc"') + ensure + io.close + rm_r filename + end + end + + it "raises ArgumentError when passed not supported :output value" do + -> { ObjectSpace.dump("abc", output: Object.new) }.should raise_error(ArgumentError, /wrong output option/) + end +end diff --git a/spec/ruby/library/set/shared/inspect.rb b/spec/ruby/library/set/shared/inspect.rb index 69fbdd12f6..564020e90e 100644 --- a/spec/ruby/library/set/shared/inspect.rb +++ b/spec/ruby/library/set/shared/inspect.rb @@ -7,6 +7,14 @@ describe "set_inspect", shared: true do Set[:a, "b", Set[?c]].send(@method).should be_kind_of(String) end + it "does include the elements of the set" do + Set["1"].send(@method).should == '#' + end + + it "puts spaces between the elements" do + Set["1", "2"].send(@method).should include('", "') + end + it "correctly handles self-references" do (set = Set[]) << set set.send(@method).should be_kind_of(String) diff --git a/spec/ruby/library/stringio/set_encoding_spec.rb b/spec/ruby/library/stringio/set_encoding_spec.rb index 21d45750f3..19ca0875bf 100644 --- a/spec/ruby/library/stringio/set_encoding_spec.rb +++ b/spec/ruby/library/stringio/set_encoding_spec.rb @@ -17,4 +17,12 @@ describe "StringIO#set_encoding" do io.set_encoding Encoding::UTF_8 io.string.encoding.should == Encoding::US_ASCII end + + it "accepts a String" do + str = "".encode(Encoding::US_ASCII) + io = StringIO.new(str) + io.set_encoding("ASCII-8BIT") + io.external_encoding.should == Encoding::BINARY + str.encoding.should == Encoding::BINARY + end end diff --git a/spec/ruby/library/stringio/shared/write.rb b/spec/ruby/library/stringio/shared/write.rb index d9c21028e0..b91e6ecec1 100644 --- a/spec/ruby/library/stringio/shared/write.rb +++ b/spec/ruby/library/stringio/shared/write.rb @@ -88,6 +88,27 @@ describe :stringio_write_string, shared: true do @io.write "fghi" @io.string.should == "12fghi" end + + it "transcodes the given string when the external encoding is set and neither is BINARY" do + utf8_str = "hello" + io = StringIO.new.set_encoding(Encoding::UTF_16BE) + io.external_encoding.should == Encoding::UTF_16BE + + io.send(@method, utf8_str) + + expected = [0, 104, 0, 101, 0, 108, 0, 108, 0, 111] # UTF-16BE bytes for "hello" + io.string.bytes.should == expected + end + + it "does not transcode the given string when the external encoding is set and the string encoding is BINARY" do + str = "été".b + io = StringIO.new.set_encoding(Encoding::UTF_16BE) + io.external_encoding.should == Encoding::UTF_16BE + + io.send(@method, str) + + io.string.bytes.should == str.bytes + end end describe :stringio_write_not_writable, shared: true do diff --git a/spec/ruby/optional/capi/ext/io_spec.c b/spec/ruby/optional/capi/ext/io_spec.c index f257cef554..cd4bc80229 100644 --- a/spec/ruby/optional/capi/ext/io_spec.c +++ b/spec/ruby/optional/capi/ext/io_spec.c @@ -162,6 +162,60 @@ VALUE io_spec_rb_io_wait_writable(VALUE self, VALUE io) { return ret ? Qtrue : Qfalse; } +#ifdef RUBY_VERSION_IS_3_1 +VALUE io_spec_rb_io_maybe_wait_writable(VALUE self, VALUE error, VALUE io, VALUE timeout) { + int ret = rb_io_maybe_wait_writable(NUM2INT(error), io, timeout); + return INT2NUM(ret); +} +#endif + +#ifdef RUBY_VERSION_IS_3_1 +VALUE io_spec_rb_io_maybe_wait_readable(VALUE self, VALUE error, VALUE io, VALUE timeout, VALUE read_p) { + int fd = io_spec_get_fd(io); +#ifndef SET_NON_BLOCKING_FAILS_ALWAYS + char buf[RB_IO_WAIT_READABLE_BUF]; + int ret, saved_errno; +#endif + + if (set_non_blocking(fd) == -1) + rb_sys_fail("set_non_blocking failed"); + +#ifndef SET_NON_BLOCKING_FAILS_ALWAYS + if(RTEST(read_p)) { + if (read(fd, buf, RB_IO_WAIT_READABLE_BUF) != -1) { + return Qnil; + } + saved_errno = errno; + rb_ivar_set(self, rb_intern("@write_data"), Qtrue); + errno = saved_errno; + } + + // main part + ret = rb_io_maybe_wait_readable(NUM2INT(error), io, timeout); + + if(RTEST(read_p)) { + ssize_t r = read(fd, buf, RB_IO_WAIT_READABLE_BUF); + if (r != RB_IO_WAIT_READABLE_BUF) { + perror("read"); + return SSIZET2NUM(r); + } + rb_ivar_set(self, rb_intern("@read_data"), + rb_str_new(buf, RB_IO_WAIT_READABLE_BUF)); + } + + return INT2NUM(ret); +#else + UNREACHABLE; +#endif +} +#endif + +#ifdef RUBY_VERSION_IS_3_1 +VALUE io_spec_rb_io_maybe_wait(VALUE self, VALUE error, VALUE io, VALUE events, VALUE timeout) { + return rb_io_maybe_wait(NUM2INT(error), io, events, timeout); +} +#endif + VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) { rb_thread_wait_fd(io_spec_get_fd(io)); return Qnil; @@ -294,6 +348,11 @@ void Init_io_spec(void) { rb_define_method(cls, "rb_io_taint_check", io_spec_rb_io_taint_check, 1); rb_define_method(cls, "rb_io_wait_readable", io_spec_rb_io_wait_readable, 2); rb_define_method(cls, "rb_io_wait_writable", io_spec_rb_io_wait_writable, 1); +#ifdef RUBY_VERSION_IS_3_1 + rb_define_method(cls, "rb_io_maybe_wait_writable", io_spec_rb_io_maybe_wait_writable, 3); + rb_define_method(cls, "rb_io_maybe_wait_readable", io_spec_rb_io_maybe_wait_readable, 4); + rb_define_method(cls, "rb_io_maybe_wait", io_spec_rb_io_maybe_wait, 4); +#endif rb_define_method(cls, "rb_thread_wait_fd", io_spec_rb_thread_wait_fd, 1); rb_define_method(cls, "rb_thread_fd_writable", io_spec_rb_thread_fd_writable, 1); rb_define_method(cls, "rb_thread_fd_select_read", io_spec_rb_thread_fd_select_read, 1); diff --git a/spec/ruby/optional/capi/io_spec.rb b/spec/ruby/optional/capi/io_spec.rb index 95717351a6..92a95df60a 100644 --- a/spec/ruby/optional/capi/io_spec.rb +++ b/spec/ruby/optional/capi/io_spec.rb @@ -256,6 +256,27 @@ describe "C-API IO function" do end end + ruby_version_is "3.1" do + describe "rb_io_maybe_wait_writable" do + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait_writable(Errno::EINTR::Errno, @w_io, nil).should == IO::WRITABLE + end + + it "returns 0 if there is no error condition" do + @o.rb_io_maybe_wait_writable(0, @w_io, nil).should == 0 + end + + it "raises an IOError if the IO is closed" do + @w_io.close + -> { @o.rb_io_maybe_wait_writable(0, @w_io, nil) }.should raise_error(IOError, "closed stream") + end + + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait_writable(0, IO.allocate, nil) }.should raise_error(IOError, "uninitialized stream") + end + end + end + describe "rb_thread_fd_writable" do it "waits til an fd is ready for writing" do @o.rb_thread_fd_writable(@w_io).should be_nil @@ -305,6 +326,40 @@ describe "C-API IO function" do thr.join end end + + ruby_version_is "3.1" do + describe "rb_io_maybe_wait_readable" do + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait_readable(Errno::EINTR::Errno, @r_io, nil, false).should == IO::READABLE + end + + it "returns 0 if there is no error condition" do + @o.rb_io_maybe_wait_readable(0, @r_io, nil, false).should == 0 + end + + it "blocks until the io is readable and returns events that actually occurred" do + @o.instance_variable_set :@write_data, false + thr = Thread.new do + Thread.pass until @o.instance_variable_get(:@write_data) + @w_io.write "rb_io_wait_readable" + end + + @o.rb_io_maybe_wait_readable(Errno::EAGAIN::Errno, @r_io, IO::READABLE, true).should == IO::READABLE + @o.instance_variable_get(:@read_data).should == "rb_io_wait_re" + + thr.join + end + + it "raises an IOError if the IO is closed" do + @r_io.close + -> { @o.rb_io_maybe_wait_readable(0, @r_io, nil, false) }.should raise_error(IOError, "closed stream") + end + + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait_readable(0, IO.allocate, nil, false) }.should raise_error(IOError, "uninitialized stream") + end + end + end end describe "rb_thread_wait_fd" do @@ -344,6 +399,42 @@ describe "C-API IO function" do @o.rb_wait_for_single_fd(@r_io, 1, 0, 0).should == 0 end end + + ruby_version_is "3.1" do + describe "rb_io_maybe_wait" do + it "waits til an fd is ready for reading" do + start = false + thr = Thread.new do + start = true + sleep 0.05 + @w_io.write "rb_io_maybe_wait" + end + + Thread.pass until start + + @o.rb_io_maybe_wait(Errno::EAGAIN::Errno, @r_io, IO::READABLE, nil).should == IO::READABLE + + thr.join + end + + it "returns mask for events if operation was interrupted" do + @o.rb_io_maybe_wait(Errno::EINTR::Errno, @w_io, IO::WRITABLE, nil).should == IO::WRITABLE + end + + it "returns false if there is no error condition" do + @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil).should == false + end + + it "raises an IOError if the IO is closed" do + @w_io.close + -> { @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil) }.should raise_error(IOError, "closed stream") + end + + it "raises an IOError if the IO is not initialized" do + -> { @o.rb_io_maybe_wait(0, IO.allocate, IO::WRITABLE, nil) }.should raise_error(IOError, "uninitialized stream") + end + end + end end describe "rb_fd_fix_cloexec" do diff --git a/spec/ruby/optional/capi/time_spec.rb b/spec/ruby/optional/capi/time_spec.rb index 579e81fc19..ca5fc5952a 100644 --- a/spec/ruby/optional/capi/time_spec.rb +++ b/spec/ruby/optional/capi/time_spec.rb @@ -283,6 +283,11 @@ describe "CApiTimeSpecs" do -> { @s.rb_time_timespec_new(1447087832, 476451125, 86400) }.should raise_error(ArgumentError) -> { @s.rb_time_timespec_new(1447087832, 476451125, -86400) }.should raise_error(ArgumentError) end + + it "doesn't call Time.at directly" do + Time.should_not_receive(:at) + @s.rb_time_timespec_new(1447087832, 476451125, 32400).should be_kind_of(Time) + end end describe "rb_timespec_now" do diff --git a/spec/ruby/shared/rational/Rational.rb b/spec/ruby/shared/rational/Rational.rb index a56d027c96..500f7ed271 100644 --- a/spec/ruby/shared/rational/Rational.rb +++ b/spec/ruby/shared/rational/Rational.rb @@ -143,4 +143,8 @@ describe :kernel_Rational, shared: true do end end end + + it "freezes its result" do + Rational(1).frozen?.should == true + end end -- cgit v1.2.1