Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add DeepEqual and start using it in tests #174

Merged
merged 1 commit into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions equal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package ipld

// DeepEqual reports whether x and y are "deeply equal" as IPLD nodes.
// This is similar to reflect.DeepEqual, but based around the Node interface.
//
// Two nodes must have the same kind to be deeply equal.
// If either node has the invalid kind, the nodes are not deeply equal.
//
// Two nodes of scalar kinds (null, bool, int, float, string, bytes, link)
// are deeply equal if their Go values, as returned by AsKind methods, are equal as
// per Go's == comparison operator.
//
// Note that Links are compared in a shallow way, without being followed.
// This will generally be enough, as it's rare to have two different links to the
// same IPLD data by using a different codec or multihash type.
//
// Two nodes of recursive kinds (map, list)
// must have the same length to be deeply equal.
// Their elements, as reported by iterators, must be deeply equal.
// The elements are compared in the iterator's order,
// meaning two maps sorting the same keys differently might not be equal.
//
// Note that this function panics if either Node returns an error.
// We only call valid methods for each Kind,
// so an error should only happen if a Node implementation breaks that contract.
// It is generally not recommended to call DeepEqual on ADL nodes.
func DeepEqual(x, y Node) bool {
xk, yk := x.Kind(), y.Kind()
if xk != yk {
return false
}

switch xk {

// Scalar kinds.
case Kind_Null:
return x.IsNull() == y.IsNull()
case Kind_Bool:
xv, err := x.AsBool()
if err != nil {
panic(err)
}
yv, err := y.AsBool()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Int:
xv, err := x.AsInt()
if err != nil {
panic(err)
}
yv, err := y.AsInt()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Float:
xv, err := x.AsFloat()
if err != nil {
panic(err)
}
yv, err := y.AsFloat()
if err != nil {
panic(err)
}
return xv == yv
case Kind_String:
xv, err := x.AsString()
if err != nil {
panic(err)
}
yv, err := y.AsString()
if err != nil {
panic(err)
}
return xv == yv
case Kind_Bytes:
xv, err := x.AsBytes()
if err != nil {
panic(err)
}
yv, err := y.AsBytes()
if err != nil {
panic(err)
}
return string(xv) == string(yv)
case Kind_Link:
xv, err := x.AsLink()
if err != nil {
panic(err)
}
yv, err := y.AsLink()
if err != nil {
panic(err)
}
// Links are just compared via ==.
// This requires the types to exactly match,
// and the values to be equal as per == too.
// This will generally work,
// as ipld-prime assumes link types to be consistent.
return xv == yv

// Recursive kinds.
case Kind_Map:
if x.Length() != y.Length() {
return false
}
xitr := x.MapIterator()
yitr := y.MapIterator()
for !xitr.Done() && !yitr.Done() {
xkey, xval, err := xitr.Next()
if err != nil {
panic(err)
}
ykey, yval, err := yitr.Next()
if err != nil {
panic(err)
}
if !DeepEqual(xkey, ykey) {
return false
}
if !DeepEqual(xval, yval) {
return false
}
}
return true
case Kind_List:
if x.Length() != y.Length() {
return false
}
xitr := x.ListIterator()
yitr := y.ListIterator()
for !xitr.Done() && !yitr.Done() {
_, xval, err := xitr.Next()
if err != nil {
panic(err)
}
_, yval, err := yitr.Next()
if err != nil {
panic(err)
}
if !DeepEqual(xval, yval) {
return false
}
}
return true

// As per the docs, other kinds such as Invalid are not deeply equal.
default:
return false
}
}
147 changes: 147 additions & 0 deletions equal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package ipld_test

import (
"testing"

"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent/qp"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
basic "github.com/ipld/go-ipld-prime/node/basic" // shorter name for the tests
)

var (
globalNode = basic.NewString("global")
globalLink = func() ipld.Link {
someCid, _ := cid.Cast([]byte{1, 85, 0, 5, 0, 1, 2, 3, 4})
return cidlink.Link{Cid: someCid}
}()
globalLink2 = func() ipld.Link {
someCid, _ := cid.Cast([]byte{1, 85, 0, 5, 0, 5, 6, 7, 8})
return cidlink.Link{Cid: someCid}
}()
)

func qpMust(node ipld.Node, err error) ipld.Node {
if err != nil {
panic(err)
}
return node
}

