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