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.