mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
9e0075a3d9
iff means if and only if, but readers without that knowledge might assume this to be a spelling mistake. To me, this seems like exclusionary language that is unnecessary. Simply using "if and only if" instead should suffice.
250 lines
6.9 KiB
Ruby
250 lines
6.9 KiB
Ruby
describe :hash_eql, shared: true do
|
|
it "does not compare values when keys don't match" do
|
|
value = mock('x')
|
|
value.should_not_receive(:==)
|
|
value.should_not_receive(:eql?)
|
|
{ 1 => value }.send(@method, { 2 => value }).should be_false
|
|
end
|
|
|
|
it "returns false when the numbers of keys differ without comparing any elements" do
|
|
obj = mock('x')
|
|
h = { obj => obj }
|
|
|
|
obj.should_not_receive(:==)
|
|
obj.should_not_receive(:eql?)
|
|
|
|
{}.send(@method, h).should be_false
|
|
h.send(@method, {}).should be_false
|
|
end
|
|
|
|
it "first compares keys via hash" do
|
|
x = mock('x')
|
|
x.should_receive(:hash).any_number_of_times.and_return(0)
|
|
y = mock('y')
|
|
y.should_receive(:hash).any_number_of_times.and_return(0)
|
|
|
|
{ x => 1 }.send(@method, { y => 1 }).should be_false
|
|
end
|
|
|
|
it "does not compare keys with different hash codes via eql?" do
|
|
x = mock('x')
|
|
y = mock('y')
|
|
x.should_not_receive(:eql?)
|
|
y.should_not_receive(:eql?)
|
|
|
|
x.should_receive(:hash).any_number_of_times.and_return(0)
|
|
y.should_receive(:hash).any_number_of_times.and_return(1)
|
|
|
|
{ x => 1 }.send(@method, { y => 1 }).should be_false
|
|
end
|
|
|
|
it "computes equality for recursive hashes" do
|
|
h = {}
|
|
h[:a] = h
|
|
h.send(@method, h[:a]).should be_true
|
|
(h == h[:a]).should be_true
|
|
end
|
|
|
|
it "doesn't call to_hash on objects" do
|
|
mock_hash = mock("fake hash")
|
|
def mock_hash.to_hash() {} end
|
|
{}.send(@method, mock_hash).should be_false
|
|
end
|
|
|
|
it "computes equality for complex recursive hashes" do
|
|
a, b = {}, {}
|
|
a.merge! self: a, other: b
|
|
b.merge! self: b, other: a
|
|
a.send(@method, b).should be_true # they both have the same structure!
|
|
|
|
c = {}
|
|
c.merge! other: c, self: c
|
|
c.send(@method, a).should be_true # subtle, but they both have the same structure!
|
|
a[:delta] = c[:delta] = a
|
|
c.send(@method, a).should be_false # not quite the same structure, as a[:other][:delta] = nil
|
|
c[:delta] = 42
|
|
c.send(@method, a).should be_false
|
|
a[:delta] = 42
|
|
c.send(@method, a).should be_false
|
|
b[:delta] = 42
|
|
c.send(@method, a).should be_true
|
|
end
|
|
|
|
it "computes equality for recursive hashes & arrays" do
|
|
x, y, z = [], [], []
|
|
a, b, c = {foo: x, bar: 42}, {foo: y, bar: 42}, {foo: z, bar: 42}
|
|
x << a
|
|
y << c
|
|
z << b
|
|
b.send(@method, c).should be_true # they clearly have the same structure!
|
|
y.send(@method, z).should be_true
|
|
a.send(@method, b).should be_true # subtle, but they both have the same structure!
|
|
x.send(@method, y).should be_true
|
|
y << x
|
|
y.send(@method, z).should be_false
|
|
z << x
|
|
y.send(@method, z).should be_true
|
|
|
|
a[:foo], a[:bar] = a[:bar], a[:foo]
|
|
a.send(@method, b).should be_false
|
|
b[:bar] = b[:foo]
|
|
b.send(@method, c).should be_false
|
|
end
|
|
end
|
|
|
|
describe :hash_eql_additional, shared: true do
|
|
it "compares values when keys match" do
|
|
x = mock('x')
|
|
y = mock('y')
|
|
def x.==(o) false end
|
|
def y.==(o) false end
|
|
def x.eql?(o) false end
|
|
def y.eql?(o) false end
|
|
{ 1 => x }.send(@method, { 1 => y }).should be_false
|
|
|
|
x = mock('x')
|
|
y = mock('y')
|
|
def x.==(o) true end
|
|
def y.==(o) true end
|
|
def x.eql?(o) true end
|
|
def y.eql?(o) true end
|
|
{ 1 => x }.send(@method, { 1 => y }).should be_true
|
|
end
|
|
|
|
it "compares keys with eql? semantics" do
|
|
{ 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true
|
|
{ 1.0 => "x" }.send(@method, { 1.0 => "x" }).should be_true
|
|
{ 1 => "x" }.send(@method, { 1.0 => "x" }).should be_false
|
|
{ 1.0 => "x" }.send(@method, { 1 => "x" }).should be_false
|
|
end
|
|
|
|
it "returns true if and only if other Hash has the same number of keys and each key-value pair matches" do
|
|
a = { a: 5 }
|
|
b = {}
|
|
a.send(@method, b).should be_false
|
|
|
|
b[:a] = 5
|
|
a.send(@method, b).should be_true
|
|
|
|
not_supported_on :opal do
|
|
c = { "a" => 5 }
|
|
a.send(@method, c).should be_false
|
|
end
|
|
|
|
c = { "A" => 5 }
|
|
a.send(@method, c).should be_false
|
|
|
|
c = { a: 6 }
|
|
a.send(@method, c).should be_false
|
|
end
|
|
|
|
it "does not call to_hash on hash subclasses" do
|
|
{ 5 => 6 }.send(@method, HashSpecs::ToHashHash[5 => 6]).should be_true
|
|
end
|
|
|
|
it "ignores hash class differences" do
|
|
h = { 1 => 2, 3 => 4 }
|
|
HashSpecs::MyHash[h].send(@method, h).should be_true
|
|
HashSpecs::MyHash[h].send(@method, HashSpecs::MyHash[h]).should be_true
|
|
h.send(@method, HashSpecs::MyHash[h]).should be_true
|
|
end
|
|
|
|
ruby_version_is '2.7' do
|
|
# Why isn't this true of eql? too ?
|
|
it "compares keys with matching hash codes via eql?" do
|
|
a = Array.new(2) do
|
|
obj = mock('0')
|
|
obj.should_receive(:hash).at_least(1).and_return(0)
|
|
|
|
def obj.eql?(o)
|
|
return true if self.equal?(o)
|
|
false
|
|
end
|
|
|
|
obj
|
|
end
|
|
|
|
{ a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false
|
|
|
|
a = Array.new(2) do
|
|
obj = mock('0')
|
|
obj.should_receive(:hash).at_least(1).and_return(0)
|
|
|
|
def obj.eql?(o)
|
|
true
|
|
end
|
|
|
|
obj
|
|
end
|
|
|
|
{ a[0] => 1 }.send(@method, { a[1] => 1 }).should be_true
|
|
end
|
|
end
|
|
|
|
ruby_version_is ''...'2.7' do
|
|
# Why isn't this true of eql? too ?
|
|
it "compares keys with matching hash codes via eql?" do
|
|
a = Array.new(2) do
|
|
obj = mock('0')
|
|
obj.should_receive(:hash).at_least(1).and_return(0)
|
|
|
|
# It's undefined whether the impl does a[0].eql?(a[1]) or
|
|
# a[1].eql?(a[0]) so we taint both.
|
|
def obj.eql?(o)
|
|
return true if self.equal?(o)
|
|
taint
|
|
o.taint
|
|
false
|
|
end
|
|
|
|
obj
|
|
end
|
|
|
|
{ a[0] => 1 }.send(@method, { a[1] => 1 }).should be_false
|
|
a[0].tainted?.should be_true
|
|
a[1].tainted?.should be_true
|
|
|
|
a = Array.new(2) do
|
|
obj = mock('0')
|
|
obj.should_receive(:hash).at_least(1).and_return(0)
|
|
|
|
def obj.eql?(o)
|
|
# It's undefined whether the impl does a[0].send(@method, a[1]) or
|
|
# a[1].send(@method, a[0]) so we taint both.
|
|
taint
|
|
o.taint
|
|
true
|
|
end
|
|
|
|
obj
|
|
end
|
|
|
|
{ a[0] => 1 }.send(@method, { a[1] => 1 }).should be_true
|
|
a[0].tainted?.should be_true
|
|
a[1].tainted?.should be_true
|
|
end
|
|
end
|
|
|
|
it "compares the values in self to values in other hash" do
|
|
l_val = mock("left")
|
|
r_val = mock("right")
|
|
|
|
l_val.should_receive(:eql?).with(r_val).and_return(true)
|
|
|
|
{ 1 => l_val }.eql?({ 1 => r_val }).should be_true
|
|
end
|
|
end
|
|
|
|
describe :hash_eql_additional_more, shared: true do
|
|
it "returns true if other Hash has the same number of keys and each key-value pair matches, even though the default-value are not same" do
|
|
Hash.new(5).send(@method, Hash.new(1)).should be_true
|
|
Hash.new {|h, k| 1}.send(@method, Hash.new {}).should be_true
|
|
Hash.new {|h, k| 1}.send(@method, Hash.new(2)).should be_true
|
|
|
|
d = Hash.new {|h, k| 1}
|
|
e = Hash.new {}
|
|
d[1] = 2
|
|
e[1] = 2
|
|
d.send(@method, e).should be_true
|
|
end
|
|
end
|