mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
util/deephash: handle slice edge-cases (#5471)
It is unclear whether the lack of checking nil-ness of slices was an oversight or a deliberate feature. Lacking a comment, the assumption is that this was an oversight. Also, expand the logic to perform cycle detection for recursive slices. We do this on a per-element basis since a slice is semantically equivalent to a list of pointers. Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
parent
90dc0e1702
commit
7e40071571
@ -294,6 +294,11 @@ func makeSliceHasher(t reflect.Type) typeHasherFunc {
|
|||||||
if typeIsMemHashable(t.Elem()) {
|
if typeIsMemHashable(t.Elem()) {
|
||||||
return func(h *hasher, p pointer) {
|
return func(h *hasher, p pointer) {
|
||||||
pa := p.sliceArray()
|
pa := p.sliceArray()
|
||||||
|
if pa.isNil() {
|
||||||
|
h.HashUint8(0) // indicates nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.HashUint8(1) // indicates visiting slice
|
||||||
n := p.sliceLen()
|
n := p.sliceLen()
|
||||||
b := pa.asMemory(uintptr(n) * nb)
|
b := pa.asMemory(uintptr(n) * nb)
|
||||||
h.HashUint64(uint64(n))
|
h.HashUint64(uint64(n))
|
||||||
@ -305,11 +310,30 @@ func makeSliceHasher(t reflect.Type) typeHasherFunc {
|
|||||||
var hashElem typeHasherFunc
|
var hashElem typeHasherFunc
|
||||||
init := func() {
|
init := func() {
|
||||||
hashElem = lookupTypeHasher(t.Elem())
|
hashElem = lookupTypeHasher(t.Elem())
|
||||||
|
if typeIsRecursive(t) {
|
||||||
|
hashElemDefault := hashElem
|
||||||
|
hashElem = func(h *hasher, p pointer) {
|
||||||
|
if idx, ok := h.visitStack.seen(p.p); ok {
|
||||||
|
h.HashUint8(2) // indicates cycle
|
||||||
|
h.HashUint64(uint64(idx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.HashUint8(1) // indicates visiting slice element
|
||||||
|
h.visitStack.push(p.p)
|
||||||
|
defer h.visitStack.pop(p.p)
|
||||||
|
hashElemDefault(h, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(h *hasher, p pointer) {
|
return func(h *hasher, p pointer) {
|
||||||
pa := p.sliceArray()
|
pa := p.sliceArray()
|
||||||
|
if pa.isNil() {
|
||||||
|
h.HashUint8(0) // indicates nil
|
||||||
|
return
|
||||||
|
}
|
||||||
once.Do(init)
|
once.Do(init)
|
||||||
|
h.HashUint8(1) // indicates visiting slice
|
||||||
n := p.sliceLen()
|
n := p.sliceLen()
|
||||||
h.HashUint64(uint64(n))
|
h.HashUint64(uint64(n))
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
|
@ -95,6 +95,12 @@ type scalars struct {
|
|||||||
{in: tuple{scalars{F64: float64(math.NaN())}, scalars{F64: float64(math.NaN())}}, wantEq: true},
|
{in: tuple{scalars{F64: float64(math.NaN())}, scalars{F64: float64(math.NaN())}}, wantEq: true},
|
||||||
{in: tuple{scalars{C64: 32 + 32i}, scalars{C64: complex(math.Nextafter32(32, 0), 32)}}, wantEq: false},
|
{in: tuple{scalars{C64: 32 + 32i}, scalars{C64: complex(math.Nextafter32(32, 0), 32)}}, wantEq: false},
|
||||||
{in: tuple{scalars{C128: 64 + 64i}, scalars{C128: complex(math.Nextafter(64, 0), 64)}}, wantEq: false},
|
{in: tuple{scalars{C128: 64 + 64i}, scalars{C128: complex(math.Nextafter(64, 0), 64)}}, wantEq: false},
|
||||||
|
{in: tuple{[]int(nil), []int(nil)}, wantEq: true},
|
||||||
|
{in: tuple{[]int{}, []int(nil)}, wantEq: false},
|
||||||
|
{in: tuple{[]int{}, []int{}}, wantEq: true},
|
||||||
|
{in: tuple{[]string(nil), []string(nil)}, wantEq: true},
|
||||||
|
{in: tuple{[]string{}, []string(nil)}, wantEq: false},
|
||||||
|
{in: tuple{[]string{}, []string{}}, wantEq: true},
|
||||||
{in: tuple{[]appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}, []appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}}, wantEq: true},
|
{in: tuple{[]appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}, []appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}}, wantEq: true},
|
||||||
{in: tuple{[]appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}, []appendBytes{{0, 0, 0, 0, 0, 0, 0, 1}, {}}}, wantEq: false},
|
{in: tuple{[]appendBytes{{}, {0, 0, 0, 0, 0, 0, 0, 1}}, []appendBytes{{0, 0, 0, 0, 0, 0, 0, 1}, {}}}, wantEq: false},
|
||||||
{in: tuple{iface{MyBool(true)}, iface{MyBool(true)}}, wantEq: true},
|
{in: tuple{iface{MyBool(true)}, iface{MyBool(true)}}, wantEq: true},
|
||||||
@ -413,13 +419,13 @@ func TestGetTypeHasher(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "string_slice",
|
name: "string_slice",
|
||||||
val: []string{"foo", "bar"},
|
val: []string{"foo", "bar"},
|
||||||
out: "\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x03\x00\x00\x00\x00\x00\x00\x00bar",
|
out: "\x01\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x03\x00\x00\x00\x00\x00\x00\x00bar",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "int_slice",
|
name: "int_slice",
|
||||||
val: []int{1, 0, -1},
|
val: []int{1, 0, -1},
|
||||||
out: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff",
|
out: "\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff",
|
||||||
out32: "\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff",
|
out32: "\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "struct",
|
name: "struct",
|
||||||
@ -454,8 +460,8 @@ func TestGetTypeHasher(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "packet_filter",
|
name: "packet_filter",
|
||||||
val: filterRules,
|
val: filterRules,
|
||||||
out: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
out: "\x01\x04\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x01\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04 \x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00",
|
||||||
out32: "\x04\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x01\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
out32: "\x01\x04\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00*\v\x00\x00\x00\x00\x00\x00\x0010.1.3.4/32\v\x00\x00\x00\x00\x00\x00\x0010.0.0.0/24\x01\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x001.2.3.4/32\x01 \x00\x00\x00\x01\x00\x02\x00\x01\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04 \x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00foo\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00foooooooooo\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00baaaaaarrrrr\x00\x01\x00\x02\x00\x00\x00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "netip.Addr",
|
name: "netip.Addr",
|
||||||
@ -569,7 +575,7 @@ func TestGetTypeHasher(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "tailcfg.Node",
|
name: "tailcfg.Node",
|
||||||
val: &tailcfg.Node{},
|
val: &tailcfg.Node{},
|
||||||
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + u64(uint64(time.Time{}.Unix())) + u64(0) + u32(0) + u32(0) + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + u64(uint64(time.Time{}.Unix())) + u32(0) + u32(0) + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -594,6 +600,44 @@ func TestGetTypeHasher(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSliceCycle(t *testing.T) {
|
||||||
|
type S []S
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
a := make(S, 1) // cylic graph of 1 node
|
||||||
|
a[0] = a
|
||||||
|
b := make(S, 1) // cylic graph of 1 node
|
||||||
|
b[0] = b
|
||||||
|
ha := Hash(&a)
|
||||||
|
hb := Hash(&b)
|
||||||
|
c.Assert(ha, qt.Equals, hb)
|
||||||
|
|
||||||
|
c1 := make(S, 1) // cyclic graph of 2 nodes
|
||||||
|
c2 := make(S, 1) // cyclic graph of 2 nodes
|
||||||
|
c1[0] = c2
|
||||||
|
c2[0] = c1
|
||||||
|
hc1 := Hash(&c1)
|
||||||
|
hc2 := Hash(&c2)
|
||||||
|
c.Assert(hc1, qt.Equals, hc2)
|
||||||
|
c.Assert(ha, qt.Not(qt.Equals), hc1)
|
||||||
|
c.Assert(hb, qt.Not(qt.Equals), hc2)
|
||||||
|
|
||||||
|
c3 := make(S, 1) // graph of 1 node pointing to cyclic graph of 2 nodes
|
||||||
|
c3[0] = c1
|
||||||
|
hc3 := Hash(&c3)
|
||||||
|
c.Assert(hc1, qt.Not(qt.Equals), hc3)
|
||||||
|
|
||||||
|
c4 := make(S, 2) // cyclic graph of 3 nodes
|
||||||
|
c5 := make(S, 2) // cyclic graph of 3 nodes
|
||||||
|
c4[0] = nil
|
||||||
|
c4[1] = c4
|
||||||
|
c5[0] = c5
|
||||||
|
c5[1] = nil
|
||||||
|
hc4 := Hash(&c4)
|
||||||
|
hc5 := Hash(&c5)
|
||||||
|
c.Assert(hc4, qt.Not(qt.Equals), hc5) // cycle occurs through different indexes
|
||||||
|
}
|
||||||
|
|
||||||
func TestMapCycle(t *testing.T) {
|
func TestMapCycle(t *testing.T) {
|
||||||
type M map[string]M
|
type M map[string]M
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
@ -620,6 +664,16 @@ func TestMapCycle(t *testing.T) {
|
|||||||
c3["child"] = c1
|
c3["child"] = c1
|
||||||
hc3 := Hash(&c3)
|
hc3 := Hash(&c3)
|
||||||
c.Assert(hc1, qt.Not(qt.Equals), hc3)
|
c.Assert(hc1, qt.Not(qt.Equals), hc3)
|
||||||
|
|
||||||
|
c4 := make(M) // cyclic graph of 3 nodes
|
||||||
|
c5 := make(M) // cyclic graph of 3 nodes
|
||||||
|
c4["0"] = nil
|
||||||
|
c4["1"] = c4
|
||||||
|
c5["0"] = c5
|
||||||
|
c5["1"] = nil
|
||||||
|
hc4 := Hash(&c4)
|
||||||
|
hc5 := Hash(&c5)
|
||||||
|
c.Assert(hc4, qt.Not(qt.Equals), hc5) // cycle occurs through different keys
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPointerCycle(t *testing.T) {
|
func TestPointerCycle(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user