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