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

Issue

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,
  }

Solution

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:

Answered By – mpx

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.