From 2dfae90ccbae4ce275da48faaab187a3e7c465af Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Thu, 2 Oct 2025 14:06:49 +0200 Subject: [PATCH 1/3] cmd/evm: add stdin support to blocktest command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable blocktest to read filenames from stdin when no path argument is provided, matching the existing statetest behavior. This allows efficient batch processing of blockchain tests. Usage: - Single file: evm blocktest - Batch mode: find tests/ -name "*.json" | evm blocktest 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- cmd/evm/blockrunner.go | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index f6538b13567..ed5ef0fa673 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -17,8 +17,8 @@ package main import ( + "bufio" "encoding/json" - "errors" "fmt" "maps" "os" @@ -34,7 +34,7 @@ import ( var blockTestCommand = &cli.Command{ Action: blockTestCmd, Name: "blocktest", - Usage: "Executes the given blockchain tests", + Usage: "Executes the given blockchain tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).", ArgsUsage: "", Flags: slices.Concat([]cli.Flag{ DumpFlag, @@ -46,21 +46,36 @@ var blockTestCommand = &cli.Command{ func blockTestCmd(ctx *cli.Context) error { path := ctx.Args().First() - if len(path) == 0 { - return errors.New("path argument required") + + // If path is provided, run the tests at that path. + if len(path) != 0 { + var ( + collected = collectFiles(path) + results []testResult + ) + for _, fname := range collected { + r, err := runBlockTest(ctx, fname) + if err != nil { + return err + } + results = append(results, r...) + } + report(ctx, results) + return nil } - var ( - collected = collectFiles(path) - results []testResult - ) - for _, fname := range collected { - r, err := runBlockTest(ctx, fname) + // Otherwise, read filenames from stdin and execute back-to-back. + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + fname := scanner.Text() + if len(fname) == 0 { + return nil + } + results, err := runBlockTest(ctx, fname) if err != nil { return err } - results = append(results, r...) + report(ctx, results) } - report(ctx, results) return nil } From 94b4de031950a718d1702b4a38f5028788fa93c1 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 6 Oct 2025 09:59:38 +0200 Subject: [PATCH 2/3] Add end marker --- cmd/evm/blockrunner.go | 58 +++++++++++++++++++++++++++++++++++++++- tests/block_test_util.go | 11 +++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index ed5ef0fa673..c4c0eebf3eb 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -25,8 +25,10 @@ import ( "regexp" "slices" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" "github.com/urfave/cli/v2" ) @@ -79,6 +81,42 @@ func blockTestCmd(ctx *cli.Context) error { return nil } +// blocktestEndMarker represents the final status of a blocktest execution. +// It is written as the last line of trace output in JSONL format (single-line JSON). +type blocktestEndMarker struct { + TestEnd blocktestEndDetails `json:"testEnd"` +} + +type blocktestEndDetails struct { + Name string `json:"name"` + Pass bool `json:"pass"` + Fork string `json:"fork,omitempty"` + Root string `json:"root,omitempty"` + Error string `json:"error,omitempty"` + V int `json:"v"` // Version: 1 +} + +// writeEndMarker writes the blocktest end marker to stderr in JSONL format. +// This marker indicates the final outcome of the test as a single-line JSON object. +func writeEndMarker(result *testResult, fork string, root *common.Hash) { + details := blocktestEndDetails{ + Name: result.Name, + Pass: result.Pass, + Fork: fork, + V: 1, + } + if !result.Pass && result.Error != "" { + details.Error = result.Error + } + if root != nil { + details.Root = root.Hex() + } + marker := blocktestEndMarker{TestEnd: details} + if data, err := json.Marshal(marker); err == nil { + fmt.Fprintf(os.Stderr, "%s\n", data) + } +} + func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { src, err := os.ReadFile(fname) if err != nil { @@ -94,6 +132,11 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { } tracer := tracerFromFlags(ctx) + // Suppress INFO logs when tracing to avoid polluting stderr + if tracer != nil { + log.SetDefault(log.NewLogger(log.DiscardHandler())) + } + // Pull out keys to sort and ensure tests are run in order. keys := slices.Sorted(maps.Keys(tests)) @@ -103,17 +146,30 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { if !re.MatchString(name) { continue } + test := tests[name] result := &testResult{Name: name, Pass: true} - if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) { + var finalRoot *common.Hash + if err := test.Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) { if ctx.Bool(DumpFlag.Name) { if s, _ := chain.State(); s != nil { result.State = dump(s) } } + // Capture final state root for end marker + if chain != nil { + root := chain.CurrentBlock().Root + finalRoot = &root + } }); err != nil { result.Pass, result.Error = false, err.Error() } results = append(results, *result) + + // Write end marker when tracing is enabled + if tracer != nil { + fork := test.Network() + writeEndMarker(result, fork, finalRoot) + } } return results, nil } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 3b88753b1c8..e32e8a432fb 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -116,6 +116,15 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t if !ok { return UnsupportedForkError{t.json.Network} } + return t.run(config, snapshotter, scheme, witness, tracer, postCheck) +} + +// Network returns the network/fork name for this test. +func (t *BlockTest) Network() string { + return t.json.Network +} + +func (t *BlockTest) run(config *params.ChainConfig, snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { // import pre accounts & construct test genesis block & state root var ( db = rawdb.NewMemoryDatabase() @@ -259,7 +268,7 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) } if b.BlockHeader == nil { if data, err := json.MarshalIndent(cb.Header(), "", " "); err == nil { - fmt.Fprintf(os.Stderr, "block (index %d) insertion should have failed due to: %v:\n%v\n", + fmt.Fprintf(os.Stdout, "block (index %d) insertion should have failed due to: %v:\n%v\n", bi, b.ExpectException, string(data)) } return nil, fmt.Errorf("block (index %d) insertion should have failed due to: %v", From 838ef5c04162504312feb6916894ce46e866ab85 Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Mon, 6 Oct 2025 13:08:48 +0200 Subject: [PATCH 3/3] cmd/evm: remove superflous result struct --- cmd/evm/blockrunner.go | 45 +++++------------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index c4c0eebf3eb..b2c64d6d839 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -81,42 +81,6 @@ func blockTestCmd(ctx *cli.Context) error { return nil } -// blocktestEndMarker represents the final status of a blocktest execution. -// It is written as the last line of trace output in JSONL format (single-line JSON). -type blocktestEndMarker struct { - TestEnd blocktestEndDetails `json:"testEnd"` -} - -type blocktestEndDetails struct { - Name string `json:"name"` - Pass bool `json:"pass"` - Fork string `json:"fork,omitempty"` - Root string `json:"root,omitempty"` - Error string `json:"error,omitempty"` - V int `json:"v"` // Version: 1 -} - -// writeEndMarker writes the blocktest end marker to stderr in JSONL format. -// This marker indicates the final outcome of the test as a single-line JSON object. -func writeEndMarker(result *testResult, fork string, root *common.Hash) { - details := blocktestEndDetails{ - Name: result.Name, - Pass: result.Pass, - Fork: fork, - V: 1, - } - if !result.Pass && result.Error != "" { - details.Error = result.Error - } - if root != nil { - details.Root = root.Hex() - } - marker := blocktestEndMarker{TestEnd: details} - if data, err := json.Marshal(marker); err == nil { - fmt.Fprintf(os.Stderr, "%s\n", data) - } -} - func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { src, err := os.ReadFile(fname) if err != nil { @@ -163,13 +127,14 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { }); err != nil { result.Pass, result.Error = false, err.Error() } - results = append(results, *result) - // Write end marker when tracing is enabled + // Write result between transactions when tracing is enabled if tracer != nil { - fork := test.Network() - writeEndMarker(result, fork, finalRoot) + result.Fork = test.Network() + result.Root = finalRoot + report(ctx, []testResult{*result}) } + results = append(results, *result) } return results, nil }