diff options
Diffstat (limited to 'spec/ruby/core/string/shared')
21 files changed, 739 insertions, 416 deletions
diff --git a/spec/ruby/core/string/shared/byte_index_common.rb b/spec/ruby/core/string/shared/byte_index_common.rb new file mode 100644 index 0000000000..3de1453f4f --- /dev/null +++ b/spec/ruby/core/string/shared/byte_index_common.rb @@ -0,0 +1,63 @@ +# -*- encoding: utf-8 -*- +require_relative '../../../spec_helper' + +describe :byte_index_common, shared: true do + describe "raises on type errors" do + it "raises a TypeError if passed nil" do + -> { "abc".send(@method, nil) }.should raise_error(TypeError, "no implicit conversion of nil into String") + end + + it "raises a TypeError if passed a boolean" do + -> { "abc".send(@method, true) }.should raise_error(TypeError, "no implicit conversion of true into String") + end + + it "raises a TypeError if passed a Symbol" do + not_supported_on :opal do + -> { "abc".send(@method, :a) }.should raise_error(TypeError, "no implicit conversion of Symbol into String") + end + end + + it "raises a TypeError if passed a Symbol" do + obj = mock('x') + obj.should_not_receive(:to_int) + -> { "hello".send(@method, obj) }.should raise_error(TypeError, "no implicit conversion of MockObject into String") + end + + it "raises a TypeError if passed an Integer" do + -> { "abc".send(@method, 97) }.should raise_error(TypeError, "no implicit conversion of Integer into String") + end + end + + describe "with multibyte codepoints" do + it "raises an IndexError when byte offset lands in the middle of a multibyte character" do + -> { "わ".send(@method, "", 1) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + -> { "わ".send(@method, "", 2) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + -> { "わ".send(@method, "", -1) }.should raise_error(IndexError, "offset 2 does not land on character boundary") + -> { "わ".send(@method, "", -2) }.should raise_error(IndexError, "offset 1 does not land on character boundary") + end + + it "raises an Encoding::CompatibilityError if the encodings are incompatible" do + re = Regexp.new "れ".encode(Encoding::EUC_JP) + -> do + "あれ".send(@method, re) + end.should raise_error(Encoding::CompatibilityError, "incompatible encoding regexp match (EUC-JP regexp with UTF-8 string)") + end + end + + describe "with global variables" do + it "doesn't set $~ for non regex search" do + $~ = nil + + 'hello.'.send(@method, 'll') + $~.should == nil + end + + it "sets $~ to MatchData of match and nil when there's none" do + 'hello.'.send(@method, /.e./) + $~[0].should == 'hel' + + 'hello.'.send(@method, /not/) + $~.should == nil + end + end +end diff --git a/spec/ruby/core/string/shared/chars.rb b/spec/ruby/core/string/shared/chars.rb index c1cf324dc5..c730643cf4 100644 --- a/spec/ruby/core/string/shared/chars.rb +++ b/spec/ruby/core/string/shared/chars.rb @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' describe :string_chars, shared: true do it "passes each char in self to the given block" do @@ -20,61 +20,47 @@ describe :string_chars, shared: true do ["\303\207", "\342\210\202", "\303\251", "\306\222", "g"] end - with_feature :encoding do - it "returns characters in the same encoding as self" do - "&%".force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} - "&%".encode('ASCII-8BIT').send(@method).to_a.all? {|c| c.encoding.name.should == 'ASCII-8BIT'} - end - - it "works with multibyte characters" do - s = "\u{8987}".force_encoding("UTF-8") - s.bytesize.should == 3 - s.send(@method).to_a.should == [s] - end - - it "works if the String's contents is invalid for its encoding" do - xA4 = [0xA4].pack('C') - xA4.force_encoding('UTF-8') - xA4.valid_encoding?.should be_false - xA4.send(@method).to_a.should == [xA4.force_encoding("UTF-8")] - end - - it "returns a different character if the String is transcoded" do - s = "\u{20AC}".force_encoding('UTF-8') - s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] - s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')] - s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".force_encoding('UTF-8')] - end + it "returns characters in the same encoding as self" do + "&%".dup.force_encoding('Shift_JIS').send(@method).to_a.all? {|c| c.encoding.name.should == 'Shift_JIS'} + "&%".encode('BINARY').send(@method).to_a.all? {|c| c.encoding.should == Encoding::BINARY } + end - it "uses the String's encoding to determine what characters it contains" do - s = "\u{24B62}" + it "works with multibyte characters" do + s = "\u{8987}".dup.force_encoding("UTF-8") + s.bytesize.should == 3 + s.send(@method).to_a.should == [s] + end - s.force_encoding('UTF-8').send(@method).to_a.should == [ - s.force_encoding('UTF-8') - ] - s.force_encoding('BINARY').send(@method).to_a.should == [ - [0xF0].pack('C').force_encoding('BINARY'), - [0xA4].pack('C').force_encoding('BINARY'), - [0xAD].pack('C').force_encoding('BINARY'), - [0xA2].pack('C').force_encoding('BINARY') - ] - s.force_encoding('SJIS').send(@method).to_a.should == [ - [0xF0,0xA4].pack('CC').force_encoding('SJIS'), - [0xAD].pack('C').force_encoding('SJIS'), - [0xA2].pack('C').force_encoding('SJIS') - ] - end + it "works if the String's contents is invalid for its encoding" do + xA4 = [0xA4].pack('C') + xA4.force_encoding('UTF-8') + xA4.valid_encoding?.should be_false + xA4.send(@method).to_a.should == [xA4.force_encoding("UTF-8")] + end - it "taints resulting strings when self is tainted" do - str = "hello" + it "returns a different character if the String is transcoded" do + s = "\u{20AC}".dup.force_encoding('UTF-8') + s.encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] + s.encode('iso-8859-15').send(@method).to_a.should == [[0xA4].pack('C').force_encoding('iso-8859-15')] + s.encode('iso-8859-15').encode('UTF-8').send(@method).to_a.should == ["\u{20AC}".dup.force_encoding('UTF-8')] + end - str.send(@method) do |x| - x.tainted?.should == false - end + it "uses the String's encoding to determine what characters it contains" do + s = +"\u{24B62}" - str.dup.taint.send(@method) do |x| - x.tainted?.should == true - end - end + s.force_encoding('UTF-8').send(@method).to_a.should == [ + s.force_encoding('UTF-8') + ] + s.force_encoding('BINARY').send(@method).to_a.should == [ + [0xF0].pack('C').force_encoding('BINARY'), + [0xA4].pack('C').force_encoding('BINARY'), + [0xAD].pack('C').force_encoding('BINARY'), + [0xA2].pack('C').force_encoding('BINARY') + ] + s.force_encoding('SJIS').send(@method).to_a.should == [ + [0xF0,0xA4].pack('CC').force_encoding('SJIS'), + [0xAD].pack('C').force_encoding('SJIS'), + [0xA2].pack('C').force_encoding('SJIS') + ] end end diff --git a/spec/ruby/core/string/shared/codepoints.rb b/spec/ruby/core/string/shared/codepoints.rb index 68f82b4468..1c28ba3d5e 100644 --- a/spec/ruby/core/string/shared/codepoints.rb +++ b/spec/ruby/core/string/shared/codepoints.rb @@ -1,9 +1,15 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_codepoints, shared: true do + it "returns self" do + s = "foo" + result = s.send(@method) {} + result.should equal s + end + it "raises an ArgumentError when self has an invalid encoding and a method is called on the returned Enumerator" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false - lambda { s.send(@method).to_a }.should raise_error(ArgumentError) + -> { s.send(@method).to_a }.should raise_error(ArgumentError) end it "yields each codepoint to the block if one is given" do @@ -15,18 +21,18 @@ describe :string_codepoints, shared: true do end it "raises an ArgumentError if self's encoding is invalid and a block is given" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false - lambda { s.send(@method) { } }.should raise_error(ArgumentError) + -> { s.send(@method) { } }.should raise_error(ArgumentError) end - it "returns codepoints as Fixnums" do + it "yields codepoints as Integers" do "glark\u{20}".send(@method).to_a.each do |codepoint| - codepoint.should be_an_instance_of(Fixnum) + codepoint.should be_an_instance_of(Integer) end end - it "returns one codepoint for each character" do + it "yields one codepoint for each character" do s = "\u{9876}\u{28}\u{1987}" s.send(@method).to_a.size.should == s.chars.to_a.size end @@ -37,18 +43,18 @@ describe :string_codepoints, shared: true do s.send(@method).to_a.should == [38937] end - it "returns the codepoint corresponding to the character's position in the String's encoding" do + it "yields the codepoints corresponding to the character's position in the String's encoding" do "\u{787}".send(@method).to_a.should == [1927] end it "round-trips to the original String using Integer#chr" do s = "\u{13}\u{7711}\u{1010}" - s2 = "" + s2 = +"" s.send(@method) {|n| s2 << n.chr(Encoding::UTF_8)} s.should == s2 end - it "is synonymous with #bytes for Strings which are single-byte optimisable" do + it "is synonymous with #bytes for Strings which are single-byte optimizable" do s = "(){}".encode('ascii') s.ascii_only?.should be_true s.send(@method).to_a.should == s.bytes.to_a diff --git a/spec/ruby/core/string/shared/concat.rb b/spec/ruby/core/string/shared/concat.rb index 7da995fdc7..dded9a69e7 100644 --- a/spec/ruby/core/string/shared/concat.rb +++ b/spec/ruby/core/string/shared/concat.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_concat, shared: true do it "concatenates the given argument to self and returns self" do str = 'hello ' @@ -5,24 +6,12 @@ describe :string_concat, shared: true do str.should == "hello world" end - it "converts the given argument to a String using to_str" do - obj = mock('world!') - obj.should_receive(:to_str).and_return("world!") - a = 'hello '.send(@method, obj) - a.should == 'hello world!' - end - - it "raises a TypeError if the given argument can't be converted to a String" do - lambda { 'hello '.send(@method, []) }.should raise_error(TypeError) - lambda { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError) - end - - it "raises a RuntimeError when self is frozen" do + it "raises a FrozenError when self is frozen" do a = "hello" a.freeze - lambda { a.send(@method, "") }.should raise_error(RuntimeError) - lambda { a.send(@method, "test") }.should raise_error(RuntimeError) + -> { a.send(@method, "") }.should raise_error(FrozenError) + -> { a.send(@method, "test") }.should raise_error(FrozenError) end it "returns a String when given a subclass instance" do @@ -39,18 +28,8 @@ describe :string_concat, shared: true do str.should be_an_instance_of(StringSpecs::MyString) end - it "taints self if other is tainted" do - "x".send(@method, "".taint).tainted?.should == true - "x".send(@method, "y".taint).tainted?.should == true - end - - it "untrusts self if other is untrusted" do - "x".send(@method, "".untrust).untrusted?.should == true - "x".send(@method, "y".untrust).untrusted?.should == true - end - describe "with Integer" do - it "concatencates the argument interpreted as a codepoint" do + it "concatenates the argument interpreted as a codepoint" do b = "".send(@method, 33) b.should == "!" @@ -60,39 +39,39 @@ describe :string_concat, shared: true do end # #5855 - it "returns a ASCII-8BIT string if self is US-ASCII and the argument is between 128-255 (inclusive)" do + it "returns a BINARY string if self is US-ASCII and the argument is between 128-255 (inclusive)" do a = ("".encode(Encoding::US_ASCII).send(@method, 128)) - a.encoding.should == Encoding::ASCII_8BIT + a.encoding.should == Encoding::BINARY a.should == 128.chr a = ("".encode(Encoding::US_ASCII).send(@method, 255)) - a.encoding.should == Encoding::ASCII_8BIT + a.encoding.should == Encoding::BINARY a.should == 255.chr end it "raises RangeError if the argument is an invalid codepoint for self's encoding" do - lambda { "".encode(Encoding::US_ASCII).send(@method, 256) }.should raise_error(RangeError) - lambda { "".encode(Encoding::EUC_JP).send(@method, 0x81) }.should raise_error(RangeError) + -> { "".encode(Encoding::US_ASCII).send(@method, 256) }.should raise_error(RangeError) + -> { "".encode(Encoding::EUC_JP).send(@method, 0x81) }.should raise_error(RangeError) end it "raises RangeError if the argument is negative" do - lambda { "".send(@method, -200) }.should raise_error(RangeError) - lambda { "".send(@method, -bignum_value) }.should raise_error(RangeError) + -> { "".send(@method, -200) }.should raise_error(RangeError) + -> { "".send(@method, -bignum_value) }.should raise_error(RangeError) end it "doesn't call to_int on its argument" do x = mock('x') x.should_not_receive(:to_int) - lambda { "".send(@method, x) }.should raise_error(TypeError) + -> { "".send(@method, x) }.should raise_error(TypeError) end - it "raises a RuntimeError when self is frozen" do + it "raises a FrozenError when self is frozen" do a = "hello" a.freeze - lambda { a.send(@method, 0) }.should raise_error(RuntimeError) - lambda { a.send(@method, 33) }.should raise_error(RuntimeError) + -> { a.send(@method, 0) }.should raise_error(FrozenError) + -> { a.send(@method, 33) }.should raise_error(FrozenError) end end end @@ -112,7 +91,7 @@ describe :string_concat_encoding, shared: true do end it "raises Encoding::CompatibilityError if neither are empty" do - lambda { "x".encode("UTF-16LE").send(@method, "y".encode("UTF-8")) }.should raise_error(Encoding::CompatibilityError) + -> { "x".encode("UTF-16LE").send(@method, "y".encode("UTF-8")) }.should raise_error(Encoding::CompatibilityError) end end @@ -130,7 +109,7 @@ describe :string_concat_encoding, shared: true do end it "raises Encoding::CompatibilityError if neither are empty" do - lambda { "x".encode("UTF-8").send(@method, "y".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError) + -> { "x".encode("UTF-8").send(@method, "y".encode("UTF-16LE")) }.should raise_error(Encoding::CompatibilityError) end end @@ -148,13 +127,33 @@ describe :string_concat_encoding, shared: true do end it "raises Encoding::CompatibilityError if neither are ASCII-only" do - lambda { "\u00E9".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")) }.should raise_error(Encoding::CompatibilityError) + -> { "\u00E9".encode("UTF-8").send(@method, "\u00E9".encode("ISO-8859-1")) }.should raise_error(Encoding::CompatibilityError) end end - describe "when self is ASCII-8BIT and argument is US-ASCII" do - it "uses ASCII-8BIT encoding" do - "abc".encode("ASCII-8BIT").send(@method, "123".encode("US-ASCII")).encoding.should == Encoding::ASCII_8BIT + describe "when self is BINARY and argument is US-ASCII" do + it "uses BINARY encoding" do + "abc".encode("BINARY").send(@method, "123".encode("US-ASCII")).encoding.should == Encoding::BINARY end end end + +describe :string_concat_type_coercion, shared: true do + it "converts the given argument to a String using to_str" do + obj = mock('world!') + obj.should_receive(:to_str).and_return("world!") + a = 'hello '.send(@method, obj) + a.should == 'hello world!' + end + + it "raises a TypeError if the given argument can't be converted to a String" do + -> { 'hello '.send(@method, []) }.should raise_error(TypeError) + -> { 'hello '.send(@method, mock('x')) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + obj = mock('world!') + obj.should_receive(:to_str).and_raise(NoMethodError) + -> { 'hello '.send(@method, obj) }.should raise_error(NoMethodError) + end +end diff --git a/spec/ruby/core/string/shared/dedup.rb b/spec/ruby/core/string/shared/dedup.rb new file mode 100644 index 0000000000..1ffd6aa0fd --- /dev/null +++ b/spec/ruby/core/string/shared/dedup.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: false +describe :string_dedup, shared: true do + it 'returns self if the String is frozen' do + input = 'foo'.freeze + output = input.send(@method) + + output.should equal(input) + output.should.frozen? + end + + it 'returns a frozen copy if the String is not frozen' do + input = 'foo' + output = input.send(@method) + + output.should.frozen? + output.should_not equal(input) + output.should == 'foo' + end + + it "returns the same object for equal unfrozen strings" do + origin = "this is a string" + dynamic = %w(this is a string).join(' ') + + origin.should_not equal(dynamic) + origin.send(@method).should equal(dynamic.send(@method)) + end + + it "returns the same object when it's called on the same String literal" do + "unfrozen string".send(@method).should equal("unfrozen string".send(@method)) + "unfrozen string".send(@method).should_not equal("another unfrozen string".send(@method)) + end + + it "deduplicates frozen strings" do + dynamic = %w(this string is frozen).join(' ').freeze + + dynamic.should_not equal("this string is frozen".freeze) + + dynamic.send(@method).should equal("this string is frozen".freeze) + dynamic.send(@method).should equal("this string is frozen".send(@method).freeze) + end + + it "does not deduplicate a frozen string when it has instance variables" do + dynamic = %w(this string is frozen).join(' ') + dynamic.instance_variable_set(:@a, 1) + dynamic.freeze + + dynamic.send(@method).should_not equal("this string is frozen".freeze) + dynamic.send(@method).should_not equal("this string is frozen".send(@method).freeze) + dynamic.send(@method).should equal(dynamic) + end +end diff --git a/spec/ruby/core/string/shared/each_char_without_block.rb b/spec/ruby/core/string/shared/each_char_without_block.rb index 40808cfd9f..397100ce0e 100644 --- a/spec/ruby/core/string/shared/each_char_without_block.rb +++ b/spec/ruby/core/string/shared/each_char_without_block.rb @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' describe :string_each_char_without_block, shared: true do describe "when no block is given" do diff --git a/spec/ruby/core/string/shared/each_codepoint_without_block.rb b/spec/ruby/core/string/shared/each_codepoint_without_block.rb index 92b7f76032..c88e5c54c7 100644 --- a/spec/ruby/core/string/shared/each_codepoint_without_block.rb +++ b/spec/ruby/core/string/shared/each_codepoint_without_block.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_each_codepoint_without_block, shared: true do describe "when no block is given" do it "returns an Enumerator" do @@ -6,7 +6,7 @@ describe :string_each_codepoint_without_block, shared: true do end it "returns an Enumerator even when self has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).should be_an_instance_of(Enumerator) end @@ -23,7 +23,7 @@ describe :string_each_codepoint_without_block, shared: true do end it "should return the size of the string even when the string has an invalid encoding" do - s = "\xDF".force_encoding(Encoding::UTF_8) + s = "\xDF".dup.force_encoding(Encoding::UTF_8) s.valid_encoding?.should be_false s.send(@method).size.should == 1 end diff --git a/spec/ruby/core/string/shared/each_line.rb b/spec/ruby/core/string/shared/each_line.rb index dee741e270..231a6d9d4f 100644 --- a/spec/ruby/core/string/shared/each_line.rb +++ b/spec/ruby/core/string/shared/each_line.rb @@ -27,10 +27,17 @@ describe :string_each_line, shared: true do c.should == ["hello\n", "\n", "\n", "world"] end - it "taints substrings that are passed to the block if self is tainted" do - "one\ntwo\r\nthree".taint.send(@method) { |s| s.tainted?.should == true } + it "splits strings containing multibyte characters" do + s = <<~EOS + foo + 🤡🤡🤡🤡🤡🤡🤡 + bar + baz + EOS - "x.y.".send(@method, ".".taint) { |s| s.tainted?.should == false } + b = [] + s.send(@method) { |part| b << part } + b.should == ["foo\n", "🤡🤡🤡🤡🤡🤡🤡\n", "bar\n", "baz\n"] end it "passes self as a whole to the block if the separator is nil" do @@ -39,31 +46,15 @@ describe :string_each_line, shared: true do a.should == ["one\ntwo\r\nthree"] end - ruby_version_is ''...'2.5' do - it "yields paragraphs (broken by 2 or more successive newlines) when passed ''" do - a = [] - "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '') { |s| a << s } - a.should == ["hello\nworld\n\n\n", "and\nuniverse\n\n\n\n\n"] - - a = [] - "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '') { |s| a << s } - a.should == ["hello\nworld\n\n\n", "and\nuniverse\n\n\n\n\n", "dog"] - end - end - -quarantine! do # Currently fails on Travis - ruby_version_is '2.5' do - it "yields paragraphs (broken by 2 or more successive newlines) when passed ''" do - a = [] - "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '') { |s| a << s } - a.should == ["hello\nworld\n\n", "and\nuniverse\n\n"] + it "yields paragraphs (broken by 2 or more successive newlines) when passed '' and replaces multiple newlines with only two ones" do + a = [] + "hello\nworld\n\n\nand\nuniverse\n\n\n\n\n".send(@method, '') { |s| a << s } + a.should == ["hello\nworld\n\n", "and\nuniverse\n\n"] - a = [] - "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '') { |s| a << s } - a.should == ["hello\nworld\n\n", "and\nuniverse\n\n", "dog"] - end + a = [] + "hello\nworld\n\n\nand\nuniverse\n\n\n\n\ndog".send(@method, '') { |s| a << s } + a.should == ["hello\nworld\n\n", "and\nuniverse\n\n", "dog"] end -end describe "uses $/" do before :each do @@ -71,7 +62,7 @@ end end after :each do - $/ = @before_separator + suppress_warning {$/ = @before_separator} end it "as the separator when none is given" do @@ -83,10 +74,10 @@ end expected = [] str.send(@method, sep) { |x| expected << x } - $/ = sep + suppress_warning {$/ = sep} actual = [] - str.send(@method) { |x| actual << x } + suppress_warning {str.send(@method) { |x| actual << x }} actual.should == expected end @@ -94,10 +85,10 @@ end end end - it "yields subclass instances for subclasses" do + it "yields String instances for subclasses" do a = [] StringSpecs::MyString.new("hello\nworld").send(@method) { |s| a << s.class } - a.should == [StringSpecs::MyString, StringSpecs::MyString] + a.should == [String, String] end it "returns self" do @@ -115,15 +106,21 @@ end end it "does not care if the string is modified while substituting" do - str = "hello\nworld." + str = +"hello\nworld." out = [] str.send(@method){|x| out << x; str[-1] = '!' }.should == "hello\nworld!" out.should == ["hello\n", "world."] end + it "returns Strings in the same encoding as self" do + "one\ntwo\r\nthree".encode("US-ASCII").send(@method) do |s| + s.encoding.should == Encoding::US_ASCII + end + end + it "raises a TypeError when the separator can't be converted to a string" do - lambda { "hello world".send(@method, false) {} }.should raise_error(TypeError) - lambda { "hello world".send(@method, mock('x')) {} }.should raise_error(TypeError) + -> { "hello world".send(@method, false) {} }.should raise_error(TypeError) + -> { "hello world".send(@method, mock('x')) {} }.should raise_error(TypeError) end it "accepts a string separator" do @@ -131,20 +128,35 @@ end end it "raises a TypeError when the separator is a symbol" do - lambda { "hello world".send(@method, :o).to_a }.should raise_error(TypeError) + -> { "hello world".send(@method, :o).to_a }.should raise_error(TypeError) end - ruby_version_is '2.4' do - context "when `chomp` keyword argument is passed" do - it "removes new line characters" do - a = [] - "hello \nworld\n".send(@method, chomp: true) { |s| a << s } - a.should == ["hello ", "world"] + context "when `chomp` keyword argument is passed" do + it "removes new line characters when separator is not specified" do + a = [] + "hello \nworld\n".send(@method, chomp: true) { |s| a << s } + a.should == ["hello ", "world"] - a = [] - "hello \r\nworld\r\n".send(@method, chomp: true) { |s| a << s } - a.should == ["hello ", "world"] - end + a = [] + "hello \r\nworld\r\n".send(@method, chomp: true) { |s| a << s } + a.should == ["hello ", "world"] + end + + it "removes only specified separator" do + a = [] + "hello world".send(@method, ' ', chomp: true) { |s| a << s } + a.should == ["hello", "world"] + end + + # https://bugs.ruby-lang.org/issues/14257 + it "ignores new line characters when separator is specified" do + a = [] + "hello\n world\n".send(@method, ' ', chomp: true) { |s| a << s } + a.should == ["hello\n", "world\n"] + + a = [] + "hello\r\n world\r\n".send(@method, ' ', chomp: true) { |s| a << s } + a.should == ["hello\r\n", "world\r\n"] end end end diff --git a/spec/ruby/core/string/shared/encode.rb b/spec/ruby/core/string/shared/encode.rb index 71d46b1bd3..9466308886 100644 --- a/spec/ruby/core/string/shared/encode.rb +++ b/spec/ruby/core/string/shared/encode.rb @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: false describe :string_encode, shared: true do describe "when passed no options" do it "transcodes to Encoding.default_internal when set" do @@ -8,20 +9,20 @@ describe :string_encode, shared: true do end it "transcodes a 7-bit String despite no generic converting being available" do - lambda do - Encoding::Converter.new Encoding::Emacs_Mule, Encoding::ASCII_8BIT + -> do + Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY end.should raise_error(Encoding::ConverterNotFoundError) Encoding.default_internal = Encoding::Emacs_Mule - str = "\x79".force_encoding Encoding::ASCII_8BIT + str = "\x79".force_encoding Encoding::BINARY - str.send(@method).should == "y".force_encoding(Encoding::ASCII_8BIT) + str.send(@method).should == "y".force_encoding(Encoding::BINARY) end it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do Encoding.default_internal = Encoding::Emacs_Mule - str = [0x80].pack('C').force_encoding Encoding::ASCII_8BIT - lambda { str.send(@method) }.should raise_error(Encoding::ConverterNotFoundError) + str = [0x80].pack('C').force_encoding Encoding::BINARY + -> { str.send(@method) }.should raise_error(Encoding::ConverterNotFoundError) end end @@ -51,23 +52,23 @@ describe :string_encode, shared: true do end it "transcodes a 7-bit String despite no generic converting being available" do - lambda do - Encoding::Converter.new Encoding::Emacs_Mule, Encoding::ASCII_8BIT + -> do + Encoding::Converter.new Encoding::Emacs_Mule, Encoding::BINARY end.should raise_error(Encoding::ConverterNotFoundError) - str = "\x79".force_encoding Encoding::ASCII_8BIT - str.send(@method, Encoding::Emacs_Mule).should == "y".force_encoding(Encoding::ASCII_8BIT) + str = "\x79".force_encoding Encoding::BINARY + str.send(@method, Encoding::Emacs_Mule).should == "y".force_encoding(Encoding::BINARY) end it "raises an Encoding::ConverterNotFoundError when no conversion is possible" do - str = [0x80].pack('C').force_encoding Encoding::ASCII_8BIT - lambda do + str = [0x80].pack('C').force_encoding Encoding::BINARY + -> do str.send(@method, Encoding::Emacs_Mule) end.should raise_error(Encoding::ConverterNotFoundError) end it "raises an Encoding::ConverterNotFoundError for an invalid encoding" do - lambda do + -> do "abc".send(@method, "xyz") end.should raise_error(Encoding::ConverterNotFoundError) end @@ -83,7 +84,7 @@ describe :string_encode, shared: true do options = mock("string encode options") options.should_receive(:to_hash).and_return({ undef: :replace }) - result = "あ\ufffdあ".send(@method, options) + result = "あ\ufffdあ".send(@method, **options) result.should == "あ\ufffdあ" end @@ -95,8 +96,8 @@ describe :string_encode, shared: true do it "raises an Encoding::ConverterNotFoundError when no conversion is possible despite 'invalid: :replace, undef: :replace'" do Encoding.default_internal = Encoding::Emacs_Mule - str = [0x80].pack('C').force_encoding Encoding::ASCII_8BIT - lambda do + str = [0x80].pack('C').force_encoding Encoding::BINARY + -> do str.send(@method, invalid: :replace, undef: :replace) end.should raise_error(Encoding::ConverterNotFoundError) end @@ -145,7 +146,7 @@ describe :string_encode, shared: true do options = mock("string encode options") options.should_receive(:to_hash).and_return({ undef: :replace }) - result = "あ?あ".send(@method, Encoding::EUC_JP, options) + result = "あ?あ".send(@method, Encoding::EUC_JP, **options) xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8') result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp") end @@ -153,7 +154,7 @@ describe :string_encode, shared: true do describe "when passed to, from, options" do it "replaces undefined characters in the destination encoding" do - str = "あ?あ".force_encoding Encoding::ASCII_8BIT + str = "あ?あ".force_encoding Encoding::BINARY result = str.send(@method, "euc-jp", "utf-8", undef: :replace) xA4xA2 = [0xA4, 0xA2].pack('CC').force_encoding('utf-8') result.should == "#{xA4xA2}?#{xA4xA2}".force_encoding("euc-jp") @@ -161,7 +162,7 @@ describe :string_encode, shared: true do it "replaces invalid characters in the destination encoding" do xFF = [0xFF].pack('C').force_encoding('utf-8') - str = "ab#{xFF}c".force_encoding Encoding::ASCII_8BIT + str = "ab#{xFF}c".force_encoding Encoding::BINARY str.send(@method, "iso-8859-1", "utf-8", invalid: :replace).should == "ab?c" end @@ -170,7 +171,7 @@ describe :string_encode, shared: true do to.should_receive(:to_str).and_return("iso-8859-1") xFF = [0xFF].pack('C').force_encoding('utf-8') - str = "ab#{xFF}c".force_encoding Encoding::ASCII_8BIT + str = "ab#{xFF}c".force_encoding Encoding::BINARY str.send(@method, to, "utf-8", invalid: :replace).should == "ab?c" end @@ -179,7 +180,7 @@ describe :string_encode, shared: true do from.should_receive(:to_str).and_return("utf-8") xFF = [0xFF].pack('C').force_encoding('utf-8') - str = "ab#{xFF}c".force_encoding Encoding::ASCII_8BIT + str = "ab#{xFF}c".force_encoding Encoding::BINARY str.send(@method, "iso-8859-1", from, invalid: :replace).should == "ab?c" end @@ -188,8 +189,192 @@ describe :string_encode, shared: true do options.should_receive(:to_hash).and_return({ invalid: :replace }) xFF = [0xFF].pack('C').force_encoding('utf-8') - str = "ab#{xFF}c".force_encoding Encoding::ASCII_8BIT - str.send(@method, "iso-8859-1", "utf-8", options).should == "ab?c" + str = "ab#{xFF}c".force_encoding Encoding::BINARY + str.send(@method, "iso-8859-1", "utf-8", **options).should == "ab?c" + end + end + + describe "given the fallback option" do + context "given a hash" do + it "looks up the replacement value from the hash" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "bar" }) + encoded.should == "Bbar" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the key is not present in the hash" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "foo" => "bar" }) + }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII") + end + + it "raises an error if the value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: { "\ufffd" => "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + + it "uses the hash's default value if set" do + hash = {} + hash.default = "bar" + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash) + encoded.should == "Bbar" + end + + it "uses the result of calling default_proc if set" do + hash = {} + hash.default_proc = -> _, _ { "bar" } + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: hash) + encoded.should == "Bbar" + end + end + + context "given an object inheriting from Hash" do + before do + klass = Class.new(Hash) + @hash_like = klass.new + @hash_like["\ufffd"] = "bar" + end + + it "looks up the replacement value from the object" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like) + encoded.should == "Bbar" + end + end + + context "given an object responding to []" do + before do + klass = Class.new do + def [](c) = c.bytes.inspect + end + @hash_like = klass.new + end + + it "calls [] on the object, passing the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: @hash_like) + encoded.should == "B[239, 191, 189]" + end + end + + context "given an object not responding to []" do + before do + @non_hash_like = Object.new + end + + it "raises an error" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: @non_hash_like) + }.should raise_error(Encoding::UndefinedConversionError, "U+FFFD from UTF-8 to US-ASCII") + end + end + + context "given a proc" do + it "calls the proc to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| c.bytes.inspect }) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: proc { |c| obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + end + + context "given a lambda" do + it "calls the lambda to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { c.bytes.inspect }) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj }) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + obj = Object.new + obj.should_not_receive(:to_s) + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { obj }) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: -> c { "\uffee" }) + }.should raise_error(ArgumentError, "too big fallback string") + end + end + + context "given a method" do + def replace(c) = c.bytes.inspect + def replace_bad(c) = "\uffee" + + def replace_to_str(c) + obj = Object.new + obj.should_receive(:to_str).and_return("bar") + obj + end + + def replace_to_s(c) + obj = Object.new + obj.should_not_receive(:to_s) + obj + end + + it "calls the method to get the replacement value, passing in the invalid character" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace)) + encoded.should == "B[239, 191, 189]" + end + + it "calls to_str on the returned value" do + encoded = "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_str)) + encoded.should == "Bbar" + end + + it "does not call to_s on the returned value" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_to_s)) + }.should raise_error(TypeError, "no implicit conversion of Object into String") + end + + it "raises an error if the returned value is itself invalid" do + -> { + "B\ufffd".encode(Encoding::US_ASCII, fallback: method(:replace_bad)) + }.should raise_error(ArgumentError, "too big fallback string") + end end end @@ -242,6 +427,6 @@ describe :string_encode, shared: true do end it "raises ArgumentError if the value of the :xml option is not :text or :attr" do - lambda { ''.send(@method, "UTF-8", xml: :other) }.should raise_error(ArgumentError) + -> { ''.send(@method, "UTF-8", xml: :other) }.should raise_error(ArgumentError) end end diff --git a/spec/ruby/core/string/shared/eql.rb b/spec/ruby/core/string/shared/eql.rb index 92dfa91923..d5af337d53 100644 --- a/spec/ruby/core/string/shared/eql.rb +++ b/spec/ruby/core/string/shared/eql.rb @@ -1,6 +1,6 @@ -# -*- encoding: binary -*- -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +# encoding: binary +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' describe :string_eql_value, shared: true do it "returns true if self <=> string returns 0" do @@ -13,15 +13,15 @@ describe :string_eql_value, shared: true do end it "ignores encoding difference of compatible string" do - "hello".force_encoding("utf-8").send(@method, "hello".force_encoding("iso-8859-1")).should be_true + "hello".dup.force_encoding("utf-8").send(@method, "hello".dup.force_encoding("iso-8859-1")).should be_true end it "considers encoding difference of incompatible string" do - "\xff".force_encoding("utf-8").send(@method, "\xff".force_encoding("iso-8859-1")).should be_false + "\xff".dup.force_encoding("utf-8").send(@method, "\xff".dup.force_encoding("iso-8859-1")).should be_false end it "considers encoding compatibility" do - "hello".force_encoding("utf-8").send(@method, "hello".force_encoding("utf-32le")).should be_false + "abcd".dup.force_encoding("utf-8").send(@method, "abcd".dup.force_encoding("utf-32le")).should be_false end it "ignores subclass differences" do @@ -31,4 +31,8 @@ describe :string_eql_value, shared: true do a.send(@method, b).should be_true b.send(@method, a).should be_true end + + it "returns true when comparing 2 empty strings but one is not ASCII-compatible" do + "".send(@method, "".dup.force_encoding('iso-2022-jp')).should == true + end end diff --git a/spec/ruby/core/string/shared/equal_value.rb b/spec/ruby/core/string/shared/equal_value.rb index 6df76478c7..fccafb5821 100644 --- a/spec/ruby/core/string/shared/equal_value.rb +++ b/spec/ruby/core/string/shared/equal_value.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../../spec_helper', __FILE__) -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' describe :string_equal_value, shared: true do it "returns false if obj does not respond to to_str" do @@ -17,7 +17,7 @@ describe :string_equal_value, shared: true do # not call it. obj.stub!(:to_str) - # Don't use @method for :== in `obj.should_recerive(:==)` + # Don't use @method for :== in `obj.should_receive(:==)` obj.should_receive(:==).and_return(true) 'hello'.send(@method, obj).should be_true diff --git a/spec/ruby/core/string/shared/grapheme_clusters.rb b/spec/ruby/core/string/shared/grapheme_clusters.rb new file mode 100644 index 0000000000..8b666868b1 --- /dev/null +++ b/spec/ruby/core/string/shared/grapheme_clusters.rb @@ -0,0 +1,16 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :string_grapheme_clusters, shared: true do + it "passes each grapheme cluster in self to the given block" do + a = [] + # test string: abc[rainbow flag emoji][paw prints] + "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}".send(@method) { |c| a << c } + a.should == ['a', 'b', "\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}", "\u{1F43E}"] + end + + it "returns self" do + s = StringSpecs::MyString.new "ab\u{1f3f3}\u{fe0f}\u{200d}\u{1f308}\u{1F43E}" + s.send(@method) {}.should equal(s) + end +end diff --git a/spec/ruby/core/string/shared/length.rb b/spec/ruby/core/string/shared/length.rb index 0e6e66ee1c..ae572ba755 100644 --- a/spec/ruby/core/string/shared/length.rb +++ b/spec/ruby/core/string/shared/length.rb @@ -10,19 +10,46 @@ describe :string_length, shared: true do "four".send(@method).should == 4 end - with_feature :encoding do - it "returns the length of a string in different encodings" do - utf8_str = 'こにちわ' * 100 - utf8_str.size.should == 400 - utf8_str.encode(Encoding::UTF_32BE).size.should == 400 - utf8_str.encode(Encoding::SHIFT_JIS).size.should == 400 - end - - it "returns the length of the new self after encoding is changed" do - str = 'こにちわ' - str.send(@method) - - str.force_encoding('ASCII-8BIT').send(@method).should == 12 - end + it "returns the length of a string in different encodings" do + utf8_str = 'こにちわ' * 100 + utf8_str.send(@method).should == 400 + utf8_str.encode(Encoding::UTF_32BE).send(@method).should == 400 + utf8_str.encode(Encoding::SHIFT_JIS).send(@method).should == 400 + end + + it "returns the length of the new self after encoding is changed" do + str = +'こにちわ' + str.send(@method) + + str.force_encoding('BINARY').send(@method).should == 12 + end + + it "returns the correct length after force_encoding(BINARY)" do + utf8 = "あ" + ascii = "a" + concat = utf8 + ascii + + concat.encoding.should == Encoding::UTF_8 + concat.bytesize.should == 4 + + concat.send(@method).should == 2 + concat.force_encoding(Encoding::ASCII_8BIT) + concat.send(@method).should == 4 + end + + it "adds 1 for every invalid byte in UTF-8" do + "\xF4\x90\x80\x80".send(@method).should == 4 + "a\xF4\x90\x80\x80b".send(@method).should == 6 + "é\xF4\x90\x80\x80è".send(@method).should == 6 + end + + it "adds 1 (and not 2) for a incomplete surrogate in UTF-16" do + "\x00\xd8".dup.force_encoding("UTF-16LE").send(@method).should == 1 + "\xd8\x00".dup.force_encoding("UTF-16BE").send(@method).should == 1 + end + + it "adds 1 for a broken sequence in UTF-32" do + "\x04\x03\x02\x01".dup.force_encoding("UTF-32LE").send(@method).should == 1 + "\x01\x02\x03\x04".dup.force_encoding("UTF-32BE").send(@method).should == 1 end end diff --git a/spec/ruby/core/string/shared/partition.rb b/spec/ruby/core/string/shared/partition.rb new file mode 100644 index 0000000000..4cac149ce5 --- /dev/null +++ b/spec/ruby/core/string/shared/partition.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :string_partition, shared: true do + it "returns String instances when called on a subclass" do + StringSpecs::MyString.new("hello").send(@method, "l").each do |item| + item.should be_an_instance_of(String) + end + + StringSpecs::MyString.new("hello").send(@method, "x").each do |item| + item.should be_an_instance_of(String) + end + + StringSpecs::MyString.new("hello").send(@method, /l./).each do |item| + item.should be_an_instance_of(String) + end + end + + it "returns before- and after- parts in the same encoding as self" do + strings = "hello".encode("US-ASCII").send(@method, "ello") + strings[0].encoding.should == Encoding::US_ASCII + strings[2].encoding.should == Encoding::US_ASCII + + strings = "hello".encode("US-ASCII").send(@method, /ello/) + strings[0].encoding.should == Encoding::US_ASCII + strings[2].encoding.should == Encoding::US_ASCII + end + + it "returns the matching part in the separator's encoding" do + strings = "hello".encode("US-ASCII").send(@method, "ello") + strings[1].encoding.should == Encoding::UTF_8 + end +end diff --git a/spec/ruby/core/string/shared/replace.rb b/spec/ruby/core/string/shared/replace.rb index 9f5446fbbe..24dac0eb27 100644 --- a/spec/ruby/core/string/shared/replace.rb +++ b/spec/ruby/core/string/shared/replace.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: false describe :string_replace, shared: true do it "returns self" do a = "a" @@ -10,34 +11,6 @@ describe :string_replace, shared: true do a.should == "another string" end - it "taints self if other is tainted" do - a = "" - b = "".taint - a.send(@method, b) - a.tainted?.should == true - end - - it "does not untaint self if other is untainted" do - a = "".taint - b = "" - a.send(@method, b) - a.tainted?.should == true - end - - it "untrusts self if other is untrusted" do - a = "" - b = "".untrust - a.send(@method, b) - a.untrusted?.should == true - end - - it "does not trust self if other is trusted" do - a = "".untrust - b = "" - a.send(@method, b) - a.untrusted?.should == true - end - it "replaces the encoding of self with that of other" do a = "".encode("UTF-16LE") b = "".encode("UTF-8") @@ -57,19 +30,19 @@ describe :string_replace, shared: true do end it "raises a TypeError if other can't be converted to string" do - lambda { "hello".send(@method, 123) }.should raise_error(TypeError) - lambda { "hello".send(@method, []) }.should raise_error(TypeError) - lambda { "hello".send(@method, mock('x')) }.should raise_error(TypeError) + -> { "hello".send(@method, 123) }.should raise_error(TypeError) + -> { "hello".send(@method, []) }.should raise_error(TypeError) + -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError) end - it "raises a RuntimeError on a frozen instance that is modified" do + it "raises a FrozenError on a frozen instance that is modified" do a = "hello".freeze - lambda { a.send(@method, "world") }.should raise_error(RuntimeError) + -> { a.send(@method, "world") }.should raise_error(FrozenError) end # see [ruby-core:23666] - it "raises a RuntimeError on a frozen instance when self-replacing" do + it "raises a FrozenError on a frozen instance when self-replacing" do a = "hello".freeze - lambda { a.send(@method, a) }.should raise_error(RuntimeError) + -> { a.send(@method, a) }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/string/shared/slice.rb b/spec/ruby/core/string/shared/slice.rb index 697fa2e530..7b9b9f6a14 100644 --- a/spec/ruby/core/string/shared/slice.rb +++ b/spec/ruby/core/string/shared/slice.rb @@ -21,17 +21,17 @@ describe :string_slice, shared: true do end it "raises a TypeError if the given index is nil" do - lambda { "hello".send(@method, nil) }.should raise_error(TypeError) + -> { "hello".send(@method, nil) }.should raise_error(TypeError) end it "raises a TypeError if the given index can't be converted to an Integer" do - lambda { "hello".send(@method, mock('x')) }.should raise_error(TypeError) - lambda { "hello".send(@method, {}) }.should raise_error(TypeError) - lambda { "hello".send(@method, []) }.should raise_error(TypeError) + -> { "hello".send(@method, mock('x')) }.should raise_error(TypeError) + -> { "hello".send(@method, {}) }.should raise_error(TypeError) + -> { "hello".send(@method, []) }.should raise_error(TypeError) end it "raises a RangeError if the index is too big" do - lambda { "hello".send(@method, bignum_value) }.should raise_error(RangeError) + -> { "hello".send(@method, bignum_value) }.should raise_error(RangeError) end end @@ -80,21 +80,12 @@ describe :string_slice_index_length, shared: true do "hello there".send(@method, -3,2).should == "er" end - it "always taints resulting strings when self is tainted" do - str = "hello world" - str.taint - - str.send(@method, 0,0).tainted?.should == true - str.send(@method, 0,1).tainted?.should == true - str.send(@method, 2,1).tainted?.should == true - end - - it "returns a string with the same encoding" do + it "returns a string with the same encoding as self" do s = "hello there" s.send(@method, 1, 9).encoding.should == s.encoding - a = "hello".force_encoding("binary") - b = " there".force_encoding("ISO-8859-1") + a = "hello".dup.force_encoding("binary") + b = " there".dup.force_encoding("ISO-8859-1") c = (a + b).force_encoding(Encoding::US_ASCII) c.send(@method, 0, 5).encoding.should == Encoding::US_ASCII @@ -119,6 +110,8 @@ describe :string_slice_index_length, shared: true do "x".send(@method, -2,0).should == nil "x".send(@method, -2,1).should == nil + + "x".send(@method, fixnum_max, 1).should == nil end it "returns nil if the length is negative" do @@ -126,6 +119,18 @@ describe :string_slice_index_length, shared: true do "hello there".send(@method, -4,-3).should == nil end + platform_is pointer_size: 64 do + it "returns nil if the length is negative big value" do + "hello there".send(@method, 4, -(1 << 31)).should == nil + + # by some reason length < -(1 << 31) on CI on Windows leads to + # 'RangeError: bignum too big to convert into `long'' error + platform_is_not :windows do + "hello there".send(@method, 4, -(1 << 63)).should == nil + end + end + end + it "calls to_int on the given index and the given length" do "hello".send(@method, 0.5, 1).should == "h" "hello".send(@method, 0.5, 2.5).should == "he" @@ -140,30 +145,35 @@ describe :string_slice_index_length, shared: true do end it "raises a TypeError when idx or length can't be converted to an integer" do - lambda { "hello".send(@method, mock('x'), 0) }.should raise_error(TypeError) - lambda { "hello".send(@method, 0, mock('x')) }.should raise_error(TypeError) + -> { "hello".send(@method, mock('x'), 0) }.should raise_error(TypeError) + -> { "hello".send(@method, 0, mock('x')) }.should raise_error(TypeError) # I'm deliberately including this here. # It means that str.send(@method, other, idx) isn't supported. - lambda { "hello".send(@method, "", 0) }.should raise_error(TypeError) + -> { "hello".send(@method, "", 0) }.should raise_error(TypeError) end it "raises a TypeError when the given index or the given length is nil" do - lambda { "hello".send(@method, 1, nil) }.should raise_error(TypeError) - lambda { "hello".send(@method, nil, 1) }.should raise_error(TypeError) - lambda { "hello".send(@method, nil, nil) }.should raise_error(TypeError) + -> { "hello".send(@method, 1, nil) }.should raise_error(TypeError) + -> { "hello".send(@method, nil, 1) }.should raise_error(TypeError) + -> { "hello".send(@method, nil, nil) }.should raise_error(TypeError) end it "raises a RangeError if the index or length is too big" do - lambda { "hello".send(@method, bignum_value, 1) }.should raise_error(RangeError) - lambda { "hello".send(@method, 0, bignum_value) }.should raise_error(RangeError) + -> { "hello".send(@method, bignum_value, 1) }.should raise_error(RangeError) + -> { "hello".send(@method, 0, bignum_value) }.should raise_error(RangeError) + end + + it "raises a RangeError if the index or length is too small" do + -> { "hello".send(@method, -bignum_value, 1) }.should raise_error(RangeError) + -> { "hello".send(@method, 0, -bignum_value) }.should raise_error(RangeError) end - it "returns subclass instances" do + it "returns String instances" do s = StringSpecs::MyString.new("hello") - s.send(@method, 0,0).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, 0,4).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, 1,4).should be_an_instance_of(StringSpecs::MyString) + s.send(@method, 0,0).should be_an_instance_of(String) + s.send(@method, 0,4).should be_an_instance_of(String) + s.send(@method, 1,4).should be_an_instance_of(String) end it "handles repeated application" do @@ -202,6 +212,10 @@ describe :string_slice_range, shared: true do "x".send(@method, 1..-1).should == "" end + it "returns a String in the same encoding as self" do + "hello there".encode("US-ASCII").send(@method, 1..1).encoding.should == Encoding::US_ASCII + end + it "returns nil if the beginning of the range falls outside of self" do "hello there".send(@method, 12..-1).should == nil "hello there".send(@method, 20..25).should == nil @@ -234,23 +248,11 @@ describe :string_slice_range, shared: true do "x".send(@method, 1...-1).should == "" end - it "always taints resulting strings when self is tainted" do - str = "hello world" - str.taint - - str.send(@method, 0..0).tainted?.should == true - str.send(@method, 0...0).tainted?.should == true - str.send(@method, 0..1).tainted?.should == true - str.send(@method, 0...1).tainted?.should == true - str.send(@method, 2..3).tainted?.should == true - str.send(@method, 2..0).tainted?.should == true - end - - it "returns subclass instances" do + it "returns String instances" do s = StringSpecs::MyString.new("hello") - s.send(@method, 0...0).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, 0..4).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, 1..4).should be_an_instance_of(StringSpecs::MyString) + s.send(@method, 0...0).should be_an_instance_of(String) + s.send(@method, 0..4).should be_an_instance_of(String) + s.send(@method, 1..4).should be_an_instance_of(String) end it "calls to_int on range arguments" do @@ -289,6 +291,30 @@ describe :string_slice_range, shared: true do "hello world".send(@method, 6..5).send(@method, -1..-1).should == nil "hello world".send(@method, 6..5).send(@method, 1..1).should == nil end + + it "raises a type error if a range is passed with a length" do + ->{ "hello".send(@method, 1..2, 1) }.should raise_error(TypeError) + end + + it "raises a RangeError if one of the bound is too big" do + -> { "hello".send(@method, bignum_value..(bignum_value + 1)) }.should raise_error(RangeError) + -> { "hello".send(@method, 0..bignum_value) }.should raise_error(RangeError) + end + + it "works with endless ranges" do + "hello there".send(@method, eval("(2..)")).should == "llo there" + "hello there".send(@method, eval("(2...)")).should == "llo there" + "hello there".send(@method, eval("(-4..)")).should == "here" + "hello there".send(@method, eval("(-4...)")).should == "here" + end + + it "works with beginless ranges" do + "hello there".send(@method, (..5)).should == "hello " + "hello there".send(@method, (...5)).should == "hello" + "hello there".send(@method, (..-4)).should == "hello th" + "hello there".send(@method, (...-4)).should == "hello t" + "hello there".send(@method, (...nil)).should == "hello there" + end end describe :string_slice_regexp, shared: true do @@ -301,31 +327,14 @@ describe :string_slice_regexp, shared: true do "hello there".send(@method, /xyz/).should == nil end - not_supported_on :opal do - it "always taints resulting strings when self or regexp is tainted" do - strs = ["hello world"] - strs += strs.map { |s| s.dup.taint } - - strs.each do |str| - str.send(@method, //).tainted?.should == str.tainted? - str.send(@method, /hello/).tainted?.should == str.tainted? - - tainted_re = /./ - tainted_re.taint - - str.send(@method, tainted_re).tainted?.should == true - end - end - - it "returns an untrusted string if the regexp is untrusted" do - "hello".send(@method, /./.untrust).untrusted?.should be_true - end + it "returns a String in the same encoding as self" do + "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/).encoding.should == Encoding::US_ASCII end - it "returns subclass instances" do + it "returns String instances" do s = StringSpecs::MyString.new("hello") - s.send(@method, //).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, /../).should be_an_instance_of(StringSpecs::MyString) + s.send(@method, //).should be_an_instance_of(String) + s.send(@method, /../).should be_an_instance_of(String) end it "sets $~ to MatchData when there is a match and nil when there's none" do @@ -352,42 +361,28 @@ describe :string_slice_regexp_index, shared: true do "har".send(@method, /(.)(.)(.)/, -3).should == "h" end - it "always taints resulting strings when self or regexp is tainted" do - strs = ["hello world"] - strs += strs.map { |s| s.dup.taint } - - strs.each do |str| - str.send(@method, //, 0).tainted?.should == str.tainted? - str.send(@method, /hello/, 0).tainted?.should == str.tainted? - - str.send(@method, /(.)(.)(.)/, 0).tainted?.should == str.tainted? - str.send(@method, /(.)(.)(.)/, 1).tainted?.should == str.tainted? - str.send(@method, /(.)(.)(.)/, -1).tainted?.should == str.tainted? - str.send(@method, /(.)(.)(.)/, -2).tainted?.should == str.tainted? - - tainted_re = /(.)(.)(.)/ - tainted_re.taint + it "returns nil if there is no match" do + "hello there".send(@method, /(what?)/, 1).should == nil + end - str.send(@method, tainted_re, 0).tainted?.should == true - str.send(@method, tainted_re, 1).tainted?.should == true - str.send(@method, tainted_re, -1).tainted?.should == true - end + it "returns nil if the index is larger than the number of captures" do + "hello there".send(@method, /hello (.)/, 2).should == nil + # You can't refer to 0 using negative indices + "hello there".send(@method, /hello (.)/, -2).should == nil end - not_supported_on :opal do - it "returns an untrusted string if the regexp is untrusted" do - "hello".send(@method, /(.)/.untrust, 1).untrusted?.should be_true - end + it "returns nil if there is no capture for the given index" do + "hello there".send(@method, /[aeiou](.)\1/, 2).should == nil end - it "returns nil if there is no match" do - "hello there".send(@method, /(what?)/, 1).should == nil + it "returns nil if the given capture group was not matched but still sets $~" do + "test".send(@method, /te(z)?/, 1).should == nil + $~[0].should == "te" + $~[1].should == nil end - it "returns nil if there is no capture for the given index" do - "hello there".send(@method, /[aeiou](.)\1/, 2).should == nil - # You can't refer to 0 using negative indices - "hello there".send(@method, /[aeiou](.)\1/, -2).should == nil + it "returns a String in the same encoding as self" do + "hello there".encode("US-ASCII").send(@method, /[aeiou](.)\1/, 0).encoding.should == Encoding::US_ASCII end it "calls to_int on the given index" do @@ -399,19 +394,19 @@ describe :string_slice_regexp_index, shared: true do end it "raises a TypeError when the given index can't be converted to Integer" do - lambda { "hello".send(@method, /(.)(.)(.)/, mock('x')) }.should raise_error(TypeError) - lambda { "hello".send(@method, /(.)(.)(.)/, {}) }.should raise_error(TypeError) - lambda { "hello".send(@method, /(.)(.)(.)/, []) }.should raise_error(TypeError) + -> { "hello".send(@method, /(.)(.)(.)/, mock('x')) }.should raise_error(TypeError) + -> { "hello".send(@method, /(.)(.)(.)/, {}) }.should raise_error(TypeError) + -> { "hello".send(@method, /(.)(.)(.)/, []) }.should raise_error(TypeError) end it "raises a TypeError when the given index is nil" do - lambda { "hello".send(@method, /(.)(.)(.)/, nil) }.should raise_error(TypeError) + -> { "hello".send(@method, /(.)(.)(.)/, nil) }.should raise_error(TypeError) end - it "returns subclass instances" do + it "returns String instances" do s = StringSpecs::MyString.new("hello") - s.send(@method, /(.)(.)/, 0).should be_an_instance_of(StringSpecs::MyString) - s.send(@method, /(.)(.)/, 1).should be_an_instance_of(StringSpecs::MyString) + s.send(@method, /(.)(.)/, 0).should be_an_instance_of(String) + s.send(@method, /(.)(.)/, 1).should be_an_instance_of(String) end it "sets $~ to MatchData when there is a match and nil when there's none" do @@ -432,19 +427,6 @@ describe :string_slice_string, shared: true do "hello there".send(@method, s).should == s end - it "taints resulting strings when other is tainted" do - strs = ["", "hello world", "hello"] - strs += strs.map { |s| s.dup.taint } - - strs.each do |str| - strs.each do |other| - r = str.send(@method, other) - - r.tainted?.should == !r.nil? & other.tainted? - end - end - end - it "doesn't set $~" do $~ = nil @@ -460,14 +442,14 @@ describe :string_slice_string, shared: true do o = mock('x') o.should_not_receive(:to_str) - lambda { "hello".send(@method, o) }.should raise_error(TypeError) + -> { "hello".send(@method, o) }.should raise_error(TypeError) end - it "returns a subclass instance when given a subclass instance" do + it "returns a String instance when given a subclass instance" do s = StringSpecs::MyString.new("el") r = "hello".send(@method, s) r.should == "el" - r.should be_an_instance_of(StringSpecs::MyString) + r.should be_an_instance_of(String) end end @@ -493,51 +475,29 @@ describe :string_slice_regexp_group, shared: true do "hello there".send(@method, /(?<g>h(?<g>.))/, 'g').should == "e" end - it "always taints resulting strings when self or regexp is tainted" do - strs = ["hello world"] - strs += strs.map { |s| s.dup.taint } - - strs.each do |str| - str.send(@method, /(?<hi>hello)/, 'hi').tainted?.should == str.tainted? - - str.send(@method, /(?<g>(.)(.)(.))/, 'g').tainted?.should == str.tainted? - str.send(@method, /(?<h>.)(.)(.)/, 'h').tainted?.should == str.tainted? - str.send(@method, /(.)(?<a>.)(.)/, 'a').tainted?.should == str.tainted? - str.send(@method, /(.)(.)(?<r>.)/, 'r').tainted?.should == str.tainted? - str.send(@method, /(?<h>.)(?<a>.)(?<r>.)/, 'r').tainted?.should == str.tainted? - - tainted_re = /(?<a>.)(?<b>.)(?<c>.)/ - tainted_re.taint - - str.send(@method, tainted_re, 'a').tainted?.should be_true - str.send(@method, tainted_re, 'b').tainted?.should be_true - str.send(@method, tainted_re, 'c').tainted?.should be_true - end - end - it "returns nil if there is no match" do "hello there".send(@method, /(?<whut>what?)/, 'whut').should be_nil end it "raises an IndexError if there is no capture for the given name" do - lambda do + -> do "hello there".send(@method, /[aeiou](.)\1/, 'non') end.should raise_error(IndexError) end it "raises a TypeError when the given name is not a String" do - lambda { "hello".send(@method, /(?<q>.)/, mock('x')) }.should raise_error(TypeError) - lambda { "hello".send(@method, /(?<q>.)/, {}) }.should raise_error(TypeError) - lambda { "hello".send(@method, /(?<q>.)/, []) }.should raise_error(TypeError) + -> { "hello".send(@method, /(?<q>.)/, mock('x')) }.should raise_error(TypeError) + -> { "hello".send(@method, /(?<q>.)/, {}) }.should raise_error(TypeError) + -> { "hello".send(@method, /(?<q>.)/, []) }.should raise_error(TypeError) end it "raises an IndexError when given the empty String as a group name" do - lambda { "hello".send(@method, /(?<q>)/, '') }.should raise_error(IndexError) + -> { "hello".send(@method, /(?<q>)/, '') }.should raise_error(IndexError) end - it "returns subclass instances" do + it "returns String instances" do s = StringSpecs::MyString.new("hello") - s.send(@method, /(?<q>.)/, 'q').should be_an_instance_of(StringSpecs::MyString) + s.send(@method, /(?<q>.)/, 'q').should be_an_instance_of(String) end it "sets $~ to MatchData when there is a match and nil when there's none" do @@ -552,6 +512,6 @@ end describe :string_slice_symbol, shared: true do it "raises TypeError" do - lambda { 'hello'.send(@method, :hello) }.should raise_error(TypeError) + -> { 'hello'.send(@method, :hello) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/string/shared/strip.rb b/spec/ruby/core/string/shared/strip.rb new file mode 100644 index 0000000000..3af77b50fe --- /dev/null +++ b/spec/ruby/core/string/shared/strip.rb @@ -0,0 +1,14 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :string_strip, shared: true do + it "returns a String in the same encoding as self" do + " hello ".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII + end + + it "returns String instances when called on a subclass" do + StringSpecs::MyString.new(" hello ").send(@method).should be_an_instance_of(String) + StringSpecs::MyString.new(" ").send(@method).should be_an_instance_of(String) + StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String) + end +end diff --git a/spec/ruby/core/string/shared/succ.rb b/spec/ruby/core/string/shared/succ.rb index 4854cb7146..7c68345f10 100644 --- a/spec/ruby/core/string/shared/succ.rb +++ b/spec/ruby/core/string/shared/succ.rb @@ -1,4 +1,4 @@ -# -*- encoding: binary -*- +# encoding: binary describe :string_succ, shared: true do it "returns an empty string for empty strings" do "".send(@method).should == "" @@ -59,30 +59,29 @@ describe :string_succ, shared: true do "\xFF\xFF".send(@method).should == "\x01\x00\x00" end - it "returns subclass instances when called on a subclass" do - StringSpecs::MyString.new("").send(@method).should be_an_instance_of(StringSpecs::MyString) - StringSpecs::MyString.new("a").send(@method).should be_an_instance_of(StringSpecs::MyString) - StringSpecs::MyString.new("z").send(@method).should be_an_instance_of(StringSpecs::MyString) + it "returns String instances when called on a subclass" do + StringSpecs::MyString.new("").send(@method).should be_an_instance_of(String) + StringSpecs::MyString.new("a").send(@method).should be_an_instance_of(String) + StringSpecs::MyString.new("z").send(@method).should be_an_instance_of(String) end - it "taints the result if self is tainted" do - ["", "a", "z", "Z", "9", "\xFF", "\xFF\xFF"].each do |s| - s.taint.send(@method).tainted?.should == true - end + it "returns a String in the same encoding as self" do + "z".encode("US-ASCII").send(@method).encoding.should == Encoding::US_ASCII end end describe :string_succ_bang, shared: true do it "is equivalent to succ, but modifies self in place (still returns self)" do ["", "abcd", "THX1138"].each do |s| + s = +s r = s.dup.send(@method) s.send(@method).should equal(s) s.should == r end end - it "raises a RuntimeError if self is frozen" do - lambda { "".freeze.send(@method) }.should raise_error(RuntimeError) - lambda { "abcd".freeze.send(@method) }.should raise_error(RuntimeError) + it "raises a FrozenError if self is frozen" do + -> { "".freeze.send(@method) }.should raise_error(FrozenError) + -> { "abcd".freeze.send(@method) }.should raise_error(FrozenError) end end diff --git a/spec/ruby/core/string/shared/to_a.rb b/spec/ruby/core/string/shared/to_a.rb deleted file mode 100644 index bad3ea6584..0000000000 --- a/spec/ruby/core/string/shared/to_a.rb +++ /dev/null @@ -1,9 +0,0 @@ -describe :string_to_a, shared: true do - it "returns an empty array for empty strings" do - "".send(@method).should == [] - end - - it "returns an array containing the string for non-empty strings" do - "hello".send(@method).should == ["hello"] - end -end diff --git a/spec/ruby/core/string/shared/to_s.rb b/spec/ruby/core/string/shared/to_s.rb index a5a13e4f26..4b87a6cbe1 100644 --- a/spec/ruby/core/string/shared/to_s.rb +++ b/spec/ruby/core/string/shared/to_s.rb @@ -10,9 +10,4 @@ describe :string_to_s, shared: true do s.should == "a string" s.should be_an_instance_of(String) end - - it "taints the result when self is tainted" do - "x".taint.send(@method).tainted?.should == true - StringSpecs::MyString.new("x").taint.send(@method).tainted?.should == true - end end diff --git a/spec/ruby/core/string/shared/to_sym.rb b/spec/ruby/core/string/shared/to_sym.rb index 1180d64712..833eae100e 100644 --- a/spec/ruby/core/string/shared/to_sym.rb +++ b/spec/ruby/core/string/shared/to_sym.rb @@ -53,11 +53,20 @@ describe :string_to_sym, shared: true do sym.to_s.should == binary_string end + it "ignores existing symbols with different encoding" do + source = "fée" + + iso_symbol = source.dup.force_encoding(Encoding::ISO_8859_1).send(@method) + iso_symbol.encoding.should == Encoding::ISO_8859_1 + binary_symbol = source.dup.force_encoding(Encoding::BINARY).send(@method) + binary_symbol.encoding.should == Encoding::BINARY + end + it "raises an EncodingError for UTF-8 String containing invalid bytes" do invalid_utf8 = "\xC3" - invalid_utf8.valid_encoding?.should == false + invalid_utf8.should_not.valid_encoding? -> { invalid_utf8.send(@method) - }.should raise_error(EncodingError, /invalid/) + }.should raise_error(EncodingError, 'invalid symbol in encoding UTF-8 :"\xC3"') end end |
