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

test(basicnode): Increase test coverage for int and map types #454

Merged
merged 6 commits into from
Aug 25, 2022
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
35 changes: 35 additions & 0 deletions node/basicnode/int_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package basicnode_test

import (
qt "github.com/frankban/quicktest"
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode"
"testing"
)

func TestBasicInt(t *testing.T) {
m := basicnode.NewInt(3)
b := m.Prototype().NewBuilder()
b.AssignInt(4)
n := b.Build()
u := basicnode.NewUint(5)
qt.Check(t, must.Int(m), qt.Equals, int64(3))
qt.Check(t, must.Int(n), qt.Equals, int64(4))
qt.Check(t, must.Int(u), qt.Equals, int64(5))
}

func TestIntErrors(t *testing.T) {
x := basicnode.NewInt(3)

_, err := x.LookupByIndex(0)
errExpect := `func called on wrong kind: "LookupByIndex" called on a int node \(kind: int\), but only makes sense on list`
qt.Check(t, err, qt.ErrorMatches, errExpect)

_, err = x.LookupByString("n")
errExpect = `func called on wrong kind: "LookupByString" called on a int node \(kind: int\), but only makes sense on map`
qt.Check(t, err, qt.ErrorMatches, errExpect)

_, err = x.LookupByNode(x)
errExpect = `func called on wrong kind: "LookupByNode" called on a int node \(kind: int\), but only makes sense on map`
qt.Check(t, err, qt.ErrorMatches, errExpect)
}
279 changes: 279 additions & 0 deletions node/basicnode/map_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package basicnode_test

import (
"fmt"
"testing"

qt "github.com/frankban/quicktest"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/must"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/node/tests"
"github.com/ipld/go-ipld-prime/printer"
)

