diff --git a/gnovm/pkg/doctest/exec.go b/gnovm/pkg/doctest/exec.go index 3b0638d52ed..9005113a2be 100644 --- a/gnovm/pkg/doctest/exec.go +++ b/gnovm/pkg/doctest/exec.go @@ -62,7 +62,7 @@ func ExecuteCodeBlock(c codeBlock, stdlibDir string) (string, error) { } // Extract the actual language from the lang field - lang := strings.Split(c.lang, ",")[0] + lang := extractLanguage(c.lang) if lang != goLang && lang != gnoLang { return fmt.Sprintf("SKIPPED (Unsupported language: %s)", lang), nil @@ -76,39 +76,10 @@ func ExecuteCodeBlock(c codeBlock, stdlibDir string) (string, error) { // get the result from the cache if it exists if result, found := cache.get(hashKey); found { - res := strings.TrimSpace(result) - - if c.expectedOutput == "" && c.expectedError == "" { - return fmt.Sprintf("%s (cached)", res), nil - } - - res, err := compareResults(res, c.expectedOutput, c.expectedError) - if err != nil { - return "", err - } - - return fmt.Sprintf("%s (cached)", res), nil + return handleCachedResult(result, c) } - baseKey := store.NewStoreKey("baseKey") - iavlKey := store.NewStoreKey("iavlKey") - - db := memdb.NewMemDB() - - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, db) - ms.MountStoreWithDB(iavlKey, iavl.StoreConstructor, db) - ms.LoadLatestVersion() - - ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) - acck := authm.NewAccountKeeper(iavlKey, std.ProtoBaseAccount) - bank := bankm.NewBankKeeper(acck) - stdlibsDir := GetStdlibsDir() - vmk := vm.NewVMKeeper(baseKey, iavlKey, acck, bank, stdlibsDir, 100_000_000) - - mcw := ms.MultiCacheWrap() - vmk.Initialize(log.NewNoopLogger(), mcw, true) - mcw.MultiWrite() + ctx, acck, _, vmk, stdlibCtx := setupEnvironment() files := []*std.MemFile{ {Name: fmt.Sprintf("%d.%s", c.index, lang), Body: c.content}, @@ -120,15 +91,9 @@ func ExecuteCodeBlock(c codeBlock, stdlibDir string) (string, error) { msg2 := vm.NewMsgRun(addr, std.Coins{}, files) - res, err := vmk.Run(ctx, msg2) + res, err := vmk.Run(stdlibCtx, msg2) if c.options.PanicMessage != "" { - if err == nil { - return "", fmt.Errorf("expected panic with message: %s, but executed successfully", c.options.PanicMessage) - } - if !strings.Contains(err.Error(), c.options.PanicMessage) { - return "", fmt.Errorf("expected panic with message: %s, but got: %s", c.options.PanicMessage, err.Error()) - } - return fmt.Sprintf("panicked as expected: %v", err), nil + return handlePanicMessage(err, c.options.PanicMessage) } if err != nil { @@ -147,6 +112,65 @@ func ExecuteCodeBlock(c codeBlock, stdlibDir string) (string, error) { return compareResults(res, c.expectedOutput, c.expectedError) } +func extractLanguage(lang string) string { + return strings.Split(lang, ",")[0] +} + +func handleCachedResult(result string, c codeBlock) (string, error) { + res := strings.TrimSpace(result) + + if c.expectedOutput == "" && c.expectedError == "" { + return fmt.Sprintf("%s (cached)", res), nil + } + + res, err := compareResults(res, c.expectedOutput, c.expectedError) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s (cached)", res), nil +} + +func setupEnvironment() (sdk.Context, authm.AccountKeeper, bankm.BankKeeper, *vm.VMKeeper, sdk.Context) { + baseKey := store.NewStoreKey("baseKey") + iavlKey := store.NewStoreKey("iavlKey") + + db := memdb.NewMemDB() + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, db) + ms.MountStoreWithDB(iavlKey, iavl.StoreConstructor, db) + ms.LoadLatestVersion() + + ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) + acck := authm.NewAccountKeeper(iavlKey, std.ProtoBaseAccount) + bank := bankm.NewBankKeeper(acck) + stdlibsDir := GetStdlibsDir() + vmk := vm.NewVMKeeper(baseKey, iavlKey, acck, bank, 100_000_000) + + mcw := ms.MultiCacheWrap() + + vmk.Initialize(log.NewNoopLogger(), mcw) + + stdlibCtx := vmk.MakeGnoTransactionStore(ctx.WithMultiStore(mcw)) + vmk.LoadStdlib(stdlibCtx, stdlibsDir) + vmk.CommitGnoTransactionStore(stdlibCtx) + + mcw.MultiWrite() + + return ctx, acck, bank, vmk, stdlibCtx +} + +func handlePanicMessage(err error, panicMessage string) (string, error) { + if err == nil { + return "", fmt.Errorf("expected panic with message: %s, but executed successfully", panicMessage) + } + if !strings.Contains(err.Error(), panicMessage) { + return "", fmt.Errorf("expected panic with message: %s, but got: %s", panicMessage, err.Error()) + } + return fmt.Sprintf("panicked as expected: %v", err), nil +} + // compareResults compares the actual output of code execution with the expected output or error. func compareResults(actual, expectedOutput, expectedError string) (string, error) { actual = strings.TrimSpace(actual) diff --git a/gnovm/pkg/doctest/parser.go b/gnovm/pkg/doctest/parser.go index 8707dea5e4d..283ba80ebc3 100644 --- a/gnovm/pkg/doctest/parser.go +++ b/gnovm/pkg/doctest/parser.go @@ -15,6 +15,11 @@ import ( "github.com/yuin/goldmark/text" ) +var ( + outputRegex = regexp.MustCompile(`(?m)^// Output:$([\s\S]*?)(?:^(?://\s*$|// Error:|$))`) + errorRegex = regexp.MustCompile(`(?m)^// Error:$([\s\S]*?)(?:^(?://\s*$|// Output:|$))`) +) + // codeBlock represents a block of code extracted from the input text. type codeBlock struct { content string // The content of the code block. @@ -39,7 +44,10 @@ func GetCodeBlocks(body string) []codeBlock { mast.Walk(doc, func(n mast.Node, entering bool) (mast.WalkStatus, error) { if entering { if cb, ok := n.(*mast.FencedCodeBlock); ok { - codeBlock := createCodeBlock(cb, body, len(codeBlocks)) + codeBlock, err := createCodeBlock(cb, body, len(codeBlocks)) + if err != nil { + return mast.WalkStop, err + } codeBlock.name = generateCodeBlockName(codeBlock.content, codeBlock.expectedOutput) codeBlocks = append(codeBlocks, codeBlock) } @@ -51,7 +59,7 @@ func GetCodeBlocks(body string) []codeBlock { } // createCodeBlock creates a CodeBlock from a code block node. -func createCodeBlock(node *mast.FencedCodeBlock, body string, index int) codeBlock { +func createCodeBlock(node *mast.FencedCodeBlock, body string, index int) (codeBlock, error) { var buf bytes.Buffer lines := node.Lines() for i := 0; i < lines.Len(); i++ { @@ -73,7 +81,7 @@ func createCodeBlock(node *mast.FencedCodeBlock, body string, index int) codeBlo expectedOutput, expectedError, err := parseExpectedResults(content) if err != nil { - panic(err) + return codeBlock{}, err } return codeBlock{ @@ -85,15 +93,12 @@ func createCodeBlock(node *mast.FencedCodeBlock, body string, index int) codeBlo expectedOutput: expectedOutput, expectedError: expectedError, options: options, - } + }, nil } // parseExpectedResults scans the code block content for expecting outputs and errors, // which are typically indicated by special comments in the code. func parseExpectedResults(content string) (string, string, error) { - outputRegex := regexp.MustCompile(`(?m)^// Output:$([\s\S]*?)(?:^(?://\s*$|// Error:|$))`) - errorRegex := regexp.MustCompile(`(?m)^// Error:$([\s\S]*?)(?:^(?://\s*$|// Output:|$))`) - var outputs, errors []string outputMatches := outputRegex.FindAllStringSubmatch(content, -1) @@ -362,7 +367,8 @@ func parseExecutionOptions(language string, firstLine []byte) ExecutionOptions { if match[2] != nil { options.PanicMessage = string(match[2]) } - // TODO: add more options + case "ignore": + options.Ignore = true } } } diff --git a/go.mod b/go.mod index d890ab020a4..c2f58d02f54 100644 --- a/go.mod +++ b/go.mod @@ -65,6 +65,7 @@ require ( github.com/nxadm/tail v1.4.11 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.3 // indirect + github.com/yuin/goldmark v1.7.6 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/otel/trace v1.29.0 // indirect diff --git a/go.sum b/go.sum index 1af73112f4a..4c94efc0850 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.3 h1:fdk0a/y60GsS4NbEd13GSIP+d8OjtTkmluY32Dy1Z/A= github.com/yuin/goldmark v1.7.3/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.6 h1:cZgJxVh5mL5cu8KOnwxvFJy5TFB0BHUskZZyq7TYbDg= +github.com/yuin/goldmark v1.7.6/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=