github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/vm/proxyapp/proxyappclient_test.go (about) 1 // Copyright 2022 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package proxyapp 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "net/rpc" 12 "net/rpc/jsonrpc" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/google/syzkaller/pkg/report" 18 "github.com/google/syzkaller/vm/proxyapp/proxyrpc" 19 "github.com/google/syzkaller/vm/vmimpl" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/mock" 22 ) 23 24 var testEnv = &vmimpl.Env{ 25 Config: []byte(` 26 { 27 "cmd": "/path/to/proxyapp_binary", 28 "config": { 29 "internal_values": 123 30 } 31 } 32 `)} 33 34 func makeTestParams() *proxyAppParams { 35 return &proxyAppParams{ 36 CommandRunner: osutilCommandContext, 37 InitRetryDelay: 0, 38 LogOutput: io.Discard, 39 } 40 } 41 42 func makeMockProxyAppProcess(t *testing.T) ( 43 *mockProxyAppInterface, io.WriteCloser, io.ReadCloser, io.ReadCloser) { 44 rStdin, wStdin := io.Pipe() 45 rStdout, wStdout := io.Pipe() 46 rStderr, wStderr := io.Pipe() 47 wStderr.Close() 48 49 server := rpc.NewServer() 50 handler := makeMockProxyAppInterface(t) 51 server.RegisterName("ProxyVM", struct{ proxyrpc.ProxyAppInterface }{handler}) 52 53 go server.ServeCodec(jsonrpc.NewServerCodec(stdInOutCloser{ 54 rStdin, 55 wStdout, 56 })) 57 58 return handler, wStdin, rStdout, rStderr 59 } 60 61 type nopWriteCloser struct { 62 io.Writer 63 } 64 65 func (nopWriteCloser) Close() error { 66 return nil 67 } 68 69 func TestCtor_Ok(t *testing.T) { 70 _, mCmdRunner, params := proxyAppServerFixture(t) 71 p, err := ctor(params, testEnv) 72 73 assert.Nil(t, err) 74 assert.Equal(t, 2, p.Count()) 75 76 <-mCmdRunner.onWaitCalled 77 } 78 79 func TestCtor_ReadBadConfig(t *testing.T) { 80 pool, err := ctor(makeTestParams(), &vmimpl.Env{ 81 Config: []byte(`{"wrong_key": 1}`), 82 }) 83 assert.NotNil(t, err) 84 assert.Nil(t, pool) 85 } 86 87 func TestCtor_FailedPipes(t *testing.T) { 88 mCmdRunner, params := makeMockCommandRunner(t) 89 mCmdRunner. 90 On("StdinPipe"). 91 Return(nil, fmt.Errorf("stdinpipe error")). 92 Once(). 93 On("StdinPipe"). 94 Return(nopWriteCloser{&bytes.Buffer{}}, nil). 95 On("StdoutPipe"). 96 Return(nil, fmt.Errorf("stdoutpipe error")). 97 Once(). 98 On("StdoutPipe"). 99 Return(io.NopCloser(strings.NewReader("")), nil). 100 On("StderrPipe"). 101 Return(nil, fmt.Errorf("stderrpipe error")). 102 Once(). 103 On("StderrPipe"). 104 Return(io.NopCloser(strings.NewReader("")), nil) 105 106 for i := 0; i < 3; i++ { 107 p, err := ctor(params, testEnv) 108 assert.NotNil(t, err) 109 assert.Nil(t, p) 110 } 111 } 112 113 func TestClose_waitDone(t *testing.T) { 114 _, mCmdRunner, params := proxyAppServerFixture(t) 115 mCmdRunner. 116 On("waitDone"). 117 Return(nil) 118 119 p, _ := ctor(params, testEnv) 120 p.(io.Closer).Close() 121 } 122 123 func TestCtor_FailedStartProxyApp(t *testing.T) { 124 mCmdRunner, params := makeMockCommandRunner(t) 125 mCmdRunner. 126 On("StdinPipe"). 127 Return(nopWriteCloser{&bytes.Buffer{}}, nil). 128 On("StdoutPipe"). 129 Return(io.NopCloser(strings.NewReader("")), nil). 130 On("StderrPipe"). 131 Return(io.NopCloser(strings.NewReader("")), nil). 132 On("Start"). 133 Return(fmt.Errorf("failed to start program")) 134 135 p, err := ctor(params, testEnv) 136 assert.NotNil(t, err) 137 assert.Nil(t, p) 138 } 139 140 // TODO: reuse proxyAppServerFixture() code: func could be called here once Mock.Unset() error 141 // 142 // fixed https://github.com/stretchr/testify/issues/1236 143 // nolint: dupl 144 func TestCtor_FailedConstructPool(t *testing.T) { 145 mProxyAppServer, stdin, stdout, stderr := 146 makeMockProxyAppProcess(t) 147 148 mProxyAppServer. 149 On("CreatePool", mock.Anything, mock.Anything). 150 Return(fmt.Errorf("failed to construct pool")). 151 On("PoolLogs", mock.Anything, mock.Anything). 152 Return(nil). 153 Maybe() // on CreatePool error we close logger. This close makes PoolLogs racy. 154 155 mCmdRunner, params := makeMockCommandRunner(t) 156 mCmdRunner. 157 On("StdinPipe"). 158 Return(stdin, nil). 159 On("StdoutPipe"). 160 Return(stdout, nil). 161 On("StderrPipe"). 162 Return(stderr, nil). 163 On("Start"). 164 Return(nil). 165 On("Wait"). 166 Run(func(args mock.Arguments) { 167 <-mCmdRunner.ctx.Done() 168 }). 169 Return(nil) 170 171 p, err := ctor(params, testEnv) 172 assert.NotNil(t, err) 173 assert.Nil(t, p) 174 } 175 176 func initProxyAppServerFixture(mProxyAppServer *mockProxyAppInterface) *mockProxyAppInterface { 177 mProxyAppServer. 178 On("CreatePool", mock.Anything, mock.Anything). 179 Run(func(args mock.Arguments) { 180 out := args.Get(1).(*proxyrpc.CreatePoolResult) 181 out.Count = 2 182 }). 183 Return(nil). 184 Once(). 185 On("PoolLogs", mock.Anything, mock.Anything). 186 Run(func(args mock.Arguments) { 187 select { 188 case mProxyAppServer.OnLogsReceived <- true: 189 default: 190 } 191 }). 192 Return(nil). 193 // PoolLogs is optional as we can call .closeProxy any time. 194 // If PoolLogs call is expected we are checking for OnLogsReceived. 195 // TODO: refactor it once Mock.Unset() is available. 196 Maybe() 197 198 return mProxyAppServer 199 } 200 201 // TODO: to remove duplicate see TestCtor_FailedConstructPool() comment 202 // 203 // nolint: dupl 204 func proxyAppServerFixture(t *testing.T) (*mockProxyAppInterface, *mockCommandRunner, *proxyAppParams) { 205 mProxyAppServer, stdin, stdout, stderr := 206 makeMockProxyAppProcess(t) 207 initProxyAppServerFixture(mProxyAppServer) 208 209 mCmdRunner, params := makeMockCommandRunner(t) 210 mCmdRunner. 211 On("StdinPipe"). 212 Return(stdin, nil). 213 On("StdoutPipe"). 214 Return(stdout, nil). 215 On("StderrPipe"). 216 Return(stderr, nil). 217 On("Start"). 218 Return(nil). 219 On("Wait"). 220 Run(func(args mock.Arguments) { 221 <-mCmdRunner.ctx.Done() 222 mCmdRunner.MethodCalled("waitDone") 223 }). 224 Return(nil). 225 Maybe() 226 227 return mProxyAppServer, mCmdRunner, params 228 } 229 230 func poolFixture(t *testing.T) (*mockProxyAppInterface, *mockCommandRunner, vmimpl.Pool) { 231 mProxyAppServer, mCmdRunner, params := proxyAppServerFixture(t) 232 p, _ := ctor(params, testEnv) 233 return mProxyAppServer, mCmdRunner, p 234 } 235 236 func TestPool_Create_Ok(t *testing.T) { 237 mockServer, _, p := poolFixture(t) 238 mockServer. 239 On("CreateInstance", mock.Anything, mock.Anything). 240 Return(nil) 241 242 inst, err := p.Create(t.Context(), "workdir", 0) 243 assert.NotNil(t, inst) 244 assert.Nil(t, err) 245 } 246 247 func TestPool_Logs_Ok(t *testing.T) { 248 mockServer, _, _ := poolFixture(t) 249 <-mockServer.OnLogsReceived 250 } 251 252 func TestPool_Create_ProxyNilError(t *testing.T) { 253 _, mCmdRunner, p := poolFixture(t) 254 mCmdRunner. 255 On("waitDone"). 256 Return(nil) 257 258 p.(io.Closer).Close() 259 260 inst, err := p.Create(t.Context(), "workdir", 0) 261 assert.Nil(t, inst) 262 assert.NotNil(t, err) 263 } 264 265 func TestPool_Create_OutOfPoolError(t *testing.T) { 266 mockServer, _, p := poolFixture(t) 267 mockServer. 268 On("CreateInstance", mock.Anything, mock.Anything). 269 Run(func(args mock.Arguments) { 270 in := args.Get(0).(proxyrpc.CreateInstanceParams) 271 assert.Equal(t, p.Count(), in.Index) 272 }). 273 Return(fmt.Errorf("out of pool size")) 274 275 inst, err := p.Create(t.Context(), "workdir", p.Count()) 276 assert.Nil(t, inst) 277 assert.NotNil(t, err) 278 } 279 280 func TestPool_Create_ProxyFailure(t *testing.T) { 281 mockServer, _, p := poolFixture(t) 282 mockServer. 283 On("CreateInstance", mock.Anything, mock.Anything). 284 Return(fmt.Errorf("create instance failure")) 285 286 inst, err := p.Create(t.Context(), "workdir", 0) 287 assert.Nil(t, inst) 288 assert.NotNil(t, err) 289 } 290 291 // nolint: dupl 292 func createInstanceFixture(t *testing.T) (*mock.Mock, vmimpl.Instance) { 293 mockServer, _, p := poolFixture(t) 294 mockServer. 295 On("CreateInstance", mock.Anything, mock.Anything). 296 Run(func(args mock.Arguments) { 297 in := args.Get(0).(proxyrpc.CreateInstanceParams) 298 out := args.Get(1).(*proxyrpc.CreateInstanceResult) 299 out.ID = fmt.Sprintf("instance_id_%v", in.Index) 300 }). 301 Return(nil) 302 303 inst, err := p.Create(t.Context(), "workdir", 0) 304 assert.Nil(t, err) 305 assert.NotNil(t, inst) 306 307 return &mockServer.Mock, inst 308 } 309 310 func TestInstance_Close(t *testing.T) { 311 mockInstance, inst := createInstanceFixture(t) 312 mockInstance. 313 On("Close", mock.Anything, mock.Anything). 314 Return(fmt.Errorf("mock error")) 315 316 inst.Close() 317 } 318 319 func TestInstance_Diagnose_Ok(t *testing.T) { 320 mockInstance, inst := createInstanceFixture(t) 321 mockInstance. 322 On("Diagnose", mock.Anything, mock.Anything). 323 Run(func(args mock.Arguments) { 324 out := args.Get(1).(*proxyrpc.DiagnoseReply) 325 out.Diagnosis = "diagnostic result" 326 }). 327 Return(nil) 328 329 diagnosis, wait := inst.Diagnose(nil) 330 assert.NotNil(t, diagnosis) 331 assert.Equal(t, wait, false) 332 333 diagnosis, wait = inst.Diagnose(&report.Report{}) 334 assert.NotNil(t, diagnosis) 335 assert.Equal(t, wait, false) 336 } 337 338 func TestInstance_Diagnose_Failure(t *testing.T) { 339 mockInstance, inst := createInstanceFixture(t) 340 mockInstance. 341 On("Diagnose", mock.Anything, mock.Anything). 342 Return(fmt.Errorf("diagnose failed")) 343 344 diagnosis, wait := inst.Diagnose(&report.Report{}) 345 assert.Nil(t, diagnosis) 346 assert.Equal(t, wait, false) 347 } 348 349 func TestInstance_Copy_OK(t *testing.T) { 350 mockInstance, inst := createInstanceFixture(t) 351 mockInstance. 352 On("Copy", mock.Anything, mock.Anything). 353 Run(func(args mock.Arguments) { 354 out := args.Get(1).(*proxyrpc.CopyResult) 355 out.VMFileName = "remote_file_path" 356 }). 357 Return(nil) 358 359 remotePath, err := inst.Copy("host/path") 360 assert.Nil(t, err) 361 assert.NotEmpty(t, remotePath) 362 } 363 364 // nolint: dupl 365 func TestInstance_Copy_Failure(t *testing.T) { 366 mockInstance, inst := createInstanceFixture(t) 367 mockInstance. 368 On("Copy", mock.Anything, mock.Anything). 369 Return(fmt.Errorf("copy failure")) 370 371 remotePath, err := inst.Copy("host/path") 372 assert.NotNil(t, err) 373 assert.Empty(t, remotePath) 374 } 375 376 // nolint: dupl 377 func TestInstance_Forward_OK(t *testing.T) { 378 mockInstance, inst := createInstanceFixture(t) 379 mockInstance. 380 On("Forward", mock.Anything, mock.Anything). 381 Run(func(args mock.Arguments) { 382 in := args.Get(0).(proxyrpc.ForwardParams) 383 out := args.Get(1).(*proxyrpc.ForwardResult) 384 out.ManagerAddress = fmt.Sprintf("manager_address:%v", in.Port) 385 }). 386 Return(nil) 387 388 remoteAddressToUse, err := inst.Forward(12345) 389 assert.Nil(t, err) 390 assert.Equal(t, "manager_address:12345", remoteAddressToUse) 391 } 392 393 // nolint: dupl 394 func TestInstance_Forward_Failure(t *testing.T) { 395 mockInstance, inst := createInstanceFixture(t) 396 mockInstance. 397 On("Forward", mock.Anything, mock.Anything). 398 Return(fmt.Errorf("forward failure")) 399 400 remoteAddressToUse, err := inst.Forward(12345) 401 assert.NotNil(t, err) 402 assert.Empty(t, remoteAddressToUse) 403 } 404 405 func TestInstance_Run_Failure(t *testing.T) { 406 mockInstance, inst := createInstanceFixture(t) 407 mockInstance. 408 On("RunStart", mock.Anything, mock.Anything). 409 Return(fmt.Errorf("run start error")) 410 411 outc, errc, err := inst.Run(contextWithTimeout(t, 10*time.Second), "command") 412 assert.Nil(t, outc) 413 assert.Nil(t, errc) 414 assert.NotEmpty(t, err) 415 } 416 417 func TestInstance_Run_OnTimeout(t *testing.T) { 418 mockInstance, inst := createInstanceFixture(t) 419 mockInstance. 420 On("RunStart", mock.Anything, mock.Anything). 421 Return(nil). 422 On("RunReadProgress", mock.Anything, mock.Anything). 423 Return(nil).Maybe(). 424 On("RunStop", mock.Anything, mock.Anything). 425 Return(nil) 426 427 _, errc, _ := inst.Run(contextWithTimeout(t, time.Second), "command") 428 err := <-errc 429 430 assert.Equal(t, err, vmimpl.ErrTimeout) 431 } 432 433 func TestInstance_Run_OnStop(t *testing.T) { 434 mockInstance, inst := createInstanceFixture(t) 435 mockInstance. 436 On("RunStart", mock.Anything, mock.Anything). 437 Return(nil). 438 On("RunReadProgress", mock.Anything, mock.Anything). 439 Return(nil). 440 Maybe(). 441 On("RunStop", mock.Anything, mock.Anything). 442 Return(nil) 443 444 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 445 _, errc, _ := inst.Run(ctx, "command") 446 cancel() 447 err := <-errc 448 assert.Equal(t, err, vmimpl.ErrTimeout) 449 } 450 451 func TestInstance_RunReadProgress_OnErrorReceived(t *testing.T) { 452 mockInstance, inst := createInstanceFixture(t) 453 mockInstance. 454 On("RunStart", mock.Anything, mock.Anything). 455 Return(nil). 456 On("RunReadProgress", mock.Anything, mock.Anything). 457 Return(nil). 458 Times(100). 459 On("RunReadProgress", mock.Anything, mock.Anything). 460 Run(func(args mock.Arguments) { 461 out := args.Get(1).(*proxyrpc.RunReadProgressReply) 462 out.Error = "mock error" 463 }). 464 Return(nil). 465 Once() 466 467 outc, _, _ := inst.Run(contextWithTimeout(t, 10*time.Second), "command") 468 output := string(<-outc) 469 470 assert.Equal(t, "mock error\nSYZFAIL: proxy app plugin error\n", output) 471 } 472 473 // nolint: dupl 474 func TestInstance_RunReadProgress_OnFinished(t *testing.T) { 475 mockInstance, inst := createInstanceFixture(t) 476 mockInstance. 477 On("RunStart", mock.Anything, mock.Anything). 478 Return(nil). 479 On("RunReadProgress", mock.Anything, mock.Anything). 480 Return(nil).Times(100). 481 On("RunReadProgress", mock.Anything, mock.Anything). 482 Run(func(args mock.Arguments) { 483 out := args.Get(1).(*proxyrpc.RunReadProgressReply) 484 out.Finished = true 485 }). 486 Return(nil). 487 Once() 488 489 _, errc, _ := inst.Run(contextWithTimeout(t, 10*time.Second), "command") 490 err := <-errc 491 492 assert.Equal(t, err, nil) 493 } 494 495 func TestInstance_RunReadProgress_Failed(t *testing.T) { 496 mockInstance, inst := createInstanceFixture(t) 497 mockInstance. 498 On("RunStart", mock.Anything, mock.Anything). 499 Run(func(args mock.Arguments) { 500 out := args.Get(1).(*proxyrpc.RunStartReply) 501 out.RunID = "test_run_id" 502 }). 503 Return(nil). 504 On("RunReadProgress", mock.Anything, mock.Anything). 505 Return(fmt.Errorf("runreadprogresserror")). 506 Once() 507 508 outc, _, _ := inst.Run(contextWithTimeout(t, 10*time.Second), "command") 509 output := string(<-outc) 510 511 assert.Equal(t, 512 "error reading progress from instance_id_0:test_run_id: runreadprogresserror\nSYZFAIL: proxy app plugin error\n", 513 output, 514 ) 515 } 516 517 // TODO: test for periodical proxyapp subprocess crashes handling. 518 // [option] check pool size was changed 519 520 // TODO: test pool.Close() calls plugin API and return error. 521 522 func contextWithTimeout(t *testing.T, timeout time.Duration) context.Context { 523 ctx, cancel := context.WithTimeout(context.Background(), timeout) 524 t.Cleanup(cancel) 525 return ctx 526 }