github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/buildcontrol/local_target_build_and_deployer_test.go (about)

     1  package buildcontrol
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"runtime"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/jonboulle/clockwork"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
    18  
    19  	"github.com/tilt-dev/tilt/internal/controllers/core/cmd"
    20  	"github.com/tilt-dev/tilt/internal/controllers/fake"
    21  	"github.com/tilt-dev/tilt/internal/localexec"
    22  	"github.com/tilt-dev/tilt/internal/store"
    23  	"github.com/tilt-dev/tilt/internal/testutils"
    24  	"github.com/tilt-dev/tilt/internal/testutils/tempdir"
    25  	"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
    26  	"github.com/tilt-dev/tilt/pkg/model"
    27  )
    28  
    29  func TestNoLocalTargets(t *testing.T) {
    30  	f := newLTFixture(t)
    31  
    32  	specs := []model.TargetSpec{
    33  		model.ImageTarget{}, model.K8sTarget{}, model.DockerComposeTarget{},
    34  	}
    35  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, specs, store.BuildStateSet{})
    36  	assert.Empty(t, res, "expect empty result for failed BuildAndDeploy")
    37  
    38  	require.NotNil(t, err)
    39  	assert.Contains(t, err.Error(),
    40  		"LocalTargetBuildAndDeployer requires exactly one LocalTarget (got 0)")
    41  }
    42  
    43  func TestTooManyLocalTargets(t *testing.T) {
    44  	f := newLTFixture(t)
    45  
    46  	specs := []model.TargetSpec{
    47  		model.LocalTarget{}, model.ImageTarget{}, model.K8sTarget{}, model.LocalTarget{},
    48  	}
    49  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, specs, store.BuildStateSet{})
    50  	assert.Empty(t, res, "expect empty result for failed BuildAndDeploy")
    51  
    52  	require.NotNil(t, err)
    53  	assert.Contains(t, err.Error(),
    54  		"LocalTargetBuildAndDeployer requires exactly one LocalTarget (got 2)")
    55  }
    56  
    57  func TestSuccessfulCommand(t *testing.T) {
    58  	f := newLTFixture(t)
    59  
    60  	targ := f.localTarget("echo hello world")
    61  
    62  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, []model.TargetSpec{targ}, store.BuildStateSet{})
    63  	require.Nil(t, err)
    64  
    65  	assert.Equal(t, targ.ID(), res[targ.ID()].TargetID())
    66  
    67  	assert.Contains(t, f.out.String(), "hello world", "expect cmd stdout in logs")
    68  }
    69  
    70  func TestWorkdir(t *testing.T) {
    71  	f := newLTFixture(t)
    72  
    73  	f.MkdirAll(filepath.Join("some", "internal", "dir"))
    74  	workdir := f.JoinPath("some", "internal", "dir")
    75  	cmd := "echo the directory is $(pwd)"
    76  	if runtime.GOOS == "windows" {
    77  		cmd = "echo the directory is %cd%"
    78  	}
    79  	targ := f.localTargetWithWorkdir(cmd, workdir)
    80  
    81  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, []model.TargetSpec{targ}, store.BuildStateSet{})
    82  	require.Nil(t, err)
    83  
    84  	assert.Equal(t, targ.ID(), res[targ.ID()].TargetID())
    85  
    86  	expectedOut := fmt.Sprintf("the directory is %s", workdir)
    87  	assert.Contains(t, f.out.String(), expectedOut, "expect cmd stdout (with appropriate pwd) in logs")
    88  }
    89  
    90  func TestExtractOneLocalTarget(t *testing.T) {
    91  	f := newLTFixture(t)
    92  
    93  	targ := f.localTarget("echo hello world")
    94  
    95  	// Even if there are multiple other targets, should correctly extract and run the one LocalTarget
    96  	specs := []model.TargetSpec{
    97  		targ, model.ImageTarget{}, model.K8sTarget{},
    98  	}
    99  
   100  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, specs, store.BuildStateSet{})
   101  	require.Nil(t, err)
   102  
   103  	assert.Equal(t, targ.ID(), res[targ.ID()].TargetID())
   104  
   105  	assert.Contains(t, f.out.String(), "hello world", "expect cmd stdout in logs")
   106  }
   107  
   108  func TestFailedCommand(t *testing.T) {
   109  	f := newLTFixture(t)
   110  
   111  	targ := f.localTarget("echo oh no && exit 1")
   112  
   113  	res, err := f.ltbad.BuildAndDeploy(f.ctx, f.st, []model.TargetSpec{targ}, store.BuildStateSet{})
   114  	assert.Empty(t, res, "expect empty build result for failed cmd")
   115  
   116  	require.NotNil(t, err, "failed cmd should throw error")
   117  	assert.Contains(t, err.Error(),
   118  		"Command \"echo oh no && exit 1\" failed: exit status 1")
   119  	assert.True(t, IsDontFallBackError(err), "expect DontFallBackError")
   120  
   121  	assert.Contains(t, f.out.String(), "oh no", "expect cmd stdout in logs")
   122  }
   123  
   124  type testStore struct {
   125  	*store.TestingStore
   126  	out io.Writer
   127  }
   128  
   129  func NewTestingStore(out io.Writer) *testStore {
   130  	return &testStore{
   131  		TestingStore: store.NewTestingStore(),
   132  		out:          out,
   133  	}
   134  }
   135  
   136  func (s *testStore) Dispatch(action store.Action) {
   137  	s.TestingStore.Dispatch(action)
   138  
   139  	if action, ok := action.(store.LogAction); ok {
   140  		_, _ = s.out.Write(action.Message())
   141  	}
   142  }
   143  
   144  type ltFixture struct {
   145  	*tempdir.TempDirFixture
   146  
   147  	ctx        context.Context
   148  	out        *bytes.Buffer
   149  	ltbad      *LocalTargetBuildAndDeployer
   150  	st         *testStore
   151  	ctrlClient ctrlclient.Client
   152  }
   153  
   154  func newLTFixture(t *testing.T) *ltFixture {
   155  	f := tempdir.NewTempDirFixture(t)
   156  
   157  	out := new(bytes.Buffer)
   158  	ctx, _, _ := testutils.ForkedCtxAndAnalyticsForTest(out)
   159  	clock := fakeClock{time.Date(2019, 1, 1, 1, 1, 1, 1, time.UTC)}
   160  
   161  	ctrlClient := fake.NewFakeTiltClient()
   162  
   163  	fe := cmd.NewProcessExecer(localexec.EmptyEnv())
   164  	fpm := cmd.NewFakeProberManager()
   165  	cclock := clockwork.NewFakeClock()
   166  	st := NewTestingStore(out)
   167  	cmds := cmd.NewController(ctx, fe, fpm, ctrlClient, st, cclock, v1alpha1.NewScheme())
   168  	ltbad := NewLocalTargetBuildAndDeployer(clock, ctrlClient, cmds)
   169  
   170  	return &ltFixture{
   171  		TempDirFixture: f,
   172  		ctx:            ctx,
   173  		out:            out,
   174  		ltbad:          ltbad,
   175  		st:             st,
   176  		ctrlClient:     ctrlClient,
   177  	}
   178  }
   179  
   180  func (f *ltFixture) localTarget(cmd string) model.LocalTarget {
   181  	return f.localTargetWithWorkdir(cmd, f.Path())
   182  }
   183  
   184  func (f *ltFixture) localTargetWithWorkdir(cmd string, workdir string) model.LocalTarget {
   185  	c := model.ToHostCmd(cmd)
   186  	c.Dir = workdir
   187  	lt := model.NewLocalTarget("local", c, model.Cmd{}, nil)
   188  
   189  	cmdObj := &v1alpha1.Cmd{
   190  		ObjectMeta: metav1.ObjectMeta{Name: lt.UpdateCmdName()},
   191  		Spec:       *(lt.UpdateCmdSpec),
   192  	}
   193  	require.NoError(f.T(), f.ctrlClient.Create(f.ctx, cmdObj))
   194  	return lt
   195  }