var deepEqualTests = []struct {
name string
left, right ipld.Node
want bool
}{
{"MismatchingKinds", basic.NewBool(true), basic.NewInt(3), false},

{"SameNodeSamePointer", globalNode, globalNode, true},
// Repeated basicnode.New invocations might return different pointers.
{"SameNodeDiffPointer", basic.NewString("same"), basic.NewString("same"), true},

{"SameKindNull", ipld.Null, ipld.Null, true},
{"DiffKindNull", ipld.Null, ipld.Absent, false},
{"SameKindBool", basic.NewBool(true), basic.NewBool(true), true},
{"DiffKindBool", basic.NewBool(true), basic.NewBool(false), false},
{"SameKindInt", basic.NewInt(12), basic.NewInt(12), true},
{"DiffKindInt", basic.NewInt(12), basic.NewInt(15), false},
{"SameKindFloat", basic.NewFloat(1.25), basic.NewFloat(1.25), true},
{"DiffKindFloat", basic.NewFloat(1.25), basic.NewFloat(1.75), false},
{"SameKindString", basic.NewString("foobar"), basic.NewString("foobar"), true},
{"DiffKindString", basic.NewString("foobar"), basic.NewString("baz"), false},
{"SameKindBytes", basic.NewBytes([]byte{5, 2, 3}), basic.NewBytes([]byte{5, 2, 3}), true},
{"DiffKindBytes", basic.NewBytes([]byte{5, 2, 3}), basic.NewBytes([]byte{5, 8, 3}), false},
{"SameKindLink", basic.NewLink(globalLink), basic.NewLink(globalLink), true},
{"DiffKindLink", basic.NewLink(globalLink), basic.NewLink(globalLink2), false},

{
"SameKindList",
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(7))
qp.ListEntry(am, qp.Int(8))
})),
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(7))
qp.ListEntry(am, qp.Int(8))
})),
true,
},
{
"DiffKindList_length",
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(7))
qp.ListEntry(am, qp.Int(8))
})),
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(7))
})),
false,
},
{
"DiffKindList_elems",
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(7))
qp.ListEntry(am, qp.Int(8))
})),
qpMust(qp.BuildList(basic.Prototype.Any, -1, func(am ipld.ListAssembler) {
qp.ListEntry(am, qp.Int(3))
qp.ListEntry(am, qp.Int(2))
})),
false,
},

{
"SameKindMap",
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(7))
qp.MapEntry(am, "bar", qp.Int(8))
})),
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(7))
qp.MapEntry(am, "bar", qp.Int(8))
})),
true,
},
{
"DiffKindMap_length",
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(7))
qp.MapEntry(am, "bar", qp.Int(8))
})),
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(7))
})),
false,
},
{
"DiffKindMap_elems",
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(7))
qp.MapEntry(am, "bar", qp.Int(8))
})),
qpMust(qp.BuildMap(basic.Prototype.Any, -1, func(am ipld.MapAssembler) {
qp.MapEntry(am, "foo", qp.Int(3))
qp.MapEntry(am, "baz", qp.Int(8))
})),
false,
},

// TODO: tests involving different implementations, once bindnode is ready

}

func TestDeepEqual(t *testing.T) {
t.Parallel()
for _, tc := range deepEqualTests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

got := ipld.DeepEqual(tc.left, tc.right)
if got != tc.want {
t.Fatalf("DeepEqual got %v, want %v", got, tc.want)
}
})
}
}
6 changes: 3 additions & 3 deletions schema/gen/go/testLists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestListsContainingMaybe(t *testing.T) {
la.AssembleValue().AssignString("1")
la.AssembleValue().AssignString("2")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
t.Run("nullable", func(t *testing.T) {
Expand Down Expand Up @@ -93,7 +93,7 @@ func TestListsContainingMaybe(t *testing.T) {
la.AssembleValue().AssignString("1")
la.AssembleValue().AssignNull()
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
}
Expand Down Expand Up @@ -207,7 +207,7 @@ func TestListsContainingLists(t *testing.T) {
la.AssembleValue().CreateMap(1, func(ma fluent.MapAssembler) { ma.AssembleEntry("encoded").AssignString("32") })
})
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})

Expand Down
8 changes: 4 additions & 4 deletions schema/gen/go/testMaps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestMapsContainingMaybe(t *testing.T) {
ma.AssembleEntry("one").AssignString("1")
ma.AssembleEntry("two").AssignString("2")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
t.Run("nullable", func(t *testing.T) {
Expand Down Expand Up @@ -93,7 +93,7 @@ func TestMapsContainingMaybe(t *testing.T) {
ma.AssembleEntry("one").AssignString("1")
ma.AssembleEntry("none").AssignNull()
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
}
Expand Down Expand Up @@ -206,7 +206,7 @@ func TestMapsContainingMaps(t *testing.T) {
})
t.Run("repr-create", func(t *testing.T) {
nr := creation(t, nrp, "encoded")
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
}
Expand Down Expand Up @@ -276,7 +276,7 @@ func TestMapsWithComplexKeys(t *testing.T) {
ma.AssembleEntry("c:d").AssignString("2")
ma.AssembleEntry("e:f").AssignString("3")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
}
6 changes: 3 additions & 3 deletions schema/gen/go/testStructReprStringjoin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestStructReprStringjoin(t *testing.T) {
nr := fluent.MustBuild(nrp, func(na fluent.NodeAssembler) {
na.AssignString("valoo")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})

Expand Down Expand Up @@ -102,7 +102,7 @@ func TestStructReprStringjoin(t *testing.T) {
nr := fluent.MustBuild(nrp, func(na fluent.NodeAssembler) {
na.AssignString("v1:v2")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})

Expand Down Expand Up @@ -139,7 +139,7 @@ func TestStructReprStringjoin(t *testing.T) {
nr := fluent.MustBuild(nrp, func(na fluent.NodeAssembler) {
na.AssignString("v1-v2:v3-v4")
})
Wish(t, n, ShouldEqual, nr)
Wish(t, ipld.DeepEqual(n, nr), ShouldEqual, true)
})
})
})
Expand Down
Loading