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  }