diff --git a/.mspec.constants b/.mspec.constants index 6e09a44362..4da3633715 100644 --- a/.mspec.constants +++ b/.mspec.constants @@ -146,6 +146,7 @@ Prime Private ProcFromMethod Psych +RactorLocalSingleton REXML RUBY_SIGNALS RbReadline diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddb0608c4a..5c3256948b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ end platform_is_not :linux, :darwin do # Not Linux and not Darwin end -platform_is wordsize: 64 do +platform_is pointer_size: 64 do # 64-bit platform end diff --git a/command_line/fixtures/debug_info.rb b/command_line/fixtures/debug_info.rb index ee607910c0..f02b041920 100644 --- a/command_line/fixtures/debug_info.rb +++ b/command_line/fixtures/debug_info.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true a = 'string' b = a c = b diff --git a/command_line/frozen_strings_spec.rb b/command_line/frozen_strings_spec.rb index 06889755d2..014153e0b4 100644 --- a/command_line/frozen_strings_spec.rb +++ b/command_line/frozen_strings_spec.rb @@ -57,9 +57,18 @@ describe "The --debug flag produces" do it "debugging info on attempted frozen string modification" do - error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--debug', args: "2>&1") + error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--enable-frozen-string-literal --debug', args: "2>&1") error_str.should include("can't modify frozen String") error_str.should include("created at") - error_str.should include("command_line/fixtures/debug_info.rb:2") + error_str.should include("command_line/fixtures/debug_info.rb:1") + end + + guard -> { ruby_version_is "3.4" and !"test".frozen? } do + it "debugging info on mutating chilled string" do + error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '-w --debug', args: "2>&1") + error_str.should include("literal string will be frozen in the future") + error_str.should include("the string was created here") + error_str.should include("command_line/fixtures/debug_info.rb:1") + end end end diff --git a/core/array/fetch_values_spec.rb b/core/array/fetch_values_spec.rb new file mode 100644 index 0000000000..559b6c2b2f --- /dev/null +++ b/core/array/fetch_values_spec.rb @@ -0,0 +1,48 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Array#fetch_values" do + before :each do + @array = [:a, :b, :c] + end + + ruby_version_is "3.4" do + describe "with matched indexes" do + it "returns the values for indexes" do + @array.fetch_values(0).should == [:a] + @array.fetch_values(0, 2).should == [:a, :c] + end + + it "returns the values for indexes ordered in the order of the requested indexes" do + @array.fetch_values(2, 0).should == [:c, :a] + end + end + + describe "with unmatched indexes" do + it "raises a index error if no block is provided" do + -> { @array.fetch_values(0, 1, 44) }.should raise_error(IndexError) + end + + it "returns the default value from block" do + @array.fetch_values(44) { |index| "`#{index}' is not found" }.should == ["`44' is not found"] + @array.fetch_values(0, 44) { |index| "`#{index}' is not found" }.should == [:a, "`44' is not found"] + end + end + + describe "without keys" do + it "returns an empty Array" do + @array.fetch_values.should == [] + end + end + + it "tries to convert the passed argument to an Integer using #to_int" do + obj = mock('to_int') + obj.should_receive(:to_int).and_return(2) + @array.fetch_values(obj).should == [:c] + end + + it "raises a TypeError when the passed argument can't be coerced to Integer" do + -> { [].fetch_values("cat") }.should raise_error(TypeError) + end + end +end diff --git a/core/array/pack/l_spec.rb b/core/array/pack/l_spec.rb index b446a7a36a..f6dfb1da83 100644 --- a/core/array/pack/l_spec.rb +++ b/core/array/pack/l_spec.rb @@ -29,7 +29,7 @@ it_behaves_like :array_pack_32bit_be, 'L>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_32bit_le, 'L<_' it_behaves_like :array_pack_32bit_le, 'L_<' @@ -51,7 +51,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_64bit_le, 'L<_' it_behaves_like :array_pack_64bit_le, 'L_<' @@ -83,7 +83,7 @@ it_behaves_like :array_pack_32bit_be, 'l>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_32bit_le, 'l<_' it_behaves_like :array_pack_32bit_le, 'l_<' @@ -105,7 +105,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :array_pack_64bit_le, 'l<_' it_behaves_like :array_pack_64bit_le, 'l_<' @@ -137,7 +137,7 @@ it_behaves_like :array_pack_32bit_le, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_32bit_le, 'L_' end @@ -155,7 +155,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_64bit_le, 'L_' end @@ -183,7 +183,7 @@ it_behaves_like :array_pack_32bit_be, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_32bit_be, 'L_' end @@ -201,7 +201,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "Array#pack with format 'L' with modifier '_'" do it_behaves_like :array_pack_64bit_be, 'L_' end diff --git a/core/array/pack/shared/integer.rb b/core/array/pack/shared/integer.rb index fd21b25b19..a89b5b733b 100644 --- a/core/array/pack/shared/integer.rb +++ b/core/array/pack/shared/integer.rb @@ -273,7 +273,7 @@ str.should == "\x78\x65\x43\x12\xcd\xab\xf0\xde\x21\x43\x65\x78" end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "encodes the least significant 32 bits of a number that is greater than 32 bits" do [ [[0xff_7865_4321], "\x21\x43\x65\x78"], [[-0xff_7865_4321], "\xdf\xbc\x9a\x87"] @@ -299,7 +299,7 @@ str.should == "\x12\x43\x65\x78\xde\xf0\xab\xcd\x78\x65\x43\x21" end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "encodes the least significant 32 bits of a number that is greater than 32 bits" do [ [[0xff_7865_4321], "\x78\x65\x43\x21"], [[-0xff_7865_4321], "\x87\x9a\xbc\xdf"] diff --git a/core/encoding/compatible_spec.rb b/core/encoding/compatible_spec.rb index f18d8680a9..d5b958ea0b 100644 --- a/core/encoding/compatible_spec.rb +++ b/core/encoding/compatible_spec.rb @@ -161,6 +161,379 @@ Encoding.compatible?(@str, "").should == Encoding::UTF_7 end end + + # Encoding negotiation depends on whether encodings are ASCII-compatible, empty + # and contain only ASCII characters (that take 7 bits). Check US-ASCII, UTF-8 and + # BINARY encodings (as most common) as well as an ASCII-compatible, a non-ASCII-compatible and a dummy + # encodings in all possible combinations. + describe "compatibility matrix" do + +# Use the following script to regenerate the matrix: +# +# ``` +# # -*- encoding: binary -*- +# +# ENCODINGS = [ +# "US-ASCII", +# "UTF-8", +# "ASCII-8BIT", +# "ISO-8859-1", # ASCII-compatible +# "UTF-16BE", # non-ASCII-compatible +# "ISO-2022-JP" # dummy +# ] +# +# TYPES = [:empty, :"7bits", :non7bits] +# +# VALUES = { +# empty: "", +# :"7bits" => "\x01", +# non7bits: "\x81" +# } +# +# ENCODINGS.product(TYPES, ENCODINGS, TYPES).each do |encoding1, type1, encoding2, type2| +# value1 = VALUES[type1].dup.force_encoding(encoding1) +# value2 = VALUES[type2].dup.force_encoding(encoding2) +# +# result_encoding = Encoding.compatible?(value1, value2) +# +# puts "[#{encoding1.inspect}, #{value1.inspect}, #{encoding2.inspect}, #{value2.inspect}, #{result_encoding&.name.inspect}]," +# end +# ``` + + matrix = [ + ["US-ASCII", "", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "", "US-ASCII", "\x01", "US-ASCII"], + ["US-ASCII", "", "US-ASCII", "\x81", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "\u0001", "US-ASCII"], + ["US-ASCII", "", "UTF-8", "\x81", "UTF-8"], + ["US-ASCII", "", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "", "ASCII-8BIT", "\x01", "US-ASCII"], + ["US-ASCII", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["US-ASCII", "", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "", "ISO-8859-1", "\x01", "US-ASCII"], + ["US-ASCII", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["US-ASCII", "", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["US-ASCII", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["US-ASCII", "", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["US-ASCII", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["US-ASCII", "\x01", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "\x01", "US-ASCII", "\x01", "US-ASCII"], + ["US-ASCII", "\x01", "US-ASCII", "\x81", "US-ASCII"], + ["US-ASCII", "\x01", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "\x01", "UTF-8", "\u0001", "US-ASCII"], + ["US-ASCII", "\x01", "UTF-8", "\x81", "UTF-8"], + ["US-ASCII", "\x01", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "\x01", "ASCII-8BIT", "\x01", "US-ASCII"], + ["US-ASCII", "\x01", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["US-ASCII", "\x01", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "\x01", "ISO-8859-1", "\x01", "US-ASCII"], + ["US-ASCII", "\x01", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["US-ASCII", "\x01", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "\x01", "UTF-16BE", "\x01", nil], + ["US-ASCII", "\x01", "UTF-16BE", "\x81", nil], + ["US-ASCII", "\x01", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "\x01", "ISO-2022-JP", "\x01", nil], + ["US-ASCII", "\x01", "ISO-2022-JP", "\x81", nil], + ["US-ASCII", "\x81", "US-ASCII", "", "US-ASCII"], + ["US-ASCII", "\x81", "US-ASCII", "\x01", "US-ASCII"], + ["US-ASCII", "\x81", "US-ASCII", "\x81", "US-ASCII"], + ["US-ASCII", "\x81", "UTF-8", "", "US-ASCII"], + ["US-ASCII", "\x81", "UTF-8", "\u0001", "US-ASCII"], + ["US-ASCII", "\x81", "UTF-8", "\x81", nil], + ["US-ASCII", "\x81", "ASCII-8BIT", "", "US-ASCII"], + ["US-ASCII", "\x81", "ASCII-8BIT", "\x01", "US-ASCII"], + ["US-ASCII", "\x81", "ASCII-8BIT", "\x81", nil], + ["US-ASCII", "\x81", "ISO-8859-1", "", "US-ASCII"], + ["US-ASCII", "\x81", "ISO-8859-1", "\x01", "US-ASCII"], + ["US-ASCII", "\x81", "ISO-8859-1", "\x81", nil], + ["US-ASCII", "\x81", "UTF-16BE", "", "US-ASCII"], + ["US-ASCII", "\x81", "UTF-16BE", "\x01", nil], + ["US-ASCII", "\x81", "UTF-16BE", "\x81", nil], + ["US-ASCII", "\x81", "ISO-2022-JP", "", "US-ASCII"], + ["US-ASCII", "\x81", "ISO-2022-JP", "\x01", nil], + ["US-ASCII", "\x81", "ISO-2022-JP", "\x81", nil], + ["UTF-8", "", "US-ASCII", "", "UTF-8"], + ["UTF-8", "", "US-ASCII", "\x01", "UTF-8"], + ["UTF-8", "", "US-ASCII", "\x81", "US-ASCII"], + ["UTF-8", "", "UTF-8", "", "UTF-8"], + ["UTF-8", "", "UTF-8", "\u0001", "UTF-8"], + ["UTF-8", "", "UTF-8", "\x81", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "\x01", "UTF-8"], + ["UTF-8", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["UTF-8", "", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "", "ISO-8859-1", "\x01", "UTF-8"], + ["UTF-8", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["UTF-8", "", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["UTF-8", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["UTF-8", "", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["UTF-8", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["UTF-8", "\u0001", "US-ASCII", "", "UTF-8"], + ["UTF-8", "\u0001", "US-ASCII", "\x01", "UTF-8"], + ["UTF-8", "\u0001", "US-ASCII", "\x81", "US-ASCII"], + ["UTF-8", "\u0001", "UTF-8", "", "UTF-8"], + ["UTF-8", "\u0001", "UTF-8", "\u0001", "UTF-8"], + ["UTF-8", "\u0001", "UTF-8", "\x81", "UTF-8"], + ["UTF-8", "\u0001", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "\u0001", "ASCII-8BIT", "\x01", "UTF-8"], + ["UTF-8", "\u0001", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["UTF-8", "\u0001", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "\u0001", "ISO-8859-1", "\x01", "UTF-8"], + ["UTF-8", "\u0001", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["UTF-8", "\u0001", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "\u0001", "UTF-16BE", "\x01", nil], + ["UTF-8", "\u0001", "UTF-16BE", "\x81", nil], + ["UTF-8", "\u0001", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "\u0001", "ISO-2022-JP", "\x01", nil], + ["UTF-8", "\u0001", "ISO-2022-JP", "\x81", nil], + ["UTF-8", "\x81", "US-ASCII", "", "UTF-8"], + ["UTF-8", "\x81", "US-ASCII", "\x01", "UTF-8"], + ["UTF-8", "\x81", "US-ASCII", "\x81", nil], + ["UTF-8", "\x81", "UTF-8", "", "UTF-8"], + ["UTF-8", "\x81", "UTF-8", "\u0001", "UTF-8"], + ["UTF-8", "\x81", "UTF-8", "\x81", "UTF-8"], + ["UTF-8", "\x81", "ASCII-8BIT", "", "UTF-8"], + ["UTF-8", "\x81", "ASCII-8BIT", "\x01", "UTF-8"], + ["UTF-8", "\x81", "ASCII-8BIT", "\x81", nil], + ["UTF-8", "\x81", "ISO-8859-1", "", "UTF-8"], + ["UTF-8", "\x81", "ISO-8859-1", "\x01", "UTF-8"], + ["UTF-8", "\x81", "ISO-8859-1", "\x81", nil], + ["UTF-8", "\x81", "UTF-16BE", "", "UTF-8"], + ["UTF-8", "\x81", "UTF-16BE", "\x01", nil], + ["UTF-8", "\x81", "UTF-16BE", "\x81", nil], + ["UTF-8", "\x81", "ISO-2022-JP", "", "UTF-8"], + ["UTF-8", "\x81", "ISO-2022-JP", "\x01", nil], + ["UTF-8", "\x81", "ISO-2022-JP", "\x81", nil], + ["ASCII-8BIT", "", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "US-ASCII", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "US-ASCII", "\x81", "US-ASCII"], + ["ASCII-8BIT", "", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-8", "\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-8", "\x81", "UTF-8"], + ["ASCII-8BIT", "", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ASCII-8BIT", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ASCII-8BIT", "", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["ASCII-8BIT", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["ASCII-8BIT", "", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["ASCII-8BIT", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["ASCII-8BIT", "\x01", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "US-ASCII", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "US-ASCII", "\x81", "US-ASCII"], + ["ASCII-8BIT", "\x01", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "UTF-8", "\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "UTF-8", "\x81", "UTF-8"], + ["ASCII-8BIT", "\x01", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ASCII-8BIT", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ISO-8859-1", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ASCII-8BIT", "\x01", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "UTF-16BE", "\x01", nil], + ["ASCII-8BIT", "\x01", "UTF-16BE", "\x81", nil], + ["ASCII-8BIT", "\x01", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x01", "ISO-2022-JP", "\x01", nil], + ["ASCII-8BIT", "\x01", "ISO-2022-JP", "\x81", nil], + ["ASCII-8BIT", "\x81", "US-ASCII", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "US-ASCII", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "US-ASCII", "\x81", nil], + ["ASCII-8BIT", "\x81", "UTF-8", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "UTF-8", "\u0001", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "UTF-8", "\x81", nil], + ["ASCII-8BIT", "\x81", "ASCII-8BIT", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ASCII-8BIT", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ISO-8859-1", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ISO-8859-1", "\x01", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ISO-8859-1", "\x81", nil], + ["ASCII-8BIT", "\x81", "UTF-16BE", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "UTF-16BE", "\x01", nil], + ["ASCII-8BIT", "\x81", "UTF-16BE", "\x81", nil], + ["ASCII-8BIT", "\x81", "ISO-2022-JP", "", "ASCII-8BIT"], + ["ASCII-8BIT", "\x81", "ISO-2022-JP", "\x01", nil], + ["ASCII-8BIT", "\x81", "ISO-2022-JP", "\x81", nil], + ["ISO-8859-1", "", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "", "US-ASCII", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "US-ASCII", "\x81", "US-ASCII"], + ["ISO-8859-1", "", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-8", "\u0001", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-8", "\x81", "UTF-8"], + ["ISO-8859-1", "", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ASCII-8BIT", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ISO-8859-1", "", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-8859-1", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["ISO-8859-1", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["ISO-8859-1", "", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["ISO-8859-1", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["ISO-8859-1", "\x01", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "US-ASCII", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "US-ASCII", "\x81", "US-ASCII"], + ["ISO-8859-1", "\x01", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "UTF-8", "\u0001", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "UTF-8", "\x81", "UTF-8"], + ["ISO-8859-1", "\x01", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "ASCII-8BIT", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ISO-8859-1", "\x01", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "ISO-8859-1", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "UTF-16BE", "\x01", nil], + ["ISO-8859-1", "\x01", "UTF-16BE", "\x81", nil], + ["ISO-8859-1", "\x01", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "\x01", "ISO-2022-JP", "\x01", nil], + ["ISO-8859-1", "\x01", "ISO-2022-JP", "\x81", nil], + ["ISO-8859-1", "\x81", "US-ASCII", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "US-ASCII", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "US-ASCII", "\x81", nil], + ["ISO-8859-1", "\x81", "UTF-8", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "UTF-8", "\u0001", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "UTF-8", "\x81", nil], + ["ISO-8859-1", "\x81", "ASCII-8BIT", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "ASCII-8BIT", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "ASCII-8BIT", "\x81", nil], + ["ISO-8859-1", "\x81", "ISO-8859-1", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "ISO-8859-1", "\x01", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "UTF-16BE", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "UTF-16BE", "\x01", nil], + ["ISO-8859-1", "\x81", "UTF-16BE", "\x81", nil], + ["ISO-8859-1", "\x81", "ISO-2022-JP", "", "ISO-8859-1"], + ["ISO-8859-1", "\x81", "ISO-2022-JP", "\x01", nil], + ["ISO-8859-1", "\x81", "ISO-2022-JP", "\x81", nil], + ["UTF-16BE", "", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "", "US-ASCII", "\x01", "US-ASCII"], + ["UTF-16BE", "", "US-ASCII", "\x81", "US-ASCII"], + ["UTF-16BE", "", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "", "UTF-8", "\u0001", "UTF-8"], + ["UTF-16BE", "", "UTF-8", "\x81", "UTF-8"], + ["UTF-16BE", "", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "", "ASCII-8BIT", "\x01", "ASCII-8BIT"], + ["UTF-16BE", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["UTF-16BE", "", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "", "ISO-8859-1", "\x01", "ISO-8859-1"], + ["UTF-16BE", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["UTF-16BE", "", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["UTF-16BE", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["UTF-16BE", "", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["UTF-16BE", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["UTF-16BE", "\x01", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "US-ASCII", "\x01", nil], + ["UTF-16BE", "\x01", "US-ASCII", "\x81", nil], + ["UTF-16BE", "\x01", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "UTF-8", "\u0001", nil], + ["UTF-16BE", "\x01", "UTF-8", "\x81", nil], + ["UTF-16BE", "\x01", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "ASCII-8BIT", "\x01", nil], + ["UTF-16BE", "\x01", "ASCII-8BIT", "\x81", nil], + ["UTF-16BE", "\x01", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "ISO-8859-1", "\x01", nil], + ["UTF-16BE", "\x01", "ISO-8859-1", "\x81", nil], + ["UTF-16BE", "\x01", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "UTF-16BE", "\x01", "UTF-16BE"], + ["UTF-16BE", "\x01", "UTF-16BE", "\x81", "UTF-16BE"], + ["UTF-16BE", "\x01", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "\x01", "ISO-2022-JP", "\x01", nil], + ["UTF-16BE", "\x01", "ISO-2022-JP", "\x81", nil], + ["UTF-16BE", "\x81", "US-ASCII", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "US-ASCII", "\x01", nil], + ["UTF-16BE", "\x81", "US-ASCII", "\x81", nil], + ["UTF-16BE", "\x81", "UTF-8", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "UTF-8", "\u0001", nil], + ["UTF-16BE", "\x81", "UTF-8", "\x81", nil], + ["UTF-16BE", "\x81", "ASCII-8BIT", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "ASCII-8BIT", "\x01", nil], + ["UTF-16BE", "\x81", "ASCII-8BIT", "\x81", nil], + ["UTF-16BE", "\x81", "ISO-8859-1", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "ISO-8859-1", "\x01", nil], + ["UTF-16BE", "\x81", "ISO-8859-1", "\x81", nil], + ["UTF-16BE", "\x81", "UTF-16BE", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "UTF-16BE", "\x01", "UTF-16BE"], + ["UTF-16BE", "\x81", "UTF-16BE", "\x81", "UTF-16BE"], + ["UTF-16BE", "\x81", "ISO-2022-JP", "", "UTF-16BE"], + ["UTF-16BE", "\x81", "ISO-2022-JP", "\x01", nil], + ["UTF-16BE", "\x81", "ISO-2022-JP", "\x81", nil], + ["ISO-2022-JP", "", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "US-ASCII", "\x01", "US-ASCII"], + ["ISO-2022-JP", "", "US-ASCII", "\x81", "US-ASCII"], + ["ISO-2022-JP", "", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "UTF-8", "\u0001", "UTF-8"], + ["ISO-2022-JP", "", "UTF-8", "\x81", "UTF-8"], + ["ISO-2022-JP", "", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ASCII-8BIT", "\x01", "ASCII-8BIT"], + ["ISO-2022-JP", "", "ASCII-8BIT", "\x81", "ASCII-8BIT"], + ["ISO-2022-JP", "", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-8859-1", "\x01", "ISO-8859-1"], + ["ISO-2022-JP", "", "ISO-8859-1", "\x81", "ISO-8859-1"], + ["ISO-2022-JP", "", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "UTF-16BE", "\x01", "UTF-16BE"], + ["ISO-2022-JP", "", "UTF-16BE", "\x81", "UTF-16BE"], + ["ISO-2022-JP", "", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "US-ASCII", "\x01", nil], + ["ISO-2022-JP", "\x01", "US-ASCII", "\x81", nil], + ["ISO-2022-JP", "\x01", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "UTF-8", "\u0001", nil], + ["ISO-2022-JP", "\x01", "UTF-8", "\x81", nil], + ["ISO-2022-JP", "\x01", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "ASCII-8BIT", "\x01", nil], + ["ISO-2022-JP", "\x01", "ASCII-8BIT", "\x81", nil], + ["ISO-2022-JP", "\x01", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "ISO-8859-1", "\x01", nil], + ["ISO-2022-JP", "\x01", "ISO-8859-1", "\x81", nil], + ["ISO-2022-JP", "\x01", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "UTF-16BE", "\x01", nil], + ["ISO-2022-JP", "\x01", "UTF-16BE", "\x81", nil], + ["ISO-2022-JP", "\x01", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "\x01", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "US-ASCII", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "US-ASCII", "\x01", nil], + ["ISO-2022-JP", "\x81", "US-ASCII", "\x81", nil], + ["ISO-2022-JP", "\x81", "UTF-8", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "UTF-8", "\u0001", nil], + ["ISO-2022-JP", "\x81", "UTF-8", "\x81", nil], + ["ISO-2022-JP", "\x81", "ASCII-8BIT", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "ASCII-8BIT", "\x01", nil], + ["ISO-2022-JP", "\x81", "ASCII-8BIT", "\x81", nil], + ["ISO-2022-JP", "\x81", "ISO-8859-1", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "ISO-8859-1", "\x01", nil], + ["ISO-2022-JP", "\x81", "ISO-8859-1", "\x81", nil], + ["ISO-2022-JP", "\x81", "UTF-16BE", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "UTF-16BE", "\x01", nil], + ["ISO-2022-JP", "\x81", "UTF-16BE", "\x81", nil], + ["ISO-2022-JP", "\x81", "ISO-2022-JP", "", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "ISO-2022-JP", "\x01", "ISO-2022-JP"], + ["ISO-2022-JP", "\x81", "ISO-2022-JP", "\x81", "ISO-2022-JP"], + ] + + matrix.each do |encoding1, value1, encoding2, value2, compatible_encoding| + it "returns #{compatible_encoding} for #{value1.inspect} in #{encoding1} and #{value2.inspect} in #{encoding2}" do + actual_encoding = Encoding.compatible?(value1.dup.force_encoding(encoding1), value2.dup.force_encoding(encoding2)) + actual_encoding&.name.should == compatible_encoding + end + end + end end describe "Encoding.compatible? String, Regexp" do @@ -377,3 +750,9 @@ Encoding.compatible?(:sym, Object.new).should be_nil end end + +describe "Encoding.compatible? nil, nil" do + it "returns nil" do + Encoding.compatible?(nil, nil).should be_nil + end +end diff --git a/core/exception/full_message_spec.rb b/core/exception/full_message_spec.rb index 5154354555..d752083db2 100644 --- a/core/exception/full_message_spec.rb +++ b/core/exception/full_message_spec.rb @@ -79,6 +79,24 @@ err.full_message(highlight: true).should !~ /unhandled exception/ err.full_message(highlight: false).should !~ /unhandled exception/ end + + it "adds escape sequences to highlight some strings if the message is not specified and :highlight option is specified" do + e = RuntimeError.new("") + + full_message = e.full_message(highlight: true, order: :top).lines + full_message[0].should.end_with? "\e[1;4munhandled exception\e[m\n" + + full_message = e.full_message(highlight: true, order: :bottom).lines + full_message[0].should == "\e[1mTraceback\e[m (most recent call last):\n" + full_message[-1].should.end_with? "\e[1;4munhandled exception\e[m\n" + + full_message = e.full_message(highlight: false, order: :top).lines + full_message[0].should.end_with? "unhandled exception\n" + + full_message = e.full_message(highlight: false, order: :bottom).lines + full_message[0].should == "Traceback (most recent call last):\n" + full_message[-1].should.end_with? "unhandled exception\n" + end end describe "generic Error" do diff --git a/core/file/shared/update_time.rb b/core/file/shared/update_time.rb index 9c063a8e93..3fe7266a00 100644 --- a/core/file/shared/update_time.rb +++ b/core/file/shared/update_time.rb @@ -84,7 +84,7 @@ end platform_is :linux do - platform_is wordsize: 64 do + platform_is pointer_size: 64 do it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps # "Therefore, timestamps should not overflow until May 2446." diff --git a/core/hash/new_spec.rb b/core/hash/new_spec.rb index 6279815fd6..5ae3e1f98d 100644 --- a/core/hash/new_spec.rb +++ b/core/hash/new_spec.rb @@ -34,7 +34,7 @@ -> { Hash.new(nil) { 0 } }.should raise_error(ArgumentError) end - ruby_version_is "3.3" do + ruby_version_is "3.3"..."3.4" do it "emits a deprecation warning if keyword arguments are passed" do -> { Hash.new(unknown: true) }.should complain( Regexp.new(Regexp.escape("Calling Hash.new with keyword arguments is deprecated and will be removed in Ruby 3.4; use Hash.new({ key: value }) instead")) @@ -46,4 +46,22 @@ Hash.new({ unknown: true }).default.should == { unknown: true } end end + + ruby_version_is "3.4" do + it "accepts a capacity: argument" do + Hash.new(5, capacity: 42).default.should == 5 + Hash.new(capacity: 42).default.should == nil + (Hash.new(capacity: 42) { 1 }).default_proc.should_not == nil + end + + it "ignores negative capacity" do + -> { Hash.new(capacity: -42) }.should_not raise_error + end + + it "raises an error if unknown keyword arguments are passed" do + -> { Hash.new(unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(1, unknown: true) }.should raise_error(ArgumentError) + -> { Hash.new(unknown: true) { 0 } }.should raise_error(ArgumentError) + end + end end diff --git a/core/hash/shared/to_s.rb b/core/hash/shared/to_s.rb index 7864d7cd4c..5f0a8f97fd 100644 --- a/core/hash/shared/to_s.rb +++ b/core/hash/shared/to_s.rb @@ -4,14 +4,8 @@ describe :hash_to_s, shared: true do it "returns a string representation with same order as each()" do h = { a: [1, 2], b: -2, d: -6, nil => nil } - - pairs = [] - h.each do |key, value| - pairs << key.inspect + '=>' + value.inspect - end - - str = '{' + pairs.join(', ') + '}' - h.send(@method).should == str + expected = ruby_version_is("3.4") ? "{a: [1, 2], b: -2, d: -6, nil => nil}" : "{:a=>[1, 2], :b=>-2, :d=>-6, nil=>nil}" + h.send(@method).should == expected end it "calls #inspect on keys and values" do @@ -19,31 +13,31 @@ val = mock('val') key.should_receive(:inspect).and_return('key') val.should_receive(:inspect).and_return('val') - - { key => val }.send(@method).should == '{key=>val}' + expected = ruby_version_is("3.4") ? "{key => val}" : "{key=>val}" + { key => val }.send(@method).should == expected end it "does not call #to_s on a String returned from #inspect" do str = +"abc" str.should_not_receive(:to_s) - - { a: str }.send(@method).should == '{:a=>"abc"}' + expected = ruby_version_is("3.4") ? '{a: "abc"}' : '{:a=>"abc"}' + { a: str }.send(@method).should == expected end it "calls #to_s on the object returned from #inspect if the Object isn't a String" do obj = mock("Hash#inspect/to_s calls #to_s") obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return("abc") - - { a: obj }.send(@method).should == "{:a=>abc}" + expected = ruby_version_is("3.4") ? "{a: abc}" : "{:a=>abc}" + { a: obj }.send(@method).should == expected end it "does not call #to_str on the object returned from #inspect when it is not a String" do obj = mock("Hash#inspect/to_s does not call #to_str") obj.should_receive(:inspect).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #\}$/ : /^\{:a=>#\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not call #to_str on the object returned from #to_s when it is not a String" do @@ -51,8 +45,8 @@ obj.should_receive(:inspect).and_return(obj) obj.should_receive(:to_s).and_return(obj) obj.should_not_receive(:to_str) - - { a: obj }.send(@method).should =~ /^\{:a=>#\}$/ + expected_pattern = ruby_version_is("3.4") ? /^\{a: #\}$/ : /^\{:a=>#\}$/ + { a: obj }.send(@method).should =~ expected_pattern end it "does not swallow exceptions raised by #to_s" do @@ -66,24 +60,28 @@ it "handles hashes with recursive values" do x = {} x[0] = x - x.send(@method).should == '{0=>{...}}' + expected = ruby_version_is("3.4") ? '{0 => {...}}' : '{0=>{...}}' + x.send(@method).should == expected x = {} y = {} x[0] = y y[1] = x - x.send(@method).should == "{0=>{1=>{...}}}" - y.send(@method).should == "{1=>{0=>{...}}}" + expected_x = ruby_version_is("3.4") ? '{0 => {1 => {...}}}' : '{0=>{1=>{...}}}' + expected_y = ruby_version_is("3.4") ? '{1 => {0 => {...}}}' : '{1=>{0=>{...}}}' + x.send(@method).should == expected_x + y.send(@method).should == expected_y end it "does not raise if inspected result is not default external encoding" do utf_16be = mock("utf_16be") utf_16be.should_receive(:inspect).and_return(%<"utf_16be \u3042">.encode(Encoding::UTF_16BE)) - - {a: utf_16be}.send(@method).should == '{:a=>"utf_16be \u3042"}' + expected = ruby_version_is("3.4") ? '{a: "utf_16be \u3042"}' : '{:a=>"utf_16be \u3042"}' + {a: utf_16be}.send(@method).should == expected end it "works for keys and values whose #inspect return a frozen String" do - { true => false }.to_s.should == "{true=>false}" + expected = ruby_version_is("3.4") ? "{true => false}" : "{true=>false}" + { true => false }.to_s.should == expected end end diff --git a/core/integer/round_spec.rb b/core/integer/round_spec.rb index 45ac126fd3..189384f11a 100644 --- a/core/integer/round_spec.rb +++ b/core/integer/round_spec.rb @@ -21,10 +21,8 @@ (-25 * 10**70).round(-71).should eql(-30 * 10**70) end - platform_is_not wordsize: 32 do - it "raises a RangeError when passed a big negative value" do - -> { 42.round(fixnum_min) }.should raise_error(RangeError) - end + it "raises a RangeError when passed a big negative value" do + -> { 42.round(min_long - 1) }.should raise_error(RangeError) end it "raises a RangeError when passed Float::INFINITY" do diff --git a/core/integer/size_spec.rb b/core/integer/size_spec.rb index a134e82384..725e9eb062 100644 --- a/core/integer/size_spec.rb +++ b/core/integer/size_spec.rb @@ -1,7 +1,7 @@ require_relative '../../spec_helper' describe "Integer#size" do - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "returns the number of bytes in the machine representation of self" do -1.size.should == 4 0.size.should == 4 @@ -9,7 +9,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "returns the number of bytes in the machine representation of self" do -1.size.should == 8 0.size.should == 8 diff --git a/core/kernel/eval_spec.rb b/core/kernel/eval_spec.rb index 454bc4a58e..5f4cd27da0 100644 --- a/core/kernel/eval_spec.rb +++ b/core/kernel/eval_spec.rb @@ -274,6 +274,26 @@ class EvalSpecs eval("").should == nil end + context "with shebang" do + it "ignores shebang with ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env ruby + Process.pid + CODE + + pid.should == Process.pid + end + + it "ignores shebang with non-ruby interpreter" do + pid = eval(<<~CODE.b) + #!/usr/bin/env puma + Process.pid + CODE + + pid.should == Process.pid + end + end + # See language/magic_comment_spec.rb for more magic comments specs describe "with a magic encoding comment" do it "uses the magic comment encoding for the encoding of literal strings" do diff --git a/core/kernel/extend_spec.rb b/core/kernel/extend_spec.rb index 47b22f3a18..6342d8cae1 100644 --- a/core/kernel/extend_spec.rb +++ b/core/kernel/extend_spec.rb @@ -76,4 +76,16 @@ def self.append_features(o) -> { @frozen.extend @module }.should raise_error(FrozenError) end end + + it "updated class methods of a module when it extends self and includes another module" do + a = Module.new do + extend self + end + b = Module.new do + def foo; :foo; end + end + + a.include b + a.foo.should == :foo + end end diff --git a/core/kernel/format_spec.rb b/core/kernel/format_spec.rb index e8b031e480..1d0c000c15 100644 --- a/core/kernel/format_spec.rb +++ b/core/kernel/format_spec.rb @@ -12,4 +12,36 @@ it "is accessible as a module function" do Kernel.format("%s", "hello").should == "hello" end + + describe "when $VERBOSE is true" do + it "warns if too many arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test", 1) + RUBY + + ruby_exe(code, args: "2>&1").should include("warning: too many arguments for format string") + end + + it "does not warns if too many keyword arguments are passed" do + code = <<~RUBY + $VERBOSE = true + format("test %{test}", test: 1, unused: 2) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + + ruby_bug "#20593", ""..."3.4" do + it "doesn't warns if keyword arguments are passed and none are used" do + code = <<~RUBY + $VERBOSE = true + format("test", test: 1) + format("test", {}) + RUBY + + ruby_exe(code, args: "2>&1").should_not include("warning") + end + end + end end diff --git a/core/kernel/raise_spec.rb b/core/kernel/raise_spec.rb index 4f190c120b..a038dcf031 100644 --- a/core/kernel/raise_spec.rb +++ b/core/kernel/raise_spec.rb @@ -46,6 +46,22 @@ cause = StandardError.new -> { raise(cause: cause) }.should raise_error(ArgumentError) end + + it "re-raises a rescued exception" do + -> do + begin + raise StandardError, "aaa" + rescue Exception + begin + raise ArgumentError + rescue ArgumentError + end + + # should raise StandardError "aaa" + raise + end + end.should raise_error(StandardError, "aaa") + end end describe "Kernel#raise" do diff --git a/core/kernel/sleep_spec.rb b/core/kernel/sleep_spec.rb index d35e313006..1de52a707f 100644 --- a/core/kernel/sleep_spec.rb +++ b/core/kernel/sleep_spec.rb @@ -51,6 +51,20 @@ def o.divmod(*); [0, 0.001]; end t.value.should == 5 end + platform_is_not :darwin do + it "sleeps with nanosecond precision" do + start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 100.times do + sleep(0.0001) + end + end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + actual_duration = end_time - start_time + (actual_duration > 0.01).should == true # 100 * 0.0001 => 0.01 + (actual_duration < 0.03).should == true + end + end + ruby_version_is ""..."3.3" do it "raises a TypeError when passed nil" do -> { sleep(nil) }.should raise_error(TypeError) diff --git a/core/marshal/dump_spec.rb b/core/marshal/dump_spec.rb index 0f77279a4f..adabfdf025 100644 --- a/core/marshal/dump_spec.rb +++ b/core/marshal/dump_spec.rb @@ -38,7 +38,7 @@ ].should be_computed_by(:dump) end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "dumps a positive Fixnum > 31 bits as a Bignum" do Marshal.dump(2**31 + 1).should == "\x04\bl+\a\x01\x00\x00\x80" end diff --git a/core/marshal/shared/load.rb b/core/marshal/shared/load.rb index f599042529..4eac21a952 100644 --- a/core/marshal/shared/load.rb +++ b/core/marshal/shared/load.rb @@ -1049,7 +1049,7 @@ def io.binmode; raise "binmode"; end end describe "for a Bignum" do - platform_is wordsize: 64 do + platform_is c_long_size: 64 do context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do it "dumps a Fixnum" do val = Marshal.send(@method, "\004\bl+\ab:wU") diff --git a/core/module/ancestors_spec.rb b/core/module/ancestors_spec.rb index 5e4c196206..43ebdb864f 100644 --- a/core/module/ancestors_spec.rb +++ b/core/module/ancestors_spec.rb @@ -21,6 +21,17 @@ class << ModuleSpecs::Child; self; end.ancestors.should include(ModuleSpecs::Int ModuleSpecs::Parent.ancestors.should == ModuleSpecs::Parent.ancestors.uniq end + it "returns a module that is included later into a nested module as well" do + m1 = Module.new + m2 = Module.new + m3 = Module.new do + include m2 + end + m2.include m1 # should be after m3 includes m2 + + m3.ancestors.should == [m3, m2, m1] + end + describe "when called on a singleton class" do it "includes the singleton classes of ancestors" do parent = Class.new diff --git a/core/module/const_added_spec.rb b/core/module/const_added_spec.rb index f9edda3a07..4b10dd5963 100644 --- a/core/module/const_added_spec.rb +++ b/core/module/const_added_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'fixtures/const_added' describe "Module#const_added" do ruby_version_is "3.2" do @@ -63,6 +64,27 @@ module SubModule ScratchPad.recorded.should == [:SubModule] end + it "is called when a new module is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << name + end + + module self::A + def self.const_added(name) + ScratchPad << name + end + + module self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + end + it "is called when a new class is defined under self" do ScratchPad.record [] @@ -83,6 +105,27 @@ class SubClass ScratchPad.recorded.should == [:SubClass] end + it "is called when a new class is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModuleB = Module.new do + def self.const_added(name) + ScratchPad << name + end + + class self::A + def self.const_added(name) + ScratchPad << name + end + + class self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + end + it "is called when an autoload is defined" do ScratchPad.record [] diff --git a/core/module/fixtures/const_added.rb b/core/module/fixtures/const_added.rb new file mode 100644 index 0000000000..0f5baad65d --- /dev/null +++ b/core/module/fixtures/const_added.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module ConstAddedSpecs + end +end diff --git a/core/module/fixtures/name.rb b/core/module/fixtures/name.rb index fb9e66c309..25c74d3944 100644 --- a/core/module/fixtures/name.rb +++ b/core/module/fixtures/name.rb @@ -7,4 +7,7 @@ def name Cß.name end end + + module NameSpecs + end end diff --git a/core/module/include_spec.rb b/core/module/include_spec.rb index 78f6b41031..862c6976e1 100644 --- a/core/module/include_spec.rb +++ b/core/module/include_spec.rb @@ -581,6 +581,29 @@ def foo c2.include(m) c2.new.foo.should == [:c2, :m1] end + + it "update a module when a nested module is updated and includes a module on its own" do + m1 = Module.new + m2 = Module.new do + def m2; [:m2]; end + end + m3 = Module.new do + def m3; [:m3]; end + end + m4 = Module.new do + def m4; [:m4]; end + end + c = Class.new + + c.include(m1) + m1.include(m2) + m2.include(m3) + m3.include(m4) + + c.new.m2.should == [:m2] + c.new.m3.should == [:m3] + c.new.m4.should == [:m4] + end end describe "Module#include?" do diff --git a/core/module/name_spec.rb b/core/module/name_spec.rb index 0d1f4e24d5..33e8400e88 100644 --- a/core/module/name_spec.rb +++ b/core/module/name_spec.rb @@ -140,6 +140,47 @@ module ModuleSpecs::Anonymous valid_names.should include(m::N.name) # You get one of the two, but you don't know which one. end + ruby_version_is "3.2" do + it "is set in #const_added callback when a module defined in the top-level scope" do + ruby_exe(<<~RUBY, args: "2>&1").chomp.should == "TEST1\nTEST2" + class Module + def const_added(name) + puts const_get(name).name + end + end + + # module with name + module TEST1 + end + + # anonymous module + TEST2 = Module.new + RUBY + end + + it "is set in #const_added callback for a nested module when an outer module defined in the top-level scope" do + ScratchPad.record [] + + ModuleSpecs::NameSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << const_get(name).name + end + + module self::A + def self.const_added(name) + ScratchPad << const_get(name).name + end + + module self::B + end + end + end + + ScratchPad.recorded.should.one?(/#::A$/) + ScratchPad.recorded.should.one?(/#::A::B$/) + end + end + it "returns a frozen String" do ModuleSpecs.name.should.frozen? end diff --git a/core/module/new_spec.rb b/core/module/new_spec.rb index da7f3b8720..ec521360bd 100644 --- a/core/module/new_spec.rb +++ b/core/module/new_spec.rb @@ -6,6 +6,10 @@ Module.new.is_a?(Module).should == true end + it "creates a module without a name" do + Module.new.name.should be_nil + end + it "creates a new Module and passes it to the provided block" do test_mod = nil m = Module.new do |mod| diff --git a/core/process/daemon_spec.rb b/core/process/daemon_spec.rb index 70ffd1b320..36825771d1 100644 --- a/core/process/daemon_spec.rb +++ b/core/process/daemon_spec.rb @@ -2,6 +2,9 @@ require_relative 'fixtures/common' platform_is_not :windows do + # macOS 15 beta is not working this examples + return if /darwin/ =~ RUBY_PLATFORM && /15/ =~ `sw_vers -productVersion` + describe :process_daemon_keep_stdio_open_false, shared: true do it "redirects stdout to /dev/null" do @daemon.invoke("keep_stdio_open_false_stdout", @object).should == "" diff --git a/core/range/step_spec.rb b/core/range/step_spec.rb index 64ea3de4ed..31cfd400cc 100644 --- a/core/range/step_spec.rb +++ b/core/range/step_spec.rb @@ -10,44 +10,50 @@ r.step { }.should equal(r) end - it "raises TypeError if step" do - obj = mock("mock") - -> { (1..10).step(obj) { } }.should raise_error(TypeError) - end + ruby_version_is ""..."3.4" do + it "calls #to_int to coerce step to an Integer" do + obj = mock("Range#step") + obj.should_receive(:to_int).and_return(1) - it "calls #to_int to coerce step to an Integer" do - obj = mock("Range#step") - obj.should_receive(:to_int).and_return(1) + (1..2).step(obj) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 2]) + end - (1..2).step(obj) { |x| ScratchPad << x } - ScratchPad.recorded.should eql([1, 2]) - end + it "raises a TypeError if step does not respond to #to_int" do + obj = mock("Range#step non-integer") - it "raises a TypeError if step does not respond to #to_int" do - obj = mock("Range#step non-integer") + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end - -> { (1..2).step(obj) { } }.should raise_error(TypeError) - end + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("Range#step non-integer") + obj.should_receive(:to_int).and_return("1") - it "raises a TypeError if #to_int does not return an Integer" do - obj = mock("Range#step non-integer") - obj.should_receive(:to_int).and_return("1") + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end - -> { (1..2).step(obj) { } }.should raise_error(TypeError) - end + it "raises a TypeError if the first element does not respond to #succ" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) - it "coerces the argument to integer by invoking to_int" do - (obj = mock("2")).should_receive(:to_int).and_return(2) - res = [] - (1..10).step(obj) {|x| res << x} - res.should == [1, 3, 5, 7, 9] + -> { (obj..obj).step { |x| x } }.should raise_error(TypeError) + end end - it "raises a TypeError if the first element does not respond to #succ" do - obj = mock("Range#step non-comparable") - obj.should_receive(:<=>).with(obj).and_return(1) + ruby_version_is "3.4" do + it "calls #coerce to coerce step to an Integer" do + obj = mock("Range#step") + obj.should_receive(:coerce).at_least(:once).and_return([1, 2]) + + (1..3).step(obj) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 3]) + end + + it "raises a TypeError if step does not respond to #coerce" do + obj = mock("Range#step non-coercible") - -> { (obj..obj).step { |x| x } }.should raise_error(TypeError) + -> { (1..2).step(obj) { } }.should raise_error(TypeError) + end end it "raises an ArgumentError if step is 0" do @@ -58,8 +64,17 @@ -> { (-1..1).step(0.0) { |x| x } }.should raise_error(ArgumentError) end - it "raises an ArgumentError if step is negative" do - -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError) + ruby_version_is "3.4" do + it "does not raise an ArgumentError if step is 0 for non-numeric ranges" do + t = Time.utc(2023, 2, 24) + -> { (t..t+1).step(0) { break } }.should_not raise_error(ArgumentError) + end + end + + ruby_version_is ""..."3.4" do + it "raises an ArgumentError if step is negative" do + -> { (-1..1).step(-2) { |x| x } }.should raise_error(ArgumentError) + end end describe "with inclusive end" do @@ -78,6 +93,18 @@ (-2..2).step(1.5) { |x| ScratchPad << x } ScratchPad.recorded.should eql([-2.0, -0.5, 1.0]) end + + ruby_version_is "3.4" do + it "does not iterate if step is negative for forward range" do + (-1..1).step(-1) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([]) + end + + it "iterates backward if step is negative for backward range" do + (1..-1).step(-1) { |x| ScratchPad << x } + ScratchPad.recorded.should eql([1, 0, -1]) + end + end end describe "and Float values" do @@ -162,13 +189,96 @@ -> { ("A".."G").step(2.0) { } }.should raise_error(TypeError) end - it "calls #succ on begin and each element returned by #succ" do - obj = mock("Range#step String start") - obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0) - obj.should_receive(:succ).exactly(2).times.and_return(obj) + ruby_version_is ""..."3.4" do + it "calls #succ on begin and each element returned by #succ" do + obj = mock("Range#step String start") + obj.should_receive(:<=>).exactly(3).times.and_return(-1, -1, -1, 0) + obj.should_receive(:succ).exactly(2).times.and_return(obj) + + (obj..obj).step { |x| ScratchPad << x } + ScratchPad.recorded.should == [obj, obj, obj] + end + end + + ruby_version_is "3.4" do + it "yields String values adjusted by step and less than or equal to end" do + ("A".."AAA").step("A") { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) + end + + it "calls #+ on begin and each element returned by #+" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") + + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(-1) + # Deciding whether the step moves iteration in the right direction + start.should_receive(:<=>).with(mid1).and_return(-1) + # Iteration 1 + start.should_receive(:+).at_least(:once).with(step).and_return(mid1) + # Iteration 2 + mid1.should_receive(:<=>).with(stop).and_return(-1) + mid1.should_receive(:+).with(step).and_return(mid2) + # Iteration 3 + mid2.should_receive(:<=>).with(stop).and_return(0) + + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [start, mid1, mid2] + end + + it "iterates backward if the step is decreasing values, and the range is backward" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") + + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1) + # Deciding whether the step moves iteration in the right direction + start.should_receive(:<=>).with(mid1).and_return(1) + # Iteration 1 + start.should_receive(:+).at_least(:once).with(step).and_return(mid1) + # Iteration 2 + mid1.should_receive(:<=>).with(stop).and_return(1) + mid1.should_receive(:+).with(step).and_return(mid2) + # Iteration 3 + mid2.should_receive(:<=>).with(stop).and_return(0) + + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [start, mid1, mid2] + end + + it "does no iteration of the direction of the range and of the step don't match" do + start = mock("Range#step String start") + stop = mock("Range#step String stop") + + mid1 = mock("Range#step String mid1") + mid2 = mock("Range#step String mid2") + + step = mock("Range#step String step") + + # Deciding on the direction of iteration: stop > start + start.should_receive(:<=>).with(stop).at_least(:twice).and_return(1) + # Deciding whether the step moves iteration in the right direction + # start + step < start, the direction is opposite to the range's + start.should_receive(:+).with(step).and_return(mid1) + start.should_receive(:<=>).with(mid1).and_return(-1) - (obj..obj).step { |x| ScratchPad << x } - ScratchPad.recorded.should == [obj, obj, obj] + (start..stop).step(step) { |x| ScratchPad << x } + ScratchPad.recorded.should == [] + end end end end @@ -266,18 +376,31 @@ end describe "and String values" do - it "yields String values incremented by #succ and less than or equal to end when not passed a step" do - ("A"..."E").step { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "B", "C", "D"] - end + ruby_version_is ""..."3.4" do + it "yields String values incremented by #succ and less than or equal to end when not passed a step" do + ("A"..."E").step { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "B", "C", "D"] + end - it "yields String values incremented by #succ called Integer step times" do - ("A"..."G").step(2) { |x| ScratchPad << x } - ScratchPad.recorded.should == ["A", "C", "E"] + it "yields String values incremented by #succ called Integer step times" do + ("A"..."G").step(2) { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "C", "E"] + end + + it "raises a TypeError when passed a Float step" do + -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError) + end end - it "raises a TypeError when passed a Float step" do - -> { ("A"..."G").step(2.0) { } }.should raise_error(TypeError) + ruby_version_is "3.4" do + it "yields String values adjusted by step and less than or equal to end" do + ("A"..."AAA").step("A") { |x| ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { ("A".."G").step([]) { } }.should raise_error(TypeError) + end end end end @@ -373,6 +496,22 @@ -> { eval("('A'..)").step(2.0) { } }.should raise_error(TypeError) -> { eval("('A'...)").step(2.0) { } }.should raise_error(TypeError) end + + ruby_version_is "3.4" do + it "yields String values adjusted by step" do + eval("('A'..)").step("A") { |x| break if x > "AAA"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + + ScratchPad.record [] + eval("('A'...)").step("A") { |x| break if x > "AAA"; ScratchPad << x } + ScratchPad.recorded.should == ["A", "AA", "AAA"] + end + + it "raises a TypeError when passed an incompatible type step" do + -> { eval("('A'..)").step([]) { } }.should raise_error(TypeError) + -> { eval("('A'...)").step([]) { } }.should raise_error(TypeError) + end + end end end @@ -383,15 +522,24 @@ describe "returned Enumerator" do describe "size" do - it "raises a TypeError if step does not respond to #to_int" do - obj = mock("Range#step non-integer") - -> { (1..2).step(obj) }.should raise_error(TypeError) + ruby_version_is ""..."3.4" do + it "raises a TypeError if step does not respond to #to_int" do + obj = mock("Range#step non-integer") + -> { (1..2).step(obj) }.should raise_error(TypeError) + end + + it "raises a TypeError if #to_int does not return an Integer" do + obj = mock("Range#step non-integer") + obj.should_receive(:to_int).and_return("1") + -> { (1..2).step(obj) }.should raise_error(TypeError) + end end - it "raises a TypeError if #to_int does not return an Integer" do - obj = mock("Range#step non-integer") - obj.should_receive(:to_int).and_return("1") - -> { (1..2).step(obj) }.should raise_error(TypeError) + ruby_version_is "3.4" do + it "does not raise if step is incompatible" do + obj = mock("Range#step non-integer") + -> { (1..2).step(obj) }.should_not raise_error + end end it "returns the ceil of range size divided by the number of steps" do @@ -431,19 +579,36 @@ (1.0...6.4).step(1.8).size.should == 3 end - it "returns nil with begin and end are String" do - ("A".."E").step(2).size.should == nil - ("A"..."E").step(2).size.should == nil - ("A".."E").step.size.should == nil - ("A"..."E").step.size.should == nil + ruby_version_is ""..."3.4" do + it "returns nil with begin and end are String" do + ("A".."E").step(2).size.should == nil + ("A"..."E").step(2).size.should == nil + ("A".."E").step.size.should == nil + ("A"..."E").step.size.should == nil + end + + it "return nil and not raises a TypeError if the first element does not respond to #succ" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) + enum = (obj..obj).step + -> { enum.size }.should_not raise_error + enum.size.should == nil + end end - it "return nil and not raises a TypeError if the first element does not respond to #succ" do - obj = mock("Range#step non-comparable") - obj.should_receive(:<=>).with(obj).and_return(1) - enum = (obj..obj).step - -> { enum.size }.should_not raise_error - enum.size.should == nil + ruby_version_is "3.4" do + it "returns nil with begin and end are String" do + ("A".."E").step("A").size.should == nil + ("A"..."E").step("A").size.should == nil + end + + it "return nil and not raises a TypeError if the first element is not of compatible type" do + obj = mock("Range#step non-comparable") + obj.should_receive(:<=>).with(obj).and_return(1) + enum = (obj..obj).step(obj) + -> { enum.size }.should_not raise_error + enum.size.should == nil + end end end @@ -470,22 +635,48 @@ (1..).step(2).take(3).should == [1, 3, 5] end - it "returns an instance of Enumerator when begin is not numeric" do - ("a"..).step.class.should == Enumerator - ("a"..).step(2).take(3).should == %w[a c e] + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator when begin is not numeric" do + ("a"..).step.class.should == Enumerator + ("a"..).step(2).take(3).should == %w[a c e] + end + end + + ruby_version_is "3.4" do + it "returns an instance of Enumerator when begin is not numeric" do + ("a"..).step("a").class.should == Enumerator + ("a"..).step("a").take(3).should == %w[a aa aaa] + end end end context "when range is beginless and endless" do - it "returns an instance of Enumerator" do - Range.new(nil, nil).step.class.should == Enumerator + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator" do + Range.new(nil, nil).step.class.should == Enumerator + end + end + + ruby_version_is "3.4" do + it "raises an ArgumentError" do + -> { Range.new(nil, nil).step(1) }.should raise_error(ArgumentError) + end end end context "when begin and end are not numerics" do - it "returns an instance of Enumerator" do - ("a".."z").step.class.should == Enumerator - ("a".."z").step(3).take(4).should == %w[a d g j] + ruby_version_is ""..."3.4" do + it "returns an instance of Enumerator" do + ("a".."z").step.class.should == Enumerator + ("a".."z").step(3).take(4).should == %w[a d g j] + end + end + + ruby_version_is "3.4" do + it "returns an instance of Enumerator" do + ("a".."z").step("a").class.should == Enumerator + ("a".."z").step("a").take(4).should == %w[a aa aaa aaaa] + end end end end diff --git a/core/string/append_as_bytes_spec.rb b/core/string/append_as_bytes_spec.rb new file mode 100644 index 0000000000..0e1d09558b --- /dev/null +++ b/core/string/append_as_bytes_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' + +describe "String#append_bytes" do + ruby_version_is "3.4" do + it "doesn't allow to mutate frozen strings" do + str = "hello".freeze + -> { str.append_as_bytes("\xE2\x82") }.should raise_error(FrozenError) + end + + it "allows creating broken strings" do + str = +"hello" + str.append_as_bytes("\xE2\x82") + str.valid_encoding?.should == false + + str.append_as_bytes("\xAC") + str.valid_encoding?.should == true + + str = "abc".encode(Encoding::UTF_32LE) + str.append_as_bytes("def") + str.encoding.should == Encoding::UTF_32LE + str.valid_encoding?.should == false + end + + it "never changes the receiver encoding" do + str = "".b + str.append_as_bytes("€") + str.encoding.should == Encoding::BINARY + end + + it "accepts variadic String or Integer arguments" do + str = "hello".b + str.append_as_bytes("\xE2\x82", 12, 43, "\xAC") + str.encoding.should == Encoding::BINARY + str.should == "hello\xE2\x82\f+\xAC".b + end + + it "only accepts strings or integers, and doesn't attempt to cast with #to_str or #to_int" do + to_str = mock("to_str") + to_str.should_not_receive(:to_str) + to_str.should_not_receive(:to_int) + + str = +"hello" + -> { str.append_as_bytes(to_str) }.should raise_error(TypeError, "wrong argument type MockObject (expected String or Integer)") + end + end +end diff --git a/core/string/bytesplice_spec.rb b/core/string/bytesplice_spec.rb index 967edcba29..5cb3b2c158 100644 --- a/core/string/bytesplice_spec.rb +++ b/core/string/bytesplice_spec.rb @@ -58,6 +58,79 @@ -> { s.bytesplice(2, 1, "xxx") }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") end end + + ruby_version_is "3.3" do + it "raises IndexError when str_index is less than -bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", -6, 0) }.should raise_error(IndexError, "index -6 out of string") + end + + it "raises IndexError when str_index is greater than bytesize" do + -> { "hello".bytesplice(2, 1, "HELLO", 6, 0) }.should raise_error(IndexError, "index 6 out of string") + end + + it "raises IndexError for negative str length" do + -> { "abc".bytesplice(0, 1, "", 0, -2) }.should raise_error(IndexError, "negative length -2") + end + + it "replaces with integer str indices" do + "hello".bytesplice(1, 2, "HELLO", -5, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 0).should == "hlo" + "hello".bytesplice(1, 2, "HELLO", 0, 1).should == "hHlo" + "hello".bytesplice(1, 2, "HELLO", 0, 5).should == "hHELLOlo" + "hello".bytesplice(1, 2, "HELLO", 0, 6).should == "hHELLOlo" + end + + it "raises RangeError when str range left boundary is less than -bytesize" do + -> { "hello".bytesplice(0..1, "HELLO", -6...-6) }.should raise_error(RangeError, "-6...-6 out of range") + end + + it "replaces with str ranges" do + "hello".bytesplice(1..2, "HELLO", -5...-5).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0...0).should == "hlo" + "hello".bytesplice(1..2, "HELLO", 0..0).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0...1).should == "hHlo" + "hello".bytesplice(1..2, "HELLO", 0..1).should == "hHElo" + "hello".bytesplice(1..2, "HELLO", 0..-1).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...5).should == "hHELLOlo" + "hello".bytesplice(1..2, "HELLO", 0...6).should == "hHELLOlo" + end + + it "raises ArgumentError when integer str index is provided without str length argument" do + -> { "hello".bytesplice(0, 1, "xxx", 0) }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 2, 3, or 5)") + end + + it "replaces on an empty string with str index/length" do + "".bytesplice(0, 0, "", 0, 0).should == "" + "".bytesplice(0, 0, "xxx", 0, 1).should == "x" + end + + it "mutates self with substring and str index/length" do + s = "hello" + s.bytesplice(2, 1, "xxx", 1, 2).should.equal?(s) + s.should.eql?("hexxlo") + end + + it "raises when string is frozen and str index/length" do + s = "hello".freeze + -> { s.bytesplice(2, 1, "xxx", 0, 1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + end + + it "replaces on an empty string with str range" do + "".bytesplice(0..0, "", 0..0).should == "" + "".bytesplice(0..0, "xyz", 0..1).should == "xy" + end + + it "mutates self with substring and str range" do + s = "hello" + s.bytesplice(2..2, "xyz", 1..2).should.equal?(s) + s.should.eql?("heyzlo") + end + + it "raises when string is frozen and str range" do + s = "hello".freeze + -> { s.bytesplice(2..2, "yzx", 0..1) }.should raise_error(FrozenError, "can't modify frozen String: \"hello\"") + end + end end describe "String#bytesplice with multibyte characters" do @@ -131,4 +204,95 @@ result.encoding.should == Encoding::UTF_8 end end + + ruby_version_is "3.3" do + it "raises IndexError when str_index is out of byte size boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", -16, 0) }.should raise_error(IndexError, "index -16 out of string") + end + + it "raises IndexError when str_index is not on a codepoint boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 1, 0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + end + + it "raises IndexError when str_length is not matching the codepoint boundary" do + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "こんにちは".bytesplice(3, 3, "こんにちは", 0, 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + end + + it "replaces with integer str indices" do + "こんにちは".bytesplice(3, 3, "こんにちは", -15, 0).should == "こにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 0, 0).should == "こにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 0, 3).should == "ここにちは" + "こんにちは".bytesplice(3, 3, "はは", 3, 3).should == "こはにちは" + "こんにちは".bytesplice(3, 3, "こんにちは", 15, 0).should == "こにちは" + end + + it "replaces with str range" do + "こんにちは".bytesplice(0..2, "こんにちは", -15...-16).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...0).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3..5).should == "んんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3...6).should == "んんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3..8).should == "んにんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0..-1).should == "こんにちはんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...15).should == "こんにちはんにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 0...18).should == "こんにちはんにちは" + end + + it "treats negative length for str range as 0" do + "こんにちは".bytesplice(0..2, "こんにちは", 0...-100).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", 3...-100).should == "んにちは" + "こんにちは".bytesplice(0..2, "こんにちは", -15...-100).should == "んにちは" + end + + it "raises when ranges not match codepoint boundaries in str" do + -> { "こんにちは".bytesplice(3...3, "こ", 0..0) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こ", 0..1) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + # Begin is incorrect + -> { "こんにちは".bytesplice(3...3, "こんにちは", -4..-1) }.should raise_error(IndexError, "offset 11 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こんにちは", -5..-1) }.should raise_error(IndexError, "offset 10 does not land on character boundary") + # End is incorrect + -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-2) }.should raise_error(IndexError, "offset 14 does not land on character boundary") + -> { "こんにちは".bytesplice(3...3, "こんにちは", -3..-3) }.should raise_error(IndexError, "offset 13 does not land on character boundary") + end + + it "deals with a different encoded argument with str index/length" do + s = "こんにちは" + s.encoding.should == Encoding::UTF_8 + sub = "goodbye" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(3, 3, sub, 0, 3) + result.should == "こgooにちは" + result.encoding.should == Encoding::UTF_8 + + s = "hello" + s.force_encoding(Encoding::US_ASCII) + sub = "こんにちは" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(1, 2, sub, 3, 3) + result.should == "hんlo" + result.encoding.should == Encoding::UTF_8 + end + + it "deals with a different encoded argument with str range" do + s = "こんにちは" + s.encoding.should == Encoding::UTF_8 + sub = "goodbye" + sub.force_encoding(Encoding::US_ASCII) + + result = s.bytesplice(3..5, sub, 0..2) + result.should == "こgooにちは" + result.encoding.should == Encoding::UTF_8 + + s = "hello" + s.force_encoding(Encoding::US_ASCII) + sub = "こんにちは" + sub.encoding.should == Encoding::UTF_8 + + result = s.bytesplice(1..2, sub, 3..5) + result.should == "hんlo" + result.encoding.should == Encoding::UTF_8 + end + end end diff --git a/core/string/modulo_spec.rb b/core/string/modulo_spec.rb index 9045afa263..8e3853551f 100644 --- a/core/string/modulo_spec.rb +++ b/core/string/modulo_spec.rb @@ -570,7 +570,7 @@ def universal.to_f() 0.0 end ("%1$p" % [10, 5]).should == "10" ("%-22p" % 10).should == "10 " ("%*p" % [10, 10]).should == " 10" - ("%p" % {capture: 1}).should == "{:capture=>1}" + ("%p" % {capture: 1}).should == {capture: 1}.inspect ("%p" % "str").should == "\"str\"" end @@ -749,6 +749,9 @@ def obj.to_s() "obj" end (format % "-10.4e-20").should == (format % -10.4e-20) (format % ".5").should == (format % 0.5) (format % "-.5").should == (format % -0.5) + ruby_bug("#20705", ""..."3.4") do + (format % "10.").should == (format % 10) + end # Something's strange with this spec: # it works just fine in individual mode, but not when run as part of a group (format % "10_1_0.5_5_5").should == (format % 1010.555) @@ -758,7 +761,6 @@ def obj.to_s() "obj" end -> { format % "" }.should raise_error(ArgumentError) -> { format % "x" }.should raise_error(ArgumentError) -> { format % "." }.should raise_error(ArgumentError) - -> { format % "10." }.should raise_error(ArgumentError) -> { format % "5x" }.should raise_error(ArgumentError) -> { format % "0b1" }.should raise_error(ArgumentError) -> { format % "10e10.5" }.should raise_error(ArgumentError) diff --git a/core/string/unpack/l_spec.rb b/core/string/unpack/l_spec.rb index 18bb68b8d0..0adb567eca 100644 --- a/core/string/unpack/l_spec.rb +++ b/core/string/unpack/l_spec.rb @@ -14,7 +14,7 @@ it_behaves_like :string_unpack_32bit_be_unsigned, 'L>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_32bit_le, 'L<_' it_behaves_like :string_unpack_32bit_le, 'L_<' @@ -44,7 +44,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_64bit_le, 'L<_' it_behaves_like :string_unpack_64bit_le, 'L_<' @@ -86,7 +86,7 @@ it_behaves_like :string_unpack_32bit_be_signed, 'l>' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_32bit_le, 'l<_' it_behaves_like :string_unpack_32bit_le, 'l_<' @@ -116,7 +116,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "with modifier '<' and '_'" do it_behaves_like :string_unpack_64bit_le, 'l<_' it_behaves_like :string_unpack_64bit_le, 'l_<' @@ -160,7 +160,7 @@ it_behaves_like :string_unpack_32bit_le_signed, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_32bit_le, 'L_' it_behaves_like :string_unpack_32bit_le_unsigned, 'L_' @@ -182,7 +182,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_64bit_le, 'L_' it_behaves_like :string_unpack_64bit_le_unsigned, 'L_' @@ -218,7 +218,7 @@ it_behaves_like :string_unpack_32bit_be_signed, 'l' end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_32bit_be, 'L_' it_behaves_like :string_unpack_32bit_be_unsigned, 'L_' @@ -240,7 +240,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "String#unpack with format 'L' with modifier '_'" do it_behaves_like :string_unpack_64bit_be, 'L_' it_behaves_like :string_unpack_64bit_be_unsigned, 'L_' diff --git a/core/thread/element_reference_spec.rb b/core/thread/element_reference_spec.rb index 85280cb287..fde9d1f440 100644 --- a/core/thread/element_reference_spec.rb +++ b/core/thread/element_reference_spec.rb @@ -37,6 +37,17 @@ t2["value"].should == 2 end + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('value') + key.should_receive(:to_str).and_return('value') + + th = Thread.new do + Thread.current[:value] = 1 + end.join + + th[key].should == 1 + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current[nil] }.should raise_error(TypeError) -> { Thread.current[5] }.should raise_error(TypeError) diff --git a/core/thread/element_set_spec.rb b/core/thread/element_set_spec.rb index c7498f7ac9..f205177304 100644 --- a/core/thread/element_set_spec.rb +++ b/core/thread/element_set_spec.rb @@ -12,10 +12,33 @@ th.freeze -> { th[:foo] = "bar" - }.should raise_error(FrozenError, /frozen/) + }.should raise_error(FrozenError, "can't modify frozen thread locals") end.join end + it "accepts Strings and Symbols" do + t1 = Thread.new do + Thread.current[:value] = 1 + end.join + t2 = Thread.new do + Thread.current["value"] = 2 + end.join + + t1[:value].should == 1 + t2[:value].should == 2 + end + + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('value') + key.should_receive(:to_str).and_return('value') + + th = Thread.new do + Thread.current[key] = 1 + end.join + + th[:value].should == 1 + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current[nil] = true }.should raise_error(TypeError) -> { Thread.current[5] = true }.should raise_error(TypeError) diff --git a/core/thread/key_spec.rb b/core/thread/key_spec.rb index 6940cf5f28..339fa98f53 100644 --- a/core/thread/key_spec.rb +++ b/core/thread/key_spec.rb @@ -16,6 +16,13 @@ @th.key?(:stanley.to_s).should == false end + it "converts a key that is neither String nor Symbol with #to_str" do + key = mock('key') + key.should_receive(:to_str).and_return('oliver') + + @th.key?(key).should == true + end + it "raises exceptions on the wrong type of keys" do -> { Thread.current.key? nil }.should raise_error(TypeError) -> { Thread.current.key? 5 }.should raise_error(TypeError) diff --git a/core/thread/thread_variable_get_spec.rb b/core/thread/thread_variable_get_spec.rb index 67017771fd..1ea34cf2b3 100644 --- a/core/thread/thread_variable_get_spec.rb +++ b/core/thread/thread_variable_get_spec.rb @@ -41,13 +41,20 @@ @t.thread_variable_get(:a).should be_nil end - it "does not raise a TypeError if the key is neither Symbol nor String, nor responds to #to_str" do - @t.thread_variable_get(123).should be_nil + it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do + @t.thread_variable_set(:a, 49) + -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/) end - it "does not try to convert the key with #to_sym" do - key = mock('key') - key.should_not_receive(:to_sym) - @t.thread_variable_get(key).should be_nil + ruby_version_is '3.4' do + it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do + -> { @t.thread_variable_get(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do + key = mock('key') + key.should_not_receive(:to_sym) + -> { @t.thread_variable_get(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/) + end end end diff --git a/core/thread/thread_variable_set_spec.rb b/core/thread/thread_variable_set_spec.rb index c262a6614e..eadee76afb 100644 --- a/core/thread/thread_variable_set_spec.rb +++ b/core/thread/thread_variable_set_spec.rb @@ -51,12 +51,12 @@ end it "raises a TypeError if the key is neither Symbol nor String, nor responds to #to_str" do - -> { @t.thread_variable_set(123, 1) }.should raise_error(TypeError, '123 is not a symbol') + -> { @t.thread_variable_set(123, 1) }.should raise_error(TypeError, /123 is not a symbol/) end it "does not try to convert the key with #to_sym" do key = mock('key') key.should_not_receive(:to_sym) - -> { @t.thread_variable_set(key, 42) }.should raise_error(TypeError, "#{key.inspect} is not a symbol") + -> { @t.thread_variable_set(key, 42) }.should raise_error(TypeError, /#{Regexp.quote(key.inspect)} is not a symbol/) end end diff --git a/core/thread/thread_variable_spec.rb b/core/thread/thread_variable_spec.rb index d64e6ec63d..1b021e9404 100644 --- a/core/thread/thread_variable_spec.rb +++ b/core/thread/thread_variable_spec.rb @@ -41,13 +41,20 @@ @t.thread_variable?(:a).should be_false end - it "does not raise a TypeError if the key is neither Symbol nor String, nor responds to #to_str" do - @t.thread_variable?(123).should be_false + it "raises a TypeError if the key is neither Symbol nor String when thread variables are already set" do + @t.thread_variable_set(:a, 49) + -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/) end - it "does not try to convert the key with #to_sym" do - key = mock('key') - key.should_not_receive(:to_sym) - @t.thread_variable?(key).should be_false + ruby_version_is '3.4' do + it "raises a TypeError if the key is neither Symbol nor String when no thread variables are set" do + -> { @t.thread_variable?(123) }.should raise_error(TypeError, /123 is not a symbol/) + end + + it "raises a TypeError if the key is neither Symbol nor String without calling #to_sym" do + key = mock('key') + key.should_not_receive(:to_sym) + -> { @t.thread_variable?(key) }.should raise_error(TypeError, /#{Regexp.escape(key.inspect)} is not a symbol/) + end end end diff --git a/core/time/iso8601_spec.rb b/core/time/iso8601_spec.rb new file mode 100644 index 0000000000..ad60c3bb32 --- /dev/null +++ b/core/time/iso8601_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#iso8601" do + it_behaves_like :time_xmlschema, :iso8601 +end diff --git a/core/time/new_spec.rb b/core/time/new_spec.rb index d686355270..fa5b5c56d6 100644 --- a/core/time/new_spec.rb +++ b/core/time/new_spec.rb @@ -625,15 +625,21 @@ def obj.to_int; 3; end -> { Time.new("2020-12-25 00:56:17 +23:59:60") - }.should raise_error(ArgumentError, "utc_offset out of range") + }.should raise_error(ArgumentError, /utc_offset/) -> { Time.new("2020-12-25 00:56:17 +24:00") - }.should raise_error(ArgumentError, "utc_offset out of range") + }.should raise_error(ArgumentError, /utc_offset/) -> { Time.new("2020-12-25 00:56:17 +23:61") - }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +23:61') + }.should raise_error(ArgumentError, /utc_offset/) + + ruby_bug '#20797', ''...'3.4' do + -> { + Time.new("2020-12-25 00:56:17 +00:23:61") + }.should raise_error(ArgumentError, /utc_offset/) + end end it "raises ArgumentError if string has not ascii-compatible encoding" do diff --git a/core/time/shared/xmlschema.rb b/core/time/shared/xmlschema.rb new file mode 100644 index 0000000000..d68c18df36 --- /dev/null +++ b/core/time/shared/xmlschema.rb @@ -0,0 +1,31 @@ +describe :time_xmlschema, shared: true do + ruby_version_is "3.4" do + it "generates ISO-8601 strings in Z for UTC times" do + t = Time.utc(1985, 4, 12, 23, 20, 50, 521245) + t.send(@method).should == "1985-04-12T23:20:50Z" + t.send(@method, 2).should == "1985-04-12T23:20:50.52Z" + t.send(@method, 9).should == "1985-04-12T23:20:50.521245000Z" + end + + it "generates ISO-8601 string with timeone offset for non-UTC times" do + t = Time.new(1985, 4, 12, 23, 20, 50, "+02:00") + t.send(@method).should == "1985-04-12T23:20:50+02:00" + t.send(@method, 2).should == "1985-04-12T23:20:50.00+02:00" + end + + it "year is always at least 4 digits" do + t = Time.utc(12, 4, 12) + t.send(@method).should == "0012-04-12T00:00:00Z" + end + + it "year can be more than 4 digits" do + t = Time.utc(40_000, 4, 12) + t.send(@method).should == "40000-04-12T00:00:00Z" + end + + it "year can be negative" do + t = Time.utc(-2000, 4, 12) + t.send(@method).should == "-2000-04-12T00:00:00Z" + end + end +end diff --git a/core/time/xmlschema_spec.rb b/core/time/xmlschema_spec.rb new file mode 100644 index 0000000000..bdf1dc7923 --- /dev/null +++ b/core/time/xmlschema_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#xmlschema" do + it_behaves_like :time_xmlschema, :xmlschema +end diff --git a/core/tracepoint/inspect_spec.rb b/core/tracepoint/inspect_spec.rb index d0e633c49b..6cc2ebe243 100644 --- a/core/tracepoint/inspect_spec.rb +++ b/core/tracepoint/inspect_spec.rb @@ -67,7 +67,7 @@ def trace_point_spec_test_return end trace_point_spec_test_return end - ruby_version_is("3.4") { line = "(?:#{line}|#{line-1})" } + ruby_version_is("3.4") { line -= 1 } inspect.should =~ /\A#\z/ end diff --git a/core/warning/warn_spec.rb b/core/warning/warn_spec.rb index 1c21fe29e0..572885c2b4 100644 --- a/core/warning/warn_spec.rb +++ b/core/warning/warn_spec.rb @@ -121,7 +121,7 @@ def Warning.warn(msg) end end - ruby_bug '#19530', ''...'3.4' do + ruby_bug '#20573', ''...'3.4' do it "isn't called by Kernel.warn when category is :deprecated but Warning[:deprecated] is false" do warn_deprecated = Warning[:deprecated] begin diff --git a/language/pattern_matching_spec.rb b/language/pattern_matching_spec.rb index 52608b48be..94432b1fa0 100644 --- a/language/pattern_matching_spec.rb +++ b/language/pattern_matching_spec.rb @@ -232,11 +232,12 @@ end }.should raise_error(NoMatchingPatternError, /\[0, 1\]/) + error_pattern = ruby_version_is("3.4") ? /\{a: 0, b: 1\}/ : /\{:a=>0, :b=>1\}/ -> { case {a: 0, b: 1} in a: 1, b: 1 end - }.should raise_error(NoMatchingPatternError, /\{:a=>0, :b=>1\}/) + }.should raise_error(NoMatchingPatternError, error_pattern) end it "raises NoMatchingPatternError if no pattern matches and evaluates the expression only once" do diff --git a/language/predefined_spec.rb b/language/predefined_spec.rb index ac28f1e8a0..cc231e341e 100644 --- a/language/predefined_spec.rb +++ b/language/predefined_spec.rb @@ -243,6 +243,16 @@ def test(arg) end describe "Predefined global $!" do + it "is Fiber-local" do + Fiber.new do + raise "hi" + rescue + Fiber.yield + end.resume + + $!.should == nil + end + # See http://jira.codehaus.org/browse/JRUBY-5550 it "remains nil after a failed core class \"checked\" coercion against a class that defines method_missing" do $!.should == nil diff --git a/library/bigdecimal/sqrt_spec.rb b/library/bigdecimal/sqrt_spec.rb index 8fd1ec0f39..42cf4545cb 100644 --- a/library/bigdecimal/sqrt_spec.rb +++ b/library/bigdecimal/sqrt_spec.rb @@ -36,7 +36,7 @@ BigDecimal('121').sqrt(5).should be_close(11, 0.00001) end - platform_is_not wordsize: 32 do # fails on i686 + platform_is_not c_long_size: 32 do # fails on i686 it "returns square root of 0.9E-99999 with desired precision" do @frac_2.sqrt(1).to_s.should =~ /\A0\.3E-49999\z/i end diff --git a/library/net-http/http/post_spec.rb b/library/net-http/http/post_spec.rb index d7d94fec4a..ac020bd6be 100644 --- a/library/net-http/http/post_spec.rb +++ b/library/net-http/http/post_spec.rb @@ -27,7 +27,7 @@ it "sends Content-Type: application/x-www-form-urlencoded by default" do response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test") - response.body.should include('"Content-Type"=>"application/x-www-form-urlencoded"') + response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}")) end it "does not support HTTP Basic Auth" do diff --git a/library/net-http/http/send_request_spec.rb b/library/net-http/http/send_request_spec.rb index e82b2a96a1..af35c068ce 100644 --- a/library/net-http/http/send_request_spec.rb +++ b/library/net-http/http/send_request_spec.rb @@ -54,7 +54,7 @@ @methods.each do |method| response = @http.send_request(method, "/request/header", "test=test", "referer" => referer) - response.body.should include('"Referer"=>"' + referer + '"') + response.body.should include({ "Referer" => referer }.inspect.delete("{}")) end end end diff --git a/library/objectspace/memsize_of_spec.rb b/library/objectspace/memsize_of_spec.rb index eefafbb334..cbb5a07d54 100644 --- a/library/objectspace/memsize_of_spec.rb +++ b/library/objectspace/memsize_of_spec.rb @@ -13,7 +13,7 @@ end it "returns 0 for literal Symbols" do - ObjectSpace.memsize_of(:abc).should == 0 + ObjectSpace.memsize_of(:object_space_memsize_spec_static_sym).should == 0 end it "returns a positive Integer for an Object" do diff --git a/library/pp/pp_spec.rb b/library/pp/pp_spec.rb index 243478efd9..e45a6bb94f 100644 --- a/library/pp/pp_spec.rb +++ b/library/pp/pp_spec.rb @@ -25,6 +25,6 @@ hash = { 'key' => 42 } -> { PP.pp hash - }.should output('{"key"=>42}' + "\n") + }.should output("#{hash.inspect}\n") end end diff --git a/library/rbconfig/rbconfig_spec.rb b/library/rbconfig/rbconfig_spec.rb index 3e06219621..b9a4588bf0 100644 --- a/library/rbconfig/rbconfig_spec.rb +++ b/library/rbconfig/rbconfig_spec.rb @@ -9,6 +9,13 @@ end end + it 'has MAJOR, MINOR, TEENY, and PATCHLEVEL matching RUBY_VERSION and RUBY_PATCHLEVEL' do + major, minor, teeny = RUBY_VERSION.split('.') + RbConfig::CONFIG.values_at("MAJOR", "MINOR", "TEENY", "PATCHLEVEL").should == [ + major, minor, teeny, RUBY_PATCHLEVEL.to_s + ] + end + # These directories have no meanings before the installation. guard -> { RbConfig::TOPDIR } do it "['rubylibdir'] returns the directory containing Ruby standard libraries" do diff --git a/library/set/hash_spec.rb b/library/set/hash_spec.rb index 47c43c05f1..c5bab73931 100644 --- a/library/set/hash_spec.rb +++ b/library/set/hash_spec.rb @@ -10,4 +10,9 @@ Set[].hash.should_not == Set[1, 2, 3].hash Set[1, 2, 3].hash.should_not == Set[:a, "b", ?c].hash end + + # see https://github.com/jruby/jruby/issues/8393 + it "is equal to nil.hash for an uninitialized Set" do + Set.allocate.hash.should == nil.hash + end end diff --git a/library/socket/socket/connect_spec.rb b/library/socket/socket/connect_spec.rb index 8653fba552..175c9942bc 100644 --- a/library/socket/socket/connect_spec.rb +++ b/library/socket/socket/connect_spec.rb @@ -53,4 +53,18 @@ end end end + + ruby_version_is "3.4" do + it "fails with timeout" do + # TEST-NET-1 IP address are reserved for documentation and example purposes. + address = Socket.pack_sockaddr_in(1, "192.0.2.1") + + client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + client.timeout = 0 + + -> { + client.connect(address) + }.should raise_error(IO::TimeoutError) + end + end end diff --git a/library/stringio/set_encoding_by_bom_spec.rb b/library/stringio/set_encoding_by_bom_spec.rb new file mode 100644 index 0000000000..1030aad042 --- /dev/null +++ b/library/stringio/set_encoding_by_bom_spec.rb @@ -0,0 +1,237 @@ +require 'stringio' +require_relative '../../spec_helper' + +# Should be synced with specs for IO#set_encoding_by_bom +describe "StringIO#set_encoding_by_bom" do + it "returns nil if not readable" do + io = StringIO.new("".b, "wb") + + io.set_encoding_by_bom.should be_nil + io.external_encoding.should == Encoding::ASCII_8BIT + end + + it "returns the result encoding if found BOM UTF-8 sequence" do + io = StringIO.new("\u{FEFF}".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_8 + io.external_encoding.should == Encoding::UTF_8 + io.read.b.should == "".b + + io = StringIO.new("\u{FEFF}abc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_8 + io.external_encoding.should == Encoding::UTF_8 + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_16LE sequence" do + io = StringIO.new("\xFF\xFE".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFEabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_16BE sequence" do + io = StringIO.new("\xFE\xFF".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16BE + io.external_encoding.should == Encoding::UTF_16BE + io.read.b.should == "".b + + io = StringIO.new("\xFE\xFFabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16BE + io.external_encoding.should == Encoding::UTF_16BE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_32LE sequence" do + io = StringIO.new("\xFF\xFE\x00\x00".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32LE + io.external_encoding.should == Encoding::UTF_32LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFE\x00\x00abc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32LE + io.external_encoding.should == Encoding::UTF_32LE + io.read.b.should == "abc".b + end + + it "returns the result encoding if found BOM UTF_32BE sequence" do + io = StringIO.new("\x00\x00\xFE\xFF".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32BE + io.external_encoding.should == Encoding::UTF_32BE + io.read.b.should == "".b + + io = StringIO.new("\x00\x00\xFE\xFFabc".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_32BE + io.external_encoding.should == Encoding::UTF_32BE + io.read.b.should == "abc".b + end + + it "returns nil if io is empty" do + io = StringIO.new("".b, "rb") + io.set_encoding_by_bom.should be_nil + io.external_encoding.should == Encoding::ASCII_8BIT + end + + it "returns nil if UTF-8 BOM sequence is incomplete" do + io = StringIO.new("\xEF".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF".b + + io = StringIO.new("\xEFa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEFa".b + + io = StringIO.new("\xEF\xBB".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF\xBB".b + + io = StringIO.new("\xEF\xBBa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xEF\xBBa".b + end + + it "returns nil if UTF-16BE BOM sequence is incomplete" do + io = StringIO.new("\xFE".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFE".b + + io = StringIO.new("\xFEa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFEa".b + end + + it "returns nil if UTF-16LE/UTF-32LE BOM sequence is incomplete" do + io = StringIO.new("\xFF".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFF".b + + io = StringIO.new("\xFFa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\xFFa".b + end + + it "returns UTF-16LE if UTF-32LE BOM sequence is incomplete" do + io = StringIO.new("\xFF\xFE".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "".b + + io = StringIO.new("\xFF\xFE\x00".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "\x00".b + + io = StringIO.new("\xFF\xFE\x00a".b, "rb") + + io.set_encoding_by_bom.should == Encoding::UTF_16LE + io.external_encoding.should == Encoding::UTF_16LE + io.read.b.should == "\x00a".b + end + + it "returns nil if UTF-32BE BOM sequence is incomplete" do + io = StringIO.new("\x00".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00".b + + io = StringIO.new("\x00a".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00a".b + + io = StringIO.new("\x00\x00".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00".b + + io = StringIO.new("\x00\x00a".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00a".b + + io = StringIO.new("\x00\x00\xFE".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00\xFE".b + + io = StringIO.new("\x00\x00\xFEa".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read.b.should == "\x00\x00\xFEa".b + end + + it "returns nil if found BOM sequence not provided" do + io = StringIO.new("abc".b, "rb") + + io.set_encoding_by_bom.should == nil + io.external_encoding.should == Encoding::ASCII_8BIT + io.read(3).should == "abc".b + end + + it "does not raise exception if io not in binary mode" do + io = StringIO.new("", 'r') + io.set_encoding_by_bom.should == nil + end + + it "does not raise exception if encoding already set" do + io = StringIO.new("".b, "rb") + io.set_encoding("utf-8") + io.set_encoding_by_bom.should == nil + end + + it "does not raise exception if encoding conversion is already set" do + io = StringIO.new("".b, "rb") + io.set_encoding(Encoding::UTF_8, Encoding::UTF_16BE) + + io.set_encoding_by_bom.should == nil + end + + it "raises FrozenError when io is frozen" do + io = StringIO.new() + io.freeze + -> { io.set_encoding_by_bom }.should raise_error(FrozenError) + end + + it "does not raise FrozenError when initial string is frozen" do + io = StringIO.new("".freeze) + io.set_encoding_by_bom.should == nil + end +end diff --git a/library/stringscanner/check_until_spec.rb b/library/stringscanner/check_until_spec.rb index ad222fd76b..1d89f88a25 100644 --- a/library/stringscanner/check_until_spec.rb +++ b/library/stringscanner/check_until_spec.rb @@ -13,9 +13,11 @@ @s.check_until(/test/).should == "This is a test" end - it "raises TypeError if given a String" do - -> { - @s.check_until('T') - }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + ruby_version_is ""..."3.4" do + it "raises TypeError if given a String" do + -> { + @s.check_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end end end diff --git a/library/stringscanner/exist_spec.rb b/library/stringscanner/exist_spec.rb index ff860a0d3e..a18f5ce352 100644 --- a/library/stringscanner/exist_spec.rb +++ b/library/stringscanner/exist_spec.rb @@ -22,9 +22,11 @@ @s.exist?(/i/).should == nil end - it "raises TypeError if given a String" do - -> { - @s.exist?('T') - }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + ruby_version_is ""..."3.4" do + it "raises TypeError if given a String" do + -> { + @s.exist?('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end end end diff --git a/library/stringscanner/scan_until_spec.rb b/library/stringscanner/scan_until_spec.rb index a8162f1f03..1e318d053b 100644 --- a/library/stringscanner/scan_until_spec.rb +++ b/library/stringscanner/scan_until_spec.rb @@ -21,9 +21,11 @@ @s.scan_until(/^h/).should == "h" end - it "raises TypeError if given a String" do - -> { - @s.scan_until('T') - }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + ruby_version_is ""..."3.4" do + it "raises TypeError if given a String" do + -> { + @s.scan_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end end end diff --git a/library/stringscanner/search_full_spec.rb b/library/stringscanner/search_full_spec.rb index 7d2a714fa5..713ab00d22 100644 --- a/library/stringscanner/search_full_spec.rb +++ b/library/stringscanner/search_full_spec.rb @@ -28,9 +28,11 @@ @s.pos.should == 4 end - it "raises TypeError if given a String" do - -> { - @s.search_full('T', true, true) - }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + ruby_version_is ""..."3.4" do + it "raises TypeError if given a String" do + -> { + @s.search_full('T', true, true) + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end end end diff --git a/library/stringscanner/skip_until_spec.rb b/library/stringscanner/skip_until_spec.rb index 7b56f13e4f..b6a020f9ba 100644 --- a/library/stringscanner/skip_until_spec.rb +++ b/library/stringscanner/skip_until_spec.rb @@ -16,9 +16,11 @@ @s.skip_until(/d+/).should == nil end - it "raises TypeError if given a String" do - -> { - @s.skip_until('T') - }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + ruby_version_is ""..."3.4" do + it "raises TypeError if given a String" do + -> { + @s.skip_until('T') + }.should raise_error(TypeError, 'wrong argument type String (expected Regexp)') + end end end diff --git a/library/time/iso8601_spec.rb b/library/time/iso8601_spec.rb index 4a9eb45613..9e2607fbd0 100644 --- a/library/time/iso8601_spec.rb +++ b/library/time/iso8601_spec.rb @@ -3,5 +3,5 @@ require 'time' describe "Time.xmlschema" do - it_behaves_like :time_xmlschema, :iso8601 + it_behaves_like :time_library_xmlschema, :iso8601 end diff --git a/library/time/shared/xmlschema.rb b/library/time/shared/xmlschema.rb index 427ed29d57..0002886ca5 100644 --- a/library/time/shared/xmlschema.rb +++ b/library/time/shared/xmlschema.rb @@ -1,4 +1,4 @@ -describe :time_xmlschema, shared: true do +describe :time_library_xmlschema, shared: true do it "parses ISO-8601 strings" do t = Time.utc(1985, 4, 12, 23, 20, 50, 520000) s = "1985-04-12T23:20:50.52Z" diff --git a/library/time/xmlschema_spec.rb b/library/time/xmlschema_spec.rb index 4279311199..ff3c864a02 100644 --- a/library/time/xmlschema_spec.rb +++ b/library/time/xmlschema_spec.rb @@ -3,5 +3,5 @@ require 'time' describe "Time.xmlschema" do - it_behaves_like :time_xmlschema, :xmlschema + it_behaves_like :time_library_xmlschema, :xmlschema end diff --git a/library/uri/shared/parse.rb b/library/uri/shared/parse.rb index 87e1ee933e..c5057b6c4b 100644 --- a/library/uri/shared/parse.rb +++ b/library/uri/shared/parse.rb @@ -192,8 +192,15 @@ file.should be_kind_of(URI::Generic) end - it "raises errors on malformed URIs" do - -> { @object.parse('http://a_b:80/') }.should raise_error(URI::InvalidURIError) - -> { @object.parse('http://a_b/') }.should raise_error(URI::InvalidURIError) + if URI::DEFAULT_PARSER == URI::RFC2396_Parser + it "raises errors on malformed URIs" do + -> { @object.parse('http://a_b:80/') }.should raise_error(URI::InvalidURIError) + -> { @object.parse('http://a_b/') }.should raise_error(URI::InvalidURIError) + end + elsif URI::DEFAULT_PARSER == URI::RFC3986_Parser + it "does not raise errors on URIs contained underscore" do + -> { @object.parse('http://a_b:80/') }.should_not raise_error(URI::InvalidURIError) + -> { @object.parse('http://a_b/') }.should_not raise_error(URI::InvalidURIError) + end end end diff --git a/optional/capi/bignum_spec.rb b/optional/capi/bignum_spec.rb index cde929af28..179f053eec 100644 --- a/optional/capi/bignum_spec.rb +++ b/optional/capi/bignum_spec.rb @@ -7,21 +7,23 @@ def ensure_bignum(n) n end -full_range_longs = (fixnum_max == 2**(0.size * 8 - 1) - 1) +full_range_longs = (fixnum_max == max_long) +max_ulong = begin + require 'rbconfig/sizeof' + RbConfig::LIMITS['ULONG_MAX'] +rescue LoadError + nil +end +# If the system doesn't offer ULONG_MAX, assume 2's complement and derive it +# from LONG_MAX. +max_ulong ||= 2 * (max_long + 1) - 1 describe "CApiBignumSpecs" do before :each do @s = CApiBignumSpecs.new - - if full_range_longs - @max_long = 2**(0.size * 8 - 1) - 1 - @min_long = -@max_long - 1 - @max_ulong = ensure_bignum(2**(0.size * 8) - 1) - else - @max_long = ensure_bignum(2**(0.size * 8 - 1) - 1) - @min_long = ensure_bignum(-@max_long - 1) - @max_ulong = ensure_bignum(2**(0.size * 8) - 1) - end + @max_long = max_long + @min_long = min_long + @max_ulong = ensure_bignum(max_ulong) end describe "rb_big2long" do @@ -123,7 +125,7 @@ def ensure_bignum(n) val.should == @max_ulong end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "packs max_ulong into 2 ulongs to allow sign bit" do val = @s.rb_big_pack_length(@max_ulong) val.should == 2 diff --git a/optional/capi/debug_spec.rb b/optional/capi/debug_spec.rb index 148b8c38fb..14ba25609c 100644 --- a/optional/capi/debug_spec.rb +++ b/optional/capi/debug_spec.rb @@ -37,9 +37,12 @@ it "matches the locations in rb_debug_inspector_backtrace_locations" do frames = @o.rb_debug_inspector_open(42) - frames.each do |_s, _klass, binding, _iseq, backtrace_location| + frames.each do |_s, klass, binding, iseq, backtrace_location| if binding - binding.source_location.should == [backtrace_location.path, backtrace_location.lineno] + # YJIT modifies Array#each backtraces but leaves its source_location as is + unless defined?(RubyVM::YJIT) && klass == Array && iseq.label == "each" + binding.source_location.should == [backtrace_location.path, backtrace_location.lineno] + end method_name = binding.eval('__method__') if method_name method_name.should == backtrace_location.base_label.to_sym diff --git a/optional/capi/ext/io_spec.c b/optional/capi/ext/io_spec.c index 1b4d4fccce..e0b1df0de7 100644 --- a/optional/capi/ext/io_spec.c +++ b/optional/capi/ext/io_spec.c @@ -143,7 +143,11 @@ VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) { errno = saved_errno; } +#ifdef RUBY_VERSION_IS_3_1 + ret = rb_io_maybe_wait_readable(errno, io, Qnil); +#else ret = rb_io_wait_readable(fd); +#endif if (RTEST(read_p)) { ssize_t r = read(fd, buf, RB_IO_WAIT_READABLE_BUF); @@ -162,7 +166,11 @@ VALUE io_spec_rb_io_wait_readable(VALUE self, VALUE io, VALUE read_p) { } VALUE io_spec_rb_io_wait_writable(VALUE self, VALUE io) { +#ifdef RUBY_VERSION_IS_3_1 + int ret = rb_io_maybe_wait_writable(errno, io, Qnil); +#else int ret = rb_io_wait_writable(io_spec_get_fd(io)); +#endif return ret ? Qtrue : Qfalse; } @@ -230,13 +238,23 @@ VALUE io_spec_rb_thread_wait_fd(VALUE self, VALUE io) { } VALUE io_spec_rb_wait_for_single_fd(VALUE self, VALUE io, VALUE events, VALUE secs, VALUE usecs) { - int fd = io_spec_get_fd(io); +#ifdef RUBY_VERSION_IS_3_0 + VALUE timeout = Qnil; + if (!NIL_P(secs)) { + timeout = rb_float_new((double)FIX2INT(secs) + (0.000001f * FIX2INT(usecs))); + } + VALUE result = rb_io_wait(io, events, timeout); + if (result == Qfalse) return INT2FIX(0); + else return result; +#else struct timeval tv; if (!NIL_P(secs)) { tv.tv_sec = FIX2INT(secs); tv.tv_usec = FIX2INT(usecs); } + int fd = io_spec_get_fd(io); return INT2FIX(rb_wait_for_single_fd(fd, FIX2INT(events), NIL_P(secs) ? NULL : &tv)); +#endif } VALUE io_spec_rb_thread_fd_writable(VALUE self, VALUE io) { diff --git a/optional/capi/ext/kernel_spec.c b/optional/capi/ext/kernel_spec.c index 04252b2848..e6bf4870ec 100644 --- a/optional/capi/ext/kernel_spec.c +++ b/optional/capi/ext/kernel_spec.c @@ -7,16 +7,12 @@ extern "C" { #endif -VALUE kernel_spec_call_proc(VALUE arg_array) { +static VALUE kernel_spec_call_proc(VALUE arg_array) { VALUE arg = rb_ary_pop(arg_array); VALUE proc = rb_ary_pop(arg_array); return rb_funcall(proc, rb_intern("call"), 1, arg); } -VALUE kernel_spec_call_proc_raise(VALUE arg_array, VALUE raised_exc) { - return kernel_spec_call_proc(arg_array); -} - static VALUE kernel_spec_rb_block_given_p(VALUE self) { return rb_block_given_p() ? Qtrue : Qfalse; } @@ -134,7 +130,16 @@ VALUE kernel_spec_rb_throw_obj(VALUE self, VALUE obj, VALUE result) { return ID2SYM(rb_intern("rb_throw_failed")); } -VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) { +VALUE kernel_spec_rb_errinfo(VALUE self) { + return rb_errinfo(); +} + +VALUE kernel_spec_rb_set_errinfo(VALUE self, VALUE exc) { + rb_set_errinfo(exc); + return Qnil; +} + +static VALUE kernel_spec_call_proc_with_raised_exc(VALUE arg_array, VALUE raised_exc) { VALUE argv[2]; int argc; @@ -181,7 +186,7 @@ VALUE kernel_spec_rb_rescue2(int argc, VALUE *args, VALUE self) { rb_ary_push(raise_array, args[3]); return rb_rescue2(kernel_spec_call_proc, main_array, - kernel_spec_call_proc_raise, raise_array, args[4], args[5], (VALUE)0); + kernel_spec_call_proc_with_raised_exc, raise_array, args[4], args[5], (VALUE)0); } static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) { @@ -195,7 +200,7 @@ static VALUE kernel_spec_rb_protect_yield(VALUE self, VALUE obj, VALUE ary) { return res; } -static VALUE kernel_spec_rb_protect_errinfo(VALUE self, VALUE obj, VALUE ary) { +static VALUE kernel_spec_rb_protect_ignore_status(VALUE self, VALUE obj, VALUE ary) { int status = 0; VALUE res = rb_protect(rb_yield, obj, &status); rb_ary_store(ary, 0, INT2NUM(23)); @@ -382,10 +387,13 @@ void Init_kernel_spec(void) { rb_define_method(cls, "rb_raise", kernel_spec_rb_raise, 1); rb_define_method(cls, "rb_throw", kernel_spec_rb_throw, 1); rb_define_method(cls, "rb_throw_obj", kernel_spec_rb_throw_obj, 2); + rb_define_method(cls, "rb_errinfo", kernel_spec_rb_errinfo, 0); + rb_define_method(cls, "rb_set_errinfo", kernel_spec_rb_set_errinfo, 1); + rb_define_method(cls, "rb_rescue", kernel_spec_rb_rescue, 4); rb_define_method(cls, "rb_rescue", kernel_spec_rb_rescue, 4); rb_define_method(cls, "rb_rescue2", kernel_spec_rb_rescue2, -1); rb_define_method(cls, "rb_protect_yield", kernel_spec_rb_protect_yield, 2); - rb_define_method(cls, "rb_protect_errinfo", kernel_spec_rb_protect_errinfo, 2); + rb_define_method(cls, "rb_protect_ignore_status", kernel_spec_rb_protect_ignore_status, 2); rb_define_method(cls, "rb_protect_null_status", kernel_spec_rb_protect_null_status, 1); rb_define_method(cls, "rb_eval_string_protect", kernel_spec_rb_eval_string_protect, 2); rb_define_method(cls, "rb_catch", kernel_spec_rb_catch, 2); diff --git a/optional/capi/ext/mutex_spec.c b/optional/capi/ext/mutex_spec.c index c2fdf917ac..d2c8f98e89 100644 --- a/optional/capi/ext/mutex_spec.c +++ b/optional/capi/ext/mutex_spec.c @@ -29,15 +29,34 @@ VALUE mutex_spec_rb_mutex_sleep(VALUE self, VALUE mutex, VALUE timeout) { return rb_mutex_sleep(mutex, timeout); } - VALUE mutex_spec_rb_mutex_callback(VALUE arg) { return rb_funcall(arg, rb_intern("call"), 0); } +VALUE mutex_spec_rb_mutex_naughty_callback(VALUE arg) { + int *result = (int *) arg; + return (VALUE) result; +} + +VALUE mutex_spec_rb_mutex_callback_basic(VALUE arg) { + return arg; +} + VALUE mutex_spec_rb_mutex_synchronize(VALUE self, VALUE mutex, VALUE value) { return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback, value); } +VALUE mutex_spec_rb_mutex_synchronize_with_naughty_callback(VALUE self, VALUE mutex) { + // a naughty callback accepts or returns not a Ruby object but arbitrary value + int arg = 42; + VALUE result = rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_naughty_callback, (VALUE) &arg); + return INT2NUM(*((int *) result)); +} + +VALUE mutex_spec_rb_mutex_synchronize_with_native_callback(VALUE self, VALUE mutex, VALUE value) { + return rb_mutex_synchronize(mutex, mutex_spec_rb_mutex_callback_basic, value); +} + void Init_mutex_spec(void) { VALUE cls = rb_define_class("CApiMutexSpecs", rb_cObject); rb_define_method(cls, "rb_mutex_new", mutex_spec_rb_mutex_new, 0); @@ -47,6 +66,8 @@ void Init_mutex_spec(void) { rb_define_method(cls, "rb_mutex_unlock", mutex_spec_rb_mutex_unlock, 1); rb_define_method(cls, "rb_mutex_sleep", mutex_spec_rb_mutex_sleep, 2); rb_define_method(cls, "rb_mutex_synchronize", mutex_spec_rb_mutex_synchronize, 2); + rb_define_method(cls, "rb_mutex_synchronize_with_naughty_callback", mutex_spec_rb_mutex_synchronize_with_naughty_callback, 1); + rb_define_method(cls, "rb_mutex_synchronize_with_native_callback", mutex_spec_rb_mutex_synchronize_with_native_callback, 2); } #ifdef __cplusplus diff --git a/optional/capi/ext/string_spec.c b/optional/capi/ext/string_spec.c index cec3f65f45..94f412267f 100644 --- a/optional/capi/ext/string_spec.c +++ b/optional/capi/ext/string_spec.c @@ -117,6 +117,10 @@ VALUE string_spec_rb_str_cmp(VALUE self, VALUE str1, VALUE str2) { return INT2NUM(rb_str_cmp(str1, str2)); } +VALUE string_spec_rb_str_strlen(VALUE self, VALUE str) { + return LONG2NUM(rb_str_strlen(str)); +} + VALUE string_spec_rb_str_conv_enc(VALUE self, VALUE str, VALUE from, VALUE to) { rb_encoding* from_enc; rb_encoding* to_enc; @@ -600,6 +604,7 @@ void Init_string_spec(void) { rb_define_method(cls, "rb_str_cat_cstr", string_spec_rb_str_cat_cstr, 2); rb_define_method(cls, "rb_str_cat_cstr_constant", string_spec_rb_str_cat_cstr_constant, 1); rb_define_method(cls, "rb_str_cmp", string_spec_rb_str_cmp, 2); + rb_define_method(cls, "rb_str_strlen", string_spec_rb_str_strlen, 1); rb_define_method(cls, "rb_str_conv_enc", string_spec_rb_str_conv_enc, 3); rb_define_method(cls, "rb_str_conv_enc_opts", string_spec_rb_str_conv_enc_opts, 5); rb_define_method(cls, "rb_str_drop_bytes", string_spec_rb_str_drop_bytes, 2); diff --git a/optional/capi/fixnum_spec.rb b/optional/capi/fixnum_spec.rb index aa02a0543b..e691aa3893 100644 --- a/optional/capi/fixnum_spec.rb +++ b/optional/capi/fixnum_spec.rb @@ -25,7 +25,7 @@ end end - platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + platform_is c_long_size: 64 do # sizeof(long) > sizeof(int) it "raises a TypeError if passed nil" do -> { @s.FIX2INT(nil) }.should raise_error(TypeError) end @@ -74,7 +74,7 @@ end end - platform_is wordsize: 64 do # sizeof(long) > sizeof(int) + platform_is c_long_size: 64 do # sizeof(long) > sizeof(int) it "raises a TypeError if passed nil" do -> { @s.FIX2UINT(nil) }.should raise_error(TypeError) end diff --git a/optional/capi/io_spec.rb b/optional/capi/io_spec.rb index bdec46f5e1..01588408e1 100644 --- a/optional/capi/io_spec.rb +++ b/optional/capi/io_spec.rb @@ -458,10 +458,6 @@ @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") @@ -521,6 +517,14 @@ end end end + + ruby_version_is "3.4" do + describe "rb_io_maybe_wait" do + it "returns nil if there is no error condition" do + @o.rb_io_maybe_wait(0, @w_io, IO::WRITABLE, nil).should == nil + end + end + end end describe "rb_fd_fix_cloexec" do diff --git a/optional/capi/kernel_spec.rb b/optional/capi/kernel_spec.rb index 3b61d4f0f1..a169813cd5 100644 --- a/optional/capi/kernel_spec.rb +++ b/optional/capi/kernel_spec.rb @@ -3,11 +3,19 @@ kernel_path = load_extension("kernel") +class CApiKernelSpecs::Exc < StandardError +end +exception_class = CApiKernelSpecs::Exc + describe "C-API Kernel function" do before :each do @s = CApiKernelSpecs.new end + after :each do + @s.rb_errinfo.should == nil + end + describe "rb_block_given_p" do it "returns false if no block is passed" do @s.should_not.rb_block_given_p @@ -78,6 +86,22 @@ -> { @s.rb_raise(h) }.should raise_error(TypeError) h[:stage].should == :before end + + it "re-raises a rescued exception" do + -> do + begin + raise StandardError, "aaa" + rescue Exception + begin + @s.rb_raise({}) + rescue TypeError + end + + # should raise StandardError "aaa" + raise + end + end.should raise_error(StandardError, "aaa") + end end describe "rb_throw" do @@ -295,7 +319,7 @@ it "will allow cleanup code to run after a raise" do proof = [] # Hold proof of work performed after the yield. -> do - @s.rb_protect_yield(77, proof) { |x| raise NameError} + @s.rb_protect_yield(77, proof) { |x| raise NameError } end.should raise_error(NameError) proof[0].should == 23 end @@ -303,7 +327,7 @@ it "will return nil if an error was raised" do proof = [] # Hold proof of work performed after the yield. -> do - @s.rb_protect_yield(77, proof) { |x| raise NameError} + @s.rb_protect_yield(77, proof) { |x| raise NameError } end.should raise_error(NameError) proof[0].should == 23 proof[1].should == nil @@ -311,14 +335,21 @@ it "accepts NULL as status and returns nil if it failed" do @s.rb_protect_null_status(42) { |x| x + 1 }.should == 43 - @s.rb_protect_null_status(42) { |x| raise }.should == nil + @s.rb_protect_null_status(42) { |x| raise NameError }.should == nil + @s.rb_errinfo().should.is_a? NameError + ensure + @s.rb_set_errinfo(nil) end - it "populates errinfo with the captured exception" do + it "populates rb_errinfo() with the captured exception" do proof = [] - @s.rb_protect_errinfo(77, proof) { |x| raise NameError }.class.should == NameError + @s.rb_protect_ignore_status(77, proof) { |x| raise NameError } + @s.rb_errinfo().should.is_a? NameError + # Note: on CRuby $! is the NameError here, but not clear if that is desirable or bug proof[0].should == 23 proof[1].should == nil + ensure + @s.rb_set_errinfo(nil) end end @@ -382,9 +413,21 @@ -> { @s.rb_rescue(@std_error_proc, nil, @std_error_proc, nil) }.should raise_error(StandardError) end - it "makes $! available only during the 'rescue function' execution" do - @s.rb_rescue(@std_error_proc, nil, -> *_ { $! }, nil).class.should == StandardError + it "sets $! and rb_errinfo() during the 'rescue function' execution" do + @s.rb_rescue(-> *_ { raise exception_class, '' }, nil, -> _, exc { + exc.should.is_a?(exception_class) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, nil) + + @s.rb_rescue(-> _ { @s.rb_raise({}) }, nil, -> _, exc { + exc.should.is_a?(TypeError) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, nil) + $!.should == nil + @s.rb_errinfo.should == nil end it "returns the break value if the passed function yields to a block with a break" do @@ -402,7 +445,7 @@ def proc_caller describe "rb_rescue2" do it "only rescues if one of the passed exceptions is raised" do - proc = -> x { x } + proc = -> x, _exc { x } arg_error_proc = -> *_ { raise ArgumentError, '' } run_error_proc = -> *_ { raise RuntimeError, '' } type_error_proc = -> *_ { raise Exception, 'custom error' } @@ -418,6 +461,23 @@ def proc_caller @s.rb_rescue2(-> *_ { raise RuntimeError, "foo" }, :no_exc, -> x { x }, :exc, Object.new, 42) }.should raise_error(TypeError, /class or module required/) end + + it "sets $! and rb_errinfo() during the 'rescue function' execution" do + @s.rb_rescue2(-> *_ { raise exception_class, '' }, :no_exc, -> _, exc { + exc.should.is_a?(exception_class) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, :exc, exception_class, ScriptError) + + @s.rb_rescue2(-> *_ { @s.rb_raise({}) }, :no_exc, -> _, exc { + exc.should.is_a?(TypeError) + $!.should.equal?(exc) + @s.rb_errinfo.should.equal?(exc) + }, :exc, TypeError, ArgumentError) + + $!.should == nil + @s.rb_errinfo.should == nil + end end describe "rb_catch" do @@ -486,12 +546,33 @@ def proc_caller it "executes passed 'ensure function' when an exception is raised" do foo = nil - raise_proc = -> { raise '' } + raise_proc = -> _ { raise exception_class } ensure_proc = -> x { foo = x } - @s.rb_ensure(raise_proc, nil, ensure_proc, :foo) rescue nil + -> { + @s.rb_ensure(raise_proc, nil, ensure_proc, :foo) + }.should raise_error(exception_class) foo.should == :foo end + it "sets $! and rb_errinfo() during the 'ensure function' execution" do + -> { + @s.rb_ensure(-> _ { raise exception_class }, nil, -> _ { + $!.should.is_a?(exception_class) + @s.rb_errinfo.should.is_a?(exception_class) + }, nil) + }.should raise_error(exception_class) + + -> { + @s.rb_ensure(-> _ { @s.rb_raise({}) }, nil, -> _ { + $!.should.is_a?(TypeError) + @s.rb_errinfo.should.is_a?(TypeError) + }, nil) + }.should raise_error(TypeError) + + $!.should == nil + @s.rb_errinfo.should == nil + end + it "raises the same exception raised inside passed function" do raise_proc = -> *_ { raise RuntimeError, 'foo' } proc = -> *_ { } diff --git a/optional/capi/mutex_spec.rb b/optional/capi/mutex_spec.rb index 34659974f5..71a2212e36 100644 --- a/optional/capi/mutex_spec.rb +++ b/optional/capi/mutex_spec.rb @@ -85,5 +85,18 @@ callback = -> { @m.locked?.should be_true } @s.rb_mutex_synchronize(@m, callback) end + + it "returns a value returned from a callback" do + callback = -> { :foo } + @s.rb_mutex_synchronize(@m, callback).should == :foo + end + + it "calls a C-function that accepts and returns non-VALUE values" do + @s.rb_mutex_synchronize_with_naughty_callback(@m).should == 42 + end + + it "calls a native function" do + @s.rb_mutex_synchronize_with_native_callback(@m, 42).should == 42 + end end end diff --git a/optional/capi/numeric_spec.rb b/optional/capi/numeric_spec.rb index 95213d3f2b..e9667da5ba 100644 --- a/optional/capi/numeric_spec.rb +++ b/optional/capi/numeric_spec.rb @@ -106,7 +106,7 @@ @s.NUM2LONG(5).should == 5 end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "converts -1 to an signed number" do @s.NUM2LONG(-1).should == -1 end @@ -120,7 +120,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "converts -1 to an signed number" do @s.NUM2LONG(-1).should == -1 end @@ -210,7 +210,7 @@ @s.NUM2ULONG(5).should == 5 end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "converts -1 to an unsigned number" do @s.NUM2ULONG(-1).should == 4294967295 end @@ -231,7 +231,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "converts -1 to an unsigned number" do @s.NUM2ULONG(-1).should == 18446744073709551615 end diff --git a/optional/capi/string_spec.rb b/optional/capi/string_spec.rb index eb0379f224..8f2244bcea 100644 --- a/optional/capi/string_spec.rb +++ b/optional/capi/string_spec.rb @@ -449,6 +449,20 @@ def inspect end end + describe "rb_str_strlen" do + it 'returns 0 as the length of an empty string' do + @s.rb_str_strlen('').should == 0 + end + + it 'returns the number of characters in a string' do + @s.rb_str_strlen('hello').should == 5 + end + + it 'returns the number of characters in a string with multi-byte characters' do + @s.rb_str_strlen('こんにちは').should == 5 + end + end + describe "rb_str_split" do it "splits strings over a splitter" do @s.rb_str_split("Hello,Goodbye").should == ["Hello", "Goodbye"] @@ -1100,7 +1114,7 @@ def inspect end it "tries to convert the passed argument to a string by calling #to_s" do - @s.rb_String({"bar" => "foo"}).should == '{"bar"=>"foo"}' + @s.rb_String({"bar" => "foo"}).should == {"bar" => "foo"}.to_s end end diff --git a/optional/capi/util_spec.rb b/optional/capi/util_spec.rb index 9ff8b4760a..6cf064bf97 100644 --- a/optional/capi/util_spec.rb +++ b/optional/capi/util_spec.rb @@ -209,7 +209,7 @@ end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do describe "rb_long2int" do it "raises a RangeError if the value is outside the range of a C int" do -> { @o.rb_long2int(0xffff_ffff_ffff) }.should raise_error(RangeError) diff --git a/shared/string/times.rb b/shared/string/times.rb index aaf748bad9..4814f894cf 100644 --- a/shared/string/times.rb +++ b/shared/string/times.rb @@ -44,13 +44,13 @@ class MyString < String; end result.encoding.should equal(Encoding::UTF_8) end - platform_is wordsize: 32 do + platform_is c_long_size: 32 do it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do -> { @object.call("abc", (2 ** 31) - 1) }.should raise_error(ArgumentError) end end - platform_is wordsize: 64 do + platform_is c_long_size: 64 do it "raises an ArgumentError if the length of the resulting string doesn't fit into a long" do -> { @object.call("abc", (2 ** 63) - 1) }.should raise_error(ArgumentError) end