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  }