github.com/tilt-dev/tilt@v0.36.0/internal/controllers/core/cmd/execer_unix_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package cmd 5 6 import ( 7 "os" 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 18 func TestStopsBackgroundGrandchildren(t *testing.T) { 19 f := newProcessExecFixture(t) 20 21 f.start(`bash -c 'sleep 100 & 22 echo BACKGROUND $! 23 '`) 24 f.waitForStatus(Done) 25 26 // Since execer uses cmd.Process.Wait instead of cmd.Wait, it doesn't block until the writers have finished. 27 // (https://github.com/tilt-dev/tilt/blob/7dad0c01169c7e7825268eff27e96068288280b7/internal/controllers/core/cmd/execer.go#L184) 28 // This is probably not currently really a problem for Tilt since the underlying goroutine will still write to the 29 // logger's Writer after Wait has returned, but it's the kind of thing that could lead to surprises in the future. 30 // (e.g., tests like this, or if we use Cmd to power `local` in the Tiltfile) 31 var grandkidPid int 32 timeoutInterval := time.Second 33 timeout := time.After(time.Second) 34 checkInterval := 5 * time.Millisecond 35 for { 36 lines := strings.Split(f.testWriter.String(), "\n") 37 if strings.Contains(lines[1], "BACKGROUND") { 38 var err error 39 grandkidPid, err = strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(lines[1], "BACKGROUND"))) 40 require.NoError(t, err) 41 break 42 } 43 select { 44 case <-time.After(checkInterval): 45 case <-timeout: 46 t.Fatalf("timed out after %s waiting for grandkid pid. current output: %q", timeoutInterval, f.testWriter.String()) 47 } 48 } 49 50 grandkid, err := os.FindProcess(grandkidPid) 51 require.NoError(t, err) 52 53 // Old unix trick - signal to check if the process is still alive. 54 assert.Eventually(t, func() bool { 55 err := grandkid.Signal(syscall.SIGCONT) 56 return err != nil && strings.Contains(err.Error(), "process already finished") 57 }, time.Second, time.Millisecond) 58 }