github.com/ethereum/go-ethereum@v1.16.1/cmd/workload/testsuite.go (about)

     1  // Copyright 2020 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  	"embed"
    21  	"fmt"
    22  	"io/fs"
    23  	"os"
    24  
    25  	"github.com/ethereum/go-ethereum/core/history"
    26  	"github.com/ethereum/go-ethereum/internal/flags"
    27  	"github.com/ethereum/go-ethereum/internal/utesting"
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/params"
    30  	"github.com/ethereum/go-ethereum/rpc"
    31  	"github.com/urfave/cli/v2"
    32  )
    33  
    34  //go:embed queries
    35  var builtinTestFiles embed.FS
    36  
    37  var (
    38  	runTestCommand = &cli.Command{
    39  		Name:      "test",
    40  		Usage:     "Runs workload tests against an RPC endpoint",
    41  		ArgsUsage: "<RPC endpoint URL>",
    42  		Action:    runTestCmd,
    43  		Flags: []cli.Flag{
    44  			testPatternFlag,
    45  			testTAPFlag,
    46  			testSlowFlag,
    47  			testArchiveFlag,
    48  			testSepoliaFlag,
    49  			testMainnetFlag,
    50  			filterQueryFileFlag,
    51  			historyTestFileFlag,
    52  			traceTestFileFlag,
    53  			traceTestInvalidOutputFlag,
    54  		},
    55  	}
    56  	testPatternFlag = &cli.StringFlag{
    57  		Name:     "run",
    58  		Usage:    "Pattern of test suite(s) to run",
    59  		Category: flags.TestingCategory,
    60  	}
    61  	testTAPFlag = &cli.BoolFlag{
    62  		Name:     "tap",
    63  		Usage:    "Output test results in TAP format",
    64  		Category: flags.TestingCategory,
    65  	}
    66  	testSlowFlag = &cli.BoolFlag{
    67  		Name:     "slow",
    68  		Usage:    "Enable slow tests",
    69  		Value:    false,
    70  		Category: flags.TestingCategory,
    71  	}
    72  	testArchiveFlag = &cli.BoolFlag{
    73  		Name:     "archive",
    74  		Usage:    "Enable archive tests",
    75  		Value:    false,
    76  		Category: flags.TestingCategory,
    77  	}
    78  	testSepoliaFlag = &cli.BoolFlag{
    79  		Name:     "sepolia",
    80  		Usage:    "Use test cases for sepolia network",
    81  		Category: flags.TestingCategory,
    82  	}
    83  	testMainnetFlag = &cli.BoolFlag{
    84  		Name:     "mainnet",
    85  		Usage:    "Use test cases for mainnet network",
    86  		Category: flags.TestingCategory,
    87  	}
    88  )
    89  
    90  // testConfig holds the parameters for testing.
    91  type testConfig struct {
    92  	client            *client
    93  	fsys              fs.FS
    94  	filterQueryFile   string
    95  	historyTestFile   string
    96  	historyPruneBlock *uint64
    97  	traceTestFile     string
    98  }
    99  
   100  var errPrunedHistory = fmt.Errorf("attempt to access pruned history")
   101  
   102  // validateHistoryPruneErr checks whether the given error is caused by access
   103  // to history before the pruning threshold block (it is an rpc.Error with code 4444).
   104  // In this case, errPrunedHistory is returned.
   105  // If the error is a pruned history error that occurs when accessing a block past the
   106  // historyPrune block, an error is returned.
   107  // Otherwise, the original value of err is returned.
   108  func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint64) error {
   109  	if err != nil {
   110  		if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == 4444 {
   111  			if historyPruneBlock != nil && blockNum > *historyPruneBlock {
   112  				return fmt.Errorf("pruned history error returned after pruning threshold")
   113  			}
   114  			return errPrunedHistory
   115  		}
   116  	}
   117  	return err
   118  }
   119  
   120  func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
   121  	flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag)
   122  	if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
   123  		exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name)
   124  	}
   125  
   126  	// configure ethclient
   127  	cfg.client = makeClient(ctx)
   128  
   129  	// configure test files
   130  	switch {
   131  	case ctx.Bool(testMainnetFlag.Name):
   132  		cfg.fsys = builtinTestFiles
   133  		cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
   134  		cfg.historyTestFile = "queries/history_mainnet.json"
   135  		cfg.historyPruneBlock = new(uint64)
   136  		*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
   137  		cfg.traceTestFile = "queries/trace_mainnet.json"
   138  	case ctx.Bool(testSepoliaFlag.Name):
   139  		cfg.fsys = builtinTestFiles
   140  		cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
   141  		cfg.historyTestFile = "queries/history_sepolia.json"
   142  		cfg.historyPruneBlock = new(uint64)
   143  		*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
   144  		cfg.traceTestFile = "queries/trace_sepolia.json"
   145  	default:
   146  		cfg.fsys = os.DirFS(".")
   147  		cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
   148  		cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
   149  		cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
   150  	}
   151  	return cfg
   152  }
   153  
   154  // workloadTest represents a single test in the workload. It's a wrapper
   155  // of utesting.Test by adding a few additional attributes.
   156  type workloadTest struct {
   157  	utesting.Test
   158  
   159  	archive bool // Flag whether the archive node (full state history) is required for this test
   160  }
   161  
   162  func newWorkLoadTest(name string, fn func(t *utesting.T)) workloadTest {
   163  	return workloadTest{
   164  		Test: utesting.Test{
   165  			Name: name,
   166  			Fn:   fn,
   167  		},
   168  	}
   169  }
   170  
   171  func newSlowWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
   172  	t := newWorkLoadTest(name, fn)
   173  	t.Slow = true
   174  	return t
   175  }
   176  
   177  func newArchiveWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
   178  	t := newWorkLoadTest(name, fn)
   179  	t.archive = true
   180  	return t
   181  }
   182  
   183  func filterTests(tests []workloadTest, pattern string, filterFn func(t workloadTest) bool) []utesting.Test {
   184  	var utests []utesting.Test
   185  	for _, t := range tests {
   186  		if filterFn(t) {
   187  			utests = append(utests, t.Test)
   188  		}
   189  	}
   190  	if pattern == "" {
   191  		return utests
   192  	}
   193  	return utesting.MatchTests(utests, pattern)
   194  }
   195  
   196  func runTestCmd(ctx *cli.Context) error {
   197  	cfg := testConfigFromCLI(ctx)
   198  	filterSuite := newFilterTestSuite(cfg)
   199  	historySuite := newHistoryTestSuite(cfg)
   200  	traceSuite := newTraceTestSuite(cfg, ctx)
   201  
   202  	// Filter test cases.
   203  	tests := filterSuite.allTests()
   204  	tests = append(tests, historySuite.allTests()...)
   205  	tests = append(tests, traceSuite.allTests()...)
   206  
   207  	utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
   208  		if t.Slow && !ctx.Bool(testSlowFlag.Name) {
   209  			return false
   210  		}
   211  		if t.archive && !ctx.Bool(testArchiveFlag.Name) {
   212  			return false
   213  		}
   214  		return true
   215  	})
   216  
   217  	// Disable logging unless explicitly enabled.
   218  	if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
   219  		log.SetDefault(log.NewLogger(log.DiscardHandler()))
   220  	}
   221  
   222  	// Run the tests.
   223  	var run = utesting.RunTests
   224  	if ctx.Bool(testTAPFlag.Name) {
   225  		run = utesting.RunTAP
   226  	}
   227  	results := run(utests, os.Stdout)
   228  	if utesting.CountFailures(results) > 0 {
   229  		os.Exit(1)
   230  	}
   231  	return nil
   232  }