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

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