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(gno.land): add unit tests for sdk/vm.vmHandler #2459

Merged
merged 18 commits into from
Oct 30, 2024
Merged
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")
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
}()
res := vmHandler.Query(env.ctx, req)
if tc.expectedErrorMatch == "" {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
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