github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/target_queue_test.go (about) 1 package engine 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/docker/distribution/reference" 9 "github.com/stretchr/testify/assert" 10 11 "github.com/windmilleng/tilt/internal/testutils" 12 13 "github.com/windmilleng/tilt/internal/container" 14 "github.com/windmilleng/tilt/internal/store" 15 "github.com/windmilleng/tilt/pkg/model" 16 ) 17 18 func TestTargetQueue_Simple(t *testing.T) { 19 f := newTargetQueueFixture(t) 20 21 t1 := model.NewImageTarget(container.MustParseSelector("vigoda")) 22 s1 := store.BuildState{} 23 24 targets := []model.ImageTarget{t1} 25 buildStateSet := store.BuildStateSet{ 26 t1.ID(): s1, 27 } 28 29 f.run(targets, buildStateSet) 30 31 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{ 32 t1.ID(): newFakeBuildHandlerCall(t1, s1, 1, []store.BuildResult{}), 33 } 34 assert.Equal(t, expectedCalls, f.handler.calls) 35 } 36 37 func TestTargetQueue_DepsBuilt(t *testing.T) { 38 f := newTargetQueueFixture(t) 39 40 fooTarget := model.NewImageTarget(container.MustParseSelector("foo")) 41 s1 := store.BuildState{LastSuccessfulResult: store.NewImageBuildResult(fooTarget.ID(), container.MustParseNamedTagged("foo:1234"))} 42 barTarget := model.NewImageTarget(container.MustParseSelector("bar")).WithDependencyIDs([]model.TargetID{fooTarget.ID()}) 43 s2 := store.BuildState{} 44 45 targets := []model.ImageTarget{fooTarget, barTarget} 46 buildStateSet := store.BuildStateSet{ 47 fooTarget.ID(): s1, 48 barTarget.ID(): s2, 49 } 50 51 f.run(targets, buildStateSet) 52 53 barCall := newFakeBuildHandlerCall(barTarget, s2, 1, []store.BuildResult{ 54 store.NewImageBuildResult(fooTarget.ID(), store.ImageFromBuildResult(s1.LastSuccessfulResult)), 55 }) 56 57 // foo has a valid last result, so only bar gets rebuilt 58 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{ 59 barTarget.ID(): barCall, 60 } 61 assert.Equal(t, expectedCalls, f.handler.calls) 62 } 63 64 func TestTargetQueue_DepsUnbuilt(t *testing.T) { 65 f := newTargetQueueFixture(t) 66 67 fooTarget := model.NewImageTarget(container.MustParseSelector("foo")) 68 s1 := store.BuildState{} 69 barTarget := model.NewImageTarget(container.MustParseSelector("bar")).WithDependencyIDs([]model.TargetID{fooTarget.ID()}) 70 var s2 = store.BuildState{LastSuccessfulResult: store.NewImageBuildResult( 71 barTarget.ID(), 72 container.MustParseNamedTagged("bar:54321"), 73 )} 74 targets := []model.ImageTarget{fooTarget, barTarget} 75 buildStateSet := store.BuildStateSet{ 76 fooTarget.ID(): s1, 77 barTarget.ID(): s2, 78 } 79 80 f.run(targets, buildStateSet) 81 82 fooCall := newFakeBuildHandlerCall(fooTarget, s1, 1, []store.BuildResult{}) 83 // bar's dep is dirty, so bar should not get its old state 84 barCall := newFakeBuildHandlerCall(barTarget, store.BuildState{}, 2, []store.BuildResult{fooCall.result}) 85 86 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{ 87 fooTarget.ID(): fooCall, 88 barTarget.ID(): barCall, 89 } 90 assert.Equal(t, expectedCalls, f.handler.calls) 91 } 92 93 func TestTargetQueue_IncrementalBuild(t *testing.T) { 94 f := newTargetQueueFixture(t) 95 96 fooTarget := model.NewImageTarget(container.MustParseSelector("foo")) 97 s1 := store.BuildState{ 98 LastSuccessfulResult: store.NewImageBuildResult( 99 fooTarget.ID(), 100 container.MustParseNamedTagged("foo:1234"), 101 ), 102 FilesChangedSet: map[string]bool{"hello.txt": true}, 103 } 104 105 targets := []model.ImageTarget{fooTarget} 106 buildStateSet := store.BuildStateSet{fooTarget.ID(): s1} 107 108 f.run(targets, buildStateSet) 109 110 fooCall := newFakeBuildHandlerCall(fooTarget, s1, 1, []store.BuildResult{}) 111 112 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{ 113 fooTarget.ID(): fooCall, 114 } 115 assert.Equal(t, expectedCalls, f.handler.calls) 116 } 117 118 func TestTargetQueue_CachedBuild(t *testing.T) { 119 f := newTargetQueueFixture(t) 120 121 fooTarget := model.NewImageTarget(container.MustParseSelector("foo")) 122 s1 := store.BuildState{ 123 LastSuccessfulResult: store.NewImageBuildResult( 124 fooTarget.ID(), 125 container.MustParseNamedTagged("foo:1234"), 126 ), 127 } 128 129 targets := []model.ImageTarget{fooTarget} 130 buildStateSet := store.BuildStateSet{fooTarget.ID(): s1} 131 132 f.run(targets, buildStateSet) 133 134 // last result is still valid, so handler doesn't get called at all 135 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{} 136 assert.Equal(t, expectedCalls, f.handler.calls) 137 } 138 139 func TestTargetQueue_DepsBuiltButReaped(t *testing.T) { 140 f := newTargetQueueFixture(t) 141 142 fooTarget := model.NewImageTarget(container.MustParseSelector("foo")) 143 s1 := store.BuildState{LastSuccessfulResult: store.NewImageBuildResult(fooTarget.ID(), container.MustParseNamedTagged("foo:1234"))} 144 barTarget := model.NewImageTarget(container.MustParseSelector("bar")).WithDependencyIDs([]model.TargetID{fooTarget.ID()}) 145 s2 := store.BuildState{} 146 147 targets := []model.ImageTarget{fooTarget, barTarget} 148 buildStateSet := store.BuildStateSet{ 149 fooTarget.ID(): s1, 150 barTarget.ID(): s2, 151 } 152 153 f.setMissingImage(store.ImageFromBuildResult(s1.LastSuccessfulResult)) 154 155 f.run(targets, buildStateSet) 156 157 fooCall := newFakeBuildHandlerCall(fooTarget, s1, 1, []store.BuildResult{}) 158 barCall := newFakeBuildHandlerCall(barTarget, s2, 2, []store.BuildResult{ 159 store.NewImageBuildResult(fooTarget.ID(), store.ImageFromBuildResult(fooCall.result)), 160 }) 161 162 // foo has a valid last result, but its image is missing, so we have to rebuild it and its deps 163 expectedCalls := map[model.TargetID]fakeBuildHandlerCall{ 164 fooTarget.ID(): fooCall, 165 barTarget.ID(): barCall, 166 } 167 assert.Equal(t, expectedCalls, f.handler.calls) 168 } 169 170 func newFakeBuildHandlerCall(target model.ImageTarget, state store.BuildState, num int, depResults []store.BuildResult) fakeBuildHandlerCall { 171 return fakeBuildHandlerCall{ 172 target: target, 173 state: state, 174 result: store.NewImageBuildResult( 175 target.ID(), 176 container.MustParseNamedTagged(fmt.Sprintf("%s:%d", target.ConfigurationRef.String(), num)), 177 ), 178 depResults: depResults, 179 } 180 } 181 182 type fakeBuildHandlerCall struct { 183 target model.TargetSpec 184 state store.BuildState 185 depResults []store.BuildResult 186 result store.BuildResult 187 } 188 189 type fakeBuildHandler struct { 190 buildNum int 191 calls map[model.TargetID]fakeBuildHandlerCall 192 } 193 194 func newFakeBuildHandler() *fakeBuildHandler { 195 return &fakeBuildHandler{ 196 calls: make(map[model.TargetID]fakeBuildHandlerCall), 197 } 198 } 199 200 func (fbh *fakeBuildHandler) handle(target model.TargetSpec, state store.BuildState, depResults []store.BuildResult) (store.BuildResult, error) { 201 iTarget := target.(model.ImageTarget) 202 fbh.buildNum++ 203 namedTagged := container.MustParseNamedTagged(fmt.Sprintf("%s:%d", iTarget.ConfigurationRef, fbh.buildNum)) 204 result := store.NewImageBuildResult(target.ID(), namedTagged) 205 fbh.calls[target.ID()] = fakeBuildHandlerCall{target, state, depResults, result} 206 return result, nil 207 } 208 209 type targetQueueFixture struct { 210 t *testing.T 211 ctx context.Context 212 handler *fakeBuildHandler 213 missingImages []reference.NamedTagged 214 } 215 216 func newTargetQueueFixture(t *testing.T) *targetQueueFixture { 217 ctx, _, _ := testutils.CtxAndAnalyticsForTest() 218 return &targetQueueFixture{ 219 t: t, 220 ctx: ctx, 221 handler: newFakeBuildHandler(), 222 } 223 } 224 225 func (f *targetQueueFixture) imageExists(ctx context.Context, namedTagged reference.NamedTagged) (b bool, e error) { 226 for _, ref := range f.missingImages { 227 if ref == namedTagged { 228 return false, nil 229 } 230 } 231 return true, nil 232 } 233 234 func (f *targetQueueFixture) setMissingImage(namedTagged reference.NamedTagged) { 235 f.missingImages = append(f.missingImages, namedTagged) 236 } 237 238 func (f *targetQueueFixture) run(targets []model.ImageTarget, buildStateSet store.BuildStateSet) { 239 tq, err := NewImageTargetQueue(f.ctx, targets, buildStateSet, f.imageExists) 240 if err != nil { 241 f.t.Fatal(err) 242 } 243 244 err = tq.RunBuilds(f.handler.handle) 245 if err != nil { 246 f.t.Fatal(err) 247 } 248 }