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

chore(gno.land): add unit tests for sdk/vm.vmHandler #2459

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion gno.land/pkg/sdk/vm/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type testEnv struct {
vmk *VMKeeper
bank bankm.BankKeeper
acck authm.AccountKeeper
vmh vmHandler
}

func setupTestEnv() testEnv {
Expand Down Expand Up @@ -60,6 +61,7 @@ func _setupTestEnv(cacheStdlibs bool) testEnv {
}
vmk.CommitGnoTransactionStore(stdlibCtx)
mcw.MultiWrite()
vmh := NewHandler(vmk)

return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck}
return testEnv{ctx: ctx, vmk: vmk, bank: bank, acck: acck, vmh: vmh}
}
3 changes: 1 addition & 2 deletions gno.land/pkg/sdk/vm/gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ func setupAddPkg(success bool) (sdk.Context, sdk.Tx, vmHandler) {
ctx := env.ctx
// conduct base gas meter tests from a non-genesis block since genesis block use infinite gas meter instead.
ctx = ctx.WithBlockHeader(&bft.Header{Height: int64(1)})
vmHandler := NewHandler(env.vmk)
// Create an account with 10M ugnot (10gnot)
addr := crypto.AddressFromPreimage([]byte("test1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
Expand Down Expand Up @@ -182,5 +181,5 @@ func Echo() UnknowType {
fee := std.NewFee(500000, std.MustParseCoin(ugnot.ValueString(1)))
tx := std.NewTx(msgs, fee, []std.Signature{}, "")

return ctx, tx, vmHandler
return ctx, tx, env.vmh
}
271 changes: 271 additions & 0 deletions gno.land/pkg/sdk/vm/handler_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package vm

import (
"fmt"
"testing"

abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -48,3 +52,270 @@ func Test_parseQueryEval_panic(t *testing.T) {
parseQueryEvalData("gno.land/r/demo/users")
})
}

func TestVmHandlerQuery_Eval(t *testing.T) {
tt := []struct {
input []byte
expectedResult string
expectedResultMatch string
expectedErrorMatch string
expectedPanicMatch string
// XXX: expectedEvents
}{
// valid queries
{input: []byte(`gno.land/r/hello.Echo("hello")`), expectedResult: `("echo:hello" string)`},
{input: []byte(`gno.land/r/hello.caller()`), expectedResult: `("" std.Address)`}, // FIXME?
{input: []byte(`gno.land/r/hello.GetHeight()`), expectedResult: `(0 int64)`},
// {input: []byte(`gno.land/r/hello.time.RFC3339`), expectedResult: `test`}, // not working, but should we care?
{input: []byte(`gno.land/r/hello.PubString`), expectedResult: `("public string" string)`},
{input: []byte(`gno.land/r/hello.ConstString`), expectedResult: `("const string" string)`},
{input: []byte(`gno.land/r/hello.pvString`), expectedResult: `("private string" string)`},
{input: []byte(`gno.land/r/hello.counter`), expectedResult: `(42 int)`},
{input: []byte(`gno.land/r/hello.GetCounter()`), expectedResult: `(42 int)`},
{input: []byte(`gno.land/r/hello.Inc()`), expectedResult: `(43 int)`},
{input: []byte(`gno.land/r/hello.pvEcho("hello")`), expectedResult: `("pvecho:hello" string)`},
{input: []byte(`gno.land/r/hello.1337`), expectedResult: `(1337 int)`},
{input: []byte(`gno.land/r/hello.13.37`), expectedResult: `(13.37 float64)`},
{input: []byte(`gno.land/r/hello.float64(1337)`), expectedResult: `(1337 float64)`},
{input: []byte(`gno.land/r/hello.myStructInst`), expectedResult: `(struct{(1000 int)} gno.land/r/hello.myStruct)`},
{input: []byte(`gno.land/r/hello.myStructInst.Foo()`), expectedResult: `("myStruct.Foo" string)`},
{input: []byte(`gno.land/r/hello.myStruct`), expectedResultMatch: `\(typeval{gno.land/r/hello.myStruct \(0x.*\)} type{}\)`},
{input: []byte(`gno.land/r/hello.Inc`), expectedResult: `(Inc func()( int))`},
{input: []byte(`gno.land/r/hello.fn()("hi")`), expectedResult: `("echo:hi" string)`},
{input: []byte(`gno.land/r/hello.sl`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value
{input: []byte(`gno.land/r/hello.sl[1]`), expectedResultMatch: `(slice[ref(.*)] []int)`}, // XXX: should return the actual value
{input: []byte(`gno.land/r/hello.println(1234)`), expectedResultMatch: `^$`}, // XXX: compare stdout?

// panics
{input: []byte(`gno.land/r/hello`), expectedPanicMatch: `expected <pkgpath>.<expression> syntax in query input data`},

// errors
{input: []byte(`gno.land/r/hello.doesnotexist`), expectedErrorMatch: `^/:0:0: name doesnotexist not declared:`}, // multiline error
{input: []byte(`gno.land/r/doesnotexist.Foo`), expectedErrorMatch: `^invalid package path$`},
{input: []byte(`gno.land/r/hello.Panic()`), expectedErrorMatch: `^foo$`},
{input: []byte(`gno.land/r/hello.sl[6]`), expectedErrorMatch: `^slice index out of bounds: 6 \(len=5\)$`},
}

for _, tc := range tt {
name := string(tc.input)
t.Run(name, func(t *testing.T) {
env := setupTestEnv()
ctx := env.vmk.MakeGnoTransactionStore(env.ctx)
vmHandler := env.vmh

// Give "addr1" some gnots.
addr := crypto.AddressFromPreimage([]byte("addr1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
env.acck.SetAccount(ctx, acc)
env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot"))
assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot")))

// Create test package.
files := []*std.MemFile{
{"hello.gno", `
package hello

import "std"
import "time"

var _ = time.RFC3339
func caller() std.Address { return std.GetOrigCaller() }
var GetHeight = std.GetHeight
var sl = []int{1,2,3,4,5}
func fn() func(string) string { return Echo }
type myStruct struct{a int}
var myStructInst = myStruct{a: 1000}
func (ms myStruct) Foo() string { return "myStruct.Foo" }
func Panic() { panic("foo") }
var counter int = 42
var pvString = "private string"
var PubString = "public string"
const ConstString = "const string"
func Echo(msg string) string { return "echo:"+msg }
func GetCounter() int { return counter }
func Inc() int { counter += 1; return counter }
func pvEcho(msg string) string { return "pvecho:"+msg }
`},
}
pkgPath := "gno.land/r/hello"
msg1 := NewMsgAddPackage(addr, pkgPath, files)
err := env.vmk.AddPackage(ctx, msg1)
assert.NoError(t, err)
env.vmk.CommitGnoTransactionStore(ctx)

req := abci.RequestQuery{
Path: "vm/qeval",
Data: tc.input,
}

defer func() {
if r := recover(); r != nil {
output := fmt.Sprintf("%v", r)
assert.Regexp(t, tc.expectedPanicMatch, output)
} else {
assert.Equal(t, "", tc.expectedPanicMatch, "should not panic")
Copy link
Contributor

Choose a reason for hiding this comment

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

here should be:

assert.Equal(t, tc.expectedPanicMatch, "", "should not panic")

right?

}
}()
res := vmHandler.Query(env.ctx, req)
if tc.expectedErrorMatch == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think here should be something like:

if tc.expectedPanicMatch == "" {
    if tc.expectedErrorMatch == "" {
        .......
    }
}

to ensure these two tests are isolated.

assert.True(t, res.IsOK(), "should not have error")
if tc.expectedResult != "" {
assert.Equal(t, string(res.Data), tc.expectedResult)
}
if tc.expectedResultMatch != "" {
assert.Regexp(t, tc.expectedResultMatch, string(res.Data))
}
} else {
assert.False(t, res.IsOK(), "should have an error")
errmsg := res.Error.Error()
assert.Regexp(t, tc.expectedErrorMatch, errmsg)
}
})
}
}

func TestVmHandlerQuery_Funcs(t *testing.T) {
tt := []struct {
input []byte
expectedResult string
expectedErrorMatch string
}{
// valid queries
{input: []byte(`gno.land/r/hello`), expectedResult: `[{"FuncName":"Panic","Params":null,"Results":null},{"FuncName":"Echo","Params":[{"Name":"msg","Type":"string","Value":""}],"Results":[{"Name":"_","Type":"string","Value":""}]},{"FuncName":"GetCounter","Params":null,"Results":[{"Name":"_","Type":"int","Value":""}]},{"FuncName":"Inc","Params":null,"Results":[{"Name":"_","Type":"int","Value":""}]}]`},
{input: []byte(`gno.land/r/doesnotexist`), expectedErrorMatch: `invalid package path`},
{input: []byte(`std`), expectedErrorMatch: `invalid package path`},
{input: []byte(`strings`), expectedErrorMatch: `invalid package path`},
}

for _, tc := range tt {
name := string(tc.input)
t.Run(name, func(t *testing.T) {
env := setupTestEnv()
ctx := env.ctx
vmHandler := env.vmh

// Give "addr1" some gnots.
addr := crypto.AddressFromPreimage([]byte("addr1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
env.acck.SetAccount(ctx, acc)
env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot"))
assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot")))

// Create test package.
files := []*std.MemFile{
{"hello.gno", `
package hello

var sl = []int{1,2,3,4,5}
func fn() func(string) string { return Echo }
type myStruct struct{a int}
var myStructInst = myStruct{a: 1000}
func (ms myStruct) Foo() string { return "myStruct.Foo" }
func Panic() { panic("foo") }
var counter int = 42
var pvString = "private string"
var PubString = "public string"
const ConstString = "const string"
func Echo(msg string) string { return "echo:"+msg }
func GetCounter() int { return counter }
func Inc() int { counter += 1; return counter }
func pvEcho(msg string) string { return "pvecho:"+msg }
`},
}
pkgPath := "gno.land/r/hello"
msg1 := NewMsgAddPackage(addr, pkgPath, files)
err := env.vmk.AddPackage(ctx, msg1)
assert.NoError(t, err)

req := abci.RequestQuery{
Path: "vm/qfuncs",
Data: tc.input,
}

res := vmHandler.Query(env.ctx, req)
if tc.expectedErrorMatch == "" {
assert.True(t, res.IsOK(), "should not have error")
if tc.expectedResult != "" {
assert.Equal(t, string(res.Data), tc.expectedResult)
}
} else {
assert.False(t, res.IsOK(), "should have an error")
errmsg := res.Error.Error()
assert.Regexp(t, tc.expectedErrorMatch, errmsg)
}
})
}
}

func TestVmHandlerQuery_File(t *testing.T) {
tt := []struct {
input []byte
expectedResult string
expectedResultMatch string
expectedErrorMatch string
expectedPanicMatch string
// XXX: expectedEvents
}{
// valid queries
{input: []byte(`gno.land/r/hello/hello.gno`), expectedResult: "package hello\nfunc Hello() string { return \"hello\" }"},
{input: []byte(`gno.land/r/hello/README.md`), expectedResult: "# Hello"},
{input: []byte(`gno.land/r/hello/doesnotexist.gno`), expectedErrorMatch: `file "gno.land/r/hello/doesnotexist.gno" is not available`},
{input: []byte(`gno.land/r/hello`), expectedResult: "README.md\nhello.gno"},
{input: []byte(`gno.land/r/doesnotexist`), expectedErrorMatch: `package "gno.land/r/doesnotexist" is not available`},
{input: []byte(`gno.land/r/doesnotexist/hello.gno`), expectedErrorMatch: `file "gno.land/r/doesnotexist/hello.gno" is not available`},
}

for _, tc := range tt {
name := string(tc.input)
t.Run(name, func(t *testing.T) {
env := setupTestEnv()
ctx := env.ctx
vmHandler := env.vmh

// Give "addr1" some gnots.
addr := crypto.AddressFromPreimage([]byte("addr1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
env.acck.SetAccount(ctx, acc)
env.bank.SetCoins(ctx, addr, std.MustParseCoins("10000000ugnot"))
assert.True(t, env.bank.GetCoins(ctx, addr).IsEqual(std.MustParseCoins("10000000ugnot")))

// Create test package.
files := []*std.MemFile{
{"README.md", "# Hello"},
{"hello.gno", "package hello\nfunc Hello() string { return \"hello\" }"},
}
pkgPath := "gno.land/r/hello"
msg1 := NewMsgAddPackage(addr, pkgPath, files)
err := env.vmk.AddPackage(ctx, msg1)
assert.NoError(t, err)

req := abci.RequestQuery{
Path: "vm/qfile",
Data: tc.input,
}

defer func() {
if r := recover(); r != nil {
output := fmt.Sprintf("%v", r)
assert.Regexp(t, tc.expectedPanicMatch, output)
} else {
assert.Equal(t, "", tc.expectedPanicMatch, "should not panic")
}
}()
res := vmHandler.Query(env.ctx, req)
if tc.expectedErrorMatch == "" {
assert.True(t, res.IsOK(), "should not have error")
if tc.expectedResult != "" {
assert.Equal(t, string(res.Data), tc.expectedResult)
}
if tc.expectedResultMatch != "" {
assert.Regexp(t, tc.expectedResultMatch, string(res.Data))
}
} else {
assert.False(t, res.IsOK(), "should have an error")
errmsg := res.Error.Error()
assert.Regexp(t, tc.expectedErrorMatch, errmsg)
}
})
}
}
Loading