From 8b7a45719b8b2d6f05e375a3ac7f37a7f40b642f Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:39:00 +0100 Subject: [PATCH] fix: tests Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnodev/cmd/gnodev/setup_loader.go | 28 +- contribs/gnodev/pkg/dev/node_state_test.go | 387 +++++++------- contribs/gnodev/pkg/dev/node_test.go | 497 +++++++++--------- .../pkg/packages/resolver_middleware.go | 13 + 4 files changed, 463 insertions(+), 462 deletions(-) diff --git a/contribs/gnodev/cmd/gnodev/setup_loader.go b/contribs/gnodev/cmd/gnodev/setup_loader.go index f278228d8b6..1116b30289c 100644 --- a/contribs/gnodev/cmd/gnodev/setup_loader.go +++ b/contribs/gnodev/cmd/gnodev/setup_loader.go @@ -86,9 +86,9 @@ func setupPackagesResolver(logger *slog.Logger, cfg *devCfg, dirs ...string) (pa packages.CacheMiddleware(func(pkg *packages.Package) bool { return pkg.Kind == packages.PackageKindRemote // Only cache remote package }), - packages.FilterPathMiddleware("stdlib", isStdPath), // Filter stdlib package from resolving - packages.PackageCheckerMiddleware(logger), // Pre-check syntax to avoid bothering the node reloading on invalid files - packages.LogMiddleware(logger), // Log request + packages.FilterStdlibs, // Filter stdlib package from resolving + packages.PackageCheckerMiddleware(logger), // Pre-check syntax to avoid bothering the node reloading on invalid files + packages.LogMiddleware(logger), // Log request ), paths } @@ -111,25 +111,3 @@ func guessPath(cfg *devCfg, dir string) (path string) { rname := reInvalidChar.ReplaceAllString(filepath.Base(dir), "-") return filepath.Join(cfg.chainDomain, "/r/dev/", rname) } - -func isStdPath(path string) bool { - if i := strings.IndexRune(path, '/'); i > 0 { - if j := strings.IndexRune(path[:i], '.'); j >= 0 { - return false - } - } - - return true -} - -// func guessPathFromRoots(dir string, roots ...string) (path string, ok bool) { -// for _, root := range roots { -// if !strings.HasPrefix(dir, root) { -// continue -// } - -// return strings.TrimPrefix(dir, root), true -// } - -// return "", false -// } diff --git a/contribs/gnodev/pkg/dev/node_state_test.go b/contribs/gnodev/pkg/dev/node_state_test.go index ab792b05740..cbde5a43d0b 100644 --- a/contribs/gnodev/pkg/dev/node_state_test.go +++ b/contribs/gnodev/pkg/dev/node_state_test.go @@ -1,193 +1,198 @@ package dev -// import ( -// emitter "github.com/gnolang/gno/contribs/gnodev/internal/mock" -// "github.com/gnolang/gno/contribs/gnodev/pkg/events" -// "github.com/gnolang/gno/gno.land/pkg/gnoland" -// "github.com/gnolang/gno/gno.land/pkg/sdk/vm" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// ) - -// const testCounterRealm = "gno.land/r/dev/counter" - -// func TestNodeMovePreviousTX(t *testing.T) { -// const callInc = 5 - -// node, emitter := testingCounterRealm(t, callInc) - -// t.Run("Prev TX", func(t *testing.T) { -// ctx := testingContext(t) -// err := node.MoveToPreviousTX(ctx) -// require.NoError(t, err) -// assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) - -// // Check for correct render update -// render, err := testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, "4") -// }) - -// t.Run("Next TX", func(t *testing.T) { -// ctx := testingContext(t) -// err := node.MoveToNextTX(ctx) -// require.NoError(t, err) -// assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) - -// // Check for correct render update -// render, err := testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, "5") -// }) - -// t.Run("Multi Move TX", func(t *testing.T) { -// ctx := testingContext(t) -// moves := []struct { -// Move int -// ExpectedResult string -// }{ -// {-2, "3"}, -// {2, "5"}, -// {-5, "0"}, -// {5, "5"}, -// {-100, "0"}, -// {100, "5"}, -// {0, "5"}, -// } - -// t.Logf("initial state %d", callInc) -// for _, tc := range moves { -// t.Logf("moving from `%d`", tc.Move) -// err := node.MoveBy(ctx, tc.Move) -// require.NoError(t, err) -// if tc.Move != 0 { -// assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) -// } - -// // Check for correct render update -// render, err := testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, tc.ExpectedResult) -// } -// }) -// } - -// func TestSaveCurrentState(t *testing.T) { -// ctx := testingContext(t) - -// node, emitter := testingCounterRealm(t, 2) - -// // Save current state -// err := node.SaveCurrentState(ctx) -// require.NoError(t, err) - -// // Send a new tx -// msg := vm.MsgCall{ -// PkgPath: testCounterRealm, -// Func: "Inc", -// Args: []string{"10"}, -// } - -// res, err := testingCallRealm(t, node, msg) -// require.NoError(t, err) -// require.NoError(t, res.CheckTx.Error) -// require.NoError(t, res.DeliverTx.Error) -// assert.Equal(t, events.EvtTxResult, emitter.NextEvent().Type()) - -// // Test render -// render, err := testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, "12") // 2 + 10 - -// // Reset state -// err = node.Reset(ctx) -// require.NoError(t, err) -// assert.Equal(t, events.EvtReset, emitter.NextEvent().Type()) - -// render, err = testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, "2") // Back to the original state -// } - -// func TestExportState(t *testing.T) { -// node, _ := testingCounterRealm(t, 3) - -// t.Run("export state", func(t *testing.T) { -// ctx := testingContext(t) -// state, err := node.ExportCurrentState(ctx) -// require.NoError(t, err) -// assert.Equal(t, 3, len(state)) -// }) - -// t.Run("export genesis doc", func(t *testing.T) { -// ctx := testingContext(t) -// doc, err := node.ExportStateAsGenesis(ctx) -// require.NoError(t, err) -// require.NotNil(t, doc.AppState) - -// state, ok := doc.AppState.(gnoland.GnoGenesisState) -// require.True(t, ok) -// assert.Equal(t, 3, len(state.Txs)) -// }) -// } - -// func testingCounterRealm(t *testing.T, inc int) (*Node, *emitter.ServerEmitter) { -// t.Helper() - -// const ( -// // foo package -// counterGnoMod = "module gno.land/r/dev/counter\n" -// counterFile = `package counter -// import "strconv" - -// var value int = 0 -// func Inc(v int) { value += v } // method to increment value -// func Render(_ string) string { return strconv.Itoa(value) } -// ` -// ) - -// // Generate package counter -// counterPkg := generateTestingPackage(t, -// "gno.mod", counterGnoMod, -// "foo.gno", counterFile) - -// // Call NewDevNode with no package should work -// node, emitter := newTestingDevNode(t, counterPkg) -// assert.Len(t, node.ListPkgs(), 1) - -// // Test rendering -// render, err := testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, "0") - -// // Increment the counter 10 times -// for i := 0; i < inc; i++ { -// t.Logf("call %d", i) -// // Craft `Inc` msg -// msg := vm.MsgCall{ -// PkgPath: testCounterRealm, -// Func: "Inc", -// Args: []string{"1"}, -// } - -// res, err := testingCallRealm(t, node, msg) -// require.NoError(t, err) -// require.NoError(t, res.CheckTx.Error) -// require.NoError(t, res.DeliverTx.Error) -// assert.Equal(t, events.EvtTxResult, emitter.NextEvent().Type()) -// } - -// render, err = testingRenderRealm(t, node, testCounterRealm) -// require.NoError(t, err) -// require.Equal(t, render, strconv.Itoa(inc)) - -// return node, emitter -// } - -// func testingContext(t *testing.T) context.Context { -// t.Helper() - -// ctx, cancel := context.WithTimeout(context.Background(), time.Second*7) -// t.Cleanup(cancel) -// return ctx -// } +import ( + "context" + "strconv" + "testing" + "time" + + mock "github.com/gnolang/gno/contribs/gnodev/internal/mock" + "github.com/gnolang/gno/contribs/gnodev/pkg/events" + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testCounterRealm = "gno.land/r/dev/counter" + +func TestNodeMovePreviousTX(t *testing.T) { + const callInc = 5 + + node, emitter := testingCounterRealm(t, callInc) + + t.Run("Prev TX", func(t *testing.T) { + ctx := testingContext(t) + err := node.MoveToPreviousTX(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "4") + }) + + t.Run("Next TX", func(t *testing.T) { + ctx := testingContext(t) + err := node.MoveToNextTX(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "5") + }) + + t.Run("Multi Move TX", func(t *testing.T) { + ctx := testingContext(t) + moves := []struct { + Move int + ExpectedResult string + }{ + {-2, "3"}, + {2, "5"}, + {-5, "0"}, + {5, "5"}, + {-100, "0"}, + {100, "5"}, + {0, "5"}, + } + + t.Logf("initial state %d", callInc) + for _, tc := range moves { + t.Logf("moving from `%d`", tc.Move) + err := node.MoveBy(ctx, tc.Move) + require.NoError(t, err) + if tc.Move != 0 { + assert.Equal(t, events.EvtReload, emitter.NextEvent().Type()) + } + + // Check for correct render update + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, tc.ExpectedResult) + } + }) +} + +func TestSaveCurrentState(t *testing.T) { + ctx := testingContext(t) + + node, emitter := testingCounterRealm(t, 2) + + // Save current state + err := node.SaveCurrentState(ctx) + require.NoError(t, err) + + // Send a new tx + msg := vm.MsgCall{ + PkgPath: testCounterRealm, + Func: "Inc", + Args: []string{"10"}, + } + + res, err := testingCallRealm(t, node, msg) + require.NoError(t, err) + require.NoError(t, res.CheckTx.Error) + require.NoError(t, res.DeliverTx.Error) + assert.Equal(t, events.EvtTxResult, emitter.NextEvent().Type()) + + // Test render + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "12") // 2 + 10 + + // Reset state + err = node.Reset(ctx) + require.NoError(t, err) + assert.Equal(t, events.EvtReset, emitter.NextEvent().Type()) + + render, err = testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "2") // Back to the original state +} + +func TestExportState(t *testing.T) { + node, _ := testingCounterRealm(t, 3) + + t.Run("export state", func(t *testing.T) { + ctx := testingContext(t) + state, err := node.ExportCurrentState(ctx) + require.NoError(t, err) + assert.Equal(t, 3, len(state)) + }) + + t.Run("export genesis doc", func(t *testing.T) { + ctx := testingContext(t) + doc, err := node.ExportStateAsGenesis(ctx) + require.NoError(t, err) + require.NotNil(t, doc.AppState) + + state, ok := doc.AppState.(gnoland.GnoGenesisState) + require.True(t, ok) + assert.Equal(t, 3, len(state.Txs)) + }) +} + +func testingCounterRealm(t *testing.T, inc int) (*Node, *mock.ServerEmitter) { + t.Helper() + + const ( + // foo package + counterPath = "gno.land/r/dev/counter" + counterFile = ` +package counter + +import "strconv" + +var value int = 0 +func Inc(v int) { value += v } // method to increment value +func Render(_ string) string { return strconv.Itoa(value) } +` + ) + + // Generate package counter + counterPkg := generateMemPackage(t, counterPath, "foo.gno", counterFile) + + // Call NewDevNode with no package should work + node, emitter := newTestingDevNode(t, &counterPkg) + assert.Len(t, node.ListPkgs(), 1) + + // Test rendering + render, err := testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, "0") + + // Increment the counter 10 times + for i := 0; i < inc; i++ { + t.Logf("call %d", i) + // Craft `Inc` msg + msg := vm.MsgCall{ + PkgPath: testCounterRealm, + Func: "Inc", + Args: []string{"1"}, + } + + res, err := testingCallRealm(t, node, msg) + require.NoError(t, err) + require.NoError(t, res.CheckTx.Error) + require.NoError(t, res.DeliverTx.Error) + assert.Equal(t, events.EvtTxResult, emitter.NextEvent().Type()) + } + + render, err = testingRenderRealm(t, node, testCounterRealm) + require.NoError(t, err) + require.Equal(t, render, strconv.Itoa(inc)) + + return node, emitter +} + +func testingContext(t *testing.T) context.Context { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*7) + t.Cleanup(cancel) + return ctx +} diff --git a/contribs/gnodev/pkg/dev/node_test.go b/contribs/gnodev/pkg/dev/node_test.go index 7f69a92386a..b141369bbe7 100644 --- a/contribs/gnodev/pkg/dev/node_test.go +++ b/contribs/gnodev/pkg/dev/node_test.go @@ -2,8 +2,10 @@ package dev import ( "context" + "encoding/json" "path/filepath" "testing" + "time" mock "github.com/gnolang/gno/contribs/gnodev/internal/mock" "github.com/gnolang/gno/contribs/gnodev/pkg/events" @@ -15,18 +17,15 @@ import ( "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" core_types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" - "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto/keys" + tm2events "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// XXX: We should probably use txtar to test this package. - -var nodeTestingAddress = crypto.MustAddressFromString("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - -// // TestNewNode_NoPackages tests the NewDevNode method with no package. +// TestNewNode_NoPackages tests the NewDevNode method with no package. func TestNewNode_NoPackages(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -44,7 +43,7 @@ func TestNewNode_NoPackages(t *testing.T) { require.NoError(t, node.Close()) } -// // TestNewNode_WithPackage tests the NewDevNode with a single package. +// TestNewNode_WithPackage tests the NewDevNode with a single package. func TestNewNode_WithLoader(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -165,244 +164,248 @@ func Render(_ string) string { return "bar" } assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) } -// func TestNodeReset(t *testing.T) { -// const ( -// // foo package -// foobarGnoMod = "module gno.land/r/dev/foo\n" -// fooFile = `package foo -// var str string = "foo" -// func UpdateStr(newStr string) { str = newStr } // method to update 'str' variable -// func Render(_ string) string { return str } -// ` -// ) - -// // Generate package foo -// foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile) - -// // Call NewDevNode with no package should work -// node, emitter := newTestingDevNode(t, foopkg) -// assert.Len(t, node.ListPkgs(), 1) - -// // Test rendering -// render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo") -// require.NoError(t, err) -// require.Equal(t, render, "foo") - -// // Call `UpdateStr` to update `str` value with "bar" -// msg := vm.MsgCall{ -// PkgPath: "gno.land/r/dev/foo", -// Func: "UpdateStr", -// Args: []string{"bar"}, -// Send: nil, -// } -// res, err := testingCallRealm(t, node, msg) -// require.NoError(t, err) -// require.NoError(t, res.CheckTx.Error) -// require.NoError(t, res.DeliverTx.Error) -// assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) - -// // Check for correct render update -// render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") -// require.NoError(t, err) -// require.Equal(t, render, "bar") - -// // Reset state -// err = node.Reset(context.Background()) -// require.NoError(t, err) -// assert.Equal(t, emitter.NextEvent().Type(), events.EvtReset) - -// // Test rendering should return initial `str` value -// render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") -// require.NoError(t, err) -// require.Equal(t, render, "foo") - -// assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) -// } - -// func TestTxTimestampRecover(t *testing.T) { -// const ( -// // foo package -// foobarGnoMod = "module gno.land/r/dev/foo\n" -// fooFile = `package foo -// import ( -// "strconv" -// "strings" -// "time" -// ) - -// var times = []time.Time{ -// time.Now(), // Evaluate at genesis -// } - -// func SpanTime() { -// times = append(times, time.Now()) -// } - -// func Render(_ string) string { -// var strs strings.Builder - -// strs.WriteRune('[') -// for i, t := range times { -// if i > 0 { -// strs.WriteRune(',') -// } -// strs.WriteString(strconv.Itoa(int(t.UnixNano()))) -// } -// strs.WriteRune(']') - -// return strs.String() -// } -// ` -// ) - -// // Add a hard deadline of 20 seconds to avoid potential deadlock and fail early -// ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) -// defer cancel() - -// parseJSONTimesList := func(t *testing.T, render string) []time.Time { -// t.Helper() - -// var times []time.Time -// var nanos []int64 - -// err := json.Unmarshal([]byte(render), &nanos) -// require.NoError(t, err) - -// for _, nano := range nanos { -// sec, nsec := nano/int64(time.Second), nano%int64(time.Second) -// times = append(times, time.Unix(sec, nsec)) -// } - -// return times -// } - -// // Generate package foo -// foopkg := generateTestingPackage(t, "gno.mod", foobarGnoMod, "foo.gno", fooFile) - -// // Call NewDevNode with no package should work -// cfg := createDefaultTestingNodeConfig(foopkg) - -// // XXX(gfanton): Setting this to `false` somehow makes the time block -// // drift from the time spanned by the VM. -// cfg.TMConfig.Consensus.SkipTimeoutCommit = false -// cfg.TMConfig.Consensus.TimeoutCommit = 500 * time.Millisecond -// cfg.TMConfig.Consensus.TimeoutPropose = 100 * time.Millisecond -// cfg.TMConfig.Consensus.CreateEmptyBlocks = true - -// node, emitter := newTestingDevNodeWithConfig(t, cfg) - -// // We need to make sure that blocks are separated by at least 1 second -// // (minimal time between blocks). We can ensure this by listening for -// // new blocks and comparing timestamps -// cc := make(chan types.EventNewBlock) -// node.Node.EventSwitch().AddListener("test-timestamp", func(evt tm2events.Event) { -// newBlock, ok := evt.(types.EventNewBlock) -// if !ok { -// return -// } - -// select { -// case cc <- newBlock: -// default: -// } -// }) - -// // wait for first block for reference -// var refHeight, refTimestamp int64 - -// select { -// case <-ctx.Done(): -// require.FailNow(t, ctx.Err().Error()) -// case res := <-cc: -// refTimestamp = res.Block.Time.Unix() -// refHeight = res.Block.Height -// } - -// // number of span to process -// const nevents = 3 - -// // Span multiple time -// for i := 0; i < nevents; i++ { -// t.Logf("waiting for a bock greater than height(%d) and unix(%d)", refHeight, refTimestamp) -// for { -// var block types.EventNewBlock -// select { -// case <-ctx.Done(): -// require.FailNow(t, ctx.Err().Error()) -// case block = <-cc: -// } - -// t.Logf("got a block height(%d) and unix(%d)", -// block.Block.Height, block.Block.Time.Unix()) - -// // Ensure we consume every block before tx block -// if refHeight >= block.Block.Height { -// continue -// } - -// // Ensure new block timestamp is before previous reference timestamp -// if newRefTimestamp := block.Block.Time.Unix(); newRefTimestamp > refTimestamp { -// refTimestamp = newRefTimestamp -// break // break the loop -// } -// } - -// t.Logf("found a valid block(%d)! continue", refHeight) - -// // Span a new time -// msg := vm.MsgCall{ -// PkgPath: "gno.land/r/dev/foo", -// Func: "SpanTime", -// } - -// res, err := testingCallRealm(t, node, msg) - -// require.NoError(t, err) -// require.NoError(t, res.CheckTx.Error) -// require.NoError(t, res.DeliverTx.Error) -// assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) - -// // Set the new height from the tx as reference -// refHeight = res.Height -// } - -// // Render JSON times list -// render, err := testingRenderRealm(t, node, "gno.land/r/dev/foo") -// require.NoError(t, err) - -// // Parse times list -// timesList1 := parseJSONTimesList(t, render) -// t.Logf("list of times: %+v", timesList1) - -// // Ensure times are correctly expending. -// for i, t2 := range timesList1 { -// if i == 0 { -// continue -// } - -// t1 := timesList1[i-1] -// require.Greater(t, t2.UnixNano(), t1.UnixNano()) -// } - -// // Reload the node -// err = node.Reload(context.Background()) -// require.NoError(t, err) -// assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload) - -// // Fetch time list again from render -// render, err = testingRenderRealm(t, node, "gno.land/r/dev/foo") -// require.NoError(t, err) - -// timesList2 := parseJSONTimesList(t, render) - -// // Times list should be identical from the orignal list -// require.Len(t, timesList2, len(timesList1)) -// for i := 0; i < len(timesList1); i++ { -// t1nsec, t2nsec := timesList1[i].UnixNano(), timesList2[i].UnixNano() -// assert.Equal(t, t1nsec, t2nsec, -// "comparing times1[%d](%d) == times2[%d](%d)", i, t1nsec, i, t2nsec) -// } -// } +func TestNodeReset(t *testing.T) { + const ( + // foo package + foobarPath = "gno.land/r/dev/foo" + fooFile = `package foo +var str string = "foo" +func UpdateStr(newStr string) { str = newStr } // method to update 'str' variable +func Render(_ string) string { return str } +` + ) + + // Generate package foo + foopkg := generateMemPackage(t, foobarPath, "foo.gno", fooFile) + + // Call NewDevNode with no package should work + node, emitter := newTestingDevNode(t, &foopkg) + assert.Len(t, node.ListPkgs(), 1) + + // Test rendering + render, err := testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + require.Equal(t, render, "foo") + + // Call `UpdateStr` to update `str` value with "bar" + msg := vm.MsgCall{ + PkgPath: foobarPath, + Func: "UpdateStr", + Args: []string{"bar"}, + Send: nil, + } + res, err := testingCallRealm(t, node, msg) + require.NoError(t, err) + require.NoError(t, res.CheckTx.Error) + require.NoError(t, res.DeliverTx.Error) + assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) + + // Check for correct render update + render, err = testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + require.Equal(t, render, "bar") + + // Reset state + err = node.Reset(context.Background()) + require.NoError(t, err) + assert.Equal(t, emitter.NextEvent().Type(), events.EvtReset) + + // Test rendering should return initial `str` value + render, err = testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + require.Equal(t, render, "foo") + + assert.Equal(t, mock.EvtNull, emitter.NextEvent().Type()) +} + +func TestTxTimestampRecover(t *testing.T) { + const ( + // foo package + foobarPath = "gno.land/r/dev/foo" + fooFile = ` +package foo + +import ( + "strconv" + "strings" + "time" +) + +var times = []time.Time{ + time.Now(), // Evaluate at genesis +} + +func SpanTime() { + times = append(times, time.Now()) +} + +func Render(_ string) string { + var strs strings.Builder + + strs.WriteRune('[') + for i, t := range times { + if i > 0 { + strs.WriteRune(',') + } + strs.WriteString(strconv.Itoa(int(t.UnixNano()))) + } + strs.WriteRune(']') + + return strs.String() +} +` + ) + + // Add a hard deadline of 20 seconds to avoid potential deadlock and fail early + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + // Generate package foo + foopkg := generateMemPackage(t, foobarPath, "foo.gno", fooFile) + + // XXX(gfanton): Setting this to `false` somehow makes the time block + // drift from the time spanned by the VM. + cfg := newTestingNodeConfig(&foopkg) + cfg.TMConfig.Consensus.SkipTimeoutCommit = false + cfg.TMConfig.Consensus.TimeoutCommit = 500 * time.Millisecond + cfg.TMConfig.Consensus.TimeoutPropose = 100 * time.Millisecond + cfg.TMConfig.Consensus.CreateEmptyBlocks = true + + node, emitter := newTestingDevNodeWithConfig(t, cfg, foopkg.Path) + + render, err := testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + require.NotEmpty(t, render) + + parseJSONTimesList := func(t *testing.T, render string) []time.Time { + t.Helper() + + var times []time.Time + var nanos []int64 + + err := json.Unmarshal([]byte(render), &nanos) + require.NoError(t, err) + + for _, nano := range nanos { + sec, nsec := nano/int64(time.Second), nano%int64(time.Second) + times = append(times, time.Unix(sec, nsec)) + } + + return times + } + + // We need to make sure that blocks are separated by at least 1 second + // (minimal time between blocks). We can ensure this by listening for + // new blocks and comparing timestamps + cc := make(chan types.EventNewBlock) + node.Node.EventSwitch().AddListener("test-timestamp", func(evt tm2events.Event) { + newBlock, ok := evt.(types.EventNewBlock) + if !ok { + return + } + + select { + case cc <- newBlock: + default: + } + }) + + // wait for first block for reference + var refHeight, refTimestamp int64 + + select { + case <-ctx.Done(): + require.FailNow(t, ctx.Err().Error()) + case res := <-cc: + refTimestamp = res.Block.Time.Unix() + refHeight = res.Block.Height + } + + // number of span to process + const nevents = 3 + + // Span multiple time + for i := 0; i < nevents; i++ { + t.Logf("waiting for a block greater than height(%d) and unix(%d)", refHeight, refTimestamp) + for { + var block types.EventNewBlock + select { + case <-ctx.Done(): + require.FailNow(t, ctx.Err().Error()) + case block = <-cc: + } + + t.Logf("got a block height(%d) and unix(%d)", + block.Block.Height, block.Block.Time.Unix()) + + // Ensure we consume every block before tx block + if refHeight >= block.Block.Height { + continue + } + + // Ensure new block timestamp is before previous reference timestamp + if newRefTimestamp := block.Block.Time.Unix(); newRefTimestamp > refTimestamp { + refTimestamp = newRefTimestamp + break // break the loop + } + } + + t.Logf("found a valid block(%d)! continue", refHeight) + + // Span a new time + msg := vm.MsgCall{ + PkgPath: foobarPath, + Func: "SpanTime", + } + + res, err := testingCallRealm(t, node, msg) + + require.NoError(t, err) + require.NoError(t, res.CheckTx.Error) + require.NoError(t, res.DeliverTx.Error) + assert.Equal(t, emitter.NextEvent().Type(), events.EvtTxResult) + + // Set the new height from the tx as reference + refHeight = res.Height + } + + // Render JSON times list + render, err = testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + + // Parse times list + timesList1 := parseJSONTimesList(t, render) + t.Logf("list of times: %+v", timesList1) + + // Ensure times are correctly expending. + for i, t2 := range timesList1 { + if i == 0 { + continue + } + + t1 := timesList1[i-1] + require.Greater(t, t2.UnixNano(), t1.UnixNano()) + } + + // Reload the node + err = node.Reload(context.Background()) + require.NoError(t, err) + assert.Equal(t, emitter.NextEvent().Type(), events.EvtReload) + + // Fetch time list again from render + render, err = testingRenderRealm(t, node, foobarPath) + require.NoError(t, err) + + timesList2 := parseJSONTimesList(t, render) + + // Times list should be identical from the original list + require.Len(t, timesList2, len(timesList1)) + for i := 0; i < len(timesList1); i++ { + t1nsec, t2nsec := timesList1[i].UnixNano(), timesList2[i].UnixNano() + assert.Equal(t, t1nsec, t2nsec, + "comparing times1[%d](%d) == times2[%d](%d)", i, t1nsec, i, t2nsec) + } +} func testingRenderRealm(t *testing.T, node *Node, rlmpath string) (string, error) { t.Helper() @@ -476,7 +479,9 @@ func generateMemPackage(t *testing.T, path string, pairNameFile ...string) gnovm func newTestingNodeConfig(pkgs ...*gnovm.MemPackage) *NodeConfig { var loader packages.BaseLoader - loader.Resolver = packages.NewMockResolver(pkgs...) + loader.Resolver = packages.MiddlewareResolver( + packages.NewMockResolver(pkgs...), + packages.FilterStdlibs) cfg := DefaultNodeConfig(gnoenv.RootDir(), "gno.land") cfg.Loader = &loader return cfg diff --git a/contribs/gnodev/pkg/packages/resolver_middleware.go b/contribs/gnodev/pkg/packages/resolver_middleware.go index 9e9e5346e43..c64f17b20b2 100644 --- a/contribs/gnodev/pkg/packages/resolver_middleware.go +++ b/contribs/gnodev/pkg/packages/resolver_middleware.go @@ -7,6 +7,7 @@ import ( "go/scanner" "go/token" "log/slog" + "strings" "time" ) @@ -117,6 +118,18 @@ func FilterPathMiddleware(name string, filter FilterPathHandler) MiddlewareHandl } } +var FilterStdlibs = FilterPathMiddleware("stdlibs", isStdPath) + +func isStdPath(path string) bool { + if i := strings.IndexRune(path, '/'); i > 0 { + if j := strings.IndexRune(path[:i], '.'); j >= 0 { + return false + } + } + + return true +} + // PackageCheckerMiddleware creates a middleware handler for post-processing syntax. func PackageCheckerMiddleware(logger *slog.Logger) MiddlewareHandler { return func(fset *token.FileSet, path string, next Resolver) (*Package, error) {