func TestMap(t *testing.T) {
Expand Down Expand Up @@ -46,3 +51,277 @@ func BenchmarkSpec_Unmarshal_Map3StrInt(b *testing.B) {
func BenchmarkSpec_Unmarshal_MapNStrMap3StrInt(b *testing.B) {
tests.BenchmarkSpec_Unmarshal_MapNStrMap3StrInt(b, basicnode.Prototype.Map)
}

// Test that the map builder cannot be assigned arbitrary values, and trying to
// will result in a sensible error
func TestMapAssignError(t *testing.T) {
b := basicnode.Prototype.Map.NewBuilder()
err := b.AssignBool(true)
errExpect := `func called on wrong kind: "AssignBool" called on a map node \(kind: map\), but only makes sense on bool`
qt.Check(t, err, qt.ErrorMatches, errExpect)

err = b.AssignInt(3)
errExpect = `func called on wrong kind: "AssignInt" called on a map node \(kind: map\), but only makes sense on int`
qt.Check(t, err, qt.ErrorMatches, errExpect)

err = b.AssignFloat(5.7)
errExpect = `func called on wrong kind: "AssignFloat" called on a map node \(kind: map\), but only makes sense on float`
qt.Check(t, err, qt.ErrorMatches, errExpect)

err = b.AssignString("hi")
errExpect = `func called on wrong kind: "AssignString" called on a map node \(kind: map\), but only makes sense on string`
qt.Check(t, err, qt.ErrorMatches, errExpect)

err = b.AssignNode(basicnode.NewInt(3))
errExpect = `func called on wrong kind: "AssignNode" called on a map node \(kind: int\), but only makes sense on map`
qt.Check(t, err, qt.ErrorMatches, errExpect)

// TODO(dustmop): BeginList, AssignNull, AssignBytes, AssignLink
}

// Test that the map builder can create map nodes, and AssignNode will copy
// such a node, and lookup methods on that node will work correctly
func TestMapBuilder(t *testing.T) {
b := basicnode.Prototype.Map.NewBuilder()

// construct a map of three keys, using the MapBuilder
ma, err := b.BeginMap(3)
if err != nil {
t.Fatal(err)
}
a := ma.AssembleKey()
a.AssignString("cat")
a = ma.AssembleValue()
a.AssignString("meow")
rvagg marked this conversation as resolved.
Show resolved Hide resolved

a, err = ma.AssembleEntry("dog")
if err != nil {
t.Fatal(err)
}
a.AssignString("bark")

a = ma.AssembleKey()
a.AssignString("eel")
a = ma.AssembleValue()
a.AssignString("zap")

err = ma.Finish()
if err != nil {
t.Fatal(err)
}

// test the builder's prototypes and its key and value prototypes, while we're here
np := b.Prototype()
qt.Check(t, fmt.Sprintf("%T", np), qt.Equals, "basicnode.Prototype__Map")
np = ma.KeyPrototype()
qt.Check(t, fmt.Sprintf("%T", np), qt.Equals, "basicnode.Prototype__String")
np = ma.ValuePrototype("")
qt.Check(t, fmt.Sprintf("%T", np), qt.Equals, "basicnode.Prototype__Any")

// compare the printed map
mapNode := b.Build()
actual := printer.Sprint(mapNode)

expect := `map{
string{"cat"}: string{"meow"}
string{"dog"}: string{"bark"}
string{"eel"}: string{"zap"}
}`
qt.Check(t, expect, qt.Equals, actual)

// copy the map using AssignNode
c := basicnode.Prototype.Map.NewBuilder()
err = c.AssignNode(mapNode)
if err != nil {
t.Fatal(err)
}
anotherNode := c.Build()

actual = printer.Sprint(anotherNode)
qt.Assert(t, expect, qt.Equals, actual)

// access values of map, using string
r, err := anotherNode.LookupByString("cat")
if err != nil {
t.Fatal(err)
}
qt.Check(t, "meow", qt.Equals, must.String(r))

// access values of map, using node
r, err = anotherNode.LookupByNode(basicnode.NewString("dog"))
if err != nil {
t.Fatal(err)
}
qt.Check(t, "bark", qt.Equals, must.String(r))

// access values of map, using PathSegment
r, err = anotherNode.LookupBySegment(datamodel.ParsePathSegment("eel"))
if err != nil {
t.Fatal(err)
}
qt.Check(t, "zap", qt.Equals, must.String(r))

// validate the node's prototype
np = anotherNode.Prototype()
qt.Check(t, fmt.Sprintf("%T", np), qt.Equals, "basicnode.Prototype__Map")
}

// test that AssignNode will fail if called twice, it expects an empty
// node to assign to
func TestMapCantAssignNodeTwice(t *testing.T) {
b := basicnode.Prototype.Map.NewBuilder()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for almost all of these (I pick just one instance at random to comment on), I wonder if we can immediately generalize them to TestTheTrait(t *testing.T, np datamodel.NodePrototype) ?

It would need:

  • the reusable test would need comments on it on what kind of NodePrototype it's expecting (in this case: a map)
  • another quick func of boilerplate for TestTheTrait(t *testing.T) { sharedtests.TestTheTrait(t, basicnode.Prototype.Map) }
  • ... that's about it?

Not sure if you saw it, but we have the go-ipld-prime//node/tests package for some of these already.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It's also very likely that this will run into issues being applied on more than one node implementation, especially in the area of error message string matching... but I'd like to be aspirational about that!)


// construct a map of three keys, using the MapBuilder
ma, err := b.BeginMap(3)
if err != nil {
t.Fatal(err)
}
a := ma.AssembleKey()
a.AssignString("cat")
a = ma.AssembleValue()
a.AssignString("meow")

a, err = ma.AssembleEntry("dog")
if err != nil {
t.Fatal(err)
}
a.AssignString("bark")

a = ma.AssembleKey()
a.AssignString("eel")
a = ma.AssembleValue()
a.AssignString("zap")

err = ma.Finish()
if err != nil {
t.Fatal(err)
}
mapNode := b.Build()

// copy the map using AssignNode, works the first time
c := basicnode.Prototype.Map.NewBuilder()
err = c.AssignNode(mapNode)
if err != nil {
t.Fatal(err)
}
qt.Assert(t,
func() {
_ = c.AssignNode(mapNode)
},
qt.PanicMatches,
// TODO(dustmop): Error message here should be better
`misuse`)
}

func TestMapLookupError(t *testing.T) {
b := basicnode.Prototype.Map.NewBuilder()

// construct a map of three keys, using the MapBuilder
ma, err := b.BeginMap(3)
if err != nil {
t.Fatal(err)
}
a := ma.AssembleKey()
a.AssignString("cat")
a = ma.AssembleValue()
a.AssignString("meow")

a, err = ma.AssembleEntry("dog")
if err != nil {
t.Fatal(err)
}
a.AssignString("bark")

a = ma.AssembleKey()
a.AssignString("eel")
a = ma.AssembleValue()
a.AssignString("zap")

err = ma.Finish()
if err != nil {
t.Fatal(err)
}

mapNode := b.Build()

_, err = mapNode.LookupByString("frog")
qt.Check(t, err, qt.ErrorMatches, `key not found: "frog"`)

_, err = mapNode.LookupByNode(basicnode.NewInt(3))
// TODO(dustmop): This error message is not great. It's about how the
rvagg marked this conversation as resolved.
Show resolved Hide resolved
// int node could not be converted when the real problem is that this
// method should not accept ints as parameters
qt.Check(t, err, qt.ErrorMatches, `func called on wrong kind: "AsString" called on a int node \(kind: int\), but only makes sense on string`)

_, err = mapNode.LookupByIndex(0)
qt.Check(t, err, qt.ErrorMatches, `func called on wrong kind: "LookupByIndex" called on a map node \(kind: map\), but only makes sense on list`)
}

func TestMapNewBuilderUsageError(t *testing.T) {
qt.Assert(t,
func() {
b := basicnode.Prototype.Map.NewBuilder()
_ = b.Build()
},
qt.PanicMatches,
`invalid state: assembler must be 'finished' before Build can be called!`)

// construct an empty map
b := basicnode.Prototype.Map.NewBuilder()
ma, err := b.BeginMap(0)
if err != nil {
t.Fatal(err)
}
err = ma.Finish()
if err != nil {
t.Fatal(err)
}
mapNode := b.Build()
actual := printer.Sprint(mapNode)

expect := `map{}`
qt.Check(t, expect, qt.Equals, actual)

// reset will return the state to 'initial', so Build will panic once again
b.Reset()
qt.Assert(t,
func() {
_ = b.Build()
},
qt.PanicMatches,
`invalid state: assembler must be 'finished' before Build can be called!`)

// assembling a key without a value will cause Finish to panic
b.Reset()
ma, err = b.BeginMap(0)
if err != nil {
t.Fatal(err)
}
a := ma.AssembleKey()
a.AssignString("cat")
qt.Assert(t,
func() {
_ = ma.Finish()
},
qt.PanicMatches,
// TODO(dustmop): Error message here should be better
`misuse`)
}

func TestMapDupKeyError(t *testing.T) {
b := basicnode.Prototype.Map.NewBuilder()

// construct a map with duplicate keys
ma, err := b.BeginMap(3)
if err != nil {
t.Fatal(err)
}
a := ma.AssembleKey()
a.AssignString("cat")
a = ma.AssembleValue()
a.AssignString("meow")
a = ma.AssembleKey()
err = a.AssignString("cat")

qt.Check(t, err, qt.ErrorMatches, `cannot repeat map key "cat"`)
}