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 <Fixture{ 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 }