github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/localexec/execer_test.go (about) 1 package localexec 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "runtime" 8 "strconv" 9 "strings" 10 "syscall" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "github.com/tilt-dev/tilt/internal/testutils" 18 "github.com/tilt-dev/tilt/internal/testutils/bufsync" 19 "github.com/tilt-dev/tilt/pkg/model" 20 ) 21 22 func TestProcessExecer_Run(t *testing.T) { 23 ctx, _, _ := testutils.CtxAndAnalyticsForTest() 24 ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 25 defer cancel() 26 27 // this works across both cmd.exe + sh 28 script := `echo hello from stdout && echo hello from stderr 1>&2` 29 30 execer := NewProcessExecer(EmptyEnv()) 31 32 r, err := OneShot(ctx, execer, model.ToHostCmd(script)) 33 34 require.NoError(t, err) 35 assert.Equal(t, 0, r.ExitCode) 36 // trim space to not deal with line-ending/whitespace differences between cmd.exe/sh 37 assert.Equal(t, "hello from stdout", strings.TrimSpace(string(r.Stdout))) 38 assert.Equal(t, "hello from stderr", strings.TrimSpace(string(r.Stderr))) 39 } 40 41 func TestProcessExecer_Run_ProcessGroup(t *testing.T) { 42 if testing.Short() { 43 t.Skip("skipping test in short mode") 44 } 45 if runtime.GOOS == "windows" { 46 t.Skip("test not supported on Windows") 47 } 48 49 ctx, _, _ := testutils.CtxAndAnalyticsForTest() 50 ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 51 defer cancel() 52 53 script := `sleep 60 & echo $!` 54 55 // to speed up test execution, as soon as we see the PID written to stdout, cancel the context 56 // to trigger process termination 57 var childPid int 58 stdoutBuf := bufsync.NewThreadSafeBuffer() 59 go func() { 60 for { 61 if ctx.Err() != nil { 62 return 63 } 64 output := strings.TrimSpace(stdoutBuf.String()) 65 if output != "" { 66 var err error 67 childPid, err = strconv.Atoi(output) 68 if err == nil { 69 cancel() 70 return 71 } 72 } 73 time.Sleep(5 * time.Millisecond) 74 } 75 }() 76 77 execer := NewProcessExecer(EmptyEnv()) 78 exitCode, err := execer.Run(ctx, model.ToUnixCmd(script), RunIO{Stdout: stdoutBuf}) 79 80 require.NoError(t, err) 81 assert.Equal(t, 137, exitCode) 82 83 if assert.NotZero(t, childPid, "Process did not write child PID to stdout") { 84 // os.FindProcess is a no-op on Unix-like systems and always succeeds; need to send signal 0 to probe it 85 proc, _ := os.FindProcess(childPid) 86 childProcStopped := assert.Eventually(t, func() bool { 87 err = proc.Signal(syscall.Signal(0)) 88 return errors.Is(err, os.ErrProcessDone) 89 }, time.Second, 50*time.Millisecond, "Child process was still running") 90 if !childProcStopped { 91 _ = proc.Kill() 92 } 93 } 94 }