diff --git a/diff.go b/diff.go index 6aa7f74..40a09dc 100644 --- a/diff.go +++ b/diff.go @@ -41,7 +41,12 @@ type Printfer interface { // It calls Printf once for each difference, with no trailing newline. // The standard library log.Logger is a Printfer. func Pdiff(p Printfer, a, b interface{}) { - diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) + d := diffPrinter{ + w: p, + aVisited: make(map[visit]visit), + bVisited: make(map[visit]visit), + } + d.diff(reflect.ValueOf(a), reflect.ValueOf(b)) } type Logfer interface { @@ -66,6 +71,9 @@ func Ldiff(l Logfer, a, b interface{}) { type diffPrinter struct { w Printfer l string // label + + aVisited map[visit]visit + bVisited map[visit]visit } func (w diffPrinter) printf(f string, a ...interface{}) { @@ -96,6 +104,28 @@ func (w diffPrinter) diff(av, bv reflect.Value) { return } + if av.CanAddr() && bv.CanAddr() { + avis := visit{av.UnsafeAddr(), at} + bvis := visit{bv.UnsafeAddr(), bt} + var cycle bool + + // Have we seen this value before? + if vis, ok := w.aVisited[avis]; ok { + cycle = true + if vis != bvis { + w.printf("%# v (previously visited) != %# v", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) + } + } else if _, ok := w.bVisited[bvis]; ok { + cycle = true + w.printf("%# v != %# v (previously visited)", formatter{v: av, quote: true}, formatter{v: bv, quote: true}) + } + w.aVisited[avis] = bvis + w.bVisited[bvis] = avis + if cycle { + return + } + } + switch kind := at.Kind(); kind { case reflect.Bool: if a, b := av.Bool(), bv.Bool(); a != b { diff --git a/diff_test.go b/diff_test.go index a951e4b..08ec64b 100644 --- a/diff_test.go +++ b/diff_test.go @@ -130,20 +130,23 @@ var diffs = []difftest{ func TestDiff(t *testing.T) { for _, tt := range diffs { - got := Diff(tt.a, tt.b) - eq := len(got) == len(tt.exp) - if eq { - for i := range got { - eq = eq && got[i] == tt.exp[i] - } - } - if !eq { - t.Errorf("diffing % #v", tt.a) - t.Errorf("with % #v", tt.b) - diffdiff(t, got, tt.exp) - continue + expectDiffOutput(t, tt.a, tt.b, tt.exp) + } +} + +func expectDiffOutput(t *testing.T, a, b interface{}, exp []string) { + got := Diff(a, b) + eq := len(got) == len(exp) + if eq { + for i := range got { + eq = eq && got[i] == exp[i] } } + if !eq { + t.Errorf("diffing % #v", a) + t.Errorf("with % #v", b) + diffdiff(t, got, exp) + } } func TestKeyEqual(t *testing.T) { @@ -193,6 +196,47 @@ func TestFdiff(t *testing.T) { } } +func TestDiffCycle(t *testing.T) { + // Diff two cyclic structs + a := &I{i: 1, R: nil} + a.R = a + b := &I{i: 2, R: nil} + b.R = b + expectDiffOutput(t, a, b, []string{ + `i: 1 != 2`, + }) + + // Diff two equal cyclic structs + b.i = 1 + expectDiffOutput(t, a, b, []string{}) + + // Diff two structs with different cycles + b2 := &I{i: 1, R: b} + b.R = b2 + expectDiffOutput(t, a, b, []string{`R: pretty.I{ + i: 1, + R: &pretty.I{(CYCLIC REFERENCE)}, +} (previously visited) != pretty.I{ + i: 1, + R: &pretty.I{ + i: 1, + R: &pretty.I{(CYCLIC REFERENCE)}, + }, +}`}) + + // ... and the same in the other direction + expectDiffOutput(t, b, a, []string{`R: pretty.I{ + i: 1, + R: &pretty.I{ + i: 1, + R: &pretty.I{(CYCLIC REFERENCE)}, + }, +} != pretty.I{ + i: 1, + R: &pretty.I{(CYCLIC REFERENCE)}, +} (previously visited)`}) +} + func diffdiff(t *testing.T, got, exp []string) { minus(t, "unexpected:", got, exp) minus(t, "missing:", exp, got)