Why does go-cmp Equal() say that the structs are not deeply equal, even though all fields are deeply equal?

huangapple go评论93阅读模式
英文:

Why does go-cmp Equal() say that the structs are not deeply equal, even though all fields are deeply equal?

问题

我一直在使用reflect.DeepEqual来深度比较具有循环指针的结构体。由于这在处理映射时不起作用,并且为了获得更好的测试输出,我已经切换到了go-cmp

现在我注意到,尽管cmp.Equal被认为是reflect.DeepEqual的替代品,但在某些情况下,即使前者正确工作,结果也会有所不同,尽管它们实际上是深度相等的。

有人可以告诉我为什么在这种情况下结果不同,并且最好的情况下,如何修复它?

Go Playground中的代码:https://play.golang.com/p/rLWKwMlAfwu
(更新为使用fmt.Printf(),因为我无法在Playground中运行testing

diff的输出:

StrongConnect() mismatch (-want +got):
  &⟪ref#0⟫main.Edge{
  	StartNode: &⟪ref#1⟫main.Node{
  		Variable: 1,
- 		Low:      &⟪ref#0: 0xc00005c120⟫(...),
+ 		Low:      &⟪ref#0: 0xc00005c120⟫(...),
  		High:     &{StartNode: &⟪ref#1⟫(...), EndNode: &{Variable: 2}, EdgeType: 1, Weight: 1},
  	},
  	EndNode:  &{Variable: 2},
  	EdgeType: 0,
  	Weight:   1,
  }
英文:

I've been using reflect.DeepEqual to deeply compare structs using circular pointers. As that does not work with maps and for better test output, I've switched to go-cmp.

Now I had to take note that even though cmp.Equal is supposed to be a drop-in replacement for reflect.DeepEqual, in cases where the former works correctly, there's a different result in this case, even though it actually is deeply equal.

Can anyone tell me why the result is different in this case and ideally, how to fix it?

Code in Go playground: https://play.golang.com/p/rLWKwMlAfwu
(Updated to use fmt.Printf(), because I couldn't get testing running in playground)

Output for diff:

StrongConnect() mismatch (-want +got):
  &⟪ref#0⟫main.Edge{
  	StartNode: &⟪ref#1⟫main.Node{
  		Variable: 1,
- 		Low:      &⟪ref#0: 0xc00005c120⟫(...),
+ 		Low:      &⟪ref#0: 0xc00005c120⟫(...),
  		High:     &{StartNode: &⟪ref#1⟫(...), EndNode: &{Variable: 2}, EdgeType: 1, Weight: 1},
  	},
  	EndNode:  &{Variable: 2},
  	EdgeType: 0,
  	Weight:   1,
  }

答案1

得分: 2

reflect.DeepEqual在比较具有循环结构的结构体时比cmp.Equal更宽松(并且可能是错误的)。

当且仅当图中的节点和边的集合相同时,cmp.Equal才认为重叠的图是等价的。注意:在此图比较中,NodeEdge结构体都是节点。

在你的示例中,两个图/结构体是重叠的,但是wantEdge0有一个额外的Edge结构体作为根节点(在前面插入)。

以下是你的数据结构中循环的简化表示:

wantEdge0 := &main.Edge{   // n0
    StartNode: &main.Node{ // n1
        Low: &main.Edge{}  // n2
    },
}
wantEdge0.StartNode.Low.StartNode = wantEdge0.StartNode // n1
got := wantEdge0.StartNode.Low                          // n2

因此,存在两个不同的循环:

  1. wantEdge0 [n0] -> wantEdge0.StartNode [n1] -> got [n2] -> wantEdge0.StartNode [n1]
  2. got [n2] -> wantEdge0.StartNode [n1] -> got [n2]

以下是一个简单的示例,演示了reflect.DeepEqualcmp.Equal之间的差异:

package main

import (
	"fmt"
	"reflect"

	"github.com/google/go-cmp/cmp"
)

type Node struct {
	Next  *Node
	Value int
}

func main() {
	a0 := &Node{}
	a1 := &Node{}
	a2 := &Node{}
	a0.Next = a1
	a1.Next = a2
	a2.Next = a1

	b1 := &Node{}
	b2 := &Node{}
	b1.Next = b2
	b2.Next = b1

	fmt.Println("DeepEqual\tcmp.Equal")
	fmt.Printf("\t%v\t%v\t\t独立的图\n", reflect.DeepEqual(a1, b1), cmp.Equal(a1, b1))
	fmt.Printf("\t%v\t%v\t\t相同的图,不同的根节点\n", reflect.DeepEqual(a1, a2), cmp.Equal(a1, a2))
	fmt.Printf("\t%v\t%v\t\t相同的图,有/无前置节点\n", reflect.DeepEqual(a0, a1), cmp.Equal(a0, a1))
}

输出结果:

$ ./compare 
DeepEqual	cmp.Equal
	true	true		独立的图
	true	true		相同的图,不同的根节点
	true	false		相同的图,有/无前置节点

解决方案:我建议为你的“want”结构体和测试输入分配完全独立的结构体。这样,“want”和“got”结构体就不会重叠,它们应该按预期进行比较。

参考资料:

英文:

reflect.DeepEqual is more lax than cmp.Equal when comparing structures with cycles (and arguably incorrect).

cmp.Equal will only consider overlapping graphs equivalent if the set of nodes and edges in the graph is the same. Note: Both Node and Edge structs are nodes in this graph comparison.

In your example, the 2 graphs/structs overlap but wantEdge0 is has an extra Edge struct as the root node (prepended).

Here is cut down representation of the cycles in your data structures:

wantEdge0 := &main.Edge{   // n0
    StartNode: &main.Node{ // n1
        Low: &main.Edge{}  // n2
    },
}
wantEdge0.StartNode.Low.StartNode = wantEdge0.StartNode // n1
got := wantEdge0.StartNode.Low                          // n2

Hence there are the 2 different cycles:

  1. wantEdge0 [n0] -> wantEdge0.StartNode [n1] -> got [n2] -> wantEdge0.StartNode [n1]
  2. got [n2] -> wantEdge0.StartNode [n1] -> got [n2]

Here is a simple example that demonstrates this difference between reflect.DeepEqual and cmp.Equal:

package main

import (
	"fmt"
	"reflect"

	"github.com/google/go-cmp/cmp"
)

type Node struct {
	Next  *Node
	Value int
}

func main() {
	a0 := &Node{}
	a1 := &Node{}
	a2 := &Node{}
	a0.Next = a1
	a1.Next = a2
	a2.Next = a1

	b1 := &Node{}
	b2 := &Node{}
	b1.Next = b2
	b2.Next = b1

	fmt.Println("DeepEqual\tcmp.Equal")
	fmt.Printf("\t%v\t%v\t\tIndependent graphs\n", reflect.DeepEqual(a1, b1), cmp.Equal(a1, b1))
	fmt.Printf("\t%v\t%v\t\tSame graph, different root\n", reflect.DeepEqual(a1, a2), cmp.Equal(a1, a2))
	fmt.Printf("\t%v\t%v\t\tSame graph prepend vs no prepend\n", reflect.DeepEqual(a0, a1), cmp.Equal(a0, a1))
}

Output:

$ ./compare 
DeepEqual	cmp.Equal
true	true		Independent graphs
true	true		Same graph, different root
true	false		Same graph prepend vs no prepend

Solution: I would recommend allocating completely separate structures for your want struct and the test input. This way the want and got structs won't overlap and they should compare as intended.

References:

huangapple
  • 本文由 发表于 2022年4月18日 06:28:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/71905930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定