github.com/ethereum/go-ethereum@v1.16.1/cmd/evm/staterunner.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"bufio"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"regexp"
    25  	"slices"
    26  
    27  	"github.com/ethereum/go-ethereum/common"
    28  	"github.com/ethereum/go-ethereum/core/rawdb"
    29  	"github.com/ethereum/go-ethereum/core/vm"
    30  	"github.com/ethereum/go-ethereum/internal/flags"
    31  	"github.com/ethereum/go-ethereum/tests"
    32  	"github.com/urfave/cli/v2"
    33  )
    34  
    35  var (
    36  	forkFlag = &cli.StringFlag{
    37  		Name:     "statetest.fork",
    38  		Usage:    "Only run tests for the specified fork.",
    39  		Category: flags.VMCategory,
    40  	}
    41  	idxFlag = &cli.IntFlag{
    42  		Name:     "statetest.index",
    43  		Usage:    "The index of the subtest to run.",
    44  		Category: flags.VMCategory,
    45  		Value:    -1, // default to select all subtest indices
    46  	}
    47  )
    48  var stateTestCommand = &cli.Command{
    49  	Action:    stateTestCmd,
    50  	Name:      "statetest",
    51  	Usage:     "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
    52  	ArgsUsage: "<file>",
    53  	Flags: slices.Concat([]cli.Flag{
    54  		BenchFlag,
    55  		DumpFlag,
    56  		forkFlag,
    57  		HumanReadableFlag,
    58  		idxFlag,
    59  		RunFlag,
    60  	}, traceFlags),
    61  }
    62  
    63  func stateTestCmd(ctx *cli.Context) error {
    64  	path := ctx.Args().First()
    65  
    66  	// If path is provided, run the tests at that path.
    67  	if len(path) != 0 {
    68  		var (
    69  			collected = collectFiles(path)
    70  			results   []testResult
    71  		)
    72  		for _, fname := range collected {
    73  			r, err := runStateTest(ctx, fname)
    74  			if err != nil {
    75  				return err
    76  			}
    77  			results = append(results, r...)
    78  		}
    79  		report(ctx, results)
    80  		return nil
    81  	}
    82  	// Otherwise, read filenames from stdin and execute back-to-back.
    83  	scanner := bufio.NewScanner(os.Stdin)
    84  	for scanner.Scan() {
    85  		fname := scanner.Text()
    86  		if len(fname) == 0 {
    87  			return nil
    88  		}
    89  		results, err := runStateTest(ctx, fname)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		report(ctx, results)
    94  	}
    95  	return nil
    96  }
    97  
    98  // runStateTest loads the state-test given by fname, and executes the test.
    99  func runStateTest(ctx *cli.Context, fname string) ([]testResult, error) {
   100  	src, err := os.ReadFile(fname)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	var testsByName map[string]tests.StateTest
   105  	if err := json.Unmarshal(src, &testsByName); err != nil {
   106  		return nil, fmt.Errorf("unable to read test file %s: %w", fname, err)
   107  	}
   108  
   109  	cfg := vm.Config{Tracer: tracerFromFlags(ctx)}
   110  	re, err := regexp.Compile(ctx.String(RunFlag.Name))
   111  	if err != nil {
   112  		return nil, fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err)
   113  	}
   114  
   115  	// Iterate over all the tests, run them and aggregate the results
   116  	results := make([]testResult, 0, len(testsByName))
   117  	for key, test := range testsByName {
   118  		if !re.MatchString(key) {
   119  			continue
   120  		}
   121  		for i, st := range test.Subtests() {
   122  			if idx := ctx.Int(idxFlag.Name); idx != -1 && idx != i {
   123  				// If specific index requested, skip all tests that do not match.
   124  				continue
   125  			}
   126  			if fork := ctx.String(forkFlag.Name); fork != "" && st.Fork != fork {
   127  				// If specific fork requested, skip all tests that do not match.
   128  				continue
   129  			}
   130  			// Run the test and aggregate the result
   131  			result := &testResult{Name: key, Fork: st.Fork, Pass: true}
   132  			test.Run(st, cfg, false, rawdb.HashScheme, func(err error, state *tests.StateTestState) {
   133  				var root common.Hash
   134  				if state.StateDB != nil {
   135  					root = state.StateDB.IntermediateRoot(false)
   136  					result.Root = &root
   137  					fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
   138  					// Dump any state to aid debugging.
   139  					if ctx.Bool(DumpFlag.Name) {
   140  						result.State = dump(state.StateDB)
   141  					}
   142  				}
   143  				// Collect bench stats if requested.
   144  				if ctx.Bool(BenchFlag.Name) {
   145  					_, stats, _ := timedExec(true, func() ([]byte, uint64, error) {
   146  						_, _, gasUsed, _ := test.RunNoVerify(st, cfg, false, rawdb.HashScheme)
   147  						return nil, gasUsed, nil
   148  					})
   149  					result.Stats = &stats
   150  				}
   151  				if err != nil {
   152  					// Test failed, mark as so.
   153  					result.Pass, result.Error = false, err.Error()
   154  					return
   155  				}
   156  			})
   157  			results = append(results, *result)
   158  		}
   159  	}
   160  	return results, nil
   161  }