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  }