github.com/vmware/govmomi@v0.51.0/toolbox/process/process_test.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package process
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"net/http/httptest"
    16  	"net/url"
    17  	"runtime"
    18  	"strconv"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/vmware/govmomi/toolbox/vix"
    23  )
    24  
    25  func checkGOOS(t *testing.T) {
    26  	switch runtime.GOOS {
    27  	case "linux", "darwin":
    28  	default:
    29  		t.Skipf("GOOS=%s", runtime.GOOS)
    30  	}
    31  }
    32  
    33  func TestProcessFunction(t *testing.T) {
    34  	checkGOOS(t)
    35  
    36  	m := NewManager()
    37  	var pids []int64
    38  
    39  	for i := 0; i <= 2; i++ {
    40  		r := &vix.StartProgramRequest{
    41  			ProgramPath: "test",
    42  			Arguments:   strconv.Itoa(i),
    43  		}
    44  
    45  		pid, _ := m.Start(r, NewFunc(func(_ context.Context, arg string) error {
    46  			rc, _ := strconv.Atoi(arg)
    47  			if rc == 0 {
    48  				return nil
    49  
    50  			}
    51  			return &Error{Err: errors.New("fail"), ExitCode: int32(rc)}
    52  		}))
    53  
    54  		if pid == 0 {
    55  			t.Fatalf("no pid")
    56  		}
    57  
    58  		pids = append(pids, pid)
    59  	}
    60  
    61  	m.wg.Wait()
    62  
    63  	_ = m.ListProcesses(pids)
    64  
    65  	for i, pid := range pids {
    66  		p := m.entries[pid]
    67  		if p.ExitCode != int32(i) {
    68  			t.Errorf("%d: %d != %d", pid, p.ExitCode, i)
    69  		}
    70  	}
    71  }
    72  
    73  func TestProcessCommand(t *testing.T) {
    74  	checkGOOS(t)
    75  
    76  	m := NewManager()
    77  	var pids []int64
    78  
    79  	for i := 0; i <= 2; i++ {
    80  		r := &vix.StartProgramRequest{
    81  			ProgramPath: shell,
    82  			Arguments:   fmt.Sprintf(`-c "exit %d"`, i),
    83  		}
    84  
    85  		pid, err := m.Start(r, New())
    86  		if err != nil {
    87  			t.Fatal(err)
    88  		}
    89  		pids = append(pids, pid)
    90  	}
    91  
    92  	m.wg.Wait()
    93  
    94  	_ = m.ListProcesses(nil)
    95  
    96  	for i, pid := range pids {
    97  		p := m.entries[pid]
    98  		if p.ExitCode != int32(i) {
    99  			t.Errorf("%d: %d != %d", pid, p.ExitCode, i)
   100  		}
   101  	}
   102  
   103  	r := &vix.StartProgramRequest{
   104  		ProgramPath: shell,
   105  	}
   106  
   107  	shell = "/enoent/enoent"
   108  	_, err := m.Start(r, New())
   109  	if err == nil {
   110  		t.Error("expected error")
   111  	}
   112  	shell = r.ProgramPath
   113  
   114  	r.ProgramPath = "/enoent/enoent"
   115  	_, err = m.Start(r, New())
   116  	if err == nil {
   117  		t.Error("expected error")
   118  	}
   119  }
   120  
   121  func TestProcessKill(t *testing.T) {
   122  	checkGOOS(t)
   123  
   124  	m := NewManager()
   125  	var pids []int64
   126  
   127  	procs := []struct {
   128  		r *vix.StartProgramRequest
   129  		p *Process
   130  	}{
   131  		{
   132  			&vix.StartProgramRequest{
   133  				ProgramPath: "test",
   134  				Arguments:   "none",
   135  			},
   136  			NewFunc(func(ctx context.Context, _ string) error {
   137  				select {
   138  				case <-ctx.Done():
   139  					return &Error{Err: ctx.Err(), ExitCode: 42}
   140  				case <-time.After(time.Minute):
   141  				}
   142  
   143  				return nil
   144  			}),
   145  		},
   146  		{
   147  			&vix.StartProgramRequest{
   148  				ProgramPath: shell,
   149  				Arguments:   fmt.Sprintf(`-c "while true; do sleep 1; done"`),
   150  			},
   151  			New(),
   152  		},
   153  	}
   154  
   155  	for _, test := range procs {
   156  		pid, err := m.Start(test.r, test.p)
   157  		if err != nil {
   158  			t.Fatal(err)
   159  		}
   160  
   161  		pids = append(pids, pid)
   162  	}
   163  
   164  	for {
   165  		b := m.ListProcesses(pids)
   166  		if bytes.Count(b, []byte("<proc>")) == len(pids) {
   167  			break
   168  		}
   169  
   170  		<-time.After(time.Millisecond * 100)
   171  	}
   172  
   173  	for _, pid := range pids {
   174  		if !m.Kill(pid) {
   175  			t.Errorf("kill %d", pid)
   176  		}
   177  	}
   178  
   179  	m.wg.Wait()
   180  
   181  	for _, pid := range pids {
   182  		p := m.entries[pid]
   183  
   184  		if p.ExitCode == 0 {
   185  			t.Errorf("%s: exit=%d", p.Name, p.ExitCode)
   186  		}
   187  	}
   188  
   189  	if m.Kill(-1) {
   190  		t.Error("kill -1")
   191  	}
   192  }
   193  
   194  func TestProcessRemove(t *testing.T) {
   195  	checkGOOS(t)
   196  
   197  	m := NewManager()
   198  
   199  	m.expire = time.Millisecond
   200  
   201  	r := &vix.StartProgramRequest{
   202  		ProgramPath: "test",
   203  	}
   204  
   205  	pid, _ := m.Start(r, NewFunc(func(_ context.Context, arg string) error {
   206  		return nil
   207  	}))
   208  
   209  	m.wg.Wait()
   210  
   211  	<-time.After(m.expire * 20)
   212  	// pid should be removed by now
   213  	b := m.ListProcesses([]int64{pid})
   214  	if len(b) != 0 {
   215  		t.Error("expected 0 processes")
   216  	}
   217  }
   218  
   219  func TestEscapeXML(t *testing.T) {
   220  	tests := []struct {
   221  		in  string
   222  		out string
   223  	}{
   224  		{`echo "foo bar" > /dev/null`, "echo %22foo bar%22 %3E /dev/null"},
   225  	}
   226  
   227  	for i, test := range tests {
   228  		e := EscapeXML.Replace(test.in)
   229  		if e != test.out {
   230  			t.Errorf("%d: %s != %s", i, e, test.out)
   231  		}
   232  	}
   233  }
   234  
   235  func TestProcessError(t *testing.T) {
   236  	fault := errors.New("fail")
   237  	var err error = &Error{Err: fault}
   238  
   239  	if err.Error() != fault.Error() {
   240  		t.Fatal()
   241  	}
   242  }
   243  
   244  func TestProcessIO(t *testing.T) {
   245  	checkGOOS(t)
   246  
   247  	m := NewManager()
   248  
   249  	r := &vix.StartProgramRequest{
   250  		ProgramPath: "/bin/date",
   251  	}
   252  
   253  	p := New().WithIO()
   254  
   255  	_, err := m.Start(r, p)
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  
   260  	m.wg.Wait()
   261  
   262  	var buf bytes.Buffer
   263  
   264  	_, _ = io.Copy(&buf, p.IO.Out)
   265  
   266  	if buf.Len() == 0 {
   267  		t.Error("no data")
   268  	}
   269  }
   270  
   271  type testRoundTripper struct {
   272  	*Process
   273  }
   274  
   275  func (c *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   276  	req.Header.Set("Connection", "close") // we need the server to close the connection after 1 request
   277  
   278  	err := req.Write(c.IO.In.Writer)
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	_ = c.IO.In.Close()
   284  
   285  	<-c.ctx.Done()
   286  
   287  	return http.ReadResponse(bufio.NewReader(c.IO.Out), req)
   288  }
   289  
   290  func TestProcessRoundTripper(t *testing.T) {
   291  	checkGOOS(t)
   292  
   293  	echo := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   294  		_ = r.Write(w)
   295  	}))
   296  
   297  	u, _ := url.Parse(echo.URL)
   298  
   299  	m := NewManager()
   300  
   301  	r := &vix.StartProgramRequest{
   302  		ProgramPath: "http.RoundTrip",
   303  		Arguments:   u.Host,
   304  	}
   305  
   306  	p := NewRoundTrip()
   307  
   308  	_, err := m.Start(r, p)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  
   313  	res, err := (&http.Client{Transport: &testRoundTripper{p}}).Get(echo.URL)
   314  	if err != nil {
   315  		t.Logf("Err: %s", p.IO.Err.String())
   316  		t.Fatal(err)
   317  	}
   318  
   319  	if res.ContentLength == 0 {
   320  		t.Errorf("len=%d", res.ContentLength)
   321  	}
   322  }