github.com/m3db/m3@v1.5.0/src/m3em/os/exec/process_monitor_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package exec
    22  
    23  import (
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path"
    28  	"path/filepath"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func newTempDir(t *testing.T) string {
    36  	dir, err := ioutil.TempDir("", "temp-dir-prefix-")
    37  	require.NoError(t, err)
    38  	return dir
    39  }
    40  
    41  // nolint: unparam
    42  func newTestScript(t *testing.T, dir string, scriptNum int, scriptContents []byte) string {
    43  	file, err := ioutil.TempFile(dir, fmt.Sprintf("testscript%d.sh", scriptNum))
    44  	require.NoError(t, err)
    45  	name := file.Name()
    46  	require.NoError(t, file.Chmod(0755))
    47  	numWritten, err := file.Write(scriptContents)
    48  	require.NoError(t, err)
    49  	require.Equal(t, len(scriptContents), numWritten)
    50  	require.NoError(t, file.Close())
    51  	return name
    52  }
    53  
    54  func TestSimpleExec(t *testing.T) {
    55  	tempDir := newTempDir(t)
    56  	defer os.RemoveAll(tempDir)
    57  
    58  	scriptNum := 0
    59  	scriptContents := []byte(`#!/usr/bin/env bash
    60  	echo -ne "testing random output"`)
    61  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
    62  	basePath := filepath.Base(testScript)
    63  	cmd := Cmd{
    64  		Path:      testScript,
    65  		Args:      []string{},
    66  		OutputDir: tempDir,
    67  	}
    68  
    69  	success := false
    70  	tl := NewProcessListener(
    71  		func() { success = true },
    72  		func(err error) { require.FailNow(t, "unexpected error: %s", err.Error()) },
    73  	)
    74  	pm, err := NewProcessMonitor(cmd, tl)
    75  	require.NoError(t, err)
    76  	pmStruct, ok := pm.(*processMonitor)
    77  	require.True(t, ok)
    78  	pmStruct.startFn = pmStruct.startSync
    79  	require.NoError(t, pm.Start())
    80  	require.NoError(t, pm.Err())
    81  	require.True(t, success)
    82  
    83  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
    84  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
    85  	require.NoError(t, err)
    86  	require.Equal(t, []byte("testing random output"), stdoutContents)
    87  
    88  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
    89  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
    90  	require.NoError(t, err)
    91  	require.Equal(t, []byte{}, stderrContents)
    92  }
    93  
    94  func TestEnvSet(t *testing.T) {
    95  	tempDir := newTempDir(t)
    96  	defer os.RemoveAll(tempDir)
    97  
    98  	scriptNum := 0
    99  	scriptContents := []byte(`#!/usr/bin/env bash
   100  if [ "$NEW_ENV_VAR" != "expected_arg" ]; then
   101  	echo "Invalid value for NEW_ENV_VAR: $NEW_ENV_VAR" >&2
   102  	exit 1
   103  fi
   104  echo -ne "testing random output"`)
   105  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
   106  	basePath := filepath.Base(testScript)
   107  	cmd := Cmd{
   108  		Path:      testScript,
   109  		Args:      []string{testScript},
   110  		OutputDir: tempDir,
   111  		Env:       map[string]string{"NEW_ENV_VAR": "expected_arg"},
   112  	}
   113  
   114  	success := false
   115  	tl := NewProcessListener(
   116  		func() { success = true },
   117  		func(err error) { require.FailNow(t, "unexpected error", err.Error()) },
   118  	)
   119  	pm, err := NewProcessMonitor(cmd, tl)
   120  	require.NoError(t, err)
   121  	pmStruct, ok := pm.(*processMonitor)
   122  	require.True(t, ok)
   123  	pmStruct.startFn = pmStruct.startSync
   124  	require.NoError(t, pm.Start())
   125  	require.NoError(t, pm.Err())
   126  	require.True(t, success)
   127  
   128  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
   129  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
   130  	require.NoError(t, err)
   131  	require.Equal(t, []byte{}, stderrContents, string(stderrContents))
   132  
   133  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
   134  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
   135  	require.NoError(t, err)
   136  	require.Equal(t, []byte("testing random output"), stdoutContents)
   137  }
   138  
   139  func TestArgPassing(t *testing.T) {
   140  	tempDir := newTempDir(t)
   141  	defer os.RemoveAll(tempDir)
   142  
   143  	scriptNum := 0
   144  	scriptContents := []byte(`#!/usr/bin/env bash
   145  if [ "$#" -ne 1 ]; then
   146  	echo "Invalid number of args" >&2
   147  	exit 1
   148  fi
   149  arg=$1
   150  if [ "$arg" != "expected_arg" ]; then
   151  	echo "Invalid value for arg: $arg" >&2
   152  	exit 1
   153  fi
   154  echo -ne "testing random output"`)
   155  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
   156  	basePath := filepath.Base(testScript)
   157  	cmd := Cmd{
   158  		Path:      testScript,
   159  		Args:      []string{testScript, "expected_arg"},
   160  		OutputDir: tempDir,
   161  	}
   162  
   163  	success := false
   164  	tl := NewProcessListener(
   165  		func() { success = true },
   166  		func(err error) { require.FailNow(t, "unexpected error", err.Error()) },
   167  	)
   168  	pm, err := NewProcessMonitor(cmd, tl)
   169  	require.NoError(t, err)
   170  	pmStruct, ok := pm.(*processMonitor)
   171  	require.True(t, ok)
   172  	pmStruct.startFn = pmStruct.startSync
   173  	require.NoError(t, pm.Start())
   174  	require.NoError(t, pm.Err())
   175  	require.True(t, success)
   176  
   177  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
   178  	require.Equal(t, expectedStderrFile, pm.StderrPath())
   179  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
   180  	require.NoError(t, err)
   181  	require.Equal(t, []byte{}, stderrContents, string(stderrContents))
   182  
   183  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
   184  	require.Equal(t, expectedStdoutFile, pm.StdoutPath())
   185  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
   186  	require.NoError(t, err)
   187  	require.Equal(t, []byte("testing random output"), stdoutContents)
   188  }
   189  
   190  func TestStderrOutput(t *testing.T) {
   191  	tempDir := newTempDir(t)
   192  	defer os.RemoveAll(tempDir)
   193  
   194  	scriptNum := 0
   195  	scriptContents := []byte(`#!/usr/bin/env bash
   196  	echo -ne "testing random output" >&2`)
   197  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
   198  	basePath := filepath.Base(testScript)
   199  	cmd := Cmd{
   200  		Path:      testScript,
   201  		Args:      []string{},
   202  		OutputDir: tempDir,
   203  	}
   204  
   205  	success := false
   206  	tl := NewProcessListener(
   207  		func() { success = true },
   208  		func(err error) { require.FailNow(t, "unexpected error: %v", err) },
   209  	)
   210  	pm, err := NewProcessMonitor(cmd, tl)
   211  	require.NoError(t, err)
   212  	pmStruct, ok := pm.(*processMonitor)
   213  	require.True(t, ok)
   214  	pmStruct.startFn = pmStruct.startSync
   215  	require.NoError(t, pm.Start())
   216  	require.NoError(t, pm.Err())
   217  	require.True(t, success)
   218  
   219  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
   220  	require.Equal(t, expectedStdoutFile, pm.StdoutPath())
   221  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
   222  	require.NoError(t, err)
   223  	require.Equal(t, []byte{}, stdoutContents)
   224  
   225  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
   226  	require.Equal(t, expectedStderrFile, pm.StderrPath())
   227  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
   228  	require.NoError(t, err)
   229  	require.Equal(t, []byte("testing random output"), stderrContents)
   230  }
   231  
   232  func TestFailingExec(t *testing.T) {
   233  	tempDir := newTempDir(t)
   234  	defer os.RemoveAll(tempDir)
   235  
   236  	scriptNum := 0
   237  	scriptContents := []byte(`#!/usr/bin/env bash
   238  	echo -ne "testing random output"
   239  	exit 1`)
   240  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
   241  	basePath := filepath.Base(testScript)
   242  	cmd := Cmd{
   243  		Path:      testScript,
   244  		Args:      []string{},
   245  		OutputDir: tempDir,
   246  	}
   247  
   248  	success := false
   249  	tl := NewProcessListener(
   250  		func() { require.FailNow(t, "unexpected successful execution") },
   251  		func(err error) { success = true },
   252  	)
   253  	pm, err := NewProcessMonitor(cmd, tl)
   254  	require.NoError(t, err)
   255  	pmStruct, ok := pm.(*processMonitor)
   256  	require.True(t, ok)
   257  	pmStruct.startFn = pmStruct.startSync
   258  	pm.Start()
   259  	require.Error(t, pm.Err())
   260  	require.True(t, success)
   261  
   262  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
   263  	require.Equal(t, expectedStdoutFile, pm.StdoutPath())
   264  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
   265  	require.NoError(t, err)
   266  	require.Equal(t, []byte("testing random output"), stdoutContents)
   267  
   268  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
   269  	require.Equal(t, expectedStderrFile, pm.StderrPath())
   270  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
   271  	require.NoError(t, err)
   272  	require.Equal(t, []byte{}, stderrContents)
   273  }
   274  
   275  func TestStop(t *testing.T) {
   276  	tempDir := newTempDir(t)
   277  	defer os.RemoveAll(tempDir)
   278  
   279  	scriptNum := 0
   280  	scriptContents := []byte(`#!/usr/bin/env bash
   281  	echo -ne "testing random output"
   282  	while true; do sleep 1; done
   283  	echo -ne "should never get this"`)
   284  	testScript := newTestScript(t, tempDir, scriptNum, scriptContents)
   285  	basePath := filepath.Base(testScript)
   286  	cmd := Cmd{
   287  		Path:      testScript,
   288  		Args:      []string{},
   289  		OutputDir: tempDir,
   290  	}
   291  	tl := NewProcessListener(
   292  		func() { require.FailNow(t, "unexpected OnComplete notification") },
   293  		func(err error) { require.FailNow(t, "unexpected error: %s", err.Error()) },
   294  	)
   295  
   296  	pm, err := NewProcessMonitor(cmd, tl)
   297  	require.NoError(t, err)
   298  	require.NoError(t, pm.Start())
   299  
   300  	// wait until execution has started
   301  	time.Sleep(100 * time.Millisecond)
   302  	seenRunning := false
   303  	for !seenRunning {
   304  		if pm.Running() {
   305  			seenRunning = true
   306  		}
   307  		time.Sleep(100 * time.Millisecond)
   308  	}
   309  
   310  	// now crash the program
   311  	err = pm.Stop()
   312  	require.NoError(t, err)
   313  
   314  	// give bg routines a chance to finish
   315  	time.Sleep(time.Millisecond)
   316  	require.NoError(t, pm.Err())
   317  
   318  	expectedStdoutFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStdoutSuffix))
   319  	require.Equal(t, expectedStdoutFile, pm.StdoutPath())
   320  	stdoutContents, err := ioutil.ReadFile(expectedStdoutFile)
   321  	require.NoError(t, err)
   322  	require.Equal(t, []byte("testing random output"), stdoutContents)
   323  
   324  	expectedStderrFile := path.Join(tempDir, fmt.Sprintf("%s.%s", basePath, defaultStderrSuffix))
   325  	require.Equal(t, expectedStderrFile, pm.StderrPath())
   326  	stderrContents, err := ioutil.ReadFile(expectedStderrFile)
   327  	require.NoError(t, err)
   328  	require.Equal(t, []byte{}, stderrContents)
   329  }