github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/buildcontroller_test.go (about)

     1  package engine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	v1 "k8s.io/api/core/v1"
    13  
    14  	"github.com/windmilleng/tilt/internal/hud/server"
    15  	"github.com/windmilleng/tilt/internal/k8s/testyaml"
    16  	"github.com/windmilleng/tilt/internal/testutils/manifestbuilder"
    17  	"github.com/windmilleng/tilt/internal/testutils/podbuilder"
    18  
    19  	"github.com/windmilleng/tilt/internal/container"
    20  	"github.com/windmilleng/tilt/internal/store"
    21  	"github.com/windmilleng/tilt/internal/watch"
    22  	"github.com/windmilleng/tilt/pkg/model"
    23  )
    24  
    25  func TestBuildControllerOnePod(t *testing.T) {
    26  	f := newTestFixture(t)
    27  	defer f.TearDown()
    28  
    29  	manifest := f.newManifest("fe")
    30  	f.Start([]model.Manifest{manifest}, true)
    31  
    32  	call := f.nextCall()
    33  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
    34  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
    35  
    36  	pod := podbuilder.New(f.T(), manifest).Build()
    37  	f.podEvent(pod, manifest.Name)
    38  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
    39  
    40  	call = f.nextCall()
    41  	assert.Equal(t, pod.Name, call.oneState().OneContainerInfo().PodID.String())
    42  
    43  	err := f.Stop()
    44  	assert.NoError(t, err)
    45  	f.assertAllBuildsConsumed()
    46  }
    47  
    48  func TestBuildControllerTooManyPodsForLiveUpdateErrorMessage(t *testing.T) {
    49  	f := newTestFixture(t)
    50  	defer f.TearDown()
    51  
    52  	manifest := NewSanchoLiveUpdateManifest(f)
    53  	f.Start([]model.Manifest{manifest}, true)
    54  
    55  	// initial build
    56  	call := f.nextCall()
    57  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
    58  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
    59  
    60  	p1 := podbuilder.New(t, manifest).WithPodID("pod1").Build()
    61  	p2 := podbuilder.New(t, manifest).WithPodID("pod2").Build()
    62  
    63  	f.podEvent(p1, manifest.Name)
    64  	f.podEvent(p2, manifest.Name)
    65  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
    66  
    67  	call = f.nextCall()
    68  	// Should not have sent container info b/c too many pods
    69  	assert.Equal(t, store.ContainerInfo{}, call.oneState().OneContainerInfo())
    70  
    71  	err := f.Stop()
    72  	assert.NoError(t, err)
    73  	f.assertAllBuildsConsumed()
    74  
    75  	err = call.oneState().RunningContainerError
    76  	if assert.Error(t, err) {
    77  		assert.Contains(t, err.Error(), "can only get container info for a single pod",
    78  			"should print error message when trying to get Running Containers for manifest with more than one pod")
    79  	}
    80  }
    81  
    82  func TestBuildControllerTooManyPodsForDockerBuildNoErrorMessage(t *testing.T) {
    83  	f := newTestFixture(t)
    84  	defer f.TearDown()
    85  
    86  	manifest := NewSanchoDockerBuildManifest(f)
    87  	f.Start([]model.Manifest{manifest}, true)
    88  
    89  	// intial build
    90  	call := f.nextCall()
    91  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
    92  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
    93  
    94  	p1 := podbuilder.New(t, manifest).WithPodID("pod1").Build()
    95  	p2 := podbuilder.New(t, manifest).WithPodID("pod2").Build()
    96  
    97  	f.podEvent(p1, manifest.Name)
    98  	f.podEvent(p2, manifest.Name)
    99  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   100  
   101  	call = f.nextCall()
   102  	// Should not have sent container info b/c too many pods
   103  	assert.Equal(t, store.ContainerInfo{}, call.oneState().OneContainerInfo())
   104  
   105  	err := f.Stop()
   106  	assert.NoError(t, err)
   107  	f.assertAllBuildsConsumed()
   108  
   109  	// Should not have surfaced this log line b/c manifest doesn't have LiveUpdate instructions
   110  	assert.NotContains(t, f.log.String(), "can only get container info for a single pod",
   111  		"should print error message when trying to get Running Containers for manifest with more than one pod")
   112  }
   113  
   114  func TestBuildControllerIgnoresImageTags(t *testing.T) {
   115  	f := newTestFixture(t)
   116  	defer f.TearDown()
   117  
   118  	ref := container.MustParseNamed("image-foo:tagged")
   119  	manifest := f.newManifestWithRef("fe", ref)
   120  	f.Start([]model.Manifest{manifest}, true)
   121  
   122  	call := f.nextCall()
   123  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   124  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
   125  
   126  	pod := podbuilder.New(t, manifest).
   127  		WithPodID("pod-id").
   128  		WithImage("image-foo:othertag").
   129  		Build()
   130  	f.podEvent(pod, manifest.Name)
   131  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   132  
   133  	call = f.nextCall()
   134  	assert.Equal(t, "pod-id", call.oneState().OneContainerInfo().PodID.String())
   135  
   136  	err := f.Stop()
   137  	assert.NoError(t, err)
   138  	f.assertAllBuildsConsumed()
   139  }
   140  
   141  func TestBuildControllerDockerCompose(t *testing.T) {
   142  	f := newTestFixture(t)
   143  	defer f.TearDown()
   144  
   145  	manifest := NewSanchoFastBuildDCManifest(f)
   146  	f.Start([]model.Manifest{manifest}, true)
   147  
   148  	call := f.nextCall()
   149  	imageTarget := manifest.ImageTargetAt(0)
   150  	assert.Equal(t, imageTarget, call.firstImgTarg())
   151  
   152  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   153  
   154  	call = f.nextCall()
   155  	imageState := call.state[imageTarget.ID()]
   156  	assert.Equal(t, "dc-sancho", imageState.OneContainerInfo().ContainerID.String())
   157  
   158  	err := f.Stop()
   159  	assert.NoError(t, err)
   160  	f.assertAllBuildsConsumed()
   161  }
   162  
   163  func TestBuildControllerLocalResource(t *testing.T) {
   164  	f := newTestFixture(t)
   165  	defer f.TearDown()
   166  
   167  	dep := f.JoinPath("stuff.json")
   168  	manifest := manifestbuilder.New(f, "yarn-add").
   169  		WithLocalResource("echo beep boop", []string{dep}).Build()
   170  	f.Start([]model.Manifest{manifest}, true)
   171  
   172  	call := f.nextCall()
   173  	lt := manifest.LocalTarget()
   174  	assert.Equal(t, lt, call.local())
   175  
   176  	f.fsWatcher.events <- watch.NewFileEvent(dep)
   177  
   178  	call = f.nextCall()
   179  	assert.Equal(t, lt, call.local())
   180  
   181  	err := f.Stop()
   182  	assert.NoError(t, err)
   183  	f.assertAllBuildsConsumed()
   184  }
   185  
   186  func TestBuildControllerWontContainerBuildWithTwoPods(t *testing.T) {
   187  	f := newTestFixture(t)
   188  	defer f.TearDown()
   189  
   190  	manifest := f.newManifest("fe")
   191  	f.Start([]model.Manifest{manifest}, true)
   192  
   193  	call := f.nextCall()
   194  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   195  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
   196  
   197  	// Associate the pods with the manifest state
   198  	podA := podbuilder.New(f.T(), manifest).WithPodID("pod-a").Build()
   199  	podB := podbuilder.New(f.T(), manifest).WithPodID("pod-b").Build()
   200  	f.podEvent(podA, manifest.Name)
   201  	f.podEvent(podB, manifest.Name)
   202  
   203  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   204  
   205  	// We expect two pods associated with this manifest. We don't want to container-build
   206  	// if there are multiple pods, so make sure we're not sending deploy info (i.e. that
   207  	// we're doing an image build)
   208  	call = f.nextCall()
   209  	assert.Equal(t, "", call.oneState().OneContainerInfo().PodID.String())
   210  
   211  	err := f.Stop()
   212  	assert.NoError(t, err)
   213  	f.assertAllBuildsConsumed()
   214  }
   215  
   216  func TestBuildControllerTwoContainers(t *testing.T) {
   217  	f := newTestFixture(t)
   218  	defer f.TearDown()
   219  
   220  	manifest := f.newManifest("fe")
   221  	f.Start([]model.Manifest{manifest}, true)
   222  
   223  	call := f.nextCall()
   224  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   225  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
   226  
   227  	// container already on this pod matches the image built by this manifest
   228  	pod := podbuilder.New(f.T(), manifest).Build()
   229  	imgName := pod.Status.ContainerStatuses[0].Image
   230  	pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, v1.ContainerStatus{
   231  		Name:        "same image",
   232  		Image:       imgName, // matches image built by this manifest
   233  		Ready:       true,
   234  		ContainerID: "docker://cID-same-image",
   235  	}, v1.ContainerStatus{
   236  		Name:        "different image",
   237  		Image:       "different-image", // does NOT match image built by this manifest
   238  		Ready:       false,
   239  		ContainerID: "docker://cID-different-image",
   240  	})
   241  	f.podEvent(pod, manifest.Name)
   242  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   243  
   244  	call = f.nextCall()
   245  	runningContainers := call.oneState().RunningContainers
   246  
   247  	require.Len(t, runningContainers, 2, "expect info for two containers (those "+
   248  		"matching the image built by this manifest")
   249  
   250  	c0 := runningContainers[0]
   251  	c1 := runningContainers[1]
   252  
   253  	assert.Equal(t, pod.Name, c0.PodID.String(), "pod ID for cInfo at index 0")
   254  	assert.Equal(t, pod.Name, c1.PodID.String(), "pod ID for cInfo at index 1")
   255  
   256  	assert.Equal(t, podbuilder.FakeContainerID(), c0.ContainerID, "container ID for cInfo at index 0")
   257  	assert.Equal(t, "cID-same-image", c1.ContainerID.String(), "container ID for cInfo at index 1")
   258  
   259  	assert.Equal(t, "sancho", c0.ContainerName.String(), "container name for cInfo at index 0")
   260  	assert.Equal(t, "same image", c1.ContainerName.String(), "container name for cInfo at index 1")
   261  
   262  	err := f.Stop()
   263  	assert.NoError(t, err)
   264  	f.assertAllBuildsConsumed()
   265  }
   266  
   267  func TestBuildControllerWontContainerBuildWithSomeButNotAllReadyContainers(t *testing.T) {
   268  	f := newTestFixture(t)
   269  	defer f.TearDown()
   270  
   271  	manifest := f.newManifest("fe")
   272  	f.Start([]model.Manifest{manifest}, true)
   273  
   274  	call := f.nextCall()
   275  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   276  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
   277  
   278  	// container already on this pod matches the image built by this manifest
   279  	pod := podbuilder.New(f.T(), manifest).Build()
   280  	imgName := pod.Status.ContainerStatuses[0].Image
   281  	pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, v1.ContainerStatus{
   282  		Name:        "same image",
   283  		Image:       imgName, // matches image built by this manifest
   284  		Ready:       false,
   285  		ContainerID: "docker://cID-same-image",
   286  	})
   287  	f.podEvent(pod, manifest.Name)
   288  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   289  
   290  	// If even one of the containers matching this image is !ready, we have to do a
   291  	// full rebuild, so don't return ANY RunningContainers.
   292  	call = f.nextCall()
   293  	runningContainers := call.oneState().RunningContainers
   294  	assert.Empty(t, runningContainers)
   295  
   296  	err := f.Stop()
   297  	assert.NoError(t, err)
   298  	f.assertAllBuildsConsumed()
   299  }
   300  
   301  func TestBuildControllerCrashRebuild(t *testing.T) {
   302  	f := newTestFixture(t)
   303  	defer f.TearDown()
   304  
   305  	manifest := f.newManifest("fe")
   306  	f.Start([]model.Manifest{manifest}, true)
   307  
   308  	call := f.nextCall()
   309  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   310  	assert.Equal(t, []string{}, call.oneState().FilesChanged())
   311  	f.waitForCompletedBuildCount(1)
   312  
   313  	f.b.nextLiveUpdateContainerIDs = []container.ID{podbuilder.FakeContainerID()}
   314  	pb := podbuilder.New(f.T(), manifest)
   315  	pod := pb.Build()
   316  	f.podEvent(pod, manifest.Name)
   317  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   318  
   319  	call = f.nextCall()
   320  	assert.Equal(t, pod.Name, call.oneState().OneContainerInfo().PodID.String())
   321  	f.waitForCompletedBuildCount(2)
   322  	f.withManifestState("fe", func(ms store.ManifestState) {
   323  		assert.Equal(t, model.BuildReasonFlagChangedFiles, ms.LastBuild().Reason)
   324  		assert.Equal(t, podbuilder.FakeContainerIDSet(1), ms.LiveUpdatedContainerIDs)
   325  	})
   326  
   327  	// Restart the pod with a new container id, to simulate a container restart.
   328  	f.podEvent(pb.WithContainerID("funnyContainerID").Build(), manifest.Name)
   329  	call = f.nextCall()
   330  	assert.True(t, call.oneState().OneContainerInfo().Empty())
   331  	f.waitForCompletedBuildCount(3)
   332  
   333  	f.withManifestState("fe", func(ms store.ManifestState) {
   334  		assert.Equal(t, model.BuildReasonFlagCrash, ms.LastBuild().Reason)
   335  	})
   336  
   337  	err := f.Stop()
   338  	assert.NoError(t, err)
   339  	f.assertAllBuildsConsumed()
   340  }
   341  
   342  func TestCrashRebuildTwoContainersOneImage(t *testing.T) {
   343  	f := newTestFixture(t)
   344  	defer f.TearDown()
   345  
   346  	manifest := manifestbuilder.New(f, "sancho").
   347  		WithK8sYAML(testyaml.SanchoTwoContainersOneImageYAML).
   348  		WithImageTarget(NewSanchoLiveUpdateImageTarget(f)).
   349  		Build()
   350  	f.Start([]model.Manifest{manifest}, true)
   351  
   352  	call := f.nextCall()
   353  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   354  	f.waitForCompletedBuildCount(1)
   355  
   356  	f.b.nextLiveUpdateContainerIDs = []container.ID{"c1", "c2"}
   357  	f.podEvent(podbuilder.New(t, manifest).
   358  		WithContainerIDAtIndex("c1", 0).
   359  		WithContainerIDAtIndex("c2", 1).
   360  		Build(), manifest.Name)
   361  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   362  
   363  	call = f.nextCall()
   364  	f.waitForCompletedBuildCount(2)
   365  	f.withManifestState("sancho", func(ms store.ManifestState) {
   366  		assert.Equal(t, 2, len(ms.LiveUpdatedContainerIDs))
   367  	})
   368  
   369  	// Simulate pod event where one of the containers has been restarted with a new ID.
   370  	f.podEvent(podbuilder.New(t, manifest).
   371  		WithContainerID("c1").
   372  		WithContainerIDAtIndex("c3", 1).
   373  		Build(), manifest.Name)
   374  
   375  	call = f.nextCall()
   376  	f.waitForCompletedBuildCount(3)
   377  
   378  	f.withManifestState("sancho", func(ms store.ManifestState) {
   379  		assert.Equal(t, model.BuildReasonFlagCrash, ms.LastBuild().Reason)
   380  	})
   381  
   382  	err := f.Stop()
   383  	assert.NoError(t, err)
   384  	f.assertAllBuildsConsumed()
   385  }
   386  
   387  func TestCrashRebuildTwoContainersTwoImages(t *testing.T) {
   388  	f := newTestFixture(t)
   389  	defer f.TearDown()
   390  
   391  	manifest := manifestbuilder.New(f, "sancho").
   392  		WithK8sYAML(testyaml.SanchoTwoContainersOneImageYAML).
   393  		WithImageTarget(NewSanchoLiveUpdateImageTarget(f)).
   394  		WithImageTarget(NewSanchoSidecarLiveUpdateImageTarget(f)).
   395  		Build()
   396  	f.Start([]model.Manifest{manifest}, true)
   397  
   398  	call := f.nextCall()
   399  	iTargs := call.imageTargets()
   400  	require.Len(t, iTargs, 2)
   401  	assert.Equal(t, manifest.ImageTargetAt(0), iTargs[0])
   402  	assert.Equal(t, manifest.ImageTargetAt(1), iTargs[1])
   403  	f.waitForCompletedBuildCount(1)
   404  
   405  	f.b.nextLiveUpdateContainerIDs = []container.ID{"c1", "c2"}
   406  	f.podEvent(podbuilder.New(t, manifest).
   407  		WithContainerIDAtIndex("c1", 0).
   408  		WithContainerIDAtIndex("c2", 1).
   409  		Build(), manifest.Name)
   410  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   411  
   412  	call = f.nextCall()
   413  	f.waitForCompletedBuildCount(2)
   414  	f.withManifestState("sancho", func(ms store.ManifestState) {
   415  		assert.Equal(t, 2, len(ms.LiveUpdatedContainerIDs))
   416  	})
   417  
   418  	// Simulate pod event where one of the containers has been restarted with a new ID.
   419  	f.podEvent(podbuilder.New(t, manifest).
   420  		WithContainerID("c1").
   421  		WithContainerIDAtIndex("c3", 1).
   422  		Build(), manifest.Name)
   423  
   424  	call = f.nextCall()
   425  	f.waitForCompletedBuildCount(3)
   426  
   427  	f.withManifestState("sancho", func(ms store.ManifestState) {
   428  		assert.Equal(t, model.BuildReasonFlagCrash, ms.LastBuild().Reason)
   429  	})
   430  
   431  	err := f.Stop()
   432  	assert.NoError(t, err)
   433  	f.assertAllBuildsConsumed()
   434  }
   435  
   436  func TestRecordLiveUpdatedContainerIDsForFailedLiveUpdate(t *testing.T) {
   437  	f := newTestFixture(t)
   438  	defer f.TearDown()
   439  
   440  	manifest := manifestbuilder.New(f, "sancho").
   441  		WithK8sYAML(testyaml.SanchoTwoContainersOneImageYAML).
   442  		WithImageTarget(NewSanchoLiveUpdateImageTarget(f)).
   443  		Build()
   444  	f.Start([]model.Manifest{manifest}, true)
   445  
   446  	call := f.nextCall()
   447  	assert.Equal(t, manifest.ImageTargetAt(0), call.firstImgTarg())
   448  	f.waitForCompletedBuildCount(1)
   449  
   450  	expectedErr := fmt.Errorf("i can't let you do that dave")
   451  	f.b.nextBuildFailure = expectedErr
   452  	f.b.nextLiveUpdateContainerIDs = []container.ID{"c1", "c2"}
   453  
   454  	f.podEvent(podbuilder.New(t, manifest).
   455  		WithContainerIDAtIndex("c1", 0).
   456  		WithContainerIDAtIndex("c2", 1).
   457  		Build(), manifest.Name)
   458  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   459  
   460  	call = f.nextCall()
   461  	f.waitForCompletedBuildCount(2)
   462  	f.withManifestState("sancho", func(ms store.ManifestState) {
   463  		// Manifest should have recorded last build as a failure, but
   464  		// ALSO have recorded the LiveUpdatedContainerIDs
   465  		require.Equal(t, expectedErr, ms.BuildHistory[0].Error)
   466  
   467  		assert.Equal(t, 2, len(ms.LiveUpdatedContainerIDs))
   468  	})
   469  }
   470  
   471  func TestBuildControllerManualTriggerWithFileChanges(t *testing.T) {
   472  	for _, tc := range []struct {
   473  		name        string
   474  		triggerMode model.TriggerMode
   475  	}{
   476  		{"manual including initial", model.TriggerModeManualIncludingInitial},
   477  		{"manual after initial", model.TriggerModeManualAfterInitial},
   478  	} {
   479  		t.Run(tc.name, func(t *testing.T) {
   480  			f := newTestFixture(t)
   481  			defer f.TearDown()
   482  			mName := model.ManifestName("foobar")
   483  
   484  			manifest := f.newManifest(mName.String()).WithTriggerMode(tc.triggerMode)
   485  			manifests := []model.Manifest{manifest}
   486  			f.Start(manifests, true)
   487  
   488  			// if we expect an initial build from the manifest, wait for it to complete
   489  			if manifest.TriggerMode.AutoInitial() {
   490  				f.nextCallComplete()
   491  			}
   492  
   493  			f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   494  			f.WaitUntil("pending change appears", func(st store.EngineState) bool {
   495  				return len(st.BuildStatus(manifest.ImageTargetAt(0).ID()).PendingFileChanges) > 0
   496  			})
   497  			f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
   498  
   499  			f.store.Dispatch(server.AppendToTriggerQueueAction{Name: mName})
   500  			call := f.nextCallComplete()
   501  			state := call.oneState()
   502  			assert.Equal(t, []string{f.JoinPath("main.go")}, state.FilesChanged())
   503  			assert.False(t, state.NeedsForceUpdate) // it was NOT a force update, b/c there were changed files
   504  
   505  			f.WaitUntil("manifest removed from queue", func(st store.EngineState) bool {
   506  				for _, mn := range st.TriggerQueue {
   507  					if mn == mName {
   508  						return false
   509  					}
   510  				}
   511  				return true
   512  			})
   513  		})
   514  	}
   515  }
   516  
   517  func TestBuildControllerManualTriggerWithoutFileChangesForceUpdates(t *testing.T) {
   518  	f := newTestFixture(t)
   519  	defer f.TearDown()
   520  	mName := model.ManifestName("foobar")
   521  
   522  	manifest := f.newManifest(mName.String())
   523  	manifests := []model.Manifest{manifest}
   524  	f.Start(manifests, true)
   525  
   526  	f.nextCall()
   527  	f.waitForCompletedBuildCount(1)
   528  
   529  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: mName})
   530  	call := f.nextCall()
   531  	state := call.oneState()
   532  	assert.Empty(t, state.FilesChanged())
   533  	assert.True(t, state.NeedsForceUpdate)
   534  	f.waitForCompletedBuildCount(2)
   535  
   536  	f.WaitUntil("manifest removed from queue", func(st store.EngineState) bool {
   537  		for _, mn := range st.TriggerQueue {
   538  			if mn == mName {
   539  				return false
   540  			}
   541  		}
   542  		return true
   543  	})
   544  }
   545  
   546  func TestBuildQueueOrdering(t *testing.T) {
   547  	f := newTestFixture(t)
   548  	defer f.TearDown()
   549  
   550  	m1 := f.newManifestWithRef("manifest1", container.MustParseNamed("manifest1")).
   551  		WithTriggerMode(model.TriggerModeManualAfterInitial)
   552  	m2 := f.newManifestWithRef("manifest2", container.MustParseNamed("manifest2")).
   553  		WithTriggerMode(model.TriggerModeManualAfterInitial)
   554  	m3 := f.newManifestWithRef("manifest3", container.MustParseNamed("manifest3")).
   555  		WithTriggerMode(model.TriggerModeManualIncludingInitial)
   556  	m4 := f.newManifestWithRef("manifest4", container.MustParseNamed("manifest4")).
   557  		WithTriggerMode(model.TriggerModeManualIncludingInitial)
   558  
   559  	// attach to state in different order than we plan to trigger them
   560  	manifests := []model.Manifest{m4, m2, m3, m1}
   561  	f.Start(manifests, true)
   562  
   563  	expectedInitialBuildCount := 0
   564  	for _, m := range manifests {
   565  		if m.TriggerMode.AutoInitial() {
   566  			expectedInitialBuildCount++
   567  			f.nextCall()
   568  		}
   569  	}
   570  
   571  	f.waitForCompletedBuildCount(expectedInitialBuildCount)
   572  
   573  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("main.go"))
   574  	f.WaitUntil("pending change appears", func(st store.EngineState) bool {
   575  		return len(st.BuildStatus(m1.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   576  			len(st.BuildStatus(m2.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   577  			len(st.BuildStatus(m3.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   578  			len(st.BuildStatus(m4.ImageTargetAt(0).ID()).PendingFileChanges) > 0
   579  	})
   580  	f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
   581  
   582  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest1"})
   583  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest2"})
   584  	time.Sleep(10 * time.Millisecond)
   585  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest3"})
   586  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest4"})
   587  
   588  	for i, _ := range manifests {
   589  		expName := fmt.Sprintf("manifest%d", i+1)
   590  		call := f.nextCall()
   591  		imgID := call.firstImgTarg().ID().String()
   592  		if assert.True(t, strings.HasSuffix(imgID, expName),
   593  			"expected to get manifest '%s' but instead got: '%s' (checking suffix for manifest name)", expName, imgID) {
   594  			assert.Equal(t, []string{f.JoinPath("main.go")}, call.oneState().FilesChanged(),
   595  				"for manifest '%s", expName)
   596  		}
   597  	}
   598  	f.waitForCompletedBuildCount(expectedInitialBuildCount + len(manifests))
   599  }
   600  
   601  // This test is tightly coupled with FastBuild, and needs to be
   602  // rewritten to use DockerBuild
   603  func TestBuildQueueAndAutobuildOrdering(t *testing.T) {
   604  	f := newTestFixture(t)
   605  	defer f.TearDown()
   606  
   607  	// changes to this dir. will register with our manual manifests
   608  	syncDirManual := model.Sync{LocalPath: f.JoinPath("dirManual/"), ContainerPath: "/go"}
   609  	// changes to this dir. will register with our automatic manifests
   610  	syncDirAuto := model.Sync{LocalPath: f.JoinPath("dirAuto/"), ContainerPath: "/go"}
   611  
   612  	m1 := f.newFastBuildManifest("manifest1", []model.Sync{syncDirManual}).WithTriggerMode(model.TriggerModeManualAfterInitial)
   613  	m2 := f.newFastBuildManifest("manifest2", []model.Sync{syncDirManual}).WithTriggerMode(model.TriggerModeManualAfterInitial)
   614  	m3 := f.newFastBuildManifest("manifest3", []model.Sync{syncDirManual}).WithTriggerMode(model.TriggerModeManualIncludingInitial)
   615  	m4 := f.newFastBuildManifest("manifest4", []model.Sync{syncDirManual}).WithTriggerMode(model.TriggerModeManualIncludingInitial)
   616  	m5 := f.newFastBuildManifest("manifest5", []model.Sync{syncDirAuto}).WithTriggerMode(model.TriggerModeAuto)
   617  
   618  	// attach to state in different order than we plan to trigger them
   619  	manifests := []model.Manifest{m5, m4, m2, m3, m1}
   620  	f.Start(manifests, true)
   621  
   622  	expectedInitialBuildCount := 0
   623  	for _, m := range manifests {
   624  		if m.TriggerMode.AutoInitial() {
   625  			expectedInitialBuildCount++
   626  			f.nextCall()
   627  		}
   628  	}
   629  
   630  	f.waitForCompletedBuildCount(expectedInitialBuildCount)
   631  
   632  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("dirManual/main.go"))
   633  	f.WaitUntil("pending change appears", func(st store.EngineState) bool {
   634  		return len(st.BuildStatus(m1.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   635  			len(st.BuildStatus(m2.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   636  			len(st.BuildStatus(m3.ImageTargetAt(0).ID()).PendingFileChanges) > 0 &&
   637  			len(st.BuildStatus(m4.ImageTargetAt(0).ID()).PendingFileChanges) > 0
   638  	})
   639  	f.assertNoCall("even tho there are pending changes, manual manifest shouldn't build w/o explicit trigger")
   640  
   641  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest1"})
   642  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest2"})
   643  	// make our one auto-trigger manifest build - should be evaluated LAST, after
   644  	// all the manual manifests waiting in the queue
   645  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("dirAuto/main.go"))
   646  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest3"})
   647  	f.store.Dispatch(server.AppendToTriggerQueueAction{Name: "manifest4"})
   648  
   649  	for i, _ := range manifests {
   650  		call := f.nextCall()
   651  		assert.True(t, strings.HasSuffix(call.firstImgTarg().ID().String(), fmt.Sprintf("manifest%d", i+1)))
   652  
   653  		if i < 4 {
   654  			assert.Equal(t, []string{f.JoinPath("dirManual/main.go")}, call.oneState().FilesChanged(), "for manifest %d", i+1)
   655  		} else {
   656  			// the automatic manifest
   657  			assert.Equal(t, []string{f.JoinPath("dirAuto/main.go")}, call.oneState().FilesChanged(), "for manifest %d", i+1)
   658  		}
   659  	}
   660  	f.waitForCompletedBuildCount(len(manifests) + expectedInitialBuildCount)
   661  }
   662  
   663  // any manifests without image targets should be deployed before any manifests WITH image targets
   664  func TestBuildControllerNoBuildManifestsFirst(t *testing.T) {
   665  	f := newTestFixture(t)
   666  	defer f.TearDown()
   667  
   668  	manifests := make([]model.Manifest, 10)
   669  	for i := 0; i < 10; i++ {
   670  		manifests[i] = f.newManifest(fmt.Sprintf("built%d", i+1))
   671  	}
   672  
   673  	for _, i := range []int{3, 7, 8} {
   674  		manifests[i] = manifestbuilder.New(f, model.ManifestName(fmt.Sprintf("unbuilt%d", i+1))).
   675  			WithK8sYAML(SanchoYAML).
   676  			Build()
   677  	}
   678  	f.Start(manifests, true)
   679  
   680  	var observedBuildOrder []string
   681  	for i := 0; i < len(manifests); i++ {
   682  		call := f.nextCall()
   683  		observedBuildOrder = append(observedBuildOrder, call.k8s().Name.String())
   684  	}
   685  
   686  	// throwing a bunch of elements at it to increase confidence we maintain order between built and unbuilt
   687  	// this might miss bugs since we might just get these elements back in the right order via luck
   688  	expectedBuildOrder := []string{
   689  		"unbuilt4",
   690  		"unbuilt8",
   691  		"unbuilt9",
   692  		"built1",
   693  		"built2",
   694  		"built3",
   695  		"built5",
   696  		"built6",
   697  		"built7",
   698  		"built10",
   699  	}
   700  	assert.Equal(t, expectedBuildOrder, observedBuildOrder)
   701  }
   702  
   703  func TestBuildControllerUnresourcedYAMLFirst(t *testing.T) {
   704  	f := newTestFixture(t)
   705  	defer f.TearDown()
   706  
   707  	manifests := []model.Manifest{
   708  		f.newManifest("built1"),
   709  		f.newManifest("built2"),
   710  		f.newManifest("built3"),
   711  		f.newManifest("built4"),
   712  	}
   713  
   714  	manifests = append(manifests, manifestbuilder.New(f, model.UnresourcedYAMLManifestName).
   715  		WithK8sYAML(testyaml.SecretYaml).Build())
   716  	f.Start(manifests, true)
   717  
   718  	var observedBuildOrder []string
   719  	for i := 0; i < len(manifests); i++ {
   720  		call := f.nextCall()
   721  		observedBuildOrder = append(observedBuildOrder, call.k8s().Name.String())
   722  	}
   723  
   724  	expectedBuildOrder := []string{
   725  		model.UnresourcedYAMLManifestName.String(),
   726  		"built1",
   727  		"built2",
   728  		"built3",
   729  		"built4",
   730  	}
   731  	assert.Equal(t, expectedBuildOrder, observedBuildOrder)
   732  }
   733  
   734  func TestBuildControllerRespectDockerComposeOrder(t *testing.T) {
   735  	f := newTestFixture(t)
   736  	defer f.TearDown()
   737  
   738  	sancho := NewSanchoFastBuildDCManifest(f)
   739  	redis := manifestbuilder.New(f, "redis").WithDockerCompose().Build()
   740  	donQuixote := manifestbuilder.New(f, "don-quixote").WithDockerCompose().Build()
   741  	manifests := []model.Manifest{redis, sancho, donQuixote}
   742  	f.Start(manifests, true)
   743  
   744  	var observedBuildOrder []string
   745  	for i := 0; i < len(manifests); i++ {
   746  		call := f.nextCall()
   747  		observedBuildOrder = append(observedBuildOrder, call.dc().Name.String())
   748  	}
   749  
   750  	// If these were Kubernetes resources, we would try to deploy don-quixote
   751  	// before sancho, because it doesn't have an image build.
   752  	//
   753  	// But this would be wrong, because Docker Compose has stricter ordering requirements, see:
   754  	// https://docs.docker.com/compose/startup-order/
   755  	expectedBuildOrder := []string{
   756  		"redis",
   757  		"sancho",
   758  		"don-quixote",
   759  	}
   760  	assert.Equal(t, expectedBuildOrder, observedBuildOrder)
   761  }
   762  
   763  func TestBuildControllerLocalResourcesBeforeClusterResources(t *testing.T) {
   764  	f := newTestFixture(t)
   765  	defer f.TearDown()
   766  
   767  	manifests := []model.Manifest{
   768  		f.newManifest("clusterBuilt1"),
   769  		f.newManifest("clusterBuilt2"),
   770  		manifestbuilder.New(f, "clusterUnbuilt").
   771  			WithK8sYAML(SanchoYAML).Build(),
   772  		manifestbuilder.New(f, "local1").
   773  			WithLocalResource("echo local1", nil).Build(),
   774  		f.newManifest("clusterBuilt3"),
   775  		manifestbuilder.New(f, "local2").
   776  			WithLocalResource("echo local2", nil).Build(),
   777  	}
   778  
   779  	manifests = append(manifests, manifestbuilder.New(f, model.UnresourcedYAMLManifestName).
   780  		WithK8sYAML(testyaml.SecretYaml).Build())
   781  	f.Start(manifests, true)
   782  
   783  	var observedBuildOrder []string
   784  	for i := 0; i < len(manifests); i++ {
   785  		call := f.nextCall()
   786  		if !call.k8s().Empty() {
   787  			observedBuildOrder = append(observedBuildOrder, call.k8s().Name.String())
   788  			continue
   789  		}
   790  		observedBuildOrder = append(observedBuildOrder, call.local().Name.String())
   791  	}
   792  
   793  	expectedBuildOrder := []string{
   794  		model.UnresourcedYAMLManifestName.String(),
   795  		"local1", // local resource comes after UnresourcedYAML but before all cluster resources
   796  		"local2",
   797  		"clusterUnbuilt",
   798  		"clusterBuilt1",
   799  		"clusterBuilt2",
   800  		"clusterBuilt3",
   801  	}
   802  	assert.Equal(t, expectedBuildOrder, observedBuildOrder)
   803  }
   804  
   805  func TestBuildControllerResourceDeps(t *testing.T) {
   806  	f := newTestFixture(t)
   807  	defer f.TearDown()
   808  
   809  	depGraph := map[string][]string{
   810  		"a": {"e"},
   811  		"b": {"e"},
   812  		"c": {"d", "g"},
   813  		"d": {},
   814  		"e": {"d", "f"},
   815  		"f": {"c"},
   816  		"g": {},
   817  	}
   818  
   819  	var manifests []model.Manifest
   820  	manifestsByName := make(map[string]model.Manifest)
   821  	for name, deps := range depGraph {
   822  		m := f.newManifest(name)
   823  		for _, dep := range deps {
   824  			m.ResourceDependencies = append(m.ResourceDependencies, model.ManifestName(dep))
   825  		}
   826  		manifests = append(manifests, m)
   827  		manifestsByName[name] = m
   828  	}
   829  
   830  	f.Start(manifests, true)
   831  
   832  	var observedOrder []string
   833  	for i := range manifests {
   834  		call := f.nextCall("%dth build. have built: %v", i, observedOrder)
   835  		name := call.k8s().Name.String()
   836  		observedOrder = append(observedOrder, name)
   837  		pb := podbuilder.New(t, manifestsByName[name]).WithContainerReady(true)
   838  		f.podEvent(pb.Build(), model.ManifestName(name))
   839  	}
   840  
   841  	var expectedManifests []string
   842  	for name := range depGraph {
   843  		expectedManifests = append(expectedManifests, name)
   844  	}
   845  
   846  	// make sure everything built
   847  	require.ElementsMatch(t, expectedManifests, observedOrder)
   848  
   849  	buildIndexes := make(map[string]int)
   850  	for i, n := range observedOrder {
   851  		buildIndexes[n] = i
   852  	}
   853  
   854  	// make sure it happened in an acceptable order
   855  	for name, deps := range depGraph {
   856  		for _, dep := range deps {
   857  			require.Truef(t, buildIndexes[name] > buildIndexes[dep], "%s built before %s, contrary to resource deps", name, dep)
   858  		}
   859  	}
   860  }
   861  
   862  // normally, local builds go before k8s builds
   863  // if the local build depends on the k8s build, the k8s build should go first
   864  func TestBuildControllerResourceDepTrumpsLocalResourcePriority(t *testing.T) {
   865  	f := newTestFixture(t)
   866  	defer f.TearDown()
   867  
   868  	k8sManifest := f.newManifest("foo")
   869  	localManifest := manifestbuilder.New(f, "bar").
   870  		WithLocalResource("echo bar", nil).
   871  		WithResourceDeps("foo").Build()
   872  	manifests := []model.Manifest{localManifest, k8sManifest}
   873  	f.Start(manifests, true)
   874  
   875  	var observedBuildOrder []string
   876  	for i := 0; i < len(manifests); i++ {
   877  		call := f.nextCall()
   878  		if !call.k8s().Empty() {
   879  			observedBuildOrder = append(observedBuildOrder, call.k8s().Name.String())
   880  			pb := podbuilder.New(t, k8sManifest).WithContainerReady(true)
   881  			f.podEvent(pb.Build(), k8sManifest.Name)
   882  			continue
   883  		}
   884  		observedBuildOrder = append(observedBuildOrder, call.local().Name.String())
   885  	}
   886  
   887  	expectedBuildOrder := []string{"foo", "bar"}
   888  	assert.Equal(t, expectedBuildOrder, observedBuildOrder)
   889  }
   890  
   891  // bar depends on foo, we build foo three times before marking it ready, and make sure bar waits
   892  func TestBuildControllerResourceDepTrumpsInitialBuild(t *testing.T) {
   893  	f := newTestFixture(t)
   894  	defer f.TearDown()
   895  
   896  	foo := manifestbuilder.New(f, "foo").
   897  		WithLocalResource("foo cmd", []string{f.JoinPath("foo")}).
   898  		Build()
   899  	bar := manifestbuilder.New(f, "bar").
   900  		WithLocalResource("bar cmd", []string{f.JoinPath("bar")}).
   901  		WithResourceDeps("foo").
   902  		Build()
   903  	manifests := []model.Manifest{foo, bar}
   904  	f.b.nextBuildFailure = errors.New("failure")
   905  	f.Start(manifests, true)
   906  
   907  	call := f.nextCall()
   908  	require.Equal(t, "foo", call.local().Name.String())
   909  
   910  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("foo", "main.go"))
   911  	f.b.nextBuildFailure = errors.New("failure")
   912  	call = f.nextCall()
   913  	require.Equal(t, "foo", call.local().Name.String())
   914  
   915  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("foo", "main.go"))
   916  	call = f.nextCall()
   917  	require.Equal(t, "foo", call.local().Name.String())
   918  
   919  	// now that the foo build has succeeded, bar should get queued
   920  	call = f.nextCall()
   921  	require.Equal(t, "bar", call.local().Name.String())
   922  }
   923  
   924  // bar depends on foo, we build foo three times before marking it ready, and make sure bar waits
   925  func TestBuildControllerResourceDepTrumpsPendingBuild(t *testing.T) {
   926  	f := newTestFixture(t)
   927  	defer f.TearDown()
   928  
   929  	foo := manifestbuilder.New(f, "foo").
   930  		WithLocalResource("foo cmd", []string{f.JoinPath("foo")}).
   931  		Build()
   932  	bar := manifestbuilder.New(f, "bar").
   933  		WithLocalResource("bar cmd", []string{f.JoinPath("bar")}).
   934  		WithResourceDeps("foo").
   935  		Build()
   936  
   937  	manifests := []model.Manifest{bar, foo}
   938  	f.b.nextBuildFailure = errors.New("failure")
   939  	f.Start(manifests, true)
   940  
   941  	// trigger a change for bar so that it would try to build if not for its resource dep
   942  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("bar", "main.go"))
   943  
   944  	call := f.nextCall()
   945  	require.Equal(t, "foo", call.local().Name.String())
   946  
   947  	f.b.nextBuildFailure = errors.New("failure")
   948  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("foo", "main.go"))
   949  	call = f.nextCall()
   950  	require.Equal(t, "foo", call.local().Name.String())
   951  
   952  	f.fsWatcher.events <- watch.NewFileEvent(f.JoinPath("foo", "main2.go"))
   953  	call = f.nextCall()
   954  	require.Equal(t, "foo", call.local().Name.String())
   955  
   956  	// since the foo build succeeded, bar should now queue
   957  	call = f.nextCall()
   958  	require.Equal(t, "bar", call.local().Name.String())
   959  }