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  }