mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
ffd0820ab3
This removes the related tests, and puts the related specs behind version guards. This affects all code in lib, including some libraries that may want to support older versions of Ruby.
583 lines
17 KiB
Ruby
583 lines
17 KiB
Ruby
require_relative '../../spec_helper'
|
|
require_relative 'fixtures/classes'
|
|
|
|
describe "String#sub with pattern, replacement" do
|
|
it "returns a copy of self when no modification is made" do
|
|
a = "hello"
|
|
b = a.sub(/w.*$/, "*")
|
|
|
|
b.should_not equal(a)
|
|
b.should == "hello"
|
|
end
|
|
|
|
it "returns a copy of self with all occurrences of pattern replaced with replacement" do
|
|
"hello".sub(/[aeiou]/, '*').should == "h*llo"
|
|
"hello".sub(//, ".").should == ".hello"
|
|
end
|
|
|
|
it "ignores a block if supplied" do
|
|
"food".sub(/f/, "g") { "w" }.should == "good"
|
|
end
|
|
|
|
it "supports \\G which matches at the beginning of the string" do
|
|
"hello world!".sub(/\Ghello/, "hi").should == "hi world!"
|
|
end
|
|
|
|
it "supports /i for ignoring case" do
|
|
"Hello".sub(/h/i, "j").should == "jello"
|
|
"hello".sub(/H/i, "j").should == "jello"
|
|
end
|
|
|
|
it "doesn't interpret regexp metacharacters if pattern is a string" do
|
|
"12345".sub('\d', 'a').should == "12345"
|
|
'\d'.sub('\d', 'a').should == "a"
|
|
end
|
|
|
|
it "replaces \\1 sequences with the regexp's corresponding capture" do
|
|
str = "hello"
|
|
|
|
str.sub(/([aeiou])/, '<\1>').should == "h<e>llo"
|
|
str.sub(/(.)/, '\1\1').should == "hhello"
|
|
|
|
str.sub(/.(.?)/, '<\0>(\1)').should == "<he>(e)llo"
|
|
|
|
str.sub(/.(.)+/, '\1').should == "o"
|
|
|
|
str = "ABCDEFGHIJKL"
|
|
re = /#{"(.)" * 12}/
|
|
str.sub(re, '\1').should == "A"
|
|
str.sub(re, '\9').should == "I"
|
|
# Only the first 9 captures can be accessed in MRI
|
|
str.sub(re, '\10').should == "A0"
|
|
end
|
|
|
|
it "treats \\1 sequences without corresponding captures as empty strings" do
|
|
str = "hello!"
|
|
|
|
str.sub("", '<\1>').should == "<>hello!"
|
|
str.sub("h", '<\1>').should == "<>ello!"
|
|
|
|
str.sub(//, '<\1>').should == "<>hello!"
|
|
str.sub(/./, '\1\2\3').should == "ello!"
|
|
str.sub(/.(.{20})?/, '\1').should == "ello!"
|
|
end
|
|
|
|
it "replaces \\& and \\0 with the complete match" do
|
|
str = "hello!"
|
|
|
|
str.sub("", '<\0>').should == "<>hello!"
|
|
str.sub("", '<\&>').should == "<>hello!"
|
|
str.sub("he", '<\0>').should == "<he>llo!"
|
|
str.sub("he", '<\&>').should == "<he>llo!"
|
|
str.sub("l", '<\0>').should == "he<l>lo!"
|
|
str.sub("l", '<\&>').should == "he<l>lo!"
|
|
|
|
str.sub(//, '<\0>').should == "<>hello!"
|
|
str.sub(//, '<\&>').should == "<>hello!"
|
|
str.sub(/../, '<\0>').should == "<he>llo!"
|
|
str.sub(/../, '<\&>').should == "<he>llo!"
|
|
str.sub(/(.)./, '<\0>').should == "<he>llo!"
|
|
end
|
|
|
|
it "replaces \\` with everything before the current match" do
|
|
str = "hello!"
|
|
|
|
str.sub("", '<\`>').should == "<>hello!"
|
|
str.sub("h", '<\`>').should == "<>ello!"
|
|
str.sub("l", '<\`>').should == "he<he>lo!"
|
|
str.sub("!", '<\`>').should == "hello<hello>"
|
|
|
|
str.sub(//, '<\`>').should == "<>hello!"
|
|
str.sub(/..o/, '<\`>').should == "he<he>!"
|
|
end
|
|
|
|
it "replaces \\' with everything after the current match" do
|
|
str = "hello!"
|
|
|
|
str.sub("", '<\\\'>').should == "<hello!>hello!"
|
|
str.sub("h", '<\\\'>').should == "<ello!>ello!"
|
|
str.sub("ll", '<\\\'>').should == "he<o!>o!"
|
|
str.sub("!", '<\\\'>').should == "hello<>"
|
|
|
|
str.sub(//, '<\\\'>').should == "<hello!>hello!"
|
|
str.sub(/../, '<\\\'>').should == "<llo!>llo!"
|
|
end
|
|
|
|
it "replaces \\\\\\+ with \\\\+" do
|
|
"x".sub(/x/, '\\\+').should == "\\+"
|
|
end
|
|
|
|
it "replaces \\+ with the last paren that actually matched" do
|
|
str = "hello!"
|
|
|
|
str.sub(/(.)(.)/, '\+').should == "ello!"
|
|
str.sub(/(.)(.)+/, '\+').should == "!"
|
|
str.sub(/(.)()/, '\+').should == "ello!"
|
|
str.sub(/(.)(.{20})?/, '<\+>').should == "<h>ello!"
|
|
|
|
str = "ABCDEFGHIJKL"
|
|
re = /#{"(.)" * 12}/
|
|
str.sub(re, '\+').should == "L"
|
|
end
|
|
|
|
it "treats \\+ as an empty string if there was no captures" do
|
|
"hello!".sub(/./, '\+').should == "ello!"
|
|
end
|
|
|
|
it "maps \\\\ in replacement to \\" do
|
|
"hello".sub(/./, '\\\\').should == '\\ello'
|
|
end
|
|
|
|
it "leaves unknown \\x escapes in replacement untouched" do
|
|
"hello".sub(/./, '\\x').should == '\\xello'
|
|
"hello".sub(/./, '\\y').should == '\\yello'
|
|
end
|
|
|
|
it "leaves \\ at the end of replacement untouched" do
|
|
"hello".sub(/./, 'hah\\').should == 'hah\\ello'
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "taints the result if the original string or replacement is tainted" do
|
|
hello = "hello"
|
|
hello_t = "hello"
|
|
a = "a"
|
|
a_t = "a"
|
|
empty = ""
|
|
empty_t = ""
|
|
|
|
hello_t.taint; a_t.taint; empty_t.taint
|
|
|
|
hello_t.sub(/./, a).tainted?.should == true
|
|
hello_t.sub(/./, empty).tainted?.should == true
|
|
|
|
hello.sub(/./, a_t).tainted?.should == true
|
|
hello.sub(/./, empty_t).tainted?.should == true
|
|
hello.sub(//, empty_t).tainted?.should == true
|
|
|
|
hello.sub(//.taint, "foo").tainted?.should == false
|
|
end
|
|
end
|
|
|
|
it "tries to convert pattern to a string using to_str" do
|
|
pattern = mock('.')
|
|
pattern.should_receive(:to_str).and_return(".")
|
|
|
|
"hello.".sub(pattern, "!").should == "hello!"
|
|
end
|
|
|
|
not_supported_on :opal do
|
|
it "raises a TypeError when pattern is a Symbol" do
|
|
-> { "hello".sub(:woot, "x") }.should raise_error(TypeError)
|
|
end
|
|
end
|
|
|
|
it "raises a TypeError when pattern is an Array" do
|
|
-> { "hello".sub([], "x") }.should raise_error(TypeError)
|
|
end
|
|
|
|
it "raises a TypeError when pattern can't be converted to a string" do
|
|
-> { "hello".sub(Object.new, nil) }.should raise_error(TypeError)
|
|
end
|
|
|
|
it "tries to convert replacement to a string using to_str" do
|
|
replacement = mock('hello_replacement')
|
|
replacement.should_receive(:to_str).and_return("hello_replacement")
|
|
|
|
"hello".sub(/hello/, replacement).should == "hello_replacement"
|
|
end
|
|
|
|
it "raises a TypeError when replacement can't be converted to a string" do
|
|
-> { "hello".sub(/[aeiou]/, []) }.should raise_error(TypeError)
|
|
-> { "hello".sub(/[aeiou]/, 99) }.should raise_error(TypeError)
|
|
end
|
|
|
|
it "returns subclass instances when called on a subclass" do
|
|
StringSpecs::MyString.new("").sub(//, "").should be_an_instance_of(StringSpecs::MyString)
|
|
StringSpecs::MyString.new("").sub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
|
|
StringSpecs::MyString.new("foo").sub(/foo/, "").should be_an_instance_of(StringSpecs::MyString)
|
|
StringSpecs::MyString.new("foo").sub("foo", "").should be_an_instance_of(StringSpecs::MyString)
|
|
end
|
|
|
|
it "sets $~ to MatchData of match and nil when there's none" do
|
|
'hello.'.sub('hello', 'x')
|
|
$~[0].should == 'hello'
|
|
|
|
'hello.'.sub('not', 'x')
|
|
$~.should == nil
|
|
|
|
'hello.'.sub(/.(.)/, 'x')
|
|
$~[0].should == 'he'
|
|
|
|
'hello.'.sub(/not/, 'x')
|
|
$~.should == nil
|
|
end
|
|
|
|
it "replaces \\\\\\1 with \\1" do
|
|
"ababa".sub(/(b)/, '\\\1').should == "a\\1aba"
|
|
end
|
|
|
|
it "replaces \\\\\\\\1 with \\1" do
|
|
"ababa".sub(/(b)/, '\\\\1').should == "a\\1aba"
|
|
end
|
|
|
|
it "replaces \\\\\\\\\\1 with \\" do
|
|
"ababa".sub(/(b)/, '\\\\\1').should == "a\\baba"
|
|
end
|
|
|
|
end
|
|
|
|
describe "String#sub with pattern and block" do
|
|
it "returns a copy of self with the first occurrences of pattern replaced with the block's return value" do
|
|
"hi".sub(/./) { |s| s + ' ' }.should == "h i"
|
|
"hi!".sub(/(.)(.)/) { |*a| a.inspect }.should == '["hi"]!'
|
|
end
|
|
|
|
it "sets $~ for access from the block" do
|
|
str = "hello"
|
|
str.sub(/([aeiou])/) { "<#{$~[1]}>" }.should == "h<e>llo"
|
|
str.sub(/([aeiou])/) { "<#{$1}>" }.should == "h<e>llo"
|
|
str.sub("l") { "<#{$~[0]}>" }.should == "he<l>lo"
|
|
|
|
offsets = []
|
|
|
|
str.sub(/([aeiou])/) do
|
|
md = $~
|
|
md.string.should == str
|
|
offsets << md.offset(0)
|
|
str
|
|
end.should == "hhellollo"
|
|
|
|
offsets.should == [[1, 2]]
|
|
end
|
|
|
|
it "sets $~ to MatchData of last match and nil when there's none for access from outside" do
|
|
'hello.'.sub('l') { 'x' }
|
|
$~.begin(0).should == 2
|
|
$~[0].should == 'l'
|
|
|
|
'hello.'.sub('not') { 'x' }
|
|
$~.should == nil
|
|
|
|
'hello.'.sub(/.(.)/) { 'x' }
|
|
$~[0].should == 'he'
|
|
|
|
'hello.'.sub(/not/) { 'x' }
|
|
$~.should == nil
|
|
end
|
|
|
|
it "doesn't raise a RuntimeError if the string is modified while substituting" do
|
|
str = "hello"
|
|
str.sub(//) { str[0] = 'x' }.should == "xhello"
|
|
str.should == "xello"
|
|
end
|
|
|
|
it "doesn't interpolate special sequences like \\1 for the block's return value" do
|
|
repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
|
|
"hello".sub(/(.+)/) { repl }.should == repl
|
|
end
|
|
|
|
it "converts the block's return value to a string using to_s" do
|
|
obj = mock('hello_replacement')
|
|
obj.should_receive(:to_s).and_return("hello_replacement")
|
|
"hello".sub(/hello/) { obj }.should == "hello_replacement"
|
|
|
|
obj = mock('ok')
|
|
obj.should_receive(:to_s).and_return("ok")
|
|
"hello".sub(/.+/) { obj }.should == "ok"
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "taints the result if the original string or replacement is tainted" do
|
|
hello = "hello"
|
|
hello_t = "hello"
|
|
a = "a"
|
|
a_t = "a"
|
|
empty = ""
|
|
empty_t = ""
|
|
|
|
hello_t.taint; a_t.taint; empty_t.taint
|
|
|
|
hello_t.sub(/./) { a }.tainted?.should == true
|
|
hello_t.sub(/./) { empty }.tainted?.should == true
|
|
|
|
hello.sub(/./) { a_t }.tainted?.should == true
|
|
hello.sub(/./) { empty_t }.tainted?.should == true
|
|
hello.sub(//) { empty_t }.tainted?.should == true
|
|
|
|
hello.sub(//.taint) { "foo" }.tainted?.should == false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "String#sub! with pattern, replacement" do
|
|
it "modifies self in place and returns self" do
|
|
a = "hello"
|
|
a.sub!(/[aeiou]/, '*').should equal(a)
|
|
a.should == "h*llo"
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "taints self if replacement is tainted" do
|
|
a = "hello"
|
|
a.sub!(/./.taint, "foo").tainted?.should == false
|
|
a.sub!(/./, "foo".taint).tainted?.should == true
|
|
end
|
|
end
|
|
|
|
it "returns nil if no modifications were made" do
|
|
a = "hello"
|
|
a.sub!(/z/, '*').should == nil
|
|
a.sub!(/z/, 'z').should == nil
|
|
a.should == "hello"
|
|
end
|
|
|
|
it "raises a #{frozen_error_class} when self is frozen" do
|
|
s = "hello"
|
|
s.freeze
|
|
|
|
-> { s.sub!(/ROAR/, "x") }.should raise_error(frozen_error_class)
|
|
-> { s.sub!(/e/, "e") }.should raise_error(frozen_error_class)
|
|
-> { s.sub!(/[aeiou]/, '*') }.should raise_error(frozen_error_class)
|
|
end
|
|
end
|
|
|
|
describe "String#sub! with pattern and block" do
|
|
it "modifies self in place and returns self" do
|
|
a = "hello"
|
|
a.sub!(/[aeiou]/) { '*' }.should equal(a)
|
|
a.should == "h*llo"
|
|
end
|
|
|
|
it "sets $~ for access from the block" do
|
|
str = "hello"
|
|
str.dup.sub!(/([aeiou])/) { "<#{$~[1]}>" }.should == "h<e>llo"
|
|
str.dup.sub!(/([aeiou])/) { "<#{$1}>" }.should == "h<e>llo"
|
|
str.dup.sub!("l") { "<#{$~[0]}>" }.should == "he<l>lo"
|
|
|
|
offsets = []
|
|
|
|
str.dup.sub!(/([aeiou])/) do
|
|
md = $~
|
|
md.string.should == str
|
|
offsets << md.offset(0)
|
|
str
|
|
end.should == "hhellollo"
|
|
|
|
offsets.should == [[1, 2]]
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "taints self if block's result is tainted" do
|
|
a = "hello"
|
|
a.sub!(/./.taint) { "foo" }.tainted?.should == false
|
|
a.sub!(/./) { "foo".taint }.tainted?.should == true
|
|
end
|
|
end
|
|
|
|
it "returns nil if no modifications were made" do
|
|
a = "hello"
|
|
a.sub!(/z/) { '*' }.should == nil
|
|
a.sub!(/z/) { 'z' }.should == nil
|
|
a.should == "hello"
|
|
end
|
|
|
|
it "raises a RuntimeError if the string is modified while substituting" do
|
|
str = "hello"
|
|
-> { str.sub!(//) { str << 'x' } }.should raise_error(RuntimeError)
|
|
end
|
|
|
|
it "raises a #{frozen_error_class} when self is frozen" do
|
|
s = "hello"
|
|
s.freeze
|
|
|
|
-> { s.sub!(/ROAR/) { "x" } }.should raise_error(frozen_error_class)
|
|
-> { s.sub!(/e/) { "e" } }.should raise_error(frozen_error_class)
|
|
-> { s.sub!(/[aeiou]/) { '*' } }.should raise_error(frozen_error_class)
|
|
end
|
|
end
|
|
|
|
describe "String#sub with pattern and Hash" do
|
|
|
|
it "returns a copy of self with the first occurrence of pattern replaced with the value of the corresponding hash key" do
|
|
"hello".sub(/./, 'l' => 'L').should == "ello"
|
|
"hello!".sub(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she llo!'
|
|
"hello".sub('l', 'l' => 'el').should == 'heello'
|
|
end
|
|
|
|
it "removes keys that don't correspond to matches" do
|
|
"hello".sub(/./, 'z' => 'b', 'o' => 'ow').should == "ello"
|
|
end
|
|
|
|
it "ignores non-String keys" do
|
|
"tattoo".sub(/(tt)/, 'tt' => 'b', tt: 'z').should == "taboo"
|
|
end
|
|
|
|
it "uses a key's value only a single time" do
|
|
"food".sub(/o/, 'o' => '0').should == "f0od"
|
|
end
|
|
|
|
it "uses the hash's default value for missing keys" do
|
|
hsh = {}
|
|
hsh.default='?'
|
|
hsh['o'] = '0'
|
|
"food".sub(/./, hsh).should == "?ood"
|
|
end
|
|
|
|
it "coerces the hash values with #to_s" do
|
|
hsh = {}
|
|
hsh.default=[]
|
|
hsh['o'] = 0
|
|
obj = mock('!')
|
|
obj.should_receive(:to_s).and_return('!')
|
|
hsh['f'] = obj
|
|
"food!".sub(/./, hsh).should == "!ood!"
|
|
end
|
|
|
|
it "uses the hash's value set from default_proc for missing keys" do
|
|
hsh = {}
|
|
hsh.default_proc = -> k, v { 'lamb' }
|
|
"food!".sub(/./, hsh).should == "lambood!"
|
|
end
|
|
|
|
it "sets $~ to MatchData of first match and nil when there's none for access from outside" do
|
|
'hello.'.sub('l', 'l' => 'L')
|
|
$~.begin(0).should == 2
|
|
$~[0].should == 'l'
|
|
|
|
'hello.'.sub('not', 'ot' => 'to')
|
|
$~.should == nil
|
|
|
|
'hello.'.sub(/.(.)/, 'o' => ' hole')
|
|
$~[0].should == 'he'
|
|
|
|
'hello.'.sub(/not/, 'z' => 'glark')
|
|
$~.should == nil
|
|
end
|
|
|
|
it "doesn't interpolate special sequences like \\1 for the block's return value" do
|
|
repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
|
|
"hello".sub(/(.+)/, 'hello' => repl ).should == repl
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "untrusts the result if the original string is untrusted" do
|
|
str = "Ghana".untrust
|
|
str.sub(/[Aa]na/, 'ana' => '').untrusted?.should be_true
|
|
end
|
|
|
|
it "untrusts the result if a hash value is untrusted" do
|
|
str = "Ghana"
|
|
str.sub(/a$/, 'a' => 'di'.untrust).untrusted?.should be_true
|
|
end
|
|
|
|
it "taints the result if the original string is tainted" do
|
|
str = "Ghana".taint
|
|
str.sub(/[Aa]na/, 'ana' => '').tainted?.should be_true
|
|
end
|
|
|
|
it "taints the result if a hash value is tainted" do
|
|
str = "Ghana"
|
|
str.sub(/a$/, 'a' => 'di'.taint).tainted?.should be_true
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
describe "String#sub! with pattern and Hash" do
|
|
|
|
it "returns self with the first occurrence of pattern replaced with the value of the corresponding hash key" do
|
|
"hello".sub!(/./, 'l' => 'L').should == "ello"
|
|
"hello!".sub!(/(.)(.)/, 'he' => 'she ', 'll' => 'said').should == 'she llo!'
|
|
"hello".sub!('l', 'l' => 'el').should == 'heello'
|
|
end
|
|
|
|
it "removes keys that don't correspond to matches" do
|
|
"hello".sub!(/./, 'z' => 'b', 'o' => 'ow').should == "ello"
|
|
end
|
|
|
|
it "ignores non-String keys" do
|
|
"hello".sub!(/(ll)/, 'll' => 'r', ll: 'z').should == "hero"
|
|
end
|
|
|
|
it "uses a key's value only a single time" do
|
|
"food".sub!(/o/, 'o' => '0').should == "f0od"
|
|
end
|
|
|
|
it "uses the hash's default value for missing keys" do
|
|
hsh = {}
|
|
hsh.default='?'
|
|
hsh['o'] = '0'
|
|
"food".sub!(/./, hsh).should == "?ood"
|
|
end
|
|
|
|
it "coerces the hash values with #to_s" do
|
|
hsh = {}
|
|
hsh.default=[]
|
|
hsh['o'] = 0
|
|
obj = mock('!')
|
|
obj.should_receive(:to_s).and_return('!')
|
|
hsh['f'] = obj
|
|
"food!".sub!(/./, hsh).should == "!ood!"
|
|
end
|
|
|
|
it "uses the hash's value set from default_proc for missing keys" do
|
|
hsh = {}
|
|
hsh.default_proc = -> k, v { 'lamb' }
|
|
"food!".sub!(/./, hsh).should == "lambood!"
|
|
end
|
|
|
|
it "sets $~ to MatchData of first match and nil when there's none for access from outside" do
|
|
'hello.'.sub!('l', 'l' => 'L')
|
|
$~.begin(0).should == 2
|
|
$~[0].should == 'l'
|
|
|
|
'hello.'.sub!('not', 'ot' => 'to')
|
|
$~.should == nil
|
|
|
|
'hello.'.sub!(/.(.)/, 'o' => ' hole')
|
|
$~[0].should == 'he'
|
|
|
|
'hello.'.sub!(/not/, 'z' => 'glark')
|
|
$~.should == nil
|
|
end
|
|
|
|
it "doesn't interpolate special sequences like \\1 for the block's return value" do
|
|
repl = '\& \0 \1 \` \\\' \+ \\\\ foo'
|
|
"hello".sub!(/(.+)/, 'hello' => repl ).should == repl
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
it "keeps untrusted state" do
|
|
str = "Ghana".untrust
|
|
str.sub!(/[Aa]na/, 'ana' => '').untrusted?.should be_true
|
|
end
|
|
|
|
it "untrusts self if a hash value is untrusted" do
|
|
str = "Ghana"
|
|
str.sub!(/a$/, 'a' => 'di'.untrust).untrusted?.should be_true
|
|
end
|
|
|
|
it "keeps tainted state" do
|
|
str = "Ghana".taint
|
|
str.sub!(/[Aa]na/, 'ana' => '').tainted?.should be_true
|
|
end
|
|
|
|
it "taints self if a hash value is tainted" do
|
|
str = "Ghana"
|
|
str.sub!(/a$/, 'a' => 'di'.taint).tainted?.should be_true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "String#sub with pattern and without replacement and block" do
|
|
it "raises a ArgumentError" do
|
|
-> { "abca".sub(/a/) }.should raise_error(ArgumentError)
|
|
end
|
|
end
|
|
|
|
describe "String#sub! with pattern and without replacement and block" do
|
|
it "raises a ArgumentError" do
|
|
-> { "abca".sub!(/a/) }.should raise_error(ArgumentError)
|
|
end
|
|
end
|