Skip to content

Commit

Permalink
test(gno.land): add unit tests for sdk/vm.vmHandler (#2459)
Browse files Browse the repository at this point in the history
```console
gnome$ go test -v ./sdk/vm -run TestVmHandler
=== RUN   TestVmHandlerQuery_Eval
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.Echo("hello")
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.PubString
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.ConstString
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.pvString
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.counter
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.GetCounter()
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.Inc()
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.pvEcho("hello")
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.1337
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.13.37
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.float64(1337)
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst.Foo()
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.myStruct
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.Inc
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.fn()("hi")
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.sl
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.sl[1]
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.println(1234)
1234
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.doesnotexist
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/doesnotexist.Foo
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.Panic()
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.panic("bar")
=== RUN   TestVmHandlerQuery_Eval/gno.land/r/hello.sl[6]
--- PASS: TestVmHandlerQuery_Eval (0.03s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Echo("hello") (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.PubString (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.ConstString (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.pvString (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.counter (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.GetCounter() (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Inc() (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.pvEcho("hello") (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.1337 (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.13.37 (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.float64(1337) (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStructInst.Foo() (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.myStruct (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Inc (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.fn()("hi") (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl[1] (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.println(1234) (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.doesnotexist (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/doesnotexist.Foo (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.Panic() (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.panic("bar") (0.00s)
    --- PASS: TestVmHandlerQuery_Eval/gno.land/r/hello.sl[6] (0.00s)
=== RUN   TestVmHandlerQuery_Funcs
=== RUN   TestVmHandlerQuery_Funcs/gno.land/r/hello
=== RUN   TestVmHandlerQuery_Funcs/gno.land/r/doesnotexist
=== RUN   TestVmHandlerQuery_Funcs/std
=== RUN   TestVmHandlerQuery_Funcs/strings
--- PASS: TestVmHandlerQuery_Funcs (0.00s)
    --- PASS: TestVmHandlerQuery_Funcs/gno.land/r/hello (0.00s)
    --- PASS: TestVmHandlerQuery_Funcs/gno.land/r/doesnotexist (0.00s)
    --- PASS: TestVmHandlerQuery_Funcs/std (0.00s)
    --- PASS: TestVmHandlerQuery_Funcs/strings (0.00s)
=== RUN   TestVmHandlerQuery_File
=== RUN   TestVmHandlerQuery_File/gno.land/r/hello/hello.gno
=== RUN   TestVmHandlerQuery_File/gno.land/r/hello/README.md
=== RUN   TestVmHandlerQuery_File/gno.land/r/hello/doesnotexist.gno
=== RUN   TestVmHandlerQuery_File/gno.land/r/hello
=== RUN   TestVmHandlerQuery_File/gno.land/r/doesnotexist
=== RUN   TestVmHandlerQuery_File/gno.land/r/doesnotexist/hello.gno
--- PASS: TestVmHandlerQuery_File (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/hello.gno (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/README.md (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/hello/doesnotexist.gno (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/hello (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/doesnotexist (0.00s)
    --- PASS: TestVmHandlerQuery_File/gno.land/r/doesnotexist/hello.gno (0.00s)
PASS
ok  	github.com/gnolang/gno/gno.land/pkg/sdk/vm	(cached)
```

---------

Signed-off-by: moul <[email protected]>
  • Loading branch information
moul authored Oct 30, 2024
1 parent 494976d commit e9640ef
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 3 deletions.
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 @@ -23,6 +23,7 @@ type testEnv struct {
vmk *VMKeeper
bank bankm.BankKeeper
acck authm.AccountKeeper
vmh vmHandler
}

func setupTestEnv() testEnv {
Expand Down Expand Up @@ -62,6 +63,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 @@ -143,7 +143,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 @@ -183,5 +182,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
}
274 changes: 274 additions & 0 deletions gno.land/pkg/sdk/vm/handler_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package vm

import (
"fmt"
"testing"

"github.com/gnolang/gno/gnovm"
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 +53,272 @@ 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 := []*gnovm.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")
}
}()
res := vmHandler.Query(env.ctx, req)
if tc.expectedPanicMatch == "" {
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)
}
}
})
}
}

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.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 := []*gnovm.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\n\nfunc Hello() string { return \"hello\" }\n"},
{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.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 := []*gnovm.MemFile{
{"README.md", "# Hello"},
{"hello.gno", "package hello\n\nfunc Hello() string { return \"hello\" }\n"},
}
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)
}
})
}
}

0 comments on commit e9640ef

Please sign in to comment.