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 }