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 }