github.com/MetalBlockchain/metalgo@v1.11.9/vms/rpcchainvm/vm_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package rpcchainvm
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"reflect"
    12  	"slices"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/stretchr/testify/require"
    17  	"go.uber.org/mock/gomock"
    18  
    19  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/block"
    20  	"github.com/MetalBlockchain/metalgo/utils/logging"
    21  	"github.com/MetalBlockchain/metalgo/vms/rpcchainvm/grpcutils"
    22  	"github.com/MetalBlockchain/metalgo/vms/rpcchainvm/runtime"
    23  	"github.com/MetalBlockchain/metalgo/vms/rpcchainvm/runtime/subprocess"
    24  
    25  	vmpb "github.com/MetalBlockchain/metalgo/proto/pb/vm"
    26  )
    27  
    28  const (
    29  	chainVMTestKey                                 = "chainVMTest"
    30  	stateSyncEnabledTestKey                        = "stateSyncEnabledTest"
    31  	getOngoingSyncStateSummaryTestKey              = "getOngoingSyncStateSummaryTest"
    32  	getLastStateSummaryTestKey                     = "getLastStateSummaryTest"
    33  	parseStateSummaryTestKey                       = "parseStateSummaryTest"
    34  	getStateSummaryTestKey                         = "getStateSummaryTest"
    35  	acceptStateSummaryTestKey                      = "acceptStateSummaryTest"
    36  	lastAcceptedBlockPostStateSummaryAcceptTestKey = "lastAcceptedBlockPostStateSummaryAcceptTest"
    37  	contextTestKey                                 = "contextTest"
    38  	batchedParseBlockCachingTestKey                = "batchedParseBlockCachingTest"
    39  )
    40  
    41  var TestServerPluginMap = map[string]func(*testing.T, bool) block.ChainVM{
    42  	stateSyncEnabledTestKey:                        stateSyncEnabledTestPlugin,
    43  	getOngoingSyncStateSummaryTestKey:              getOngoingSyncStateSummaryTestPlugin,
    44  	getLastStateSummaryTestKey:                     getLastStateSummaryTestPlugin,
    45  	parseStateSummaryTestKey:                       parseStateSummaryTestPlugin,
    46  	getStateSummaryTestKey:                         getStateSummaryTestPlugin,
    47  	acceptStateSummaryTestKey:                      acceptStateSummaryTestPlugin,
    48  	lastAcceptedBlockPostStateSummaryAcceptTestKey: lastAcceptedBlockPostStateSummaryAcceptTestPlugin,
    49  	contextTestKey:                                 contextEnabledTestPlugin,
    50  	batchedParseBlockCachingTestKey:                batchedParseBlockCachingTestPlugin,
    51  }
    52  
    53  // helperProcess helps with creating the subnet binary for testing.
    54  func helperProcess(s ...string) *exec.Cmd {
    55  	cs := []string{"-test.run=TestHelperProcess", "--"}
    56  	cs = append(cs, s...)
    57  	env := []string{
    58  		"TEST_PROCESS=1",
    59  	}
    60  	run := os.Args[0]
    61  	cmd := exec.Command(run, cs...)
    62  	env = append(env, os.Environ()...)
    63  	cmd.Env = env
    64  	return cmd
    65  }
    66  
    67  func TestHelperProcess(t *testing.T) {
    68  	if os.Getenv("TEST_PROCESS") != "1" {
    69  		return
    70  	}
    71  
    72  	args := os.Args
    73  	for len(args) > 0 {
    74  		if args[0] == "--" {
    75  			args = args[1:]
    76  			break
    77  		}
    78  		args = args[1:]
    79  	}
    80  
    81  	if len(args) == 0 {
    82  		fmt.Fprintln(os.Stderr, "failed to receive testKey")
    83  		os.Exit(2)
    84  	}
    85  
    86  	testKey := args[0]
    87  	if testKey == "dummy" {
    88  		// block till killed
    89  		select {}
    90  	}
    91  
    92  	mockedVM := TestServerPluginMap[testKey](t, true /*loadExpectations*/)
    93  	err := Serve(context.Background(), mockedVM)
    94  	if err != nil {
    95  		os.Exit(1)
    96  	}
    97  
    98  	os.Exit(0)
    99  }
   100  
   101  // TestVMServerInterface ensures that the RPCs methods defined by VMServer
   102  // interface are implemented.
   103  func TestVMServerInterface(t *testing.T) {
   104  	var wantMethods, gotMethods []string
   105  	pb := reflect.TypeOf((*vmpb.VMServer)(nil)).Elem()
   106  	for i := 0; i < pb.NumMethod()-1; i++ {
   107  		wantMethods = append(wantMethods, pb.Method(i).Name)
   108  	}
   109  	slices.Sort(wantMethods)
   110  
   111  	impl := reflect.TypeOf(&VMServer{})
   112  	for i := 0; i < impl.NumMethod(); i++ {
   113  		gotMethods = append(gotMethods, impl.Method(i).Name)
   114  	}
   115  	slices.Sort(gotMethods)
   116  
   117  	require.Equal(t, wantMethods, gotMethods)
   118  }
   119  
   120  func TestRuntimeSubprocessBootstrap(t *testing.T) {
   121  	tests := []struct {
   122  		name      string
   123  		config    *subprocess.Config
   124  		assertErr func(require *require.Assertions, err error)
   125  		// if false vm initialize bootstrap will fail
   126  		serveVM bool
   127  	}{
   128  		{
   129  			name: "happy path",
   130  			config: &subprocess.Config{
   131  				Stderr:           logging.NoLog{},
   132  				Stdout:           logging.NoLog{},
   133  				Log:              logging.NoLog{},
   134  				HandshakeTimeout: runtime.DefaultHandshakeTimeout,
   135  			},
   136  			assertErr: func(require *require.Assertions, err error) {
   137  				require.NoError(err)
   138  			},
   139  			serveVM: true,
   140  		},
   141  		{
   142  			name: "invalid stderr",
   143  			config: &subprocess.Config{
   144  				Stdout:           logging.NoLog{},
   145  				Log:              logging.NoLog{},
   146  				HandshakeTimeout: runtime.DefaultHandshakeTimeout,
   147  			},
   148  			assertErr: func(require *require.Assertions, err error) {
   149  				require.ErrorIs(err, runtime.ErrInvalidConfig)
   150  			},
   151  			serveVM: true,
   152  		},
   153  		{
   154  			name: "handshake timeout",
   155  			config: &subprocess.Config{
   156  				Stderr:           logging.NoLog{},
   157  				Stdout:           logging.NoLog{},
   158  				Log:              logging.NoLog{},
   159  				HandshakeTimeout: time.Microsecond,
   160  			},
   161  			assertErr: func(require *require.Assertions, err error) {
   162  				require.ErrorIs(err, runtime.ErrHandshakeFailed)
   163  			},
   164  			serveVM: false,
   165  		},
   166  	}
   167  	for _, test := range tests {
   168  		t.Run(test.name, func(t *testing.T) {
   169  			require := require.New(t)
   170  
   171  			ctrl := gomock.NewController(t)
   172  			vm := block.NewMockChainVM(ctrl)
   173  
   174  			listener, err := grpcutils.NewListener()
   175  			require.NoError(err)
   176  
   177  			require.NoError(os.Setenv(runtime.EngineAddressKey, listener.Addr().String()))
   178  
   179  			ctx, cancel := context.WithCancel(context.Background())
   180  			defer cancel()
   181  
   182  			if test.serveVM {
   183  				go func() {
   184  					_ = Serve(ctx, vm)
   185  				}()
   186  			}
   187  
   188  			status, stopper, err := subprocess.Bootstrap(
   189  				context.Background(),
   190  				listener,
   191  				helperProcess("dummy"),
   192  				test.config,
   193  			)
   194  			if err == nil {
   195  				require.NotEmpty(status.Addr)
   196  				stopper.Stop(ctx)
   197  			}
   198  			test.assertErr(require, err)
   199  		})
   200  	}
   201  }